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

* skydive node and edge module

Signed-off-by: Sumit Jaiswal <sjaiswal@redhat.com>
pull/53736/head
Sumit Jaiswal 6 years ago committed by GitHub
parent c2cb82ec14
commit cd091ba49f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -27,6 +27,7 @@
# #
import os import os
import uuid
from ansible.module_utils.six import iteritems from ansible.module_utils.six import iteritems
from ansible.module_utils.six import iterkeys from ansible.module_utils.six import iterkeys
@ -34,13 +35,18 @@ from ansible.module_utils._text import to_text
from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import env_fallback
try: try:
from skydive.graph import Node, Edge
from skydive.rest.client import RESTClient 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 HAS_SKYDIVE_CLIENT = True
except ImportError: except ImportError:
HAS_SKYDIVE_CLIENT = False HAS_SKYDIVE_CLIENT = False
# defining skydive constants # defining skydive constants
SKYDIVE_GREMLIN_QUERY = 'G.V().Has' SKYDIVE_GREMLIN_QUERY = 'G.V().Has'
SKYDIVE_GREMLIN_EDGE_QUERY = 'G.E().Has'
SKYDIVE_PROVIDER_SPEC = { SKYDIVE_PROVIDER_SPEC = {
'endpoint': dict(fallback=(env_fallback, ['SKYDIVE_ENDPOINT'])), 'endpoint': dict(fallback=(env_fallback, ['SKYDIVE_ENDPOINT'])),
@ -51,11 +57,13 @@ SKYDIVE_PROVIDER_SPEC = {
} }
class skydive_restclient(object): class skydive_client_check(object):
''' Base class for implementing Skydive Rest API ''' """ Base class for implementing Skydive Rest API """
provider_spec = {'provider': dict(type='dict', options=SKYDIVE_PROVIDER_SPEC)} provider_spec = {'provider': dict(type='dict', options=SKYDIVE_PROVIDER_SPEC)}
def __init__(self, **kwargs): def __init__(self, **kwargs):
''' Base class for implementing Skydive Rest API '''
if not HAS_SKYDIVE_CLIENT: if not HAS_SKYDIVE_CLIENT:
raise Exception('skydive-client is required but does not appear ' raise Exception('skydive-client is required but does not appear '
'to be installed. It can be installed using the ' 'to be installed. It can be installed using the '
@ -74,6 +82,135 @@ class skydive_restclient(object):
env = ('SKYDIVE_%s' % key).upper() env = ('SKYDIVE_%s' % key).upper()
if env in os.environ: if env in os.environ:
kwargs[key] = os.environ.get(env) kwargs[key] = os.environ.get(env)
class skydive_inject_protocol(object):
""" Implements inject protocol for node and edge modules """
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:
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" kwargs['scheme'] = "http"
if 'ssl' in kwargs: if 'ssl' in kwargs:
if kwargs['ssl']: if kwargs['ssl']:
@ -88,6 +225,8 @@ class skydive_restclient(object):
class skydive_lookup(skydive_restclient): class skydive_lookup(skydive_restclient):
""" Implements Skydive Lookup queries """
provider_spec = {'provider': dict(type='dict', options=SKYDIVE_PROVIDER_SPEC)} provider_spec = {'provider': dict(type='dict', options=SKYDIVE_PROVIDER_SPEC)}
def __init__(self, provider): def __init__(self, provider):
@ -107,7 +246,8 @@ class skydive_lookup(skydive_restclient):
class skydive_flow_capture(skydive_restclient): class skydive_flow_capture(skydive_restclient):
''' Implements Skydive Flow capture modules ''' """ Implements Skydive Flow capture modules """
def __init__(self, module): def __init__(self, module):
self.module = module self.module = module
provider = module.params['provider'] provider = module.params['provider']
@ -161,3 +301,66 @@ class skydive_flow_capture(skydive_restclient):
result['changed'] = True result['changed'] = True
return result 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

@ -0,0 +1,198 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2019, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: skydive_edge
version_added: "2.8"
author:
- "Sumit Jaiswal (@sjaiswal)"
short_description: Module to add edges to Skydive topology
description:
- This module handles setting up edges between two nodes based on the
relationship type to the Skydive topology.
requirements:
- skydive-client
extends_documentation_fragment: skydive
options:
parent_node:
description:
- To defined the first node of the link, it can be either an ID or
a gremlin expression
required: true
child_node:
description:
- To defined the second node of the link, it can be either an ID or
a gremlin expression
required: true
relation_type:
description:
- To define relation type of the node I(ownership, layer2, layer3).
required: true
host:
description:
- To define the host of the node.
default: ""
required: False
metadata:
description:
- To define metadata for the edge.
required: false
state:
description:
- State of the Skydive Edge. If value is I(present) new edge
will be created else if it is I(absent) it will be deleted.
default: present
choices:
- present
- absent
"""
EXAMPLES = """
- name: create tor
skydive_node:
name: 'TOR'
node_type: "fabric"
seed: TOR
metadata:
Model: Cisco xxxx
provider:
endpoint: localhost:8082
username: admin
password: admin
register: tor_result
- name: create port 1
skydive_node:
name: 'PORT1'
node_type: 'fabric'
seed: PORT1
provider:
endpoint: localhost:8082
username: admin
password: admin
register: port1_result
- name: create port 2
skydive_node:
name: 'PORT2'
node_type: 'fabric'
seed: PORT2
provider:
endpoint: localhost:8082
username: admin
password: admin
register: port2_result
- name: link node tor and port 1
skydive_edge:
parent_node: "{{ tor_result.UUID }}"
child_node: "{{ port1_result.UUID }}"
relation_type: ownership
state: present
provider:
endpoint: localhost:8082
username: admin
password: admin
- name: link node tor and port 2
skydive_edge:
parent_node: "{{ tor_result.UUID }}"
child_node: "{{ port2_result.UUID }}"
relation_type: ownership
state: present
provider:
endpoint: localhost:8082
username: admin
password: admin
- name: update link node tor and port 1 relation
skydive_edge:
parent_node: "{{ tor_result.UUID }}"
child_node: "{{ port2_result.UUID }}"
relation_type: layer2
state: upadte
provider:
endpoint: localhost:8082
username: admin
password: admin
- name: Unlink tor and port 2
skydive_edge:
parent_node: "{{ tor_result.UUID }}"
child_node: "{{ port2_result.UUID }}"
relation_type: ownership
state: absent
provider:
endpoint: localhost:8082
username: admin
password: admin
- name: link tor and port 2 via Gremlin expression
skydive_edge:
parent_node: G.V().Has('Name', 'TOR')
child_node: G.V().Has('Name', 'PORT2')
relation_type: ownership
state: present
provider:
endpoint: localhost:8082
username: admin
password: admin
- name: Unlink tor and port 2 via Gremlin expression
skydive_edge:
parent_node: G.V().Has('Name', 'TOR')
child_node: G.V().Has('Name', 'PORT2')
relation_type: ownership
state: absent
provider:
endpoint: localhost:8082
username: admin
password: admin
"""
RETURN = """ # """
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.skydive.api import skydive_edge
def main():
''' Main entry point for module execution
'''
ib_spec = dict(
relation_type=dict(type='str', required=True),
parent_node=dict(type='str', required=True),
child_node=dict(type='str', required=True),
host=dict(type='str', default=""),
metadata=dict(type='dict', default=dict())
)
argument_spec = dict(
provider=dict(required=False),
state=dict(default='present', choices=['present', 'absent'])
)
argument_spec.update(ib_spec)
argument_spec.update(skydive_edge.provider_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
skydive_obj = skydive_edge(module)
result = skydive_obj.run()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -0,0 +1,133 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2019, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: skydive_node
version_added: "2.8"
author:
- "Sumit Jaiswal (@sjaiswal)"
short_description: Module which add nodes to Skydive topology
description:
- This module handles adding node to the Skydive topology.
requirements:
- skydive-client
extends_documentation_fragment: skydive
options:
name:
description:
- To define name for the node.
required: true
node_type:
description:
- To define type for the node.
required: true
host:
description:
- To define host for the node.
required: false
seed:
description:
- used to generate the UUID of the node
default: ""
metadata:
description:
- To define metadata for the node.
required: false
state:
description:
- State of the Skydive Node. If value is I(present) new node
will be created else if it is I(absent) it will be deleted.
default: present
choices:
- present
- update
- absent
"""
EXAMPLES = """
- name: create tor node
skydive_node:
name: TOR
node_type: fabric
seed: TOR1
metadata:
Model: Cisco 5300
state: present
provider:
endpoint: localhost:8082
username: admin
password: admin
- name: update tor node
skydive_node:
name: TOR
node_type: host
seed: TOR1
metadata:
Model: Cisco 3400
state: update
provider:
endpoint: localhost:8082
username: admin
password: admin
- name: Delete the tor node
skydive_node:
name: TOR
node_type: host
seed: TOR1
metadata:
Model: Cisco 3400
state: absent
provider:
endpoint: localhost:8082
username: admin
password: admin
"""
RETURN = """ # """
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.skydive.api import skydive_node
def main():
''' Main entry point for module execution
'''
ib_spec = dict(
name=dict(required=True, ib_req=True),
node_type=dict(required=True, ib_req=True),
host=dict(required=False, ib_req=True, default=""),
seed=dict(required=False, ib_req=True, default=""),
metadata=dict(required=False, ib_req=True, default=dict())
)
argument_spec = dict(
provider=dict(required=False),
state=dict(default='present', choices=['present', 'update', 'absent'])
)
argument_spec.update(ib_spec)
argument_spec.update(skydive_node.provider_spec)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
skydive_obj = skydive_node(module)
result = skydive_obj.run()
module.exit_json(**result)
if __name__ == '__main__':
main()
Loading…
Cancel
Save