diff --git a/ci/roles/baremetal_inspect/defaults/main.yml b/ci/roles/baremetal_inspect/defaults/main.yml new file mode 100644 index 00000000..d993fcdb --- /dev/null +++ b/ci/roles/baremetal_inspect/defaults/main.yml @@ -0,0 +1,56 @@ +expected_fields: + - allocation_id + - bios_interface + - boot_interface + - boot_mode + - chassis_id + - clean_step + - conductor + - conductor_group + - console_interface + - created_at + - deploy_interface + - deploy_step + - driver + - driver_info + - driver_internal_info + - extra + - fault + - id + - inspect_interface + - instance_id + - instance_info + - is_automated_clean_enabled + - is_console_enabled + - is_maintenance + - is_protected + - is_retired + - is_secure_boot + - last_error + - links + - maintenance_reason + - management_interface + - name + - network_interface + - owner + - port_groups + - ports + - power_interface + - power_state + - properties + - protected_reason + - provision_state + - raid_config + - raid_interface + - rescue_interface + - reservation + - resource_class + - retired_reason + - states + - storage_interface + - target_power_state + - target_provision_state + - target_raid_config + - traits + - updated_at + - vendor_interface diff --git a/ci/roles/baremetal_inspect/tasks/main.yml b/ci/roles/baremetal_inspect/tasks/main.yml new file mode 100644 index 00000000..63860c99 --- /dev/null +++ b/ci/roles/baremetal_inspect/tasks/main.yml @@ -0,0 +1,15 @@ +--- +# TODO: Actually run this role in CI. Atm we do not have DevStack's ironic plugin enabled. +- name: Introspect node + openstack.cloud.baremetal_inspect: + cloud: "{{ cloud }}" + name: node-1 + register: inspect + +- debug: var=inspect + +- name: assert return values of baremetal_inspect module + assert: + that: + # allow new fields to be introduced but prevent fields from being removed + - expected_fields|difference(inspect.node.keys())|length == 0 diff --git a/plugins/modules/baremetal_inspect.py b/plugins/modules/baremetal_inspect.py index 0692f7ba..d77ee837 100644 --- a/plugins/modules/baremetal_inspect.py +++ b/plugins/modules/baremetal_inspect.py @@ -10,32 +10,21 @@ module: baremetal_inspect short_description: Explicitly triggers baremetal node introspection in ironic. author: OpenStack Ansible SIG description: - - Requests Ironic to set a node into inspect state in order to collect metadata regarding the node. - This command may be out of band or in-band depending on the ironic driver configuration. - This is only possible on nodes in 'manageable' and 'available' state. + - Requests Ironic to set a node into inspect state in order to collect + metadata regarding the node. This command may be out of band or in-band + depending on the ironic driver configuration. This is only possible on + nodes in 'manageable' and 'available' state. options: mac: description: - unique mac address that is used to attempt to identify the host. type: str - uuid: - description: - - globally unique identifier (UUID) to identify the host. - type: str name: description: - - unique name identifier to identify the host in Ironic. + - Name or id of the node to inspect. + - Mutually exclusive with I(mac) type: str - ironic_url: - description: - - If noauth mode is utilized, this is required to be set to the endpoint URL for the Ironic API. - Use with "auth" and "auth_type" settings set to None. - type: str - timeout: - description: - - A timeout in seconds to tell the role to wait for the node to complete introspection if wait is set to True. - default: 1200 - type: int + aliases: [id, uuid] requirements: - "python >= 3.6" @@ -46,27 +35,260 @@ extends_documentation_fragment: ''' RETURN = ''' -ansible_facts: - description: Dictionary of new facts representing discovered properties of the node.. - returned: changed - type: complex - contains: +node: + description: A dictionary describing the node after inspection + returned: changed + type: dict + contains: + allocation_id: + description: The UUID of the allocation associated with the node. + type: str + bios_interface: + description: The bios interface to be used for this node. + type: str + boot_interface: + description: The boot interface for a Node, e.g. "pxe". + type: str + boot_mode: + description: The current boot mode state (uefi/bios). + type: str + chassis_id: + description: UUID of the chassis associated with this Node. + type: str + clean_step: + description: | + The current clean step. Introduced with the cleaning feature. + type: str + conductor: + description: The conductor currently servicing a node. + type: str + conductor_group: + description: The conductor group for a node. + type: str + console_interface: + description: Console interface to use when working with serial console. + type: str + sample: no-console + created_at: + description: Timestamp at which the node was last updated. + type: str + deploy_interface: + description: The deploy interface for a node + type: str + sample: iscsi + deploy_step: + description: The current deploy step. + type: str + driver: + description: The name of the driver. + type: str + driver_info: + description: | + All the metadata required by the driver to manage this Node. List + of fields varies between drivers. + type: dict + driver_internal_info: + description: Internal metadata set and stored by the Node's driver. + type: dict + extra: + description: A set of one or more arbitrary metadata key and value pairs. + type: dict + fault: + description: | + The fault indicates the active fault detected by ironic, typically the + Node is in "maintenance mode". None means no fault has been detected by + ironic. "power failure" indicates ironic failed to retrieve power state + from this node. There are other possible types, e.g., "clean failure" + and "rescue abort failure". + type: str + id: + description: The UUID for the resource. + type: str + inspect_interface: + description: The interface used for node inspection. + type: str + sample: no-inspect + instance_id: + description: UUID of the Nova instance associated with this Node. + type: str + instance_info: + description: | + Information used to customize the deployed image. May include root + partition size, a base 64 encoded config drive, and other metadata. + Note that this field is erased automatically when the instance is + deleted (this is done by requesting the Node provision state be changed + to DELETED). + type: dict + is_automated_clean_enabled: + description: Override enabling of automated cleaning. + type: bool + is_console_enabled: + description: | + Indicates whether console access is enabled or disabled on this node. + type: bool + is_maintenance: + description: | + Whether or not this Node is currently in "maintenance mode". Setting + a Node into maintenance mode removes it from the available resource + pool and halts some internal automation. This can happen manually (eg, + via an API request) or automatically when Ironic detects a hardware + fault that prevents communication with the machine. + type: bool + is_protected: + description: | + Whether the node is protected from undeploying, rebuilding and + deletion. + type: bool + is_retired: + description: Whether the node is marked for retirement. + type: bool + is_secure_boot: + description: | + Whether the node is currently booted with secure boot turned on. + type: bool + last_error: + description: | + Any error from the most recent (last) transaction that started but + failed to finish. + type: str + links: + description: | + A list of relative links. Includes the self and bookmark links. + type: list + maintenance_reason: + description: | + User-settable description of the reason why this Node was placed into + maintenance mode. + type: str + management_interface: + description: Interface for out-of-band node management. + type: str + sample: ipmitool + name: + description: | + Human-readable identifier for the Node resource. Certain words are + reserved. + type: str + network_interface: + description: | + Which Network Interface provider to use when plumbing the network + connections for this Node. + type: str + owner: + description: A string or UUID of the tenant who owns the object. + type: str + port_groups: + description: Links to the collection of portgroups on this node. + type: list + ports: + description: Links to the collection of ports on this node + type: list + power_interface: + description: Interface used for performing power actions on the node. + type: str + sample: ipmitool + power_state: + description: | + The current power state of this Node. Usually, "power on" or "power + off", but may be "None" if Ironic is unable to determine the power + state (eg, due to hardware failure). + type: str + properties: + description: Properties of the node as found by inspection + type: dict + contains: memory_mb: - description: Amount of node memory as updated in the node properties - type: str - sample: "1024" + description: Amount of node memory as updated in the node properties + type: str + sample: "1024" cpu_arch: - description: Detected CPU architecture type - type: str - sample: "x86_64" + description: Detected CPU architecture type + type: str + sample: "x86_64" local_gb: - description: Total size of local disk storage as updated in node properties. - type: str - sample: "10" + description: | + Total size of local disk storage as updated in node properties. + type: str + sample: "10" cpus: - description: Count of cpu cores defined in the updated node properties. - type: str - sample: "1" + description: | + Count of cpu cores defined in the updated node properties. + type: str + sample: "1" + protected_reason: + description: The reason the node is marked as protected. + type: str + provision_state: + description: The current provisioning state of this Node. + type: str + raid_config: + description: | + Represents the current RAID configuration of the node. Introduced with + the cleaning feature. + type: dict + raid_interface: + description: Interface used for configuring RAID on this node. + type: str + sample: no-raid + rescue_interface: + description: The interface used for node rescue. + type: str + sample: no-rescue + reservation: + description: | + The name of an Ironic Conductor host which is holding a lock on this + node, if a lock is held. Usually "null", but this field can be useful + for debugging. + type: str + resource_class: + description: | + A string which can be used by external schedulers to identify this + Node as a unit of a specific type of resource. + type: str + retired_reason: + description: TODO + type: str + states: + description: | + Links to the collection of states. Note that this resource is also + used to request state transitions. + type: list + storage_interface: + description: | + Interface used for attaching and detaching volumes on this node, e.g. + "cinder". + type: str + target_power_state: + description: | + If a power state transition has been requested, this field represents + the requested (ie, "target") state, either "power on" or "power off". + type: str + target_provision_state: + description: | + If a provisioning action has been requested, this field represents + the requested (ie, "target") state. Note that a Node may go through + several states during its transition to this target state. For + instance, when requesting an instance be deployed to an AVAILABLE + Node, the Node may go through the following state change progression: + AVAILABLE -> DEPLOYING -> DEPLOYWAIT -> DEPLOYING -> ACTIVE. + type: str + target_raid_config: + description: | + Represents the requested RAID configuration of the node, which will + be applied when the Node next transitions through the CLEANING state. + Introduced with the cleaning feature. + type: dict + traits: + description: List of traits for this node. + type: list + updated_at: + description: TODO + type: str + vendor_interface: + description: | + Interface for vendor-specific functionality on this node, e.g. + "no-vendor". + type: str ''' EXAMPLES = ''' @@ -75,58 +297,50 @@ EXAMPLES = ''' name: "testnode1" ''' -from ansible_collections.openstack.cloud.plugins.module_utils.ironic import ( - IronicModule, - ironic_argument_spec, -) from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( - openstack_module_kwargs, - openstack_cloud_from_module + OpenStackModule ) -def _choose_id_value(module): - if module.params['uuid']: - return module.params['uuid'] - if module.params['name']: - return module.params['name'] - return None +class BaremetalInspectModule(OpenStackModule): + argument_spec = dict( + name=dict(aliases=['uuid', 'id']), + mac=dict(), + ) + + module_kwargs = dict( + mutually_exclusive=[ + ('name', 'mac'), + ], + required_one_of=[ + ('name', 'mac'), + ], + ) + + def run(self): + node_name_or_id = self.params['name'] + node = None + if node_name_or_id is not None: + node = self.conn.baremetal.find_node(node_name_or_id) + else: + node = self.conn.get_machine_by_mac(self.params['mac']) + + if node is None: + self.fail_json(msg="node not found.") + + node = self.conn.inspect_machine(node['id'], + wait=self.params['wait'], + timeout=self.params['timeout']) + node = node.to_dict(computed=False) + # TODO(TheJulia): diff properties, ?and ports? and determine + # if a change occurred. In theory, the node is always changed + # if introspection is able to update the record. + self.exit_json(changed=True, node=node) def main(): - argument_spec = ironic_argument_spec( - uuid=dict(), - name=dict(), - mac=dict(), - timeout=dict(default=1200, type='int'), - ) - module_kwargs = openstack_module_kwargs() - module = IronicModule(argument_spec, **module_kwargs) - - sdk, cloud = openstack_cloud_from_module(module) - try: - if module.params['name'] or module.params['uuid']: - server = cloud.get_machine(_choose_id_value(module)) - elif module.params['mac']: - server = cloud.get_machine_by_mac(module.params['mac']) - else: - module.fail_json(msg="The worlds did not align, " - "the host was not found as " - "no name, uuid, or mac was " - "defined.") - if server: - cloud.inspect_machine(server['uuid'], module.params['wait']) - # TODO(TheJulia): diff properties, ?and ports? and determine - # if a change occurred. In theory, the node is always changed - # if introspection is able to update the record. - module.exit_json(changed=True, - ansible_facts=server['properties']) - - else: - module.fail_json(msg="node not found.") - - except sdk.exceptions.OpenStackCloudException as e: - module.fail_json(msg=str(e)) + module = BaremetalInspectModule() + module() if __name__ == "__main__":