Browse Source

Merge pull request #3224 from kwoodson/oc_service

Adding oc_service to lib_openshift.
Kenny Woodson 8 years ago
parent
commit
287282e3a1

+ 5 - 3
roles/lib_openshift/library/oadm_manage_node.py

@@ -910,11 +910,13 @@ class OpenShiftCLI(object):
 
     def _run(self, cmds, input_data):
         ''' Actually executes the command. This makes mocking easier. '''
+        curr_env = os.environ.copy()
+        curr_env.update({'KUBECONFIG': self.kubeconfig})
         proc = subprocess.Popen(cmds,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
-                                env={'KUBECONFIG': self.kubeconfig})
+                                env=curr_env)
 
         stdout, stderr = proc.communicate(input_data)
 
@@ -925,9 +927,9 @@ class OpenShiftCLI(object):
         '''Base command for oc '''
         cmds = []
         if oadm:
-            cmds = ['/usr/bin/oadm']
+            cmds = ['oadm']
         else:
-            cmds = ['/usr/bin/oc']
+            cmds = ['oc']
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])

+ 5 - 3
roles/lib_openshift/library/oc_edit.py

@@ -938,11 +938,13 @@ class OpenShiftCLI(object):
 
     def _run(self, cmds, input_data):
         ''' Actually executes the command. This makes mocking easier. '''
+        curr_env = os.environ.copy()
+        curr_env.update({'KUBECONFIG': self.kubeconfig})
         proc = subprocess.Popen(cmds,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
-                                env={'KUBECONFIG': self.kubeconfig})
+                                env=curr_env)
 
         stdout, stderr = proc.communicate(input_data)
 
@@ -953,9 +955,9 @@ class OpenShiftCLI(object):
         '''Base command for oc '''
         cmds = []
         if oadm:
-            cmds = ['/usr/bin/oadm']
+            cmds = ['oadm']
         else:
-            cmds = ['/usr/bin/oc']
+            cmds = ['oc']
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])

+ 5 - 3
roles/lib_openshift/library/oc_obj.py

@@ -917,11 +917,13 @@ class OpenShiftCLI(object):
 
     def _run(self, cmds, input_data):
         ''' Actually executes the command. This makes mocking easier. '''
+        curr_env = os.environ.copy()
+        curr_env.update({'KUBECONFIG': self.kubeconfig})
         proc = subprocess.Popen(cmds,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
-                                env={'KUBECONFIG': self.kubeconfig})
+                                env=curr_env)
 
         stdout, stderr = proc.communicate(input_data)
 
@@ -932,9 +934,9 @@ class OpenShiftCLI(object):
         '''Base command for oc '''
         cmds = []
         if oadm:
-            cmds = ['/usr/bin/oadm']
+            cmds = ['oadm']
         else:
-            cmds = ['/usr/bin/oc']
+            cmds = ['oc']
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])

+ 5 - 3
roles/lib_openshift/library/oc_route.py

@@ -942,11 +942,13 @@ class OpenShiftCLI(object):
 
     def _run(self, cmds, input_data):
         ''' Actually executes the command. This makes mocking easier. '''
+        curr_env = os.environ.copy()
+        curr_env.update({'KUBECONFIG': self.kubeconfig})
         proc = subprocess.Popen(cmds,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
-                                env={'KUBECONFIG': self.kubeconfig})
+                                env=curr_env)
 
         stdout, stderr = proc.communicate(input_data)
 
@@ -957,9 +959,9 @@ class OpenShiftCLI(object):
         '''Base command for oc '''
         cmds = []
         if oadm:
-            cmds = ['/usr/bin/oadm']
+            cmds = ['oadm']
         else:
-            cmds = ['/usr/bin/oc']
+            cmds = ['oc']
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])

+ 5 - 3
roles/lib_openshift/library/oc_scale.py

