From d20f526eb7adb27abd8d5c41c1e14c0eb22b0736 Mon Sep 17 00:00:00 2001
From: Monis Khan <mkhan@redhat.com>
Date: Thu, 16 Feb 2017 16:03:01 -0500
Subject: Add SDNValidator Module

Signed-off-by: Monis Khan <mkhan@redhat.com>
---
 roles/lib_openshift/src/ansible/oc_sdnvalidator.py |  24 +
 roles/lib_openshift/src/class/oc_sdnvalidator.py   |  58 +++
 roles/lib_openshift/src/doc/sdnvalidator           |  27 ++
 roles/lib_openshift/src/sources.yml                |  10 +
 .../lib_openshift/src/test/unit/oc_sdnvalidator.py | 481 +++++++++++++++++++++
 5 files changed, 600 insertions(+)
 create mode 100644 roles/lib_openshift/src/ansible/oc_sdnvalidator.py
 create mode 100644 roles/lib_openshift/src/class/oc_sdnvalidator.py
 create mode 100644 roles/lib_openshift/src/doc/sdnvalidator
 create mode 100755 roles/lib_openshift/src/test/unit/oc_sdnvalidator.py

(limited to 'roles/lib_openshift/src')

diff --git a/roles/lib_openshift/src/ansible/oc_sdnvalidator.py b/roles/lib_openshift/src/ansible/oc_sdnvalidator.py
new file mode 100644
index 000000000..e91417d63
--- /dev/null
+++ b/roles/lib_openshift/src/ansible/oc_sdnvalidator.py
@@ -0,0 +1,24 @@
+# pylint: skip-file
+# flake8: noqa
+
+def main():
+    '''
+    ansible oc module for validating OpenShift SDN objects
+    '''
+
+    module = AnsibleModule(
+        argument_spec=dict(
+            kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
+        ),
+        supports_check_mode=False,
+    )
+
+
+    rval = OCSDNValidator.run_ansible(module.params)
+    if 'failed' in rval:
+        module.fail_json(**rval)
+
+    module.exit_json(**rval)
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/lib_openshift/src/class/oc_sdnvalidator.py b/roles/lib_openshift/src/class/oc_sdnvalidator.py
new file mode 100644
index 000000000..da923337b
--- /dev/null
+++ b/roles/lib_openshift/src/class/oc_sdnvalidator.py
@@ -0,0 +1,58 @@
+# pylint: skip-file
+# flake8: noqa
+
+# pylint: disable=too-many-instance-attributes
+class OCSDNValidator(OpenShiftCLI):
+    ''' Class to wrap the oc command line tools '''
+
+    def __init__(self, kubeconfig):
+        ''' Constructor for OCSDNValidator '''
+        # namespace has no meaning for SDN validation, hardcode to 'default'
+        super(OCSDNValidator, self).__init__('default', kubeconfig)
+
+    def get(self, kind, invalid_filter):
+        ''' return SDN information '''
+
+        rval = self._get(kind)
+        if rval['returncode'] != 0:
+            return False, rval, []
+
+        return True, rval, filter(invalid_filter, rval['results'][0]['items'])
+
+    # pylint: disable=too-many-return-statements
+    @staticmethod
+    def run_ansible(params):
+        ''' run the idempotent ansible code
+
+            params comes from the ansible portion of this module
+        '''
+
+        sdnvalidator = OCSDNValidator(params['kubeconfig'])
+        all_invalid = {}
+        failed = False
+
+        checks = (
+            (
+                'hostsubnet',
+                lambda x: x['metadata']['name'] != x['host'],
+                u'hostsubnets where metadata.name != host',
+            ),
+            (
+                'netnamespace',
+                lambda x: x['metadata']['name'] != x['netname'],
+                u'netnamespaces where metadata.name != netname',
+            ),
+        )
+
+        for resource, invalid_filter, invalid_msg in checks:
+            success, rval, invalid = sdnvalidator.get(resource, invalid_filter)
+            if not success:
+                return {'failed': True, 'msg': 'Failed to GET {}.'.format(resource), 'state': 'list', 'results': rval}
+            if invalid:
+                failed = True
+                all_invalid[invalid_msg] = invalid
+
+        if failed:
+            return {'failed': True, 'msg': 'All SDN objects are not valid.', 'state': 'list', 'results': all_invalid}
+
+        return {'msg': 'All SDN objects are valid.'}
diff --git a/roles/lib_openshift/src/doc/sdnvalidator b/roles/lib_openshift/src/doc/sdnvalidator
new file mode 100644
index 000000000..0b1862ed1
--- /dev/null
+++ b/roles/lib_openshift/src/doc/sdnvalidator
@@ -0,0 +1,27 @@
+# flake8: noqa
+# pylint: skip-file
+
+DOCUMENTATION = '''
+---
+module: oc_sdnvalidator
+short_description: Validate SDN objects
+description:
+  - Validate SDN objects
+options:
+  kubeconfig:
+    description:
+    - The path for the kubeconfig file to use for authentication
+    required: false
+    default: /etc/origin/master/admin.kubeconfig
+    aliases: []
+author:
+- "Mo Khan <monis@redhat.com>"
+extends_documentation_fragment: []
+'''
+
+EXAMPLES = '''
+oc_version:
+- name: get oc sdnvalidator
+  sdnvalidator:
+  register: oc_sdnvalidator
+'''
diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml
index fca1f4818..8445c6fac 100644
--- a/roles/lib_openshift/src/sources.yml
+++ b/roles/lib_openshift/src/sources.yml
@@ -166,3 +166,13 @@ oc_version.py:
 - lib/base.py
 - class/oc_version.py
 - ansible/oc_version.py
