blob: 238caf38daa940f3e6219573e9e096049f221665 [file] [log] [blame]
# Copyright 2013 - 2017 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import reclass
# from reclass.adapters import salt as reclass_salt
from reclass import config as reclass_config
from reclass import core as reclass_core
from reclass import defaults as reclass_defaults
from reclass.datatypes import parameters as reclass_parameters
from reclass.utils.refvalue import RefValue
import yaml
import mock
from reclass_tools import helpers
# import salt.cli.call
# import salt.cli.caller
def refvalue_representer(dumper, data):
return dumper.represent_str(
data._assemble(
lambda s: s.join(reclass_defaults.PARAMETER_INTERPOLATION_SENTINELS)))
yaml.add_representer(RefValue, refvalue_representer)
class ReclassCore(reclass_core.Core):
"""Track the specific key
:param key: string with dot-separated keys
"""
track_key_path = None
def __init__(self, storage, class_mappings, input_data=None,
key=None):
if key:
if ':' in key:
# Linux pillar notation: linux:network:interface
self.track_key_path = key.split(':')
else:
# Python notation: linux.network.interface
self.track_key_path = key.split('.')
if self.track_key_path[0] == 'parameters':
# Remove the first 'parameters' element because the model entities
# keep parameters in different object format.
self.track_key_path = self.track_key_path[1:]
super(ReclassCore, self).__init__(storage, class_mappings, input_data)
def _recurse_entity(self, entity, merge_base=None, seen=None, nodename=None):
def _new_merge_dict(self, cur, new, path):
try:
return orig_merge_dict(self, cur, new, path)
except TypeError as e:
if "Current value:" not in e.message:
e.message +="\nValue path: {}\nCurrent value: {}\nNew value: {}\n".format(path, cur, new)
raise TypeError(e.message)
if seen is None:
seen = {}
if '__visited' not in seen:
seen['__visited'] = []
orig_visited = copy.deepcopy(seen['__visited'])
seen['__visited'].append(entity.name)
orig_merge_dict = reclass_parameters.Parameters._merge_dict
with mock.patch.object(reclass_parameters.Parameters, '_merge_dict', new=_new_merge_dict):
try:
result = super(ReclassCore, self)._recurse_entity(entity,
merge_base,
seen,
nodename)
except Exception:
print("### Interpolation failed in the class: " + ' < '.join(seen['__visited']))
raise
if self.track_key_path:
key = helpers.get_nested_key(entity.parameters.as_dict(),
path=self.track_key_path)
if key is not None:
print("# " + ' < '.join(seen['__visited']))
out_dict = {}
helpers.create_nested_key(out_dict, ['parameters'] + self.track_key_path, key)
print(yaml.dump(out_dict,
default_flow_style=False,
width=255))
# Reset the data collected by child entries
seen['__visited'] = orig_visited
return result
def _nodeinfo(self, nodename):
if self.track_key_path:
print("\n" + nodename)
print("-" * len(nodename))
result = super(ReclassCore, self)._nodeinfo(nodename)
if self.track_key_path:
key = helpers.get_nested_key(result.parameters.as_dict(),
path=self.track_key_path)
if key is not None:
print("### Final result after interpolation: ###")
out_dict = {}
helpers.create_nested_key(out_dict, ['parameters'] + self.track_key_path, key)
print(yaml.dump(out_dict,
default_flow_style=False,
width=255))
return result
def get_core(key=None):
"""Initializes reclass Core() using /etc/reclass settings"""
defaults = reclass_config.find_and_read_configfile()
inventory_base_uri = defaults['inventory_base_uri']
storage_type = defaults['storage_type']
nodes_uri, classes_uri = reclass_config.path_mangler(inventory_base_uri,
None, None)
storage = reclass.get_storage(storage_type, nodes_uri, classes_uri,
default_environment='base')
#key = '_param.keepalived_vip_interface'
return ReclassCore(storage, None, None, key=key)
# def get_minion_domain():
# """Try to get domain from the local salt minion"""
# client = salt.cli.call.SaltCall()
# client.parse_args(args=['pillar.items'])
# caller = salt.cli.caller.Caller.factory(client.config)
# result = caller.call()
# # Warning! There is a model-related parameter
# # TODO(ddmitriev): move the path to the parameter to a settings/defaults
# domain = result['return']['_param']['cluster_domain']
# return domain
def inventory_list(domain=None):
core = get_core()
inventory = core.inventory()['nodes']
if domain is not None:
inventory = {key: val for (key, val) in inventory.items()
if key.endswith(domain)}
return inventory
def nodes_list(domain=None):
core = get_core()
nodes = core._storage.enumerate_nodes()
if domain is not None:
nodes = [node for node in nodes
if node.endswith(domain)]
return nodes
def get_nodeinfo(minion_id):
core = get_core()
return core.nodeinfo(minion_id)
def trace_key(key, domain=None, node=None):
if node:
nodes = [node]
else:
nodes = nodes_list(domain=domain)
core = get_core(key=key)
for node in nodes:
core.nodeinfo(node)
def vcp_list(domain=None, inventory=None):
"""List VCP node names
Scan all nodes for the object salt.control.cluster.internal.node.XXX.name
Return set of tuples ((nodename1, domain), (nodename2, domain), ...)
"""
inventory = inventory or inventory_list(domain=domain)
vcp_path = 'parameters.salt.control.cluster.internal.node'.split('.')
domain_path = 'parameters._param.cluster_domain'.split('.')
vcp_node_names = set()
for node_name, node in inventory.items():
vcp_nodes = helpers.get_nested_key(node, path=vcp_path)
if vcp_nodes is not None:
for vcp_node_name, vcp_node in vcp_nodes.items():
vcp_node_names.add((
vcp_node['name'],
helpers.get_nested_key(node, path=domain_path)))
return vcp_node_names
def reclass_storage(domain=None, inventory=None):
"""List VCP node names
Scan all nodes for the object salt.control.cluster.internal.node.XXX.name
"""
inventory = inventory or inventory_list(domain=domain)
storage_path = 'parameters.reclass.storage.node'.split('.')
res = dict()
for node_name, node in inventory.items():
storage_nodes = helpers.get_nested_key(node, path=storage_path)
if storage_nodes is not None:
for storage_node_name, storage_node in storage_nodes.items():
if storage_node['domain'] not in res:
res[storage_node['domain']] = dict()
res[storage_node['domain']][storage_node_name] = storage_node
return res