@@ -892,11 +892,13 @@ class OpenShiftCLI(object):
 
     def _run(self, cmds, input_data):
         ''' Actually executes the command. This makes mocking easier. '''
+        curr_env = os.environ.copy()
+        curr_env.update({'KUBECONFIG': self.kubeconfig})
         proc = subprocess.Popen(cmds,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
-                                env={'KUBECONFIG': self.kubeconfig})
+                                env=curr_env)
 
         stdout, stderr = proc.communicate(input_data)
 
@@ -907,9 +909,9 @@ class OpenShiftCLI(object):
         '''Base command for oc '''
         cmds = []
         if oadm:
-            cmds = ['/usr/bin/oadm']
+            cmds = ['oadm']
         else:
-            cmds = ['/usr/bin/oc']
+            cmds = ['oc']
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])

+ 5 - 3
roles/lib_openshift/library/oc_secret.py

@@ -938,11 +938,13 @@ class OpenShiftCLI(object):
 
     def _run(self, cmds, input_data):
         ''' Actually executes the command. This makes mocking easier. '''
+        curr_env = os.environ.copy()
+        curr_env.update({'KUBECONFIG': self.kubeconfig})
         proc = subprocess.Popen(cmds,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
-                                env={'KUBECONFIG': self.kubeconfig})
+                                env=curr_env)
 
         stdout, stderr = proc.communicate(input_data)
 
@@ -953,9 +955,9 @@ class OpenShiftCLI(object):
         '''Base command for oc '''
         cmds = []
         if oadm:
-            cmds = ['/usr/bin/oadm']
+            cmds = ['oadm']
         else:
-            cmds = ['/usr/bin/oc']
+            cmds = ['oc']
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])

File diff suppressed because it is too large
+ 1607 - 0
roles/lib_openshift/library/oc_service.py


+ 5 - 3
roles/lib_openshift/library/oc_serviceaccount.py

@@ -890,11 +890,13 @@ class OpenShiftCLI(object):
 
     def _run(self, cmds, input_data):
         ''' Actually executes the command. This makes mocking easier. '''
+        curr_env = os.environ.copy()
+        curr_env.update({'KUBECONFIG': self.kubeconfig})
         proc = subprocess.Popen(cmds,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
-                                env={'KUBECONFIG': self.kubeconfig})
+                                env=curr_env)
 
         stdout, stderr = proc.communicate(input_data)
 
@@ -905,9 +907,9 @@ class OpenShiftCLI(object):
         '''Base command for oc '''
         cmds = []
         if oadm:
-            cmds = ['/usr/bin/oadm']
+            cmds = ['oadm']
         else:
-            cmds = ['/usr/bin/oc']
+            cmds = ['oc']
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])

+ 5 - 3
roles/lib_openshift/library/oc_version.py

@@ -862,11 +862,13 @@ class OpenShiftCLI(object):
 
     def _run(self, cmds, input_data):
         ''' Actually executes the command. This makes mocking easier. '''
+        curr_env = os.environ.copy()
+        curr_env.update({'KUBECONFIG': self.kubeconfig})
         proc = subprocess.Popen(cmds,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
-                                env={'KUBECONFIG': self.kubeconfig})
+                                env=curr_env)
 
         stdout, stderr = proc.communicate(input_data)
 
@@ -877,9 +879,9 @@ class OpenShiftCLI(object):
         '''Base command for oc '''
         cmds = []
         if oadm:
-            cmds = ['/usr/bin/oadm']
+            cmds = ['oadm']
         else:
-            cmds = ['/usr/bin/oc']
+            cmds = ['oc']
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])

+ 36 - 0
roles/lib_openshift/src/ansible/oc_service.py

