Skip to content

Commit 16c23d6

Browse files
committed
fixup! Add a new sdn controller plugin
Signed-off-by: David Morel <david.morel@vates.tech>
1 parent c4f604a commit 16c23d6

File tree

4 files changed

+568
-224
lines changed

4 files changed

+568
-224
lines changed

SOURCES/etc/xapi.d/plugins/sdncontroller.py

Lines changed: 115 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
3-
43
import json
54
import re
65

@@ -26,152 +25,136 @@ def log_and_raise_error(code, desc):
2625
# All functions starting with `parse_` are helper functions compatible with the `Parser` class.
2726
# Each should accept `args` as input and return either (result, None) on success,
2827
# or (None, error_message) on failure.
28+
class Parser:
29+
def __init__(self, args):
30+
self.args = args
31+
self.values = {}
32+
self.errors = []
2933

34+
def parse_bridge(self, args):
35+
BRIDGE_REGEX = re.compile(r"\b\w+\d+\b")
36+
bridge = args.get("bridge")
3037

31-
def parse_bridge(args):
32-
BRIDGE_REGEX = re.compile(r"\b\w+\d+\b")
33-
bridge = args.get("bridge")
34-
35-
if bridge is None:
36-
return None, "bridge parameter is missing"
37-
38-
if not BRIDGE_REGEX.match(bridge):
39-
return None, "'{}' is not a valid bridge name".format(bridge)
40-
41-
return bridge, None
42-
43-
44-
def parse_mac(args):
45-
MAC_REGEX = re.compile(r"^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$")
46-
mac_addr = args.get("mac")
47-
48-
if mac_addr is None:
49-
return None, None # no mac means network wide rule
50-
51-
if not MAC_REGEX.match(mac_addr) or MAC_REGEX.match(mac_addr).group(0) != mac_addr:
52-
return None, "'{}' is not a valid MAC".format(mac_addr)
53-
54-
return mac_addr, None
55-
38+
if bridge is None:
39+
log_and_raise_error(E_PARSER, "bridge parameter is missing")
5640

57-
def parse_iprange(args):
58-
IPRANGE_REGEX = re.compile(
59-
r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}"
60-
r"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/\d{1,2})?$"
61-
)
62-
ip_range = args.get("iprange")
41+
if not BRIDGE_REGEX.match(bridge):
42+
log_and_raise_error(E_PARSER, "'{}' is not a valid bridge name".format(bridge))
6343

64-
if ip_range is None:
65-
return None, "ip range parameter is missing"
44+
return bridge
6645

67-
if not IPRANGE_REGEX.match(ip_range):
68-
return None, "'{}' is not a valid IP range".format(ip_range)
46+
def parse_mac(self, args):
47+
MAC_REGEX = re.compile(r"^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$")
48+
mac_addr = args.get("mac")
6949

70-
return ip_range, None
50+
if mac_addr is None:
51+
return None
7152

53+
if not MAC_REGEX.match(mac_addr) or MAC_REGEX.match(mac_addr).group(0) != mac_addr:
54+
log_and_raise_error(E_PARSER, "'{}' is not a valid MAC".format(mac_addr))
7255

73-
def parse_direction(args):
74-
direction = args.get("direction")
56+
return mac_addr
7557

76-
if direction is None:
77-
return (None, None), "direction parameter is missing"
58+
def parse_iprange(self, args):
59+
IPRANGE_REGEX = re.compile(
60+
r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}"
61+
r"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/\d{1,2})?$"
62+
)
63+
ip_range = args.get("iprange")
7864

79-
dir = direction.lower().split("/")
80-
has_to = "to" in dir
81-
has_from = "from" in dir
65+
if ip_range is None:
66+
log_and_raise_error(E_PARSER, "ip range parameter is missing")
8267

83-
if not (has_to or has_from):
84-
return (None, None), "'{}' is not a valid direction".format(direction)
68+
if not IPRANGE_REGEX.match(ip_range):
69+
log_and_raise_error(E_PARSER, "'{}' is not a valid IP range".format(ip_range))
8570

86-
return (has_to, has_from), None
71+
return ip_range
8772

73+
def parse_direction(self, args):
74+
direction = args.get("direction")
8875

89-
def parse_protocol(args):
90-
protocol = args.get("protocol")
76+
if direction is None:
77+
log_and_raise_error(E_PARSER, "direction parameter is missing")
9178

92-
if protocol is None:
93-
return None, "protocol parameter is missing"
79+
dir = direction.lower().split("/")
80+
has_to = "to" in dir
81+
has_from = "from" in dir
9482

95-
protocol = protocol.lower()
83+
if not (has_to or has_from):
84+
log_and_raise_error(E_PARSER, "'{}' is not a valid direction".format(direction))
9685

