Browse Source

Adding oc_service to lib_openshift.

Kenny Woodson 8 years ago
parent
commit
8237eebd0d

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


+ 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()

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

@@ -0,0 +1,168 @@
+# 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 yedit var '''
+        self.svc = data
+
+    def exists(self):
+        ''' return whether a volume exists '''
+        if self.service:
+            return True
+
+        return False
+
+    def get(self):
+        '''return volume 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'] = ''
+
+        return result
+
+    def delete(self):
+        '''delete the object'''
+        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)
+
+    @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}

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

@@ -0,0 +1,121 @@
+# 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: true
+    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 address to use with this service.
+    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
+'''

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

@@ -0,0 +1,124 @@
+# pylint: skip-file
+
+# 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):
+        ''' return a service as a 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 wrap the oc command line tools '''
+    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,27 @@ 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_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_serviceaccount.py:
 - doc/generated
@@ -83,3 +84,13 @@ oc_serviceaccount.py:
 - lib/serviceaccount.py
 - class/oc_serviceaccount.py
 - ansible/oc_serviceaccount.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

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

@@ -0,0 +1,91 @@
+#!/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: svcout
+
+  - assert:
+      that: "svcout.results.results[0]['metadata']['name'] == 'test-registry'"
+      msg: route create failed
+
+  - name: oc_service
+    oc_service:
+      name: test-registry
+      namespace: default
+      state: list
+    register: svc_out
+
+  - assert:
+      that: "svcout.results.results[0]['metadata']['name'] == 'test-registry'"
+      msg: route create failed
+
+
+#  - name: get route
+#    oc_route:
+#      state: list
+#      name: test
+#      namespace: default
+#    register: routeout
+#  - debug: var=routeout
+#
+#  - assert:
+#      that: "routeout.results[0]['metadata']['name'] == 'test'"
+#      msg: get route failed
+#
+#  - name: delete route
+#    oc_route:
+#      state: absent
+#      name: test
+#      namespace: default
+#    register: routeout
+#  - debug: var=routeout
+#
+#  - assert:
+#      that: "routeout.results.returncode == 0"
+#      msg: delete route failed
+#
+#  - name: create route
+#    oc_route:
+#      name: test
+#      namespace: default
+#      tls_termination: edge
+#      cert_content: testing cert
+#      cacert_content: testing cacert
+#      key_content: testing key
+#      service_name: test
+#      host: test.example
+#    register: routeout
+#  - debug: var=routeout
+#
+#  - name: create route noop
+#    oc_route:
+#      name: test
+#      namespace: default
+#      tls_termination: edge
+#      cert_content: testing cert
+#      cacert_content: testing cacert
+#      key_content: testing key
+#      service_name: test
+#      host: test.example
+#    register: routeout
+#  - debug: var=routeout
+#
+#  - assert:
+#      that: "routeout.changed == False"
+#      msg: Route create not idempotent