Skip to content

Commit 6ec8670

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add VLAN and Port allowlist"
2 parents 68fc9b0 + f0655a9 commit 6ec8670

6 files changed

Lines changed: 314 additions & 40 deletions

File tree

doc/source/configuration.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Switch configuration format::
1616
password = <credential password>
1717
key_file = <ssh key file>
1818
secret = <enable secret>
19+
ngs_allowed_vlans = <comma-separated list of allowed vlans for switch>
20+
ngs_allowed_ports = <comma-separated list of allowed ports for switch>
1921

2022
# If set ngs_port_default_vlan to default_vlan, switch's
2123
# interface will restore the default_vlan.
@@ -24,6 +26,10 @@ Switch configuration format::
2426
The ``device_type`` entry is mandatory. Most other configuration entries
2527
are optional, see below.
2628

29+
The two new optional configuration parameters ``ngs_allowed_vlans`` and
30+
``ngs_allowed_ports`` have been introduced to manage allowed VLANs and ports
31+
on switches. If not set, all ports or VLANS are allowed.
32+
2733
.. note::
2834

2935
Switch will be selected by local_link_connection/switch_info

networking_generic_switch/devices/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@
5454
{'name': 'ngs_fake_sleep_min_s'},
5555
{'name': 'ngs_fake_sleep_max_s'},
5656
{'name': 'ngs_fake_failure_prob'},
57+
# Allow list for VLANs and ports for this switch
58+
# default open, but setting empty string blocks all ports
59+
{'name': 'ngs_allowed_vlans'},
60+
{'name': 'ngs_allowed_ports'},
5761
]
5862

5963

@@ -179,6 +183,37 @@ def _batch_requests(self):
179183
return strutils.bool_from_string(
180184
self.ngs_config['ngs_batch_requests'])
181185

186+
def _get_allowed_vlans(self):
187+
allowed_vlans = self.ngs_config.get('ngs_allowed_vlans')
188+
if allowed_vlans is None:
189+
return None
190+
return allowed_vlans.split(',')
191+
192+
def _get_allowed_ports(self):
193+
allowed_ports = self.ngs_config.get('ngs_allowed_ports')
194+
if allowed_ports is None:
195+
return None
196+
return allowed_ports.split(',')
197+
198+
def is_allowed(self, port_id, segmentation_id):
199+
is_port_id_allowed = True
200+
allowed_ports = self._get_allowed_ports()
201+
if allowed_ports is not None:
202+
is_port_id_allowed = port_id in allowed_ports
203+
LOG.debug("Port %(port_id) allowed: %(is_port_id_allowed",
204+
{"port_id": port_id,
205+
"is_port_id_allowed": is_port_id_allowed})
206+
207+
is_vlan_allowed = True
208+
allowed_vlans = self._get_allowed_vlans()
209+
if allowed_vlans is not None:
210+
is_vlan_allowed = str(segmentation_id) in allowed_vlans
211+
LOG.debug("VLAN %(vlan) allowed: %(is_allowed",
212+
{"vlan": segmentation_id,
213+
"is_allowed": is_vlan_allowed})
214+
215+
return is_port_id_allowed and is_vlan_allowed
216+
182217
@abc.abstractmethod
183218
def add_network(self, segmentation_id, network_id):
184219
pass

networking_generic_switch/generic_switch_mech.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,47 @@ def bind_port(self, context):
477477
if not self._is_link_valid(port, network):
478478
return
479479

480-
segments = context.segments_to_bind
480+
is_802_3ad = self._is_802_3ad(port)
481+
for link in local_link_information:
482+
port_id = link.get('port_id')
483+
switch_info = link.get('switch_info')
484+
switch_id = link.get('switch_id')
485+
switch = device_utils.get_switch_device(
486+
self.switches, switch_info=switch_info,
487+
ngs_mac_address=switch_id)
488+
489+
segments = context.segments_to_bind
490+
# If segmentation ID is None, set vlan 1
491+
segmentation_id = segments[0].get('segmentation_id') or 1
492+
493+
# Fail if port or vlan not in allow list
494+
if not switch.is_allowed(port_id, segmentation_id):
495+
LOG.warn("Skipped binding port %(port_id)s, "
496+
"port %(port)s in segment "
497+
"%(segment_id)s on device %(device)s, as either "
498+
"the port or vlan is not on the allow list",
499+
{'port_id': port['id'], 'port': port_id,
500+
'device': switch_info,
501+
'segment_id': segmentation_id})
502+
return
503+
504+
LOG.debug("Putting port %(port_id)s on %(switch_info)s "
505+
"to vlan: %(segmentation_id)s",
506+
{'port_id': port_id, 'switch_info': switch_info,
507+
'segmentation_id': segmentation_id})
508+
# Move port to network
509+
if is_802_3ad and hasattr(switch, 'plug_bond_to_network'):
510+
switch.plug_bond_to_network(port_id, segmentation_id)
511+
else:
512+
switch.plug_port_to_network(port_id, segmentation_id)
513+
LOG.info("Successfully bound port %(port_id)s in segment "
514+
"%(segment_id)s on device %(device)s",
515+
{'port_id': port['id'], 'device': switch_info,
516+
'segment_id': segmentation_id})
517+
481518
context.set_binding(segments[0][api.ID],
482519
portbindings.VIF_TYPE_OTHER, {})
520+
483521
provisioning_blocks.add_provisioning_component(
484522
context._plugin_context, port['id'], resources.PORT,
485523
GENERIC_SWITCH_ENTITY)