97-
if protocol not in VALID_PROTOCOLS:
98-
return None, "'{}' is not a supported protocol".format(protocol)
86+
return (has_to, has_from)
9987

100-
return protocol, None
88+
def parse_protocol(self, args):
89+
protocol = args.get("protocol")
10190

91+
if protocol is None:
92+
log_and_raise_error(E_PARSER, "protocol parameter is missing")
10293

103-
def parse_port(args):
104-
port = args.get("port")
105-
if port is None:
106-
return None, None # Port is optional
94+
protocol = protocol.lower()
10795

108-
try:
109-
p = int(port)
110-
if not (0 <= p <= 65535):
111-
raise ValueError
112-
return port, None
113-
except ValueError:
114-
return None, "'{}' is not a valid port".format(port)
96+
if protocol not in VALID_PROTOCOLS:
97+
log_and_raise_error(E_PARSER, "'{}' is not a supported protocol".format(protocol))
11598

99+
return protocol
116100

117-
def parse_allow(args):
118-
allow = args.get("allow")
119-
if allow is None:
120-
return None, "allow parameter is missing"
101+
def parse_port(self, args):
102+
port = args.get("port")
103+
if port is None:
104+
return None
121105

122-
if allow.lower() not in ["true", "false"]:
123-
return None, "allow parameter should be true or false, not '{}'".format(allow)
106+
try:
107+
p = int(port)
108+
if not (0 <= p <= 65535):
109+
raise ValueError
110+
return port
111+
except ValueError:
112+
log_and_raise_error(E_PARSER, "'{}' is not a valid port".format(port))
124113

125-
if allow.lower() == "true":
126-
return True, None
114+
def parse_allow(self, args):
115+
allow = args.get("allow")
116+
if allow is None:
117+
log_and_raise_error(E_PARSER, "allow parameter is missing")
127118

128-
return False, None
119+
if allow.lower() not in ["true", "false"]:
120+
log_and_raise_error(E_PARSER, "allow parameter should be true or false, not '{}'".format(allow))
129121

130-
def parse_priority(args):
131-
priority = args.get("priority")
132-
if priority is None:
133-
return None, None # Priority is optional
122+
if allow.lower() == "true":
123+
return True
134124

135-
try:
136-
p = int(priority)
137-
if not (0 <= p <= 65535):
138-
raise ValueError
139-
return priority, None
140-
except ValueError:
141-
return None, "'{}' is not a valid priority".format(priority)
125+
return False
142126

127+
def parse_priority(self, args):
128+
priority = args.get("priority")
129+
if priority is None:
130+
return None
143131

144-
class Parser:
145-
def __init__(self, args):
146-
self.args = args
147-
self.values = {}
148-
self.errors = []
132+
try:
133+
p = int(priority)
134+
if not (0 <= p <= 65535):
135+
raise ValueError
136+
return priority
137+
except ValueError:
138+
log_and_raise_error(E_PARSER, "'{}' is not a valid priority".format(priority))
149139

150140
def read(self, key, parse_fn, dests=None):
151141
# parse_fn can return a single value or a tuple of values.
152142
# In this case we are expecting dests to match the expected
153143
# returned tuple
154-
val, err = parse_fn(self.args)
155-
if err:
156-
self.errors.append(err)
157-
return self
144+
val = parse_fn(self.args)
158145

