Updates supporting ironic-neutron integration

This patchset adds port creation with new optional parameters specifying
new port attributes (local_link_connection, pxe_enabled).
It also adds new parameter network_interface to node object.

Co-Authored-By: Vasyl Saienko (vsaienko@mirantis.com)
Co-Authored-By: William Stevenson (will.stevenson@sap.com)

Partial-bug: #1526403
Depends-on: I67495196c3334f51ed034f4ca6e32a3e01a58f15

Change-Id: If2fb996783b9ac26a5bae2aadd6387207750def9
This commit is contained in:
Vasyl Saienko 2016-06-30 06:17:24 -04:00 committed by Jim Rollenhagen
parent d2debb7a38
commit d1ea9b44f6
14 changed files with 118 additions and 10 deletions

View File

@ -23,7 +23,7 @@ LOG = logging.getLogger(__name__)
DEFAULT_BAREMETAL_API_VERSION = '1.6' DEFAULT_BAREMETAL_API_VERSION = '1.6'
API_VERSION_OPTION = 'os_baremetal_api_version' API_VERSION_OPTION = 'os_baremetal_api_version'
API_NAME = 'baremetal' API_NAME = 'baremetal'
LAST_KNOWN_API_VERSION = 14 LAST_KNOWN_API_VERSION = 20
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)

View File