networking_generic_switch/tests/unit/test_devices.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ def test_driver_ngs_config(self):
118118
"ngs_physical_networks": "physnet1,physnet2",
119119
"ngs_port_default_vlan": "20",
120120
"ngs_disable_inactive_ports": "true",
121-
"ngs_network_name_format": "net-{network_id}"}
121+
"ngs_network_name_format": "net-{network_id}",
122+
"ngs_allowed_vlans": "123,124",
123+
"ngs_allowed_ports": "Ethernet1/1,Ethernet1/2"}
122124
device = devices.device_manager(device_cfg)
123125
self.assertIsInstance(device, devices.GenericSwitchDevice)
124126
self.assertNotIn('ngs_mac_address', device.config)
@@ -127,6 +129,8 @@ def test_driver_ngs_config(self):
127129
self.assertNotIn('ngs_trunk_ports', device.config)
128130
self.assertNotIn('ngs_physical_networks', device.config)
129131
self.assertNotIn('ngs_port_default_vlan', device.config)
132+
self.assertNotIn('ngs_allowed_vlans', device.config)
133+
self.assertNotIn('ngs_allowed_ports', device.config)
130134
self.assertEqual('aa:bb:cc:dd:ee:ff',
131135
device.ngs_config['ngs_mac_address'])
132136
self.assertEqual('120', device.ngs_config['ngs_ssh_connect_timeout'])
@@ -139,6 +143,49 @@ def test_driver_ngs_config(self):
139143
device.ngs_config['ngs_disable_inactive_ports'])
140144
self.assertEqual('net-{network_id}',
141145
device.ngs_config['ngs_network_name_format'])
146+
self.assertEqual("123,124",
147+
device.ngs_config['ngs_allowed_vlans'])
148+
self.assertEqual(["123", "124"],
149+
device._get_allowed_vlans())
150+
self.assertEqual("Ethernet1/1,Ethernet1/2",
151+
device.ngs_config['ngs_allowed_ports'])
152+
self.assertEqual(["Ethernet1/1", "Ethernet1/2"],
153+
device._get_allowed_ports())
154+
155+
def test_driver_ngs_is_allowed(self):
156+
device_cfg = {"device_type": 'netmiko_ovs_linux',
157+
"ngs_allowed_vlans": "123,124",
158+
"ngs_allowed_ports": "Ethernet1/1,Ethernet1/2"}
159+
device = devices.device_manager(device_cfg)
160+
161+
# test all allowed
162+
self.assertTrue(device.is_allowed("Ethernet1/1", 123))
163+
self.assertTrue(device.is_allowed("Ethernet1/2", 124))
164+
# fail on vlan
165+
self.assertFalse(device.is_allowed("Ethernet1/2", 125))
166+
# fail on port
167+
self.assertFalse(device.is_allowed("Ethernet1/3", 124))
168+
# fail on both
169+
self.assertFalse(device.is_allowed("Ethernet2/2", 1))
170+
171+
def test_driver_ngs_is_allowed_fails_on_empty_ports(self):
172+
device_cfg = {"device_type": 'netmiko_ovs_linux',
173+
"ngs_allowed_vlans": "123",
174+
"ngs_allowed_ports": ""}
175+
device = devices.device_manager(device_cfg)
176+
self.assertFalse(device.is_allowed("Ethernet1/1", 123))
177+
178+
def test_driver_ngs_is_allowed_fails_on_empty_vlans(self):
179+
device_cfg = {"device_type": 'netmiko_ovs_linux',
180+
"ngs_allowed_vlans": "",
181+
"ngs_allowed_ports": "Ethernet1/1"}
182+
device = devices.device_manager(device_cfg)
183+
self.assertFalse(device.is_allowed("Ethernet1/1", 123))
184+
185+
def test_driver_ngs_is_allowed_default(self):
186+
device_cfg = {"device_type": 'netmiko_ovs_linux'}
187+
device = devices.device_manager(device_cfg)
188+
self.assertTrue(device.is_allowed("Ethernet1/1", 123))
142189

143190
def test_driver_ngs_config_defaults(self):
144191
device_cfg = {"device_type": 'netmiko_ovs_linux'}
@@ -153,6 +200,8 @@ def test_driver_ngs_config_defaults(self):
153200
self.assertFalse(device.ngs_config['ngs_disable_inactive_ports'])
154201
self.assertEqual('{network_id}',
155202
device.ngs_config['ngs_network_name_format'])
203+
self.assertNotIn('ngs_allowed_vlans', device.ngs_config)
204+
self.assertNotIn('ngs_allowed_ports', device.ngs_config)
156205

157206
def test__get_trunk_ports(self):
158207
device_cfg = {"ngs_trunk_ports": 'port1, Po 1/30,port42'}

0 commit comments

Comments
 (0)