Module functions for visualization purposes
diff --git a/_modules/reclass.py b/_modules/reclass.py
index a79410f..732e4ef 100644
--- a/_modules/reclass.py
+++ b/_modules/reclass.py
@@ -10,11 +10,14 @@
import json
import logging
import os
+import socket
import sys
import six
import yaml
+from urlparse import urlparse
from reclass import get_storage, output
+from reclass.adapters.salt import ext_pillar
from reclass.core import Core
from reclass.config import find_and_read_configfile
from string import Template
@@ -217,6 +220,154 @@
return ret
+def _is_valid_ipv4_address(address):
+ try:
+ socket.inet_pton(socket.AF_INET, address)
+ except AttributeError:
+ try:
+ socket.inet_aton(address)
+ except socket.error:
+ return False
+ return address.count('.') == 3
+ except socket.error:
+ return False
+ return True
+
+
+def _is_valid_ipv6_address(address):
+ try:
+ socket.inet_pton(socket.AF_INET6, address)
+ except socket.error:
+ return False
+ return True
+
+
+def _guess_host_from_target(host, domain=None):
+ '''
+ Guess minion ID from given host and domain arguments. Host argument can contain
+ hostname, FQDN, IPv4 or IPv6 addresses.
+ '''
+ if _is_valid_ipv4_address(host):
+ tgt = 'ipv4:%s' % host
+ elif _is_valid_ipv6_address(host):
+ tgt = 'ipv6:%s' % host
+ elif host.endswith(domain):
+ tgt = 'fqdn:%s' % host
+ else:
+ tgt = 'fqdn:%s.%s' % (host, domain)
+
+ res = __salt__['saltutil.cmd'](tgt=tgt,
+ expr_form='grain',
+ fun='grains.item',
+ arg=('id',))
+
+ return res.values()[0].get('ret', {}).get('id', '')
+
+
+def _interpolate_graph_data(graph_data, **kwargs):
+ new_nodes = []
+ for node in graph_data:
+ for relation in node.get('relations', []):
+ if relation.get('host_from_target', None):
+ host = _guess_host_from_target(relation.pop('host_from_target'))
+ relation['host'] = host
+ if relation.get('host_external', None):
+ parsed_host_external = urlparse(relation.pop('host_external'))
+ service = parsed_host_external.netloc
+ host = relation.get('service', '')
+ relation['host'] = host
+ relation['service'] = service
+ if host not in [n.get('host', '') for n in graph_data + new_nodes]:
+ new_node = {
+ 'host': host,
+ 'service': service,
+ 'type': relation.get('type', ''),
+ 'relations': []
+ }
+ new_nodes.append(new_node)
+
+ graph_data = graph_data + new_nodes
+
+ return graph_data
+
+
+def _grain_graph_data(*args, **kwargs):
+ ret = __salt__['saltutil.cmd'](tgt='*',
+ fun='grains.item',
+ arg=('salt:graph',))
+ graph_data = []
+ for minion_ret in ret.values():
+ if minion_ret.get('retcode', 1) == 0:
+ graph_datum = minion_ret.get('ret', {}).get('salt:graph', [])
+ graph_data = graph_data + graph_datum
+
+ graph_nodes = _interpolate_graph_data(graph_data)
+ graph = {}
+
+ for node in graph_nodes:
+ if node.get('host') not in graph:
+ graph[node.get('host')] = {}
+ graph[node.pop('host')][node.pop('service')] = node
+
+ return {'graph': graph}
+
+
+def _pillar_graph_data(*args, **kwargs):
+ graph = {}
+ nodes = inventory()
+ for node, node_data in nodes.items():
+ for role in node_data.get('roles', []):
+ if node not in graph:
+ graph[node] = {}
+ graph[node][role] = {'relations': []}
+
+ return {'graph': graph}
+
+
+def graph_data(*args, **kwargs):
+ '''
+ Returns graph data for visualization app
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt '*' reclass.graph_data
+
+ '''
+ pillar_data = _pillar_graph_data().get('graph')
+ grain_data = _grain_graph_data().get('graph')
+
+ for host, services in pillar_data.items():
+ for service, service_data in services.items():
+ grain_service = grain_data.get(host, {}).get(service, {})
+ service_data.update(grain_service)
+
+ graph = []
+ for host, services in pillar_data.items():
+ for service, service_data in services.items():
+ additional_data = {
+ 'host': host,
+ 'service': service
+ }
+ service_data.update(additional_data)
+ graph.append(service_data)
+
+ for host, services in grain_data.items():
+ for service, service_data in services.items():
+ additional_data = {
+ 'host': host,
+ 'service': service
+ }
+ service_data.update(additional_data)
+ host_list = [g.get('host', '') for g in graph]
+ service_list = [g.get('service', '') for g in graph if g.get('host') == host]
+ if host not in host_list or (host in host_list and service not in service_list):
+ graph.append(service_data)
+
+ return {'graph': graph}
+
+
def node_update(name, classes=None, parameters=None, **connection_args):
'''
Update a node metadata information, classes and parameters.
@@ -319,6 +470,26 @@
return ret
+def node_pillar(node_name, **kwargs):
+ '''
+ Returns pillar data for given minion from reclass inventory.
+
+ :param node_name: target minion ID
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt-call reclass.node_pillar minion_id
+
+ '''
+ defaults = find_and_read_configfile()
+ pillar = ext_pillar(node_name, {}, defaults['storage_type'], defaults['inventory_base_uri'])
+ output = {node_name: pillar}
+
+ return output
+
+
def inventory(**connection_args):
'''
Get all nodes in inventory and their associated services/roles classification.
@@ -352,6 +523,18 @@
def cluster_meta_list(file_name="overrides.yml", cluster="", **kwargs):
+ '''
+ List all cluster level overrides
+
+ :param file_name: name of the override file, defaults to: overrides.yml
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt-call reclass.cluster_meta_list
+
+ '''
path = os.path.join(_get_cluster_dir(), cluster, file_name)
try:
with io.open(path, 'r') as file_handle:
@@ -365,6 +548,19 @@
def cluster_meta_delete(name, file_name="overrides.yml", cluster="", **kwargs):
+ '''
+ Delete cluster level override entry
+
+ :param name: name of the override entry (dictionary key)
+ :param file_name: name of the override file, defaults to: overrides.yml
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt-call reclass.cluster_meta_delete foo
+
+ '''
ret = {}
path = os.path.join(_get_cluster_dir(), cluster, file_name)
meta = __salt__['reclass.cluster_meta_list'](path, **kwargs)
@@ -385,6 +581,20 @@
def cluster_meta_set(name, value, file_name="overrides.yml", cluster="", **kwargs):
+ '''
+ Create cluster level override entry
+
+ :param name: name of the override entry (dictionary key)
+ :param value: value of the override entry (dictionary value)
+ :param file_name: name of the override file, defaults to: overrides.yml
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt-call reclass.cluster_meta_set foo bar
+
+ '''
path = os.path.join(_get_cluster_dir(), cluster, file_name)
meta = __salt__['reclass.cluster_meta_list'](path, **kwargs)
if 'Error' not in meta:
@@ -406,6 +616,19 @@
def cluster_meta_get(name, file_name="overrides.yml", cluster="", **kwargs):
+ '''
+ Get single cluster level override entry
+
+ :param name: name of the override entry (dictionary key)
+ :param file_name: name of the override file, defaults to: overrides.yml
+
+ CLI Examples:
+
+ .. code-block:: bash
+
+ salt-call reclass.cluster_meta_get foo
+
+ '''
ret = {}
path = os.path.join(_get_cluster_dir(), cluster, file_name)
meta = __salt__['reclass.cluster_meta_list'](path, **kwargs)