Server IP : 85.214.239.14 / Your IP : 18.222.177.138 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Mon Sep 30 15:36:27 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /lib/python3/dist-packages/ansible_collections/ovirt/ovirt/plugins/modules/ |
Upload File : |
#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright (c) 2016 Red Hat, Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see <http://www.gnu.org/licenses/>. # from __future__ import (absolute_import, division, print_function) __metaclass__ = type DOCUMENTATION = ''' --- module: ovirt_storage_domain short_description: Module to manage storage domains in oVirt/RHV version_added: "1.0.0" author: - "Ondra Machacek (@machacekondra)" - "Martin Necas (@mnecas)" description: - "Module to manage storage domains in oVirt/RHV" options: id: description: - "Id of the storage domain to be imported." type: str name: description: - "Name of the storage domain to manage. (Not required when state is I(imported))" type: str state: description: - "Should the storage domain be present/absent/maintenance/unattached/imported/update_ovf_store" - "I(imported) is supported since version 2.4." - "I(update_ovf_store) is supported since version 2.5, currently if C(wait) is (true), we don't wait for update." choices: ['present', 'absent', 'maintenance', 'unattached', 'imported', 'update_ovf_store'] default: present type: str description: description: - "Description of the storage domain." type: str comment: description: - "Comment of the storage domain." type: str data_center: description: - "Data center name where storage domain should be attached." - "This parameter isn't idempotent, it's not possible to change data center of storage domain." type: str domain_function: description: - "Function of the storage domain." - "This parameter isn't idempotent, it's not possible to change domain function of storage domain." choices: ['data', 'iso', 'export'] default: 'data' aliases: ['type'] type: str host: description: - "Host to be used to mount storage." type: str localfs: description: - "Dictionary with values for localfs storage type:" - "Note that these parameters are not idempotent." suboptions: path: description: - "Path of the mount point. E.g.: /path/to/my/data" type: dict nfs: description: - "Dictionary with values for NFS storage type:" - "Note that these parameters are not idempotent." type: dict suboptions: address: description: - "Address of the NFS server. E.g.: myserver.mydomain.com" path: description: - "Path of the mount point. E.g.: /path/to/my/data" version: description: - "NFS version. One of: I(auto), I(v3), I(v4) or I(v4_1)." timeout: description: - "The time in tenths of a second to wait for a response before retrying NFS requests. Range 0 to 65535." retrans: description: - "The number of times to retry a request before attempting further recovery actions. Range 0 to 65535." mount_options: description: - "Option which will be passed when mounting storage." iscsi: description: - "Dictionary with values for iSCSI storage type:" - "Note that these parameters are not idempotent." type: dict suboptions: address: description: - Address of the iSCSI storage server. port: description: - Port of the iSCSI storage server. target: description: - The target IQN for the storage device. lun_id: description: - LUN id(s). username: description: - A CHAP user name for logging into a target. password: description: - A CHAP password for logging into a target. override_luns: description: - If I(True) ISCSI storage domain luns will be overridden before adding. type: bool target_lun_map: description: - List of dictionary containing targets and LUNs. posixfs: description: - "Dictionary with values for PosixFS storage type:" - "Note that these parameters are not idempotent." type: dict suboptions: path: description: - "Path of the mount point. E.g.: /path/to/my/data" vfs_type: description: - Virtual File System type. mount_options: description: - Option which will be passed when mounting storage. glusterfs: description: - "Dictionary with values for GlusterFS storage type:" - "Note that these parameters are not idempotent." type: dict suboptions: address: description: - "Address of the Gluster server. E.g.: myserver.mydomain.com" path: description: - "Path of the mount point. E.g.: /path/to/my/data" mount_options: description: - Option which will be passed when mounting storage. managed_block_storage: description: - "Dictionary with values for managed block storage type" - "Note: available from ovirt 4.3" type: dict suboptions: driver_options: description: - "The options to be passed when creating a storage domain using a cinder driver." - "List of dictionary containing C(name) and C(value) of driver option" type: list elements: dict driver_sensitive_options: description: - "Parameters containing sensitive information, to be passed when creating a storage domain using a cinder driver." - "List of dictionary containing C(name) and C(value) of driver sensitive option" type: list elements: dict fcp: description: - "Dictionary with values for fibre channel storage type:" - "Note that these parameters are not idempotent." type: dict suboptions: lun_id: description: - LUN id. override_luns: description: - If I(True) FCP storage domain LUNs will be overridden before adding. type: bool wipe_after_delete: description: - "Boolean flag which indicates whether the storage domain should wipe the data after delete." type: bool backup: description: - "Boolean flag which indicates whether the storage domain is configured as backup or not." type: bool critical_space_action_blocker: description: - "Indicates the minimal free space the storage domain should contain in percentages." type: int warning_low_space: description: - "Indicates the minimum percentage of a free space in a storage domain to present a warning." type: int destroy: description: - "Logical remove of the storage domain. If I(true) retains the storage domain's data for import." - "This parameter is relevant only when C(state) is I(absent)." type: bool format: description: - "If I(True) storage domain will be formatted after removing it from oVirt/RHV." - "This parameter is relevant only when C(state) is I(absent)." type: bool discard_after_delete: description: - "If I(True) storage domain blocks will be discarded upon deletion. Enabled by default." - "This parameter is relevant only for block based storage domains." type: bool storage_format: description: - "One of v1, v2, v3, v4, v5 - sets the storage format of the domain." type: str extends_documentation_fragment: ovirt.ovirt.ovirt ''' EXAMPLES = ''' # Examples don't contain auth parameter for simplicity, # look at ovirt_auth module to see how to reuse authentication: # Add data NFS storage domain - ovirt.ovirt.ovirt_storage_domain: name: data_nfs host: myhost data_center: mydatacenter nfs: address: 10.34.63.199 path: /path/data # Add data NFS storage domain with id for data center - ovirt.ovirt.ovirt_storage_domain: name: data_nfs host: myhost data_center: 11111 nfs: address: 10.34.63.199 path: /path/data mount_options: noexec,nosuid # Add data NFS storage domain in an older format # E.g. the following will work if the data center is in 4.2 level. # Without this, you might get as error like: # Cannot attach Storage. Storage Domain format V5 is illegal. - ovirt.ovirt.ovirt_storage_domain: name: data_nfs host: myhost data_center: mydatacenter nfs: address: 10.34.63.199 path: /path/data storage_format: v4 # Add data localfs storage domain - ovirt.ovirt.ovirt_storage_domain: name: data_localfs host: myhost data_center: mydatacenter localfs: path: /path/to/data # Add data iSCSI storage domain: - ovirt.ovirt.ovirt_storage_domain: name: data_iscsi host: myhost data_center: mydatacenter iscsi: target: iqn.2016-08-09.domain-01:nickname lun_id: - 1IET_000d0001 - 1IET_000d0002 address: 10.34.63.204 discard_after_delete: True backup: False critical_space_action_blocker: 5 warning_low_space: 10 # Since Ansible 2.5 you can specify multiple targets for storage domain, # Add data iSCSI storage domain with multiple targets: - ovirt.ovirt.ovirt_storage_domain: name: data_iscsi host: myhost data_center: mydatacenter iscsi: target_lun_map: - target: iqn.2016-08-09.domain-01:nickname lun_id: 1IET_000d0001 - target: iqn.2016-08-09.domain-02:nickname lun_id: 1IET_000d0002 address: 10.34.63.204 discard_after_delete: True # Add data glusterfs storage domain - ovirt.ovirt.ovirt_storage_domain: name: glusterfs_1 host: myhost data_center: mydatacenter glusterfs: address: 10.10.10.10 path: /path/data # Create export NFS storage domain: - ovirt.ovirt.ovirt_storage_domain: name: myexportdomain domain_function: export host: myhost data_center: mydatacenter nfs: address: 10.34.63.199 path: /path/export wipe_after_delete: False backup: True critical_space_action_blocker: 2 warning_low_space: 5 # Import export NFS storage domain: - ovirt.ovirt.ovirt_storage_domain: state: imported domain_function: export host: myhost data_center: mydatacenter nfs: address: 10.34.63.199 path: /path/export # Import FCP storage domain: - ovirt.ovirt.ovirt_storage_domain: state: imported name: data_fcp host: myhost data_center: mydatacenter fcp: {} # Update OVF_STORE: - ovirt.ovirt.ovirt_storage_domain: state: update_ovf_store name: domain # Create ISO NFS storage domain - ovirt.ovirt.ovirt_storage_domain: name: myiso domain_function: iso host: myhost data_center: mydatacenter nfs: address: 10.34.63.199 path: /path/iso # Create managed storage domain # Available from ovirt 4.3 and ansible 2.9 - ovirt.ovirt.ovirt_storage_domain: name: my_managed_domain host: myhost data_center: mydatacenter managed_block_storage: driver_options: - name: rbd_pool value: pool1 - name: rbd_user value: admin - name: volume_driver value: cinder.volume.drivers.rbd.RBDDriver - name: rbd_keyring_conf value: /etc/ceph/keyring driver_sensitive_options: - name: secret_password value: password # Remove storage domain - ovirt.ovirt.ovirt_storage_domain: state: absent name: mystorage_domain format: true ''' RETURN = ''' id: description: ID of the storage domain which is managed returned: On success if storage domain is found. type: str sample: 7de90f31-222c-436c-a1ca-7e655bd5b60c storage_domain: description: "Dictionary of all the storage domain attributes. Storage domain attributes can be found on your oVirt/RHV instance at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/storage_domain." returned: On success if storage domain is found. type: dict ''' try: import ovirtsdk4.types as otypes from ovirtsdk4.types import StorageDomainStatus as sdstate from ovirtsdk4.types import HostStatus as hoststate from ovirtsdk4.types import DataCenterStatus as dcstatus except ImportError: pass import traceback from ansible.module_utils.basic import AnsibleModule from ansible_collections.ovirt.ovirt.plugins.module_utils.ovirt import ( BaseModule, check_sdk, create_connection, equal, get_entity, get_id_by_name, OvirtRetry, ovirt_full_argument_spec, search_by_name, search_by_attributes, wait, ) class StorageDomainModule(BaseModule): def _get_storage_type(self): for sd_type in ['nfs', 'iscsi', 'posixfs', 'glusterfs', 'fcp', 'localfs', 'managed_block_storage']: if self.param(sd_type) is not None: return sd_type def _get_storage(self): for sd_type in ['nfs', 'iscsi', 'posixfs', 'glusterfs', 'fcp', 'localfs', 'managed_block_storage']: if self.param(sd_type) is not None: return self.param(sd_type) def _get_storage_format(self): if self.param('storage_format') is not None: for sd_format in otypes.StorageFormat: if self.param('storage_format').lower() == str(sd_format): return sd_format def _login(self, storage_type, storage): if storage_type == 'iscsi': hosts_service = self._connection.system_service().hosts_service() host_id = get_id_by_name(hosts_service, self.param('host')) if storage.get('target'): hosts_service.host_service(host_id).iscsi_login( iscsi=otypes.IscsiDetails( username=storage.get('username'), password=storage.get('password'), address=storage.get('address'), target=storage.get('target'), ), ) elif storage.get('target_lun_map'): for target in [m['target'] for m in storage.get('target_lun_map')]: hosts_service.host_service(host_id).iscsi_login( iscsi=otypes.IscsiDetails( username=storage.get('username'), password=storage.get('password'), address=storage.get('address'), target=target, ), ) def __target_lun_map(self, storage): if storage.get('target'): lun_ids = storage.get('lun_id') if isinstance(storage.get('lun_id'), list) else [(storage.get('lun_id'))] return [(lun_id, storage.get('target')) for lun_id in lun_ids] elif storage.get('target_lun_map'): return [(target_map.get('lun_id'), target_map.get('target')) for target_map in storage.get('target_lun_map')] else: lun_ids = storage.get('lun_id') if isinstance(storage.get('lun_id'), list) else [(storage.get('lun_id'))] return [(lun_id, None) for lun_id in lun_ids] def build_entity(self): storage_type = self._get_storage_type() storage = self._get_storage() self._login(storage_type, storage) return otypes.StorageDomain( name=self.param('name'), description=self.param('description'), comment=self.param('comment'), wipe_after_delete=self.param('wipe_after_delete'), backup=self.param('backup'), critical_space_action_blocker=self.param('critical_space_action_blocker'), warning_low_space_indicator=self.param('warning_low_space'), import_=True if self.param('state') == 'imported' else None, id=self.param('id') if self.param('state') == 'imported' else None, type=otypes.StorageDomainType(storage_type if storage_type == 'managed_block_storage' else self.param('domain_function')), host=otypes.Host(name=self.param('host')), discard_after_delete=self.param('discard_after_delete'), storage=otypes.HostStorage( driver_options=[ otypes.Property( name=do.get('name'), value=do.get('value') ) for do in storage.get('driver_options') ] if storage.get('driver_options') else None, driver_sensitive_options=[ otypes.Property( name=dso.get('name'), value=dso.get('value') ) for dso in storage.get('driver_sensitive_options') ] if storage.get('driver_sensitive_options') else None, type=otypes.StorageType(storage_type), logical_units=[ otypes.LogicalUnit( id=lun_id, address=storage.get('address'), port=int(storage.get('port', 3260)), target=target, username=storage.get('username'), password=storage.get('password'), ) for lun_id, target in self.__target_lun_map(storage) ] if storage_type in ['iscsi', 'fcp'] else None, override_luns=storage.get('override_luns'), mount_options=storage.get('mount_options'), vfs_type=( 'glusterfs' if storage_type in ['glusterfs'] else storage.get('vfs_type') ), address=storage.get('address'), path=storage.get('path'), nfs_retrans=storage.get('retrans'), nfs_timeo=storage.get('timeout'), nfs_version=otypes.NfsVersion( storage.get('version') ) if storage.get('version') else None, ) if storage_type is not None else None, storage_format=self._get_storage_format(), ) def _find_attached_datacenter_name(self, sd_name): """ Finds the name of the datacenter that a given storage domain is attached to. Args: sd_name (str): Storage Domain name Returns: str: Data Center name Raises: Exception: In case storage domain in not attached to an active Datacenter """ dcs_service = self._connection.system_service().data_centers_service() dc = search_by_attributes(dcs_service, storage=sd_name) if dc is None: raise Exception( "Can't bring storage to state `%s`, because it seems that" "it is not attached to any datacenter" % self.param('state') ) else: if dc.status == dcstatus.UP: return dc.name else: raise Exception( "Can't bring storage to state `%s`, because Datacenter " "%s is not UP" % (self.param('state'), dc.name) ) def _attached_sds_service(self, dc_name): # Get data center object of the storage domain: dcs_service = self._connection.system_service().data_centers_service() # Search the data_center name, if it does not exist, try to search by guid. dc = search_by_name(dcs_service, dc_name) if dc is None: dc = get_entity(dcs_service.service(dc_name)) if dc is None: return None dc_service = dcs_service.data_center_service(dc.id) return dc_service.storage_domains_service() def _attached_sd_service(self, storage_domain): dc_name = self.param('data_center') if not dc_name: # Find the DC, where the storage resides: dc_name = self._find_attached_datacenter_name(storage_domain.name) attached_sds_service = self._attached_sds_service(dc_name) attached_sd_service = attached_sds_service.storage_domain_service(storage_domain.id) return attached_sd_service def _maintenance(self, storage_domain): attached_sd_service = self._attached_sd_service(storage_domain) attached_sd = get_entity(attached_sd_service) if attached_sd and attached_sd.status != sdstate.MAINTENANCE: if not self._module.check_mode: attached_sd_service.deactivate() self.changed = True wait( service=attached_sd_service, condition=lambda sd: sd.status == sdstate.MAINTENANCE, wait=self.param('wait'), timeout=self.param('timeout'), ) def _unattach(self, storage_domain): attached_sd_service = self._attached_sd_service(storage_domain) attached_sd = get_entity(attached_sd_service) if attached_sd and attached_sd.status == sdstate.MAINTENANCE: if not self._module.check_mode: # Detach the storage domain: attached_sd_service.remove() self.changed = True # Wait until storage domain is detached: wait( service=attached_sd_service, condition=lambda sd: sd is None, wait=self.param('wait'), timeout=self.param('timeout'), ) def pre_remove(self, entity): # In case the user chose to destroy the storage domain there is no need to # move it to maintenance or detach it, it should simply be removed from the DB. # Also if storage domain in already unattached skip this step. if entity.status == sdstate.UNATTACHED or self.param('destroy'): return # Before removing storage domain we need to put it into maintenance state: self._maintenance(entity) # Before removing storage domain we need to detach it from data center: self._unattach(entity) def post_create_check(self, sd_id): storage_domain = self._service.service(sd_id).get() dc_name = self.param('data_center') if not dc_name: # Find the DC, where the storage resides: dc_name = self._find_attached_datacenter_name(storage_domain.name) self._service = self._attached_sds_service(dc_name) # If storage domain isn't attached, attach it: attached_sd_service = self._service.service(storage_domain.id) if get_entity(attached_sd_service) is None: self._service.add( otypes.StorageDomain( id=storage_domain.id, ), ) self.changed = True # Wait until storage domain is in maintenance: wait( service=attached_sd_service, condition=lambda sd: sd.status == sdstate.ACTIVE, wait=self.param('wait'), timeout=self.param('timeout'), ) def unattached_pre_action(self, storage_domain): dc_name = self.param('data_center') if not dc_name: # Find the DC, where the storage resides: dc_name = self._find_attached_datacenter_name(storage_domain.name) self._service = self._attached_sds_service(dc_name) self._maintenance(storage_domain) def update_check(self, entity): return ( equal(self.param('comment'), entity.comment) and equal(self.param('description'), entity.description) and equal(self.param('backup'), entity.backup) and equal(self.param('critical_space_action_blocker'), entity.critical_space_action_blocker) and equal(self.param('discard_after_delete'), entity.discard_after_delete) and equal(self.param('wipe_after_delete'), entity.wipe_after_delete) and equal(self.param('warning_low_space'), entity.warning_low_space_indicator) ) def failed_state(sd): return sd.status in [sdstate.UNKNOWN, sdstate.INACTIVE] def control_state(sd_module): sd = sd_module.search_entity() if sd is None: return sd_service = sd_module._service.service(sd.id) # In the case of no status returned, it's an attached storage domain. # Redetermine the corresponding service and entity: if sd.status is None: sd_service = sd_module._attached_sd_service(sd) sd = get_entity(sd_service) if sd is None: return if sd.status == sdstate.LOCKED: wait( service=sd_service, condition=lambda sd: sd.status != sdstate.LOCKED, fail_condition=failed_state, ) if failed_state(sd): raise Exception("Not possible to manage storage domain '%s'." % sd.name) elif sd.status == sdstate.ACTIVATING: wait( service=sd_service, condition=lambda sd: sd.status == sdstate.ACTIVE, fail_condition=failed_state, ) elif sd.status == sdstate.DETACHING: wait( service=sd_service, condition=lambda sd: sd.status == sdstate.UNATTACHED, fail_condition=failed_state, ) elif sd.status == sdstate.PREPARING_FOR_MAINTENANCE: wait( service=sd_service, condition=lambda sd: sd.status == sdstate.MAINTENANCE, fail_condition=failed_state, ) def main(): argument_spec = ovirt_full_argument_spec( state=dict( choices=['present', 'absent', 'maintenance', 'unattached', 'imported', 'update_ovf_store'], default='present', ), id=dict(default=None), name=dict(default=None), description=dict(default=None), comment=dict(default=None), data_center=dict(default=None), domain_function=dict(choices=['data', 'iso', 'export'], default='data', aliases=['type']), host=dict(default=None), localfs=dict(default=None, type='dict'), nfs=dict(default=None, type='dict'), iscsi=dict(default=None, type='dict'), managed_block_storage=dict(default=None, type='dict', options=dict( driver_options=dict(type='list', elements='dict'), driver_sensitive_options=dict(type='list', no_log=True, elements='dict'))), posixfs=dict(default=None, type='dict'), glusterfs=dict(default=None, type='dict'), fcp=dict(default=None, type='dict'), wipe_after_delete=dict(type='bool', default=None), backup=dict(type='bool', default=None), critical_space_action_blocker=dict(type='int', default=None), warning_low_space=dict(type='int', default=None), destroy=dict(type='bool', default=None), format=dict(type='bool', default=None), discard_after_delete=dict(type='bool', default=None), storage_format=dict(default=None), ) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, ) check_sdk(module) try: auth = module.params.pop('auth') connection = create_connection(auth) storage_domains_service = connection.system_service().storage_domains_service() storage_domains_module = StorageDomainModule( connection=connection, module=module, service=storage_domains_service, ) state = module.params['state'] control_state(storage_domains_module) if state == 'absent': # Pick random available host when host parameter is missing host_param = module.params['host'] if not host_param: host = search_by_attributes(connection.system_service().hosts_service(), status='up') if host is None: raise Exception( "Not possible to remove storage domain '%s' " "because no host found with status `up`." % module.params['name'] ) host_param = host.name ret = storage_domains_module.remove( destroy=module.params['destroy'], format=module.params['format'], host=host_param, ) elif state == 'present' or state == 'imported': sd_id = storage_domains_module.create()['id'] storage_domains_module.post_create_check(sd_id) ret = storage_domains_module.action( action='activate', action_condition=lambda s: s.status == sdstate.MAINTENANCE, wait_condition=lambda s: s.status == sdstate.ACTIVE, fail_condition=failed_state, search_params={'id': sd_id} if state == 'imported' else None ) elif state == 'maintenance': sd_id = storage_domains_module.create()['id'] storage_domains_module.post_create_check(sd_id) ret = OvirtRetry.backoff(tries=5, delay=1, backoff=2)( storage_domains_module.action )( action='deactivate', action_condition=lambda s: s.status == sdstate.ACTIVE, wait_condition=lambda s: s.status == sdstate.MAINTENANCE, fail_condition=failed_state, ) elif state == 'unattached': ret = storage_domains_module.create() storage_domains_module.pre_remove( entity=storage_domains_service.service(ret['id']).get() ) ret['changed'] = storage_domains_module.changed elif state == 'update_ovf_store': ret = storage_domains_module.action( action='update_ovf_store' ) module.exit_json(**ret) except Exception as e: module.fail_json(msg=str(e), exception=traceback.format_exc()) finally: connection.close(logout=auth.get('token') is None) if __name__ == "__main__": main()