Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR to include support for Skydive Node and Edge modules with Ansible #53112

Merged
merged 11 commits into from
Mar 13, 2019
209 changes: 206 additions & 3 deletions lib/ansible/module_utils/network/skydive/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,26 @@
#

import os
import uuid

from ansible.module_utils.six import iteritems
from ansible.module_utils.six import iterkeys
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import env_fallback

try:
from skydive.graph import Node, Edge
from skydive.rest.client import RESTClient
from skydive.websocket.client import NodeAddedMsgType, NodeUpdatedMsgType, NodeDeletedMsgType
from skydive.websocket.client import EdgeAddedMsgType, EdgeUpdatedMsgType, EdgeDeletedMsgType
from skydive.websocket.client import WSClient, WSClientDefaultProtocol, WSMessage
HAS_SKYDIVE_CLIENT = True
except ImportError:
HAS_SKYDIVE_CLIENT = False

# defining skydive constants
SKYDIVE_GREMLIN_QUERY = 'G.V().Has'
SKYDIVE_GREMLIN_EDGE_QUERY = 'G.E().Has'

SKYDIVE_PROVIDER_SPEC = {
'endpoint': dict(fallback=(env_fallback, ['SKYDIVE_ENDPOINT'])),
Expand All @@ -51,11 +57,13 @@
}


class skydive_restclient(object):
''' Base class for implementing Skydive Rest API '''
class skydive_client_check(object):
""" Base class for implementing Skydive Rest API """

provider_spec = {'provider': dict(type='dict', options=SKYDIVE_PROVIDER_SPEC)}

