4f724ec01cd618dee71d026182c1e024cbf2bea0
[ta/config-manager.git] / cmdatahandlers / src / cmdatahandlers / networking / config.py
1 # Copyright 2019 Nokia
2
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import re
16
17 from cmdatahandlers.api import configerror
18 from cmdatahandlers.api import config
19 from serviceprofiles import profiles
20 from netaddr import IPNetwork, IPSet, IPRange
21
22
23 VALID_NETWORKS = \
24     ['infra_external', 'infra_storage_cluster', 'infra_hw_management', 'infra_internal', 'cloud_tenant', 'infra_access']
25
26 NETWORK_DOMAINS = 'network_domains'
27
28 class Config(config.Config):
29     def __init__(self, confman):
30         super(Config, self).__init__(confman)
31         self.ROOT = 'cloud.networking'
32         self.DOMAIN = 'networking'
33         self.freepool = {}
34         self.external_vip = None
35
36     def init(self):
37         if self.ROOT not in self.config:
38             return
39         try:
40             # a mapping between network and free IPSet
41             self.freepool = {}
42             for network in self.config[self.ROOT].keys():
43                 if network in VALID_NETWORKS:
44                     if NETWORK_DOMAINS not in self.config[self.ROOT][network]:
45                         raise configerror.ConfigError('No network domains for network %s' % network)
46
47                     self.freepool[network] = {}
48                     for domain in self.config[self.ROOT][network][NETWORK_DOMAINS].keys():
49                         self.freepool[network][domain] = self._get_free_set(network, domain)
50
51         except configerror.ConfigError:
52             raise
53
54         except Exception as exp:
55             raise configerror.ConfigError(str(exp))
56
57     def _validate_network(self, network, domain=None):
58         networks = self.get_networks()
59         if network not in networks:
60             raise configerror.ConfigError('Invalid network name %s' % network)
61
62         if NETWORK_DOMAINS not in self.config[self.ROOT][network]:
63             raise configerror.ConfigError('No network domains for network %s' % network)
64
65         if domain and domain not in self.config[self.ROOT][network][NETWORK_DOMAINS]:
66             raise configerror.ConfigError('Invalid network domain name %s' % domain)
67
68     def _get_free_set(self, network, domain):
69         ip_range_start = self.get_network_ip_range_start(network, domain)
70         ip_range_end = self.get_network_ip_range_end(network, domain)
71         select_range = IPRange(ip_range_start, ip_range_end)
72         netset = IPSet(select_range)
73         if (network == self.get_infra_external_network_name() and
74                 domain == self._get_vip_domain()):
75             iterator = netset.__iter__()
76             self.external_vip = str(iterator.next())
77             netset.remove(self.external_vip)
78
79         # check for the IP(s) taken by the nodes
80         try:
81             hostsconfig = self.confman.get_hosts_config_handler()
82             hosts = hostsconfig.get_hosts()
83             for host in hosts:
84                 try:
85                     hostip = self.get_host_ip(host, network)
86                     netset.remove(hostip)
87                 except configerror.ConfigError:
88                     pass
89         except configerror.ConfigError:
90             pass
91
92         service_profiles_lib = profiles.Profiles()
93
94         # check for the IP(s) taken as VIPs
95         if network == self.get_infra_internal_network_name() and domain == self._get_vip_domain():
96             vips = self.get_net_vips(network)
97             for _, vip in vips.iteritems():
98                 try:
99                     netset.remove(vip)
100                 except configerror.ConfigError:
101                     pass
102
103         return netset
104
105     def get_dns(self):
106         """ get the list of dns servers
107
108             Return:
109
110             A list of dns servers
111
112             Raise:
113
114             ConfigError is raised in-case of an error
115         """
116         self.validate_root()
117         if 'dns' not in self.config[self.ROOT]:
118             raise configerror.ConfigError('dns not found!')
119
120         return self.config[self.ROOT]['dns']
121
122     def get_mtu(self):
123         """ get the mtu value
124
125             Return:
126
127             A number representing the mtu size
128
129             Raise:
130
131             ConfigError is raised in-case of an error
132         """
133         self.validate_root()
134         if 'mtu' not in self.config['cloud.networking']:
135             raise configerror.ConfigError('mtu not found!')
136         return self.config[self.ROOT]['mtu']
137
138     def get_networks(self):
139         """ get the list of network names
140
141             Return:
142
143             A list of network names
144
145             Raise:
146
147             ConfigError is raised in-case of an error
148         """
149         self.validate_root()
150         networks = []
151         for entry in self.config[self.ROOT]:
152             if entry in VALID_NETWORKS:
153                 networks.append(entry)
154         return networks
155
156     def allocate_ip(self, network, domain):
157         """ get a new free ip in some network
158
159             Arguments:
160
161             Network name
162
163             Network domain
164
165             Return:
166
167             The free ip address
168
169             Raise:
170
171             ConfigError in-case of an error
172         """
173         self._validate_network(network, domain)
174
175         try:
176             iterator = self.freepool[network][domain].__iter__()
177             ip = str(iterator.next())
178             self.freepool[network][domain].remove(ip)
179             return ip
180         except Exception:
181             raise configerror.ConfigError('Failed to allocate ip for network %s in %s' % (network, domain))
182
183     def allocate_static_ip(self, ip, network, domain=None):
184         """ allocate the given ip in some network
185
186             Arguments:
187
188             Ip address
189
190             Network name
191
192             Network domain
193
194             Return:
195
196             The allocated ip address
197
198             Raise:
199
200             ConfigError in-case of an error
201         """
202         self._validate_network(network, domain)
203
204         try:
205             self.freepool[network][domain].remove(ip)
206             return ip
207         except Exception:
208             raise configerror.ConfigError('Failed to allocate %s for network %s in %s' % (ip, network, domain))
209
210     def allocate_vip(self, network):
211         return self.allocate_ip(network, self._get_vip_domain())
212
213     def get_network_domains(self, network):
214         self._validate_network(network)
215         return self.config[self.ROOT][network][NETWORK_DOMAINS].keys()
216
217     def get_network_cidr(self, network, domain):
218         """ get the network cidr
219
220             Arguments:
221
222             Network name
223
224             Network domain
225
226             Return:
227
228             The cidr address
229
230             Raise:
231
232             ConfigError in-case of an error
233         """
234         self._validate_network(network, domain)
235
236         if 'cidr' not in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
237             raise configerror.ConfigError('No CIDR for network %s in %s' % (network, domain))
238
239         return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['cidr']
240
241     def get_vip_network_cidr(self, network):
242         return self.get_network_cidr(network, self._get_vip_domain())
243
244     def get_network_mask(self, network, domain):
245         """ get the network mask
246
247             Arguments:
248
249             Network name
250
251             Network domain
252
253             Return:
254
255             A number representing the mask
256
257             Raise:
258
259             ConfigError in-case of an error
260         """
261         cidr = self.get_network_cidr(network, domain)
262         try:
263             mask = cidr.split('/')[1]
264             return int(mask)
265         except Exception as exp:
266             raise configerror.ConfigError('Invalid network mask in %s: %s' % (cidr, str(exp)))
267
268     def get_vip_network_mask(self, network):
269         return self.get_network_mask(network, self._get_vip_domain())
270
271     def get_network_gateway(self, network, domain):
272         """ get the network gateway
273
274             Arguments:
275
276             Network name
277
278             Network domain
279
280             Return:
281
282             The gateway address
283
284             Raise:
285
286             ConfigError in-case of an error
287         """
288         self._validate_network(network, domain)
289
290         if 'gateway' not in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
291             raise configerror.ConfigError('No gateway configured for network %s in %s' % (network, domain))
292
293         return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['gateway']
294
295     def get_network_routes(self, network, domain):
296         self._validate_network(network, domain)
297
298         if 'routes' not in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
299             raise configerror.ConfigError('No routes configured for network %s in %s' % (network, domain))
300
301         return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['routes']
302
303     def get_network_ip_range_start(self, network, domain):
304         """ get the network allocation range start
305
306             Arguments:
307
308             Network name
309
310             Network domain
311
312             Return:
313
314             The starting allocation range
315
316             Raise:
317
318             ConfigError in-case of an error
319         """
320         net = IPNetwork(self.get_network_cidr(network, domain))
321
322         if 'ip_range_start' in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
323             return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['ip_range_start']
324         else:
325             return str(net[1])
326
327     def get_network_ip_range_end(self, network, domain):
328         """ get the network allocation range end
329
330             Arguments:
331
332             Network name
333
334             Network domain
335
336             Return:
337
338             The end of the allocation range
339
340             Raise:
341
342             ConfigError in-case of an error
343         """
344         net = IPNetwork(self.get_network_cidr(network, domain))
345
346         if 'ip_range_end' in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
347             return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['ip_range_end']
348         else:
349             return str(net[-2])
350
351     def get_network_vlan_id(self, network, domain):
352         """ get the network vlan id
353
354             Arguments:
355
356             Network name
357
358             Network domain
359
360             Return:
361
362             The vlan id
363
364             Raise:
365
366             ConfigError in-case of an error
367         """
368         self._validate_network(network, domain)
369
370         if 'vlan' not in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
371             raise configerror.ConfigError('No vlan specified for %s in %s' % (network, domain))
372
373         return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['vlan']
374
375     def get_vip_network_vlan_id(self, network):
376         return self.get_network_vlan_id(network, self._get_vip_domain())
377
378     def get_network_mtu(self, network):
379         """ get the network mtu
380
381             Argument:
382
383             Network name
384
385             Return:
386
387             The mtu of the network
388
389             Raise:
390
391             ConfigError in-case of an error
392         """
393         self._validate_network(network)
394
395         if 'mtu' not in self.config[self.ROOT][network]:
396             raise configerror.ConfigError('No mtu specified for %s' % network)
397
398         return self.config[self.ROOT][network]['mtu']
399
400     def get_host_ip(self, host, network):
401         """ get the host ip allocated from a specific network
402
403             Argument:
404
405             hostname: The name of the host
406             networkname: The name of the network
407
408             Return:
409
410             The ip address assigned for the host
411
412             Raise:
413
414             ConfigError in-case of an error
415         """
416         self._validate_network(network)
417
418         hostnetconfigkey = host + '.' + self.DOMAIN
419         if hostnetconfigkey not in self.config:
420             raise configerror.ConfigError('No network configuration available for %s' % host)
421
422         if network not in self.config[hostnetconfigkey]:
423             raise configerror.ConfigError('No network configuration available for %s' % host)
424
425         if 'ip' not in self.config[hostnetconfigkey][network]:
426             raise configerror.ConfigError('No IP assigned for %s in network %s' % (host, network))
427
428         return self.config[hostnetconfigkey][network]['ip']
429
430     def _get_vip_domain(self):
431         return self.confman.get_hosts_config_handler().get_managements_network_domain()
432
433     @staticmethod
434     def get_infra_external_network_name():
435         """ get the network name for the external network
436
437             Return:
438
439             The external network name
440
441             Raise:
442
443             ConfigError in-case the network is not configured
444         """
445         return 'infra_external'
446
447     @staticmethod
448     def get_infra_storage_cluster_network_name():
449         """ get the infra storage cluster network name
450
451             Return:
452
453             The infra stroage cluster network name
454
455             Raise:
456
457             ConfigError in-case the network is not configured
458         """
459         return 'infra_storage_cluster'
460
461     @staticmethod
462     def get_hwmgmt_network_name():
463         """ get the hwmgmt network name
464
465             Return:
466
467             The hwmgmt network name
468
469             Raise:
470
471             ConfigError in-case the network is not defined
472         """
473         return 'infra_hw_management'
474
475     @staticmethod
476     def get_infra_internal_network_name():
477         """ get the infra management network name
478
479             Return:
480
481             The infra management network name
482
483             Raise:
484
485             ConfigError in-case the network is not defined
486         """
487         return 'infra_internal'
488
489     def get_cloud_tenant_network_name(self):
490         """ get the network name for the cloud tenant network
491
492             Return:
493
494             The cloud tenant network name
495
496             Raise:
497
498             ConfigError in-case the network is not configured
499         """
500         return 'cloud_tenant'
501
502     def get_infra_access_network_name(self):
503         """ get the network name for the infra access network
504
505             Return:
506
507             The infra access network name
508
509             Raise:
510
511             ConfigError in-case the network is not configured
512         """
513         return 'infra_access'
514
515     def add_host_networks(self, host):
516         """ add host network data
517
518             Argument:
519
520             Host name
521
522             Raise:
523
524             ConfigError in-case of an error
525         """
526         hostsconf = self.confman.get_hosts_config_handler()
527         networks = hostsconf.get_host_networks(host)
528         domain = hostsconf.get_host_network_domain(host)
529         for network in networks:
530             try:
531                 ip = self.get_host_ip(host, network)
532                 continue
533             except configerror.ConfigError:
534                 pass
535
536             static_ip = hostsconf.get_pre_allocated_ips(host, network)
537             if static_ip:
538                 ip = self.allocate_static_ip(static_ip, network, domain)
539             else:
540                 ip = self.allocate_ip(network, domain)
541             interface = hostsconf.get_host_network_ip_holding_interface(host, network)
542             netmask = self.get_network_mask(network, domain)
543             networkdata = {'ip': ip, 'interface': interface, 'mask': netmask}
544
545             try:
546                 vlan = self.get_network_vlan_id(network, domain)
547                 networkdata['vlan'] = vlan
548             except configerror.ConfigError:
549                 pass
550
551             try:
552                 gw = self.get_network_gateway(network, domain)
553                 networkdata['gateway'] = gw
554             except configerror.ConfigError:
555                 pass
556
557             try:
558                 routes = self.get_network_routes(network, domain)
559                 networkdata['routes'] = routes
560             except configerror.ConfigError:
561                 pass
562
563             key = host + '.' + self.DOMAIN
564             if key not in self.config:
565                 self.config[key] = {}
566             if network not in self.config[key]:
567                 self.config[key][network] = {}
568
569             self.config[key][network] = networkdata
570
571     def delete_host_networks(self, host):
572         """ delete host network data
573
574             Argument:
575
576             Host name
577         """
578         key = '{}.{}'.format(host, self.DOMAIN)
579         if key in self.config:
580             del self.config[key]
581
582     def get_networking_hosts(self):
583         """ get hosts with networking data
584
585         Return:
586
587         List of host names with existing networking data
588         """
589         hosts = []
590         match = r'^[^.]*\.networking$'
591         for key in self.config.keys():
592             if key != self.ROOT and re.match(match, key):
593                 hosts.append(key.split('.')[0])
594         return hosts
595
596     def get_host_interface(self, host, network):
597         """ get the host interface allocated from a specific network
598
599             Argument:
600
601             hostname: The name of the host
602             networkname: The name of the network
603
604             Return:
605
606             The interface for the host
607
608             Raise:
609
610             ConfigError in-case of an error
611         """
612         self._validate_network(network)
613
614         hostnetconfigkey = host + '.' + self.DOMAIN
615         if hostnetconfigkey not in self.config:
616             raise configerror.ConfigError('No network configuration available for %s' % host)
617
618         if network not in self.config[hostnetconfigkey]:
619             raise configerror.ConfigError('No network configuration available for %s' % host)
620
621         if 'interface' not in self.config[hostnetconfigkey][network]:
622             raise configerror.ConfigError(
623                 'No interface assigned for %s in network %s' % (host, network))
624
625         return self.config[hostnetconfigkey][network]['interface']
626
627     def get_host_mask(self, host, network):
628         """ get the network mask for the host
629
630             Argument:
631
632             hostname: The name of the host
633             networkname: The name of the network
634
635             Return:
636
637             The network mask
638
639             Raise:
640
641             ConfigError in-case of an error
642         """
643         self._validate_network(network)
644
645         hostnetconfigkey = host + '.' + self.DOMAIN
646         if hostnetconfigkey not in self.config:
647             raise configerror.ConfigError('No network configuration available for %s' % host)
648
649         if network not in self.config[hostnetconfigkey]:
650             raise configerror.ConfigError('No network configuration available for %s' % host)
651
652         if 'mask' not in self.config[hostnetconfigkey][network]:
653             raise configerror.ConfigError('No mask assigned for %s in network %s' % (host, network))
654
655         return self.config[hostnetconfigkey][network]['mask']
656
657
658     def get_external_vip(self):
659         """ get the external vip ip, this is always the first ip in the range
660         """
661         return self.external_vip
662
663
664     def get_provider_networks(self):
665         """
666         Get provider network names
667
668         Returns:
669             A list of provider network names
670
671         Raises:
672             ConfigError in-case of an error
673         """
674         if 'provider_networks' not in self.config[self.ROOT]:
675             raise configerror.ConfigError('No provider networks configured')
676
677         return self.config[self.ROOT]['provider_networks'].keys()
678
679     def is_shared_provider_network(self, network):
680         """
681         Is shared provider network
682
683         Arguments:
684             Provider network name
685
686         Returns:
687             True if given provider network is shared, False otherwise
688
689         Raises:
690             ConfigError in-case of an error
691         """
692         networks = self.get_provider_networks()
693         if network not in networks:
694             raise configerror.ConfigError('Missing configuration for provider network %s' % network)
695
696         return (self.config[self.ROOT]['provider_networks'][network].get('shared') is True)
697
698     def get_provider_network_vlan_ranges(self, network):
699         """
700         Get vlan ranges for the given provider network
701
702         Arguments:
703             Provider network name
704
705         Returns:
706             Vlan ranges for the provider network
707
708         Raises:
709             ConfigError in-case of an error
710         """
711         networks = self.get_provider_networks()
712         if network not in networks:
713             raise configerror.ConfigError('Missing configuration for provider network %s' % network)
714
715         if 'vlan_ranges' not in self.config[self.ROOT]['provider_networks'][network]:
716             raise configerror.ConfigError(
717                 'Missing vlan ranges configuration for provider network %s' % network)
718
719         return self.config[self.ROOT]['provider_networks'][network]['vlan_ranges']
720
721
722     def get_provider_network_mtu(self, network):
723         """
724         Get mtu for the given provider network
725
726         Arguments:
727             Provider network name
728
729         Returns:
730             mtu for the provider network
731
732         Raises:
733             ConfigError in-case of an error
734         """
735         networks = self.get_provider_networks()
736         if network not in networks:
737             raise configerror.ConfigError('Missing configuration for provider network %s' % network)
738
739         if 'mtu' not in self.config[self.ROOT]['provider_networks'][network]:
740             raise configerror.ConfigError(
741                 'Missing mtu configuration for provider network %s' % network)
742
743         return self.config[self.ROOT]['provider_networks'][network]['mtu']
744
745     def is_l3_ha_enabled(self):
746         """ is L3 HA enabled
747
748             Return:
749
750             True if L3 HA is enabled, False otherwise
751         """
752         return True if 'l3_ha' in self.config[self.ROOT] else False
753
754     def _get_l3_ha_config(self):
755         if 'l3_ha' not in self.config[self.ROOT]:
756             raise configerror.ConfigError('Missing L3 HA configuration')
757
758         return self.config[self.ROOT]['l3_ha']
759
760     def get_l3_ha_provider_network(self):
761         """ get L3 HA provider network
762
763             Return:
764
765             L3 HA provider network name
766
767             Raise:
768
769             ConfigError in-case of an error
770         """
771         conf = self._get_l3_ha_config()
772         if 'provider_network' not in conf:
773             raise configerror.ConfigError('Missing L3 HA provider network configuration')
774
775         return conf['provider_network']
776
777     def get_l3_ha_cidr(self):
778         """ get L3 HA CIDR
779
780             Return:
781
782             L3 HA CIDR
783
784             Raise:
785
786             ConfigError in-case of an error
787         """
788         conf = self._get_l3_ha_config()
789         if 'cidr' not in conf:
790             raise configerror.ConfigError('Missing L3 HA CIDR configuration')
791
792         return conf['cidr']
793
794     def add_ovs_config_defaults(self, host):
795         """ Add Openvswitch default config """
796
797         ovs_defaults = { 'tx-flush-interval': 0, 'rxq-rebalance': 0 }
798
799         key = self.ROOT
800         if key not in self.config:
801             self.config[key] = {}
802         if 'ovs_config' not in self.config[key]:
803             self.config[key]['ovs_config'] = {}
804         if host not in self.config[key]['ovs_config']:
805             self.config[key]['ovs_config'][host] = {}
806
807         self.config[key]['ovs_config'][host] = ovs_defaults
808
809     def del_ovs_config(self, host):
810         """ Delete Openvswitch config """
811         if host in self.config[self.ROOT]['ovs_config']:
812             self.config[self.ROOT]['ovs_config'].pop(host, None)
813
814     def get_ovs_config(self, host):
815         return self.config[self.ROOT]['ovs_config'].get(host, None)
816
817     def _validate_ovs_config_args(self, host, args):
818         ovs_conf = self.config[self.ROOT]['ovs_config']
819
820         if args.get('tx_flush_interval') is not None:
821             if int(args['tx_flush_interval']) >= 0 and int(args['tx_flush_interval']) <= 1000000:
822                 ovs_conf[host]['tx-flush-interval'] = int(args['tx_flush_interval'])
823             else:
824                 raise ValueError("tx-flush-interval value must be 0..1000000")
825
826         if args.get('rxq_rebalance_interval') is not None:
827             if int(args['rxq_rebalance_interval']) >= 0 and int(args['rxq_rebalance_interval']) <= 1000000:
828                 ovs_conf[host]['rxq-rebalance'] = int(args['rxq_rebalance_interval'])
829             else:
830                 raise ValueError("rxq_rebalance_interval value must be 0..1000000")
831
832     def update_ovs_config(self, host, args):
833         if self.config[self.ROOT]['ovs_config'].get(host, None) is None:
834             return None
835         self._validate_ovs_config_args(host, args)
836         return self.config[self.ROOT]
837
838     def get_ovs_config_hosts(self):
839         return [host for host in self.config[self.ROOT]['ovs_config']]
840
841     def add_vip(self, network, name, ip):
842         if 'vips' not in self.config[self.ROOT]:
843             self.config[self.ROOT]['vips'] = {}
844
845         if network not in self.config[self.ROOT]['vips']:
846             self.config[self.ROOT]['vips'][network] = {}
847
848         self.config[self.ROOT]['vips'][network][name] = ip
849
850     def add_external_vip(self):
851         external_vip = self.get_external_vip()
852         self.add_vip('infra_external', 'external_vip', external_vip)
853
854     def add_internal_vip(self):
855         internal_vip = self.allocate_vip('infra_internal')
856         self.add_vip('infra_internal', 'internal_vip', internal_vip)
857
858     def get_internal_vip(self):
859         try:
860             return self.config[self.ROOT]['vips']['infra_internal']['internal_vip']
861         except KeyError as exp:
862             raise configerror.ConfigError('Internal vip not found')
863
864     def get_vips(self):
865         if 'vips' not in self.config[self.ROOT]:
866             return {}
867
868         return self.config[self.ROOT]['vips']
869
870     def get_net_vips(self, net):
871         if 'vips' not in self.config[self.ROOT]:
872             return {}
873
874         if net not in self.config[self.ROOT]['vips']:
875             return {}
876
877         return self.config[self.ROOT]['vips'][net]
878
879         return self.config[self.ROOT]['vips']