Add 'create_inventory_context' command
The command 'create_inventory_context' can be used to dump all the
nodes from reclass inventory, they names from recalss.storage,
and any additional keys from nodes if needed, for example:
$ reclass-create-inventory-context \
-d mcp11-ovs-dpdk.local \
parameters.linux.network.interface \
parameters.linux.storage
diff --git a/reclass_tools/cli.py b/reclass_tools/cli.py
index 48e3d9f..1bd80b7 100644
--- a/reclass_tools/cli.py
+++ b/reclass_tools/cli.py
@@ -105,6 +105,7 @@
help=('Show only the nodes which names are ended with the specified domain, for example:'
' reclass-inventory-list -d example.local'))
+
params = parser.parse_args(args)
inventory = reclass_models.inventory_list(domain=params.domain)
@@ -127,5 +128,27 @@
params = parser.parse_args(args)
vcp_node_names = reclass_models.vcp_list(domain=params.domain)
- print('\n'.join(sorted(vcp_node_names)))
+ print('\n'.join(sorted(('{0}.{1}'.format(name, domain) for name, domain in vcp_node_names))))
+
+
+def create_inventory_context(args=None):
+ try:
+ from reclass_tools import create_inventory
+ except ImportError:
+ print("Please run this tool on the salt-master node with installed 'reclass'")
+ return
+
+ parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
+ description="Dumps nodes and specified node parameters from reclass, for example: create_inventory_context -d example.local parameters.linux.network.interface parameters.linux.storage")
+ parser.add_argument('--domain', '-d', dest='domain',
+ help=('Show only the nodes which names are ended with the specified domain, for example:'
+ ' reclass-inventory-list -d example.local'))
+ parser.add_argument('keys', help=(
+ 'Reclass key names to dump with nodes'), nargs='*')
+
+ params = parser.parse_args(args)
+
+ current_underlay_context = create_inventory.create_inventory_context(domain=params.domain, keys=params.keys)
+
+ print(yaml.dump(current_underlay_context, default_flow_style=False))
diff --git a/reclass_tools/create_inventory.py b/reclass_tools/create_inventory.py
new file mode 100644
index 0000000..26ad89e
--- /dev/null
+++ b/reclass_tools/create_inventory.py
@@ -0,0 +1,153 @@
+import yaml
+import json
+
+from cookiecutter import __version__
+#from cookiecutter.log import configure_logger
+#from cookiecutter.main import cookiecutter
+
+from cookiecutter import generate
+from cookiecutter.exceptions import UndefinedVariableInTemplate
+
+from reclass_tools import helpers
+from reclass_tools import reclass_models
+from reclass_tools import walk_models
+
+
+def create_inventory_context(domain=None, keys=None):
+ """Dumps the current inventory per domain
+
+ Example of context:
+
+ <global_settings>: # only if required
+ ...
+ current_clusters:
+ <cluster_names>:
+ # here are cluster settings if required
+ nodes:
+ <node_names>:
+ name: ctl01
+ reclass_storage_name: openstack_control_node01
+ roles:
+ - vcp # 'vcp' or None
+ parameters: # specified keys to dump, for example
+ # parameters.linux.network.interface below:
+ linux:
+ network:
+ interfaces:
+ ..
+ """
+ inventory = reclass_models.inventory_list(domain=domain)
+ vcp_list = reclass_models.vcp_list(domain=domain, inventory=inventory)
+ reclass_storage = reclass_models.reclass_storage(domain=domain, inventory=inventory)
+
+ current_underlay_context = {
+ 'current_clusters': {
+ }
+ }
+
+ for domain, storage_nodes in reclass_storage.items():
+
+ current_cluster_nodes = {}
+ for storage_node_name, storage_node in storage_nodes.items():
+ inventory_node_name = "{0}.{1}".format(storage_node['name'], storage_node['domain'])
+ current_cluster_nodes[inventory_node_name] = {
+ 'name': storage_node['name'],
+ 'reclass_storage_name': storage_node_name,
+ 'roles': list(),
+ 'parameters': dict(),
+ }
+
+ if (storage_node['name'], storage_node['domain']) in vcp_list:
+ # Add role 'vcp' to mark the VM nodes.
+ current_cluster_nodes[inventory_node_name]['roles'].append('vcp')
+
+ if keys:
+ # Dump specified parameters for the node
+ # Will fail with KeyError if 'inventory_node_name' doesn't
+ # exists in reclass inventory
+ # (wasn't generated with reclass.storage yet, for example)
+ node = inventory[inventory_node_name]
+ for key in keys:
+ key_path = key.split('.')
+ reclass_key = helpers.get_nested_key(node, path=key_path)
+ if reclass_key:
+ helpers.create_nested_key(current_cluster_nodes[inventory_node_name], path=key_path, value=reclass_key)
+
+ current_underlay_context['current_clusters'][domain] = {
+ 'nodes': current_cluster_nodes
+ }
+
+ return current_underlay_context
+
+
+ #1. Generate jinga interfaces / hw details based on node information provided to jinja
+
+ #2. Generate appropriate includes to reclass.storate model in config node
+ #configure_logger(
+ # stream_level='DEBUG' if verbose else 'INFO',
+ # debug_file=debug_file,
+ #)
+
+#current_clusters:
+# <cluster_names>:
+# nodes:
+# <node_names>:
+# name: ctl01
+# reclass_storage_name: openstack_control_node01
+# # if classes - then classes
+# roles:
+# - vcp # to select wich interface type to use
+# #- openstack_controller # Don't forget to map the roles to corresponded classes if needed
+# parameters: # there is just a DUMP of the existing model,
+# # which could be re-used complete or particulary for rendering new model
+# linux:
+# network:
+# interfaces:
+# ..
+
+
+def render_environment_class():
+ """Coockiecutter echancement to use several source JSON files
+
+ :param template_dir: directory with templates to render
+ :param output_dir: directory that should be created from templates
+ :param context_files: list of strings, paths to YAML or JSON files
+ that provide the context variables for rendering.
+ Merge of the files usind update() into a single
+ dict is in the same order as files in the list.
+ """
+
+#ipdb> repo_dir
+#u'/root/cookiecutter-templates/cluster_product/openstack'
+#ipdb> context
+#{u'cookiecutter': {u'openstack_telemetry_node02_hostname': u'mdb02', ... }}
+#ipdb> overwrite_if_exists
+#False
+#ipdb> output_dir
+#'/root/my_new_deployment/'
+
+ repo_dir = '/root/cookiecutter-templates/cluster_product/openstack'
+ overwrite_if_exists = True
+ output_dir = '/root/my_new_deployment/'
+ context = {'cookiecutter': {'openstack_telemetry_node02_hostname': 'mdb02' }}
+
+ try:
+ generate.generate_files(
+ repo_dir=repo_dir,
+ context=context,
+ overwrite_if_exists=overwrite_if_exists,
+ output_dir=output_dir
+ )
+
+
+ except UndefinedVariableInTemplate as undefined_err:
+ print('>>> {}'.format(undefined_err.message))
+ print('>>> Error message: {}'.format(undefined_err.error.message))
+
+ context_str = yaml.dump(
+ undefined_err.context,
+ indent=4,
+ default_flow_style=False
+ )
+ print('='*15 + ' Context: '+ '='*15 + '\n{}'.format(context_str) + '='*40)
+ return
diff --git a/reclass_tools/helpers.py b/reclass_tools/helpers.py
index fcfc564..75e3185 100644
--- a/reclass_tools/helpers.py
+++ b/reclass_tools/helpers.py
@@ -11,6 +11,18 @@
return data
+def create_nested_key(data, path=None, value=None):
+ if type(data) is not dict:
+ raise("Use 'dict' object for 'data'")
+ if type(path) is not list:
+ raise("Use 'list' object with key names for 'path'")
+ for key in path[:-1]:
+ if key not in data:
+ data[key] = {}
+ data = data[key]
+ data[path[-1]] = value
+
+
def remove_nested_key(data, path=None):
if type(path) is not list:
raise("Use 'list' object with key names for 'path'")
diff --git a/reclass_tools/reclass_models.py b/reclass_tools/reclass_models.py
index 9f90fa7..7ebb1f3 100644
--- a/reclass_tools/reclass_models.py
+++ b/reclass_tools/reclass_models.py
@@ -43,14 +43,21 @@
return inventory
-def vcp_list(domain=None):
+def get_nodeinfo(minion_id):
+ core = get_core()
+ return core.nodeinfo(minion_id)
+
+
+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_list(domain=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()
@@ -58,7 +65,24 @@
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'])
+ 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('.')
+
+ result = 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 result:
+ result[storage_node['domain']] = dict()
+ result[storage_node['domain']][storage_node_name] = storage_node
+ return result