159146
if dests is not None:
160147
if not isinstance(val, tuple):
161-
self.errors.append(
162-
"Internal error: parse {}: doesn't return a tuple while dests is set".format(
163-
key
164-
)
148+
log_and_raise_error(
149+
E_PARSER,
150+
"Internal error: parse {}: doesn't return a tuple while dests is set".format(key)
165151
)
166-
return self
167152

168153
if len(dests) != len(val):
169-
self.errors.append(
170-
"Internal error: parse {}: dests is {} while {} was expected".format(
171-
key, len(dests), len(val)
172-
)
154+
log_and_raise_error(
155+
E_PARSER,
156+
"Internal error: parse {}: dests is {} while {} was expected".format(key, len(dests), len(val))
173157
)
174-
return self
175158

176159
for d, v in zip(dests, val):
177160
self.values[d] = v
@@ -231,17 +214,18 @@ def build_rules_string(args):
231214
def add_rule(_session, args):
232215
_LOGGER.info("Calling add rule with args {}".format(args))
233216

234-
parser = (
235-
Parser(args)
236-
.read("bridge", parse_bridge)
237-
.read("mac", parse_mac)
238-
.read("direction", parse_direction, dests=["has_to", "has_from"])
239-
.read("protocol", parse_protocol)
240-
.read("iprange", parse_iprange)
241-
.read("port", parse_port)
242-
.read("allow", parse_allow)
243-
.read("priority", parse_priority)
244-
)
217+
try:
218+
parser = Parser(args)
219+
parser.read("bridge", parser.parse_bridge)
220+
parser.read("mac", parser.parse_mac)
221+
parser.read("direction", parser.parse_direction, dests=["has_to", "has_from"])
222+
parser.read("protocol", parser.parse_protocol)
223+
parser.read("iprange", parser.parse_iprange)
224+
parser.read("port", parser.parse_port)
225+
parser.read("allow", parser.parse_allow)
226+
parser.read("priority", parser.parse_priority)
227+
except XenAPIPlugin.Failure as e:
228+
log_and_raise_error(E_PARSER, "add_rule: Failed to get parameters: {}".format(e.params[1]))
245229

246230
if parser.errors:
247231
log_and_raise_error(E_PARSER, "add_rule: Failed to get parameters: {}".format(parser.errors))
@@ -274,15 +258,16 @@ def add_rule(_session, args):
274258
def del_rule(_session, args):
275259
_LOGGER.info("Calling delete rule with args {}".format(args))
276260

277-
parser = (
278-
Parser(args)
279-
.read("bridge", parse_bridge)
280-
.read("mac", parse_mac)
281-
.read("destination", parse_direction, dests=["has_to", "has_from"])
282-
.read("protocol", parse_protocol)
283-
.read("iprange", parse_iprange)
284-
.read("port", parse_port)
285-
)
261+
try:
262+
parser = Parser(args)
263+
parser.read("bridge", parser.parse_bridge)
264+
parser.read("mac", parser.parse_mac)
265+
parser.read("destination", parser.parse_direction, dests=["has_to", "has_from"])
266+
parser.read("protocol", parser.parse_protocol)
267+
parser.read("iprange", parser.parse_iprange)
268+
parser.read("port", parser.parse_port)
269+
except XenAPIPlugin.Failure as e:
270+
log_and_raise_error(E_PARSER, "del_rule: Failed to get parameters: {}".format(e.params[1]))
286271

287272
if parser.errors:
288273
log_and_raise_error(E_PARSER, "del_rule: Failed to get parameters: {}".format(parser.errors))
@@ -311,10 +296,11 @@ def del_rule(_session, args):
311296
def dump_flows(_session, args):
312297
_LOGGER.info("Calling dump flows with args {}".format(args))
313298

314-
bridge, err = parse_bridge(args)
315-
316-
if err:
317-
log_and_raise_error(E_PARSER, "dump flows: {}".format(err))
299+
parser = Parser(args)
300+
try:
301+
bridge = parser.parse_bridge(args)
302+
except XenAPIPlugin.Failure as e:
303+
log_and_raise_error(E_PARSER, "dump_flows: Failed to get parameters: {}".format(e.params[1]))
318304

319305
ofctl_cmd = [OVS_OFCTL_CMD, "-O", OPENFLOW_PROTOCOL, "dump-flows", bridge]
320306
cmd = run_command(ofctl_cmd, check=False)

tests/sdncontroller_test_cases/functions.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
'exception': {
1010
'type': XenAPIPlugin.Failure,
1111
'code': '1',
12-
'text': "add_rule: Failed to get parameters: ['bridge parameter is missing', "
13-
"'direction parameter is missing', 'protocol parameter is missing', 'ip range parameter is missing', "
14-
"'allow parameter is missing']"
12+
'text': "add_rule: Failed to get parameters: bridge parameter is missing"
1513
},
1614
'cmd': {
1715
'returncode': 0,
@@ -100,8 +98,7 @@
10098
'exception': {
10199
'type': XenAPIPlugin.Failure,
102100
'code': '1',
103-
'text': "del_rule: Failed to get parameters: ['bridge parameter is missing', "
104-
"'direction parameter is missing', 'protocol parameter is missing', 'ip range parameter is missing']"
101+
'text': "del_rule: Failed to get parameters: bridge parameter is missing"
105102
},
106103
'cmd': {
107104
'returncode': 0,
@@ -171,7 +168,7 @@
171168
'exception': {
172169
'type': XenAPIPlugin.Failure,
173170
'code': '1',
174-
'text': "dump flows: bridge parameter is missing"
171+
'text': "dump_flows: Failed to get parameters: bridge parameter is missing"
175172
},
176173
'cmd': {
177174
'returncode': 0,
@@ -184,7 +181,7 @@
184181
'exception': {
185182
'type': XenAPIPlugin.Failure,
186183
'code': '1',
187-
'text': "dump flows: '' is not a valid bridge name"
184+
'text': "dump_flows: Failed to get parameters: '' is not a valid bridge name"
188185
},
189186
'cmd': {
190187
'returncode': 0,

0 commit comments

Comments
 (0)