From 0c3c694cb4039132ceea71f169bc58f46d51227e Mon Sep 17 00:00:00 2001
From: Kenny Woodson <kwoodson@redhat.com>
Date: Fri, 17 Mar 2017 11:23:46 -0400
Subject: Adding a pvc create test case.

---
 roles/lib_openshift/src/ansible/oc_pvc.py        |  35 ++++
 roles/lib_openshift/src/class/oc_pvc.py          | 167 +++++++++++++++++++
 roles/lib_openshift/src/doc/pvc                  |  76 +++++++++
 roles/lib_openshift/src/lib/pvc.py               | 167 +++++++++++++++++++
 roles/lib_openshift/src/sources.yml              |  11 ++
 roles/lib_openshift/src/test/unit/test_oc_pvc.py | 203 +++++++++++++++++++++++
 6 files changed, 659 insertions(+)
 create mode 100644 roles/lib_openshift/src/ansible/oc_pvc.py
 create mode 100644 roles/lib_openshift/src/class/oc_pvc.py
 create mode 100644 roles/lib_openshift/src/doc/pvc
 create mode 100644 roles/lib_openshift/src/lib/pvc.py
 create mode 100755 roles/lib_openshift/src/test/unit/test_oc_pvc.py

(limited to 'roles/lib_openshift/src')

