From e26011ab20fd20a61287d6d05189326afab64ac2 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 18 Mar 2024 19:27:32 +0300 Subject: [PATCH 01/82] bug fix --- library/decort_rg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_rg.py b/library/decort_rg.py index 0ebce2f..78260e6 100644 --- a/library/decort_rg.py +++ b/library/decort_rg.py @@ -226,7 +226,7 @@ class decort_rg(DecortController): 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) if amodule.params['rg_id'] > 0: self.validated_rg_id = amodule.params['rg_id'] From d622dd84535899d0c93438c6577555de9a1b853b Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 20 Mar 2024 14:58:02 +0300 Subject: [PATCH 02/82] fix account vins find logic --- module_utils/decort_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 70c2765..0a2c1d0 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -2250,9 +2250,9 @@ class DecortController(object): # self.result['msg'] = "vins_find(): cannot find Account ID {}.".format(account_id) # self.amodule.fail_json(**self.result) # NOTE: account's 'vins' attribute does not list destroyed ViNSes! - for runner in rg_facts['vins']: - # api_params['vinsId'] = runner - ret_vins_id, ret_vins_facts = self._vins_get_by_id(runner) + account_vinses = self._get_all_account_vinses(account_id) + for vins in account_vinses: + ret_vins_id, ret_vins_facts = self._vins_get_by_id(vins['id']) if ret_vins_id and ret_vins_facts['name'] == vins_name: if not check_state or ret_vins_facts['status'] not in VINS_INVALID_STATES: return ret_vins_id, ret_vins_facts From 058de4884f1ff3d7a2e13601711d53d79d6d2816 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 22 Mar 2024 13:39:06 +0300 Subject: [PATCH 03/82] exclude call of compute_bootdisk_size method with new_size=None --- library/decort_kvmvm.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/decort_kvmvm.py b/library/decort_kvmvm.py index 5753f51..d5e99c5 100644 --- a/library/decort_kvmvm.py +++ b/library/decort_kvmvm.py @@ -624,7 +624,11 @@ class decort_kvmvm(DecortController): Note that it does not modify power state of KVM VM. """ self.compute_networks(self.comp_info, self.amodule.params['networks']) - self.compute_bootdisk_size(self.comp_info, self.amodule.params['boot_disk']) + + boot_disk_new_size = self.amodule.params['boot_disk'] + if boot_disk_new_size: + self.compute_bootdisk_size(self.comp_info, boot_disk_new_size) + self.compute_data_disks(self.comp_info, self.amodule.params['data_disks']) self.compute_resize(self.comp_info, self.amodule.params['cpu'], self.amodule.params['ram'], From 1c6b46c535164a611b862895fc85764aa2ac6f88 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 22 Mar 2024 17:01:50 +0300 Subject: [PATCH 04/82] remove new_ram value conversion from GB to MB --- module_utils/decort_utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 70c2765..b11c299 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1102,10 +1102,6 @@ class DecortController(object): elif not new_ram: new_ram = comp_dict['ram'] - # stupid hack? - if new_ram > 1 and new_ram < 512: - new_ram = new_ram * 1024 - if comp_dict['cpus'] == new_cpu and comp_dict['ram'] == new_ram: # no need to call API in this case, as requested size is not different from the current one self.result['failed'] = False From 21e853c1f21d3b4c3c97a403fe4456ae2aa7e63a Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 22 Mar 2024 18:42:44 +0300 Subject: [PATCH 05/82] Add response text to error msg of decort_api_call method --- module_utils/decort_utils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 70c2765..f6c3499 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -392,10 +392,11 @@ class DecortController(object): retry_counter = retry_counter - 1 else: self.result['failed'] = True - self.result['msg'] = ("Error when calling DECORT API '{}', HTTP status code '{}', " - "reason '{}', parameters '{}'.").format(api_resp.url, - api_resp.status_code, - api_resp.reason, arg_params) + self.result['msg'] =\ + (f'Error when calling DECORT API {api_resp.url}' + f', HTTP status code {api_resp.status_code}' + f', reason "{api_resp.reason}"' + f', parameters {arg_params}, text {api_resp.text}.') self.amodule.fail_json(**self.result) return None # actually, this directive will never be executed as fail_json aborts the script From b18bdef269e6be80f920f69e0669c4cc01adcbd7 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 22 Mar 2024 18:54:57 +0300 Subject: [PATCH 06/82] fix code style --- module_utils/decort_utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index f6c3499..7322838 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -392,11 +392,12 @@ class DecortController(object): retry_counter = retry_counter - 1 else: self.result['failed'] = True - self.result['msg'] =\ - (f'Error when calling DECORT API {api_resp.url}' - f', HTTP status code {api_resp.status_code}' - f', reason "{api_resp.reason}"' - f', parameters {arg_params}, text {api_resp.text}.') + self.result['msg'] = ( + f'Error when calling DECORT API {api_resp.url}' + f', HTTP status code {api_resp.status_code}' + f', reason "{api_resp.reason}"' + f', parameters {arg_params}, text {api_resp.text}.' + ) self.amodule.fail_json(**self.result) return None # actually, this directive will never be executed as fail_json aborts the script From 1304a0fcbfd1db177d75ad19fb3fc2f752c65aad Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 3 Apr 2024 11:00:56 +0300 Subject: [PATCH 07/82] Add call of API /cloudapi/rg/getResourceConsumption to DecortController._rg_get_by_id and update its logic and code style --- module_utils/decort_utils.py | 45 ++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 6d04c75..414a425 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1561,9 +1561,10 @@ class DecortController(object): @param (int) )rg_id: ID of the RG to find and return facts for. - @return: RG ID and a dictionary of RG facts as provided by rg/get API call. Note that if it fails - to find the RG with the specified ID, it may return 0 for ID and empty dictionary for the facts. So - it is suggested to check the return values accordingly. + @return: RG ID and a dictionary of RG facts as provided by rg/get + API call. Note that if it fails to find the RG with the specified ID, + it may return 0 for ID and empty dictionary for the facts. So it is + suggested to check the return values accordingly. """ ret_rg_id = 0 ret_rg_dict = dict() @@ -1573,14 +1574,38 @@ class DecortController(object): self.result['msg'] = "rg_get_by_id(): zero RG ID specified." self.amodule.fail_json(**self.result) - api_params = dict(rgId=rg_id, ) - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/get", api_params) - if api_resp.status_code == 200: - ret_rg_id = rg_id - ret_rg_dict = json.loads(api_resp.content.decode('utf8')) + api_params = {'rgId': rg_id} + + # Get RG base info + api_rg_resp = self.decort_api_call( + arg_req_function=requests.post, + arg_api_name='/restmachine/cloudapi/rg/get', + arg_params=api_params + ) + if api_rg_resp.status_code != 200: + self.result['warning'] = ( + f'rg_get_by_id(): failed to get RG by ID {rg_id}.' + f' HTTP code {api_rg_resp.status_code}' + f', response {api_rg_resp.reason}.' + ) + return ret_rg_id, ret_rg_dict + ret_rg_id = rg_id + ret_rg_dict = api_rg_resp.json() + + # Get RG resources info + api_rg_res_resp = self.decort_api_call( + arg_req_function=requests.post, + arg_api_name='/restmachine/cloudapi/rg/getResourceConsumption', + arg_params=api_params + ) + if api_rg_res_resp.status_code != 200: + self.result['warning'] = ( + f'rg_get_by_id(): failed to get RG Resources by ID {rg_id}.' + f' HTTP code {api_rg_res_resp.status_code}' + f', response {api_rg_res_resp.reason}.' + ) else: - self.result['warning'] = ("rg_get_by_id(): failed to get RG by ID {}. HTTP code {}, " - "response {}.").format(rg_id, api_resp.status_code, api_resp.reason) + ret_rg_dict['Resources'] = api_rg_res_resp.json() return ret_rg_id, ret_rg_dict From 19534384a8db771be05397d78d737ef50c02191f Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 3 Apr 2024 13:18:53 +0300 Subject: [PATCH 08/82] Fix value for query_key_map['disk'] in method DecortController.rg_update: 'CU_D' -> 'CU_DM' --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 6d04c75..c39c3b8 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1839,7 +1839,7 @@ class DecortController(object): # - when quering resource limits, the keys are in the form of cloud units (CU_*) query_key_map = dict(cpu='CU_C', ram='CU_M', - disk='CU_D', + disk='CU_DM', ext_ips='CU_I', net_transfer='CU_NP',) set_key_map = dict(cpu='maxCPUCapacity', From f11ec8fefb21bb6292331dffbc7192d63cba8abf Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 4 Apr 2024 11:11:18 +0300 Subject: [PATCH 09/82] Update logic to only request RG resources info if the RG is not deleted. --- module_utils/decort_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 414a425..b71fa02 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1593,6 +1593,9 @@ class DecortController(object): ret_rg_dict = api_rg_resp.json() # Get RG resources info + rg_status = ret_rg_dict.get('status') + if not rg_status or rg_status == 'DELETED': + return ret_rg_id, ret_rg_dict api_rg_res_resp = self.decort_api_call( arg_req_function=requests.post, arg_api_name='/restmachine/cloudapi/rg/getResourceConsumption', From 200e8f7151094f3f36b869db826814d2f6268378 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 4 Apr 2024 15:53:29 +0300 Subject: [PATCH 10/82] Add execution of restoring in the case of state=disabled and state=enabled --- library/decort_rg.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/decort_rg.py b/library/decort_rg.py index 78260e6..44d2f29 100644 --- a/library/decort_rg.py +++ b/library/decort_rg.py @@ -493,8 +493,12 @@ def main(): elif decon.rg_facts['status'] == "DELETED": if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True: decon.destroy() - elif amodule.params['state'] == 'present': + elif (amodule.params['state'] == 'present' + or amodule.params['state'] == 'disabled'): decon.restore() + elif amodule.params['state'] == 'enabled': + decon.restore() + decon.enable() elif decon.rg_facts['status'] in ("DISABLED"): if amodule.params['state'] == 'absent': decon.destroy() From a45ab19d387cfcd060f000c0b5d0defbb8597de1 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 4 Apr 2024 18:30:07 +0300 Subject: [PATCH 11/82] Add check for "rg_name" parameter in case of resource group creating --- library/decort_rg.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/library/decort_rg.py b/library/decort_rg.py index 44d2f29..750c9a7 100644 --- a/library/decort_rg.py +++ b/library/decort_rg.py @@ -507,9 +507,16 @@ def main(): else: if amodule.params['state'] in ('present', 'enabled'): - decon.create() - if amodule.params['access']: - decon.access() + if not amodule.params['rg_name']: + decon.result['failed'] = True + decon.result['msg'] = ( + 'Resource group could not be created because' + ' the "rg_name" parameter was not specified.' + ) + else: + decon.create() + if amodule.params['access']: + decon.access() elif amodule.params['state'] in ('disabled'): decon.error() From d50509e0c384dbe3982989f16b8b76c1818640ad Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 5 Apr 2024 11:10:40 +0300 Subject: [PATCH 12/82] Enable output of deleted RGs for API request /restmachine/cloudapi/rg/list in the DecortController.rg_find method. --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 523d383..3274f8d 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1684,7 +1684,7 @@ class DecortController(object): self.amodule.fail_json(**self.result) # try to locate RG by name - start with getting all RGs IDs within the specified account #api_params['accountId'] = arg_account_id - api_params['includedeleted'] = False + api_params['includedeleted'] = True #api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/account/listRG", api_params) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/list",api_params) if api_resp.status_code == 200: From 6cd828d031e3573ad7f47d054079bc3c8714cf16 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 5 Apr 2024 12:17:05 +0300 Subject: [PATCH 13/82] Add status DESTROYED for condition excluding of API request /restmachine/cloudapi/rg/getResourceConsumption in the DecortController._rg_get_by_id method. --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 523d383..b535ddc 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1594,7 +1594,7 @@ class DecortController(object): # Get RG resources info rg_status = ret_rg_dict.get('status') - if not rg_status or rg_status == 'DELETED': + if not rg_status or rg_status in ('DELETED', 'DESTROYED'): return ret_rg_id, ret_rg_dict api_rg_res_resp = self.decort_api_call( arg_req_function=requests.post, From 9e7a33a44a1318b3c5d9d05fb4a697c7e22bfe11 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 15 Apr 2024 17:03:51 +0300 Subject: [PATCH 14/82] Add NoneType check for the 'aff' argument to Affinity Rules management logic of the 'Decort.Controller.compute_affinity' method. --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 41f9d46..b6cc733 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1245,7 +1245,7 @@ class DecortController(object): if comp_dict['affinityRules']: for rule in comp_dict['affinityRules']: del rule['guid'] - if rule not in aff: + if not aff or rule not in aff: affrule_del.append(rule) if aff: From 694e68fe22d0c1bcd4a172a49d4ae1b06720bc8d Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 15 Apr 2024 18:02:50 +0300 Subject: [PATCH 15/82] Add NoneType check for the 'aaff' argument to Affinity Rules management logic of the 'Decort.Controller.compute_affinity' method. --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 41f9d46..cf7efc6 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1257,7 +1257,7 @@ class DecortController(object): if comp_dict['antiAffinityRules']: for rule in comp_dict['antiAffinityRules']: del rule['guid'] - if rule not in aaff: + if not aaff or rule not in aaff: aaffrule_del.append(rule) if aaff: From 240e2ce2df75453a0af65bed327d505efd6f78e2 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 6 May 2024 18:48:08 +0300 Subject: [PATCH 16/82] Assign upload_files variable outside of if construction in DecortController.k8s_provision method --- module_utils/decort_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index c2918e8..84ad388 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -3514,6 +3514,7 @@ class DecortController(object): extnetOnly=extnet_only, ) + upload_files = None if oidc_cert: upload_files = {'oidcCertificate': ('cert.pem', str(oidc_cert),'application/x-x509-ca-cert')} From 4c4be07550680f5a9691b2201c3e1523d2a6048b Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 8 May 2024 14:34:33 +0300 Subject: [PATCH 17/82] Add rg_name parameter to the arguments of the DecortController.rg_find method call in library/decort_k8s.py --- library/decort_k8s.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/decort_k8s.py b/library/decort_k8s.py index 13ea565..f466f19 100644 --- a/library/decort_k8s.py +++ b/library/decort_k8s.py @@ -102,8 +102,11 @@ class decort_k8s(DecortController): self.amodule.fail_json(**self.result) # fail the module -> exit # now validate RG - validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id, - arg_amodule.params['rg_id'],) + validated_rg_id, validated_rg_facts = self.rg_find( + arg_account_id=validated_acc_id, + arg_rg_id=arg_amodule.params['rg_id'], + arg_rg_name=arg_amodule.params['rg_name'] + ) if not validated_rg_id: self.result['failed'] = True self.result['changed'] = False From 8b407c6f692edf5b0b7cd0e852984f97f6396ce5 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 8 May 2024 16:06:39 +0300 Subject: [PATCH 18/82] Change cluster_conf, kublet_conf, kubeproxy_conf, join_conf decort_k8s parameter types to dict. --- library/decort_k8s.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/decort_k8s.py b/library/decort_k8s.py index 13ea565..bfad2c6 100644 --- a/library/decort_k8s.py +++ b/library/decort_k8s.py @@ -333,10 +333,10 @@ class decort_k8s(DecortController): 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), + cluster_conf=dict(type='dict', required=False, default=None), + kublet_conf=dict(type='dict', required=False, default=None), + kubeproxy_conf=dict(type='dict', required=False, default=None), + join_conf=dict(type='dict', 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), From 5da120f2d3ceb04fa923063332cd3f4303f88c5c Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 17 May 2024 14:11:51 +0300 Subject: [PATCH 19/82] Fix logic for the case when DecortController.k8s_provision method returns 0 --- library/decort_k8s.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/decort_k8s.py b/library/decort_k8s.py index f3dbe57..421fc98 100644 --- a/library/decort_k8s.py +++ b/library/decort_k8s.py @@ -235,10 +235,13 @@ class decort_k8s(DecortController): self.amodule.params['description'], self.amodule.params['extnet_only'], ) - + if not k8s_id: - self.result['failed'] = True - self.amodule.fail_json(**self.result) + if k8s_id == 0: + return + else: + self.result['failed'] = True + self.amodule.fail_json(**self.result) self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=k8s_id, k8s_name=self.amodule.params['name'], From bcf384a91095b3d8a2513f03d4d0f148ebdb3053 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 17 May 2024 16:37:35 +0300 Subject: [PATCH 20/82] Fix logic for permanently deleting k8s from recycle bin --- library/decort_k8s.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/decort_k8s.py b/library/decort_k8s.py index 421fc98..3566318 100644 --- a/library/decort_k8s.py +++ b/library/decort_k8s.py @@ -394,7 +394,10 @@ def main(): subj.k8s_restore(subj.k8s_id) subj.action(amodule.params['state']) if amodule.params['state'] == 'absent': - subj.nop() + if amodule.params['permanent']: + subj.destroy() + else: + subj.nop() elif subj.k8s_info['techStatus'] in ("STARTED","STOPPED"): if amodule.params['state'] == 'disabled': subj.action(amodule.params['state']) From 6a99ea4d85f3680bd6d02d10c4633e2fbeb981ec Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 20 May 2024 12:41:06 +0300 Subject: [PATCH 21/82] Fix parameter name 'vinsId' for API request in DecortController.k8s_provision method --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 84ad388..ff7b240 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -3482,7 +3482,7 @@ class DecortController(object): api_params = dict(name=k8s_name, rgId=rg_id, k8ciId=k8ci_id, - vins_Id=vins_id, + vinsId=vins_id, workerGroupName=def_wg_name, networkPlugin=plugin, masterNum=master_count, From 5003991cf5dae03d5b464c82c0f6aeb7fae4521e Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 20 May 2024 16:37:41 +0300 Subject: [PATCH 22/82] Fix logic for 'changed' setting in DecortController.k8s_workers_modify method --- module_utils/decort_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index ff7b240..85c9899 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -3563,7 +3563,6 @@ class DecortController(object): if self.k8s_info['techStatus'] != "STARTED": - self.result['changed'] = False self.result['msg'] = ("k8s_workers_modify(): Can't modify with TechStatus other then STARTED") return From 6725e4342ebb8d1c10ba7b4b3ef1c0a8d39470ab Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 20 May 2024 20:45:45 +0300 Subject: [PATCH 23/82] Fix logic of starting k8s after restore it from recycle bin --- library/decort_k8s.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/decort_k8s.py b/library/decort_k8s.py index 3566318..e415812 100644 --- a/library/decort_k8s.py +++ b/library/decort_k8s.py @@ -260,8 +260,10 @@ class decort_k8s(DecortController): self.k8s_should_exist = False return - def action(self,disared_state,started=True): - + def action(self, disared_state, started=True, preupdate: bool = False): + if preupdate: + # K8s info updating + self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id) #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'], @@ -392,7 +394,8 @@ def main(): elif subj.k8s_info['status'] == "DELETED": if amodule.params['state'] in ('disabled', 'enabled', 'present'): subj.k8s_restore(subj.k8s_id) - subj.action(amodule.params['state']) + subj.action(disared_state=amodule.params['state'], + preupdate=True) if amodule.params['state'] == 'absent': if amodule.params['permanent']: subj.destroy() From abcb7d52f8da999fdd9074f65498ff0f3b4696d6 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 21 May 2024 16:17:12 +0300 Subject: [PATCH 24/82] Move execution of getConfig before if-condition of check mode in decort_k8s.package_facts method --- library/decort_k8s.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/decort_k8s.py b/library/decort_k8s.py index e415812..27d92e9 100644 --- a/library/decort_k8s.py +++ b/library/decort_k8s.py @@ -153,6 +153,9 @@ class decort_k8s(DecortController): config=None, ) + if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED": + ret_dict['config'] = self.k8s_getConfig() + if check_mode: # in check mode return immediately with the default values return ret_dict @@ -172,9 +175,6 @@ class decort_k8s(DecortController): 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() - return ret_dict def nop(self): From 3f4cfd40d6d76f911f6fc46793b8b197534ba27e Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 22 May 2024 11:50:06 +0300 Subject: [PATCH 25/82] Add check mode logic to DecortController.k8s_workers_modify method. --- module_utils/decort_utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 85c9899..59b0c15 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -3561,6 +3561,13 @@ class DecortController(object): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_workers_modify") + if self.amodule.check_mode: + result_msg = 'k8s_workers_modify() in check mode: No changing.' + if self.result.get('msg'): + self.result['msg'] += f'\n{result_msg}' + else: + self.result['msg'] = result_msg + return if self.k8s_info['techStatus'] != "STARTED": self.result['msg'] = ("k8s_workers_modify(): Can't modify with TechStatus other then STARTED") From 51707cbb6988a96ae256173f2e73d90defc7a1ba Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 22 May 2024 12:53:22 +0300 Subject: [PATCH 26/82] Fix call of AnsibleModule.fail_json method in decort_disk.__init__ method. --- library/decort_disk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/decort_disk.py b/library/decort_disk.py index 0df148b..d90fca0 100644 --- a/library/decort_disk.py +++ b/library/decort_disk.py @@ -276,7 +276,7 @@ class decort_disk(DecortController): self.result['msg'] = ("Current user does not have access to the account ID {} / " "name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'], arg_amodule.params['account_name']) - self.fail_json(**self.result) + self.amodule.fail_json(**self.result) else: self.acc_id = validated_acc_id self.acc_info = validated_acc_info @@ -289,7 +289,7 @@ class decort_disk(DecortController): else: self.result['failed'] = True self.result['msg'] = ("Cannot manage Disk when its ID is 0 and name is empty") - self.fail_json(**self.result) + self.amodule.fail_json(**self.result) if arg_amodule.params['place_with']: image_id, image_facts = self.image_find(arg_amodule.params['place_with'], "", 0) From df619e79989b829fc6d3ab4117e4741d9a403ad1 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 22 May 2024 17:59:38 +0300 Subject: [PATCH 27/82] Fix incorrect access to MIN_IOPS variable in DecortController.disk_check_iotune_arg method --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 59b0c15..ccb6261 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -2755,7 +2755,7 @@ class DecortController(object): "write_iops_sec_max", "size_iops_sec", ): - if val and val < self.MIN_IOPS: + if val and val < MIN_IOPS: self.result['msg'] = (f"{arg} was set below the minimum iops {MIN_IOPS}: {val} provided") return From 53634b7fa51d72439fc4b193dde175891532880b Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 22 May 2024 19:34:40 +0300 Subject: [PATCH 28/82] Fix call of DecortController.disk_limitIO method in decort_disk.create method --- library/decort_disk.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/decort_disk.py b/library/decort_disk.py index d90fca0..ec22681 100644 --- a/library/decort_disk.py +++ b/library/decort_disk.py @@ -311,7 +311,8 @@ class decort_disk(DecortController): ) #IO tune if self.amodule.params['limitIO']: - self.disk_limitIO(self.amodule.params['limitIO'],self.disk_id) + self.disk_limitIO(disk_id=self.disk_id, + limits=self.amodule.params['limitIO']) #set share status if self.amodule.params['shareable'] and self.amodule.params['type'] == "D": self.dick_share(self.disk_id,self.amodule.params['shareable']) From fde986bb421a080f8f2afa6fea400a6d141a7206 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 23 May 2024 13:56:35 +0300 Subject: [PATCH 29/82] Fix call of DecortController.disk_share method in decort_disk.create method --- library/decort_disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_disk.py b/library/decort_disk.py index ec22681..0a1c838 100644 --- a/library/decort_disk.py +++ b/library/decort_disk.py @@ -315,7 +315,7 @@ class decort_disk(DecortController): limits=self.amodule.params['limitIO']) #set share status if self.amodule.params['shareable'] and self.amodule.params['type'] == "D": - self.dick_share(self.disk_id,self.amodule.params['shareable']) + self.disk_share(self.disk_id,self.amodule.params['shareable']) return def action(self,restore=False): From 35fe2bdf0e9cf8c493a29f9306f3cb5b33930826 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 23 May 2024 16:41:45 +0300 Subject: [PATCH 30/82] Fix disk deleting logic for permanently deleting from recycle bin --- library/decort_disk.py | 3 +++ module_utils/decort_utils.py | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/library/decort_disk.py b/library/decort_disk.py index 0a1c838..f779b71 100644 --- a/library/decort_disk.py +++ b/library/decort_disk.py @@ -520,6 +520,9 @@ def main(): elif decon.disk_info['status'] == "DELETED": if amodule.params['state'] in ('present'): decon.action(restore=True) + elif (amodule.params['state'] == 'absent' and + amodule.params['permanently']): + decon.delete() else: decon.nop() else: diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index ccb6261..bd0cfe9 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -2859,10 +2859,18 @@ class DecortController(object): return 0, None elif name: if account_id: - api_params = dict(accountId=account_id,name=name) + api_params = { + 'accountId': account_id, + 'name': name, + 'show_all': True + } api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/search", api_params) + disks_list = api_resp.json() + # Filtering disks by status + excluded_statuses = ('PURGED', 'DESTROYED') + filter_f = lambda x: x.get('status') not in excluded_statuses + disks_list = [d for d in disks_list if filter_f(d)] # the above call may return more than one matching disk - disks_list = json.loads(api_resp.content.decode('utf8')) if len(disks_list) == 0: return 0, None elif len(disks_list) > 1: From 772f389d98084c3197bdf5f07804bd35bd61d180 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 23 May 2024 19:00:29 +0300 Subject: [PATCH 31/82] Fix call of DecortController.disk_rename method in decort_disk.action method --- library/decort_disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_disk.py b/library/decort_disk.py index f779b71..b4d4078 100644 --- a/library/decort_disk.py +++ b/library/decort_disk.py @@ -325,7 +325,7 @@ class decort_disk(DecortController): self.disk_restore(self.disk_id) #rename if id present if self.amodule.params['name'] != self.disk_info['name']: - self.disk_rename(diskId=self.disk_id, + self.disk_rename(disk_id=self.disk_id, name=self.amodule.params['name']) self.disk_info['name'] = self.amodule.params['name'] #resize From 2873e4da8256d2ff466cfd89b345fdefce34be56 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 24 May 2024 14:08:43 +0300 Subject: [PATCH 32/82] Remove if-condition for check mode from DecortController.disk_find method --- module_utils/decort_utils.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index bd0cfe9..53f71c9 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -2838,12 +2838,6 @@ class DecortController(object): DISK_INVALID_STATES = ["MODELED", "CREATING", "DELETING", "DESTROYING"] - if self.amodule.check_mode: - self.result['failed'] = False - self.result['msg'] = "disk_find() in check mode: find Disk ID {} / name '{}' was requested.".format(disk_id, - name) - return - ret_disk_id = 0 ret_disk_facts = None From 007c7f4bad222c21a4618dd17ce33db8ad8f8fe6 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 27 May 2024 12:20:33 +0300 Subject: [PATCH 33/82] Fix if-condition in DecortController.pfw_configure method for excluding case of TypeError --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 53f71c9..7eb3bd1 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -3136,7 +3136,7 @@ class DecortController(object): if runner['vmId'] == comp_facts['id']: existing_rules.append(runner) - if not len(existing_rules) and not len(new_rules): + if not existing_rules and not new_rules: self.result['failed'] = False self.result['warning'] = ("pfw_configure(): both existing and new port forwarding rule lists " "for Compute ID {} are empty - nothing to do.").format(comp_facts['id']) From 5b731d009bb64da7701c08a7f3d5138449007446 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 28 May 2024 16:26:42 +0300 Subject: [PATCH 34/82] Add validated_image_id check to if-condition of deleting image logic --- library/decort_osimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_osimage.py b/library/decort_osimage.py index b78c89e..8aef066 100644 --- a/library/decort_osimage.py +++ b/library/decort_osimage.py @@ -522,7 +522,7 @@ def main(): decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id'] - elif amodule.params['state'] == "absent": + elif amodule.params['state'] == "absent" and decon.validated_image_id: if amodule.params['image_name'] or amodule.params['image_id'] and\ decort_osimage.decort_osimage_package_facts(image_facts)['accountId'] == amodule.params['account_Id']: amodule.image_id_delete = decon.validated_image_id From ff32b509f83b3743070404fafa766e9384ffeb3c Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 28 May 2024 16:41:17 +0300 Subject: [PATCH 35/82] Exclude failed setting in case when image not found in DecortController.image_find method --- module_utils/decort_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 7eb3bd1..ea19522 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1386,7 +1386,6 @@ class DecortController(object): return image_record['id'], image_record self.result['failed'] = False - self.result['failed'] = True self.result['msg'] = ("Failed to find OS image by name '{}', SEP ID {}, pool '{}' for " "account ID '{}'.").format(image_name, sepid, pool, From 9dc3a5e780f90d2f953ae7d3a7c26d616a1c8dd9 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 28 May 2024 17:31:44 +0300 Subject: [PATCH 36/82] Fix account_Id parameter value for call of decort_osimage.decort_image_create method --- library/decort_osimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_osimage.py b/library/decort_osimage.py index b78c89e..22f0a49 100644 --- a/library/decort_osimage.py +++ b/library/decort_osimage.py @@ -350,7 +350,7 @@ class decort_osimage(DecortController): hotresize=amodule.params['hotresize'], username=amodule.params['image_username'], password=amodule.params['image_password'], - account_Id=amodule.params['account_Id'], + account_Id=self.validated_account_id, usernameDL=amodule.params['usernameDL'], passwordDL=amodule.params['passwordDL'], sepId=amodule.params['sepId'], From 76a1ff1788709b9f751da1951a6c2974e1cb0abf Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 29 May 2024 13:18:12 +0300 Subject: [PATCH 37/82] Fix image renaming logic in decort_osimage.__init__ method --- library/decort_osimage.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/decort_osimage.py b/library/decort_osimage.py index b78c89e..0b95c39 100644 --- a/library/decort_osimage.py +++ b/library/decort_osimage.py @@ -314,8 +314,9 @@ class decort_osimage(DecortController): if amodule.params['image_id'] != 0 and amodule.params['image_name']: - self.validated_image_id = amodule.params['image_id'] - if amodule.params['image_name']: + self.validated_image_id, image_facts = self.decort_image_find(amodule) + if (self.validated_image_id and + amodule.params['image_name'] != image_facts['name']): decort_osimage.decort_image_rename(self,amodule) self.result['msg'] = ("Image renamed successfully") From a39ab95c1f710ce908c378558601d68b2bb13b49 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 29 May 2024 14:55:47 +0300 Subject: [PATCH 38/82] Fix access to API response image list in DecortController.virt_image_find method --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 7eb3bd1..851d6a1 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1439,7 +1439,7 @@ class DecortController(object): api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/list", api_params) # On success the above call will return here. On error it will abort execution by calling fail_json. images_list = json.loads(api_resp.content.decode('utf8')) - for image_record in images_list: + for image_record in images_list['data']: if image_record['name'] == virt_name and image_record['status'] == "CREATED" and image_record['type'] == "virtual": if sepid == 0 and pool == "": # if no filtering by SEP ID or pool name is requested, return the first match From aa96af1455e3c502d95baf9cf2977688536388c0 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 29 May 2024 18:11:05 +0300 Subject: [PATCH 39/82] Exclude 'failed' setting in case when image not found in DecortController.virt_image_find method --- module_utils/decort_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 7eb3bd1..0d481fb 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1448,7 +1448,6 @@ class DecortController(object): if full_match: return image_record['id'], image_record - self.result['failed'] = True self.result['msg'] = ("Failed to find virtual OS image by name '{}', SEP ID {}, pool '{}' for " "account ID '{}'.").format(virt_name, sepid, pool, From 2eb43815e79cce2ad87d4157c72b12e51a0cebfb Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 29 May 2024 19:02:27 +0300 Subject: [PATCH 40/82] Fix if-condition logic in main function in decort_osimage.py --- library/decort_osimage.py | 45 +++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/library/decort_osimage.py b/library/decort_osimage.py index 8aef066..c08fba8 100644 --- a/library/decort_osimage.py +++ b/library/decort_osimage.py @@ -507,29 +507,6 @@ def main(): decon = decort_osimage(amodule) - if amodule.params['image_name'] or amodule.params['image_id']: - image_id, image_facts = decort_osimage.decort_image_find(decon, amodule) - decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id'] - if decort_osimage.decort_osimage_package_facts(image_facts)['id'] > 0: - decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode) - - if amodule.params['state'] == "present" and decon.validated_image_id == 0 and amodule.params['image_name'] and amodule.params['url']: - decort_osimage.decort_image_create(decon,amodule) - decon.result['changed'] = True - image_id, image_facts = decort_osimage.decort_image_find(decon, amodule) - decon.result['msg'] = ("OS image '{}' created").format(decort_osimage.decort_osimage_package_facts(image_facts)['id']) - decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode) - decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id'] - - - elif amodule.params['state'] == "absent" and decon.validated_image_id: - if amodule.params['image_name'] or amodule.params['image_id'] and\ - decort_osimage.decort_osimage_package_facts(image_facts)['accountId'] == amodule.params['account_Id']: - amodule.image_id_delete = decon.validated_image_id - decort_osimage.decort_image_delete(decon,amodule) - - - if amodule.params['virt_name'] or amodule.params['virt_id']: image_id, image_facts = decort_osimage.decort_virt_image_find(decon, amodule) @@ -538,7 +515,6 @@ def main(): decon.validated_virt_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id'] decon.validated_virt_image_name = decort_osimage.decort_osimage_package_facts(image_facts)['name'] - if decort_osimage.decort_osimage_package_facts(image_facts)['id'] == 0 and amodule.params['state'] == "present" and decon.validated_image_id > 0: image_id, image_facts = decort_osimage.decort_virt_image_create(decon,amodule) decon.result['msg'] = ("Virtual image '{}' created").format(decort_osimage.decort_osimage_package_facts(image_facts)['id']) @@ -547,19 +523,36 @@ def main(): decon.result['msg'] = ("Cannot find OS image") amodule.fail_json(**decon.result) - if decon.validated_image_id: if decort_osimage.decort_osimage_package_facts(image_facts)['linkto'] != decon.validated_image_id: decort_osimage.decort_virt_image_link(decon,amodule) decon.result['changed'] = True amodule.exit_json(**decon.result) - if decon.validated_virt_image_id > 0 and amodule.params['state'] == "absent": decon.result['msg'] = ("Osimage module cannot delete virtual images.") decon.result['failed'] = True amodule.exit_json(**decon.result) + elif amodule.params['image_name'] or amodule.params['image_id']: + image_id, image_facts = decort_osimage.decort_image_find(decon, amodule) + decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id'] + if decort_osimage.decort_osimage_package_facts(image_facts)['id'] > 0: + decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode) + + if amodule.params['state'] == "present" and decon.validated_image_id == 0 and amodule.params['image_name'] and amodule.params['url']: + decort_osimage.decort_image_create(decon,amodule) + decon.result['changed'] = True + image_id, image_facts = decort_osimage.decort_image_find(decon, amodule) + decon.result['msg'] = ("OS image '{}' created").format(decort_osimage.decort_osimage_package_facts(image_facts)['id']) + decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode) + decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id'] + + elif amodule.params['state'] == "absent" and decon.validated_image_id: + if amodule.params['image_name'] or amodule.params['image_id'] and\ + decort_osimage.decort_osimage_package_facts(image_facts)['accountId'] == amodule.params['account_Id']: + amodule.image_id_delete = decon.validated_image_id + decort_osimage.decort_image_delete(decon,amodule) if decon.result['failed'] == True: # we failed to find the specified image - fail the module From e5504b3ac9ca1223336c43c0c0925527ac5c1d88 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 30 May 2024 14:30:10 +0300 Subject: [PATCH 41/82] Add if-condition logic correction in main function in decort_osimage.py --- library/decort_osimage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/library/decort_osimage.py b/library/decort_osimage.py index c08fba8..b75470a 100644 --- a/library/decort_osimage.py +++ b/library/decort_osimage.py @@ -510,6 +510,7 @@ def main(): if amodule.params['virt_name'] or amodule.params['virt_id']: image_id, image_facts = decort_osimage.decort_virt_image_find(decon, amodule) + decon.validated_image_id, _ = decort_osimage.decort_image_find(decon, amodule) if decort_osimage.decort_osimage_package_facts(image_facts)['id'] > 0: decon.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode) decon.validated_virt_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id'] From 2777059b6bf217f4a1b0ae296f0f8ae6e4922453 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 30 May 2024 16:06:25 +0300 Subject: [PATCH 42/82] Add logic for virtual image renaming in decort_osimage class --- library/decort_osimage.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/library/decort_osimage.py b/library/decort_osimage.py index e93009c..f607769 100644 --- a/library/decort_osimage.py +++ b/library/decort_osimage.py @@ -313,7 +313,14 @@ class decort_osimage(DecortController): amodule.fail_json(**self.result) - if amodule.params['image_id'] != 0 and amodule.params['image_name']: + if amodule.params['virt_id'] != 0 and amodule.params['virt_name']: + self.validated_virt_image_id, image_facts =\ + self.decort_virt_image_find(amodule) + if (self.validated_virt_image_id and + amodule.params['virt_name'] != image_facts['name']): + self.decort_virt_image_rename(amodule) + self.result['msg'] = 'Virtual image renamed successfully' + elif amodule.params['image_id'] != 0 and amodule.params['image_name']: self.validated_image_id, image_facts = self.decort_image_find(amodule) if (self.validated_image_id and amodule.params['image_name'] != image_facts['name']): @@ -390,6 +397,12 @@ class decort_osimage(DecortController): image_id, image_facts = decort_osimage.decort_image_find(self, amodule) return image_id, image_facts + def decort_virt_image_rename(self, amodule): + image_facts = self.image_rename(imageId=self.validated_virt_image_id, + name=amodule.params['virt_name']) + self.result['msg'] = ("Virtual image renamed successfully") + image_id, image_facts = self.decort_virt_image_find(amodule) + return image_id, image_facts def decort_osimage_package_facts(arg_osimage_facts, arg_check_mode=False): """Package a dictionary of OS image according to the decort_osimage module specification. This From 1e994410fe5b8bbfafbd19ba40ff71a02dbb9193 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 30 May 2024 16:24:28 +0300 Subject: [PATCH 43/82] Fix if-condition for executing logic of virtual image link changing --- library/decort_osimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_osimage.py b/library/decort_osimage.py index f607769..cbe84e5 100644 --- a/library/decort_osimage.py +++ b/library/decort_osimage.py @@ -538,7 +538,7 @@ def main(): decon.result['msg'] = ("Cannot find OS image") amodule.fail_json(**decon.result) - if decon.validated_image_id: + if decon.validated_virt_image_id: if decort_osimage.decort_osimage_package_facts(image_facts)['linkto'] != decon.validated_image_id: decort_osimage.decort_virt_image_link(decon,amodule) decon.result['changed'] = True From eb0766b15f0eba8acefe70a613116be9ff0bd603 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Sun, 2 Jun 2024 22:18:58 +0300 Subject: [PATCH 44/82] Fix if-condition for rg_name parameter check --- library/decort_lb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_lb.py b/library/decort_lb.py index aa2b0c9..39e419c 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -75,7 +75,7 @@ class decort_lb(DecortController): elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "": - if arg_amodule.params['rg_name']: + if not arg_amodule.params['rg_name']: self.result['failed'] = True self.result['msg'] = ("RG name must be specified with account present") self.amodule.fail_json(**self.result) From 56f7f354c18357c56ad305a3f6f14b2dc47f5ebf Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Sun, 2 Jun 2024 22:27:52 +0300 Subject: [PATCH 45/82] Fix access to acc_id class attrubute in decort_lb.__init__ method --- library/decort_lb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_lb.py b/library/decort_lb.py index 39e419c..8605af2 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -86,7 +86,7 @@ class decort_lb(DecortController): self.result['msg'] = ("Current user does not have access to the requested account " "or non-existent account specified.") 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']) + 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: self.lb_id, self.lb_facts = self.lb_find(0,arg_amodule.params['lb_name'],self.rg_id) From 9ce5a3d711290e7e33f5fb0249bc508e3e203d1d Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 3 Jun 2024 15:09:35 +0300 Subject: [PATCH 46/82] Add logic for processing vins_name parameter --- library/decort_lb.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/library/decort_lb.py b/library/decort_lb.py index 8605af2..815498b 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -66,13 +66,6 @@ class decort_lb(DecortController): self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['vins_id']) 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.amodule.fail_json(**self.result) - elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "": if not arg_amodule.params['rg_name']: @@ -88,6 +81,30 @@ class decort_lb(DecortController): 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 arg_amodule.params['vins_id']: + self.vins_id, self.vins_facts = self.vins_find( + vins_id=arg_amodule.params['vins_id'] + ) + if not self.vins_id: + self.result['failed'] = True + self.result['msg'] = ( + f'Specified ViNS ID {arg_amodule.params["vins_id"]}' + f' not found' + ) + self.amodule.fail_json(**self.result) + elif arg_amodule.params['vins_name']: + self.vins_id, self.vins_facts = self.vins_find( + vins_id=arg_amodule.params['vins_id'], + vins_name=arg_amodule.params['vins_name'], + rg_id=self.rg_id) + if not self.vins_id: + self.result['failed'] = True + self.result['msg'] = ( + f'Specified ViNS name {arg_amodule.params["vins_name"]}' + f' not found in RG ID {self.rg_id}' + ) + self.amodule.fail_json(**self.result) + if self.rg_id and self.vins_id: self.lb_id, self.lb_facts = self.lb_find(0,arg_amodule.params['lb_name'],self.rg_id) return From 876ff5b98d1d86f6cd1d875fd32a6028c43d2d8c Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 3 Jun 2024 16:12:05 +0300 Subject: [PATCH 47/82] Fix API request parameter name for LB description in DecortController.lb_provision method --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 0be52ea..167d213 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -4138,7 +4138,7 @@ class DecortController(object): vinsId=vins_id, highlyAvailable=ha_status, start=start, - decs=annotation + desc=annotation ) api_resp = self.decort_api_call(requests.post, api_url, api_params) # On success the above call will return here. On error it will abort execution by calling fail_json. From ca45f49c2ef756dd3f6c4befbcf457f67d8976d1 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 3 Jun 2024 18:11:40 +0300 Subject: [PATCH 48/82] Fix logic of frontends creating in DecortController._lb_add_fronts method --- module_utils/decort_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 167d213..f3fd6fa 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -4580,7 +4580,8 @@ class DecortController(object): bind['address']if "address" in bind else bind_ip, bind['port'], ) - return + + return def _lb_create_backends(self,back_list,mod_backs,mod_serv): ''' Create backends and add servers to them From db67a3b2d29b8227414398d0864ed3bb0c1a86d9 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 3 Jun 2024 19:14:02 +0300 Subject: [PATCH 49/82] Fix logic of LB frontends updating in DecortController._lb_update_fronts method --- module_utils/decort_utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index f3fd6fa..32d8934 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -4724,11 +4724,14 @@ class DecortController(object): lb_bind, = list(filter(lambda i: i['name'] == bind['name'],lb_front['bindings'])) del lb_bind['guid'] + if not bind.get('address'): + bind['address'] = bind_ip + if dict(sorted(bind.items())) != dict(sorted(lb_bind.items())): self._lb_bind_frontend( front, bind['name'], - bind['address'] if "address" in bind else bind_ip, + bind['address'], bind['port'], update=True, ) From 41731c3dd7c034a6922cdcbea829bdabd64dcda2 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 4 Jun 2024 11:49:14 +0300 Subject: [PATCH 50/82] Fix logic of DecortController.lb_update method running in decort_lb.create method --- library/decort_lb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/decort_lb.py b/library/decort_lb.py index 815498b..976f345 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -115,7 +115,8 @@ class decort_lb(DecortController): 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']: + if self.lb_id and (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'], From 90ae212d0c3ff514b84d853587c28d45acdd8ff4 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 4 Jun 2024 16:30:00 +0300 Subject: [PATCH 51/82] Fix logic of LB deleting from Recycle Bin and LB list getting --- library/decort_lb.py | 3 ++- module_utils/decort_utils.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/library/decort_lb.py b/library/decort_lb.py index 976f345..3015d13 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -322,7 +322,8 @@ def main(): elif decon.lb_facts['status'] == "DELETED": if amodule.params['state'] in ['present', 'enabled']: decon.action(restore=True) - elif amodule.params['state'] == 'absent': + elif (amodule.params['state'] == 'absent' and + amodule.params['permanently']): decon.delete() elif amodule.params['state'] == 'disabled': decon.error() diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 32d8934..129f7e5 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -4056,8 +4056,8 @@ class DecortController(object): self.result['msg'] = "_rg_listlb(): zero RG ID specified." self.amodule.fail_json(**self.result) - api_params = dict(rgId=rg_id) - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/listLb", api_params) + api_params = dict(rgId=rg_id, includedeleted=True) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/list", api_params) if api_resp.status_code == 200: ret_rg_vins_list = json.loads(api_resp.content.decode('utf8')) else: @@ -4071,7 +4071,7 @@ class DecortController(object): @returns: LB ID and dictionary with LB facts. """ - LB_INVALID_STATES = ["ENABLING", "DISABLING", "DELETING", "DELETED", "DESTROYING", "DESTROYED"] + LB_INVALID_STATES = ["ENABLING", "DISABLING", "DELETING", "DESTROYING", "DESTROYED"] ret_lb_id = 0 ret_lb_facts = None From 23ad78b1cffe6021082aa6bf2581d624d8fdf8f4 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 5 Jun 2024 10:30:03 +0300 Subject: [PATCH 52/82] Fix argument name of DecortController.lb_restore method call in decort_lb.action method --- library/decort_lb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_lb.py b/library/decort_lb.py index 3015d13..73c7d27 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -132,7 +132,7 @@ class decort_lb(DecortController): def action(self,d_state='',restore=False): if restore == True: - self.lb_restore(arg_vins_id=self.lb_id) + self.lb_restore(lb_id=self.lb_id) self.lb_state(self.vins_facts, 'enabled') self.lb_facts['status'] = "ENABLED" self.lb_facts['techStatus'] = "STARTED" From 0ae16ddc1dd2bc95c60c0f762e9a5ccb1be89f62 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 5 Jun 2024 10:39:34 +0300 Subject: [PATCH 53/82] Fix lb_dict argument of DecortController.lb_state method call in decort_lb.action method --- library/decort_lb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_lb.py b/library/decort_lb.py index 73c7d27..8fcbbb2 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -133,7 +133,7 @@ class decort_lb(DecortController): def action(self,d_state='',restore=False): if restore == True: self.lb_restore(lb_id=self.lb_id) - self.lb_state(self.vins_facts, 'enabled') + self.lb_state(self.lb_facts, 'enabled') self.lb_facts['status'] = "ENABLED" self.lb_facts['techStatus'] = "STARTED" From a6a6954d4693490fc079c2be4f0e12a97abfe266 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 5 Jun 2024 11:03:33 +0300 Subject: [PATCH 54/82] Add lb_facts updating logic after DecortController.lb_restore method call in decort_lb.action method --- library/decort_lb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/library/decort_lb.py b/library/decort_lb.py index 8fcbbb2..5452d8e 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -133,6 +133,7 @@ class decort_lb(DecortController): def action(self,d_state='',restore=False): if restore == True: self.lb_restore(lb_id=self.lb_id) + _, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) self.lb_state(self.lb_facts, 'enabled') self.lb_facts['status'] = "ENABLED" self.lb_facts['techStatus'] = "STARTED" From 4311eee4353bca70356e27b31b966351248bbc6c Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 5 Jun 2024 13:14:27 +0300 Subject: [PATCH 55/82] Add LB starting logic in LB restoring logic and fix DecortController.lb_state method VALID_TARGET_STATES list --- library/decort_lb.py | 5 ++--- module_utils/decort_utils.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/library/decort_lb.py b/library/decort_lb.py index 5452d8e..d05a5cc 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -135,8 +135,7 @@ class decort_lb(DecortController): self.lb_restore(lb_id=self.lb_id) _, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) self.lb_state(self.lb_facts, 'enabled') - self.lb_facts['status'] = "ENABLED" - self.lb_facts['techStatus'] = "STARTED" + _, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) self.lb_update( self.lb_facts['primaryNode'], @@ -322,7 +321,7 @@ def main(): decon.action(amodule.params['state']) elif decon.lb_facts['status'] == "DELETED": if amodule.params['state'] in ['present', 'enabled']: - decon.action(restore=True) + decon.action(d_state='started', restore=True) elif (amodule.params['state'] == 'absent' and amodule.params['permanently']): decon.delete() diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 129f7e5..8b0404c 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -4177,7 +4177,7 @@ class DecortController(object): NOP_STATES_FOR_LB_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING", "DESTROYED"] - VALID_TARGET_STATES = ["enabled", "disabled","restart"] + VALID_TARGET_STATES = ["enabled", "disabled","restart", 'started'] VALID_TARGET_TSTATES = ["STARTED","STOPPED"] if lb_dict['status'] in NOP_STATES_FOR_LB_CHANGE: From b1f2167d0020a3951ae35d33f52ad6116b5defc0 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 5 Jun 2024 17:39:27 +0300 Subject: [PATCH 56/82] Fix logic of LB state changing --- library/decort_lb.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/library/decort_lb.py b/library/decort_lb.py index d05a5cc..e0fa09e 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -150,6 +150,14 @@ class decort_lb(DecortController): if d_state != '': self.lb_state(self.lb_facts, d_state) + _, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) + + if (d_state == 'enabled' and + self.lb_facts.get('status') == 'ENABLED' and + self.lb_facts.get('techStatus') == 'STOPPED'): + self.lb_state(self.lb_facts, 'started') + _, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) + return def delete(self): @@ -306,22 +314,22 @@ def main(): elif decon.lb_facts['status'] == "DISABLED": if amodule.params['state'] == 'absent': decon.delete() - elif amodule.params['state'] in ('present', 'disabled'): + elif amodule.params['state'] == 'disabled': decon.action() - elif amodule.params['state'] == 'enabled': + elif amodule.params['state'] in ('enabled', 'present'): decon.action('enabled') elif decon.lb_facts['status'] in ["CREATED", "ENABLED"]: if amodule.params['state'] == 'absent': decon.delete() elif amodule.params['state'] in ('present', 'enabled'): - decon.action() + decon.action(d_state='enabled') elif amodule.params['state'] == 'disabled': decon.action('disabled') elif amodule.params['state'] in ('stopped', 'started','restart'): decon.action(amodule.params['state']) elif decon.lb_facts['status'] == "DELETED": if amodule.params['state'] in ['present', 'enabled']: - decon.action(d_state='started', restore=True) + decon.action(d_state='enabled', restore=True) elif (amodule.params['state'] == 'absent' and amodule.params['permanently']): decon.delete() From 6b4957f8aa1c4402d46e8ae4534dbcd748b0a68e Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 6 Jun 2024 14:46:50 +0300 Subject: [PATCH 57/82] Fix deleting element of frontends list in DecortController._lb_delete_fronts method --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 8b0404c..20e34dd 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -4557,7 +4557,7 @@ class DecortController(object): api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendDelete", api_params) #del from cloud dict if type(front)==dict: - del self.lb_facts['frontends'][front['name']] + self.lb_facts['frontends'].remove(front) self.result['changed'] = True return From bb6394873b2ac0d730da1fce3ade2a219a029d2e Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 6 Jun 2024 14:54:16 +0300 Subject: [PATCH 58/82] Fix location of lb_front_list formation code in DecortController.lb_update method --- module_utils/decort_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 20e34dd..433a3d3 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -4478,9 +4478,6 @@ class DecortController(object): #FE - mod_front_list = [front['name'] for front in mod_frontends] - lb_front_list = [front['name'] for front in lb_frontends] - if del_list_backs: self._lb_delete_backends( @@ -4503,6 +4500,9 @@ class DecortController(object): mod_servers ) + mod_front_list = [front['name'] for front in mod_frontends] + lb_front_list = [front['name'] for front in lb_frontends] + del_list_fronts = set(lb_front_list).difference(mod_front_list) add_list_fronts = set(mod_front_list).difference(lb_front_list) upd_front_list = set(lb_front_list).intersection(mod_front_list) From ea63959289bc8d42dcc22454a67a1e169dd8ed90 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 6 Jun 2024 15:34:01 +0300 Subject: [PATCH 59/82] Fix API param values for servers adding in DecortController._lb_update_backends method --- module_utils/decort_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 433a3d3..9d16811 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -4664,8 +4664,8 @@ class DecortController(object): serverName = server['name'], address = server['address'], port = mod_back['port'], - check = server['check'] if "check" in server else None, - **server['server_settings'] if "server_settings" in server else {}, + check = mod_back['check'] if "check" in mod_back else None, + **mod_back['server_settings'] if "server_settings" in mod_back else {}, ) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerAdd", api_params) self.result['changed'] = True From 9449afa2acf29cb46dcd32f5296aae22e59bcb22 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 6 Jun 2024 16:29:22 +0300 Subject: [PATCH 60/82] Add check mode simple logic to DecortController.lb_update method --- module_utils/decort_utils.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 9d16811..72dcb07 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -4468,6 +4468,15 @@ class DecortController(object): 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") + + if self.amodule.check_mode: + result_msg = 'lb_update() in check mode: No changing.' + if self.result.get('msg'): + self.result['msg'] += f'\n{result_msg}' + else: + self.result['msg'] = result_msg + return + #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] From 5c3194b94dd8ef017620a350f44455bb4b68a7ff Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 7 Jun 2024 12:39:29 +0300 Subject: [PATCH 61/82] Fix variable name for if-condition in decort_bservice.nop method --- library/decort_bservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_bservice.py b/library/decort_bservice.py index 5e1270e..1b771db 100644 --- a/library/decort_bservice.py +++ b/library/decort_bservice.py @@ -76,7 +76,7 @@ class decort_bservice(DecortController): """ self.result['failed'] = False self.result['changed'] = False - if self.k8s_id: + if self.bservice_id: self.result['msg'] = ("No state change required for B-service ID {} because of its " "current status '{}'.").format(self.bservice_id, self.bservice_info['status']) else: From 3dc9cbcbd8897bfef487638a69c8d8262c99125f Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 7 Jun 2024 12:56:04 +0300 Subject: [PATCH 62/82] Add rg_name param value to call of DecortController.rg_find method in decort_bservice.__init__ method --- library/decort_bservice.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/decort_bservice.py b/library/decort_bservice.py index 1b771db..87b3310 100644 --- a/library/decort_bservice.py +++ b/library/decort_bservice.py @@ -43,8 +43,11 @@ class decort_bservice(DecortController): self.fail_json(**self.result) # fail the module -> exit # now validate RG - validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id, - arg_amodule.params['rg_id'],) + validated_rg_id, validated_rg_facts = self.rg_find( + arg_account_id=validated_acc_id, + arg_rg_id=arg_amodule.params['rg_id'], + arg_rg_name=arg_amodule.params['rg_name'] + ) if not validated_rg_id: self.result['failed'] = True self.result['changed'] = False From 3fec6f014b8266336986523ae8a57b798f0717b0 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 7 Jun 2024 14:12:45 +0300 Subject: [PATCH 63/82] Add existing check for 'groupsName' key of self.bservice_info dict in decort_bservice.package_facts method --- library/decort_bservice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/decort_bservice.py b/library/decort_bservice.py index 87b3310..a23c979 100644 --- a/library/decort_bservice.py +++ b/library/decort_bservice.py @@ -151,7 +151,9 @@ class decort_bservice(DecortController): ret_dict['state'] = self.bservice_info['status'] ret_dict['rg_id'] = self.bservice_info['rgId'] ret_dict['account_id'] = self.acc_id - ret_dict['groupsName'] = self.bservice_info['groupsName'] + bservice_info_groupsName = self.bservice_info.get('groupsName') + if bservice_info_groupsName: + ret_dict['groupsName'] = bservice_info_groupsName ret_dict['groupsIds'] = self.bservice_info['groups'] return ret_dict @staticmethod From 7e372511bcd046ede8bb022b762463dc1f528828 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 7 Jun 2024 14:53:28 +0300 Subject: [PATCH 64/82] Add check mode simple logic in DecortController.bservice_provision method --- module_utils/decort_utils.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 0be52ea..073c2c8 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -3731,6 +3731,14 @@ class DecortController(object): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_provision") + if self.amodule.check_mode: + result_msg = 'bservice_provision() in check mode: No changing.' + if self.result.get('msg'): + self.result['msg'] += f'\n{result_msg}' + else: + self.result['msg'] = result_msg + return 0 + api_url = "/restmachine/cloudapi/bservice/create" api_params = dict( name = bs_name, From cb13649586138d7b5120257333617cc0362b44f5 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 7 Jun 2024 14:54:30 +0300 Subject: [PATCH 65/82] Fix executing logic of DecortController.bservice_state method call in decort_bservice.create method --- library/decort_bservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_bservice.py b/library/decort_bservice.py index a23c979..da52fef 100644 --- a/library/decort_bservice.py +++ b/library/decort_bservice.py @@ -112,7 +112,7 @@ class decort_bservice(DecortController): ) if self.bservice_id: _, self.bservice_info = self.bservice_get_by_id(self.bservice_id) - self.bservice_state(self.bservice_info,'enabled',self.amodule.params['started']) + self.bservice_state(self.bservice_info,'enabled',self.amodule.params['started']) return def action(self,d_state,started=False): From 7998046cfb911cf733b359d8c3157fd1fa85ec15 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 10 Jun 2024 16:02:19 +0300 Subject: [PATCH 66/82] Fix return value for DecortController.group_provision method. --- module_utils/decort_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 073c2c8..18675f1 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -4006,10 +4006,11 @@ class DecortController(object): vinses = list_vins, timeoutStart = arg_timeout ) - self.decort_api_call(requests.post, api_url, api_params) + api_resp = self.decort_api_call(requests.post, api_url, api_params) + new_bsgroup_id = int(api_resp.text) self.result['failed'] = False self.result['changed'] = True - return + return new_bsgroup_id def group_delete(self,bs_id,gr_id): From 27e7c2749f7bc868b8c5866f51d26f91fbb61744 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 10 Jun 2024 17:01:41 +0300 Subject: [PATCH 67/82] Fix adding networks logic in DecortController.group_provision method --- module_utils/decort_utils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 18675f1..9a18a5c 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -3987,11 +3987,7 @@ class DecortController(object): ): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_provision") - - list_vins= list() - for net in arg_network: - if net['type'] == 'VINS': - list_vins.append(net['id']) + api_url = "/restmachine/cloudapi/bservice/groupAdd" api_params = dict( serviceId = bs_id, @@ -4003,7 +3999,8 @@ class DecortController(object): imageId = arg_image_id, driver = arg_driver, role = arg_role, - vinses = list_vins, + vinses = [n['id'] for n in arg_network if n['type'] == 'VINS'], + extnets = [n['id'] for n in arg_network if n['type'] == 'EXTNET'], timeoutStart = arg_timeout ) api_resp = self.decort_api_call(requests.post, api_url, api_params) From 3a2d9904cf663a32150ab54dbc3972b93cc81f38 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 11 Jun 2024 12:46:57 +0300 Subject: [PATCH 68/82] Update DecortController.group_find method logic for >=3.8.6 Dynamix version. --- module_utils/decort_utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 9a18a5c..9290b37 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -3868,11 +3868,12 @@ class DecortController(object): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_find") if group_id == 0: - try: - i = bs_info['groupsName'].index(group_name) - except: - return 0,None - group_id = int(bs_info['groups'][i]) + for group in bs_info['groups']: + if group['name'] == group_name: + return self._group_get_by_id(bs_id=bs_id, + g_id=group['id']) + return 0, None + return self._group_get_by_id(bs_id,group_id) def group_state(self,bs_id,gr_id,desired_state): From f22be4fe08755f8e05b176032142e5e1b9563b23 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 11 Jun 2024 14:16:23 +0300 Subject: [PATCH 69/82] Add default value '' for 'role' parameter in decort_group module. --- library/decort_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_group.py b/library/decort_group.py index 8162f0d..f8a1f9a 100644 --- a/library/decort_group.py +++ b/library/decort_group.py @@ -206,7 +206,7 @@ class decort_group(DecortController): bservice_id=dict(type='int', required=True), count=dict(type='int', required=True), timeoutStart=dict(type='int', required=False), - role=dict(type='str', required=False), + role=dict(type='str', required=False, default=''), cpu=dict(type='int', required=False), ram=dict(type='int', required=False), networks=dict(type='list', default=[], required=False), From 190a1d302c710c83f94abf7a15d7cea0b158da9e Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 11 Jun 2024 16:16:35 +0300 Subject: [PATCH 70/82] Add sub-elements specification for 'networks' parameter of decort_group module --- library/decort_group.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/library/decort_group.py b/library/decort_group.py index f8a1f9a..129c7cd 100644 --- a/library/decort_group.py +++ b/library/decort_group.py @@ -209,7 +209,20 @@ class decort_group(DecortController): role=dict(type='str', required=False, default=''), cpu=dict(type='int', required=False), ram=dict(type='int', required=False), - networks=dict(type='list', default=[], required=False), + networks=dict( + type='list', default=[], elements='dict', + options=dict( + type=dict( + type='str', + required=True, + choices=['VINS', 'EXTNET'] + ), + id=dict( + type='int', + required=True + ) + ) + ), description=dict(type='str', default="Created by decort ansible module"), verify_ssl=dict(type='bool', required=False, default=True), workflow_callback=dict(type='str', required=False), From 825ce068c8717ac2999df2fc91e40d7cbf8ad433 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 11 Jun 2024 17:30:23 +0300 Subject: [PATCH 71/82] Fix logic of decort_group.destroy method --- library/decort_group.py | 1 + 1 file changed, 1 insertion(+) diff --git a/library/decort_group.py b/library/decort_group.py index 129c7cd..9c29ff8 100644 --- a/library/decort_group.py +++ b/library/decort_group.py @@ -132,6 +132,7 @@ class decort_group(DecortController): self.bservice_id, self.group_id ) + self.group_should_exist = False return From b51136b711aafe9c070cb0390108a141341d4b78 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Fri, 14 Jun 2024 15:26:23 +0300 Subject: [PATCH 72/82] Fix ViNS list comparison in DecortController.group_update_net method --- module_utils/decort_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 9290b37..197c3a9 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -3967,7 +3967,7 @@ class DecortController(object): else: list_extnet.append(net['id']) - if gr_dict['vinses'] != list_vins: + if sorted(gr_dict['vinses']) != sorted(list_vins): api_url = "/restmachine/cloudapi/bservice/groupUpdateVins" api_params = dict( serviceId=bs_id, From db854acc11fe42d24cb02219fd0212e5164606c0 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 17 Jun 2024 12:31:04 +0300 Subject: [PATCH 73/82] Fix check mode logic for RG creating in decort_rg module --- library/decort_rg.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/library/decort_rg.py b/library/decort_rg.py index 750c9a7..8f1f5a8 100644 --- a/library/decort_rg.py +++ b/library/decort_rg.py @@ -324,11 +324,14 @@ class decort_rg(DecortController): "", # this is location code. TODO: add module argument ) - self.validated_rg_id, self.rg_facts = self.rg_find(self.validated_acc_id, - self.validated_rg_id, - arg_rg_name="", - arg_check_state=False) - self.rg_should_exist = True + if self.validated_rg_id: + self.validated_rg_id, self.rg_facts = self.rg_find( + arg_account_id=self.validated_acc_id, + arg_rg_id=self.validated_rg_id, + arg_rg_name="", + arg_check_state=False + ) + self.rg_should_exist = True return def enable(self): @@ -515,7 +518,7 @@ def main(): ) else: decon.create() - if amodule.params['access']: + if amodule.params['access'] and not amodule.check_mode: decon.access() elif amodule.params['state'] in ('disabled'): decon.error() From d287c8829341db72c7e2501d5fb3520944ca1509 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 19 Jun 2024 10:35:36 +0300 Subject: [PATCH 74/82] Fix bs groups info keys in decort_bservice.package_facts method --- library/decort_bservice.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/library/decort_bservice.py b/library/decort_bservice.py index da52fef..a7b3ab7 100644 --- a/library/decort_bservice.py +++ b/library/decort_bservice.py @@ -151,10 +151,7 @@ class decort_bservice(DecortController): ret_dict['state'] = self.bservice_info['status'] ret_dict['rg_id'] = self.bservice_info['rgId'] ret_dict['account_id'] = self.acc_id - bservice_info_groupsName = self.bservice_info.get('groupsName') - if bservice_info_groupsName: - ret_dict['groupsName'] = bservice_info_groupsName - ret_dict['groupsIds'] = self.bservice_info['groups'] + ret_dict['groups'] = self.bservice_info['groups'] return ret_dict @staticmethod def build_parameters(): From 36930bda0d69c6725db1055ef79dbed8dab6cdc8 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Wed, 26 Jun 2024 13:35:11 +0300 Subject: [PATCH 75/82] Fix ci_user_data param type in decort_kvmvm module --- library/decort_kvmvm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/decort_kvmvm.py b/library/decort_kvmvm.py index d5e99c5..a0be17a 100644 --- a/library/decort_kvmvm.py +++ b/library/decort_kvmvm.py @@ -782,7 +782,7 @@ class decort_kvmvm(DecortController): affinity_label=dict(type='str', required=False), aff_rule=dict(type='list', required=False), aaff_rule=dict(type='list', required=False), - ci_user_data=dict(type='list',elements='dict', required=False), + ci_user_data=dict(type='dict', required=False), state=dict(type='str', default='present', choices=['absent', 'paused', 'poweredoff', 'halted', 'poweredon', 'present', 'check']), From 614c7d98d919769326e8f20b94eeaae8d70368cd Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 2 Jul 2024 14:50:44 +0300 Subject: [PATCH 76/82] Fix logic of deleting all port forwarding rules for compute in DecortController.pfw_configure method --- module_utils/decort_utils.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 29aeb55..b3cd0be 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -3142,9 +3142,15 @@ class DecortController(object): if new_rules == None or len(new_rules) == 0: # delete all existing rules for this Compute - api_params = dict(vinsId=vins_facts['id'], - ruleId=-1) - self.decort_api_call(requests.post, "/restmachine/cloudapi/vins/natRuleDel", api_params) + for rule in existing_rules: + self.decort_api_call( + arg_req_function=requests.post, + arg_api_name="/restmachine/cloudapi/vins/natRuleDel", + arg_params={ + 'vinsId': vins_facts['id'], + 'ruleId': rule['id'] + } + ) self.result['changed'] = True return ret_rules From 3ce022a8007c2e53b39757caf0792cc74c1f3cce Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Thu, 4 Jul 2024 17:54:26 +0300 Subject: [PATCH 77/82] Fix access to lb_facts attribute in decort_lb class (in two places) --- library/decort_lb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/decort_lb.py b/library/decort_lb.py index e0fa09e..042691a 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -174,7 +174,7 @@ class decort_lb(DecortController): self.result['changed'] = False if self.lb_id: self.result['msg'] = ("No state change required for LB ID {} because of its " - "current status '{}'.").format(self.lb_id, self.vins_facts['status']) + "current status '{}'.").format(self.lb_id, self.lb_facts['status']) else: self.result['msg'] = ("No state change to '{}' can be done for " "non-existent LB instance.").format(self.amodule.params['state']) @@ -213,7 +213,7 @@ class decort_lb(DecortController): # in check mode return immediately with the default values return ret_dict - if self.vins_facts is None: + if self.lb_facts is None: # if void facts provided - change state value to ABSENT and return ret_dict['state'] = "ABSENT" return ret_dict From 02e55e77f49b24f93b7f76051a5cd6ac559e860f Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 8 Jul 2024 16:28:17 +0300 Subject: [PATCH 78/82] Add CHANGELOG.md file for 5.3.0 version --- CHANGELOG.md | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9eb74e0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,137 @@ +# Список изменений в версии 5.3.0 + +## Обновления + +### Глобальные +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-62 | Добавлен вывод текста ответа в сообщение об ошибке запроса к API. | + + +### Модуль decort_bservice + +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-434 | В модуле **decort_bservice** в возвращаемом словаре `facts` ключ `groupsIds` заменён на ключ `groups`, который содержит словари с информацией о группах базовой службы. | + +## Исправления + +### Модуль decort_bservice + +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-366 | Работа модуля **decort_bservice** завершалась ошибкой Python при удалении базовой службы. | +| BANS-367 | Работа модуля **decort_bservice** завершалась ошибкой модуля при использовании параметров `rg_name` и `account_name` вместо параметра `rg_id`. | +| BANS-368 | Работа модуля **decort_bservice** завершалась ошибкой Python при повторном запуске создания базовой службы. | +| BANS-369 | Модуль **decort_bservice** не реагировал на режим **check mode** при создании базовой службы. | + +### Модуль decort_disk + +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-246 | Работа модуля **decort_disk** завершалась ошибкой Python при обработке ошибочных сценариев работы. | +| BANS-248 | Работа модуля **decort_disk** завершалась ошибкой Python при попытке валидировать заданный параметр `limitIO`. | +| BANS-249 | Работа модуля **decort_disk** завершалась ошибкой Python при попытке при попытке выполнить изменения в соответствии с заданным параметром `limitIO`. | +| BANS-250 | Работа модуля **decort_disk** завершалась ошибкой Python при заданном параметре `shareable`. | +| BANS-255 | Модуль **decort_disk** не выполнял безвозвратное удаление кластера из корзины. | +| BANS-260 | Работа модуля **decort_disk** завершалась ошибкой Python при попытке переименовать диск. | +| BANS-264 | Работа модуля **decort_disk** завершалась ошибкой Python при попытке изменить объём диска в режиме **check mode**. | + +### Модуль decort_group + +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-390 | Работа модуля **decort_group** завершалась ошибкой запроса к API при создании группы БС. | +| BANS-391 | Модуль **decort_group** не использовал заданные в параметре `networks` внешние сети при создании группы БС. | +| BANS-395 | Работа модуля **decort_group** завершалась ошибкой запроса к API при повторном запуске создания группы БС. | +| BANS-396 | Модуль **decort_group** при повторном запуске создания сообщал о том, что были выполнены изменения. | +| BANS-399 | Работа модуля **decort_group** завершалась ошибкой запроса к API при удалении группы БС. | +| BANS-419 | Модуль **decort_group** при повторном запуске изменения параметра networks сообщал о том, что были выполнены изменения. | + +### Модуль decort_k8s + +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-197 | Работа модуля **decort_k8s** завершалась ошибкой Python если не задан параметр `oidc_cert`. | +| BANS-202 | Работа модуля **decort_k8s** завершалась ошибкой модуля при попытке использовать `rg_name` и `account_name` вместо `rg_id` при создании кластера. | +| BANS-203 | В модуле **decort_k8s** был задан неверный тип данных для параметров `cluster_conf`, `kublet_conf`, `kubeproxy_conf`, `join_conf` | +| BANS-216 | Работа модуля **decort_k8s** завершалась ошибкой модуля при попытке создать кластер в режиме **check mode**. | +| BANS-218 | Модуль **decort_k8s** не выполнял безвозвратное удаление кластера из корзины. | +| BANS-221 | Модуль **decort_k8s** некорректно обрабатывал параметр `vins_id`, что вызывало создание новой внутренней сети при заданном параметре `vins_id`. | +| BANS-225 | Модуль **decort_k8s** не сообщал о выполненных изменениях при выполнении восстановления кластера из корзины. | +| BANS-226 | Модуль **decort_k8s** не выполнял запуск кластера после его восстановления из корзины. | +| BANS-236 | Модуль **decort_k8s** игнорировал заданный параметр `getConfig` при запуске в режиме **check mode**. | +| BANS-243 | Модуль **decort_k8s** выполнял изменения в соответствии с заданным параметром `workers` при запуске в режиме **check mode**. | + +### Модуль decort_kvmvm + +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-38 | Работа модуля **decort_kvmvm** завершалась ошибкой Python при попытке удалить все существующие правила affinity у ВМ. | +| BANS-39 | Работа модуля **decort_kvmvm** завершалась ошибкой Python при попытке удалить все существующие правила anti affinity у ВМ. | +| BANS-60 | Работа модуля **decort_kvmvm** завершалась ошибкой Python при незаданном параметре `boot_disk` для существующей ВМ. | +| BANS-61 | В модуле **decort_kvmvm** происходила автоматическая конвертация из ГБ в МБ при изменении параметра `ram` на значение менее 512 для существующей ВМ. | +| BANS-445 | В модуле **decort_kvmvm** был задан неверный тип данных для параметра `ci_user_data`. | + +### Модуль decort_lb + +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-321 | Работа модуля **decort_lb** завершалась ошибкой модуля при использовании параметра `rg_name`. | +| BANS-322 | Работа модуля **decort_lb** завершалась ошибкой Python при использовании параметра `account_id`. | +| BANS-323 | Модуль **decort_lb** игнорировал параметр `vins_name` и создавал балансировщик без подключения к внутренней сети. | +| BANS-324 | Модуль **decort_lb** при повторном запуске создания сообщал о том, что были выполнены изменения. | +| BANS-325 | Модуль **decort_lb** не реагировал на параметр `annotation`. | +| BANS-326 | Модуль **decort_lb** при указании нескольких конфигураций frontend в параметре `frontends` добавлял в балансировщик только первую конфигурацию frontend. | +| BANS-331 | Работа модуля **decort_lb** завершалась ошибкой Python при выполнении создания балансировщика в режиме **check mode**. | +| BANS-334 | Модуль **decort_lb** не выполнял безвозвратное удаление балансировщика из корзины. | +| BANS-338 | Работа модуля **decort_lb** завершалась ошибкой Python при запуске восстановления балансировщика из корзины. | +| BANS-339 | Работа модуля **decort_lb** завершалась ошибкой Python при запуске восстановления балансировщика из корзины. | +| BANS-340 | Модуль **decort_lb** оставлял балансировщик выключенным после его восстановления из корзины. | +| BANS-341 | Модуль **decort_lb** оставлял балансировщик остановленным после его восстановления из корзины. | +| BANS-346 | Модуль **decort_lb** не производил запуск балансировщика при запуске с заданным параметром `state: enabled` или `state: present`. | +| BANS-352 | Работа модуля **decort_lb** завершалась ошибкой Python при удалении конфигурации frontend у балансировщика. | +| BANS-353 | Работа модуля **decort_lb** завершалась ошибкой запроса к API при удалении конфигурации frontend и связанной с ней конфигурации backend у балансировщика | +| BANS-354 | Модуль **decort_lb** не применял значения из подпараметров `check` и `server_settings` при добавлении нового сервера в существующую конфигурацию backend балансировщика. | +| BANS-357 | Модуль **decort_lb** игнорировал режим **check mode** для параметров `backends`, `frontends`, `servers`. | +| BANS-457 | Модуль **decort_lb** возвращал некорректный статус для удалённого балансировщика и указывал некорректный статус балансировщика в сообщении при повторном запуске модуля. + +### Модуль decort_osimage + +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-295 | Работа модуля **decort_osimage** завершалась ошибкой запроса к API при повторном запуске удаления шаблонного образа. | +| BANS-296 | Работа модуля **decort_osimage** завершалась ошибкой модуля при повторном запуске удаления шаблонного образа. | +| BANS-297 | Работа модуля **decort_osimage** завершалась ошибкой запроса к API при указании параметра `account_name` вместо параметра `account_Id` | +| BANS-299 | Модуль **decort_osimage** при повторном запуске переименования образа сообщал о том, что были выполнены изменения. | +| BANS-300 | Работа модуля **decort_osimage** завершалась ошибкой Python при создании виртуального образа. | +| BANS-301 | Работа модуля **decort_osimage** завершалась ошибкой модуля при повторном запуске удаления виртуального образа. | +| BANS-302 | Работа модуля **decort_osimage** завершалась ошибкой Python с удалением шаблонного образа при попытке удалить виртуальный образ. | +| BANS-303 | Модуль **decort_osimage** не выполнял переименование виртуального образа. | +| BANS-304 | Работа модуля **decort_osimage** завершалась ошибкой Python при повторном запуске удаления виртуального образа. | + +### Модуль decort_pfw + +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-283 | Работа модуля **decort_pfw** завершалась ошибкой Python при повторном запуске удаления правил. | +| BANS-451 | Модуль **decort_pfw** при запуске удаления удалял все правила переадресации портов на виртуальном маршрутизаторе заданной внутренней сети, вместо удаления правил только для заданной ВМ. | + +### Модуль decort_rg + +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-58 | Работа модуля **decort_rg** завершалась ошибкой Python при передаче некорректных данных об аккаунте и/или пользователе. | +| BANS-63 | Модуль **decort_rg** выполнял некорректное получение/изменение квоты на объём дисков. | +| BANS-64 | Работа модуля **decort_rg** завершалась ошибкой Python при повторном запуске на создание. | +| BANS-65 | Модуль **decort_rg** не выполнял восстановление РГ из корзины при заданном параметре `state: disabled` или `state: enabled`. | +| BANS-66 | В модуле **decort_rg** не выполнялась проверка наличия параметра `rg_name` при создании РГ. | +| BANS-67 | Работа модуля **decort_rg** завершалась ошибкой запроса к API при создании РГ когда РГ с таким именем есть в корзине. | +| BANS-68 | Модуль **decort_rg** не выполнял восстановление РГ из корзины при указании `rg_name` вместо `rg_id`. | +| BANS-425 | Работа модуля **decort_rg** завершалась ошибкой модуля при создании РГ в режиме **check mode**. | + +### Модуль decort_vins + +| Идентификатор
задачи | Описание | +| --- | --- | +| BANS-59 | Работа модуля **decort_vins** завершалась ошибкой Python при создании внутренней сети на уровне аккаунта. | \ No newline at end of file From 3b84a5f63396c25efd6802910bd53006880b4177 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Mon, 8 Jul 2024 16:58:09 +0300 Subject: [PATCH 79/82] Fix BANS-434 task description in CHANGELOG.md file --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eb74e0..6f6f766 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ | Идентификатор
задачи | Описание | | --- | --- | -| BANS-434 | В модуле **decort_bservice** в возвращаемом словаре `facts` ключ `groupsIds` заменён на ключ `groups`, который содержит словари с информацией о группах базовой службы. | +| BANS-434 | В модуле **decort_bservice** в возвращаемом словаре `facts` ключи `groupsIds` и `groupsName` заменены на ключ `groups`, который содержит словари с информацией о группах базовой службы. | ## Исправления From a94a5a2e62615faf578496eb4b26e1d3349616c6 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 9 Jul 2024 15:24:16 +0300 Subject: [PATCH 80/82] Update README.md file --- README.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b269ee8..76f9186 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,18 @@ # decort-ansible -Ansible modules for Digital Energy Orchestration Technology (DECORT) platform v3.6.1 and above. +Модули Ansible для платформы Digital Energy Orchestration Technology (DECORT). -Note that this module may produce unreliable results when used with older DECORT API versions. +## Соответствие версий платформы версиям модулей Ansible -Requirements: -* Ansible 2.7 or higher -* Python 3.7 or higher -* PyJWT 2.0.0 Python module or higher -* requests Python module -* netaddr Python module -* DECORT cloud platform version 3.8.7 or higher +| Версия платформы | Версия модулей Ansible | +| :---: | :---: | +| 4.0.0 | 5.3.0 | +| 3.8.9 | 5.2.6 | +| 3.8.8 | 5.2.6 | +| 3.8.7 | 5.2.5 | +| 3.8.6 | 5.2.4 | + +## Ссылки + +- [Документация](./wiki/Home.md) + +- [Список изменений](./CHANGELOG.md) From c5f2e143ba099f2d0f4c6c728a5f5a3f403c6eb6 Mon Sep 17 00:00:00 2001 From: Dmitriy Smirnov Date: Tue, 9 Jul 2024 15:27:10 +0300 Subject: [PATCH 81/82] Delete decort-ansible.tar file --- decort-ansible.tar | Bin 481280 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 decort-ansible.tar diff --git a/decort-ansible.tar b/decort-ansible.tar deleted file mode 100644 index e31075be3065cfe0122437b71910bb66ef5a87a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 481280 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%0Ac>ei&x)le6zl> z@e27L&tGkBe9iy(5@h+W%l~+u?RBrZW0W)B@8`p-`)s#2m~^t!K@aZRC^ljh?+e*xf%o z-Csld=J?m~3dUOaEj{40ceMM{oBhKxXiG*ihd+D>9$zTO^yisyOk||_<%<%`z`#@ z#Q(F8A5{TP>%~bsln6LGIePti|D^TP$!lv!4RI^21)FC$Csm96Z_Sx-ZE8C1efguT>tiSj+{*WO}emH8W zScubqq#0fZKt))*@Uj(B9-LMbeS##6U3tbd`pj9OTDQH)RksgV_K@F@_heL-ZOVrZ z;2ExI3W2+;4f&cUn5=eVee;F?1g10F5gQ~c=Lh`>_7?`|wa;-bhO>E1deE4?u{|1f zLc{RICcjXaalr7!bAF-V<#Rz+2sFbNedZZB47^+gG;r zfT+;#Gaw43EXC=-_uoB)rU8Hr(W%)P+~oZxpQq|2a2V!SU2@7+HbRS*vlA>(R@A3x zCC|G3tKWiyczV)IFpM`N56lBTWZEEr0n!IT^{JkK3qw1}mW(YyKUnDW1T-l74BxHF z$8ll?51IMRkbPy`prL!)PLgL+Y8ZvA6jI(cm$#7Du4#p|r&+G=4 zDn6m>WNm6Okp-FxpIlKGJgF;>B2ajt!f|08?24>*6RCvpyLl!L*tVlNljmR_{6?NZ zp&o3W7N$@R187pBnbKH}>aVZmfejpx0$Z!ZNFhmf;m_P`sLcxujLtd zYW}T~CKlYLQYEz~*)a3TEo&YB`|t8| zb#h`ii5X_ks-=4$EB|#+`z8~O%t&JqJvV%O)HTR~adlnL6Vv4nl%IWLqXj$B z?6U4Hs6Z3zOPWBP!&f5NCdl<)FU%g%xRl!^xM7d;6r~BAC+O zJgvod0Bvosz;H` zjbj6SmR;M*u=xmYq`Ym|ou&M0?$gyBAvpexo|9G3r*fkPrt{XkAcQRT?(@7Q>*&Qx zD@vzN`;n~{rLQ|eK2=GKS5gq%kD%yzp`TP7Au4uV7$lEdtu7JH`*^8^-xr|H|zeUc+4V7a!_39BLq~7B~DT@N(hCeG8 zV-9ha9(a<0%0&Re3ew-Etp|S7r&vuzO_FpC(O=V+Gs2Qw%++=)F92> zsTzKtVgPb&{hDo{`Tk{ZN+zuwR6KqkBD2c#iLlXu<=6SJT^?tOsmJfyu5VSS;)ouU z)9gLzAadR7<$bM+QUr}>hL!I&vI^4RLR^~`j?M0+KET|R1p=MbrBuSGerJ0BlRxD@ zy!Ki2Fn1M&Wcy9VuQ@A3s;BcoMDhFDeiXgro1%oHuI74^Z`Z&$5 zuFqCyC(EPz`WCYTVf~xHN!)9kP&^&N36#mX(+}lP8{kX4#k`bt+T%7tq7e~T+ES4? zd`f<)pbA2LOT9g}I>5?5^N<@NjeFb?J>P3-=JJs`%xtpDju@gP*bV&IC6UER26dwor@(syf>x{wgQQJ5mW_xz5SG)k2Z7_XGL?- z2%`={f!4mXMQ09*7g^Y61#VO8P25)|E9}qD?NS7ak44|rthpen3q%>4ec~19>TS-A znqF09I;_uTbsrdX>gQqJMRhkCaO(SQ6cOmQCFYuPw*q86<B@0%QXb zSB#_Dt>&P_15Jms^_m9fB5qtCz`1pEK4mH-jTH9kdJ@#FtEpWpJ0?qy!R`2KjTWa=**{-vw9K@D&#*}wgM6MEEyROe{1Wtc&wQ3CxvKP^!N_b5i`#_?Nu-)UcWx*4Ioc}{A@~1 zq4+O(%X>~TfIe%f^-*wreg}KVd z6zc!<&Y!;g&y4q;KG!zsENEpH=^7=kWC4``7!e zH~VKhdpl=4h|~DcxXH(D`qEmk8I4eRsIiqbHkunXg~D0xn}_)pOTiBF_uc%iF0QKK zUrB47-*Z$v`fxMpcgOdQj|&S`^?xXX&W`quq^L)+08sknm#g_PWhPg42nCyZ2rs{p znxubPSh(o5QIynu**pKVnjhKPcZK{SfHNO1`(Y(aXQkP~`?%e4tF;_u6UyZ%WB^G_ zO79%*bs#*H{jiTUQ)ydf^Czr)>9SL-ohl?z}grC$mCT%WZHpSPQ6<=A`m(@LQ+P@-dic2X!tW)^rlP%L%y^>u(sv`K%GN zGThpCwTaYj#PlwM*%@_doomxKi^>!*N^7^@g>6d)vc_gxSvSq^qcoAVdD9-pEZGcz$t0dN-3RBFxf7i(t`uV{^esKZ*yaV2{sp5_wy|!_ zWmFgX6dot?a|8iU=)f;ZGepr%@oGJIYDmS|e+sIo?47H12}Y6du$>60tglv)pBr;k zXcK>kzZ8B^zZNbqCLZaKF2O^==wX^suGF}Kc+9RQB6INh)e>b`SOm%kDg)la(pP9=5wk?SGtnxs?vJv|kyf9@WhmRa@lLR+tiproQO z1gkP}LHM4T+*K57ZPc`y{SoKWVhSUVRRXI9vyRk@g7;~_ySxYr7Gttiz6#IX%ENMQ z556E)N(6opwRFoC_9MioGS1N=|42yDKvsKXSOaIS4u_Ua@{oV0A&mjP>1JcuqPs|u z$(dD-xh%*}5-ty@f<&Jd4751I98~6e$U0jDy(Zad*4^VCZx<(|>9;mNo<;i35cC4A zmcnv%J(cWLxbdtIR_B)q>?rW%IP|&Nm7+-+it^zWSf{j9pG0Ww$S=$oRumnXvN&j{ znX^B!d1Rgn%Yg1;f6T}u3v>IOPowy-KurN~;0W5^%+5u@hbW5H6D072vXL>H84`<= zG6Xa4clh!kXje8>Y=VN3P~4?W%9aLLuXfZUyK>+F@c_(#ZO<8Krmx+m&$hSwknt zc132k4b>^%!m8G2dIS{h749^dLJM^hl|WoSS*dBDuX(FY0JaY=@sTZFOol^N|51r0 z5k(lvagfP;35p_XNF@@94z5J%veo?>l-lu8aIBv^YASyH+yZ7SS$nL%1 zV;b$@eKxqH@m6pxH{f9hmVH)W8{=^e?^JQ;VdgHk2&F^!W>}w;wu(l*>-Ksq0j}%D z<=PE6xkc1>-oD9P&G{_v;{gTazG`S7pFi*A9GmO%yXR6|)F9rXXa*0?pT&ACcB8QM zt&DNtdApA-aNN8{FVv+*8``{XIK&@f#I#7BpO!_f{2$m!iyMTpDxuy;kBRL6MUo;4 zw--T!`C>iW%YnSTxcis=-M_Zp9PRB-X`3o|yw^t?_KV~)K6lAKyIzKql5Y|+c_NkX zSjSV9{NuI^{(3A!*zwj-toY{|vc#daFJmw*_y>M-f{w;24@v88rA;H_f@`i^TG2t! z8miNU3G{r3CRqxzIO~y`LW3d0&E>vLJPV<(Sc$48EVwU1k1OuAye0YA@5m5cBm4fi zy{JKk1#@heCES3@Q-$yRd?}WrTTZ!m^ri2Pc@w;IxQ8c`gXh1WbbGJ{qEMk5vM4V_ z=;Uy4j!-@sE-)GoL*+m6g-I*KK((pF-cYpWw`k~>iw?=V8yUUjhyg&cF?h+FMcKox ziQMr6aDRbn;&c|xPBJCh*#lBBWF19L$+*!==-7AsKyb*ADy0br15`Z!^far+8>yEl zAS*~?f7~<*=`vrF;31?m(I`rr-Z?&Qo$ljh%`;r1Q~Z)R;-9Zc@VBs$%^=Jo)deeU zOx;!76}A5&!by&!E80T){7Z^i|+r38+4emeWhCLbUJPZXHq z7J?Otdz~1YLK3x8&bzdk%X(x!zG{j^IbFGI7t6E#IWp^U<7aeCN!t7-(%nT1icveA zQHx2c&Mg)X@IY}P3h^TkRA(uPSMjiIJXM0L*u*}48SWMv4^V(+MN+NlgmJGzM)D7Q zv~bmE{B7~r^LpW@58+%npFTij-Hr!wxyc2I;&Y~DV+DEj<&%uPM8S2FR&~BTx9p6A zdSh-y$}euh`32saM-9QR$;O3~>T9xbe48!+NB;Z_xyE`Cj&g^mE5%&K+lENG#w!2S zEB!p3oSa5CFZH8)edTsNZ&iJ(##ORYAGcl4Rv}NBop2$S7V2CJ)|U8lXD~2R11(!` z7in9fb9EKjj|Zo4v>qJ($UnsC>V9X7Gx;4ElHzUCKThz+(FuPqJQ}A$eU?5+kKVJ*{f|EUSylC(2|6OY7i*r*_wgzmX7 z2Hc1$n~3+RfSQ`0JcWs84(c&iMzaoEnJZt*M+!?9cu_F&PGOuhqclLX0{c3(<&{1u z1VcvcT*OP9JP3TL4|EDRYO$@GPuDpW#d89-2B=eqp0a%%&L~H4z|`RfB8>;(vh@}@^cA@ag(>GyVCAoL4xS3K{li%jPR_c8;C(pmFHrscv?HnN{??w z>g#l(00sf$cPF2REs>7W^@$1~eiZ#Y^~&0MgP$(UVc)SPN;P<}ik9f3t(H{aZMAq8 zYqcC7>c#A_S)TR%3;DlPa-Tc{m%#t$xa77ILHQYt-XG|yRfvdleaKZ8m!L`e1ke@N|C-?VICY z$150XVd1x)0KB%gwxITxEs;U6xsW9nP_h8OM)mc!GWD)yKX-@Y34nN*k7-8APV+0i zP&UeznE|vyC3$au_vqvd{zkwT)}GPDuzO1oZ)M-ObVx?t&*SPnh2vwnVu znUGOb7%sc*wa4^(u4aStF$5PQ&UH|XheG}t-#&+YAL0s;BO? z;1aLtJIOm++2Uq%b8&%1n+L2~ZZ4gw`^RQ)X?^y#*#{*odeu-6=C^W8eJnJw!yPT;R4p!TrI-2S-M<|{(z8mXoE zZ~-}#`N;2SzyH3AuzY!6?tOdMWdbi?r?+6k2VENs=~riTWTSI&H@Y6i<1(lR7(qzP zd5&~f7Z19Yg^PCm>mscAf%NS+Kl*9^#s$tT05csv~RdT0*#-(7bv zt}{a2;()kLKUL$jd(-W;hrRn%X~cZ^9lF-(M%>y;s^8t_rz z$aKOw1|nstPPWXaO^vZuNEQgM(duRH%XSYTRGoW28SeIG0}t+JtNbm*;KZb`HJnhA|0tYifY{(Jd3KoO+