@ -151,6 +151,11 @@ class CreateBaremetalNode(show.ShowOne):
'--name', '--name',
metavar='<name>', metavar='<name>',
help="Unique name for the node.") help="Unique name for the node.")
parser.add_argument(
'--network-interface',
metavar='<network_interface>',
help='Network interface used for switching node to '
'cleaning/provisioning networks.')
return parser return parser
@ -160,7 +165,8 @@ class CreateBaremetalNode(show.ShowOne):
baremetal_client = self.app.client_manager.baremetal baremetal_client = self.app.client_manager.baremetal
field_list = ['chassis_uuid', 'driver', 'driver_info', field_list = ['chassis_uuid', 'driver', 'driver_info',
'properties', 'extra', 'uuid', 'name'] 'properties', 'extra', 'uuid', 'name',
'network_interface']
fields = dict((k, v) for (k, v) in vars(parsed_args).items() fields = dict((k, v) for (k, v) in vars(parsed_args).items()
if k in field_list and not (v is None)) if k in field_list and not (v is None))
fields = utils.args_array_to_dict(fields, 'driver_info') fields = utils.args_array_to_dict(fields, 'driver_info')
@ -554,6 +560,11 @@ class SetBaremetalNode(command.Command):
metavar="<driver>", metavar="<driver>",
help="Set the driver for the node", help="Set the driver for the node",
) )
parser.add_argument(
'--network-interface',
metavar='<network_interface>',
help='Set the network interface for the node',
)
parser.add_argument( parser.add_argument(
"--property", "--property",
metavar="<key=value>", metavar="<key=value>",
@ -603,6 +614,11 @@ class SetBaremetalNode(command.Command):
driver = ["driver=%s" % parsed_args.driver] driver = ["driver=%s" % parsed_args.driver]
properties.extend(utils.args_array_to_patch( properties.extend(utils.args_array_to_patch(
'add', driver)) 'add', driver))
if parsed_args.network_interface:
network_interface = [
"network_interface=%s" % parsed_args.network_interface]
properties.extend(utils.args_array_to_patch(
'add', network_interface))
if parsed_args.property: if parsed_args.property:
properties.extend(utils.args_array_to_patch( properties.extend(utils.args_array_to_patch(
'add', ['properties/' + x for x in parsed_args.property])) 'add', ['properties/' + x for x in parsed_args.property]))

View File

@ -46,6 +46,18 @@ class CreateBaremetalPort(show.ShowOne):
action='append', action='append',
help="Record arbitrary key/value metadata. " help="Record arbitrary key/value metadata. "
"Can be specified multiple times.") "Can be specified multiple times.")
parser.add_argument(
'-l', '--local-link-connection',
metavar="<key=value>",
action='append',
help="Key/value metadata describing Local link connection "
"information. Valid keys are switch_info, switch_id, "
"port_id. Can be specified multiple times.")
parser.add_argument(
'--pxe-enabled',
metavar='<boolean>',
help='Indicates whether this Port should be used when '
'PXE booting this Node.')
return parser return parser
@ -53,10 +65,12 @@ class CreateBaremetalPort(show.ShowOne):
self.log.debug("take_action(%s)" % parsed_args) self.log.debug("take_action(%s)" % parsed_args)
baremetal_client = self.app.client_manager.baremetal baremetal_client = self.app.client_manager.baremetal
field_list = ['address', 'extra', 'node_uuid'] field_list = ['address', 'extra', 'node_uuid', 'pxe_enabled',
'local_link_connection']
fields = dict((k, v) for (k, v) in vars(parsed_args).items() fields = dict((k, v) for (k, v) in vars(parsed_args).items()
if k in field_list and v is not None) if k in field_list and v is not None)
fields = utils.args_array_to_dict(fields, 'extra') fields = utils.args_array_to_dict(fields, 'extra')
fields = utils.args_array_to_dict(fields, 'local_link_connection')
port = baremetal_client.port.create(**fields) port = baremetal_client.port.create(**fields)
data = dict([(f, getattr(port, f, '')) for f in data = dict([(f, getattr(port, f, '')) for f in

View File

@ -162,6 +162,11 @@ class TestBaremetalCreate(TestBaremetal):
[('name', 'name')], [('name', 'name')],
{'name': 'name'}) {'name': 'name'})
def test_baremetal_create_with_network_interface(self):
self.check_with_options(['--network-interface', 'neutron'],
[('network_interface', 'neutron')],
{'network_interface': 'neutron'})
class TestBaremetalDelete(TestBaremetal): class TestBaremetalDelete(TestBaremetal):
def setUp(self): def setUp(self):
@ -315,7 +320,8 @@ class TestBaremetalList(TestBaremetal):
'Target Power State', 'Target Provision State', 'Target Power State', 'Target Provision State',
'Target RAID configuration', 'Target RAID configuration',
'Updated At', 'Inspection Finished At', 'Updated At', 'Inspection Finished At',
'Inspection Started At', 'UUID', 'Name') 'Inspection Started At', 'UUID', 'Name',
'Network Interface')
self.assertEqual(collist, columns) self.assertEqual(collist, columns)
datalist = (( datalist = ((
'', '',
@ -345,6 +351,7 @@ class TestBaremetalList(TestBaremetal):
'', '',
baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_uuid,
baremetal_fakes.baremetal_name, baremetal_fakes.baremetal_name,
'',
), ) ), )
self.assertEqual(datalist, tuple(data)) self.assertEqual(datalist, tuple(data))
@ -801,6 +808,25 @@ class TestBaremetalSet(TestBaremetal):
[{'path': '/driver', 'value': 'xxxxx', 'op': 'add'}] [{'path': '/driver', 'value': 'xxxxx', 'op': 'add'}]
) )
def test_baremetal_set_network_interface(self):
arglist = [
'node_uuid',
'--network-interface', 'xxxxx',
]
verifylist = [
('node', 'node_uuid'),
('network_interface', 'xxxxx')
]
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': '/network_interface', 'value': 'xxxxx', 'op': 'add'}]
)
def test_baremetal_set_extra(self): def test_baremetal_set_extra(self):
arglist = [ arglist = [
'node_uuid', 'node_uuid',

View File

@ -53,7 +53,6 @@ PORT = {'id': 456,
'node_id': 123, 'node_id': 123,
'address': 'AA:AA:AA:AA:AA:AA', 'address': 'AA:AA:AA:AA:AA:AA',
'extra': {}} 'extra': {}}
POWER_STATE = {'power_state': 'power off', POWER_STATE = {'power_state': 'power off',
'target_power_state': 'power on'} 'target_power_state': 'power on'}

View File

@ -46,6 +46,7 @@ class NodeShellTest(utils.BaseTestCase):
'maintenance', 'maintenance',
'maintenance_reason', 'maintenance_reason',
'name', 'name',
'network_interface',
'power_state', 'power_state',
'properties', 'properties',
'provision_state', 'provision_state',

View File

@ -26,12 +26,16 @@ PORT = {'id': 987,
'uuid': '11111111-2222-3333-4444-555555555555', 'uuid': '11111111-2222-3333-4444-555555555555',
'node_uuid': '55555555-4444-3333-2222-111111111111', 'node_uuid': '55555555-4444-3333-2222-111111111111',
'address': 'AA:BB:CC:DD:EE:FF', 'address': 'AA:BB:CC:DD:EE:FF',
'pxe_enabled': True,
'local_link_connection': {},
'extra': {}} 'extra': {}}
PORT2 = {'id': 988, PORT2 = {'id': 988,
'uuid': '55555555-4444-3333-2222-111111111111', 'uuid': '55555555-4444-3333-2222-111111111111',
'node_uuid': '55555555-4444-3333-2222-111111111111', 'node_uuid': '55555555-4444-3333-2222-111111111111',
'address': 'AA:AA:AA:BB:BB:BB', 'address': 'AA:AA:AA:BB:BB:BB',
'pxe_enabled': True,
'local_link_connection': {},
'extra': {}} 'extra': {}}
CREATE_PORT = copy.deepcopy(PORT) CREATE_PORT = copy.deepcopy(PORT)
@ -263,6 +267,9 @@ class PortManagerTest(testtools.TestCase):
self.assertEqual(PORT['uuid'], port.uuid) self.assertEqual(PORT['uuid'], port.uuid)
self.assertEqual(PORT['address'], port.address) self.assertEqual(PORT['address'], port.address)
self.assertEqual(PORT['node_uuid'], port.node_uuid) self.assertEqual(PORT['node_uuid'], port.node_uuid)
self.assertEqual(PORT['pxe_enabled'], port.pxe_enabled)
self.assertEqual(PORT['local_link_connection'],
port.local_link_connection)
def test_ports_show_by_address(self): def test_ports_show_by_address(self):
port = self.mgr.get_by_address(PORT['address']) port = self.mgr.get_by_address(PORT['address'])
@ -274,6 +281,9 @@ class PortManagerTest(testtools.TestCase):
self.assertEqual(PORT['uuid'], port.uuid) self.assertEqual(PORT['uuid'], port.uuid)
self.assertEqual(PORT['address'], port.address) self.assertEqual(PORT['address'], port.address)
self.assertEqual(PORT['node_uuid'], port.node_uuid) self.assertEqual(PORT['node_uuid'], port.node_uuid)
self.assertEqual(PORT['pxe_enabled'], port.pxe_enabled)
self.assertEqual(PORT['local_link_connection'],
port.local_link_connection)
def test_port_show_fields(self): def test_port_show_fields(self):
port = self.mgr.get(PORT['uuid'], fields=['uuid', 'address']) port = self.mgr.get(PORT['uuid'], fields=['uuid', 'address'])

