def process_properties(cpc, storage_group, params): """ Process the properties specified in the 'properties' module parameter, and return two dictionaries (create_props, update_props) that contain the properties that can be created, and the properties that can be updated, respectively. If the resource exists, the input property values are compared with the existing resource property values and the returned set of properties is the minimal set of properties that need to be changed. - Underscores in the property names are translated into hyphens. - The presence of read-only properties, invalid properties (i.e. not defined in the data model for storage groups), and properties that are not allowed because of restrictions or because they are auto-created from an artificial property is surfaced by raising ParameterError. - The properties resulting from handling artificial properties are added to the returned dictionaries. Parameters: cpc (zhmcclient.Cpc): CPC with the partition to be updated, and with the adapters to be used for the partition. storage_group (zhmcclient.StorageGroup): Storage group object to be updated with the full set of current properties, or `None` if it did not previously exist. params (dict): Module input parameters. Returns: tuple of (create_props, update_props), where: * create_props: dict of properties for zhmcclient.StorageGroupManager.create() * update_props: dict of properties for zhmcclient.StorageGroup.update_properties() Raises: ParameterError: An issue with the module parameters. """ create_props = {} update_props = {} # handle 'name' and 'cpc-uri' properties. sg_name = to_unicode(params['name']) if storage_group is None: # SG does not exist yet. create_props['name'] = sg_name create_props['cpc-uri'] = cpc.uri else: # SG does already exist. # We looked up the storage group by name, so we will never have to # update the storage group name. # Updates to the associated CPC are not possible. sg_cpc = storage_group.cpc if sg_cpc.uri != cpc.uri: raise ParameterError( "Storage group {!r} is not associated with the specified " "CPC %r, but with CPC %r.".format(sg_name, cpc.name, sg_cpc.name)) # handle the other properties input_props = params.get('properties', None) if input_props is None: input_props = {} for prop_name in input_props: if prop_name not in ZHMC_STORAGE_GROUP_PROPERTIES: raise ParameterError( "Property {!r} is not defined in the data model for " "storage groups.".format(prop_name)) allowed, create, update, update_while_active, eq_func, type_cast = \ ZHMC_STORAGE_GROUP_PROPERTIES[prop_name] if not allowed: raise ParameterError( "Property {!r} is not allowed in the 'properties' module " "parameter.".format(prop_name)) # Process a normal (= non-artificial) property _create_props, _update_props, _stop = process_normal_property( prop_name, ZHMC_STORAGE_GROUP_PROPERTIES, input_props, storage_group) create_props.update(_create_props) update_props.update(_update_props) assert _stop is False return create_props, update_props
def ensure_present(params, check_mode): """ Ensure that the storage volume is defined and has the specified properties. Raises: ParameterError: An issue with the module parameters. zhmcclient.Error: Any zhmcclient exception can happen. """ host = params['hmc_host'] userid, password = get_hmc_auth(params['hmc_auth']) cpc_name = params['cpc_name'] storage_group_name = params['storage_group_name'] storage_volume_name = params['name'] faked_session = params.get('faked_session', None) changed = False result = {} try: session = get_session(faked_session, host, userid, password) client = zhmcclient.Client(session) console = client.consoles.console cpc = client.cpcs.find(name=cpc_name) storage_group = console.storage_groups.find(name=storage_group_name) # The default exception handling is sufficient for the above. sg_cpc = storage_group.cpc if sg_cpc.uri != cpc.uri: raise ParameterError( "Storage group {!r} is not associated with the specified " "CPC %r, but with CPC %r.".format(storage_group_name, cpc.name, sg_cpc.name)) try: storage_volume = storage_group.storage_volumes.find( name=storage_volume_name) except zhmcclient.NotFound: storage_volume = None except zhmcclient.NoUniqueMatch: # The name of storage volumes within their storage group is not # enforced to be unique. raise if storage_volume is None: # It does not exist. Create it and update it if there are # update-only properties. if not check_mode: create_props, update_props = \ process_properties(cpc, storage_group, storage_volume, params) storage_volume = storage_group.storage_volumes.create( create_props) update2_props = {} for name in update_props: if name not in create_props: update2_props[name] = update_props[name] if update2_props: storage_volume.update_properties(update2_props) # We refresh the properties after the update, in case an # input property value gets changed. storage_volume.pull_full_properties() else: # TODO: Show props in module result also in check mode. pass changed = True else: # It exists. Update its properties. storage_volume.pull_full_properties() create_props, update_props = \ process_properties(cpc, storage_group, storage_volume, params) assert not create_props, \ "Unexpected create_props: %r" % create_props if update_props: if not check_mode: storage_volume.update_properties(update_props) # We refresh the properties after the update, in case an # input property value gets changed. storage_volume.pull_full_properties() else: # TODO: Show updated props in mod.result also in chk.mode storage_volume.pull_full_properties() changed = True if not check_mode: assert storage_volume if storage_volume: add_artificial_properties(storage_volume) result = storage_volume.properties return changed, result finally: session.logoff()
def process_properties(adapter, params): """ Process the properties specified in the 'properties' module parameter, and return a dictionary (update_props) that contains the properties that can be updated. The input property values are compared with the existing resource property values and the returned set of properties is the minimal set of properties that need to be changed. - Underscores in the property names are translated into hyphens. - The presence of properties that cannot be updated is surfaced by raising ParameterError. Parameters: adapter (zhmcclient.Adapter): Existing adapter to be updated, or `None` if the adapter does not exist. params (dict): Module input parameters. Returns: tuple of (create_props, update_props), where: * create_props: dict of properties from params that may be specified in zhmcclient.AdapterManager.create_hipersocket() (may overlap with update_props). * update_props: dict of properties from params that may be specified in zhmcclient.Adapter.update_properties() (may overlap with create_props). * change_adapter_type: String with new adapter type (i.e. input for Change Adapter Type operation), or None if no change needed. * change_crypto_type: String with new crypto type (i.e. input for Change Crypto Type operation), or None if no change needed. Raises: ParameterError: An issue with the module parameters. """ # Prepare return values create_props = {} update_props = {} change_adapter_type = None # New adapter type, if needed change_crypto_type = None # New crypto type, if needed # handle the 'name' module parameter adapter_name = to_unicode(params['name']) if adapter and adapter.properties.get('name', None) == adapter_name: pass # adapter exists and has the desired name else: create_props['name'] = adapter_name update_props['name'] = adapter_name # handle the other input properties input_props = params.get('properties', None) if input_props is None: input_props = {} for prop_name in input_props: try: allowed, create, update, update_active, eq_func, type_cast = \ ZHMC_ADAPTER_PROPERTIES[prop_name] except KeyError: allowed = False if not allowed: raise ParameterError( "Invalid adapter property {!r} specified in the 'properties' " "module parameter.".format(prop_name)) if prop_name == 'type': # Determine need to change the adapter type _current_adapter_type = adapter.properties.get('type', None) _input_adapter_type = input_props[prop_name] if _input_adapter_type != _current_adapter_type: change_adapter_type = _input_adapter_type elif prop_name == 'crypto_type': # Determine need to change the crypto type _current_crypto_type = adapter.properties.get('crypto-type', None) _input_crypto_type = CRYPTO_TYPES_MOD2HMC[input_props[prop_name]] if _input_crypto_type != _current_crypto_type: change_crypto_type = _input_crypto_type else: # Process a normal (= non-artificial) property _create_props, _update_props, _stop = process_normal_property( prop_name, ZHMC_ADAPTER_PROPERTIES, input_props, adapter) create_props.update(_create_props) update_props.update(_update_props) assert _stop is False return create_props, update_props, change_adapter_type, change_crypto_type
def ensure_present(params, check_mode): """ Ensure that the specified Hipersockets adapter exists and has the specified properties set. Raises: ParameterError: An issue with the module parameters. Error: Other errors during processing. zhmcclient.Error: Any zhmcclient exception can happen. """ # Note: Defaults specified in argument_spec will be set in params dict host = params['hmc_host'] userid, password = get_hmc_auth(params['hmc_auth']) cpc_name = params['cpc_name'] adapter_name = params['name'] faked_session = params.get('faked_session', None) # No default specified changed = False try: session = get_session(faked_session, host, userid, password) client = zhmcclient.Client(session) cpc = client.cpcs.find(name=cpc_name) # The default exception handling is sufficient for the above. try: adapter = cpc.adapters.find(name=adapter_name) except zhmcclient.NotFound: adapter = None if not adapter: # It does not exist. The only possible adapter type # that can be created is a Hipersockets adapter, but before # creating one we check the 'type' input property to verify that # the intention is really Hipersockets creation, and not just a # mispelled name. input_props = params.get('properties', None) if input_props is None: adapter_type = None else: adapter_type = input_props.get('type', None) if adapter_type is None: raise ParameterError( "Input property 'type' missing when creating " "Hipersockets adapter {!r} (must specify 'hipersockets')". format(adapter_name)) if adapter_type != 'hipersockets': raise ParameterError( "Input property 'type' specifies {!r} when creating " "Hipersockets adapter {!r} (must specify 'hipersockets').". format(adapter_type, adapter_name)) create_props, update_props = process_properties(adapter, params) # This is specific to Hipersockets: There are no update-only # properties, so any remaining such property is an input error invalid_update_props = {} for name in update_props: if name not in create_props: invalid_update_props[name] = update_props[name] if invalid_update_props: raise ParameterError( "Invalid input properties specified when creating " "Hipersockets adapter {!r}: {!r}".format( adapter_name, invalid_update_props)) # While the 'type' input property is required for verifying # the intention, it is not allowed as input for the # Create Hipersocket HMC operation. del create_props['type'] if not check_mode: adapter = cpc.adapters.create_hipersocket(create_props) adapter.pull_full_properties() result = adapter.properties # from actual values else: adapter = None result = dict() result.update(create_props) # from input values changed = True else: # It does exist. # Update its properties and change adapter and crypto type, if # needed. adapter.pull_full_properties() result = adapter.properties create_props, update_props, chg_adapter_type, chg_crypto_type = \ process_properties(adapter, params) if update_props: if not check_mode: adapter.update_properties(update_props) else: result.update(update_props) # from input values changed = True if chg_adapter_type: if not check_mode: adapter.change_adapter_type(chg_adapter_type) else: result['type'] = chg_adapter_type changed = True if chg_crypto_type: if not check_mode: adapter.change_crypto_type(chg_crypto_type) else: result['crypto-type'] = chg_crypto_type changed = True if changed and not check_mode: adapter.pull_full_properties() result = adapter.properties # from actual values if adapter: ports = adapter.ports.list() result_ports = list() for port in ports: port.pull_full_properties() result_ports.append(port.properties) result['ports'] = result_ports else: # For now, we return no ports when creating in check mode result['ports'] = dict() return changed, result finally: session.logoff()
def ensure_attached(params, check_mode): """ Ensure that the specified crypto adapters and crypto domains are attached to the target partition. Raises: ParameterError: An issue with the module parameters. Error: Other errors during processing. zhmcclient.Error: Any zhmcclient exception can happen. """ # Note: Defaults specified in argument_spec will be set in params dict host = params['hmc_host'] userid, password = get_hmc_auth(params['hmc_auth']) cpc_name = params['cpc_name'] partition_name = params['partition_name'] adapter_count = params['adapter_count'] domain_range = params['domain_range'] access_mode = params['access_mode'] crypto_type = params['crypto_type'] faked_session = params.get('faked_session', None) # No default specified try: assert len(domain_range) == 2, \ "len(domain_range)={}".format(len(domain_range)) domain_range_lo = int(domain_range[0]) domain_range_hi = int(domain_range[1]) except (ValueError, AssertionError): raise ParameterError( "The 'domain_range' parameter must be a list containing two " "integer numbers, but is: {!r}".format(domain_range)) hmc_crypto_type = CRYPTO_TYPES_MOD2HMC[crypto_type] hmc_access_mode = ACCESS_MODES_MOD2HMC[access_mode] changed = False result = dict() result_changes = dict() try: session = get_session(faked_session, host, userid, password) client = zhmcclient.Client(session) cpc = client.cpcs.find(name=cpc_name) partition = cpc.partitions.find(name=partition_name) # The default exception handling is sufficient for the above. # Determine all crypto adapters of the specified crypto type. filter_args = { 'adapter-family': 'crypto', 'crypto-type': hmc_crypto_type, } all_adapters = cpc.adapters.list(filter_args=filter_args, full_properties=True) if not all_adapters: raise Error( "No crypto adapters of type {!r} found on CPC {!r} ".format( crypto_type, cpc_name)) # All crypto adapters in a CPC have the same number of domains # (otherwise the concept of attaching domains across all attached # adapters cannot work). Therefore, the max number of domains can be # gathered from any adapter. max_domains = all_adapters[0].maximum_crypto_domains # Parameter checking on domain range. # (can be done only now because it requires the max_domains). if domain_range_hi == -1: domain_range_hi = max_domains - 1 if domain_range_lo > domain_range_hi: raise ParameterError( "In the 'domain_range' parameter, the lower boundary (={}) " "of the range must be less than the higher boundary (={})". format(domain_range_lo, domain_range_hi)) # Parameter checking on adapter count. # (can be done only now because it requires the number of adapters). if adapter_count == -1: adapter_count = len(all_adapters) if adapter_count < 1: raise ParameterError( "The 'adapter_count' parameter must be at least 1, but is: {}". format(adapter_count)) if adapter_count > len(all_adapters): raise ParameterError( "The 'adapter_count' parameter must not exceed the number of " "{} crypto adapters of type {!r} in CPC {!r}, but is {}". format(len(all_adapters), crypto_type, cpc_name, adapter_count)) # # Get current crypto config of the target partition. # # Domains attached to the partition, as a dict with: # key: domain index # value: access mode attached_domains = dict() # Adapters attached to the partition, as a list of Adapter objects: attached_adapters = list() # Adapters not attached to the partition, as a list of Adapter objects: detached_adapters = list() _attached_adapter_uris = list() # URIs of attached adapters cc = partition.get_property('crypto-configuration') if cc: _attached_adapter_uris = cc['crypto-adapter-uris'] for dc in cc['crypto-domain-configurations']: di = int(dc['domain-index']) am = dc['access-mode'] LOGGER.debug("Crypto config of partition {!r}: " "Domain {} is attached in {!r} mode".format( partition.name, di, am)) attached_domains[di] = am for a in all_adapters: if a.uri in _attached_adapter_uris: LOGGER.debug("Crypto config of partition {!r}: " "Adapter {!r} is attached".format( partition.name, a.name)) attached_adapters.append(a) else: LOGGER.debug("Crypto config of partition {!r}: " "Adapter {!r} is not attached".format( partition.name, a.name)) detached_adapters.append(a) del _attached_adapter_uris # # Get the current crypto config of all partitions of the CPC. # # This is needed because finding out whether an adapter has the right # domains available by simply attaching it to the target partition # and reacting to the returned status does not work for stopped # partitions. # # All partition of the CPC, as a dict: # key: partition URI # value: Partition object all_partitions = cpc.partitions.list() all_partitions = dict( zip([p.uri for p in all_partitions], all_partitions)) # Crypto config of all partitions of the CPC, as a dict with: # key: adapter URI # value: dict: # key: domain index (for attached domains) # value: list of tuple(access mode, partition URI) all_crypto_config = dict() for p_uri in all_partitions: p = all_partitions[p_uri] cc = p.get_property('crypto-configuration') # The 'crypto-configuration' property is None or: # { # 'crypto-adapter-uris': ['/api/...', ...], # 'crypto-domain-configurations': [ # {'domain-index': 15, 'access-mode': 'control-usage'}, # ... # ] # } if cc: _adapter_uris = cc['crypto-adapter-uris'] for dc in cc['crypto-domain-configurations']: di = int(dc['domain-index']) am = dc['access-mode'] for a_uri in _adapter_uris: if a_uri not in all_crypto_config: all_crypto_config[a_uri] = dict() domains_dict = all_crypto_config[a_uri] # mutable if di not in domains_dict: domains_dict[di] = list() domains_dict[di].append((am, p.uri)) # # Determine the domains to be attached to the target partition # desired_domains = list(range(domain_range_lo, domain_range_hi + 1)) add_domains = list() # List of domain index numbers to be attached for di in desired_domains: if di not in attached_domains: # This domain is not attached to the target partition add_domains.append(di) elif attached_domains[di] != hmc_access_mode: # This domain is attached to the target partition but not in # the desired access mode. The access mode could be extended # from control to control+usage, but that is not implemented # by this code here. raise Error( "Domain {} is currently attached in {!r} mode to target " "partition {!r}, but requested was {!r} mode".format( di, ACCESS_MODES_HMC2MOD[attached_domains[di]], partition.name, access_mode)) else: # This domain is attached to the target partition in the # desired access mode pass # Create the domain config structure for the domains to be attached add_domain_config = list() for di in add_domains: add_domain_config.append({ 'domain-index': di, 'access-mode': hmc_access_mode }) # Check that the domains to be attached to the partition are available # on the currently attached adapters for a in attached_adapters: domains_dict = all_crypto_config[a.uri] for di in add_domains: if di in domains_dict: for am, p_uri in domains_dict[di]: if am != 'control' and hmc_access_mode != 'control': # Multiple attachments conflict only when both are # in usage mode p = all_partitions[p_uri] raise Error( "Domain {} cannot be attached in {!r} mode to " "target partition {!r} because it is already " "attached in {!r} mode to partition {!r}". format(di, access_mode, partition.name, ACCESS_MODES_HMC2MOD[am], p.name)) # Make sure the desired number of adapters is attached to the partition # and the desired domains are attached. # The HMC enforces the following for non-empty crypto configurations of # a partition: # - In the resulting config, the partition needs to have at least one # adapter attached. # - In the resulting config, the partition needs to have at least one # domain attached in usage mode. # As a result, on an empty crypto config, the first adapter and the # first domain(s) need to be attached at the same time. result_changes['added-adapters'] = [] result_changes['added-domains'] = [] missing_count = max(0, adapter_count - len(attached_adapters)) assert missing_count <= len(detached_adapters), \ "missing_count={}, len(detached_adapters)={}".\ format(missing_count, len(detached_adapters)) if missing_count == 0 and add_domain_config: # Adapters already sufficient, but domains to be attached LOGGER.debug( "Adapters sufficient - attaching domains {!r} in {!r} mode to " "target partition {!r}".format(add_domains, access_mode, partition.name)) if not check_mode: try: partition.increase_crypto_config([], add_domain_config) except zhmcclient.Error as exc: raise Error( "Attaching domains {!r} in {!r} mode to target " "partition {!r} failed: {}".format( add_domains, access_mode, partition.name, exc)) changed = True result_changes['added-domains'].extend(add_domains) elif missing_count > 0: # Adapters need to be attached for adapter in detached_adapters: if missing_count == 0: break # Check that the adapter has all needed domains available conflicting_domains = dict() if adapter.uri in all_crypto_config: domains_dict = all_crypto_config[adapter.uri] for di in desired_domains: if di in domains_dict: # The domain is already attached to some # partition(s) in some access mode for am, p_uri in domains_dict[di]: if am == 'control': # An attachment in control mode does not # prevent additional attachments continue if p_uri == partition.uri and \ am == hmc_access_mode: # This is our target partition, and the # domain is already attached in the desired # mode. continue p = all_partitions[p_uri] conflicting_domains[di] = (am, p.name) if conflicting_domains: LOGGER.debug( "Skipping adapter {!r} because the following of its " "domains are already attached to other partitions: " "{!r}".format(adapter.name, conflicting_domains)) continue LOGGER.debug( "Attaching adapter {!r} and domains {!r} in {!r} mode to " "target partition {!r}".format(adapter.name, add_domains, access_mode, partition.name)) if not check_mode: try: partition.increase_crypto_config([adapter], add_domain_config) except zhmcclient.Error as exc: raise Error( "Attaching adapter {!r} and domains {!r} in {!r} " "mode to target partition {!r} failed: {}".format( adapter.name, add_domains, access_mode, partition.name, exc)) changed = True result_changes['added-adapters'].append(adapter.name) result_changes['added-domains'].extend(add_domains) # Don't try to add again for next adapter: add_domain_config = [] add_domains = [] missing_count -= 1 if missing_count > 0: # Because adapters may be skipped, it is possible that there # are not enough adapters raise Error( "Did not find enough crypto adapters with attachable " "domains - missing adapters: {}; Requested domains: {}, " "Access mode: {}".format(missing_count, desired_domains, access_mode)) if not check_mode: # This is not optimal because it does not produce a result # in check mode, but because the actual config is determined, # instead of the artificially calculated one, it seems better # to return no config than the unchanged actual config. result.update(get_partition_config(partition, all_adapters)) return changed, result, result_changes finally: session.logoff()