Merge pull request #14 from atengler/reactor/register-node
Reactor - node_register
diff --git a/_modules/reclass.py b/_modules/reclass.py
index ae94541..a79410f 100644
--- a/_modules/reclass.py
+++ b/_modules/reclass.py
@@ -17,6 +17,7 @@
from reclass import get_storage, output
from reclass.core import Core
from reclass.config import find_and_read_configfile
+from string import Template
LOG = logging.getLogger(__name__)
@@ -233,6 +234,91 @@
return {'Error': 'Error in retrieving node'}
+def _get_node_classes(node_data, class_mapping_fragment):
+ classes = []
+
+ for value_tmpl_string in class_mapping_fragment.get('value_template', []):
+ value_tmpl = Template(value_tmpl_string.replace('<<', '${').replace('>>', '}'))
+ rendered_value = value_tmpl.safe_substitute(node_data)
+ classes.append(rendered_value)
+
+ for value in class_mapping_fragment.get('value', []):
+ classes.append(value)
+
+ return classes
+
+
+def _get_params(node_data, class_mapping_fragment):
+ params = {}
+
+ for param_name, param in class_mapping_fragment.items():
+ value = param.get('value', None)
+ value_tmpl_string = param.get('value_template', None)
+ if value:
+ params.update({param_name: value})
+ elif value_tmpl_string:
+ value_tmpl = Template(value_tmpl_string.replace('<<', '${').replace('>>', '}'))
+ rendered_value = value_tmpl.safe_substitute(node_data)
+ params.update({param_name: rendered_value})
+
+ return params
+
+
+def _validate_condition(node_data, expression_tmpl_string):
+ expression_tmpl = Template(expression_tmpl_string.replace('<<', '${').replace('>>', '}'))
+ expression = expression_tmpl.safe_substitute(node_data)
+
+ if expression and expression == 'all':
+ return True
+ elif expression:
+ val_a = expression.split('__')[0]
+ val_b = expression.split('__')[2]
+ condition = expression.split('__')[1]
+ if condition == 'startswith':
+ return val_a.startswith(val_b)
+ elif condition == 'equals':
+ return val_a == val_b
+
+ return False
+
+
+def node_classify(node_name, node_data={}, class_mapping={}, **kwargs):
+ '''
+ CLassify node by given class_mapping dictionary
+
+ :param node_name: node FQDN
+ :param node_data: dictionary of known informations about the node
+ :param class_mapping: dictionary of classes and parameters, with conditions
+
+ '''
+ # clean node_data
+ node_data = {k: v for (k, v) in node_data.items() if not k.startswith('__')}
+
+ classes = []
+ node_params = {}
+ cluster_params = {}
+ ret = {'node_create': '', 'cluster_param': {}}
+
+ for type_name, node_type in class_mapping.items():
+ valid = _validate_condition(node_data, node_type.get('expression', ''))
+ if valid:
+ gen_classes = _get_node_classes(node_data, node_type.get('node_class', {}))
+ classes = classes + gen_classes
+ gen_node_params = _get_params(node_data, node_type.get('node_param', {}))
+ node_params.update(gen_node_params)
+ gen_cluster_params = _get_params(node_data, node_type.get('cluster_param', {}))
+ cluster_params.update(gen_cluster_params)
+
+ if classes:
+ create_kwargs = {'name': node_name, 'path': '_generated', 'classes': classes, 'parameters': node_params}
+ ret['node_create'] = node_create(**create_kwargs)
+
+ for name, value in cluster_params.items():
+ ret['cluster_param'][name] = cluster_meta_set(name, value)
+
+ return ret
+
+
def inventory(**connection_args):
'''
Get all nodes in inventory and their associated services/roles classification.
diff --git a/_states/reclass.py b/_states/reclass.py
index 8f94b3e..4f9e09e 100644
--- a/_states/reclass.py
+++ b/_states/reclass.py
@@ -79,6 +79,27 @@
return ret
+def dynamic_node_present(name, node_data={}, class_mapping={}, **kwargs):
+ '''
+ Classify node, create cluster level overrides and node metadata
+
+ :param name: node FQDN
+ :param node_data: dictionary of known informations about the node
+ :param class_mapping: dictionary of classes and parameters, with conditions
+
+ '''
+ ret = {'name': name,
+ 'changes': {},
+ 'result': True,
+ 'comment': 'Node "{0}" already exists and it is in correct state'.format(name)}
+
+ classify_ret = __salt__['reclass.node_classify'](name, node_data, class_mapping, **kwargs)
+ ret['comment'] = 'Node "{0}" has been created'.format(name)
+ ret['changes']['Node'] = classify_ret
+
+ return ret
+
+
def cluster_meta_present(name, value, file_name="overrides.yml", cluster="", **kwargs):
'''
Ensures that the cluster metadata entry exists
diff --git a/reclass/orchestrate/reactor/node_register.sls b/reclass/orchestrate/reactor/node_register.sls
new file mode 100644
index 0000000..ec00392
--- /dev/null
+++ b/reclass/orchestrate/reactor/node_register.sls
@@ -0,0 +1,22 @@
+{%- set node_name = salt['pillar.get']('event_originator') %}
+{%- set node_data = salt['pillar.get']('event_data') %}
+
+classify_node_{{ node_name }}:
+ salt.state:
+ - tgt: 'salt:master'
+ - tgt_type: pillar
+ - sls: reclass.reactor_sls.node_register
+ - queue: True
+ - pillar:
+ node_name: {{ node_name }}
+ node_data: {{ node_data }}
+
+regenerate_all_nodes:
+ salt.state:
+ - tgt: 'salt:master'
+ - tgt_type: pillar
+ - sls: reclass.storage.node
+ - queue: True
+ - requires:
+ - salt: classify_node_{{ node_name }}
+
diff --git a/reclass/reactor/node_register.sls b/reclass/reactor/node_register.sls
new file mode 100644
index 0000000..fbe5f46
--- /dev/null
+++ b/reclass/reactor/node_register.sls
@@ -0,0 +1,8 @@
+orchestrate_node_register:
+ runner.state.orchestrate:
+ - mods: reclass.orchestrate.reactor.node_register
+ - queue: True
+ - pillar:
+ event_originator: {{ data.id }}
+ event_data: {{ data.data }}
+
diff --git a/reclass/reactor_sls/node_register.sls b/reclass/reactor_sls/node_register.sls
new file mode 100644
index 0000000..af2031f
--- /dev/null
+++ b/reclass/reactor_sls/node_register.sls
@@ -0,0 +1,10 @@
+{%- set node_name = salt['pillar.get']('node_name') %}
+{%- set node_data = salt['pillar.get']('node_data') %}
+{%- set class_mapping = salt['pillar.get']('reclass:storage:class_mapping') %}
+
+classify_node_{{ node_name }}:
+ reclass.dynamic_node_present:
+ - name: {{ node_name }}
+ - node_data: {{ node_data }}
+ - class_mapping: {{ class_mapping }}
+