diff --git a/roles/lib_openshift/src/ansible/oc_pvc.py b/roles/lib_openshift/src/ansible/oc_pvc.py
new file mode 100644
index 000000000..a5181e281
--- /dev/null
+++ b/roles/lib_openshift/src/ansible/oc_pvc.py
@@ -0,0 +1,35 @@
+# pylint: skip-file
+# flake8: noqa
+
+#pylint: disable=too-many-branches
+def main():
+    '''
+    ansible oc module for pvc
+    '''
+
+    module = AnsibleModule(
+        argument_spec=dict(
+            kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
+            state=dict(default='present', type='str',
+                       choices=['present', 'absent', 'list']),
+            debug=dict(default=False, type='bool'),
+            name=dict(default=None, required=True, type='str'),
+            namespace=dict(default=None, required=True, type='str'),
+            volume_capacity=dict(default='1G', type='str'),
+            access_modes=dict(default='ReadWriteOnce',
+                              choices=['ReadWriteOnce', 'ReadOnlyMany', 'ReadWriteMany'],
+                              type='str'),
+        ),
+        supports_check_mode=True,
+    )
+
+    rval = OCPVC.run_ansible(module.params, module.check_mode)
+
+    if 'failed' in rval:
+        module.fail_json(**rval)
+
+    return module.exit_json(**rval)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/lib_openshift/src/class/oc_pvc.py b/roles/lib_openshift/src/class/oc_pvc.py
new file mode 100644
index 000000000..c73abc47c
--- /dev/null
+++ b/roles/lib_openshift/src/class/oc_pvc.py
@@ -0,0 +1,167 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+# pylint: disable=too-many-instance-attributes
+class OCPVC(OpenShiftCLI):
+    ''' Class to wrap the oc command line tools '''
+    kind = 'pvc'
+
+    # pylint allows 5
+    # pylint: disable=too-many-arguments
+    def __init__(self,
+                 config,
+                 verbose=False):
+        ''' Constructor for OCVolume '''
+        super(OCPVC, self).__init__(config.namespace, config.kubeconfig)
+        self.config = config
+        self.namespace = config.namespace
+        self._pvc = None
+
+    @property
+    def pvc(self):
+        ''' property function pvc'''
+        if not self._pvc:
+            self.get()
+        return self._pvc
+
+    @pvc.setter
+    def pvc(self, data):
+        ''' setter function for yedit var '''
+        self._pvc = data
+
+    def bound(self):
+        '''return whether the pvc is bound'''
+        if self.pvc.get_volume_name():
+            return True
+
+        return False
+
+    def exists(self):
+        ''' return whether a pvc exists '''
+        if self.pvc:
+            return True
+
+        return False
+
+    def get(self):
+        '''return pvc information '''
+        result = self._get(self.kind, self.config.name)
+        if result['returncode'] == 0:
+            self.pvc = PersistentVolumeClaim(content=result['results'][0])
+        elif '\"%s\" not found' % self.config.name in result['stderr']:
+            result['returncode'] = 0
+            result['results'] = [{}]
+
+        return result
+
+    def delete(self):
+        '''delete the object'''
+        return self._delete(self.kind, self.config.name)
+
+    def create(self):
+        '''create the object'''
+        return self._create_from_content(self.config.name, self.config.data)
+
+    def update(self):
+        '''update the object'''
+        # need to update the tls information and the service name
+        return self._replace_content(self.kind, self.config.name, self.config.data)
+
+    def needs_update(self):
+        ''' verify an update is needed '''
+        if self.pvc.get_volume_name() or self.pvc.is_bound():
+            return False
+
+        skip = []
+        return not Utils.check_def_equal(self.config.data, self.pvc.yaml_dict, skip_keys=skip, debug=True)
+
+    # pylint: disable=too-many-branches,too-many-return-statements
+    @staticmethod
+    def run_ansible(params, check_mode):
+        '''run the idempotent ansible code'''
+        pconfig = PersistentVolumeClaimConfig(params['name'],
+                                              params['namespace'],
+                                              params['kubeconfig'],
+                                              params['access_modes'],
+                                              params['volume_capacity'],
+                                             )
+        oc_pvc = OCPVC(pconfig, verbose=params['debug'])
+
+        state = params['state']
+
+        api_rval = oc_pvc.get()
+        if api_rval['returncode'] != 0:
+            return {'failed': True, 'msg': api_rval}
+
+        #####
+        # Get
+        #####
+        if state == 'list':
+            return {'changed': False, 'results': api_rval['results'], 'state': state}
+
+        ########
+        # Delete
+        ########
+        if state == 'absent':
+            if oc_pvc.exists():
+
+                if check_mode:
+                    return {'changed': False, 'msg': 'CHECK_MODE: Would have performed a delete.'}
+
+                api_rval = oc_pvc.delete()
+
+                if api_rval['returncode'] != 0:
+                    return {'failed': True, 'msg': api_rval}
+
+                return {'changed': True, 'results': api_rval, 'state': state}
+
+            return {'changed': False, 'state': state}
+
+        if state == 'present':
+            ########
+            # Create
+            ########
+            if not oc_pvc.exists():
+
+                if check_mode:
+                    return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'}
+
+                # Create it here
+                api_rval = oc_pvc.create()
+
+                if api_rval['returncode'] != 0:
+                    return {'failed': True, 'msg': api_rval}
+
+                # return the created object
+                api_rval = oc_pvc.get()
+
+                if api_rval['returncode'] != 0:
+                    return {'failed': True, 'msg': api_rval}
+
+                return {'changed': True, 'results': api_rval, 'state': state}
+
+            ########
+            # Update
+            ########
+            if oc_pvc.pvc.is_bound() or oc_pvc.pvc.get_volume_name():
+                api_rval['msg'] = '##### - This volume is currently bound.  Will not update - ####'
+                return {'changed': False, 'results': api_rval, 'state': state}
+
+            if oc_pvc.needs_update():
+                api_rval = oc_pvc.update()
+
+                if api_rval['returncode'] != 0:
+                    return {'failed': True, 'msg': api_rval}
+
+                # return the created object
+                api_rval = oc_pvc.get()
+
+                if api_rval['returncode'] != 0:
+                    return {'failed': True, 'msg': api_rval}
+
+                return {'changed': True, 'results': api_rval, 'state': state}
+
+            return {'changed': False, 'results': api_rval, 'state': state}
+
+        return {'failed': True, 'msg': 'Unknown state passed. {}'.format(state)}
diff --git a/roles/lib_openshift/src/doc/pvc b/roles/lib_openshift/src/doc/pvc
new file mode 100644
index 000000000..9240f2a0f
--- /dev/null
+++ b/roles/lib_openshift/src/doc/pvc
@@ -0,0 +1,76 @@
+# flake8: noqa
+# pylint: skip-file
+
+DOCUMENTATION = '''
+---
+module: oc_pvc
+short_description: Modify, and idempotently manage openshift persistent volume claims
+description:
+  - Modify openshift persistent volume claims programmatically.
+options:
+  state:
+    description:
+    - Supported states, present, absent, list
+    - present - will ensure object is created or updated to the value specified
+    - list - will return a pvc
+    - absent - will remove a pvc
+    required: False
+    default: present
+    choices: ["present", 'absent', 'list']
+    aliases: []
+  kubeconfig:
+    description:
+    - The path for the kubeconfig file to use for authentication
+    required: false
+    default: /etc/origin/master/admin.kubeconfig
+    aliases: []
+  debug:
+    description:
+    - Turn on debug output.
+    required: false
+    default: False
+    aliases: []
+  name:
+    description:
+    - Name of the object that is being queried.
+    required: false
+    default: None
+    aliases: []
+  namespace:
+    description:
+    - The namespace where the object lives.
+    required: false
+    default: str
+    aliases: []
+  volume_capacity:
+    description:
+    - The requested volume capacity
+    required: False
+    default: 1G
+    aliases: []
+  access_modes:
+    description:
+    - The access modes allowed for the pvc
+    - Expects a list
+    required: False
+    default: ReadWriteOnce
+    choices:
+    - ReadWriteOnce
+    - ReadOnlyMany
+    - ReadWriteMany
+    aliases: []
+author:
+- "Kenny Woodson <kwoodson@redhat.com>"
+extends_documentation_fragment: []
+'''
+
+EXAMPLES = '''
+- name: create a pvc
+  oc_pvc:
+    namespace: awesomeapp
+    name: dbstorage
+    access_modes:
+    - ReadWriteOnce
+    volume_capacity: 5G
+  register: pvcout
+'''
diff --git a/roles/lib_openshift/src/lib/pvc.py b/roles/lib_openshift/src/lib/pvc.py
new file mode 100644
index 000000000..929b50990
--- /dev/null
+++ b/roles/lib_openshift/src/lib/pvc.py
@@ -0,0 +1,167 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+# pylint: disable=too-many-instance-attributes
+class PersistentVolumeClaimConfig(object):
+    ''' Handle pvc options '''
+    # pylint: disable=too-many-arguments
+    def __init__(self,
+                 sname,
+                 namespace,
+                 kubeconfig,
+                 access_modes=None,
+                 vol_capacity='1G'):
+        ''' constructor for handling pvc options '''
+        self.kubeconfig = kubeconfig
+        self.name = sname
+        self.namespace = namespace
+        self.access_modes = access_modes
+        self.vol_capacity = vol_capacity
+        self.data = {}
+
+        self.create_dict()
+
+    def create_dict(self):
+        ''' return a service as a dict '''
+        # version
+        self.data['apiVersion'] = 'v1'
+        # kind
+        self.data['kind'] = 'PersistentVolumeClaim'
+        # metadata
+        self.data['metadata'] = {}
+        self.data['metadata']['name'] = self.name
+        # spec
+        self.data['spec'] = {}
+        self.data['spec']['accessModes'] = ['ReadWriteOnce']
+        if self.access_modes:
+            self.data['spec']['accessModes'] = self.access_modes
+
+        # storage capacity
+        self.data['spec']['resources'] = {}
+        self.data['spec']['resources']['requests'] = {}
+        self.data['spec']['resources']['requests']['storage'] = self.vol_capacity
+
+
+# pylint: disable=too-many-instance-attributes,too-many-public-methods
+class PersistentVolumeClaim(Yedit):
+    ''' Class to wrap the oc command line tools '''
+    access_modes_path = "spec.accessModes"
+    volume_capacity_path = "spec.requests.storage"
+    volume_name_path = "spec.volumeName"
+    bound_path = "status.phase"
+    kind = 'PersistentVolumeClaim'
+
+    def __init__(self, content):
+        '''RoleBinding constructor'''
+        super(PersistentVolumeClaim, self).__init__(content=content)
+        self._access_modes = None
+        self._volume_capacity = None
+        self._volume_name = None
+
+    @property
+    def volume_name(self):
+        ''' volume_name property '''
+        if self._volume_name is None:
+            self._volume_name = self.get_volume_name()
+        return self._volume_name
+
+    @volume_name.setter
+    def volume_name(self, data):
+        ''' volume_name property setter'''
+        self._volume_name = data
+
+    @property
+    def access_modes(self):
+        ''' access_modes property '''
+        if self._access_modes is None:
+            self._access_modes = self.get_access_modes()
+            if not isinstance(self._access_modes, list):
+                self._access_modes = list(self._access_modes)
+
+        return self._access_modes
+
+    @access_modes.setter
+    def access_modes(self, data):
+        ''' access_modes property setter'''
+        if not isinstance(data, list):
+            data = list(data)
+
+        self._access_modes = data
+
+    @property
+    def volume_capacity(self):
+        ''' volume_capacity property '''
+        if self._volume_capacity is None:
+            self._volume_capacity = self.get_volume_capacity()
+        return self._volume_capacity
+
+    @volume_capacity.setter
+    def volume_capacity(self, data):
+        ''' volume_capacity property setter'''
+        self._volume_capacity = data
+
+    def get_access_modes(self):
+        '''get access_modes'''
+        return self.get(PersistentVolumeClaim.access_modes_path) or []
+
+    def get_volume_capacity(self):
+        '''get volume_capacity'''
+        return self.get(PersistentVolumeClaim.volume_capacity_path) or []
+
+    def get_volume_name(self):
+        '''get volume_name'''
+        return self.get(PersistentVolumeClaim.volume_name_path) or []
+
+    def is_bound(self):
+        '''return whether volume is bound'''
+        return self.get(PersistentVolumeClaim.bound_path) or []
+
+    #### ADD #####
+    def add_access_mode(self, inc_mode):
+        ''' add an access_mode'''
+        if self.access_modes:
+            self.access_modes.append(inc_mode)
+        else:
+            self.put(PersistentVolumeClaim.access_modes_path, [inc_mode])
+
+        return True
+
+    #### /ADD #####
+
+    #### Remove #####
+    def remove_access_mode(self, inc_mode):
+        ''' remove an access_mode'''
+        try:
+            self.access_modes.remove(inc_mode)
+        except ValueError as _:
+            return False
+
+        return True
+
+    #### /REMOVE #####
+
+    #### UPDATE #####
+    def update_access_mode(self, inc_mode):
+        ''' update an access_mode'''
+        try:
+            index = self.access_modes.index(inc_mode)
+        except ValueError as _:
+            return self.add_access_mode(inc_mode)
+
+        self.access_modes[index] = inc_mode
+
+        return True
+
+    #### /UPDATE #####
+
+    #### FIND ####
+    def find_access_mode(self, inc_mode):
+        ''' find a user '''
+        index = None
+        try:
+            index = self.access_modes.index(inc_mode)
+        except ValueError as _:
+            return index
+
+        return index
diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml
index 91ee86750..42b85c2b4 100644
--- a/roles/lib_openshift/src/sources.yml
+++ b/roles/lib_openshift/src/sources.yml
@@ -152,6 +152,17 @@ oc_project.py:
 - class/oc_project.py
 - ansible/oc_project.py
 
