blob: a85116995fc808d8b77d3590dae64ac7615b3bff [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):
83 result = super(ReclassCore, self)._recurse_entity(entity,
84 merge_base,
85 seen,
86 nodename)
Dennis Dmitriev806706d2017-07-29 22:31:23 +030087 if self.track_key_path:
88 key = helpers.get_nested_key(entity.parameters.as_dict(),
89 path=self.track_key_path)
90 if key:
91 print("# " + ' < '.join(seen['__visited']))
92 out_dict = {}
93 helpers.create_nested_key(out_dict, ['parameters'] + self.track_key_path, key)
94 print(yaml.dump(out_dict,
95 default_flow_style=False))
96
97 # Reset the data collected by child entries
98 seen['__visited'] = orig_visited
99
100 return result
101
102 def _nodeinfo(self, nodename):
103 if self.track_key_path:
104 print("\n" + nodename)
105 print("-" * len(nodename))
106
107 result = super(ReclassCore, self)._nodeinfo(nodename)
108
109 if self.track_key_path:
110 key = helpers.get_nested_key(result.parameters.as_dict(),
111 path=self.track_key_path)
112 if key:
113 print("### Final result after interpolation: ###")
114 out_dict = {}
115 helpers.create_nested_key(out_dict, ['parameters'] + self.track_key_path, key)
116 print(yaml.dump(out_dict,
117 default_flow_style=False))
118 return result
119
120
121def get_core(key=None):
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300122 """Initializes reclass Core() using /etc/reclass settings"""
123
124 defaults = reclass_config.find_and_read_configfile()
125 inventory_base_uri = defaults['inventory_base_uri']
126 storage_type = defaults['storage_type']
127
128 nodes_uri, classes_uri = reclass_config.path_mangler(inventory_base_uri,
129 None, None)
130 storage = reclass.get_storage(storage_type, nodes_uri, classes_uri,
131 default_environment='base')
132
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300133 #key = '_param.keepalived_vip_interface'
134 return ReclassCore(storage, None, None, key=key)
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300135
136
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300137# def get_minion_domain():
138# """Try to get domain from the local salt minion"""
139# client = salt.cli.call.SaltCall()
140# client.parse_args(args=['pillar.items'])
141# caller = salt.cli.caller.Caller.factory(client.config)
142# result = caller.call()
143# # Warning! There is a model-related parameter
144# # TODO(ddmitriev): move the path to the parameter to a settings/defaults
145# domain = result['return']['_param']['cluster_domain']
146# return domain
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300147
148
Dennis Dmitriev94239b12017-06-23 13:18:38 +0300149def inventory_list(domain=None):
Dennis Dmitriev1110ac52017-06-22 21:07:37 +0300150 core = get_core()
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300151 inventory = core.inventory()['nodes']
Dennis Dmitriev94239b12017-06-23 13:18:38 +0300152 if domain is not None:
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300153 inventory = {key: val for (key, val) in inventory.items()
154 if key.endswith(domain)}
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300155 return inventory
156
157
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300158def nodes_list(domain=None):
159 core = get_core()
160 nodes = core._storage.enumerate_nodes()
161 if domain is not None:
162 nodes = [node for node in nodes
163 if node.endswith(domain)]
164 return nodes
165
166
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300167def get_nodeinfo(minion_id):
168 core = get_core()
169 return core.nodeinfo(minion_id)
170
171
Dennis Dmitriev806706d2017-07-29 22:31:23 +0300172def trace_key(key, domain=None, node=None):
173 if node:
174 nodes = [node]
175 else:
176 nodes = nodes_list(domain=domain)
177
178 core = get_core(key=key)
179 for node in nodes:
180 core.nodeinfo(node)
181
182
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300183def vcp_list(domain=None, inventory=None):
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300184 """List VCP node names
185
186 Scan all nodes for the object salt.control.cluster.internal.node.XXX.name
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300187 Return set of tuples ((nodename1, domain), (nodename2, domain), ...)
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300188 """
189
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300190 inventory = inventory or inventory_list(domain=domain)
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300191 vcp_path = 'parameters.salt.control.cluster.internal.node'.split('.')
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300192 domain_path = 'parameters._param.cluster_domain'.split('.')
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300193
194 vcp_node_names = set()
195
196 for node_name, node in inventory.items():
197 vcp_nodes = helpers.get_nested_key(node, path=vcp_path)
198 if vcp_nodes is not None:
199 for vcp_node_name, vcp_node in vcp_nodes.items():
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300200 vcp_node_names.add((
201 vcp_node['name'],
202 helpers.get_nested_key(node, path=domain_path)))
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300203 return vcp_node_names
204
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300205
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300206def reclass_storage(domain=None, inventory=None):
207 """List VCP node names
Dennis Dmitrievde847d92017-06-26 18:58:05 +0300208
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300209 Scan all nodes for the object salt.control.cluster.internal.node.XXX.name
210 """
211
212 inventory = inventory or inventory_list(domain=domain)
213 storage_path = 'parameters.reclass.storage.node'.split('.')
214
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300215 res = dict()
Dennis Dmitriev30dfb892017-06-29 20:58:11 +0300216 for node_name, node in inventory.items():
217 storage_nodes = helpers.get_nested_key(node, path=storage_path)
218 if storage_nodes is not None:
219 for storage_node_name, storage_node in storage_nodes.items():
Dennis Dmitriev566db4b2017-07-18 18:13:07 +0300220 if storage_node['domain'] not in res:
221 res[storage_node['domain']] = dict()
222 res[storage_node['domain']][storage_node_name] = storage_node
223 return res