diff --git a/decort-ansible.tar b/decort-ansible.tar new file mode 100644 index 0000000..e31075b Binary files /dev/null and b/decort-ansible.tar differ diff --git a/library/decort_group.py b/library/decort_group.py index f5ea549..8162f0d 100644 --- a/library/decort_group.py +++ b/library/decort_group.py @@ -29,7 +29,7 @@ class decort_group(DecortController): self.result['failed'] = True self.result['changed'] = False self.result['msg'] = ("Cannot find B-service ID {}.").format(arg_amodule.params['bservice_id']) - self.fail_json(**self.result) + self.amodule.fail_json(**self.result) #find group self.bservice_id = validated_bservice_id self.bservice_info = bservice_info diff --git a/library/decort_k8s.py b/library/decort_k8s.py index d64f226..13ea565 100644 --- a/library/decort_k8s.py +++ b/library/decort_k8s.py @@ -132,6 +132,7 @@ class decort_k8s(DecortController): k8s_name=arg_amodule.params['name'], rg_id=validated_rg_id, check_state=False) + if self.k8s_id: self.k8s_should_exist = True self.acc_id = self.k8s_info['accountId'] @@ -165,6 +166,9 @@ class decort_k8s(DecortController): ret_dict['rg_id'] = self.rg_id ret_dict['vins_id'] = self.k8s_vins_id ret_dict['account_id'] = self.acc_id + ret_dict['k8s_Masters'] = self.k8s_info['k8sGroups']['masters'] + ret_dict['k8s_Workers'] = self.k8s_info['k8sGroups']['workers'] + if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED": ret_dict['config'] = self.k8s_getConfig() @@ -206,6 +210,7 @@ class decort_k8s(DecortController): k8s_id = self.k8s_provision(self.amodule.params['name'], self.amodule.params['k8ci_id'], self.amodule.params['rg_id'], + self.amodule.params['vins_id'], self.amodule.params['network_plugin'], self.amodule.params['master_count'], self.amodule.params['master_cpu'], @@ -216,7 +221,17 @@ class decort_k8s(DecortController): self.amodule.params['workers'][0], self.amodule.params['extnet_id'], self.amodule.params['with_lb'], - self.amodule.params['description'],) + self.amodule.params['ha_lb'], + self.amodule.params['additionalSANs'], + self.amodule.params['init_conf'], + self.amodule.params['cluster_conf'], + self.amodule.params['kublet_conf'], + self.amodule.params['kubeproxy_conf'], + self.amodule.params['join_conf'], + self.amodule.params['oidc_cert'], + self.amodule.params['description'], + self.amodule.params['extnet_only'], + ) if not k8s_id: self.result['failed'] = True @@ -241,7 +256,8 @@ class decort_k8s(DecortController): def action(self,disared_state,started=True): - self.k8s_state(self.k8s_info, disared_state,started) + #k8s state + self.k8s_state(self.k8s_info, disared_state, started) self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=self.amodule.params['id'], k8s_name=self.amodule.params['name'], rg_id=self.rg_id, @@ -249,7 +265,11 @@ class decort_k8s(DecortController): if started == True and self.k8s_info['techStatus'] == "STOPPED": self.k8s_state(self.k8s_info, disared_state,started) self.k8s_info['techStatus'] == "STARTED" + #check groups and modify if needed self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers']) + if self.result['changed'] == True: + self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id) + #TODO check workers metadata and modify if needed return @staticmethod @@ -295,23 +315,29 @@ class decort_k8s(DecortController): getConfig=dict(type='bool',required=False, default=False), rg_id=dict(type='int', default=0), rg_name=dict(type='str',default=""), + vins_id=dict(type='int', required=False,default=None), k8ci_id=dict(type='int', required=True), network_plugin=dict(type='str',required=False,default="flannel"), - wg_name=dict(type='str', required=False), master_count=dict(type='int', default=1), master_cpu=dict(type='int', default=2), master_ram=dict(type='int', default=2048), master_disk=dict(type='int', default=10), master_sepid=dict(type='int', required=False, default=None), master_pool=dict(type='str', required=False, default=None), - worker_count=dict(type='int', default=1), - worker_cpu=dict(type='int', default=1), - worker_ram_mb=dict(type='int', default=1024), - worker_disk_gb=dict(type='int', default=10), workers=dict(type='list',required=True), + workers_metadata=dict(type='bool',required=False,default=False), extnet_id=dict(type='int', default=0), description=dict(type='str', default="Created by decort ansible module"), with_lb=dict(type='bool', default=True), + ha_lb=dict(type='bool', default=False), + extnet_only=dict(type='bool', default=False), + additionalSANs=dict(type='list',required=False, default=None), + init_conf=dict(type='dict', required=False, default=None), + cluster_conf=dict(type='str', required=False, default=None), + kublet_conf=dict(type='str', required=False, default=None), + kubeproxy_conf=dict(type='str', required=False, default=None), + join_conf=dict(type='str', required=False, default=None), + oidc_cert=dict(type='raw',required=False,default=None), verify_ssl=dict(type='bool', required=False, default=True), workflow_callback=dict(type='str', required=False), workflow_context=dict(type='str', required=False),) diff --git a/library/decort_kvmvm.py b/library/decort_kvmvm.py index f19aa2f..5753f51 100644 --- a/library/decort_kvmvm.py +++ b/library/decort_kvmvm.py @@ -531,9 +531,7 @@ class decort_kvmvm(DecortController): "shell": '/bin/bash'} ]} elif self.amodule.params['ci_user_data']: - cloud_init_params = {} - for ci_param in self.amodule.params['ci_user_data']: - cloud_init_params.update(ci_param) + cloud_init_params = self.amodule.params['ci_user_data'] else: cloud_init_params = None # if we get through here, all parameters required to create new Compute instance should be at hand diff --git a/library/decort_lb.py b/library/decort_lb.py index 61c35aa..aa2b0c9 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -21,7 +21,6 @@ EXAMPLES = ''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import env_fallback - from ansible.module_utils.decort_utils import * class decort_lb(DecortController): @@ -65,28 +64,28 @@ class decort_lb(DecortController): if not self.rg_id: self.result['failed'] = True self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['vins_id']) - self.fail_json(**self.result) + self.amodule.fail_json(**self.result) if arg_amodule.params['vins_id']: self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id']) if not self.vins_id: self.result['failed'] = True self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id']) - self.fail_json(**self.result) + self.amodule.fail_json(**self.result) elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "": if arg_amodule.params['rg_name']: self.result['failed'] = True self.result['msg'] = ("RG name must be specified with account present") - self.fail_json(**self.result) + self.amodule.fail_json(**self.result) self.acc_id, self.acc_facts = self.account_find(arg_amodule.params['account_name'], arg_amodule.params['account_id']) if not self.acc_id: self.result['failed'] = True self.result['msg'] = ("Current user does not have access to the requested account " "or non-existent account specified.") - self.fail_json(**self.result) + self.amodule.fail_json(**self.result) self.rg_id, self.rg_facts = self.rg_find(self._acc_id,0, arg_rg_name=arg_amodule.params['rg_name']) if self.rg_id and self.vins_id: @@ -97,10 +96,14 @@ class decort_lb(DecortController): self.lb_id = self.lb_provision(self.amodule.params['lb_name'], self.rg_id,self.vins_id, self.amodule.params['ext_net_id'], + self.amodule.params['ha_lb'], self.amodule.params['annotation']) if self.amodule.params['backends'] or self.amodule.params['frontends']: self.lb_id, self.lb_facts = self.lb_find(0,self.amodule.params['lb_name'],self.rg_id) self.lb_update( + self.lb_facts['primaryNode'], + self.lb_facts['frontendHAIP'], + self.lb_facts['backendHAIP'], self.lb_facts['backends'], self.lb_facts['frontends'], self.amodule.params['backends'], @@ -117,11 +120,14 @@ class decort_lb(DecortController): self.lb_facts['techStatus'] = "STARTED" self.lb_update( + self.lb_facts['primaryNode'], + self.lb_facts['frontendHAIP'], + self.lb_facts['backendHAIP'], self.lb_facts['backends'], self.lb_facts['frontends'], self.amodule.params['backends'], self.amodule.params['servers'], - self.amodule.params['frontends'] + self.amodule.params['frontends'], ) if d_state != '': @@ -243,6 +249,7 @@ class decort_lb(DecortController): verify_ssl=dict(type='bool', required=False, default=True), lb_id=dict(type='int', required=False, default=0), lb_name=dict(type='str', required=True), + ha_lb=dict(type='bool', required=False, default=False), backends=dict(type='list',required=False,default=[]), frontends=dict(type='list',required=False,default=[]), servers=dict(type='list',required=False,default=[]), diff --git a/library/decort_vins.py b/library/decort_vins.py index 65629b2..49447cf 100644 --- a/library/decort_vins.py +++ b/library/decort_vins.py @@ -260,7 +260,7 @@ class decort_vins(DecortController): if self.vins_id == 0: self.result['failed'] = True self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id']) - self.fail_json(**self.result) + self.amodule.fail_json(**self.result) self.vins_level = "ID" #raise Exception(self.vins_facts) validated_acc_id = self.vins_facts['accountId'] diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 55de31f..70c2765 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -335,7 +335,7 @@ class DecortController(object): return True - def decort_api_call(self, arg_req_function, arg_api_name, arg_params): + def decort_api_call(self, arg_req_function, arg_api_name, arg_params, arg_files=None): """Wrapper around DECORT API calls. It uses authorization mode and credentials validated at the class instance creation to properly format API call and send it to the DECORT controller URL. If connection errors are detected, it aborts execution of the script and relay error messages to upstream @@ -366,7 +366,12 @@ class DecortController(object): while retry_counter > 0: try: - api_resp = arg_req_function(req_url, params=arg_params, headers=http_headers, verify=self.verify_ssl) + api_resp = arg_req_function( + req_url, + files=arg_files, + params=arg_params, + headers=http_headers, + verify=self.verify_ssl) except requests.exceptions.ConnectionError: self.result['failed'] = True self.result['msg'] = "Failed to connect to '{}' when calling DECORT API.".format(api_resp.url) @@ -801,7 +806,8 @@ class DecortController(object): sepId=sep_id, pool=pool_name, start=start_on_create, # start_machine parameter requires DECORT API ver 3.3.1 or higher - interfaces='[]') # we create VM without any network connections + interfaces='[]'# we create VM without any network connections + ) if userdata: api_params['userdata'] = json.dumps(userdata) # we need to pass a string object as "userdata" @@ -3209,8 +3215,12 @@ class DecortController(object): ret_rules = self._pfw_get(comp_facts['id'], vins_facts['id']) return ret_rules - - def _k8s_get_by_id(self, k8s_id): +############################## +# +# K8s management +# +############################## + def k8s_get_by_id(self, k8s_id): """Helper function that locates k8s by ID and returns k8s facts. @param (int) k8s_id: ID of the k8s to find and return facts for. @@ -3230,19 +3240,13 @@ class DecortController(object): api_params = dict(k8sId=k8s_id, ) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/get", api_params) if api_resp.status_code == 200: - ret_k8s_id = k8s_id ret_k8s_dict = json.loads(api_resp.content.decode('utf8')) else: self.result['warning'] = ("k8s_get_by_id(): failed to get k8s by ID {}. HTTP code {}, " "response {}.").format(k8s_id, api_resp.status_code, api_resp.reason) - return ret_k8s_id, ret_k8s_dict + return ret_k8s_dict - ############################## - # - # K8s management - # - ############################## def k8s_find(self, k8s_id, k8s_name="",rg_id=0,check_state=True): """Returns non zero k8s ID and a dictionary with k8s details on success, 0 and empty dictionary otherwise. This method does not fail the run if k8s cannot be located by its name (arg_k8s_name), because this could be @@ -3259,41 +3263,30 @@ class DecortController(object): @return: dictionary with k8s facts if k8s is present. Empty dictionary otherwise. None on error. """ - # Resource group can be in one of the following states: - # MODELED, CREATED, DISABLING, DISABLED, ENABLING, DELETING, DELETED, DESTROYED, DESTROYED - # - # Transient state (ending with ING) are invalid from k8s manipulation viewpoint - # - K8S_INVALID_STATES = ["MODELED","DESTROYED","DESTROYING"] self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_find") ret_k8s_id = 0 - api_params = dict(includedeleted=True) + api_params = dict(includedeleted=True,name=k8s_name,rgId=rg_id) ret_k8s_dict = None - if k8s_id: - ret_k8s_id, ret_k8s_dict = self._k8s_get_by_id(k8s_id) - if not ret_k8s_id: - self.result['failed'] = True - self.result['msg'] = "k8s_find(): cannot find k8s cluster by ID {}.".format(k8s_id) - self.amodule.fail_json(**self.result) - else: - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/list", api_params) - if api_resp.status_code == 200: - k8s_list = json.loads(api_resp.content.decode('utf8')) - for k8s_item in k8s_list['data']: - if k8s_item['name'] == k8s_name and k8s_item['rgId'] == rg_id: - if not check_state or k8s_item['status'] not in K8S_INVALID_STATES: - # TODO: rework after k8s/get wilb be updated - self.k8s_vins_id = None - self.k8s_vins_id = k8s_item['vinsId'] - # - ret_k8s_id = k8s_item['id'] - _, ret_k8s_dict = self._k8s_get_by_id(ret_k8s_id) - + + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/list", api_params) + + if api_resp.status_code == 200: + k8s_list = json.loads(api_resp.content.decode('utf8')) + + if k8s_list['entryCount'] == 0: + return None,None + + for k8s_item in k8s_list['data']: + if not check_state or k8s_item['status'] not in K8S_INVALID_STATES: + ret_k8s_id = k8s_item['id'] + ret_k8s_dict = self.k8s_get_by_id(ret_k8s_id) + self.k8s_vins_id = k8s_item['vinsId'] + return ret_k8s_id, ret_k8s_dict def k8s_state(self, arg_k8s_dict, arg_desired_state, arg_started=False): @@ -3414,7 +3407,9 @@ class DecortController(object): return def k8s_provision(self, k8s_name, - k8ci_id,rg_id, + k8ci_id, + rg_id, + vins_id, plugin, master_count, master_cpu, @@ -3424,8 +3419,18 @@ class DecortController(object): master_pool, default_worker, extnet_id, - with_lb, - annotation, ): + with_lb, + ha_lb, + sans, + init_conf, + cluster_conf, + kublet_conf, + kubeproxy_conf, + join_conf, + oidc_cert, + annotation, + extnet_only, + ): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_provision") @@ -3441,14 +3446,17 @@ class DecortController(object): def_wg_disk = default_worker['disk'] def_wg_sepid = default_worker['sep_id'] def_wg_pool = default_worker['pool'] + def_wg_ud = json.dumps(default_worker['ci_user_data']) if \ + "ci_user_data" in default_worker else None def_wg_lab = default_worker['labels'] if "labels" in default_worker else None def_wg_taints = default_worker['taints'] if "taints" in default_worker else None def_wg_ann = default_worker['annotations'] if "annotations" in default_worker else None - + api_url = "/restmachine/cloudapi/k8s/create" api_params = dict(name=k8s_name, rgId=rg_id, k8ciId=k8ci_id, + vins_Id=vins_id, workerGroupName=def_wg_name, networkPlugin=plugin, masterNum=master_count, @@ -3468,9 +3476,22 @@ class DecortController(object): annotations=def_wg_ann, extnetId=extnet_id, withLB=with_lb, + highlyAvailableLB=ha_lb, + additionalSANs=sans, + initConfiguration=json.dumps(init_conf) if init_conf else None, + clusterConfiguration=json.dumps(cluster_conf) if cluster_conf else None, + kubeletConfiguration=json.dumps(kublet_conf) if kublet_conf else None, + kubeProxyConfiguration=json.dumps(kubeproxy_conf)if kubeproxy_conf else None, + joinConfiguration=json.dumps(join_conf)if join_conf else None, desc=annotation, + userData=def_wg_ud, + extnetOnly=extnet_only, ) - api_resp = self.decort_api_call(requests.post, api_url, api_params) + + if oidc_cert: + upload_files = {'oidcCertificate': ('cert.pem', str(oidc_cert),'application/x-x509-ca-cert')} + + api_resp = self.decort_api_call(requests.post, api_url, api_params, upload_files) k8s_id = "" if api_resp.status_code == 200: for i in range(300): @@ -3490,7 +3511,7 @@ class DecortController(object): self.result['changed'] = False return elif ret_info['status'] == "OK": - k8s_id = ret_info['result'] + k8s_id = ret_info['result'][0] self.result['msg'] = f"k8s_provision(): K8s cluster {k8s_name} created successful" self.result['changed'] = True return k8s_id @@ -3564,6 +3585,7 @@ class DecortController(object): labels=wg['labels'] if "labels" in wg else None, taints=wg['taints'] if "taints" in wg else None, annotations=wg['annotations'] if "annotations" in wg else None, + userData=json.dumps(wg['ci_user_data']) if 'ci_user_data' in wg else None ) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/workersGroupAdd", api_params) self.result['changed'] = True @@ -4054,7 +4076,7 @@ class DecortController(object): self.amodule.fail_json(**self.result) return 0, None - def lb_provision(self,lb_name,rg_id,vins_id,ext_net_id,annotation,start=True): + def lb_provision(self,lb_name,rg_id,vins_id,ext_net_id,ha_status,annotation,start=True): """Provision LB according to the specified arguments. If critical error occurs the embedded call to API function will abort further execution of the script and relay error to Ansible. @@ -4081,6 +4103,7 @@ class DecortController(object): rgId=rg_id, extnetId=ext_net_id, vinsId=vins_id, + highlyAvailable=ha_status, start=start, decs=annotation ) @@ -4392,6 +4415,9 @@ class DecortController(object): return def _lb_bind_frontend(self,front_name,bind_name,bind_addr=None,bind_port=None,update=False): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_bind_frontend") + api_params = dict( lbId=self.lb_id, frontendName=front_name, @@ -4406,7 +4432,9 @@ class DecortController(object): api_resp = self.decort_api_call(requests.post, api_url, api_params) self.result['changed'] = True - def lb_update(self,lb_backends=[],lb_frontends=[],mod_backends=[],mod_servers=[],mod_frontends=[]): + def lb_update(self,prime,front_ha_ip,back_ha_ip,lb_backends=[],lb_frontends=[],mod_backends=[],mod_servers=[],mod_frontends=[]): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_update") #lists from module and cloud mod_backs_list = [back['name'] for back in mod_backends] lb_backs_list = [back['name'] for back in lb_backends] @@ -4449,16 +4477,30 @@ class DecortController(object): if del_list_fronts: self._lb_delete_fronts(del_list_fronts) + #set bind_ip + if front_ha_ip != "": + bind_ip = front_ha_ip + + if front_ha_ip == "" and back_ha_ip != "": + bind_ip = back_ha_ip + + if front_ha_ip == "" and back_ha_ip == "": + if prime["frontendIp"] != "": + bind_ip = prime["frontendIp"] + else: + bind_ip = prime["backendIp"] + if add_list_fronts: - self._lb_add_fronts(add_list_fronts,mod_frontends) + self._lb_add_fronts(add_list_fronts,mod_frontends,bind_ip) if upd_front_list: - self._lb_update_fronts(upd_front_list,lb_frontends,mod_frontends) + self._lb_update_fronts(upd_front_list,lb_frontends,mod_frontends,bind_ip) return def _lb_delete_backends(self,back_list,lb_fronts): #delete frontends with that backend + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_delete_backends") for back in back_list: fronts = list(filter(lambda i: i['backend'] == back,lb_fronts)) if fronts: @@ -4471,6 +4513,9 @@ class DecortController(object): self.result['changed'] = True return def _lb_delete_fronts(self,d_fronts): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_delete_fronts") + for front in d_fronts: api_params = dict( lbId=self.lb_id, @@ -4483,7 +4528,10 @@ class DecortController(object): self.result['changed'] = True return - def _lb_add_fronts(self,front_list,mod_fronts): + def _lb_add_fronts(self,front_list,mod_fronts,bind_ip): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_add_fronts") + for front in front_list: add_front, = list(filter(lambda i: i['name'] == front,mod_fronts)) api_params = dict( @@ -4496,14 +4544,17 @@ class DecortController(object): self._lb_bind_frontend( add_front['name'], bind['name'], - bind['address']if "address" in bind else None, - bind['port'] if "port" in bind else None, + bind['address']if "address" in bind else bind_ip, + bind['port'], ) return def _lb_create_backends(self,back_list,mod_backs,mod_serv): ''' Create backends and add servers to them ''' + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_create_backends") + for back in back_list: backend, = list(filter(lambda i: i['name'] == back,mod_backs)) api_params = dict( @@ -4536,15 +4587,14 @@ class DecortController(object): def _lb_update_backends(self,back_list,lb_backs,mod_backs,mod_serv): + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_update_backends") + lb_backs = list(filter(lambda i: i['name'] in back_list,lb_backs)) - #mod_back = list(filter(lambda i: i['name'] in back_list,mod_backs)) for back in lb_backs: del back['serverDefaultSettings']['guid'] mod_back, = list(filter(lambda i: i['name']==back['name'],mod_backs)) - #mod_servers = list(filter(lambda i: i['name']==back['name'],mod_serv)) - #raise Exception(mod_servers) if "default_settings" not in mod_back: mod_back["default_settings"] = self.default_settings else: @@ -4619,7 +4669,9 @@ class DecortController(object): api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerDelete", api_params) self.result['changed'] = True return - def _lb_update_fronts(self,upd_front_list,lb_frontends,mod_frontends): + def _lb_update_fronts(self,upd_front_list,lb_frontends,mod_frontends,bind_ip): + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_update_fronts") for front in upd_front_list: mod_front, = list(filter(lambda i: i['name'] == front,mod_frontends)) @@ -4631,8 +4683,8 @@ class DecortController(object): self._lb_bind_frontend( front, bind['name'], - bind['address']if "address" in bind else None, - bind['port'] if "port" in bind else None, + bind['address']if "address" in bind else bind_ip, + bind['port'], ) else: lb_bind, = list(filter(lambda i: i['name'] == bind['name'],lb_front['bindings'])) @@ -4642,8 +4694,8 @@ class DecortController(object): self._lb_bind_frontend( front, bind['name'], - bind['address']if "address" in bind else None, - bind['port'] if "port" in bind else None, + bind['address'] if "address" in bind else bind_ip, + bind['port'], update=True, ) lb_binds_list.remove(bind['name'])