diff --git a/README.md b/README.md index e7de5c5..4b6abf5 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,4 @@ Requirements: * PyJWT 2.0.0 Python module or higher * requests Python module * netaddr Python module -* DECORT cloud platform version 3.5.0 or higher +* DECORT cloud platform version 3.8.6 or higher diff --git a/library/decort_disk.py b/library/decort_disk.py index 5167d96..0df148b 100644 --- a/library/decort_disk.py +++ b/library/decort_disk.py @@ -252,161 +252,127 @@ from ansible.module_utils.basic import env_fallback from ansible.module_utils.decort_utils import * class decort_disk(DecortController): - def __init__(self,amodule): - super(decort_disk, self).__init__(amodule) - - self.validated_account_id = 0 - self.validated_disk_id = 0 - self.disk_facts = None # will hold Disk facts - self.acc_facts = None # will hold Account facts - + def __init__(self,arg_amodule): + super(decort_disk, self).__init__(arg_amodule) + + validated_acc_id = 0 + validated_acc_info = None + validated_disk_id = 0 + self.disk_id = 0 + self.account_id = 0 + validated_disk_facts = None # limitIO check for exclusive parameters - if amodule.params['limitIO']: - limit = amodule.params['limitIO'] - if limit['total_bytes_sec'] > 0 and limit['read_bytes_sec'] > 0 or \ - limit['total_bytes_sec'] > 0 and limit['write_bytes_sec'] > 0: - self.result['failed'] = True - self.result['msg'] = ("total and read/write of bytes_sec cannot be set at the same time.") - amodule.fail_json(**self.result) - elif limit['total_iops_sec'] > 0 and limit['read_iops_sec'] > 0 or \ - limit['total_iops_sec'] > 0 and limit['write_iops_sec'] > 0: - self.result['failed'] = True - self.result['msg'] = ("total and read/write of iops_sec cannot be set at the same time.") - amodule.fail_json(**self.result) - elif limit['total_bytes_sec_max'] > 0 and limit['read_bytes_sec_max'] > 0 or \ - limit['total_bytes_sec_max'] > 0 and limit['write_bytes_sec_max'] > 0: - self.result['failed'] = True - self.result['msg'] = ("total and read/write of bytes_sec_max cannot be set at the same time.") - amodule.fail_json(**self.result) - elif limit['total_iops_sec_max'] > 0 and limit['read_iops_sec_max'] > 0 or \ - limit['total_iops_sec_max'] > 0 and limit['write_iops_sec_max'] > 0: - self.result['failed'] = True - self.result['msg'] = ("total and read/write of iops_sec_max cannot be set at the same time.") - amodule.fail_json(**self.result) - - - if amodule.params['account_id']: - self.validated_account_id = amodule.params['account_id'] - elif amodule.params['account_name']: - self.validated_account_id, _ = self.account_find(amodule.params['account_name']) - elif not amodule.params['id'] and not amodule.params['account_name']: - self.result['failed'] = True - self.result['msg'] = ("Cannot found disk without account id or name.") - amodule.fail_json(**self.result) - - if self.validated_account_id == 0 and not amodule.params['id']: - # we failed either to find or access the specified account - fail the module - self.result['failed'] = True - self.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name']) - amodule.fail_json(**self.result) - - if amodule.params['id'] or amodule.params['name']: - self.validated_disk_id, self.disk_facts = self.decort_disk_find(amodule) - - else: - self.result['failed'] = True - self.result['msg'] = ("Cannot find or create disk without disk name or disk id") - amodule.fail_json(**self.result) - if amodule.params['place_with'] > 0: - image_id, image_facts = self.image_find(amodule.params['place_with'], "", 0) - amodule.params['sep_id']= image_facts['sepId'] - - + if arg_amodule.params['limitIO']: + self.disk_check_iotune_arg(arg_amodule.params['limitIO']) - def decort_disk_create(self, amodule): - if not self.disk_facts: - self.disk_id = self.disk_create(accountId=self.validated_account_id, gid=amodule.params['gid'], - name=amodule.params['name'], description=amodule.params['description'], - size=amodule.params['size'], type=amodule.params['type'], - iops=amodule.params['iops'], - sep_id=amodule.params['sep_id'], pool=amodule.params['pool']) - self.result['msg'] = ("Disk with id '{}' successfully created.").format(self.disk_id) - - elif self.disk_facts['status'] in ["DESTROYED", "PURGED"]: - if not amodule.params['limitIO']: - amodule.params['limitIO'] = self.disk_facts['iotune'] - if amodule.params['sep_id'] == 0: - validated_sep_id = self.disk_facts['sepId'] - else: - validated_sep_id = amodule.params['sep_id'] - - if amodule.params['pool'] == 0: - validated_pool = self.disk_facts['pool'] - else: - validated_pool = amodule.params['pool'] - - if amodule.params['size'] == 0: - validated_size = self.disk_facts['sizeMax'] - else: - validated_size = amodule.params['size'] - - if amodule.params['gid'] == 0: - validated_gid = self.disk_facts['gid'] + if arg_amodule.params['id'] or arg_amodule.params['name']: + if arg_amodule.params['account_id'] or arg_amodule.params['account_name'] : + validated_acc_id,validated_acc_info = self.account_find(arg_amodule.params['account_name'],arg_amodule.params['account_id']) + if not validated_acc_id: + self.result['failed'] = True + self.result['changed'] = False + 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) else: - validated_gid = amodule.params['gid'] + self.acc_id = validated_acc_id + self.acc_info = validated_acc_info + validated_disk_id,validated_disk_facts = self.disk_find( + disk_id=arg_amodule.params['id'], + name=arg_amodule.params['name'] if "name" in arg_amodule.params else "", + account_id=self.acc_id, + check_state=False, + ) + 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.disk_id = self.disk_create(accountId=self.validated_account_id, gid=validated_gid, - name=self.disk_facts['name'], description=amodule.params['description'], - size=validated_size, type=self.disk_facts['type'], - iops=self.disk_facts['iotune']['total_iops_sec'], - sep_id=validated_sep_id, pool=validated_pool) - if not amodule.params['limitIO']: - amodule.params['limitIO'] = self.disk_facts['iotune'] - self.result['msg'] = ("Disk with id '{}' successfully recreated.").format(self.disk_id) + if arg_amodule.params['place_with']: + image_id, image_facts = self.image_find(arg_amodule.params['place_with'], "", 0) + arg_amodule.params['sep_id'] = image_facts['sepId'] - self.result['failed'] = False - self.result['changed'] = True - return self.disk_id + self.disk_id = validated_disk_id + self.disk_info = validated_disk_facts - def decort_disk_delete(self, amodule): - self.disk_id = self.disk_delete(disk_id=self.validated_disk_id, - detach=amodule.params['force_detach'], - permanently=amodule.params['permanently'], - reason=amodule.params['reason']) + def create(self): + + self.disk_id = self.disk_create(accountId=self.acc_id, + name = self.amodule.params['name'], + description=self.amodule.params['annotation'], + size=self.amodule.params['size'], + type=self.amodule.params['type'], + iops=self.amodule.params['iops'], + sep_id=self.amodule.params['sep_id'], + pool=self.amodule.params['pool'], + ) + #IO tune + if self.amodule.params['limitIO']: + self.disk_limitIO(self.amodule.params['limitIO'],self.disk_id) + #set share status + if self.amodule.params['shareable'] and self.amodule.params['type'] == "D": + self.dick_share(self.disk_id,self.amodule.params['shareable']) return - - def decort_disk_find(self, amodule): - if amodule.params['name'] and not amodule.params['id']: - self.disk_id, self.disk_facts = self.disk_find(disk_id=self.validated_disk_id, - name=amodule.params['name'], - account_id=self.validated_account_id) - elif self.validated_disk_id > 0: - self.disk_id, self.disk_facts = self.disk_find(disk_id=self.validated_disk_id, - name=self.disk_facts['name'], - account_id=0) - elif amodule.params['id']: - self.disk_id, self.disk_facts = self.disk_find(disk_id=amodule.params['id'], - name=amodule.params['name'], - account_id=0) - - if not self.disk_id and not amodule.params['name']: - self.result['failed'] = True - self.result['msg'] = "Specified Disk ID {} not found.".format(amodule.params['id']) - amodule.fail_json(**self.result) - self.result['facts'] = decort_disk.decort_disk_package_facts(self.disk_facts) - return self.disk_id, self.disk_facts - - def decort_disk_limitIO(self, amodule): - self.limits = amodule.params['limitIO'] - - self.disk_limitIO(limits = self.limits, - diskId = self.validated_disk_id) - self.disk_facts['iotune'] = amodule.params['limitIO'] - self.result['facts'] = decort_disk.decort_disk_package_facts(self.disk_facts) + def action(self,restore=False): + + #restore never be done + if restore: + 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, + name=self.amodule.params['name']) + self.disk_info['name'] = self.amodule.params['name'] + #resize + if self.amodule.params['size'] != self.disk_info['sizeMax']: + self.disk_resize(self.disk_info,self.amodule.params['size']) + #IO TUNE + if self.amodule.params['limitIO']: + clean_io = [param for param in self.amodule.params['limitIO'] \ + if self.amodule.params['limitIO'][param] == None] + for key in clean_io: del self.amodule.params['limitIO'][key] + if self.amodule.params['limitIO'] != self.disk_info['iotune']: + self.disk_limitIO(self.disk_id,self.amodule.params['limitIO']) + #share check/update + #raise Exception(self.amodule.params['shareable']) + if self.amodule.params['shareable'] != self.disk_info['shareable'] and \ + self.amodule.params['type'] == "D": + self.disk_share(self.disk_id,self.amodule.params['shareable']) + return + + def delete(self): + self.disk_id = self.disk_delete(disk_id=self.disk_id, + detach=self.amodule.params['force_detach'], + permanently=self.amodule.params['permanently'], + reason=self.amodule.params['reason']) + self.disk_info['status'] = "DELETED" return - def decort_disk_rename(self, amodule): - self.disk_rename(diskId = self.validated_disk_id, - name = amodule.params['name']) - self.disk_facts['name'] = amodule.params['name'] - self.result['facts'] = decort_disk.decort_disk_package_facts(self.disk_facts) - self.result['msg'] = ("Disk with id '{}',successfully renamed to '{}'.").format(self.validated_disk_id, amodule.params['name']) + def rename(self): + + + self.disk_rename(diskId = self.disk_id, + name = self.amodule.params['name']) + self.disk_info['name'] = self.amodule.params['name'] return - def decort_disk_package_facts(disk_facts, check_mode=False): + def nop(self): + + self.result['failed'] = False + self.result['changed'] = False + if self.disk_id: + self.result['msg'] = ("No state change required for Disk ID {} because of its " + "current status '{}'.").format(self.disk_id, self.disk_info['status']) + else: + self.result['msg'] = ("No state change to '{}' can be done for " + "non-existent Disk.").format(self.amodule.params['state']) + return + + def package_facts(self, check_mode=False): ret_dict = dict(id=0, name="none", state="CHECK_MODE", @@ -418,29 +384,27 @@ class decort_disk(DecortController): gid=0 ) - if check_mode: - # in check mode return immediately with the default values - return ret_dict - - if disk_facts is None: - # if void facts provided - change state value to ABSENT and return - ret_dict['state'] = "ABSENT" + if check_mode or self.disk_info is None: return ret_dict - ret_dict['id'] = disk_facts['id'] - ret_dict['name'] = disk_facts['name'] - ret_dict['size'] = disk_facts['sizeMax'] - ret_dict['state'] = disk_facts['status'] - ret_dict['account_id'] = disk_facts['accountId'] - ret_dict['sep_id'] = disk_facts['sepId'] - ret_dict['pool'] = disk_facts['pool'] - ret_dict['attached_to'] = disk_facts['vmid'] - ret_dict['gid'] = disk_facts['gid'] - ret_dict['iotune'] = disk_facts['iotune'] + # remove io param with zero value + clean_io = [param for param in self.disk_info['iotune'] if self.disk_info['iotune'][param] == 0] + for key in clean_io: del self.disk_info['iotune'][key] + + ret_dict['id'] = self.disk_info['id'] + ret_dict['name'] = self.disk_info['name'] + ret_dict['size'] = self.disk_info['sizeMax'] + ret_dict['state'] = self.disk_info['status'] + ret_dict['account_id'] = self.disk_info['accountId'] + ret_dict['sep_id'] = self.disk_info['sepId'] + ret_dict['pool'] = self.disk_info['pool'] + ret_dict['attached_to'] = self.disk_info['vmid'] + ret_dict['gid'] = self.disk_info['gid'] + ret_dict['iotune'] = self.disk_info['iotune'] return ret_dict - - def decort_disk_parameters(): + @staticmethod + def build_parameters(): """Build and return a dictionary of parameters expected by decort_disk module in a form accepted by AnsibleModule utility class.""" @@ -476,32 +440,30 @@ class decort_disk(DecortController): place_with=dict(type='int', default=0), pool=dict(type='str', default=''), sep_id=dict(type='int', default=0), - gid=dict(type='int', default=0), size=dict(type='int', default=0), type=dict(type='str', required=False, default="D", choices=['B', 'D', 'T']), - iops=dict(type='int', default=2000), + iops=dict(type='int',required=False,default=2000), limitIO=dict(type='dict', options=dict( - total_bytes_sec=dict(default=0,type='int'), - read_bytes_sec=dict(default=0,type='int'), - write_bytes_sec=dict(default=0,type='int'), - total_iops_sec=dict(default=0,type='int'), - read_iops_sec=dict(default=0,type='int'), - write_iops_sec=dict(default=0,type='int'), - total_bytes_sec_max=dict(default=0,type='int'), - read_bytes_sec_max=dict(default=0,type='int'), - write_bytes_sec_max=dict(default=0,type='int'), - total_iops_sec_max=dict(default=0,type='int'), - read_iops_sec_max=dict(default=0,type='int'), - write_iops_sec_max=dict(default=0,type='int'), - size_iops_sec=dict(default=0,type='int'),)), + total_bytes_sec=dict(required=False,type='int'), + read_bytes_sec=dict(required=False,type='int'), + write_bytes_sec=dict(required=False,type='int'), + total_iops_sec=dict(required=False,type='int'), + read_iops_sec=dict(required=False,type='int'), + write_iops_sec=dict(required=False,type='int'), + total_bytes_sec_max=dict(required=False,type='int'), + read_bytes_sec_max=dict(required=False,type='int'), + write_bytes_sec_max=dict(required=False,type='int'), + total_iops_sec_max=dict(required=False,type='int'), + read_iops_sec_max=dict(required=False,type='int'), + write_iops_sec_max=dict(required=False,type='int'), + size_iops_sec=dict(required=False,type='int'),)), permanently=dict(type='bool', required=False, default=False), - reason=dict(type='int', required=False), - description=dict(type='str', required=False, - default="Disk created with Ansible Decort_disk module."), + shareable=dict(type='bool', required=False, default=False), + reason=dict(type='str', required=False,default='Managed by Ansible decort_disk'), state=dict(type='str', default='present', choices=['absent', 'present']), @@ -514,7 +476,7 @@ class decort_disk(DecortController): ) def main(): - module_parameters = decort_disk.decort_disk_parameters() + module_parameters = decort_disk.build_parameters() amodule = AnsibleModule(argument_spec=module_parameters, supports_check_mode=True, @@ -530,105 +492,51 @@ def main(): ) decon = decort_disk(amodule) - - if decon.validated_disk_id == 0 and amodule.params['state'] == 'present': - # if sep_id or place_with not specified, then exit with error - if amodule.params['sep_id'] == 0 and amodule.params['place_with'] == 0: - decon.result['msg'] = ("To create a disk, you must specify sep_id or place_with.")\ - .format(decon.validated_disk_id) - amodule.fail_json(**decon.result) - # if id cannot cannot be found and have a state 'present', then create a new disk - decon.validated_disk_id = decon.decort_disk_create(amodule) - _, decon.disk_facts = decon.decort_disk_find(amodule) - decon.result['changed'] = True - decon.result['msg'] = ("Disk with id '{}' successfully created.").format(decon.validated_disk_id) - - elif decon.validated_disk_id == 0 and amodule.params['state'] == 'absent' and amodule.params['name']: - # if disk with specified name cannot be found and have a state 'absent', then nothing to do, - # specified disk already deleted - decon.result['msg'] = ("Disk with name '{}' has already been deleted or your account does not have" - " access to it.")\ - .format(amodule.params['name']) - amodule.exit_json(**decon.result) - - elif decon.validated_disk_id == 0 and amodule.params['state'] == 'absent' and amodule.params['id']: - # if disk with specified id cannot be found and have a state 'absent', then nothing to do, - # specified disk already deleted - decon.result['msg'] = ("Disk with name '{}' has already been deleted or your account does not have" - " access to it.")\ - .format(decon.validated_disk_id) - amodule.exit_json(**decon.result) - - elif decon.disk_facts['status'] == "CREATED": - if amodule.params['state'] == 'present': - # if disk status in condition "CREATED" and state "present", nothing to do, - # specified disk already created - decon.result['msg'] = "Specified Disk ID {} already created.".format(decon.validated_disk_id) - + # + #Full range of Disk status is as follows: + # + # "ASSIGNED","MODELED", "CREATING","CREATED","DELETED", "DESTROYED","PURGED", + # + if decon.disk_id: + #disk exist + if decon.disk_info['status'] in ["MODELED", "CREATING"]: + decon.result['failed'] = True + decon.result['changed'] = False + decon.result['msg'] = ("No change can be done for existing Disk ID {} because of its current " + "status '{}'").format(decon.disk_id, decon.disk_info['status']) + # "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED" + elif decon.disk_info['status'] in ["ASSIGNED","CREATED"]: + if amodule.params['state'] == 'absent': + decon.delete() + elif amodule.params['state'] == 'present': + decon.action() + elif decon.disk_info['status'] in ["PURGED", "DESTROYED"]: + #re-provision disk + if amodule.params['state'] in ('present'): + decon.create() + else: + decon.nop() + elif decon.disk_info['status'] == "DELETED": + if amodule.params['state'] in ('present'): + decon.action(restore=True) + else: + decon.nop() + else: + # preexisting Disk was not found if amodule.params['state'] == 'absent': - # if disk status in condition "CREATED" and state "absent", delete the disk - decon.validated_disk_id = decon.decort_disk_delete(amodule) - decon.disk_facts['status'] = "DESTROYED" - decon.result['msg'] = ("Disk with id '{}' successfully deleted.").format(decon.disk_facts['id']) - decon.result['facts'] = decon.decort_disk_package_facts(decon.disk_facts) - amodule.exit_json(**decon.result) - - elif decon.disk_facts['status'] in ["MODELED", "CREATING" ]: - # if disk in status "MODELED" or "CREATING", - # then we cannot do anything, while disk in this status - decon.result['changed'] = False - decon.result['msg'] = ("Cannot do anything with disk id '{}',please wait until disk will be created.")\ - .format(decon.validated_disk_id) - amodule.fail_json(**decon.result) - - elif decon.disk_facts['status'] == "DELETED": - if amodule.params['state'] == 'present': - # if disk in "DELETED" status and "present" state, restore - decon.disk_restore(decon.validated_disk_id) - _, decon.disk_facts = decon.decort_disk_find(amodule) - decon.result['changed'] = True - decon.result['msg'] = ("Disk with id '{}',restored successfully.").format(decon.validated_disk_id) - - elif amodule.params['state'] == 'absent': - # if disk in "DELETED" status and "absent" state, nothing to do - decon.result['msg'] = "Specified Disk ID {} already destroyed.".format(decon.validated_disk_id) - amodule.exit_json(**decon.result) - - elif decon.disk_facts['status'] in ["DESTROYED", "PURGED"]: - if amodule.params['state'] == 'present': - decon.validated_disk_id = decon.decort_disk_create(amodule) - _, decon.disk_facts = decon.decort_disk_find(amodule) - - elif amodule.params['state'] == 'absent': - decon.result['msg'] = "Specified Disk ID {} already destroyed.".format(decon.validated_disk_id) - amodule.exit_json(**decon.result) - - if amodule.params['state'] == "present": - if decon.disk_facts['sizeMax'] != amodule.params['size']: - if decon.disk_facts['sizeMax'] > amodule.params['size'] and amodule.params['size'] != 0: - decon.result['failed'] = True - decon.result['msg'] = ("Disk id '{}', cannot reduce disk size.").format(decon.validated_disk_id) - amodule.fail_json(**decon.result) - elif decon.disk_facts['sizeMax'] < amodule.params['size']: - decon.disk_resize(disk_facts=decon.disk_facts, - new_size=amodule.params['size']) - decon.result['changed'] = True - decon.disk_facts['size'] = amodule.params['size'] - decon.result['msg'] = ("Disk with id '{}',resized successfully.").format(decon.validated_disk_id) - - if amodule.params['limitIO'] and amodule.params['limitIO'] != decon.disk_facts['iotune']: - decon.decort_disk_limitIO(amodule) - decon.result['changed'] = True - decon.result['msg'] = ("Disk with id '{}',limited successfully.").format(decon.validated_disk_id) + decon.nop() + else: + decon.create() - if amodule.params['name'] and amodule.params['id']: - if amodule.params['name'] != decon.disk_facts['name']: - decon.decort_disk_rename(amodule) - decon.result['changed'] = True - decon.result['msg'] = ("Disk with id '{}',renamed successfully from '{}' to '{}'.")\ - .format(decon.validated_disk_id, decon.disk_facts['name'], amodule.params['name']) - - amodule.exit_json(**decon.result) + if decon.result['failed']: + amodule.fail_json(**decon.result) + else: + if decon.result['changed'] and amodule.params['state'] in ('present'): + _, decon.disk_info = decon.disk_find(decon.disk_id) + decon.result['facts'] = decon.package_facts(amodule.check_mode) + amodule.exit_json(**decon.result) if __name__ == "__main__": main() + +#SHARE \ No newline at end of file diff --git a/library/decort_k8s.py b/library/decort_k8s.py index 11291ac..b15e39c 100644 --- a/library/decort_k8s.py +++ b/library/decort_k8s.py @@ -1,19 +1,68 @@ #!/usr/bin/python # # Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible -# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC +# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC # # Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) # -# -# Author: Aleksandr Malyavin (aleksandr.malyavin@digitalenergy.online) -# ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} +DOCUMENTATION = ''' +--- +--- +''' +EXAMPLES = ''' +- name: Create k8s cluster + decort_k8s: + verify_ssl: false + authenticator: jwt + jwt: "{{ run_jwt.jwt }}" + controller_url: "{{CONTROLLER_URL}}" + name: SOME_NAME + rg_id: {{RG_ID}} + k8ci_id: 10 + master_count: 3 + master_cpu: 2 + master_ram: 2048 + master_disk: 10 + state: present + permanent: True + started: True + getConfig: True + network_plugin: flannel + workers: + - name: wg1 + ram: 1024 + cpu: 2 + disk: 10 + num: 1 + labels: + - disktype1=ssd1 + - disktype2=ssd2 + - disktype3=ssd3 + taints: + - key1=value1:NoSchedule + - key2=value2:NoSchedule + - key3=value3:NoSchedule + annotations: + - node.deckhouse.io/group1=g1 + - node.deckhouse.io/group2=g2 + - node.deckhouse.io/group3=g3 + - name: wg2 + ram: 1024 + cpu: 2 + disk: 10 + num: 1 + labels: + - apptype=main + annotations: + - node.mainapp.domen.local/group1=g1 + register: some_cluster +''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import env_fallback from ansible.module_utils.decort_utils import * @@ -21,11 +70,17 @@ from ansible.module_utils.decort_utils import * class decort_k8s(DecortController): def __init__(self,arg_amodule): super(decort_k8s, self).__init__(arg_amodule) - + validated_acc_id = 0 validated_rg_id = 0 validated_rg_facts = None validated_k8ci_id = 0 + self.k8s_should_exist = False + if not arg_amodule.params['workers']: + self.result['failed'] = True + self.result['changed'] = False + self.result['msg'] = "At least one worker group must be present" + self.fail_json(**self.result) if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0: self.result['failed'] = True @@ -108,9 +163,11 @@ class decort_k8s(DecortController): ret_dict['techStatus'] = self.k8s_info['techStatus'] ret_dict['state'] = self.k8s_info['status'] ret_dict['rg_id'] = self.rg_id + ret_dict['vins_id'] = self.k8s_vins_id ret_dict['account_id'] = self.acc_id if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED": ret_dict['config'] = self.k8s_getConfig() + return ret_dict def nop(self): @@ -149,10 +206,11 @@ class decort_k8s(DecortController): self.k8s_provision(self.amodule.params['name'], self.amodule.params['k8ci_id'], self.amodule.params['rg_id'], + self.amodule.params['network_plugin'], self.amodule.params['master_count'], self.amodule.params['master_cpu'], - self.amodule.params['master_ram_mb'], - self.amodule.params['master_disk_gb'], + self.amodule.params['master_ram'], + self.amodule.params['master_disk'], self.amodule.params['workers'][0], self.amodule.params['extnet_id'], self.amodule.params['with_lb'], @@ -165,12 +223,12 @@ class decort_k8s(DecortController): if self.k8s_id: self.k8s_should_exist = True - if self.k8s_id and self.amodule.params['workers'][1]: + if self.k8s_id and len(self.amodule.params['workers'])>1 : self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers']) return def destroy(self): - self.k8s_delete(self.k8s_id) + self.k8s_delete(self.k8s_id,self.amodule.params['permanent']) self.k8s_info['status'] = 'DELETED' self.k8s_should_exist = False return @@ -232,16 +290,17 @@ class decort_k8s(DecortController): rg_id=dict(type='int', default=0), rg_name=dict(type='str',default=""), k8ci_id=dict(type='int', required=True), + network_plugin=dict(type='str',required=False,default="flannel"), wg_name=dict(type='str', required=False), master_count=dict(type='int', default=1), master_cpu=dict(type='int', default=2), - master_ram_mb=dict(type='int', default=2048), - master_disk_gb=dict(type='int', default=10), + master_ram=dict(type='int', default=2048), + master_disk=dict(type='int', default=10), worker_count=dict(type='int', default=1), worker_cpu=dict(type='int', default=1), worker_ram_mb=dict(type='int', default=1024), worker_disk_gb=dict(type='int', default=10), - workers=dict(type='list'), + workers=dict(type='list',required=True), extnet_id=dict(type='int', default=0), description=dict(type='str', default="Created by decort ansible module"), with_lb=dict(type='bool', default=True), diff --git a/library/decort_kvmvm.py b/library/decort_kvmvm.py index 2be1ddd..fddb266 100644 --- a/library/decort_kvmvm.py +++ b/library/decort_kvmvm.py @@ -1,15 +1,11 @@ #!/usr/bin/python # # Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible -# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC +# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC # # Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) # -# -# Author: Sergey Shubin (sergey.shubin@digitalenergy.online) -# - ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} @@ -24,15 +20,14 @@ description: > network port forwarding rules, restart guest OS and delete a virtual machine thus releasing corresponding cloud resources. version_added: "2.2" -author: - - Sergey Shubin + requirements: - - python >= 2.6 + - python >= 3.8 - PyJWT Python module - requests Python module - netaddr Python module - decort_utils utility library (module) - - DECORT cloud platform version 3.6.1 or higher + - DECORT cloud platform version 3.8.6 or higher notes: - Environment variables can be used to pass selected parameters to the module, see details below. - Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used. @@ -231,7 +226,7 @@ options: choices: [ present, absent, poweredon, poweredoff, halted, paused, check ] tags: description: - - String of custom tags to be assigned to the VM (This feature is not implemented yet!). + - Dict of custom tags to be assigned to the VM. - These tags are arbitrary text that can be used for grouping or indexing the VMs by other applications. required: no user: @@ -287,24 +282,14 @@ EXAMPLES = ''' name: SimpleVM cpu: 2 ram: 4096 - boot_disk: - size: 10 - model: ovs - pool: boot + boot_disk: 10 image_name: "Ubuntu 16.04 v1.1" data_disks: - - size: 50 - model: ovs - pool: data - port_forwards: - - ext_port: 21022 - int_port: 22 - proto: tcp - - ext_port: 80 - int_port: 80 - proto: tcp + - {{DISK_ID}} state: present - tags: "PROJECT:Ansible STATUS:Test" + tags: + PROJECT:Ansible + STATUS:Test account_name: "Development" rg_name: "ANewVDC" delegate_to: localhost @@ -337,18 +322,6 @@ EXAMPLES = ''' state: poweredoff delegate_to: localhost register: simple_vm -- name: check if VM exists and read in its specs. - decort_kvmvm: - authenticator: oauth2 - app_id: "{{ MY_APP_ID }}" - app_secret: "{{ MY_APP_SECRET }}" - controller_url: "https://ds1.digitalenergy.online" - name: "{{ TARGET_VM_NAME }}" - rg_name: "{{ TARGET_VDC_NAME }}" - account_name: "{{ TRAGET_TENANT }}" - state: check - delegate_to: localhost - register: existing_vm ''' RETURN = ''' @@ -573,6 +546,8 @@ class decort_kvmvm(DecortController): image_id=image_facts['id'], annotation=self.amodule.params['annotation'], userdata=cloud_init_params, + sep_id=self.amodule.params['sep_id' ] if "sep_id" in self.amodule.params else None, + pool_name=self.amodule.params['pool'] if "pool" in self.amodule.params else None, start_on_create=start_compute) self.comp_should_exist = True @@ -652,11 +627,13 @@ class decort_kvmvm(DecortController): self.compute_resize(self.comp_info, self.amodule.params['cpu'], self.amodule.params['ram'], wait_for_state_change=arg_wait_cycles) + self.compute_affinity(self.comp_info, self.amodule.params['tag'], self.amodule.params['aff_rule'], self.amodule.params['aaff_rule'], label=self.amodule.params['affinity_label'],) + return def package_facts(self, check_mode=False): @@ -686,6 +663,7 @@ class decort_kvmvm(DecortController): public_ips=[], # direct IPs; this list can be empty private_ips=[], # IPs on ViNSes; usually, at least one IP is listed nat_ip="", # IP of the external ViNS interface; can be empty. + tags={}, ) if check_mode or self.comp_info is None: @@ -703,6 +681,8 @@ class decort_kvmvm(DecortController): ret_dict['tech_status'] = self.comp_info['techStatus'] ret_dict['account_id'] = self.comp_info['accountId'] ret_dict['rg_id'] = self.comp_info['rgId'] + if self.comp_info['tags']: + ret_dict['tags'] = self.comp_info['tags'] # if the VM is an imported VM, then the 'accounts' list may be empty, # so check for this case before trying to access login and passowrd values if len(self.comp_info['osUsers']): @@ -764,6 +744,8 @@ class decort_kvmvm(DecortController): required=True, choices=['legacy', 'oauth2', 'jwt']), boot_disk=dict(type='int', required=False), + sep_id=dict(type='int', required=False), + pool=dict(type='str', required=False), controller_url=dict(type='str', required=True), # count=dict(type='int', required=False, default=1), cpu=dict(type='int', required=False), @@ -790,7 +772,7 @@ class decort_kvmvm(DecortController): rg_name=dict(type='str', default=""), ssh_key=dict(type='str', required=False), ssh_key_user=dict(type='str', required=False), - tag=dict(type='list', required=False), + tag=dict(type='dict', required=False), affinity_label=dict(type='str', required=False), aff_rule=dict(type='list', required=False), aaff_rule=dict(type='list', required=False), diff --git a/library/decort_lb.py b/library/decort_lb.py index 3296e79..61c35aa 100644 --- a/library/decort_lb.py +++ b/library/decort_lb.py @@ -1,7 +1,7 @@ #!/usr/bin/python # # Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible -# Copyright: (c) 2018-2022 Digital Energy Cloud Solutions LLC +# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC # # Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) # diff --git a/library/decort_rg.py b/library/decort_rg.py index 87e5bf1..0ebce2f 100644 --- a/library/decort_rg.py +++ b/library/decort_rg.py @@ -388,6 +388,8 @@ class decort_rg(DecortController): ret_dict['resTypes'] = self.rg_facts['resourceTypes'] ret_dict['defNetId'] = self.rg_facts['def_net_id'] ret_dict['defNetType'] = self.rg_facts['def_net_type'] + ret_dict['ViNS'] = self.rg_facts['vins'] + ret_dict['computes'] = self.rg_facts['vms'] return ret_dict diff --git a/library/decort_vins.py b/library/decort_vins.py index a50faa1..65629b2 100644 --- a/library/decort_vins.py +++ b/library/decort_vins.py @@ -6,9 +6,6 @@ # Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) # -# -# Author: Sergey Shubin (sergey.shubin@digitalenergy.online) -# ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], @@ -23,14 +20,13 @@ description: > modify its characteristics, and delete it. version_added: "2.2" author: - - Sergey Shubin requirements: - - python >= 2.6 + - python >= 3.8 - PyJWT Python module - requests Python module - netaddr Python module - decort_utils utility library (module) - - DECORT cloud platform version 3.6.1 or higher + - DECORT cloud platform version 3.8.6 or higher notes: - Environment variables can be used to pass selected parameters to the module, see details below. - Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used. @@ -260,14 +256,15 @@ class decort_vins(DecortController): if arg_amodule.params['vins_id']: # expect existing ViNS with the specified ID # This call to vins_find will abort the module if no ViNS with such ID is present - self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id']) - if not self.vins_id: + self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id'],check_state=False) + if self.vins_id == 0: self.result['failed'] = True self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id']) self.fail_json(**self.result) self.vins_level = "ID" - validated_acc_id = vins_facts['accountId'] - validated_rg_id = vins_facts['rgId'] + #raise Exception(self.vins_facts) + validated_acc_id = self.vins_facts['accountId'] + validated_rg_id = self.vins_facts['rgId'] elif arg_amodule.params['rg_id']: # expect ViNS @ RG level in the RG with specified ID @@ -291,7 +288,7 @@ class decort_vins(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 arg_amodule.params['rg_name'] != "": # at this point we know that rg_id=0 # expect ViNS @ RG level in the RG with specified name under specified account # RG with the specified name must be present under the account, otherwise abort the module @@ -300,7 +297,7 @@ class decort_vins(DecortController): rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]): self.result['failed'] = True self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name']) - self.fail_json(**self.result) + self.amodule.fail_json(**self.result) # This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'], account_id=0, # set to 0, as we are looking for ViNS under RG @@ -323,12 +320,13 @@ class decort_vins(DecortController): # this is "invalid arguments combination" sink # if we end up here, it means that module was invoked with vins_id=0 and rg_id=0 self.result['failed'] = True - if arg_amodule.params['account_id'] == 0 and arg_amodule.params['account_name'] == "": + self.result['msg'] = "Cannot find ViNS by name" + if arg_amodule.params['account_id'] == 0 and arg_amodule.params['account_name'] == '': self.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0." if arg_amodule.params['rg_name'] == "": # rg_name without account specified self.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0." - self.fail_json(**self.result) + self.amodule.fail_json(**self.result) return self.rg_id = validated_rg_id @@ -465,11 +463,6 @@ class decort_vins(DecortController): ret_dict['ext_ip_addr'] = "" ret_dict['ext_net_id'] = -1 - # arg_vins_facts['vnfs']['GW']['config'] - # ext_ip_addr -> ext_net_ip - # ??? -> ext_net_id - # tech_status -> techStatus - return ret_dict @staticmethod @@ -478,7 +471,7 @@ class decort_vins(DecortController): by AnsibleModule utility class.""" return dict( - account_id=dict(type='int', required=False), + account_id=dict(type='int', required=False,default=0), account_name=dict(type='str', required=False, default=''), annotation=dict(type='str', required=False, default=''), app_id=dict(type='str', @@ -521,7 +514,7 @@ class decort_vins(DecortController): rg_name=dict(type='str', required=False, default=''), verify_ssl=dict(type='bool', required=False, default=True), vins_id=dict(type='int', required=False, default=0), - vins_name=dict(type='str', required=True), + vins_name=dict(type='str', required=False,default=""), workflow_callback=dict(type='str', required=False), workflow_context=dict(type='str', required=False), ) @@ -548,6 +541,9 @@ def main(): ['app_id', 'app_secret'], ['user', 'password'], ], + required_one_of=[ + ['vins_id', 'vins_name'], + ], ) decon = decort_vins(amodule) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 044a281..334ed83 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -1,13 +1,10 @@ # # Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible -# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC +# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC # # Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) # -# -# Author: Sergey Shubin (sergey.shubin@digitalenergy.online) -# """ This is the library of utility functions and classes for managing DECORT cloud platform. @@ -28,7 +25,7 @@ Requirements: - PyJWT Python module - requests Python module - netaddr Python module -- DECORT cloud platform version 3.6.1 or higher +- DECORT cloud platform version 3.8.6 or higher """ import json @@ -39,17 +36,6 @@ import requests from ansible.module_utils.basic import AnsibleModule - -# -# TODO: the following functionality to be implemented and/or tested -# 4) workflow callbacks -# 5) run phase states -# 6) vm_tags - set/manage VM tags -# 7) vm_attributes - change VM attributes (name, annotation) after VM creation - do we need this in Ansible? -# 9) test vm_restore() method and execution plans that involve vm_restore() -# - - class DecortController(object): """DecortController is a utility class that holds target controller context and handles API requests formatting based on the requested authentication type. @@ -761,9 +747,12 @@ class DecortController(object): def kvmvm_provision(self, rg_id, comp_name, arch, cpu, ram, - boot_disk, image_id, + boot_disk, + image_id, annotation="", userdata=None, + sep_id=None, + pool_name=None, start_on_create=True): """Manage KVM VM provisioning. To remove existing KVM VM compute instance use compute_remove method, to resize use compute_resize, to manage power state use compute_powerstate method. @@ -808,6 +797,8 @@ class DecortController(object): cpu=cpu, ram=ram, imageId=image_id, bootDisk=boot_disk, + sepId=sep_id, + pool=pool_name, start=start_on_create, # start_machine parameter requires DECORT API ver 3.3.1 or higher netType="NONE") # we create VM without any network connections if userdata: @@ -1189,46 +1180,118 @@ class DecortController(object): return False def compute_affinity(self,comp_dict,tags,aff,aaff,label=""): - + """ + Manage Compute Tags,Affinitylabel and rules + @param (dict) comp_dict: dictionary of the Compute parameters + @param (dict) tags: dictionary of the tags + @param (list) aff: affinity rules + @param (list) aaff: antiaffinity rules + @param (str) label: affinity group label + """ self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "compute_affinity") - - api_params = dict(computeId=comp_dict['id']) - self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRulesClear", api_params) - self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRulesClear", api_params) - if tags: - for tag in tags: + + for tag in tags.items(): + if tag not in comp_dict['tags'].items(): api_params = dict(computeId=comp_dict['id'], - key=tag['key'], - value=tag['value'], ) + key=tag[0], + value=tag[1], ) self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/tagAdd", api_params) - if label: + self.result['failed'] = False + self.result['changed'] = True + + for tag in comp_dict['tags'].items(): + if tag not in tags.items(): + api_params = dict(computeId=comp_dict['id'], + key=tag[0],) + self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/tagRemove", api_params) + self.result['failed'] = False + self.result['changed'] = True + + if label and comp_dict['affinityLabel'] != label: api_params = dict(computeId=comp_dict['id'], affinityLabel=label,) self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityLabelSet", api_params) - if aff: - if len(aff)>0: - for rule in aff: - api_params = dict(computeId=comp_dict['id'], - key=rule['key'], - value=rule['value'], - topology=rule['topology'], - mode=rule['mode'], - policy=rule['policy'],) - self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRuleAdd", api_params) - if aaff: - if len(aaff)>0: - for rule in aaff: - api_params = dict(computeId=comp_dict['id'], - key=rule['key'], - value=rule['value'], - topology=rule['topology'], - mode=rule['mode'], - policy=rule['policy'],) - self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRuleAdd", api_params) + self.result['failed'] = False + self.result['changed'] = True + elif label == "" and comp_dict['affinityLabel']: + api_params = dict(computeId=comp_dict['id']) + self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityLabelRemove", api_params) + self.result['failed'] = False + self.result['changed'] = True - self.result['failed'] = False - self.result['changed'] = True - + affrule_del = [] + affrule_add = [] + aaffrule_del = [] + aaffrule_add = [] + + #AFFINITY + for rule in comp_dict['affinityRules']: + del rule['guid'] + if rule not in aff: + affrule_del.append(rule) + + for rule in aff: + if rule not in comp_dict['affinityRules']: + affrule_add.append(rule) + + #ANTI AFFINITY + for rule in comp_dict['antiAffinityRules']: + del rule['guid'] + if rule not in aaff: + aaffrule_del.append(rule) + + for rule in aaff: + if rule not in comp_dict['antiAffinityRules']: + aaffrule_add.append(rule) + + #AFFINITY + if len (affrule_del): + for rule in affrule_del: + api_params = dict(computeId=comp_dict['id'], + key=rule['key'], + value=rule['value'], + topology=rule['topology'], + mode=rule['mode'], + policy=rule['policy'],) + self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRuleRemove", api_params) + self.result['failed'] = False + self.result['changed'] = True + if len(affrule_add)>0: + for rule in affrule_add: + api_params = dict(computeId=comp_dict['id'], + key=rule['key'], + value=rule['value'], + topology=rule['topology'], + mode=rule['mode'], + policy=rule['policy'],) + self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRuleAdd", api_params) + self.result['failed'] = False + self.result['changed'] = True + #ANTI AFFINITY + if len(aaffrule_del): + for rule in aaffrule_del: + api_params = dict(computeId=comp_dict['id'], + key=rule['key'], + value=rule['value'], + topology=rule['topology'], + mode=rule['mode'], + policy=rule['policy'],) + self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRuleRemove", api_params) + self.result['failed'] = False + self.result['changed'] = True + + if len(aaffrule_add)>0: + for rule in aaffrule_add: + api_params = dict(computeId=comp_dict['id'], + key=rule['key'], + value=rule['value'], + topology=rule['topology'], + mode=rule['mode'], + policy=rule['policy'],) + self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRuleAdd", api_params) + self.result['failed'] = False + self.result['changed'] = True + return ################################### # OS image manipulation methods ################################### @@ -1571,15 +1634,18 @@ class DecortController(object): self.result['msg'] = "rg_find(): cannot find RG by name if account ID is zero or less." 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_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/account/listRG", api_params) + #api_params['accountId'] = arg_account_id + api_params['includedeleted'] = False + #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: account_specs = json.loads(api_resp.content.decode('utf8')) - api_params.pop('accountId') + #api_params.pop('accountId') for rg_item in account_specs: - got_id, got_specs = self._rg_get_by_id(rg_item['id']) - if got_id and got_specs['name'] == arg_rg_name: + # + if rg_item['name'] == arg_rg_name: # name matches + got_id, got_specs = self._rg_get_by_id(rg_item['id']) if not arg_check_state or got_specs['status'] not in RG_INVALID_STATES: ret_rg_id = got_id ret_rg_dict = got_specs @@ -2595,7 +2661,54 @@ class DecortController(object): # Disk management # ############################## - + def disk_check_iotune_arg(self,iotune_list): + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_check_iotune_arg") + MIN_IOPS = 80 + total_bytes_sec=iotune_list['total_bytes_sec'] + read_bytes_sec=iotune_list['read_bytes_sec'] + write_bytes_sec=iotune_list['write_bytes_sec'] + total_iops_sec=iotune_list['total_iops_sec'] + read_iops_sec=iotune_list['read_iops_sec'] + write_iops_sec=iotune_list['write_iops_sec'] + total_bytes_sec_max=iotune_list['total_bytes_sec_max'] + read_bytes_sec_max=iotune_list['read_bytes_sec_max'] + write_bytes_sec_max=iotune_list['write_bytes_sec_max'] + total_iops_sec_max=iotune_list['total_iops_sec_max'] + read_iops_sec_max=iotune_list['read_iops_sec_max'] + write_iops_sec_max=iotune_list['write_iops_sec_max'] + size_iops_sec=iotune_list['size_iops_sec'] + + if total_iops_sec and (read_iops_sec or write_iops_sec): + self.result['failed'] = True + self.result['changed'] = False + self.result['msg'] = (f"total and read/write of iops_sec cannot be set at the same time") + if total_bytes_sec and (read_bytes_sec or write_bytes_sec): + self.result['failed'] = True + self.result['changed'] = False + self.result['msg'] = (f"total and read/write of bytes_sec cannot be set at the same time") + if total_bytes_sec_max and (read_bytes_sec_max or write_bytes_sec_max): + self.result['failed'] = True + self.result['changed'] = False + self.result['msg'] =(f"total and read/write of bytes_sec_max cannot be set at the same time") + if total_iops_sec_max and (read_iops_sec_max or write_iops_sec_max): + self.result['failed'] = True + self.result['changed'] = False + self.result['msg'] =(f"total and read/write of iops_sec_max cannot be set at the same time") + + for arg, val in iotune_list.items(): + if arg in ( + "total_iops_sec", + "read_iops_sec", + "write_iops_sec", + "total_iops_sec_max", + "read_iops_sec_max", + "write_iops_sec_max", + "size_iops_sec", + ): + if val and val < self.MIN_IOPS: + self.result['msg'] = (f"{arg} was set below the minimum iops {MIN_IOPS}: {val} provided") + return + def disk_delete(self, disk_id, permanently, detach, reason): """Deletes specified Disk. @@ -2654,7 +2767,7 @@ class DecortController(object): return ret_disk_id, ret_disk_dict - def disk_find(self, disk_id, name, account_id, check_state=False): + def disk_find(self, disk_id=0, name="", account_id=0, check_state=False): """Find specified Disk. @param (int) disk_id: ID of the Disk. If non-zero disk_id is specified, all other arguments @@ -2678,13 +2791,13 @@ class DecortController(object): 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, - disk_name) + name) return ret_disk_id = 0 ret_disk_facts = None - if disk_id > 0: + if disk_id: ret_disk_id, ret_disk_facts = self._disk_get_by_id(disk_id) if not ret_disk_id: self.result['failed'] = True @@ -2694,19 +2807,20 @@ class DecortController(object): return ret_disk_id, ret_disk_facts else: return 0, None - elif name != "": - if account_id > 0: - api_params = dict(accountId=account_id) - api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/list", api_params) + elif name: + if account_id: + api_params = dict(accountId=account_id,name=name) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/search", api_params) # the above call may return more than one matching disk disks_list = json.loads(api_resp.content.decode('utf8')) - for runner in disks_list: - # return the first disk of the specified name that fulfills status matching rule - if runner['name'] == name: - if not check_state or runner['status']: - return runner['id'], runner - else: + if len(disks_list) == 0: return 0, None + elif len(disks_list) > 1: + self.result['failed'] = True + self.result['msg'] = "disk_find(): Found more then one Disk with Name: {}.".format(name) + self.amodule.fail_json(**self.result) + else: + return disks_list[0]['id'], disks_list[0] else: # we are missing meaningful account_id - fail the module self.result['failed'] = True self.result['msg'] = ("disk_find(): cannot find Disk by name '{}' " @@ -2719,7 +2833,7 @@ class DecortController(object): return 0, None - def disk_create(self, accountId, gid, name, description, size, type, iops, sep_id, pool): + def disk_create(self, accountId, name, description, size, type, iops, sep_id, pool): """Provision Disk according to the specified arguments. Note that disks created by this method will be of type 'D' (data disks). If critical error occurs the embedded call to API function will abort further execution @@ -2739,13 +2853,13 @@ class DecortController(object): self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_creation") api_params = dict(accountId=accountId, - gid=gid, + gid=0, # depricated name=name, description=description, size=size, type=type, iops=iops, - sepId=sep_id, + sep_id=sep_id, pool=pool ) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/create", api_params) if api_resp.status_code == 200: @@ -2807,32 +2921,21 @@ class DecortController(object): return - def disk_limitIO(self, limits, diskId): + def disk_limitIO(self,disk_id, limits): """Limits already created Disk identified by its ID. @param (dict) limits: Dictionary with limits. @param (int) diskId: ID of the Disk to limit. @returns: nothing on success. On error this method will abort module execution. """ self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_limitIO") - api_params = dict(diskId=diskId, - total_bytes_sec=limits['total_bytes_sec'], - read_bytes_sec=limits['read_bytes_sec'], - write_bytes_sec=limits['write_bytes_sec'], - total_iops_sec=limits['total_iops_sec'], - read_iops_sec=limits['read_iops_sec'], - write_iops_sec=limits['write_iops_sec'], - total_bytes_sec_max=limits['total_bytes_sec_max'], - read_bytes_sec_max=limits['read_bytes_sec_max'], - write_bytes_sec_max=limits['write_bytes_sec_max'], - total_iops_sec_max=limits['total_iops_sec_max'], - read_iops_sec_max=limits['read_iops_sec_max'], - write_iops_sec_max=limits['write_iops_sec_max'], - size_iops_sec=limits['size_iops_sec']) + api_params = dict(diskId=disk_id, + **limits) self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/limitIO", api_params) - self.result['msg'] = "Specified Disk ID {} limited successfully.".format(self.validated_disk_id) + self.result['changed'] = True + self.result['msg'] = "Specified Disk ID {} limited successfully.".format(disk_id) return - def disk_rename(self, diskId, name): + def disk_rename(self, disk_id, name): """Renames disk to the specified new name. @param disk_id: ID of the Disk to rename. @@ -2841,12 +2944,13 @@ class DecortController(object): @returns: nothing on success. On error this method will abort module execution. """ self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_rename") - api_params = dict(diskId=diskId, + api_params = dict(diskId=disk_id, name=name) self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/rename", api_params) # On success the above call will return here. On error it will abort execution by calling fail_json. self.result['failed'] = False self.result['changed'] = True + self.result['msg'] = ("Disk with id '{}',successfully renamed to '{}'.").format(disk_id, name) return def disk_restore(self, disk_id): @@ -2872,6 +2976,30 @@ class DecortController(object): self.result['failed'] = False self.result['changed'] = True return + def disk_share(self, disk_id, share='false'): + """Share data disk + + @param disk_id: ID of the Disk to share. + + @param share: share status of the disk + + @returns: nothing on success. On error this method will abort module execution. + """ + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_share") + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = "disk_share() in check mode: share Disk ID {} was requested.".format(disk_id) + return + + api_params = dict(diskId=disk_id) + if share: + self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/share", api_params) + else: + self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/unshare", api_params) + # On success the above call will return here. On error it will abort execution by calling fail_json. + self.result['failed'] = False + self.result['changed'] = True + return ############################## # @@ -3140,6 +3268,10 @@ class DecortController(object): for k8s_item in k8s_list: if k8s_item['name'] == k8s_name and k8s_item['rgId'] == rg_id: if not check_state or k8s_item['status'] not in K8S_INVALID_STATES: + # TODO: rework after k8s/get wilb be updated + self.k8s_vins_id = None + self.k8s_vins_id = k8s_item['vinsId'] + # ret_k8s_id = k8s_item['id'] _, ret_k8s_dict = self._k8s_get_by_id(ret_k8s_id) @@ -3264,7 +3396,7 @@ class DecortController(object): return def k8s_provision(self, k8s_name, - k8ci_id,rg_id, master_count, + k8ci_id,rg_id,plugin,master_count, master_cpu, master_ram, master_disk, default_worker, extnet_id, with_lb, annotation, ): @@ -3290,6 +3422,7 @@ class DecortController(object): rgId=rg_id, k8ciId=k8ci_id, workerGroupName=def_wg_name, + networkPlugin=plugin, masterNum=master_count, masterCpu=master_cpu, masterRam=master_ram, @@ -3326,6 +3459,7 @@ class DecortController(object): return elif ret_info['status'] == "OK": k8s_id = ret_info['result'] + self.result['msg'] = f"k8s_provision(): K8s cluster {k8s_name} created successful" self.result['changed'] = True return k8s_id else: @@ -3339,6 +3473,9 @@ class DecortController(object): else: self.result['msg'] = ("k8s_provision(): Can't create cluster") self.result['failed'] = True + + self.result['changed'] = False + self.fail_json(**self.result) return def k8s_workers_modify(self,arg_k8swg,arg_modwg):