Add support for Security Groups for baremetal servers
This patch adds support for Neutron Security Groups to the baremetal severs when neutron network interface is used for deployments. Specifically, this patch adds support so that security groups could be specified (and applied) for provisioning and cleaning networks. Change-Id: I0cf652bdd220480b104e478f2096bf89a9ba8bdf Partial-bug: #1594242
This commit is contained in:
parent
e05e060964
commit
3197e44c04
@ -1933,6 +1933,13 @@
|
|||||||
# interface or "neutron" DHCP provider. (string value)
|
# interface or "neutron" DHCP provider. (string value)
|
||||||
#cleaning_network_uuid = <None>
|
#cleaning_network_uuid = <None>
|
||||||
|
|
||||||
|
# List of Neutron Security Group UUIDs to be applied during
|
||||||
|
# cleaning of the nodes. Optional for the "neutron" network
|
||||||
|
# interface and not used for the "flat" or "noop" network
|
||||||
|
# interfaces. If not specified, default security
|
||||||
|
# group is used. (list value)
|
||||||
|
#cleaning_network_security_groups =
|
||||||
|
|
||||||
# Optional domain ID to use with v3 and v2 parameters. It will
|
# Optional domain ID to use with v3 and v2 parameters. It will
|
||||||
# be used for both the user and project domain in v3 and
|
# be used for both the user and project domain in v3 and
|
||||||
# ignored in v2 authentication. (string value)
|
# ignored in v2 authentication. (string value)
|
||||||
@ -1982,6 +1989,13 @@
|
|||||||
# interface. (string value)
|
# interface. (string value)
|
||||||
#provisioning_network_uuid = <None>
|
#provisioning_network_uuid = <None>
|
||||||
|
|
||||||
|
# List of Neutron Security Group UUIDs to be applied during
|
||||||
|
# provisioning of the nodes. Optional for the "neutron"
|
||||||
|
# network interface and not used for the "flat" or "noop"
|
||||||
|
# network interfaces. If not specified, default
|
||||||
|
# security group is used. (list value)
|
||||||
|
#provisioning_network_security_groups =
|
||||||
|
|
||||||
# Client retries in the case of a failed request. (integer
|
# Client retries in the case of a failed request. (integer
|
||||||
# value)
|
# value)
|
||||||
#retries = 3
|
#retries = 3
|
||||||
|
@ -68,7 +68,30 @@ def get_client(token=None):
|
|||||||
return clientv20.Client(**params)
|
return clientv20.Client(**params)
|
||||||
|
|
||||||
|
|
||||||
def add_ports_to_network(task, network_uuid, is_flat=False):
|
def _verify_security_groups(security_groups, client):
|
||||||
|
if not security_groups:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
neutron_sec_groups = (
|
||||||
|
client.list_security_groups().get('security_groups') or [])
|
||||||
|
except neutron_exceptions.NeutronClientException as e:
|
||||||
|
msg = (_("Could not retrieve neutron security groups %(exc)s") %
|
||||||
|
{'exc': e})
|
||||||
|
LOG.exception(msg)
|
||||||
|
raise exception.NetworkError(msg)
|
||||||
|
|
||||||
|
existing_sec_groups = [sec_group['id'] for sec_group in neutron_sec_groups]
|
||||||
|
missing_sec_groups = set(security_groups) - set(existing_sec_groups)
|
||||||
|
if missing_sec_groups:
|
||||||
|
msg = (_('Security Groups specified in Ironic config '
|
||||||
|
'%(ir-sg)s are not found') %
|
||||||
|
{'ir-sg': list(missing_sec_groups)})
|
||||||
|
LOG.exception(msg)
|
||||||
|
raise exception.NetworkError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def add_ports_to_network(task, network_uuid, is_flat=False,
|
||||||
|
security_groups=None):
|
||||||
"""Create neutron ports to boot the ramdisk.
|
"""Create neutron ports to boot the ramdisk.
|
||||||
|
|
||||||
Create neutron ports for each pxe_enabled port on task.node to boot
|
Create neutron ports for each pxe_enabled port on task.node to boot
|
||||||
@ -78,12 +101,17 @@ def add_ports_to_network(task, network_uuid, is_flat=False):
|
|||||||
:param network_uuid: UUID of a neutron network where ports will be
|
:param network_uuid: UUID of a neutron network where ports will be
|
||||||
created.
|
created.
|
||||||
:param is_flat: Indicates whether it is a flat network or not.
|
:param is_flat: Indicates whether it is a flat network or not.
|
||||||
|
:param security_groups: List of Security Groups UUIDs to be used for
|
||||||
|
network.
|
||||||
:raises: NetworkError
|
:raises: NetworkError
|
||||||
:returns: a dictionary in the form {port.uuid: neutron_port['id']}
|
:returns: a dictionary in the form {port.uuid: neutron_port['id']}
|
||||||
"""
|
"""
|
||||||
client = get_client(task.context.auth_token)
|
client = get_client(task.context.auth_token)
|
||||||
node = task.node
|
node = task.node
|
||||||
|
|
||||||
|
# If Security Groups are specified, verify that they exist
|
||||||
|
_verify_security_groups(security_groups, client)
|
||||||
|
|
||||||
LOG.debug('For node %(node)s, creating neutron ports on network '
|
LOG.debug('For node %(node)s, creating neutron ports on network '
|
||||||
'%(network_uuid)s using %(net_iface)s network interface.',
|
'%(network_uuid)s using %(net_iface)s network interface.',
|
||||||
{'net_iface': task.driver.network.__class__.__name__,
|
{'net_iface': task.driver.network.__class__.__name__,
|
||||||
@ -96,6 +124,8 @@ def add_ports_to_network(task, network_uuid, is_flat=False):
|
|||||||
'device_owner': 'baremetal:none',
|
'device_owner': 'baremetal:none',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if security_groups:
|
||||||
|
body['port']['security_groups'] = security_groups
|
||||||
|
|
||||||
if not is_flat:
|
if not is_flat:
|
||||||
# NOTE(vdrok): It seems that change
|
# NOTE(vdrok): It seems that change
|
||||||
|
@ -54,6 +54,22 @@ opts = [
|
|||||||
help=_('Neutron network UUID for the ramdisk to be booted '
|
help=_('Neutron network UUID for the ramdisk to be booted '
|
||||||
'into for provisioning nodes. Required for "neutron" '
|
'into for provisioning nodes. Required for "neutron" '
|
||||||
'network interface.')),
|
'network interface.')),
|
||||||
|
cfg.ListOpt('provisioning_network_security_groups',
|
||||||
|
default=[],
|
||||||
|
help=_('List of Neutron Security Group UUIDs to be '
|
||||||
|
'applied during provisioning of the nodes. '
|
||||||
|
'Optional for the "neutron" network interface and not '
|
||||||
|
'used for the "flat" or "noop" network interfaces. '
|
||||||
|
'If not specified, default security group '
|
||||||
|
'is used.')),
|
||||||
|
cfg.ListOpt('cleaning_network_security_groups',
|
||||||
|
default=[],
|
||||||
|
help=_('List of Neutron Security Group UUIDs to be '
|
||||||
|
'applied during cleaning of the nodes. '
|
||||||
|
'Optional for the "neutron" network interface and not '
|
||||||
|
'used for the "flat" or "noop" network interfaces. '
|
||||||
|
'If not specified, default security group '
|
||||||
|
'is used.')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,7 +62,8 @@ class NeutronNetwork(base.NetworkInterface):
|
|||||||
LOG.info(_LI('Adding provisioning network to node %s'),
|
LOG.info(_LI('Adding provisioning network to node %s'),
|
||||||
task.node.uuid)
|
task.node.uuid)
|
||||||
vifs = neutron.add_ports_to_network(
|
vifs = neutron.add_ports_to_network(
|
||||||
task, CONF.neutron.provisioning_network_uuid)
|
task, CONF.neutron.provisioning_network_uuid,
|
||||||
|
security_groups=CONF.neutron.provisioning_network_security_groups)
|
||||||
for port in task.ports:
|
for port in task.ports:
|
||||||
if port.uuid in vifs:
|
if port.uuid in vifs:
|
||||||
internal_info = port.internal_info
|
internal_info = port.internal_info
|
||||||
@ -97,8 +98,10 @@ class NeutronNetwork(base.NetworkInterface):
|
|||||||
# If we have left over ports from a previous cleaning, remove them
|
# If we have left over ports from a previous cleaning, remove them
|
||||||
neutron.rollback_ports(task, CONF.neutron.cleaning_network_uuid)
|
neutron.rollback_ports(task, CONF.neutron.cleaning_network_uuid)
|
||||||
LOG.info(_LI('Adding cleaning network to node %s'), task.node.uuid)
|
LOG.info(_LI('Adding cleaning network to node %s'), task.node.uuid)
|
||||||
|
security_groups = CONF.neutron.cleaning_network_security_groups
|
||||||
vifs = neutron.add_ports_to_network(task,
|
vifs = neutron.add_ports_to_network(task,
|
||||||
CONF.neutron.cleaning_network_uuid)
|
CONF.neutron.cleaning_network_uuid,
|
||||||
|
security_groups=security_groups)
|
||||||
for port in task.ports:
|
for port in task.ports:
|
||||||
if port.uuid in vifs:
|
if port.uuid in vifs:
|
||||||
internal_info = port.internal_info
|
internal_info = port.internal_info
|
||||||
|
@ -136,7 +136,8 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
|
|||||||
patcher.start()
|
patcher.start()
|
||||||
self.addCleanup(patcher.stop)
|
self.addCleanup(patcher.stop)
|
||||||
|
|
||||||
def _test_add_ports_to_vlan_network(self, is_client_id):
|
def _test_add_ports_to_vlan_network(self, is_client_id,
|
||||||
|
security_groups=None):
|
||||||
# Ports will be created only if pxe_enabled is True
|
# Ports will be created only if pxe_enabled is True
|
||||||
object_utils.create_test_port(
|
object_utils.create_test_port(
|
||||||
self.context, node_id=self.node.id,
|
self.context, node_id=self.node.id,
|
||||||
@ -164,6 +165,9 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if security_groups:
|
||||||
|
expected_body['port']['security_groups'] = security_groups
|
||||||
|
|
||||||
if is_client_id:
|
if is_client_id:
|
||||||
expected_body['port']['extra_dhcp_opts'] = (
|
expected_body['port']['extra_dhcp_opts'] = (
|
||||||
[{'opt_name': 'client-id', 'opt_value': self._CLIENT_ID}])
|
[{'opt_name': 'client-id', 'opt_value': self._CLIENT_ID}])
|
||||||
@ -172,13 +176,80 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
|
|||||||
'port': self.neutron_port}
|
'port': self.neutron_port}
|
||||||
expected = {port.uuid: self.neutron_port['id']}
|
expected = {port.uuid: self.neutron_port['id']}
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
ports = neutron.add_ports_to_network(task, self.network_uuid)
|
ports = neutron.add_ports_to_network(
|
||||||
|
task, self.network_uuid, security_groups=security_groups)
|
||||||
self.assertEqual(expected, ports)
|
self.assertEqual(expected, ports)
|
||||||
self.client_mock.create_port.assert_called_once_with(
|
self.client_mock.create_port.assert_called_once_with(
|
||||||
expected_body)
|
expected_body)
|
||||||
|
|
||||||
def test_add_ports_to_vlan_network(self):
|
def test_add_ports_to_vlan_network(self):
|
||||||
self._test_add_ports_to_vlan_network(is_client_id=False)
|
self._test_add_ports_to_vlan_network(is_client_id=False,
|
||||||
|
security_groups=None)
|
||||||
|
|
||||||
|
@mock.patch.object(neutron, '_verify_security_groups')
|
||||||
|
def test_add_ports_to_vlan_network_with_sg(self, verify_mock):
|
||||||
|
sg_ids = []
|
||||||
|
for i in range(2):
|
||||||
|
sg_ids.append(uuidutils.generate_uuid())
|
||||||
|
self._test_add_ports_to_vlan_network(is_client_id=False,
|
||||||
|
security_groups=sg_ids)
|
||||||
|
|
||||||
|
def test_verify_sec_groups(self):
|
||||||
|
sg_ids = []
|
||||||
|
for i in range(2):
|
||||||
|
sg_ids.append(uuidutils.generate_uuid())
|
||||||
|
|
||||||
|
expected_vals = {'security_groups': []}
|
||||||
|
for sg in sg_ids:
|
||||||
|
expected_vals['security_groups'].append({'id': sg})
|
||||||
|
|
||||||
|
client = mock.MagicMock()
|
||||||
|
client.list_security_groups.return_value = expected_vals
|
||||||
|
|
||||||
|
self.assertIsNone(
|
||||||
|
neutron._verify_security_groups(sg_ids, client))
|
||||||
|
|
||||||
|
def test_verify_sec_groups_less_than_configured(self):
|
||||||
|
sg_ids = []
|
||||||
|
for i in range(2):
|
||||||
|
sg_ids.append(uuidutils.generate_uuid())
|
||||||
|
|
||||||
|
expected_vals = {'security_groups': []}
|
||||||
|
for sg in sg_ids:
|
||||||
|
expected_vals['security_groups'].append({'id': sg})
|
||||||
|
|
||||||
|
client = mock.MagicMock()
|
||||||
|
client.list_security_groups.return_value = expected_vals
|
||||||
|
|
||||||
|
self.assertIsNone(
|
||||||
|
neutron._verify_security_groups(sg_ids[:1], client))
|
||||||
|
|
||||||
|
def test_verify_sec_groups_more_than_configured(self):
|
||||||
|
sg_ids = []
|
||||||
|
for i in range(1):
|
||||||
|
sg_ids.append(uuidutils.generate_uuid())
|
||||||
|
|
||||||
|
client = mock.MagicMock()
|
||||||
|
expected_vals = {'security_groups': []}
|
||||||
|
client.list_security_groups.return_value = expected_vals
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exception.NetworkError,
|
||||||
|
neutron._verify_security_groups, sg_ids, client)
|
||||||
|
|
||||||
|
def test_verify_sec_groups_exception_by_neutronclient(self):
|
||||||
|
sg_ids = []
|
||||||
|
for i in range(2):
|
||||||
|
sg_ids.append(uuidutils.generate_uuid())
|
||||||
|
|
||||||
|
client = mock.MagicMock()
|
||||||
|
client.list_security_groups.side_effect = \
|
||||||
|
neutron_client_exc.NeutronClientException
|
||||||
|
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
exception.NetworkError,
|
||||||
|
"Could not retrieve neutron security groups",
|
||||||
|
neutron._verify_security_groups, sg_ids, client)
|
||||||
|
|
||||||
def test_add_ports_with_client_id_to_vlan_network(self):
|
def test_add_ports_with_client_id_to_vlan_network(self):
|
||||||
self._test_add_ports_to_vlan_network(is_client_id=True)
|
self._test_add_ports_to_vlan_network(is_client_id=True)
|
||||||
|
@ -65,7 +65,31 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
|
|||||||
rollback_mock.assert_called_once_with(
|
rollback_mock.assert_called_once_with(
|
||||||
task, CONF.neutron.provisioning_network_uuid)
|
task, CONF.neutron.provisioning_network_uuid)
|
||||||
add_ports_mock.assert_called_once_with(
|
add_ports_mock.assert_called_once_with(
|
||||||
|
task, CONF.neutron.provisioning_network_uuid,
|
||||||
|
security_groups=[])
|
||||||
|
self.port.refresh()
|
||||||
|
self.assertEqual(self.neutron_port['id'],
|
||||||
|
self.port.internal_info['provisioning_vif_port_id'])
|
||||||
|
|
||||||
|
@mock.patch.object(neutron_common, 'rollback_ports')
|
||||||
|
@mock.patch.object(neutron_common, 'add_ports_to_network')
|
||||||
|
def test_add_provisioning_network_with_sg(self, add_ports_mock,
|
||||||
|
rollback_mock):
|
||||||
|
sg_ids = []
|
||||||
|
for i in range(2):
|
||||||
|
sg_ids.append(uuidutils.generate_uuid())
|
||||||
|
|
||||||
|
self.config(provisioning_network_security_groups=sg_ids,
|
||||||
|
group='neutron')
|
||||||
|
add_ports_mock.return_value = {self.port.uuid: self.neutron_port['id']}
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
self.interface.add_provisioning_network(task)
|
||||||
|
rollback_mock.assert_called_once_with(
|
||||||
task, CONF.neutron.provisioning_network_uuid)
|
task, CONF.neutron.provisioning_network_uuid)
|
||||||
|
add_ports_mock.assert_called_once_with(
|
||||||
|
task, CONF.neutron.provisioning_network_uuid,
|
||||||
|
security_groups=(
|
||||||
|
CONF.neutron.provisioning_network_security_groups))
|
||||||
self.port.refresh()
|
self.port.refresh()
|
||||||
self.assertEqual(self.neutron_port['id'],
|
self.assertEqual(self.neutron_port['id'],
|
||||||
self.port.internal_info['provisioning_vif_port_id'])
|
self.port.internal_info['provisioning_vif_port_id'])
|
||||||
@ -94,6 +118,26 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
|
|||||||
self.assertEqual(self.neutron_port['id'],
|
self.assertEqual(self.neutron_port['id'],
|
||||||
self.port.internal_info['cleaning_vif_port_id'])
|
self.port.internal_info['cleaning_vif_port_id'])
|
||||||
|
|
||||||
|
@mock.patch.object(neutron_common, 'rollback_ports')
|
||||||
|
@mock.patch.object(neutron_common, 'add_ports_to_network')
|
||||||
|
def test_add_cleaning_network_with_sg(self, add_ports_mock, rollback_mock):
|
||||||
|
add_ports_mock.return_value = {self.port.uuid: self.neutron_port['id']}
|
||||||
|
sg_ids = []
|
||||||
|
for i in range(2):
|
||||||
|
sg_ids.append(uuidutils.generate_uuid())
|
||||||
|
self.config(cleaning_network_security_groups=sg_ids, group='neutron')
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
res = self.interface.add_cleaning_network(task)
|
||||||
|
add_ports_mock.assert_called_once_with(
|
||||||
|
task, CONF.neutron.cleaning_network_uuid,
|
||||||
|
security_groups=CONF.neutron.cleaning_network_security_groups)
|
||||||
|
rollback_mock.assert_called_once_with(
|
||||||
|
task, CONF.neutron.cleaning_network_uuid)
|
||||||
|
self.assertEqual(res, add_ports_mock.return_value)
|
||||||
|
self.port.refresh()
|
||||||
|
self.assertEqual(self.neutron_port['id'],
|
||||||
|
self.port.internal_info['cleaning_vif_port_id'])
|
||||||
|
|
||||||
@mock.patch.object(neutron_common, 'remove_ports_from_network')
|
@mock.patch.object(neutron_common, 'remove_ports_from_network')
|
||||||
def test_remove_cleaning_network(self, remove_ports_mock):
|
def test_remove_cleaning_network(self, remove_ports_mock):
|
||||||
self.port.internal_info = {'cleaning_vif_port_id': 'vif-port-id'}
|
self.port.internal_info = {'cleaning_vif_port_id': 'vif-port-id'}
|
||||||
|
10
releasenotes/notes/security_groups-b57a5d6c30c2fae4.yaml
Normal file
10
releasenotes/notes/security_groups-b57a5d6c30c2fae4.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Adds support for security groups for the provisioning and cleaning
|
||||||
|
network. These are optionally specified by the configuration options
|
||||||
|
``[neutron]/provisioning_network_security_groups`` and
|
||||||
|
``[neutron]/cleaning_network_security_groups``, respectively.
|
||||||
|
If not specified,
|
||||||
|
the default security group for the network is used. These options are only
|
||||||
|
applicable for nodes using the "neutron" network interface. These options
|
||||||
|
are ignored for nodes using the "flat" and "noop" network interfaces.
|
Loading…
x
Reference in New Issue
Block a user