@@ -0,0 +1,36 @@
+# pylint: skip-file
+# flake8: noqa
+
+def main():
+    '''
+    ansible oc module for services
+    '''
+
+    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'),
+            namespace=dict(default='default', type='str'),
+            name=dict(default=None, type='str'),
+            labels=dict(default=None, type='dict'),
+            selector=dict(default=None, type='dict'),
+            clusterip=dict(default=None, type='str'),
+            portalip=dict(default=None, type='str'),
+            ports=dict(default=None, type='list'),
+            session_affinity=dict(default='None', type='str'),
+            service_type=dict(default='ClusterIP', type='str'),
+        ),
+        supports_check_mode=True,
+    )
+
+    rval = OCService.run_ansible(module.params, module.check_mode)
+    if 'failed' in rval:
+        return module.fail_json(**rval)
+
+    return module.exit_json(**rval)
+
+
+if __name__ == '__main__':
+    main()

+ 170 - 0
roles/lib_openshift/src/class/oc_service.py

@@ -0,0 +1,170 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+# pylint: disable=too-many-instance-attributes
+class OCService(OpenShiftCLI):
+    ''' Class to wrap the oc command line tools '''
+    kind = 'service'
+
+    # pylint allows 5
+    # pylint: disable=too-many-arguments
+    def __init__(self,
+                 sname,
+                 namespace,
+                 labels,
+                 selector,
+                 cluster_ip,
+                 portal_ip,
+                 ports,
+                 session_affinity,
+                 service_type,
+                 kubeconfig='/etc/origin/master/admin.kubeconfig',
+                 verbose=False):
+        ''' Constructor for OCVolume '''
+        super(OCService, self).__init__(namespace, kubeconfig)
+        self.namespace = namespace
+        self.config = ServiceConfig(sname, namespace, ports, selector, labels,
+                                    cluster_ip, portal_ip, session_affinity, service_type)
+        self.user_svc = Service(content=self.config.data)
+        self.svc = None
+
+    @property
+    def service(self):
+        ''' property function service'''
+        if not self.svc:
+            self.get()
+        return self.svc
+
+    @service.setter
+    def service(self, data):
+        ''' setter function for service var '''
+        self.svc = data
+
+    def exists(self):
+        ''' return whether a service exists '''
+        if self.service:
+            return True
+
+        return False
+
+    def get(self):
+        '''return service information '''
+        result = self._get(self.kind, self.config.name)
+        if result['returncode'] == 0:
+            self.service = Service(content=result['results'][0])
+            result['clusterip'] = self.service.get('spec.clusterIP')
+        elif 'services \"%s\" not found' % self.config.name  in result['stderr']:
+            result['clusterip'] = ''
+            result['returncode'] = 0
+
+        return result
+
+    def delete(self):
+        '''delete the service'''
+        return self._delete(self.kind, self.config.name)
+
+    def create(self):
+        '''create a service '''
+        return self._create_from_content(self.config.name, self.user_svc.yaml_dict)
+
+    def update(self):
+        '''create a service '''
+        # Need to copy over the portalIP and the serviceIP settings
+
+        self.user_svc.add_cluster_ip(self.service.get('spec.clusterIP'))
+        self.user_svc.add_portal_ip(self.service.get('spec.portalIP'))
+        return self._replace_content(self.kind, self.config.name, self.user_svc.yaml_dict)
+
+    def needs_update(self):
+        ''' verify an update is needed '''
+        skip = ['clusterIP', 'portalIP']
+        return not Utils.check_def_equal(self.user_svc.yaml_dict, self.service.yaml_dict, skip_keys=skip, debug=True)
+
+    # pylint: disable=too-many-return-statements,too-many-branches
+    @staticmethod
+    def run_ansible(params, check_mode):
+        '''Run the idempotent ansible code'''
+        oc_svc = OCService(params['name'],
+                           params['namespace'],
+                           params['labels'],
+                           params['selector'],
+                           params['clusterip'],
+                           params['portalip'],
+                           params['ports'],
+                           params['session_affinity'],
+                           params['service_type'])
+
+        state = params['state']
+
+        api_rval = oc_svc.get()
+
+        if api_rval['returncode'] != 0:
+            return {'failed': True, 'msg': api_rval}
+
+        #####
+        # Get
+        #####
+        if state == 'list':
+            return {'changed': False, 'results': api_rval, 'state': state}
+
+        ########
+        # Delete
+        ########
+        if state == 'absent':
+            if oc_svc.exists():
+
+                if check_mode:
+                    return {'changed': True,
+                            'msg': 'CHECK_MODE: Would have performed a delete.'}  # noqa: E501
+
+                api_rval = oc_svc.delete()
+
+                return {'changed': True, 'results': api_rval, 'state': state}
+
+            return {'changed': False, 'state': state}
+
+        if state == 'present':
+            ########
+            # Create
+            ########
+            if not oc_svc.exists():
+
+                if check_mode:
+                    return {'changed': True,
+                            'msg': 'CHECK_MODE: Would have performed a create.'}  # noqa: E501
+
+                # Create it here
+                api_rval = oc_svc.create()
+
+                if api_rval['returncode'] != 0:
+                    return {'failed': True, 'msg': api_rval}
+
+                # return the created object
+                api_rval = oc_svc.get()
+
+                if api_rval['returncode'] != 0:
+                    return {'failed': True, 'msg': api_rval}
+
+                return {'changed': True, 'results': api_rval, 'state': state}
+
+            ########
+            # Update
+            ########
+            if oc_svc.needs_update():
+                api_rval = oc_svc.update()
+
+                if api_rval['returncode'] != 0:
+                    return {'failed': True, 'msg': api_rval}
+
+                # return the created object
+                api_rval = oc_svc.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. [%s]' % state}

