Skip to content
This repository was archived by the owner on Apr 27, 2022. It is now read-only.

Commit 1248996

Browse files
authored
Merge pull request #882 from naved001/legal-switch-ops
Introduce a method in the switch drivers that check if an operation i…
2 parents 03c12b7 + 8811824 commit 1248996

File tree

5 files changed

+198
-2
lines changed

5 files changed

+198
-2
lines changed

docs/network-drivers.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ more valid interface types.
229229
The switch's API server either runs on port 8008 (HTTP) or 8888 (HTTPS), so be
230230
sure to specify that in the ``hostname``.
231231

232+
This switch must have a native VLAN connected first before having any trunked
233+
VLANs. The switchport is turned on only when a native VLAN is connected.
234+
232235
If you have multiple types of ports on the same switch, register the switch
233236
multiple times with different parameters for ``interface_type``.
234237

@@ -247,6 +250,10 @@ The body of the api call request will look like:
247250
It accepts interface names the same way they would be accepted in the console
248251
of the switch, ex. ``1/3``.
249252

253+
When a port is registered, ensure that it is turned off (otherwise it might be
254+
sitting on a default native vlan). HIL will then take care of turning on/off
255+
the port.
256+
250257
### Using multiple switches
251258

252259
Networks managed by HIL may span multiple switches. No special configuration

hil/api.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,9 @@ def _have_attachment(nic, query):
419419
raise errors.BadArgumentError(
420420
"Channel %r, is not legal for this network." % channel)
421421