+oc_pvc.py:
+- doc/generated
+- doc/license
+- lib/import.py
+- doc/pvc
+- ../../lib_utils/src/class/yedit.py
+- lib/base.py
+- lib/pvc.py
+- class/oc_pvc.py
+- ansible/oc_pvc.py
+
 oc_route.py:
 - doc/generated
 - doc/license
diff --git a/roles/lib_openshift/src/test/unit/test_oc_pvc.py b/roles/lib_openshift/src/test/unit/test_oc_pvc.py
new file mode 100755
index 000000000..19747f531
--- /dev/null
+++ b/roles/lib_openshift/src/test/unit/test_oc_pvc.py
@@ -0,0 +1,203 @@
+'''
+ Unit tests for oc pvc
+'''
+
+import copy
+import os
+import six
+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,wrong-import-position
+# 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_pvc import OCPVC, locate_oc_binary  # noqa: E402
+
+
+class OCPVCTest(unittest.TestCase):
+    '''
+     Test class for OCPVC
+    '''
+    params = {'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+              'state': 'present',
+              'debug': False,
+              'name': 'mypvc',
+              'namespace': 'test',
+              'volume_capacity': '1G',
+              'access_modes': 'ReadWriteMany'}
+
+    @mock.patch('oc_pvc.Utils.create_tmpfile_copy')
+    @mock.patch('oc_pvc.OCPVC._run')
+    def test_create_pvc(self, mock_run, mock_tmpfile_copy):
+        ''' Testing a pvc create '''
+        params = copy.deepcopy(OCPVCTest.params)
+
+        pvc = '''{"kind": "PersistentVolumeClaim",
+               "apiVersion": "v1",
+               "metadata": {
+                   "name": "mypvc",
+                   "namespace": "test",
+                   "selfLink": "/api/v1/namespaces/test/persistentvolumeclaims/mypvc",
+                   "uid": "77597898-d8d8-11e6-aea5-0e3c0c633889",
+                   "resourceVersion": "126510787",
+                   "creationTimestamp": "2017-01-12T15:04:50Z",
+                   "labels": {
+                       "mypvc": "database"
+                   },
+                   "annotations": {
+                       "pv.kubernetes.io/bind-completed": "yes",
+                       "pv.kubernetes.io/bound-by-controller": "yes",
+                       "v1.2-volume.experimental.kubernetes.io/provisioning-required": "volume.experimental.kubernetes.io/provisioning-completed"
+                   }
+               },
+               "spec": {
+                   "accessModes": [
+                       "ReadWriteOnce"
+                   ],
+                   "resources": {
+                       "requests": {
+                           "storage": "1Gi"
+                       }
+                   },
+                   "volumeName": "pv-aws-ow5vl"
+               },
+               "status": {
+                  "phase": "Bound",
+                   "accessModes": [
+                       "ReadWriteOnce"
+                   ],
+                    "capacity": {
+                      "storage": "1Gi"
+                    }
+               }
+              }'''
+
+        mock_run.side_effect = [
+            (1, '', 'Error from server: persistentvolumeclaims "mypvc" not found'),
+            (1, '', 'Error from server: persistentvolumeclaims "mypvc" not found'),
+            (0, '', ''),
+            (0, pvc, ''),
+        ]
+
+        mock_tmpfile_copy.side_effect = [
+            '/tmp/mocked_kubeconfig',
+        ]
+
+        results = OCPVC.run_ansible(params, False)
+
+        self.assertTrue(results['changed'])
+        self.assertEqual(results['results']['results'][0]['metadata']['name'], 'mypvc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
-- 
cgit v1.2.3