+ 122 - 0
roles/lib_openshift/src/doc/service

@@ -0,0 +1,122 @@
+# flake8: noqa
+# pylint: skip-file
+
+DOCUMENTATION = '''
+---
+module: oc_service
+short_description: Create, modify, and idempotently manage openshift services.
+description:
+  - Manage openshift service objects programmatically.
+options:
+  state:
+    description:
+    - State represents whether to create, modify, delete, or list
+    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: default
+    aliases: []
+  selector:
+    description:
+    - The selector to apply when filtering for services.
+    required: false
+    default: None
+    aliases: []
+  labels:
+    description:
+    - The labels to apply on the service.
+    required: false
+    default: None
+    aliases: []
+  clusterip:
+    description:
+    - The cluster ip address to use with this service.
+    required: false
+    default: None
+    aliases: []
+  portalip:
+    description:
+    - The portal ip(virtual ip) address to use with this service.
+    - "https://docs.openshift.com/enterprise/3.0/architecture/core_concepts/pods_and_services.html#services"
+    required: false
+    default: None
+    aliases: []
+  ports:
+    description:
+    - A list of the ports that are used for this service.  This includes name, port, protocol, and targetPort.
+    - See examples.
+    required: false
+    default: None
+    aliases: []
+  session_affinity:
+    description:
+    - The type of session affinity to use.
+    required: false
+    default: 'None'
+    aliases: []
+  service_type:
+    description:
+    - The type of service desired.  Each option tells the service to behave accordingly.
+    - https://kubernetes.io/docs/user-guide/services/
+    required: false
+    default: ClusterIP
+    choices:
+    - ClusterIP
+    - NodePort
+    - LoadBalancer
+    - ExternalName
+    aliases: []
+author:
+- "Kenny Woodson <kwoodson@redhat.com>"
+extends_documentation_fragment: []
+'''
+
+EXAMPLES = '''
+- name: get docker-registry service
+  run_once: true
+  oc_service:
+    namespace: default
+    name: docker-registry
+    state: list
+  register: registry_service_out
+
+- name: create the docker-registry service
+  oc_service:
+    namespace: default
+    name: docker-registry
+    ports:
+    - name: 5000-tcp
+      port: 5000
+      protocol: TCP
+      targetPort: 5000
+    selector:
+      docker-registry: default
+    session_affinity: ClientIP
+    service_type: ClusterIP
+  register: svc_out
+  notify:
+  - restart openshift master services
+'''

