Merge "Option to send all portgroup data"

This commit is contained in:
Zuul 2019-07-30 02:07:34 +00:00 committed by Gerrit Code Review
commit cc6e61f1dd
4 changed files with 114 additions and 20 deletions

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from neutronclient.common import exceptions as neutron_exceptions
from neutronclient.v2_0 import client as clientv20
from oslo_log import log
@ -203,6 +205,7 @@ def add_ports_to_network(task, network_uuid, security_groups=None):
"""
client = get_client(context=task.context)
node = task.node
add_all_ports = CONF.neutron.add_all_ports
# If Security Groups are specified, verify that they exist
_verify_security_groups(security_groups, client)
@ -211,6 +214,7 @@ def add_ports_to_network(task, network_uuid, security_groups=None):
'%(network_uuid)s using %(net_iface)s network interface.',
{'net_iface': task.driver.network.__class__.__name__,
'node': node.uuid, 'network_uuid': network_uuid})
body = {
'port': {
'network_id': network_uuid,
@ -231,21 +235,33 @@ def add_ports_to_network(task, network_uuid, security_groups=None):
ports = {}
failures = []
portmap = get_node_portmap(task)
pxe_enabled_ports = [p for p in task.ports if p.pxe_enabled]
if not add_all_ports:
pxe_enabled_ports = [p for p in task.ports if p.pxe_enabled]
else:
pxe_enabled_ports = task.ports
if not pxe_enabled_ports:
raise exception.NetworkError(_(
"No available PXE-enabled port on node %s.") % node.uuid)
for ironic_port in pxe_enabled_ports:
# Start with a clean state for each port
port_body = copy.deepcopy(body)
# Skip ports that are missing required information for deploy.
if not validate_port_info(node, ironic_port):
failures.append(ironic_port.uuid)
continue
body['port']['mac_address'] = ironic_port.address
port_body['port']['mac_address'] = ironic_port.address
binding_profile = {'local_link_information':
[portmap[ironic_port.uuid]]}
body['port']['binding:profile'] = binding_profile
port_body['port']['binding:profile'] = binding_profile
if add_all_ports and not ironic_port.pxe_enabled:
LOG.debug("Adding port %(port)s to network %(net) for "
"provisioning without an IP allocation.",
{'port': ironic_port.uuid,
'net': network_uuid})
port_body['fixed_ips'] = []
is_smart_nic = is_smartnic_port(ironic_port)
if is_smart_nic:
@ -254,21 +270,22 @@ def add_ports_to_network(task, network_uuid, security_groups=None):
'port %(port_id)s, hostname %(hostname)s',
{'port_id': ironic_port.uuid,
'hostname': link_info['hostname']})
body['port']['binding:host_id'] = link_info['hostname']
port_body['port']['binding:host_id'] = link_info['hostname']
# TODO(hamdyk): use portbindings.VNIC_SMARTNIC from neutron-lib
body['port']['binding:vnic_type'] = VNIC_SMARTNIC
port_body['port']['binding:vnic_type'] = VNIC_SMARTNIC
client_id = ironic_port.extra.get('client-id')
if client_id:
client_id_opt = {'opt_name': DHCP_CLIENT_ID,
'opt_value': client_id}
extra_dhcp_opts = body['port'].get('extra_dhcp_opts', [])
extra_dhcp_opts = port_body['port'].get('extra_dhcp_opts', [])
extra_dhcp_opts.append(client_id_opt)
body['port']['extra_dhcp_opts'] = extra_dhcp_opts
port_body['port']['extra_dhcp_opts'] = extra_dhcp_opts
try:
if is_smart_nic:
wait_for_host_agent(client, body['port']['binding:host_id'])
port = client.create_port(body)
wait_for_host_agent(client,
port_body['port']['binding:host_id'])
port = client.create_port(port_body)
if is_smart_nic:
wait_for_port_status(client, port['port']['id'], 'ACTIVE')
except neutron_exceptions.NeutronClientException as e:
@ -307,7 +324,11 @@ def remove_ports_from_network(task, network_uuid):
:param network_uuid: UUID of a neutron network ports will be deleted from.
:raises: NetworkError
"""
macs = [p.address for p in task.ports if p.pxe_enabled]
add_all_ports = CONF.neutron.add_all_ports
if not add_all_ports:
macs = [p.address for p in task.ports if p.pxe_enabled]
else:
macs = [p.address for p in task.ports]
if macs:
params = {
'network_id': network_uuid,

View File

@ -83,7 +83,13 @@ opts = [
'performs pre-commit validation prior returning to '
'the API client which can take longer than normal '
'client/server interactions.')),
cfg.BoolOpt('add_all_ports',
default=False,
help=_('Option to enable transmission of all ports '
'to neutron when creating ports for provisioning, '
'cleaning, or rescue. This is done without IP '
'addresses assigned to the port, and may be useful '
'in some bonded network configurations.')),
]

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import time
from keystoneauth1 import loading as kaloading
@ -171,16 +172,19 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
self.addCleanup(patcher.stop)
def _test_add_ports_to_network(self, is_client_id,
security_groups=None):
security_groups=None,
add_all_ports=False):
# Ports will be created only if pxe_enabled is True
self.node.network_interface = 'neutron'
self.node.save()
object_utils.create_test_port(
port2 = object_utils.create_test_port(
self.context, node_id=self.node.id,
uuid=uuidutils.generate_uuid(),
address='52:54:00:cf:2d:22',
address='54:00:00:cf:2d:22',
pxe_enabled=False
)
if add_all_ports:
self.config(add_all_ports=True, group="neutron")
port = self.ports[0]
if is_client_id:
extra = port.extra
@ -207,21 +211,46 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
if is_client_id:
expected_body['port']['extra_dhcp_opts'] = (
[{'opt_name': '61', 'opt_value': self._CLIENT_ID}])
# Ensure we can create ports
self.client_mock.create_port.return_value = {
'port': self.neutron_port}
expected = {port.uuid: self.neutron_port['id']}
if add_all_ports:
expected_body2 = copy.deepcopy(expected_body)
expected_body2['port']['mac_address'] = port2.address
expected_body2['fixed_ips'] = []
neutron_port2 = {'id': '132f871f-eaec-4fed-9475-0d54465e0f01',
'mac_address': port2.address}
self.client_mock.create_port.side_effect = [
{'port': self.neutron_port},
{'port': neutron_port2}
]
expected = {port.uuid: self.neutron_port['id'],
port2.uuid: neutron_port2['id']}
else:
self.client_mock.create_port.return_value = {
'port': self.neutron_port}
expected = {port.uuid: self.neutron_port['id']}
with task_manager.acquire(self.context, self.node.uuid) as task:
ports = neutron.add_ports_to_network(
task, self.network_uuid, security_groups=security_groups)
self.assertEqual(expected, ports)
self.client_mock.create_port.assert_called_once_with(
expected_body)
if add_all_ports:
calls = [mock.call(expected_body),
mock.call(expected_body2)]
self.client_mock.create_port.assert_has_calls(calls)
else:
self.client_mock.create_port.assert_called_once_with(
expected_body)
def test_add_ports_to_network(self):
self._test_add_ports_to_network(is_client_id=False,
security_groups=None)
def test_add_ports_to_network_all_ports(self):
self._test_add_ports_to_network(is_client_id=False,
security_groups=None,
add_all_ports=True)
@mock.patch.object(neutron, '_verify_security_groups', autospec=True)
def test_add_ports_to_network_with_sg(self, verify_mock):
sg_ids = []
@ -413,6 +442,25 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
'mac_address': [self.ports[0].address]}
)
@mock.patch.object(neutron, 'remove_neutron_ports', autospec=True)
def test_remove_ports_from_network_not_all_pxe_enabled_all_ports(
self, remove_mock):
self.config(add_all_ports=True, group="neutron")
object_utils.create_test_port(
self.context, node_id=self.node.id,
uuid=uuidutils.generate_uuid(),
address='52:54:55:cf:2d:32',
pxe_enabled=False
)
with task_manager.acquire(self.context, self.node.uuid) as task:
neutron.remove_ports_from_network(task, self.network_uuid)
calls = [
mock.call(task, {'network_id': self.network_uuid,
'mac_address': [task.ports[0].address,
task.ports[1].address]}),
]
remove_mock.assert_has_calls(calls)
def test_remove_neutron_ports(self):
with task_manager.acquire(self.context, self.node.uuid) as task:
self.client_mock.list_ports.return_value = {

View File

@ -0,0 +1,19 @@
---
fixes:
- |
Provides an opt-in fix to change the default port attachment behavior
for deployment and cleaning operations through a new configuration option,
``[neutron]add_all_ports``. This option causes ironic to transmits all
port information to neutron as opposed to only a single physical network
port. This enables operators to successfully operate static Port Group
configurations with Neutron ML2 drivers, where previously configuration
of networking would fail.
When these ports are configured with ``pxe_enabled`` set to ``False``,
neutron will be requested not to assign an IP address to the port. This
is to prevent additional issues that may occur depending on physical
switch configuration with static Port Group configurations.
- |
Fixes an issue during provisioning network attachment where
neutron ports were being created with the same data structure
being re-used.