View File

@ -32,7 +32,7 @@ class PortShellTest(utils.BaseTestCase):
port = object() port = object()
p_shell._print_port_show(port) p_shell._print_port_show(port)
exp = ['address', 'created_at', 'extra', 'node_uuid', 'updated_at', exp = ['address', 'created_at', 'extra', 'node_uuid', 'updated_at',
'uuid'] 'uuid', 'pxe_enabled', 'local_link_connection']
act = actual.keys() act = actual.keys()
self.assertEqual(sorted(exp), sorted(act)) self.assertEqual(sorted(exp), sorted(act))

View File

@ -37,7 +37,8 @@ class Node(base.Resource):
class NodeManager(base.CreateManager): class NodeManager(base.CreateManager):
resource_class = Node resource_class = Node
_creation_attributes = ['chassis_uuid', 'driver', 'driver_info', _creation_attributes = ['chassis_uuid', 'driver', 'driver_info',
'extra', 'uuid', 'properties', 'name'] 'extra', 'uuid', 'properties', 'name',
'network_interface']
_resource_name = 'nodes' _resource_name = 'nodes'
def list(self, associated=None, maintenance=None, marker=None, limit=None, def list(self, associated=None, maintenance=None, marker=None, limit=None,

View File

@ -202,10 +202,16 @@ def do_node_list(cc, args):
'-n', '--name', '-n', '--name',
metavar='<name>', metavar='<name>',
help="Unique name for the node.") help="Unique name for the node.")
@cliutils.arg(
'--network-interface',
metavar='<network_interface>',
help='Network interface used for switching node to cleaning/provisioning '
'networks.')
def do_node_create(cc, args): def do_node_create(cc, args):
"""Register a new node with the Ironic service.""" """Register a new node with the Ironic service."""
field_list = ['chassis_uuid', 'driver', 'driver_info', field_list = ['chassis_uuid', 'driver', 'driver_info',
'properties', 'extra', 'uuid', 'name'] 'properties', 'extra', 'uuid', 'name',
'network_interface']
fields = dict((k, v) for (k, v) in vars(args).items() fields = dict((k, v) for (k, v) in vars(args).items()
if k in field_list and not (v is None)) if k in field_list and not (v is None))
fields = utils.args_array_to_dict(fields, 'driver_info') fields = utils.args_array_to_dict(fields, 'driver_info')

