summaryrefslogtreecommitdiffstats
path: root/ansible/library
diff options
context:
space:
mode:
authorPetr Ospalý <p.ospaly@partner.samsung.com>2018-12-19 13:54:09 +0100
committerPetr Ospalý <p.ospaly@partner.samsung.com>2018-12-19 13:56:09 +0100
commita0eff59859eba3afe8f4c8966f768e14f41aaf96 (patch)
treeabe36b69cbce9f859f09d0be98529f22f03111a7 /ansible/library
parent3a2d79f398d9b2b89f55419dd771fcee7198dc4a (diff)
Add ansible library for creating the k8n env.
Issue-ID: OOM-1551 Change-Id: I485f58e560086345bd27a4a7f94d2651a0cd357e Signed-off-by: Petr Ospalý <p.ospaly@partner.samsung.com>
Diffstat (limited to 'ansible/library')
-rw-r--r--ansible/library/rancher_k8s_environment.py341
1 files changed, 341 insertions, 0 deletions
diff --git a/ansible/library/rancher_k8s_environment.py b/ansible/library/rancher_k8s_environment.py
new file mode 100644
index 00000000..d3d8ac02
--- /dev/null
+++ b/ansible/library/rancher_k8s_environment.py
@@ -0,0 +1,341 @@
+#!/usr/bin/python
+
+DOCUMENTATION='''
+---
+module: rancher_k8s_environment
+description:
+ - This module will create or delete Kubernetes environment.
+ - It will also delete other environments when variables are set accordingly.
+notes:
+ - It identifies environment only by name. Expect problems with same named environments.
+ - All hosts running Kubernetes cluster should have same OS otherwise there
+ is possibility of misbehavement.
+options:
+ server:
+ required: true
+ description:
+ - Url of rancher server i.e. "http://10.0.0.1:8080".
+ name:
+ required: true
+ descritpion:
+ - Name of the environment to create/remove.
+ descr:
+ description:
+ - Description of environment to create.
+ state:
+ description:
+ - If "present" environment will be created or setup depending if it exists.
+ With multiple environments with same name expect error.
+ If "absent" environment will be removed. If multiple environments have same
+ name all will be deleted.
+ default: present
+ choices: [present, absent]
+ delete_not_k8s:
+ description:
+ - Indicates if environments with different orchestration than Kubernetes should
+ be deleted.
+ type: bool
+ default: yes
+ delete_other_k8s:
+ description:
+ - Indicates if environments with different name than specified should
+ be deleted.
+ type: bool
+ default: no
+ force:
+ description:
+ - Indicates if environment should be deleted and recreated.
+ type: bool
+ default: yes
+ host_os:
+ required: true
+ description:
+ - OS (family from ansible_os_family variable) of the hosts running cluster. If
+ "RedHat" then datavolume fix will be applied.
+ Fix described here:
+ https://github.com/rancher/rancher/issues/10015
+'''
+
+import json
+import time
+
+import requests
+from ansible.module_utils.basic import AnsibleModule
+
+
+
+def get_existing_environments(rancher_address):
+ req = requests.get('{}/v2-beta/projects'.format(rancher_address))
+ envs = req.json()['data']
+ return envs
+
+
+def not_k8s_ids(environments):
+ envs = filter(lambda x: x['orchestration'] != 'kubernetes', environments)
+ return [env['id'] for env in envs]
+
+
+def other_k8s_ids(environments, name):
+ envs = filter(lambda x: x['orchestration'] == 'kubernetes' and x['name'] != name,
+ environments)
+ return [env['id'] for env in envs]
+
+
+def env_ids_by_name(environments, name):
+ envs = filter(lambda x: x['name'] == name, environments)
+ return [env['id'] for env in envs]
+
+
+def env_info_by_id(environments, env_id):
+ env = filter(lambda x: x['id'] == env_id, environments)
+ return [{'id': x['id'], 'name': x['name']} for x in env][0]
+
+
+def delete_multiple_environments(rancher_address, env_ids):
+ deleted = []
+ for env_id in env_ids:
+ deleted.append(delete_environment(rancher_address, env_id))
+ return deleted
+
+
+def delete_environment(rancher_address, env_id):
+ req = requests.delete('{}/v2-beta/projects/{}'.format(rancher_address, env_id))
+ deleted = req.json()['data'][0]
+ return {'id': deleted['id'],
+ 'name': deleted['name'],
+ 'orchestration': deleted['orchestration']}
+
+
+def create_k8s_environment(rancher_address, name, descr):
+ k8s_template_id = None
+ for _ in range(10):
+ k8s_template = requests.get(
+ '{}/v2-beta/projecttemplates?name=Kubernetes'.format(rancher_address)).json()
+ if k8s_template['data']:
+ k8s_template_id = k8s_template['data'][0]['id']
+ break
+ time.sleep(3)
+ if k8s_template_id is None:
+ raise ValueError('Template for kubernetes not found.')
+ body = {
+ 'name': name,
+ 'description': descr,
+ 'projectTemplateId': k8s_template_id,
+ 'allowSystemRole': False,
+ 'members': [],
+ 'virtualMachine': False,
+ 'servicesPortRange': None,
+ 'projectLinks': []
+ }
+
+ body_json = json.dumps(body)
+ req = requests.post('{}/v2-beta/projects'.format(rancher_address), data=body_json)
+ created = req.json()
+ return {'id': created['id'], 'name': created['name']}
+
+
+def get_kubelet_service(rancher_address, env_id):
+ for _ in range(10):
+ response = requests.get(
+ '{}/v2-beta/projects/{}/services/?name=kubelet'.format(rancher_address,
+ env_id))
+
+ if response.status_code >= 400:
+ # too early or too late for obtaining data
+ # small delay will improve our chances to collect it
+ time.sleep(1)
+ continue
+
+ content = response.json()
+
+ if content['data']:
+ return content['data'][0]
+
+ # this is unfortunate, response from service api received but data
+ # not available, lets try again
+ time.sleep(5)
+
+ return None
+
+
+def fix_datavolume_rhel(rancher_address, env_id):
+ kubelet_svc = get_kubelet_service(rancher_address, env_id)
+ if kubelet_svc:
+ try:
+ data_volume_index = kubelet_svc['launchConfig']['dataVolumes'].index(
+ '/sys:/sys:ro,rprivate')
+ except ValueError:
+ return 'Already changed'
+ kubelet_svc['launchConfig']['dataVolumes'][
+ data_volume_index] = '/sys/fs/cgroup:/sys/fs/cgroup:ro,rprivate'
+ body = {
+ 'inServiceStrategy': {
+ 'batchSize': 1,
+ 'intervalMillis': 2000,
+ 'startFirst': False,
+ 'launchConfig': kubelet_svc['launchConfig'],
+ 'secondaryLaunchConfigs': []
+ }
+ }
+ body_json = json.dumps(body)
+ requests.post(
+ '{}/v2-beta/projects/{}/services/{}?action=upgrade'.format(rancher_address,
+ env_id,
+ kubelet_svc[
+ 'id']),
+ data=body_json)
+ for _ in range(10):
+ req_svc = requests.get(
+ '{}/v2-beta/projects/{}/services/{}'.format(rancher_address, env_id,
+ kubelet_svc['id']))
+ req_svc_content = req_svc.json()
+ if 'finishupgrade' in req_svc_content['actions']:
+ req_finish = requests.post(
+ req_svc_content['actions']['finishupgrade'])
+ return {
+ 'dataVolumes': req_finish.json()['upgrade']['inServiceStrategy'][
+ 'launchConfig']['dataVolumes']}
+ time.sleep(5)
+ else:
+ raise ValueError('Could not get kubelet service')
+
+
+def create_registration_tokens(rancher_address, env_id):
+ body = {'name': str(env_id)}
+ body_json = json.dumps(body)
+ response = requests.post(
+ '{}/v2-beta/projects/{}/registrationtokens'.format(rancher_address, env_id,
+ data=body_json))
+ for _ in range(10):
+ tokens = requests.get(response.json()['links']['self'])
+ tokens_content = tokens.json()
+ if tokens_content['image'] is not None and tokens_content[
+ 'registrationUrl'] is not None:
+ return {'image': tokens_content['image'],
+ 'reg_url': tokens_content['registrationUrl']}
+ time.sleep(3)
+ return None
+
+
+def get_registration_tokens(rancher_address, env_id):
+ reg_tokens = requests.get(
+ '{}/v2-beta/projects/{}/registrationtokens'.format(rancher_address, env_id))
+ reg_tokens_content = reg_tokens.json()
+ tokens = reg_tokens_content['data']
+ if not tokens:
+ return None
+ return {'image': tokens[0]['image'], 'reg_url': tokens[0]['registrationUrl']}
+
+
+def create_apikey(rancher_address, env_id):
+ body = {
+ 'name': 'kubectl_env_{}'.format(env_id),
+ 'description': "Provides access to kubectl"
+ }
+ body_json = json.dumps(body)
+ apikey_req = requests.post(
+ '{}/v2-beta/apikey'.format(rancher_address, env_id, data=body_json))
+ apikey_content = apikey_req.json()
+ return {'public': apikey_content['publicValue'],
+ 'private': apikey_content['secretValue']}
+
+
+def run_module():
+ module = AnsibleModule(
+ argument_spec=dict(
+ server=dict(type='str', required=True),
+ name=dict(type='str', required=True),
+ descr=dict(type='str'),
+ state=dict(type='str', choices=['present', 'absent'], default='present'),
+ delete_other_k8s=dict(type='bool', default=False),
+ delete_not_k8s=dict(type='bool', default=True),
+ force=dict(type='bool', default=True),
+ host_os=dict(type='str', required=True)
+ )
+ )
+
+ params = module.params
+ rancher_address = params['server']
+ name = params['name']
+ descr = params['descr']
+ delete_not_k8s = params['delete_not_k8s']
+ delete_other_k8s = params['delete_other_k8s']
+ force = params['force']
+ host_os = params['host_os']
+ state = params['state']
+
+ existing_envs = get_existing_environments(rancher_address)
+ same_name_ids = env_ids_by_name(existing_envs, name)
+
+ to_delete_ids = []
+ changes = {}
+
+ if delete_other_k8s:
+ to_delete_ids += other_k8s_ids(existing_envs, name)
+
+ if delete_not_k8s:
+ to_delete_ids += not_k8s_ids(existing_envs)
+ if force or state == 'absent':
+ to_delete_ids += same_name_ids
+
+ deleted = delete_multiple_environments(rancher_address, to_delete_ids)
+
+ if deleted:
+ changes['deleted'] = deleted
+ if state == 'absent':
+ module.exit_json(changed=True, deleted=changes['deleted'])
+ else:
+ if state == 'absent':
+ module.exit_json(changed=False)
+
+ if len(same_name_ids) > 1 and not force:
+ module.fail_json(msg='Multiple environments with same name. '
+ 'Use "force: yes" to delete '
+ 'all environments with same name.')
+
+ if same_name_ids and not force:
+ changes['environment'] = env_info_by_id(existing_envs, same_name_ids[0])
+ if host_os == 'RedHat':
+ try:
+ rhel_fix = fix_datavolume_rhel(rancher_address, same_name_ids[0])
+ changes['rhel_fix'] = rhel_fix
+ except ValueError as err:
+ module.fail_json(
+ msg='Error: {} Try to recreate k8s environment.'.format(err))
+
+ reg_tokens = get_registration_tokens(rancher_address, same_name_ids[0])
+ if not reg_tokens:
+ reg_tokens = create_registration_tokens(rancher_address, same_name_ids[0])
+ changes['registration_tokens'] = reg_tokens
+
+ apikey = create_apikey(rancher_address, same_name_ids[0])
+ changes['apikey'] = apikey
+ module.exit_json(changed=True, data=changes,
+ msg='New environment was not created. Only set up was done')
+ try:
+ new_env = create_k8s_environment(rancher_address, name, descr)
+ except ValueError as err:
+ module.fail_json(msg='Error: {} Try to recreate k8s environment.'.format(err))
+
+ if host_os == 'RedHat':
+ try:
+ rhel_fix = fix_datavolume_rhel(rancher_address, new_env['id'])
+ changes['rhel_fix'] = rhel_fix
+ except ValueError as err:
+ module.fail_json(msg='Error: {} Try to recreate k8s environment.'.format(
+ err))
+
+ reg_tokens = create_registration_tokens(rancher_address, new_env['id'])
+
+ apikey = create_apikey(rancher_address, new_env['id'])
+
+ changes['environment'] = new_env
+ changes['registration_tokens'] = reg_tokens
+ changes['apikey'] = apikey
+
+ module.exit_json(changed=True, data=changes)
+
+
+if __name__ == '__main__':
+ run_module()
+