Skip to content

Commit 0626819

Browse files
committed
sdn-controller: add optional cookie argument support
if used, set cookie on OF rule to help deleting group of rules Behaviour notes: - add-rule : if the same rule is added twice but with a different cookie, the cookie information is updated (OpenvSwitch behaviour) - del-rule : if cookie is used, others parameters are ignored and only the cookie information is used to delete rules Signed-off-by: Sebastien Marie <semarie@kapouay.eu.org>
1 parent 572dd4d commit 0626819

File tree

4 files changed

+110
-11
lines changed

4 files changed

+110
-11
lines changed

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

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
E_PARAMS = 2
2020
E_OVSCALL = 3
2121
E_PORTS = 4
22+
E_COOKIEINUSE = 5
2223

2324
# rules names per direction
2425
TO = 0
@@ -161,6 +162,23 @@ def parse_priority(self):
161162
E_PARSER, "'{}' is not a valid priority".format(priority)
162163
)
163164

165+
def parse_cookie(self):
166+
COOKIE_REGEX = re.compile(
167+
r"^0x[0-9a-fA-F]{1,16}$"
168+
)
169+
170+
cookie = self.args.get("cookie")
171+
if cookie is None:
172+
return "0x0"
173+
174+
if not COOKIE_REGEX.match(cookie):
175+
log_and_raise_error(
176+
E_PARSER, "'{}' is not a valid cookie".format(cookie)
177+
)
178+
179+
# normalize output (0x01Ff2 => 0x1ff2)
180+
return hex(int(cookie, 16))
181+
164182
def read(self, key, parse_fn, dests=None):
165183
# parse_fn can return a single value or a tuple of values.
166184
# In this case we are expecting dests to match the expected
@@ -303,6 +321,7 @@ def build_rule_string(direction, ofport, args, uplink=False):
303321
rule_parts = {
304322
"priority": ("priority", "priority"),
305323
"protocol": (None, None),
324+
"cookie": ("cookie", "cookie"),
306325
"ofport": ("in_port", "in_port"),
307326
"mac": ("dl_src", "dl_dst"),
308327
"iprange": ("nw_dst", "nw_src"),
@@ -318,6 +337,8 @@ def build_rule_string(direction, ofport, args, uplink=False):
318337
if args.get("priority"):
319338
rule += "priority={}".format(args["priority"]) + ","
320339
rule += args["protocol"]
340+
if args.get("cookie"):
341+
rule += ",cookie={}".format(args["cookie"])
321342
if uplink:
322343
rule += ",dl_vlan={}".format(vlanid)
323344
if ofport:
@@ -342,6 +363,7 @@ def run_ofctl_cmd(cmd, bridge, rule):
342363
% (format(ofctl_cmd), cmd["stderr"]),
343364
)
344365
_LOGGER.info("Applied rule: {}".format(ofctl_cmd))
366+
return cmd["stdout"]
345367

346368

347369
@error_wrapped
@@ -358,6 +380,7 @@ def add_rule(_session, args):
358380
parser.read("port", parser.parse_port)
359381
parser.read("allow", parser.parse_allow)
360382
parser.read("priority", parser.parse_priority)
383+
parser.read("cookie", parser.parse_cookie)
361384
except XenAPIPlugin.Failure as e:
362385
log_and_raise_error(
363386
E_PARSER, "add_rule: Failed to get parameters: {}".format(e.params[1])
@@ -383,6 +406,18 @@ def add_rule(_session, args):
383406
E_PORTS, "No ports found for bridge: {}".format(rule_args["bridge"])
384407
)
385408

