blob: 238caf38daa940f3e6219573e9e096049f221665 [file] [log] [blame]
Dennis Dmitriev566db4b2017-07-18 18:13:07 +03001# Copyright 2013 - 2017 Mirantis, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# 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, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
Dennis Dmitriev806706d2017-07-29 22:31:23 +030015import copy
16
Dennis Dmitriev1110ac52017-06-22 21:07:37 +030017import reclass
Dennis Dmitriev566db4b2017-07-18 18:13:07 +030018# from reclass.adapters import salt as reclass_salt
Dennis Dmitriev1110ac52017-06-22 21:07:37 +030019from reclass import config as reclass_config
20from reclass import core as reclass_core
Dennis Dmitriev806706d2017-07-29 22:31:23 +030021from reclass import defaults as reclass_defaults
Dennis Dmitriev1f375cf2017-11-22 16:14:51 +020022from reclass.datatypes import parameters as reclass_parameters
Dennis Dmitriev806706d2017-07-29 22:31:23 +030023from reclass.utils.refvalue import RefValue
24import yaml
Dennis Dmitriev1f375cf2017-11-22 16:14:51 +020025import mock
Dennis Dmitrievde847d92017-06-26 18:58:05 +030026
27from reclass_tools import helpers
Dennis Dmitriev566db4b2017-07-18 18:13:07 +030028# import salt.cli.call
29# import salt.cli.caller
Dennis Dmitriev1110ac52017-06-22 21:07:37 +030030
31
Dennis Dmitriev806706d2017-07-29 22:31:23 +030032def refvalue_representer(dumper, data):
33 return dumper.represent_str(
34 data._assemble(
35 lambda s: s.join(reclass_defaults.PARAMETER_INTERPOLATION_SENTINELS)))
36yaml.add_representer(RefValue, refvalue_representer)
37
38class ReclassCore(reclass_core.Core):
39 """Track the specific key
40
41 :param key: string with dot-separated keys
42 """
43 track_key_path = None
44
45 def __init__(self, storage, class_mappings, input_data=None,
46 key=None):
47 if key:
Dennis Dmitrievdfe1fb22017-11-01 16:40:09 +020048 if ':' in key:
49 # Linux pillar notation: linux:network:interface
50 self.track_key_path = key.split(':')
51 else:
52 # Python notation: linux.network.interface
53 self.track_key_path = key.split('.')
54
55 if self.track_key_path[0] == 'parameters':
56 # Remove the first 'parameters' element because the model entities
57 # keep parameters in different object format.
58 self.track_key_path = self.track_key_path[1:]
Dennis Dmitriev806706d2017-07-29 22:31:23 +030059
60 super(ReclassCore, self).__init__(storage, class_mappings, input_data)
61
Dennis Dmitriev1f375cf2017-11-22 16:14:51 +020062
Dennis Dmitriev806706d2017-07-29 22:31:23 +030063 def _recurse_entity(self, entity, merge_base=None, seen=None, nodename=None):
Dennis Dmitriev1f375cf2017-11-22 16:14:51 +020064
65 def _new_merge_dict(self, cur, new, path):
66 try:
67 return orig_merge_dict(self, cur, new, path)
68 except TypeError as e:
69 if "Current value:" not in e.message:
70 e.message +="\nValue path: {}\nCurrent value: {}\nNew value: {}\n".format(path, cur, new)
71 raise TypeError(e.message)
72
Dennis Dmitriev806706d2017-07-29 22:31:23 +030073 if seen is None:
74 seen = {}
75 if '__visited' not in seen:
76 seen['__visited'] = []
77
78 orig_visited = copy.deepcopy(seen['__visited'])
79 seen['__visited'].append(entity.name)
80
Dennis Dmitriev1f375cf2017-11-22 16:14:51 +020081 orig_merge_dict = reclass_parameters.Parameters._merge_dict
82 with mock.patch.object(reclass_parameters.Parameters, '_merge_dict', new=_new_merge_dict):
Dennis Dmitrievb0990582017-11-22 16:33:10 +020083 try:
84 result = super(ReclassCore, self)._recurse_entity(entity,
85 merge_base,
86 seen,
87 nodename)
88 except Exception:
89 print("### Interpolation failed in the class: " + ' < '.join(seen['__visited']))
90 raise
Dennis Dmitriev806706d2017-07-29 22:31:23 +030091 if self.track_key_path:
92 key = helpers.get_nested_key(entity.parameters.as_dict(),
93 path=self.track_key_path)
Dennis Dmitrievcef1c072018-10-24 15:00:46 +030094 if key is not None:
Dennis Dmitriev806706d2017-07-29 22:31:23 +030095 print("# " + ' < '.join(seen['__visited']))
96 out_dict = {}
97 helpers.create_nested_key(out_dict, ['parameters'] + self.track_key_path, key)
98 print(yaml.dump(out_dict,
Dennis Dmitrieva7de8b52018-08-08 23:14:03 +030099 default_flow_style=False,
100 width=255))
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300101
102 # Reset the data collected by child entries
103 seen['__visited'] = orig_visited
104
105 return result
106
107 def _nodeinfo(self, nodename):
108 if self.track_key_path:
109 print("\n" + nodename)
110 print("-" * len(nodename))
111
112 result = super(ReclassCore, self)._nodeinfo(nodename)
113
114 if self.track_key_path:
115 key = helpers.get_nested_key(result.parameters.as_dict(),
116 path=self.track_key_path)
Dennis Dmitrievcef1c072018-10-24 15:00:46 +0300117 if key is not None:
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300118 print("### Final result after interpolation: ###")
119 out_dict = {}
120 helpers.create_nested_key(out_dict, ['parameters'] + self.track_key_path, key)
121 print(yaml.dump(out_dict,
Dennis Dmitrieva7de8b52018-08-08 23:14:03 +0300122 default_flow_style=False,
123 width=255))
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300124 return result
125
126
127def get_core(key=None):
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300128 """Initializes reclass Core() using /etc/reclass settings"""
129
130 defaults = reclass_config.find_and_read_configfile()
131 inventory_base_uri = defaults['inventory_base_uri']
132 storage_type = defaults['storage_type']
133
134 nodes_uri, classes_uri = reclass_config.path_mangler(inventory_base_uri,
135 None, None)
136 storage = reclass.get_storage(storage_type, nodes_uri, classes_uri,
137 default_environment='base')
138
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300139 #key = '_param.keepalived_vip_interface'
140 return ReclassCore(storage, None, None, key=key)
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300141
142
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300143# def get_minion_domain():
144# """Try to get domain from the local salt minion"""
145# client = salt.cli.call.SaltCall()
146# client.parse_args(args=['pillar.items'])
147# caller = salt.cli.caller.Caller.factory(client.config)
148# result = caller.call()
149# # Warning! There is a model-related parameter
150# # TODO(ddmitriev): move the path to the parameter to a settings/defaults
151# domain = result['return']['_param']['cluster_domain']
152# return domain
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300153
154
Dennis Dmitriev94239b12017-06-23 13:18:38 +0300155def inventory_list(domain=None):
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300156 core = get_core()
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300157 inventory = core.inventory()['nodes']
Dennis Dmitriev94239b12017-06-23 13:18:38 +0300158 if domain is not None:
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300159 inventory = {key: val for (key, val) in inventory.items()
160 if key.endswith(domain)}
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300161 return inventory
162
163
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300164def nodes_list(domain=None):
165 core = get_core()
166 nodes = core._storage.enumerate_nodes()
167 if domain is not None:
168 nodes = [node for node in nodes
169 if node.endswith(domain)]
170 return nodes
171
172
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300173def get_nodeinfo(minion_id):
174 core = get_core()
175 return core.nodeinfo(minion_id)
176
177
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300178def trace_key(key, domain=None, node=None):
179 if node:
180 nodes = [node]
181 else:
182 nodes = nodes_list(domain=domain)
183
184 core = get_core(key=key)
185 for node in nodes:
186 core.nodeinfo(node)
187
188
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300189def vcp_list(domain=None, inventory=None):
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300190 """List VCP node names
191
192 Scan all nodes for the object salt.control.cluster.internal.node.XXX.name
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300193 Return set of tuples ((nodename1, domain), (nodename2, domain), ...)
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300194 """
195
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300196 inventory = inventory or inventory_list(domain=domain)
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300197 vcp_path = 'parameters.salt.control.cluster.internal.node'.split('.')
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300198 domain_path = 'parameters._param.cluster_domain'.split('.')
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300199
200 vcp_node_names = set()
201
202 for node_name, node in inventory.items():
203 vcp_nodes = helpers.get_nested_key(node, path=vcp_path)
204 if vcp_nodes is not None:
205 for vcp_node_name, vcp_node in vcp_nodes.items():
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300206 vcp_node_names.add((
207 vcp_node['name'],
208 helpers.get_nested_key(node, path=domain_path)))
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300209 return vcp_node_names
210
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300211
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300212def reclass_storage(domain=None, inventory=None):
213 """List VCP node names
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300214
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300215 Scan all nodes for the object salt.control.cluster.internal.node.XXX.name
216 """
217
218 inventory = inventory or inventory_list(domain=domain)
219 storage_path = 'parameters.reclass.storage.node'.split('.')
220
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300221 res = dict()
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300222 for node_name, node in inventory.items():
223 storage_nodes = helpers.get_nested_key(node, path=storage_path)
224 if storage_nodes is not None:
225 for storage_node_name, storage_node in storage_nodes.items():
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300226 if storage_node['domain'] not in res:
227 res[storage_node['domain']] = dict()
228 res[storage_node['domain']][storage_node_name] = storage_node
229 return res