Add support for retired{_reason} fields.

This change adds support to set and unset the 'retired'
and 'retired_reason' fields. It also extends the 'list'
command with an additional '--retired' parameter to
list only nodes which have the retired property.

Story: #2005425
Task: #38143
Depends-On: https://review.opendev.org/703981
Change-Id: I5fe379c4ff439b3a083ae819ce5b4bdbddd22b3a
This commit is contained in:
Arne Wiebalck 2020-01-17 13:51:37 +01:00
parent a64370701e
commit 81eebfc404
7 changed files with 175 additions and 11 deletions

View File

@ -40,7 +40,7 @@ from ironicclient import exc
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
# for full details.
DEFAULT_VER = '1.9'
LAST_KNOWN_API_VERSION = 60
LAST_KNOWN_API_VERSION = 61
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
LOG = logging.getLogger(__name__)

View File

@ -591,6 +591,12 @@ class ListBaremetalNode(command.Lister):
default=None,
help=_("Limit list to nodes not in maintenance mode"),
)
parser.add_argument(
'--retired',
dest='retired',
action='store_true',
default=None,
help=_("Limit list to retired nodes."))
parser.add_argument(
'--fault',
dest='fault',
@ -683,7 +689,7 @@ class ListBaremetalNode(command.Lister):
params['associated'] = True
if parsed_args.unassociated:
params['associated'] = False
for field in ['maintenance', 'fault', 'conductor_group']:
for field in ['maintenance', 'fault', 'conductor_group', 'retired']:
if getattr(parsed_args, field) is not None:
params[field] = getattr(parsed_args, field)
for field in ['provision_state', 'driver', 'resource_class',
@ -1160,6 +1166,16 @@ class SetBaremetalNode(command.Command):
metavar='<protected_reason>',
help=_('Set the reason of marking the node as protected'),
)
parser.add_argument(
'--retired',
action='store_true',
help=_('Mark the node as retired'),
)
parser.add_argument(
'--retired-reason',
metavar='<retired_reason>',
help=_('Set the reason of marking the node as retired'),
)
parser.add_argument(
'--target-raid-config',
metavar='<target_raid_config>',
@ -1227,7 +1243,7 @@ class SetBaremetalNode(command.Command):
for field in ['automated_clean', 'instance_uuid', 'name',
'chassis_uuid', 'driver', 'resource_class',
'conductor_group', 'protected', 'protected_reason',
'owner', 'description']:
'retired', 'retired_reason', 'owner', 'description']:
value = getattr(parsed_args, field)
if value:
properties.extend(utils.args_array_to_patch(
@ -1500,6 +1516,17 @@ class UnsetBaremetalNode(command.Command):
help=_('Unset the protected reason (gets unset automatically when '
'protected is unset)'),
)
parser.add_argument(
"--retired",
action="store_true",
help=_('Unset the retired flag on the node'),
)
parser.add_argument(
"--retired-reason",
action="store_true",
help=_('Unset the retired reason (gets unset automatically when '
'retired is unset)'),
)
parser.add_argument(
"--owner",
action="store_true",
@ -1532,8 +1559,8 @@ class UnsetBaremetalNode(command.Command):
'management_interface', 'network_interface',
'power_interface', 'raid_interface', 'rescue_interface',
'storage_interface', 'vendor_interface',
'protected', 'protected_reason', 'owner',
'description']:
'protected', 'protected_reason', 'retired',
'retired_reason', 'owner', 'description']:
if getattr(parsed_args, field):
properties.extend(utils.args_array_to_patch('remove', [field]))

View File

@ -658,6 +658,8 @@ class TestBaremetalList(TestBaremetal):
'Rescue Interface',
'Reservation',
'Resource Class',
'Retired',
'Retired Reason',
'Storage Interface',
'Target Power State',
'Target Provision State',
@ -2454,6 +2456,50 @@ class TestBaremetalSet(TestBaremetal):
reset_interfaces=None,
)
def test_baremetal_set_retired(self):
arglist = [
'node_uuid',
'--retired'
]
verifylist = [
('node', 'node_uuid'),
('retired', True)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.baremetal_mock.node.update.assert_called_once_with(
'node_uuid',
[{'path': '/retired', 'value': 'True', 'op': 'add'}],
reset_interfaces=None,
)
def test_baremetal_set_retired_with_reason(self):
arglist = [
'node_uuid',
'--retired',
'--retired-reason', 'out of warranty!'
]
verifylist = [
('node', 'node_uuid'),
('retired', True),
('retired_reason', 'out of warranty!')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.baremetal_mock.node.update.assert_called_once_with(
'node_uuid',
[{'path': '/retired', 'value': 'True', 'op': 'add'},
{'path': '/retired_reason', 'value': 'out of warranty!',
'op': 'add'}],
reset_interfaces=None,
)
def test_baremetal_set_extra(self):
arglist = [
'node_uuid',
@ -3007,6 +3053,44 @@ class TestBaremetalUnset(TestBaremetal):
[{'path': '/protected_reason', 'op': 'remove'}]
)
def test_baremetal_unset_retired(self):
arglist = [
'node_uuid',
'--retired',
]
verifylist = [
('node', 'node_uuid'),
('retired', True)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.baremetal_mock.node.update.assert_called_once_with(
'node_uuid',
[{'path': '/retired', 'op': 'remove'}]
)
def test_baremetal_unset_retired_reason(self):
arglist = [
'node_uuid',
'--retired-reason',
]
verifylist = [
('node', 'node_uuid'),
('retired_reason', True)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.baremetal_mock.node.update.assert_called_once_with(
'node_uuid',
[{'path': '/retired_reason', 'op': 'remove'}]
)
def test_baremetal_unset_extra(self):
arglist = [
'node_uuid',

View File

@ -48,7 +48,8 @@ NODE2 = {'uuid': '66666666-7777-8888-9999-111111111111',
'properties': {'num_cpu': 4},
'resource_class': 'bar',
'extra': {},
'owner': '33333333-2222-1111-0000-111111111111'}
'owner': '33333333-2222-1111-0000-111111111111',
'retired': True}
PORT = {'uuid': '11111111-2222-3333-4444-555555555555',
'node_uuid': '66666666-7777-8888-9999-000000000000',
'address': 'AA:AA:AA:AA:AA:AA',
@ -170,6 +171,13 @@ fake_responses = {
{"nodes": [NODE2]},
)
},
'/v1/nodes/?retired=True':
{
'GET': (
{},
{"nodes": [NODE2]},
)
},
'/v1/nodes/?associated=True&maintenance=True':
{
'GET': (
@ -177,6 +185,13 @@ fake_responses = {
{"nodes": [NODE2]},
)
},
'/v1/nodes/?associated=True&retired=True':
{
'GET': (
{},
{"nodes": [NODE2]},
)
},
'/v1/nodes/?provision_state=available':
{
'GET': (
@ -798,6 +813,15 @@ class NodeManagerTest(testtools.TestCase):
self.assertThat(nodes, HasLength(1))
self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid'))
def test_node_list_retired(self):
nodes = self.mgr.list(retired=True)
expect = [
('GET', '/v1/nodes/?retired=True', {}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertThat(nodes, HasLength(1))
self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid'))
def test_node_list_provision_state(self):
nodes = self.mgr.list(provision_state="available")
expect = [
@ -875,6 +899,15 @@ class NodeManagerTest(testtools.TestCase):
self.assertThat(nodes, HasLength(1))
self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid'))
def test_node_list_associated_and_retired(self):
nodes = self.mgr.list(associated=True, retired=True)
expect = [
('GET', '/v1/nodes/?associated=True&retired=True', {}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertThat(nodes, HasLength(1))
self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid'))
def test_node_list_with_conductor(self):
nodes = self.mgr.list(conductor='fake-conductor')
expect = [

View File

@ -56,11 +56,12 @@ class NodeManager(base.CreateManager):
'automated_clean']
_resource_name = 'nodes'
def list(self, associated=None, maintenance=None, marker=None, limit=None,
detail=False, sort_key=None, sort_dir=None, fields=None,
provision_state=None, driver=None, resource_class=None,
chassis=None, fault=None, os_ironic_api_version=None,
conductor_group=None, conductor=None, owner=None):
def list(self, associated=None, maintenance=None, marker=None,
limit=None, detail=False, sort_key=None, sort_dir=None,
fields=None, provision_state=None, driver=None,
resource_class=None, chassis=None, fault=None,
os_ironic_api_version=None, conductor_group=None,
conductor=None, owner=None, retired=None):
"""Retrieve a list of nodes.
:param associated: Optional. Either a Boolean or a string
@ -72,6 +73,9 @@ class NodeManager(base.CreateManager):
to return nodes in maintenance mode (True or
"True"), or not in maintenance mode (False or
"False").
:param retired: Optional. Either a Boolean or a string representation
of a Boolean that indicates whether to return retired
nodes (True or "True").
:param provision_state: Optional. String value to get only nodes in
that provision state.
:param marker: Optional, the UUID of a node, eg the last
@ -135,6 +139,8 @@ class NodeManager(base.CreateManager):
filters.append('associated=%s' % associated)
if maintenance is not None:
filters.append('maintenance=%s' % maintenance)
if retired is not None:
filters.append('retired=%s' % retired)
if fault is not None:
filters.append('fault=%s' % fault)
if provision_state is not None:

View File

@ -103,6 +103,8 @@ class Resource(object):
'raid_config': 'Current RAID configuration',
'reservation': 'Reservation',
'resource_class': 'Resource Class',
'retired': 'Retired',
'retired_reason': 'Retired Reason',
'state': 'State',
'steps': 'Steps',
'target_power_state': 'Target Power State',
@ -258,6 +260,8 @@ NODE_DETAILED_RESOURCE = Resource(
'rescue_interface',
'reservation',
'resource_class',
'retired',
'retired_reason',
'storage_interface',
'target_power_state',
'target_provision_state',

View File

@ -0,0 +1,10 @@
---
features:
- |
Adds the ability to set and unset the ``retired`` and
``retired_reason`` with API 1.61. Setting the ``retired``
field on a node excludes it from scheduling, but still
allows the node to be cleaned (unlike maintenance, for
instance). The fields can be set irrespective of the
node's state and are meant to be used to prepare nodes
for removal from ironic.