409+
# validate cookie isn't already used
410+
if rule_args["cookie"] != "0x0":
411+
out = run_ofctl_cmd(
412+
"dump-flows",
413+
rule_args["parent-bridge"],
414+
"cookie={}/-1".format(rule_args["cookie"]),
415+
)
416+
if len(out.split('\n')) != 2:
417+
log_and_raise_error(
418+
E_COOKIEINUSE, "add_rule: this cookie is already used"
419+
)
420+
386421
# We can now build the open flow rule
387422
rules = build_rules_strings(rule_args)
388423
_LOGGER.info("Built rules: {}".format(rules))
@@ -408,6 +443,7 @@ def del_rule(_session, args):
408443
parser.read("protocol", parser.parse_protocol)
409444
parser.read("iprange", parser.parse_iprange)
410445
parser.read("port", parser.parse_port)
446+
parser.read("cookie", parser.parse_cookie)
411447
except XenAPIPlugin.Failure as e:
412448
log_and_raise_error(
413449
E_PARSER, "del_rule: Failed to get parameters: {}".format(e.params[1])
@@ -427,12 +463,22 @@ def del_rule(_session, args):
427463
E_PARAMS, "del_rule: No port provided, tcp and udp requires one"
428464
)
429465

466+
# to match on a cookie, need to specify a mask
467+
rule_args["cookie"] = "{}/-1".format(rule_args["cookie"])
468+
430469
update_args_from_ovs(rule_args)
431470

432-
# We can now build the open flow rule
433-
rules = build_rules_strings(rule_args)
434-
_LOGGER.info("Built rules: {}".format(rules))
471+
if rule_args["cookie"] == "0x0/-1":
472+
# if no cookie, build the open flow rule
473+
rules = build_rules_strings(rule_args)
435474

475+
else:
476+
# if cookie value is meanful, use it to remove all related rules
477+
rules = [
478+
"cookie={}".format(rule_args["cookie"]),
479+
]
480+
481+
_LOGGER.info("Built rules: {}".format(rules))
436482
for rule in rules:
437483
run_ofctl_cmd("del-flows", rule_args["parent-bridge"], rule)
438484

