Initial commit
[ta/config-manager.git] / cmdatahandlers / src / cmdatahandlers / hosts / 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 cmdatahandlers.api import utils
20 from serviceprofiles import profiles
21
22
23 class Config(config.Config):
24     def __init__(self, confman):
25         super(Config, self).__init__(confman)
26         self.ROOT = 'cloud.hosts'
27         self.DOMAIN = 'hosts'
28         try:
29             self.update_service_profiles()
30         except Exception:
31             pass
32
33     def init(self):
34         pass
35
36     def validate(self):
37         hosts = []
38         try:
39             hosts = self.get_hosts()
40         except configerror.ConfigError:
41             pass
42
43         if hosts:
44             utils.validate_list_items_unique(hosts)
45
46         for host in hosts:
47             self._validate_host(host)
48
49     def mask_sensitive_data(self):
50         for hostname in self.config[self.ROOT].keys():
51             self.config[self.ROOT][hostname]['hwmgmt']['password'] = self.MASK
52             self.config[self.ROOT][hostname]['hwmgmt']['snmpv2_trap_community_string'] = self.MASK
53             self.config[self.ROOT][hostname]['hwmgmt']['snmpv3_authpass'] = self.MASK
54             self.config[self.ROOT][hostname]['hwmgmt']['snmpv3_privpass'] = self.MASK
55
56     def _validate_host(self, hostname):
57         self._validate_hwmgmt(hostname)
58         self._validate_service_profiles(hostname)
59         self._validate_network_profiles(hostname)
60         self._validate_performance_profiles(hostname)
61         self._validate_storage_profiles(hostname)
62
63     def _validate_hwmgmt(self, hostname):
64         ip = self.get_hwmgmt_ip(hostname)
65         utils.validate_ipv4_address(ip)
66         self.get_hwmgmt_user(hostname)
67         self.get_hwmgmt_password(hostname)
68         netconf = self.confman.get_networking_config_handler()
69
70         hwmgmtnet = None
71         try:
72             hwmgmtnet = netconf.get_hwmgmt_network_name()
73         except configerror.ConfigError:
74             pass
75
76         if hwmgmtnet:
77             domain = self.get_host_network_domain(hostname)
78             cidr = netconf.get_network_cidr(hwmgmtnet, domain)
79             utils.validate_ip_in_network(ip, cidr)
80
81     def _validate_service_profiles(self, hostname):
82         node_profiles = self.get_service_profiles(hostname)
83         utils.validate_list_items_unique(node_profiles)
84         service_profiles_lib = profiles.Profiles()
85         serviceprofiles = service_profiles_lib.get_service_profiles()
86         for profile in node_profiles:
87             if profile not in serviceprofiles:
88                 raise configerror.ConfigError('Invalid service profile %s specified for host %s' % (profile, hostname))
89
90     def _validate_network_profiles(self, hostname):
91         node_profiles = self.get_network_profiles(hostname)
92         utils.validate_list_items_unique(profiles)
93         netprofconf = self.confman.get_network_profiles_config_handler()
94         netprofiles = netprofconf.get_network_profiles()
95         for profile in node_profiles:
96             if profile not in netprofiles:
97                 raise configerror.ConfigError('Invalid network profile %s specified for host %s' % (profile, hostname))
98
99     def _validate_performance_profiles(self, hostname):
100         node_performance_profiles = []
101         try:
102             node_performance_profiles = self.get_performance_profiles(hostname)
103         except configerror.ConfigError:
104             pass
105
106         if node_performance_profiles:
107             utils.validate_list_items_unique(node_performance_profiles)
108             perfprofconf = self.confman.get_performance_profiles_config_handler()
109             perfprofiles = perfprofconf.get_performance_profiles()
110             for profile in node_performance_profiles:
111                 if profile not in perfprofiles:
112                     raise configerror.ConfigError('Invalid performance profile %s specified for host %s' % (profile, hostname))
113
114     def _validate_storage_profiles(self, hostname):
115         node_storage_profiles = []
116         try:
117             node_storage_profiles = self.get_storage_profiles(hostname)
118         except configerror.ConfigError:
119             pass
120
121         if node_storage_profiles:
122             utils.validate_list_items_unique(node_storage_profiles)
123             storageprofconf = self.confman.get_storage_profiles_config_handler()
124             storageprofiles = storageprofconf.get_storage_profiles()
125             for profile in node_storage_profiles:
126                 if profile not in storageprofiles:
127                     raise configerror.ConfigError('Invalid storage profile %s specific for %s' % (profile, hostname))
128
129     def get_hosts(self):
130         """ get the list of hosts in the cloud
131
132             Return:
133
134             A sorted list of host names
135
136             Raise:
137
138             ConfigError in-case of an error
139         """
140         self.validate_root()
141
142         return sorted(self.config[self.ROOT].keys())
143
144     def get_labels(self, hostname):
145         noderole_label = "node-role.kubernetes.io/{}".format(self.get_noderole(hostname))
146         mandatory_labels = \
147             {"nodetype": self.get_nodetype(hostname),
148              "nodeindex": self.get_nodeindex(hostname),
149              "nodename": self.get_nodename(hostname),
150              noderole_label: ""}
151         labels = self.config[self.ROOT][hostname].get('labels', {}).copy()
152         labels.update(mandatory_labels)
153
154         if self.is_sriov_enabled(hostname):
155             labels.update({"sriov": "enabled"})
156
157         black_list = ['name']
158         return {name: attributes
159                 for name, attributes in labels.iteritems()
160                 if name not in black_list}
161
162     def get_nodetype(self, hostname):
163         service_profiles_lib = profiles.Profiles()
164         service_profiles = self.get_service_profiles(hostname)
165
166         if service_profiles_lib.get_caasmaster_service_profile() in service_profiles:
167             return service_profiles_lib.get_caasmaster_service_profile()
168         if service_profiles_lib.get_caasworker_service_profile() in service_profiles:
169             return service_profiles_lib.get_caasworker_service_profile()
170
171         return service_profiles[0]
172
173     def get_nodeindex(self, hostname):
174         return re.search(r'[-_](\d+)$', hostname).group(1)
175
176     def get_nodename(self, hostname):
177         return "{}{}".format(self.get_nodetype(hostname), self.get_nodeindex(hostname))
178
179     def get_noderole(self, hostname):
180         service_profiles_lib = profiles.Profiles()
181         service_profiles = self.get_service_profiles(hostname)
182
183         if service_profiles_lib.get_caasmaster_service_profile() in service_profiles:
184             return "master"
185         return "worker"
186
187     def is_sriov_enabled(self, hostname):
188         netprofs = self.get_network_profiles(hostname)
189         netprofconf = self.confman.get_network_profiles_config_handler()
190         for netprof in netprofs:
191             if 'sriov_provider_networks' in self.config[netprofconf.ROOT][netprof]:
192                 return True
193         return False
194
195     def get_enabled_hosts(self):
196         """ get the list of enabled hosts in the cloud
197
198             Return:
199
200             A list of host names
201
202             Raise:
203
204             ConfigError in-case of an error
205         """
206         self.validate_root()
207         hosts = self.get_hosts()
208         ret = []
209         for host in hosts:
210             if self.is_host_enabled(host):
211                 ret.append(host)
212         return ret
213
214     def get_hwmgmt_ip(self, hostname):
215         """get the hwmgmt ip address
216
217             Arguments:
218
219             hostname: The name of the node
220
221             Return:
222
223             The BMC ip address as a string
224
225             Raise:
226
227             ConfigError in-case of an error
228         """
229         self._validate_hostname(hostname)
230
231         if 'hwmgmt' not in self.config[self.ROOT][hostname] or 'address' not in self.config[self.ROOT][hostname]['hwmgmt']:
232             raise configerror.ConfigError('No hwmgmt info defined for host')
233
234         return self.config[self.ROOT][hostname]['hwmgmt']['address']
235
236     def get_hwmgmt_user(self, hostname):
237         """get the hwmgmt user
238
239             Arguments:
240
241             hostname: The name of the node
242
243             Return:
244
245             The BMC user name.
246
247             Raise:
248
249             ConfigError in-case of an error
250         """
251         self._validate_hostname(hostname)
252
253         if 'hwmgmt' not in self.config[self.ROOT][hostname] or 'user' not in self.config[self.ROOT][hostname]['hwmgmt']:
254             raise configerror.ConfigError('No hwmgmt info defined for host')
255
256         return self.config[self.ROOT][hostname]['hwmgmt']['user']
257
258     def get_hwmgmt_password(self, hostname):
259         """get the hwmgmt password
260
261            Arguments:
262
263            hostname: The name of the node
264
265            Return:
266
267            The BMC password
268
269            Raise:
270
271            ConfigError in-case of an error
272         """
273         self._validate_hostname(hostname)
274
275         if 'hwmgmt' not in self.config[self.ROOT][hostname] or 'password' not in self.config[self.ROOT][hostname]['hwmgmt']:
276             raise configerror.ConfigError('No hwmgmt info defined for host')
277
278         return self.config[self.ROOT][hostname]['hwmgmt']['password']
279
280     def get_service_profiles(self, hostname):
281         """get the node service profiles
282
283            Arguments:
284
285            hostname: The name of the node
286
287            Return:
288
289            A list containing service profile names
290
291            Raise:
292
293            ConfigError in-case of an error
294         """
295         self._validate_hostname(hostname)
296
297         if 'service_profiles' not in self.config[self.ROOT][hostname]:
298             raise configerror.ConfigError('No service profiles found')
299
300         return self.config[self.ROOT][hostname]['service_profiles']
301
302     def get_performance_profiles(self, hostname):
303         """ get the performance profiles
304
305             Arguments:
306
307             hostname: The name of the node
308
309             Return:
310
311             A list containing the perfromance profile names.
312
313             Raise:
314
315             ConfigError in-case of an error
316         """
317         self._validate_hostname(hostname)
318
319         if 'performance_profiles' not in self.config[self.ROOT][hostname]:
320             raise configerror.ConfigError('No performance profiles found')
321
322         return self.config[self.ROOT][hostname]['performance_profiles']
323
324     def get_network_profiles(self, hostname):
325         """get the node network profiles
326
327            Arguments:
328
329            hostname: The name of the node
330
331            Return:
332
333            A list containing network profile names
334
335            Raise:
336
337            ConfigError in-case of an error
338         """
339         self._validate_hostname(hostname)
340
341         if 'network_profiles' not in self.config[self.ROOT][hostname]:
342             raise configerror.ConfigError('No network profiles found')
343
344         return self.config[self.ROOT][hostname]['network_profiles']
345
346     def get_storage_profiles(self, hostname):
347         """get the node storage profiles
348
349            Arguments:
350
351            hostname: The name of the node
352
353            Return:
354
355            A list containing storage profile names
356
357            Raise:
358
359            ConfigError in-case of an error
360         """
361         self._validate_hostname(hostname)
362
363         if 'storage_profiles' not in self.config[self.ROOT][hostname]:
364             raise configerror.ConfigError('No storage profiles found')
365
366         return self.config[self.ROOT][hostname]['storage_profiles']
367
368     def _validate_hostname(self, hostname):
369         if not self.is_valid_host(hostname):
370             raise configerror.ConfigError('Invalid hostname given %s' % hostname)
371
372     def is_valid_host(self, hostname):
373         """check if a host is valid
374
375            Arguments:
376
377            hostname: The name of the node
378
379            Return:
380
381            True or False
382
383            Raise:
384
385            ConfigError in-case of an error
386         """
387         self.validate_root()
388         if hostname in self.config[self.ROOT]:
389             return True
390         return False
391
392     def get_service_profile_hosts(self, profile):
393         """ get hosts having some service profile
394
395             Argument:
396
397             service profile name
398
399             Return:
400
401             A list of host names
402
403             Raise:
404
405             ConfigError in-case of an error
406         """
407         hosts = self.get_hosts()
408         result = []
409         for host in hosts:
410             node_profiles = self.get_service_profiles(host)
411             if profile in node_profiles:
412                 result.append(host)
413
414         return result
415
416     def get_network_profile_hosts(self, profile):
417         """ get hosts having some network profile
418
419             Argument:
420
421             network profile name
422
423             Return:
424
425             A list of host names
426
427             Raise:
428
429             ConfigError in-case of an error
430         """
431         hosts = self.get_hosts()
432         result = []
433         for host in hosts:
434             node_network_profiles = self.get_network_profiles(host)
435             if profile in node_network_profiles:
436                 result.append(host)
437         if not result:
438             raise configerror.ConfigError('No hosts found for profile %s' % profile)
439
440         return result
441
442     def get_performance_profile_hosts(self, profile):
443         """ get hosts having some performance profile
444
445             Argument:
446
447             performance profile name
448
449             Return:
450
451             A list of host names
452
453             Raise:
454
455             ConfigError in-case of an error
456         """
457         hosts = self.get_hosts()
458         result = []
459         for host in hosts:
460             node_performance_profiles = self.get_performance_profiles(host)
461             if profile in node_performance_profiles:
462                 result.append(host)
463         if not result:
464             raise configerror.ConfigError('No hosts found for profile %s' % profile)
465
466         return result
467
468     def get_storage_profile_hosts(self, profile):
469         """ get hosts having some storage profile
470
471             Argument:
472
473             storage profile name
474
475             Return:
476
477             A list of host names
478
479             Raise:
480
481             ConfigError in-case of an error
482         """
483         hosts = self.get_hosts()
484         result = []
485         for host in hosts:
486             try:
487                 node_storage_profiles = self.get_storage_profiles(host)
488                 if profile in node_storage_profiles:
489                     result.append(host)
490             except configerror.ConfigError:
491                 pass
492
493         if not result:
494             raise configerror.ConfigError('No hosts found for profile %s' % profile)
495
496         return result
497
498     def get_host_network_interface(self, host, network):
499         """ get the host interface used for some network
500
501             Argument:
502
503             the host name
504
505             the network name
506
507             Return:
508
509             The interface name
510
511             Raise:
512
513             ConfigError in-case of an error
514         """
515         node_network_profiles = self.get_network_profiles(host)
516         netprofconf = self.confman.get_network_profiles_config_handler()
517         for profile in node_network_profiles:
518             interfaces = netprofconf.get_profile_network_mapped_interfaces(profile)
519             for interface in interfaces:
520                 networks = netprofconf.get_profile_interface_mapped_networks(profile, interface)
521                 if network in networks:
522                     return interface
523
524         raise configerror.ConfigError('No interfaces found for network %s in host %s' % (network, host))
525
526     def get_host_network_ip_holding_interface(self, host, network):
527         """ get the host ip holding interface some network
528
529             Argument:
530
531             the host name
532
533             the network name
534
535             Return:
536
537             The interface name
538
539             Raise:
540
541             ConfigError in-case of an error
542         """
543         networkingconf = self.confman.get_networking_config_handler()
544         vlan = None
545         try:
546             domain = self.get_host_network_domain(host)
547             vlan = networkingconf.get_network_vlan_id(network, domain)
548         except configerror.ConfigError as exp:
549             pass
550
551         if vlan:
552             return 'vlan'+str(vlan)
553
554         return self.get_host_network_interface(host, network)
555
556     def get_host_networks(self, hostname):
557         """ get the host networks
558
559             Argument:
560
561             The host name
562
563             Return:
564
565             A list of network names
566
567             Raise:
568
569             ConfigError in-case of an error
570         """
571         node_network_profiles = self.get_network_profiles(hostname)
572         netprofconf = self.confman.get_network_profiles_config_handler()
573         result = []
574         for profile in node_network_profiles:
575             interfaces = netprofconf.get_profile_network_mapped_interfaces(profile)
576             for interface in interfaces:
577                 networks = netprofconf.get_profile_interface_mapped_networks(profile, interface)
578                 for network in networks:
579                     if network not in result:
580                         result.append(network)
581         if not result:
582             raise configerror.ConfigError('No networks found for host %s' % hostname)
583
584         return result
585
586     def get_host_having_hwmgmt_address(self, hwmgmtips):
587         """ get the node name matching an ipmi address
588
589             Argument:
590
591             The ipmi address
592
593             Return:
594
595             The node name
596
597             Raise:
598
599             ConfigError in-case of an error
600         """
601         import ipaddress
602         hosts = self.get_hosts()
603         for host in hosts:
604             ip = self.get_hwmgmt_ip(host)
605             for hwmgtip in hwmgmtips:
606                 addr=ipaddress.ip_address(unicode(hwmgtip))
607                 if addr.version == 6:
608                    hwmgtip=addr.compressed
609                    ip=ipaddress.ip_address(unicode(ip))
610                    ip=ip.compressed
611                 if ip == hwmgtip:
612                    return host
613         raise configerror.ConfigError('No hosts are matching the provided hw address %s' % hwmgmtips)
614
615     def set_installation_host(self, name):
616         """ set the installation node
617
618             Argument:
619
620             The installation node name
621
622             Raise:
623
624             ConfigError in-case of an error
625         """
626         self._validate_hostname(name)
627
628         self.config[self.ROOT][name]['installation_host'] = True
629
630     def is_installation_host(self, name):
631         """ get if the node is an installation node
632
633             Argument:
634
635             The node name
636
637             Return:
638
639             True if installation node
640
641             Raise:
642
643             ConfigError in-case of an error
644         """
645         self._validate_hostname(name)
646
647         if 'installation_host' in self.config[self.ROOT][name]:
648             return self.config[self.ROOT][name]['installation_host']
649
650         return False
651
652     def get_installation_host(self):
653         """ get the name of the node used for installation
654
655             Return:
656
657             The node name
658
659             Raise:
660
661             ConfigError in-case of an error
662         """
663         hosts = self.get_hosts()
664         for host in hosts:
665             if self.is_installation_host(host):
666                 return host
667
668         raise configerror.ConfigError('No installation host found')
669
670     def disable_host(self, host):
671         """ disable the hosts visible via configuration.
672             This can be used in bootstrapping phase.
673
674             Argument:
675
676             host to disable
677
678             Raise:
679
680             ConfigError in-case if provided host is not valid
681         """
682         self._validate_hostname(host)
683
684         self.config[self.ROOT][host]['disabled'] = True
685
686     def enable_host(self, host):
687         """ enable  the hosts visible via configuration.
688             This can be used in bootstrapping phase.
689
690             Argument:
691
692             host to enable
693
694             Raise:
695
696             ConfigError in-case if provided host is not valid
697         """
698         self._validate_hostname(host)
699
700         self.config[self.ROOT][host]['disabled'] = False
701
702     def is_host_enabled(self, host):
703         """ is the host enabled
704
705             Argument:
706
707             the host to be checked
708
709             Raise:
710
711             ConfigError in-case if provided host is not valid
712         """
713         self._validate_hostname(host)
714
715         if 'disabled' in self.config[self.ROOT][host]:
716             return not self.config[self.ROOT][host]['disabled']
717
718         return True
719
720     def get_mgmt_mac(self, host):
721         self._validate_hostname(host)
722
723         if 'mgmt_mac' in self.config[self.ROOT][host]:
724             return self.config[self.ROOT][host]['mgmt_mac']
725         return []
726
727     def get_host_network_domain(self, host):
728         self._validate_hostname(host)
729         if 'network_domain' not in self.config[self.ROOT][host]:
730             raise configerror.ConfigError('Missing network domain for host %s' % host)
731         return self.config[self.ROOT][host]['network_domain']
732
733     def get_controllers_network_domain(self):
734         controllers = self.get_service_profile_hosts('controller')
735         domains = set()
736         for controller in controllers:
737             domains.add(self.get_host_network_domain(controller))
738
739         if len(domains) != 1:
740             raise configerror.ConfigError('Controllers in different networking domains not supported')
741         return domains.pop()
742
743     def get_managements_network_domain(self):
744         managements = self.get_service_profile_hosts('management')
745         domains = set()
746         for management in managements:
747             domains.add(self.get_host_network_domain(management))
748         if len(domains) != 1:
749             raise configerror.ConfigError('Management in different networking domains not supported')
750         return domains.pop()
751
752     def update_service_profiles(self):
753         profs = profiles.Profiles()
754         hosts = self.get_hosts()
755         for host in hosts:
756             new_profiles = []
757             current_profiles = self.config[self.ROOT][host]['service_profiles']
758             new_profiles = current_profiles
759             for profile in current_profiles:
760                 included_profiles = profs.get_included_profiles(profile)
761                 new_profiles = utils.add_lists(new_profiles, included_profiles)
762             self.config[self.ROOT][host]['service_profiles'] = new_profiles
763
764     def get_pre_allocated_ips(self, host, network):
765         ips_field = "pre_allocated_ips"
766         self._validate_hostname(host)
767         if (ips_field not in self.config[self.ROOT][host]
768                 or network not in self.config[self.ROOT][host][ips_field]):
769             return None
770         return self.config[self.ROOT][host][ips_field][network]
771
772     def allocate_port(self, host, base, name):
773         used_ports = []
774         hosts = self.get_hosts()
775         for node in hosts:
776             if name in self.config[self.ROOT][node]:
777                 used_ports.append(self.config[self.ROOT][node][name])
778
779         free_port = 0
780
781         for port in range(base, base+1000):
782             if port not in used_ports:
783                 free_port = port
784                 break
785
786         if free_port == 0:
787             raise configerror.ConfigError('No free ports available')
788
789         self.config[self.ROOT][host][name] = free_port
790
791     def add_vbmc_port(self, host):
792         base_vbmc_port = 61600
793         name = 'vbmc_port'
794         self._validate_hostname(host)
795         if name not in self.config[self.ROOT][host]:
796             self.allocate_port(host, base_vbmc_port, name)
797
798     def add_ipmi_terminal_port(self, host):
799         base_console_port = 61401
800         name = 'ipmi_terminal_port'
801         self._validate_hostname(host)
802         if name not in self.config[self.ROOT][host]:
803             self.allocate_port(host, base_console_port, name)
804
805     def get_ceph_osd_disks(self, host):
806         self._validate_hostname(host)
807         caas_disks = self.config[self.ROOT][host].get('caas_disks', [])
808         osd_disks = filter(lambda disk: disk.get('osd_disk', False), caas_disks)
809         return map(lambda disk: _get_path_for_virtio_id(disk), osd_disks)
810
811
812 def _get_path_for_virtio_id(disk):
813     disk_id = disk.get('id', '')
814     if disk_id:
815         return "/dev/disk/by-id/virtio-{}".format(disk_id[:20])