فهرست منبع

Merge pull request #3692 from kwoodson/oc_volume

Merged by openshift-bot
OpenShift Bot 8 سال پیش
والد
کامیت
3d1f4de1f4

+ 6 - 1
roles/lib_openshift/library/oc_adm_registry.py

@@ -2061,7 +2061,7 @@ class Service(Yedit):
 # -*- -*- -*- Begin included fragment: lib/volume.py -*- -*- -*-
 
 class Volume(object):
-    ''' Class to model an openshift volume object'''
+    ''' Class to represent an openshift volume object'''
     volume_mounts_path = {"pod": "spec.containers[0].volumeMounts",
                           "dc":  "spec.template.spec.containers[0].volumeMounts",
                           "rc":  "spec.template.spec.containers[0].volumeMounts",
@@ -2093,6 +2093,11 @@ class Volume(object):
         elif volume_type == 'hostpath':
             volume['hostPath'] = {}
             volume['hostPath']['path'] = volume_info['path']
+        elif volume_type == 'configmap':
+            volume['configMap'] = {}
+            volume['configMap']['name'] = volume_info['configmap_name']
+            volume_mount = {'mountPath': volume_info['path'],
+                            'name': volume_info['name']}
 
         return (volume, volume_mount)
 

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2024 - 0
roles/lib_openshift/library/oc_volume.py


+ 41 - 0
roles/lib_openshift/src/ansible/oc_volume.py

@@ -0,0 +1,41 @@
+# pylint: skip-file
+# flake8: noqa
+
+def main():
+    '''
+    ansible oc module for volumes
+    '''
+
+    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'),
+            kind=dict(default='dc', choices=['dc', 'rc', 'pods'], type='str'),
+            namespace=dict(default='default', type='str'),
+            vol_name=dict(default=None, type='str'),
+            name=dict(default=None, type='str'),
+            mount_type=dict(default=None,
+                            choices=['emptydir', 'hostpath', 'secret', 'pvc', 'configmap'],
+                            type='str'),
+            mount_path=dict(default=None, type='str'),
+            # secrets require a name
+            secret_name=dict(default=None, type='str'),
+            # pvc requires a size
+            claim_size=dict(default=None, type='str'),
+            claim_name=dict(default=None, type='str'),
+            # configmap requires a name
+            configmap_name=dict(default=None, type='str'),
+        ),
+        supports_check_mode=True,
+    )
+    rval = OCVolume.run_ansible(module.params, module.check_mode)
+    if 'failed' in rval:
+        module.fail_json(**rval)
+
+    module.exit_json(**rval)
+
+
+if __name__ == '__main__':
+    main()

+ 195 - 0
roles/lib_openshift/src/class/oc_volume.py

