diff options
55 files changed, 625 insertions, 390 deletions
diff --git a/api-ref/source/baremetal-api-v1-chassis.inc b/api-ref/source/baremetal-api-v1-chassis.inc index adf4b92ac..02d13c02d 100644 --- a/api-ref/source/baremetal-api-v1-chassis.inc +++ b/api-ref/source/baremetal-api-v1-chassis.inc @@ -167,7 +167,7 @@ Request .. rest_parameters:: parameters.yaml - - chassis: req_chassis + - uuid: req_uuid - description: req_description - extra: req_extra @@ -228,10 +228,13 @@ Response Parameters .. rest_parameters:: parameters.yaml - - uuid: uuid - - chassis: chassis - description: description + - links: links - extra: extra + - created_at: created_at + - updated_at: updated_at + - nodes: nodes + - uuid: uuid Response Example ---------------- diff --git a/api-ref/source/baremetal-api-v1-deploy-templates.inc b/api-ref/source/baremetal-api-v1-deploy-templates.inc index 96e28341e..286e13bda 100644 --- a/api-ref/source/baremetal-api-v1-deploy-templates.inc +++ b/api-ref/source/baremetal-api-v1-deploy-templates.inc @@ -36,6 +36,16 @@ Request - uuid: req_uuid - extra: req_extra +Request Step +------------ + +.. rest_parameters:: parameters.yaml + + - interface: deploy_template_step_interface + - step: deploy_template_step_step + - args: deploy_template_step_args + - priority: deploy_template_step_priority + Request Example --------------- diff --git a/api-ref/source/baremetal-api-v1-nodes-vifs.inc b/api-ref/source/baremetal-api-v1-nodes-vifs.inc index 22904d404..98f921bb1 100644 --- a/api-ref/source/baremetal-api-v1-nodes-vifs.inc +++ b/api-ref/source/baremetal-api-v1-nodes-vifs.inc @@ -60,6 +60,8 @@ Request .. rest_parameters:: parameters.yaml - id: req_node_vif_ident + - port_uuid: req_node_vif_port_uuid + - portgroup_uuid: req_node_vif_portgroup_uuid - node_ident: node_ident **Example request to attach a VIF to a Node:** diff --git a/api-ref/source/baremetal-api-v1-nodes.inc b/api-ref/source/baremetal-api-v1-nodes.inc index 526f6e0d2..9aa8482fe 100644 --- a/api-ref/source/baremetal-api-v1-nodes.inc +++ b/api-ref/source/baremetal-api-v1-nodes.inc @@ -113,28 +113,40 @@ Request .. rest_parameters:: parameters.yaml - - boot_interface: req_boot_interface - - conductor_group: req_conductor_group - - console_interface: req_console_interface - - deploy_interface: req_deploy_interface - - driver_info: req_driver_info - - driver: req_driver_name - - extra: req_extra - - inspect_interface: req_inspect_interface - - management_interface: req_management_interface - - name: node_name - - network_interface: req_network_interface - - power_interface: req_power_interface - - properties: req_properties - - raid_interface: req_raid_interface - - rescue_interface: req_rescue_interface - - resource_class: req_resource_class_create - - storage_interface: req_storage_interface - - uuid: req_uuid - - vendor_interface: req_vendor_interface - - owner: owner - - description: n_description - - lessee: lessee + - boot_interface: req_boot_interface + - conductor_group: req_conductor_group + - console_interface: req_console_interface + - deploy_interface: req_deploy_interface + - driver_info: req_driver_info + - driver: req_driver_name + - extra: req_extra + - inspect_interface: req_inspect_interface + - management_interface: req_management_interface + - name: node_name + - network_interface: req_network_interface + - power_interface: req_power_interface + - properties: req_properties + - raid_interface: req_raid_interface + - rescue_interface: req_rescue_interface + - resource_class: req_resource_class_create + - storage_interface: req_storage_interface + - uuid: req_uuid + - vendor_interface: req_vendor_interface + - owner: owner + - description: req_n_description + - lessee: lessee + - automated_clean: req_automated_clean + - bios_interface: req_bios_interface + - chassis_uuid: req_chassis_uuid + - instance_info: req_instance_info + - instance_uuid: req_instance_uuid + - maintenance: req_maintenance + - maintenance_reason: maintenance_reason + - network_data: network_data + - protected: protected + - protected_reason: protected_reason + - retired: retired + - retired_reason: retired_reason **Example Node creation request with a dynamic driver:** @@ -208,6 +220,11 @@ microversion 1.48. - lessee: lessee - description: n_description - allocation_uuid: allocation_uuid + - automated_clean: automated_clean + - bios_interface: bios_interface + - network_data: network_data + - retired: retired + - retired_reason: retired_reason **Example JSON representation of a Node:** @@ -562,6 +579,7 @@ Response - description: n_description - conductor: conductor - allocation_uuid: allocation_uuid + - network_data: network_data **Example JSON representation of a Node:** @@ -658,6 +676,7 @@ Response - description: n_description - conductor: conductor - allocation_uuid: allocation_uuid + - network_data: network_data **Example JSON representation of a Node:** diff --git a/api-ref/source/baremetal-api-v1-portgroups.inc b/api-ref/source/baremetal-api-v1-portgroups.inc index d558743d3..3b2489222 100644 --- a/api-ref/source/baremetal-api-v1-portgroups.inc +++ b/api-ref/source/baremetal-api-v1-portgroups.inc @@ -88,7 +88,12 @@ Request - node_uuid: req_node_uuid - address: req_portgroup_address - - name: portgroup_name + - name: req_portgroup_name + - mode: req_portgroup_mode + - standalone_ports_supported: req_standalone_ports_supported + - properties: req_portgroup_properties + - extra: req_extra + - uuid: req_uuid **Example Portgroup creation request:** diff --git a/api-ref/source/baremetal-api-v1-ports.inc b/api-ref/source/baremetal-api-v1-ports.inc index b7ea31c90..f40d13391 100644 --- a/api-ref/source/baremetal-api-v1-ports.inc +++ b/api-ref/source/baremetal-api-v1-ports.inc @@ -121,6 +121,7 @@ Request - physical_network: req_physical_network - extra: req_extra - is_smartnic: req_is_smartnic + - uuid: req_uuid **Example Port creation request:** diff --git a/api-ref/source/baremetal-api-v1-volume.inc b/api-ref/source/baremetal-api-v1-volume.inc index 203af581b..e71b639ea 100644 --- a/api-ref/source/baremetal-api-v1-volume.inc +++ b/api-ref/source/baremetal-api-v1-volume.inc @@ -118,6 +118,7 @@ Request - type: volume_connector_type - connector_id: volume_connector_connector_id - extra: req_extra + - uuid: req_uuid **Example Volume connector creation request:** @@ -322,10 +323,11 @@ Request - node_uuid: req_node_uuid - volume_type: volume_target_volume_type - - properties: volume_target_properties + - properties: req_volume_target_properties - boot_index: volume_target_boot_index - volume_id: volume_target_volume_id - extra: req_extra + - uuid: req_uuid **Example Volume target creation request:** diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 0d3c8b388..882223d6f 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -493,6 +493,18 @@ allocation_uuid: in: body required: true type: string +automated_clean: + description: | + Indicates whether the node will perform automated clean or not. + in: body + required: true + type: boolean +bios_interface: + description: | + The bios interface to be used for this node. + in: body + required: true + type: string bios_setting_name: description: | The name of a Bios setting for a Node, eg. "virtualization". @@ -721,18 +733,35 @@ deploy_template_name: in: body required: true type: string +deploy_template_step_args: + description: | + A dictionary of arguments that are passed to the deploy step method. + in: body + required: true + type: object +deploy_template_step_interface: + description: | + The name of the driver interface. + in: body + required: true + type: string +deploy_template_step_priority: + description: | + A non-negative integer priority for the step. A value of ``0`` will + disable that step. + in: body + required: true + type: integer +deploy_template_step_step: + description: | + The name of the deploy step method on the driver interface. + in: body + required: true + type: string deploy_template_steps: description: | - The deploy steps of the deploy template. Must be a list containing at least - one deploy step. - - A deploy step is a dictionary with required keys ``interface``, ``step``, - ``args``, and ``priority``. The value for ``interface`` is the name of the - driver interface. The value for ``step`` is the name of the deploy step - method on the driver interface. The value for ``args`` is a dictionary of - arguments that are passed to the deploy step method. The value for - ``priority`` is a non-negative integer priority for the step. A value of - ``0`` for ``priority`` will disable that step. + The deploy steps of the deploy template. Must be a list of dictionaries + containing at least one deploy step. See `Request Step`_ for step parameters. in: body required: true type: array @@ -1079,8 +1108,10 @@ name: type: string network_data: description: | - Static network configuration for the node to eventually pass to node's - operating system. + Static network configuration in the OpenStack network data format to use + during deployment and cleaning. Requires a specially crafted ramdisk, see + `DHCP-less documentation + <https://docs.openstack.org/ironic/latest/admin/dhcp-less.html>`_. in: body required: false type: JSON @@ -1210,7 +1241,7 @@ portgroup_name: description: | Human-readable identifier for the Portgroup resource. May be undefined. in: body - required: false + required: true type: string portgroup_properties: description: | @@ -1342,6 +1373,18 @@ req_allocation_traits: in: body required: false type: array +req_automated_clean: + description: | + Indicates whether the node will perform automated clean or not. + in: body + required: false + type: boolean +req_bios_interface: + description: | + The bios interface to be used for this node. + in: body + required: false + type: string req_boot_device: description: | The boot device for a Node, eg. "pxe" or "disk". @@ -1367,6 +1410,12 @@ req_chassis: in: body required: true type: array +req_chassis_uuid: + description: | + UUID of the chassis associated with this Node. May be empty or None. + in: body + required: false + type: string req_conductor_group: description: | The conductor group for a node. Case-insensitive string up to 255 @@ -1425,6 +1474,21 @@ req_inspect_interface: in: body required: false type: string +req_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). + in: body + required: false + type: JSON +req_instance_uuid: + description: | + UUID of the Nova instance associated with this Node. + in: body + required: false + type: string req_is_smartnic: description: | Indicates whether the Port is a Smart NIC port. @@ -1441,12 +1505,28 @@ req_local_link_connection: in: body required: false type: JSON +req_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. + in: body + required: false + type: boolean req_management_interface: description: | Interface for out-of-band node management, e.g. "ipmitool". in: body required: false type: string +req_n_description: + description: | + Informational text about this node. + in: body + required: false + type: string req_network_interface: description: | Which Network Interface provider to use when plumbing the network @@ -1466,6 +1546,20 @@ req_node_vif_ident: in: body required: true type: string +req_node_vif_port_uuid: + description: | + The UUID of a port to attach the VIF to. Cannot be specified with + ``portgroup_uuid``. + in: body + required: false + type: string +req_node_vif_portgroup_uuid: + description: | + The UUID of a portgroup to attach the VIF to. Cannot be specified with + ``port_uuid``. + in: body + required: false + type: string req_persistent: description: | Whether the boot device should be set only for the next reboot, or @@ -1494,6 +1588,28 @@ req_portgroup_address: in: body required: false type: string +req_portgroup_mode: + description: | + Mode of the port group. For possible values, refer to + https://www.kernel.org/doc/Documentation/networking/bonding.txt. If not + specified in a request to create a port group, it will be set to the value + of the ``[DEFAULT]default_portgroup_mode`` configuration option. When set, + can not be removed from the port group. + in: body + required: false + type: string +req_portgroup_name: + description: | + Human-readable identifier for the Portgroup resource. May be undefined. + in: body + required: false + type: string +req_portgroup_properties: + description: | + Key/value properties related to the port group's configuration. + in: body + required: false + type: JSON req_portgroup_uuid: description: | UUID of the Portgroup this resource belongs to. @@ -1544,6 +1660,13 @@ req_resource_class_create: in: body required: false type: string +req_standalone_ports_supported: + description: | + Indicates whether ports that are members of this portgroup can be + used as stand-alone ports. + in: body + required: false + type: boolean req_storage_interface: description: | Interface used for attaching and detaching volumes on this node, e.g. @@ -1579,6 +1702,15 @@ req_vendor_interface: in: body required: false type: string +req_volume_target_properties: + description: | + A set of physical information of the volume such as the identifier + (eg. IQN) and LUN number of the volume. This information is used to connect + the node to the volume by the storage interface. The contents depend on the + volume type. + in: body + required: false + type: object requested_provision_state: description: | One of the provisioning verbs: manage, provide, inspect, clean, active, diff --git a/devstack/lib/ironic b/devstack/lib/ironic index 732d1d956..d1c98f597 100644 --- a/devstack/lib/ironic +++ b/devstack/lib/ironic @@ -316,7 +316,7 @@ if [[ -z "$IRONIC_DIB_RAMDISK_OPTIONS" ]]; then if [[ "$IRONIC_DIB_RAMDISK_OS" == "centos8" ]]; then # Adapt for DIB naming change IRONIC_DIB_RAMDISK_OS=centos - IRONIC_DIB_RAMDISK_RELEASE=8-stream + IRONIC_DIB_RAMDISK_RELEASE=8 fi IRONIC_DIB_RAMDISK_OPTIONS="$IRONIC_DIB_RAMDISK_OS" fi diff --git a/doc/source/admin/dhcp-less.rst b/doc/source/admin/dhcp-less.rst new file mode 100644 index 000000000..ab6e52ecf --- /dev/null +++ b/doc/source/admin/dhcp-less.rst @@ -0,0 +1,88 @@ +Layer 3 or DHCP-less ramdisk booting +==================================== + +Booting nodes via PXE, while universally supported, suffers from one +disadvantage: it requires a direct L2 connectivity between the node and the +control plane for DHCP. Using virtual media it is possible to avoid not only +the unreliable TFTP protocol, but DHCP altogether. + +When network data is provided for a node as explained below, the generated +virtual media ISO will also serve as a configdrive_, and the network data will +be stored in the standard OpenStack location. + +The simple-init_ element needs to be used when creating the deployment ramdisk. +The Glean_ tool will look for a media labeled as ``config-2``. If found, the +network information from it will be read, and the node's networking stack will +be configured accordingly. + +.. code-block:: console + + ironic-python-agent-builder -o /output/ramdisk \ + debian-minimal -e simple-init + +.. warning:: + The simple-init_ element is found to conflict to NetworkManager, which makes + this feature not operational with ramdisks based on CentOS, RHEL and Fedora. + The ``debian-minimal`` element seems to work correctly. + +.. note:: + If desired, some interfaces can still be configured to use DHCP. + +Hardware type support +--------------------- + +This feature is known to work with the following hardware types: + +* :doc:`Redfish </admin/drivers/redfish>` with ``redfish-virtual-media`` boot +* :doc:`iLO </admin/drivers/ilo>` with ``ilo-virtual-media`` boot + +Configuring network data +------------------------ + +When the Bare Metal service is running within OpenStack, no additional +configuration is required - the network configuration will be fetched from the +Network service. + +Alternatively, the user can build and pass network configuration in form of +a network_data_ JSON to a node via the ``network_data`` field. Node-based +configuration takes precedence over the configuration generated by the +Network service and also works in standalone mode. + +.. code-block:: bash + + baremetal node set --network-data ~/network_data.json <node> + +An example network data: + +.. code-block:: json + + { + "links": [ + { + "id": "port-92750f6c-60a9-4897-9cd1-090c5f361e18", + "type": "phy", + "ethernet_mac_address": "52:54:00:d3:6a:71" + } + ], + "networks": [ + { + "id": "network0", + "type": "ipv4", + "link": "port-92750f6c-60a9-4897-9cd1-090c5f361e18", + "ip_address": "192.168.122.42", + "netmask": "255.255.255.0", + "network_id": "network0", + "routes": [] + } + ], + "services": [] + } + +.. note:: + Some fields are redundant with the port information. We're looking into + simplifying the format, but currently all these fields are mandatory. + +.. _configdrive: https://docs.openstack.org/nova/queens/user/config-drive.html +.. _Glean: https://docs.openstack.org/infra/glean/ +.. _simple-init: https://docs.openstack.org/diskimage-builder/latest/elements/simple-init/README.html +.. _network_data: https://specs.openstack.org/openstack/nova-specs/specs/liberty/implemented/metadata-service-network-info.html diff --git a/doc/source/admin/drivers/ilo.rst b/doc/source/admin/drivers/ilo.rst index b1891c403..fe97d94e8 100644 --- a/doc/source/admin/drivers/ilo.rst +++ b/doc/source/admin/drivers/ilo.rst @@ -2164,7 +2164,7 @@ Layer 3 or DHCP-less ramdisk booting ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DHCP-less deploy is supported by ``ilo`` and ``ilo5`` hardware types. However it would work only with ilo-virtual-media boot interface. See -:ref:`dhcpless_booting` for more information. +:doc:`/admin/dhcp-less` for more information. .. _`ssacli documentation`: https://support.hpe.com/hpsc/doc/public/display?docId=c03909334 .. _`proliant-tools`: https://docs.openstack.org/diskimage-builder/latest/elements/proliant-tools/README.html diff --git a/doc/source/admin/drivers/redfish.rst b/doc/source/admin/drivers/redfish.rst index a2ef27bee..dc5451978 100644 --- a/doc/source/admin/drivers/redfish.rst +++ b/doc/source/admin/drivers/redfish.rst @@ -137,6 +137,8 @@ into the introspection ramdisk. node does not have local storage or the Redfish implementation does not support the required schema. In this case the property will be set to 0. +.. _redfish-virtual-media: + Virtual media boot ================== @@ -269,49 +271,11 @@ This initial interface does not support bootloader configuration parameter injection, as such the ``[instance_info]/kernel_append_params`` setting is ignored. Configuration drives are not supported yet. - -.. _`dhcpless_booting`: - Layer 3 or DHCP-less ramdisk booting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The DHCP used by PXE requires direct L2 connectivity between the node and the -service since it's a User Datagram Protocol (UDP) like other protocols used by -the PXE suite, there is no guarantee that packets will be delivered. - -One of the solutions is the reliance on virtual media boot capability coupled -with another feature of hardware type - its ability to provide -network configuration that is placed in the config-drive_ of the node, the -configuration follows the same schema that OpenStack Nova uses for the -``network_data.json``. The config drive filesystem information is on the IPA -ramdisk ISO image from which the node is booted. - -The Glean_ tool is available in the simple-init_ element that needs to be used -when creating the ramdisk image. The ironic ramdisk will probe all removable -media devices on the node in search of media labeled as `config-2`. If found, -this tool will consume static network configuration and set up node's -networking stack accordingly without calling out for DHCP. - -When ironic is running within OpenStack, no additional configuration is required -on the ironic side - config drive with ramdisk network configuration will be -collected from Networking service and written on the IPA ramdisk ISO. - -Alternatively, the user can build and pass node network configuration, in -form of a network_data_ JSON blob, to ironic node being managed via the -``--network-data`` CLI option. Node-based configuration takes precedence over -the configuration generated by the Network service. - -.. code-block:: bash - - baremetal node set \ - --network-data ~/network_data.json <node> - -Node-based configuration can be useful in standalone ironic deployment -scenario. - -.. note:: - - Make sure to use add the simple-init_ element when building the IPA ramdisk. +DHCP-less deploy is supported by the Redfish virtual media boot. See +:doc:`/admin/dhcp-less` for more information. Firmware update using manual cleaning ===================================== @@ -447,7 +411,3 @@ In the following example, the JSON is specified directly on the command line:: .. _Sushy: https://opendev.org/openstack/sushy .. _TLS: https://en.wikipedia.org/wiki/Transport_Layer_Security .. _ESP: https://wiki.ubuntu.com/EFIBootLoaders#Booting_from_EFI -.. _network_data: https://specs.openstack.org/openstack/nova-specs/specs/liberty/implemented/metadata-service-network-info.html -.. _config-drive: https://docs.openstack.org/nova/queens/user/config-drive.html -.. _Glean: https://docs.openstack.org/infra/glean/ -.. _simple-init: https://docs.openstack.org/diskimage-builder/latest/elements/simple-init/README.html diff --git a/doc/source/admin/index.rst b/doc/source/admin/index.rst index 09b1171da..c2bb92190 100644 --- a/doc/source/admin/index.rst +++ b/doc/source/admin/index.rst @@ -52,6 +52,7 @@ Advanced Topics Service State Reporting <gmr> Agent Token <agent-token> Deploying without BMC Credentials <agent-power> + Layer 3 or DHCP-less Ramdisk Booting <dhcp-less> .. toctree:: :hidden: diff --git a/doc/source/contributor/releasing.rst b/doc/source/contributor/releasing.rst index 44ec3616e..5d2da977c 100644 --- a/doc/source/contributor/releasing.rst +++ b/doc/source/contributor/releasing.rst @@ -191,19 +191,27 @@ following the next steps: minor (feature) or patch (bugfix). Note that in this case ``series`` is a code name (train, ussuri), not a - branch. + branch. That is also valid for the current development branch (master) that + takes the code name of the future stable release, for example if the future + stable release code name is wallaby, we need to use wallaby as ``series``. The ``--stable-branch argument`` is used only for branching in the end of a cycle, independent projects are not branched this way though. + The ``--intermediate-branch`` option is used to create an intermediate + bugfix branch following the + `new release model for ironic projects <https://specs.openstack.org/openstack/ironic-specs/specs/not-implemented/new-release-model.html>`_. + To propose the release, use the script to update the deliverable file, then commit the change, and propose it for review. - For example, to propose a minor release for ironic in the master branch use: + For example, to propose a minor release for ironic in the master branch + (current development branch), considering that the code name of the future + stable release is wallaby, use: .. code-block:: bash - tox -e venv -- new-release -v master ironic feature + tox -e venv -- new-release -v wallaby ironic feature Remember to use a meaningful topic, usually using the name of the deliverable, the new version and the branch, if applicable. diff --git a/doc/source/install/refarch/common.rst b/doc/source/install/refarch/common.rst index a1024f02c..0c1e99803 100644 --- a/doc/source/install/refarch/common.rst +++ b/doc/source/install/refarch/common.rst @@ -183,8 +183,8 @@ This default can be overridden by setting the ``boot_option`` capability on a node. See :ref:`local-boot-partition-images` for details. .. note:: - Currently, network boot is used by default. However, we plan on changing it - in the future, so it's safer to set the ``default_boot_option`` explicitly. + Currently, local boot is used by default. It's safer to set + the ``default_boot_option`` explicitly. .. _refarch-common-networking: diff --git a/ironic/api/controllers/v1/allocation.py b/ironic/api/controllers/v1/allocation.py index 037e2c643..14a7201fc 100644 --- a/ironic/api/controllers/v1/allocation.py +++ b/ironic/api/controllers/v1/allocation.py @@ -26,7 +26,6 @@ from ironic.api import method from ironic.common import args from ironic.common import exception from ironic.common.i18n import _ -from ironic.common import policy from ironic import objects METRICS = metrics_utils.get_metrics_logger(__name__) @@ -266,18 +265,17 @@ class AllocationsController(pecan.rest.RestController): return convert_with_links(rpc_allocation, fields=fields) def _authorize_create_allocation(self, allocation): - cdict = api.request.context.to_policy_values() try: - policy.authorize('baremetal:allocation:create', cdict, cdict) + api_utils.check_policy('baremetal:allocation:create') self._check_allowed_allocation_fields(allocation) except exception.HTTPForbidden: + cdict = api.request.context.to_policy_values() owner = cdict.get('project_id') if not owner or (allocation.get('owner') and owner != allocation.get('owner')): raise - policy.authorize('baremetal:allocation:create_restricted', - cdict, cdict) + api_utils.check_policy('baremetal:allocation:create_restricted') self._check_allowed_allocation_fields(allocation) allocation['owner'] = owner @@ -460,8 +458,7 @@ class NodeAllocationController(pecan.rest.RestController): @method.expose() @args.validate(fields=args.string_list) def get_all(self, fields=None): - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:allocation:get', cdict, cdict) + api_utils.check_policy('baremetal:allocation:get') result = self.inner._get_allocations_collection(self.parent_node_ident, fields=fields) @@ -476,8 +473,7 @@ class NodeAllocationController(pecan.rest.RestController): @method.expose(status_code=http_client.NO_CONTENT) def delete(self): context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:allocation:delete', cdict, cdict) + api_utils.check_policy('baremetal:allocation:delete') rpc_node = api_utils.get_rpc_node_with_suffix(self.parent_node_ident) allocations = objects.Allocation.list( diff --git a/ironic/api/controllers/v1/bios.py b/ironic/api/controllers/v1/bios.py index be6743d70..fd35689e2 100644 --- a/ironic/api/controllers/v1/bios.py +++ b/ironic/api/controllers/v1/bios.py @@ -21,7 +21,6 @@ from ironic.api.controllers.v1 import utils as api_utils from ironic.api import method from ironic.common import args from ironic.common import exception -from ironic.common import policy from ironic import objects METRICS = metrics_utils.get_metrics_logger(__name__) @@ -57,8 +56,7 @@ class NodeBiosController(rest.RestController): @method.expose() def get_all(self): """List node bios settings.""" - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:node:bios:get', cdict, cdict) + api_utils.check_policy('baremetal:node:bios:get') node = api_utils.get_rpc_node(self.node_ident) settings = objects.BIOSSettingList.get_by_node_id( @@ -73,8 +71,7 @@ class NodeBiosController(rest.RestController): :param setting_name: Logical name of the setting to retrieve. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:node:bios:get', cdict, cdict) + api_utils.check_policy('baremetal:node:bios:get') node = api_utils.get_rpc_node(self.node_ident) try: diff --git a/ironic/api/controllers/v1/chassis.py b/ironic/api/controllers/v1/chassis.py index 03cf770c5..9c280fa58 100644 --- a/ironic/api/controllers/v1/chassis.py +++ b/ironic/api/controllers/v1/chassis.py @@ -29,7 +29,6 @@ from ironic.api import method from ironic.common import args from ironic.common import exception from ironic.common.i18n import _ -from ironic.common import policy from ironic import objects METRICS = metrics_utils.get_metrics_logger(__name__) @@ -157,8 +156,7 @@ class ChassisController(rest.RestController): :param fields: Optional, a list with a specified set of fields of the resource to be returned. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:chassis:get', cdict, cdict) + api_utils.check_policy('baremetal:chassis:get') api_utils.check_allow_specify_fields(fields) @@ -183,8 +181,7 @@ class ChassisController(rest.RestController): :param sort_key: column to sort results by. Default: id. :param sort_dir: direction to sort. "asc" or "desc". Default: asc. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:chassis:get', cdict, cdict) + api_utils.check_policy('baremetal:chassis:get') # /detail should only work against collections parent = api.request.path.split('/')[:-1][-1] @@ -205,8 +202,7 @@ class ChassisController(rest.RestController): :param fields: Optional, a list with a specified set of fields of the resource to be returned. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:chassis:get', cdict, cdict) + api_utils.check_policy('baremetal:chassis:get') api_utils.check_allow_specify_fields(fields) rpc_chassis = objects.Chassis.get_by_uuid(api.request.context, @@ -223,8 +219,7 @@ class ChassisController(rest.RestController): :param chassis: a chassis within the request body. """ context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:chassis:create', cdict, cdict) + api_utils.check_policy('baremetal:chassis:create') # NOTE(yuriyz): UUID is mandatory for notifications payload if not chassis.get('uuid'): @@ -250,8 +245,7 @@ class ChassisController(rest.RestController): :param patch: a json PATCH document to apply to this chassis. """ context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:chassis:update', cdict, cdict) + api_utils.check_policy('baremetal:chassis:update') api_utils.patch_validate_allowed_fields( patch, CHASSIS_SCHEMA['properties']) @@ -282,8 +276,7 @@ class ChassisController(rest.RestController): :param chassis_uuid: UUID of a chassis. """ context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:chassis:delete', cdict, cdict) + api_utils.check_policy('baremetal:chassis:delete') rpc_chassis = objects.Chassis.get_by_uuid(context, chassis_uuid) notify.emit_start_notification(context, rpc_chassis, 'delete') diff --git a/ironic/api/controllers/v1/conductor.py b/ironic/api/controllers/v1/conductor.py index c6e55a38f..61cbba78a 100644 --- a/ironic/api/controllers/v1/conductor.py +++ b/ironic/api/controllers/v1/conductor.py @@ -22,7 +22,6 @@ from ironic.api import method from ironic.common import args from ironic.common import exception from ironic.common.i18n import _ -from ironic.common import policy import ironic.conf from ironic import objects @@ -122,8 +121,7 @@ class ConductorsController(rest.RestController): :param detail: Optional, boolean to indicate whether retrieve a list of conductors with detail. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:conductor:get', cdict, cdict) + api_utils.check_policy('baremetal:conductor:get') if not api_utils.allow_expose_conductors(): raise exception.NotFound() @@ -149,8 +147,7 @@ class ConductorsController(rest.RestController): :param fields: Optional, a list with a specified set of fields of the resource to be returned. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:conductor:get', cdict, cdict) + api_utils.check_policy('baremetal:conductor:get') if not api_utils.allow_expose_conductors(): raise exception.NotFound() diff --git a/ironic/api/controllers/v1/driver.py b/ironic/api/controllers/v1/driver.py index d3d920cc4..9027e4638 100644 --- a/ironic/api/controllers/v1/driver.py +++ b/ironic/api/controllers/v1/driver.py @@ -25,7 +25,6 @@ from ironic.api import method from ironic.common import args from ironic.common import exception from ironic.common.i18n import _ -from ironic.common import policy from ironic.drivers import base as driver_base @@ -206,8 +205,7 @@ class DriverPassthruController(rest.RestController): :raises: DriverNotFound if the driver name is invalid or the driver cannot be loaded. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:driver:vendor_passthru', cdict, cdict) + api_utils.check_policy('baremetal:driver:vendor_passthru') if driver_name not in _VENDOR_METHODS: topic = api.request.rpcapi.get_topic_for_driver(driver_name) @@ -230,8 +228,7 @@ class DriverPassthruController(rest.RestController): :param data: body of data to supply to the specified method. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:driver:vendor_passthru', cdict, cdict) + api_utils.check_policy('baremetal:driver:vendor_passthru') topic = api.request.rpcapi.get_topic_for_driver(driver_name) resp = api_utils.vendor_passthru(driver_name, method, topic, @@ -262,9 +259,8 @@ class DriverRaidController(rest.RestController): :raises: DriverNotFound, if driver is not loaded on any of the conductors. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:driver:get_raid_logical_disk_properties', - cdict, cdict) + api_utils.check_policy( + 'baremetal:driver:get_raid_logical_disk_properties') if not api_utils.allow_raid_config(): raise exception.NotAcceptable() @@ -305,9 +301,7 @@ class DriversController(rest.RestController): # will break from a single-line doc string. # This is a result of a bug in sphinxcontrib-pecanwsme # https://github.com/dreamhost/sphinxcontrib-pecanwsme/issues/8 - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:driver:get', cdict, cdict) - + api_utils.check_policy('baremetal:driver:get') api_utils.check_allow_driver_detail(detail) api_utils.check_allow_filter_driver_type(type) if type not in (None, 'classic', 'dynamic'): @@ -332,8 +326,7 @@ class DriversController(rest.RestController): # retrieving a list of drivers using the current sqlalchemy schema, but # this path must be exposed for Pecan to route any paths we might # choose to expose below it. - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:driver:get', cdict, cdict) + api_utils.check_policy('baremetal:driver:get') hw_type_dict = api.request.dbapi.get_active_hardware_type_dict() for name, hosts in hw_type_dict.items(): @@ -355,8 +348,7 @@ class DriversController(rest.RestController): :raises: DriverNotFound (HTTP 404) if the driver name is invalid or the driver cannot be loaded. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:driver:get_properties', cdict, cdict) + api_utils.check_policy('baremetal:driver:get_properties') if driver_name not in _DRIVER_PROPERTIES: topic = api.request.rpcapi.get_topic_for_driver(driver_name) diff --git a/ironic/api/controllers/v1/event.py b/ironic/api/controllers/v1/event.py index 8e17d3bfa..ed6164b4f 100644 --- a/ironic/api/controllers/v1/event.py +++ b/ironic/api/controllers/v1/event.py @@ -16,12 +16,10 @@ from ironic_lib import metrics_utils from oslo_log import log import pecan -from ironic import api from ironic.api.controllers.v1 import utils as api_utils from ironic.api import method from ironic.common import args from ironic.common import exception -from ironic.common import policy METRICS = metrics_utils.get_metrics_logger(__name__) @@ -104,7 +102,6 @@ class EventsController(pecan.rest.RestController): def post(self, evts): if not api_utils.allow_expose_events(): raise exception.NotFound() - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:events:post', cdict, cdict) + api_utils.check_policy('baremetal:events:post') for e in evts['events']: LOG.debug("Received external event: %s", e) diff --git a/ironic/api/controllers/v1/node.py b/ironic/api/controllers/v1/node.py index d07561f9c..e6b444c30 100644 --- a/ironic/api/controllers/v1/node.py +++ b/ironic/api/controllers/v1/node.py @@ -506,8 +506,7 @@ class IndicatorController(rest.RestController): mod:`ironic.common.indicator_states`. """ - cdict = pecan.request.context.to_policy_values() - policy.authorize('baremetal:node:set_indicator_state', cdict, cdict) + api_utils.check_policy('baremetal:node:set_indicator_state') rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) @@ -529,8 +528,7 @@ class IndicatorController(rest.RestController): :returns: a dict with the "state" key and one of mod:`ironic.common.indicator_states` as a value. """ - cdict = pecan.request.context.to_policy_values() - policy.authorize('baremetal:node:get_indicator_state', cdict, cdict) + api_utils.check_policy('baremetal:node:get_indicator_state') rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) @@ -553,8 +551,7 @@ class IndicatorController(rest.RestController): (from `get_supported_indicators`) as values. """ - cdict = pecan.request.context.to_policy_values() - policy.authorize('baremetal:node:get_indicator_state', cdict, cdict) + api_utils.check_policy('baremetal:node:get_indicator_state') rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) @@ -1995,8 +1992,7 @@ class NodesController(rest.RestController): raise exception.OperationNotPermitted() context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:node:create', cdict, cdict) + api_utils.check_policy('baremetal:node:create') reject_fields_in_newer_versions(node) diff --git a/ironic/api/controllers/v1/port.py b/ironic/api/controllers/v1/port.py index 53be406e8..f4480ef7b 100644 --- a/ironic/api/controllers/v1/port.py +++ b/ironic/api/controllers/v1/port.py @@ -30,7 +30,6 @@ from ironic.api import method from ironic.common import args from ironic.common import exception from ironic.common.i18n import _ -from ironic.common import policy from ironic.common import states as ir_states from ironic import objects @@ -501,8 +500,7 @@ class PortsController(rest.RestController): raise exception.OperationNotPermitted() context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:port:create', cdict, cdict) + api_utils.check_policy('baremetal:port:create') # NOTE(lucasagomes): Create the node_id attribute on-the-fly # to satisfy the api -> rpc object diff --git a/ironic/api/controllers/v1/portgroup.py b/ironic/api/controllers/v1/portgroup.py index 6e57ff78f..077e9ab71 100644 --- a/ironic/api/controllers/v1/portgroup.py +++ b/ironic/api/controllers/v1/portgroup.py @@ -27,7 +27,6 @@ from ironic.api import method from ironic.common import args from ironic.common import exception from ironic.common.i18n import _ -from ironic.common import policy from ironic.common import states as ir_states from ironic import objects @@ -269,8 +268,7 @@ class PortgroupsController(pecan.rest.RestController): if not api_utils.allow_portgroups(): raise exception.NotFound() - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:portgroup:get', cdict, cdict) + api_utils.check_policy('baremetal:portgroup:get') api_utils.check_allowed_portgroup_fields(fields) api_utils.check_allowed_portgroup_fields([sort_key]) @@ -308,8 +306,7 @@ class PortgroupsController(pecan.rest.RestController): if not api_utils.allow_portgroups(): raise exception.NotFound() - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:portgroup:get', cdict, cdict) + api_utils.check_policy('baremetal:portgroup:get') api_utils.check_allowed_portgroup_fields([sort_key]) # NOTE: /detail should only work against collections @@ -335,8 +332,7 @@ class PortgroupsController(pecan.rest.RestController): if not api_utils.allow_portgroups(): raise exception.NotFound() - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:portgroup:get', cdict, cdict) + api_utils.check_policy('baremetal:portgroup:get') if self.parent_node_ident: raise exception.OperationNotPermitted() @@ -360,8 +356,7 @@ class PortgroupsController(pecan.rest.RestController): raise exception.NotFound() context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:portgroup:create', cdict, cdict) + api_utils.check_policy('baremetal:portgroup:create') if self.parent_node_ident: raise exception.OperationNotPermitted() @@ -414,8 +409,7 @@ class PortgroupsController(pecan.rest.RestController): raise exception.NotFound() context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:portgroup:update', cdict, cdict) + api_utils.check_policy('baremetal:portgroup:update') if self.parent_node_ident: raise exception.OperationNotPermitted() @@ -511,8 +505,7 @@ class PortgroupsController(pecan.rest.RestController): raise exception.NotFound() context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:portgroup:delete', cdict, cdict) + api_utils.check_policy('baremetal:portgroup:delete') if self.parent_node_ident: raise exception.OperationNotPermitted() diff --git a/ironic/api/controllers/v1/ramdisk.py b/ironic/api/controllers/v1/ramdisk.py index 705389534..46cc9fa53 100644 --- a/ironic/api/controllers/v1/ramdisk.py +++ b/ironic/api/controllers/v1/ramdisk.py @@ -25,7 +25,6 @@ from ironic.api import method from ironic.common import args from ironic.common import exception from ironic.common.i18n import _ -from ironic.common import policy from ironic.common import states from ironic.common import utils from ironic import objects @@ -95,8 +94,7 @@ class LookupController(rest.RestController): if not api_utils.allow_ramdisk_endpoints(): raise exception.NotFound() - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:driver:ipa_lookup', cdict, cdict) + api_utils.check_policy('baremetal:driver:ipa_lookup') # Validate the list of MAC addresses if addresses is None: @@ -187,8 +185,7 @@ class HeartbeatController(rest.RestController): raise exception.InvalidParameterValue( _('Field "agent_version" not recognised')) - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:node:ipa_heartbeat', cdict, cdict) + api_utils.check_policy('baremetal:node:ipa_heartbeat') if (agent_verify_ca is not None and not api_utils.allow_verify_ca_in_heartbeat()): diff --git a/ironic/api/controllers/v1/volume.py b/ironic/api/controllers/v1/volume.py index 5c4e85542..11e2744d7 100644 --- a/ironic/api/controllers/v1/volume.py +++ b/ironic/api/controllers/v1/volume.py @@ -24,7 +24,6 @@ from ironic.api.controllers.v1 import volume_connector from ironic.api.controllers.v1 import volume_target from ironic.api import method from ironic.common import exception -from ironic.common import policy def convert(node_ident=None): @@ -72,8 +71,7 @@ class VolumeController(rest.RestController): if not api_utils.allow_volume(): raise exception.NotFound() - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:volume:get', cdict, cdict) + api_utils.check_policy('baremetal:volume:get') return convert(self.parent_node_ident) diff --git a/ironic/api/controllers/v1/volume_connector.py b/ironic/api/controllers/v1/volume_connector.py index eb653a906..0a6ffa4d5 100644 --- a/ironic/api/controllers/v1/volume_connector.py +++ b/ironic/api/controllers/v1/volume_connector.py @@ -27,7 +27,6 @@ from ironic.api import method from ironic.common import args from ironic.common import exception from ironic.common.i18n import _ -from ironic.common import policy from ironic import objects METRICS = metrics_utils.get_metrics_logger(__name__) @@ -180,8 +179,7 @@ class VolumeConnectorsController(rest.RestController): :raises: InvalidParameterValue if sort key is invalid for sorting. :raises: InvalidParameterValue if both fields and detail are specified. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:volume:get', cdict, cdict) + api_utils.check_policy('baremetal:volume:get') if fields is None and not detail: fields = _DEFAULT_RETURN_FIELDS @@ -212,8 +210,7 @@ class VolumeConnectorsController(rest.RestController): :raises: VolumeConnectorNotFound if no volume connector exists with the specified UUID. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:volume:get', cdict, cdict) + api_utils.check_policy('baremetal:volume:get') if self.parent_node_ident: raise exception.OperationNotPermitted() @@ -241,8 +238,7 @@ class VolumeConnectorsController(rest.RestController): same UUID already exists """ context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:volume:create', cdict, cdict) + api_utils.check_policy('baremetal:volume:create') if self.parent_node_ident: raise exception.OperationNotPermitted() @@ -298,8 +294,7 @@ class VolumeConnectorsController(rest.RestController): volume connector is not powered off. """ context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:volume:update', cdict, cdict) + api_utils.check_policy('baremetal:volume:update') if self.parent_node_ident: raise exception.OperationNotPermitted() @@ -375,8 +370,7 @@ class VolumeConnectorsController(rest.RestController): volume connector is not powered off. """ context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:volume:delete', cdict, cdict) + api_utils.check_policy('baremetal:volume:delete') if self.parent_node_ident: raise exception.OperationNotPermitted() diff --git a/ironic/api/controllers/v1/volume_target.py b/ironic/api/controllers/v1/volume_target.py index 483038163..9fa5f8909 100644 --- a/ironic/api/controllers/v1/volume_target.py +++ b/ironic/api/controllers/v1/volume_target.py @@ -27,7 +27,6 @@ from ironic.api import method from ironic.common import args from ironic.common import exception from ironic.common.i18n import _ -from ironic.common import policy from ironic import objects METRICS = metrics_utils.get_metrics_logger(__name__) @@ -189,8 +188,7 @@ class VolumeTargetsController(rest.RestController): :raises: InvalidParameterValue if sort key is invalid for sorting. :raises: InvalidParameterValue if both fields and detail are specified. """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:volume:get', cdict, cdict) + api_utils.check_policy('baremetal:volume:get') if fields is None and not detail: fields = _DEFAULT_RETURN_FIELDS @@ -222,8 +220,7 @@ class VolumeTargetsController(rest.RestController): node. :raises: VolumeTargetNotFound if no volume target with this UUID exists """ - cdict = api.request.context.to_policy_values() - policy.authorize('baremetal:volume:get', cdict, cdict) + api_utils.check_policy('baremetal:volume:get') if self.parent_node_ident: raise exception.OperationNotPermitted() @@ -251,8 +248,7 @@ class VolumeTargetsController(rest.RestController): UUID exists """ context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:volume:create', cdict, cdict) + api_utils.check_policy('baremetal:volume:create') if self.parent_node_ident: raise exception.OperationNotPermitted() @@ -305,8 +301,7 @@ class VolumeTargetsController(rest.RestController): volume target is not powered off. """ context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:volume:update', cdict, cdict) + api_utils.check_policy('baremetal:volume:update') if self.parent_node_ident: raise exception.OperationNotPermitted() @@ -379,8 +374,7 @@ class VolumeTargetsController(rest.RestController): volume target is not powered off. """ context = api.request.context - cdict = context.to_policy_values() - policy.authorize('baremetal:volume:delete', cdict, cdict) + api_utils.check_policy('baremetal:volume:delete') if self.parent_node_ident: raise exception.OperationNotPermitted() diff --git a/ironic/common/exception.py b/ironic/common/exception.py index ba636e154..6f9a7d331 100644 --- a/ironic/common/exception.py +++ b/ironic/common/exception.py @@ -77,6 +77,10 @@ class PortAlreadyExists(Conflict): _msg_fmt = _("A port with UUID %(uuid)s already exists.") +class PortDuplicateName(Conflict): + _msg_fmt = _("A port with name %(name)s already exists.") + + class PortgroupAlreadyExists(Conflict): _msg_fmt = _("A portgroup with UUID %(uuid)s already exists.") diff --git a/ironic/common/release_mappings.py b/ironic/common/release_mappings.py index 624e004ed..0e3d18992 100644 --- a/ironic/common/release_mappings.py +++ b/ironic/common/release_mappings.py @@ -265,7 +265,7 @@ RELEASE_MAPPING = { 'VolumeTarget': ['1.0'], } }, - 'master': { + '16.1': { 'api': '1.68', 'rpc': '1.51', 'objects': { @@ -283,6 +283,24 @@ RELEASE_MAPPING = { 'VolumeTarget': ['1.0'], } }, + 'master': { + 'api': '1.68', + 'rpc': '1.51', + 'objects': { + 'Allocation': ['1.1'], + 'Node': ['1.35'], + 'Conductor': ['1.3'], + 'Chassis': ['1.3'], + 'Deployment': ['1.0'], + 'DeployTemplate': ['1.1'], + 'Port': ['1.10'], + 'Portgroup': ['1.4'], + 'Trait': ['1.0'], + 'TraitList': ['1.0'], + 'VolumeConnector': ['1.0'], + 'VolumeTarget': ['1.0'], + } + }, } # NOTE(xek): Assign each named release to the appropriate semver. diff --git a/ironic/db/api.py b/ironic/db/api.py index 48228773f..bee805f5c 100644 --- a/ironic/db/api.py +++ b/ironic/db/api.py @@ -252,6 +252,14 @@ class Connection(object, metaclass=abc.ABCMeta): """ @abc.abstractmethod + def get_port_by_name(self, port_name): + """Return a network port representation. + + :param port_name: The name of a port. + :returns: A port. + """ + + @abc.abstractmethod def get_port_list(self, limit=None, marker=None, sort_key=None, sort_dir=None): """Return a list of ports. diff --git a/ironic/db/sqlalchemy/alembic/versions/c0455649680c_port_name.py b/ironic/db/sqlalchemy/alembic/versions/c0455649680c_port_name.py new file mode 100644 index 000000000..80023664b --- /dev/null +++ b/ironic/db/sqlalchemy/alembic/versions/c0455649680c_port_name.py @@ -0,0 +1,33 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""port-name + +Revision ID: c0455649680c +Revises: cf1a80fdb352 +Create Date: 2020-11-27 20:12:24.752897 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c0455649680c' +down_revision = 'cf1a80fdb352' + + +def upgrade(): + op.add_column('ports', sa.Column('name', sa.String(length=255), + nullable=True)) + op.create_unique_constraint('uniq_ports0name', 'ports', ['name']) diff --git a/ironic/db/sqlalchemy/api.py b/ironic/db/sqlalchemy/api.py index 7c478b009..7891122ce 100644 --- a/ironic/db/sqlalchemy/api.py +++ b/ironic/db/sqlalchemy/api.py @@ -705,6 +705,13 @@ class Connection(api.Connection): except NoResultFound: raise exception.PortNotFound(port=address) + def get_port_by_name(self, port_name): + query = model_query(models.Port).filter_by(name=port_name) + try: + return query.one() + except NoResultFound: + raise exception.PortNotFound(port=port_name) + def get_port_list(self, limit=None, marker=None, sort_key=None, sort_dir=None, owner=None, project=None): @@ -773,8 +780,11 @@ class Connection(api.Connection): session.flush() except NoResultFound: raise exception.PortNotFound(port=port_id) - except db_exc.DBDuplicateEntry: - raise exception.MACAlreadyExists(mac=values['address']) + except db_exc.DBDuplicateEntry as exc: + if 'name' in exc.columns: + raise exception.PortDuplicateName(name=values['name']) + else: + raise exception.MACAlreadyExists(mac=values['address']) return ref @oslo_db_api.retry_on_deadlock diff --git a/ironic/db/sqlalchemy/models.py b/ironic/db/sqlalchemy/models.py index 68b366f21..2072153e2 100644 --- a/ironic/db/sqlalchemy/models.py +++ b/ironic/db/sqlalchemy/models.py @@ -210,6 +210,7 @@ class Port(Base): __table_args__ = ( schema.UniqueConstraint('address', name='uniq_ports0address'), schema.UniqueConstraint('uuid', name='uniq_ports0uuid'), + schema.UniqueConstraint('name', name='uniq_ports0name'), table_args()) id = Column(Integer, primary_key=True) uuid = Column(String(36)) @@ -222,6 +223,7 @@ class Port(Base): internal_info = Column(db_types.JsonEncodedDict) physical_network = Column(String(64), nullable=True) is_smartnic = Column(Boolean, nullable=True, default=False) + name = Column(String(255), nullable=True) class Portgroup(Base): diff --git a/ironic/drivers/modules/ipmitool.py b/ironic/drivers/modules/ipmitool.py index c3b5600e3..13391ef4e 100644 --- a/ironic/drivers/modules/ipmitool.py +++ b/ironic/drivers/modules/ipmitool.py @@ -950,6 +950,7 @@ class IPMIPower(base.PowerInterface): # call to store it. vendor = task.driver.management.detect_vendor(task) if vendor: + task.upgrade_lock() props = task.node.properties props['vendor'] = vendor task.node.properties = props @@ -1241,7 +1242,6 @@ class IPMIManagement(base.ManagementInterface): response['persistent'] = 'Options apply to all future boots' in out return response - @task_manager.require_exclusive_lock @METRICS.timer('IPMIManagement.detect_vendor') def detect_vendor(self, task): """Detects, stores, and returns the hardware vendor. diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py index 3463f3543..222e952da 100644 --- a/ironic/drivers/modules/pxe.py +++ b/ironic/drivers/modules/pxe.py @@ -37,7 +37,8 @@ class PXEBoot(pxe_base.PXEBaseMixin, base.BootInterface): capabilities = ['ramdisk_boot', 'pxe_boot'] -class PXERamdiskDeploy(agent_base.AgentBaseMixin, base.DeployInterface): +class PXERamdiskDeploy(agent_base.AgentBaseMixin, agent_base.HeartbeatMixin, + base.DeployInterface): def get_properties(self, task): return {} diff --git a/ironic/objects/port.py b/ironic/objects/port.py index 85690b162..b4d0e78eb 100644 --- a/ironic/objects/port.py +++ b/ironic/objects/port.py @@ -20,6 +20,7 @@ from oslo_utils import versionutils from oslo_versionedobjects import base as object_base from ironic.common import exception +from ironic.common import utils from ironic.db import api as dbapi from ironic.objects import base from ironic.objects import fields as object_fields @@ -42,7 +43,8 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat): # internal_info['tenant_vif_port_id'] (not an explicit db # change) # Version 1.9: Add support for Smart NIC port - VERSION = '1.9' + # Version 1.10: Add name field + VERSION = '1.10' dbapi = dbapi.get_instance() @@ -60,8 +62,26 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat): 'physical_network': object_fields.StringField(nullable=True), 'is_smartnic': object_fields.BooleanField(nullable=True, default=False), + 'name': object_fields.StringField(nullable=True), } + def _convert_name_field(self, target_version, + remove_unavailable_fields=True): + name_is_set = self.obj_attr_is_set('name') + if target_version >= (1, 10): + # Target version supports name. Set it to its default + # value if it is not set. + if not name_is_set: + self.name = None + elif name_is_set: + # Target version does not support name, and it is set. + if remove_unavailable_fields: + # (De)serialising: remove unavailable fields. + delattr(self, 'name') + elif self.name is not None: + # DB: set unavailable fields to their default. + self.name = None + def _convert_to_version(self, target_version, remove_unavailable_fields=True): """Convert to the target version. @@ -82,6 +102,9 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat): Version 1.9: remove is_smartnic field for unsupported versions if remove_unavailable_fields is True. + Version 1.10: remove name field for unsupported versions if + remove_unavailable_fields is True. + :param target_version: the desired version of the object :param remove_unavailable_fields: True to remove fields that are unavailable in the target version; set this to True when @@ -134,6 +157,9 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat): # DB: set unavailable fields to their default. self.is_smartnic = False + # Convert the name field. + self._convert_name_field(target_version, remove_unavailable_fields) + # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable # methods can be used in the future to replace current explicit RPC calls. # Implications of calling new remote procedures should be thought through. @@ -142,11 +168,11 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat): def get(cls, context, port_id): """Find a port. - Find a port based on its id or uuid or MAC address and return a Port - object. + Find a port based on its id or uuid or name or MAC address and return + a Port object. :param context: Security context - :param port_id: the id *or* uuid *or* MAC address of a port. + :param port_id: the id *or* uuid *or* name *or* MAC address of a port. :returns: a :class:`Port` object. :raises: InvalidIdentity @@ -157,6 +183,8 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat): return cls.get_by_uuid(context, port_id) elif netutils.is_valid_mac(port_id): return cls.get_by_address(context, port_id) + elif utils.is_valid_logical_name(port_id): + return cls.get_by_name(context, port_id) else: raise exception.InvalidIdentity(identity=port_id) @@ -226,6 +254,25 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat): # Implications of calling new remote procedures should be thought through. # @object_base.remotable_classmethod @classmethod + def get_by_name(cls, context, name): + """Find a port based on name and return a :class:`Port` object. + + :param cls: the :class:`Port` + :param context: Security context + :param name: the name of a port. + :returns: a :class:`Port` object. + :raises: PortNotFound + + """ + db_port = cls.dbapi.get_port_by_name(name) + port = cls._from_db_object(context, cls(), db_port) + return port + + # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable + # methods can be used in the future to replace current explicit RPC calls. + # Implications of calling new remote procedures should be thought through. + # @object_base.remotable_classmethod + @classmethod def list(cls, context, limit=None, marker=None, sort_key=None, sort_dir=None, owner=None, project=None): """Return a list of Port objects. @@ -435,7 +482,8 @@ class PortCRUDPayload(notification.NotificationPayloadBase): # Version 1.1: Add "portgroup_uuid" field # Version 1.2: Add "physical_network" field # Version 1.3: Add "is_smartnic" field - VERSION = '1.3' + # Version 1.4: Add "name" field + VERSION = '1.4' SCHEMA = { 'address': ('port', 'address'), @@ -447,6 +495,7 @@ class PortCRUDPayload(notification.NotificationPayloadBase): 'updated_at': ('port', 'updated_at'), 'uuid': ('port', 'uuid'), 'is_smartnic': ('port', 'is_smartnic'), + 'name': ('port', 'name'), } fields = { @@ -463,6 +512,7 @@ class PortCRUDPayload(notification.NotificationPayloadBase): 'uuid': object_fields.UUIDField(), 'is_smartnic': object_fields.BooleanField(nullable=True, default=False), + 'name': object_fields.StringField(nullable=True), } def __init__(self, port, node_uuid, portgroup_uuid): diff --git a/ironic/tests/unit/db/sqlalchemy/test_migrations.py b/ironic/tests/unit/db/sqlalchemy/test_migrations.py index 39293c6ac..7a2641323 100644 --- a/ironic/tests/unit/db/sqlalchemy/test_migrations.py +++ b/ironic/tests/unit/db/sqlalchemy/test_migrations.py @@ -1002,6 +1002,11 @@ class MigrationCheckersMixin(object): col_names = [column.name for column in nodes.c] self.assertIn('lessee', col_names) + def _check_c0455649680c(self, engine, data): + ports = db_utils.get_table(engine, 'ports') + col_names = [column.name for column in ports.c] + self.assertIn('name', col_names) + def test_upgrade_and_version(self): with patch_with_engine(self.engine): self.migration_api.upgrade('head') diff --git a/ironic/tests/unit/db/test_ports.py b/ironic/tests/unit/db/test_ports.py index d2434d603..18b8a9032 100644 --- a/ironic/tests/unit/db/test_ports.py +++ b/ironic/tests/unit/db/test_ports.py @@ -32,7 +32,8 @@ class DbPortTestCase(base.DbTestCase): lessee='54321') self.portgroup = db_utils.create_test_portgroup(node_id=self.node.id) self.port = db_utils.create_test_port(node_id=self.node.id, - portgroup_id=self.portgroup.id) + portgroup_id=self.portgroup.id, + name='port-name') def test_get_port_by_id(self): res = self.dbapi.get_port_by_id(self.port.id) @@ -68,6 +69,10 @@ class DbPortTestCase(base.DbTestCase): self.port.address, project='55555') + def test_get_port_by_name(self): + res = self.dbapi.get_port_by_name(self.port.name) + self.assertEqual(self.port.id, res.id) + def test_get_port_list(self): uuids = [] for i in range(1, 6): diff --git a/ironic/tests/unit/db/utils.py b/ironic/tests/unit/db/utils.py index 96254889d..436849054 100644 --- a/ironic/tests/unit/db/utils.py +++ b/ironic/tests/unit/db/utils.py @@ -277,6 +277,7 @@ def get_test_port(**kw): 'internal_info': kw.get('internal_info', {"bar": "buzz"}), 'physical_network': kw.get('physical_network'), 'is_smartnic': kw.get('is_smartnic', False), + 'name': kw.get('name'), } diff --git a/ironic/tests/unit/objects/test_objects.py b/ironic/tests/unit/objects/test_objects.py index 3852a9abd..7b7788933 100644 --- a/ironic/tests/unit/objects/test_objects.py +++ b/ironic/tests/unit/objects/test_objects.py @@ -679,7 +679,7 @@ expected_object_fingerprints = { 'Node': '1.35-aee8ecf5c4d0ed590eb484762aee7fca', 'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6', 'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905', - 'Port': '1.9-0cb9202a4ec442e8c0d87a324155eaaf', + 'Port': '1.10-67381b065c597c8d3a13c5dbc6243c33', 'Portgroup': '1.4-71923a81a86743b313b190f5c675e258', 'Conductor': '1.3-d3f53e853b4d58cae5bfbd9a8341af4a', 'EventType': '1.1-aa2ba1afd38553e3880c267404e8d370', @@ -700,7 +700,7 @@ expected_object_fingerprints = { 'NodeCRUDNotification': '1.0-59acc533c11d306f149846f922739c15', 'NodeCRUDPayload': '1.13-8f673253ff8d7389897a6a80d224ac33', 'PortCRUDNotification': '1.0-59acc533c11d306f149846f922739c15', - 'PortCRUDPayload': '1.3-21235916ed54a91b2a122f59571194e7', + 'PortCRUDPayload': '1.4-9411a1701077ae9dc0aea27d6bf586fc', 'NodeMaintenanceNotification': '1.0-59acc533c11d306f149846f922739c15', 'NodeConsoleNotification': '1.0-59acc533c11d306f149846f922739c15', 'PortgroupCRUDNotification': '1.0-59acc533c11d306f149846f922739c15', diff --git a/ironic/tests/unit/objects/test_port.py b/ironic/tests/unit/objects/test_port.py index 43c58876e..43459c958 100644 --- a/ironic/tests/unit/objects/test_port.py +++ b/ironic/tests/unit/objects/test_port.py @@ -34,7 +34,7 @@ class TestPortObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn): def setUp(self): super(TestPortObject, self).setUp() - self.fake_port = db_utils.get_test_port() + self.fake_port = db_utils.get_test_port(name='port-name') def test_get_by_id(self): port_id = self.fake_port['id'] @@ -69,9 +69,20 @@ class TestPortObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn): mock_get_port.assert_called_once_with(address, project=None) self.assertEqual(self.context, port._context) - def test_get_bad_id_and_uuid_and_address(self): + def test_get_by_name(self): + name = self.fake_port['name'] + with mock.patch.object(self.dbapi, 'get_port_by_name', + autospec=True) as mock_get_port: + mock_get_port.return_value = self.fake_port + + port = objects.Port.get(self.context, name) + + mock_get_port.assert_called_once_with(name) + self.assertEqual(self.context, port._context) + + def test_get_bad_id_and_uuid_and_name_and_address(self): self.assertRaises(exception.InvalidIdentity, - objects.Port.get, self.context, 'not-a-uuid') + objects.Port.get, self.context, '#not-valid') def test_create(self): port = objects.Port(self.context, **self.fake_port) @@ -349,3 +360,59 @@ class TestConvertToVersion(db_base.DbTestCase): port._convert_to_version("1.8", False) self.assertFalse(port.is_smartnic) self.assertNotIn('is_smartnic', port.obj_get_changes()) + + def test_name_supported_missing(self): + # name not set, should be set to default. + port = objects.Port(self.context, **self.fake_port) + delattr(port, 'name') + port.obj_reset_changes() + port._convert_to_version("1.10") + self.assertIsNone(port.name) + self.assertIn('name', port.obj_get_changes()) + self.assertIsNone(port.obj_get_changes()['name']) + + def test_name_supported_set(self): + # Physical network set, no change required. + port = objects.Port(self.context, **self.fake_port) + port.name = 'meow' + port.obj_reset_changes() + port._convert_to_version("1.10") + self.assertEqual('meow', port.name) + self.assertNotIn('name', port.obj_get_changes()) + + def test_name_unsupported_missing(self): + # name not set, no change required. + port = objects.Port(self.context, **self.fake_port) + delattr(port, 'name') + port.obj_reset_changes() + port._convert_to_version("1.9") + self.assertNotIn('name', port) + self.assertNotIn('name', port.obj_get_changes()) + + def test_name_unsupported_set_remove(self): + # name set, should be removed. + port = objects.Port(self.context, **self.fake_port) + port.name = 'meow' + port.obj_reset_changes() + port._convert_to_version("1.9") + self.assertNotIn('name', port) + self.assertNotIn('name', port.obj_get_changes()) + + def test_name_unsupported_set_no_remove_non_default(self): + # name set, should be set to default. + port = objects.Port(self.context, **self.fake_port) + port.name = 'meow' + port.obj_reset_changes() + port._convert_to_version("1.9", False) + self.assertIsNone(port.name) + self.assertIn('name', port.obj_get_changes()) + self.assertIsNone(port.obj_get_changes()['name']) + + def test_name_unsupported_set_no_remove_default(self): + # name set, no change required. + port = objects.Port(self.context, **self.fake_port) + port.name = None + port.obj_reset_changes() + port._convert_to_version("1.9", False) + self.assertIsNone(port.name) + self.assertNotIn('name', port.obj_get_changes()) diff --git a/lower-constraints.txt b/lower-constraints.txt deleted file mode 100644 index 273632a6a..000000000 --- a/lower-constraints.txt +++ /dev/null @@ -1,148 +0,0 @@ -alembic==1.4.2 -amqp==2.5.2 -appdirs==1.4.3 -automaton==1.9.0 -Babel==2.3.4 -bandit==1.1.0 -bashate==0.5.1 -beautifulsoup4==4.9.0 -cachetools==4.1.0 -cffi==1.14.0 -chardet==3.0.4 -cliff==3.1.0 -cmd2==0.8.9 -contextlib2==0.6.0.post1 -coverage==4.0 -cryptography==2.9.2 -ddt==1.0.1 -debtcollector==2.0.1 -decorator==4.4.2 -doc8==0.6.0 -docutils==0.16 -dogpile.cache==0.9.2 -entrypoints==0.3 -eventlet==0.18.2 -extras==1.0.0 -fasteners==0.15 -fixtures==3.0.0 -future==0.18.2 -futurist==1.2.0 -gitdb==4.0.5 -GitPython==3.1.2 -greenlet==0.4.15 -ifaddr==0.1.6 -importlib-metadata==1.6.0 -ironic-lib==4.3.0 -iso8601==0.1.11 -Jinja2==2.10 -jmespath==0.9.5 -jsonpatch==1.16 -jsonpointer==2.0 -jsonschema==3.2.0 -keystoneauth1==4.2.0 -keystonemiddleware==4.17.0 -kombu==4.6.8 -linecache2==1.0.0 -logutils==0.3.5 -Mako==1.1.2 -MarkupSafe==1.1.1 -monotonic==1.5 -mox3==1.0.0 -msgpack-python==0.5.6 -munch==2.5.0 -netaddr==0.7.19 -netifaces==0.10.9 -openstacksdk==0.48.0 -os-client-config==2.1.0 -os-service-types==1.7.0 -os-traits==0.4.0 -osc-lib==2.0.0 -oslo.cache==2.5.0 -oslo.concurrency==4.2.0 -oslo.config==5.2.0 -oslo.context==2.19.2 -oslo.db==6.0.0 -oslo.i18n==3.15.3 -oslo.log==3.36.0 -oslo.messaging==5.29.0 -oslo.middleware==3.31.0 -oslo.policy==1.30.0 -oslo.reports==1.18.0 -oslo.rootwrap==5.8.0 -oslo.serialization==2.18.0 -oslo.service==1.24.0 -oslo.upgradecheck==0.1.0 -oslo.utils==3.38.0 -oslo.versionedobjects==1.31.2 -oslotest==3.2.0 -osprofiler==1.5.0 -Paste==3.4.0 -PasteDeploy==2.1.0 -pbr==2.0.0 -pecan==1.0.0 -pika==0.10.0 -pika-pool==0.1.3 -positional==1.2.1 -prettytable==0.7.2 -psutil==3.2.2 -psycopg2==2.8.5 -pycadf==3.0.0 -pycodestyle==2.5.0 -pycparser==2.20 -Pygments==2.2.0 -pyinotify==0.9.6 -PyMySQL==0.8.0 -pyOpenSSL==19.1.0 -pyparsing==2.4.7 -pyperclip==1.8.0 -pysendfile==2.0.0 -pysnmp==4.4.12 -python-cinderclient==3.3.0 -python-dateutil==2.8.1 -python-editor==1.0.4 -python-glanceclient==2.8.0 -python-keystoneclient==4.0.0 -python-mimeparse==1.6.0 -python-subunit==1.4.0 -python-swiftclient==3.2.0 -pytz==2013.6 -PyYAML==5.3.1 -repoze.lru==0.7 -requests==2.14.2 -requestsexceptions==1.4.0 -restructuredtext-lint==1.3.0 -retrying==1.2.3 -rfc3986==0.3.1 -Routes==2.4.1 -simplegeneric==0.8.1 -simplejson==3.17.0 -six==1.14.0 -smmap==3.0.4 -soupsieve==2.0 -SQLAlchemy==1.2.19 -sqlalchemy-migrate==0.11.0 -sqlparse==0.3.1 -statsd==3.3.0 -stestr==1.0.0 -stevedore==1.20.0 -Tempita==0.5.2 -tenacity==6.2.0 -testrepository==0.0.20 -testresources==2.0.0 -testscenarios==0.4 -testtools==2.2.0 -tooz==2.7.0 -traceback2==1.4.0 -unittest2==1.1.0 -vine==1.3.0 -virtualbmc==1.4.0 -voluptuous==0.11.7 -waitress==1.4.3 -warlock==1.3.3 -wcwidth==0.1.9 -WebOb==1.7.1 -WebTest==2.0.27 -wrapt==1.12.1 -WSME==0.9.3 -zeroconf==0.26.1 -zipp==3.1.0 diff --git a/releasenotes/notes/dhcp-less-less-2a35df67d840f9d5.yaml b/releasenotes/notes/dhcp-less-less-2a35df67d840f9d5.yaml new file mode 100644 index 000000000..4b1788272 --- /dev/null +++ b/releasenotes/notes/dhcp-less-less-2a35df67d840f9d5.yaml @@ -0,0 +1,6 @@ +--- +issues: + - | + Building ramdisks for DHCP-less deploy using the ``simple-init`` element + is known not to work for distributions using NetworkManager. + The ``debian-minimal`` element seems to work. diff --git a/releasenotes/notes/empty-physical-network-2248a4adef210289.yaml b/releasenotes/notes/empty-physical-network-2248a4adef210289.yaml index d554b907d..af806ee16 100644 --- a/releasenotes/notes/empty-physical-network-2248a4adef210289.yaml +++ b/releasenotes/notes/empty-physical-network-2248a4adef210289.yaml @@ -1,5 +1,5 @@ --- fixes: - | - fixes an issue that physical_network could be set to an empty string, - which makes the port unusable. + It is no longer possible to set a port's ``physical_network`` to an empty + string, making the port unusable. diff --git a/releasenotes/notes/fix-inspection-for-idrac-34b3ea09452af8be.yaml b/releasenotes/notes/fix-inspection-for-idrac-34b3ea09452af8be.yaml index c790f3248..6cf11e202 100644 --- a/releasenotes/notes/fix-inspection-for-idrac-34b3ea09452af8be.yaml +++ b/releasenotes/notes/fix-inspection-for-idrac-34b3ea09452af8be.yaml @@ -1,4 +1,4 @@ --- fixes: - | - Fix an issue when using idrac with vmedia and trying to inspect a node. + Fixes inspection with the ``idrac-redfish-virtual-media`` boot interface. diff --git a/releasenotes/notes/ipmi_command_retry_timeout-889a49b402e82b97.yaml b/releasenotes/notes/ipmi_command_retry_timeout-889a49b402e82b97.yaml index b599b0b14..4a39991ca 100644 --- a/releasenotes/notes/ipmi_command_retry_timeout-889a49b402e82b97.yaml +++ b/releasenotes/notes/ipmi_command_retry_timeout-889a49b402e82b97.yaml @@ -1,9 +1,10 @@ --- fixes: - | - Calculating the ipmitool `-N` and `-R` arguments from ironic.conf [ipmi] - `command_retry_timeout` and `min_command_interval` now takes into account the - 1 second interval increment that ipmitool adds on each retry event. + Calculating the ipmitool ``-N`` and ``-R`` arguments from the configuration + options ``[ipmi]command_retry_timeout`` and ``[ipmi]min_command_interval`` + now takes into account the 1 second interval increment that ipmitool adds + on each retry event. Failure-path ipmitool run duration will now be just less than - `command_retry_timeout` instead of much longer.
\ No newline at end of file + ``command_retry_timeout`` instead of much longer. diff --git a/releasenotes/notes/json-rpc-ipv6-host-30eca350f34bc091.yaml b/releasenotes/notes/json-rpc-ipv6-host-30eca350f34bc091.yaml index 96944012c..18fb1a424 100644 --- a/releasenotes/notes/json-rpc-ipv6-host-30eca350f34bc091.yaml +++ b/releasenotes/notes/json-rpc-ipv6-host-30eca350f34bc091.yaml @@ -1,6 +1,6 @@ --- fixes: - | - When configured to use json-rpc, the ``[DEFAULT].host`` configuration - option to ironic-conductor can now be set to an IPv6 address. Previously - it could only be an IPv4 address or a DNS name. + When configured to use JSON RPC, the ``[DEFAULT]host`` configuration + option can now be set to an IPv6 address. Previously it could only be + an IPv4 address or a DNS name. diff --git a/releasenotes/notes/optimize-ramdisk-log-filename-270c401780b16e9c.yaml b/releasenotes/notes/optimize-ramdisk-log-filename-270c401780b16e9c.yaml index 64bf56f8b..60f6a9231 100644 --- a/releasenotes/notes/optimize-ramdisk-log-filename-270c401780b16e9c.yaml +++ b/releasenotes/notes/optimize-ramdisk-log-filename-270c401780b16e9c.yaml @@ -1,4 +1,4 @@ --- features: - | - The ramdisk log filename will contain the node name when it exists. + The ramdisk log file name now contains the node name when it is set. diff --git a/releasenotes/notes/raid-remove-root-hint-ec87efd18e894256.yaml b/releasenotes/notes/raid-remove-root-hint-ec87efd18e894256.yaml index be6832189..5eddd4833 100644 --- a/releasenotes/notes/raid-remove-root-hint-ec87efd18e894256.yaml +++ b/releasenotes/notes/raid-remove-root-hint-ec87efd18e894256.yaml @@ -1,10 +1,10 @@ --- upgrade: - | - Agent raid will remove the root device hint after the RAID configuration - is successfully deleted. + The ``agent`` RAID interface now removes any root device hint after + the RAID configuration is successfully deleted. fixes: - | - Fixes the issue that root device hint is not removed after agent raid - interface has successfully deleted RAID configuration, the previous hint - is not guranteed to be valid thus will cause a deployment failed. + Fixes the issue that root device hint is not removed after the ``agent`` + RAID interface has successfully deleted RAID configuration. The previous + hint is not guranteed to be valid and may cause a deployment failure. diff --git a/releasenotes/notes/ramdisk-clean-2d3b033a401b911b.yaml b/releasenotes/notes/ramdisk-clean-2d3b033a401b911b.yaml new file mode 100644 index 000000000..022372fb7 --- /dev/null +++ b/releasenotes/notes/ramdisk-clean-2d3b033a401b911b.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixes cleaning with the ``ramdisk`` deploy interface by reusing the same + procedure as for the ``direct`` deploy interface. diff --git a/releasenotes/notes/sync-boot-mode-after-changing-redfish-device-f60ef90ba5675215.yaml b/releasenotes/notes/sync-boot-mode-after-changing-redfish-device-f60ef90ba5675215.yaml index 629d9527c..47f0a9631 100644 --- a/releasenotes/notes/sync-boot-mode-after-changing-redfish-device-f60ef90ba5675215.yaml +++ b/releasenotes/notes/sync-boot-mode-after-changing-redfish-device-f60ef90ba5675215.yaml @@ -1,8 +1,8 @@ --- fixes: - | - After changing the boot device via Redfish, check that the boot mode being - reported matches what is configured and, if not, set it to the configured - value. Some BMCs change the boot mode when the device is + After changing the boot device via Redfish, checks that the boot mode being + reported matches what is configured and, if not, sets it to the configured + value. Some BMCs change the boot mode when the device is set via Redfish, see `story 2008252 - <https://storyboard.openstack.org/#!/story/2008252>`__ for details. + <https://storyboard.openstack.org/#!/story/2008252>`_ for details. @@ -160,12 +160,6 @@ extension = N323 = checks:check_explicit_underscore_import paths = ./ironic/hacking/ -[testenv:lower-constraints] -deps = - -c{toxinidir}/lower-constraints.txt - -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt - [testenv:bandit] usedevelop = False deps = -r{toxinidir}/test-requirements.txt diff --git a/zuul.d/ironic-jobs.yaml b/zuul.d/ironic-jobs.yaml index 3f8398181..489370a5b 100644 --- a/zuul.d/ironic-jobs.yaml +++ b/zuul.d/ironic-jobs.yaml @@ -414,7 +414,8 @@ - job: name: ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode description: ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode - parent: tempest-multinode-full-py3 + parent: tempest-multinode-full-base + nodeset: openstack-two-node-focal pre-run: playbooks/ci-workarounds/pre.yaml required-projects: - openstack/ironic diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml index 1e72e3a6c..5a702ea4a 100644 --- a/zuul.d/project.yaml +++ b/zuul.d/project.yaml @@ -2,7 +2,6 @@ templates: - check-requirements - openstack-cover-jobs - - openstack-lower-constraints-jobs - openstack-python3-wallaby-jobs - periodic-stable-jobs - publish-openstack-docs-pti @@ -19,8 +18,7 @@ - ironic-tempest-wholedisk-bios-snmp-pxe - ironic-tempest-ipa-partition-pxe_ipmitool - ironic-tempest-ipa-partition-uefi-pxe_ipmitool - - ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode: - voting: false + - ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode - ironic-tempest-bios-ipmi-direct-tinyipa - ironic-tempest-bfv - ironic-tempest-ipa-partition-uefi-pxe-grub2 @@ -57,6 +55,7 @@ - ironic-tempest-wholedisk-bios-snmp-pxe - ironic-tempest-ipa-partition-pxe_ipmitool - ironic-tempest-ipa-partition-uefi-pxe_ipmitool + - ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode - ironic-tempest-bios-ipmi-direct-tinyipa - ironic-tempest-bfv - ironic-tempest-ipa-partition-uefi-pxe-grub2 |