422+
switch = nic.port.owner
423+
switch.ensure_legal_operation(nic, 'connect', channel)
424+
422425
db.session.add(model.NetworkingAction(type='modify_port',
423426
nic=nic,
424427
new_network=network,
@@ -457,6 +460,10 @@ def node_detach_network(node, nic, network):
457460
if attachment is None:
458461
raise errors.BadArgumentError(
459462
"The network is not attached to the nic.")
463+
464+
switch = nic.port.owner
465+
switch.ensure_legal_operation(nic, 'detach', attachment.channel)
466+
460467
db.session.add(model.NetworkingAction(type='modify_port',
461468
nic=nic,
462469
channel=attachment.channel,

hil/ext/switches/dellnos9.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
import requests
2424
import schema
2525

26+
from hil import model
2627
from hil.model import db, Switch, SwitchSession
27-
from hil.errors import BadArgumentError
28+
from hil.errors import BadArgumentError, BlockedError
2829
from hil.model import BigIntegerType
2930
from hil.network_allocator import get_network_allocator
3031

@@ -61,6 +62,25 @@ def validate(kwargs):
6162
def session(self):
6263
return self
6364

65+
def ensure_legal_operation(self, nic, op_type, channel):
66+
# get the network attachments for <nic> from the database
67+
table = model.NetworkAttachment
68+
query = db.session.query(table).filter(table.nic_id == nic.id)
69+
70+
if channel != 'vlan/native' and op_type == 'connect' and \
71+
query.filter(table.channel == 'vlan/native').count() == 0:
72+
# checks if it is trying to attach a trunked network, and then in
73+
# in the db see if nic does not have any networks attached natively
74+
raise BlockedError("Please attach a native network first")
75+
elif channel == 'vlan/native' and op_type == 'detach' and \
76+
query.filter(table.channel != 'vlan/native').count() > 0:
77+
# if it is detaching a network, then check in the database if there
78+
# are any trunked vlans.
79+
raise BlockedError("Please remove all trunked Vlans"
80+
" before removing the native vlan")
81+
else:
82+
return
83+
6484
@staticmethod
6585
def validate_port_name(port):
6686
"""Valid port names for this switch are of the form 1/0/1 or 1/2"""
@@ -81,6 +101,7 @@ def modify_port(self, port, channel, new_network):
81101
if channel == 'vlan/native':
82102
if new_network is None:
83103
self._remove_native_vlan(interface)
104+
self._port_shutdown(interface)
84105
else:
85106
self._set_native_vlan(interface, new_network)
86107
else:
@@ -99,6 +120,7 @@ def revert_port(self, port):
99120
self._remove_all_vlans_from_trunk(port)
100121
if self._get_native_vlan(port) is not None:
101122
self._remove_native_vlan(port)
123+
self._port_shutdown(port)
102124

103125
def get_port_networks(self, ports):
104126
response = {}
@@ -128,6 +150,8 @@ def _get_vlans(self, interface):
128150
# worked reliably in the first place) and then find our interface there
129151
# which is not feasible.
130152

153+
if not self._is_port_on(interface):
154+
return []
131155
response = self._get_port_info(interface)
132156
# finds a comma separated list of integers starting with "T"
133157
match = re.search(r'T(\d+)((,\d+)?)*', response)
@@ -146,6 +170,8 @@ def _get_native_vlan(self, interface):
146170
147171
Similar to _get_vlans()
148172
"""
173+
if not self._is_port_on(interface):
174+
return None
149175
response = self._get_port_info(interface)
150176
match = re.search(r'NativeVlanId:(\d+)\.', response)
151177
if match is not None:
@@ -179,7 +205,6 @@ def _get_port_info(self, interface):
179205
\r\n\r\n Native Vlan Id: 1512.\r\n\r\n\r\n\r\n
180206
MOC-Dell-S3048-ON#</command>\n</output>\n"
181207
"""
182-
183208
command = 'interfaces switchport %s %s' % \
184209
(self.interface_type, interface)
185210
response = self._execute(interface, SHOW, command)

hil/model.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,18 @@ def session(self):
246246
and have ``session`` just return ``self``.
247247
"""
248248

249+
def ensure_legal_operation(self, nic, op_type, channel):
250+
"""Checks with the switch if the operation is legal before queueing it.
251+
252+
channel is network channel
253+
nic is Nic object
254+
op_type is type of operation (connect, detach)
255+
256+
Some drivers don't need this check at all. So the default behaviour is
257+
to just return"""
258+
259+
return
260+
249261

250262
class SwitchSession(object):
251263
"""A session object for a switch.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Copyright 2017 Massachusetts Open Cloud Contributors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the
5+
# License. You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing,
10+
# software distributed under the License is distributed on an "AS
11+
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
# express or implied. See the License for the specific language
13+
# governing permissions and limitations under the License.
14+
"""Unit tests for dell switches running Dell Networking OS 9 (with REST API)"""
15+
16+
import pytest
17+
18+
from hil import model, api, config
19+
from hil.model import db
20+
from hil.test_common import config_testsuite, config_merge, fresh_database, \
21+
fail_on_log_warnings, with_request_context, server_init, \
22+
network_create_simple
23+
from hil.errors import BlockedError
24+
25+
fail_on_log_warnings = pytest.fixture(autouse=True)(fail_on_log_warnings)
26+
fresh_database = pytest.fixture(fresh_database)
27+
server_init = pytest.fixture(server_init)
28+
with_request_context = pytest.yield_fixture(with_request_context)
29+
30+
SWITCH_TYPE = 'http://schema.massopencloud.org/haas/v0/switches/dellnos9'
31+
32+
33+
@pytest.fixture
34+
def configure():
35+
"""Configure HIL"""
36+
config_testsuite()
37+
config_merge({
38+
'auth': {
39+
'require_authentication': 'True',
40+
},
41+
'extensions': {
42+
'hil.ext.auth.null': '',
43+
'hil.ext.switches.dellnos9': '',
44+
'hil.ext.obm.mock': '',
45+
'hil.ext.network_allocators.null': None,
46+
'hil.ext.network_allocators.vlan_pool': '',
47+
},
48+
'hil.ext.network_allocators.vlan_pool': {
49+
'vlans': '40-80',
50+
},
51+
})
52+
config.load_extensions()
53+
54+
55+
default_fixtures = ['fail_on_log_warnings',
56+
'configure',
57+
'fresh_database',
58+
'server_init',
59+
'with_request_context']
60+
61+
pytestmark = pytest.mark.usefixtures(*default_fixtures)
62+
63+
64+
def mock_networking_action():
65+
"""performs the required db operations and clears up the networking action
66+
queue, so that we can queue more items to test the api.the
67+
68+
This is useful because calling deferred.apply_networking would require a
69+
real switch
70+
"""
71+
action = db.session.query(model.NetworkingAction) \
72+
.order_by(model.NetworkingAction.id).one_or_none()
73+
74+
if action.new_network is None:
75+
db.session.query(model.NetworkAttachment) \
76+
.filter_by(nic=action.nic, channel=action.channel)\
77+
.delete()
78+
else:
79+
db.session.add(model.NetworkAttachment(
80+
nic=action.nic,
81+
network=action.new_network,
82+
channel=action.channel))
83+
84+
db.session.delete(action)
85+
db.session.commit()
86+
87+
88+
def test_ensure_legal_operations():
89+
"""Test to ensure that ensure_legal_operations works as expected"""
90+
91+
# create a project and a network
92+
api.project_create('anvil-nextgen')
93+
network_create_simple('hammernet', 'anvil-nextgen')
94+
network_create_simple('pineapple', 'anvil-nextgen')
95+
96+
# register a switch of type dellnos9 and add a port to it
97+
api.switch_register('s3048',
98+
type=SWITCH_TYPE,
99+
username="switch_user",
100+
password="switch_pass",
101+
hostname="switchname",
102+
interface_type="GigabitEthernet")
103+
api.switch_register_port('s3048', '1/3')
104+
switch = api._must_find(model.Switch, 's3048')
105+
106+
# register a ndoe and a nic
107+
api.node_register('compute-01', obm={
108+
"type": "http://schema.massopencloud.org/haas/v0/obm/mock",
109+
"host": "ipmihost",
110+
"user": "root",
111+
"password": "tapeworm"})
112+
api.project_connect_node('anvil-nextgen', 'compute-01')
113+
api.node_register_nic('compute-01', 'eth0', 'DE:AD:BE:EF:20:14')
114+
nic = api._must_find(model.Nic, 'eth0')
115+
116+
api.port_connect_nic('s3048', '1/3', 'compute-01', 'eth0')
117+
118+
# connecting a trunked network wihtout having a native should fail.
119+
# call the method directly and test the API too.
120+
with pytest.raises(BlockedError):
121+
switch.ensure_legal_operation(nic, 'connect', 'vlan/1212')
122+
123+
with pytest.raises(BlockedError):
124+
api.node_connect_network('compute-01', 'eth0', 'hammernet', 'vlan/40')
125+
126+
# doing these operations in the correct order, that is native network first
127+
# and then trunked, should work.
128+
api.node_connect_network('compute-01', 'eth0', 'hammernet', 'vlan/native')
129+
mock_networking_action()
130+
api.node_connect_network('compute-01', 'eth0', 'pineapple', 'vlan/41')
131+
mock_networking_action()
132+
133+
# removing these networks in the wrong order should not work.
134+
with pytest.raises(BlockedError):
135+
switch.ensure_legal_operation(nic, 'detach', 'vlan/native')
136+
137+
with pytest.raises(BlockedError):
138+
api.node_detach_network('compute-01', 'eth0', 'hammernet')
139+
140+
# removing networks in the right order should work
141+
api.node_detach_network('compute-01', 'eth0', 'pineapple')
142+
mock_networking_action()
143+
api.node_detach_network('compute-01', 'eth0', 'hammernet')
144+
mock_networking_action()
145+
db.session.close()

0 commit comments

Comments
 (0)