+ 5 - 3
roles/lib_openshift/src/lib/base.py

@@ -207,11 +207,13 @@ class OpenShiftCLI(object):
 
     def _run(self, cmds, input_data):
         ''' Actually executes the command. This makes mocking easier. '''
+        curr_env = os.environ.copy()
+        curr_env.update({'KUBECONFIG': self.kubeconfig})
         proc = subprocess.Popen(cmds,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
-                                env={'KUBECONFIG': self.kubeconfig})
+                                env=curr_env)
 
         stdout, stderr = proc.communicate(input_data)
 
@@ -222,9 +224,9 @@ class OpenShiftCLI(object):
         '''Base command for oc '''
         cmds = []
         if oadm:
-            cmds = ['/usr/bin/oadm']
+            cmds = ['oadm']
         else:
-            cmds = ['/usr/bin/oc']
+            cmds = ['oc']
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])

+ 126 - 0
roles/lib_openshift/src/lib/service.py

@@ -0,0 +1,126 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+# pylint: disable=too-many-instance-attributes
+class ServiceConfig(object):
+    ''' Handle service options '''
+    # pylint: disable=too-many-arguments
+    def __init__(self,
+                 sname,
+                 namespace,
+                 ports,
+                 selector=None,
+                 labels=None,
+                 cluster_ip=None,
+                 portal_ip=None,
+                 session_affinity=None,
+                 service_type=None):
+        ''' constructor for handling service options '''
+        self.name = sname
+        self.namespace = namespace
+        self.ports = ports
+        self.selector = selector
+        self.labels = labels
+        self.cluster_ip = cluster_ip
+        self.portal_ip = portal_ip
+        self.session_affinity = session_affinity
+        self.service_type = service_type
+        self.data = {}
+
+        self.create_dict()
+
+    def create_dict(self):
+        ''' instantiates a service dict '''
+        self.data['apiVersion'] = 'v1'
+        self.data['kind'] = 'Service'
+        self.data['metadata'] = {}
+        self.data['metadata']['name'] = self.name
+        self.data['metadata']['namespace'] = self.namespace
+        if self.labels:
+            for lab, lab_value  in self.labels.items():
+                self.data['metadata'][lab] = lab_value
+        self.data['spec'] = {}
+
+        if self.ports:
+            self.data['spec']['ports'] = self.ports
+        else:
+            self.data['spec']['ports'] = []
+
+        if self.selector:
+            self.data['spec']['selector'] = self.selector
+
+        self.data['spec']['sessionAffinity'] = self.session_affinity or 'None'
+
+        if self.cluster_ip:
+            self.data['spec']['clusterIP'] = self.cluster_ip
+
+        if self.portal_ip:
+            self.data['spec']['portalIP'] = self.portal_ip
+
+        if self.service_type:
+            self.data['spec']['type'] = self.service_type
+
+# pylint: disable=too-many-instance-attributes,too-many-public-methods
+class Service(Yedit):
+    ''' Class to model the oc service object '''
+    port_path = "spec.ports"
+    portal_ip = "spec.portalIP"
+    cluster_ip = "spec.clusterIP"
+    kind = 'Service'
+
+    def __init__(self, content):
+        '''Service constructor'''
+        super(Service, self).__init__(content=content)
+
+    def get_ports(self):
+        ''' get a list of ports '''
+        return self.get(Service.port_path) or []
+
+    def add_ports(self, inc_ports):
+        ''' add a port object to the ports list '''
+        if not isinstance(inc_ports, list):
+            inc_ports = [inc_ports]
+
+        ports = self.get_ports()
+        if not ports:
+            self.put(Service.port_path, inc_ports)
+        else:
+            ports.extend(inc_ports)
+
+        return True
+
+    def find_ports(self, inc_port):
+        ''' find a specific port '''
+        for port in self.get_ports():
+            if port['port'] == inc_port['port']:
+                return port
+
+        return None
+
+    def delete_ports(self, inc_ports):
+        ''' remove a port from a service '''
+        if not isinstance(inc_ports, list):
+            inc_ports = [inc_ports]
+
+        ports = self.get(Service.port_path) or []
+
+        if not ports:
+            return True
+
+        removed = False
+        for inc_port in inc_ports:
+            port = self.find_ports(inc_port)
+            if port:
+                ports.remove(port)
+                removed = True
+
+        return removed
+
+    def add_cluster_ip(self, sip):
+        '''add cluster ip'''
+        self.put(Service.cluster_ip, sip)
+
+    def add_portal_ip(self, pip):
+        '''add cluster ip'''
+        self.put(Service.portal_ip, pip)