def __init__(self, **kwargs):
''' Base class for implementing Skydive Rest API '''
if not HAS_SKYDIVE_CLIENT:
raise Exception('skydive-client is required but does not appear '
'to be installed. It can be installed using the '
Expand All @@ -74,6 +82,135 @@ def __init__(self, **kwargs):
env = ('SKYDIVE_%s' % key).upper()
if env in os.environ:
kwargs[key] = os.environ.get(env)


class skydive_inject_protocol(object):
justjais marked this conversation as resolved.
Show resolved Hide resolved
""" Implements inject protocol for node and edge modules """
justjais marked this conversation as resolved.
Show resolved Hide resolved

def onOpen(self):
module = self.factory.kwargs["module"]
params = self.factory.kwargs["params"]
result = self.factory.kwargs["result"]
if "node1" and "node2" in self.factory.kwargs:
node1 = self.factory.kwargs["node1"]
node2 = self.factory.kwargs["node2"]

if module.check_mode:
self.stop()
return
try:
host = params["host"]
if params["metadata"]:
metadata = module._check_type_dict(params["metadata"])
else:
metadata = {}
if "node_type" in params:
metadata["Name"] = params["name"]
metadata["Type"] = params["node_type"]
seed = params["seed"]
if not seed:
seed = "%s:%s" % (params["name"], params["node_type"])
if module.params['state'] == 'present' or module.params['state'] == 'update':
uid = str(uuid.uuid5(uuid.NAMESPACE_OID, seed))
node = Node(uid, host, metadata=metadata)
if module.params['state'] == 'present':
msg = WSMessage("Graph", NodeAddedMsgType, node)
else:
msg = WSMessage("Graph", NodeUpdatedMsgType, node)
else:
uid = params['id']
node = Node(uid, host, metadata=metadata)
msg = WSMessage("Graph", NodeDeletedMsgType, node)
elif "relation_type" in params:
metadata["RelationType"] = params["relation_type"]
if module.params['state'] == 'present' or module.params['state'] == 'update':
uid = str(uuid.uuid5(uuid.NAMESPACE_OID, "%s:%s:%s" %
(node1, node2, params["relation_type"])))
edge = Edge(uid, host, node1, node2, metadata=metadata)
if module.params['state'] == 'present':
msg = WSMessage("Graph", EdgeAddedMsgType, edge)
else:
msg = WSMessage("Graph", EdgeUpdatedMsgType, edge)
else:
uid = module.params['id']
edge = Edge(uid, host, node1, node2, metadata=metadata)
msg = WSMessage("Graph", EdgeDeletedMsgType, edge)

self.sendWSMessage(msg)
if uid:
result["UUID"] = uid
result["changed"] = True
except Exception as e:
module.fail_json(
msg='Error during topology update %s' % e, **result)
finally:
self.stop()


class skydive_wsclient(skydive_client_check):
""" Base class for implementing Skydive Websocket API """

def __init__(self, module, **kwargs):
super(skydive_wsclient, self).__init__(**kwargs)

class skydive_full_inject_protocol(skydive_inject_protocol, WSClientDefaultProtocol):
pass
kwargs['scheme'] = "ws"
if 'ssl' in kwargs:
if kwargs['ssl']:
kwargs['scheme'] = "wss"
if 'insecure' not in kwargs:
kwargs['insecure'] = False
scheme = kwargs['scheme']
self.result = dict(changed=False)
if "node_type" in module.params:
justjais marked this conversation as resolved.
Show resolved Hide resolved
self.wsclient_object = WSClient("ansible-" + str(os.getpid()) + "-" + module.params['host'],
"%s://%s/ws/publisher" % (scheme, kwargs["endpoint"]),
protocol=type('skydive_full_inject_protocol', (skydive_inject_protocol,
WSClientDefaultProtocol), dict()),
persistent=True,
insecure=kwargs["insecure"],
username=kwargs["username"],
password=kwargs["password"],
module=module,
params=module.params,
result=self.result)
elif "relation_type" in module.params:
self.parent_node = self.get_node_id(module.params['parent_node'])
self.child_node = self.get_node_id(module.params['child_node'])

self.wsclient_object = WSClient("ansible-" + str(os.getpid()) + "-" + module.params['host'],
"%s://%s/ws/publisher" % (scheme, kwargs["endpoint"]),
protocol=type('skydive_full_inject_protocol', (skydive_inject_protocol,
WSClientDefaultProtocol), dict()),
persistent=True,
insecure=kwargs["insecure"],
username=kwargs["username"],
password=kwargs["password"],
module=module,
params=module.params,
node1=self.parent_node,
node2=self.child_node,
result=self.result)

def get_node_id(self, node_selector):
""" Checks if Gremlin expresssion is passed as input to get the nodes UUID """
if node_selector.startswith("G.") or node_selector.startswith("g."):
nodes = self.restclient_object.lookup_nodes(node_selector)
if len(nodes) == 0:
raise self.module.fail_json(msg=to_text("Node not found: {0}".format(node_selector)))
elif len(nodes) > 1:
raise self.module.fail_json(
msg=to_text("Node selection should return only one node: {0}".format(node_selector)))
return str(nodes[0].id)
return node_selector


class skydive_restclient(skydive_client_check):
""" Base class for implementing Skydive Rest API """

def __init__(self, **kwargs):
super(skydive_restclient, self).__init__(**kwargs)
kwargs['scheme'] = "http"
if 'ssl' in kwargs:
if kwargs['ssl']:
Expand All @@ -88,6 +225,8 @@ def __init__(self, **kwargs):


class skydive_lookup(skydive_restclient):
""" Implements Skydive Lookup queries """

provider_spec = {'provider': dict(type='dict', options=SKYDIVE_PROVIDER_SPEC)}

def __init__(self, provider):
Expand All @@ -107,7 +246,8 @@ def lookup_query(self, filter_data):


class skydive_flow_capture(skydive_restclient):
''' Implements Skydive Flow capture modules '''
""" Implements Skydive Flow capture modules """

def __init__(self, module):
self.module = module
provider = module.params['provider']
Expand Down Expand Up @@ -161,3 +301,66 @@ def run(self, ib_spec):
result['changed'] = True

return result


class skydive_node(skydive_wsclient, skydive_restclient):
""" Implements Skydive Node modules """

def __init__(self, module):
self.module = module
provider = module.params['provider']
super(skydive_node, self).__init__(self.module, **provider)

def run(self):
try:
lookup_query = SKYDIVE_GREMLIN_QUERY + "('Name', '{0}', 'Type', '{1}')".format(self.module.params['name'],
self.module.params['node_type'])
node_exists = self.restclient_object.lookup_nodes(lookup_query)

if not node_exists and self.module.params['state'] == 'present':
self.wsclient_object.connect()
self.wsclient_object.start()
elif len(node_exists) > 0 and self.module.params['state'] == 'update':
self.wsclient_object.connect()
self.wsclient_object.start()
elif len(node_exists) > 0 and self.module.params['state'] == 'absent':
self.module.params['id'] = node_exists[0].__dict__['id']
self.wsclient_object.connect()
self.wsclient_object.start()
except Exception as e:
self.module.fail_json(msg=to_text(e))
return self.result


class skydive_edge(skydive_wsclient, skydive_restclient):
""" Implements Skydive Edge modules """

def __init__(self, module):
self.module = module
provider = module.params['provider']

super(skydive_edge, self).__init__(self.module, **provider)

def run(self):
try:
edge_exists = False
edge_query = SKYDIVE_GREMLIN_EDGE_QUERY + "('Parent', '{0}', 'Child', '{1}')".format(self.parent_node,
self.child_node)
query_result = self.restclient_object.lookup_edges(edge_query)
if query_result:
query_result = query_result[0].__dict__
edge_exists = True

if not edge_exists and self.module.params['state'] == 'present':
self.wsclient_object.connect()
self.wsclient_object.start()
elif edge_exists and self.module.params['state'] == 'update':
self.wsclient_object.connect()
self.wsclient_object.start()
elif edge_exists and self.module.params['state'] == 'absent':
self.module.params['id'] = query_result['id']
self.wsclient_object.connect()
self.wsclient_object.start()
except Exception as e:
self.module.fail_json(msg=to_text(e))
return self.result