tests/sdncontroller_test_cases/functions.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@
192192
}, # list-interfaces
193193
],
194194
"calls": [
195-
call("add-flow", "xenbr0", "ip,in_port=5,nw_dst=1.1.1.1,actions=drop"),
195+
call("add-flow", "xenbr0", "ip,cookie=0x0,in_port=5,nw_dst=1.1.1.1,actions=drop"),
196196
],
197197
},
198198
{ # subnet tcp 4242 allow
@@ -221,7 +221,7 @@
221221
call(
222222
"add-flow",
223223
"xenbr0",
224-
"tcp,in_port=5,nw_dst=1.1.1.1/24,tp_dst=4242,actions=normal",
224+
"tcp,cookie=0x0,in_port=5,nw_dst=1.1.1.1/24,tp_dst=4242,actions=normal",
225225
)
226226
],
227227
},
@@ -252,7 +252,7 @@
252252
call(
253253
"add-flow",
254254
"xenbr0",
255-
"udp,dl_dst=DE:AD:BE:EF:CA:FE,nw_src=4.4.4.4,tp_src=2121,actions=drop",
255+
"udp,cookie=0x0,dl_dst=DE:AD:BE:EF:CA:FE,nw_src=4.4.4.4,tp_src=2121,actions=drop",
256256
),
257257
],
258258
},
@@ -283,7 +283,7 @@
283283
"type": XenAPIPlugin.Failure,
284284
"code": "3",
285285
"text": "Error running ovs-ofctl command: ['ovs-ofctl', '-O', 'OpenFlow11', 'add-flow', 'xenbr0', "
286-
"'ip,in_port=5,nw_dst=1.1.1.1/24,actions=drop']: fake error",
286+
"'ip,cookie=0x0,in_port=5,nw_dst=1.1.1.1/24,actions=drop']: fake error",
287287
},
288288
"cmd": [
289289
{"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent
@@ -341,7 +341,7 @@
341341
}, # list-interfaces
342342
],
343343
"calls": [
344-
call("del-flows", "xenbr0", "ip,in_port=5,nw_dst=1.1.1.1"),
344+
call("del-flows", "xenbr0", "ip,cookie=0x0/-1,in_port=5,nw_dst=1.1.1.1"),
345345
],
346346
},
347347
{ # subnet tcp 4242 allow
@@ -370,7 +370,7 @@
370370
call(
371371
"del-flows",
372372
"xenbr0",
373-
"tcp,in_port=5,nw_dst=1.1.1.1/24,tp_dst=4242",
373+
"tcp,cookie=0x0/-1,in_port=5,nw_dst=1.1.1.1/24,tp_dst=4242",
374374
)
375375
],
376376
},
@@ -401,7 +401,7 @@
401401
call(
402402
"del-flows",
403403
"xenbr0",
404-
"udp,dl_dst=DE:AD:BE:EF:CA:FE,nw_src=4.4.4.4,tp_src=2121",
404+
"udp,cookie=0x0/-1,dl_dst=DE:AD:BE:EF:CA:FE,nw_src=4.4.4.4,tp_src=2121",
405405
),
406406
],
407407
},
@@ -432,7 +432,7 @@
432432
"type": XenAPIPlugin.Failure,
433433
"code": "3",
434434
"text": "Error running ovs-ofctl command: ['ovs-ofctl', '-O', 'OpenFlow11', 'del-flows', 'xenbr0', "
435-
"'ip,in_port=5,nw_dst=1.1.1.1/24']: fake error",
435+
"'ip,cookie=0x0/-1,in_port=5,nw_dst=1.1.1.1/24']: fake error",
436436
},
437437
"cmd": [
438438
{"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent

tests/sdncontroller_test_cases/parser.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,3 +370,47 @@
370370
},
371371
]
372372
PRIORITY_IDS = ["100", "0", "65535", "65536", "aoeui", "empty string", "no parameters"]
373+
374+
375+
COOKIE_PARAMS = [
376+
{"input": {"cookie": "0x0"}, "result": "0x0", "exception": None},
377+
{"input": {"cookie": "0x01234"}, "result": "0x1234", "exception": None},
378+
{"input": {"cookie": "0xFFFFffffFFFFffff"}, "result": "0xffffffffffffffff", "exception": None},
379+
{"input": {}, "result": "0x0", "exception": None},
380+
{
381+
"input": {"cookie": "0x"},
382+
"result": None,
383+
"exception": {
384+
"type": XenAPIPlugin.Failure,
385+
"code": "1",
386+
"text": "'0x' is not a valid cookie",
387+
},
388+
},
389+
{
390+
"input": {"cookie": "1234"},
391+
"result": None,
392+
"exception": {
393+
"type": XenAPIPlugin.Failure,
394+
"code": "1",
395+
"text": "'1234' is not a valid cookie",
396+
},
397+
},
398+
{
399+
"input": {"cookie": "0xAZ"},
400+
"result": None,
401+
"exception": {
402+
"type": XenAPIPlugin.Failure,
403+
"code": "1",
404+
"text": "'0xAZ' is not a valid cookie",
405+
},
406+
},
407+
]
408+
COOKIE_IDS = [
409+
"0x0",
410+
"0x01234",
411+
"0xFFFFffffFFFFffff",
412+
"no parameter",
413+
"0x",
414+
"1234",
415+
"0xAZ",
416+
]

tests/test_sdn-controller.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
ALLOW_IDS,
2222
PRIORITY_PARAMS,
2323
PRIORITY_IDS,
24+
COOKIE_PARAMS,
25+
COOKIE_IDS,
2426
)
2527

2628
from sdncontroller_test_cases.functions import (
@@ -112,6 +114,13 @@ def test_parse_priority(self, priority):
112114
p = Parser(priority["input"])
113115
parser_test(p.parse_priority, priority)
114116

117+
@pytest.fixture(params=COOKIE_PARAMS, ids=COOKIE_IDS)
118+
def cookie(self, request):
119+
return request.param
120+
121+
def test_parse_cookie(self, cookie):
122+
p = Parser(cookie["input"])
123+
parser_test(p.parse_cookie, cookie)
115124

116125
@mock.patch("sdncontroller.run_command", autospec=True)
117126
class TestSdnControllerFunctions:

0 commit comments

Comments
 (0)