+ 26 - 15
roles/lib_openshift/src/sources.yml

@@ -40,17 +40,6 @@ oc_route.py:
 - class/oc_route.py
 - ansible/oc_route.py
 
-oc_secret.py:
-- doc/generated
-- doc/license
-- lib/import.py
-- doc/secret
-- ../../lib_utils/src/class/yedit.py
-- lib/base.py
-- lib/secret.py
-- class/oc_secret.py
-- ansible/oc_secret.py
-
 oc_scale.py:
 - doc/generated
 - doc/license
@@ -63,15 +52,16 @@ oc_scale.py:
 - class/oc_scale.py
 - ansible/oc_scale.py
 
-oc_version.py:
+oc_secret.py:
 - doc/generated
 - doc/license
 - lib/import.py
-- doc/version
+- doc/secret
 - ../../lib_utils/src/class/yedit.py
 - lib/base.py
-- class/oc_version.py
-- ansible/oc_version.py
+- lib/secret.py
+- class/oc_secret.py
+- ansible/oc_secret.py
 
 oc_serviceaccount.py:
 - doc/generated
@@ -83,3 +73,24 @@ oc_serviceaccount.py:
 - lib/serviceaccount.py
 - class/oc_serviceaccount.py
 - ansible/oc_serviceaccount.py
+
+oc_service.py:
+- doc/generated
+- doc/license
+- lib/import.py
+- doc/service
+- ../../lib_utils/src/class/yedit.py
+- lib/base.py
+- lib/service.py
+- class/oc_service.py
+- ansible/oc_service.py
+
+oc_version.py:
+- doc/generated
+- doc/license
+- lib/import.py
+- doc/version
+- ../../lib_utils/src/class/yedit.py
+- lib/base.py
+- class/oc_version.py
+- ansible/oc_version.py

+ 128 - 0
roles/lib_openshift/src/test/integration/oc_service.yml