B7;LeS*mE||L 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 Date: Tue, 9 Jul 2024 15:30:44 +0300 Subject: [PATCH 82/82] Add wiki directory with documentation files --- wiki/5.2.6/Home.md | 20 + ...лачной-платформы-DECORT.md | 30 + wiki/5.2.6/введение.md | 90 ++ wiki/5.2.6/модуль-decort_bservice.md | 103 ++ wiki/5.2.6/модуль-decort_disk.md | 167 ++++ wiki/5.2.6/модуль-decort_group.md | 103 ++ wiki/5.2.6/модуль-decort_jwt.md | 56 ++ wiki/5.2.6/модуль-decort_k8s.md | 102 ++ wiki/5.2.6/модуль-decort_kvmvm.md | 112 +++ wiki/5.2.6/модуль-decort_osimage.md | 135 +++ wiki/5.2.6/модуль-decort_pfw.md | 73 ++ wiki/5.2.6/модуль-decort_rg.md | 223 +++++ wiki/5.2.6/модуль-decort_vins.md | 120 +++ wiki/5.2.6/полезные-советы.md | 16 + wiki/5.3.0/Home.md | 16 + wiki/5.3.0/введение.md | 27 + wiki/5.3.0/модуль-decort_bservice.md | 92 ++ wiki/5.3.0/модуль-decort_disk.md | 177 ++++ wiki/5.3.0/модуль-decort_group.md | 105 ++ wiki/5.3.0/модуль-decort_jwt.md | 53 ++ wiki/5.3.0/модуль-decort_k8s.md | 108 +++ wiki/5.3.0/модуль-decort_kvmvm.md | 106 +++ wiki/5.3.0/модуль-decort_lb.md | 893 ++++++++++++++++++ wiki/5.3.0/модуль-decort_osimage.md | 132 +++ wiki/5.3.0/модуль-decort_pfw.md | 76 ++ wiki/5.3.0/модуль-decort_rg.md | 200 ++++ wiki/5.3.0/модуль-decort_vins.md | 120 +++ wiki/Home.md | 4 + 28 files changed, 3459 insertions(+) create mode 100644 wiki/5.2.6/Home.md create mode 100644 wiki/5.2.6/Обзор-облачной-платформы-DECORT.md create mode 100644 wiki/5.2.6/введение.md create mode 100644 wiki/5.2.6/модуль-decort_bservice.md create mode 100644 wiki/5.2.6/модуль-decort_disk.md create mode 100644 wiki/5.2.6/модуль-decort_group.md create mode 100644 wiki/5.2.6/модуль-decort_jwt.md create mode 100644 wiki/5.2.6/модуль-decort_k8s.md create mode 100644 wiki/5.2.6/модуль-decort_kvmvm.md create mode 100644 wiki/5.2.6/модуль-decort_osimage.md create mode 100644 wiki/5.2.6/модуль-decort_pfw.md create mode 100644 wiki/5.2.6/модуль-decort_rg.md create mode 100644 wiki/5.2.6/модуль-decort_vins.md create mode 100644 wiki/5.2.6/полезные-советы.md create mode 100644 wiki/5.3.0/Home.md create mode 100644 wiki/5.3.0/введение.md create mode 100644 wiki/5.3.0/модуль-decort_bservice.md create mode 100644 wiki/5.3.0/модуль-decort_disk.md create mode 100644 wiki/5.3.0/модуль-decort_group.md create mode 100644 wiki/5.3.0/модуль-decort_jwt.md create mode 100644 wiki/5.3.0/модуль-decort_k8s.md create mode 100644 wiki/5.3.0/модуль-decort_kvmvm.md create mode 100644 wiki/5.3.0/модуль-decort_lb.md create mode 100644 wiki/5.3.0/модуль-decort_osimage.md create mode 100644 wiki/5.3.0/модуль-decort_pfw.md create mode 100644 wiki/5.3.0/модуль-decort_rg.md create mode 100644 wiki/5.3.0/модуль-decort_vins.md create mode 100644 wiki/Home.md diff --git a/wiki/5.2.6/Home.md b/wiki/5.2.6/Home.md new file mode 100644 index 0000000..0f4cb12 --- /dev/null +++ b/wiki/5.2.6/Home.md @@ -0,0 +1,20 @@ +1. [Введение](./введение.md) + - [Введение](./введение.md#введение) + - [Системные требования](./введение.md#системные-требования) + - [Подготовка к работе](./введение.md#подготовка-к-работе) +2. [Обзор облачной платформы DECORT.](./Обзор-облачной-платформы-DECORT.md) + - [Основные понятия](./Обзор-облачной-платформы-DECORT.md#основные-понятия) + - [Способы авторизации](./Обзор-облачной-платформы-DECORT.md#способы-авторизации) +3. Ansible модули DECORT: + - [Модуль decort_kvmvm](./модуль-decort_kvmvm.md) - управление виртуальными серверами KVM. + - [Модуль decort_osimage](./модуль-decort_osimage.md) - получение идентификатора образа ОС. + - [Модуль decort_disk](./модуль-decort_disk.md) - управление дисковыми ресурсами. + - [Модуль decort_pfw](./модуль-decort_pfw.md) - настройки правил трансляции сетевых портов для виртуального сервера. + - [Модуль decort_rg](./модуль-decort_rg.md) - управление ресурсными группами. + - [Модуль decort_vins](./модуль-decort_vins.md) - управление виртуальными сетевыми сегментами. + - [Модуль decort_jwt](./модуль-decort_jwt.md) - получение авторизационного токена. + - [Модуль decort_bservice](./модуль-decort_bservice.md) - управление Basic сервисами. + - [Модуль decort_group](./модуль-decort_group.md)- управление группами виртуальных серверов. + - [Модуль decort_k8s](./модуль-decort_k8s.md)- управление кластерами Kubernetes. +4. [Полезные советы](./полезные-советы.md) + - [Как хранить авторизационную информацию отдельно от плейбука.](./полезные-советы.md#41-как-хранить-авторизационную-информацию-отдельно-от-плейбука) diff --git a/wiki/5.2.6/Обзор-облачной-платформы-DECORT.md b/wiki/5.2.6/Обзор-облачной-платформы-DECORT.md new file mode 100644 index 0000000..9424cb2 --- /dev/null +++ b/wiki/5.2.6/Обзор-облачной-платформы-DECORT.md @@ -0,0 +1,30 @@ +## Основные понятия + +Ниже перечислены основные понятия с указанием соответствующих им аргументов в Terraform провайдере DECORT. + +1. Контроллер облачной платформы DECORT – управляющее приложение, которое обеспечивает авторизацию пользователей и оркестрацию облачных ресурсов. + - Адрес контроллера задается в обязательном аргументе `controller_url` в Вашем плейбуке. Например, `controller_url= "https://ds1.digitalenergy.online"` +2. Авторизационный провайдер – приложение, работающее по протоколу Oauth2, предназначенное для выпуска и валидации токенов доступа к контроллеру облачной платформы в соответствующих режимах авторизации. Все действия в платформе должны выполняться авторизованными пользователями, и авторизационное приложение позволяет получить токен доступа, действующий некоторое ограниченное время, наличие которого подтверждает успешную авторизацию. + - Адрес авторизационного провайдера задается в аргументе `oauth2_url` в Вашем плейбуке. Например `oauth2_url= "https://sso.digitalenergy.online"`. При этом необходимо указать тип авторизации `authenticator: oauth2` +3. Подписчик (account) – сущность, которая используется для группирования облачных ресурсов по принадлежности к определенному клиенту для целей учета потребления и биллинга. + - Имя подписчика задается аргументом `account_name` в Вашем плейбуке. Альтернативой является задание численного идентификатора подписчика в аргументе `account_id`. +4. Пользователь (user) – пользователь облачной инфраструктуры, представленный учетной записью. Чтобы получить возможность управлять облачными ресурсами (например, создавать виртуальные серверы или дискт) пользователь должен быть ассоциирован с одним или несколькими подписчиками и иметь соответствующие права, определяемые ролевой моделью, принятой в облачной платформе DECORT. Для доступа к платформе пользователь должен авторизоваться одним из способов, описанных ниже в разделе «Способы авторизации». +5. Ресурсная группа (resource group) – способ группирования вычислительных ресурсов (например, виртуальных серверов по функциональному признаку или принадлежности к одному и тому же проекту). Ресурсную группу можно рассматривать как небольшой персональный дата-центр, в котором размещаются один или несколько серверов и виртуальных сетевых сегментов. Ресурсная группа идентифицируется по комбинации параметров `account` и `name`. Обратите внимание, что имя имя ресурсной группы уникально только в рамках одного и того же `account`. +6. Вычислительный ресурс (compute) - универсальная абстракция пользовательского сервера в платформе DECORT. Благодаря использованию такой абстракции можно, например, создать одну виртуальную машину на базе KVM Intel x86, а другую - на базе KVM IBM Power, а потом управлять ими - изменять количество CPU/RAM, подключать/отключать диски и т.п. - одинаковым образом, не задумываясь об их архитектурных различиях. +7. Ресурс хранения (disk) - универсальная абстракция дискового ресурса в платформе DECORT. Платформа поддерживает различные типы систем хранения данных, но при этом управление созданными на разных системах хранения дисками осуществляется посредством унифицированного набора действий, например, "подключить диск к compute", "увеличить размер диска", "сделать мгновенный снимок диска", "настроить параметры быстродействия диска". +8. Виртуальный сервер – экземпляр compute, в основе технической реализации которого лежит виртуальная машина, работающая в облаке DECORT и доступна по сети. Виртуальный сервер характеризуется количеством выделенных ему CPU (аргумент `cpu`), объемом ОЗУ (`ram`), размером загрузочного диска (`boot_disk size`). При создании виртуального сервера на загрузочный диск устанавливается образ операционной системы, заданный в аргументе `image_id`. Помимо загрузочного диска к виртуальному серверу можно подключить несколько дисков для хранения прикладных данных, список которых задается аргументами `extra_disks`. Виртуальный сервер идентифицируется по комбинации аргументов `name` (имя сервера) и `rgid` (идентификатор ресурсной группы). Обратите внимание, что имя виртуального сервера `name` уникально только в рамках одной и той же ресурсной группы. +9. Виртуальный сетевой сегмент (Virtual Network Segment или ViNS) - сетевой сегмент и обеспечивающая его функционирование виртуальная инфраструктура, которые пользователь может создавать для своих нужд на уровне ресурсной группы или подписчика (account). ViNS можно создать полностью изолированным от внешних сетей (см. ниже External Network) или с подключением во внешнюю сеть. Внутри ViNS работает DHCP-сервис, обеспечивающий управление IP адресами экземпляров compute, подключённых в этот ViNS. +10. Внешняя сеть (External Network) - сетевой сегмент, через который платформа DECORT взаимодействует с внешними по отношению к ней сетевыми ресурсами. Например, в случае с публичным облаком на базе DECORT в качестве внешней сети выступает сеть Интернет. В отличие от ViNS платформа не управляет внешней сетью, а лишь пользуется её ресурсами. В платформе может быть настроено несколько внешних сетей с различными диапазонами IP адресов, и существует механизм управления доступом пользователей к внешним сетям. +11. Сетевой доступ к экземпляру compute (виртуальному серверу) реализуется через его подключение к ViNS и/или прямое подключение во внешнюю сеть (External Network). Один и тот же экземпляр compute может одновременно иметь несколько подключений в разные ViNS и/или различные внешние сети. + +## Способы авторизации + +Облачная платформа DECORT поддерживает два базовых типа авторизации: + +1. С использованием авторизационного провайдера, работающего по протоколу Oauth2. Данный способ является предпочтительным, так как обеспечивает бОльшую гибкость и безопасность. Для авторизации в этом режиме в Вашем плейбуке необходимо указать параметры `oauth2_url` и `controller_url`, а также предоставить одно из нижеперечисленного: + - Комбинация Application ID & Application secret, соответствующих пользователю, от имени которого будет осуществляться управление облачными ресурсами в текущей сессии. В процессе проверки предоставленных Application ID & Application secret модуль получает от авторизационного провайдера токен (JSON Web Token, JWT), который затем используется для доступа к указанному контроллеру DECORT. Для авторизации по данному варианту, в Вашем плейбуке следует установить аргумент `authenticator=oauth2` и задать аргументы `app_id` и `app_secret` (или определить соответствующие переменные окружения `DECORT_APP_ID` и `DECORT_APP_SECRET`). + - JSON Web Token – заранее полученный от авторизационного провайдера токен доступа, ассоциированный с определенным пользователем, от имени которого будет осуществляться управление облачными ресурсами в текущей сессии. Для авторизации по данному варианту, при инициализации Terraform провайдера DECORT следует установить аргумент `authenticator=jwt` и задать аргумент `jwt` (или определить переменную окружения `DECORT_JWT`). +2. С использованием комбинации имя пользователя : пароль. Данный режим не использует внешних авторизационных провайдеров и подразумевает, что пользователь с такой комбинацией зарегистрирован непосредственно на указанном в параметре `controller_url` контроллере облачной платформы DECORT. + - Чтобы провайдер авторизовался по данному варианту, при его инициализации следует установить аргумент `authenticator=legacy` и задать аргументы `user` и `password` (или определить соответствующие переменные окружения `DECORT_USER` и `DECORT_PASSWORD`). + +После успешной авторизации пользователь (или приложение-клиент) получает доступ к ресурсам, находящимся под управлением соответствующего DECORT контроллера. Доступ предоставляется в рамках подписчиков (`account`), с которыми ассоциирован данный пользователь (`user`), и в соответствии с присвоенными ему ролями. \ No newline at end of file diff --git a/wiki/5.2.6/введение.md b/wiki/5.2.6/введение.md new file mode 100644 index 0000000..a697840 --- /dev/null +++ b/wiki/5.2.6/введение.md @@ -0,0 +1,90 @@ +# Модули Ansible для управления облачными ресурсами в платформе DECORT +## Введение + +Настоящая документация содержит руководство пользователя по библиотеке модулей decort для Ansible. С помощью этих модулей Вы сможете управлять созданием и конфигурированием облачных ресурсов в платформе DECORT (Digital Energy Cloud Orchestration Technology). + +У каждого модуля есть свой раздел, в котором Вы можете найти подробную информацию о параметрах, которые использует и возращает данный модуль, а также примеры использования данного модуля. + +В состав библиотеки модулей DECORT для Ansible на текущий момент входят: + +- decort_kvmvm - модуль для управления жизненным циклом виртуальных серверов (экземпляров compute). Платформа DECORT поддерживает виртуальные серверы на базе технологии KVM и аппаратных архитектур Intel x86 и IBM PowerPC. +Позволяет: + 1. Создавать новые виртуальные сервера указанной аппаратной архитектуры. Платформа поддерживает виртуальные сервера архитектуры Intel x86 и IBM PowerPC. + 2. Изменять конфигурацию существующего виртуального сервера: + - Изменять количества выделенных CPU и виртуальной ОЗУ. Следует иметь ввиду, что изменение этих параметров в меньшую сторону может потребовать перезагрузки гостевой ОС. + - Увеличивать размера загрузочного диска + - Подключать / отключать дополнительные дисковые ресурсы. + - Создавать / удалять сетевые подключения. + 3. Изменять состояние существующего виртуального сервера: + - Выключать / включать. + - Перезагружать, приостанаваливать / возобновлять работу гостевой ОС. + 4. Удалять существующий виртуальный сервер. + 5. Получать информацию о существующем виртуальном сервере без изменения его конфигурации. +- decort_disk - модуль для управления жизненным циклом дисковых ресурсов, которые можно подключать к экземплярам compute. +Позволяет: + 1) создавать / удалять диск; + 2) изменять размер диска. +- decort_rg - модуль для управления жизненным циклом ресурсных групп (Resource Groups, RG). +Позволяет: + 1) Создавать\удалять ресурсные группы. + 2) Запрашивать информацию об уже существующих ресурсных группах. + 3) Редактировать ресурсные группы. + 4) Настраивать квоты в ресурсных группах. + 5) Восстанавливать ресурсные группы. +- decort_vins - модуль для управления жизненным циклом виртуальных сетевых сегментов (Virtual Network Segment, ViNS). Экземпляры compute (напр., виртуальные серверы) могут быть подключены к нескольким виртуальным сетевым сегментам. +Позволяет: + 1) Создавать, удалять виртуальные сетевые сегменты. + 2) Изменять виртуальные сетевые сегменты. + 3) Запрашивать информацию о виртуальных сетевых сегментах. + 4) Восстанавливать удалённые виртуальные сетевые сегменты. + +- decort_pfw - модуль для настройки правил трансляции сетевых портов виртуальных серверов. +- decort_osimage - модуль для взаимодействия с образами. +Позволяет: + 1) Создавать новый образ операционной системы для виртуальных машин. + 2) Создавать виртуальный образ для образа операционной системы. + 3) Изменять имя образа (в случае изменения имени виртуального образа, имя и id виртуального образа указываются как image_name и image_id). + 4) Привязывать другой образ операционной системы к виртуальному образу. + 5) Удалять существующий образ, привязанный к аккаунту. + 6) Получать информацию об образе. +- decort_jwt - модуль для получения авторизационного токена (JWT - JSON Web Token), который можно использовать при массовом создании экземпляров compute для ускорения работы playbook-а. Подробности см. в соответствующем разделе. +- decort_bservice - модуль для взаимодействия с Basic сервисами (Basic service). +Позволяет: + 1) Создавать Basic Service. + 2) Удалять Basic Service. + 3) Включать/выключать Basic Service. + 4) Запрашивать информацию о Basic Service. +- decort_group - модуль для взаимодействия с группами виртуальных серверов(compute). +Позволяет: + 1) Получить информацию о группе виртуальных серверов. + 2) Запустить/остановить группу виртуальных серверов. + 3) Изменить параметры группы виртуальных серверов (cpu, ram, disk, и т.д). + 4) Изменить количество виртуальных серверов в группе. + 5) Изменить параметры сети (ext_net, VINS). + 6) Создать группу виртуальных серверов и подключить её к Basic сервису. + 7) Удалить группу виртуальных серверов. +- decort_k8s - модуль для взаимодействия с кластерами Kubernetes. +Позволяет: + 1) Создавать/удалять кластера Kubernetes. + 2) Enable/Disable кластера Kubernetes. + 3) Запускать/Останавливать кластера Kubernetes + 4) Модифицировать кластера Kubernetes + 5) Получать информацию об уже существующем кластере Kubernetes. + +## Системные требования +Убедитесь, что Ваша система соответствует требованиям для работы модуля DECORT. +Системные требования для работы модуля: +- Ansible 2.7 or higher +- Python 3.7 or higher +- PyJWT 2.0.0 Python module or higher +- requests Python module +- netaddr Python module +- DECORT cloud platform version 3.5.0 or higher + +## Подготовка к работе + +Для начала работы необходимо разместить директории library и module_utils в Вашей рабочей директории, откуда будут запускаться плейбуки, либо указать в ansible.cfg путь до них. + + + + diff --git a/wiki/5.2.6/модуль-decort_bservice.md b/wiki/5.2.6/модуль-decort_bservice.md new file mode 100644 index 0000000..7289a3e --- /dev/null +++ b/wiki/5.2.6/модуль-decort_bservice.md @@ -0,0 +1,103 @@ +# 03.08 Модуль decort_bservice + +## Обзор модуля decort_bservice + +Базовый сервис (Basic Service) это несколько групп виртуальных серверов (compute), создаваемых и управляемых как единое целое. + +Все compute(s) в группе имеют одни и те же характеристики (cpu/ram/boot disk size/OS image + сетевые подключения). Для разных групп эти характеристики могут быть разными. + +Группы в составе Basic Service могут иметь отношения parent-child с другими группами. Наличие таких отношений определяет последовательность запуска групп ("сначала parents"). + +На основе ресурсов, предоставляемых и управляемых через Basic Service, могут создаваться другие сервисы. + +`Для взаимодействия с группами виртуальных серверов используется модуль decort_group.` + +Модуль decort_disk предназначен для управления basic сервисами, в которых находятся группы виртуальных серверов. +Данный модуль позволяет: +- Создавать Basic Service. +- Удалять Basic Service. +- Включать/выключать Basic Service. +- Запрашивать информацию о Basic Service. + + +## Параметры модуля decort_bservice + +Ниже в алфавитном порядке приведен полный список параметров для модуля decort_bservice. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на Вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_bservice` + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| account_id | (int) | Уникальный целочисленный идентификатор учётной записи (account), которой принадлежит данный диск. При идентификации диска по имени (см. параметр `name`) должно быть задан либо идентификатор, либо имя учётной записи (см. параметр `account_name`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется.| +|account_name | (string) | Имя учётной записи (account), которой принадлежит данный диск. При идентификации диска по имени (см. параметр name) должно быть задано либо имя, либо идентификатор учётной записи (см. параметр `account_id`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется. | +| annotation | (string) | Текстовое описание диска. Данный аргумент является опциональным и учитывается только при создании диска, а при всех прочих операциях игнорируется.| +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`.| +|app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`.| +| authenticator | Значения:
`legacy`
`oauth2`
`jwt` <- default | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. | +|controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) данный виртуальный сервер. Данный параметр является обязательным.| +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt`
Данный параметр является обязательным для указанного режима.
Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в _playbook_.
Если этот параметр не определен в _playbook_, то модуль будет использовать значение переменной окружения _DECORT_JWT_. | +|oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`.| +|state | Значения: absent, disabled, enabled, present, check| Состояние Basic сервиса.| +| started | (bool) | Параметр, определяющий состояние добавленных виртуальных серверов в Basic Service. Запускать их, или нет. По умолчанию: False. | +| name | (string) | Имя, с которым будет создан Basic сервис. Также используется для поиска ID Basic сервиса. | +| sshuser | (string) | Имя пользователя, который будет создан на всех виртуальных серверах в группе basic сервиса. Используется в паре с sshkey. | +| sshkey | (string) | SSH ключ, который будет загружен на все виртуальные сервера в группе basic сервиса. Используется в паре с sshuser. | +| id | (int) | Уникальный целочисленный идентификатор Basic сервиса, используется для поиска, взаимодействия и удаления Basic сервиса. | +| rg_id | (int) | Уникальный целочисленный идентификатор ресурсной группы, в которой будет работать модуль. | +| rg_name | (string) | Имя ресурсной группы, в которой будет работать модуль. | +| description | (string) | Описание. По умолчанию: `Created by decort ansible module`| +| verify_ssl | (bool)
`True` <- default
`False` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +| workflow_callback | (string) | URL, по которому вышестоящее приложение (например, пользовательский портал или оркестратор верхнего уровня, инициирующий запуск Ansible playbook) ожидает API вызова, в параметрах которого модуль будет оперативно передавать информацию о своем статусе и текущей фазе исполнения.
Данный параметр является опциональным.
Функциональность callbacks в текущей версии модуля не реализована.| +| workflow_context | (string) | Контекстная информация, которая будет содержаться в параметрах API вызова, адресованного к `workflow_callback` URL.
Данная информация призвана однозначно идентифицировать задачу, выполняемую модулем в настоящий момент, чтобы оркестратор верхнего уровня мог сопоставить получаемые через вызов `workflow_callback` данные со своим внутренним состоянием и отслеживать инициированные им задачи.
Параметр является опциональным и имеет значение только при условии, что также задан `workflow_callback`.
Функциональность callbacks в текущей версии модуля не реализована.| + +## Возвращаемые значения модуля decort_bservice + +Модуль decort_bservice возвращает информацию о диске в виде словаря facts со следующими ключами: + + +|Ключ | Тип данных | Описание| +| ------ | ------ | ------ | +|id | int | Уникальный целочисленный идентификатор данного Basic сервиса.| +|name | string | Имя Basic сервиса.| +| techStatus | string | Технический статус Basic сервиса. | +|state | string | Текущий статус Basic сервиса. Корректные состояния:MODELED, DISABLING, ENABLING, DELETING, DELETED, "DESTROYING, DESTROYED, RESTORYNG, RECONFIGURING| +| rg_id | int | Уникальный целочисленный идентификатор ресурсной группы, в котором находится Basic сервис.| +| account_id | int | Уникальный целочисленный идентификатор пользователя, к которому привязан данный Basic сервис.| +| groupsName | string | Имена групп виртуальных серверов, которые находятся в данном Basic сервисе.| +| groupsIds | string | Уникальные целочисленные идентификаторы групп виртуальных серверов, которые находятся в данном Basic сервисе. | + + +## Пример использования модуля decort_bservice + +Данный пример создаёт Basic сервис с названием databases. + +``` + - name: Manage bservice at RG + decort_bservice: + account_id: 98 + verify_ssl: false + authenticator: jwt + jwt: "{{ my_jwt.jwt }}" + controller_url: "https://ds1.digitalenergy.online" + rg_id: 1629 + state: present + name: databases + started: True + register: db_bservice +``` + +Данный пример удаляет Basic сервис с названием databases. +``` + - name: Manage bservice at RG + decort_bservice: + account_id: 98 + verify_ssl: false + authenticator: jwt + jwt: "{{ my_jwt.jwt }}" + controller_url: "https://ds1.digitalenergy.online" + rg_id: 1629 + state: absent + name: databases + started: False + register: db_bservice +``` \ No newline at end of file diff --git a/wiki/5.2.6/модуль-decort_disk.md b/wiki/5.2.6/модуль-decort_disk.md new file mode 100644 index 0000000..bb47cda --- /dev/null +++ b/wiki/5.2.6/модуль-decort_disk.md @@ -0,0 +1,167 @@ +# 03.03 Модуль decort_disk +## Обзор модуля decort_disk + +Модуль decort_disk предназначен для управления дисковыми ресурсами в платформе DECORT: + +- создание / удаление диска; +- изменение размера диска. +- переименование диска. +- ограничение ввода / вывода диска. +- восстановление удаленного диска из корзины. + +Обратите внимание: + +- загрузочный диск для виртуального сервера создаётся и подключается автоматически в процессе создания этого сервера. - Модуль decort_disk служит для управления дополнительными дисками (т.н. data-дисками); +- подключение дополнительных дисков, созданных посредством decort_disk, к виртуальным серверам выполняется с помощью модулей управления compute-ресурсами (например, подробнее см. модуль decort_kvmvm, параметр data_disks). + +## Параметры модуля decort_disk + +Ниже в алфавитном порядке приведен полный список параметров для модуля decort_disk. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_disk` + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| account_id | (int) | Уникальный целочисленный идентификатор учётной записи (account), которой принадлежит данный диск. При идентификации диска по имени (см. параметр `name`) должно быть задан либо идентификатор, либо имя учётной записи (см. параметр `account_name`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется.| +|account_name | (string) | Имя учётной записи (account), которой принадлежит данный диск. При идентификации диска по имени (см. параметр name) должно быть задано либо имя, либо идентификатор учётной записи (см. параметр `account_id`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется. | +| annotation | (string) | Текстовое описание диска. Данный аргумент является опциональным и учитывается только при создании диска, а при всех прочих операциях игнорируется.| +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`.| +|app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`.| +| authenticator | Значения:
`legacy`
`oauth2`
`jwt` <- default | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. | +|controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) данный виртуальный сервер. Данный параметр является обязательным.| +| description | (string) | Описание диска. Используется при создании диска, по умолчанию установлено: "Disk created with Ansible Decort_disk module." | +|id | (int) | Уникальный целочисленный идентификатор диска. Соответствующий диск должен существовать (таким образом, с помощью id нельзя создать новый диск, а только управлять уже имеющимися). Если задан данный параметр, то параметры `name`, `account_name` и `account_id` игнорируются.| +| iops | (int) | Ограничение ввода/вывода диска. Используется при создании диска. | +| force_detach | (bool)
`True`
`False` <- default | Задаёт поведение платформы при попытке удалить диск, подключённый к экземпляру _compute_.
По умолчанию, удаление подключённых дисков не разрешается, и попытка удалить такой диск приведёт к аварийному завершению модуля. Чтобы изменить это поведение, явно установите `force_detach: True`. | +|jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt` Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`.| +| limitIO | (dict) | Параметр, позволяющий ограничить скорость ввода/вывода диска в iops, так и в байтах в секунду. Обратите внимание, что параметры с total не задаются вместе с read/write. Все возможные подпараметры можно увидеть в примерах.| +| name | (string) | Имя диска. Для идентификации диска требуется либо его имя name и информация об учётной записи (`account_id` или `account_name`), которой принадлежит диск, либо его `id`.| +|oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`.| +| permanently | (bool) | Параметр, использующийся при удалении диска, при значении True - диск удалится навсегда, а при False - попадёт в корзину. | +|place_with | (int) | Идентификатор образа ОС, из которого следует взять параметр `sep_id`, чтобы разместить данный диск на той же системе хранения данных, что и указанный образ ОС. Данный параметр является опциональным и используется только на стадии создания диска. Если задан `place_with`, то `sep_id` игнорируется.| +|pool | (string) | Название пула на системе хранения данных, в рамках которой следует создать данный диск. Этот параметр используется только на стадии создания диска и игнорируется при операциях над уже существующими дисками. Параметр является опциональным, значение по умолчанию - пустая строка (в этом случае платформа использует пул, который сконфигурирован на целевой системе хранения как пул по умолчанию). | +| reason | (string) | Причина, по которой было выполнено какое-либо действие, в данном модуле используется только при удалении диска. | +|sep_id | (int) | Идентификатор провайдера системы хранения данных (Storage End-point Provider). Данный параметр определяет систему хранения данных, на ресурсах которой создаётся диск. Используется только при создании диска и игнорируется при прочих операциях. Альтернативой данному параметру является `place_with`, позволяющий разместить диск на той же системе хранения, что и указанный загрузочный образ, на базе которого создаётся экземпляр compute.| +|size | (int) | Размер диска в ГБ. Этот параметр является обязательным при создании диска. Если он задан для уже существующих дисков, а текущий размер диска меньше заданного, то будет предпринята попытка увеличить размер диска. При прочих операциях данный параметр игнорируется.| +| state | Значения:
`present` <- default
`absent`
| Целевое состояние диска. | +| type | (string) | Тип создаваемого диска. Возможные значения: B-Boot, D-Data, T-Temp. По умолчанию установлено значение D.| +| verify_ssl | (bool)
`True` <- default
`False` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +| workflow_callback | (string) | URL, по которому вышестоящее приложение (например, пользовательский портал или оркестратор верхнего уровня, инициирующий запуск Ansible playbook) ожидает API вызова, в параметрах которого модуль будет оперативно передавать информацию о своем статусе и текущей фазе исполнения.
Данный параметр является опциональным.
Функциональность callbacks в текущей версии модуля не реализована.| +| workflow_context | (string) | Контекстная информация, которая будет содержаться в параметрах API вызова, адресованного к `workflow_callback` URL.
Данная информация призвана однозначно идентифицировать задачу, выполняемую модулем в настоящий момент, чтобы оркестратор верхнего уровня мог сопоставить получаемые через вызов `workflow_callback` данные со своим внутренним состоянием и отслеживать инициированные им задачи.
Параметр является опциональным и имеет значение только при условии, что также задан `workflow_callback`.
Функциональность callbacks в текущей версии модуля не реализована.| + +## Возвращаемые значения модуля decort_disk + +Модуль decort_disk возвращает информацию о диске в виде словаря facts со следующими ключами: + + +|Ключ | Тип данных | Описание| +| ------ | ------ | ------ | +|account_id | int | Уникальный целочисленный идентификатор учётной записи (account), которой принадлежит диск.| +|attached_to | int | Идентификатор экземпляра Compute (напр., виртуального сервера), к которому в настоящий момент подключён диск. Если диск не подключён, то attached_to=0| +|gid | int | Идентификатор физического кластера (Grid ID), на ресурсах которого создан диск.| +|id | int | Уникальный целочисленный идентификатор данного диска.| +| iotune | dict | Текущие ограничение ввода / вывода диска. | +|name | string | Имя диска. Обратите внимание, что имя диска не является уникальным с точки зрения системы хранения данных, на которой этот диск расположен.| +|pool | string | Имя пула на системе хранения данных, в котором размещаются ресурсы диска.| +|sep_id | int | Идентификатор системы хранения (Storage Endpoint Provider), на которой размещаются ресурсы данного диска.| +|size | int | Размер диска в ГБ.| +|state | string | Текущий статус диска. Корректные состояния: CREATED, ASSIGNED, DELETED, DESTROYED, PURGED.| + + +## Пример использования модуля decort_disk + +В данном примере создаётся диск размером 50ГБ (size: 50), с ограничением ввода / вывода в 2000 iops, на ресурсах системы хранения, доступной через Storage Endpoint provider под номером 1 (sep_id: 1) в пуле "data01". + +``` +- name: manage data disk + decort_disk: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + name: "DataDisk01" + size: 50 + account_name: "MyAccount" + sep_id: 1 + iops: 2000 + description: "Disk example" + pool: data01 + state: present + register: my_data_disk01 + delegate_to: localhost +``` +В данном примере ограничивается уже существующий диск с именем "DataDisk01", ограничивается как чтение/запись в iops, так и в байтах, также задаётся их максимальное чтение / запись(read/write_*_sec_max). + +``` +- name: manage data disk + decort_disk: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + name: "DataDisk01" + account_name: "MyAccount" + limitIO: + read_bytes_sec: 100 + read_bytes_sec_max: 100 + read_iops_sec: 100 + read_iops_sec_max: 100 + size_iops_sec: 100 + write_bytes_sec: 100 + write_bytes_sec_max: 100 + write_iops_sec: 100 + write_iops_sec_max: 100 + total_bytes_sec: 0 + total_iops_sec: 0 + total_bytes_sec_max: 0 + total_iops_sec_max: 0 + state: present + register: my_data_disk01 + delegate_to: localhost +``` + +В данном примере выполняется восстановление удаленного диска с id 111 из корзины. + +``` +- name: manage data disk + decort_disk: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + id: 111 + state: present + register: my_data_disk01 + delegate_to: localhost +``` + +В данном примере выполняется переименование диска с id 111 на новое имя "NewExampleDisk". + +``` +- name: manage data disk + decort_disk: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + id: 111 + name: "NewExampleDisk" + state: present + register: my_data_disk01 + delegate_to: localhost +``` + +Здесь результат работы модуля decort_disk записывается в переменную my_data_disk01. Для получения идентификатора диска, например, при подключении его к виртуальному серверу, следует воспользоваться показанной ниже конструкцией: + +``` +- name: manage virtual server + decort_kvmvm: + << для краткости фрагмент опущен >> + data_disks: + - "{{ my_data_disk01.facts.id }}" + << для краткости фрагмент опущен >> +``` \ No newline at end of file diff --git a/wiki/5.2.6/модуль-decort_group.md b/wiki/5.2.6/модуль-decort_group.md new file mode 100644 index 0000000..2f0a003 --- /dev/null +++ b/wiki/5.2.6/модуль-decort_group.md @@ -0,0 +1,103 @@ +# 03.09 Модуль decort_group +## Обзор модуля decort_group +Модуль decort_group позволяет взаимодействовать с группами виртуальных серверов, которые находятся в Basic сервисе. `Взаимодействие с Basic сервисами происходит в модуле decort_bservice.` + +Модуль decort_group позволяет: +- Получить информацию о группе виртуальных серверов. +- Запустить/остановить группу виртуальных серверов. +- Изменить параметры группы виртуальных серверов (cpu, ram, disk, role, name). +- Изменить количество виртуальных серверов в группе. +- Изменить параметры сети (ext_net, VINS). +- Создать группу виртуальных серверов и подключить её к Basic сервису. +- Удалить группу виртуальных серверов. + +## Параметры модуля decort_group +Ниже приведен полный список параметров для модуля decort_group. + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| account_id | (int) | Уникальный целочисленный идентификатор учётной записи (account), которой принадлежит данный диск. При идентификации диска по имени (см. параметр `name`) должно быть задан либо идентификатор, либо имя учётной записи (см. параметр `account_name`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется.| +|account_name | (string) | Имя учётной записи (account), которой принадлежит данный диск. При идентификации диска по имени (см. параметр name) должно быть задано либо имя, либо идентификатор учётной записи (см. параметр `account_id`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется. | +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`.| +|app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`.| +| authenticator | Значения:
`legacy`
`oauth2`
`jwt` <- default | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. | +|controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) данный виртуальный сервер. Данный параметр является обязательным.| +|jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt` Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`.| +|oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`.| +|id | (int) | Уникальный целочисленный идентификатор группы виртуальных серверов.| +| image_id | (int) | Уникальный целочисленный идентификатор образа операционной системы. Используется для создания виртуальных серверов в группе. | +| image_name | (string) | Имя образа операционной системы. Используется для создания виртуальных серверов в группе.| +| driver | (string) | Тип виртуального сервера. Прим. `KVM_X86` | +| boot_disk | (int) | Обьём виртуального диска на виртуальных серверах. | +| bservice_id | (int) | Уникальный целочисленный идентификатор Basic серфиса, в который необходимо добавить группу виртуальных серверов. | +| count | (int) | Количество виртуальных серверов в группе. | +| timeoutStart | (int) | Задержка в секундах до отображения группы виртуальных серверов. | +| role | (string) | Роль группы виртуальных серверов. | +| cpu | (int) | Количество ядер процессора на каждом виртуальном сервере в группе. | +| ram | (int) | Объём оперативной памяти на каждом виртуальном сервере в группе. | +| networks | (list) | Список сетей, которые необходимо подключить к каждому виртуальному серверу в группе | +| description | (string) | Описание группы виртуальных серверов. | +|verify_ssl | (bool) | True <- default False Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты. Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах.| +| workflow_callback | (string) | URL, по которому вышестоящее приложение (например, пользовательский портал или оркестратор верхнего уровня, инициирующий запуск Ansible playbook) ожидает API вызова, в параметрах которого модуль будет оперативно передавать информацию о своем статусе и текущей фазе исполнения. Данный параметр является опциональным. Функциональность callbacks в текущей версии модуля не реализована. | +|workflow_context | (string) | Контекстная информация, которая будет содержаться в параметрах API вызова, адресованного к `workflow_callback` URL. Данная информация призвана однозначно идентифицировать задачу, выполняемую модулем в настоящий момент, чтобы оркестратор верхнего уровня мог сопоставить получаемые через вызов `workflow_callback` данные со своим внутренним состоянием и отслеживать инициированные им задачи. Параметр является опциональным и имеет значение только при условии, что также задан `workflow_callback`.| + +## Возвращаемые значения модуля decort_group + +Модуль decort_group возвращает информацию о диске в виде словаря facts со следующими ключами: + + +|Ключ | Тип данных | Описание| +| ------ | ------ | ------ | +| account_id | int | Уникальный целочисленный идентификатор аккаунта, к которому привязана данная группа виртуальных серверов. | +| rg_id | int | Уникальный целочисленный идентификатор ресурсной группы, в которой находится данная группа виртуальных серверов. | +| id | int | Уникальный целочисленный идентификатор данной группы виртуальных серверов.| +| name | string | Имя данной группы виртуальных серверов.| +| techStatus | string | Технический статус данной группы виртуальных серверов. | +| state | string | Текущее состояние группы виртуальных серверов. Корректные состояния: absent, started, stopped, present, check.| +| Computes | string | Уникальные целочисленные идентификаторы виртуальных серверов, входящих в группу. | + +## Пример использования модуля decort_group + +Данный пример создаёт группу виртуальных серверов с названием test_group. +``` +- hosts: localhost + tasks: + - name: create + decort_group: + authenticator: oauth2 + verify_ssl: False + controller_url: "https://ds1.digitalenergy.online" + state: present + bservice_id: 1823 + count: 2 + name: "test_group" + cpu: 2 + ram: 2 + boot_disk: 10 + image_id: 518 + driver: "KVM_X86" + timeoutStart: 5 + networks: + - type: VINS + id: 1987 + delegate_to: localhost + register: group_test +``` + +Данный пример удаляет группу виртуальных серверов с названием test_group. + +``` +- hosts: localhost + tasks: + - name: create + decort_group: + authenticator: oauth2 + verify_ssl: False + controller_url: "https://ds1.digitalenergy.online" + state: absent + bservice_id: 1823 + name: "test_group" + delegate_to: localhost + register: group_test +``` \ No newline at end of file diff --git a/wiki/5.2.6/модуль-decort_jwt.md b/wiki/5.2.6/модуль-decort_jwt.md new file mode 100644 index 0000000..0726013 --- /dev/null +++ b/wiki/5.2.6/модуль-decort_jwt.md @@ -0,0 +1,56 @@ +# 03.07 Вспомогательный модуль для получения авторизационного JWT токена decort_jwt +## Обзор модуля decort_jwt + +Модуль decort_jwt предназначен для получения авторизационного токена JWT (JSON Web Token). Данный модуль может быть полезен при массовом создании виртуальных серверов на базе одного и того же образа, так как позволяет оптимизировать количество API вызовов, инициируемых в адрес контроллера облачной платформы. + +По сути, данный модуль является провайдером информации и не управляет облачными ресурсами (всегда возвращает changed: False). +## Параметры модуля decort_jwt + +Ниже в алфавитном порядке приведен полный список параметров для модуля decort_jwt. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_jwt` + + +|Параметр | Тип, допустимые значения | Описание| +| ------ | ------ | ------ | +|app_id | (string) | Идентификатор приложения, использующийся для подключения к авторизационному серверу. Данный параметр является обязательным. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`.| +|app_secret | (string) | Секретный ключ приложения, который используется для подключения к авторизационному серверу. Данный параметр является обязательным. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`.| +|oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, у которого запрашивается JWT. Данный параметр является обязательным. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`.| +|validity | (int) | Срок действия JWT в секундах. Данный параметр является опциональным, значение по умолчанию - 3600 сек.| +| verify_ssl | (bool)
`True` <- default
`False` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес авторизационного сервера, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +|workflow_callback | (string) | URL, по которому вышестоящее приложение (например, пользовательский портал или оркестратор верхнего уровня, инициирующий запуск Ansible playbook) ожидает API вызова, в параметрах которого модуль будет оперативно передавать информацию о своем статусе и текущей фазе исполнения. Данный параметр является опциональным. Функциональность callbacks в текущей версии модуля не реализована.| +|workflow_context | (string) | Контекстная информация, которая будет содержаться в параметрах API вызова, адресованного к `workflow_callback` URL. Данная информация призвана однозначно идентифицировать задачу, выполняемую модулем в настоящий момент, чтобы оркестратор верхнего уровня мог сопоставить получаемые через вызов workflow_callback данные со своим внутренним состоянием и отслеживать инициированные им задачи. Параметр является опциональным и имеет значение только при условии, что также задан `workflow_callback`. Функциональность callbacks в текущей версии модуля не реализована.| +## Возвращаемые значения модуля decort_jwt + +При успешном выполнении модуль decort_jwt возвращает словарь, в котором по ключу jwt находится значение JWT-токена (тип string). + +## Пример использования модуля decort_jwt + +В данном примере сначала получается JWT со сроком действия 1200 сек., а затем этот JWT используется для создания виртуального сервера в режиме авторизации jwt (подробнее о данном режиме см. в разделе «Примеры различных режимов авторизации»). +``` +- hosts: ansible_master + tasks: + - name: obtain JWT with validity of 1200 sec from the OAuth2 provider + decort_jwt: + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + validity: 1200 + delegate_to: localhost + register: my_token +``` +``` + - name: manage KVM VM in JWT authorization mode + decort_kvmvm: + authenticator: jwt + jwt: "{{ my_token.jwt }}" + controller_url: "https://cloud.digitalenergy.online" + name: NewVM01 + state: present + cpu: 2 + ram: 4096 + <<<дальнейшие детали опущены>>> +``` +Обратите внимание, как используется JWT при создании нового виртуального сервера (в предположении, что результат выполнения task для модуля decort_jwt был сохранен в переменной my_token): +``` + jwt: "{{ my_token.jwt }}" +``` \ No newline at end of file diff --git a/wiki/5.2.6/модуль-decort_k8s.md b/wiki/5.2.6/модуль-decort_k8s.md new file mode 100644 index 0000000..a4fbc15 --- /dev/null +++ b/wiki/5.2.6/модуль-decort_k8s.md @@ -0,0 +1,102 @@ +# 03.10 Модуль decort_k8s +## Обзор модуля decort_k8s + +Модуль decort_k8s предназначен для выполнения следующих действий над кластерами Kubernetes, созданными в облачной платформе DECORT: +- Создание/удаление кластера Kubernetes. +- Enable/Disable кластера Kubernetes. +- Запуск/Остановка кластера Kubernetes +- Модификация кластера Kubernetes +- Получение идентификатора образа Kubernetes по имени образа. + + +## Параметры модуля decort_k8s +Ниже приведен полный список параметров для модуля decort_k8s: + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения DECORT_APP_ID. | +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения DECORT_APP_SECRET. | +| authenticator | Значения:
`legacy`
`oauth2`
`jwt` <- default | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. | +| controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) данный виртуальный сервер. Данный параметр является обязательным. | +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме authenticator: jwt Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`.| +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`. | +| verify_ssl | (bool) | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты. Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +| workflow_callback | (string) | URL, по которому вышестоящее приложение (например, пользовательский портал или оркестратор верхнего уровня, инициирующий запуск Ansible playbook) ожидает API вызова, в параметрах которого модуль будет оперативно передавать информацию о своем статусе и текущей фазе исполнения. Данный параметр является опциональным. Функциональность callbacks в текущей версии модуля не реализована. | +| workflow_context | (string) | Контекстная информация, которая будет содержаться в параметрах API вызова, адресованного к `workflow_callback` URL. Данная информация призвана однозначно идентифицировать задачу, выполняемую модулем в настоящий момент, чтобы оркестратор верхнего уровня мог сопоставить получаемые через вызов `workflow_callback` данные со своим внутренним состоянием и отслеживать инициированные им задачи. Параметр является опциональным и имеет значение только при условии, что также задан `workflow_callback`. Функциональность callbacks в текущей версии модуля не реализована. | +| account_id | (int) | Уникальный целочисленный идентификатор аккаунта, к которому привязан кластер. | +| account_name | (string) | Имя аккаунта, к которому привязан кластер. | +| annotation | (string) | Аннотации для рабочих групп в кластере Kubernetes. | +| quotas | (dict) | Библиотека, содержащая квоты. | +| state | (string) | Состояние кластера. Поддерживаемые состояния: absent, disabled, enabled, present,check. | +| permanent | (bool) | Параметр, отвечающий за полное удаление кластера. При значениие `True` кластер будет полностью удалён, при значении `False` кластер будет перемещён в корзину. | +| started | (bool) | Параметр, отвечающий за запуск кластера после создания. При значении `True`, после создания кластер будет автоматически запущен. | +| name | (string) | Имя кластера Kubernetes. | +| id | (int) | Уникальный целочисленный идентификатор кластера Kubernetes, позволяющий удалять, изменять и получать сведения о кластере Kubernetes. | +| getConfig | (bool) | Параметр, передающий конфигурационный файл кластера Kubernetes, при значении `True`. | +| rg_id | (int) | Уникальный целочисленный идентификатор ресурсной группы, в которой находится кластер Kubernetes. | +| rg_name | (int) | Имя ресурсной группы, в которой находится кластер Kubernetes. | +| k8ci_id | (int) | Уникальный целочисленный идентификатор образа кластера Kubernetes. | +| wg_name | (string) | Имя рабочей группы. В кластере может быть несколько рабочих групп. | +| master_count | (int) | Количество master узлов в рабочей группе. По умолчанию: `1`| +| master_cpu | (int) | Количество ядер процессора на каждом master узле. По умолчанию: `2` | +| master_ram_mb | (int) | Объём оперативной памяти на каждом master узле. По умолчанию: `2048` | +| master_disk_gb | (int) | Объём жесткого диска на каждом master узле. По умолчанию: `10` | +| worker_count | (int) | Количество worker узлов в рабочей группе. По умолчанию: `1`| +| worker_cpu | (int) | Количество ядер процессора на каждом worker узле. По умолчанию: `1` | +| worker_ram_mb | (int) | Объём оперативной памяти на каждом worker узле. По умолчанию: `1024` | +| worker_disk_gb | (int) | Объём жесткого диска на каждом worker узле. По умолчанию: `10` | +| workers | (dict) | Библиотека, в которой необходимо указывать рабочие группы и их параметры. См. пример 13.4 | +| extnet_id | (int) | Уникальный целочисленный идентификатор внешней сети, к которой будет подключен кластер при создании. По умолчанию: `0` | +| description | (string) | Описание кластера. По умолчанию: `Created by decort ansible module` | +| with_lb | (bool) | Отвечает за создание Load Balancer при создании кластера. По умолчанию: `True` | + + + +## Возвращаемые значения модуля decort_k8s + +Модуль decort_k8s возвращает информацию о кластере в виде словаря facts со следующими ключами: + + +| Ключ | Тип данных | Описание | +| ------ | ------ | ------ | +| id | int | Уникальный целочисленный идентификатор данного кластера. | +| name | string | Имя данного кластера. | +| techStatus | string | Технический статус кластера. Возможные технические статусы кластера: STARTED, DELETED, STOPPED, CREATED, , DISABLED, ENABLED, RESTORED, MODELED.| +| state | string | Текущий статус кластера. Поддерживаемые состояния: absent, disabled, enabled, present,check. | +| rg_id | int | Уникальный целочисленный идентификатор ресурсной группы, в которой находится данный кластер. | +| account_id | int | Уникальный целочисленный идентификатор аккаунта, к которому привязан данный кластер. | + + +## Пример использования модуля decort_k8s + + +Пример создания кластера Kubernetes с названием cluster-test. +``` + - name: create a cluster named cluster-test + decort_k8s: + state: present + started: True + getConfig: True + authenticator: jwt + jwt: "{{ token.jwt }}" + controller_url: "https://ds1.digitalenergy.online" + name: "cluster-test" + rg_id: 125 + k8ci_id: 18 + workers: + - name: wg1 + ram: 1024 + cpu: 10 + disk: 10 + num: 1 + - name: wg2 + ram: 1024 + cpu: 10 + disk: 10 + num: 2 + verify_ssl: false + delegate_to: localhost + register: kube + +``` \ No newline at end of file diff --git a/wiki/5.2.6/модуль-decort_kvmvm.md b/wiki/5.2.6/модуль-decort_kvmvm.md new file mode 100644 index 0000000..05f93b6 --- /dev/null +++ b/wiki/5.2.6/модуль-decort_kvmvm.md @@ -0,0 +1,112 @@ +# 03.01 Модуль decort_kvmvm +## Обзор модуля decort_kvmvm + +Модуль decort_kvmvm предназначен для предназначен для выполнения следующих действий над виртуальными серверами, созданными на базе технологии виртуализации KVM, в облачной платформе DECORT: + +1. Создание нового виртуального сервера указанной аппаратной архитектуры. Платформа поддерживает виртуальные сервера архитектуры Intel x86 и IBM PowerPC. +2. Изменение конфигурации существующего виртуального сервера: + - Изменение количества выделенных CPU и виртуальной ОЗУ. Следует иметь ввиду, что изменение этих параметров в меньшую сторону может потребовать перезагрузки гостевой ОС. + - Увеличение размера загрузочного диска + - Подключение / отключение дополнительных дисковых ресурсов. + - Создание / удаление сетевых подлкючений. +3. Изменение состояния существующего виртуального сервера: + - Выключение / включение. + - Перезагрузка, приостановка / возобновление работы гостевой ОС. +4. Удаление существующего виртуального сервера. +5. Получение информации о существующем виртуальном сервере без изменения его конфигурации. + +## Параметры модуля decort_kvmvm + +Ниже в алфавитном порядке приведен полный список параметров для модуля decort_kvmvm. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_kvmvm` + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| account_id | int | Уникальный цифровой идентификатор подписчика, владеющего данным виртуальным сервером.Этот параметр является опциональным и используется в сценариях, когда уже существующая ресурсная группа задается комбинацией `account_id` и `rg_name`. Если задан `account_id`, то `account_name` игнорируется.| +| account_name | (string) | Имя подписчика, которому будет принадлежать новый виртуальный сервер (или уже принадлежит существующий). Параметр должен задаваться в точном соответствии с тем, как назван нужный подписчик в облачной инфраструктуре (с соблюдением заглавных и строчных символов, а также пробелов). Этот параметр является опциональным и используется в сценариях, когда уже существующая ресурсная группа задается комбинацией account_name и rg_name. Если задан `account_id`, то `account_name` игнорируется.| +| annotation | (string) | Опциональное описание виртуального сервера. Этот параметр используется только в момент создания нового виртуального сервера и игнорируется при любых действиях над существующими серверами.| +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль `decort_kvmvm` будет использовать значение переменной окружения `DECORT_APP_ID`.| +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DCORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль `decort_kvmvm` будет использовать значение переменной окружения `DECORT_APP_SECRET`.| +| arch | Значения:
`KVM_X86` <- default
`KVM_PPC`
| Аппаратная архитектура виртуального сервера. Данный параметр является обязательным при создании нового вирутального сервера и игнорируется для уже существующего сервера.
Платформа DECORT поддерживает виртуальные серверы KVM аппаратных архитектур Intel x86 и IBM PowerPC. | +| authenticator | Значения:
`legacy`
`oauth2`
`jwt` <- default | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. | +|boot_disk | (int) | Размер загрузочного диска виртуального сервера в ГБ. Загрузочный диск всегда создаётся на той же системе хранения и в том же пуле, где размещён образ ОС (см. параметры `image_name` или `image_id`), на базе которого создаётся данный виртуальный сервер. Если данный параметр не указан на момент создания виртуального сервера, то размер загрузочного диска будет установлен равным размеру образа ОС.| +| controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) данный виртуальный сервер. Данный параметр является обязательным.| +| cpu | (int) | Количество виртуальных CPU, выделенных виртуальному серверу. Параметр является обязательным при создании нового сервера, во всех других случаях он опциональный. Если указать его для уже существующего сервера, то будет выполнена попытка изменить количество CPU. Следует иметь ввиду, что уменьшение количества CPU у работающего сервера, как правило, потребует его перезагрузки.| +| data_disks | (list) | Список идентификаторов дисков, которые следует подключить к данному виртуальному серверу как дополнительные (помимо загрузочного диска, который создаётся автоматически вместе с виртуальным сервером).| +| id | (int) | Уникальный цифровой идентификатор виртуального сервера внутри платформы. Этот параметр используется как один из методов идентификации существующего сервера (альтернатива – по комбинации name, `rg_name` и `account_name`) и игнорируется при создании нового сервера, так как для нового сервера облачная платформа назначает этот идентификатор автоматически. Если при вызове модуля `decort_kvmvm` существующий виртуальный сервер идентифицируется по своему id, то параметры `account_id`, `account_name`, `rg_id` и `rg_name` игнорируются.| +| image_id | (int) | Уникальный цифровой идентификатор образа ОС, на базе которого следует создать виртуальный сервер. При создании нового виртуального сервера требуется задать этот параметр или параметр image_name. При любых других операциях данные параметры игнорируются. Если заданы оба этих параметра (`image_id` и `image_name`), то `image_name` игнорируется. Для получения `image_id` по `image_name` можно использовать модуль `decort_osimage`.| +| image_name | (string) | Название образа ОС, на базе которого следует создать виртуальный сервер. При создании нового виртуального сервера требуется задать этот параметр или параметр `image_id`. При любых других операциях данные параметры игнорируются. Параметр `image_name` должен задаваться в точном соответствии с тем, как назван нужный образ ОС в облачной инфраструктуре (с соблюдением заглавных и строчных символов, а также пробелов).| +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt`. Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль `decort_kvmvm` будет использовать значение переменной окружения `DECORT_JWT`. +| name | (string) | Название виртуального сервера. Чтобы модуль `decort_kvmvm` мог управлять сервером по его названию, также необходимо задать комбинацию `account_name` и `rg_name` или идентификатор `rg_id`. Параметр должен задаваться в точном соответствии с тем, как назван сервер в облачной инфраструктуре (с соблюдением заглавных и строчных символов, а также пробелов). Если для существующего виртуального сервера указаны и name, и id, то параметр name игнорируется и идентификация сервера выполняется по его id.| +| networks | (list of dicts) | Опциональный список словарей, задающих сетевые подключения для данного виртуального сервера. Структура словаря:- (string) type - тип сетевого подключения, одно из VINS или EXTNET; - (int) id - идентификатор сетевого сегмента данного подключения; для type: VINS это VINS ID, для type: EXTNET это ID внешнего сетевого сегмента; - (string) ip_addr - опциональный IP адрес, который надлежит присвоить данному подключению; если данный параметр не указан, то платформа назначит свободный IP адрес из нужного диапазона автоматически. Если этот параметр не задан, то будет создан сервер без сетевых подключений, а если модуль вызван для уже существуюшего сервера, то все его сетевые подключения будут удалены.| +|oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль `decort_kvmvm` будет использовать значение переменной окружения `DECORT_OAUTH2_URL`.| +| password | (string) | Пароль для подключения к контроллеру облачной инфраструктуры DECORT в режиме `authenticator: legacy`. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать в playbook. Если параметр не задан в playbook, то модуль `decort_kvmvm` будет использовать значение переменной окружения `DECORT_PASSWORD`.| +| ram | (int) | Объем оперативной памяти (RAM) в MB, выделенной данному виртуальному серверу. Параметр является обязательным при создании нового сервера. Если указать его для уже существующего сервера, то будет выполнена попытка изменить объем выделенной серверу памяти. Следует иметь ввиду, что уменьшение объема памяти работающего сервера в большинстве случаев потребует его перезагрузки.| +| rg_id | (int) | Уникальный цифровой идентификатор уже существующей ресурсной группы (RG), в которой будет создан новый или находится уже существующий виртуальный сервер. Данный параметр является одним из методов идентификации существующей RG (альтернативой является задание комбинации `account_name` и `rg_name`).| +| rg_name | (string) | Имя уже существующей ресурсной группы (RG), в которой будет создан новый или находится уже существующий виртуальный сервер. Данный параметр является одним из методов идентификации существующей RG, когда задается пара `account_name` и `rg_name` (альтернативой является задание `rg_id`, который можно получить с помощью модуля `decort_rg`). Параметр должен задаваться в точном соответствии с тем, как нужная ресурсная группа названа в облачной инфраструктуре (с соблюдением заглавных и строчных символов, а также пробелов). Если заданы и `rg_id`, и `rg_name`, то параметр `rg_name` игнорируется.| +| ssh_key | (string) | Открытая часть SSH ключа, который необходимо авторизовать на создаваемом виртуальном сервере для пользователя, заданного параметром `ssh_key_user`. Данный параметр применим только для Linux серверов, является опциональным, используется только при создании нового сервера и игнорируется при других операциях.| +| ssh_key_user | (string) | Имя пользователя на уровне гостевой ОС (только для Linux серверов) для которого авторизуется SSH ключ, заданный параметром `ssh_key`. Данный параметр является обязательным, если задан `ssh_key`, используется только при создании нового сервера и игнорируется при других операциях.| +| state | Значения:
`present` <- default
`absent`
`poweredon`
`poweredoff`
`halted`
`paused`
`check` | Целевое состояние виртуального сервера на выходе из модуля _decort_kvmvm_.
Значение 'halted' - синоним к 'poweredoff'.
Значение 'check' вызывает модуль в _read-only_ режиме и считывает характеристики существующего виртуального сервера. | +| tags | (string) | Строка, содержащая набор текстовых меток, которые надлежит присвоить данному виртуальному серверу. Данные текстовые метки представляют собой произвольный текст, который можно использовать для группировки и индексирования виртуальных серверов во внешних приложениях. Параметр является опциональным.| +| user | (string) | Имя пользователя, непосредственно зарегистрированного на контроллере облачной инфраструктуры DECORT, которое используется для подключения к контроллеру в режиме `authenticator: legacy`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль `decort_kvmvm` будет использовать значение переменной окружения `DECORT_USER`.| +| verify_ssl | (bool)
`True` <- default
`False` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +| workflow_callback | (string) | URL, по которому вышестоящее приложение (например, пользовательский портал или оркестратор верхнего уровня, инициирующий запуск Ansible playbook) ожидает API вызова, в параметрах которого модуль будет оперативно передавать информацию о своем статусе и текущей фазе исполнения. Данный параметр является опциональным. Функциональность callbacks в текущей версии модуля не реализована.| +| workflow_context | (string) | Контекстная информация, которая будет содержаться в параметрах API вызова, адресованного к `workflow_callback` URL. Данная информация призвана однозначно идентифицировать задачу, выполняемую модулем в настоящий момент, чтобы оркестратор верхнего уровня мог сопоставить получаемые через вызов `workflow_callback` данные со своим внутренним состоянием и отслеживать инициированные им задачи. Параметр является опциональным и имеет значение только при условии, что также задан `workflow_callback`. Функциональность callbacks в текущей версии модуля `decort_kvmvm` не реализована.| + + +## Возвращаемые значения модуля decort_kvmvm + +Модуль decort_kvmvm возвращает информацию о виртуальном сервере в виде словаря facts со следующими ключами: + + +| Ключ | Тип данных | Описание | +| ------ | ------ | ------ | +| account_id | int |Цифровой идентификатор подписчика, владеющего данным виртуальным сервером.| +| account_name | string | Имя подписчика, владеющего данным виртуальным сервером.| +| arch | string | Аппаратная архитектура данного виртуального сервера. Допустимые значения: KVM_X86 для Intel x86 и KVM_PPC для IBM PowerPC.| +| cpu | int | Количество виртуальных CPU, выделенных данному виртуальному серверу.| +| data_disks | list of ints | Список идентификаторов data-дисков, в настоящий момент подключенных к данному серверу.| +| disk_size | int | Размер загрузочного диска в ГБ.| +| id | int | Уникальный цифровой идентификатор данного виртуального сервера (экземпляра compute) в платформе DECORT.| +| name | string | Имя виртуального сервера. Имя уникально только в рамках одной и той же ресурсной группы.| +| password | string | Пароль системного пользователя по умолчанию.| +| private_ips | list of strings | Список IP адресов на сетевых интерфейсах сервера, которые подключены к виртуальным сетевым сегментам (Virtual Network Segments, ViNS).| +| public_ips | list of strings | Список IP адресов на сетевых интерфейсах сервера, которые подключены непосредственно к внешним сетевым сегментам (external network segments).| +| ram | int | Объём ОЗУ в МБ, выделенный виртуальному серверу.| +| rg_id | int | Уникальный цифровой идентификатор ресурсной группы (RG), к которой принадлежит данный сервер.| +| state | string | Состояние виртуального сервера.| +| username | string |Имя системного пользователя по умолчанию.| + +## Пример использования модуля decort_kvmvm + +В данном примере создается виртуальный сервер KVM VM по имени MyFirstVM с аппаратной архитектурой Intel x86. +``` +- name: create new x86 KVM VM according to the specs + decort_kvmvm: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + name: MyFirstVM + arch: KVM_X86 + state: present + cpu: 2 + ram: 4096 + boot_disk: 10 + image_id: "{{ my_img.facts.id }}" + networks: + - type: VINS + id: "{{ my_vins.facts.id }}" + rg_id: "{{ my_rg.facts.id }}" + annotation: "My 1st VM created and managed with DECORT KVMVM module" + delegate_to: localhost + register: new_vm +``` + +Виртуальный сервер создаётся на базе некоторого системного образа (дискового ресурса, содержащего операционную систему). В данном примере системный образ задаётся по его идентификатору (image_id: "{{ my_img.facts.id }}"). Для получения такого идентификатора можно использовать модуль decort_osimage. + +Виртуальный сервер всегда создаётся в принадлежности к некоторой ресурсной группе. В данном примере соответствующая ресурсная группа задаётся по её идентификатору (rg_id: "{{ my_rg.facts.id }}"). Для управления ресурсными группами предназначен модуль decort_rg. С помощью этого модуля можно также получит идентификатор ресурсной группы, чтобы использовать его при создании виртуального сервера. + +Для управления сетевыми подключениями виртуального сервера в модуле decort_kvmvm используется параметр networks, представляющий собой список с описанием требующихся подключений. В данном примере задано одно подключение в виртуальному сетевому сегменту (type: VINS) с указанным идентификатором (id: "{{ my_vins.facts.id }}"). Для управления виртуальными сетевыми сегментами и получения их идентификаторов служит модуль decort_vins. \ No newline at end of file diff --git a/wiki/5.2.6/модуль-decort_osimage.md b/wiki/5.2.6/модуль-decort_osimage.md new file mode 100644 index 0000000..60cdbbb --- /dev/null +++ b/wiki/5.2.6/модуль-decort_osimage.md @@ -0,0 +1,135 @@ +# 03.02 Модуль decort_osimage +## Обзор модуля decort_osimage + +Модуль decort_osimage предназначен для выполнения следующих действий над образами, созданными в облачной платформе DECORT: + +1. Создание нового образа операционной системы для виртуальных машин. +2. Создание виртуального образа для образа операционной системы. +3. Изменение имени образа (в случае изменения имени виртуального образа, имя и id виртуального образа указываются как image_name и image_id. +4. Привязка другого образа операционной системы к виртуальному образу. +5. Удаление существующего образа, привязанного к аккаунту. +6. Получение информации об образе. + +Также данный модуль может быть полезен при массовом создании экземпляров compute (напр., виртуальных серверов) на базе одного и того же образа, так как позволяет оптимизировать количество API вызовов, инициируемых в адрес контроллера облачной платформы. + +## Параметры модуля decort_osimage +Ниже приведен полный список параметров для модуля decort_osimage. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения DECORT_APP_ID. | +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения DECORT_APP_SECRET. | +| authenticator | Значения:
`legacy`
`oauth2`
`jwt` <- default | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. | +| controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) данный виртуальный сервер. Данный параметр является обязательным. | +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме authenticator: jwt Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`.| +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`. | +| verify_ssl | (bool) | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты. Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +| workflow_callback | (string) | URL, по которому вышестоящее приложение (например, пользовательский портал или оркестратор верхнего уровня, инициирующий запуск Ansible playbook) ожидает API вызова, в параметрах которого модуль будет оперативно передавать информацию о своем статусе и текущей фазе исполнения. Данный параметр является опциональным. Функциональность callbacks в текущей версии модуля не реализована. | +| workflow_context | (string) | Контекстная информация, которая будет содержаться в параметрах API вызова, адресованного к `workflow_callback` URL. Данная информация призвана однозначно идентифицировать задачу, выполняемую модулем в настоящий момент, чтобы оркестратор верхнего уровня мог сопоставить получаемые через вызов `workflow_callback` данные со своим внутренним состоянием и отслеживать инициированные им задачи. Параметр является опциональным и имеет значение только при условии, что также задан `workflow_callback`. Функциональность callbacks в текущей версии модуля не реализована. | +| account_Id | (string) | Уникальный целочисленный идентификатор аккаунта. Используется для поиска образов, а тажке для их создания. | +| account_name | (string) | Имя аккаунта. Используется для получения уникального целочисленного идентификатора аккаунта. | +| virt_id | (integer) | Уникальный целочисленный идентификатор виртуального образа. Может использоваться для получения информации о виртуальном образе, а также для привязки к нему другого образа операционной системы.| +| virt_name | (string) | Имя виртуального образа. Используется для получения `virt_id`, а в последвии информации о виртуальном образе, а также для создания виртуального образа и привязки к нему другого образа операционной системы.| +| state | (string) | Состояние образов. При значении present идет создание образов операционной системы, к которым привязан указанный в `account_Id` или `account_name` аккаунт. При значении absent идет их удаление. | +| drivers | (string) | Список типов compute (напр., виртуальных серверов), подходящих для образа операционной системы. Прим. `KVM_X86`. Используется при создании образа операционной системы.| +| architecture | (string) | Бинарная архитектура образа. Прим. `X86_64` или `PPC64_LE`. Используется при создании образа операционной системы.| +| imagetype | (string) | Тип образа. `linux`, `windows` или `other`. По умолчанию установлено `linux`. Используется при создании образа операционной системы.| +| boottype | (string) | Тип загрузки образа. `bios` или `uefi`. По умолчанию установлено `uefi`. Используется при создании образа операционной системы.| +| url | (string) | Унифицированный указатель ресурса (URL), указывающий на образ iso операционной системы. Используется при создании образа операционной системы.| +| sepId | (integer) | Уникальный целочисленный идентификатор конечной точки провайдера хранения. Указывается в паре с `poolName`. Используется при создании образа операционной системы.| +| poolName | (string) | Пул, в котором будет создан образ. Указывается в паре с `sepId`. Используется при создании образа операционной системы.| +| hotresize | (bool) | Поддерживает ли образ "горячее" изменение размера. По умолчанию установлено `false`. Используется при создании образа операционной системы.| +| image_username | (string) | Опциональное имя пользователя для образа. Используется при создании образа операционной системы. | +| image_password | (string) | Опциональный пароль для образа. Используется при создании образа операционной системы. Используется при создании образа операционной системы.| +| usernameDL | (string) | Имя пользователя для загрузки бинарного носителя. Используется в паре с `passwordDL`. Используется при создании образа операционной системы. | +| passwordDL | (string)| Пароль для загрузки бинарного носителя. Используется в паре с `usernameDL`. Используется при создании образа операционной системы. | +| permanently | (bool) | Навсегда ли удалить образ. Используется при удалении убраза. По умолчанию установлено false.| + + +## Возвращаемые значения модуля decort_osimage + +Модуль decort_osimage возвращает информацию о XXX в виде словаря facts со следующими ключами: + + +| Ключ | Тип данных | Описание | +| ------ | ------ | ------ | +| arch | string | Аппаратная архитектура, с которой совместим данный образ. Возможные значения: X86_64 (Intel x86), PPC64_LE (IBM PowerPC). | +| id | int | Уникальный целочисленный идентификатор данного образа. | +| linkto | int | Уникальный целочисленный идентификатор образа операционной системы, который привязан к данному виртуальному. | +| name | string | Имя образа. Обратите внимание, что имя образа может содержать пробелы. | +| pool | string | Пул на системе хранения данных, в котором находится данный образ.| +| sep_id |int | Идентификатор системы хранения (Storage Endpoint Provider), на которой хранится данный образ. | +| size |int | Размер образа в ГБ. Загрузочный диск экземпляра compute, создаваемого на базе данного образа, должен иметь как минимум такой же размер. | +| state | string | Текущий статус образа. Возможные значения: CREATED, DISABLED, DESTROYED. Экземпляры compute можно создавать только на базе образов, которые находятся в статусе CREATED.| +| type | string | Тип операционной системы внутри данного образа. Возможные значения: Linux, Windows.| + + + +## Пример использования модуля decort_osimage + + +Пример создания образа операционной системы Alpine Linux. +``` + - name: create_osimage + decort_osimage: + authenticator: oauth2 + verify_ssl: False + controller_url: "https://ds1.digitalenergy.online" + state: present + image_name: "alpine_linux3.14.0" + account_Id: 12345 + url: "https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-virt-3.14.0-x86_64.iso" + boottype: "uefi" + imagetype: "linux" + hotresize: False + image_username: "test" + image_password: "p@ssw0rd" + usernameDL: "testDL" + passwordDL: "p@ssw0rdDL" + architecture: "X86_64" + drivers: "KVM_X86" + delegate_to: localhost + register: osimage +``` + +Пример получения образа операционной системы по имени образа. + +``` + - name: get_osimage + decort_osimage: + authenticator: oauth2 + controller_url: "https://ds1.digitalenergy.online" + image_name: "alpine_linux_3.14.0" + account_Id: 12345 + delegate_to: localhost + register: osimage +``` + +Пример создания виртуального образа. Также в случае, если виртуальный образ уже существует, но к нему привязан другой образ операционной системы, он привяжет к себе указанный в примере образ операционной системы. + +``` + - name: create_virtual_osimage + decort_osimage: + authenticator: oauth2 + controller_url: "https://ds1.digitalenergy.online" + image_name: "alpine_linux_3.14.0" + virt_name: "alpine_last" + delegate_to: localhost + register: osimage +``` +Обратите внимание, что в данным примере можно использовать как image_name, так и image_id. Также можно использовать либо virt_name, либо virt_id. + +Пример переименования образа. + +``` + - name: rename_osimage + decort_osimage: + authenticator: oauth2 + controller_url: "https://ds1.digitalenergy.online" + image_name: "alpine_linux_3.14.0v2.0" + image_id: 54321 + delegate_to: localhost + register: osimage +``` + diff --git a/wiki/5.2.6/модуль-decort_pfw.md b/wiki/5.2.6/модуль-decort_pfw.md new file mode 100644 index 0000000..18eca68 --- /dev/null +++ b/wiki/5.2.6/модуль-decort_pfw.md @@ -0,0 +1,73 @@ +# 03.04 Модуль decort_pfw +## Обзор модуля decort_pfw + +Модуль decort_pfw предназначен для управления правилами трансляции сетевых портов для указанного экземпляра Compute (например, для виртуального сервера, созданного с помощью decort_kvmvm). + +##Параметры модуля decort_pfw + +Ниже в алфавитном порядке приведен полный список параметров для модуля decort_pfw. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_pfw` + + +| Параметр | Тип, допустимые значения | Описание| +| ------ | ------ | ------ | +|app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`.| +|app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`.| +| authenticator | Значения:
`legacy`
`oauth2`
`jwt` <- default | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. | +|compute_id | (int) | Идентификатор экземпляра Compute (напр., виртуального сервера), для которого требуется настроит правила.| +|controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) данный виртуальный сервер. Данный параметр является обязательным.| +|jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме authenticator: jwt Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`.| +|oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`.| +|rules | (list of dicts) | Список словарей, задающих правила трансляции сетевых портов. Правила могут задавать как трансляцию "один к одному", так и "диапазон в диапазон". Структура словаря: (int) public_port_start - номер внешнего порта (на публичном интерфейсе ViNS, заданного параметром vins_id) для начала диапазона. (int) public_port_end - номер внешнего порта для конца диапазона. Если требуется правило "один к одному", то public_port_end можно не указывать или указать равным public_port_start. (int) local_port - номер порта на интерфейсе экземпляра Compute, заданного параметром compute_id. Если задаётся правило "диапазон в диапазон", то local_port становится началом диапазона для трансляции. В случае правила "один к одному" public_port_start просто транслируется в local_port (string) proto - протокол, tcp или udp.| +| state | Значения:
`present` <- default
`absent`
`check`
| Целевое состояние правил.
Если `state=absent`, то будут удалены все правила для указанного `compute_id` независимо от содержания параметра `rules`. В противном случае набор правил на выходе из модуля будет соответствовать тому, который содержится в параметре `rules`.
Если задан `state=check`, то модуль запускается в режиме read-only и возвращает текущие настройки правил трансляции сетевых портов для указанных `compute_id` и `vins_id`. | +| verify_ssl | (bool)
`True` <- default
`False` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +|vins_id | (int) | Идентификатор виртуального сетевого сегмента (Virtual Network Segment, ViNS), в рамках которого настраиваются правила трансляции сетевых портов. Экземпляр Compute, заданный в параметре `compute_id`, должен быть уже подключён к данному ViNS.| +| workflow_callback | (string) | URL, по которому вышестоящее приложение (например, пользовательский портал или оркестратор верхнего уровня, инициирующий запуск Ansible playbook) ожидает API вызова, в параметрах которого модуль будет оперативно передавать информацию о своем статусе и текущей фазе исполнения. Данный параметр является опциональным. Функциональность callbacks в текущей версии модуля не реализована. | +|workflow_context | (string) | Контекстная информация, которая будет содержаться в параметрах API вызова, адресованного к `workflow_callback` URL. Данная информация призвана однозначно идентифицировать задачу, выполняемую модулем в настоящий момент, чтобы оркестратор верхнего уровня мог сопоставить получаемые через вызов `workflow_callback` данные со своим внутренним состоянием и отслеживать инициированные им задачи. Параметр является опциональным и имеет значение только при условии, что также задан `workflow_callback`. Функциональность callbacks в текущей версии модуля не реализована.| + +## Возвращаемые значения модуля decort_pfw + +Модуль decort_pfw возвращает информацию о правилах трансляции сетевых портов и сопутсвтующую информацию в виде словаря facts со следующими ключами: + + +|Ключ | Тип данных | Описание| +| ------ | ------ | ------ | +|compute_id | int | Идентификатор compute, для которого настроены перечисленные правила трансляции.| +|public_ip | string |IP адрес на внешнем сетевом интерфейсе ViNS. Перечисленные ниже правила трансляции настраиваются на этом интерфейсе.| +|rules | list of dict | Список словарей, описывающих правила трансляции сетевых портов. Структура словаря: (int) id - идентификатор правила. (int) publicPortStart - номер внешнего порта (на публичном интерфейсе ViNS, см. public_ip) для начала диапазона. (int) publicPortEnd - номер внешнего порта для конца диапазона. Если требуется правило "один к одному", то publicPortEnd можно не указывать или указать равным publicPortStart. (int) localPort - номер порта на интерфейсе экземпляра Compute, заданного параметром compute_id. Если задаётся правило "диапазон в диапазон", то localPort указывает начало диапазона для трансляции. В случае правила "один к одному" publicPortStart просто транслируется в localPort. (string) protocol - протокол, tcp или udp.| +|state | string | Статус правил. Для корректно отработавшего модуля это поле должно иметь значение "PRESENT".| +|vins_id | int | Идентификатор виртуального сетевого сегмента (ViNS), в котором настроены перечисленные выше правила трансляции.| + +## Пример использования модуля decort_pfw + +В данном примере для ранее созданного экземпляра KVM VM compute (идентифицируется по compute_id), подключённого к указанному виртуальному сетевому сегменту (идентифицируется по vins_id) настраиваются два правила трансляции сетевых портов: + +- Правило "один к одному": внешний порт 30022 -> внутренний порт 22, протокол tcp. +- Правило "диапазон в диапазон": внешние порты с 30080 по 30085 включительно -> внутренние порты начиная с 30080, протокол tcp. + +``` +- name: manage port forwarding rules + decort_pfw: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + compute_id: "{{ my_kvmvm.facts.id }}" + vins_id: "{{ my_vins01.facts.id }}" + rules: + - public_port_start: 30022 + local_port: 22 + proto: tcp + - public_port_start: 30080 + public_port_end: 30085 + local_port: 30080 + proto: tcp + state: present + register: my_pfw + delegate_to: localhost +``` + +В данном примере результат выполнения модуля decort_pfw записывается в переменную my_pfw. Для доступа к списку правил используйте следующую конструкцию: + +`"{{ my_pfw.facts.rules }}"` diff --git a/wiki/5.2.6/модуль-decort_rg.md b/wiki/5.2.6/модуль-decort_rg.md new file mode 100644 index 0000000..427d475 --- /dev/null +++ b/wiki/5.2.6/модуль-decort_rg.md @@ -0,0 +1,223 @@ +# 03.05 Модуль decort_rg + +## Обзор модуля decort_rg + +Модуль decort_rg предназначен для создания, редактирования и удаления ресурсных групп (Resource Group, RG) в облачной платформе DECORT, а также для получения информации об уже существующей ресурсной группе. Модуль decort_rg позволяет: + +- Создавать\\удалять ресурсные группы. +- Запрашивать информацию об уже существующих ресурсных группах. +- Редактировать ресурсные группы. +- Настраивать квоты в ресурсных группах. +- Восстанавливать ресурсные группы. +- Задавать стандартную сеть ресурсной группы. +- Настраивать доступ к ресурсным группам. + +## Параметры модуля decort_rg + +Ниже в алфавитном порядке приведен полный список параметров для модуля decort_rg. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: `ansible-doc -t module decort_rg` + +| Параметр | Тип, допустимые значения | Описание | +|----------|--------------------------|----------| +| account_id | (int) | Уникальный целочисленный идентификатор учётной записи (account), в рамках которой создаётся или уже существует данная ресурсная группа. Должен быть задан либо идентификатор, либо имя учётной записи (см. параметр `account_name`). Если одновременно заданы и `account_id`, и `account_name`, то account_name игнорируется. | +| account_name | (string) | Имя учётной записи (account), в рамках которой создаётся или уже существует данная ресурсная группа. Должно быть задано либо имя, либо идентификатор учётной записи (см. параметр `account_id`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется. | +| access | (dict) | Параметр, позволяющий выдать,забрать, изменить права у пользователя в ресурсной группе. | +| annotation | (string) | Текстовое описание ресурсной группы. Данный аргумент является опциональным и учитывается только при создании ресурсной группы, а при всех прочих операциях игнорируется. | +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`. | +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения DECORT_APP_SECRET. | +| authenticator | Значения:
`legacy`
`oauth2`
`jwt` <- default | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. | +| controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) данный виртуальный сервер. Данный параметр является обязательным. | +| def_netType | (string)
Значения:
`PRIVATE` <- default
`PUBLIC`
`NONE` | Тип сети по умолчанию в ресурсной группе. Используется при создании ресурсной группы, а также, чтобы изменить ресурсную группу по умолчанию. Не обязателен. | +| def_netId | (int) | Уникальный целочисленный идентификатор сети (ViNS), используется для изменения сети по умолчанию в ресурсной группе. | +| extNetIp | (string) | IP-адрес внешней сети, к которой ресурсная группа будет подключена при создании. | +| owner | (string) | Владелец ресурсной группы. Задаётся при создании ресурсной группы, не обязателен. Если оставить пустое значение - владельцем ресурсной группы будет пользователь, создавший ресурсную группу. | +| ipcidr | (string) | IP-адрес приватной сети. Используется при создании ресурсной группы, чтобы задать ip адрес ViNS, который создастся вместе с ресурсной группой. Задаётся при параметре `def_netType` в значении `PRIVATE`. | +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt`
Данный параметр является обязательным для указанного режима.
Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в _playbook_.
Если этот параметр не определен в _playbook_, то модуль будет использовать значение переменной окружения _DECORT_JWT_. | +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`. | +| rename | (string) | Новое имя ресурсной группы. Используется при переименовании ресурсной группы. | +| quotas | (dict) | Зарезервировано для будущих расширений, предназначено для задания или изменения квоты на ресурсы в составе данной ресурсной группы. | +| resType | (list) | Типы ресурсов, которые можно создать в ресурсной группе. | +| rg_name | (string) | Имя ресурсной группы. Данный параметр является обязательным. | +| rg_id | (int) | Уникальный целочисленный идентификатор ресурсной группы. | +| state | Значения:
`present` <- default
`absent`
`enabled`
`disabled` | Целевое состояние ресурсной группы. | +| permanently | (bool)
`False` <- default
`True` | Параметр, использующийся при удалении ресурсной группы, при значении True - ресурсная группа удалится навсегда, а при False - попадёт в корзину. | +| verify_ssl | (bool)
`True` <- default
`False` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +| workflow_callback | (string) | URL, по которому вышестоящее приложение (например, пользовательский портал или оркестратор верхнего уровня, инициирующий запуск Ansible playbook) ожидает API вызова, в параметрах которого модуль будет оперативно передавать информацию о своем статусе и текущей фазе исполнения.
Данный параметр является опциональным.
Функциональность callbacks в текущей версии модуля не реализована. | +| workflow_context | (string) | Контекстная информация, которая будет содержаться в параметрах API вызова, адресованного к `workflow_callback` URL.
Данная информация призвана однозначно идентифицировать задачу, выполняемую модулем в настоящий момент, чтобы оркестратор верхнего уровня мог сопоставить получаемые через вызов `workflow_callback` данные со своим внутренним состоянием и отслеживать инициированные им задачи.
Параметр является опциональным и имеет значение только при условии, что также задан `workflow_callback`.
Функциональность callbacks в текущей версии модуля не реализована. | + +## Возвращаемые значения модуля decort_rg + +Модуль decort_rg возвращает информацию о XXX в виде словаря facts со следующими ключами: +| Ключ | Тип данных | Описание | +|------|------------|----------| +| account_id | int | Уникальный целочисленный идентификатор учётной записи (account), которому принадлежит данная ресурсная группа. | +| gid | int | Идентификатор физического кластера (Grid ID), на базе которого развёрнута ресурсная группа. | +| id | int | Уникальный целочисленный идентификатор ресурсной группы. | +| name | string | Имя ресурсной группы. Обратите внимание, что это имя уникально только в рамках учётной записи, которой принадлежит данная ресурсная группа. | +| state | string | Текущее состояние ресурсной группы. Корректные состояния: CREATED, DISABLED, DELETED, DESTROYED | +| quota | dict | Текущие квоты ресурсной группы. | +| resTypes | list | Список типов ресурсов, разрешенных к созданию в данной ресурсной группе. | +| defNetId | int | Уникальный целочисленный идентификатор сети по умолчанию в данной ресурсной группе. | +| defNetType | string | Тип сети по умолчанию в данной ресурсной группе. Корректные типы сети по умолчанию: NONE, PRIVATE, PUBLIC. | + +## Пример использования модуля decort_rg + +В данном примере показано, как создать ресурсную группу по имени MyRG в учётной записи MyAccount. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере показано, как создать ресурсную группу по имени MyRG в учётной записи MyAccount, также ограничить в ней доступ к ресурсам, задать квоты и выдать права на чтение пользователю MyUser. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + access: + action: "grant" + user: "MyUser" + right: "R" + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере показано, как выдать доступ на чтение и запись пользователю MyUser в ресурсной группе по имени MyRG в учётной записи MyAccount. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + access: + action: "grant" + user: "MyUser" + right: "RCX" + quotas: + cpu: 16 + ram: 16384 + disk: 100 + ext_ips: 20 + net_transfer: 1000 + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере показано, как сменить квоту в ресурсной группе по имени MyRG в учётной записи MyAccount. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + quotas: + cpu: 16 + ram: 16384 + disk: 100 + ext_ips: 20 + net_transfer: 1000 + resType: + - vins + - compute + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере показано, как изменить сеть по умолчанию в ресурсной группе по имени MyRG в учётной записи MyAccount. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + def_netType: "PRIVATE" + def_netId: 99 + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере показано, как переименовать ресурсную группу по имени MyRG на новое имя "NewRg" в учётной записи MyAccount. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + rename: "NewRg" + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере показано, как ограничить типы создаваемых ресурсов в ресурсной группе MyRG в учётной записи MyAccount. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + resType: + - vins + - compute + - k8s + - openshift + - lb + - flipgroup + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере результат работы модуля decort_rg записывается в переменную my_rg. + +Модули DECORT, которым для работы требуется ресурсная группа, в качестве одного из параметров, как правило, принимают идентификатор `rg_id`. Так, например, используя модуль `decort_kvmvm` для создания виртуального сервера необходимо указать ресурсную группу, к которой будет принадлежать этот виртуальный сервер. Сделать это можно следующим образом: + +``` + <прочие детали опущены> + rg_id: "{{ my_rg.facts.id }}" +``` \ No newline at end of file diff --git a/wiki/5.2.6/модуль-decort_vins.md b/wiki/5.2.6/модуль-decort_vins.md new file mode 100644 index 0000000..c2a2bd2 --- /dev/null +++ b/wiki/5.2.6/модуль-decort_vins.md @@ -0,0 +1,120 @@ +# 03.06 Модуль decort_vins +## Обзор модуля decort_vins + +Модуль decort_vins предназначен для создания, изменения, получения текущих характеристик и удаления виртуального сетевого сегмента (Virtual Network Segment, ViNS). +Позволяет: +- Создавать, удалять виртуальные сетевые сегменты. +- Изменять виртуальные сетевые сегменты. +- Запрашивать информацию о виртуальных сетевых сегментах. +- Восстанавливать удалённые виртуальные сетевые сегменты. +- Соединять виртуальные сетевые сегменты. + +## Параметры модуля decort_vins + +Ниже в алфавитном порядке приведен полный список параметров для модуля decort_vins. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_vins` + + +|Параметр | Тип, допустимые значения | Описание| +| ------ | ------ | ------ | +|account_id | (int) | Уникальный целочисленный идентификатор учётной записи (account), которой принадлежит данный ViNS. При идентификации виртуального сетевого сегмента по имени (см. параметр `vins_name`) должно быть задан либо идентификатор, либо имя учётной записи (см. параметр `account_name`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется.| +|account_name | (string) | Имя учётной записи (account), которой принадлежит данный ViNS. При идентификации виртуального сетевого сегмента по имени (см. параметр `vins_name`) должно быть задано либо имя, либо идентификатор учётной записи (см. параметр account_id). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется.| +|annotation | (string)| Текстовое описание виртуального сетевого сегмента. Данный аргумент является опциональным и учитывается только при создании ViNS, а при всех прочих операциях игнорируется.| +|app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`.| +|app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`.| +| authenticator | Значения:
`legacy`
`oauth2`
`jwt` <- default | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. | +|controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) данный виртуальный сервер. Данный параметр является обязательным. ext_net_id (int) Данный аргумент контролирует подключение виртуального сетевого сегмента во внешнюю сеть: * -1 (default) - ViNS не подключён ко внешней сети; * 0 - ViNS подключён ко внешней сети, которую платформа выбирает по умолчанию; * >0 - ViNS подключён ко внешней сети с заданным идентификатором;| +|ext_ip_addr | (string) | Данный аргумент позволяет выбрать IP адрес для подключения виртуального сетевого сегмента ко внешней сети (когда значение аргумента ext_net_id больше либо равно 0) и учитывается только при новых подключених - в рамках модуля `decort_vins` вы не можете изменить внешний IP адрес не меняя идентификатор внешней сети. Параметр является опциональным: если он не задан, то в случае ViNS, подключённого во внешнюю сеть, платформа назначит IP адрес автоматически.| При ручном задании данного параметра следует иметь ввиду, что если будет задан неверный или уже занятый IP адрес, то модуль вернёт ошибку. +|ipcidr | string | Адрес, которые надлежит присвоить внутренней сети сетевого сегмента. Данный аргумент является опциональным и используется только при создании нового виртуального сетевого сегмента, а при всех прочих операциях игнорируется. Если этот аргумент не задан, то платформа назначит адрес автоматически. Обратите внимание, что виртуальные сетевые сегменты, принадлежащие одной и той же учётной записи, не могут иметь пересекающихся диапазонов внутренних адресов.| +|jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt` Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`.| +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу _Oauth2_, который должен использоваться в режиме `authenticator: oauth2`.
Данный параметр является обязательным для указанного режима.
Если параметр не задан в _playbook_, модуль будет использовать значение переменной окружения _DECORT_OAUTH2_URL_. | +|rg_id | (int) | Идентификатор ресурсной группы, в которой должен быть создан или уже существует ViNS. Если одновременно заданы `rg_id` и `rg_name`, то `rg_name` игнорируется.| +|rg_name | (string) | Имя ресурсной группы, в которой должен быть создан или уже существует ViNS. Если одновременно заданы `rg_name` и `rg_id`, то `rg_name` игнорируется.| +| state | Значения:
`present` <- default
`absent`
`enabled`
`disabled`
| Целевое состояние ViNS. | +| verify_ssl | (bool)
`True` <- default
`False` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +|vins_id | (int) | Идентификатор виртуального сетевого сегмента. Соответствующий сетевой сегмент должен существовать (таким образом, с помощью `vins_id` нельзя создать новый сегмент, а только управлять уже имеющимися. Если задан данный параметр, то параметры `vins_name`, `account_name`, `account_id`, `rg_name` и `rg_id` игнорируются.| +|vins_name| (string) | Имя виртуального сетевого сегмента. Для идентификации виртуального сетевого сегмента требуется либо vins_name и информация об учётной записи/ресурсной группе, которой принадлежит сегмент, либо `vins_id`. Обратите внимание, что это имя уникально только в рамках ресурсной группы или учетной записи, на уровне которой существует данный сетевой сегмент.| +| ext_net_id | (int) | Уникальный целочисленный идентификатор внешней сети. | +| mgmtaddr | (str) | Адрес менеджмента интерфейса. В данный параметр указывается ip адрес менеджмента интерфейса, который предоставляет доступ по SSH. | +| custom_config | (bool) | Параметр, отвечающий за использование пользовательской конфигурации. По умолчанию: `False` | +| config_save | (bool) | Параметр, отвечающий за сохранение файла конфигурации. По умолчанию: `False` | +| connect_to | (list) | Список, в котором необходимо указывать виртуальные сетевые сегменты, которые Вы хотите соединить. См. примеры. +|workflow_callback | (string) | URL, по которому вышестоящее приложение (например, пользовательский портал или оркестратор верхнего уровня, инициирующий запуск Ansible playbook) ожидает API вызова, в параметрах которого модуль desc_vm будет оперативно передавать информацию о своем статусе и текущей фазе исполнения. Данный параметр является опциональным. Функциональность callbacks в текущей версии модуля не реализована.| +|workflow_context | (string) | Контекстная информация, которая будет содержаться в параметрах API вызова, адресованного к `workflow_callback` URL. Данная информация призвана однозначно идентифицировать задачу, выполняемую модулем в настоящий момент, чтобы оркестратор верхнего уровня мог сопоставить получаемые через вызов workflow_callback данные со своим внутренним состоянием и отслеживать инициированные им задачи. Параметр является опциональным и имеет значение только при условии, что также задан `workflow_callback`. Функциональность callbacks в текущей версии модуля не реализована.| + +## Возвращаемые значения модуля decort_vins + +Модуль decort_vins возвращает информацию о виртуальном сетевом сегменте (Virtual Network Segment, ViNS) в виде словаря facts со следующими ключами: + +| Ключ | Тип данных | Описание | +| --- | --- | --- | +| account_id | int | Уникальный целочисленный идентификатор учётной записи (_account_), которой принадлежит ViNS. | +| ext_ip_addr | (string) | IP адрес интерфейса, которым ViNS подключён во внешнюю сеть. Если ViNS не подключён к внешней сети, то пустая строка. | +| ext_net_id | (int) | Идентификатор внешней сети, к которой подключён ViNS. -1 означает, что ViNS в данный момент не подключён к внешней сети. | +| gid | (int) | Идентификатор физического кластера (Grid ID), на базе которого развёрнуты ресурсы данного ViNS. | +| id | (int) | Уникальный целочисленный идентификатор ViNS. | +| name | (string) | Имя ViNS. Обратите внимание, что это имя уникально только в рамках учётной записи или ресурсной группы, на уровне которых создан ViNS. | +| int_net_addr | (string) | Адрес внутренней сети ViNS. Обратите внимание, что адреса внутренних сетей всех ViNS, принадлежащих одной и той же учётной записи (независимо от того, создан ли ViNS на уровне учётной записи или ресурсной группы), не пересекаются. | +| rg_id | (int) | Уникальный целочисленный идентификатор учётной ресурсной группы, которой принадлежит данный ViNS. Если данный ViNS создан на уровне учётной записи, то `rg_id=0`. | +| state | (string) | Текущее состояние ViNS.
Корректные состояния: `CREATED`, `ENABLED`, `DISABLED`, `DELETED`, `DESTROYED`. | + + + +## Пример использования модуля decort_vins + +В данном примере создаётся виртуальный сетевой сегмент с именем "MyVins01" (vins_name: "MyVins01"). + +Сетевой сегмент создаётся на уровне ресурсной группы "MyRg01" (rg_name: "MyRg01"), принадлежащей учётной записи "MyMainAccount" (account_name: "MyMainAccount"). Виртуальный сетевой сегмент будет иметь подключение во внешнюю сеть по умолчанию (ext_net_id: 0). +``` + - name: Manage ViNS at resource group level + decort_vins: + authenticator: jwt + jwt: "{{ my_jwt.jwt }}" + controller_url: "https://ds1.digitalenergy.online" + vins_name: "vins_created_by_decort_VINS_module" + state: present + rg_id: 198 + ext_net_id: -1 + ipcidr: "10.20.30.0/24" + mgmtaddr: "10.20.30.1" + custom_config: false + config_save: false + verify_ssl: false + register: my_vins + ``` + +Здесь результат исполнения модуля decort_vins записывается в переменную my_vins, которую можно дальше использовать в Ansible playbooks. Ниже показано, как получить и использовать идентификатор ViNS для подключения к нему виртуального сервера. +``` +- name: manage virtual server + decort_kvmvm: + << для краткости фрагмент опущен >> + networks: + - type: VINS + id: "{{ my_vins.facts.id }}" + << для краткости фрагмент опущен >> +``` + +В данном примере идёт создание виртуального сетевого сегмента, а потом его привязка к виртуальным сетевым сегментам с id 864 и 196. +``` + - name: Manage ViNS at resource group level + decort_vins: + authenticator: jwt + jwt: "{{ my_jwt.jwt }}" + controller_url: "https://cloud.digitalenergy.online" + vins_name: "vins_connected_by_decort_vins_module" + state: present + rg_id: 98 + connect_to: + - type: VINS + id: 864 + ipaddr: 192.168.5.66 + netmask: 24 + - type: VINS + id: 196 + ipaddr: 192.168.9.133 + netmask: 24 + register: managed_vins + +``` + + diff --git a/wiki/5.2.6/полезные-советы.md b/wiki/5.2.6/полезные-советы.md new file mode 100644 index 0000000..62efe8f --- /dev/null +++ b/wiki/5.2.6/полезные-советы.md @@ -0,0 +1,16 @@ +## 4.1 Как хранить авторизационную информацию отдельно от плейбука. + +В зависимости от метода авторизации нужная информация может передаваться в аргументах app_id & app_secret (пара аргументов), jwt (единичный аргумент) или user & password (пара аргументов). Однако, помещать данную информацию непосредственно в плейбук или роль может быть небезопасным, так как она позволяет любому, кто получил этот файл, осуществить доступ к контроллеру облачной инфраструктуры от лица данного пользователя. + +Если вышеперечисленные параметры не указаны в плейбуке или роли, то Ansible модуль DECORT попытается использовать значения, заданные в соответствующих переменных окружения: + + +| Аргумент | Переменная окружения| +| ------ | ------ | +| app_id | DECORT_APP_ID | +| app_secret | DECORT_APP_SECRET | +| jwt | DECORT_JWT | +| user | DECORT_USER | +| password | DECORT_PASSWORD | + +Поэтому для использования плейбука, не содержащего секретной информации, рекомендуется вместо внесения значений непосредственно в файл перед запуском провайдера установить нужные переменные окружения. \ No newline at end of file diff --git a/wiki/5.3.0/Home.md b/wiki/5.3.0/Home.md new file mode 100644 index 0000000..c7f062d --- /dev/null +++ b/wiki/5.3.0/Home.md @@ -0,0 +1,16 @@ +1. [Введение](./введение.md) + - [Введение](./введение.md#введение) + - [Системные требования](./введение.md#системные-требования) + - [Подготовка к работе](./введение.md#подготовка-к-работе) +1. Ansible модули DECORT: + - [Модуль decort_kvmvm](./модуль-decort_kvmvm.md) - управление виртуальными машинами + - [Модуль decort_osimage](./модуль-decort_osimage.md) - управление образами + - [Модуль decort_disk](./модуль-decort_disk.md) - управление дисками + - [Модуль decort_pfw](./модуль-decort_pfw.md) - управление правилами переадресации портов + - [Модуль decort_rg](./модуль-decort_rg.md) - управление ресурсными группами + - [Модуль decort_vins](./модуль-decort_vins.md) - управление внутренними сетями + - [Модуль decort_jwt](./модуль-decort_jwt.md) - получение авторизационного токена + - [Модуль decort_bservice](./модуль-decort_bservice.md) - управление базовыми службами + - [Модуль decort_group](./модуль-decort_group.md)- управление группами базовой службы + - [Модуль decort_k8s](./модуль-decort_k8s.md) - управление кластерами Kubernetes + - [Модуль decort_lb](./модуль-decort_lb.md) - управление балансировщиками нагрузки diff --git a/wiki/5.3.0/введение.md b/wiki/5.3.0/введение.md new file mode 100644 index 0000000..bcc3cae --- /dev/null +++ b/wiki/5.3.0/введение.md @@ -0,0 +1,27 @@ +# Модули Ansible для управления облачными ресурсами в платформе DECORT +## Введение + +Настоящая документация содержит руководство пользователя по библиотеке модулей decort для Ansible. С помощью этих модулей Вы сможете управлять созданием и конфигурированием облачных ресурсов в платформе DECORT (Digital Energy Cloud Orchestration Technology). + +По каждому модулю есть своя документация, в которой вы можете найти список доступных действий, подробную информацию о входных параметрах и возвращаемых данных, а также примеры использования. + +## Системные требования +Убедитесь, что Ваша система соответствует требованиям для работы модуля DECORT. +Системные требования для работы модуля: +- Ansible 2.16.5 or higher +- Python 3.10.12 or higher +- PyJWT 1.7.1 Python module or higher +- requests Python module +- netaddr Python module +- DECORT cloud platform version 4.0.0 + +## Подготовка к работе + +Для начала работы необходимо, чтобы Ansible было известно местоположение файлов модулей. Для этого необходимо: +- либо разместить директории **library** и **module_utils** в одной директории с плейбуками +- либо в рабочей директории, из которой будет запускаться Ansible, разместить файл **ansible.cfg**, в котором задать пути к файлам модулей, например: +``` +[defaults] +library=./library +module_utils=./module_utils +``` diff --git a/wiki/5.3.0/модуль-decort_bservice.md b/wiki/5.3.0/модуль-decort_bservice.md new file mode 100644 index 0000000..2348c45 --- /dev/null +++ b/wiki/5.3.0/модуль-decort_bservice.md @@ -0,0 +1,92 @@ +# Модуль decort_bservice + +## Обзор модуля decort_bservice + +Базовая служба (Basic Service) это несколько групп виртуальных серверов (compute), создаваемых и управляемых как единое целое. + +Все compute(s) в группе имеют одни и те же характеристики (cpu/ram/boot disk size/OS image + сетевые подключения). Для разных групп эти характеристики могут быть разными. + +Группы в составе Basic Service могут иметь отношения parent-child с другими группами. Наличие таких отношений определяет последовательность запуска групп ("сначала parents"). + +На основе ресурсов, предоставляемых и управляемых через Basic Service, могут создаваться другие сервисы. + +`Для взаимодействия с группами виртуальных серверов используется модуль decort_group.` + +Модуль decort_bservice предназначен для управления базовыми службами, в которых находятся группы виртуальных серверов. +Данный модуль позволяет: +- Создавать Basic Service +- Удалять Basic Service (безвозвратно). +- Включать/выключать Basic Service +- Запускать Basic Service +- Запрашивать информацию о Basic Service. + + +## Параметры модуля decort_bservice + +Ниже в алфавитном порядке приведен полный список параметров для модуля decort_bservice. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на Вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_bservice` + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| account_id | (int) | Уникальный целочисленный идентификатор аккаунта, которому принадлежит ресурсная группа. При идентификации базовой службы и ресурсной группы по именам (см. параметры `name` и `rg_name`) должен быть задан либо идентификатор, либо имя аккаунта (см. параметр `account_name`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется.| +|account_name | (string) | Имя аккаунта, которому принадлежит ресурсная группа. При идентификации базовой службы и ресурсной группы по именам (см. параметры `name` и `rg_name`) должен быть задан либо идентификатор (см. `account_id`), либо имя аккаунта. Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется. | +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`.| +|app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`.| +| authenticator | Значения:
`oauth2`
`jwt` | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. Данный параметр является обязательным. | +|controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должна быть создана (или уже существует) данная базовая служба. Данный параметр является обязательным.| +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt`
Данный параметр является обязательным для указанного режима.
Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в _playbook_.
Если этот параметр не определен в _playbook_, то модуль будет использовать значение переменной окружения `DECORT_JWT`. | +|oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`.| +|state | Значения: `absent`, `disabled`, `enabled`, `present`, `check`.
Default: `present`| Целевое состояние базовой службы. Значения `present` и `enabled` равнозначны.| +| started | (bool)
Default: `true` | Параметр, определяющий состояние добавленных виртуальных серверов в Basic Service. Запускать их, или нет. | +| name | (string) | Имя базовой службы. Данный параметр является обязательным при создании базовой службы и при изменении если не задан `id`. | +| sshuser | (string) | Имя пользователя, который будет создан на всех виртуальных серверах базовой службы. Используется в паре с `sshkey`. | +| sshkey | (string) | SSH ключ, который будет загружен на все виртуальные сервера базовой службы. Используется в паре с `sshuser`. | +| id | (int) | Уникальный целочисленный идентификатор базовой службы. Используется для поиска, изменения и удаления базовой службы. | +| rg_id | (int) | Уникальный целочисленный идентификатор ресурсной группы базовой службы. | +| rg_name | (string) | Имя ресурсной группы базовой службы. | +| verify_ssl | (bool)
Default: `true` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | + +## Возвращаемые значения модуля decort_bservice + +Модуль decort_bservice возвращает информацию о базовой службе в виде словаря facts со следующими ключами: + + +|Ключ | Тип данных | Описание| +| ------ | ------ | ------ | +|id | int | Уникальный целочисленный идентификатор базовой службы.| +|name | string | Имя базовой службы.| +| techStatus | string | Технический статус базовой службы. | +|state | string | Статус базовой службы.| +| rg_id | int | Уникальный целочисленный идентификатор ресурсной группы, в которой находится базовая служба.| +| account_id | int | Уникальный целочисленный идентификатор аккаунта, в котором находится ресурсная группа.| +| groups | list | Список словарей с информацией о группах базовой службы.| + + +## Пример использования модуля decort_bservice + +Данный пример создаёт базовую службу с именем databases. + +``` + - name: Create Basic Service + decort_bservice: + authenticator: jwt + controller_url: "{{ controller_url }}" + jwt: "{{ auth_token }}" + name: databases + rg_id: "{{ rg_id }}" + register: db_bservice +``` + +Данный пример удаляет базовую службу с названием databases. +``` + - name: Delete Basic Service + decort_bservice: + authenticator: jwt + controller_url: "{{ controller_url }}" + jwt: "{{ auth_token }}" + state: absent + name: databases + rg_id: "{{ rg_id }}" + register: db_bservice +``` \ No newline at end of file diff --git a/wiki/5.3.0/модуль-decort_disk.md b/wiki/5.3.0/модуль-decort_disk.md new file mode 100644 index 0000000..4cf625e --- /dev/null +++ b/wiki/5.3.0/модуль-decort_disk.md @@ -0,0 +1,177 @@ +# Модуль decort_disk +## Обзор модуля decort_disk + +Модуль decort_disk предназначен для управления дисками и позволяет производить с ними следующие действия: +- создавать +- удалять (в корзину или безвозвратно) +- восстановливать из корзины +- изменять: + - имя + - размер + - лимиты ввода/вывода + - включать/отключать общий доступ + + +Обратите внимание: + +- Загрузочный диск для виртуальной машины создаётся и подключается автоматически в процессе создания этой машины. +- Модуль decort_disk служит для управления дополнительными дисками (т.н. data-дисками); +- Подключение дополнительных дисков, созданных посредством decort_disk, к виртуальным машинам выполняется с помощью модуля управления виртуальными машинами (см. модуль decort_kvmvm). + +## Параметры модуля decort_disk + +Ниже в алфавитном порядке приведен полный список параметров для модуля decort_disk. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_disk` + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| account_id | (int) | Уникальный целочисленный идентификатор аккаунта, которому принадлежит данный диск. При идентификации диска по имени (см. параметр `name`) должно быть задан либо идентификатор, либо имя аккаунта (см. параметр `account_name`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется.| +|account_name | (string) | Имя аккаунта, которому принадлежит данный диск. При идентификации диска по имени (см. параметр name) должно быть задано либо имя, либо идентификатор учётной записи (см. параметр `account_id`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется. | +| annotation | (string)
Default: `Disk by decort_disk` | Текстовое описание диска. Данный параметр является опциональным и учитывается только при создании диска, а при всех прочих операциях игнорируется.| +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`.| +|app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`.| +| authenticator | Значения:
`oauth2`
`jwt` | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. Данный параметр является обязательным. | +|controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) данный диск. Данный параметр является обязательным.| +|id | (int) | Уникальный целочисленный идентификатор диска. Соответствующий диск должен существовать (таким образом, с помощью id нельзя создать новый диск, а только управлять уже имеющимися). Если задан данный параметр, то параметры `name`, `account_name` и `account_id` игнорируются.| +| iops | (int)
Default: `2000` | Ограничение ввода/вывода диска. Используется при создании диска. | +| force_detach | (bool)
Default: `false` | Задаёт поведение платформы при попытке удалить диск, подключённый к виртуальной машине.
По умолчанию, удаление подключённых дисков не разрешается, и попытка удалить такой диск приведёт к аварийному завершению модуля. Чтобы изменить это поведение, явно установите `force_detach: true`. | +|jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt` Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`.| +| limitIO | (dict) | Параметр, позволяющий ограничить скорость ввода/вывода диска как в iops, так и в байтах в секунду. Обратите внимание, что параметры с total не задаются вместе с read/write. Все возможные подпараметры можно увидеть в примерах. | +| name | (string) | Имя диска. Для идентификации диска требуется либо его `name` и информация об аккаунте (`account_id` или `account_name`), которому принадлежит диск, либо его `id`.| +|oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`.| +| permanently | (bool)
Default: `false` | Параметр, использующийся при удалении диска, при значении `true` - диск удалится навсегда, а при `false` - попадёт в корзину. | +| place_with | (int) | Идентификатор образа диска, из которого следует взять параметр `sep_id`, чтобы разместить данный диск на той же системе хранения данных, что и указанный образ диска. Данный параметр является опциональным и используется только на стадии создания диска. Если задан `place_with`, то `sep_id` игнорируется.| +| pool | (string) | Название пула на системе хранения данных, в рамках которой следует создать данный диск. Этот параметр используется только на стадии создания диска и игнорируется при операциях над уже существующими дисками. Параметр является опциональным, если не задан, то платформа будет использовать пул, который сконфигурирован на целевой системе хранения как пул по умолчанию. | +| reason | (string)
Default: `Managed by Ansible decort_disk` | Причина, по которой было выполнено какое-либо действие. В данном модуле используется только при удалении диска. | +| sep_id | (int) | Идентификатор системы хранения данных (Storage End-point). Данный параметр определяет систему хранения данных, на ресурсах которой создаётся диск. Используется только при создании диска и игнорируется при прочих операциях. Альтернативой данному параметру является `place_with`, позволяющий разместить диск на той же системе хранения, что и указанный образ диска, на базе которого создаётся виртуальная машина.| +| shareable | (bool)
Default: `false` | Включение/отключение общего доступа к диску. +| size | (int) | Размер диска в ГБ. Этот параметр является обязательным при создании диска. Если он задан для уже существующего диска, а текущий размер диска меньше заданного, то будет предпринята попытка увеличить размер диска. При прочих операциях данный параметр игнорируется.| +| state | (str)
Значения:
`present`
`absent`
Default: `present` | Целевое состояние диска. | +| type | (string)
Значения:
`B`
`D`
Default: `D` | Тип создаваемого диска. `B` - Boot/загрузочный, `D` - Data/с данными. | +| verify_ssl | (bool)
Default: `false` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | + +## Возвращаемые значения модуля decort_disk + +Модуль decort_disk возвращает информацию о диске в виде словаря facts со следующими ключами: + + +|Ключ | Тип данных | Описание| +| ------ | ------ | ------ | +| account_id | int | Уникальный целочисленный идентификатор аккаунта, которому принадлежит диск.| +| attached_to | int | Идентификатор виртуальной машины, к которой в настоящий момент подключён диск. Если диск не подключён, то `attached_to: 0`| +| gid | int | Идентификатор физического кластера (Grid ID), на ресурсах которого создан диск.| +| id | int | Уникальный целочисленный идентификатор данного диска.| +| iotune | dict | Текущие ограничения ввода/вывода диска. | +| name | string | Имя диска. Обратите внимание, что имя диска не является уникальным с точки зрения системы хранения данных, на которой этот диск расположен.| +| pool | string | Имя пула на системе хранения данных, в котором размещаются ресурсы диска.| +|sep_id | int | Идентификатор системы хранения данных (Storage Endpoint), на которой размещаются ресурсы данного диска.| +| size | int | Размер диска в ГБ.| +| state | string | Текущий статус диска. | + + +## Пример использования модуля decort_disk + +В данном примере создаётся диск размером 50ГБ (`size: 50`), с ограничением ввода/вывода в 2000 iops, на ресурсах системы хранения данных под номером 1 (`sep_id: 1`) в пуле "data01". + +``` +- name: Manage disk + decort_disk: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + name: "DataDisk01" + size: 50 + account_name: "MyAccount" + sep_id: 1 + iops: 2000 + annotation: "Disk example" + pool: data01 + register: my_data_disk01 +``` +В следующих двух примерах для существующего диска с именем "DataDisk01" задаются все доступные лимиты на операции ввода/вывода. + +``` +- name: Manage disk + decort_disk: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + name: "DataDisk01" + account_name: "MyAccount" + limitIO: + read_bytes_sec: 10000 + write_bytes_sec: 5000 + read_iops_sec и write_iops_sec + read_iops_sec: 2500 + write_iops_sec: 1000 + read_bytes_sec_max: 11000 + write_bytes_sec_max: 6000 + read_iops_sec_max: 3000 + write_iops_sec_max: 1500 + size_iops_sec: 1000 + register: my_data_disk01 + +- name: Manage disk + decort_disk: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + name: "DataDisk01" + account_name: "MyAccount" + limitIO: + total_bytes_sec: 15000 + total_iops_sec: 3500 + total_bytes_sec_max: 17000 + total_iops_sec_max: 4500 + size_iops_sec: 1000 + register: my_data_disk01 +``` + +В данном примере выполняется восстановление удаленного диска с id 111 из корзины. + +``` +- name: Manage disk + decort_disk: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + id: 111 + size: 10 + register: my_data_disk01 +``` + +В данном примере выполняется переименование диска с id 111 на новое имя "NewExampleDisk". + +``` +- name: Manage disk + decort_disk: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + id: 111 + name: "NewExampleDisk" + size: 10 + register: my_data_disk01 +``` + +Здесь результат работы модуля decort_disk записывается в переменную my_data_disk01. Для получения идентификатора диска, например, при подключении его к виртуальной машине, следует воспользоваться показанной ниже конструкцией: + +``` +- name: Manage compute + decort_kvmvm: + << для краткости фрагмент опущен >> + data_disks: + - "{{ my_data_disk01.facts.id }}" + << для краткости фрагмент опущен >> +``` \ No newline at end of file diff --git a/wiki/5.3.0/модуль-decort_group.md b/wiki/5.3.0/модуль-decort_group.md new file mode 100644 index 0000000..46f651f --- /dev/null +++ b/wiki/5.3.0/модуль-decort_group.md @@ -0,0 +1,105 @@ +# Модуль decort_group +## Обзор модуля decort_group +Модуль **decort_group** позволяет производить следующие действия над группами базовой службы: +- создать +- получить информацию +- запустить/остановить +- подключить/отключить сети (для существующей группы только внутренние сети) +- изменить + - имя + - объём загрузочного диска + - количество ВМ + - роль + - количество виртуальных процессоров + - объём ОЗУ +- удалить (безвозвратно) + +## Параметры модуля decort_group +Ниже приведен полный список параметров для модуля **decort_group**. + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| account_id | (int) | Идентификатор аккаунта. При идентификации диска по имени (см. параметр `name`) должен быть задан либо идентификатор, либо имя аккаунта (см. параметр `account_name`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется. | +| account_name | (string) | Имя аккаунта. При идентификации диска по имени (см. параметр `name`) должно быть задано либо имя, либо идентификатор аккаунта (см. параметр `account_id`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется. | +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`. | +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`. | +| authenticator | (str)
Значения:
`oauth2`
`jwt` | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. Обязательный параметр. | +| boot_disk | (int) | Обьём загрузочного диска. | +| bservice_id | (int) | Идентификатор базовой службы. Обязательный параметр. | +| controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT. Данный параметр является обязательным. | +| count | (int) | Количество виртуальных машин. Обязательный параметр. | +| cpu | (int) | Количество виртуальных процессоров. | +| driver | (string)
Default: `KVM_X86` | Драйвер. | +| id | (int) | Идентификатор группы. | +| image_id | (int) | Идентификатор образа. | +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt` Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`. | +| name | (str) | Имя группы. Обязательный параметр. +| networks | (list) | Список словарей, описывающих сети, которые должны быть подключены.
Ключи словаря:
• `type` (string) (обязательный) - тип сети; значения: `VINS` (внутренняя) или `EXTNET` (внешняя)
• `id` (int) (обязательный) - идентификатор сети | +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`. | +| ram | (int) | Объём оперативной памяти. | +| role | (string) | Тег роли. | +| state | (str)
Значения:
`present`
`absent`
`started`
`stopped`
`check`
Default: `present` | +| timeoutStart | (int) | Время отсрочки запуска группы после создания в секундах. | +| verify_ssl | (bool)
Default: `true` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты. Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | + +## Возвращаемые значения модуля decort_group + +Модуль **decort_group** возвращает информацию о диске в виде словаря facts со следующими ключами: + + +|Ключ | Тип данных | Описание| +| ------ | ------ | ------ | +| account_id | int | Идентификатор аккаунта. | +| Computes | list | Список словарей, содержащих информацию о виртуальных машинах группы.
Ключи словаря:
• `id` (int) - идентификатор ВМ
• `ipAddresses` (list) - список IP-адресов ВМ
• `name` (str) - имя ВМ
• `osUsers` (list) - список словарей, содержащих учётные данные пользователей ОС ВМ по умолчанию; ключи: `login` (str) и `password` (str) | +| id | int | Идентификатор группы.| +| name | string | Имя группы.| +| rg_id | int | Идентификатор ресурсной группы. | +| state | string | Состояние группы. | +| techStatus | string | Технический статус группы. | + +## Пример использования модуля decort_group + +Данный пример создаёт группу с именем `test_group`. +``` +- hosts: localhost + tasks: + - name: Create BS group + decort_group: + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + authenticator: oauth2 + boot_disk: 10 + bservice_id: 1823 + controller_url: "https://ds1.digitalenergy.online" + count: 2 + cpu: 2 + image_id: 518 + name: test_group + networks: + - type: VINS + id: 1987 + oauth2_url: "https://sso.digitalenergy.online" + ram: 2 + verify_ssl: false + register: group_test +``` + +Данный пример удаляет группу с именем `test_group`. + +``` +- hosts: localhost + tasks: + - name: Delete BS group + decort_group: + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + authenticator: oauth2 + bservice_id: 1823 + controller_url: "https://ds1.digitalenergy.online" + name: test_group + oauth2_url: "https://sso.digitalenergy.online" + state: absent + verify_ssl: False + register: group_test +``` \ No newline at end of file diff --git a/wiki/5.3.0/модуль-decort_jwt.md b/wiki/5.3.0/модуль-decort_jwt.md new file mode 100644 index 0000000..4cd124b --- /dev/null +++ b/wiki/5.3.0/модуль-decort_jwt.md @@ -0,0 +1,53 @@ +# Вспомогательный модуль для получения авторизационного JWT токена decort_jwt +## Обзор модуля decort_jwt + +Модуль **decort_jwt** предназначен для получения авторизационного токена JWT (JSON Web Token). Данный модуль может быть полезен при массовом создании ресурсов (например, виртуальных машин), так как позволяет оптимизировать количество API вызовов, инициируемых в адрес контроллера облачной платформы. + +По сути, данный модуль является провайдером информации и не управляет облачными ресурсами (всегда возвращает `changed: False`). +## Параметры модуля decort_jwt + +Ниже в алфавитном порядке приведен полный список параметров для модуля **decort_jwt**. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_jwt` + + +|Параметр | Тип, допустимые значения | Описание| +| ------ | ------ | ------ | +| app_id | (string) | Идентификатор приложения, использующийся для подключения к авторизационному серверу. Данный параметр является обязательным. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`. | +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к авторизационному серверу. Данный параметр является обязательным. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`. | +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, у которого запрашивается JWT. Данный параметр является обязательным. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`. | +| validity | (int)
Default: `3600` | Срок действия JWT в секундах. | +| verify_ssl | (bool)
Default: `true` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес авторизационного сервера, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | + +## Возвращаемые значения модуля decort_jwt + +При успешном выполнении модуль **decort_jwt** возвращает JWT-токен, который доступен по ключу `jwt` (str). + +## Пример использования модуля decort_jwt + +В данном примере сначала запрашивается JWT со сроком действия 1200 сек., а затем этот JWT используется для создания виртуальной машины в режиме авторизации `jwt` (подробнее о данном режиме см. в разделе «Примеры различных режимов авторизации»). +``` +- hosts: localhost + tasks: + - name: Obtain JWT with validity of 1200 sec from the OAuth2 provider + decort_jwt: + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + validity: 1200 + register: my_token +``` +``` + - name: Create VM in JWT authorization mode + decort_kvmvm: + authenticator: jwt + jwt: "{{ my_token.jwt }}" + controller_url: "https://cloud.digitalenergy.online" + name: NewVM01 + cpu: 2 + ram: 4096 + <<<дальнейшие детали опущены>>> +``` +Обратите внимание, как используется JWT при создании новой ВМ (в предположении, что результат выполнения task для модуля **decort_jwt** был сохранен в переменной my_token): +``` + jwt: "{{ my_token.jwt }}" +``` \ No newline at end of file diff --git a/wiki/5.3.0/модуль-decort_k8s.md b/wiki/5.3.0/модуль-decort_k8s.md new file mode 100644 index 0000000..0decbad --- /dev/null +++ b/wiki/5.3.0/модуль-decort_k8s.md @@ -0,0 +1,108 @@ +# Модуль decort_k8s +## Обзор модуля decort_k8s + +Модуль **decort_k8s** предназначен для выполнения следующих действий над кластерами Kubernetes: +- создать +- получить информацию +- отключить/включить/запустить +- изменить группы Worker-узлов +- удалить (в корзину или безвозвратно) +- восстановить из корзины + +## Параметры модуля decort_k8s +Ниже приведен полный список параметров для модуля **decort_k8s**: + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| account_id | (int) | Идентификатор аккаунта. | +| account_name | (string) | Имя аккаунта. | +| additionalSANs | (list) | Список дополнительных SAN (Subject Alternative Names) для использования в процессе автоматического выписывания сертификата Кластера Kubernetes. Можно использовать IP-адреса и доменные имена. +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`. | +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`. | +| authenticator | (str)
Значения:
`oauth2`
`jwt` | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. Обязательный параметр. | +| cluster_conf | (dict) | Словарь с глобальными настройками и конфигурацией для всего кластера. Включает в себя такие настройки, как имя кластера, настройки DNS, методы аутентификации и другие конфигурации всего кластера. | +| controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT. Данный параметр является обязательным. | +| description | (string)
Default: `Created by decort ansible module` | Описание кластера. | +| extnet_id | (int)
Default: `0` | Идентификатор внешней сети. Если задан `0`, то внешняя сеть выбирается автоматически. Если задан параметр `vins_id`, то значение данного параметра будет проигнорировано. Если задан `extnet_only: false`, то внешняя сеть будет подключена к создаваемой внутренней сети, а если `extnet_only: true`, то либо напрямую к каждому узлу кластера (если `with_lb: false`), либо напрямую к балансировщику нагрузки (если `with_lb: true`). | +| extnet_only | (bool)
Default: `false` | Не использовать внутреннюю сеть, подключать напрямую к внешней. | +| getConfig | (bool)
Default: `false` | Получить данные конфигурации для доступа к кластеру Kubernetes. | +| ha_lb | (bool)
Default: `false` | Использовать схему высокой доступности для создаваемого балансировщика нагрузки (если `with_lb: true`). | +| id | (int) | Идентификатор кластера Kubernetes. | +| init_conf | (dict) | Словарь для определения настроек и действий, которые должны быть выполнены перед запуском любого другого компонента в кластере. Позволяет настраивать такие процессы, как регистрация узла, настройка сети и другие задачи инициализации. +| join_conf | (dict) | Словарь для настройки поведения и параметров присоединения узла к кластеру. Включает в себя такие параметры, как control-plane-endpoint кластера, токен и certificate-key. | +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt`. Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`. | +| k8ci_id | (int) | Идентификатор конфигурации кластера Kubernetes. Обязательный параметр. | +| kubeproxy_conf | (dict) | Словарь для настройки поведения и настроек Kube-proxy, отвечающего за сетевое проксирование и балансировку нагрузки внутри кластера. Включает в себя такие параметры, как режим прокси, диапазоны IP-адресов кластера и другие конфигурации, специфичные для Kube-proxy. | +| kublet_conf | (dict) | Словарь для настройки поведения и настроек Kubelet, который является агентом основного узла, работающим на каждом узле кластера. Включает в себя такие параметры, как IP-адрес узла, распределение ресурсов, политики вытеснения модулей и другие конфигурации, специфичные для Kubelet. | +| master_count | (int)
Default: `1` | Количество Master-узлов. | +| master_cpu | (int)
Default: `2` | Количество виртуальных процессоров на Master-узле. | +| master_disk | (int)
Default: `10` | Объём загрузочного диска на Master-узле. | +| master_pool | (str) | Пул СХД, заданной параметром `master_sepid`. | +| master_ram | (int)
Default: `2048` | Объём оперативной памяти на Master-узле. | +| master_sepid | (int) | Идентификатор СХД для Master-узла. Если не задан, то используется СХД образа Master-узла. | +| name | (string) | Имя кластера Kubernetes. | +| network_plugin | (str)
Значения:
`flannel`
`calico`
`weavenet`
Default: `flannel` | CNI plugin (модуль для управления сетевыми интерфейсами контейнера). Возможные значения могут быть ограничены в используемой конфигурации кластера Kubernetes (параметр `k8ci_id`) | +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`. | +| oidc_cert | (raw) | Сертификат X.509 для OIDC-провайдера. +| permanent | (bool)
Default: `false` | Если выполняется удаление, то выполнить безвозвратное удаление (минуя корзину). | +| rg_id | (int) | Идентификатор ресурсной группы. | +| rg_name | (str) | Имя ресурсной группы. | +| started | (bool)
Default: `true` | Автоматический запуск кластера после его создания. | +| state | (string)
Значения:
`present`
`absent`
`enabled`
`disabled`
`check`
Default: `present` | Целевое состояние кластера. | +| verify_ssl | (bool)
Default: `true` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты. Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +| vins_id | (int) | Идентификатор внутренней сети. Если не задан, то будет создана новая внутренняя сеть. Если задан, то заданная внутренняя сеть должна иметь подключение к внешней сети. Если задано `extnet_only: true`, то параметр игнорируется. +| with_lb | (bool)
Default: `true` | Создание кластера Kubernetes с размещением Master-узлов за балансировщиком нагрузки. В ином случае каждый узел получит отдельный адрес из внешней сети. +| workers | (list) | Обязательный параметр. Список словарей, описывающих группы Worker-узлов.
Ключи словаря:
• `annotations` (list) (необязательный) - список строк с annotations в формате: `key1=value1`
• `ci_user_data` (dict) (необязательный) - конфигурация для cloud-init
• `cpu` (int) (обязательный) - количество виртуальных процессоров на узле
• `disk` (int) (обязательный) - объём загрузочного диска на узле
• `labels` (list) (необязательный) - список строк с labels в формате: `label1=value1`
• `name` (string) (обязательный) - имя группы Worker-узлов
• `num` (int) (обязательный) - количество узлов
• `pool` (str) (обязательный) - пул СХД, заданной ключом `sep_id`
• `ram` (int) (обязательный) - объём оперативной памяти на узле
• `sep_id` (int) (обязательный) - идентификатор СХД; если задать `null`, то используется СХД образа узла
• `taints` (list) (необязательный) - список строк с taints в формате: `key1=value1:NoSchedule` | + + +## Возвращаемые значения модуля decort_k8s + +Модуль **decort_k8s** возвращает информацию о кластере в виде словаря `facts` со следующими ключами: + + +| Ключ | Тип данных | Описание | +| ------ | ------ | ------ | +| account_id | int | Идентификатор аккаунта. | +| config | str | Kuber config кластера. +| id | int | Идентификатор кластера. | +| k8s_Masters | dict | Словарь с информацией о группе Master-узлов.
Ключи словаря:
• `cpu` (int) - количество виртуальных процессоров
• `detailedInfo` (list) - список словарей с информацией об узлах; ключи словаря: `id` (int) - идентификатор ВМ, `name` (str) - имя ВМ, `status` (str) - статус ВМ, `techStatus` (str) - технический статус ВМ
• `disk` (int) - объём загрузочного диска
• `id` (int) - идентификатор группы
• `name` (str) - имя группы
• `num` (int) - количество узлов
• `ram` (int) - объём оперативной памяти +| k8s_Workers | dict | Список словарей с информацией о группах Worker-узлов.
Ключи словаря:
• `cpu` (int) - количество виртуальных процессоров
• `detailedInfo` (list) - список словарей с информацией об узлах; ключи словаря: `id` (int) - идентификатор ВМ, `name` (str) - имя ВМ, `status` (str) - статус ВМ, `techStatus` (str) - технический статус ВМ
• `disk` (int) - объём загрузочного диска
• `id` (int) - идентификатор группы
• `labels` (list) - список строк с labels
• `name` (str) - имя группы
• `num` (int) - количество узлов
• `ram` (int) - объём оперативной памяти
• `taints` (list) - список строк с taints +| name | string | Имя кластера. | +| rg_id | int | Идентификатор ресурсной группы. | +| state | string | Статус кластера. | +| techStatus | string | Технический статус кластера. | +| vins_id | int | Идентификатор внутренней сети кластера. | + + +## Пример использования модуля decort_k8s + + +Пример создания кластера Kubernetes с именем `cluster-test` с получением Kuber config. +``` + - name: Create a k8s cluster named cluster-test + decort_k8s: + authenticator: jwt + controller_url: "https://ds1.digitalenergy.online" + getConfig: true + jwt: "{{ token.jwt }}" + k8ci_id: 18 + name: cluster-test + rg_id: 125 + workers: + - cpu: 10 + disk: 10 + name: wg1 + num: 1 + pool: null + ram: 1024 + sep_id: null + - cpu: 10 + disk: 10 + name: wg2 + num: 2 + pool: null + ram: 1024 + sep_id: null + register: k8s_cluster +``` \ No newline at end of file diff --git a/wiki/5.3.0/модуль-decort_kvmvm.md b/wiki/5.3.0/модуль-decort_kvmvm.md new file mode 100644 index 0000000..a6008f8 --- /dev/null +++ b/wiki/5.3.0/модуль-decort_kvmvm.md @@ -0,0 +1,106 @@ +# Модуль decort_kvmvm +## Обзор модуля decort_kvmvm + +Модуль **decort_kvmvm** предназначен для выполнения следующих действий над виртуальными машинами: +- создать +- получить информацию +- остановить/приостановить/запустить +- подключить/отключить диски с данными +- подключить/отключить сети +- изменить + - объём загрузочного диска в большую сторону + - количество CPU + - объём ОЗУ + - тэги + - affinity метку + - affinity правила + - anti-affinity правила +- удалить (безвозвратно) + +## Параметры модуля decort_kvmvm + +Ниже в алфавитном порядке приведен полный список параметров для модуля **decort_kvmvm**. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_kvmvm` + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| aaff_rule | (list) | Список словарей, описывающих anti-affinity правила.
Ключи словаря:
• `topology` (string) (обязательный) - назначение правила; значения: `node` (узел) или `compute` (ВМ)
• `policy` (string) (обязательный) - степень "необходимости" этого правила; значения: `RECOMMENDED` или `REQUIRED`
• `mode` (string) (обязательный) - режим сравнения; значения: `EQ` (должно соответствовать), `NE` (не должно соответствовать), `ANY` (любое)
• `key` (string) (обязательный) - ключ, который учитывается при анализе данного правила
• `value` (string) (обязательный) - значение ключа, учитываемого при анализе данного правила (зависит от ключа `mode`) +| account_id | (int) | Идентификатор аккаунта. Этот параметр является опциональным и используется в сценариях, когда уже существующая ресурсная группа задается комбинацией `account_id` и `rg_name`. Если задан `account_id`, то `account_name` игнорируется.| +| account_name | (string) | Имя аккаунта. Этот параметр является опциональным и используется в сценариях, когда уже существующая ресурсная группа задается комбинацией `account_name` и `rg_name`. Если задан `account_id`, то `account_name` игнорируется. | +| aff_rule | (list) | Список словарей, описывающих affinity правила.
Ключи словаря:
• `topology` (string) (обязательный) - назначение правила; значения: `node` (узел) или `compute` (ВМ)
• `policy` (string) (обязательный) - степень "необходимости" этого правила; значения: `RECOMMENDED` или `REQUIRED`
• `mode` (string) (обязательный) - режим сравнения; значения: `EQ` (должно соответствовать), `NE` (не должно соответствовать), `ANY` (любое)
• `key` (string) (обязательный) - ключ, который учитывается при анализе данного правила
• `value` (string) (обязательный) - значение ключа, учитываемого при анализе данного правила (зависит от ключа `mode`) +| affinity_label | (str) | Метка affinity. +| annotation | (string) | Опциональное описание виртуальной машины. Этот параметр используется только при создании.| +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль **decort_kvmvm** будет использовать значение переменной окружения `DECORT_APP_ID`.| +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DCORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль **decort_kvmvm** будет использовать значение переменной окружения `DECORT_APP_SECRET`.| +| arch | (str)
Значения:
`X86_64`
`PPC64_LE`
Default: `X86_64` | Архитектура виртуальной машины. | +| authenticator | (str)
Значения:
`oauth2`
`jwt` | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. Обязательный параметр. | +| boot_disk | (int) | Объём загрузочного диска виртуальной машины в ГБ. | +| ci_user_data | (dict) | конфигурация для cloud-init +| controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) данная виртуальная машина. Данный параметр является обязательным. | +| cpu | (int) | Количество виртуальных процессоров, выделяемых виртуальной машине. | +| data_disks | (list) | Список идентификаторов дисков, которые следует подключить к данной виртуальной машине как дополнительные. | +| id | (int) | Уникальный цифровой идентификатор виртуальной машины. Этот параметр используется как один из методов идентификации существующей ВМ (альтернатива – по комбинации `name`, `rg_name` и `account_name`). Если при вызове модуля **decort_kvmvm** существующая ВМ идентифицируется по `id`, то параметры `account_id`, `account_name`, `rg_id` и `rg_name` игнорируются. | +| image_id | (int) | Уникальный цифровой идентификатор образа, на базе которого следует создать виртуальную машину. При создании задать этот параметр или параметр `image_name`. При любых других операциях данные параметры игнорируются. Если заданы оба этих параметра (`image_id` и `image_name`), то `image_name` игнорируется. | +| image_name | (string) | Название образа, на базе которого следует создать ВМ. При создании требуется задать этот параметр или параметр `image_id`. При любых других операциях данные параметры игнорируются. | +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt`. Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль **decort_kvmvm** будет использовать значение переменной окружения `DECORT_JWT`. +| name | (string) | Имя ВМ. Чтобы модуль **decort_kvmvm** мог управлять сервером по его названию, также необходимо задать комбинацию `account_name` и `rg_name` или `rg_id`. Если для существующей ВМ указаны и `name`, и `id`, то параметр `name` игнорируется и идентификация сервера выполняется по `id`.| +| networks | (list) | Список словарей, описывающих сети для подключения к ВМ.
Ключи словаря:
• `type` (string) (обязательный) - тип сети; значения: `VINS` (внутренняя) или `EXTNET` (внешняя)
• `id` (int) (обязательный) - идентификатор сети
• `ip_addr` (string) (необязательный) - IP-адрес, используемый для подключения к данной сети. | +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль **decort_kvmvm** будет использовать значение переменной окружения `DECORT_OAUTH2_URL`. | +| pool | (str) | Пул СХД, заданной параметром `sep_id`. Если пул не задан, то он будет выбран платформой. +| ram | (int) | Объем оперативной памяти в МБ, выделенной данной ВМ. Параметр является обязательным при создании. Если указать его для уже существующей ВМ, то будет выполнена попытка изменить объем выделенной памяти. | +| rg_id | (int) | Уникальный цифровой идентификатор уже существующей ресурсной группы, в которой будет создана новая или находится уже существующая ВМ. Данный параметр является одним из методов идентификации существующей РГ (альтернативой является задание комбинации `account_name` и `rg_name`).| +| rg_name | (string) | Имя уже существующей ресурсной группы, в которой будет создаа новая или находится уже существующая ВМ. Данный параметр является одним из методов идентификации существующей РГ, когда задается пара `account_name` и `rg_name` (альтернативой является задание `rg_id`). Если заданы и `rg_id`, и `rg_name`, то параметр `rg_name` игнорируется.| +| sep_id | (int) | Идентификатор СХД для загрузочного диска ВМ. Если не задан, то будет использоваться СХД образа. +| ssh_key | (string) | Открытая часть SSH-ключа, который необходимо добавить на создаваемую ВМ для пользователя, заданного параметром `ssh_key_user`. Данный параметр применим только для ОС Linux, используется только при создании и игнорируется при других операциях. | +| ssh_key_user | (string) | Имя пользователя в гостевой ОС (только для Linux), для которого добавляется SSH-ключ, заданный параметром `ssh_key`. Данный параметр является обязательным, если задан `ssh_key`. Используется только при создании и игнорируется при других операциях. | +| state | (str)
Значения:
`present`
`absent`
`poweredon`
`poweredoff`
`halted`
`paused`
`check`
Default: `present` | Целевое состояние ВМ.
Значение `halted` - синоним к `poweredoff`. Значения `present` и `poweredon` равнозначны.
Значение `check` вызывает модуль в _read-only_ режиме и считывает характеристики существующей ВМ. | +| tag | (dict) | Словарь, пары ключ-значение которого, описывают тэги для ВМ. | +| verify_ssl | (bool)
Default: `true` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | + +## Возвращаемые значения модуля decort_kvmvm + +Модуль **decort_kvmvm** возвращает информацию о виртуальной машине в виде словаря `facts` со следующими ключами: + + +| Ключ | Тип данных | Описание | +| ------ | ------ | ------ | +| account_id | int | Идентификатор аккаунта. | +| arch | string | Архитектура ВМ. | +| cpu | int | Количество виртуальных процессоров. | +| data_disks | list | Список идентификаторов дисков с данными, подключенных к ВМ. | +| disk_size | int | Размер загрузочного диска в ГБ. | +| id | int | Идентификатор ВМ. | +| image_id | id | Идентификатор образа. +| name | string | Имя ВМ. | +| password | string | Пароль пользователя ОС по умолчанию. | +| private_ips | list | Список IP-адресов на сетевых интерфейсах ВМ, которые подключены к внутренним сетям. | +| public_ips | list | Список IP адресов на сетевых интерфейсах ВМ, которые подключены к внешним сетям. | +| ram | int | Объём ОЗУ ВМ в МБ. | +| rg_id | int | Идентификатор ресурсной группы, которой принадлежит данная ВМ. | +| state | string | Состояние ВМ.| +| tags | dict | Словарь, пары ключ-значение которого, описывают тэги ВМ. +| tech_status | str | Технический статус ВМ. +| username | string | Имя пользователя ОС по умолчанию. | + +## Пример использования модуля decort_kvmvm + +В данном примере создается ВМ с именем MyFirstVM. +``` +- name: Сreate VM + decort_kvmvm: + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + authenticator: oauth2 + controller_url: "https://cloud.digitalenergy.online" + cpu: 2 + image_id: "{{ my_img.facts.id }}" + name: MyFirstVM + networks: + - type: VINS + id: "{{ my_vins.facts.id }}" + oauth2_url: "https://sso.digitalenergy.online" + ram: 4096 + rg_id: "{{ my_rg.facts.id }}" + register: my_vm +``` \ No newline at end of file diff --git a/wiki/5.3.0/модуль-decort_lb.md b/wiki/5.3.0/модуль-decort_lb.md new file mode 100644 index 0000000..543a8ac --- /dev/null +++ b/wiki/5.3.0/модуль-decort_lb.md @@ -0,0 +1,893 @@ +# Модуль decort_lb +## Обзор модуля decort_lb + +Модуль **decort_lb** предназначен для выполнения следующих действий над балансировщиками нагрузки: +- создать +- получить информацию +- отключить/включить/запустить/перезапустить +- изменить конфигурации backend и frontend +- удалить (в корзину или безвозвратно) +- восстановить из корзины + +## Параметры модуля decort_lb +Ниже приведен полный список параметров для модуля **decort_lb**: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ПараметрТипОписание
+ account_id + + (int) + + Идентификатор аккаунта. +
+ account_name + + (str) + + Имя аккаунта. +
Может быть альтернативой параметру account_id. +
+ annotation + + (str) +
Default: Managed by Ansible module decort_lb +
+ Описание балансировщика. +
+ app_id + + (str) + + Идентификатор приложения, использующийся для подключения к контроллеру + облачной платформы DECORT в режиме authenticator: oauth2. + Данный параметр является обязательным для указанного режима. Если + параметр не задан в playbook, модуль будет использовать значение + переменной окружения DECORT_APP_ID. +
+ app_secret + + (str) + + Секретный ключ приложения, который используется для подключения к + контроллеру облачной платформы DECORT в режиме + authenticator: oauth2. Данный параметр является + обязательным для указанного режима. Так как он содержит секретную + информацию, то его не рекомендуется задавать непосредственно в + playbook. Если параметр не задан в playbook, то модуль будет + использовать значение переменной окружения + DECORT_APP_SECRET. +
+ authenticator + + (str) +
Значения: +
oauth2 +
jwt +
+ Режим аутентификации при подключении к контроллеру облачной платформы + DECORT. Обязательный параметр. +
+ backends + + (list) + + Список словарей, описывающих конфигурации backend. +
+ algorithm + + (str) +
Значения: +
leastconn +
roundrobin +
static-rr +
Default: roundrobin +
+ Используемый алгоритм. +
+ + default_settings + + + (dict) + + Cловарь, описывающий параметры по умолчанию для backend-серверов. +
+ downinter + + (int) +
Default: 1000 +
+ Интервал в миллисекундах между двумя последовательными проверками + доступности сервера, который считается недоступным. +
+ fall + + (int) +
Default: 2 +
+ Количество последовательных неудачных проверок доступности, после + которых сервер, ранее считавшийся доступным, начинает считаться + недоступным и временно исключается из схемы балансировки. +
+ inter + + (int) +
Default: 5000 +
+ Интервал в миллисекундах между двумя последовательными проверками + доступности сервера, который считается доступным. +
+ maxconn + + (int) +
Default: 250 +
+ Лимит одновременных подключений к серверу. При достижении этого лимита + сервер временно исключается из схемы балансировки. +
+ maxqueue + + (int) +
Default: 256 +
+ Лимит соединений, ожидающих в очереди. Когда этот предел будет + достигнут, все последующие подключения будут перенаправлены + на другие серверы. +
+ rise + + (int) +
Default: 2 +
+ Количество проверок, которые должен пройти сервер, считавшийся + недоступным, чтобы начать считаться доступным и снова быть + включенным в схему балансировки. +
+ slowstart + + (int) +
Default: 60000 +
+ Интервал в миллисекундах с момента когда сервер начинает считаться + доступным, по истечении которого количество фактически разрешенных + подключений к этому серверу будет возвращено до 100% от + установленного лимита. +
+ weight + + (int) +
Default: 100 +
+ Вес сервера для использования в алгоритмах балансировки. +
+ name + + (str) + + Название backend. Обязательный параметр. +
+ controller_url + + (str) + + URL контроллера, соответствующего экземпляру облачной платформы + DECORT. Данный параметр является обязательным. +
+ ext_net_id + + (int) + + Идентификатор внешней сети. +
Может быть не задан, если задан vins_id - + в таком случае балансировщик будет подключён только к + внутренней сети. +
+ + frontends + + + (list) + + Список словарей, описывающих конфигурации frontend. +
+ backend + + (str) + + Название используемого backend. Обязательный параметр. +
+ bindings + + (list) + + Список словарей, описывающих конфигурации binding. +
+ address + + (str) + + IP-адрес. +
Если не задан, то будет использоваться основной IP-адрес + балансировщика во внешней сети или, если подключена только + внутренняя сеть, основной IP-адрес балансировщика во внутренней сети. +
+ name + + (str) + + Название. Обязательный параметр. +
+ port + + (int) +
Значения: +
1-65535 +
+ Порт. Обязательный параметр. +
+ ha_lb + + bool +
Default: false +
+ Использовать схему высокой доступности для создаваемого балансировщика. +
+ jwt + + (str) + + JSON Web Token (JWT), который будет использоваться для подключения + к контроллеру облачной платформы DECORT в режиме + authenticator: jwt. Данный параметр является + обязательным для указанного режима. Так как он содержит + потенциально секретную информацию, а сам JWT, как правило, + имеет ограниченное время жизни, то его не рекомендуется задавать + непосредственно в playbook. Если этот параметр не определен в + playbook, то модуль будет использовать значение переменной + окружения DECORT_JWT. +
+ lb_id + + (int) + + Идентификатор балансировщика нагрузки. +
+ lb_name + + (str) + + Имя балансировщика. Обязательный параметр. +
+ oauth2_url + + (str) + + URL авторизационного сервера, работающего по протоколу Oauth2, + который должен использоваться в режиме + authenticator: oauth2. Данный параметр является + обязательным для указанного режима. Если параметр не задан в + playbook, модуль будет использовать значение переменной + окружения DECORT_OAUTH2_URL. +
+ permanently + + (bool) +
Default: false +
+ Если выполняется удаление, то выполнить безвозвратное удаление + (минуя корзину). +
+ rg_id + + (int) + + Идентификатор ресурсной группы. +
+ rg_name + + (str) + + Имя ресурсной группы. +
В комбинации с заданным аккаунтов может быть альтернативой + параметру rg_id +
+ servers + + (list) + + Список словарей, описывающих конфигурации backend-серверов. +
+ address + + (str) + + IP-адрес. Обязательный параметр. +
+ backends + + (list) + + Список словарей, описывающих параметры backend-сервера для разных + конфигураций backend. +
Обязательный параметр. +
+ check + + (str) +
Значения: +
enabled +
disabled +
Default: enabled +
+ Проверка доступности сервера. +
+ name + + (str) + + Название конфигурации backend. Обязательный параметр. +
+ port + + (int) +
Значения: +
1-65535 +
+ Порт. Обязательный параметр. +
+ server_settings + + (dict) + + Словарь, описывающий параметры backend-сервера. +
Ключи данного словаря аналогичны ключам словаря + + default_settings + . +
+ name + + (str) + + Название. Обязательный параметр. +
+ state + + (str) +
Значения: +
present +
absent +
enabled +
disabled +
restart +
Default: present +
+ Целевое состояние балансировщика нагрузки. +
Выполнение с state=restart позволяет выполнить + перезапуск балансировщика, соответственно, всегда возвращает + changed: true. +
Значения present и enabled равнозначны + и соответствуют включённому и запущенному балансировщику. +
+ verify_ssl + + (bool) +
Default: true +
+ Позволяет отключить проверку SSL сертификатов при выполнении API + вызовов в адрес контроллера облачной инфраструктуры, например, + при работе с изолированной облачной инфраструктурой, использующей + самоподписанные сертификаты. Применяйте данный параметр с + осторожностью, предпочтительно в защищенных средах. +
+ vins_id + + (int) + + Идентификатор внутренней сети. +
Может быть не задан, если задан ext_net_id - + в таком случае балансировщик будет подключён только к + внешней сети. +
+ vins_name + + (str) + + Имя внутренней сети. +
В комбинации с заданной РГ может быть альтернативой для + параметра vins_id. +
+ +## Возвращаемые значения модуля decort_lb + +Модуль **decort_lb** возвращает информацию о балансировщике в виде словаря `facts` со следующими ключами: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Ключ + + Тип +
данных +
+ Описание +
+ backends + + list + + Список словарей, описывающих конфигурации backend. +
+ algorithm + + str + + Используемый алгоритм. +
+ name + + str + + Название конфигурации backend. +
+ serverDefaultSettings + + dict + + Cловарь, описывающий параметры по умолчанию для backend-серверов. +
Ключи данного словаря аналогичны ключам словаря + + default_settings + . +
+ servers + + list + + Список словарей, описывающих конфигурации backend-серверов. +
+ address + + str + + IP-адрес. +
+ check + + str + + Проверка доступности сервера. +
+ name + + str + + Название. +
+ port + + int + + Порт. +
+ serverSettings + + dict + + Cловарь, описывающий параметры backend-сервера. +
Ключи данного словаря аналогичны ключам словаря + + default_settings + . +
+ frontends + + list + + Список словарей, описывающих конфигурации frontend. +
Ключи данного словаря аналогичны ключам словарей списка + + frontends + . +
+ gid + + int + + Идентификатор физического кластера (Grid ID). +
+ id + + int + + Идентификатор балансировщика. +
+ name + + str + + Имя балансировщика. +
+ rg_id + + int + + Идентификатор ресурсной группы. +
+ state + + str + + Статус балансировщика. +
diff --git a/wiki/5.3.0/модуль-decort_osimage.md b/wiki/5.3.0/модуль-decort_osimage.md new file mode 100644 index 0000000..139bc69 --- /dev/null +++ b/wiki/5.3.0/модуль-decort_osimage.md @@ -0,0 +1,132 @@ +# Модуль decort_osimage +## Обзор модуля decort_osimage + +Модуль decort_osimage предназначен для выполнения следующих действий над образами, созданными в облачной платформе DECORT: +- шаблонные образы: + - создать + - получить информацию + - изменить имя + - удалить +- виртуальные образы + - создать + - получить информацию + - изменить + - имя + - целевой образ + +## Параметры модуля decort_osimage +Ниже приведен полный список параметров для модуля decort_osimage. + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| account_Id | (int) | Уникальный целочисленный идентификатор аккаунта. Используется для поиска образов, а тажке для их создания. | +| account_name | (string) | Имя аккаунта. Используется для получения уникального целочисленного идентификатора аккаунта. | +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`. | +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`. | +| architecture | (string)
Default: `X86_64` | Архитектура микропроцессора, для которой предназначен образ. Используется при создании образа.| +| authenticator | (str)
Значения:
`oauth2`
`jwt` | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. Обязательный параметр. | +| boottype | (string)
Default: `uefi` | Тип загрузки образа. Используется при создании образа.| +| controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должен быть создан (или уже существует) образ. Данный параметр является обязательным. | +| drivers | (string)
Default: `KVM_X86` | Тип виртуальных машин, подходящих для образа. Используется при создании образа.| +| hotresize | (bool)
Default: `false` | Поддерживает ли образ "горячее" изменение размера. По умолчанию установлено `false`. Используется при создании образа операционной системы. | +| image_id | (int) | Идентификатор шаблонного образа. +| image_name | (str) | Имя шаблонного образа. +| image_password | (string) | Опциональный пароль для образа. Используется при создании образа.| +| image_username | (string) | Опциональное имя пользователя для образа. Используется при создании образа. | +| imagetype | (string)
Default: `linux` | Тип образа. Используется при создании образа.| +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt`. Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`.| +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`. | +| passwordDL | (string) | Пароль для скачивания по URL-адресу. Используется при создании образа. | +| pool | (str) | Имя пула СХД. Используется для поиска существующего образа. +| poolName | (string) | Имя пула СХД. Используется при создании образа. | +| sepId | (integer) | Уникальный целочисленный идентификатор СХД. Используется при создании образа. | +| sep_id | (int) | Идентификатор СХД. Используется для поиска существующего образа. +| state | (string)
Значения:
`present`
`absent`
Default: `present` | Целевое состояние образа. `present` - существует, `absent` - удалён. | +| url | (string) | URL-адрес, с которого будет загружен образ. Используется при создании образа.| +| usernameDL | (string) | Имя пользователя для загрузки образа с заданного URL-адреса. Используется при создании образа. | +| verify_ssl | (bool)
Default: `true` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты. Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +| virt_id | (integer) | Уникальный целочисленный идентификатор виртуального образа. Может использоваться для получения информации о виртуальном образе, а также для привязки к нему другого образа.| +| virt_name | (string) | Имя виртуального образа. Используется для получения `virt_id`, а в последствии информации о виртуальном образе, а также для создания виртуального образа и привязки к нему другого образа.| + + +## Возвращаемые значения модуля decort_osimage + +Модуль decort_osimage возвращает информацию об образе в виде словаря facts со следующими ключами: + + +| Ключ | Тип данных | Описание | +| ------ | ------ | ------ | +| accountId | int | Идентификатор аккаунта. +| id | int | Уникальный целочисленный идентификатор данного образа. | +| linkto | int | Уникальный целочисленный идентификатор образа, который привязан к данному виртуальному. | +| name | string | Имя образа. | +| pool | string | Имя пула на системе хранения данных, в котором находится данный образ.| +| sep_id | int | Идентификатор системы хранения данных, на которой хранится данный образ. | +| size | int | Размер образа в ГБ. | +| state | string | Текущий статус образа. | +| type | string | Тип образа. | + + + +## Пример использования модуля decort_osimage + + +Пример создания шаблонного образа с операционной системой Alpine Linux. +``` + - name: Create template image + decort_osimage: + account_Id: 12345 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + authenticator: oauth2 + controller_url: "https://ds1.digitalenergy.online" + image_name: alpine_linux_3.19.1 + url: https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/cloud/nocloud_alpine-3.19.1-x86_64-uefi-cloudinit-r0.qcow2 + verify_ssl: false + register: osimage +``` + +Пример получения информации о существующем шаблонном образе по его имени. + +``` + - name: Get template image + decort_osimage: + account_Id: 12345 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + authenticator: oauth2 + controller_url: "https://ds1.digitalenergy.online" + image_name: alpine_linux_3.19.1 + verify_ssl: false + register: osimage +``` + +Пример создания виртуального образа. Также в случае, если виртуальный образ уже существует, но к нему привязан другой шаблонный образ, он привяжет к себе указанный в примере шаблонный образ. + +``` + - name: Create virtual image + decort_osimage: + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + authenticator: oauth2 + controller_url: "https://ds1.digitalenergy.online" + image_name: alpine_linux_3.19.1 + virt_name: alpine_last + register: osimage +``` +Обратите внимание, что в данном примере можно использовать как `image_name`, так и `image_id`. Также можно использовать либо `virt_name`, либо `virt_id`. + +Пример переименования образа. + +``` + - name: Rename template image + decort_osimage: + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + authenticator: oauth2 + controller_url: "https://ds1.digitalenergy.online" + image_name: alpine_linux_3.19.1_new_name + image_id: 54321 + register: osimage +``` + diff --git a/wiki/5.3.0/модуль-decort_pfw.md b/wiki/5.3.0/модуль-decort_pfw.md new file mode 100644 index 0000000..65ccf02 --- /dev/null +++ b/wiki/5.3.0/модуль-decort_pfw.md @@ -0,0 +1,76 @@ +# Модуль decort_pfw +## Обзор модуля decort_pfw + +Модуль **decort_pfw** предназначен для настройки правил переадресации портов (port forwarding, destination NAT) на виртуальном маршрутизаторе заданной внутренней сети для заданной виртуальной машины. +Модуль поддерживает выполнение следующих действий над правилами: +- добавить +- получить информацию +- удалить + +## Параметры модуля decort_pfw + +Ниже в алфавитном порядке приведен полный список параметров для модуля **decort_pfw**. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_pfw` + + +| Параметр | Тип, допустимые значения | Описание| +| ------ | ------ | ------ | +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`. | +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`. | +| authenticator | (str)
Значения:
`oauth2`
`jwt` | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. Обязательный параметр. | +| compute_id | (int) | Идентификатор виртуальной машины. IP-адрес, который данная ВМ имеет во внутренней сети, заданной параметром `vins_id`, будет использоваться как внутренний IP-адрес в правилах. Обязательный параметр. | +| controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT. Данный параметр является обязательным. | +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt`. Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`. | +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`. | +| rules | (list) | Список словарей, описывающих правила переадресации портов для заданной ВМ (параметр `compute_id`) на виртуальном маршрутизаторе заданной внутренней сети (параметр `vins_id`).
Ключи словаря:
• `local_port` (int) (обязательный) - внутренний порт; значения: от `1` до `65535`
• `proto` (str) (обязательный) - протокол; значения: `tcp` или `udp`
• `public_port_end` (int) (необязательный) - верхняя граница диапазона внешних портов; значения: от значения ключа `public_port_start` до `65535`;
• `public_port_start` (int) (обязательный) - нижняя граница диапазона внешних портов; значения: от `1` до `65535` | +| state | (str)
Значения:
`present`
`absent`
Default: `present` | Целевое состояние правил.
Если `state=absent`, то, независимо от содержания параметра `rules`, будут удалены все правила для заданной ВМ (параметр `compute_id`) на виртуальном маршрутизаторе заданной внутренней сети (параметр `vins_id`). | +| verify_ssl | (bool)
Default: `true` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +| vins_id | (int) | Идентификатор внутренней сети, на виртуальном маршрутизаторе которой настраиваются правила переадресации портов. Заданная ВМ (параметр `compute_id`) должна быть подключена к этой сети. Обязательный параметр. | + + +## Возвращаемые значения модуля decort_pfw + +Модуль **decort_pfw** возвращает информацию о правилах переадресации портов и сопутствующую информацию в виде словаря `facts` со следующими ключами: + + +|Ключ | Тип данных | Описание| +| ------ | ------ | ------ | +| compute_id | int | Идентификатор ВМ. | +| public_ip | string | IP-адрес во внешней сети, настроенный на виртуальном маршрутизаторе внутренней сети. | +| rules | list | Список словарей, описывающих правила переадресации портов.
Ключи словаря:
• `id` (int) - идентификатор правила
• `localIp` (str) - IP-адрес ВМ
• `localPort` (int) - внутренний порт
• `protocol` (str) - протокол
• `publicPortEnd` (int) - верхняя граница диапазона внешних портов
• `publicPortStart` (int) - нижняя граница диапазона внешних портов
• `vmId` (int) - идентификатор ВМ
• `vmName` (str) - имя ВМ | +| state | string | Статус правил. Значения: `PRESENT` или `ABSENT`. | +| vins_id | int | Идентификатор внутренней сети. | + +## Пример использования модуля decort_pfw + +В данном примере для существующей ВМ (параметр `compute_id`), подключённой к внутренней сети (параметр `vins_id`), настраиваются два правила переадресации портов: + +- Правило для одного порта: внешний порт `30022` на внутренний порт `22` по протоколу `tcp`. +- Правило для диапазона портов: внешние порты с `30080` по `30085` на внутренние порты с `30080` по протоколу `udp`. + +``` +- name: Add port forwarding rules + decort_pfw: + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + authenticator: oauth2 + compute_id: "{{ my_vm.facts.id }}" + controller_url: "https://cloud.digitalenergy.online" + oauth2_url: "https://sso.digitalenergy.online" + rules: + - local_port: 22 + proto: tcp + public_port_start: 30022 + - local_port: 30080 + proto: udp + public_port_end: 30085 + public_port_start: 30080 + vins_id: "{{ my_vins.facts.id }}" + register: my_pfw_rules +``` + +В данном примере результат выполнения модуля **decort_pfw** записывается в переменную `my_pfw_rules`. Для доступа к списку правил используйте следующую конструкцию: + +``` +"{{ my_pfw_rules.facts.rules }}" +``` diff --git a/wiki/5.3.0/модуль-decort_rg.md b/wiki/5.3.0/модуль-decort_rg.md new file mode 100644 index 0000000..24dd2b2 --- /dev/null +++ b/wiki/5.3.0/модуль-decort_rg.md @@ -0,0 +1,200 @@ +# Модуль decort_rg + +## Обзор модуля decort_rg + +Модуль decort_rg предназначен для создания, редактирования и удаления ресурсных групп (Resource Group, RG) в облачной платформе DECORT, а также для получения информации об уже существующей ресурсной группе. Модуль decort_rg позволяет: + +- создавать ресурсные группы +- удалять ресурсные группы (в корзину или безвозвратно) +- восстанавливать ресурсные группы из корзины +- включать/отключать ресурсные группы +- запрашивать информацию об уже существующих ресурсных группах +- изменять ресурсные группы: + - переименовывать + - настраивать квоты + - задавать стандартную сеть + - настраивать права доступа для пользователей + +## Параметры модуля decort_rg + +Ниже в алфавитном порядке приведен полный список параметров для модуля decort_rg. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: `ansible-doc -t module decort_rg` + +| Параметр | Тип, допустимые значения | Описание | +|----------|--------------------------|----------| +| account_id | (int) | Уникальный целочисленный идентификатор аккаунта, в рамках которого создаётся или уже существует данная ресурсная группа. Должен быть задан либо идентификатор, либо имя аккаунта (см. параметр `account_name`). Если одновременно заданы и `account_id`, и `account_name`, то account_name игнорируется. | +| account_name | (string) | Имя аккаунта, в рамках которого создаётся или уже существует данная ресурсная группа. Должно быть задано либо имя, либо идентификатор учётной записи (см. параметр `account_id`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется. | +| access | (dict) | Параметр, позволяющий выдать, забрать или изменить права у пользователя в ресурсной группе. | +| annotation | (string) | Текстовое описание ресурсной группы. Данный параметр является опциональным и учитывается только при создании ресурсной группы, а при всех прочих операциях игнорируется. | +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`. | +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения DECORT_APP_SECRET. | +| authenticator | Значения:
`oauth2`
`jwt`| Режим аутентификации при подключении к контроллеру облачной платформы DECORT. Данный параметр является обязательным.| +| controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должна быть создана (или уже существует) данная ресурсная группа. Данный параметр является обязательным. | +| def_netType | (string)
Значения:
`PRIVATE`
`PUBLIC`
`NONE`
Default: `PRIVATE` | Тип сети по умолчанию в ресурсной группе. Используется при создании ресурсной группы, а также при изменении. | +| def_netId | (int) | Уникальный целочисленный идентификатор внутренней или внешней сети. Используется для изменения сети по умолчанию в ресурсной группе. | +| extNetId | (int) | Уникальный целочисленный идентификатор внешней сети. Используется при создании РГ для подключения внешней сети к внутренней сети, которая будет создана и задана как сеть по умолчанию для РГ. | +| extNetIp | (string) | IP-адрес для внешней сети, которая задана в параметре `extNetId`. | +| owner | (string) | Владелец ресурсной группы. Задаётся при создании ресурсной группы, не обязателен. Если оставить пустое значение - владельцем ресурсной группы будет пользователь, создавший ресурсную группу. | +| ipcidr | (string) | IP-адрес внутренней сети. Используется при создании ресурсной группы, чтобы задать IP-адрес сети для создаваемой внутренней сети, которая создастся вместе с ресурсной группой. Используется при параметре `def_netType` в значении `PRIVATE`. | +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt`
Данный параметр является обязательным для указанного режима.
Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в _playbook_.
Если этот параметр не определен в _playbook_, то модуль будет использовать значение переменной окружения `DECORT_JWT`. | +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу Oauth2, который должен использоваться в режиме authenticator: oauth2. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`. | +| rename | (string) | Новое имя ресурсной группы. Используется при переименовании ресурсной группы. | +| quotas | (dict) | Предназначено для задания или изменения квоты на ресурсы в составе данной ресурсной группы. | +| rg_name | (string) | Имя ресурсной группы. Данный параметр является обязательным при создании. | +| rg_id | (int) | Уникальный целочисленный идентификатор ресурсной группы. Является обязательным при изменении РГ если не заданы `rg_name` и `account_id`/`account_name` | +| state | Значения:
`present`
`absent`
`enabled`
`disabled`
Default: `present` | Целевое состояние ресурсной группы. | +| permanently | (bool)
Default: `false` | Параметр, использующийся при удалении ресурсной группы. При значении `true` - ресурсная группа удалится безвозвратно, а при `false` - попадёт в корзину. | +| verify_ssl | (bool)
Default: `true` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | + +## Возвращаемые значения модуля decort_rg + +Модуль decort_rg возвращает информацию о ресурсной группе в виде словаря facts со следующими ключами: +| Ключ | Тип данных | Описание | +|------|------------|----------| +| account_id | int | Уникальный целочисленный идентификатор аккаунта, которому принадлежит данная ресурсная группа. | +| gid | int | Идентификатор физического кластера (Grid ID), на базе которого развёрнута ресурсная группа. | +| id | int | Уникальный целочисленный идентификатор ресурсной группы. | +| name | string | Имя ресурсной группы. Обратите внимание, что это имя уникально только в рамках аккаунта, которому принадлежит данная ресурсная группа. | +| state | string | Текущее состояние ресурсной группы. | +| quota | dict | Текущие квоты ресурсной группы. | +| resTypes | list | Список типов ресурсов, разрешенных к созданию в данной ресурсной группе. | +| defNetId | int | Уникальный целочисленный идентификатор сети по умолчанию в данной ресурсной группе. | +| defNetType | string | Тип сети по умолчанию в данной ресурсной группе. | +| ViNS | list | Список идентификаторов внутренних сетей ресурсной группы. | +| computes | list | Список идентификаторов виртуальных машин ресурсной группы. | + +## Пример использования модуля decort_rg + +В данном примере показано, как создать ресурсную группу по имени MyRG в аккаунте MyAccount. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере показано, как создать ресурсную группу по имени MyRG в аккаунте MyAccount, также задать квоты и выдать права на чтение пользователю MyUser. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + access: + action: "grant" + user: "MyUser" + right: "R" + quotas: + cpu: 16 + ram: 16384 + disk: 100 + ext_ips: 20 + net_transfer: 1000 + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере показано, как выдать доступ на чтение и запись пользователю MyUser в ресурсной группе по имени MyRG в аккаунте MyAccount. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + access: + action: "grant" + user: "MyUser" + right: "RCX" + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере показано, как сменить квоту в ресурсной группе по имени MyRG в аккаунте MyAccount. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + quotas: + cpu: 16 + ram: 16384 + disk: 100 + ext_ips: 20 + net_transfer: 1000 + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере показано, как изменить сеть по умолчанию в ресурсной группе по имени MyRG в аккаунте MyAccount. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + def_netType: "PRIVATE" + def_netId: 99 + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере показано, как переименовать ресурсную группу по имени MyRG на новое имя "NewRg" в аккаунте MyAccount. + +``` +- name: manage resource group + decort_rg: + authenticator: oauth2 + app_id: "{{ my_app_id }}" + app_secret: "{{ my_app_secret }}" + oauth2_url: "https://sso.digitalenergy.online" + controller_url: "https://cloud.digitalenergy.online" + account_name: "MyAccount" + rg_name: "MyRG" + rename: "NewRg" + rg_id: 27 + state: present + register: my_rg + delegate_to: localhost +``` + +В данном примере результат работы модуля decort_rg записывается в переменную my_rg. + +Модули DECORT, которым для работы требуется ресурсная группа, в качестве одного из параметров, как правило, принимают идентификатор `rg_id`. Так, например, используя модуль `decort_kvmvm` для создания виртуального сервера необходимо указать ресурсную группу, к которой будет принадлежать этот виртуальный сервер. Сделать это можно следующим образом: + +``` + <прочие детали опущены> + rg_id: "{{ my_rg.facts.id }}" +``` \ No newline at end of file diff --git a/wiki/5.3.0/модуль-decort_vins.md b/wiki/5.3.0/модуль-decort_vins.md new file mode 100644 index 0000000..43b95ce --- /dev/null +++ b/wiki/5.3.0/модуль-decort_vins.md @@ -0,0 +1,120 @@ +# Модуль decort_vins +## Обзор модуля decort_vins + +Модуль decort_vins предназначен для управления внутренними сетями (Virtual Network Segment, ViNS) и позволяет производить с ними следующие действия: +- создавать + - на уровне ресурсной группы (если РГ задана) + - на уровне аккаунта (если РГ не задана) +- удалять (безвозвратно) +- восстанавливать из корзины +- запрашивать информацию +- включать/отключать +- соединять (необходимы права администратора) +- изменять: + - включать/отключать SSH-доступ к виртуальному маршрутизатору (необходимы права администратора) + - включать/отключать режим **Custom Config** для виртуального маршрутизатора (необходимы права администратора) + - сохранять конфигурацию виртуального маршрутизатора для возможности последующего отката к ней (необходимы права администратора) + +## Параметры модуля decort_vins + +Ниже в алфавитном порядке приведен полный список параметров для модуля decort_vins. Актуальную информацию по параметрам, которые поддерживает версия модуля, установленного на вашем Ansible-сервере, можно получить командой: +`ansible-doc -t module decort_vins` + + +| Параметр | Тип, допустимые значения | Описание | +| ------ | ------ | ------ | +| account_id | (int) | Уникальный целочисленный идентификатор аккаунта, которому принадлежит данная внутреняя сеть. При идентификации внутренней сети по имени (см. параметр `vins_name`) должен быть задан либо идентификатор, либо имя аккаунта (см. параметр `account_name`). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется.| +| account_name | (string) | Имя аккаунта, которому принадлежит данная внутренняя сеть. При идентификации внутренней сети по имени (см. параметр `vins_name`) должно быть задано либо имя, либо идентификатор аккаунта (см. параметр account_id). Если одновременно заданы и `account_id`, и `account_name`, то `account_name` игнорируется.| +|annotation | (string)| Текстовое описание внутренней сети. Данный аргумент является опциональным и учитывается только при создании внутренней сети, а при всех прочих операциях игнорируется.| +| app_id | (string) | Идентификатор приложения, использующийся для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Если параметр не задан в playbook, модуль будет использовать значение переменной окружения `DECORT_APP_ID`.| +| app_secret | (string) | Секретный ключ приложения, который используется для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: oauth2`. Данный параметр является обязательным для указанного режима. Так как он содержит секретную информацию, то его не рекомендуется задавать непосредственно в playbook. Если параметр не задан в playbook, то модуль будет использовать значение переменной окружения `DECORT_APP_SECRET`.| +| authenticator | Значения:
`oauth2`
`jwt` | Режим аутентификации при подключении к контроллеру облачной платформы DECORT. Данный параметр является обязательным. | +| controller_url | (string) | URL контроллера, соответствующего экземпляру облачной платформы DECORT, в рамках которого должна быть создана (или уже существует) данный внутренняя сеть. Данный параметр является обязательным. +| ext_net_id | (int) | Идентификатор внешней сети, к которой должна быть подключена внутренняя сеть. Если задать значение `0`, то платформой будет выбрана внешняя сеть по умолчанию. | +| ext_ip_addr | (string) | IP-адрес внешней сети, заданной через параметр `ext_net_id`. Если не задан, то IP-адрес для внешней сети будет выбран платформой автоматически. +| ipcidr | (string) | Адрес сети для создаваемой внутренней сети. Если этот параметр не задан, то платформа назначит адрес автоматически. Обратите внимание, что внутренние сети, принадлежащие одному и тому же аккаунту, не могут иметь пересекающихся сетей.| +| jwt | (string) | JSON Web Token (JWT), который будет использоваться для подключения к контроллеру облачной платформы DECORT в режиме `authenticator: jwt`. Данный параметр является обязательным для указанного режима. Так как он содержит потенциально секретную информацию, а сам JWT, как правило, имеет ограниченное время жизни, то его не рекомендуется задавать непосредственно в playbook. Если этот параметр не определен в playbook, то модуль будет использовать значение переменной окружения `DECORT_JWT`.| +| oauth2_url | (string) | URL авторизационного сервера, работающего по протоколу _Oauth2_, который должен использоваться в режиме `authenticator: oauth2`.
Данный параметр является обязательным для указанного режима.
Если параметр не задан в _playbook_, модуль будет использовать значение переменной окружения `DECORT_OAUTH2_URL`. | +|rg_id | (int) | Идентификатор ресурсной группы, в которой должна быть создана или уже существует внутренняя сеть. Если одновременно заданы `rg_id` и `rg_name`, то `rg_name` игнорируется.| +| rg_name | (string) | Имя ресурсной группы, в которой должна быть создана или уже существует внутренняя сеть. Если одновременно заданы `rg_name` и `rg_id`, то `rg_name` игнорируется.| +| state | (string)
Значения:
`present`
`absent`
`enabled`
`disabled`
Default: `present` | Целевое состояние внутренней сети. | +| verify_ssl | (bool)
Default: `true` | Позволяет отключить проверку SSL сертификатов при выполнении API вызовов в адрес контроллера облачной инфраструктуры, например, при работе с изолированной облачной инфраструктурой, использующей самоподписанные сертификаты.
Применяйте данный параметр с осторожностью, предпочтительно в защищенных средах. | +| vins_id | (int) | Идентификатор внутренней сети. Соответствующая внутренная сеть должна существовать. Таким образом, с помощью `vins_id` нельзя создать новую внутреннюю сеть, а только управлять уже имеющимися. Если задан данный параметр, то параметры `vins_name`, `account_name`, `account_id`, `rg_name` и `rg_id` игнорируются.| +| vins_name| (string) | Имя внутренней сети. Для идентификации внутренней сети требуется либо `vins_name` и информация об аккаунте/ресурсной группе, которой принадлежит сеть, либо `vins_id`. Обратите внимание, что это имя уникально только в рамках ресурсной группы или аккаунта, на уровне которого существует данная внутренняя сеть.| +| mgmtaddr | (list) | Список существующих IP-адресов виртуального маршрутизатора во внутренней сети, через которые необходимо разрешить SSH-доступ к виртуальному маршрутизатору. +| custom_config | (bool)
Default: `false` | Включить/отключить режим пользовательской конфигурации виртуального маршрутизатора. | +| config_save | (bool)
Default: `false` | Выполнить сохранение текущей конфигурации виртуального маршрутизатора для возможности последующего отката к ней. | +| connect_to | (list) | Список словарей, содержащих информацию о внутренних сетях, к которым необходимо подключить данную. См. примеры. + +## Возвращаемые значения модуля decort_vins + +Модуль decort_vins возвращает информацию о внутренней сети в виде словаря facts со следующими ключами: + +| Ключ | Тип данных | Описание | +| --- | --- | --- | +| account_id | (int) | Уникальный целочисленный идентификатор аккаунта, которому принадлежит внутренняя сеть. | +| custom_net_addr | (list) | Список IP-адресов пользовательских интерфейсов виртуального маршрутизатора +| ext_ip_addr | (string) | IP-адрес интерфейса, которым виртуальный маршрутизатор подключён к внешней сети. | +| ext_net_id | (int) | Идентификатор внешней сети, к которой подключена внутренняя сеть. `-1` означает, что внутренняя сеть не подключёна к внешней сети. | +| gid | (int) | Идентификатор физического кластера (Grid ID), на базе которого развёрнуты ресурсы данной внутренней сети. | +| id | (int) | Уникальный целочисленный идентификатор внутренней сети. | +| name | (string) | Имя внутренней сети. | +| int_net_addr | (string) | Адрес внутренней сети. | +| rg_id | (int) | Уникальный целочисленный идентификатор ресурсной группы, которой принадлежит данная внутренняя сеть. Если данная внутренняя сеть создана на уровне аккаунта, то `rg_id=0`. | +| ssh_ipaddr | (list) | Список IP-адресов виртуального маршрутизатора во внутренней сети, на которых включён SSH-доступ к нему. +| ssh_password | (str) | Пароль для SSH-доступа к виртуальному маршрутизатору. +| ssh_port | (int) | Номер порта для SSH-подключения к виртуальному маршрутизатору. +| state | (string) | Состояние внутренней сети. | + + + +## Пример использования модуля decort_vins + +В данном примере создаётся внутренняя сеть с именем "MyVins01" (vins_name: "MyVins01"). + +Внутренняя сеть создаётся на уровне ресурсной группы "MyRg01" (rg_name: "MyRg01"), принадлежащей аккаунту "MyMainAccount" (account_name: "MyMainAccount"). Внутренняя сеть будет иметь подключение во внешнюю сеть по умолчанию (ext_net_id: 0). +``` + - name: Manage ViNS on resource group level + decort_vins: + account_name: "MyMainAccount" + rg_name: "MyRg01" + authenticator: jwt + jwt: "{{ my_jwt.jwt }}" + controller_url: "https://ds1.digitalenergy.online" + ext_net_id: 0 + vins_name: "MyVins01" + register: my_vins + ``` + +Здесь результат исполнения модуля decort_vins записывается в переменную my_vins, которую можно дальше использовать в Ansible playbooks. Ниже показано, как получить и использовать идентификатор внутренней сети для подключения к нему виртуального сервера. +``` +- name: Manage virtual machine + decort_kvmvm: + << для краткости фрагмент опущен >> + networks: + - type: VINS + id: "{{ my_vins.facts.id }}" + << для краткости фрагмент опущен >> +``` + +В данном примере идёт создание внутренней сети, а потом её привязка к внутренним сетям с id 864 и 196. +``` + - name: Manage ViNS on resource group level + decort_vins: + authenticator: jwt + jwt: "{{ my_jwt.jwt }}" + controller_url: "https://cloud.digitalenergy.online" + vins_name: "ViNS_connected_by_decort_vins_module" + rg_id: 98 + connect_to: + - id: 864 + ipaddr: 192.168.5.66 + netmask: 24 + - id: 196 + ipaddr: 192.168.9.133 + netmask: 24 + register: managed_vins + +``` + + diff --git a/wiki/Home.md b/wiki/Home.md new file mode 100644 index 0000000..e3ddd3a --- /dev/null +++ b/wiki/Home.md @@ -0,0 +1,4 @@ +## Документация: + +- [Модули Ansible версии 5.3.0](./5.3.0/Home.md) +- [Модули Ansible версии 5.2.6](./5.2.6/Home.md) \ No newline at end of file