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