From d67c5b8f79609d2d3b07cc009f58e3dc988782c5 Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Mon, 23 Mar 2015 16:30:49 -0400 Subject: node registration changes - Remove default value for openshift_hostname and make it required - Remove workarounds that are no longer needed - Remove resources parameter from openshift_register_node module - pre-create node certificates for each node before registering node - distribute created node certificates to each node - Move node registration logic to a new openshift_register_nodes role - This is because we now have to run the steps on a master as opposed to on the nodes like we were previously doing. - Rename openshift_register_node module to kubernetes_register_node, one more step to genericizing enough for upstreaming, however there are still plenty of openshift specific commands that still need to be genericized. --- roles/openshift_register_nodes/README.md | 38 +++ roles/openshift_register_nodes/defaults/main.yml | 5 + .../library/kubernetes_register_node.py | 370 +++++++++++++++++++++ roles/openshift_register_nodes/meta/main.yml | 128 +++++++ roles/openshift_register_nodes/tasks/main.yml | 71 ++++ 5 files changed, 612 insertions(+) create mode 100644 roles/openshift_register_nodes/README.md create mode 100644 roles/openshift_register_nodes/defaults/main.yml create mode 100644 roles/openshift_register_nodes/library/kubernetes_register_node.py create mode 100644 roles/openshift_register_nodes/meta/main.yml create mode 100644 roles/openshift_register_nodes/tasks/main.yml (limited to 'roles/openshift_register_nodes') diff --git a/roles/openshift_register_nodes/README.md b/roles/openshift_register_nodes/README.md new file mode 100644 index 000000000..225dd44b9 --- /dev/null +++ b/roles/openshift_register_nodes/README.md @@ -0,0 +1,38 @@ +Role Name +========= + +A brief description of the role goes here. + +Requirements +------------ + +Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. + +Role Variables +-------------- + +A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. + +Dependencies +------------ + +A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. + +Example Playbook +---------------- + +Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: + + - hosts: servers + roles: + - { role: username.rolename, x: 42 } + +License +------- + +BSD + +Author Information +------------------ + +An optional section for the role authors to include contact information, or a website (HTML is not allowed). diff --git a/roles/openshift_register_nodes/defaults/main.yml b/roles/openshift_register_nodes/defaults/main.yml new file mode 100644 index 000000000..3501e8922 --- /dev/null +++ b/roles/openshift_register_nodes/defaults/main.yml @@ -0,0 +1,5 @@ +--- +openshift_kube_api_version: v1beta1 +openshift_cert_dir: openshift.local.certificates +openshift_cert_dir_parent: /var/lib/openshift +openshift_cert_dir_abs: "{{ openshift_cert_dir_parent ~ '/' ~ openshift_cert_dir }}" diff --git a/roles/openshift_register_nodes/library/kubernetes_register_node.py b/roles/openshift_register_nodes/library/kubernetes_register_node.py new file mode 100644 index 000000000..409215616 --- /dev/null +++ b/roles/openshift_register_nodes/library/kubernetes_register_node.py @@ -0,0 +1,370 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vim: expandtab:tabstop=4:shiftwidth=4 + +import os +import multiprocessing +import socket +from subprocess import check_output, Popen +from decimal import * + +DOCUMENTATION = ''' +--- +module: kubernetes_register_node +short_description: Registers a kubernetes node with a master +description: + - Registers a kubernetes node with a master +options: + name: + default: null + description: + - Identifier for this node (usually the node fqdn). + required: true + api_verison: + choices: ['v1beta1', 'v1beta3'] + default: 'v1beta1' + description: + - Kubernetes API version to use + required: true + host_ip: + default: null + description: + - IP Address to associate with the node when registering. + Available in the following API versions: v1beta1. + required: false + hostnames: + default: [] + description: + - Valid hostnames for this node. Available in the following API + versions: v1beta3. + required: false + external_ips: + default: [] + description: + - External IP Addresses for this node. Available in the following API + versions: v1beta3. + required: false + internal_ips: + default: [] + description: + - Internal IP Addresses for this node. Available in the following API + versions: v1beta3. + required: false + cpu: + default: null + description: + - Number of CPUs to allocate for this node. When using the v1beta1 + API, you must specify the CPU count as a floating point number + with no more than 3 decimal places. API version v1beta3 and newer + accepts arbitrary float values. + required: false + memory: + default: null + description: + - Memory available for this node. When using the v1beta1 API, you + must specify the memory size in bytes. API version v1beta3 and + newer accepts binary SI and decimal SI values. + required: false +''' +EXAMPLES = ''' +# Minimal node registration +- openshift_register_node: name=ose3.node.example.com + +# Node registration using the v1beta1 API and assigning 1 CPU core and 10 GB of +# Memory +- openshift_register_node: + name: ose3.node.example.com + api_version: v1beta1 + hostIP: 192.168.1.1 + cpu: 1 + memory: 500000000 + +# Node registration using the v1beta3 API, setting an alternate hostname, +# internalIP, externalIP and assigning 3.5 CPU cores and 1 TiB of Memory +- openshift_register_node: + name: ose3.node.example.com + api_version: v1beta3 + external_ips: ['192.168.1.5'] + internal_ips: ['10.0.0.5'] + hostnames: ['ose2.node.internal.local'] + cpu: 3.5 + memory: 1Ti +''' + + +class ClientConfigException(Exception): + pass + +class ClientConfig: + def __init__(self, client_opts, module): + _, output, error = module.run_command(["/usr/bin/openshift", "ex", + "config", "view", "-o", + "json"] + client_opts, + check_rc = True) + self.config = json.loads(output) + + if not (bool(self.config['clusters']) or + bool(self.config['contexts']) or + bool(self.config['current-context']) or + bool(self.config['users'])): + raise ClientConfigException(msg="Client config missing required " \ + "values", + output=output) + + def current_context(self): + return self.config['current-context'] + + def section_has_value(self, section_name, value): + section = self.config[section_name] + if isinstance(section, dict): + return value in section + else: + val = next((item for item in section + if item['name'] == value), None) + return val is not None + + def has_context(self, context): + return self.section_has_value('contexts', context) + + def has_user(self, user): + return self.section_has_value('users', user) + + def has_cluster(self, cluster): + return self.section_has_value('clusters', cluster) + + def get_value_for_context(self, context, attribute): + contexts = self.config['contexts'] + if isinstance(contexts, dict): + return contexts[context][attribute] + else: + return next((c['context'][attribute] for c in contexts + if c['name'] == context), None) + + def get_user_for_context(self, context): + return self.get_value_for_context(context, 'user') + + def get_cluster_for_context(self, context): + return self.get_value_for_context(context, 'cluster') + +class Util: + @staticmethod + def remove_empty_elements(mapping): + if isinstance(mapping, dict): + m = mapping.copy() + for key, val in mapping.iteritems(): + if not val: + del m[key] + return m + else: + return mapping + +class NodeResources: + def __init__(self, version, cpu=None, memory=None): + if version == 'v1beta1': + self.resources = dict(capacity=dict()) + self.resources['capacity']['cpu'] = cpu + self.resources['capacity']['memory'] = memory + + def get_resources(self): + return Util.remove_empty_elements(self.resources) + +class NodeSpec: + def __init__(self, version, cpu=None, memory=None, cidr=None, externalID=None): + if version == 'v1beta3': + self.spec = dict(podCIDR=cidr, externalID=externalID, + capacity=dict()) + self.spec['capacity']['cpu'] = cpu + self.spec['capacity']['memory'] = memory + + def get_spec(self): + return Util.remove_empty_elements(self.spec) + +class NodeStatus: + def addAddresses(self, addressType, addresses): + addressList = [] + for address in addresses: + addressList.append(dict(type=addressType, address=address)) + return addressList + + def __init__(self, version, externalIPs = [], internalIPs = [], + hostnames = []): + if version == 'v1beta3': + self.status = dict(addresses = addAddresses('ExternalIP', + externalIPs) + + addAddresses('InternalIP', + internalIPs) + + addAddresses('Hostname', + hostnames)) + + def get_status(self): + return Util.remove_empty_elements(self.status) + +class Node: + def __init__(self, module, client_opts, version='v1beta1', name=None, + hostIP = None, hostnames=[], externalIPs=[], internalIPs=[], + cpu=None, memory=None, labels=dict(), annotations=dict(), + podCIDR=None, externalID=None): + self.module = module + self.client_opts = client_opts + if version == 'v1beta1': + self.node = dict(id = name, + kind = 'Node', + apiVersion = version, + hostIP = hostIP, + resources = NodeResources(version, cpu, memory), + cidr = podCIDR, + labels = labels, + annotations = annotations + ) + elif version == 'v1beta3': + metadata = dict(name = name, + labels = labels, + annotations = annotations + ) + self.node = dict(kind = 'Node', + apiVersion = version, + metadata = metadata, + spec = NodeSpec(version, cpu, memory, podCIDR, + externalID), + status = NodeStatus(version, externalIPs, + internalIPs, hostnames), + ) + + def get_name(self): + if self.node['apiVersion'] == 'v1beta1': + return self.node['id'] + elif self.node['apiVersion'] == 'v1beta3': + return self.node['name'] + + def get_node(self): + node = self.node.copy() + if self.node['apiVersion'] == 'v1beta1': + node['resources'] = self.node['resources'].get_resources() + elif self.node['apiVersion'] == 'v1beta3': + node['spec'] = self.node['spec'].get_spec() + node['status'] = self.node['status'].get_status() + return Util.remove_empty_elements(node) + + def exists(self): + _, output, error = self.module.run_command(["/usr/bin/osc", "get", + "nodes"] + self.client_opts, + check_rc = True) + if re.search(self.module.params['name'], output, re.MULTILINE): + return True + return False + + def create(self): + cmd = ['/usr/bin/osc'] + self.client_opts + ['create', 'node', '-f', '-'] + rc, output, error = self.module.run_command(cmd, + data=self.module.jsonify(self.get_node())) + if rc != 0: + if re.search("minion \"%s\" already exists" % self.get_name(), + error): + self.module.exit_json(changed=False, + msg="node definition already exists", + node=self.get_node()) + else: + self.module.fail_json(msg="Node creation failed.", rc=rc, + output=output, error=error, + node=self.get_node()) + else: + return True + +def main(): + module = AnsibleModule( + argument_spec = dict( + name = dict(required = True, type = 'str'), + host_ip = dict(type = 'str'), + hostnames = dict(type = 'list', default = []), + external_ips = dict(type = 'list', default = []), + internal_ips = dict(type = 'list', default = []), + api_version = dict(type = 'str', default = 'v1beta1', # TODO: after kube rebase, we can default to v1beta3 + choices = ['v1beta1', 'v1beta3']), + cpu = dict(type = 'str'), + memory = dict(type = 'str'), + labels = dict(type = 'dict', default = {}), # TODO: needs documented + annotations = dict(type = 'dict', default = {}), # TODO: needs documented + pod_cidr = dict(type = 'str'), # TODO: needs documented + external_id = dict(type = 'str'), # TODO: needs documented + client_config = dict(type = 'str'), # TODO: needs documented + client_cluster = dict(type = 'str', default = 'master'), # TODO: needs documented + client_context = dict(type = 'str', default = 'master'), # TODO: needs documented + client_user = dict(type = 'str', default = 'admin') # TODO: needs documented + ), + mutually_exclusive = [ + ['host_ip', 'external_ips'], + ['host_ip', 'internal_ips'], + ['host_ip', 'hostnames'], + ], + supports_check_mode=True + ) + + user_has_client_config = os.path.exists(os.path.expanduser('~/.kube/.kubeconfig')) + if not (user_has_client_config or module.params['client_config']): + module.fail_json(msg="Could not locate client configuration, " + "client_config must be specified if " + "~/.kube/.kubeconfig is not present") + + client_opts = [] + if module.params['client_config']: + client_opts.append("--kubeconfig=%s" % module.params['client_config']) + + try: + config = ClientConfig(client_opts, module) + except ClientConfigException as e: + module.fail_json(msg="Failed to get client configuration", exception=e) + + client_context = module.params['client_context'] + if config.has_context(client_context): + if client_context != config.current_context(): + client_opts.append("--context=%s" % client_context) + else: + module.fail_json(msg="Context %s not found in client config" % + client_context) + + client_user = module.params['client_user'] + if config.has_user(client_user): + if client_user != config.get_user_for_context(client_context): + client_opts.append("--user=%s" % client_user) + else: + module.fail_json(msg="User %s not found in client config" % + client_user) + + client_cluster = module.params['client_cluster'] + if config.has_cluster(client_cluster): + if client_cluster != config.get_cluster_for_context(client_cluster): + client_opts.append("--cluster=%s" % client_cluster) + else: + module.fail_json(msg="Cluster %s not found in client config" % + client_cluster) + + # TODO: provide sane defaults for some (like hostname, externalIP, + # internalIP, etc) + node = Node(module, client_opts, module.params['api_version'], + module.params['name'], module.params['host_ip'], + module.params['hostnames'], module.params['external_ips'], + module.params['internal_ips'], module.params['cpu'], + module.params['memory'], module.params['labels'], + module.params['annotations'], module.params['pod_cidr'], + module.params['external_id']) + + # TODO: attempt to support changing node settings where possible and/or + # modifying node resources + if node.exists(): + module.exit_json(changed=False, node=node.get_node()) + elif module.check_mode: + module.exit_json(changed=True, node=node.get_node()) + else: + if node.create(): + module.exit_json(changed=True, + msg="Node created successfully", + node=node.get_node()) + else: + module.fail_json(msg="Unknown error creating node", + node=node.get_node()) + + +# import module snippets +from ansible.module_utils.basic import * +if __name__ == '__main__': + main() diff --git a/roles/openshift_register_nodes/meta/main.yml b/roles/openshift_register_nodes/meta/main.yml new file mode 100644 index 000000000..7b1f0ef0a --- /dev/null +++ b/roles/openshift_register_nodes/meta/main.yml @@ -0,0 +1,128 @@ +--- +galaxy_info: + author: your name + description: + company: your company (optional) + # Some suggested licenses: + # - BSD (default) + # - MIT + # - GPLv2 + # - GPLv3 + # - Apache + # - CC-BY + license: license (GPLv2, CC-BY, etc) + min_ansible_version: 1.2 + # + # Below are all platforms currently available. Just uncomment + # the ones that apply to your role. If you don't see your + # platform on this list, let us know and we'll get it added! + # + #platforms: + #- name: EL + # versions: + # - all + # - 5 + # - 6 + # - 7 + #- name: GenericUNIX + # versions: + # - all + # - any + #- name: Fedora + # versions: + # - all + # - 16 + # - 17 + # - 18 + # - 19 + # - 20 + #- name: SmartOS + # versions: + # - all + # - any + #- name: opensuse + # versions: + # - all + # - 12.1 + # - 12.2 + # - 12.3 + # - 13.1 + # - 13.2 + #- name: Amazon + # versions: + # - all + # - 2013.03 + # - 2013.09 + #- name: GenericBSD + # versions: + # - all + # - any + #- name: FreeBSD + # versions: + # - all + # - 8.0 + # - 8.1 + # - 8.2 + # - 8.3 + # - 8.4 + # - 9.0 + # - 9.1 + # - 9.1 + # - 9.2 + #- name: Ubuntu + # versions: + # - all + # - lucid + # - maverick + # - natty + # - oneiric + # - precise + # - quantal + # - raring + # - saucy + # - trusty + #- name: SLES + # versions: + # - all + # - 10SP3 + # - 10SP4 + # - 11 + # - 11SP1 + # - 11SP2 + # - 11SP3 + #- name: GenericLinux + # versions: + # - all + # - any + #- name: Debian + # versions: + # - all + # - etch + # - lenny + # - squeeze + # - wheezy + # + # Below are all categories currently available. Just as with + # the platforms above, uncomment those that apply to your role. + # + #categories: + #- cloud + #- cloud:ec2 + #- cloud:gce + #- cloud:rax + #- clustering + #- database + #- database:nosql + #- database:sql + #- development + #- monitoring + #- networking + #- packaging + #- system + #- web +dependencies: [] + # List your role dependencies here, one per line. Only + # dependencies available via galaxy should be listed here. + # Be sure to remove the '[]' above if you add dependencies + # to this list. + diff --git a/roles/openshift_register_nodes/tasks/main.yml b/roles/openshift_register_nodes/tasks/main.yml new file mode 100644 index 000000000..59216fc87 --- /dev/null +++ b/roles/openshift_register_nodes/tasks/main.yml @@ -0,0 +1,71 @@ +--- +# TODO: support configuration for multiple masters, currently hardcoding +# the info from the first master + +# TODO: create a failed_when condition +- name: Create node server certificates + command: > + /usr/bin/openshift admin create-server-cert + --overwrite=false + --cert={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/server.crt + --key={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/server.key + --hostnames={{ [openshift_hostname, openshift_public_hostname, openshift_ip, openshift_public_ip]|join(",") }} + args: + chdir: "{{ openshift_cert_dir_parent }}" + creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift_node_hostname }}/server.crt" + with_items: openshift_nodes + register: server_cert_result + +# TODO: create a failed_when condition +- name: Create node client certificates + command: > + /usr/bin/openshift admin create-node-cert + --overwrite=false + --cert={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/cert.crt + --key={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/key.key + --node-name={{ item.openshift_node_hostname }} + args: + chdir: "{{ openshift_cert_dir_parent }}" + creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift_node_hostname }}/cert.crt" + with_items: openshift_nodes + register: node_cert_result + +# TODO: re-create kubeconfig if certs were regenerated, not just if +# .kubeconfig doesn't exist +# TODO: create a failed_when condition +- name: Create kubeconfigs for nodes + command: > + /usr/bin/openshift admin create-kubeconfig + --client-certificate={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/cert.crt + --client-key={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/key.key + --kubeconfig={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/.kubeconfig + --master={{ openshift_master_urls[0] }} + --public-master={{ openshift_master_public_urls[0] }} + args: + chdir: "{{ openshift_cert_dir_parent }}" + creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift_node_hostname }}/.kubeconfig" + with_items: openshift_nodes + register: kubeconfig_result + +# TODO: generate the node configs (openshift start node --write-config +# --config='{{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/node.yaml' +# --kubeconfig='{{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/.kubeconfig' +# will need to modify the generated node config as needed +# (servingInfo.{certFile,clientCA,keyFile}) + +- name: Register unregistered nodes + kubernetes_register_node: + name: "{{ item.openshift_node_name }}" + api_version: "{{ openshift_kube_api_version }}" + cpu: "{{ item.openshift_node_cpu if item.openshift_node_cpu else None }}" + memory: "{{ item.openshift_node_memory if item.openshift_node_memory else None }}" + pod_cidr: "{{ item.openshift_node_pod_cidr if item.openshift_node_pod_cidr else None }}" + host_ip: "{{ item.openshift_node_host_ip }}" + labels: "{{ item.openshift_node_labels if item.openshift_node_labels else {} }}" + annotations: "{{ item.openshift_node_annotations if item.openshift_node_annotations else {} }}" + # TODO: support customizing other attributes such as: client_config, + # client_cluster, client_context, client_user + # TODO: update for v1beta3 changes after rebase: hostnames, external_ips, + # internal_ips, external_id + with_items: openshift_nodes + register: register_result -- cgit v1.2.3 From 4712e72c912a1102bff0508c98bd97da3f33ae95 Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Mon, 23 Mar 2015 23:53:17 -0400 Subject: openshift_facts role/module refactor default settings - Add openshift_facts role and module - Created new role openshift_facts that contains an openshift_facts module - Refactor openshift_* roles to use openshift_facts instead of relying on defaults - Refactor playbooks to use openshift_facts - Cleanup inventory group_vars - Update defaults - update openshift_master role firewall defaults - remove etcd peer port, since we will not be supporting clustered embedded etcd - remove 8444 since console now runs on the api port by default - add 8444 and 7001 to disabled services to ensure removal if updating - Add new role os_env_extras_node that is a subset of the docker role - previously, we were starting/enabling docker which was causing issues with some installations - Does not install or start docker, since the openshift-node role will handle that for us - Only adds root to the dockerroot group - Update playbooks to use ops_env_extras_node role instead of docker role - os_firewall bug fixes - ignore ip6tables for now, since we are not configuring any ipv6 rules - if installing package do a daemon-reload before starting/enabling service - Add aws support to bin/cluster - Add list action to bin/cluster - Add update action to bin/cluster - cleanup some stray debug statements - some variable renaming for clarity --- roles/openshift_register_nodes/README.md | 22 ++-- .../library/kubernetes_register_node.py | 3 +- roles/openshift_register_nodes/meta/main.yml | 141 +++------------------ roles/openshift_register_nodes/tasks/main.yml | 58 ++++----- 4 files changed, 53 insertions(+), 171 deletions(-) mode change 100644 => 100755 roles/openshift_register_nodes/library/kubernetes_register_node.py (limited to 'roles/openshift_register_nodes') diff --git a/roles/openshift_register_nodes/README.md b/roles/openshift_register_nodes/README.md index 225dd44b9..b96faa044 100644 --- a/roles/openshift_register_nodes/README.md +++ b/roles/openshift_register_nodes/README.md @@ -1,38 +1,34 @@ -Role Name -========= +OpenShift Register Nodes +======================== -A brief description of the role goes here. +TODO Requirements ------------ -Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. +TODO Role Variables -------------- -A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. +TODO Dependencies ------------ -A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. +TODO Example Playbook ---------------- -Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: - - - hosts: servers - roles: - - { role: username.rolename, x: 42 } +TODO License ------- -BSD +Apache License Version 2.0 Author Information ------------------ -An optional section for the role authors to include contact information, or a website (HTML is not allowed). +Jason DeTiberus (jdetiber@redhat.com) diff --git a/roles/openshift_register_nodes/library/kubernetes_register_node.py b/roles/openshift_register_nodes/library/kubernetes_register_node.py old mode 100644 new mode 100755 index 409215616..8ebeb087a --- a/roles/openshift_register_nodes/library/kubernetes_register_node.py +++ b/roles/openshift_register_nodes/library/kubernetes_register_node.py @@ -214,7 +214,8 @@ class Node: resources = NodeResources(version, cpu, memory), cidr = podCIDR, labels = labels, - annotations = annotations + annotations = annotations, + externalID = externalID ) elif version == 'v1beta3': metadata = dict(name = name, diff --git a/roles/openshift_register_nodes/meta/main.yml b/roles/openshift_register_nodes/meta/main.yml index 7b1f0ef0a..e40a152c1 100644 --- a/roles/openshift_register_nodes/meta/main.yml +++ b/roles/openshift_register_nodes/meta/main.yml @@ -1,128 +1,17 @@ --- galaxy_info: - author: your name - description: - company: your company (optional) - # Some suggested licenses: - # - BSD (default) - # - MIT - # - GPLv2 - # - GPLv3 - # - Apache - # - CC-BY - license: license (GPLv2, CC-BY, etc) - min_ansible_version: 1.2 - # - # Below are all platforms currently available. Just uncomment - # the ones that apply to your role. If you don't see your - # platform on this list, let us know and we'll get it added! - # - #platforms: - #- name: EL - # versions: - # - all - # - 5 - # - 6 - # - 7 - #- name: GenericUNIX - # versions: - # - all - # - any - #- name: Fedora - # versions: - # - all - # - 16 - # - 17 - # - 18 - # - 19 - # - 20 - #- name: SmartOS - # versions: - # - all - # - any - #- name: opensuse - # versions: - # - all - # - 12.1 - # - 12.2 - # - 12.3 - # - 13.1 - # - 13.2 - #- name: Amazon - # versions: - # - all - # - 2013.03 - # - 2013.09 - #- name: GenericBSD - # versions: - # - all - # - any - #- name: FreeBSD - # versions: - # - all - # - 8.0 - # - 8.1 - # - 8.2 - # - 8.3 - # - 8.4 - # - 9.0 - # - 9.1 - # - 9.1 - # - 9.2 - #- name: Ubuntu - # versions: - # - all - # - lucid - # - maverick - # - natty - # - oneiric - # - precise - # - quantal - # - raring - # - saucy - # - trusty - #- name: SLES - # versions: - # - all - # - 10SP3 - # - 10SP4 - # - 11 - # - 11SP1 - # - 11SP2 - # - 11SP3 - #- name: GenericLinux - # versions: - # - all - # - any - #- name: Debian - # versions: - # - all - # - etch - # - lenny - # - squeeze - # - wheezy - # - # Below are all categories currently available. Just as with - # the platforms above, uncomment those that apply to your role. - # - #categories: - #- cloud - #- cloud:ec2 - #- cloud:gce - #- cloud:rax - #- clustering - #- database - #- database:nosql - #- database:sql - #- development - #- monitoring - #- networking - #- packaging - #- system - #- web -dependencies: [] - # List your role dependencies here, one per line. Only - # dependencies available via galaxy should be listed here. - # Be sure to remove the '[]' above if you add dependencies - # to this list. - + author: Jason DeTiberus + description: + company: Red Hat, Inc. + license: Apache License, Version 2.0 + min_ansible_version: 1.8 + platforms: + - name: EL + versions: + - 7 + categories: + - cloud + - system +dependencies: +- { role: openshift_facts } + diff --git a/roles/openshift_register_nodes/tasks/main.yml b/roles/openshift_register_nodes/tasks/main.yml index 59216fc87..7319b88b1 100644 --- a/roles/openshift_register_nodes/tasks/main.yml +++ b/roles/openshift_register_nodes/tasks/main.yml @@ -1,18 +1,20 @@ --- -# TODO: support configuration for multiple masters, currently hardcoding -# the info from the first master +# TODO: support new create-config command to generate node certs and config +# TODO: recreate master/node configs if settings that affect the configs +# change (hostname, public_hostname, ip, public_ip, etc) # TODO: create a failed_when condition - name: Create node server certificates command: > /usr/bin/openshift admin create-server-cert --overwrite=false - --cert={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/server.crt - --key={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/server.key - --hostnames={{ [openshift_hostname, openshift_public_hostname, openshift_ip, openshift_public_ip]|join(",") }} + --cert={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/server.crt + --key={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/server.key + --hostnames={{ [item.openshift.common.hostname, + item.openshift.common.public_hostname]|unique|join(",") }} args: chdir: "{{ openshift_cert_dir_parent }}" - creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift_node_hostname }}/server.crt" + creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift.common.hostname }}/server.crt" with_items: openshift_nodes register: server_cert_result @@ -21,48 +23,42 @@ command: > /usr/bin/openshift admin create-node-cert --overwrite=false - --cert={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/cert.crt - --key={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/key.key - --node-name={{ item.openshift_node_hostname }} + --cert={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/cert.crt + --key={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/key.key + --node-name={{ item.openshift.common.hostname }} args: chdir: "{{ openshift_cert_dir_parent }}" - creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift_node_hostname }}/cert.crt" + creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift.common.hostname }}/cert.crt" with_items: openshift_nodes register: node_cert_result -# TODO: re-create kubeconfig if certs were regenerated, not just if -# .kubeconfig doesn't exist # TODO: create a failed_when condition - name: Create kubeconfigs for nodes command: > /usr/bin/openshift admin create-kubeconfig - --client-certificate={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/cert.crt - --client-key={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/key.key - --kubeconfig={{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/.kubeconfig - --master={{ openshift_master_urls[0] }} - --public-master={{ openshift_master_public_urls[0] }} + --client-certificate={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/cert.crt + --client-key={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/key.key + --kubeconfig={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/.kubeconfig + --master={{ openshift.master.api_url }} + --public-master={{ openshift.master.public_api_url }} args: chdir: "{{ openshift_cert_dir_parent }}" - creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift_node_hostname }}/.kubeconfig" + creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift.common.hostname }}/.kubeconfig" with_items: openshift_nodes register: kubeconfig_result -# TODO: generate the node configs (openshift start node --write-config -# --config='{{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/node.yaml' -# --kubeconfig='{{ openshift_cert_dir }}/node-{{ item.openshift_node_hostname }}/.kubeconfig' -# will need to modify the generated node config as needed -# (servingInfo.{certFile,clientCA,keyFile}) - - name: Register unregistered nodes kubernetes_register_node: - name: "{{ item.openshift_node_name }}" + client_user: openshift-client + name: "{{ item.openshift.common.hostname }}" api_version: "{{ openshift_kube_api_version }}" - cpu: "{{ item.openshift_node_cpu if item.openshift_node_cpu else None }}" - memory: "{{ item.openshift_node_memory if item.openshift_node_memory else None }}" - pod_cidr: "{{ item.openshift_node_pod_cidr if item.openshift_node_pod_cidr else None }}" - host_ip: "{{ item.openshift_node_host_ip }}" - labels: "{{ item.openshift_node_labels if item.openshift_node_labels else {} }}" - annotations: "{{ item.openshift_node_annotations if item.openshift_node_annotations else {} }}" + cpu: "{{ item.openshift.node.resources_cpu | default(None) }}" + memory: "{{ item.openshift.node.resources_memory | default(None) }}" + pod_cidr: "{{ item.openshift.node.pod_cidr | default(None) }}" + host_ip: "{{ item.openshift.common.ip }}" + labels: "{{ item.openshift.node.labels | default({}) }}" + annotations: "{{ item.openshift.node.annotations | default({}) }}" + external_id: "{{ item.openshift.node.external_id }}" # TODO: support customizing other attributes such as: client_config, # client_cluster, client_context, client_user # TODO: update for v1beta3 changes after rebase: hostnames, external_ips, -- cgit v1.2.3 From 6a4b7a5eb6c4b5e747bab795e2428d7c3992f559 Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Wed, 1 Apr 2015 15:09:19 -0400 Subject: Configuration updates for latest builds and major refactor Configuration updates for latest builds - Switch to using create-node-config - Switch sdn services to use etcd over SSL - This re-uses the client certificate deployed on each node - Additional node registration changes - Do not assume that metadata service is available in openshift_facts module - Call systemctl daemon-reload after installing openshift-master, openshift-sdn-master, openshift-node, openshift-sdn-node - Fix bug overriding openshift_hostname and openshift_public_hostname in byo playbooks - Start moving generated configs to /etc/openshift - Some custom module cleanup - Add known issue with ansible-1.9 to README_OSE.md - Update to genericize the kubernetes_register_node module - Default to use kubectl for commands - Allow for overriding kubectl_cmd - In openshift_register_node role, override kubectl_cmd to openshift_kube - Set default openshift_registry_url for enterprise when deployment_type is enterprise - Fix openshift_register_node for client config change - Ensure that master certs directory is created - Add roles and filter_plugin symlinks to playbooks/common/openshift-master and node - Allow non-root user with sudo nopasswd access - Updates for README_OSE.md - Update byo inventory for adding additional comments - Updates for node cert/config sync to work with non-root user using sudo - Move node config/certs to /etc/openshift/node - Don't use path for mktemp. addresses: https://github.com/openshift/openshift-ansible/issues/154 Create common playbooks - create common/openshift-master/config.yml - create common/openshift-node/config.yml - update playbooks to use new common playbooks - update launch playbooks to call update playbooks - fix openshift_registry and openshift_node_ip usage Set default deployment type to origin - openshift_repo updates for enabling origin deployments - also separate repo and gpgkey file structure - remove kubernetes repo since it isn't currently needed - full deployment type support for bin/cluster - honor OS_DEPLOYMENT_TYPE env variable - add --deployment-type option, which will override OS_DEPLOYMENT_TYPE if set - if neither OS_DEPLOYMENT_TYPE or --deployment-type is set, defaults to origin installs Additional changes: - Add separate config action to bin/cluster that runs ansible config but does not update packages - Some more duplication reduction in cluster playbooks. - Rename task files in playbooks dirs to have tasks in their name for clarity. - update aws/gce scripts to use a directory for inventory (otherwise when there are no hosts returned from dynamic inventory there is an error) libvirt refactor and update - add libvirt dynamic inventory - updates to use dynamic inventory for libvirt --- roles/openshift_register_nodes/defaults/main.yml | 3 - .../library/kubernetes_register_node.py | 63 +++++++++++---------- roles/openshift_register_nodes/tasks/main.yml | 64 ++++++++-------------- roles/openshift_register_nodes/vars/main.yml | 7 +++ 4 files changed, 64 insertions(+), 73 deletions(-) create mode 100644 roles/openshift_register_nodes/vars/main.yml (limited to 'roles/openshift_register_nodes') diff --git a/roles/openshift_register_nodes/defaults/main.yml b/roles/openshift_register_nodes/defaults/main.yml index 3501e8922..a0befab44 100644 --- a/roles/openshift_register_nodes/defaults/main.yml +++ b/roles/openshift_register_nodes/defaults/main.yml @@ -1,5 +1,2 @@ --- openshift_kube_api_version: v1beta1 -openshift_cert_dir: openshift.local.certificates -openshift_cert_dir_parent: /var/lib/openshift -openshift_cert_dir_abs: "{{ openshift_cert_dir_parent ~ '/' ~ openshift_cert_dir }}" diff --git a/roles/openshift_register_nodes/library/kubernetes_register_node.py b/roles/openshift_register_nodes/library/kubernetes_register_node.py index 8ebeb087a..1ec977716 100755 --- a/roles/openshift_register_nodes/library/kubernetes_register_node.py +++ b/roles/openshift_register_nodes/library/kubernetes_register_node.py @@ -97,10 +97,8 @@ class ClientConfigException(Exception): class ClientConfig: def __init__(self, client_opts, module): - _, output, error = module.run_command(["/usr/bin/openshift", "ex", - "config", "view", "-o", - "json"] + client_opts, - check_rc = True) + kubectl = module.params['kubectl_cmd'] + _, output, error = module.run_command(kubectl + ["config", "view", "-o", "json"] + client_opts, check_rc = True) self.config = json.loads(output) if not (bool(self.config['clusters']) or @@ -146,6 +144,9 @@ class ClientConfig: def get_cluster_for_context(self, context): return self.get_value_for_context(context, 'cluster') + def get_namespace_for_context(self, context): + return self.get_value_for_context(context, 'namespace') + class Util: @staticmethod def remove_empty_elements(mapping): @@ -247,15 +248,15 @@ class Node: return Util.remove_empty_elements(node) def exists(self): - _, output, error = self.module.run_command(["/usr/bin/osc", "get", - "nodes"] + self.client_opts, - check_rc = True) + kubectl = self.module.params['kubectl_cmd'] + _, output, error = self.module.run_command(kubectl + ["get", "nodes"] + self.client_opts, check_rc = True) if re.search(self.module.params['name'], output, re.MULTILINE): return True return False def create(self): - cmd = ['/usr/bin/osc'] + self.client_opts + ['create', 'node', '-f', '-'] + kubectl = self.module.params['kubectl_cmd'] + cmd = kubectl + self.client_opts + ['create', '-f', '-'] rc, output, error = self.module.run_command(cmd, data=self.module.jsonify(self.get_node())) if rc != 0: @@ -273,24 +274,26 @@ class Node: def main(): module = AnsibleModule( - argument_spec = dict( - name = dict(required = True, type = 'str'), - host_ip = dict(type = 'str'), - hostnames = dict(type = 'list', default = []), - external_ips = dict(type = 'list', default = []), - internal_ips = dict(type = 'list', default = []), - api_version = dict(type = 'str', default = 'v1beta1', # TODO: after kube rebase, we can default to v1beta3 - choices = ['v1beta1', 'v1beta3']), - cpu = dict(type = 'str'), - memory = dict(type = 'str'), - labels = dict(type = 'dict', default = {}), # TODO: needs documented - annotations = dict(type = 'dict', default = {}), # TODO: needs documented - pod_cidr = dict(type = 'str'), # TODO: needs documented - external_id = dict(type = 'str'), # TODO: needs documented - client_config = dict(type = 'str'), # TODO: needs documented - client_cluster = dict(type = 'str', default = 'master'), # TODO: needs documented - client_context = dict(type = 'str', default = 'master'), # TODO: needs documented - client_user = dict(type = 'str', default = 'admin') # TODO: needs documented + argument_spec = dict( + name = dict(required = True, type = 'str'), + host_ip = dict(type = 'str'), + hostnames = dict(type = 'list', default = []), + external_ips = dict(type = 'list', default = []), + internal_ips = dict(type = 'list', default = []), + api_version = dict(type = 'str', default = 'v1beta1', # TODO: after kube rebase, we can default to v1beta3 + choices = ['v1beta1', 'v1beta3']), + cpu = dict(type = 'str'), + memory = dict(type = 'str'), + labels = dict(type = 'dict', default = {}), # TODO: needs documented + annotations = dict(type = 'dict', default = {}), # TODO: needs documented + pod_cidr = dict(type = 'str'), # TODO: needs documented + external_id = dict(type = 'str'), # TODO: needs documented + client_config = dict(type = 'str'), # TODO: needs documented + client_cluster = dict(type = 'str', default = 'master'), # TODO: needs documented + client_context = dict(type = 'str', default = 'default'), # TODO: needs documented + client_namespace = dict(type = 'str', default = 'default'), # TODO: needs documented + client_user = dict(type = 'str', default = 'system:openshift-client'), # TODO: needs documented + kubectl_cmd = dict(type = 'list', default = ['kubectl']) # TODO: needs documented ), mutually_exclusive = [ ['host_ip', 'external_ips'], @@ -333,14 +336,16 @@ def main(): client_cluster = module.params['client_cluster'] if config.has_cluster(client_cluster): - if client_cluster != config.get_cluster_for_context(client_cluster): + if client_cluster != config.get_cluster_for_context(client_context): client_opts.append("--cluster=%s" % client_cluster) else: module.fail_json(msg="Cluster %s not found in client config" % client_cluster) - # TODO: provide sane defaults for some (like hostname, externalIP, - # internalIP, etc) + client_namespace = module.params['client_namespace'] + if client_namespace != config.get_namespace_for_context(client_context): + client_opts.append("--namespace=%s" % client_namespace) + node = Node(module, client_opts, module.params['api_version'], module.params['name'], module.params['host_ip'], module.params['hostnames'], module.params['external_ips'], diff --git a/roles/openshift_register_nodes/tasks/main.yml b/roles/openshift_register_nodes/tasks/main.yml index 7319b88b1..85f490f70 100644 --- a/roles/openshift_register_nodes/tasks/main.yml +++ b/roles/openshift_register_nodes/tasks/main.yml @@ -3,53 +3,37 @@ # TODO: recreate master/node configs if settings that affect the configs # change (hostname, public_hostname, ip, public_ip, etc) -# TODO: create a failed_when condition -- name: Create node server certificates - command: > - /usr/bin/openshift admin create-server-cert - --overwrite=false - --cert={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/server.crt - --key={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/server.key - --hostnames={{ [item.openshift.common.hostname, - item.openshift.common.public_hostname]|unique|join(",") }} - args: - chdir: "{{ openshift_cert_dir_parent }}" - creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift.common.hostname }}/server.crt" - with_items: openshift_nodes - register: server_cert_result - -# TODO: create a failed_when condition -- name: Create node client certificates - command: > - /usr/bin/openshift admin create-node-cert - --overwrite=false - --cert={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/cert.crt - --key={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/key.key - --node-name={{ item.openshift.common.hostname }} - args: - chdir: "{{ openshift_cert_dir_parent }}" - creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift.common.hostname }}/cert.crt" - with_items: openshift_nodes - register: node_cert_result +# TODO: use a template lookup here # TODO: create a failed_when condition -- name: Create kubeconfigs for nodes +- name: Use enterprise default for openshift_registry_url if not set + set_fact: + openshift_registry_url: "openshift3_beta/ose-${component}:${version}" + when: openshift.common.deployment_type == 'enterprise' and openshift_registry_url is not defined +- name: Create node config command: > - /usr/bin/openshift admin create-kubeconfig - --client-certificate={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/cert.crt - --client-key={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/key.key - --kubeconfig={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}/.kubeconfig - --master={{ openshift.master.api_url }} - --public-master={{ openshift.master.public_api_url }} + /usr/bin/openshift admin create-node-config + --node-dir={{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }} + --node={{ item.openshift.common.hostname }} + --hostnames={{ [item.openshift.common.hostname, item.openshift.common.public_hostname]|unique|join(",") }} + --dns-domain={{ openshift.dns.domain }} + --dns-ip={{ openshift.dns.ip }} + --master={{ openshift.master.api_url }} + --signer-key={{ openshift_master_ca_key }} + --signer-cert={{ openshift_master_ca_cert }} + --certificate-authority={{ openshift_master_ca_cert }} + --signer-serial={{ openshift_master_ca_dir }}/serial.txt + --node-client-certificate-authority={{ openshift_master_ca_cert }} + {{ ('--images=' ~ openshift_registry_url) if openshift_registry_url is defined else '' }} + --listen=https://0.0.0.0:10250 args: - chdir: "{{ openshift_cert_dir_parent }}" - creates: "{{ openshift_cert_dir_abs }}/node-{{ item.openshift.common.hostname }}/.kubeconfig" + chdir: "{{ openshift_cert_parent_dir }}" + creates: "{{ openshift_cert_dir }}/node-{{ item.openshift.common.hostname }}" with_items: openshift_nodes - register: kubeconfig_result - name: Register unregistered nodes kubernetes_register_node: - client_user: openshift-client + kubectl_cmd: ['openshift', 'kube'] name: "{{ item.openshift.common.hostname }}" api_version: "{{ openshift_kube_api_version }}" cpu: "{{ item.openshift.node.resources_cpu | default(None) }}" @@ -61,7 +45,5 @@ external_id: "{{ item.openshift.node.external_id }}" # TODO: support customizing other attributes such as: client_config, # client_cluster, client_context, client_user - # TODO: update for v1beta3 changes after rebase: hostnames, external_ips, - # internal_ips, external_id with_items: openshift_nodes register: register_result diff --git a/roles/openshift_register_nodes/vars/main.yml b/roles/openshift_register_nodes/vars/main.yml new file mode 100644 index 000000000..bd497f08f --- /dev/null +++ b/roles/openshift_register_nodes/vars/main.yml @@ -0,0 +1,7 @@ +--- +openshift_cert_parent_dir: /var/lib/openshift +openshift_cert_relative_dir: openshift.local.certificates +openshift_cert_dir: "{{ openshift_cert_parent_dir }}/{{ openshift_cert_relative_dir }}" +openshift_master_ca_dir: "{{ openshift_cert_dir }}/ca" +openshift_master_ca_cert: "{{ openshift_master_ca_dir }}/cert.crt" +openshift_master_ca_key: "{{ openshift_master_ca_dir }}/key.key" -- cgit v1.2.3 From 2823c990eda7abd19447b988131f2c0eb8a9fdcc Mon Sep 17 00:00:00 2001 From: Wesley Hearn Date: Wed, 22 Apr 2015 10:49:29 -0400 Subject: Use docker-registry.ops when deploying as online --- roles/openshift_register_nodes/tasks/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'roles/openshift_register_nodes') diff --git a/roles/openshift_register_nodes/tasks/main.yml b/roles/openshift_register_nodes/tasks/main.yml index 85f490f70..85bead16d 100644 --- a/roles/openshift_register_nodes/tasks/main.yml +++ b/roles/openshift_register_nodes/tasks/main.yml @@ -10,6 +10,12 @@ set_fact: openshift_registry_url: "openshift3_beta/ose-${component}:${version}" when: openshift.common.deployment_type == 'enterprise' and openshift_registry_url is not defined + +- name: Use online default for openshift_registry_url if not set + set_fact: + openshift_registry_url: "docker-registry.ops.rhcloud.com/openshift3_beta/ose-${component}:${version}" + when: openshift.common.deployment_type == 'online' and openshift_registry_url is not defined + - name: Create node config command: > /usr/bin/openshift admin create-node-config -- cgit v1.2.3 From 720e1b7155f09a659637cc9564cd2e76042345bf Mon Sep 17 00:00:00 2001 From: Jason DeTiberus Date: Fri, 17 Apr 2015 15:50:30 -0400 Subject: Fixes for latest osc client config changes - also pylint fixes --- .../library/kubernetes_register_node.py | 413 ++++++++++++++++----- roles/openshift_register_nodes/tasks/main.yml | 3 +- 2 files changed, 321 insertions(+), 95 deletions(-) (limited to 'roles/openshift_register_nodes') diff --git a/roles/openshift_register_nodes/library/kubernetes_register_node.py b/roles/openshift_register_nodes/library/kubernetes_register_node.py index 1ec977716..afa9eb27d 100755 --- a/roles/openshift_register_nodes/library/kubernetes_register_node.py +++ b/roles/openshift_register_nodes/library/kubernetes_register_node.py @@ -1,12 +1,21 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # vim: expandtab:tabstop=4:shiftwidth=4 +# +# disable pylint checks +# temporarily disabled until items can be addressed: +# fixme - until all TODO comments have been addressed +# permanently disabled unless someone wants to refactor the object model: +# too-few-public-methods +# no-self-use +# too-many-arguments +# too-many-locals +# too-many-branches +# pylint:disable=fixme, too-many-arguments, no-self-use +# pylint:disable=too-many-locals, too-many-branches, too-few-public-methods +"""Ansible module to register a kubernetes node to the cluster""" import os -import multiprocessing -import socket -from subprocess import check_output, Popen -from decimal import * DOCUMENTATION = ''' --- @@ -93,73 +102,170 @@ EXAMPLES = ''' class ClientConfigException(Exception): + """Client Configuration Exception""" pass -class ClientConfig: +class ClientConfig(object): + """ Representation of a client config + + Attributes: + config (dict): dictionary representing the client configuration + + Args: + client_opts (list of str): client options to use + module (AnsibleModule): + + Raises: + ClientConfigException: + """ def __init__(self, client_opts, module): kubectl = module.params['kubectl_cmd'] - _, output, error = module.run_command(kubectl + ["config", "view", "-o", "json"] + client_opts, check_rc = True) + _, output, _ = module.run_command((kubectl + + ["config", "view", "-o", "json"] + + client_opts), check_rc=True) self.config = json.loads(output) if not (bool(self.config['clusters']) or bool(self.config['contexts']) or bool(self.config['current-context']) or bool(self.config['users'])): - raise ClientConfigException(msg="Client config missing required " \ - "values", - output=output) + raise ClientConfigException( + "Client config missing required values: %s" % output + ) def current_context(self): + """ Gets the current context for the client config + + Returns: + str: The current context as set in the config + """ return self.config['current-context'] def section_has_value(self, section_name, value): + """ Test if specified section contains a value + + Args: + section_name (str): config section to test + value (str): value to test if present + Returns: + bool: True if successful, false otherwise + """ section = self.config[section_name] if isinstance(section, dict): return value in section else: val = next((item for item in section - if item['name'] == value), None) + if item['name'] == value), None) return val is not None def has_context(self, context): + """ Test if specified context exists in config + + Args: + context (str): value to test if present + Returns: + bool: True if successful, false otherwise + """ return self.section_has_value('contexts', context) def has_user(self, user): + """ Test if specified user exists in config + + Args: + context (str): value to test if present + Returns: + bool: True if successful, false otherwise + """ return self.section_has_value('users', user) def has_cluster(self, cluster): + """ Test if specified cluster exists in config + + Args: + context (str): value to test if present + Returns: + bool: True if successful, false otherwise + """ return self.section_has_value('clusters', cluster) def get_value_for_context(self, context, attribute): + """ Get the value of attribute in context + + Args: + context (str): context to search + attribute (str): attribute wanted + Returns: + str: The value for attribute in context + """ contexts = self.config['contexts'] if isinstance(contexts, dict): return contexts[context][attribute] else: return next((c['context'][attribute] for c in contexts - if c['name'] == context), None) + if c['name'] == context), None) def get_user_for_context(self, context): + """ Get the user attribute in context + + Args: + context (str): context to search + Returns: + str: The value for the attribute in context + """ return self.get_value_for_context(context, 'user') def get_cluster_for_context(self, context): + """ Get the cluster attribute in context + + Args: + context (str): context to search + Returns: + str: The value for the attribute in context + """ return self.get_value_for_context(context, 'cluster') def get_namespace_for_context(self, context): + """ Get the namespace attribute in context + + Args: + context (str): context to search + Returns: + str: The value for the attribute in context + """ return self.get_value_for_context(context, 'namespace') -class Util: +class Util(object): + """Utility methods""" @staticmethod def remove_empty_elements(mapping): + """ Recursively removes empty elements from a dict + + Args: + mapping (dict): dict to remove empty attributes from + Returns: + dict: A copy of the dict with empty elements removed + """ if isinstance(mapping, dict): - m = mapping.copy() + copy = mapping.copy() for key, val in mapping.iteritems(): if not val: - del m[key] - return m + del copy[key] + return copy else: return mapping -class NodeResources: +class NodeResources(object): + """ Kubernetes Node Resources + + Attributes: + resources (dict): A dictionary representing the node resources + + Args: + version (str): kubernetes api version + cpu (str): string representation of the cpu resources for the node + memory (str): string representation of the memory resources for the + node + """ def __init__(self, version, cpu=None, memory=None): if version == 'v1beta1': self.resources = dict(capacity=dict()) @@ -167,10 +273,31 @@ class NodeResources: self.resources['capacity']['memory'] = memory def get_resources(self): + """ Get the dict representing the node resources + + Returns: + dict: representation of the node resources with any empty + elements removed + """ return Util.remove_empty_elements(self.resources) -class NodeSpec: - def __init__(self, version, cpu=None, memory=None, cidr=None, externalID=None): +class NodeSpec(object): + """ Kubernetes Node Spec + + Attributes: + spec (dict): A dictionary representing the node resources + + Args: + version (str): kubernetes api version + cpu (str): string representation of the cpu resources for the node + memory (str): string representation of the memory resources for the + node + cidr (str): string representation of the cidr block available for + the node + externalID (str): The external id of the node + """ + def __init__(self, version, cpu=None, memory=None, cidr=None, + externalID=None): if version == 'v1beta3': self.spec = dict(podCIDR=cidr, externalID=externalID, capacity=dict()) @@ -178,67 +305,128 @@ class NodeSpec: self.spec['capacity']['memory'] = memory def get_spec(self): + """ Get the dict representing the node spec + + Returns: + dict: representation of the node spec with any empty elements + removed + """ return Util.remove_empty_elements(self.spec) -class NodeStatus: - def addAddresses(self, addressType, addresses): - addressList = [] +class NodeStatus(object): + """ Kubernetes Node Status + + Attributes: + status (dict): A dictionary representing the node status + + Args: + version (str): kubernetes api version + externalIPs (list, optional): externalIPs for the node + internalIPs (list, optional): internalIPs for the node + hostnames (list, optional): hostnames for the node + """ + def add_addresses(self, address_type, addresses): + """ Adds addresses of the specified type + + Args: + address_type (str): address type + addresses (list): addresses to add + """ + address_list = [] for address in addresses: - addressList.append(dict(type=addressType, address=address)) - return addressList + address_list.append(dict(type=address_type, address=address)) + return address_list - def __init__(self, version, externalIPs = [], internalIPs = [], - hostnames = []): + def __init__(self, version, externalIPs=None, internalIPs=None, + hostnames=None): if version == 'v1beta3': - self.status = dict(addresses = addAddresses('ExternalIP', - externalIPs) + - addAddresses('InternalIP', - internalIPs) + - addAddresses('Hostname', - hostnames)) + addresses = [] + if externalIPs is not None: + addresses += self.add_addresses('ExternalIP', externalIPs) + if internalIPs is not None: + addresses += self.add_addresses('InternalIP', internalIPs) + if hostnames is not None: + addresses += self.add_addresses('Hostname', hostnames) + + self.status = dict(addresses=addresses) def get_status(self): + """ Get the dict representing the node status + + Returns: + dict: representation of the node status with any empty elements + removed + """ return Util.remove_empty_elements(self.status) -class Node: - def __init__(self, module, client_opts, version='v1beta1', name=None, - hostIP = None, hostnames=[], externalIPs=[], internalIPs=[], - cpu=None, memory=None, labels=dict(), annotations=dict(), - podCIDR=None, externalID=None): +class Node(object): + """ Kubernetes Node + + Attributes: + status (dict): A dictionary representing the node + + Args: + module (AnsibleModule): + client_opts (list): client connection options + version (str, optional): kubernetes api version + node_name (str, optional): name for node + hostIP (str, optional): node host ip + hostnames (list, optional): hostnames for the node + externalIPs (list, optional): externalIPs for the node + internalIPs (list, optional): internalIPs for the node + cpu (str, optional): cpu resources for the node + memory (str, optional): memory resources for the node + labels (list, optional): labels for the node + annotations (list, optional): annotations for the node + podCIDR (list, optional): cidr block to use for pods + externalID (str, optional): external id of the node + """ + def __init__(self, module, client_opts, version='v1beta1', node_name=None, + hostIP=None, hostnames=None, externalIPs=None, + internalIPs=None, cpu=None, memory=None, labels=None, + annotations=None, podCIDR=None, externalID=None): self.module = module self.client_opts = client_opts if version == 'v1beta1': - self.node = dict(id = name, - kind = 'Node', - apiVersion = version, - hostIP = hostIP, - resources = NodeResources(version, cpu, memory), - cidr = podCIDR, - labels = labels, - annotations = annotations, - externalID = externalID - ) + self.node = dict(id=node_name, + kind='Node', + apiVersion=version, + hostIP=hostIP, + resources=NodeResources(version, cpu, memory), + cidr=podCIDR, + labels=labels, + annotations=annotations, + externalID=externalID) elif version == 'v1beta3': - metadata = dict(name = name, - labels = labels, - annotations = annotations - ) - self.node = dict(kind = 'Node', - apiVersion = version, - metadata = metadata, - spec = NodeSpec(version, cpu, memory, podCIDR, - externalID), - status = NodeStatus(version, externalIPs, - internalIPs, hostnames), - ) + metadata = dict(name=node_name, + labels=labels, + annotations=annotations) + self.node = dict(kind='Node', + apiVersion=version, + metadata=metadata, + spec=NodeSpec(version, cpu, memory, podCIDR, + externalID), + status=NodeStatus(version, externalIPs, + internalIPs, hostnames)) def get_name(self): + """ Get the name for the node + + Returns: + str: node name + """ if self.node['apiVersion'] == 'v1beta1': return self.node['id'] elif self.node['apiVersion'] == 'v1beta3': return self.node['name'] def get_node(self): + """ Get the dict representing the node + + Returns: + dict: representation of the node with any empty elements + removed + """ node = self.node.copy() if self.node['apiVersion'] == 'v1beta1': node['resources'] = self.node['resources'].get_resources() @@ -248,54 +436,82 @@ class Node: return Util.remove_empty_elements(node) def exists(self): + """ Tests if the node already exists + + Returns: + bool: True if node exists, otherwise False + """ kubectl = self.module.params['kubectl_cmd'] - _, output, error = self.module.run_command(kubectl + ["get", "nodes"] + self.client_opts, check_rc = True) + _, output, _ = self.module.run_command((kubectl + ["get", "nodes"] + + self.client_opts), + check_rc=True) if re.search(self.module.params['name'], output, re.MULTILINE): return True return False def create(self): + """ Creates the node + + Returns: + bool: True if node creation successful + """ kubectl = self.module.params['kubectl_cmd'] cmd = kubectl + self.client_opts + ['create', '-f', '-'] - rc, output, error = self.module.run_command(cmd, - data=self.module.jsonify(self.get_node())) - if rc != 0: + exit_code, output, error = self.module.run_command( + cmd, data=self.module.jsonify(self.get_node()) + ) + if exit_code != 0: if re.search("minion \"%s\" already exists" % self.get_name(), error): - self.module.exit_json(changed=False, - msg="node definition already exists", - node=self.get_node()) + self.module.exit_json(msg="node definition already exists", + changed=False, node=self.get_node()) else: - self.module.fail_json(msg="Node creation failed.", rc=rc, - output=output, error=error, - node=self.get_node()) + self.module.fail_json(msg="Node creation failed.", + exit_code=exit_code, + output=output, error=error, + node=self.get_node()) else: return True def main(): + """ main """ module = AnsibleModule( - argument_spec = dict( - name = dict(required = True, type = 'str'), - host_ip = dict(type = 'str'), - hostnames = dict(type = 'list', default = []), - external_ips = dict(type = 'list', default = []), - internal_ips = dict(type = 'list', default = []), - api_version = dict(type = 'str', default = 'v1beta1', # TODO: after kube rebase, we can default to v1beta3 - choices = ['v1beta1', 'v1beta3']), - cpu = dict(type = 'str'), - memory = dict(type = 'str'), - labels = dict(type = 'dict', default = {}), # TODO: needs documented - annotations = dict(type = 'dict', default = {}), # TODO: needs documented - pod_cidr = dict(type = 'str'), # TODO: needs documented - external_id = dict(type = 'str'), # TODO: needs documented - client_config = dict(type = 'str'), # TODO: needs documented - client_cluster = dict(type = 'str', default = 'master'), # TODO: needs documented - client_context = dict(type = 'str', default = 'default'), # TODO: needs documented - client_namespace = dict(type = 'str', default = 'default'), # TODO: needs documented - client_user = dict(type = 'str', default = 'system:openshift-client'), # TODO: needs documented - kubectl_cmd = dict(type = 'list', default = ['kubectl']) # TODO: needs documented + argument_spec=dict( + name=dict(required=True, type='str'), + host_ip=dict(type='str'), + hostnames=dict(type='list', default=[]), + external_ips=dict(type='list', default=[]), + internal_ips=dict(type='list', default=[]), + api_version=dict(type='str', default='v1beta1', + choices=['v1beta1', 'v1beta3']), + cpu=dict(type='str'), + memory=dict(type='str'), + # TODO: needs documented + labels=dict(type='dict', default={}), + # TODO: needs documented + annotations=dict(type='dict', default={}), + # TODO: needs documented + pod_cidr=dict(type='str'), + # TODO: needs documented + external_id=dict(type='str'), + # TODO: needs documented + client_config=dict(type='str'), + # TODO: needs documented + client_cluster=dict(type='str', default='master'), + # TODO: needs documented + client_context=dict(type='str', default='default'), + # TODO: needs documented + client_namespace=dict(type='str', default='default'), + # TODO: needs documented + client_user=dict(type='str', default='system:openshift-client'), + # TODO: needs documented + kubectl_cmd=dict(type='list', default=['kubectl']), + # TODO: needs documented + kubeconfig_flag=dict(type='str'), + # TODO: needs documented + default_client_config=dict(type='str') ), - mutually_exclusive = [ + mutually_exclusive=[ ['host_ip', 'external_ips'], ['host_ip', 'internal_ips'], ['host_ip', 'hostnames'], @@ -303,7 +519,10 @@ def main(): supports_check_mode=True ) - user_has_client_config = os.path.exists(os.path.expanduser('~/.kube/.kubeconfig')) + client_config = '~/.kube/.kubeconfig' + if 'default_client_config' in module.params: + client_config = module.params['default_client_config'] + user_has_client_config = os.path.exists(os.path.expanduser(client_config)) if not (user_has_client_config or module.params['client_config']): module.fail_json(msg="Could not locate client configuration, " "client_config must be specified if " @@ -311,12 +530,17 @@ def main(): client_opts = [] if module.params['client_config']: - client_opts.append("--kubeconfig=%s" % module.params['client_config']) + kubeconfig_flag = '--kubeconfig' + if 'kubeconfig_flag' in module.params: + kubeconfig_flag = module.params['kubeconfig_flag'] + client_opts.append(kubeconfig_flag + '=' + + os.path.expanduser(module.params['client_config'])) try: config = ClientConfig(client_opts, module) - except ClientConfigException as e: - module.fail_json(msg="Failed to get client configuration", exception=e) + except ClientConfigException as ex: + module.fail_json(msg="Failed to get client configuration", + exception=str(ex)) client_context = module.params['client_context'] if config.has_context(client_context): @@ -369,7 +593,8 @@ def main(): module.fail_json(msg="Unknown error creating node", node=node.get_node()) - +# ignore pylint errors related to the module_utils import +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import # import module snippets from ansible.module_utils.basic import * if __name__ == '__main__': diff --git a/roles/openshift_register_nodes/tasks/main.yml b/roles/openshift_register_nodes/tasks/main.yml index 85bead16d..d4d72d126 100644 --- a/roles/openshift_register_nodes/tasks/main.yml +++ b/roles/openshift_register_nodes/tasks/main.yml @@ -39,7 +39,8 @@ - name: Register unregistered nodes kubernetes_register_node: - kubectl_cmd: ['openshift', 'kube'] + kubectl_cmd: ['osc'] + default_client_config: '~/.config/openshift/.config' name: "{{ item.openshift.common.hostname }}" api_version: "{{ openshift_kube_api_version }}" cpu: "{{ item.openshift.node.resources_cpu | default(None) }}" -- cgit v1.2.3