#!/usr/bin/python DOCUMENTATION = r''' --- module: decort_disk description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). ''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import env_fallback from ansible.module_utils.decort_utils import * class decort_disk(DecortController): def __init__(self): super(decort_disk, self).__init__(AnsibleModule(**self.amodule_init_args)) arg_amodule = self.amodule validated_acc_id = 0 validated_acc_info = None self.disk_id = 0 self.account_id = 0 # limitIO check for exclusive parameters if arg_amodule.params['limitIO']: self.disk_check_iotune_arg(arg_amodule.params['limitIO']) if not arg_amodule.params['id']: if ( not arg_amodule.params['account_id'] and not arg_amodule.params['account_name'] ): self.result['changed'] = False self.result['msg'] = ( 'Cannot manage Disk by name without specifying account ID ' 'or name.' ) self.amodule.fail_json(**self.result) validated_acc_id, validated_acc_info = self.account_find( arg_amodule.params['account_name'], arg_amodule.params['account_id']) self.acc_id = validated_acc_id self._acc_info = validated_acc_info if not validated_acc_id: self.result['changed'] = False self.result['msg'] = ( f"Current user does not have access to the account " f"ID {arg_amodule.params['account_id']} / " f"name '{arg_amodule.params['account_name']}' " f"or non-existent account specified." ) self.amodule.fail_json(**self.result) 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, ) 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.disk_id = validated_disk_id self.disk_info = validated_disk_facts if self.disk_id: self.acc_id = validated_disk_facts['accountId'] self.check_amodule_args_for_change() else: self.check_amodule_args_for_create() def compare_iotune_params(self, new_iotune: dict, current_iotune: dict): io_fields = sdk_types.IOTuneAPIResultNM.model_fields.keys() for field in io_fields: new_value = new_iotune.get(field) current_value = current_iotune.get(field) if new_value != current_value: return False return True def limit_io(self, aparam_limit_io: dict): self.sdk_checkmode(self.api.cloudapi.disks.limit_io)( disk_id=self.disk_id, read_bytes_sec_max=(aparam_limit_io.get('read_bytes_sec_max')), read_bytes_sec=(aparam_limit_io.get('read_bytes_sec')), read_iops_sec_max=(aparam_limit_io.get('read_iops_sec_max')), read_iops_sec=(aparam_limit_io.get('read_iops_sec')), size_iops_sec=(aparam_limit_io.get('size_iops_sec')), total_bytes_sec_max=(aparam_limit_io.get('total_bytes_sec_max')), total_bytes_sec=(aparam_limit_io.get('total_bytes_sec')), total_iops_sec_max=(aparam_limit_io.get('total_iops_sec_max')), total_iops_sec=(aparam_limit_io.get('total_iops_sec')), write_bytes_sec_max=(aparam_limit_io.get('write_bytes_sec_max')), write_bytes_sec=(aparam_limit_io.get('write_bytes_sec')), write_iops_sec_max=(aparam_limit_io.get('write_iops_sec_max')), write_iops_sec=(aparam_limit_io.get('write_iops_sec')), ) def create(self): self.disk_id = self.sdk_checkmode(self.api.cloudapi.disks.create)( account_id=self.acc_id, name=self.amodule.params['name'], size_gb=self.amodule.params['size'], storage_policy_id=self.aparams['storage_policy_id'], description=self.amodule.params['description'], sep_id=self.amodule.params['sep_id'], sep_pool_name=self.amodule.params['pool'], ) #IO tune aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO'] self.limit_io(aparam_limit_io=aparam_limit_io) #set share status if self.amodule.params['shareable']: self.sdk_checkmode(self.api.cloudapi.disks.share)( disk_id=self.disk_id, ) return def action(self,restore=False): #restore never be done if restore: self.sdk_checkmode(self.api.cloudapi.disks.restore)( disk_id=self.disk_id, ) #rename if id present if ( self.amodule.params['name'] is not None and self.amodule.params['name'] != self.disk_info['name'] ): self.rename() #resize if ( self.amodule.params['size'] is not None and self.amodule.params['size'] != self.disk_info['sizeMax'] ): self.sdk_checkmode(self.api.cloudapi.disks.resize2)( disk_id=self.disk_id, disk_size_gb=self.amodule.params['size'], ) #IO TUNE aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO'] if aparam_limit_io: if not self.compare_iotune_params( new_iotune=aparam_limit_io, current_iotune=self.disk_info['iotune'], ): self.limit_io(aparam_limit_io=aparam_limit_io) #share check/update #raise Exception(self.amodule.params['shareable']) if self.amodule.params['shareable'] != self.disk_info['shareable']: if self.amodule.params['shareable']: self.sdk_checkmode(self.api.cloudapi.disks.share)( disk_id=self.disk_id, ) else: self.sdk_checkmode(self.api.cloudapi.disks.unshare)( disk_id=self.disk_id, ) aparam_storage_policy_id = self.aparams['storage_policy_id'] if ( aparam_storage_policy_id is not None and aparam_storage_policy_id != self.disk_info['storage_policy_id'] ): self.sdk_checkmode(self.api.ca.disks.change_disk_storage_policy)( disk_id=self.disk_id, storage_policy_id=aparam_storage_policy_id, ) return def delete(self): self.sdk_checkmode(self.api.cloudapi.disks.delete)( disk_id=self.disk_id, detach=self.amodule.params['force_detach'], permanently=self.amodule.params['permanently'], ) self.disk_id, self.disk_info = self._disk_get_by_id(self.disk_id) return def rename(self): self.sdk_checkmode(self.api.cloudapi.disks.rename)( disk_id=self.disk_id, name=self.amodule.params['name'], ) return 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", size=0, account_id=0, sep_id=0, pool="none", gid=0 ) if check_mode or self.disk_info is None: return ret_dict # 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['computes'] = self.disk_info['computes'] ret_dict['gid'] = self.disk_info['gid'] ret_dict['iotune'] = self.disk_info['iotune'] ret_dict['size_available'] = self.disk_info['sizeAvailable'] ret_dict['size_used'] = self.disk_info['sizeUsed'] ret_dict['storage_policy_id'] = self.disk_info['storage_policy_id'] ret_dict['to_clean'] = self.disk_info['to_clean'] ret_dict['cache_mode'] = self.disk_info['cache'] ret_dict['blkdiscard'] = self.disk_info['blkdiscard'] return ret_dict @property def amodule_init_args(self) -> dict: return self.pack_amodule_init_args( argument_spec=dict( account_id=dict( type='int', default=0, ), account_name=dict( type='str', default='', ), description=dict( type='str', ), id=dict( type='int', default=0, ), name=dict( type='str', ), force_detach=dict( type='bool', default=False, ), place_with=dict( type='int', default=0, ), pool=dict( type='str', default='', ), sep_id=dict( type='int', default=0, ), size=dict( type='int', ), limitIO=dict( type='dict', apply_defaults=True, options=dict( total_bytes_sec=dict( type='int', ), read_bytes_sec=dict( type='int', ), write_bytes_sec=dict( type='int', ), total_iops_sec=dict( type='int', ), read_iops_sec=dict( type='int', ), write_iops_sec=dict( type='int', ), total_bytes_sec_max=dict( type='int', ), read_bytes_sec_max=dict( type='int', ), write_bytes_sec_max=dict( type='int', ), total_iops_sec_max=dict( type='int', ), read_iops_sec_max=dict( type='int', ), write_iops_sec_max=dict( type='int', ), size_iops_sec=dict( type='int', ), ), ), permanently=dict( type='bool', default=False, ), shareable=dict( type='bool', default=False, ), state=dict( type='str', default='present', choices=[ 'absent', 'present', ], ), storage_policy_id=dict( type='int', ), ), supports_check_mode=True, required_one_of=[ ('id', 'name'), ], ) def check_amodule_args_for_change(self): check_errors = False if self.check_aparam_storage_policy_id() is False: check_errors = True if self.check_aparam_size() is False: check_errors = True if check_errors: self.exit(fail=True) def check_amodule_args_for_create(self): check_errors = False aparam_storage_policy_id = self.aparams['storage_policy_id'] if aparam_storage_policy_id is None: check_errors = True self.message( msg='Check for parameter "storage_policy_id" failed: ' 'storage_policy_id must be specified when creating ' 'a new disk' ) if self.check_aparam_storage_policy_id() is False: check_errors = True if self.check_aparam_size() is False: check_errors = True if check_errors: self.exit(fail=True) def check_aparam_storage_policy_id(self) -> bool: check_errors = False aparam_storage_policy_id = self.aparams['storage_policy_id'] if ( aparam_storage_policy_id is not None and aparam_storage_policy_id not in self.acc_info['storage_policy_ids'] ): check_errors = True self.message( msg='Check for parameter "storage_policy_id" failed: ' f'Account ID {self.acc_id} does not have access to ' f'storage_policy_id {aparam_storage_policy_id}' ) return not check_errors def check_aparam_size(self) -> bool: check_errors = False aparam_size = self.aparams['size'] if ( aparam_size is not None and aparam_size <= 0 ): check_errors = True self.message( msg=( 'Check for parameter "size" failed: ' f'Disk cannot be size {aparam_size}' ), ) return not check_errors @DecortController.handle_sdk_exceptions def run(self): # #Full range of Disk status is as follows: # # "ASSIGNED","MODELED", "CREATING","CREATED","DELETED", "DESTROYED","PURGED", # amodule = self.amodule if self.disk_id: #disk exist if self.disk_info['status'] in ["MODELED", "CREATING"]: self.result['failed'] = True self.result['changed'] = False self.result['msg'] = ("No change can be done for existing Disk ID {} because of its current " "status '{}'").format(self.disk_id, self.disk_info['status']) # "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED" elif self.disk_info['status'] in ["ASSIGNED","CREATED"]: if amodule.params['state'] == 'absent': self.delete() elif amodule.params['state'] == 'present': self.action() elif self.disk_info['status'] in ["PURGED", "DESTROYED"]: #re-provision disk if amodule.params['state'] in ('present'): self.create() else: self.nop() elif self.disk_info['status'] == "DELETED": if amodule.params['state'] in ('present'): self.action(restore=True) elif (amodule.params['state'] == 'absent' and amodule.params['permanently']): self.delete() else: self.nop() else: # preexisting Disk was not found if amodule.params['state'] == 'absent': self.nop() else: self.create() if self.result['failed']: amodule.fail_json(**self.result) else: if self.result['changed']: _, self.disk_info = self.disk_find(self.disk_id) self.result['facts'] = self.package_facts(amodule.check_mode) amodule.exit_json(**self.result) def main(): decort_disk().run() if __name__ == '__main__': main()