@@ -0,0 +1,128 @@
+#!/usr/bin/ansible-playbook --module-path=../../../library/
+# ./oc_service.yml -M ../../../library -e "cli_master_test=$OPENSHIFT_MASTER
+---
+- hosts: "{{ cli_master_test }}"
+  gather_facts: no
+  user: root
+  tasks:
+  - name: create the default registry service
+    oc_service:
+      namespace: default
+      name: test-registry
+      ports:
+      - name: 9000-tcp
+        port: 9000
+        protocol: TCP
+        targetPort: 9000
+      selector:
+        test-registtry: default
+      session_affinity: ClientIP
+      service_type: ClusterIP
+    register: svc_out
+  - debug: var=svc_out
+
+  - assert:
+      that:
+      - "svc_out.results.results[0]['metadata']['name'] == 'test-registry'"
+      - svc_out.changed
+      msg: service create failed.
+
+  # Test idempotent create
+  - name: NOOP create the default registry service
+    oc_service:
+      namespace: default
+      name: test-registry
+      ports:
+      - name: 9000-tcp
+        port: 9000
+        protocol: TCP
+        targetPort: 9000
+      selector:
+        test-registtry: default
+      session_affinity: ClientIP
+      service_type: ClusterIP
+    register: svc_out
+
+  - assert:
+      that:
+      - "svc_out.results.results[0]['metadata']['name'] == 'test-registry'"
+      - svc_out.changed == False
+      msg: service create failed.  No changes expected
+
+  - name: create the default registry service
+    oc_service:
+      namespace: default
+      name: test-registry
+      ports:
+      - name: 9000-tcp
+        port: 9000
+        protocol: TCP
+        targetPort: 9000
+      selector:
+        test-registtry: default
+      session_affinity: ClientIP
+      service_type: ClusterIP
+    register: svc_out
+
+  - assert:
+      that: "svc_out.results.results[0]['metadata']['name'] == 'test-registry'"
+      msg: service create failed
+
+  - name: oc_service
+    oc_service:
+      name: test-registry
+      namespace: default
+      state: list
+    register: svc_out
+
+  - assert:
+      that: "svc_out.results.results[0]['metadata']['name'] == 'test-registry'"
+      msg: service create failed
+
+  - name: create the default registry service
+    oc_service:
+      namespace: default
+      name: test-registry
+      ports:
+      - name: 9001-tcp
+        port: 9001
+        protocol: TCP
+        targetPort: 9001
+      selector:
+        test-registtry: default
+      session_affinity: ClientIP
+      service_type: ClusterIP
+    register: svc_out
+
+  - assert:
+      that: "svc_out.results.results[0]['spec']['ports'][0]['name'] == '9001-tcp'"
+      msg: service update failed
+
+  - name: oc delete service
+    oc_service:
+      name: test-registry
+      namespace: default
+      state: absent
+    register: svc_out
+  - debug: var=svc_out
+
+  - assert:
+      that:
+      - "svc_out.results['returncode'] == 0"
+      - "svc_out.results.results == ''"
+      msg: service delete failed
+
+  - name: oc get service
+    oc_service:
+      name: test-registry
+      namespace: default
+      state: list
+    register: svc_out
+  - debug: var=svc_out
+
+  - assert:
+      that:
+      - svc_out.changed == False
+      - svc_out.results.returncode == 1
+      - "'not found' in svc_out.results.stderr"
+      msg: service get failed

+ 206 - 0
roles/lib_openshift/src/test/unit/oc_service.py

