blob: d05be5b618bb574e62731661f8225f19dd5faca7 [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)
94 if key:
95 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,
99 default_flow_style=False))
100
101 # Reset the data collected by child entries
102 seen['__visited'] = orig_visited
103
104 return result
105
106 def _nodeinfo(self, nodename):
107 if self.track_key_path:
108 print("\n" + nodename)
109 print("-" * len(nodename))
110
111 result = super(ReclassCore, self)._nodeinfo(nodename)
112
113 if self.track_key_path:
114 key = helpers.get_nested_key(result.parameters.as_dict(),
115 path=self.track_key_path)
116 if key:
117 print("### Final result after interpolation: ###")
118 out_dict = {}
119 helpers.create_nested_key(out_dict, ['parameters'] + self.track_key_path, key)
120 print(yaml.dump(out_dict,
121 default_flow_style=False))
122 return result
123
124
125def get_core(key=None):
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300126 """Initializes reclass Core() using /etc/reclass settings"""
127
128 defaults = reclass_config.find_and_read_configfile()
129 inventory_base_uri = defaults['inventory_base_uri']
130 storage_type = defaults['storage_type']
131
132 nodes_uri, classes_uri = reclass_config.path_mangler(inventory_base_uri,
133 None, None)
134 storage = reclass.get_storage(storage_type, nodes_uri, classes_uri,
135 default_environment='base')
136
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300137 #key = '_param.keepalived_vip_interface'
138 return ReclassCore(storage, None, None, key=key)
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300139
140
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300141# def get_minion_domain():
142# """Try to get domain from the local salt minion"""
143# client = salt.cli.call.SaltCall()
144# client.parse_args(args=['pillar.items'])
145# caller = salt.cli.caller.Caller.factory(client.config)
146# result = caller.call()
147# # Warning! There is a model-related parameter
148# # TODO(ddmitriev): move the path to the parameter to a settings/defaults
149# domain = result['return']['_param']['cluster_domain']
150# return domain
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300151
152
Dennis Dmitriev94239b12017-06-23 13:18:38 +0300153def inventory_list(domain=None):
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300154 core = get_core()
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300155 inventory = core.inventory()['nodes']
Dennis Dmitriev94239b12017-06-23 13:18:38 +0300156 if domain is not None:
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300157 inventory = {key: val for (key, val) in inventory.items()
158 if key.endswith(domain)}
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300159 return inventory
160
161
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300162def nodes_list(domain=None):
163 core = get_core()
164 nodes = core._storage.enumerate_nodes()
165 if domain is not None:
166 nodes = [node for node in nodes
167 if node.endswith(domain)]
168 return nodes
169
170
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300171def get_nodeinfo(minion_id):
172 core = get_core()
173 return core.nodeinfo(minion_id)
174
175
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300176def trace_key(key, domain=None, node=None):
177 if node:
178 nodes = [node]
179 else:
180 nodes = nodes_list(domain=domain)
181
182 core = get_core(key=key)
183 for node in nodes:
184 core.nodeinfo(node)
185
186
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300187def vcp_list(domain=None, inventory=None):
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300188 """List VCP node names
189
190 Scan all nodes for the object salt.control.cluster.internal.node.XXX.name
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300191 Return set of tuples ((nodename1, domain), (nodename2, domain), ...)
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300192 """
193
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300194 inventory = inventory or inventory_list(domain=domain)
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300195 vcp_path = 'parameters.salt.control.cluster.internal.node'.split('.')
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300196 domain_path = 'parameters._param.cluster_domain'.split('.')
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300197
198 vcp_node_names = set()
199
200 for node_name, node in inventory.items():
201 vcp_nodes = helpers.get_nested_key(node, path=vcp_path)
202 if vcp_nodes is not None:
203 for vcp_node_name, vcp_node in vcp_nodes.items():
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300204 vcp_node_names.add((
205 vcp_node['name'],
206 helpers.get_nested_key(node, path=domain_path)))
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300207 return vcp_node_names
208
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300209
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300210def reclass_storage(domain=None, inventory=None):
211 """List VCP node names
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300212
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300213 Scan all nodes for the object salt.control.cluster.internal.node.XXX.name
214 """
215
216 inventory = inventory or inventory_list(domain=domain)
217 storage_path = 'parameters.reclass.storage.node'.split('.')
218
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300219 res = dict()
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300220 for node_name, node in inventory.items():
221 storage_nodes = helpers.get_nested_key(node, path=storage_path)
222 if storage_nodes is not None:
223 for storage_node_name, storage_node in storage_nodes.items():
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300224 if storage_node['domain'] not in res:
225 res[storage_node['domain']] = dict()
226 res[storage_node['domain']][storage_node_name] = storage_node
227 return res