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