@@ -0,0 +1,195 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+# pylint: disable=too-many-instance-attributes
+class OCVolume(OpenShiftCLI):
+    ''' Class to wrap the oc command line tools '''
+    volume_mounts_path = {"pod": "spec.containers[0].volumeMounts",
+                          "dc":  "spec.template.spec.containers[0].volumeMounts",
+                          "rc":  "spec.template.spec.containers[0].volumeMounts",
+                         }
+    volumes_path = {"pod": "spec.volumes",
+                    "dc":  "spec.template.spec.volumes",
+                    "rc":  "spec.template.spec.volumes",
+                   }
+
+    # pylint allows 5
+    # pylint: disable=too-many-arguments
+    def __init__(self,
+                 kind,
+                 resource_name,
+                 namespace,
+                 vol_name,
+                 mount_path,
+                 mount_type,
+                 secret_name,
+                 claim_size,
+                 claim_name,
+                 configmap_name,
+                 kubeconfig='/etc/origin/master/admin.kubeconfig',
+                 verbose=False):
+        ''' Constructor for OCVolume '''
+        super(OCVolume, self).__init__(namespace, kubeconfig)
+        self.kind = kind
+        self.volume_info = {'name': vol_name,
+                            'secret_name': secret_name,
+                            'path': mount_path,
+                            'type': mount_type,
+                            'claimSize': claim_size,
+                            'claimName': claim_name,
+                            'configmap_name': configmap_name}
+        self.volume, self.volume_mount = Volume.create_volume_structure(self.volume_info)
+        self.name = resource_name
+        self.namespace = namespace
+        self.kubeconfig = kubeconfig
+        self.verbose = verbose
+        self._resource = None
+
+    @property
+    def resource(self):
+        ''' property function for resource var '''
+        if not self._resource:
+            self.get()
+        return self._resource
+
+    @resource.setter
+    def resource(self, data):
+        ''' setter function for resource var '''
+        self._resource = data
+
+    def exists(self):
+        ''' return whether a volume exists '''
+        volume_mount_found = False
+        volume_found = self.resource.exists_volume(self.volume)
+        if not self.volume_mount and volume_found:
+            return True
+
+        if self.volume_mount:
+            volume_mount_found = self.resource.exists_volume_mount(self.volume_mount)
+
+        if volume_found and self.volume_mount and volume_mount_found:
+            return True
+
+        return False
+
+    def get(self):
+        '''return volume information '''
+        vol = self._get(self.kind, self.name)
+        if vol['returncode'] == 0:
+            if self.kind == 'dc':
+                self.resource = DeploymentConfig(content=vol['results'][0])
+                vol['results'] = self.resource.get_volumes()
+
+        return vol
+
+    def delete(self):
+        '''remove a volume'''
+        self.resource.delete_volume_by_name(self.volume)
+        return self._replace_content(self.kind, self.name, self.resource.yaml_dict)
+
+    def put(self):
+        '''place volume into dc '''
+        self.resource.update_volume(self.volume)
+        self.resource.get_volumes()
+        self.resource.update_volume_mount(self.volume_mount)
+        return self._replace_content(self.kind, self.name, self.resource.yaml_dict)
+
+    def needs_update(self):
+        ''' verify an update is needed '''
+        return self.resource.needs_update_volume(self.volume, self.volume_mount)
+
+    # pylint: disable=too-many-branches,too-many-return-statements
+    @staticmethod
+    def run_ansible(params, check_mode=False):
+        '''run the idempotent ansible code'''
+        oc_volume = OCVolume(params['kind'],
+                             params['name'],
+                             params['namespace'],
+                             params['vol_name'],
+                             params['mount_path'],
+                             params['mount_type'],
+                             # secrets
+                             params['secret_name'],
+                             # pvc
+                             params['claim_size'],
+                             params['claim_name'],
+                             # configmap
+                             params['configmap_name'],
+                             kubeconfig=params['kubeconfig'],
+                             verbose=params['debug'])
+
+        state = params['state']
+
+        api_rval = oc_volume.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_volume.exists():
+
+                if check_mode:
+                    return {'changed': False, 'msg': 'CHECK_MODE: Would have performed a delete.'}
+
+                api_rval = oc_volume.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_volume.exists():
+
+                if check_mode:
+                    exit_json(changed=False, msg='Would have performed a create.')
+
+                # Create it here
+                api_rval = oc_volume.put()
+
+                if api_rval['returncode'] != 0:
+                    return {'failed': True, 'msg': api_rval}
+
+                # return the created object
+                api_rval = oc_volume.get()
+
+                if api_rval['returncode'] != 0:
+                    return {'failed': True, 'msg': api_rval}
+
+                return {'changed': True, 'results': api_rval, 'state': state}
+
+            ########
+            # Update
+            ########
+            if oc_volume.needs_update():
+                api_rval = oc_volume.put()
+
+                if api_rval['returncode'] != 0:
+                    return {'failed': True, 'msg': api_rval}
+
+                # return the created object
+                api_rval = oc_volume.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)}

+ 105 - 0
roles/lib_openshift/src/doc/volume

