Skip to content

Commit 10e95c1

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 10e95c1

5 files changed

Lines changed: 115 additions & 12 deletions

File tree

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ Add, delete rules and dump openflow rules.
347347

348348
Parameters for adding a rule:
349349
- *bridge* : The name of the bridge to add rule to.
350+
- *cookie* (optional) : Hexdecimal number used to track the rule (in form `0x1234`). Sets to `0x0` by default.
350351
- *priority* (optional): A number between 0 and 65535 for the rule priority.
351352
- *mac* (optional): The MAC address of the VIF to create the rule for, if not
352353
specified, a network-wide rule will be created.
@@ -363,6 +364,7 @@ Parameters for adding a rule:
363364
$ xe host-call-plugin host-uuid<uuid> plugin=sdncontroller.py \
364365
fn=add-rule \
365366
args:bridge="xenbr0" \
367+
args:cookie="0x1234" \
366368
args:priority="100" \
367369
args:mac="6e:0b:9e:72:ab:c6" \
368370
args:ipRange="192.168.1.0/24" \
@@ -372,10 +374,14 @@ $ xe host-call-plugin host-uuid<uuid> plugin=sdncontroller.py \
372374
args:allow="false"
373375
```
374376

377+
Please note that if the same rule is added twice but with a different `cookie`,
378+
the cookie information on th first rule is updated (it follows the OpenvSwitch behaviour).
379+
375380
##### Delete rule
376381

377382
Parameters for removing a rule:
378-
- *bridge* : The name of the bridge to delete the rule from.
383+
- *bridge* : The name of the bridge to delete the rule from.
384+
- *cookie* (optional) : if used, the others parameters are ignored and only the cookie information is used to delete the rule.
379385
- *mac* (optional): The MAC address of the VIF to delete the rule for.
380386
- *ipRange*: An IP or range of IPs in CIDR notation, for example `192.168.1.0/24`.
381387
- *direction*: can be **from**, **to** or **from/to**
@@ -389,6 +395,7 @@ Parameters for removing a rule:
389395
$ xe host-call-plugin host-uuid<uuid> plugin=sdncontroller.py \
390396
fn=del-rule \
391397
args:bridge="xenbr0" \
398+
args:cookie="0x1234" \
392399
args:mac="6e:0b:9e:72:ab:c6" \
393400
args:ipRange="192.168.1.0/24" \
394401
args:direction="from/to" \

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

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,24 @@ def parse_priority(self):
161161
E_PARSER, "'{}' is not a valid priority".format(priority)
162162
)
163163

164+
def parse_cookie(self):
165+
COOKIE_REGEX = re.compile(
166+
r"^0x0*([0-9a-fA-F]{1,16})$"
167+
)
168+
169+
cookie = self.args.get("cookie")
170+
if cookie is None:
171+
return "0x0"
172+
173+
m = COOKIE_REGEX.match(cookie)
174+
if m is None:
175+
log_and_raise_error(
176+
E_PARSER, "'{}' is not a valid cookie".format(cookie)
177+
)
178+
179+
# normalize output (0x01Ff2 => 0x1ff2)
180+
return "0x{}".format(m.group(1).lower())
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,7 @@ 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+
rule += ",cookie={}".format(args["cookie"])
321341
if uplink:
322342
rule += ",dl_vlan={}".format(vlanid)
323343
if ofport:
@@ -342,6 +362,7 @@ def run_ofctl_cmd(cmd, bridge, rule):
342362
% (format(ofctl_cmd), cmd["stderr"]),
343363
)
344364
_LOGGER.info("Applied rule: {}".format(ofctl_cmd))
365+
return cmd["stdout"]
345366

346367

347368
@error_wrapped
@@ -358,6 +379,7 @@ def add_rule(_session, args):
358379
parser.read("port", parser.parse_port)
359380
parser.read("allow", parser.parse_allow)
360381
parser.read("priority", parser.parse_priority)
382+
parser.read("cookie", parser.parse_cookie)
361383
except XenAPIPlugin.Failure as e:
362384
log_and_raise_error(
363385
E_PARSER, "add_rule: Failed to get parameters: {}".format(e.params[1])
@@ -383,6 +405,14 @@ def add_rule(_session, args):
383405
E_PORTS, "No ports found for bridge: {}".format(rule_args["bridge"])
384406
)
385407

408+
# cleanup the cookie (if already used) to 'upgrade' the rule
409+
if rule_args["cookie"] != "0x0":
410+
run_ofctl_cmd(
411+
"del-flows",
412+
rule_args["parent-bridge"],
413+
"cookie={}/-1".format(rule_args["cookie"]),
414+
)
415+
386416
# We can now build the open flow rule
387417
rules = build_rules_strings(rule_args)
388418
_LOGGER.info("Built rules: {}".format(rules))
@@ -408,6 +438,7 @@ def del_rule(_session, args):
408438
parser.read("protocol", parser.parse_protocol)
409439
parser.read("iprange", parser.parse_iprange)
410440
parser.read("port", parser.parse_port)
441+
parser.read("cookie", parser.parse_cookie)
411442
except XenAPIPlugin.Failure as e:
412443
log_and_raise_error(
413444
E_PARSER, "del_rule: Failed to get parameters: {}".format(e.params[1])
@@ -427,12 +458,22 @@ def del_rule(_session, args):
427458
E_PARAMS, "del_rule: No port provided, tcp and udp requires one"
428459
)
429460

461+
# to match on a cookie, need to specify a mask
462+
rule_args["cookie"] = "{}/-1".format(rule_args["cookie"])
463+
430464
update_args_from_ovs(rule_args)
431465

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

470+
else:
471+
# if cookie value is meaningful, use it to remove all related rules
472+
rules = [
473+
"cookie={}".format(rule_args["cookie"]),
474+
]
475+
476+
_LOGGER.info("Built rules: {}".format(rules))
436477
for rule in rules:
437478
run_ofctl_cmd("del-flows", rule_args["parent-bridge"], rule)
438479

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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,3 +370,49 @@
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": "0xe4e59f746a3ce3db"}, "result": "0xe4e59f746a3ce3db", "exception": None},
379+
{"input": {"cookie": "0xFFFFffffFFFFffff"}, "result": "0xffffffffffffffff", "exception": None},
380+
{"input": {}, "result": "0x0", "exception": None},
381+
{
382+
"input": {"cookie": "0x"},
383+
"result": None,
384+
"exception": {
385+
"type": XenAPIPlugin.Failure,
386+
"code": "1",
387+
"text": "'0x' is not a valid cookie",
388+
},
389+
},
390+
{
391+
"input": {"cookie": "1234"},
392+
"result": None,
393+
"exception": {
394+
"type": XenAPIPlugin.Failure,
395+
"code": "1",
396+
"text": "'1234' is not a valid cookie",
397+
},
398+
},
399+
{
400+
"input": {"cookie": "0xAZ"},
401+
"result": None,
402+
"exception": {
403+
"type": XenAPIPlugin.Failure,
404+
"code": "1",
405+
"text": "'0xAZ' is not a valid cookie",
406+
},
407+
},
408+
]
409+
COOKIE_IDS = [
410+
"0x0",
411+
"0x01234",
412+
"0xe4e59f746a3ce3db",
413+
"0xFFFFffffFFFFffff",
414+
"no parameter",
415+
"0x",
416+
"1234",
417+
"0xAZ",
418+
]

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)