Merge ~corey.bryant/ubuntu/+source/ironic:master into ~ubuntu-openstack-dev/ubuntu/+source/ironic:master
- Git
- lp:~corey.bryant/ubuntu/+source/ironic
- master
- Merge into master
Proposed by
Corey Bryant
Status: | Rejected |
---|---|
Rejected by: | Corey Bryant |
Proposed branch: | ~corey.bryant/ubuntu/+source/ironic:master |
Merge into: | ~ubuntu-openstack-dev/ubuntu/+source/ironic:master |
Diff against target: |
2682 lines (+1289/-242) 59 files modified
ChangeLog (+17/-0) PKG-INFO (+43/-46) api-ref/source/baremetal-api-v1-node-management.inc (+8/-0) api-ref/source/baremetal-api-v1-nodes-firmware.inc (+1/-1) debian/changelog (+4/-2) debian/control (+15/-0) devstack/lib/ironic (+3/-4) doc/source/admin/drivers/redfish.rst (+16/-0) doc/source/contributor/webapi-version-history.rst (+10/-10) driver-requirements.txt (+1/-1) ironic.egg-info/PKG-INFO (+43/-46) ironic.egg-info/SOURCES.txt (+7/-0) ironic.egg-info/entry_points.txt (+1/-0) ironic.egg-info/pbr.json (+1/-1) ironic/api/controllers/v1/utils.py (+1/-1) ironic/common/pxe_utils.py (+0/-11) ironic/common/utils.py (+6/-2) ironic/conductor/cleaning.py (+2/-0) ironic/conductor/steps.py (+12/-9) ironic/conductor/utils.py (+12/-0) ironic/conf/inspector.py (+5/-3) ironic/conf/redfish.py (+6/-0) ironic/db/sqlalchemy/api.py (+12/-15) ironic/drivers/modules/deploy_utils.py (+4/-2) ironic/drivers/modules/fake.py (+2/-0) ironic/drivers/modules/inspect_utils.py (+3/-0) ironic/drivers/modules/redfish/firmware.py (+452/-0) ironic/drivers/modules/redfish/firmware_utils.py (+44/-0) ironic/drivers/modules/redfish/management.py (+108/-5) ironic/drivers/modules/redfish/utils.py (+38/-1) ironic/drivers/redfish.py (+5/-0) ironic/objects/firmware.py (+9/-5) ironic/tests/unit/api/base.py (+3/-6) ironic/tests/unit/api/controllers/v1/test_port.py (+0/-1) ironic/tests/unit/api/test_acl.py (+0/-10) ironic/tests/unit/common/test_neutron.py (+0/-1) ironic/tests/unit/common/test_pxe_utils.py (+0/-5) ironic/tests/unit/common/test_utils.py (+6/-0) ironic/tests/unit/conductor/test_steps.py (+9/-0) ironic/tests/unit/db/test_nodes.py (+35/-0) ironic/tests/unit/drivers/modules/ilo/test_management.py (+4/-4) ironic/tests/unit/drivers/modules/ilo/test_raid.py (+2/-2) ironic/tests/unit/drivers/modules/redfish/test_bios.py (+3/-2) ironic/tests/unit/drivers/modules/redfish/test_firmware.py (+40/-0) ironic/tests/unit/drivers/modules/redfish/test_management.py (+173/-30) ironic/tests/unit/drivers/modules/redfish/test_raid.py (+4/-2) ironic/tests/unit/drivers/modules/redfish/test_utils.py (+8/-0) ironic/tests/unit/drivers/modules/test_agent_base.py (+8/-8) ironic/tests/unit/drivers/modules/test_inspect_utils.py (+12/-0) ironic/tests/unit/drivers/test_redfish.py (+2/-1) releasenotes/notes/23.0-prelude-bobcat-ad7c24f666c22ebf.yaml (+17/-0) releasenotes/notes/add-hold-states-7be5804d6f3a119a.yaml (+1/-1) releasenotes/notes/bug-2036455-edd0e97335579684.yaml (+6/-0) releasenotes/notes/firmware-interface-8ad6f91aa1f746a0.yaml (+31/-0) releasenotes/notes/remove-400a563030224c4f.yaml (+9/-0) releasenotes/notes/uefi-and-secureboot-waits-a783215327164e2c.yaml (+20/-0) releasenotes/source/2023.1.rst (+3/-3) setup.cfg (+1/-0) zuul.d/project.yaml (+1/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu OpenStack uploaders | Pending | ||
Review via email: mp+450925@code.launchpad.net |
Commit message
New upstream snapshot for OpenStack Bobcat.
Description of the change
This is an automated merge proposal.
To post a comment you must log in.
- 66433f8... by Corey Bryant
-
New upstream version 23.0.0
- 1a2385d... by Corey Bryant
-
Merge tag '23.0.0'
Upstream version 23.0.0
- a65cc36... by Corey Bryant
-
New upstream release for OpenStack Bobcat.
- ac0de27... by Corey Bryant
-
d/control: Align (Build-)Depends with upstream.
- 109e412... by Corey Bryant
-
releasing package ironic version 1:23.0.0-0ubuntu1
Revision history for this message
Corey Bryant (corey.bryant) wrote : | # |
Unmerged commits
- 109e412... by Corey Bryant
-
releasing package ironic version 1:23.0.0-0ubuntu1
-
ubuntu-build:0 (build) ubuntu-autopkgtest:0 (build) cloud-archive-build:0 (build) cloud-archive-autopkgtest:0 (build) 1 → 4 of 4 results First • Previous • Next • Last - ac0de27... by Corey Bryant
-
d/control: Align (Build-)Depends with upstream.
- a65cc36... by Corey Bryant
-
New upstream release for OpenStack Bobcat.
- 1a2385d... by Corey Bryant
-
Merge tag '23.0.0'
Upstream version 23.0.0
- 66433f8... by Corey Bryant
-
New upstream version 23.0.0
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/ChangeLog b/ChangeLog | |||
2 | index f4e38ca..4f21043 100644 | |||
3 | --- a/ChangeLog | |||
4 | +++ b/ChangeLog | |||
5 | @@ -1,10 +1,26 @@ | |||
6 | 1 | CHANGES | 1 | CHANGES |
7 | 2 | ======= | 2 | ======= |
8 | 3 | 3 | ||
9 | 4 | 23.0.0 | ||
10 | 5 | ------ | ||
11 | 6 | |||
12 | 7 | * RedfishFirmware Interface | ||
13 | 8 | * inspect\_utils, handle bracketed IPv6 redfish addr | ||
14 | 9 | * Trivial: attach versions to release series | ||
15 | 10 | * redfish\_address - wrap\_ipv6 address | ||
16 | 11 | * Remove most prints for unit tests | ||
17 | 12 | * [releasenotes] Prelude for 2023.2/bobcat | ||
18 | 13 | * devstack - configurable ipv6 address mode | ||
19 | 14 | * Redfish: wait for secure boot state change if it's not immediate | ||
20 | 15 | * CI: Remove ubuntu focal job | ||
21 | 16 | * Fix two places that can cause issues under SQLite | ||
22 | 4 | * [CI] Unblock CI by fixing job regex and non-voting snmp | 17 | * [CI] Unblock CI by fixing job regex and non-voting snmp |
23 | 18 | * Update proliantutils driver requirements for bobcat | ||
24 | 19 | * DB: Only re-query for a lock holder if we cannot lock | ||
25 | 5 | * Add service steps and initial docs | 20 | * Add service steps and initial docs |
26 | 6 | * Log an exception from heartbeat | 21 | * Log an exception from heartbeat |
27 | 7 | * log the version of the conductor starting | 22 | * log the version of the conductor starting |
28 | 23 | * PXE: Remove DHCP option 210 from being set | ||
29 | 8 | * Fully monkey patch eventlet for consistent behavior | 24 | * Fully monkey patch eventlet for consistent behavior |
30 | 9 | * Add missing release mappings for 22.0 and 22.1 | 25 | * Add missing release mappings for 22.0 and 22.1 |
31 | 10 | * Correct bindep.txt entries for bookworm | 26 | * Correct bindep.txt entries for bookworm |
32 | @@ -33,6 +49,7 @@ CHANGES | |||
33 | 33 | * Support sha256/sha512 with the ilo firmware upgrade logic | 49 | * Support sha256/sha512 with the ilo firmware upgrade logic |
34 | 34 | * tox: Remove basepython | 50 | * tox: Remove basepython |
35 | 35 | * Fix typo in deploy\_templates docs | 51 | * Fix typo in deploy\_templates docs |
36 | 52 | * Fix minor grammar issues in the help for new inspector options | ||
37 | 36 | * Add python3.10 support in testing runtime | 53 | * Add python3.10 support in testing runtime |
38 | 37 | * DB: Select upon delete for allocations | 54 | * DB: Select upon delete for allocations |
39 | 38 | * DB: Streamline allocation interactions | 55 | * DB: Streamline allocation interactions |
40 | diff --git a/PKG-INFO b/PKG-INFO | |||
41 | index d87c849..b71bd51 100644 | |||
42 | --- a/PKG-INFO | |||
43 | +++ b/PKG-INFO | |||
44 | @@ -1,11 +1,53 @@ | |||
45 | 1 | Metadata-Version: 2.1 | 1 | Metadata-Version: 2.1 |
46 | 2 | Name: ironic | 2 | Name: ironic |
48 | 3 | Version: 22.1.1.dev36 | 3 | Version: 23.0.0 |
49 | 4 | Summary: OpenStack Bare Metal Provisioning | 4 | Summary: OpenStack Bare Metal Provisioning |
50 | 5 | Home-page: https://docs.openstack.org/ironic/latest/ | 5 | Home-page: https://docs.openstack.org/ironic/latest/ |
51 | 6 | Author: OpenStack | 6 | Author: OpenStack |
52 | 7 | Author-email: openstack-discuss@lists.openstack.org | 7 | Author-email: openstack-discuss@lists.openstack.org |
53 | 8 | License: UNKNOWN | 8 | License: UNKNOWN |
54 | 9 | Description: ====== | ||
55 | 10 | Ironic | ||
56 | 11 | ====== | ||
57 | 12 | |||
58 | 13 | Team and repository tags | ||
59 | 14 | ------------------------ | ||
60 | 15 | |||
61 | 16 | .. image:: https://governance.openstack.org/tc/badges/ironic.svg | ||
62 | 17 | :target: https://governance.openstack.org/tc/reference/tags/index.html | ||
63 | 18 | |||
64 | 19 | Overview | ||
65 | 20 | -------- | ||
66 | 21 | |||
67 | 22 | Ironic consists of an API and plug-ins for managing and provisioning | ||
68 | 23 | physical machines in a security-aware and fault-tolerant manner. It can be | ||
69 | 24 | used with nova as a hypervisor driver, or standalone service using bifrost. | ||
70 | 25 | By default, it will use PXE and IPMI to interact with bare metal machines. | ||
71 | 26 | Ironic also supports vendor-specific plug-ins which may implement additional | ||
72 | 27 | functionality. | ||
73 | 28 | |||
74 | 29 | Ironic is distributed under the terms of the Apache License, Version 2.0. The | ||
75 | 30 | full terms and conditions of this license are detailed in the LICENSE file. | ||
76 | 31 | |||
77 | 32 | Project resources | ||
78 | 33 | ~~~~~~~~~~~~~~~~~ | ||
79 | 34 | |||
80 | 35 | * Documentation: https://docs.openstack.org/ironic/latest | ||
81 | 36 | * Source: https://opendev.org/openstack/ironic | ||
82 | 37 | * Bugs: https://bugs.launchpad.net/ironic/+bugs | ||
83 | 38 | * Wiki: https://wiki.openstack.org/wiki/Ironic | ||
84 | 39 | * APIs: https://docs.openstack.org/api-ref/baremetal/index.html | ||
85 | 40 | * Release Notes: https://docs.openstack.org/releasenotes/ironic/ | ||
86 | 41 | * Design Specifications: https://specs.openstack.org/openstack/ironic-specs/ | ||
87 | 42 | |||
88 | 43 | Project status, bugs, and requests for feature enhancements (RFEs) are tracked | ||
89 | 44 | in StoryBoard: | ||
90 | 45 | https://storyboard.openstack.org/#!/project/943 | ||
91 | 46 | |||
92 | 47 | For information on how to contribute to ironic, see | ||
93 | 48 | https://docs.openstack.org/ironic/latest/contributor | ||
94 | 49 | |||
95 | 50 | |||
96 | 9 | Platform: UNKNOWN | 51 | Platform: UNKNOWN |
97 | 10 | Classifier: Environment :: OpenStack | 52 | Classifier: Environment :: OpenStack |
98 | 11 | Classifier: Intended Audience :: Information Technology | 53 | Classifier: Intended Audience :: Information Technology |
99 | @@ -23,48 +65,3 @@ Provides-Extra: devstack | |||
100 | 23 | Provides-Extra: guru_meditation_reports | 65 | Provides-Extra: guru_meditation_reports |
101 | 24 | Provides-Extra: i18n | 66 | Provides-Extra: i18n |
102 | 25 | Provides-Extra: test | 67 | Provides-Extra: test |
103 | 26 | License-File: LICENSE | ||
104 | 27 | |||
105 | 28 | ====== | ||
106 | 29 | Ironic | ||
107 | 30 | ====== | ||
108 | 31 | |||
109 | 32 | Team and repository tags | ||
110 | 33 | ------------------------ | ||
111 | 34 | |||
112 | 35 | .. image:: https://governance.openstack.org/tc/badges/ironic.svg | ||
113 | 36 | :target: https://governance.openstack.org/tc/reference/tags/index.html | ||
114 | 37 | |||
115 | 38 | Overview | ||
116 | 39 | -------- | ||
117 | 40 | |||
118 | 41 | Ironic consists of an API and plug-ins for managing and provisioning | ||
119 | 42 | physical machines in a security-aware and fault-tolerant manner. It can be | ||
120 | 43 | used with nova as a hypervisor driver, or standalone service using bifrost. | ||
121 | 44 | By default, it will use PXE and IPMI to interact with bare metal machines. | ||
122 | 45 | Ironic also supports vendor-specific plug-ins which may implement additional | ||
123 | 46 | functionality. | ||
124 | 47 | |||
125 | 48 | Ironic is distributed under the terms of the Apache License, Version 2.0. The | ||
126 | 49 | full terms and conditions of this license are detailed in the LICENSE file. | ||
127 | 50 | |||
128 | 51 | Project resources | ||
129 | 52 | ~~~~~~~~~~~~~~~~~ | ||
130 | 53 | |||
131 | 54 | * Documentation: https://docs.openstack.org/ironic/latest | ||
132 | 55 | * Source: https://opendev.org/openstack/ironic | ||
133 | 56 | * Bugs: https://bugs.launchpad.net/ironic/+bugs | ||
134 | 57 | * Wiki: https://wiki.openstack.org/wiki/Ironic | ||
135 | 58 | * APIs: https://docs.openstack.org/api-ref/baremetal/index.html | ||
136 | 59 | * Release Notes: https://docs.openstack.org/releasenotes/ironic/ | ||
137 | 60 | * Design Specifications: https://specs.openstack.org/openstack/ironic-specs/ | ||
138 | 61 | |||
139 | 62 | Project status, bugs, and requests for feature enhancements (RFEs) are tracked | ||
140 | 63 | in StoryBoard: | ||
141 | 64 | https://storyboard.openstack.org/#!/project/943 | ||
142 | 65 | |||
143 | 66 | For information on how to contribute to ironic, see | ||
144 | 67 | https://docs.openstack.org/ironic/latest/contributor | ||
145 | 68 | |||
146 | 69 | |||
147 | 70 | |||
148 | diff --git a/api-ref/source/baremetal-api-v1-node-management.inc b/api-ref/source/baremetal-api-v1-node-management.inc | |||
149 | index 6ff275e..4d9bdaa 100644 | |||
150 | --- a/api-ref/source/baremetal-api-v1-node-management.inc | |||
151 | +++ b/api-ref/source/baremetal-api-v1-node-management.inc | |||
152 | @@ -306,6 +306,10 @@ Change Node Boot Mode | |||
153 | 306 | 306 | ||
154 | 307 | Request a change to the Node's boot mode. | 307 | Request a change to the Node's boot mode. |
155 | 308 | 308 | ||
156 | 309 | .. note:: | ||
157 | 310 | Depending on the driver and the underlying hardware, changing boot mode may | ||
158 | 311 | result in an automatic reboot. | ||
159 | 312 | |||
160 | 309 | .. versionadded:: 1.76 | 313 | .. versionadded:: 1.76 |
161 | 310 | A change in node's boot mode can be requested. | 314 | A change in node's boot mode can be requested. |
162 | 311 | 315 | ||
163 | @@ -341,6 +345,10 @@ Change Node Secure Boot | |||
164 | 341 | 345 | ||
165 | 342 | Request a change to the Node's secure boot state. | 346 | Request a change to the Node's secure boot state. |
166 | 343 | 347 | ||
167 | 348 | .. note:: | ||
168 | 349 | Depending on the driver and the underlying hardware, changing the secure | ||
169 | 350 | boot state may result in an automatic reboot. | ||
170 | 351 | |||
171 | 344 | .. versionadded:: 1.76 | 352 | .. versionadded:: 1.76 |
172 | 345 | A change in node's secure boot state can be requested. | 353 | A change in node's secure boot state can be requested. |
173 | 346 | 354 | ||
174 | diff --git a/api-ref/source/baremetal-api-v1-nodes-firmware.inc b/api-ref/source/baremetal-api-v1-nodes-firmware.inc | |||
175 | index ed17e0b..15ea992 100644 | |||
176 | --- a/api-ref/source/baremetal-api-v1-nodes-firmware.inc | |||
177 | +++ b/api-ref/source/baremetal-api-v1-nodes-firmware.inc | |||
178 | @@ -4,7 +4,7 @@ | |||
179 | 4 | Node Firmware (nodes) | 4 | Node Firmware (nodes) |
180 | 5 | ===================== | 5 | ===================== |
181 | 6 | 6 | ||
183 | 7 | .. versionadded:: 1.84 | 7 | .. versionadded:: 1.86 |
184 | 8 | 8 | ||
185 | 9 | Given a Node identifier (``uuid`` or ``name``), the API exposes the list of | 9 | Given a Node identifier (``uuid`` or ``name``), the API exposes the list of |
186 | 10 | all Firmware Components associated with that Node. | 10 | all Firmware Components associated with that Node. |
187 | diff --git a/debian/changelog b/debian/changelog | |||
188 | index 105559b..1783bf3 100644 | |||
189 | --- a/debian/changelog | |||
190 | +++ b/debian/changelog | |||
191 | @@ -1,8 +1,10 @@ | |||
193 | 1 | ironic (1:22.1.0+git2023090714.985c7fdf-0ubuntu2) UNRELEASED; urgency=medium | 1 | ironic (1:23.0.0-0ubuntu1) mantic; urgency=medium |
194 | 2 | 2 | ||
195 | 3 | * d/watch: Drop major version. | 3 | * d/watch: Drop major version. |
196 | 4 | * New upstream release for OpenStack Bobcat. | ||
197 | 5 | * d/control: Align (Build-)Depends with upstream. | ||
198 | 4 | 6 | ||
200 | 5 | -- Corey Bryant <corey.bryant@canonical.com> Wed, 04 Oct 2023 10:21:10 -0400 | 7 | -- Corey Bryant <corey.bryant@canonical.com> Wed, 04 Oct 2023 10:27:14 -0400 |
201 | 6 | 8 | ||
202 | 7 | ironic (1:22.1.0+git2023090714.985c7fdf-0ubuntu1) mantic; urgency=medium | 9 | ironic (1:22.1.0+git2023090714.985c7fdf-0ubuntu1) mantic; urgency=medium |
203 | 8 | 10 | ||
204 | diff --git a/debian/control b/debian/control | |||
205 | index a66689c..1cfb645 100644 | |||
206 | --- a/debian/control | |||
207 | +++ b/debian/control | |||
208 | @@ -11,6 +11,21 @@ Build-Depends: | |||
209 | 11 | python3-setuptools, | 11 | python3-setuptools, |
210 | 12 | python3-sphinx (>= 2.0.0), | 12 | python3-sphinx (>= 2.0.0), |
211 | 13 | Build-Depends-Indep: | 13 | Build-Depends-Indep: |
212 | 14 | *** new test-requirements.txt deps (start) | ||
213 | 15 | NOT_FOUND(pyasn1-lextudio)>=1.1.0, | ||
214 | 16 | NOT_FOUND(pyasn1-modules-lextudio)>=0.2.0, | ||
215 | 17 | NOT_FOUND(pysnmp-lextudio)>=5.0.0, | ||
216 | 18 | *** new test-requirements.txt deps (end) *** | ||
217 | 19 | *** new driver-requirements.txt deps (start) | ||
218 | 20 | NOT_FOUND(pyasn1-lextudio)>=1.1.0, | ||
219 | 21 | NOT_FOUND(pyasn1-modules-lextudio)>=0.2.0, | ||
220 | 22 | NOT_FOUND(pysnmp-lextudio)>=5.0.0, | ||
221 | 23 | *** new driver-requirements.txt deps (end) *** | ||
222 | 24 | *** new doc/requirements.txt deps (start) | ||
223 | 25 | NOT_FOUND(pyasn1-lextudio)>=1.1.0, | ||
224 | 26 | NOT_FOUND(pyasn1-modules-lextudio)>=0.2.0, | ||
225 | 27 | NOT_FOUND(pysnmp-lextudio)>=5.0.0, | ||
226 | 28 | *** new doc/requirements.txt deps (end) *** | ||
227 | 14 | alembic (>= 0.9.6), | 29 | alembic (>= 0.9.6), |
228 | 15 | crudini, | 30 | crudini, |
229 | 16 | python3-alembic (>= 1.4.2), | 31 | python3-alembic (>= 1.4.2), |
230 | diff --git a/devstack/lib/ironic b/devstack/lib/ironic | |||
231 | index 882b5f6..ffd6601 100644 | |||
232 | --- a/devstack/lib/ironic | |||
233 | +++ b/devstack/lib/ironic | |||
234 | @@ -382,6 +382,7 @@ IRONIC_UWSGI=$IRONIC_BIN_DIR/ironic-api-wsgi | |||
235 | 382 | 382 | ||
236 | 383 | # Lets support IPv6 testing! | 383 | # Lets support IPv6 testing! |
237 | 384 | IRONIC_IP_VERSION=${IRONIC_IP_VERSION:-${IP_VERSION:-4}} | 384 | IRONIC_IP_VERSION=${IRONIC_IP_VERSION:-${IP_VERSION:-4}} |
238 | 385 | IRONIC_IPV6_ADDRESS_MODE=${IRONIC_IPV6_ADDRESS_MODE:-dhcpv6-stateless} | ||
239 | 385 | 386 | ||
240 | 386 | # Ironic connection info. Note the port must be specified. | 387 | # Ironic connection info. Note the port must be specified. |
241 | 387 | if is_service_enabled tls-proxy; then | 388 | if is_service_enabled tls-proxy; then |
242 | @@ -1424,11 +1425,9 @@ function configure_ironic_provision_network { | |||
243 | 1424 | --subnet-range $IRONIC_PROVISION_SUBNET_PREFIX \ | 1425 | --subnet-range $IRONIC_PROVISION_SUBNET_PREFIX \ |
244 | 1425 | --dns-nameserver 8.8.8.8 -f value -c id)" | 1426 | --dns-nameserver 8.8.8.8 -f value -c id)" |
245 | 1426 | else | 1427 | else |
246 | 1427 | # NOTE(TheJulia): Consider changing this to stateful to support UEFI once we move | ||
247 | 1428 | # CI to Ubuntu Jammy as it will support v6 and v4 UEFI firmware driven boot ops. | ||
248 | 1429 | subnet_id="$(openstack --os-cloud $OS_CLOUD subnet create --ip-version 6 \ | 1428 | subnet_id="$(openstack --os-cloud $OS_CLOUD subnet create --ip-version 6 \ |
251 | 1430 | --ipv6-address-mode dhcpv6-stateless \ | 1429 | --ipv6-address-mode $IRONIC_IPV6_ADDRESS_MODE \ |
252 | 1431 | --ipv6-ra-mode dhcpv6-stateless \ | 1430 | --ipv6-ra-mode $IRONIC_IPV6_ADDRESS_MODE \ |
253 | 1432 | --dns-nameserver 2001:4860:4860::8888 \ | 1431 | --dns-nameserver 2001:4860:4860::8888 \ |
254 | 1433 | ${net_segment_id:+--network-segment $net_segment_id} \ | 1432 | ${net_segment_id:+--network-segment $net_segment_id} \ |
255 | 1434 | $IRONIC_PROVISION_PROVIDER_SUBNET_NAME \ | 1433 | $IRONIC_PROVISION_PROVIDER_SUBNET_NAME \ |
256 | diff --git a/doc/source/admin/drivers/redfish.rst b/doc/source/admin/drivers/redfish.rst | |||
257 | index 6628f48..c4694b8 100644 | |||
258 | --- a/doc/source/admin/drivers/redfish.rst | |||
259 | +++ b/doc/source/admin/drivers/redfish.rst | |||
260 | @@ -140,6 +140,22 @@ Two clean and deploy steps are provided for key management: | |||
261 | 140 | ``management.clear_secure_boot_keys`` | 140 | ``management.clear_secure_boot_keys`` |
262 | 141 | removes all secure boot keys from the node. | 141 | removes all secure boot keys from the node. |
263 | 142 | 142 | ||
264 | 143 | Rebooting on boot mode changes | ||
265 | 144 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
266 | 145 | |||
267 | 146 | While some hardware is able to change the boot mode or the `UEFI secure boot`_ | ||
268 | 147 | state immediately, other models may require a reboot for such a change to be | ||
269 | 148 | applied. Furthermore, some hardware models cannot change the boot mode and the | ||
270 | 149 | secure boot state simultaneously, requiring several reboots. | ||
271 | 150 | |||
272 | 151 | The Bare Metal service refreshes the System resource after issuing a PATCH | ||
273 | 152 | request against it. If the expected change is not observed, the node is | ||
274 | 153 | rebooted, and the Bare Metal service waits until the change is applied. In the | ||
275 | 154 | end, the previous power state is restored. | ||
276 | 155 | |||
277 | 156 | This logic makes changing boot configuration more robust at the expense of | ||
278 | 157 | several reboots in the worst case. | ||
279 | 158 | |||
280 | 143 | Out-Of-Band inspection | 159 | Out-Of-Band inspection |
281 | 144 | ====================== | 160 | ====================== |
282 | 145 | 161 | ||
283 | diff --git a/doc/source/contributor/webapi-version-history.rst b/doc/source/contributor/webapi-version-history.rst | |||
284 | index efd34d7..e2f0a73 100644 | |||
285 | --- a/doc/source/contributor/webapi-version-history.rst | |||
286 | +++ b/doc/source/contributor/webapi-version-history.rst | |||
287 | @@ -15,8 +15,8 @@ based resources, which indicates the current step. | |||
288 | 15 | 15 | ||
289 | 16 | Adds a ``firmware_interface`` field to the ``/v1/nodes`` resources. | 16 | Adds a ``firmware_interface`` field to the ``/v1/nodes`` resources. |
290 | 17 | 17 | ||
293 | 18 | 1.85 (Bobcat) | 18 | 1.85 (Bobcat, 22.1) |
294 | 19 | ------------- | 19 | ------------------- |
295 | 20 | 20 | ||
296 | 21 | This version adds a new provision state change verb called ``unhold`` | 21 | This version adds a new provision state change verb called ``unhold`` |
297 | 22 | to be utilized with the new ``provision_state`` values ``clean hold`` | 22 | to be utilized with the new ``provision_state`` values ``clean hold`` |
298 | @@ -24,14 +24,14 @@ and ``deploy hold``. The verb instructs Ironic to remove the node | |||
299 | 24 | from it's present hold and to resume it's prior cleaning or | 24 | from it's present hold and to resume it's prior cleaning or |
300 | 25 | deployment process. | 25 | deployment process. |
301 | 26 | 26 | ||
304 | 27 | 1.84 (Bobcat) | 27 | 1.84 (Bobcat, 22.1) |
305 | 28 | ------------- | 28 | ------------------- |
306 | 29 | 29 | ||
307 | 30 | Add callback endpoint for in-band inspection ``/v1/continue_inspection``. | 30 | Add callback endpoint for in-band inspection ``/v1/continue_inspection``. |
308 | 31 | This endpoint is not designed to be used by regular users. | 31 | This endpoint is not designed to be used by regular users. |
309 | 32 | 32 | ||
312 | 33 | 1.83 (Bobcat) | 33 | 1.83 (Bobcat, 22.0) |
313 | 34 | ------------- | 34 | ------------------- |
314 | 35 | 35 | ||
315 | 36 | This version adds a concept of child nodes through the use of a | 36 | This version adds a concept of child nodes through the use of a |
316 | 37 | ``parent_node`` field which can be set on a node. | 37 | ``parent_node`` field which can be set on a node. |
317 | @@ -51,8 +51,8 @@ Additionally: | |||
318 | 51 | - Adds ``GET /v1/nodes?parent_node={node_ident}`` to explicitly request | 51 | - Adds ``GET /v1/nodes?parent_node={node_ident}`` to explicitly request |
319 | 52 | a detailed list of nodes by parent relationship. | 52 | a detailed list of nodes by parent relationship. |
320 | 53 | 53 | ||
323 | 54 | 1.82 (Antelope) | 54 | 1.82 (Antelope, 21.4) |
324 | 55 | ---------------------- | 55 | --------------------- |
325 | 56 | 56 | ||
326 | 57 | This version signifies the addition of node sharding endpoints. | 57 | This version signifies the addition of node sharding endpoints. |
327 | 58 | 58 | ||
328 | @@ -60,8 +60,8 @@ This version signifies the addition of node sharding endpoints. | |||
329 | 60 | - Adds support for ``GET /v1/shards`` which returns a list of all shards and | 60 | - Adds support for ``GET /v1/shards`` which returns a list of all shards and |
330 | 61 | the count of nodes assigned to each. | 61 | the count of nodes assigned to each. |
331 | 62 | 62 | ||
334 | 63 | 1.81 (Antelope) | 63 | 1.81 (Antelope, 21.3) |
335 | 64 | ---------------------- | 64 | --------------------- |
336 | 65 | 65 | ||
337 | 66 | Add endpoint to retrieve introspection data for nodes via the REST API. | 66 | Add endpoint to retrieve introspection data for nodes via the REST API. |
338 | 67 | 67 | ||
339 | diff --git a/driver-requirements.txt b/driver-requirements.txt | |||
340 | index a26c3cf..b0852d0 100644 | |||
341 | --- a/driver-requirements.txt | |||
342 | +++ b/driver-requirements.txt | |||
343 | @@ -4,7 +4,7 @@ | |||
344 | 4 | # python projects they should package as optional dependencies for Ironic. | 4 | # python projects they should package as optional dependencies for Ironic. |
345 | 5 | 5 | ||
346 | 6 | # These are available on pypi | 6 | # These are available on pypi |
348 | 7 | proliantutils>=2.14.0 | 7 | proliantutils>=2.16.0 |
349 | 8 | pysnmp-lextudio>=5.0.0 # BSD | 8 | pysnmp-lextudio>=5.0.0 # BSD |
350 | 9 | pyasn1-lextudio>=1.1.0 # BSD | 9 | pyasn1-lextudio>=1.1.0 # BSD |
351 | 10 | pyasn1-modules-lextudio>=0.2.0 # BSD | 10 | pyasn1-modules-lextudio>=0.2.0 # BSD |
352 | diff --git a/ironic.egg-info/PKG-INFO b/ironic.egg-info/PKG-INFO | |||
353 | index d87c849..b71bd51 100644 | |||
354 | --- a/ironic.egg-info/PKG-INFO | |||
355 | +++ b/ironic.egg-info/PKG-INFO | |||
356 | @@ -1,11 +1,53 @@ | |||
357 | 1 | Metadata-Version: 2.1 | 1 | Metadata-Version: 2.1 |
358 | 2 | Name: ironic | 2 | Name: ironic |
360 | 3 | Version: 22.1.1.dev36 | 3 | Version: 23.0.0 |
361 | 4 | Summary: OpenStack Bare Metal Provisioning | 4 | Summary: OpenStack Bare Metal Provisioning |
362 | 5 | Home-page: https://docs.openstack.org/ironic/latest/ | 5 | Home-page: https://docs.openstack.org/ironic/latest/ |
363 | 6 | Author: OpenStack | 6 | Author: OpenStack |
364 | 7 | Author-email: openstack-discuss@lists.openstack.org | 7 | Author-email: openstack-discuss@lists.openstack.org |
365 | 8 | License: UNKNOWN | 8 | License: UNKNOWN |
366 | 9 | Description: ====== | ||
367 | 10 | Ironic | ||
368 | 11 | ====== | ||
369 | 12 | |||
370 | 13 | Team and repository tags | ||
371 | 14 | ------------------------ | ||
372 | 15 | |||
373 | 16 | .. image:: https://governance.openstack.org/tc/badges/ironic.svg | ||
374 | 17 | :target: https://governance.openstack.org/tc/reference/tags/index.html | ||
375 | 18 | |||
376 | 19 | Overview | ||
377 | 20 | -------- | ||
378 | 21 | |||
379 | 22 | Ironic consists of an API and plug-ins for managing and provisioning | ||
380 | 23 | physical machines in a security-aware and fault-tolerant manner. It can be | ||
381 | 24 | used with nova as a hypervisor driver, or standalone service using bifrost. | ||
382 | 25 | By default, it will use PXE and IPMI to interact with bare metal machines. | ||
383 | 26 | Ironic also supports vendor-specific plug-ins which may implement additional | ||
384 | 27 | functionality. | ||
385 | 28 | |||
386 | 29 | Ironic is distributed under the terms of the Apache License, Version 2.0. The | ||
387 | 30 | full terms and conditions of this license are detailed in the LICENSE file. | ||
388 | 31 | |||
389 | 32 | Project resources | ||
390 | 33 | ~~~~~~~~~~~~~~~~~ | ||
391 | 34 | |||
392 | 35 | * Documentation: https://docs.openstack.org/ironic/latest | ||
393 | 36 | * Source: https://opendev.org/openstack/ironic | ||
394 | 37 | * Bugs: https://bugs.launchpad.net/ironic/+bugs | ||
395 | 38 | * Wiki: https://wiki.openstack.org/wiki/Ironic | ||
396 | 39 | * APIs: https://docs.openstack.org/api-ref/baremetal/index.html | ||
397 | 40 | * Release Notes: https://docs.openstack.org/releasenotes/ironic/ | ||
398 | 41 | * Design Specifications: https://specs.openstack.org/openstack/ironic-specs/ | ||
399 | 42 | |||
400 | 43 | Project status, bugs, and requests for feature enhancements (RFEs) are tracked | ||
401 | 44 | in StoryBoard: | ||
402 | 45 | https://storyboard.openstack.org/#!/project/943 | ||
403 | 46 | |||
404 | 47 | For information on how to contribute to ironic, see | ||
405 | 48 | https://docs.openstack.org/ironic/latest/contributor | ||
406 | 49 | |||
407 | 50 | |||
408 | 9 | Platform: UNKNOWN | 51 | Platform: UNKNOWN |
409 | 10 | Classifier: Environment :: OpenStack | 52 | Classifier: Environment :: OpenStack |
410 | 11 | Classifier: Intended Audience :: Information Technology | 53 | Classifier: Intended Audience :: Information Technology |
411 | @@ -23,48 +65,3 @@ Provides-Extra: devstack | |||
412 | 23 | Provides-Extra: guru_meditation_reports | 65 | Provides-Extra: guru_meditation_reports |
413 | 24 | Provides-Extra: i18n | 66 | Provides-Extra: i18n |
414 | 25 | Provides-Extra: test | 67 | Provides-Extra: test |
415 | 26 | License-File: LICENSE | ||
416 | 27 | |||
417 | 28 | ====== | ||
418 | 29 | Ironic | ||
419 | 30 | ====== | ||
420 | 31 | |||
421 | 32 | Team and repository tags | ||
422 | 33 | ------------------------ | ||
423 | 34 | |||
424 | 35 | .. image:: https://governance.openstack.org/tc/badges/ironic.svg | ||
425 | 36 | :target: https://governance.openstack.org/tc/reference/tags/index.html | ||
426 | 37 | |||
427 | 38 | Overview | ||
428 | 39 | -------- | ||
429 | 40 | |||
430 | 41 | Ironic consists of an API and plug-ins for managing and provisioning | ||
431 | 42 | physical machines in a security-aware and fault-tolerant manner. It can be | ||
432 | 43 | used with nova as a hypervisor driver, or standalone service using bifrost. | ||
433 | 44 | By default, it will use PXE and IPMI to interact with bare metal machines. | ||
434 | 45 | Ironic also supports vendor-specific plug-ins which may implement additional | ||
435 | 46 | functionality. | ||
436 | 47 | |||
437 | 48 | Ironic is distributed under the terms of the Apache License, Version 2.0. The | ||
438 | 49 | full terms and conditions of this license are detailed in the LICENSE file. | ||
439 | 50 | |||
440 | 51 | Project resources | ||
441 | 52 | ~~~~~~~~~~~~~~~~~ | ||
442 | 53 | |||
443 | 54 | * Documentation: https://docs.openstack.org/ironic/latest | ||
444 | 55 | * Source: https://opendev.org/openstack/ironic | ||
445 | 56 | * Bugs: https://bugs.launchpad.net/ironic/+bugs | ||
446 | 57 | * Wiki: https://wiki.openstack.org/wiki/Ironic | ||
447 | 58 | * APIs: https://docs.openstack.org/api-ref/baremetal/index.html | ||
448 | 59 | * Release Notes: https://docs.openstack.org/releasenotes/ironic/ | ||
449 | 60 | * Design Specifications: https://specs.openstack.org/openstack/ironic-specs/ | ||
450 | 61 | |||
451 | 62 | Project status, bugs, and requests for feature enhancements (RFEs) are tracked | ||
452 | 63 | in StoryBoard: | ||
453 | 64 | https://storyboard.openstack.org/#!/project/943 | ||
454 | 65 | |||
455 | 66 | For information on how to contribute to ironic, see | ||
456 | 67 | https://docs.openstack.org/ironic/latest/contributor | ||
457 | 68 | |||
458 | 69 | |||
459 | 70 | |||
460 | diff --git a/ironic.egg-info/SOURCES.txt b/ironic.egg-info/SOURCES.txt | |||
461 | index e1d1e3c..7da3f81 100644 | |||
462 | --- a/ironic.egg-info/SOURCES.txt | |||
463 | +++ b/ironic.egg-info/SOURCES.txt | |||
464 | @@ -695,6 +695,7 @@ ironic/drivers/modules/network/noop.py | |||
465 | 695 | ironic/drivers/modules/redfish/__init__.py | 695 | ironic/drivers/modules/redfish/__init__.py |
466 | 696 | ironic/drivers/modules/redfish/bios.py | 696 | ironic/drivers/modules/redfish/bios.py |
467 | 697 | ironic/drivers/modules/redfish/boot.py | 697 | ironic/drivers/modules/redfish/boot.py |
468 | 698 | ironic/drivers/modules/redfish/firmware.py | ||
469 | 698 | ironic/drivers/modules/redfish/firmware_utils.py | 699 | ironic/drivers/modules/redfish/firmware_utils.py |
470 | 699 | ironic/drivers/modules/redfish/inspect.py | 700 | ironic/drivers/modules/redfish/inspect.py |
471 | 700 | ironic/drivers/modules/redfish/management.py | 701 | ironic/drivers/modules/redfish/management.py |
472 | @@ -975,6 +976,7 @@ ironic/tests/unit/drivers/modules/network/json_samples/network_data.json | |||
473 | 975 | ironic/tests/unit/drivers/modules/redfish/__init__.py | 976 | ironic/tests/unit/drivers/modules/redfish/__init__.py |
474 | 976 | ironic/tests/unit/drivers/modules/redfish/test_bios.py | 977 | ironic/tests/unit/drivers/modules/redfish/test_bios.py |
475 | 977 | ironic/tests/unit/drivers/modules/redfish/test_boot.py | 978 | ironic/tests/unit/drivers/modules/redfish/test_boot.py |
476 | 979 | ironic/tests/unit/drivers/modules/redfish/test_firmware.py | ||
477 | 978 | ironic/tests/unit/drivers/modules/redfish/test_firmware_utils.py | 980 | ironic/tests/unit/drivers/modules/redfish/test_firmware_utils.py |
478 | 979 | ironic/tests/unit/drivers/modules/redfish/test_inspect.py | 981 | ironic/tests/unit/drivers/modules/redfish/test_inspect.py |
479 | 980 | ironic/tests/unit/drivers/modules/redfish/test_management.py | 982 | ironic/tests/unit/drivers/modules/redfish/test_management.py |
480 | @@ -1021,6 +1023,7 @@ releasenotes/config.yaml | |||
481 | 1021 | releasenotes/notes/.placeholder | 1023 | releasenotes/notes/.placeholder |
482 | 1022 | releasenotes/notes/18.2-prelude-3c8609692bab70a3.yaml | 1024 | releasenotes/notes/18.2-prelude-3c8609692bab70a3.yaml |
483 | 1023 | releasenotes/notes/20.1-prelude-612672742f417477.yaml | 1025 | releasenotes/notes/20.1-prelude-612672742f417477.yaml |
484 | 1026 | releasenotes/notes/23.0-prelude-bobcat-ad7c24f666c22ebf.yaml | ||
485 | 1024 | releasenotes/notes/5.0-release-afb1fbbe595b6bc8.yaml | 1027 | releasenotes/notes/5.0-release-afb1fbbe595b6bc8.yaml |
486 | 1025 | releasenotes/notes/Add-port-option-support-to-ipmitool-e125d07fe13c53e7.yaml | 1028 | releasenotes/notes/Add-port-option-support-to-ipmitool-e125d07fe13c53e7.yaml |
487 | 1026 | releasenotes/notes/Cleanfail-power-off-13b5fdcc2727866a.yaml | 1029 | releasenotes/notes/Cleanfail-power-off-13b5fdcc2727866a.yaml |
488 | @@ -1265,6 +1268,7 @@ releasenotes/notes/bug-2007963-idrac-wsman-raid-apply-configuration-792ccf195057 | |||
489 | 1265 | releasenotes/notes/bug-2008058-fix-factory-reset-status.yaml-52a6119b46e33b37.yaml | 1268 | releasenotes/notes/bug-2008058-fix-factory-reset-status.yaml-52a6119b46e33b37.yaml |
490 | 1266 | releasenotes/notes/bug-2009762-403eac24c4823d2d.yaml | 1269 | releasenotes/notes/bug-2009762-403eac24c4823d2d.yaml |
491 | 1267 | releasenotes/notes/bug-2010613-3ab1f32aaa776f28.yaml | 1270 | releasenotes/notes/bug-2010613-3ab1f32aaa776f28.yaml |
492 | 1271 | releasenotes/notes/bug-2036455-edd0e97335579684.yaml | ||
493 | 1268 | releasenotes/notes/bug-30315-e46eafe5b575f3da.yaml | 1272 | releasenotes/notes/bug-30315-e46eafe5b575f3da.yaml |
494 | 1269 | releasenotes/notes/bug-30316-8c53358681e464eb.yaml | 1273 | releasenotes/notes/bug-30316-8c53358681e464eb.yaml |
495 | 1270 | releasenotes/notes/bug-30317-a972c8d879c98941.yaml | 1274 | releasenotes/notes/bug-30317-a972c8d879c98941.yaml |
496 | @@ -1453,6 +1457,7 @@ releasenotes/notes/fast-track-with-cleaning-438225116a11662d.yaml | |||
497 | 1453 | releasenotes/notes/fifteen-0da3cca48dceab8b.yaml | 1457 | releasenotes/notes/fifteen-0da3cca48dceab8b.yaml |
498 | 1454 | releasenotes/notes/file-name-too-long-72265bb3fec704f8.yaml | 1458 | releasenotes/notes/file-name-too-long-72265bb3fec704f8.yaml |
499 | 1455 | releasenotes/notes/fips-hashlib-bca9beacc2b48fe7.yaml | 1459 | releasenotes/notes/fips-hashlib-bca9beacc2b48fe7.yaml |
500 | 1460 | releasenotes/notes/firmware-interface-8ad6f91aa1f746a0.yaml | ||
501 | 1456 | releasenotes/notes/fix-agent-clean-up-9a25deb85bc53d9b.yaml | 1461 | releasenotes/notes/fix-agent-clean-up-9a25deb85bc53d9b.yaml |
502 | 1457 | releasenotes/notes/fix-agent-ilo-temp-image-cleanup-711429d0e67807ae.yaml | 1462 | releasenotes/notes/fix-agent-ilo-temp-image-cleanup-711429d0e67807ae.yaml |
503 | 1458 | releasenotes/notes/fix-anaconda-deploy-interface-bfa2cfca22b04680.yaml | 1463 | releasenotes/notes/fix-anaconda-deploy-interface-bfa2cfca22b04680.yaml |
504 | @@ -2002,6 +2007,7 @@ releasenotes/notes/releasenote-b3b25c13ea1e2844.yaml | |||
505 | 2002 | releasenotes/notes/reloadable-301ec2aa421abf66.yaml | 2007 | releasenotes/notes/reloadable-301ec2aa421abf66.yaml |
506 | 2003 | releasenotes/notes/rely-on-standalone-ports-supported-8153e1135787828b.yaml | 2008 | releasenotes/notes/rely-on-standalone-ports-supported-8153e1135787828b.yaml |
507 | 2004 | releasenotes/notes/removal-pre-allocation-for-oneview-09310a215b3aaf3c.yaml | 2009 | releasenotes/notes/removal-pre-allocation-for-oneview-09310a215b3aaf3c.yaml |
508 | 2010 | releasenotes/notes/remove-400a563030224c4f.yaml | ||
509 | 2005 | releasenotes/notes/remove-DEPRECATED-options-from-[agent]-7b6cce21b5f52022.yaml | 2011 | releasenotes/notes/remove-DEPRECATED-options-from-[agent]-7b6cce21b5f52022.yaml |
510 | 2006 | releasenotes/notes/remove-agent-heartbeat-timeout-abf8787b8477bae7.yaml | 2012 | releasenotes/notes/remove-agent-heartbeat-timeout-abf8787b8477bae7.yaml |
511 | 2007 | releasenotes/notes/remove-agent-passthru-432b18e6c430cee6.yaml | 2013 | releasenotes/notes/remove-agent-passthru-432b18e6c430cee6.yaml |
512 | @@ -2149,6 +2155,7 @@ releasenotes/notes/token-reboot-b48b5981a58a30ae.yaml | |||
513 | 2149 | releasenotes/notes/train-release-59ff1643ec92c10a.yaml | 2155 | releasenotes/notes/train-release-59ff1643ec92c10a.yaml |
514 | 2150 | releasenotes/notes/transmit-all-ports-b570009d1a008067.yaml | 2156 | releasenotes/notes/transmit-all-ports-b570009d1a008067.yaml |
515 | 2151 | releasenotes/notes/type-error-str-6826c53d7e5e1243.yaml | 2157 | releasenotes/notes/type-error-str-6826c53d7e5e1243.yaml |
516 | 2158 | releasenotes/notes/uefi-and-secureboot-waits-a783215327164e2c.yaml | ||
517 | 2152 | releasenotes/notes/uefi-first-prepare-e7fa1e2a78b4af99.yaml | 2159 | releasenotes/notes/uefi-first-prepare-e7fa1e2a78b4af99.yaml |
518 | 2153 | releasenotes/notes/uefi-grub2-by-default-6b797a9e690d2dd5.yaml | 2160 | releasenotes/notes/uefi-grub2-by-default-6b797a9e690d2dd5.yaml |
519 | 2154 | releasenotes/notes/uefi-is-now-the-default-562b0d68adc59008.yaml | 2161 | releasenotes/notes/uefi-is-now-the-default-562b0d68adc59008.yaml |
520 | diff --git a/ironic.egg-info/entry_points.txt b/ironic.egg-info/entry_points.txt | |||
521 | index 7d3e573..7bb5a43 100644 | |||
522 | --- a/ironic.egg-info/entry_points.txt | |||
523 | +++ b/ironic.egg-info/entry_points.txt | |||
524 | @@ -54,6 +54,7 @@ ramdisk = ironic.drivers.modules.ramdisk:RamdiskDeploy | |||
525 | 54 | [ironic.hardware.interfaces.firmware] | 54 | [ironic.hardware.interfaces.firmware] |
526 | 55 | fake = ironic.drivers.modules.fake:FakeFirmware | 55 | fake = ironic.drivers.modules.fake:FakeFirmware |
527 | 56 | no-firmware = ironic.drivers.modules.noop:NoFirmware | 56 | no-firmware = ironic.drivers.modules.noop:NoFirmware |
528 | 57 | redfish = ironic.drivers.modules.redfish.firmware:RedfishFirmware | ||
529 | 57 | 58 | ||
530 | 58 | [ironic.hardware.interfaces.inspect] | 59 | [ironic.hardware.interfaces.inspect] |
531 | 59 | agent = ironic.drivers.modules.inspector:AgentInspect | 60 | agent = ironic.drivers.modules.inspector:AgentInspect |
532 | diff --git a/ironic.egg-info/pbr.json b/ironic.egg-info/pbr.json | |||
533 | index 3a310bb..21af2bb 100644 | |||
534 | --- a/ironic.egg-info/pbr.json | |||
535 | +++ b/ironic.egg-info/pbr.json | |||
536 | @@ -1 +1 @@ | |||
537 | 1 | {"git_version": "985c7fdf2", "is_release": false} | ||
538 | 2 | \ No newline at end of file | 1 | \ No newline at end of file |
539 | 2 | {"git_version": "f78f87227", "is_release": true} | ||
540 | 3 | \ No newline at end of file | 3 | \ No newline at end of file |
541 | diff --git a/ironic/api/controllers/v1/utils.py b/ironic/api/controllers/v1/utils.py | |||
542 | index 10c631d..3a7806e 100644 | |||
543 | --- a/ironic/api/controllers/v1/utils.py | |||
544 | +++ b/ironic/api/controllers/v1/utils.py | |||
545 | @@ -2018,6 +2018,6 @@ def allow_continue_inspection_endpoint(): | |||
546 | 2018 | def allow_firmware_interface(): | 2018 | def allow_firmware_interface(): |
547 | 2019 | """Check if we should support firmware interface and endpoints. | 2019 | """Check if we should support firmware interface and endpoints. |
548 | 2020 | 2020 | ||
550 | 2021 | Version 1.84 of the API added support for firmware interface. | 2021 | Version 1.86 of the API added support for firmware interface. |
551 | 2022 | """ | 2022 | """ |
552 | 2023 | return api.request.version.minor >= versions.MINOR_86_FIRMWARE_INTERFACE | 2023 | return api.request.version.minor >= versions.MINOR_86_FIRMWARE_INTERFACE |
553 | diff --git a/ironic/common/pxe_utils.py b/ironic/common/pxe_utils.py | |||
554 | index a4fd438..8c61e23 100644 | |||
555 | --- a/ironic/common/pxe_utils.py | |||
556 | +++ b/ironic/common/pxe_utils.py | |||
557 | @@ -58,7 +58,6 @@ DHCPV6_BOOTFILE_NAME = '59' # rfc5970 | |||
558 | 58 | # DHCPV6_BOOTFILE_PARAMS = '60' # rfc5970 | 58 | # DHCPV6_BOOTFILE_PARAMS = '60' # rfc5970 |
559 | 59 | DHCP_TFTP_SERVER_ADDRESS = '150' # rfc5859 | 59 | DHCP_TFTP_SERVER_ADDRESS = '150' # rfc5859 |
560 | 60 | DHCP_IPXE_ENCAP_OPTS = '175' # Tentatively Assigned | 60 | DHCP_IPXE_ENCAP_OPTS = '175' # Tentatively Assigned |
561 | 61 | DHCP_TFTP_PATH_PREFIX = '210' # rfc5071 | ||
562 | 62 | DHCP_SERVER_IP_ADDRESS = '255' # dnsmasq server-ip-address | 61 | DHCP_SERVER_IP_ADDRESS = '255' # dnsmasq server-ip-address |
563 | 63 | 62 | ||
564 | 64 | DEPLOY_KERNEL_RAMDISK_LABELS = ['deploy_kernel', 'deploy_ramdisk'] | 63 | DEPLOY_KERNEL_RAMDISK_LABELS = ['deploy_kernel', 'deploy_ramdisk'] |
565 | @@ -562,16 +561,6 @@ def dhcp_options_for_instance(task, ipxe_enabled=False, url_boot=False, | |||
566 | 562 | else: | 561 | else: |
567 | 563 | dhcp_opts.append({'opt_name': boot_file_param, | 562 | dhcp_opts.append({'opt_name': boot_file_param, |
568 | 564 | 'opt_value': boot_file}) | 563 | 'opt_value': boot_file}) |
569 | 565 | # 210 == tftp server path-prefix or tftp root, will be used to find | ||
570 | 566 | # pxelinux.cfg directory. The pxelinux.0 loader infers this information | ||
571 | 567 | # from it's own path, but Petitboot needs it to be specified by this | ||
572 | 568 | # option since it doesn't use pxelinux.0 loader. | ||
573 | 569 | if not url_boot: | ||
574 | 570 | # Enforce trailing slash | ||
575 | 571 | prefix = os.path.join(CONF.pxe.tftp_root, '') | ||
576 | 572 | dhcp_opts.append( | ||
577 | 573 | {'opt_name': DHCP_TFTP_PATH_PREFIX, | ||
578 | 574 | 'opt_value': prefix}) | ||
579 | 575 | 564 | ||
580 | 576 | if not url_boot: | 565 | if not url_boot: |
581 | 577 | dhcp_opts.append({'opt_name': DHCP_TFTP_SERVER_NAME, | 566 | dhcp_opts.append({'opt_name': DHCP_TFTP_SERVER_NAME, |
582 | diff --git a/ironic/common/utils.py b/ironic/common/utils.py | |||
583 | index 6bae4e7..efd3049 100644 | |||
584 | --- a/ironic/common/utils.py | |||
585 | +++ b/ironic/common/utils.py | |||
586 | @@ -578,8 +578,12 @@ def pop_node_nested_field(node, collection, field, default=None): | |||
587 | 578 | 578 | ||
588 | 579 | def wrap_ipv6(ip): | 579 | def wrap_ipv6(ip): |
589 | 580 | """Wrap the address in square brackets if it's an IPv6 address.""" | 580 | """Wrap the address in square brackets if it's an IPv6 address.""" |
592 | 581 | if ipaddress.ip_address(ip).version == 6: | 581 | try: |
593 | 582 | return "[%s]" % ip | 582 | if ipaddress.ip_address(ip).version == 6: |
594 | 583 | return "[%s]" % ip | ||
595 | 584 | except ValueError: | ||
596 | 585 | pass | ||
597 | 586 | |||
598 | 583 | return ip | 587 | return ip |
599 | 584 | 588 | ||
600 | 585 | 589 | ||
601 | diff --git a/ironic/conductor/cleaning.py b/ironic/conductor/cleaning.py | |||
602 | index 1e79f97..cd2d999 100644 | |||
603 | --- a/ironic/conductor/cleaning.py | |||
604 | +++ b/ironic/conductor/cleaning.py | |||
605 | @@ -85,6 +85,8 @@ def do_node_clean(task, clean_steps=None, disable_ramdisk=False): | |||
606 | 85 | # Retrieve BIOS config settings for this node | 85 | # Retrieve BIOS config settings for this node |
607 | 86 | utils.node_cache_bios_settings(task, node) | 86 | utils.node_cache_bios_settings(task, node) |
608 | 87 | 87 | ||
609 | 88 | # Retrieve Firmware Components for this node if possible | ||
610 | 89 | utils.node_cache_firmware_components(task) | ||
611 | 88 | # Allow the deploy driver to set up the ramdisk again (necessary for | 90 | # Allow the deploy driver to set up the ramdisk again (necessary for |
612 | 89 | # IPA cleaning) | 91 | # IPA cleaning) |
613 | 90 | try: | 92 | try: |
614 | diff --git a/ironic/conductor/steps.py b/ironic/conductor/steps.py | |||
615 | index 3dfbb2e..dcdfa46 100644 | |||
616 | --- a/ironic/conductor/steps.py | |||
617 | +++ b/ironic/conductor/steps.py | |||
618 | @@ -31,9 +31,10 @@ CLEANING_INTERFACE_PRIORITY = { | |||
619 | 31 | # by which interface is implementing the clean step. The clean step of the | 31 | # by which interface is implementing the clean step. The clean step of the |
620 | 32 | # interface with the highest value here, will be executed first in that | 32 | # interface with the highest value here, will be executed first in that |
621 | 33 | # case. | 33 | # case. |
625 | 34 | 'vendor': 6, | 34 | 'vendor': 7, |
626 | 35 | 'power': 5, | 35 | 'power': 6, |
627 | 36 | 'management': 4, | 36 | 'management': 5, |
628 | 37 | 'firmware': 4, | ||
629 | 37 | 'deploy': 3, | 38 | 'deploy': 3, |
630 | 38 | 'bios': 2, | 39 | 'bios': 2, |
631 | 39 | 'raid': 1, | 40 | 'raid': 1, |
632 | @@ -46,9 +47,10 @@ DEPLOYING_INTERFACE_PRIORITY = { | |||
633 | 46 | # TODO(rloo): If we think it makes sense to have the interface priorities | 47 | # TODO(rloo): If we think it makes sense to have the interface priorities |
634 | 47 | # the same for cleaning & deploying, replace the two with one e.g. | 48 | # the same for cleaning & deploying, replace the two with one e.g. |
635 | 48 | # 'INTERFACE_PRIORITIES'. | 49 | # 'INTERFACE_PRIORITIES'. |
639 | 49 | 'vendor': 6, | 50 | 'vendor': 7, |
640 | 50 | 'power': 5, | 51 | 'power': 6, |
641 | 51 | 'management': 4, | 52 | 'management': 5, |
642 | 53 | 'firmware': 4, | ||
643 | 52 | 'deploy': 3, | 54 | 'deploy': 3, |
644 | 53 | 'bios': 2, | 55 | 'bios': 2, |
645 | 54 | 'raid': 1, | 56 | 'raid': 1, |
646 | @@ -61,11 +63,12 @@ VERIFYING_INTERFACE_PRIORITY = { | |||
647 | 61 | # by which interface is implementing the verify step. The verifying step of | 63 | # by which interface is implementing the verify step. The verifying step of |
648 | 62 | # the interface with the highest value here, will be executed first in | 64 | # the interface with the highest value here, will be executed first in |
649 | 63 | # that case. | 65 | # that case. |
653 | 64 | 'power': 12, | 66 | 'power': 13, |
654 | 65 | 'management': 11, | 67 | 'management': 12, |
655 | 66 | 'boot': 8, | 68 | 'firmware': 11, |
656 | 67 | 'inspect': 10, | 69 | 'inspect': 10, |
657 | 68 | 'deploy': 9, | 70 | 'deploy': 9, |
658 | 71 | 'boot': 8, | ||
659 | 69 | 'bios': 7, | 72 | 'bios': 7, |
660 | 70 | 'raid': 6, | 73 | 'raid': 6, |
661 | 71 | 'vendor': 5, | 74 | 'vendor': 5, |
662 | diff --git a/ironic/conductor/utils.py b/ironic/conductor/utils.py | |||
663 | index 1255c86..e79ce3d 100644 | |||
664 | --- a/ironic/conductor/utils.py | |||
665 | +++ b/ironic/conductor/utils.py | |||
666 | @@ -1828,3 +1828,15 @@ def servicing_error_handler(task, logmsg, errmsg=None, traceback=False, | |||
667 | 1828 | 1828 | ||
668 | 1829 | if set_fail_state and node.provision_state != states.SERVICEFAIL: | 1829 | if set_fail_state and node.provision_state != states.SERVICEFAIL: |
669 | 1830 | task.process_event('fail') | 1830 | task.process_event('fail') |
670 | 1831 | |||
671 | 1832 | |||
672 | 1833 | def node_cache_firmware_components(task): | ||
673 | 1834 | """Do caching of firmware components if supported by driver""" | ||
674 | 1835 | |||
675 | 1836 | try: | ||
676 | 1837 | LOG.debug('Getting Firmware Components for node %s', task.node.uuid) | ||
677 | 1838 | task.driver.firmware.validate(task) | ||
678 | 1839 | task.driver.firmware.cache_firmware_components(task) | ||
679 | 1840 | except exception.UnsupportedDriverExtension: | ||
680 | 1841 | LOG.warning('Firmware Components are not supported for node %s, ' | ||
681 | 1842 | 'skipping', task.node.uuid) | ||
682 | diff --git a/ironic/conf/inspector.py b/ironic/conf/inspector.py | |||
683 | index 6014fb3..434c863 100644 | |||
684 | --- a/ironic/conf/inspector.py | |||
685 | +++ b/ironic/conf/inspector.py | |||
686 | @@ -20,13 +20,15 @@ from ironic.conf import auth | |||
687 | 20 | 20 | ||
688 | 21 | VALID_ADD_PORTS_VALUES = { | 21 | VALID_ADD_PORTS_VALUES = { |
689 | 22 | 'all': _('all MAC addresses'), | 22 | 'all': _('all MAC addresses'), |
691 | 23 | 'active': _('MAC addresses of NIC\'s with IP addresses'), | 23 | 'active': _('MAC addresses of NICs with IP addresses'), |
692 | 24 | 'pxe': _('only the MAC address of the PXE NIC'), | 24 | 'pxe': _('only the MAC address of the PXE NIC'), |
693 | 25 | 'disabled': _('do not create any ports'), | 25 | 'disabled': _('do not create any ports'), |
694 | 26 | } | 26 | } |
695 | 27 | VALID_KEEP_PORTS_VALUES = { | 27 | VALID_KEEP_PORTS_VALUES = { |
698 | 28 | 'all': _('keep even ports with MAC\'s not present in the inventory'), | 28 | 'all': _('keep all ports, even ones with MAC addresses that are not ' |
699 | 29 | 'present': _('keep only ports with MAC\'s present in the inventory'), | 29 | 'present in the inventory'), |
700 | 30 | 'present': _('keep only ports with MAC addresses present in ' | ||
701 | 31 | 'the inventory'), | ||
702 | 30 | 'added': _('keep only ports determined by the add_ports option'), | 32 | 'added': _('keep only ports determined by the add_ports option'), |
703 | 31 | } | 33 | } |
704 | 32 | 34 | ||
705 | diff --git a/ironic/conf/redfish.py b/ironic/conf/redfish.py | |||
706 | index 68aa961..87a359d 100644 | |||
707 | --- a/ironic/conf/redfish.py | |||
708 | +++ b/ironic/conf/redfish.py | |||
709 | @@ -115,6 +115,12 @@ opts = [ | |||
710 | 115 | default=60, | 115 | default=60, |
711 | 116 | help=_('Number of seconds to wait between checking for ' | 116 | help=_('Number of seconds to wait between checking for ' |
712 | 117 | 'failed raid config tasks')), | 117 | 'failed raid config tasks')), |
713 | 118 | cfg.IntOpt('boot_mode_config_timeout', | ||
714 | 119 | min=0, | ||
715 | 120 | default=900, | ||
716 | 121 | help=_('Number of seconds to wait for boot mode or secure ' | ||
717 | 122 | 'boot status change to take effect after a reboot. ' | ||
718 | 123 | 'Set to 0 to disable waiting.')), | ||
719 | 118 | ] | 124 | ] |
720 | 119 | 125 | ||
721 | 120 | 126 | ||
722 | diff --git a/ironic/db/sqlalchemy/api.py b/ironic/db/sqlalchemy/api.py | |||
723 | index bf591ca..a6ab523 100644 | |||
724 | --- a/ironic/db/sqlalchemy/api.py | |||
725 | +++ b/ironic/db/sqlalchemy/api.py | |||
726 | @@ -387,7 +387,7 @@ def _paginate_query(model, limit=None, marker=None, sort_key=None, | |||
727 | 387 | return [] | 387 | return [] |
728 | 388 | if return_base_tuple: | 388 | if return_base_tuple: |
729 | 389 | # The caller expects a tuple, lets just give it to them. | 389 | # The caller expects a tuple, lets just give it to them. |
731 | 390 | return res | 390 | return [tuple(r) for r in res] |
732 | 391 | # Everything is a tuple in a resultset from the unified interface | 391 | # Everything is a tuple in a resultset from the unified interface |
733 | 392 | # but for objects, our model expects just object access, | 392 | # but for objects, our model expects just object access, |
734 | 393 | # so we extract and return them. | 393 | # so we extract and return them. |
735 | @@ -585,16 +585,9 @@ class Connection(api.Connection): | |||
736 | 585 | # If we are not using sorting, or any other query magic, | 585 | # If we are not using sorting, or any other query magic, |
737 | 586 | # we could likely just do a query execution and | 586 | # we could likely just do a query execution and |
738 | 587 | # prepare the tuple responses. | 587 | # prepare the tuple responses. |
749 | 588 | results = _paginate_query(models.Node, limit, marker, | 588 | return _paginate_query(models.Node, limit, marker, |
750 | 589 | sort_key, sort_dir, query, | 589 | sort_key, sort_dir, query, |
751 | 590 | return_base_tuple=True) | 590 | return_base_tuple=True) |
742 | 591 | # Need to copy the data to close out the _paginate_query | ||
743 | 592 | # object. | ||
744 | 593 | new_result = [tuple([ent for ent in r]) for r in results] | ||
745 | 594 | # Explicitly free results so we don't hang on to it. | ||
746 | 595 | del results | ||
747 | 596 | |||
748 | 597 | return new_result | ||
752 | 598 | 591 | ||
753 | 599 | def get_node_list(self, filters=None, limit=None, marker=None, | 592 | def get_node_list(self, filters=None, limit=None, marker=None, |
754 | 600 | sort_key=None, sort_dir=None, fields=None): | 593 | sort_key=None, sort_dir=None, fields=None): |
755 | @@ -714,14 +707,17 @@ class Connection(api.Connection): | |||
756 | 714 | values(reservation=tag). | 707 | values(reservation=tag). |
757 | 715 | execution_options(synchronize_session=False)) | 708 | execution_options(synchronize_session=False)) |
758 | 716 | session.flush() | 709 | session.flush() |
759 | 717 | node = self._get_node_reservation(node.id) | ||
760 | 718 | # NOTE(TheJulia): In SQLAlchemy 2.0 style, we don't | 710 | # NOTE(TheJulia): In SQLAlchemy 2.0 style, we don't |
761 | 719 | # magically get a changed node as they moved from the | 711 | # magically get a changed node as they moved from the |
762 | 720 | # many ways to do things to singular ways to do things. | 712 | # many ways to do things to singular ways to do things. |
763 | 721 | if res.rowcount != 1: | 713 | if res.rowcount != 1: |
764 | 722 | # Nothing updated and node exists. Must already be | 714 | # Nothing updated and node exists. Must already be |
767 | 723 | # locked. | 715 | # locked. Identify who holds it and log. |
768 | 724 | raise exception.NodeLocked(node=node.uuid, host=node.reservation) | 716 | if utils.is_ironic_using_sqlite(): |
769 | 717 | lock_holder = CONF.hostname | ||
770 | 718 | else: | ||
771 | 719 | lock_holder = self._get_node_reservation(node.id).reservation | ||
772 | 720 | raise exception.NodeLocked(node=node.uuid, host=lock_holder) | ||
773 | 725 | 721 | ||
774 | 726 | @oslo_db_api.retry_on_deadlock | 722 | @oslo_db_api.retry_on_deadlock |
775 | 727 | def reserve_node(self, tag, node_id): | 723 | def reserve_node(self, tag, node_id): |
776 | @@ -1481,7 +1477,8 @@ class Connection(api.Connection): | |||
777 | 1481 | result = session.query( | 1477 | result = session.query( |
778 | 1482 | field | 1478 | field |
779 | 1483 | ).filter(models.Conductor.online.is_(False)) | 1479 | ).filter(models.Conductor.online.is_(False)) |
781 | 1484 | return [row[0] for row in result] | 1480 | result = [row[0] for row in result] |
782 | 1481 | return result | ||
783 | 1485 | 1482 | ||
784 | 1486 | def get_online_conductors(self): | 1483 | def get_online_conductors(self): |
785 | 1487 | with _session_for_read() as session: | 1484 | with _session_for_read() as session: |
786 | diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py | |||
787 | index 010e384..18a5068 100644 | |||
788 | --- a/ironic/drivers/modules/deploy_utils.py | |||
789 | +++ b/ironic/drivers/modules/deploy_utils.py | |||
790 | @@ -1536,10 +1536,12 @@ def prepare_agent_boot(task): | |||
791 | 1536 | task.driver.boot.prepare_ramdisk(task, deploy_opts) | 1536 | task.driver.boot.prepare_ramdisk(task, deploy_opts) |
792 | 1537 | 1537 | ||
793 | 1538 | 1538 | ||
795 | 1539 | def reboot_to_finish_step(task): | 1539 | def reboot_to_finish_step(task, timeout=None): |
796 | 1540 | """Reboot the node into IPA to finish a deploy/clean step. | 1540 | """Reboot the node into IPA to finish a deploy/clean step. |
797 | 1541 | 1541 | ||
798 | 1542 | :param task: a TaskManager instance. | 1542 | :param task: a TaskManager instance. |
799 | 1543 | :param timeout: timeout (in seconds) positive integer (> 0) for any | ||
800 | 1544 | power state. ``None`` indicates to use default timeout. | ||
801 | 1543 | :returns: states.CLEANWAIT if cleaning operation in progress | 1545 | :returns: states.CLEANWAIT if cleaning operation in progress |
802 | 1544 | or states.DEPLOYWAIT if deploy operation in progress. | 1546 | or states.DEPLOYWAIT if deploy operation in progress. |
803 | 1545 | """ | 1547 | """ |
804 | @@ -1552,7 +1554,7 @@ def reboot_to_finish_step(task): | |||
805 | 1552 | manager_utils.node_power_action(task, states.POWER_OFF) | 1554 | manager_utils.node_power_action(task, states.POWER_OFF) |
806 | 1553 | prepare_agent_boot(task) | 1555 | prepare_agent_boot(task) |
807 | 1554 | 1556 | ||
809 | 1555 | manager_utils.node_power_action(task, states.REBOOT) | 1557 | manager_utils.node_power_action(task, states.REBOOT, timeout) |
810 | 1556 | return get_async_step_return_state(task.node) | 1558 | return get_async_step_return_state(task.node) |
811 | 1557 | 1559 | ||
812 | 1558 | 1560 | ||
813 | diff --git a/ironic/drivers/modules/fake.py b/ironic/drivers/modules/fake.py | |||
814 | index 625ffbb..0889098 100644 | |||
815 | --- a/ironic/drivers/modules/fake.py | |||
816 | +++ b/ironic/drivers/modules/fake.py | |||
817 | @@ -477,6 +477,8 @@ class FakeFirmware(base.FirmwareInterface): | |||
818 | 477 | 'needs to contain a dictionary with name/value pairs'), | 477 | 'needs to contain a dictionary with name/value pairs'), |
819 | 478 | 'required': True}}) | 478 | 'required': True}}) |
820 | 479 | def update(self, task, settings): | 479 | def update(self, task, settings): |
821 | 480 | LOG.debug('Calling update clean step with settings %s.', | ||
822 | 481 | settings) | ||
823 | 480 | sleep(CONF.fake.firmware_delay) | 482 | sleep(CONF.fake.firmware_delay) |
824 | 481 | 483 | ||
825 | 482 | def cache_firmware_components(self, task): | 484 | def cache_firmware_components(self, task): |
826 | diff --git a/ironic/drivers/modules/inspect_utils.py b/ironic/drivers/modules/inspect_utils.py | |||
827 | index 2a66edc..14caa05 100644 | |||
828 | --- a/ironic/drivers/modules/inspect_utils.py | |||
829 | +++ b/ironic/drivers/modules/inspect_utils.py | |||
830 | @@ -342,6 +342,9 @@ def _get_bmc_addresses(node): | |||
831 | 342 | if '//' in address: | 342 | if '//' in address: |
832 | 343 | address = urllib.parse.urlparse(address).hostname | 343 | address = urllib.parse.urlparse(address).hostname |
833 | 344 | 344 | ||
834 | 345 | # Strip brackets in case used on IPv6 address. | ||
835 | 346 | address = address.strip('[').strip(']') | ||
836 | 347 | |||
837 | 345 | try: | 348 | try: |
838 | 346 | addrinfo = socket.getaddrinfo(address, None, proto=socket.SOL_TCP) | 349 | addrinfo = socket.getaddrinfo(address, None, proto=socket.SOL_TCP) |
839 | 347 | except socket.gaierror as exc: | 350 | except socket.gaierror as exc: |
840 | diff --git a/ironic/drivers/modules/redfish/firmware.py b/ironic/drivers/modules/redfish/firmware.py | |||
841 | 348 | new file mode 100644 | 351 | new file mode 100644 |
842 | index 0000000..207c61a | |||
843 | --- /dev/null | |||
844 | +++ b/ironic/drivers/modules/redfish/firmware.py | |||
845 | @@ -0,0 +1,452 @@ | |||
846 | 1 | # | ||
847 | 2 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
848 | 3 | # not use this file except in compliance with the License. You may obtain | ||
849 | 4 | # a copy of the License at | ||
850 | 5 | # | ||
851 | 6 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
852 | 7 | # | ||
853 | 8 | # Unless required by applicable law or agreed to in writing, software | ||
854 | 9 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
855 | 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
856 | 11 | # License for the specific language governing permissions and limitations | ||
857 | 12 | # under the License. | ||
858 | 13 | |||
859 | 14 | from urllib.parse import urlparse | ||
860 | 15 | |||
861 | 16 | from ironic_lib import metrics_utils | ||
862 | 17 | from oslo_log import log | ||
863 | 18 | from oslo_utils import importutils | ||
864 | 19 | from oslo_utils import timeutils | ||
865 | 20 | |||
866 | 21 | from ironic.common import exception | ||
867 | 22 | from ironic.common.i18n import _ | ||
868 | 23 | from ironic.common import states | ||
869 | 24 | from ironic.conductor import periodics | ||
870 | 25 | from ironic.conductor import utils as manager_utils | ||
871 | 26 | from ironic.conf import CONF | ||
872 | 27 | from ironic.drivers import base | ||
873 | 28 | from ironic.drivers.modules import deploy_utils | ||
874 | 29 | from ironic.drivers.modules.redfish import firmware_utils | ||
875 | 30 | from ironic.drivers.modules.redfish import utils as redfish_utils | ||
876 | 31 | from ironic import objects | ||
877 | 32 | |||
878 | 33 | LOG = log.getLogger(__name__) | ||
879 | 34 | |||
880 | 35 | METRICS = metrics_utils.get_metrics_logger(__name__) | ||
881 | 36 | |||
882 | 37 | sushy = importutils.try_import('sushy') | ||
883 | 38 | |||
884 | 39 | |||
885 | 40 | class RedfishFirmware(base.FirmwareInterface): | ||
886 | 41 | |||
887 | 42 | _FW_SETTINGS_ARGSINFO = { | ||
888 | 43 | 'settings': { | ||
889 | 44 | 'description': ( | ||
890 | 45 | 'A list of dicts with firmware components to be updated' | ||
891 | 46 | ), | ||
892 | 47 | 'required': True | ||
893 | 48 | } | ||
894 | 49 | } | ||
895 | 50 | |||
896 | 51 | def __init__(self): | ||
897 | 52 | super(RedfishFirmware, self).__init__() | ||
898 | 53 | if sushy is None: | ||
899 | 54 | raise exception.DriverLoadError( | ||
900 | 55 | driver='redfish', | ||
901 | 56 | reason=_("Unable to import the sushy library")) | ||
902 | 57 | |||
903 | 58 | def get_properties(self): | ||
904 | 59 | """Return the properties of the interface. | ||
905 | 60 | |||
906 | 61 | :returns: dictionary of <property name>:<property description> entries. | ||
907 | 62 | """ | ||
908 | 63 | return redfish_utils.COMMON_PROPERTIES.copy() | ||
909 | 64 | |||
910 | 65 | def validate(self, task): | ||
911 | 66 | """Validates the driver information needed by the redfish driver. | ||
912 | 67 | |||
913 | 68 | :param task: a TaskManager instance containing the node to act on. | ||
914 | 69 | :raises: InvalidParameterValue on malformed parameter(s) | ||
915 | 70 | :raises: MissingParameterValue on missing parameter(s) | ||
916 | 71 | """ | ||
917 | 72 | redfish_utils.parse_driver_info(task.node) | ||
918 | 73 | |||
919 | 74 | def cache_firmware_components(self, task): | ||
920 | 75 | """Store or update Firmware Components on the given node. | ||
921 | 76 | |||
922 | 77 | This method stores Firmware Components to the firmware_information | ||
923 | 78 | table during 'cleaning' operation. It will also update the timestamp | ||
924 | 79 | of each Firmware Component. | ||
925 | 80 | |||
926 | 81 | :param task: a TaskManager instance. | ||
927 | 82 | :raises: UnsupportedDriverExtension, if the node's driver doesn't | ||
928 | 83 | support getting Firmware Components from bare metal. | ||
929 | 84 | """ | ||
930 | 85 | |||
931 | 86 | node_id = task.node.id | ||
932 | 87 | settings = [] | ||
933 | 88 | # NOTE(iurygregory): currently we will only retrieve BIOS and BMC | ||
934 | 89 | # firmware information trough the redfish system and manager. | ||
935 | 90 | |||
936 | 91 | system = redfish_utils.get_system(task.node) | ||
937 | 92 | |||
938 | 93 | bios_fw = {'component': 'bios', | ||
939 | 94 | 'current_version': system.bios_version} | ||
940 | 95 | settings.append(bios_fw) | ||
941 | 96 | |||
942 | 97 | # NOTE(iurygregory): normally we only relay on the System to | ||
943 | 98 | # perform actions, but to retrieve the BMC Firmware we need to | ||
944 | 99 | # access the Manager. | ||
945 | 100 | try: | ||
946 | 101 | manager = redfish_utils.get_manager(task.node, system) | ||
947 | 102 | bmc_fw = {'component': 'bmc', | ||
948 | 103 | 'current_version': manager.firmware_version} | ||
949 | 104 | settings.append(bmc_fw) | ||
950 | 105 | except exception.RedfishError: | ||
951 | 106 | LOG.warning('No manager available to retrieve Firmware ' | ||
952 | 107 | 'from the bmc of node %s', task.node.uuid) | ||
953 | 108 | |||
954 | 109 | if not settings: | ||
955 | 110 | error_msg = (_('Cannot retrieve firmware for node %s.') | ||
956 | 111 | % task.node.uuid) | ||
957 | 112 | LOG.error(error_msg) | ||
958 | 113 | raise exception.UnsupportedDriverExtension(error_msg) | ||
959 | 114 | |||
960 | 115 | create_list, update_list, nochange_list = ( | ||
961 | 116 | objects.FirmwareComponentList.sync_firmware_components( | ||
962 | 117 | task.context, node_id, settings)) | ||
963 | 118 | |||
964 | 119 | if create_list: | ||
965 | 120 | for new_fw in create_list: | ||
966 | 121 | new_fw_cmp = objects.FirmwareComponent( | ||
967 | 122 | task.context, | ||
968 | 123 | node_id=node_id, | ||
969 | 124 | component=new_fw['component'], | ||
970 | 125 | current_version=new_fw['current_version'] | ||
971 | 126 | ) | ||
972 | 127 | new_fw_cmp.create() | ||
973 | 128 | if update_list: | ||
974 | 129 | for up_fw in update_list: | ||
975 | 130 | up_fw_cmp = objects.FirmwareComponent.get( | ||
976 | 131 | task.context, | ||
977 | 132 | node_id=node_id, | ||
978 | 133 | name=up_fw['component'] | ||
979 | 134 | ) | ||
980 | 135 | up_fw_cmp.last_version_flashed = up_fw.get('current_version') | ||
981 | 136 | up_fw_cmp.current_version = up_fw.get('current_version') | ||
982 | 137 | up_fw_cmp.save() | ||
983 | 138 | |||
984 | 139 | @METRICS.timer('RedfishFirmware.update') | ||
985 | 140 | @base.deploy_step(priority=0, argsinfo=_FW_SETTINGS_ARGSINFO) | ||
986 | 141 | @base.clean_step(priority=0, abortable=False, | ||
987 | 142 | argsinfo=_FW_SETTINGS_ARGSINFO, | ||
988 | 143 | requires_ramdisk=True) | ||
989 | 144 | @base.cache_firmware_components | ||
990 | 145 | def update(self, task, settings): | ||
991 | 146 | """Update the Firmware on the node using the settings for components. | ||
992 | 147 | |||
993 | 148 | :param task: a TaskManager instance. | ||
994 | 149 | :param settings: a list of dictionaries, each dictionary contains the | ||
995 | 150 | component name and the url that will be used to update the | ||
996 | 151 | firmware. | ||
997 | 152 | :raises: UnsupportedDriverExtension, if the node's driver doesn't | ||
998 | 153 | support update via the interface. | ||
999 | 154 | :raises: InvalidParameterValue, if validation of the settings fails. | ||
1000 | 155 | :raises: MissingParamterValue, if some required parameters are | ||
1001 | 156 | missing. | ||
1002 | 157 | :returns: states.CLEANWAIT if Firmware update with the settings is in | ||
1003 | 158 | progress asynchronously of None if it is complete. | ||
1004 | 159 | """ | ||
1005 | 160 | node = task.node | ||
1006 | 161 | |||
1007 | 162 | update_service = redfish_utils.get_update_service(node) | ||
1008 | 163 | |||
1009 | 164 | LOG.debug('Updating Firmware on node %(node_uuid)s with settings ' | ||
1010 | 165 | '%(settings)s', | ||
1011 | 166 | {'node_uuid': node.uuid, 'settings': settings}) | ||
1012 | 167 | |||
1013 | 168 | self._execute_firmware_update(node, update_service, settings) | ||
1014 | 169 | |||
1015 | 170 | fw_upd = settings[0] | ||
1016 | 171 | wait_interval = fw_upd.get('wait') | ||
1017 | 172 | |||
1018 | 173 | deploy_utils.set_async_step_flags( | ||
1019 | 174 | node, | ||
1020 | 175 | reboot=True, | ||
1021 | 176 | skip_current_step=True, | ||
1022 | 177 | polling=True | ||
1023 | 178 | ) | ||
1024 | 179 | |||
1025 | 180 | return deploy_utils.reboot_to_finish_step(task, timeout=wait_interval) | ||
1026 | 181 | |||
1027 | 182 | def _execute_firmware_update(self, node, update_service, settings): | ||
1028 | 183 | """Executes the next firmware update to the node | ||
1029 | 184 | |||
1030 | 185 | Executes the first firmware update in the settings list to the node. | ||
1031 | 186 | |||
1032 | 187 | :param node: the node that will have a firmware update executed. | ||
1033 | 188 | :param update_service: the sushy firmware update service. | ||
1034 | 189 | :param settings: remaining settings for firmware update that needs | ||
1035 | 190 | to be executed. | ||
1036 | 191 | """ | ||
1037 | 192 | fw_upd = settings[0] | ||
1038 | 193 | component_url, cleanup = self._stage_firmware_file(node, fw_upd) | ||
1039 | 194 | |||
1040 | 195 | LOG.debug('Applying new firmware %(url)s for %(component)s on node ' | ||
1041 | 196 | '%(node_uuid)s', | ||
1042 | 197 | {'url': fw_upd['url'], 'component': fw_upd['component'], | ||
1043 | 198 | 'node_uuid': node.uuid}) | ||
1044 | 199 | |||
1045 | 200 | task_monitor = update_service.simple_update(component_url) | ||
1046 | 201 | |||
1047 | 202 | fw_upd['task_monitor'] = task_monitor.task_monitor_uri | ||
1048 | 203 | node.set_driver_internal_info('redfish_fw_updates', settings) | ||
1049 | 204 | |||
1050 | 205 | if cleanup: | ||
1051 | 206 | fw_clean = node.driver_internal_info.get('firmware_cleanup') | ||
1052 | 207 | if not fw_clean: | ||
1053 | 208 | fw_clean = [cleanup] | ||
1054 | 209 | elif cleanup not in fw_clean: | ||
1055 | 210 | fw_clean.append(cleanup) | ||
1056 | 211 | node.set_driver_internal_info('firmware_cleanup', fw_clean) | ||
1057 | 212 | |||
1058 | 213 | def _continue_updates(self, task, update_service, settings): | ||
1059 | 214 | """Continues processing the firmware updates | ||
1060 | 215 | |||
1061 | 216 | Continues to process the firmware updates on the node. | ||
1062 | 217 | |||
1063 | 218 | Note that the caller must have an exclusive lock on the node. | ||
1064 | 219 | |||
1065 | 220 | :param task: a TaskManager instance containing the node to act on. | ||
1066 | 221 | :param update_service: the sushy firmware update service | ||
1067 | 222 | :param settings: the remaining firmware updates to apply | ||
1068 | 223 | """ | ||
1069 | 224 | node = task.node | ||
1070 | 225 | fw_upd = settings[0] | ||
1071 | 226 | wait_interval = fw_upd.get('wait') | ||
1072 | 227 | if wait_interval: | ||
1073 | 228 | time_now = str(timeutils.utcnow().isoformat()) | ||
1074 | 229 | fw_upd['wait_start_time'] = time_now | ||
1075 | 230 | |||
1076 | 231 | LOG.debug('Waiting at %(time)s for %(seconds)s seconds after ' | ||
1077 | 232 | '%(component)s firmware update %(url)s ' | ||
1078 | 233 | 'on node %(node)s', | ||
1079 | 234 | {'time': time_now, | ||
1080 | 235 | 'seconds': wait_interval, | ||
1081 | 236 | 'component': fw_upd['component'], | ||
1082 | 237 | 'url': fw_upd['url'], | ||
1083 | 238 | 'node': node.uuid}) | ||
1084 | 239 | |||
1085 | 240 | node.set_driver_internal_info('redfish_fw_updates', settings) | ||
1086 | 241 | node.save() | ||
1087 | 242 | return | ||
1088 | 243 | |||
1089 | 244 | if len(settings) == 1: | ||
1090 | 245 | self._clear_updates(node) | ||
1091 | 246 | |||
1092 | 247 | LOG.info('Firmware updates completed for node %(node)s', | ||
1093 | 248 | {'node': node.uuid}) | ||
1094 | 249 | |||
1095 | 250 | manager_utils.notify_conductor_resume_clean(task) | ||
1096 | 251 | else: | ||
1097 | 252 | settings.pop(0) | ||
1098 | 253 | self._execute_firmware_update(node, | ||
1099 | 254 | update_service, | ||
1100 | 255 | settings) | ||
1101 | 256 | node.save() | ||
1102 | 257 | manager_utils.node_power_action(task, states.REBOOT) | ||
1103 | 258 | |||
1104 | 259 | def _clear_updates(self, node): | ||
1105 | 260 | """Clears firmware updates artifacts | ||
1106 | 261 | |||
1107 | 262 | Clears firmware updates from driver_internal_info and any files | ||
1108 | 263 | that were staged. | ||
1109 | 264 | |||
1110 | 265 | Note that the caller must have an exclusive lock on the node. | ||
1111 | 266 | |||
1112 | 267 | :param node: the node to clear the firmware updates from | ||
1113 | 268 | """ | ||
1114 | 269 | firmware_utils.cleanup(node) | ||
1115 | 270 | node.del_driver_internal_info('redfish_fw_updates') | ||
1116 | 271 | node.del_driver_internal_info('firmware_cleanup') | ||
1117 | 272 | node.save() | ||
1118 | 273 | |||
1119 | 274 | @METRICS.timer('RedfishFirmware._query_update_failed') | ||
1120 | 275 | @periodics.node_periodic( | ||
1121 | 276 | purpose='checking if async update of firmware component failed', | ||
1122 | 277 | spacing=CONF.redfish.firmware_update_fail_interval, | ||
1123 | 278 | filters={'reserved': False, 'provision_state': states.CLEANFAIL, | ||
1124 | 279 | 'maintenance': True}, | ||
1125 | 280 | predicate_extra_fields=['driver_internal_info'], | ||
1126 | 281 | predicate=lambda n: n.driver_internal_info.get('redfish_fw_updates'), | ||
1127 | 282 | ) | ||
1128 | 283 | def _query_update_failed(self, task, manager, context): | ||
1129 | 284 | |||
1130 | 285 | """Periodic job to check for failed firmware updates.""" | ||
1131 | 286 | # A firmware update failed. Discard any remaining firmware | ||
1132 | 287 | # updates so when the user takes the node out of | ||
1133 | 288 | # maintenance mode, pending firmware updates do not | ||
1134 | 289 | # automatically continue. | ||
1135 | 290 | LOG.error('Update firmware failed for node %(node)s. ' | ||
1136 | 291 | 'Discarding remaining firmware updates.', | ||
1137 | 292 | {'node': task.node.uuid}) | ||
1138 | 293 | |||
1139 | 294 | task.upgrade_lock() | ||
1140 | 295 | self._clear_updates(task.node) | ||
1141 | 296 | |||
1142 | 297 | @METRICS.timer('RedfishFirmware._query_update_status') | ||
1143 | 298 | @periodics.node_periodic( | ||
1144 | 299 | purpose='checking async update of firmware component', | ||
1145 | 300 | spacing=CONF.redfish.firmware_update_fail_interval, | ||
1146 | 301 | filters={'reserved': False, 'provision_state': states.CLEANWAIT}, | ||
1147 | 302 | predicate_extra_fields=['driver_internal_info'], | ||
1148 | 303 | predicate=lambda n: n.driver_internal_info.get('redfish_fw_updates'), | ||
1149 | 304 | ) | ||
1150 | 305 | def _query_update_status(self, task, manager, context): | ||
1151 | 306 | """Periodic job to check firmware update tasks.""" | ||
1152 | 307 | self._check_node_redfish_firmware_update(task) | ||
1153 | 308 | |||
1154 | 309 | @METRICS.timer('RedfishFirmware._check_node_redfish_firmware_update') | ||
1155 | 310 | def _check_node_redfish_firmware_update(self, task): | ||
1156 | 311 | """Check the progress of running firmware update on a node.""" | ||
1157 | 312 | |||
1158 | 313 | node = task.node | ||
1159 | 314 | |||
1160 | 315 | settings = node.driver_internal_info['redfish_fw_updates'] | ||
1161 | 316 | current_update = settings[0] | ||
1162 | 317 | |||
1163 | 318 | try: | ||
1164 | 319 | update_service = redfish_utils.get_update_service(node) | ||
1165 | 320 | except exception.RedfishConnectionError as e: | ||
1166 | 321 | # If the BMC firmware is being updated, the BMC will be | ||
1167 | 322 | # unavailable for some amount of time. | ||
1168 | 323 | LOG.warning('Unable to communicate with firmware update service ' | ||
1169 | 324 | 'on node %(node)s. Will try again on the next poll. ' | ||
1170 | 325 | 'Error: %(error)s', | ||
1171 | 326 | {'node': node.uuid, | ||
1172 | 327 | 'error': e}) | ||
1173 | 328 | return | ||
1174 | 329 | |||
1175 | 330 | wait_start_time = current_update.get('wait_start_time') | ||
1176 | 331 | if wait_start_time: | ||
1177 | 332 | wait_start = timeutils.parse_isotime(wait_start_time) | ||
1178 | 333 | |||
1179 | 334 | elapsed_time = timeutils.utcnow(True) - wait_start | ||
1180 | 335 | if elapsed_time.seconds >= current_update['wait']: | ||
1181 | 336 | LOG.debug('Finished waiting after firmware update ' | ||
1182 | 337 | '%(firmware_image)s on node %(node)s. ' | ||
1183 | 338 | 'Elapsed time: %(seconds)s seconds', | ||
1184 | 339 | {'firmware_image': current_update['url'], | ||
1185 | 340 | 'node': node.uuid, | ||
1186 | 341 | 'seconds': elapsed_time.seconds}) | ||
1187 | 342 | current_update.pop('wait', None) | ||
1188 | 343 | current_update.pop('wait_start_time', None) | ||
1189 | 344 | |||
1190 | 345 | self._continue_updates(task, update_service, settings) | ||
1191 | 346 | else: | ||
1192 | 347 | LOG.debug('Continuing to wait after firmware update ' | ||
1193 | 348 | '%(firmware_image)s on node %(node)s. ' | ||
1194 | 349 | 'Elapsed time: %(seconds)s seconds', | ||
1195 | 350 | {'firmware_image': current_update['url'], | ||
1196 | 351 | 'node': node.uuid, | ||
1197 | 352 | 'seconds': elapsed_time.seconds}) | ||
1198 | 353 | |||
1199 | 354 | return | ||
1200 | 355 | |||
1201 | 356 | try: | ||
1202 | 357 | task_monitor = redfish_utils.get_task_monitor( | ||
1203 | 358 | node, current_update['task_monitor']) | ||
1204 | 359 | except exception.RedfishError: | ||
1205 | 360 | # The BMC deleted the Task before we could query it | ||
1206 | 361 | LOG.warning('Firmware update completed for node %(node)s, ' | ||
1207 | 362 | 'firmware %(firmware_image)s, but success of the ' | ||
1208 | 363 | 'update is unknown. Assuming update was successful.', | ||
1209 | 364 | {'node': node.uuid, | ||
1210 | 365 | 'firmware_image': current_update['url']}) | ||
1211 | 366 | self._continue_updates(task, update_service, settings) | ||
1212 | 367 | return | ||
1213 | 368 | |||
1214 | 369 | if not task_monitor.is_processing: | ||
1215 | 370 | # The last response does not necessarily contain a Task, | ||
1216 | 371 | # so get it | ||
1217 | 372 | sushy_task = task_monitor.get_task() | ||
1218 | 373 | |||
1219 | 374 | # Only parse the messages if the BMC did not return parsed | ||
1220 | 375 | # messages | ||
1221 | 376 | messages = [] | ||
1222 | 377 | if sushy_task.messages and not sushy_task.messages[0].message: | ||
1223 | 378 | sushy_task.parse_messages() | ||
1224 | 379 | |||
1225 | 380 | if sushy_task.messages is not None: | ||
1226 | 381 | messages = [m.message for m in sushy_task.messages] | ||
1227 | 382 | |||
1228 | 383 | task.upgrade_lock() | ||
1229 | 384 | if (sushy_task.task_state == sushy.TASK_STATE_COMPLETED | ||
1230 | 385 | and sushy_task.task_status in | ||
1231 | 386 | [sushy.HEALTH_OK, sushy.HEALTH_WARNING]): | ||
1232 | 387 | LOG.info('Firmware update succeeded for node %(node)s, ' | ||
1233 | 388 | 'firmware %(firmware_image)s: %(messages)s', | ||
1234 | 389 | {'node': node.uuid, | ||
1235 | 390 | 'firmware_image': current_update['url'], | ||
1236 | 391 | 'messages': ", ".join(messages)}) | ||
1237 | 392 | |||
1238 | 393 | self._continue_updates(task, update_service, settings) | ||
1239 | 394 | else: | ||
1240 | 395 | error_msg = (_('Firmware update failed for node %(node)s, ' | ||
1241 | 396 | 'firmware %(firmware_image)s. ' | ||
1242 | 397 | 'Error: %(errors)s') % | ||
1243 | 398 | {'node': node.uuid, | ||
1244 | 399 | 'firmware_image': current_update['url'], | ||
1245 | 400 | 'errors': ", ".join(messages)}) | ||
1246 | 401 | |||
1247 | 402 | self._clear_updates(node) | ||
1248 | 403 | if task.node.clean_step: | ||
1249 | 404 | manager_utils.cleaning_error_handler(task, error_msg) | ||
1250 | 405 | else: | ||
1251 | 406 | manager_utils.deploying_error_handler(task, error_msg) | ||
1252 | 407 | |||
1253 | 408 | else: | ||
1254 | 409 | LOG.debug('Firmware update in progress for node %(node)s, ' | ||
1255 | 410 | 'firmware %(firmware_image)s.', | ||
1256 | 411 | {'node': node.uuid, | ||
1257 | 412 | 'firmware_image': current_update['url']}) | ||
1258 | 413 | |||
1259 | 414 | def _stage_firmware_file(self, node, component_update): | ||
1260 | 415 | |||
1261 | 416 | try: | ||
1262 | 417 | url = component_update['url'] | ||
1263 | 418 | name = component_update['component'] | ||
1264 | 419 | parsed_url = urlparse(url) | ||
1265 | 420 | scheme = parsed_url.scheme.lower() | ||
1266 | 421 | source = (CONF.redfish.firmware_source).lower() | ||
1267 | 422 | |||
1268 | 423 | # Keep it simple, in further processing TLS does not matter | ||
1269 | 424 | if scheme == 'https': | ||
1270 | 425 | scheme = 'http' | ||
1271 | 426 | |||
1272 | 427 | # If source and scheme is HTTP, then no staging, | ||
1273 | 428 | # returning original location | ||
1274 | 429 | if scheme == 'http' and source == scheme: | ||
1275 | 430 | LOG.debug('For node %(node)s serving firmware for ' | ||
1276 | 431 | '%(component)s from original location %(url)s', | ||
1277 | 432 | {'node': node.uuid, 'component': name, 'url': url}) | ||
1278 | 433 | return url, None | ||
1279 | 434 | |||
1280 | 435 | # If source and scheme is Swift, then not moving, but | ||
1281 | 436 | # returning Swift temp URL | ||
1282 | 437 | if scheme == 'swift' and source == scheme: | ||
1283 | 438 | temp_url = firmware_utils.get_swift_temp_url(parsed_url) | ||
1284 | 439 | LOG.debug('For node %(node)s serving original firmware at ' | ||
1285 | 440 | 'for %(component)s at %(url)s via Swift temporary ' | ||
1286 | 441 | 'url %(temp_url)s', | ||
1287 | 442 | {'node': node.uuid, 'component': name, 'url': url, | ||
1288 | 443 | 'temp_url': temp_url}) | ||
1289 | 444 | return temp_url, None | ||
1290 | 445 | |||
1291 | 446 | # For remaining, download the image to temporary location | ||
1292 | 447 | temp_file = firmware_utils.download_to_temp(node, url) | ||
1293 | 448 | |||
1294 | 449 | return firmware_utils.stage(node, source, temp_file) | ||
1295 | 450 | |||
1296 | 451 | except exception.IronicException: | ||
1297 | 452 | firmware_utils.cleanup(node) | ||
1298 | diff --git a/ironic/drivers/modules/redfish/firmware_utils.py b/ironic/drivers/modules/redfish/firmware_utils.py | |||
1299 | index feeec2d..843597d 100644 | |||
1300 | --- a/ironic/drivers/modules/redfish/firmware_utils.py | |||
1301 | +++ b/ironic/drivers/modules/redfish/firmware_utils.py | |||
1302 | @@ -63,6 +63,36 @@ _UPDATE_FIRMWARE_SCHEMA = { | |||
1303 | 63 | "additionalProperties": False | 63 | "additionalProperties": False |
1304 | 64 | } | 64 | } |
1305 | 65 | } | 65 | } |
1306 | 66 | |||
1307 | 67 | _FIRMWARE_INTERFACE_UPDATE_SCHEMA = { | ||
1308 | 68 | "$schema": "http://json-schema.org/schema#", | ||
1309 | 69 | "title": "update_firmware clean step schema", | ||
1310 | 70 | "type": "array", | ||
1311 | 71 | # list of firmware update images | ||
1312 | 72 | "items": { | ||
1313 | 73 | "type": "object", | ||
1314 | 74 | "required": ["component", "url"], | ||
1315 | 75 | "properties": { | ||
1316 | 76 | "component": { | ||
1317 | 77 | "description": "name of the firmware component to be updated", | ||
1318 | 78 | "type": "string", | ||
1319 | 79 | "minLenght": 1 | ||
1320 | 80 | }, | ||
1321 | 81 | "url": { | ||
1322 | 82 | "description": "URL for firmware file", | ||
1323 | 83 | "type": "string", | ||
1324 | 84 | "minLength": 1 | ||
1325 | 85 | }, | ||
1326 | 86 | "wait": { | ||
1327 | 87 | "description": "optional wait time for firmware update", | ||
1328 | 88 | "type": "integer", | ||
1329 | 89 | "minimum": 1 | ||
1330 | 90 | } | ||
1331 | 91 | }, | ||
1332 | 92 | "additionalProperties": False | ||
1333 | 93 | } | ||
1334 | 94 | } | ||
1335 | 95 | |||
1336 | 66 | _FIRMWARE_SUBDIR = 'firmware' | 96 | _FIRMWARE_SUBDIR = 'firmware' |
1337 | 67 | 97 | ||
1338 | 68 | 98 | ||
1339 | @@ -80,6 +110,20 @@ def validate_update_firmware_args(firmware_images): | |||
1340 | 80 | % {'firmware_images': firmware_images, 'err': err}) | 110 | % {'firmware_images': firmware_images, 'err': err}) |
1341 | 81 | 111 | ||
1342 | 82 | 112 | ||
1343 | 113 | def validate_firmware_interface_update_args(settings): | ||
1344 | 114 | """Validate ``update`` step input argument | ||
1345 | 115 | |||
1346 | 116 | :param settings: args to validate. | ||
1347 | 117 | :raises: InvalidParameterValue When argument is not valid | ||
1348 | 118 | """ | ||
1349 | 119 | try: | ||
1350 | 120 | jsonschema.validate(settings, _FIRMWARE_INTERFACE_UPDATE_SCHEMA) | ||
1351 | 121 | except jsonschema.ValidationError as err: | ||
1352 | 122 | raise exception.InvalidParameterValue( | ||
1353 | 123 | _('Invalid firmware update %(settings)s. Errors: %(err)s') | ||
1354 | 124 | % {'settings': settings, 'err': err}) | ||
1355 | 125 | |||
1356 | 126 | |||
1357 | 83 | def get_swift_temp_url(parsed_url): | 127 | def get_swift_temp_url(parsed_url): |
1358 | 84 | """Gets Swift temporary URL | 128 | """Gets Swift temporary URL |
1359 | 85 | 129 | ||
1360 | diff --git a/ironic/drivers/modules/redfish/management.py b/ironic/drivers/modules/redfish/management.py | |||
1361 | index 7f7dbe7..d0b8045 100644 | |||
1362 | --- a/ironic/drivers/modules/redfish/management.py | |||
1363 | +++ b/ironic/drivers/modules/redfish/management.py | |||
1364 | @@ -14,6 +14,7 @@ | |||
1365 | 14 | # under the License. | 14 | # under the License. |
1366 | 15 | 15 | ||
1367 | 16 | import collections | 16 | import collections |
1368 | 17 | import time | ||
1369 | 17 | from urllib.parse import urlparse | 18 | from urllib.parse import urlparse |
1370 | 18 | 19 | ||
1371 | 19 | from ironic_lib import metrics_utils | 20 | from ironic_lib import metrics_utils |
1372 | @@ -44,6 +45,8 @@ METRICS = metrics_utils.get_metrics_logger(__name__) | |||
1373 | 44 | 45 | ||
1374 | 45 | sushy = importutils.try_import('sushy') | 46 | sushy = importutils.try_import('sushy') |
1375 | 46 | 47 | ||
1376 | 48 | BOOT_MODE_CONFIG_INTERVAL = 15 | ||
1377 | 49 | |||
1378 | 47 | if sushy: | 50 | if sushy: |
1379 | 48 | BOOT_DEVICE_MAP = { | 51 | BOOT_DEVICE_MAP = { |
1380 | 49 | sushy.BOOT_SOURCE_TARGET_PXE: boot_devices.PXE, | 52 | sushy.BOOT_SOURCE_TARGET_PXE: boot_devices.PXE, |
1381 | @@ -327,9 +330,13 @@ class RedfishManagement(base.ManagementInterface): | |||
1382 | 327 | """ | 330 | """ |
1383 | 328 | system = redfish_utils.get_system(task.node) | 331 | system = redfish_utils.get_system(task.node) |
1384 | 329 | 332 | ||
1385 | 333 | # NOTE(dtantsur): check the readability of the current mode before | ||
1386 | 334 | # modifying anything. I suspect it can become None transiently after | ||
1387 | 335 | # the update, while we need to know if it is supported *at all*. | ||
1388 | 336 | get_mode_unsupported = (system.boot.get('mode') is None) | ||
1389 | 337 | |||
1390 | 330 | try: | 338 | try: |
1391 | 331 | system.set_system_boot_options(mode=BOOT_MODE_MAP_REV[mode]) | 339 | system.set_system_boot_options(mode=BOOT_MODE_MAP_REV[mode]) |
1392 | 332 | |||
1393 | 333 | except sushy.exceptions.SushyError as e: | 340 | except sushy.exceptions.SushyError as e: |
1394 | 334 | error_msg = (_('Setting boot mode to %(mode)s ' | 341 | error_msg = (_('Setting boot mode to %(mode)s ' |
1395 | 335 | 'failed for node %(node)s. ' | 342 | 'failed for node %(node)s. ' |
1396 | @@ -342,7 +349,7 @@ class RedfishManagement(base.ManagementInterface): | |||
1397 | 342 | # getting or setting the boot mode. When setting failed and the | 349 | # getting or setting the boot mode. When setting failed and the |
1398 | 343 | # mode attribute is missing from the boot field, raising | 350 | # mode attribute is missing from the boot field, raising |
1399 | 344 | # UnsupportedDriverExtension will allow the deploy to continue. | 351 | # UnsupportedDriverExtension will allow the deploy to continue. |
1401 | 345 | if system.boot.get('mode') is None: | 352 | if get_mode_unsupported: |
1402 | 346 | LOG.info(_('Attempt to set boot mode on node %(node)s ' | 353 | LOG.info(_('Attempt to set boot mode on node %(node)s ' |
1403 | 347 | 'failed to set boot mode as the node does not ' | 354 | 'failed to set boot mode as the node does not ' |
1404 | 348 | 'appear to support overriding the boot mode. ' | 355 | 'appear to support overriding the boot mode. ' |
1405 | @@ -352,6 +359,66 @@ class RedfishManagement(base.ManagementInterface): | |||
1406 | 352 | driver=task.node.driver, extension='set_boot_mode') | 359 | driver=task.node.driver, extension='set_boot_mode') |
1407 | 353 | raise exception.RedfishError(error=error_msg) | 360 | raise exception.RedfishError(error=error_msg) |
1408 | 354 | 361 | ||
1409 | 362 | # NOTE(dtantsur): this case is rather hypothetical, but in our own | ||
1410 | 363 | # emulator, it's possible that mode is constantly set to None, while | ||
1411 | 364 | # the request to change the mode succeeds. | ||
1412 | 365 | if get_mode_unsupported: | ||
1413 | 366 | LOG.warning('The request to set boot mode for node %(node)s to ' | ||
1414 | 367 | '%(value)s has succeeded, but the current mode is ' | ||
1415 | 368 | 'not known. Skipping reboot and assuming ' | ||
1416 | 369 | 'the operation has succeeded.', | ||
1417 | 370 | {'node': task.node.uuid, 'value': mode}) | ||
1418 | 371 | return | ||
1419 | 372 | |||
1420 | 373 | self._wait_for_boot_mode(task, system, mode) | ||
1421 | 374 | LOG.info('Boot mode for node %(node)s has been set to ' | ||
1422 | 375 | '%(value)s', {'node': task.node.uuid, 'value': mode}) | ||
1423 | 376 | |||
1424 | 377 | def _wait_for_boot_mode(self, task, system, mode): | ||
1425 | 378 | system.refresh(force=True) | ||
1426 | 379 | |||
1427 | 380 | # NOTE(dtantsur/janders): at least Dell machines change boot mode via | ||
1428 | 381 | # a BIOS configuration job. A reboot is needed to apply it. | ||
1429 | 382 | if system.boot.get('mode') == BOOT_MODE_MAP_REV[mode]: | ||
1430 | 383 | LOG.debug('Node %(node)s is already configured with requested ' | ||
1431 | 384 | 'boot mode %(new_value)s.', | ||
1432 | 385 | {'node': task.node.uuid, | ||
1433 | 386 | 'new_value': BOOT_MODE_MAP_REV[mode]}) | ||
1434 | 387 | return | ||
1435 | 388 | |||
1436 | 389 | LOG.info('Rebooting node %(node)s to change boot mode from ' | ||
1437 | 390 | '%(old_value)s to %(new_value)s', | ||
1438 | 391 | {'node': task.node.uuid, | ||
1439 | 392 | 'old_value': system.boot.get('mode'), | ||
1440 | 393 | 'new_value': BOOT_MODE_MAP_REV[mode]}) | ||
1441 | 394 | |||
1442 | 395 | old_power_state = task.driver.power.get_power_state(task) | ||
1443 | 396 | manager_utils.node_power_action(task, states.REBOOT) | ||
1444 | 397 | |||
1445 | 398 | if CONF.redfish.boot_mode_config_timeout: | ||
1446 | 399 | threshold = time.time() + CONF.redfish.boot_mode_config_timeout | ||
1447 | 400 | while (time.time() <= threshold | ||
1448 | 401 | and system.boot.get('mode') != BOOT_MODE_MAP_REV[mode]): | ||
1449 | 402 | LOG.debug('Still waiting for boot mode of node %(node)s ' | ||
1450 | 403 | 'to become %(value)s, current is %(current)s', | ||
1451 | 404 | {'node': task.node.uuid, | ||
1452 | 405 | 'value': BOOT_MODE_MAP_REV[mode], | ||
1453 | 406 | 'current': system.boot.get('mode')}) | ||
1454 | 407 | time.sleep(BOOT_MODE_CONFIG_INTERVAL) | ||
1455 | 408 | system.refresh(force=True) | ||
1456 | 409 | |||
1457 | 410 | if system.boot.get('mode') != BOOT_MODE_MAP_REV[mode]: | ||
1458 | 411 | msg = (_('Timeout reached while waiting for boot mode of ' | ||
1459 | 412 | 'node %(node)s to become %(value)s, ' | ||
1460 | 413 | 'current is %(current)s') | ||
1461 | 414 | % {'node': task.node.uuid, | ||
1462 | 415 | 'value': BOOT_MODE_MAP_REV[mode], | ||
1463 | 416 | 'current': system.boot.get('mode')}) | ||
1464 | 417 | LOG.error(msg) | ||
1465 | 418 | raise exception.RedfishError(error=msg) | ||
1466 | 419 | |||
1467 | 420 | manager_utils.node_power_action(task, old_power_state) | ||
1468 | 421 | |||
1469 | 355 | def get_boot_mode(self, task): | 422 | def get_boot_mode(self, task): |
1470 | 356 | """Get the current boot mode for a node. | 423 | """Get the current boot mode for a node. |
1471 | 357 | 424 | ||
1472 | @@ -1142,9 +1209,45 @@ class RedfishManagement(base.ManagementInterface): | |||
1473 | 1142 | % {'node': task.node.uuid, 'value': state, 'exc': exc}) | 1209 | % {'node': task.node.uuid, 'value': state, 'exc': exc}) |
1474 | 1143 | LOG.error(msg) | 1210 | LOG.error(msg) |
1475 | 1144 | raise exception.RedfishError(error=msg) | 1211 | raise exception.RedfishError(error=msg) |
1479 | 1145 | else: | 1212 | |
1480 | 1146 | LOG.info('Secure boot state for node %(node)s has been set to ' | 1213 | self._wait_for_secure_boot(task, sb, state) |
1481 | 1147 | '%(value)s', {'node': task.node.uuid, 'value': state}) | 1214 | LOG.info('Secure boot state for node %(node)s has been set to ' |
1482 | 1215 | '%(value)s', {'node': task.node.uuid, 'value': state}) | ||
1483 | 1216 | |||
1484 | 1217 | def _wait_for_secure_boot(self, task, sb, state): | ||
1485 | 1218 | # NOTE(dtantsur): at least Dell machines change secure boot status via | ||
1486 | 1219 | # a BIOS configuration job. A reboot is needed to apply it. | ||
1487 | 1220 | sb.refresh(force=True) | ||
1488 | 1221 | if sb.enabled == state: | ||
1489 | 1222 | return | ||
1490 | 1223 | |||
1491 | 1224 | LOG.info('Rebooting node %(node)s to change secure boot state to ' | ||
1492 | 1225 | '%(value)s', {'node': task.node.uuid, 'value': state}) | ||
1493 | 1226 | |||
1494 | 1227 | old_power_state = task.driver.power.get_power_state(task) | ||
1495 | 1228 | manager_utils.node_power_action(task, states.REBOOT) | ||
1496 | 1229 | |||
1497 | 1230 | if CONF.redfish.boot_mode_config_timeout: | ||
1498 | 1231 | threshold = time.time() + CONF.redfish.boot_mode_config_timeout | ||
1499 | 1232 | while time.time() <= threshold and sb.enabled != state: | ||
1500 | 1233 | LOG.debug( | ||
1501 | 1234 | 'Still waiting for secure boot state of node %(node)s ' | ||
1502 | 1235 | 'to become %(value)s, current is %(current)s', | ||
1503 | 1236 | {'node': task.node.uuid, 'value': state, | ||
1504 | 1237 | 'current': sb.enabled}) | ||
1505 | 1238 | time.sleep(BOOT_MODE_CONFIG_INTERVAL) | ||
1506 | 1239 | sb.refresh(force=True) | ||
1507 | 1240 | |||
1508 | 1241 | if sb.enabled != state: | ||
1509 | 1242 | msg = (_('Timeout reached while waiting for secure boot state ' | ||
1510 | 1243 | 'of node %(node)s to become %(state)s, ' | ||
1511 | 1244 | 'current is %(current)s') | ||
1512 | 1245 | % {'node': task.node.uuid, 'state': state, | ||
1513 | 1246 | 'current': sb.enabled}) | ||
1514 | 1247 | LOG.error(msg) | ||
1515 | 1248 | raise exception.RedfishError(error=msg) | ||
1516 | 1249 | |||
1517 | 1250 | manager_utils.node_power_action(task, old_power_state) | ||
1518 | 1148 | 1251 | ||
1519 | 1149 | def _reset_keys(self, task, reset_type): | 1252 | def _reset_keys(self, task, reset_type): |
1520 | 1150 | system = redfish_utils.get_system(task.node) | 1253 | system = redfish_utils.get_system(task.node) |
1521 | diff --git a/ironic/drivers/modules/redfish/utils.py b/ironic/drivers/modules/redfish/utils.py | |||
1522 | index e85e2ec..4182c84 100644 | |||
1523 | --- a/ironic/drivers/modules/redfish/utils.py | |||
1524 | +++ b/ironic/drivers/modules/redfish/utils.py | |||
1525 | @@ -28,6 +28,7 @@ import tenacity | |||
1526 | 28 | 28 | ||
1527 | 29 | from ironic.common import exception | 29 | from ironic.common import exception |
1528 | 30 | from ironic.common.i18n import _ | 30 | from ironic.common.i18n import _ |
1529 | 31 | from ironic.common import utils | ||
1530 | 31 | from ironic.conf import CONF | 32 | from ironic.conf import CONF |
1531 | 32 | 33 | ||
1532 | 33 | sushy = importutils.try_import('sushy') | 34 | sushy = importutils.try_import('sushy') |
1533 | @@ -97,7 +98,7 @@ def parse_driver_info(node): | |||
1534 | 97 | 'info': missing_info}) | 98 | 'info': missing_info}) |
1535 | 98 | 99 | ||
1536 | 99 | # Validate the Redfish address | 100 | # Validate the Redfish address |
1538 | 100 | address = driver_info['redfish_address'] | 101 | address = utils.wrap_ipv6(driver_info['redfish_address']) |
1539 | 101 | try: | 102 | try: |
1540 | 102 | parsed = rfc3986.uri_reference(address) | 103 | parsed = rfc3986.uri_reference(address) |
1541 | 103 | except TypeError: | 104 | except TypeError: |
1542 | @@ -474,3 +475,39 @@ def wait_until_get_system_ready(node): | |||
1543 | 474 | driver_info = parse_driver_info(node) | 475 | driver_info = parse_driver_info(node) |
1544 | 475 | system_id = driver_info['system_id'] | 476 | system_id = driver_info['system_id'] |
1545 | 476 | return _get_system(driver_info, system_id) | 477 | return _get_system(driver_info, system_id) |
1546 | 478 | |||
1547 | 479 | |||
1548 | 480 | def get_manager(node, system, manager_id=None): | ||
1549 | 481 | """Get a node's manager. | ||
1550 | 482 | |||
1551 | 483 | :param system: a Sushy system object | ||
1552 | 484 | :param manager_id: the id of the manager | ||
1553 | 485 | :return: a sushy Manager | ||
1554 | 486 | :raises: RedfishError when the System doesn't have Managers associated | ||
1555 | 487 | """ | ||
1556 | 488 | |||
1557 | 489 | try: | ||
1558 | 490 | sushy_manager = None | ||
1559 | 491 | available_managers = system.managers | ||
1560 | 492 | if available_managers: | ||
1561 | 493 | if manager_id is None: | ||
1562 | 494 | sushy_manager = available_managers[0] | ||
1563 | 495 | else: | ||
1564 | 496 | for manager in available_managers: | ||
1565 | 497 | if manager.identity == manager_id: | ||
1566 | 498 | sushy_manager = manager | ||
1567 | 499 | if sushy_manager is None: | ||
1568 | 500 | raise Exception("Couldn't find any Sushy Manager") | ||
1569 | 501 | return sushy_manager | ||
1570 | 502 | except sushy.exceptions.MissingAttributeError as e: | ||
1571 | 503 | LOG.error('Redfish Managers for node %(node)s are not associated ' | ||
1572 | 504 | 'with system %(system)s. Error %(error)s', | ||
1573 | 505 | {'system': system.identity, | ||
1574 | 506 | 'node': node.uuid, 'error': e}) | ||
1575 | 507 | raise exception.RedfishError(error=e) | ||
1576 | 508 | except Exception as exc: | ||
1577 | 509 | LOG.error('Redfish Manager was not found for ' | ||
1578 | 510 | 'node %(node)s under system %(system)s. Error %(error)s', | ||
1579 | 511 | {'system': system.identity, | ||
1580 | 512 | 'node': node.uuid, 'error': exc}) | ||
1581 | 513 | raise exception.RedfishError(error=exc) | ||
1582 | diff --git a/ironic/drivers/redfish.py b/ironic/drivers/redfish.py | |||
1583 | index 3852cd3..094119e 100644 | |||
1584 | --- a/ironic/drivers/redfish.py | |||
1585 | +++ b/ironic/drivers/redfish.py | |||
1586 | @@ -21,6 +21,7 @@ from ironic.drivers.modules import noop_mgmt | |||
1587 | 21 | from ironic.drivers.modules import pxe | 21 | from ironic.drivers.modules import pxe |
1588 | 22 | from ironic.drivers.modules.redfish import bios as redfish_bios | 22 | from ironic.drivers.modules.redfish import bios as redfish_bios |
1589 | 23 | from ironic.drivers.modules.redfish import boot as redfish_boot | 23 | from ironic.drivers.modules.redfish import boot as redfish_boot |
1590 | 24 | from ironic.drivers.modules.redfish import firmware as redfish_firmware | ||
1591 | 24 | from ironic.drivers.modules.redfish import inspect as redfish_inspect | 25 | from ironic.drivers.modules.redfish import inspect as redfish_inspect |
1592 | 25 | from ironic.drivers.modules.redfish import management as redfish_mgmt | 26 | from ironic.drivers.modules.redfish import management as redfish_mgmt |
1593 | 26 | from ironic.drivers.modules.redfish import power as redfish_power | 27 | from ironic.drivers.modules.redfish import power as redfish_power |
1594 | @@ -69,3 +70,7 @@ class RedfishHardware(generic.GenericHardware): | |||
1595 | 69 | def supported_raid_interfaces(self): | 70 | def supported_raid_interfaces(self): |
1596 | 70 | """List of supported raid interfaces.""" | 71 | """List of supported raid interfaces.""" |
1597 | 71 | return [redfish_raid.RedfishRAID, noop.NoRAID, agent.AgentRAID] | 72 | return [redfish_raid.RedfishRAID, noop.NoRAID, agent.AgentRAID] |
1598 | 73 | |||
1599 | 74 | @property | ||
1600 | 75 | def supported_firmware_interfaces(self): | ||
1601 | 76 | return [redfish_firmware.RedfishFirmware, noop.NoFirmware] | ||
1602 | diff --git a/ironic/objects/firmware.py b/ironic/objects/firmware.py | |||
1603 | index 2a0f5f2..d30bc16 100644 | |||
1604 | --- a/ironic/objects/firmware.py | |||
1605 | +++ b/ironic/objects/firmware.py | |||
1606 | @@ -145,11 +145,15 @@ class FirmwareComponentList(base.IronicObjectListBase, base.IronicObject): | |||
1607 | 145 | for cmp in components: | 145 | for cmp in components: |
1608 | 146 | if cmp['component'] in current_components_dict: | 146 | if cmp['component'] in current_components_dict: |
1609 | 147 | values = current_components_dict[cmp['component']] | 147 | values = current_components_dict[cmp['component']] |
1615 | 148 | 148 | if values.get('last_version_flashed') is None: | |
1616 | 149 | cv_changed = cmp['current_version'] \ | 149 | lvf_changed = False |
1617 | 150 | != values.get('current_version') | 150 | cv_changed = cmp['current_version'] \ |
1618 | 151 | lvf_changed = cmp['last_version_flashed'] \ | 151 | != values.get('current_version') |
1619 | 152 | != values.get('last_version_flashed') | 152 | else: |
1620 | 153 | lvf_changed = cmp['current_version'] \ | ||
1621 | 154 | != values.get('last_version_flashed') | ||
1622 | 155 | cv_changed = cmp['current_version'] \ | ||
1623 | 156 | != values.get('current_version') | ||
1624 | 153 | 157 | ||
1625 | 154 | if cv_changed or lvf_changed: | 158 | if cv_changed or lvf_changed: |
1626 | 155 | update_list.append(cmp) | 159 | update_list.append(cmp) |
1627 | diff --git a/ironic/tests/unit/api/base.py b/ironic/tests/unit/api/base.py | |||
1628 | index 5f53e30..7a0638f 100644 | |||
1629 | --- a/ironic/tests/unit/api/base.py | |||
1630 | +++ b/ironic/tests/unit/api/base.py | |||
1631 | @@ -104,7 +104,6 @@ class BaseApiTest(db_base.DbTestCase): | |||
1632 | 104 | :param path_prefix: prefix of the url path | 104 | :param path_prefix: prefix of the url path |
1633 | 105 | """ | 105 | """ |
1634 | 106 | full_path = path_prefix + path | 106 | full_path = path_prefix + path |
1635 | 107 | print('%s: %s %s' % (method.upper(), full_path, params)) | ||
1636 | 108 | response = getattr(self.app, "%s_json" % method)( | 107 | response = getattr(self.app, "%s_json" % method)( |
1637 | 109 | str(full_path), | 108 | str(full_path), |
1638 | 110 | params=params, | 109 | params=params, |
1639 | @@ -113,7 +112,7 @@ class BaseApiTest(db_base.DbTestCase): | |||
1640 | 113 | extra_environ=extra_environ, | 112 | extra_environ=extra_environ, |
1641 | 114 | expect_errors=expect_errors | 113 | expect_errors=expect_errors |
1642 | 115 | ) | 114 | ) |
1644 | 116 | print('GOT:%s' % response) | 115 | print(method.upper(), full_path, "WITH", params, "GOT", str(response)) |
1645 | 117 | return response | 116 | return response |
1646 | 118 | 117 | ||
1647 | 119 | def put_json(self, path, params, expect_errors=False, headers=None, | 118 | def put_json(self, path, params, expect_errors=False, headers=None, |
1648 | @@ -187,13 +186,12 @@ class BaseApiTest(db_base.DbTestCase): | |||
1649 | 187 | :param path_prefix: prefix of the url path | 186 | :param path_prefix: prefix of the url path |
1650 | 188 | """ | 187 | """ |
1651 | 189 | full_path = path_prefix + path | 188 | full_path = path_prefix + path |
1652 | 190 | print('DELETE: %s' % (full_path)) | ||
1653 | 191 | response = self.app.delete(str(full_path), | 189 | response = self.app.delete(str(full_path), |
1654 | 192 | headers=headers, | 190 | headers=headers, |
1655 | 193 | status=status, | 191 | status=status, |
1656 | 194 | extra_environ=extra_environ, | 192 | extra_environ=extra_environ, |
1657 | 195 | expect_errors=expect_errors) | 193 | expect_errors=expect_errors) |
1659 | 196 | print('GOT:%s' % response) | 194 | print("DELETE", full_path, "GOT", str(response)) |
1660 | 197 | return response | 195 | return response |
1661 | 198 | 196 | ||
1662 | 199 | def get_json(self, path, expect_errors=False, headers=None, | 197 | def get_json(self, path, expect_errors=False, headers=None, |
1663 | @@ -225,15 +223,14 @@ class BaseApiTest(db_base.DbTestCase): | |||
1664 | 225 | all_params.update(params) | 223 | all_params.update(params) |
1665 | 226 | if q: | 224 | if q: |
1666 | 227 | all_params.update(query_params) | 225 | all_params.update(query_params) |
1667 | 228 | print('GET: %s %r' % (full_path, all_params)) | ||
1668 | 229 | response = self.app.get(full_path, | 226 | response = self.app.get(full_path, |
1669 | 230 | params=all_params, | 227 | params=all_params, |
1670 | 231 | headers=headers, | 228 | headers=headers, |
1671 | 232 | extra_environ=extra_environ, | 229 | extra_environ=extra_environ, |
1672 | 233 | expect_errors=expect_errors) | 230 | expect_errors=expect_errors) |
1673 | 231 | print("GET", full_path, "WITH", params, "GOT", str(response)) | ||
1674 | 234 | if not expect_errors: | 232 | if not expect_errors: |
1675 | 235 | response = response.json | 233 | response = response.json |
1676 | 236 | print('GOT:%s' % response) | ||
1677 | 237 | return response | 234 | return response |
1678 | 238 | 235 | ||
1679 | 239 | def validate_link(self, link, bookmark=False, headers=None): | 236 | def validate_link(self, link, bookmark=False, headers=None): |
1680 | diff --git a/ironic/tests/unit/api/controllers/v1/test_port.py b/ironic/tests/unit/api/controllers/v1/test_port.py | |||
1681 | index 31885f4..088fe3c 100644 | |||
1682 | --- a/ironic/tests/unit/api/controllers/v1/test_port.py | |||
1683 | +++ b/ironic/tests/unit/api/controllers/v1/test_port.py | |||
1684 | @@ -1114,7 +1114,6 @@ class TestListPortsByShard(test_api_base.BaseApiTest): | |||
1685 | 1114 | 1114 | ||
1686 | 1115 | res = self.get_json('/ports?shard=shard1,shard2', headers=self.headers) | 1115 | res = self.get_json('/ports?shard=shard1,shard2', headers=self.headers) |
1687 | 1116 | self.assertEqual(2, len(res['ports'])) | 1116 | self.assertEqual(2, len(res['ports'])) |
1688 | 1117 | print(res['ports'][0]) | ||
1689 | 1118 | self.assertNotEqual(res['ports'][0]['address'], bad_shard_address) | 1117 | self.assertNotEqual(res['ports'][0]['address'], bad_shard_address) |
1690 | 1119 | self.assertNotEqual(res['ports'][1]['address'], bad_shard_address) | 1118 | self.assertNotEqual(res['ports'][1]['address'], bad_shard_address) |
1691 | 1120 | 1119 | ||
1692 | diff --git a/ironic/tests/unit/api/test_acl.py b/ironic/tests/unit/api/test_acl.py | |||
1693 | index 0481843..ded1d0b 100644 | |||
1694 | --- a/ironic/tests/unit/api/test_acl.py | |||
1695 | +++ b/ironic/tests/unit/api/test_acl.py | |||
1696 | @@ -102,7 +102,6 @@ class TestACLBase(base.BaseApiTest): | |||
1697 | 102 | # in troubleshooting ACL testing. This is a pattern | 102 | # in troubleshooting ACL testing. This is a pattern |
1698 | 103 | # followed in API unit testing in ironic, and | 103 | # followed in API unit testing in ironic, and |
1699 | 104 | # really does help. | 104 | # really does help. |
1700 | 105 | print('API ACL Testing Path %s %s' % (method, path)) | ||
1701 | 106 | if headers: | 105 | if headers: |
1702 | 107 | for k, v in headers.items(): | 106 | for k, v in headers.items(): |
1703 | 108 | rheaders[k] = v.format(**self.format_data) | 107 | rheaders[k] = v.format(**self.format_data) |
1704 | @@ -185,8 +184,6 @@ class TestACLBase(base.BaseApiTest): | |||
1705 | 185 | if assert_dict_contains: | 184 | if assert_dict_contains: |
1706 | 186 | for k, v in assert_dict_contains.items(): | 185 | for k, v in assert_dict_contains.items(): |
1707 | 187 | self.assertIn(k, response) | 186 | self.assertIn(k, response) |
1708 | 188 | print(k) | ||
1709 | 189 | print(v) | ||
1710 | 190 | if str(v) == "None": | 187 | if str(v) == "None": |
1711 | 191 | # Compare since the variable loaded from the | 188 | # Compare since the variable loaded from the |
1712 | 192 | # json ends up being null in json or None. | 189 | # json ends up being null in json or None. |
1713 | @@ -219,13 +216,6 @@ class TestACLBase(base.BaseApiTest): | |||
1714 | 219 | # a filtered view in these cases. | 216 | # a filtered view in these cases. |
1715 | 220 | self.assertEqual(0, len(items)) | 217 | self.assertEqual(0, len(items)) |
1716 | 221 | 218 | ||
1717 | 222 | # NOTE(TheJulia): API tests in Ironic tend to have a pattern | ||
1718 | 223 | # to print request and response data to aid in development | ||
1719 | 224 | # and troubleshooting. As such the prints should remain, | ||
1720 | 225 | # at least until we are through primary development of the | ||
1721 | 226 | # this test suite. | ||
1722 | 227 | print('ACL Test GOT %s' % response) | ||
1723 | 228 | |||
1724 | 229 | 219 | ||
1725 | 230 | @ddt.ddt | 220 | @ddt.ddt |
1726 | 231 | class TestRBACBasic(TestACLBase): | 221 | class TestRBACBasic(TestACLBase): |
1727 | diff --git a/ironic/tests/unit/common/test_neutron.py b/ironic/tests/unit/common/test_neutron.py | |||
1728 | index c3595a1..fe238df 100644 | |||
1729 | --- a/ironic/tests/unit/common/test_neutron.py | |||
1730 | +++ b/ironic/tests/unit/common/test_neutron.py | |||
1731 | @@ -678,7 +678,6 @@ class TestNeutronNetworkActions(db_base.DbTestCase): | |||
1732 | 678 | 678 | ||
1733 | 679 | network_data = neutron.get_neutron_port_data('port1', 'vif1') | 679 | network_data = neutron.get_neutron_port_data('port1', 'vif1') |
1734 | 680 | 680 | ||
1735 | 681 | print(network_data) | ||
1736 | 682 | expected_port = { | 681 | expected_port = { |
1737 | 683 | 'id': 'port1', | 682 | 'id': 'port1', |
1738 | 684 | 'type': 'vif', | 683 | 'type': 'vif', |
1739 | diff --git a/ironic/tests/unit/common/test_pxe_utils.py b/ironic/tests/unit/common/test_pxe_utils.py | |||
1740 | index 190bdfb..a57441f 100644 | |||
1741 | --- a/ironic/tests/unit/common/test_pxe_utils.py | |||
1742 | +++ b/ironic/tests/unit/common/test_pxe_utils.py | |||
1743 | @@ -895,9 +895,6 @@ class TestPXEUtils(db_base.DbTestCase): | |||
1744 | 895 | expected_info = [{'opt_name': '67', | 895 | expected_info = [{'opt_name': '67', |
1745 | 896 | 'opt_value': bootfile, | 896 | 'opt_value': bootfile, |
1746 | 897 | 'ip_version': ip_version}, | 897 | 'ip_version': ip_version}, |
1747 | 898 | {'opt_name': '210', | ||
1748 | 899 | 'opt_value': '/tftp-path/', | ||
1749 | 900 | 'ip_version': ip_version}, | ||
1750 | 901 | {'opt_name': '66', | 898 | {'opt_name': '66', |
1751 | 902 | 'opt_value': '192.0.2.1', | 899 | 'opt_value': '192.0.2.1', |
1752 | 903 | 'ip_version': ip_version}, | 900 | 'ip_version': ip_version}, |
1753 | @@ -2165,8 +2162,6 @@ class iPXEBuildConfigOptionsTestCase(db_base.DbTestCase): | |||
1754 | 2165 | if iso_boot: | 2162 | if iso_boot: |
1755 | 2166 | self.node.instance_info = {'boot_iso': 'http://test.url/file.iso'} | 2163 | self.node.instance_info = {'boot_iso': 'http://test.url/file.iso'} |
1756 | 2167 | self.node.save() | 2164 | self.node.save() |
1757 | 2168 | print(expected_options) | ||
1758 | 2169 | print(image_info) | ||
1759 | 2170 | iso_url = os.path.join(http_url, self.node.uuid, 'boot_iso') | 2165 | iso_url = os.path.join(http_url, self.node.uuid, 'boot_iso') |
1760 | 2171 | expected_options.update( | 2166 | expected_options.update( |
1761 | 2172 | { | 2167 | { |
1762 | diff --git a/ironic/tests/unit/common/test_utils.py b/ironic/tests/unit/common/test_utils.py | |||
1763 | index 8d8e4aa..96c6612 100644 | |||
1764 | --- a/ironic/tests/unit/common/test_utils.py | |||
1765 | +++ b/ironic/tests/unit/common/test_utils.py | |||
1766 | @@ -321,6 +321,12 @@ class GenericUtilsTestCase(base.TestCase): | |||
1767 | 321 | self.assertFalse(utils.is_fips_enabled()) | 321 | self.assertFalse(utils.is_fips_enabled()) |
1768 | 322 | m.assert_called_once_with('/proc/sys/crypto/fips_enabled', 'r') | 322 | m.assert_called_once_with('/proc/sys/crypto/fips_enabled', 'r') |
1769 | 323 | 323 | ||
1770 | 324 | def test_wrap_ipv6(self): | ||
1771 | 325 | self.assertEqual('[2001:DB8::1]', utils.wrap_ipv6('2001:DB8::1')) | ||
1772 | 326 | self.assertEqual('example.com', utils.wrap_ipv6('example.com')) | ||
1773 | 327 | self.assertEqual('192.168.24.1', utils.wrap_ipv6('192.168.24.1')) | ||
1774 | 328 | self.assertEqual('[2001:DB8::1]', utils.wrap_ipv6('[2001:DB8::1]')) | ||
1775 | 329 | |||
1776 | 324 | 330 | ||
1777 | 325 | class TempFilesTestCase(base.TestCase): | 331 | class TempFilesTestCase(base.TestCase): |
1778 | 326 | 332 | ||
1779 | diff --git a/ironic/tests/unit/conductor/test_steps.py b/ironic/tests/unit/conductor/test_steps.py | |||
1780 | index 09d267a..64317c1 100644 | |||
1781 | --- a/ironic/tests/unit/conductor/test_steps.py | |||
1782 | +++ b/ironic/tests/unit/conductor/test_steps.py | |||
1783 | @@ -585,6 +585,11 @@ class NodeCleaningStepsTestCase(db_base.DbTestCase): | |||
1784 | 585 | 'abortable': False, 'argsinfo': None, 'interface': 'vendor', | 585 | 'abortable': False, 'argsinfo': None, 'interface': 'vendor', |
1785 | 586 | 'priority': 1, 'requires_ramdisk': True, | 586 | 'priority': 1, 'requires_ramdisk': True, |
1786 | 587 | 'step': 'log_passthrough'} | 587 | 'step': 'log_passthrough'} |
1787 | 588 | self.firmware_step = { | ||
1788 | 589 | 'abortable': False, 'argsinfo': {}, 'interface': 'firmware', | ||
1789 | 590 | 'priority': 0, 'requires_ramdisk': True, | ||
1790 | 591 | 'step': 'update' | ||
1791 | 592 | } | ||
1792 | 588 | 593 | ||
1793 | 589 | # Automated cleaning should be executed in this order | 594 | # Automated cleaning should be executed in this order |
1794 | 590 | self.clean_steps = [self.deploy_erase, self.power_update, | 595 | self.clean_steps = [self.deploy_erase, self.power_update, |
1795 | @@ -595,6 +600,8 @@ class NodeCleaningStepsTestCase(db_base.DbTestCase): | |||
1796 | 595 | 'argsinfo': {'arg1': {'description': 'desc1', 'required': True}, | 600 | 'argsinfo': {'arg1': {'description': 'desc1', 'required': True}, |
1797 | 596 | 'arg2': {'description': 'desc2'}}} | 601 | 'arg2': {'description': 'desc2'}}} |
1798 | 597 | 602 | ||
1799 | 603 | @mock.patch('ironic.drivers.modules.fake.FakeFirmware.get_clean_steps', | ||
1800 | 604 | lambda self, taks: []) | ||
1801 | 598 | @mock.patch('ironic.drivers.modules.fake.FakeBIOS.get_clean_steps', | 605 | @mock.patch('ironic.drivers.modules.fake.FakeBIOS.get_clean_steps', |
1802 | 599 | lambda self, task: []) | 606 | lambda self, task: []) |
1803 | 600 | @mock.patch('ironic.drivers.modules.fake.FakeDeploy.get_clean_steps', | 607 | @mock.patch('ironic.drivers.modules.fake.FakeDeploy.get_clean_steps', |
1804 | @@ -619,6 +626,8 @@ class NodeCleaningStepsTestCase(db_base.DbTestCase): | |||
1805 | 619 | 626 | ||
1806 | 620 | self.assertEqual(self.clean_steps, steps) | 627 | self.assertEqual(self.clean_steps, steps) |
1807 | 621 | 628 | ||
1808 | 629 | @mock.patch('ironic.drivers.modules.fake.FakeFirmware.get_clean_steps', | ||
1809 | 630 | lambda self, task: []) | ||
1810 | 622 | @mock.patch('ironic.drivers.modules.fake.FakeVendorB.get_clean_steps', | 631 | @mock.patch('ironic.drivers.modules.fake.FakeVendorB.get_clean_steps', |
1811 | 623 | lambda self, task: []) | 632 | lambda self, task: []) |
1812 | 624 | @mock.patch('ironic.drivers.modules.fake.FakeBIOS.get_clean_steps', | 633 | @mock.patch('ironic.drivers.modules.fake.FakeBIOS.get_clean_steps', |
1813 | diff --git a/ironic/tests/unit/db/test_nodes.py b/ironic/tests/unit/db/test_nodes.py | |||
1814 | index 3dee718..b10d367 100644 | |||
1815 | --- a/ironic/tests/unit/db/test_nodes.py | |||
1816 | +++ b/ironic/tests/unit/db/test_nodes.py | |||
1817 | @@ -15,6 +15,7 @@ | |||
1818 | 15 | 15 | ||
1819 | 16 | """Tests for manipulating Nodes via the DB API""" | 16 | """Tests for manipulating Nodes via the DB API""" |
1820 | 17 | 17 | ||
1821 | 18 | import copy | ||
1822 | 18 | import datetime | 19 | import datetime |
1823 | 19 | from unittest import mock | 20 | from unittest import mock |
1824 | 20 | 21 | ||
1825 | @@ -29,6 +30,7 @@ from sqlalchemy.orm import exc as sa_orm_exc | |||
1826 | 29 | 30 | ||
1827 | 30 | from ironic.common import exception | 31 | from ironic.common import exception |
1828 | 31 | from ironic.common import states | 32 | from ironic.common import states |
1829 | 33 | from ironic.common import utils as common_utils | ||
1830 | 32 | from ironic.db.sqlalchemy import api as dbapi | 34 | from ironic.db.sqlalchemy import api as dbapi |
1831 | 33 | from ironic.db.sqlalchemy.api import Connection as db_conn | 35 | from ironic.db.sqlalchemy.api import Connection as db_conn |
1832 | 34 | from ironic.db.sqlalchemy.models import NodeInventory | 36 | from ironic.db.sqlalchemy.models import NodeInventory |
1833 | @@ -1037,6 +1039,39 @@ class DbNodeTestCase(base.DbTestCase): | |||
1834 | 1037 | res = self.dbapi.get_node_by_uuid(uuid) | 1039 | res = self.dbapi.get_node_by_uuid(uuid) |
1835 | 1038 | self.assertEqual(r1, res.reservation) | 1040 | self.assertEqual(r1, res.reservation) |
1836 | 1039 | 1041 | ||
1837 | 1042 | def test_reserve_node_reads_reservation_once_sqlite(self): | ||
1838 | 1043 | node = utils.create_test_node() | ||
1839 | 1044 | uuid = node.uuid | ||
1840 | 1045 | |||
1841 | 1046 | r1 = 'fake-reservation' | ||
1842 | 1047 | |||
1843 | 1048 | with mock.patch.object(db_conn, '_get_node_reservation', | ||
1844 | 1049 | autospec=True) as mock_get_res: | ||
1845 | 1050 | mock_get_res.return_value = node | ||
1846 | 1051 | self.dbapi.reserve_node(r1, uuid) | ||
1847 | 1052 | mock_get_res.assert_called_once_with(mock.ANY, node.uuid) | ||
1848 | 1053 | |||
1849 | 1054 | @mock.patch.object(common_utils, 'is_ironic_using_sqlite', autospec=True) | ||
1850 | 1055 | def test_reserve_node_reads_reservation_twice(self, is_sqlite_mock): | ||
1851 | 1056 | # Ensure we re-query for who holds the reservation *when* lock fails | ||
1852 | 1057 | # to trigger. | ||
1853 | 1058 | node = utils.create_test_node() | ||
1854 | 1059 | uuid = node.uuid | ||
1855 | 1060 | is_sqlite_mock.return_value = False | ||
1856 | 1061 | r1 = 'fake-reservation' | ||
1857 | 1062 | self.dbapi.update_node(node.id, {'reservation': r1}) | ||
1858 | 1063 | locked_node = copy.copy(node) | ||
1859 | 1064 | locked_node.reservation = r1 | ||
1860 | 1065 | with mock.patch.object(db_conn, '_get_node_reservation', | ||
1861 | 1066 | autospec=True) as mock_get_res: | ||
1862 | 1067 | mock_get_res.side_effect = [node, locked_node] | ||
1863 | 1068 | self.assertRaisesRegex(exception.NodeLocked, | ||
1864 | 1069 | 'locked by host fake-reservation', | ||
1865 | 1070 | self.dbapi.reserve_node, r1, uuid) | ||
1866 | 1071 | mock_get_res.assert_has_calls([ | ||
1867 | 1072 | mock.call(mock.ANY, node.uuid), | ||
1868 | 1073 | mock.call(mock.ANY, node.id)]) | ||
1869 | 1074 | |||
1870 | 1040 | def test_release_reservation(self): | 1075 | def test_release_reservation(self): |
1871 | 1041 | node = utils.create_test_node() | 1076 | node = utils.create_test_node() |
1872 | 1042 | uuid = node.uuid | 1077 | uuid = node.uuid |
1873 | diff --git a/ironic/tests/unit/drivers/modules/ilo/test_management.py b/ironic/tests/unit/drivers/modules/ilo/test_management.py | |||
1874 | index f087c4d..9379954 100644 | |||
1875 | --- a/ironic/tests/unit/drivers/modules/ilo/test_management.py | |||
1876 | +++ b/ironic/tests/unit/drivers/modules/ilo/test_management.py | |||
1877 | @@ -1681,7 +1681,7 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): | |||
1878 | 1681 | ilo_mock_object.do_disk_erase.assert_called_once_with( | 1681 | ilo_mock_object.do_disk_erase.assert_called_once_with( |
1879 | 1682 | 'HDD', 'overwrite') | 1682 | 'HDD', 'overwrite') |
1880 | 1683 | self.assertEqual(states.CLEANWAIT, result) | 1683 | self.assertEqual(states.CLEANWAIT, result) |
1882 | 1684 | mock_power.assert_called_once_with(task, states.REBOOT) | 1684 | mock_power.assert_called_once_with(task, states.REBOOT, None) |
1883 | 1685 | 1685 | ||
1884 | 1686 | @mock.patch.object(deploy_utils, 'build_agent_options', | 1686 | @mock.patch.object(deploy_utils, 'build_agent_options', |
1885 | 1687 | autospec=True) | 1687 | autospec=True) |
1886 | @@ -1712,7 +1712,7 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): | |||
1887 | 1712 | ilo_mock_object.do_disk_erase.assert_called_once_with( | 1712 | ilo_mock_object.do_disk_erase.assert_called_once_with( |
1888 | 1713 | 'SSD', 'block') | 1713 | 'SSD', 'block') |
1889 | 1714 | self.assertEqual(states.CLEANWAIT, result) | 1714 | self.assertEqual(states.CLEANWAIT, result) |
1891 | 1715 | mock_power.assert_called_once_with(task, states.REBOOT) | 1715 | mock_power.assert_called_once_with(task, states.REBOOT, None) |
1892 | 1716 | 1716 | ||
1893 | 1717 | @mock.patch.object(deploy_utils, 'build_agent_options', | 1717 | @mock.patch.object(deploy_utils, 'build_agent_options', |
1894 | 1718 | autospec=True) | 1718 | autospec=True) |
1895 | @@ -1746,7 +1746,7 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): | |||
1896 | 1746 | ilo_mock_object.do_disk_erase.assert_called_once_with( | 1746 | ilo_mock_object.do_disk_erase.assert_called_once_with( |
1897 | 1747 | 'SSD', 'block') | 1747 | 'SSD', 'block') |
1898 | 1748 | self.assertEqual(states.CLEANWAIT, result) | 1748 | self.assertEqual(states.CLEANWAIT, result) |
1900 | 1749 | mock_power.assert_called_once_with(task, states.REBOOT) | 1749 | mock_power.assert_called_once_with(task, states.REBOOT, None) |
1901 | 1750 | 1750 | ||
1902 | 1751 | @mock.patch.object(ilo_management.LOG, 'info', autospec=True) | 1751 | @mock.patch.object(ilo_management.LOG, 'info', autospec=True) |
1903 | 1752 | @mock.patch.object(ilo_management.Ilo5Management, | 1752 | @mock.patch.object(ilo_management.Ilo5Management, |
1904 | @@ -1802,7 +1802,7 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): | |||
1905 | 1802 | ilo_mock_object.do_disk_erase.assert_called_once_with( | 1802 | ilo_mock_object.do_disk_erase.assert_called_once_with( |
1906 | 1803 | 'HDD', 'zero') | 1803 | 'HDD', 'zero') |
1907 | 1804 | self.assertEqual(states.CLEANWAIT, result) | 1804 | self.assertEqual(states.CLEANWAIT, result) |
1909 | 1805 | mock_power.assert_called_once_with(task, states.REBOOT) | 1805 | mock_power.assert_called_once_with(task, states.REBOOT, None) |
1910 | 1806 | 1806 | ||
1911 | 1807 | @mock.patch.object(ilo_management.LOG, 'info', autospec=True) | 1807 | @mock.patch.object(ilo_management.LOG, 'info', autospec=True) |
1912 | 1808 | @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True) | 1808 | @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True) |
1913 | diff --git a/ironic/tests/unit/drivers/modules/ilo/test_raid.py b/ironic/tests/unit/drivers/modules/ilo/test_raid.py | |||
1914 | index fcb0314..c102a5d 100644 | |||
1915 | --- a/ironic/tests/unit/drivers/modules/ilo/test_raid.py | |||
1916 | +++ b/ironic/tests/unit/drivers/modules/ilo/test_raid.py | |||
1917 | @@ -84,7 +84,7 @@ class Ilo5RAIDTestCase(db_base.DbTestCase): | |||
1918 | 84 | self.assertFalse( | 84 | self.assertFalse( |
1919 | 85 | task.node.driver_internal_info.get( | 85 | task.node.driver_internal_info.get( |
1920 | 86 | 'skip_current_deploy_step')) | 86 | 'skip_current_deploy_step')) |
1922 | 87 | mock_reboot.assert_called_once_with(task, states.REBOOT) | 87 | mock_reboot.assert_called_once_with(task, states.REBOOT, None) |
1923 | 88 | 88 | ||
1924 | 89 | def test__prepare_for_read_raid_create_raid_cleaning(self): | 89 | def test__prepare_for_read_raid_create_raid_cleaning(self): |
1925 | 90 | self.node.clean_step = {'step': 'create_configuration', | 90 | self.node.clean_step = {'step': 'create_configuration', |
1926 | @@ -122,7 +122,7 @@ class Ilo5RAIDTestCase(db_base.DbTestCase): | |||
1927 | 122 | self.assertEqual( | 122 | self.assertEqual( |
1928 | 123 | task.node.driver_internal_info.get( | 123 | task.node.driver_internal_info.get( |
1929 | 124 | 'skip_current_deploy_step'), False) | 124 | 'skip_current_deploy_step'), False) |
1931 | 125 | mock_reboot.assert_called_once_with(task, states.REBOOT) | 125 | mock_reboot.assert_called_once_with(task, states.REBOOT, None) |
1932 | 126 | 126 | ||
1933 | 127 | def test__prepare_for_read_raid_delete_raid_cleaning(self): | 127 | def test__prepare_for_read_raid_delete_raid_cleaning(self): |
1934 | 128 | self.node.clean_step = {'step': 'create_configuration', | 128 | self.node.clean_step = {'step': 'create_configuration', |
1935 | diff --git a/ironic/tests/unit/drivers/modules/redfish/test_bios.py b/ironic/tests/unit/drivers/modules/redfish/test_bios.py | |||
1936 | index 1d30730..bccaad0 100644 | |||
1937 | --- a/ironic/tests/unit/drivers/modules/redfish/test_bios.py | |||
1938 | +++ b/ironic/tests/unit/drivers/modules/redfish/test_bios.py | |||
1939 | @@ -203,10 +203,11 @@ class RedfishBiosTestCase(db_base.DbTestCase): | |||
1940 | 203 | if fast_track: | 203 | if fast_track: |
1941 | 204 | mock_power_action.assert_has_calls([ | 204 | mock_power_action.assert_has_calls([ |
1942 | 205 | mock.call(task, states.POWER_OFF), | 205 | mock.call(task, states.POWER_OFF), |
1944 | 206 | mock.call(task, states.REBOOT), | 206 | mock.call(task, states.REBOOT, None), |
1945 | 207 | ]) | 207 | ]) |
1946 | 208 | else: | 208 | else: |
1948 | 209 | mock_power_action.assert_called_once_with(task, states.REBOOT) | 209 | mock_power_action.assert_called_once_with( |
1949 | 210 | task, states.REBOOT, None) | ||
1950 | 210 | if step == 'factory_reset': | 211 | if step == 'factory_reset': |
1951 | 211 | bios.reset_bios.assert_called_once() | 212 | bios.reset_bios.assert_called_once() |
1952 | 212 | if step == 'apply_configuration': | 213 | if step == 'apply_configuration': |
1953 | diff --git a/ironic/tests/unit/drivers/modules/redfish/test_firmware.py b/ironic/tests/unit/drivers/modules/redfish/test_firmware.py | |||
1954 | 213 | new file mode 100644 | 214 | new file mode 100644 |
1955 | index 0000000..c3e984c | |||
1956 | --- /dev/null | |||
1957 | +++ b/ironic/tests/unit/drivers/modules/redfish/test_firmware.py | |||
1958 | @@ -0,0 +1,40 @@ | |||
1959 | 1 | # | ||
1960 | 2 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
1961 | 3 | # not use this file except in compliance with the License. You may obtain | ||
1962 | 4 | # a copy of the License at | ||
1963 | 5 | # | ||
1964 | 6 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
1965 | 7 | # | ||
1966 | 8 | # Unless required by applicable law or agreed to in writing, software | ||
1967 | 9 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
1968 | 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
1969 | 11 | # License for the specific language governing permissions and limitations | ||
1970 | 12 | # under the License. | ||
1971 | 13 | |||
1972 | 14 | from unittest import mock | ||
1973 | 15 | |||
1974 | 16 | from oslo_utils import importutils | ||
1975 | 17 | |||
1976 | 18 | from ironic.tests.unit.db import base as db_base | ||
1977 | 19 | from ironic.tests.unit.db import utils as db_utils | ||
1978 | 20 | from ironic.tests.unit.objects import utils as obj_utils | ||
1979 | 21 | |||
1980 | 22 | sushy = importutils.try_import('sushy') | ||
1981 | 23 | |||
1982 | 24 | INFO_DICT = db_utils.get_test_redfish_info() | ||
1983 | 25 | |||
1984 | 26 | |||
1985 | 27 | @mock.patch('oslo_utils.eventletutils.EventletEvent.wait', | ||
1986 | 28 | lambda *args, **kwargs: None) | ||
1987 | 29 | class RedfishFirmwareTestCase(db_base.DbTestCase): | ||
1988 | 30 | |||
1989 | 31 | def setUp(self): | ||
1990 | 32 | super(RedfishFirmwareTestCase, self).setUp() | ||
1991 | 33 | self.config(enabled_bios_interfaces=['redfish'], | ||
1992 | 34 | enabled_hardware_types=['redfish'], | ||
1993 | 35 | enabled_power_interfaces=['redfish'], | ||
1994 | 36 | enabled_boot_interfaces=['redfish-virtual-media'], | ||
1995 | 37 | enabled_management_interfaces=['redfish'], | ||
1996 | 38 | enabled_firmware_interfaces=['redfish']) | ||
1997 | 39 | self.node = obj_utils.create_test_node( | ||
1998 | 40 | self.context, driver='redfish', driver_info=INFO_DICT) | ||
1999 | diff --git a/ironic/tests/unit/drivers/modules/redfish/test_management.py b/ironic/tests/unit/drivers/modules/redfish/test_management.py | |||
2000 | index 1d752d9..3a9dca2 100644 | |||
2001 | --- a/ironic/tests/unit/drivers/modules/redfish/test_management.py | |||
2002 | +++ b/ironic/tests/unit/drivers/modules/redfish/test_management.py | |||
2003 | @@ -28,10 +28,12 @@ from ironic.common import states | |||
2004 | 28 | from ironic.conductor import task_manager | 28 | from ironic.conductor import task_manager |
2005 | 29 | from ironic.conductor import utils as manager_utils | 29 | from ironic.conductor import utils as manager_utils |
2006 | 30 | from ironic.conf import CONF | 30 | from ironic.conf import CONF |
2007 | 31 | from ironic.drivers.modules import boot_mode_utils | ||
2008 | 31 | from ironic.drivers.modules import deploy_utils | 32 | from ironic.drivers.modules import deploy_utils |
2009 | 32 | from ironic.drivers.modules.redfish import boot as redfish_boot | 33 | from ironic.drivers.modules.redfish import boot as redfish_boot |
2010 | 33 | from ironic.drivers.modules.redfish import firmware_utils | 34 | from ironic.drivers.modules.redfish import firmware_utils |
2011 | 34 | from ironic.drivers.modules.redfish import management as redfish_mgmt | 35 | from ironic.drivers.modules.redfish import management as redfish_mgmt |
2012 | 36 | from ironic.drivers.modules.redfish import power as redfish_power | ||
2013 | 35 | from ironic.drivers.modules.redfish import utils as redfish_utils | 37 | from ironic.drivers.modules.redfish import utils as redfish_utils |
2014 | 36 | from ironic.tests.unit.db import base as db_base | 38 | from ironic.tests.unit.db import base as db_base |
2015 | 37 | from ironic.tests.unit.db import utils as db_utils | 39 | from ironic.tests.unit.db import utils as db_utils |
2016 | @@ -268,8 +270,10 @@ class RedfishManagementTestCase(db_base.DbTestCase): | |||
2017 | 268 | boot_devices.PXE, | 270 | boot_devices.PXE, |
2018 | 269 | task.node.driver_internal_info['redfish_boot_device']) | 271 | task.node.driver_internal_info['redfish_boot_device']) |
2019 | 270 | 272 | ||
2020 | 273 | @mock.patch.object(boot_mode_utils, 'sync_boot_mode', autospec=True) | ||
2021 | 271 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) | 274 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) |
2023 | 272 | def test_set_boot_device_persistency_vendor(self, mock_get_system): | 275 | def test_set_boot_device_persistency_vendor(self, mock_get_system, |
2024 | 276 | mock_sync_boot_mode): | ||
2025 | 273 | fake_system = mock_get_system.return_value | 277 | fake_system = mock_get_system.return_value |
2026 | 274 | fake_system.boot.get.return_value = \ | 278 | fake_system.boot.get.return_value = \ |
2027 | 275 | sushy.BOOT_SOURCE_ENABLED_CONTINUOUS | 279 | sushy.BOOT_SOURCE_ENABLED_CONTINUOUS |
2028 | @@ -288,18 +292,16 @@ class RedfishManagementTestCase(db_base.DbTestCase): | |||
2029 | 288 | shared=False) as task: | 292 | shared=False) as task: |
2030 | 289 | task.driver.management.set_boot_device( | 293 | task.driver.management.set_boot_device( |
2031 | 290 | task, boot_devices.PXE, persistent=True) | 294 | task, boot_devices.PXE, persistent=True) |
2032 | 295 | fake_system.set_system_boot_options.assert_called_once_with( | ||
2033 | 296 | sushy.BOOT_SOURCE_TARGET_PXE, enabled=expected) | ||
2034 | 291 | if vendor == 'SuperMicro': | 297 | if vendor == 'SuperMicro': |
2039 | 292 | fake_system.set_system_boot_options.assert_has_calls( | 298 | mock_sync_boot_mode.assert_called_once_with(task) |
2036 | 293 | [mock.call(sushy.BOOT_SOURCE_TARGET_PXE, | ||
2037 | 294 | enabled=expected), | ||
2038 | 295 | mock.call(mode=sushy.BOOT_SOURCE_MODE_UEFI)]) | ||
2040 | 296 | else: | 299 | else: |
2044 | 297 | fake_system.set_system_boot_options.assert_has_calls( | 300 | mock_sync_boot_mode.assert_not_called() |
2042 | 298 | [mock.call(sushy.BOOT_SOURCE_TARGET_PXE, | ||
2043 | 299 | enabled=expected)]) | ||
2045 | 300 | 301 | ||
2046 | 301 | # Reset mocks | 302 | # Reset mocks |
2047 | 302 | fake_system.set_system_boot_options.reset_mock() | 303 | fake_system.set_system_boot_options.reset_mock() |
2048 | 304 | mock_sync_boot_mode.reset_mock() | ||
2049 | 303 | mock_get_system.reset_mock() | 305 | mock_get_system.reset_mock() |
2050 | 304 | 306 | ||
2051 | 305 | def test_restore_boot_device(self): | 307 | def test_restore_boot_device(self): |
2052 | @@ -391,34 +393,46 @@ class RedfishManagementTestCase(db_base.DbTestCase): | |||
2053 | 391 | self.assertEqual(list(redfish_mgmt.BOOT_MODE_MAP_REV), | 393 | self.assertEqual(list(redfish_mgmt.BOOT_MODE_MAP_REV), |
2054 | 392 | supported_boot_modes) | 394 | supported_boot_modes) |
2055 | 393 | 395 | ||
2056 | 396 | @mock.patch.object(redfish_mgmt.RedfishManagement, '_wait_for_boot_mode', | ||
2057 | 397 | autospec=True) | ||
2058 | 394 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) | 398 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) |
2060 | 395 | def test_set_boot_mode(self, mock_get_system): | 399 | def test_set_boot_mode(self, mock_get_system, mock_wait): |
2061 | 396 | boot_attribute = { | 400 | boot_attribute = { |
2062 | 397 | 'target': sushy.BOOT_SOURCE_TARGET_PXE, | 401 | 'target': sushy.BOOT_SOURCE_TARGET_PXE, |
2063 | 398 | 'enabled': sushy.BOOT_SOURCE_ENABLED_CONTINUOUS, | 402 | 'enabled': sushy.BOOT_SOURCE_ENABLED_CONTINUOUS, |
2065 | 399 | 'mode': sushy.BOOT_SOURCE_MODE_BIOS, | 403 | 'mode': None, |
2066 | 400 | } | 404 | } |
2067 | 401 | fake_system = mock.Mock(boot=boot_attribute) | 405 | fake_system = mock.Mock(boot=boot_attribute) |
2068 | 402 | fake_system = mock.Mock() | ||
2069 | 403 | mock_get_system.return_value = fake_system | 406 | mock_get_system.return_value = fake_system |
2070 | 404 | with task_manager.acquire(self.context, self.node.uuid, | 407 | with task_manager.acquire(self.context, self.node.uuid, |
2071 | 405 | shared=False) as task: | 408 | shared=False) as task: |
2072 | 406 | expected_values = [ | 409 | expected_values = [ |
2075 | 407 | (boot_modes.LEGACY_BIOS, sushy.BOOT_SOURCE_MODE_BIOS), | 410 | (boot_modes.LEGACY_BIOS, sushy.BOOT_SOURCE_MODE_BIOS, |
2076 | 408 | (boot_modes.UEFI, sushy.BOOT_SOURCE_MODE_UEFI) | 411 | sushy.BOOT_SOURCE_MODE_UEFI), |
2077 | 412 | (boot_modes.UEFI, sushy.BOOT_SOURCE_MODE_UEFI, | ||
2078 | 413 | sushy.BOOT_SOURCE_MODE_BIOS), | ||
2079 | 414 | (boot_modes.LEGACY_BIOS, sushy.BOOT_SOURCE_MODE_BIOS, None), | ||
2080 | 415 | (boot_modes.UEFI, sushy.BOOT_SOURCE_MODE_UEFI, None), | ||
2081 | 409 | ] | 416 | ] |
2082 | 410 | 417 | ||
2084 | 411 | for mode, expected in expected_values: | 418 | for mode, expected, current in expected_values: |
2085 | 419 | boot_attribute['mode'] = current | ||
2086 | 412 | task.driver.management.set_boot_mode(task, mode=mode) | 420 | task.driver.management.set_boot_mode(task, mode=mode) |
2087 | 413 | 421 | ||
2088 | 414 | # Asserts | 422 | # Asserts |
2089 | 415 | fake_system.set_system_boot_options.assert_called_once_with( | 423 | fake_system.set_system_boot_options.assert_called_once_with( |
2090 | 416 | mode=expected) | 424 | mode=expected) |
2091 | 417 | mock_get_system.assert_called_once_with(task.node) | 425 | mock_get_system.assert_called_once_with(task.node) |
2092 | 426 | if current is not None: | ||
2093 | 427 | mock_wait.assert_called_once_with(task.driver.management, | ||
2094 | 428 | task, fake_system, mode) | ||
2095 | 429 | else: | ||
2096 | 430 | mock_wait.assert_not_called() | ||
2097 | 418 | 431 | ||
2098 | 419 | # Reset mocks | 432 | # Reset mocks |
2099 | 420 | fake_system.set_system_boot_options.reset_mock() | 433 | fake_system.set_system_boot_options.reset_mock() |
2100 | 421 | mock_get_system.reset_mock() | 434 | mock_get_system.reset_mock() |
2101 | 435 | mock_wait.reset_mock() | ||
2102 | 422 | 436 | ||
2103 | 423 | @mock.patch.object(sushy, 'Sushy', autospec=True) | 437 | @mock.patch.object(sushy, 'Sushy', autospec=True) |
2104 | 424 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) | 438 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) |
2105 | @@ -462,6 +476,44 @@ class RedfishManagementTestCase(db_base.DbTestCase): | |||
2106 | 462 | mode=sushy.BOOT_SOURCE_MODE_UEFI) | 476 | mode=sushy.BOOT_SOURCE_MODE_UEFI) |
2107 | 463 | mock_get_system.assert_called_once_with(task.node) | 477 | mock_get_system.assert_called_once_with(task.node) |
2108 | 464 | 478 | ||
2109 | 479 | @mock.patch.object(manager_utils, 'node_power_action', autospec=True) | ||
2110 | 480 | def test_wait_for_boot_mode_immediate(self, mock_power): | ||
2111 | 481 | fake_system = mock.Mock(spec=['boot', 'refresh'], | ||
2112 | 482 | boot={'mode': sushy.BOOT_SOURCE_MODE_UEFI}) | ||
2113 | 483 | with task_manager.acquire(self.context, self.node.uuid, | ||
2114 | 484 | shared=False) as task: | ||
2115 | 485 | task.driver.management._wait_for_boot_mode( | ||
2116 | 486 | task, fake_system, boot_modes.UEFI) | ||
2117 | 487 | fake_system.refresh.assert_called_once_with(force=True) | ||
2118 | 488 | mock_power.assert_not_called() | ||
2119 | 489 | |||
2120 | 490 | @mock.patch('time.sleep', lambda _: None) | ||
2121 | 491 | @mock.patch.object(redfish_power.RedfishPower, 'get_power_state', | ||
2122 | 492 | autospec=True) | ||
2123 | 493 | @mock.patch.object(manager_utils, 'node_power_action', autospec=True) | ||
2124 | 494 | def test_wait_for_boot_mode(self, mock_power, mock_get_power): | ||
2125 | 495 | attempts = 3 | ||
2126 | 496 | |||
2127 | 497 | def side_effect(force): | ||
2128 | 498 | nonlocal attempts | ||
2129 | 499 | attempts -= 1 | ||
2130 | 500 | if attempts <= 0: | ||
2131 | 501 | fake_system.boot['mode'] = sushy.BOOT_SOURCE_MODE_UEFI | ||
2132 | 502 | |||
2133 | 503 | fake_system = mock.Mock(spec=['boot', 'refresh'], | ||
2134 | 504 | boot={'mode': sushy.BOOT_SOURCE_MODE_BIOS}) | ||
2135 | 505 | fake_system.refresh.side_effect = side_effect | ||
2136 | 506 | with task_manager.acquire(self.context, self.node.uuid, | ||
2137 | 507 | shared=False) as task: | ||
2138 | 508 | task.driver.management._wait_for_boot_mode( | ||
2139 | 509 | task, fake_system, boot_modes.UEFI) | ||
2140 | 510 | fake_system.refresh.assert_called_with(force=True) | ||
2141 | 511 | self.assertEqual(3, fake_system.refresh.call_count) | ||
2142 | 512 | mock_power.assert_has_calls([ | ||
2143 | 513 | mock.call(task, states.REBOOT), | ||
2144 | 514 | mock.call(task, mock_get_power.return_value), | ||
2145 | 515 | ]) | ||
2146 | 516 | |||
2147 | 465 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) | 517 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) |
2148 | 466 | def test_get_boot_mode(self, mock_get_system): | 518 | def test_get_boot_mode(self, mock_get_system): |
2149 | 467 | boot_attribute = { | 519 | boot_attribute = { |
2150 | @@ -865,7 +917,8 @@ class RedfishManagementTestCase(db_base.DbTestCase): | |||
2151 | 865 | task.node, reboot=True, skip_current_step=True, polling=True) | 917 | task.node, reboot=True, skip_current_step=True, polling=True) |
2152 | 866 | mock_get_async_step_return_state.assert_called_once_with( | 918 | mock_get_async_step_return_state.assert_called_once_with( |
2153 | 867 | task.node) | 919 | task.node) |
2155 | 868 | mock_node_power_action.assert_called_once_with(task, states.REBOOT) | 920 | mock_node_power_action.assert_called_once_with( |
2156 | 921 | task, states.REBOOT, None) | ||
2157 | 869 | 922 | ||
2158 | 870 | @mock.patch.object(redfish_mgmt.RedfishManagement, '_stage_firmware_file', | 923 | @mock.patch.object(redfish_mgmt.RedfishManagement, '_stage_firmware_file', |
2159 | 871 | autospec=True) | 924 | autospec=True) |
2160 | @@ -919,7 +972,8 @@ class RedfishManagementTestCase(db_base.DbTestCase): | |||
2161 | 919 | task.node, reboot=True, skip_current_step=True, polling=True) | 972 | task.node, reboot=True, skip_current_step=True, polling=True) |
2162 | 920 | mock_get_async_step_return_state.assert_called_once_with( | 973 | mock_get_async_step_return_state.assert_called_once_with( |
2163 | 921 | task.node) | 974 | task.node) |
2165 | 922 | mock_node_power_action.assert_called_once_with(task, states.REBOOT) | 975 | mock_node_power_action.assert_called_once_with( |
2166 | 976 | task, states.REBOOT, None) | ||
2167 | 923 | 977 | ||
2168 | 924 | @mock.patch.object(redfish_mgmt.RedfishManagement, '_stage_firmware_file', | 978 | @mock.patch.object(redfish_mgmt.RedfishManagement, '_stage_firmware_file', |
2169 | 925 | autospec=True) | 979 | autospec=True) |
2170 | @@ -979,7 +1033,8 @@ class RedfishManagementTestCase(db_base.DbTestCase): | |||
2171 | 979 | task.node, reboot=True, skip_current_step=True, polling=True) | 1033 | task.node, reboot=True, skip_current_step=True, polling=True) |
2172 | 980 | mock_get_async_step_return_state.assert_called_once_with( | 1034 | mock_get_async_step_return_state.assert_called_once_with( |
2173 | 981 | task.node) | 1035 | task.node) |
2175 | 982 | mock_node_power_action.assert_called_once_with(task, states.REBOOT) | 1036 | mock_node_power_action.assert_called_once_with( |
2176 | 1037 | task, states.REBOOT, None) | ||
2177 | 983 | 1038 | ||
2178 | 984 | def test_update_firmware_invalid_args(self): | 1039 | def test_update_firmware_invalid_args(self): |
2179 | 985 | with task_manager.acquire(self.context, self.node.uuid, | 1040 | with task_manager.acquire(self.context, self.node.uuid, |
2180 | @@ -1462,61 +1517,84 @@ class RedfishManagementTestCase(db_base.DbTestCase): | |||
2181 | 1462 | task.driver.management.get_secure_boot_state, | 1517 | task.driver.management.get_secure_boot_state, |
2182 | 1463 | task) | 1518 | task) |
2183 | 1464 | 1519 | ||
2184 | 1520 | @mock.patch.object(redfish_mgmt.RedfishManagement, '_wait_for_secure_boot', | ||
2185 | 1521 | autospec=True) | ||
2186 | 1465 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) | 1522 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) |
2188 | 1466 | def test_set_secure_boot_state(self, mock_get_system): | 1523 | def test_set_secure_boot_state(self, mock_get_system, mock_wait): |
2189 | 1467 | fake_system = mock_get_system.return_value | 1524 | fake_system = mock_get_system.return_value |
2190 | 1468 | fake_system.secure_boot.enabled = False | 1525 | fake_system.secure_boot.enabled = False |
2191 | 1469 | fake_system.boot = {'mode': sushy.BOOT_SOURCE_MODE_UEFI} | 1526 | fake_system.boot = {'mode': sushy.BOOT_SOURCE_MODE_UEFI} |
2192 | 1470 | with task_manager.acquire(self.context, self.node.uuid, | 1527 | with task_manager.acquire(self.context, self.node.uuid, |
2194 | 1471 | shared=True) as task: | 1528 | shared=False) as task: |
2195 | 1472 | task.driver.management.set_secure_boot_state(task, True) | 1529 | task.driver.management.set_secure_boot_state(task, True) |
2196 | 1473 | fake_system.secure_boot.set_enabled.assert_called_once_with(True) | 1530 | fake_system.secure_boot.set_enabled.assert_called_once_with(True) |
2197 | 1531 | mock_wait.assert_called_once_with(task.driver.management, | ||
2198 | 1532 | task, fake_system.secure_boot, | ||
2199 | 1533 | True) | ||
2200 | 1474 | 1534 | ||
2201 | 1535 | @mock.patch.object(redfish_mgmt.RedfishManagement, '_wait_for_secure_boot', | ||
2202 | 1536 | autospec=True) | ||
2203 | 1475 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) | 1537 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) |
2205 | 1476 | def test_set_secure_boot_state_boot_mode_unknown(self, mock_get_system): | 1538 | def test_set_secure_boot_state_boot_mode_unknown(self, mock_get_system, |
2206 | 1539 | mock_wait): | ||
2207 | 1477 | fake_system = mock_get_system.return_value | 1540 | fake_system = mock_get_system.return_value |
2208 | 1478 | fake_system.secure_boot.enabled = False | 1541 | fake_system.secure_boot.enabled = False |
2209 | 1479 | fake_system.boot = {} | 1542 | fake_system.boot = {} |
2210 | 1480 | with task_manager.acquire(self.context, self.node.uuid, | 1543 | with task_manager.acquire(self.context, self.node.uuid, |
2212 | 1481 | shared=True) as task: | 1544 | shared=False) as task: |
2213 | 1482 | task.driver.management.set_secure_boot_state(task, True) | 1545 | task.driver.management.set_secure_boot_state(task, True) |
2214 | 1483 | fake_system.secure_boot.set_enabled.assert_called_once_with(True) | 1546 | fake_system.secure_boot.set_enabled.assert_called_once_with(True) |
2215 | 1547 | mock_wait.assert_called_once_with(task.driver.management, | ||
2216 | 1548 | task, fake_system.secure_boot, | ||
2217 | 1549 | True) | ||
2218 | 1484 | 1550 | ||
2219 | 1551 | @mock.patch.object(redfish_mgmt.RedfishManagement, '_wait_for_secure_boot', | ||
2220 | 1552 | autospec=True) | ||
2221 | 1485 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) | 1553 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) |
2223 | 1486 | def test_set_secure_boot_state_boot_mode_no_change(self, mock_get_system): | 1554 | def test_set_secure_boot_state_boot_mode_no_change(self, mock_get_system, |
2224 | 1555 | mock_wait): | ||
2225 | 1487 | fake_system = mock_get_system.return_value | 1556 | fake_system = mock_get_system.return_value |
2226 | 1488 | fake_system.secure_boot.enabled = False | 1557 | fake_system.secure_boot.enabled = False |
2227 | 1489 | fake_system.boot = {'mode': sushy.BOOT_SOURCE_MODE_BIOS} | 1558 | fake_system.boot = {'mode': sushy.BOOT_SOURCE_MODE_BIOS} |
2228 | 1490 | with task_manager.acquire(self.context, self.node.uuid, | 1559 | with task_manager.acquire(self.context, self.node.uuid, |
2230 | 1491 | shared=True) as task: | 1560 | shared=False) as task: |
2231 | 1492 | task.driver.management.set_secure_boot_state(task, False) | 1561 | task.driver.management.set_secure_boot_state(task, False) |
2233 | 1493 | self.assertFalse(fake_system.secure_boot.set_enabled.called) | 1562 | fake_system.secure_boot.set_enabled.assert_not_called() |
2234 | 1563 | mock_wait.assert_not_called() | ||
2235 | 1494 | 1564 | ||
2236 | 1565 | @mock.patch.object(redfish_mgmt.RedfishManagement, '_wait_for_secure_boot', | ||
2237 | 1566 | autospec=True) | ||
2238 | 1495 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) | 1567 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) |
2240 | 1496 | def test_set_secure_boot_state_boot_mode_incorrect(self, mock_get_system): | 1568 | def test_set_secure_boot_state_boot_mode_incorrect(self, mock_get_system, |
2241 | 1569 | mock_wait): | ||
2242 | 1497 | fake_system = mock_get_system.return_value | 1570 | fake_system = mock_get_system.return_value |
2243 | 1498 | fake_system.secure_boot.enabled = False | 1571 | fake_system.secure_boot.enabled = False |
2244 | 1499 | fake_system.boot = {'mode': sushy.BOOT_SOURCE_MODE_BIOS} | 1572 | fake_system.boot = {'mode': sushy.BOOT_SOURCE_MODE_BIOS} |
2245 | 1500 | with task_manager.acquire(self.context, self.node.uuid, | 1573 | with task_manager.acquire(self.context, self.node.uuid, |
2247 | 1501 | shared=True) as task: | 1574 | shared=False) as task: |
2248 | 1502 | self.assertRaisesRegex( | 1575 | self.assertRaisesRegex( |
2249 | 1503 | exception.RedfishError, 'requires UEFI', | 1576 | exception.RedfishError, 'requires UEFI', |
2250 | 1504 | task.driver.management.set_secure_boot_state, task, True) | 1577 | task.driver.management.set_secure_boot_state, task, True) |
2252 | 1505 | self.assertFalse(fake_system.secure_boot.set_enabled.called) | 1578 | fake_system.secure_boot.set_enabled.assert_not_called() |
2253 | 1579 | mock_wait.assert_not_called() | ||
2254 | 1506 | 1580 | ||
2255 | 1581 | @mock.patch.object(redfish_mgmt.RedfishManagement, '_wait_for_secure_boot', | ||
2256 | 1582 | autospec=True) | ||
2257 | 1507 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) | 1583 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) |
2259 | 1508 | def test_set_secure_boot_state_boot_mode_fails(self, mock_get_system): | 1584 | def test_set_secure_boot_state_boot_mode_fails(self, mock_get_system, |
2260 | 1585 | mock_wait): | ||
2261 | 1509 | fake_system = mock_get_system.return_value | 1586 | fake_system = mock_get_system.return_value |
2262 | 1510 | fake_system.secure_boot.enabled = False | 1587 | fake_system.secure_boot.enabled = False |
2263 | 1511 | fake_system.secure_boot.set_enabled.side_effect = \ | 1588 | fake_system.secure_boot.set_enabled.side_effect = \ |
2264 | 1512 | sushy.exceptions.SushyError | 1589 | sushy.exceptions.SushyError |
2265 | 1513 | fake_system.boot = {'mode': sushy.BOOT_SOURCE_MODE_UEFI} | 1590 | fake_system.boot = {'mode': sushy.BOOT_SOURCE_MODE_UEFI} |
2266 | 1514 | with task_manager.acquire(self.context, self.node.uuid, | 1591 | with task_manager.acquire(self.context, self.node.uuid, |
2268 | 1515 | shared=True) as task: | 1592 | shared=False) as task: |
2269 | 1516 | self.assertRaisesRegex( | 1593 | self.assertRaisesRegex( |
2270 | 1517 | exception.RedfishError, 'Failed to set secure boot', | 1594 | exception.RedfishError, 'Failed to set secure boot', |
2271 | 1518 | task.driver.management.set_secure_boot_state, task, True) | 1595 | task.driver.management.set_secure_boot_state, task, True) |
2272 | 1519 | fake_system.secure_boot.set_enabled.assert_called_once_with(True) | 1596 | fake_system.secure_boot.set_enabled.assert_called_once_with(True) |
2273 | 1597 | mock_wait.assert_not_called() | ||
2274 | 1520 | 1598 | ||
2275 | 1521 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) | 1599 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) |
2276 | 1522 | def test_set_secure_boot_state_not_implemented(self, mock_get_system): | 1600 | def test_set_secure_boot_state_not_implemented(self, mock_get_system): |
2277 | @@ -1528,11 +1606,76 @@ class RedfishManagementTestCase(db_base.DbTestCase): | |||
2278 | 1528 | 1606 | ||
2279 | 1529 | mock_get_system.return_value = NoSecureBoot() | 1607 | mock_get_system.return_value = NoSecureBoot() |
2280 | 1530 | with task_manager.acquire(self.context, self.node.uuid, | 1608 | with task_manager.acquire(self.context, self.node.uuid, |
2282 | 1531 | shared=True) as task: | 1609 | shared=False) as task: |
2283 | 1532 | self.assertRaises(exception.UnsupportedDriverExtension, | 1610 | self.assertRaises(exception.UnsupportedDriverExtension, |
2284 | 1533 | task.driver.management.set_secure_boot_state, | 1611 | task.driver.management.set_secure_boot_state, |
2285 | 1534 | task, True) | 1612 | task, True) |
2286 | 1535 | 1613 | ||
2287 | 1614 | @mock.patch.object(manager_utils, 'node_power_action', autospec=True) | ||
2288 | 1615 | def test_wait_for_secure_boot_immediate(self, mock_power): | ||
2289 | 1616 | fake_sb = mock.Mock(spec=['enabled', 'refresh'], enabled=True) | ||
2290 | 1617 | with task_manager.acquire(self.context, self.node.uuid, | ||
2291 | 1618 | shared=False) as task: | ||
2292 | 1619 | task.driver.management._wait_for_secure_boot(task, fake_sb, True) | ||
2293 | 1620 | fake_sb.refresh.assert_called_once_with(force=True) | ||
2294 | 1621 | mock_power.assert_not_called() | ||
2295 | 1622 | |||
2296 | 1623 | @mock.patch('time.sleep', lambda _: None) | ||
2297 | 1624 | @mock.patch.object(redfish_power.RedfishPower, 'get_power_state', | ||
2298 | 1625 | autospec=True) | ||
2299 | 1626 | @mock.patch.object(manager_utils, 'node_power_action', autospec=True) | ||
2300 | 1627 | def test_wait_for_secure_boot(self, mock_power, mock_get_power): | ||
2301 | 1628 | attempts = 3 | ||
2302 | 1629 | |||
2303 | 1630 | def side_effect(force): | ||
2304 | 1631 | nonlocal attempts | ||
2305 | 1632 | attempts -= 1 | ||
2306 | 1633 | if attempts <= 0: | ||
2307 | 1634 | fake_sb.enabled = True | ||
2308 | 1635 | |||
2309 | 1636 | fake_sb = mock.Mock(spec=['enabled', 'refresh'], enabled=False) | ||
2310 | 1637 | fake_sb.refresh.side_effect = side_effect | ||
2311 | 1638 | with task_manager.acquire(self.context, self.node.uuid, | ||
2312 | 1639 | shared=False) as task: | ||
2313 | 1640 | task.driver.management._wait_for_secure_boot(task, fake_sb, True) | ||
2314 | 1641 | fake_sb.refresh.assert_called_with(force=True) | ||
2315 | 1642 | self.assertEqual(3, fake_sb.refresh.call_count) | ||
2316 | 1643 | mock_power.assert_has_calls([ | ||
2317 | 1644 | mock.call(task, states.REBOOT), | ||
2318 | 1645 | mock.call(task, mock_get_power.return_value), | ||
2319 | 1646 | ]) | ||
2320 | 1647 | |||
2321 | 1648 | @mock.patch.object(redfish_mgmt, 'BOOT_MODE_CONFIG_INTERVAL', 0.1) | ||
2322 | 1649 | @mock.patch.object(redfish_power.RedfishPower, 'get_power_state', | ||
2323 | 1650 | autospec=True) | ||
2324 | 1651 | @mock.patch.object(manager_utils, 'node_power_action', autospec=True) | ||
2325 | 1652 | def test_wait_for_secure_boot_timeout(self, mock_power, mock_get_power): | ||
2326 | 1653 | CONF.set_override('boot_mode_config_timeout', 1, group='redfish') | ||
2327 | 1654 | fake_sb = mock.Mock(spec=['enabled', 'refresh'], enabled=False) | ||
2328 | 1655 | with task_manager.acquire(self.context, self.node.uuid, | ||
2329 | 1656 | shared=False) as task: | ||
2330 | 1657 | self.assertRaisesRegex( | ||
2331 | 1658 | exception.RedfishError, 'Timeout reached', | ||
2332 | 1659 | task.driver.management._wait_for_secure_boot, | ||
2333 | 1660 | task, fake_sb, True) | ||
2334 | 1661 | fake_sb.refresh.assert_called_with(force=True) | ||
2335 | 1662 | mock_power.assert_called_once_with(task, states.REBOOT) | ||
2336 | 1663 | |||
2337 | 1664 | @mock.patch.object(redfish_power.RedfishPower, 'get_power_state', | ||
2338 | 1665 | autospec=True) | ||
2339 | 1666 | @mock.patch.object(manager_utils, 'node_power_action', autospec=True) | ||
2340 | 1667 | def test_wait_for_secure_boot_no_wait(self, mock_power, mock_get_power): | ||
2341 | 1668 | CONF.set_override('boot_mode_config_timeout', 0, group='redfish') | ||
2342 | 1669 | fake_sb = mock.Mock(spec=['enabled', 'refresh'], enabled=False) | ||
2343 | 1670 | with task_manager.acquire(self.context, self.node.uuid, | ||
2344 | 1671 | shared=False) as task: | ||
2345 | 1672 | task.driver.management._wait_for_secure_boot(task, fake_sb, True) | ||
2346 | 1673 | fake_sb.refresh.assert_called_once_with(force=True) | ||
2347 | 1674 | mock_power.assert_has_calls([ | ||
2348 | 1675 | mock.call(task, states.REBOOT), | ||
2349 | 1676 | mock.call(task, mock_get_power.return_value), | ||
2350 | 1677 | ]) | ||
2351 | 1678 | |||
2352 | 1536 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) | 1679 | @mock.patch.object(redfish_utils, 'get_system', autospec=True) |
2353 | 1537 | def test_reset_secure_boot_to_default(self, mock_get_system): | 1680 | def test_reset_secure_boot_to_default(self, mock_get_system): |
2354 | 1538 | with task_manager.acquire(self.context, self.node.uuid, | 1681 | with task_manager.acquire(self.context, self.node.uuid, |
2355 | diff --git a/ironic/tests/unit/drivers/modules/redfish/test_raid.py b/ironic/tests/unit/drivers/modules/redfish/test_raid.py | |||
2356 | index 843be73..e651d8b 100644 | |||
2357 | --- a/ironic/tests/unit/drivers/modules/redfish/test_raid.py | |||
2358 | +++ b/ironic/tests/unit/drivers/modules/redfish/test_raid.py | |||
2359 | @@ -406,7 +406,8 @@ class RedfishRAIDTestCase(db_base.DbTestCase): | |||
2360 | 406 | task.node, reboot=True, skip_current_step=True, polling=True) | 406 | task.node, reboot=True, skip_current_step=True, polling=True) |
2361 | 407 | mock_get_async_step_return_state.assert_called_once_with( | 407 | mock_get_async_step_return_state.assert_called_once_with( |
2362 | 408 | task.node) | 408 | task.node) |
2364 | 409 | mock_node_power_action.assert_called_once_with(task, states.REBOOT) | 409 | mock_node_power_action.assert_called_once_with( |
2365 | 410 | task, states.REBOOT, None) | ||
2366 | 410 | mock_build_agent_options.assert_called_once_with(task.node) | 411 | mock_build_agent_options.assert_called_once_with(task.node) |
2367 | 411 | self.assertEqual(mock_prepare_ramdisk.call_count, 1) | 412 | self.assertEqual(mock_prepare_ramdisk.call_count, 1) |
2368 | 412 | # Async operation, raid_config shouldn't be updated yet | 413 | # Async operation, raid_config shouldn't be updated yet |
2369 | @@ -1123,7 +1124,8 @@ class RedfishRAIDTestCase(db_base.DbTestCase): | |||
2370 | 1123 | task.node, reboot=True, skip_current_step=True, polling=True) | 1124 | task.node, reboot=True, skip_current_step=True, polling=True) |
2371 | 1124 | mock_get_async_step_return_state.assert_called_once_with( | 1125 | mock_get_async_step_return_state.assert_called_once_with( |
2372 | 1125 | task.node) | 1126 | task.node) |
2374 | 1126 | mock_node_power_action.assert_called_once_with(task, states.REBOOT) | 1127 | mock_node_power_action.assert_called_once_with( |
2375 | 1128 | task, states.REBOOT, None) | ||
2376 | 1127 | mock_build_agent_options.assert_called_once_with(task.node) | 1129 | mock_build_agent_options.assert_called_once_with(task.node) |
2377 | 1128 | self.assertEqual(mock_prepare_ramdisk.call_count, 1) | 1130 | self.assertEqual(mock_prepare_ramdisk.call_count, 1) |
2378 | 1129 | self.assertEqual( | 1131 | self.assertEqual( |
2379 | diff --git a/ironic/tests/unit/drivers/modules/redfish/test_utils.py b/ironic/tests/unit/drivers/modules/redfish/test_utils.py | |||
2380 | index 01b7089..5e91c15 100644 | |||
2381 | --- a/ironic/tests/unit/drivers/modules/redfish/test_utils.py | |||
2382 | +++ b/ironic/tests/unit/drivers/modules/redfish/test_utils.py | |||
2383 | @@ -168,6 +168,14 @@ class RedfishUtilsTestCase(db_base.DbTestCase): | |||
2384 | 168 | response = redfish_utils.parse_driver_info(self.node) | 168 | response = redfish_utils.parse_driver_info(self.node) |
2385 | 169 | self.assertEqual(self.parsed_driver_info, response) | 169 | self.assertEqual(self.parsed_driver_info, response) |
2386 | 170 | 170 | ||
2387 | 171 | def test_parse_driver_info_default_scheme_ipv6_brackets_added(self): | ||
2388 | 172 | test_redfish_address = '2001:DB8::1' | ||
2389 | 173 | self.node.driver_info['redfish_address'] = test_redfish_address | ||
2390 | 174 | response = redfish_utils.parse_driver_info(self.node) | ||
2391 | 175 | self.parsed_driver_info['address'] = ("https://[%s]" | ||
2392 | 176 | % test_redfish_address) | ||
2393 | 177 | self.assertEqual(self.parsed_driver_info, response) | ||
2394 | 178 | |||
2395 | 171 | def test_get_task_monitor(self): | 179 | def test_get_task_monitor(self): |
2396 | 172 | redfish_utils._get_connection = mock.Mock() | 180 | redfish_utils._get_connection = mock.Mock() |
2397 | 173 | fake_monitor = mock.Mock() | 181 | fake_monitor = mock.Mock() |
2398 | diff --git a/ironic/tests/unit/drivers/modules/test_agent_base.py b/ironic/tests/unit/drivers/modules/test_agent_base.py | |||
2399 | index c589d52..6d285cb 100644 | |||
2400 | --- a/ironic/tests/unit/drivers/modules/test_agent_base.py | |||
2401 | +++ b/ironic/tests/unit/drivers/modules/test_agent_base.py | |||
2402 | @@ -1593,7 +1593,7 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): | |||
2403 | 1593 | agent_base._post_step_reboot(task, 'clean') | 1593 | agent_base._post_step_reboot(task, 'clean') |
2404 | 1594 | self.assertTrue(mock_build_opt.called) | 1594 | self.assertTrue(mock_build_opt.called) |
2405 | 1595 | self.assertTrue(mock_prepare.called) | 1595 | self.assertTrue(mock_prepare.called) |
2407 | 1596 | mock_reboot.assert_called_once_with(task, states.REBOOT) | 1596 | mock_reboot.assert_called_once_with(task, states.REBOOT, None) |
2408 | 1597 | self.assertTrue(task.node.driver_internal_info['cleaning_reboot']) | 1597 | self.assertTrue(task.node.driver_internal_info['cleaning_reboot']) |
2409 | 1598 | self.assertNotIn('agent_secret_token', | 1598 | self.assertNotIn('agent_secret_token', |
2410 | 1599 | task.node.driver_internal_info) | 1599 | task.node.driver_internal_info) |
2411 | @@ -1612,7 +1612,7 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): | |||
2412 | 1612 | agent_base._post_step_reboot(task, 'deploy') | 1612 | agent_base._post_step_reboot(task, 'deploy') |
2413 | 1613 | self.assertTrue(mock_build_opt.called) | 1613 | self.assertTrue(mock_build_opt.called) |
2414 | 1614 | self.assertTrue(mock_prepare.called) | 1614 | self.assertTrue(mock_prepare.called) |
2416 | 1615 | mock_reboot.assert_called_once_with(task, states.REBOOT) | 1615 | mock_reboot.assert_called_once_with(task, states.REBOOT, None) |
2417 | 1616 | self.assertTrue( | 1616 | self.assertTrue( |
2418 | 1617 | task.node.driver_internal_info['deployment_reboot']) | 1617 | task.node.driver_internal_info['deployment_reboot']) |
2419 | 1618 | self.assertNotIn('agent_secret_token', | 1618 | self.assertNotIn('agent_secret_token', |
2420 | @@ -1633,7 +1633,7 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): | |||
2421 | 1633 | agent_base._post_step_reboot(task, 'clean') | 1633 | agent_base._post_step_reboot(task, 'clean') |
2422 | 1634 | self.assertTrue(mock_build_opt.called) | 1634 | self.assertTrue(mock_build_opt.called) |
2423 | 1635 | self.assertTrue(mock_prepare.called) | 1635 | self.assertTrue(mock_prepare.called) |
2425 | 1636 | mock_reboot.assert_called_once_with(task, states.REBOOT) | 1636 | mock_reboot.assert_called_once_with(task, states.REBOOT, None) |
2426 | 1637 | self.assertIn('agent_secret_token', | 1637 | self.assertIn('agent_secret_token', |
2427 | 1638 | task.node.driver_internal_info) | 1638 | task.node.driver_internal_info) |
2428 | 1639 | 1639 | ||
2429 | @@ -1649,7 +1649,7 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): | |||
2430 | 1649 | with task_manager.acquire(self.context, self.node['uuid'], | 1649 | with task_manager.acquire(self.context, self.node['uuid'], |
2431 | 1650 | shared=False) as task: | 1650 | shared=False) as task: |
2432 | 1651 | agent_base._post_step_reboot(task, 'clean') | 1651 | agent_base._post_step_reboot(task, 'clean') |
2434 | 1652 | mock_reboot.assert_called_once_with(task, states.REBOOT) | 1652 | mock_reboot.assert_called_once_with(task, states.REBOOT, None) |
2435 | 1653 | mock_handler.assert_called_once_with(task, mock.ANY, | 1653 | mock_handler.assert_called_once_with(task, mock.ANY, |
2436 | 1654 | traceback=True) | 1654 | traceback=True) |
2437 | 1655 | self.assertNotIn('cleaning_reboot', | 1655 | self.assertNotIn('cleaning_reboot', |
2438 | @@ -1667,7 +1667,7 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): | |||
2439 | 1667 | with task_manager.acquire(self.context, self.node['uuid'], | 1667 | with task_manager.acquire(self.context, self.node['uuid'], |
2440 | 1668 | shared=False) as task: | 1668 | shared=False) as task: |
2441 | 1669 | agent_base._post_step_reboot(task, 'deploy') | 1669 | agent_base._post_step_reboot(task, 'deploy') |
2443 | 1670 | mock_reboot.assert_called_once_with(task, states.REBOOT) | 1670 | mock_reboot.assert_called_once_with(task, states.REBOOT, None) |
2444 | 1671 | mock_handler.assert_called_once_with(task, mock.ANY, | 1671 | mock_handler.assert_called_once_with(task, mock.ANY, |
2445 | 1672 | traceback=True) | 1672 | traceback=True) |
2446 | 1673 | self.assertNotIn('deployment_reboot', | 1673 | self.assertNotIn('deployment_reboot', |
2447 | @@ -1686,7 +1686,7 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): | |||
2448 | 1686 | with task_manager.acquire(self.context, self.node['uuid'], | 1686 | with task_manager.acquire(self.context, self.node['uuid'], |
2449 | 1687 | shared=False) as task: | 1687 | shared=False) as task: |
2450 | 1688 | agent_base._post_step_reboot(task, 'service') | 1688 | agent_base._post_step_reboot(task, 'service') |
2452 | 1689 | mock_reboot.assert_called_once_with(task, states.REBOOT) | 1689 | mock_reboot.assert_called_once_with(task, states.REBOOT, None) |
2453 | 1690 | mock_handler.assert_called_once_with(task, mock.ANY, | 1690 | mock_handler.assert_called_once_with(task, mock.ANY, |
2454 | 1691 | traceback=True) | 1691 | traceback=True) |
2455 | 1692 | self.assertNotIn('servicing_reboot', | 1692 | self.assertNotIn('servicing_reboot', |
2456 | @@ -1829,7 +1829,7 @@ class ContinueCleaningTest(AgentDeployMixinBaseTest): | |||
2457 | 1829 | with task_manager.acquire(self.context, self.node['uuid'], | 1829 | with task_manager.acquire(self.context, self.node['uuid'], |
2458 | 1830 | shared=False) as task: | 1830 | shared=False) as task: |
2459 | 1831 | self.deploy.continue_cleaning(task) | 1831 | self.deploy.continue_cleaning(task) |
2461 | 1832 | reboot_mock.assert_called_once_with(task, states.REBOOT) | 1832 | reboot_mock.assert_called_once_with(task, states.REBOOT, None) |
2462 | 1833 | 1833 | ||
2463 | 1834 | @mock.patch.object(cleaning, 'continue_node_clean', autospec=True) | 1834 | @mock.patch.object(cleaning, 'continue_node_clean', autospec=True) |
2464 | 1835 | @mock.patch.object(agent_client.AgentClient, 'get_commands_status', | 1835 | @mock.patch.object(agent_client.AgentClient, 'get_commands_status', |
2465 | @@ -2147,7 +2147,7 @@ class ContinueServiceTest(AgentDeployMixinBaseTest): | |||
2466 | 2147 | with task_manager.acquire(self.context, self.node['uuid'], | 2147 | with task_manager.acquire(self.context, self.node['uuid'], |
2467 | 2148 | shared=False) as task: | 2148 | shared=False) as task: |
2468 | 2149 | self.deploy.continue_servicing(task) | 2149 | self.deploy.continue_servicing(task) |
2470 | 2150 | reboot_mock.assert_called_once_with(task, states.REBOOT) | 2150 | reboot_mock.assert_called_once_with(task, states.REBOOT, None) |
2471 | 2151 | 2151 | ||
2472 | 2152 | @mock.patch.object(servicing, 'continue_node_service', autospec=True) | 2152 | @mock.patch.object(servicing, 'continue_node_service', autospec=True) |
2473 | 2153 | @mock.patch.object(agent_client.AgentClient, 'get_commands_status', | 2153 | @mock.patch.object(agent_client.AgentClient, 'get_commands_status', |
2474 | diff --git a/ironic/tests/unit/drivers/modules/test_inspect_utils.py b/ironic/tests/unit/drivers/modules/test_inspect_utils.py | |||
2475 | index f6f5ab0..a7b2ac2 100644 | |||
2476 | --- a/ironic/tests/unit/drivers/modules/test_inspect_utils.py | |||
2477 | +++ b/ironic/tests/unit/drivers/modules/test_inspect_utils.py | |||
2478 | @@ -442,6 +442,12 @@ class GetBMCAddressesTestCase(db_base.DbTestCase): | |||
2479 | 442 | driver_info={'redfish_address': 'https://192.0.2.1/redfish'}) | 442 | driver_info={'redfish_address': 'https://192.0.2.1/redfish'}) |
2480 | 443 | self.assertEqual({'192.0.2.1'}, utils._get_bmc_addresses(node)) | 443 | self.assertEqual({'192.0.2.1'}, utils._get_bmc_addresses(node)) |
2481 | 444 | 444 | ||
2482 | 445 | def test_normal_ipv6_as_url(self): | ||
2483 | 446 | node = obj_utils.create_test_node( | ||
2484 | 447 | self.context, | ||
2485 | 448 | driver_info={'redfish_address': 'https://[2001:db8::42]/redfish'}) | ||
2486 | 449 | self.assertEqual({'2001:db8::42'}, utils._get_bmc_addresses(node)) | ||
2487 | 450 | |||
2488 | 445 | @mock.patch.object(socket, 'getaddrinfo', autospec=True) | 451 | @mock.patch.object(socket, 'getaddrinfo', autospec=True) |
2489 | 446 | def test_resolved_host(self, mock_getaddrinfo): | 452 | def test_resolved_host(self, mock_getaddrinfo): |
2490 | 447 | mock_getaddrinfo.return_value = [ | 453 | mock_getaddrinfo.return_value = [ |
2491 | @@ -474,6 +480,12 @@ class GetBMCAddressesTestCase(db_base.DbTestCase): | |||
2492 | 474 | mock_getaddrinfo.assert_called_once_with( | 480 | mock_getaddrinfo.assert_called_once_with( |
2493 | 475 | 'example.com', None, proto=socket.SOL_TCP) | 481 | 'example.com', None, proto=socket.SOL_TCP) |
2494 | 476 | 482 | ||
2495 | 483 | def test_redfish_bmc_address_ipv6_brackets_no_scheme(self): | ||
2496 | 484 | node = obj_utils.create_test_node( | ||
2497 | 485 | self.context, | ||
2498 | 486 | driver_info={'redfish_address': '[2001:db8::42]'}) | ||
2499 | 487 | self.assertEqual({'2001:db8::42'}, utils._get_bmc_addresses(node)) | ||
2500 | 488 | |||
2501 | 477 | 489 | ||
2502 | 478 | class LookupCacheTestCase(db_base.DbTestCase): | 490 | class LookupCacheTestCase(db_base.DbTestCase): |
2503 | 479 | 491 | ||
2504 | diff --git a/ironic/tests/unit/drivers/test_redfish.py b/ironic/tests/unit/drivers/test_redfish.py | |||
2505 | index b692c61..be34fbf 100644 | |||
2506 | --- a/ironic/tests/unit/drivers/test_redfish.py | |||
2507 | +++ b/ironic/tests/unit/drivers/test_redfish.py | |||
2508 | @@ -33,7 +33,8 @@ class RedfishHardwareTestCase(db_base.DbTestCase): | |||
2509 | 33 | enabled_boot_interfaces=['redfish-virtual-media'], | 33 | enabled_boot_interfaces=['redfish-virtual-media'], |
2510 | 34 | enabled_management_interfaces=['redfish'], | 34 | enabled_management_interfaces=['redfish'], |
2511 | 35 | enabled_inspect_interfaces=['redfish'], | 35 | enabled_inspect_interfaces=['redfish'], |
2513 | 36 | enabled_bios_interfaces=['redfish']) | 36 | enabled_bios_interfaces=['redfish'], |
2514 | 37 | enabled_firmware_interfaces=['redfish']) | ||
2515 | 37 | 38 | ||
2516 | 38 | def test_default_interfaces(self): | 39 | def test_default_interfaces(self): |
2517 | 39 | node = obj_utils.create_test_node(self.context, driver='redfish') | 40 | node = obj_utils.create_test_node(self.context, driver='redfish') |
2518 | diff --git a/releasenotes/notes/23.0-prelude-bobcat-ad7c24f666c22ebf.yaml b/releasenotes/notes/23.0-prelude-bobcat-ad7c24f666c22ebf.yaml | |||
2519 | 40 | new file mode 100644 | 41 | new file mode 100644 |
2520 | index 0000000..1c27277 | |||
2521 | --- /dev/null | |||
2522 | +++ b/releasenotes/notes/23.0-prelude-bobcat-ad7c24f666c22ebf.yaml | |||
2523 | @@ -0,0 +1,17 @@ | |||
2524 | 1 | --- | ||
2525 | 2 | prelude: | | ||
2526 | 3 | Ironic is proud to announce the release of 23.0, the capstone release of a | ||
2527 | 4 | six month OpenStack 2023.2 (Bobcat) cycle. | ||
2528 | 5 | |||
2529 | 6 | Our focus this cycle has been on improving the ability for operators to | ||
2530 | 7 | secure and service their Ironic nodes. There are also, as always, a myriad | ||
2531 | 8 | of quality of life fixes, including improvements to sqlite support, | ||
2532 | 9 | and graceful shutdown of conductors. | ||
2533 | 10 | |||
2534 | 11 | We hope the latest release of Ironic serves you well! | ||
2535 | 12 | upgrade: | | ||
2536 | 13 | Ironic 23.0 is part of the OpenStack 2023.2 (Bobcat) release. This a | ||
2537 | 14 | non-SLURP release, meaning users of a 2023.1 (Antelope) cycle Ironic release | ||
2538 | 15 | can upgrade directly to the release accompanying 2024.1 (Caracal) when | ||
2539 | 16 | available. For more information, please visit | ||
2540 | 17 | `Release Cadence Adjustment <https://governance.openstack.org/tc/resolutions/20220210-release-cadence-adjustment.html?_ga=2.126966605.1175089434.1694620440-1981816456.1685478379>`_. | ||
2541 | diff --git a/releasenotes/notes/add-hold-states-7be5804d6f3a119a.yaml b/releasenotes/notes/add-hold-states-7be5804d6f3a119a.yaml | |||
2542 | index 5eec68b..521e581 100644 | |||
2543 | --- a/releasenotes/notes/add-hold-states-7be5804d6f3a119a.yaml | |||
2544 | +++ b/releasenotes/notes/add-hold-states-7be5804d6f3a119a.yaml | |||
2545 | @@ -11,7 +11,7 @@ features: | |||
2546 | 11 | to start over. | 11 | to start over. |
2547 | 12 | - | | 12 | - | |
2548 | 13 | Adds the ability to send an ``unhold`` provision state verb utilizing | 13 | Adds the ability to send an ``unhold`` provision state verb utilizing |
2550 | 14 | API version *1.84*. | 14 | API version *1.85*. |
2551 | 15 | other: | 15 | other: |
2552 | 16 | - | | 16 | - | |
2553 | 17 | Fixes the generated state machine diagram and updates it to match the | 17 | Fixes the generated state machine diagram and updates it to match the |
2554 | diff --git a/releasenotes/notes/bug-2036455-edd0e97335579684.yaml b/releasenotes/notes/bug-2036455-edd0e97335579684.yaml | |||
2555 | 18 | new file mode 100644 | 18 | new file mode 100644 |
2556 | index 0000000..72a000b | |||
2557 | --- /dev/null | |||
2558 | +++ b/releasenotes/notes/bug-2036455-edd0e97335579684.yaml | |||
2559 | @@ -0,0 +1,6 @@ | |||
2560 | 1 | --- | ||
2561 | 2 | fixes: | ||
2562 | 3 | - | | ||
2563 | 4 | Fixes an issue where inspection would fail if an IPv6 address wrapped in | ||
2564 | 5 | brackets is used for the redfish BMC address. See bug: | ||
2565 | 6 | `2036455 <https://bugs.launchpad.net/ironic/+bug/2036455>`_. | ||
2566 | diff --git a/releasenotes/notes/firmware-interface-8ad6f91aa1f746a0.yaml b/releasenotes/notes/firmware-interface-8ad6f91aa1f746a0.yaml | |||
2567 | 0 | new file mode 100644 | 7 | new file mode 100644 |
2568 | index 0000000..06964b1 | |||
2569 | --- /dev/null | |||
2570 | +++ b/releasenotes/notes/firmware-interface-8ad6f91aa1f746a0.yaml | |||
2571 | @@ -0,0 +1,31 @@ | |||
2572 | 1 | --- | ||
2573 | 2 | features: | ||
2574 | 3 | - | | ||
2575 | 4 | Adds Firmware Interface support to ironic, we would like to receive | ||
2576 | 5 | feedback since this is a new feature we introduced and we as a developer | ||
2577 | 6 | community have limited hardware access, reach out to us in case of any | ||
2578 | 7 | unexpected behavior. | ||
2579 | 8 | |||
2580 | 9 | - Adds version 1.86 of the Bare Metal API, which includes: | ||
2581 | 10 | |||
2582 | 11 | * List all firmware components of a node via the | ||
2583 | 12 | ``GET /v1/nodes/{node_ident}/firmware`` API. | ||
2584 | 13 | |||
2585 | 14 | * The ``firmware_interface`` field of the node resource. A firmware | ||
2586 | 15 | interface can be set when creating or updating a node. | ||
2587 | 16 | |||
2588 | 17 | * The ``default_firmware_interface`` and ``enabled_firmware_interface`` | ||
2589 | 18 | fields of the driver resource. | ||
2590 | 19 | |||
2591 | 20 | - Adds new configuration options for the firmware interface feature: | ||
2592 | 21 | |||
2593 | 22 | * Firmware interfaces are enabled via | ||
2594 | 23 | ``[DEFAULT]/enabled_firmware_interfaces``. A default firmware | ||
2595 | 24 | interface to use when creating or updating nodes can be specified with | ||
2596 | 25 | ``[DEFAULT]/default_firmware_interface``. | ||
2597 | 26 | |||
2598 | 27 | - Available interfaces: ``redfish``, ``no-firmware`` and ``fake``. | ||
2599 | 28 | |||
2600 | 29 | - Support to update firmware of BIOS and BMC via ``update`` step, can be | ||
2601 | 30 | done via clean or deploy steps, the node should be using the | ||
2602 | 31 | ``redfish`` driver and set the ``firmware_interface``. | ||
2603 | diff --git a/releasenotes/notes/remove-400a563030224c4f.yaml b/releasenotes/notes/remove-400a563030224c4f.yaml | |||
2604 | 0 | new file mode 100644 | 32 | new file mode 100644 |
2605 | index 0000000..1ce686a | |||
2606 | --- /dev/null | |||
2607 | +++ b/releasenotes/notes/remove-400a563030224c4f.yaml | |||
2608 | @@ -0,0 +1,9 @@ | |||
2609 | 1 | --- | ||
2610 | 2 | other: | ||
2611 | 3 | - | | ||
2612 | 4 | While investigating `bug 2033430 <https://bugs.launchpad.net/ironic/+bug/2033430>`_ | ||
2613 | 5 | we discovered we were emitting DHCP option 210 *only* with OVN, and never | ||
2614 | 6 | emitted it with dnsmasq because it was not being set previously. Our | ||
2615 | 7 | internal notes also indicated this was for PXELinux support, but was | ||
2616 | 8 | never actually needed. As it was excess, and redundant configuration | ||
2617 | 9 | being provided to Neutron, it has been removed. | ||
2618 | diff --git a/releasenotes/notes/uefi-and-secureboot-waits-a783215327164e2c.yaml b/releasenotes/notes/uefi-and-secureboot-waits-a783215327164e2c.yaml | |||
2619 | 0 | new file mode 100644 | 10 | new file mode 100644 |
2620 | index 0000000..44ae3c6 | |||
2621 | --- /dev/null | |||
2622 | +++ b/releasenotes/notes/uefi-and-secureboot-waits-a783215327164e2c.yaml | |||
2623 | @@ -0,0 +1,20 @@ | |||
2624 | 1 | --- | ||
2625 | 2 | fixes: | ||
2626 | 3 | - | | ||
2627 | 4 | While updating boot mode or secure boot state in the Redfish driver, | ||
2628 | 5 | the node is now rebooted if the change is not detected on the System | ||
2629 | 6 | resource refresh. Ironic then waits up to | ||
2630 | 7 | ``[redfish]boot_mode_config_timeout`` seconds until the change is applied. | ||
2631 | 8 | upgrade: | ||
2632 | 9 | - | | ||
2633 | 10 | Changing the boot mode or the secure boot state via the direct API | ||
2634 | 11 | (``/v1/nodes/{node_ident}/states/boot_mode`` and | ||
2635 | 12 | ``/v1/nodes/{node_ident}/states/secure_boot`` accordingly) may now | ||
2636 | 13 | result in a reboot. This happens when the change cannot be applied | ||
2637 | 14 | immediately. Previously, the change would be applied whenever the next | ||
2638 | 15 | reboot happens for any unrelated reason, causing inconsistent behavior. | ||
2639 | 16 | issues: | ||
2640 | 17 | - | | ||
2641 | 18 | When boot mode needs to be changed during provisioning, an additional | ||
2642 | 19 | reboot may happen on certain hardware. This is to ensure consistent | ||
2643 | 20 | behavior when any boot setting change results in a separate internal job. | ||
2644 | diff --git a/releasenotes/source/2023.1.rst b/releasenotes/source/2023.1.rst | |||
2645 | index d123847..dc91336 100644 | |||
2646 | --- a/releasenotes/source/2023.1.rst | |||
2647 | +++ b/releasenotes/source/2023.1.rst | |||
2648 | @@ -1,6 +1,6 @@ | |||
2652 | 1 | =========================== | 1 | ============================================= |
2653 | 2 | 2023.1 Series Release Notes | 2 | 2023.1 Series (21.2.0 - 21.4.x) Release Notes |
2654 | 3 | =========================== | 3 | ============================================= |
2655 | 4 | 4 | ||
2656 | 5 | .. release-notes:: | 5 | .. release-notes:: |
2657 | 6 | :branch: stable/2023.1 | 6 | :branch: stable/2023.1 |
2658 | diff --git a/setup.cfg b/setup.cfg | |||
2659 | index c14a312..9f31ef9 100644 | |||
2660 | --- a/setup.cfg | |||
2661 | +++ b/setup.cfg | |||
2662 | @@ -86,6 +86,7 @@ ironic.hardware.interfaces.deploy = | |||
2663 | 86 | ironic.hardware.interfaces.firmware = | 86 | ironic.hardware.interfaces.firmware = |
2664 | 87 | fake = ironic.drivers.modules.fake:FakeFirmware | 87 | fake = ironic.drivers.modules.fake:FakeFirmware |
2665 | 88 | no-firmware = ironic.drivers.modules.noop:NoFirmware | 88 | no-firmware = ironic.drivers.modules.noop:NoFirmware |
2666 | 89 | redfish = ironic.drivers.modules.redfish.firmware:RedfishFirmware | ||
2667 | 89 | ironic.hardware.interfaces.inspect = | 90 | ironic.hardware.interfaces.inspect = |
2668 | 90 | agent = ironic.drivers.modules.inspector:AgentInspect | 91 | agent = ironic.drivers.modules.inspector:AgentInspect |
2669 | 91 | fake = ironic.drivers.modules.fake:FakeInspect | 92 | fake = ironic.drivers.modules.fake:FakeInspect |
2670 | diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml | |||
2671 | index 120a06e..ea1446a 100644 | |||
2672 | --- a/zuul.d/project.yaml | |||
2673 | +++ b/zuul.d/project.yaml | |||
2674 | @@ -60,7 +60,7 @@ | |||
2675 | 60 | voting: false | 60 | voting: false |
2676 | 61 | - ironic-inspector-tempest-rbac-scope-enforced: | 61 | - ironic-inspector-tempest-rbac-scope-enforced: |
2677 | 62 | voting: false | 62 | voting: false |
2679 | 63 | - bifrost-integration-tinyipa-ubuntu-focal: | 63 | - bifrost-integration-tinyipa-ubuntu-jammy: |
2680 | 64 | voting: false | 64 | voting: false |
2681 | 65 | - bifrost-integration-redfish-vmedia-uefi-centos-9: | 65 | - bifrost-integration-redfish-vmedia-uefi-centos-9: |
2682 | 66 | voting: false | 66 | voting: false |
Superseded by https:/ /code.launchpad .net/~corey. bryant/ ubuntu/ +source/ ironic/ +git/ironic/ +merge/ 452823