View File

@ -27,7 +27,8 @@ class Port(base.Resource):
class PortManager(base.CreateManager): class PortManager(base.CreateManager):
resource_class = Port resource_class = Port
_creation_attributes = ['address', 'extra', 'node_uuid', 'uuid'] _creation_attributes = ['address', 'extra', 'local_link_connection',
'node_uuid', 'pxe_enabled', 'uuid']
_resource_name = 'ports' _resource_name = 'ports'
def list(self, address=None, limit=None, marker=None, sort_key=None, def list(self, address=None, limit=None, marker=None, sort_key=None,

View File

@ -143,6 +143,18 @@ def do_port_list(cc, args):
metavar='<node>', metavar='<node>',
required=True, required=True,
help='UUID of the node that this port belongs to.') help='UUID of the node that this port belongs to.')
@cliutils.arg(
'-l', '--local-link-connection',
metavar="<key=value>",
action='append',
help="Key/value metadata describing Local link connection information. "
"Valid keys are switch_info, switch_id, port_id."
"Can be specified multiple times.")
@cliutils.arg(
'--pxe-enabled',
metavar='<boolean>',
help='Indicates whether this Port should be used when '
'PXE booting this Node.')
@cliutils.arg( @cliutils.arg(
'-e', '--extra', '-e', '--extra',
metavar="<key=value>", metavar="<key=value>",
@ -155,10 +167,12 @@ def do_port_list(cc, args):
help="UUID of the port.") help="UUID of the port.")
def do_port_create(cc, args): def do_port_create(cc, args):
"""Create a new port.""" """Create a new port."""
field_list = ['address', 'extra', 'node_uuid', 'uuid'] field_list = ['address', 'extra', 'node_uuid', 'uuid',
'local_link_connection', 'pxe_enabled']
fields = dict((k, v) for (k, v) in vars(args).items() fields = dict((k, v) for (k, v) in vars(args).items()
if k in field_list and not (v is None)) if k in field_list and not (v is None))
fields = utils.args_array_to_dict(fields, 'extra') fields = utils.args_array_to_dict(fields, 'extra')
fields = utils.args_array_to_dict(fields, 'local_link_connection')
port = cc.port.create(**fields) port = cc.port.create(**fields)
data = dict([(f, getattr(port, f, '')) for f in field_list]) data = dict([(f, getattr(port, f, '')) for f in field_list])

View File

@ -64,6 +64,9 @@ class Resource(object):
'target_raid_config': 'Target RAID configuration', 'target_raid_config': 'Target RAID configuration',
'updated_at': 'Updated At', 'updated_at': 'Updated At',
'uuid': 'UUID', 'uuid': 'UUID',
'local_link_connection': 'Local Link Connection',
'pxe_enabled': 'PXE boot enabled',
'network_interface': 'Network Interface',
} }
def __init__(self, field_ids, sort_excluded=None): def __init__(self, field_ids, sort_excluded=None):
@ -151,6 +154,7 @@ NODE_DETAILED_RESOURCE = Resource(
'inspection_started_at', 'inspection_started_at',
'uuid', 'uuid',
'name', 'name',
'network_interface',
], ],
sort_excluded=[ sort_excluded=[
# The server cannot sort on "chassis_uuid" because it isn't a column in # The server cannot sort on "chassis_uuid" because it isn't a column in
@ -189,6 +193,8 @@ PORT_DETAILED_RESOURCE = Resource(
'created_at', 'created_at',
'extra', 'extra',
'node_uuid', 'node_uuid',
'local_link_connection',
'pxe_enabled',
'updated_at', 'updated_at',
], ],
sort_excluded=[ sort_excluded=[

View File

@ -0,0 +1,14 @@
---
features:
- |
Add support of new fields:
* ``node.network_interface`` is introduced in API 1.20,
specifies the network interface to use for a node.
* ``port.local_link_connection`` contains the port binding
profile.
* ``port.pxe_enabled`` indicates whether PXE is enabled
for the port.
The ``port.local_link_connection`` and ``port.pxe_enabled`` fields
were introduced in API 1.19.