@@ -0,0 +1,105 @@
+# flake8: noqa
+# pylint: skip-file
+
+DOCUMENTATION = '''
+---
+module: oc_volume
+short_description: Create, modify, and idempotently manage openshift volumes.
+description:
+  - Modify openshift volumes programmatically.
+options:
+  state:
+    description:
+    - State controls the action that will be taken with resource
+    - 'present' will create or update and object to the desired state
+    - 'absent' will ensure volumes are removed
+    - 'list' will read the volumes
+    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: []
+  namespace:
+    description:
+    - The name of the namespace where the object lives
+    required: false
+    default: default
+    aliases: []
+  kind:
+    description:
+    - The kind of object that can be managed.
+    default: dc
+    choices:
+    - dc
+    - rc
+    - pods
+    aliases: []
+  mount_type:
+    description:
+    - The type of volume to be used
+    required: false
+    default: None
+    choices:
+    - emptydir
+    - hostpath
+    - secret
+    - pvc
+    - configmap
+    aliases: []
+  mount_path:
+    description:
+    - The path to where the mount will be attached
+    required: false
+    default: None
+    aliases: []
+  secret_name:
+    description:
+    - The name of the secret. Used when mount_type is secret.
+    required: false
+    default: None
+    aliases: []
+  claim_size:
+    description:
+    - The size in GB of the pv claim. e.g. 100G
+    required: false
+    default: None
+    aliases: []
+  claim_name:
+    description:
+    - The name of the pv claim
+    required: false
+    default: None
+    aliases: []
+  configmap_name:
+    description:
+    - The name of the configmap
+    required: false
+    default: None
+    aliases: []
+author:
+- "Kenny Woodson <kwoodson@redhat.com>"
+extends_documentation_fragment: []
+'''
+
+EXAMPLES = '''
+- name: attach storage volumes to deploymentconfig
+  oc_volume:
+    namespace: logging
+    kind: dc
+    name: name_of_the_dc
+    mount_type: pvc
+    claim_name: loggingclaim
+    claim_size: 100G
+    vol_name: logging-storage
+  run_once: true
+'''

+ 6 - 1
roles/lib_openshift/src/lib/volume.py

@@ -2,7 +2,7 @@
 # flake8: noqa
 
 class Volume(object):
