From d20f526eb7adb27abd8d5c41c1e14c0eb22b0736 Mon Sep 17 00:00:00 2001 From: Monis Khan Date: Thu, 16 Feb 2017 16:03:01 -0500 Subject: Add SDNValidator Module Signed-off-by: Monis Khan --- 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 " +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