Example #1
0
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
Example #2
0
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()
Example #3
0
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
Example #4
0
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()