@@ -0,0 +1,206 @@
+#!/usr/bin/env python2
+'''
+ Unit tests for oc service
+'''
+# To run
+# python -m unittest version
+#
+# .
+# Ran 1 test in 0.597s
+#
+# 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_service import OCService  # noqa: E402
+
+
+# pylint: disable=too-many-public-methods
+class OCServiceTest(unittest.TestCase):
+    '''
+     Test class for OCService
+    '''
+
+    def setUp(self):
+        ''' setup method will create a file and set to known configuration '''
+        pass
+
+    @mock.patch('oc_service.OCService._run')
+    def test_state_list(self, mock_cmd):
+        ''' Testing a get '''
+        params = {'name': 'router',
+                  'namespace': 'default',
+                  'ports': None,
+                  'state': 'list',
+                  'labels': None,
+                  'clusterip': None,
+                  'portalip': None,
+                  'selector': None,
+                  'session_affinity': None,
+                  'service_type': None,
+                  'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+                  'debug': False}
+
+        service = '''{
+            "kind": "Service",
+            "apiVersion": "v1",
+            "metadata": {
+                "name": "router",
+                "namespace": "default",
+                "selfLink": "/api/v1/namespaces/default/services/router",
+                "uid": "fabd2440-e3d8-11e6-951c-0e3dd518cefa",
+                "resourceVersion": "3206",
+                "creationTimestamp": "2017-01-26T15:06:14Z",
+                "labels": {
+                    "router": "router"
+                }
+            },
+            "spec": {
+                "ports": [
+                    {
+                        "name": "80-tcp",
+                        "protocol": "TCP",
+                        "port": 80,
+                        "targetPort": 80
+                    },
+                    {
+                        "name": "443-tcp",
+                        "protocol": "TCP",
+                        "port": 443,
+                        "targetPort": 443
+                    },
+                    {
+                        "name": "1936-tcp",
+                        "protocol": "TCP",
+                        "port": 1936,
+                        "targetPort": 1936
+                    },
+                    {
+                        "name": "5000-tcp",
+                        "protocol": "TCP",
+                        "port": 5000,
+                        "targetPort": 5000
+                    }
+                ],
+                "selector": {
+                    "router": "router"
+                },
+                "clusterIP": "172.30.129.161",
+                "type": "ClusterIP",
+                "sessionAffinity": "None"
+            },
+            "status": {
+                "loadBalancer": {}
+            }
+        }'''
+        mock_cmd.side_effect = [
+            (0, service, '')
+        ]
+
+        results = OCService.run_ansible(params, False)
+
+        self.assertFalse(results['changed'])
+        self.assertEqual(results['results']['results'][0]['metadata']['name'], 'router')
+
+    @mock.patch('oc_service.OCService._run')
+    def test_create(self, mock_cmd):
+        ''' Testing a create service '''
+        params = {'name': 'router',
+                  'namespace': 'default',
+                  'ports': {'name': '9000-tcp',
+                            'port': 9000,
+                            'protocol': 'TCP',
+                            'targetPOrt': 9000},
+                  'state': 'present',
+                  'labels': None,
+                  'clusterip': None,
+                  'portalip': None,
+                  'selector': {'router': 'router'},
+                  'session_affinity': 'ClientIP',
+                  'service_type': 'ClusterIP',
+                  'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+                  'debug': False}
+
+        service = '''{
+            "kind": "Service",
+            "apiVersion": "v1",
+            "metadata": {
+                "name": "router",
+                "namespace": "default",
+                "selfLink": "/api/v1/namespaces/default/services/router",
+                "uid": "fabd2440-e3d8-11e6-951c-0e3dd518cefa",
+                "resourceVersion": "3206",
+                "creationTimestamp": "2017-01-26T15:06:14Z",
+                "labels": {
+                    "router": "router"
+                }
+            },
+            "spec": {
+                "ports": [
+                    {
+                        "name": "80-tcp",
+                        "protocol": "TCP",
+                        "port": 80,
+                        "targetPort": 80
+                    },
+                    {
+                        "name": "443-tcp",
+                        "protocol": "TCP",
+                        "port": 443,
+                        "targetPort": 443
+                    },
+                    {
+                        "name": "1936-tcp",
+                        "protocol": "TCP",
+                        "port": 1936,
+                        "targetPort": 1936
+                    },
+                    {
+                        "name": "5000-tcp",
+                        "protocol": "TCP",
+                        "port": 5000,
+                        "targetPort": 5000
+                    }
+                ],
+                "selector": {
+                    "router": "router"
+                },
+                "clusterIP": "172.30.129.161",
+                "type": "ClusterIP",
+                "sessionAffinity": "None"
+            },
+            "status": {
+                "loadBalancer": {}
+            }
+        }'''
+        mock_cmd.side_effect = [
+            (1, '', 'Error from server: services "router" not found'),
+            (1, '', 'Error from server: services "router" not found'),
+            (0, service, ''),
+            (0, service, '')
+        ]
+
+        results = OCService.run_ansible(params, False)
+
+        self.assertTrue(results['changed'])
+        self.assertTrue(results['results']['returncode'] == 0)
+        self.assertEqual(results['results']['results'][0]['metadata']['name'], 'router')
+
+    def tearDown(self):
+        '''TearDown method'''
+        pass
+
+
+if __name__ == "__main__":
+    unittest.main()