From 8a2ff1cde6ca3cb5b5fcb238f26e3c261891526a Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Tue, 19 Feb 2019 16:18:40 +0530 Subject: [PATCH] PR to start support for Skydive integration with Ansible (#50857) * skydive modules Signed-off-by: Sumit Jaiswal * skydive modules Signed-off-by: Sumit Jaiswal * skydive modules Signed-off-by: Sumit Jaiswal * skydive modules Signed-off-by: Sumit Jaiswal * skydive modules Signed-off-by: Sumit Jaiswal * skydive modules Signed-off-by: Sumit Jaiswal * skydive modules Signed-off-by: Sumit Jaiswal * fix shippable Signed-off-by: Sumit Jaiswal * modifying file name Signed-off-by: Sumit Jaiswal * fix shippable Signed-off-by: Sumit Jaiswal * renamed Signed-off-by: Sumit Jaiswal * skydive lookup Signed-off-by: Sumit Jaiswal * change in str Signed-off-by: Sumit Jaiswal * change in str Signed-off-by: Sumit Jaiswal * fix shippable error Signed-off-by: Sumit Jaiswal * renamed file Signed-off-by: Sumit Jaiswal * fix shippable error Signed-off-by: Sumit Jaiswal * change in str Signed-off-by: Sumit Jaiswal * fix shippable error Signed-off-by: Sumit Jaiswal * fix shippable error Signed-off-by: Sumit Jaiswal * fix review comments Signed-off-by: Sumit Jaiswal * fix review comments Signed-off-by: Sumit Jaiswal * deleting file to add to new PR Signed-off-by: Sumit Jaiswal * fix review comments Signed-off-by: Sumit Jaiswal * shippable and doc fix Signed-off-by: Sumit Jaiswal * placing doc at correct path Signed-off-by: Sumit Jaiswal * shippable and doc fix Signed-off-by: Sumit Jaiswal * placing doc at correct path Signed-off-by: Sumit Jaiswal * updated with review and shippable fix Signed-off-by: Sumit Jaiswal * updated with review and shippable fix Signed-off-by: Sumit Jaiswal * updated with review and shippable fix Signed-off-by: Sumit Jaiswal * updated with review and shippable fix Signed-off-by: Sumit Jaiswal * fixing review comment Signed-off-by: Sumit Jaiswal * fixing review comment Signed-off-by: Sumit Jaiswal * fixing review comment Signed-off-by: Sumit Jaiswal * fixing review comment Signed-off-by: Sumit Jaiswal * fixing review comment Signed-off-by: Sumit Jaiswal * fix shippable errors Signed-off-by: Sumit Jaiswal * fix shippable error Signed-off-by: Sumit Jaiswal * fix review comments Signed-off-by: Sumit Jaiswal * fixing shippable errors Signed-off-by: Sumit Jaiswal * fix review comments Signed-off-by: Sumit Jaiswal * review comments Signed-off-by: Sumit Jaiswal * shippable fix Signed-off-by: Sumit Jaiswal * review comment Signed-off-by: Sumit Jaiswal --- .../module_utils/network/skydive/__init__.py | 0 .../module_utils/network/skydive/api.py | 163 ++++++++++++++++ .../modules/network/skydive/__init__.py | 0 .../network/skydive/skydive_capture.py | 180 ++++++++++++++++++ lib/ansible/plugins/doc_fragments/skydive.py | 55 ++++++ lib/ansible/plugins/lookup/skydive.py | 77 ++++++++ 6 files changed, 475 insertions(+) create mode 100644 lib/ansible/module_utils/network/skydive/__init__.py create mode 100644 lib/ansible/module_utils/network/skydive/api.py create mode 100644 lib/ansible/modules/network/skydive/__init__.py create mode 100644 lib/ansible/modules/network/skydive/skydive_capture.py create mode 100644 lib/ansible/plugins/doc_fragments/skydive.py create mode 100644 lib/ansible/plugins/lookup/skydive.py diff --git a/lib/ansible/module_utils/network/skydive/__init__.py b/lib/ansible/module_utils/network/skydive/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/skydive/api.py b/lib/ansible/module_utils/network/skydive/api.py new file mode 100644 index 00000000000..d110a391d0e --- /dev/null +++ b/lib/ansible/module_utils/network/skydive/api.py @@ -0,0 +1,163 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2019 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +import os + +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.rest.client import RESTClient + HAS_SKYDIVE_CLIENT = True +except ImportError: + HAS_SKYDIVE_CLIENT = False + +# defining skydive constants +SKYDIVE_GREMLIN_QUERY = 'G.V().Has' + +SKYDIVE_PROVIDER_SPEC = { + 'endpoint': dict(fallback=(env_fallback, ['SKYDIVE_ENDPOINT'])), + 'username': dict(fallback=(env_fallback, ['SKYDIVE_USERNAME'])), + 'password': dict(fallback=(env_fallback, ['SKYDIVE_PASSWORD']), no_log=True), + 'insecure': dict(type='bool', default=False, fallback=(env_fallback, ['SKYDIVE_INSECURE'])), + 'ssl': dict(type='bool', default=False, fallback=(env_fallback, ['SKYDIVE_SSL'])) +} + + +class skydive_restclient(object): + ''' Base class for implementing Skydive Rest API ''' + provider_spec = {'provider': dict(type='dict', options=SKYDIVE_PROVIDER_SPEC)} + + def __init__(self, **kwargs): + if not HAS_SKYDIVE_CLIENT: + raise Exception('skydive-client is required but does not appear ' + 'to be installed. It can be installed using the ' + 'command `pip install skydive-client`') + + if not set(kwargs.keys()).issubset(SKYDIVE_PROVIDER_SPEC.keys()): + raise Exception('invalid or unsupported keyword argument for skydive_restclient connection.') + for key, value in iteritems(SKYDIVE_PROVIDER_SPEC): + if key not in kwargs: + # apply default values from SKYDIVE_PROVIDER_SPEC since we cannot just + # assume the provider values are coming from AnsibleModule + if 'default' in value: + kwargs[key] = value['default'] + # override any values with env variables unless they were + # explicitly set + env = ('SKYDIVE_%s' % key).upper() + if env in os.environ: + kwargs[key] = os.environ.get(env) + kwargs['scheme'] = "http" + if 'ssl' in kwargs: + if kwargs['ssl']: + kwargs['scheme'] = "https" + if 'insecure' not in kwargs: + kwargs['insecure'] = False + self.restclient_object = RESTClient(kwargs['endpoint'], + scheme=kwargs['scheme'], + insecure=kwargs['insecure'], + username=kwargs['username'], + password=kwargs['password']) + + +class skydive_lookup(skydive_restclient): + provider_spec = {'provider': dict(type='dict', options=SKYDIVE_PROVIDER_SPEC)} + + def __init__(self, provider): + super(skydive_lookup, self).__init__(**provider) + self.query_str = "" + + def lookup_query(self, filter_data): + query_key = filter_data.keys()[0] + self.query_str = filter_data[query_key] + nodes = self.restclient_object.lookup_nodes(self.query_str) + result = [] + for each in nodes: + result.append(each.__dict__) + if len(result) == 0: + raise Exception("Cannot find any entry for the input Gremlin query!") + return result + + +class skydive_flow_capture(skydive_restclient): + ''' Implements Skydive Flow capture modules ''' + def __init__(self, module): + self.module = module + provider = module.params['provider'] + + super(skydive_flow_capture, self).__init__(**provider) + + def run(self, ib_spec): + state = self.module.params['state'] + if state not in ('present', 'absent'): + self.module.fail_json(msg='state must be one of `present`, `absent`, got `%s`' % state) + + result = {'changed': False} + obj_filter = dict([(k, self.module.params[k]) for k, v in iteritems(ib_spec) if v.get('ib_req')]) + + proposed_object = {} + for key in iterkeys(ib_spec): + if self.module.params[key] is not None: + proposed_object[key] = self.module.params[key] + + if obj_filter['query']: + cature_query = obj_filter['query'] + elif obj_filter['interface_name'] and obj_filter['type']: + cature_query = SKYDIVE_GREMLIN_QUERY + "('Name', '{0}', 'Type', '{1}')".format(obj_filter['interface_name'], + obj_filter['type']) + else: + raise self.module.fail_json(msg="Interface name and Type is required if gremlin query is not defined!") + + # to check current object ref for idempotency + captured_list_objs = self.restclient_object.capture_list() + current_ref_uuid = None + for each_capture in captured_list_objs: + if cature_query == each_capture.__dict__['query']: + current_ref_uuid = each_capture.__dict__['uuid'] + break + if state == 'present': + if not current_ref_uuid: + try: + self.restclient_object.capture_create(cature_query, obj_filter['capture_name'], + obj_filter['description'], obj_filter['extra_tcp_metric'], + obj_filter['ip_defrag'], obj_filter['reassemble_tcp'], + obj_filter['layer_key_mode']) + except Exception as e: + self.module.fail_json(msg=to_text(e)) + result['changed'] = True + if state == 'absent': + if current_ref_uuid: + try: + self.restclient_object.capture_delete(current_ref_uuid) + except Exception as e: + self.module.fail_json(msg=to_text(e)) + result['changed'] = True + + return result diff --git a/lib/ansible/modules/network/skydive/__init__.py b/lib/ansible/modules/network/skydive/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/modules/network/skydive/skydive_capture.py b/lib/ansible/modules/network/skydive/skydive_capture.py new file mode 100644 index 00000000000..2e6a2a2f010 --- /dev/null +++ b/lib/ansible/modules/network/skydive/skydive_capture.py @@ -0,0 +1,180 @@ +#!/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_capture +version_added: "2.8" +author: + - "Sumit Jaiswal (@sjaiswal)" +short_description: Module which manages flow capture on interfaces +description: + - This module manages flow capture on interfaces. The Gremlin + expression is continuously evaluated which means that it is + possible to define a capture on nodes that do not exist yet. + - It is useful when you want to start a capture on all OpenvSwitch + whatever the number of Skydive agents you will start. + - While starting the capture, user can specify the capture name, + capture description and capture type optionally. +requirements: + - skydive-client +extends_documentation_fragment: skydive +options: + query: + description: + - It's the complete gremlin query which the users can input, + I(G.V().Has('Name', 'eth0', 'Type', 'device')), to create + the capture. And, if the user directly inputs the gremlin + query then user is not required to input any other module + parameter as gremlin query takes care of creating the flow + capture. + required: false + interface_name: + description: + - To define flow capture interface name. + required: false + type: + description: + - To define flow capture interface type. + required: false + capture_name: + description: + - To define flow capture name. + required: false + default: "" + description: + description: + - Configures a text string to be associated with the instance + of this object. + default: "" + extra_tcp_metric: + description: + - To define flow capture ExtraTCPMetric. + type: bool + default: false + ip_defrag: + description: + - To define flow capture IPDefrag. + type: bool + default: false + reassemble_tcp: + description: + - To define flow capture ReassembleTCP. + type: bool + default: false + layer_key_mode: + description: + - To define flow capture Layer KeyMode. + type: str + default: L2 + state: + description: + - State of the flow capture. If value is I(present) flow capture + will be created else if it is I(absent) it will be deleted. + default: present + choices: + - present + - absent +""" + +EXAMPLES = """ +- name: start a new flow capture directly from gremlin query + skydive_capture: + query: G.V().Has('Name', 'eth0', 'Type', 'device') + state: present + provider: + endpoint: localhost:8082 + username: admin + password: admin + +- name: stop the flow capture directly from gremlin query + skydive_capture: + query: G.V().Has('Name', 'eth0', 'Type', 'device') + state: absent + provider: + endpoint: localhost:8082 + username: admin + password: admin + +- name: start a new flow capture from user's input + skydive_capture: + interface_name: Node1 + type: myhost + capture_name: test_capture + description: test description + extra_tcp_metric: true + ip_defrag: true + reassemble_tcp: true + state: present + provider: + endpoint: localhost:8082 + username: admin + password: admin + +- name: stop the flow capture + skydive_capture: + interface_name: Node1 + type: myhost + capture_name: test_capture + description: test description + extra_tcp_metric: true + ip_defrag: true + reassemble_tcp: true + 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_flow_capture + + +def main(): + ''' Main entry point for module execution + ''' + ib_spec = dict( + query=dict(required=False, ib_req=True), + interface_name=dict(required=False, ib_req=True), + type=dict(required=False, ib_req=True), + capture_name=dict(required=False, default='', ib_req=True), + description=dict(default='', ib_req=True), + extra_tcp_metric=dict(type='bool', required=False, ib_req=True, default=False), + ip_defrag=dict(type='bool', required=False, ib_req=True, default=False), + reassemble_tcp=dict(type='bool', required=False, ib_req=True, default=False), + layer_key_mode=dict(required=False, ib_req=True, default='L2') + ) + + argument_spec = dict( + provider=dict(required=False), + state=dict(default='present', choices=['present', 'absent']) + ) + + argument_spec.update(ib_spec) + argument_spec.update(skydive_flow_capture.provider_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + skydive_obj = skydive_flow_capture(module) + result = skydive_obj.run(ib_spec) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/plugins/doc_fragments/skydive.py b/lib/ansible/plugins/doc_fragments/skydive.py new file mode 100644 index 00000000000..c4abb7ed21a --- /dev/null +++ b/lib/ansible/plugins/doc_fragments/skydive.py @@ -0,0 +1,55 @@ +# +# (c) 2019, Sumit Jaiswal (@sjaiswal) +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = """ +options: + provider: + description: + - A dict object containing connection details. + suboptions: + endpoint: + description: + - Specifies the hostname/address along with the port as C(localhost:8082)for + connecting to the remote instance of SKYDIVE client over the REST API. + required: true + user: + description: + - Configures the username to use to authenticate the connection to + the remote instance of SKYDIVE client. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote instance of SKYDIVE client. + insecure: + description: + - Ignore SSL certification verification. + type: bool + default: false + ssl: + description: + - Specifies the ssl parameter that decides if the connection type shall be + http or https. + type: bool + default: false +notes: + - "This module must be run locally, which can be achieved by specifying C(connection: local)." +""" diff --git a/lib/ansible/plugins/lookup/skydive.py b/lib/ansible/plugins/lookup/skydive.py new file mode 100644 index 00000000000..e0430017157 --- /dev/null +++ b/lib/ansible/plugins/lookup/skydive.py @@ -0,0 +1,77 @@ +# +# Copyright 2018 Red Hat | Ansible +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +DOCUMENTATION = """ +--- +lookup: skydive +version_added: "2.8" +short_description: Query Skydive objects +description: + - Uses the Skydive python REST client to return the queried object from + Skydive network analyzer. +requirements: + - skydive-client +extends_documentation_fragment: skydive +options: + filter: + description: a dict object that is used to filter the return objects +""" + +EXAMPLES = r""" +- name: return skydive metdata if present based on Name + set_fact: + skydive_meta: "{{ lookup('skydive', filter={'query': \"G.V().Has('Name', 'sumit-VirtualBox')\"}) }}" + +- name: return all the skydive metdata having parameter Name + set_fact: + skydive: "{{ lookup('skydive', filter={'query': \"G.V().Has('Name')\"}, + provider={'endpoint': 'localhost:8082', 'username': 'admin', 'password': 'password'}) }}" +""" + +RETURN = """ +_list: + description: + - The list of queried object metadata + returned: always + type: list +""" + + +from ansible.plugins.lookup import LookupBase +from ansible.module_utils.network.skydive.api import skydive_lookup +from ansible.module_utils._text import to_text +from ansible.errors import AnsibleError + + +class LookupModule(LookupBase): + + def run(self, terms, variables=None, **kwargs): + + provider = kwargs.pop('provider', {}) + filter_data = kwargs.pop('filter', {}) + try: + skydive_obj = skydive_lookup(provider) + result = skydive_obj.lookup_query(filter_data) + except Exception as exc: + raise AnsibleError(to_text(exc)) + + return [result]