+
+oc_sdnvalidator.py:
+- doc/generated
+- doc/license
+- lib/import.py
+- doc/sdnvalidator
+- ../../lib_utils/src/class/yedit.py
+- lib/base.py
+- class/oc_sdnvalidator.py
+- ansible/oc_sdnvalidator.py
diff --git a/roles/lib_openshift/src/test/unit/oc_sdnvalidator.py b/roles/lib_openshift/src/test/unit/oc_sdnvalidator.py
new file mode 100755
index 000000000..49e2aadb2
--- /dev/null
+++ b/roles/lib_openshift/src/test/unit/oc_sdnvalidator.py
@@ -0,0 +1,481 @@
+#!/usr/bin/env python2
+'''
+ Unit tests for oc sdnvalidator
+'''
+# To run
+# ./oc_sdnvalidator.py
+#
+# ....
+# ----------------------------------------------------------------------
+# Ran 4 tests in 0.002s
+#
+# OK
+
+import os
+import sys
+import unittest
+import mock
+
+# Removing invalid variable names for tests so that I can
+# keep them brief
+# pylint: disable=invalid-name,no-name-in-module
+# Disable import-error b/c our libraries aren't loaded in jenkins
+# pylint: disable=import-error
+# place class in our python path
+module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
+sys.path.insert(0, module_path)
+from oc_sdnvalidator import OCSDNValidator  # noqa: E402
+
+
+class OCSDNValidatorTest(unittest.TestCase):
+    '''
+     Test class for OCSDNValidator
+    '''
+
+    @mock.patch('oc_sdnvalidator.Utils.create_tmpfile_copy')
+    @mock.patch('oc_sdnvalidator.OCSDNValidator._run')
+    def test_no_data(self, mock_cmd, mock_tmpfile_copy):
+        ''' Testing when both SDN objects are empty '''
+
+        # Arrange
+
+        # run_ansible input parameters
+        params = {
+            'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+        }
+
+        empty = '''{
+    "apiVersion": "v1",
+    "items": [],
+    "kind": "List",
+    "metadata": {},
+    "resourceVersion": "",
+    "selfLink": ""
+}'''
+
+        # Return values of our mocked function call. These get returned once per call.
+        mock_cmd.side_effect = [
+            # First call to mock
+            (0, empty, ''),
+
+            # Second call to mock
+            (0, empty, ''),
+        ]
+
+        mock_tmpfile_copy.side_effect = [
+            '/tmp/mocked_kubeconfig',
+        ]
+
+        # Act
+        results = OCSDNValidator.run_ansible(params)
+
+        # Assert
+        self.assertNotIn('failed', results)
+        self.assertEqual(results['msg'], 'All SDN objects are valid.')
+
+        # Making sure our mock was called as we expected
+        mock_cmd.assert_has_calls([
+            mock.call(['oc', '-n', 'default', 'get', 'hostsubnet', '-o', 'json'], None),
+            mock.call(['oc', '-n', 'default', 'get', 'netnamespace', '-o', 'json'], None),
+        ])
+
+    @mock.patch('oc_sdnvalidator.Utils.create_tmpfile_copy')
+    @mock.patch('oc_sdnvalidator.OCSDNValidator._run')
+    def test_error_code(self, mock_cmd, mock_tmpfile_copy):
+        ''' Testing when both we fail to get SDN objects '''
+
+        # Arrange
+
+        # run_ansible input parameters
+        params = {
+            'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+        }
+
+        # Return values of our mocked function call. These get returned once per call.
+        mock_cmd.side_effect = [
+            # First call to mock
+            (1, '', 'Error.'),
+        ]
+
+        mock_tmpfile_copy.side_effect = [
+            '/tmp/mocked_kubeconfig',
+        ]
+
+        error_results = {
+            'returncode': 1,
+            'stderr': 'Error.',
+            'stdout': '',
+            'cmd': 'oc -n default get hostsubnet -o json',
+            'results': [{}]
+        }
+
+        # Act
+        results = OCSDNValidator.run_ansible(params)
+
+        # Assert
+        self.assertTrue(results['failed'])
+        self.assertEqual(results['msg'], 'Failed to GET hostsubnet.')
+        self.assertEqual(results['state'], 'list')
+        self.assertEqual(results['results'], error_results)
+
+        # Making sure our mock was called as we expected
+        mock_cmd.assert_has_calls([
+            mock.call(['oc', '-n', 'default', 'get', 'hostsubnet', '-o', 'json'], None),
+        ])
+
+    @mock.patch('oc_sdnvalidator.Utils.create_tmpfile_copy')
+    @mock.patch('oc_sdnvalidator.OCSDNValidator._run')
+    def test_valid_both(self, mock_cmd, mock_tmpfile_copy):
+        ''' Testing when both SDN objects are valid '''
+
+        # Arrange
+
+        # run_ansible input parameters
+        params = {
+            'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+        }
+
+        valid_hostsubnet = '''{
+    "apiVersion": "v1",
+    "items": [
+        {
+            "apiVersion": "v1",
+            "host": "bar0",
+            "hostIP": "1.1.1.1",
+            "kind": "HostSubnet",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:47:09Z",
+                "name": "bar0",
+                "namespace": "",
+                "resourceVersion": "986",
+                "selfLink": "/oapi/v1/hostsubnetsbar0",
+                "uid": "528dbb41-f478-11e6-aae0-507b9dac97ff"
+            },
+            "subnet": "1.1.0.0/24"
+        },
+        {
+            "apiVersion": "v1",
+            "host": "bar1",
+            "hostIP": "1.1.1.1",
+            "kind": "HostSubnet",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:47:18Z",
+                "name": "bar1",
+                "namespace": "",
+                "resourceVersion": "988",
+                "selfLink": "/oapi/v1/hostsubnetsbar1",
+                "uid": "57710d84-f478-11e6-aae0-507b9dac97ff"
+            },
+            "subnet": "1.1.0.0/24"
+        },
+        {
+            "apiVersion": "v1",
+            "host": "bar2",
+            "hostIP": "1.1.1.1",
+            "kind": "HostSubnet",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:47:26Z",
+                "name": "bar2",
+                "namespace": "",
+                "resourceVersion": "991",
+                "selfLink": "/oapi/v1/hostsubnetsbar2",
+                "uid": "5c59a28c-f478-11e6-aae0-507b9dac97ff"
+            },
+            "subnet": "1.1.0.0/24"
+        }
+    ],
+    "kind": "List",
+    "metadata": {},
+    "resourceVersion": "",
+    "selfLink": ""
+    }'''
+
+        valid_netnamespace = '''{
+    "apiVersion": "v1",
+    "items": [
+        {
+            "apiVersion": "v1",
+            "kind": "NetNamespace",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:45:16Z",
+                "name": "foo0",
+                "namespace": "",
+                "resourceVersion": "959",
+                "selfLink": "/oapi/v1/netnamespacesfoo0",
+                "uid": "0f1c85b2-f478-11e6-aae0-507b9dac97ff"
+            },
+            "netid": 100,
+            "netname": "foo0"
+        },
+        {
+            "apiVersion": "v1",
+            "kind": "NetNamespace",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:45:26Z",
+                "name": "foo1",
+                "namespace": "",
+                "resourceVersion": "962",
+                "selfLink": "/oapi/v1/netnamespacesfoo1",
+                "uid": "14effa0d-f478-11e6-aae0-507b9dac97ff"
+            },
+            "netid": 100,
+            "netname": "foo1"
+        },
+        {
+            "apiVersion": "v1",
+            "kind": "NetNamespace",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:45:36Z",
+                "name": "foo2",
+                "namespace": "",
+                "resourceVersion": "965",
+                "selfLink": "/oapi/v1/netnamespacesfoo2",
+                "uid": "1aabdf84-f478-11e6-aae0-507b9dac97ff"
+            },
+            "netid": 100,
+            "netname": "foo2"
+        }
+    ],
+    "kind": "List",
+    "metadata": {},
+    "resourceVersion": "",
+    "selfLink": ""
+    }'''
+
+        # Return values of our mocked function call. These get returned once per call.
+        mock_cmd.side_effect = [
+            # First call to mock
+            (0, valid_hostsubnet, ''),
+
+            # Second call to mock
+            (0, valid_netnamespace, ''),
+        ]
+
+        mock_tmpfile_copy.side_effect = [
+            '/tmp/mocked_kubeconfig',
+        ]
+
+        # Act
+        results = OCSDNValidator.run_ansible(params)
+
+        # Assert
+        self.assertNotIn('failed', results)
+        self.assertEqual(results['msg'], 'All SDN objects are valid.')
+
+        # Making sure our mock was called as we expected
+        mock_cmd.assert_has_calls([
+            mock.call(['oc', '-n', 'default', 'get', 'hostsubnet', '-o', 'json'], None),
+            mock.call(['oc', '-n', 'default', 'get', 'netnamespace', '-o', 'json'], None),
+        ])
+
+    @mock.patch('oc_sdnvalidator.Utils.create_tmpfile_copy')
+    @mock.patch('oc_sdnvalidator.OCSDNValidator._run')
+    def test_invalid_both(self, mock_cmd, mock_tmpfile_copy):
+        ''' Testing when both SDN objects are invalid '''
+
+        # Arrange
+
+        # run_ansible input parameters
+        params = {
+            'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+        }
+
+        invalid_hostsubnet = '''{
+    "apiVersion": "v1",
+    "items": [
+        {
+            "apiVersion": "v1",
+            "host": "bar0",
+            "hostIP": "1.1.1.1",
+            "kind": "HostSubnet",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:47:09Z",
+                "name": "bar0",
+                "namespace": "",
+                "resourceVersion": "986",
+                "selfLink": "/oapi/v1/hostsubnetsbar0",
+                "uid": "528dbb41-f478-11e6-aae0-507b9dac97ff"
+            },
+            "subnet": "1.1.0.0/24"
+        },
+        {
+            "apiVersion": "v1",
+            "host": "bar1",
+            "hostIP": "1.1.1.1",
+            "kind": "HostSubnet",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:47:18Z",
+                "name": "bar1",
+                "namespace": "",
+                "resourceVersion": "988",
+                "selfLink": "/oapi/v1/hostsubnetsbar1",
+                "uid": "57710d84-f478-11e6-aae0-507b9dac97ff"
+            },
+            "subnet": "1.1.0.0/24"
+        },
+        {
+            "apiVersion": "v1",
+            "host": "bar2",
+            "hostIP": "1.1.1.1",
+            "kind": "HostSubnet",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:47:26Z",
+                "name": "bar2",
+                "namespace": "",
+                "resourceVersion": "991",
+                "selfLink": "/oapi/v1/hostsubnetsbar2",
+                "uid": "5c59a28c-f478-11e6-aae0-507b9dac97ff"
+            },
+            "subnet": "1.1.0.0/24"
+        },
+        {
+            "apiVersion": "v1",
+            "host": "baz1",
+            "hostIP": "1.1.1.1",
+            "kind": "HostSubnet",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:47:49Z",
+                "name": "baz0",
+                "namespace": "",
+                "resourceVersion": "996",
+                "selfLink": "/oapi/v1/hostsubnetsbaz0",
+                "uid": "69f75f87-f478-11e6-aae0-507b9dac97ff"
+            },
+            "subnet": "1.1.0.0/24"
+        }
+    ],
+    "kind": "List",
+    "metadata": {},
+    "resourceVersion": "",
+    "selfLink": ""
+}'''
+
+        invalid_netnamespace = '''{
+    "apiVersion": "v1",
+    "items": [
+        {
+            "apiVersion": "v1",
+            "kind": "NetNamespace",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:45:52Z",
+                "name": "bar0",
+                "namespace": "",
+                "resourceVersion": "969",
+                "selfLink": "/oapi/v1/netnamespacesbar0",
+                "uid": "245d416e-f478-11e6-aae0-507b9dac97ff"
+            },
+            "netid": 100,
+            "netname": "bar1"
+        },
+        {
+            "apiVersion": "v1",
+            "kind": "NetNamespace",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:45:16Z",
+                "name": "foo0",
+                "namespace": "",
+                "resourceVersion": "959",
+                "selfLink": "/oapi/v1/netnamespacesfoo0",
+                "uid": "0f1c85b2-f478-11e6-aae0-507b9dac97ff"
+            },
+            "netid": 100,
+            "netname": "foo0"
+        },
+        {
+            "apiVersion": "v1",
+            "kind": "NetNamespace",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:45:26Z",
+                "name": "foo1",
+                "namespace": "",
+                "resourceVersion": "962",
+                "selfLink": "/oapi/v1/netnamespacesfoo1",
+                "uid": "14effa0d-f478-11e6-aae0-507b9dac97ff"
+            },
+            "netid": 100,
+            "netname": "foo1"
+        },
+        {
+            "apiVersion": "v1",
+            "kind": "NetNamespace",
+            "metadata": {
+                "creationTimestamp": "2017-02-16T18:45:36Z",
+                "name": "foo2",
+                "namespace": "",
+                "resourceVersion": "965",
+                "selfLink": "/oapi/v1/netnamespacesfoo2",
+                "uid": "1aabdf84-f478-11e6-aae0-507b9dac97ff"
+            },
+            "netid": 100,
+            "netname": "foo2"
+        }
+    ],
+    "kind": "List",
+    "metadata": {},
+    "resourceVersion": "",
+    "selfLink": ""
+}'''
+
+        invalid_results = {
+            'hostsubnets where metadata.name != host': [{
+                'apiVersion': 'v1',
+                'host': 'baz1',
+                'hostIP': '1.1.1.1',
+                'kind': 'HostSubnet',
+                'metadata': {
+                    'creationTimestamp': '2017-02-16T18:47:49Z',
+                    'name': 'baz0',
+                    'namespace': '',
+                    'resourceVersion': '996',
+                    'selfLink': '/oapi/v1/hostsubnetsbaz0',
+                    'uid': '69f75f87-f478-11e6-aae0-507b9dac97ff'
+                },
+                'subnet': '1.1.0.0/24'
+            }],
+            'netnamespaces where metadata.name != netname': [{
+                'apiVersion': 'v1',
+                'kind': 'NetNamespace',
+                'metadata': {
+                    'creationTimestamp': '2017-02-16T18:45:52Z',
+                    'name': 'bar0',
+                    'namespace': '',
+                    'resourceVersion': '969',
+                    'selfLink': '/oapi/v1/netnamespacesbar0',
+                    'uid': '245d416e-f478-11e6-aae0-507b9dac97ff'
+                },
+                'netid': 100,
+                'netname': 'bar1'
+            }],
+        }
+
+        # Return values of our mocked function call. These get returned once per call.
+        mock_cmd.side_effect = [
+            # First call to mock
+            (0, invalid_hostsubnet, ''),
+
+            # Second call to mock
+            (0, invalid_netnamespace, ''),
+        ]
+
+        mock_tmpfile_copy.side_effect = [
+            '/tmp/mocked_kubeconfig',
+        ]
+
+        # Act
+        results = OCSDNValidator.run_ansible(params)
+
+        # Assert
+        self.assertTrue(results['failed'])
+        self.assertEqual(results['msg'], 'All SDN objects are not valid.')
+        self.assertEqual(results['state'], 'list')
+        self.assertEqual(results['results'], invalid_results)
+
+        # Making sure our mock was called as we expected
+        mock_cmd.assert_has_calls([
+            mock.call(['oc', '-n', 'default', 'get', 'hostsubnet', '-o', 'json'], None),
+            mock.call(['oc', '-n', 'default', 'get', 'netnamespace', '-o', 'json'], None),
+        ])
+
+
+if __name__ == '__main__':
+    unittest.main()
-- 
cgit v1.2.3