-    ''' Class to model an openshift volume object'''
+    ''' Class to represent an openshift volume object'''
     volume_mounts_path = {"pod": "spec.containers[0].volumeMounts",
                           "dc":  "spec.template.spec.containers[0].volumeMounts",
                           "rc":  "spec.template.spec.containers[0].volumeMounts",
@@ -34,5 +34,10 @@ class Volume(object):
         elif volume_type == 'hostpath':
             volume['hostPath'] = {}
             volume['hostPath']['path'] = volume_info['path']
+        elif volume_type == 'configmap':
+            volume['configMap'] = {}
+            volume['configMap']['name'] = volume_info['configmap_name']
+            volume_mount = {'mountPath': volume_info['path'],
+                            'name': volume_info['name']}
 
         return (volume, volume_mount)

+ 12 - 0
roles/lib_openshift/src/sources.yml

@@ -229,6 +229,18 @@ oc_version.py:
 - class/oc_version.py
 - ansible/oc_version.py
 
+oc_volume.py:
+- doc/generated
+- doc/license
+- lib/import.py
+- doc/volume
+- ../../lib_utils/src/class/yedit.py
+- lib/base.py
+- lib/deploymentconfig.py
+- lib/volume.py
+- class/oc_volume.py
+- ansible/oc_volume.py
+
 oc_objectvalidator.py:
 - doc/generated
 - doc/license

+ 633 - 0
roles/lib_openshift/src/test/unit/test_oc_volume.py

@@ -0,0 +1,633 @@
+'''
+ Unit tests for oc volume
+'''
+
+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
+# 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_volume import OCVolume, locate_oc_binary  # noqa: E402
+
+
+class OCVolumeTest(unittest.TestCase):
+    '''
+     Test class for OCVolume
+    '''
+    params = {'name': 'oso-rhel7-zagg-web',
+              'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+              'namespace': 'test',
+              'labels': None,
+              'state': 'present',
+              'kind': 'dc',
+              'mount_path': None,
+              'secret_name': None,
+              'mount_type': 'pvc',
+              'claim_name': 'testclaim',
+              'claim_size': '1G',
+              'configmap_name': None,
+              'vol_name': 'test-volume',
+              'debug': False}
+
+    @mock.patch('oc_volume.Utils.create_tmpfile_copy')
+    @mock.patch('oc_volume.OCVolume._run')
+    def test_create_pvc(self, mock_cmd, mock_tmpfile_copy):
+        ''' Testing a label list '''
+        params = copy.deepcopy(OCVolumeTest.params)
+
+        dc = '''{
+                "kind": "DeploymentConfig",
+                "apiVersion": "v1",
+                "metadata": {
+                    "name": "oso-rhel7-zagg-web",
+                    "namespace": "new-monitoring",
+                    "selfLink": "/oapi/v1/namespaces/new-monitoring/deploymentconfigs/oso-rhel7-zagg-web",
+                    "uid": "f56e9dd2-7c13-11e6-b046-0e8844de0587",
+                    "resourceVersion": "137095771",
+                    "generation": 4,
+                    "creationTimestamp": "2016-09-16T13:46:24Z",
+                    "labels": {
+                        "app": "oso-rhel7-ops-base",
+                        "name": "oso-rhel7-zagg-web"
+                    },
+                    "annotations": {
+                        "openshift.io/generated-by": "OpenShiftNewApp"
+                    }
+                },
+                "spec": {
+                    "strategy": {
+                        "type": "Rolling",
+                        "rollingParams": {
+                            "updatePeriodSeconds": 1,
+                            "intervalSeconds": 1,
+                            "timeoutSeconds": 600,
+                            "maxUnavailable": "25%",
+                            "maxSurge": "25%"
+                        },
+                        "resources": {}
+                    },
+                    "triggers": [
+                        {
+                            "type": "ConfigChange"
+                        },
+                        {
+                            "type": "ImageChange",
+                            "imageChangeParams": {
+                                "automatic": true,
+                                "containerNames": [
+                                    "oso-rhel7-zagg-web"
+                                ],
+                                "from": {
+                                    "kind": "ImageStreamTag",
+                                    "namespace": "new-monitoring",
+                                    "name": "oso-rhel7-zagg-web:latest"
+                                },
+                                "lastTriggeredImage": "notused"
+                            }
+                        }
+                    ],
+                    "replicas": 10,
+                    "test": false,
+                    "selector": {
+                        "deploymentconfig": "oso-rhel7-zagg-web"
+                    },
+                    "template": {
+                        "metadata": {
+                            "creationTimestamp": null,
+                            "labels": {
+                                "app": "oso-rhel7-ops-base",
+                                "deploymentconfig": "oso-rhel7-zagg-web"
+                            },
+                            "annotations": {
+                                "openshift.io/generated-by": "OpenShiftNewApp"
+                            }
+                        },
+                        "spec": {
+                            "volumes": [
+                                {
+                                    "name": "monitoring-secrets",
+                                    "secret": {
+                                        "secretName": "monitoring-secrets"
+                                    }
+                                }
+                            ],
+                            "containers": [
+                                {
+                                    "name": "oso-rhel7-zagg-web",
+                                    "image": "notused",
+                                    "resources": {},
+                                    "volumeMounts": [
+                                        {
+                                            "name": "monitoring-secrets",
+                                            "mountPath": "/secrets"
+                                        }
+                                    ],
+                                    "terminationMessagePath": "/dev/termination-log",
+                                    "imagePullPolicy": "Always",
+                                    "securityContext": {
+                                        "capabilities": {},
+                                        "privileged": false
+                                    }
+                                }
+                            ],
+                            "restartPolicy": "Always",
+                            "terminationGracePeriodSeconds": 30,
+                            "dnsPolicy": "ClusterFirst",
+                            "securityContext": {}
+                        }
+                    }
+                }
+            }'''
+
+        post_dc = '''{
+                "kind": "DeploymentConfig",
+                "apiVersion": "v1",
+                "metadata": {
+                    "name": "oso-rhel7-zagg-web",
+                    "namespace": "new-monitoring",
+                    "selfLink": "/oapi/v1/namespaces/new-monitoring/deploymentconfigs/oso-rhel7-zagg-web",
+                    "uid": "f56e9dd2-7c13-11e6-b046-0e8844de0587",
+                    "resourceVersion": "137095771",
+                    "generation": 4,
+                    "creationTimestamp": "2016-09-16T13:46:24Z",
+                    "labels": {
+                        "app": "oso-rhel7-ops-base",
+                        "name": "oso-rhel7-zagg-web"
+                    },
+                    "annotations": {
+                        "openshift.io/generated-by": "OpenShiftNewApp"
+                    }
+                },
+                "spec": {
+                    "strategy": {
+                        "type": "Rolling",
+                        "rollingParams": {
+                            "updatePeriodSeconds": 1,
+                            "intervalSeconds": 1,
+                            "timeoutSeconds": 600,
+                            "maxUnavailable": "25%",
+                            "maxSurge": "25%"
+                        },
+                        "resources": {}
+                    },
+                    "triggers": [
+                        {
+                            "type": "ConfigChange"
+                        },
+                        {
+                            "type": "ImageChange",
+                            "imageChangeParams": {
+                                "automatic": true,
+                                "containerNames": [
+                                    "oso-rhel7-zagg-web"
+                                ],
+                                "from": {
+                                    "kind": "ImageStreamTag",
+                                    "namespace": "new-monitoring",
+                                    "name": "oso-rhel7-zagg-web:latest"
+                                },
+                                "lastTriggeredImage": "notused"
+                            }
+                        }
+                    ],
+                    "replicas": 10,
+                    "test": false,
+                    "selector": {
+                        "deploymentconfig": "oso-rhel7-zagg-web"
+                    },
+                    "template": {
+                        "metadata": {
+                            "creationTimestamp": null,
+                            "labels": {
+                                "app": "oso-rhel7-ops-base",
+                                "deploymentconfig": "oso-rhel7-zagg-web"
+                            },
+                            "annotations": {
+                                "openshift.io/generated-by": "OpenShiftNewApp"
+                            }
+                        },
+                        "spec": {
+                            "volumes": [
+                                {
+                                    "name": "monitoring-secrets",
+                                    "secret": {
+                                        "secretName": "monitoring-secrets"
+                                    }
+                                },
+                                {
+                                    "name": "test-volume",
+                                    "persistentVolumeClaim": {
+                                        "claimName": "testclass",
+                                        "claimSize": "1G"
+                                    }
+                                }
+                            ],
+                            "containers": [
+                                {
+                                    "name": "oso-rhel7-zagg-web",
+                                    "image": "notused",
+                                    "resources": {},
+                                    "volumeMounts": [
+                                        {
+                                            "name": "monitoring-secrets",
+                                            "mountPath": "/secrets"
+                                        },
+                                        {
+                                            "name": "test-volume",
+                                            "mountPath": "/data"
+                                        }
+                                    ],
+                                    "terminationMessagePath": "/dev/termination-log",
+                                    "imagePullPolicy": "Always",
+                                    "securityContext": {
+                                        "capabilities": {},
+                                        "privileged": false
+                                    }
+                                }
+                            ],
+                            "restartPolicy": "Always",
+                            "terminationGracePeriodSeconds": 30,
+                            "dnsPolicy": "ClusterFirst",
+                            "securityContext": {}
+                        }
+                    }
+                }
+            }'''
+
+        mock_cmd.side_effect = [
+            (0, dc, ''),
+            (0, dc, ''),
+            (0, '', ''),
+            (0, post_dc, ''),
+        ]
+
+        mock_tmpfile_copy.side_effect = [
+            '/tmp/mocked_kubeconfig',
+        ]
+
+        results = OCVolume.run_ansible(params, False)
+
+        self.assertTrue(results['changed'])
+        self.assertTrue(results['results']['results'][-1]['name'] == 'test-volume')
+
+    @mock.patch('oc_volume.Utils.create_tmpfile_copy')
+    @mock.patch('oc_volume.OCVolume._run')
+    def test_create_configmap(self, mock_cmd, mock_tmpfile_copy):
+        ''' Testing a label list '''
+        params = copy.deepcopy(OCVolumeTest.params)
+        params.update({'mount_path': '/configmap',
+                       'mount_type': 'configmap',
+                       'configmap_name': 'configtest',
+                       'vol_name': 'configvol'})
+
+        dc = '''{
+                "kind": "DeploymentConfig",
+                "apiVersion": "v1",
+                "metadata": {
+                    "name": "oso-rhel7-zagg-web",
+                    "namespace": "new-monitoring",
+                    "selfLink": "/oapi/v1/namespaces/new-monitoring/deploymentconfigs/oso-rhel7-zagg-web",
+                    "uid": "f56e9dd2-7c13-11e6-b046-0e8844de0587",
+                    "resourceVersion": "137095771",
+                    "generation": 4,
+                    "creationTimestamp": "2016-09-16T13:46:24Z",
+                    "labels": {
+                        "app": "oso-rhel7-ops-base",
+                        "name": "oso-rhel7-zagg-web"
+                    },
+                    "annotations": {
+                        "openshift.io/generated-by": "OpenShiftNewApp"
+                    }
+                },
+                "spec": {
+                    "strategy": {
+                        "type": "Rolling",
+                        "rollingParams": {
+                            "updatePeriodSeconds": 1,
+                            "intervalSeconds": 1,
+                            "timeoutSeconds": 600,
+                            "maxUnavailable": "25%",
+                            "maxSurge": "25%"
+                        },
+                        "resources": {}
+                    },
+                    "triggers": [
+                        {
+                            "type": "ConfigChange"
+                        },
+                        {
+                            "type": "ImageChange",
+                            "imageChangeParams": {
+                                "automatic": true,
+                                "containerNames": [
+                                    "oso-rhel7-zagg-web"
+                                ],
+                                "from": {
+                                    "kind": "ImageStreamTag",
+                                    "namespace": "new-monitoring",
+                                    "name": "oso-rhel7-zagg-web:latest"
+                                },
+                                "lastTriggeredImage": "notused"
+                            }
+                        }
+                    ],
+                    "replicas": 10,
+                    "test": false,
+                    "selector": {
+                        "deploymentconfig": "oso-rhel7-zagg-web"
+                    },
+                    "template": {
+                        "metadata": {
+                            "creationTimestamp": null,
+                            "labels": {
+                                "app": "oso-rhel7-ops-base",
+                                "deploymentconfig": "oso-rhel7-zagg-web"
+                            },
+                            "annotations": {
+                                "openshift.io/generated-by": "OpenShiftNewApp"
+                            }
+                        },
+                        "spec": {
+                            "volumes": [
+                                {
+                                    "name": "monitoring-secrets",
+                                    "secret": {
+                                        "secretName": "monitoring-secrets"
+                                    }
+                                }
+                            ],
+                            "containers": [
+                                {
+                                    "name": "oso-rhel7-zagg-web",
+                                    "image": "notused",
+                                    "resources": {},
+                                    "volumeMounts": [
+                                        {
+                                            "name": "monitoring-secrets",
+                                            "mountPath": "/secrets"
+                                        }
+                                    ],
+                                    "terminationMessagePath": "/dev/termination-log",
+                                    "imagePullPolicy": "Always",
+                                    "securityContext": {
+                                        "capabilities": {},
+                                        "privileged": false
+                                    }
+                                }
+                            ],
+                            "restartPolicy": "Always",
+                            "terminationGracePeriodSeconds": 30,
+                            "dnsPolicy": "ClusterFirst",
+                            "securityContext": {}
+                        }
+                    }
+                }
+            }'''
+
+        post_dc = '''{
+                "kind": "DeploymentConfig",
+                "apiVersion": "v1",
+                "metadata": {
+                    "name": "oso-rhel7-zagg-web",
+                    "namespace": "new-monitoring",
+                    "selfLink": "/oapi/v1/namespaces/new-monitoring/deploymentconfigs/oso-rhel7-zagg-web",
+                    "uid": "f56e9dd2-7c13-11e6-b046-0e8844de0587",
+                    "resourceVersion": "137095771",
+                    "generation": 4,
+                    "creationTimestamp": "2016-09-16T13:46:24Z",
+                    "labels": {
+                        "app": "oso-rhel7-ops-base",
+                        "name": "oso-rhel7-zagg-web"
+                    },
+                    "annotations": {
+                        "openshift.io/generated-by": "OpenShiftNewApp"
+                    }
+                },
+                "spec": {
+                    "strategy": {
+                        "type": "Rolling",
+                        "rollingParams": {
+                            "updatePeriodSeconds": 1,
+                            "intervalSeconds": 1,
+                            "timeoutSeconds": 600,
+                            "maxUnavailable": "25%",
+                            "maxSurge": "25%"
+                        },
+                        "resources": {}
+                    },
+                    "triggers": [
+                        {
+                            "type": "ConfigChange"
+                        },
+                        {
+                            "type": "ImageChange",
+                            "imageChangeParams": {
+                                "automatic": true,
+                                "containerNames": [
+                                    "oso-rhel7-zagg-web"
+                                ],
+                                "from": {
+                                    "kind": "ImageStreamTag",
+                                    "namespace": "new-monitoring",
+                                    "name": "oso-rhel7-zagg-web:latest"
+                                },
+                                "lastTriggeredImage": "notused"
+                            }
+                        }
+                    ],
+                    "replicas": 10,
+                    "test": false,
+                    "selector": {
+                        "deploymentconfig": "oso-rhel7-zagg-web"
+                    },
+                    "template": {
+                        "metadata": {
+                            "creationTimestamp": null,
+                            "labels": {
+                                "app": "oso-rhel7-ops-base",
+                                "deploymentconfig": "oso-rhel7-zagg-web"
+                            },
+                            "annotations": {
+                                "openshift.io/generated-by": "OpenShiftNewApp"
+                            }
+                        },
+                        "spec": {
+                            "volumes": [
+                                {
+                                    "name": "monitoring-secrets",
+                                    "secret": {
+                                        "secretName": "monitoring-secrets"
+                                    }
+                                },
+                                {
+                                    "name": "configvol",
+                                    "configMap": {
+                                        "name": "configtest"
+                                    }
+                                }
+                            ],
+                            "containers": [
+                                {
+                                    "name": "oso-rhel7-zagg-web",
+                                    "image": "notused",
+                                    "resources": {},
+                                    "volumeMounts": [
+                                        {
+                                            "name": "monitoring-secrets",
+                                            "mountPath": "/secrets"
+                                        },
+                                        {
+                                            "name": "configvol",
+                                            "mountPath": "/configmap"
+                                        }
+                                    ],
+                                    "terminationMessagePath": "/dev/termination-log",
+                                    "imagePullPolicy": "Always",
+                                    "securityContext": {
+                                        "capabilities": {},
+                                        "privileged": false
+                                    }
+                                }
+                            ],
+                            "restartPolicy": "Always",
+                            "terminationGracePeriodSeconds": 30,
+                            "dnsPolicy": "ClusterFirst",
+                            "securityContext": {}
+                        }
+                    }
+                }
+            }'''
+
+        mock_cmd.side_effect = [
+            (0, dc, ''),
+            (0, dc, ''),
+            (0, '', ''),
+            (0, post_dc, ''),
+        ]
+
+        mock_tmpfile_copy.side_effect = [
+            '/tmp/mocked_kubeconfig',
+        ]
+
+        results = OCVolume.run_ansible(params, False)
+
+        self.assertTrue(results['changed'])
+        self.assertTrue(results['results']['results'][-1]['name'] == 'configvol')
+
+    @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)