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_VERSION_OPTION = 'os_baremetal_api_version'
|
||||||
API_NAME = 'baremetal'
|
API_NAME = 'baremetal'
|
||||||
LAST_KNOWN_API_VERSION = 25
|
LAST_KNOWN_API_VERSION = 28
|
||||||
API_VERSIONS = {
|
API_VERSIONS = {
|
||||||
'1.%d' % i: 'ironicclient.v1.client.Client'
|
'1.%d' % i: 'ironicclient.v1.client.Client'
|
||||||
for i in range(1, LAST_KNOWN_API_VERSION + 1)
|
for i in range(1, LAST_KNOWN_API_VERSION + 1)
|
||||||
|
@ -1148,3 +1148,85 @@ class ValidateBaremetalNode(command.Lister):
|
|||||||
data = oscutils.sort_items(data, 'interface')
|
data = oscutils.sort_items(data, 'interface')
|
||||||
return (field_labels,
|
return (field_labels,
|
||||||
(oscutils.get_dict_properties(s, fields) for s in data))
|
(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,
|
'address': baremetal_portgroup_address,
|
||||||
'extra': baremetal_portgroup_extra}
|
'extra': baremetal_portgroup_extra}
|
||||||
|
|
||||||
|
VIFS = {'vifs': [{'id': 'aaa-aa'}]}
|
||||||
|
|
||||||
|
|
||||||
class TestBaremetal(utils.TestCommand):
|
class TestBaremetal(utils.TestCommand):
|
||||||
|
|
||||||
|
@ -1730,3 +1730,69 @@ class TestValidate(TestBaremetal):
|
|||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.baremetal_mock.node.validate.assert_called_once_with('node_uuid')
|
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": "",
|
"description": "",
|
||||||
"async": "true"}}
|
"async": "true"}}
|
||||||
|
|
||||||
|
VIFS = {'vifs': [{'id': 'aaa-aaa'}]}
|
||||||
|
|
||||||
CREATE_NODE = copy.deepcopy(NODE1)
|
CREATE_NODE = copy.deepcopy(NODE1)
|
||||||
del CREATE_NODE['id']
|
del CREATE_NODE['id']
|
||||||
del CREATE_NODE['uuid']
|
del CREATE_NODE['uuid']
|
||||||
@ -383,6 +385,13 @@ fake_responses = {
|
|||||||
NODE_VENDOR_PASSTHRU_METHOD,
|
NODE_VENDOR_PASSTHRU_METHOD,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
'/v1/nodes/%s/vifs' % NODE1['uuid']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
VIFS,
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fake_responses_pagination = {
|
fake_responses_pagination = {
|
||||||
@ -1082,6 +1091,39 @@ class NodeManagerTest(testtools.TestCase):
|
|||||||
self.assertRaises(exc.InvalidAttribute, self.mgr.vendor_passthru,
|
self.assertRaises(exc.InvalidAttribute, self.mgr.vendor_passthru,
|
||||||
**kwargs)
|
**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):
|
def _test_node_set_boot_device(self, boot_device, persistent=False):
|
||||||
self.mgr.set_boot_device(NODE1['uuid'], boot_device, persistent)
|
self.mgr.set_boot_device(NODE1['uuid'], boot_device, persistent)
|
||||||
body = {'boot_device': boot_device, 'persistent': 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)
|
n_shell.do_node_get_vendor_passthru_methods(client_mock, args)
|
||||||
client_mock.node.get_vendor_passthru_methods.assert_called_once_with(
|
client_mock.node.get_vendor_passthru_methods.assert_called_once_with(
|
||||||
'node_uuid')
|
'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(
|
raise exc.InvalidAttribute(
|
||||||
_('Unknown HTTP method: %s') % http_method)
|
_('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):
|
def set_maintenance(self, node_id, state, maint_reason=None):
|
||||||
"""Set the maintenance mode for the node.
|
"""Set the maintenance mode for the node.
|
||||||
|
|
||||||
|
@ -599,3 +599,30 @@ def do_node_get_vendor_passthru_methods(cc, args):
|
|||||||
field_labels=field_labels,
|
field_labels=field_labels,
|
||||||
sortby_index=None,
|
sortby_index=None,
|
||||||
json_flag=args.json)
|
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',
|
'pxe_enabled': 'PXE boot enabled',
|
||||||
'portgroup_uuid': 'Portgroup UUID',
|
'portgroup_uuid': 'Portgroup UUID',
|
||||||
'network_interface': 'Network Interface',
|
'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):
|
def __init__(self, field_ids, sort_excluded=None):
|
||||||
@ -244,3 +245,8 @@ PORTGROUP_RESOURCE = Resource(
|
|||||||
'address',
|
'address',
|
||||||
'name',
|
'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_undeploy = ironicclient.osc.v1.baremetal_node:UndeployBaremetalNode
|
||||||
baremetal_node_unset = ironicclient.osc.v1.baremetal_node:UnsetBaremetalNode
|
baremetal_node_unset = ironicclient.osc.v1.baremetal_node:UnsetBaremetalNode
|
||||||
baremetal_node_validate = ironicclient.osc.v1.baremetal_node:ValidateBaremetalNode
|
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_create = ironicclient.osc.v1.baremetal_port:CreateBaremetalPort
|
||||||
baremetal_port_delete = ironicclient.osc.v1.baremetal_port:DeleteBaremetalPort
|
baremetal_port_delete = ironicclient.osc.v1.baremetal_port:DeleteBaremetalPort
|
||||||
baremetal_port_list = ironicclient.osc.v1.baremetal_port:ListBaremetalPort
|
baremetal_port_list = ironicclient.osc.v1.baremetal_port:ListBaremetalPort
|
||||||
|
Loading…
Reference in New Issue
Block a user