Add interface attach/detach support
This patch add the ironicclient part of the interface attach/detach API support. New commands are introduced: * ironic node-vif-list <node_id> * ironic node-vif-attach <node_id> <vif_id> * ironic node-vif-detach <node_id> <vif_id> * openstack baremetal node vif list <node_id> * openstack baremetal node vif attach <node_id> <vif_id> * openstack baremetal node vif detach <node_id> <vif_id> Bump OSC plugin last known API version to 1.28 Co-Authored-By: Vasyl Saienko <vsaienko@mirantis.com> Co-Authored-By: Vladyslav Drok <vdrok@mirantis.com> Depends-On: I70f1166a15a26f392734e21d6bc30a03da4e5486 Change-Id: I801c5633d72e3eb392e5a04362306c44f6100764 Partial-Bug: #1582188
This commit is contained in:
parent
8595b59e1a
commit
288371a43a
@ -25,7 +25,7 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
API_VERSION_OPTION = 'os_baremetal_api_version'
|
||||
API_NAME = 'baremetal'
|
||||
LAST_KNOWN_API_VERSION = 25
|
||||
LAST_KNOWN_API_VERSION = 28
|
||||
API_VERSIONS = {
|
||||
'1.%d' % i: 'ironicclient.v1.client.Client'
|
||||
for i in range(1, LAST_KNOWN_API_VERSION + 1)
|
||||
|
@ -1148,3 +1148,85 @@ class ValidateBaremetalNode(command.Lister):
|
||||
data = oscutils.sort_items(data, 'interface')
|
||||
return (field_labels,
|
||||
(oscutils.get_dict_properties(s, fields) for s in data))
|
||||
|
||||
|
||||
class VifListBaremetalNode(command.Lister):
|
||||
"""Show attached VIFs for a node"""
|
||||
|
||||
log = logging.getLogger(__name__ + ".VifListBaremetalNode")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(VifListBaremetalNode, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'node',
|
||||
metavar='<node>',
|
||||
help="Name or UUID of the node"
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
columns = res_fields.VIF_RESOURCE.fields
|
||||
labels = res_fields.VIF_RESOURCE.labels
|
||||
|
||||
baremetal_client = self.app.client_manager.baremetal
|
||||
data = baremetal_client.node.vif_list(parsed_args.node)
|
||||
|
||||
return (labels,
|
||||
(oscutils.get_item_properties(s, columns) for s in data))
|
||||
|
||||
|
||||
class VifAttachBaremetalNode(command.Command):
|
||||
"""Attach VIF to a given node"""
|
||||
|
||||
log = logging.getLogger(__name__ + ".VifAttachBaremetalNode")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(VifAttachBaremetalNode, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'node',
|
||||
metavar='<node>',
|
||||
help="Name or UUID of the node"
|
||||
)
|
||||
parser.add_argument(
|
||||
'vif_id',
|
||||
metavar='<vif-id>',
|
||||
help="Name or UUID of the VIF to attach to a node."
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
baremetal_client = self.app.client_manager.baremetal
|
||||
baremetal_client.node.vif_attach(parsed_args.node, parsed_args.vif_id)
|
||||
|
||||
|
||||
class VifDetachBaremetalNode(command.Command):
|
||||
"""Detach VIF from a given node"""
|
||||
|
||||
log = logging.getLogger(__name__ + ".VifDetachBaremetalNode")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(VifDetachBaremetalNode, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'node',
|
||||
metavar='<node>',
|
||||
help="Name or UUID of the node"
|
||||
)
|
||||
parser.add_argument(
|
||||
'vif_id',
|
||||
metavar='<vif-id>',
|
||||
help="Name or UUID of the VIF to detach from a node."
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
baremetal_client = self.app.client_manager.baremetal
|
||||
baremetal_client.node.vif_detach(parsed_args.node, parsed_args.vif_id)
|
||||
|
@ -86,6 +86,8 @@ PORTGROUP = {'uuid': baremetal_portgroup_uuid,
|
||||
'address': baremetal_portgroup_address,
|
||||
'extra': baremetal_portgroup_extra}
|
||||
|
||||
VIFS = {'vifs': [{'id': 'aaa-aa'}]}
|
||||
|
||||
|
||||
class TestBaremetal(utils.TestCommand):
|
||||
|
||||
|
@ -1730,3 +1730,69 @@ class TestValidate(TestBaremetal):
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
self.baremetal_mock.node.validate.assert_called_once_with('node_uuid')
|
||||
|
||||
|
||||
class TestVifList(TestBaremetal):
|
||||
def setUp(self):
|
||||
super(TestVifList, self).setUp()
|
||||
|
||||
self.baremetal_mock.node.vif_list.return_value = [
|
||||
baremetal_fakes.FakeBaremetalResource(
|
||||
None,
|
||||
copy.deepcopy(baremetal_fakes.VIFS),
|
||||
loaded=True,
|
||||
),
|
||||
]
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = baremetal_node.VifListBaremetalNode(self.app, None)
|
||||
|
||||
def test_baremetal_vif_list(self):
|
||||
arglist = ['node_uuid']
|
||||
verifylist = [('node', 'node_uuid')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
self.baremetal_mock.node.vif_list.assert_called_once_with('node_uuid')
|
||||
|
||||
|
||||
class TestVifAttach(TestBaremetal):
|
||||
def setUp(self):
|
||||
super(TestVifAttach, self).setUp()
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = baremetal_node.VifAttachBaremetalNode(self.app, None)
|
||||
|
||||
def test_baremetal_vif_attach(self):
|
||||
arglist = ['node_uuid', 'aaa-aaa']
|
||||
verifylist = [('node', 'node_uuid'),
|
||||
('vif_id', 'aaa-aaa')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
self.baremetal_mock.node.vif_attach.assert_called_once_with(
|
||||
'node_uuid', 'aaa-aaa')
|
||||
|
||||
|
||||
class TestVifDetach(TestBaremetal):
|
||||
def setUp(self):
|
||||
super(TestVifDetach, self).setUp()
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = baremetal_node.VifDetachBaremetalNode(self.app, None)
|
||||
|
||||
def test_baremetal_vif_detach(self):
|
||||
arglist = ['node_uuid', 'aaa-aaa']
|
||||
verifylist = [('node', 'node_uuid'),
|
||||
('vif_id', 'aaa-aaa')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
self.baremetal_mock.node.vif_detach.assert_called_once_with(
|
||||
'node_uuid', 'aaa-aaa')
|
||||
|
@ -91,6 +91,8 @@ NODE_VENDOR_PASSTHRU_METHOD = {"heartbeat": {"attach": "false",
|
||||
"description": "",
|
||||
"async": "true"}}
|
||||
|
||||
VIFS = {'vifs': [{'id': 'aaa-aaa'}]}
|
||||
|
||||
CREATE_NODE = copy.deepcopy(NODE1)
|
||||
del CREATE_NODE['id']
|
||||
del CREATE_NODE['uuid']
|
||||
@ -383,6 +385,13 @@ fake_responses = {
|
||||
NODE_VENDOR_PASSTHRU_METHOD,
|
||||
),
|
||||
},
|
||||
'/v1/nodes/%s/vifs' % NODE1['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
VIFS,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fake_responses_pagination = {
|
||||
@ -1082,6 +1091,39 @@ class NodeManagerTest(testtools.TestCase):
|
||||
self.assertRaises(exc.InvalidAttribute, self.mgr.vendor_passthru,
|
||||
**kwargs)
|
||||
|
||||
@mock.patch.object(node.NodeManager, '_list')
|
||||
def test_vif_list(self, _list_mock):
|
||||
kwargs = {
|
||||
'node_ident': NODE1['uuid'],
|
||||
}
|
||||
|
||||
final_path = '/v1/nodes/%s/vifs' % NODE1['uuid']
|
||||
self.mgr.vif_list(**kwargs)
|
||||
_list_mock.assert_called_once_with(final_path, "vifs")
|
||||
|
||||
@mock.patch.object(node.NodeManager, 'update')
|
||||
def test_vif_attach(self, update_mock):
|
||||
kwargs = {
|
||||
'node_ident': NODE1['uuid'],
|
||||
'vif_id': 'vif_id',
|
||||
}
|
||||
|
||||
final_path = '%s/vifs' % NODE1['uuid']
|
||||
self.mgr.vif_attach(**kwargs)
|
||||
update_mock.assert_called_once_with(final_path, {'id': 'vif_id'},
|
||||
http_method="POST")
|
||||
|
||||
@mock.patch.object(node.NodeManager, 'delete')
|
||||
def test_vif_detach(self, delete_mock):
|
||||
kwargs = {
|
||||
'node_ident': NODE1['uuid'],
|
||||
'vif_id': 'vif_id',
|
||||
}
|
||||
|
||||
final_path = '%s/vifs/vif_id' % NODE1['uuid']
|
||||
self.mgr.vif_detach(**kwargs)
|
||||
delete_mock.assert_called_once_with(final_path)
|
||||
|
||||
def _test_node_set_boot_device(self, boot_device, persistent=False):
|
||||
self.mgr.set_boot_device(NODE1['uuid'], boot_device, persistent)
|
||||
body = {'boot_device': boot_device, 'persistent': persistent}
|
||||
|
@ -1127,3 +1127,29 @@ class NodeShellTest(utils.BaseTestCase):
|
||||
n_shell.do_node_get_vendor_passthru_methods(client_mock, args)
|
||||
client_mock.node.get_vendor_passthru_methods.assert_called_once_with(
|
||||
'node_uuid')
|
||||
|
||||
def test_do_node_vif_list(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.node = 'node_uuid'
|
||||
n_shell.do_node_vif_list(client_mock, args)
|
||||
client_mock.node.vif_list.assert_called_once_with(
|
||||
'node_uuid')
|
||||
|
||||
def test_do_node_vif_attach(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.node = 'node_uuid'
|
||||
args.vif_id = 'aaa-aaa'
|
||||
n_shell.do_node_vif_attach(client_mock, args)
|
||||
client_mock.node.vif_attach.assert_called_once_with(
|
||||
'node_uuid', 'aaa-aaa')
|
||||
|
||||
def test_do_node_vif_detach(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.node = 'node_uuid'
|
||||
args.vif_id = 'aaa-aaa'
|
||||
n_shell.do_node_vif_detach(client_mock, args)
|
||||
client_mock.node.vif_detach.assert_called_once_with(
|
||||
'node_uuid', 'aaa-aaa')
|
||||
|
@ -244,6 +244,35 @@ class NodeManager(base.CreateManager):
|
||||
raise exc.InvalidAttribute(
|
||||
_('Unknown HTTP method: %s') % http_method)
|
||||
|
||||
def vif_list(self, node_ident):
|
||||
"""List VIFs attached to a given node.
|
||||
|
||||
:param node_ident: The UUID or Name of the node.
|
||||
"""
|
||||
path = "%s/vifs" % node_ident
|
||||
|
||||
return self._list(self._path(path), "vifs")
|
||||
|
||||
def vif_attach(self, node_ident, vif_id):
|
||||
"""Attach VIF to a given node.
|
||||
|
||||
param node_ident: The UUID or Name of the node.
|
||||
param vif_id: The UUID or Name of the VIF to attach.
|
||||
"""
|
||||
path = "%s/vifs" % node_ident
|
||||
data = {"id": vif_id}
|
||||
# TODO(vdrok): cleanup places doing custom path and http_method
|
||||
self.update(path, data, http_method="POST")
|
||||
|
||||
def vif_detach(self, node_ident, vif_id):
|
||||
"""Detach VIF from a given node.
|
||||
|
||||
param node_ident: The UUID or Name of the node.
|
||||
param vif_id: The UUID or Name of the VIF to detach.
|
||||
"""
|
||||
path = "%s/vifs/%s" % (node_ident, vif_id)
|
||||
self.delete(path)
|
||||
|
||||
def set_maintenance(self, node_id, state, maint_reason=None):
|
||||
"""Set the maintenance mode for the node.
|
||||
|
||||
|
@ -599,3 +599,30 @@ def do_node_get_vendor_passthru_methods(cc, args):
|
||||
field_labels=field_labels,
|
||||
sortby_index=None,
|
||||
json_flag=args.json)
|
||||
|
||||
|
||||
@cliutils.arg('node', metavar='<node>', help="Name or UUID of the node.")
|
||||
def do_node_vif_list(cc, args):
|
||||
"""List VIFs for a given node."""
|
||||
vifs = cc.node.vif_list(args.node)
|
||||
fields = res_fields.VIF_RESOURCE.fields
|
||||
field_labels = res_fields.VIF_RESOURCE.labels
|
||||
cliutils.print_list(vifs, fields, field_labels=field_labels,
|
||||
sortby_index=None,
|
||||
json_flag=args.json)
|
||||
|
||||
|
||||
@cliutils.arg('node', metavar='<node>', help="Name or UUID of the node.")
|
||||
@cliutils.arg('vif_id', metavar='<vif-id>',
|
||||
help="Name or UUID of the VIF to attach to node.")
|
||||
def do_node_vif_attach(cc, args):
|
||||
"""Attach VIF to a given node."""
|
||||
cc.node.vif_attach(args.node, args.vif_id)
|
||||
|
||||
|
||||
@cliutils.arg('node', metavar='<node>', help="Name or UUID of the node.")
|
||||
@cliutils.arg('vif_id', metavar='<vif-id>',
|
||||
help="Name or UUID of the VIF to detach from node.")
|
||||
def do_node_vif_detach(cc, args):
|
||||
"""Detach VIF from a given node."""
|
||||
cc.node.vif_detach(args.node, args.vif_id)
|
||||
|
@ -70,7 +70,8 @@ class Resource(object):
|
||||
'pxe_enabled': 'PXE boot enabled',
|
||||
'portgroup_uuid': 'Portgroup UUID',
|
||||
'network_interface': 'Network Interface',
|
||||
'standalone_ports_supported': 'Standalone Ports Supported'
|
||||
'standalone_ports_supported': 'Standalone Ports Supported',
|
||||
'id': 'ID',
|
||||
}
|
||||
|
||||
def __init__(self, field_ids, sort_excluded=None):
|
||||
@ -244,3 +245,8 @@ PORTGROUP_RESOURCE = Resource(
|
||||
'address',
|
||||
'name',
|
||||
])
|
||||
|
||||
# VIFs
|
||||
VIF_RESOURCE = Resource(
|
||||
['id'],
|
||||
)
|
||||
|
@ -0,0 +1,14 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds support for attaching and detaching VIFs. This is available starting
|
||||
with ironic API microversion 1.28.
|
||||
|
||||
The new commands are:
|
||||
|
||||
* ``ironic node-vif-list <node>``
|
||||
* ``ironic node-vif-attach <node> <vif-id>``
|
||||
* ``ironic node-vif-detach <node> <vif-id>``
|
||||
* ``openstack baremetal node vif list <node>``
|
||||
* ``openstack baremetal node vif attach <node> <vif-id>``
|
||||
* ``openstack baremetal node vif detach <node> <vif-id>``
|
@ -69,6 +69,9 @@ openstack.baremetal.v1 =
|
||||
baremetal_node_undeploy = ironicclient.osc.v1.baremetal_node:UndeployBaremetalNode
|
||||
baremetal_node_unset = ironicclient.osc.v1.baremetal_node:UnsetBaremetalNode
|
||||
baremetal_node_validate = ironicclient.osc.v1.baremetal_node:ValidateBaremetalNode
|
||||
baremetal_node_vif_attach = ironicclient.osc.v1.baremetal_node:VifAttachBaremetalNode
|
||||
baremetal_node_vif_detach = ironicclient.osc.v1.baremetal_node:VifDetachBaremetalNode
|
||||
baremetal_node_vif_list = ironicclient.osc.v1.baremetal_node:VifListBaremetalNode
|
||||
baremetal_port_create = ironicclient.osc.v1.baremetal_port:CreateBaremetalPort
|
||||
baremetal_port_delete = ironicclient.osc.v1.baremetal_port:DeleteBaremetalPort
|
||||
baremetal_port_list = ironicclient.osc.v1.baremetal_port:ListBaremetalPort
|
||||
|
Loading…
Reference in New Issue
Block a user