Kubernetes node role refactored.
[ta/config-manager.git] / cmframework / src / cmframework / utils / cmansibleinventory.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 from __future__ import print_function
15 import json
16 import os
17
18 from cmframework.apis import cmerror
19 from cmframework.utils.cmpluginloader import CMPluginLoader
20 from cmdatahandlers.api import configmanager
21 from cmdatahandlers.api import utils
22 from serviceprofiles import profiles
23
24
25 class AnsibleInventory(object):
26
27     def __init__(self, properties, plugin_path):
28         self.pluginloader = AnsibleInventoryPluginLoader(plugin_path)
29         self.props = properties
30         propsjson = {}
31         for name, value in properties.iteritems():
32             try:
33                 propsjson[name] = json.loads(value)
34             except Exception:  # pylint: disable=broad-except
35                 continue
36         self.confman = configmanager.ConfigManager(propsjson)
37
38     # pylint: disable=no-self-use
39     def _is_setup(self):
40         if 'CONFIG_PHASE' in os.environ and os.environ['CONFIG_PHASE'] == 'setup':
41             return True
42         return False
43
44     def _is_bootstrapping(self):
45         if 'CONFIG_PHASE' in os.environ and os.environ['CONFIG_PHASE'] == 'bootstrapping':
46             return True
47         return False
48
49     def _is_provisioning(self):
50         if 'CONFIG_PHASE' in os.environ and os.environ['CONFIG_PHASE'] == 'provisioning':
51             return True
52         return False
53
54     def _is_postconfig(self):
55         if not self._is_bootstrapping() and not self._is_provisioning():
56             return True
57         return False
58
59     def _get_own_host(self):
60
61         hostsconf = self.confman.get_hosts_config_handler()
62
63         if utils.is_virtualized():
64             return hostsconf.get_installation_host()
65
66         hwmgmtip = utils.get_own_hwmgmt_ip()
67
68         return hostsconf.get_host_having_hwmgmt_address(hwmgmtip)
69
70     def set_default_route(self, hostvars, node, infra_internal_name):
71         routes = hostvars[node]['networking'][infra_internal_name].get('routes', [])
72         infra_int_ip = hostvars[node]['networking'][infra_internal_name]['ip']
73         caasconf = self.confman.get_caas_config_handler()
74         cidr_to_set = caasconf.get_caas_parameter("service_cluster_ip_cidr")
75         routes.append({"to": cidr_to_set, "via": infra_int_ip})
76         hostvars[node]['networking'][infra_internal_name]['routes'] = routes
77
78     def set_common_caas(self, hostvars, node, hostsconf):
79         hostvars[node]['nodetype'] = hostsconf.get_nodetype(node)
80         hostvars[node]['nodeindex'] = hostsconf.get_nodeindex(node)
81         hostvars[node]['nodename'] = hostsconf.get_nodename(node)
82         host_labels = hostsconf.get_labels(node)
83         if host_labels:
84             hostvars[node]['labels'] = host_labels
85
86         hostvars[node]['ssl_alt_name'] = {}
87         dns = [node]
88         hostvars[node]['ssl_alt_name']['dns'] = dns
89         ips = ['127.0.0.1']
90         ips.append(hostvars[node]['ansible_host'])
91         hostvars[node]['ssl_alt_name']['ip'] = ips
92
93         caasconf = self.confman.get_caas_config_handler()
94         hostvars[node]['system_reserved_memory'] = hostsconf.get_system_reserved_memory(node)
95         hostvars[node]['caas_soft_eviction_threshold'] = caasconf.get_caas_soft_eviction_threshold()
96         hostvars[node]['caas_hard_eviction_threshold'] = caasconf.get_caas_hard_eviction_threshold()
97
98     def set_caas_master_data(self, hostvars, node, caasconf, hostsconf):
99         dns = hostvars[node]['ssl_alt_name']['dns']
100         dns.append(caasconf.get_kubernetes_domain())
101         dns.append(caasconf.get_apiserver_in_hosts())
102         dns.append(caasconf.get_registry_url())
103         dns.append(caasconf.get_update_registry_url())
104         dns.append(caasconf.get_swift_url())
105         dns.append(caasconf.get_swift_update_url())
106         dns.append(caasconf.get_ldap_master_url())
107         dns.append(caasconf.get_ldap_slave_url())
108         dns.append(caasconf.get_chart_repo_url())
109         dns.append(caasconf.get_caas_parameter('prometheus_url'))
110         dns.append(caasconf.get_tiller_url())
111
112         hosts = hostsconf.get_hosts()
113         for host in hosts:
114             if 'caas_master' in hostsconf.get_service_profiles(host):
115                 dns.append(host)
116
117         hostvars[node]['ssl_alt_name']['dns'] = dns
118         ips = hostvars[node]['ssl_alt_name']['ip']
119         ips.append(caasconf.get_apiserver_svc_ip())
120         hostvars[node]['ssl_alt_name']['ip'] = ips
121
122     def generate_inventory(self):
123         try:
124             inventory = {}
125
126             # convert properties to inventory using the following rules:
127             # 1. cloud scoped configuration is mapped to "all" group's "vars" section
128             # 2. The host level domain configuration will be mapped to "_meta" section
129             #    under "hostvars" group. Under this there will be a dictionary per host.
130             # 3. The mapping between hosts and profiles is created.
131             #    This is used to allow ansible to automatically identify in which hosts
132             #    the playbooks are to be run.
133             #    This mapping is done as follows:
134             #      - A mapping is created for each service profile.
135             #      - A mapping is created for the network_profiles type.
136             #      - A mapping is created for the storage_profiles type.
137             #      - A mapping is created for the performance_profiles type.
138
139             # Get the host variables and all variables
140             hostvars = {}
141             allvars = {}
142
143             netconf = self.confman.get_networking_config_handler()
144             hostsconf = self.confman.get_hosts_config_handler()
145             infra_internal_name = netconf.get_infra_internal_network_name()
146             hosts = hostsconf.get_hosts()
147
148             ownhost = self._get_own_host()
149             if self._is_bootstrapping():
150                 for host in hosts:
151                     if host != ownhost:
152                         hostsconf.disable_host(host)
153
154             hosts = hostsconf.get_enabled_hosts()
155             for name, value in self.props.iteritems():
156                 try:
157                     d = name.split('.')
158                     if len(d) != 2:
159                         continue
160                     node = d[0]
161                     domain = d[1]
162                     if node != 'cloud':
163                         if node in hosts:
164                             if node not in hostvars:
165                                 hostvars[node] = {}
166                             hostip = netconf.get_host_ip(node, infra_internal_name)
167                             hostvars[node]['ansible_host'] = hostip
168
169                             try:
170                                 hostvars[node][domain] = json.loads(value)
171                             except Exception:  # pylint: disable=broad-except
172                                 hostvars[node][domain] = value
173
174                             if 'caas_master' in hostsconf.get_service_profiles(node):
175                                 self.set_common_caas(hostvars, node, hostsconf)
176                                 caasconf = self.confman.get_caas_config_handler()
177                                 self.set_caas_master_data(hostvars, node, caasconf, hostsconf)
178                                 self.set_default_route(hostvars, node, infra_internal_name)
179
180                             if 'caas_worker' in hostsconf.get_service_profiles(node):
181                                 self.set_common_caas(hostvars, node, hostsconf)
182                                 self.set_default_route(hostvars, node, infra_internal_name)
183                     else:
184                         try:
185                             allvars[domain] = json.loads(value)
186                         except Exception:  # pylint: disable=broad-except
187                             allvars[domain] = value
188
189                 except Exception:  # pylint: disable=broad-except
190                     pass
191
192             inventory['_meta'] = {}
193             inventory['_meta']['hostvars'] = hostvars
194             inventory['all'] = {'vars': allvars}
195
196             # add hosts to service profiles mapping
197             serviceprofiles = profiles.Profiles().get_service_profiles()
198             for profile in serviceprofiles:
199                 try:
200                     servicehosts = hostsconf.get_service_profile_hosts(profile)
201                     tmp = []
202                     for host in servicehosts:
203                         if host in hosts:
204                             tmp.append(host)
205                     inventory[profile] = tmp
206                 except Exception:  # pylint: disable=broad-except
207                     continue
208
209             # add mapping between profile types and hosts
210             inventory['network_profiles'] = []
211             inventory['storage_profiles'] = []
212             inventory['performance_profiles'] = []
213             for host in hosts:
214                 # check for network profiles
215                 try:
216                     _ = hostsconf.get_network_profiles(host)
217                     inventory['network_profiles'].append(host)
218                 except Exception:  # pylint: disable=broad-except
219                     pass
220
221                 # check for storage profiles
222                 try:
223                     _ = hostsconf.get_storage_profiles(host)
224                     inventory['storage_profiles'].append(host)
225                 except Exception:  # pylint: disable=broad-except
226                     pass
227
228                 # check for perfromance profiles
229                 try:
230                     _ = hostsconf.get_performance_profiles(host)
231                     inventory['performance_profiles'].append(host)
232                 except Exception:  # pylint: disable=broad-except
233                     pass
234
235             self.pluginloader.load()
236             plugins = self.pluginloader.get_plugin_instances(self.confman, inventory, ownhost)
237             if self._is_setup():
238                 inventory.clear()
239             for name, plugin in sorted(plugins.iteritems()):
240                 if self._is_bootstrapping():
241                     plugin.handle_bootstrapping()
242                 elif self._is_provisioning():
243                     plugin.handle_provisioning()
244                 elif self._is_setup():
245                     plugin.handle_setup()
246                 else:
247                     plugin.handle_postconfig()
248
249             return inventory
250
251         except Exception as exp:  # pylint: disable=broad-except
252             raise cmerror.CMError(str(exp))
253
254
255 class AnsibleInventoryPluginLoader(CMPluginLoader):
256     def __init__(self, plugin_location, plugin_filter=None):
257         super(AnsibleInventoryPluginLoader, self).__init__(plugin_location, plugin_filter)
258
259     def build_filter_dict(self):
260         pass
261
262     def get_plugin_instances(self, confman, inventory, ownhost):
263         plugs = {}
264         for plugin, module in self.loaded_plugin.iteritems():
265             class_name = getattr(module, plugin)
266             instance = class_name(confman, inventory, ownhost)
267             plugs[plugin] = instance
268         return plugs
269
270
271 def main():
272     import argparse
273     import sys
274     import traceback
275
276     parser = argparse.ArgumentParser(description='Test ansible inventory handler', prog=sys.argv[0])
277
278     parser.add_argument('--properties',
279                         required=True,
280                         dest='properties',
281                         metavar='PROPERTIES',
282                         help='The file containing the properties',
283                         type=str,
284                         action='store')
285
286     parser.add_argument('--plugins',
287                         required=True,
288                         dest='plugins',
289                         metavar='PLUGINS',
290                         help='The path to ansible inventory plugin(s)',
291                         type=str,
292                         action='store')
293
294     try:
295         args = parser.parse_args(sys.argv[1:])
296
297         f = open(args.properties, 'r')
298         lines = f.read().splitlines()
299         f.close()
300         properties = {}
301         for line in lines:
302             d = line.split('=')
303             if len(d) != 2:
304                 continue
305             properties[d[0]] = d[1]
306         ansible = AnsibleInventory(properties, args.plugins)
307         inventory = ansible.generate_inventory()
308
309         print(json.dumps(inventory, indent=4, sort_keys=True))
310
311     except Exception as exp:  # pylint: disable=broad-except
312         print(str(exp))
313         traceback.print_exc()
314         sys.exit(1)
315     sys.exit(0)
316
317
318 if __name__ == '__main__':
319     main()