summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api-ref/source/baremetal-api-v1-chassis.inc9
-rw-r--r--api-ref/source/baremetal-api-v1-deploy-templates.inc10
-rw-r--r--api-ref/source/baremetal-api-v1-nodes-vifs.inc2
-rw-r--r--api-ref/source/baremetal-api-v1-nodes.inc63
-rw-r--r--api-ref/source/baremetal-api-v1-portgroups.inc7
-rw-r--r--api-ref/source/baremetal-api-v1-ports.inc1
-rw-r--r--api-ref/source/baremetal-api-v1-volume.inc4
-rw-r--r--api-ref/source/parameters.yaml158
-rw-r--r--devstack/lib/ironic2
-rw-r--r--doc/source/admin/dhcp-less.rst88
-rw-r--r--doc/source/admin/drivers/ilo.rst2
-rw-r--r--doc/source/admin/drivers/redfish.rst48
-rw-r--r--doc/source/admin/index.rst1
-rw-r--r--doc/source/contributor/releasing.rst14
-rw-r--r--doc/source/install/refarch/common.rst4
-rw-r--r--ironic/api/controllers/v1/allocation.py14
-rw-r--r--ironic/api/controllers/v1/bios.py7
-rw-r--r--ironic/api/controllers/v1/chassis.py19
-rw-r--r--ironic/api/controllers/v1/conductor.py7
-rw-r--r--ironic/api/controllers/v1/driver.py22
-rw-r--r--ironic/api/controllers/v1/event.py5
-rw-r--r--ironic/api/controllers/v1/node.py12
-rw-r--r--ironic/api/controllers/v1/port.py4
-rw-r--r--ironic/api/controllers/v1/portgroup.py19
-rw-r--r--ironic/api/controllers/v1/ramdisk.py7
-rw-r--r--ironic/api/controllers/v1/volume.py4
-rw-r--r--ironic/api/controllers/v1/volume_connector.py16
-rw-r--r--ironic/api/controllers/v1/volume_target.py16
-rw-r--r--ironic/common/exception.py4
-rw-r--r--ironic/common/release_mappings.py20
-rw-r--r--ironic/db/api.py8
-rw-r--r--ironic/db/sqlalchemy/alembic/versions/c0455649680c_port_name.py33
-rw-r--r--ironic/db/sqlalchemy/api.py14
-rw-r--r--ironic/db/sqlalchemy/models.py2
-rw-r--r--ironic/drivers/modules/ipmitool.py2
-rw-r--r--ironic/drivers/modules/pxe.py3
-rw-r--r--ironic/objects/port.py60
-rw-r--r--ironic/tests/unit/db/sqlalchemy/test_migrations.py5
-rw-r--r--ironic/tests/unit/db/test_ports.py7
-rw-r--r--ironic/tests/unit/db/utils.py1
-rw-r--r--ironic/tests/unit/objects/test_objects.py4
-rw-r--r--ironic/tests/unit/objects/test_port.py73
-rw-r--r--lower-constraints.txt148
-rw-r--r--releasenotes/notes/dhcp-less-less-2a35df67d840f9d5.yaml6
-rw-r--r--releasenotes/notes/empty-physical-network-2248a4adef210289.yaml4
-rw-r--r--releasenotes/notes/fix-inspection-for-idrac-34b3ea09452af8be.yaml2
-rw-r--r--releasenotes/notes/ipmi_command_retry_timeout-889a49b402e82b97.yaml9
-rw-r--r--releasenotes/notes/json-rpc-ipv6-host-30eca350f34bc091.yaml6
-rw-r--r--releasenotes/notes/optimize-ramdisk-log-filename-270c401780b16e9c.yaml2
-rw-r--r--releasenotes/notes/raid-remove-root-hint-ec87efd18e894256.yaml10
-rw-r--r--releasenotes/notes/ramdisk-clean-2d3b033a401b911b.yaml5
-rw-r--r--releasenotes/notes/sync-boot-mode-after-changing-redfish-device-f60ef90ba5675215.yaml8
-rw-r--r--tox.ini6
-rw-r--r--zuul.d/ironic-jobs.yaml3
-rw-r--r--zuul.d/project.yaml5
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.
diff --git a/tox.ini b/tox.ini
index 32b229b7a..f0c7b41ba 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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