Skip to content

Commit 95bbacc

Browse files
committed
Add a new sdn controller plugin
This plugin allows to add and delete rules. A rule will be converted into one or more openflow rules. Once converted they are applied using ovs-vsctl command. To be able to validate the application of openflow rules the plugins allows to dump them. Signed-off-by: Guillaume <[email protected]>
1 parent 7b661ff commit 95bbacc

File tree

2 files changed

+206
-0
lines changed

2 files changed

+206
-0
lines changed

Diff for: README.md

+91
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,97 @@ Restart a service only if it is already running.
338338
$ xe host-call-plugin host-uuid<uuid> plugin=service.py fn=try_restart_service args:service=<service>
339339
```
340340

341+
342+
### `sdn-controller`
343+
344+
Add, delete rules and dump openflow rules.
345+
346+
#### Add rule
347+
348+
Expected parameters when adding a rule are:
349+
- *bridge* : The name of the bridge on XAPI side
350+
- *mac*: The MAC address of the VIF
351+
- *iprange*: A range of IPs, for example `192.168.1.0/24`
352+
- *direction*: can be **from**, **to** or **from/to**
353+
- *to*: means the parametres **port** and **iprange** are to
354+
be used as destination
355+
- *from*: means they will be use as source
356+
- *from/to*: we create 2 openflow rules, on per direction
357+
- *protocol*: IP, TCP, UDP, ICMP or ARP
358+
- *port*: only valid for TCP/UDP protocol
359+
360+
```
361+
$ xe host-call-plugin host-uuid<uuid> plugin=sdn-controller.py \
362+
fn=add-rule \
363+
args:bridge="xenbr0" \
364+
args:mac="6e:0b:9e:72:ab:c6" \
365+
args:iprange="192.168.1.0/24" \
366+
args:direction="from/to" \
367+
args:protocol="tcp" \
368+
args:port="22"
369+
```
370+
371+
##### Delete rule
372+
373+
Expected the same arguments than adding rule
374+
375+
```
376+
$ xe host-call-plugin host-uuid<uuid> plugin=sdn-controller.py \
377+
fn=del-rule \
378+
args:bridge="xenbr0" \
379+
args:mac="6e:0b:9e:72:ab:c6" \
380+
args:iprange="192.168.1.0/24" \
381+
args:direction="from/to" \
382+
args:protocol="tcp" \
383+
args:port="22"
384+
```
385+
386+
##### Dump flows
387+
388+
- This command will return all flows entries in the bridge passed as a parameter.
389+
```
390+
$ xe host-call-plugin host-uuid=<uuid> plugin=sdn-controller.py fn=dump-flows args:bridge=xenbr0 | jq .
391+
{
392+
"returncode": 0,
393+
"command": [
394+
"ovs-ofctl",
395+
"dump-flows",
396+
"xenbr0"
397+
],
398+
"stderr": "",
399+
"stdout": "NXST_FLOW reply (xid=0x4):\n cookie=0x0, duration=248977.339s, table=0, n_packets=24591786, n_bytes=3278442075, idle_age=0, hard_age=65534, priority=0 actions=NORMAL\n"
400+
}
401+
```
402+
403+
- This error is raised when the bridge parameter is missing:
404+
```
405+
$ xe host-call-plugin host-uuid=<uuid> plugin=sdn-controller.py fn=dump-flows | jq .
406+
{
407+
"returncode": 1,
408+
"command": [
409+
"ovs-ofctl",
410+
"dump-flows"
411+
],
412+
"stderr": "bridge parameter is missing",
413+
"stdout": ""
414+
}
415+
```
416+
417+
- If the bridge is unknown, the following error will occur:
418+
```
419+
$ xe host-call-plugin host-uuid=<uuid> plugin=sdn-controller.py args:bridge=xenbr10 fn=dump-flows | jq .
420+
{
421+
"returncode": 1,
422+
"command": [
423+
"ovs-ofctl",
424+
"dump-flows",
425+
"xenbr10"
426+
],
427+
"stderr": "ovs-ofctl: xenbr10 is not a bridge or a socket\n",
428+
"stdout": ""
429+
}
430+
```
431+
341432
## Tests
342433

343434
To run the plugins' unit tests you'll need to install `pytest`, `pyfakefs` and `mock`.

Diff for: SOURCES/etc/xapi.d/plugins/sdn-controller.py

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
4+
import ipaddress
5+
import json
6+
import re
7+
8+
import XenAPIPlugin
9+
10+
from xcpngutils import configure_logging, error_wrapped, run_command
11+
12+
13+
def parse_bridge(args):
14+
bridge = args.get("bridge")
15+
if bridge is None:
16+
raise ValueError("bridge parameter is missing")
17+
return bridge
18+
19+
20+
def parse_mac(args):
21+
MAC_REGEX = re.compile(r"^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$")
22+
mac_addr = args.get("mac", "")
23+
if not bool(MAC_REGEX.fullmatch(mac_addr)):
24+
raise ValueError("mac address is invalid or missing")
25+
26+
return mac_addr
27+
28+
29+
def parse_iprange(args):
30+
ip_range = args.get("iprange", "")
31+
try:
32+
ipaddress.ip_network(ip_range, strict=False)
33+
return ip_range
34+
except ValueError:
35+
raise ValueError("ip range is missing or invalid")
36+
37+
38+
def parse_direction(args):
39+
# Check that direction is set and valid
40+
direction = args.get("direction", "").lower().split("/")
41+
has_to = "to" in direction
42+
has_from = "from" in direction
43+
if not (has_to or has_from):
44+
raise ValueError("valid direction is missing")
45+
return has_to, has_from
46+
47+
48+
def parse_protocol(args):
49+
VALID_PROTOCOLS = {"tcp", "udp", "icmp", "ip"}
50+
protocol = args.get("protocol", "unset").lower()
51+
if protocol not in VALID_PROTOCOLS:
52+
raise ValueError("valid protocol is missing")
53+
return protocol
54+
55+
56+
def parse_port(args):
57+
port = args.get("port", "")
58+
try:
59+
int(port)
60+
return port
61+
except ValueError:
62+
raise ValueError("port is missing or is invalid")
63+
64+
65+
@error_wrapped
66+
def add_rule(_session, args):
67+
_LOGGER.info("Calling add_rule with args {}".format(args))
68+
69+
try:
70+
_bridge = parse_bridge(args)
71+
_mac = parse_mac(args)
72+
_has_to, _has_from = parse_direction(args)
73+
_protocol = parse_protocol(args)
74+
_iprange = parse_iprange(args)
75+
_port = parse_port(args)
76+
except ValueError as e:
77+
_LOGGER.error(str(e))
78+
return json.dumps(
79+
{
80+
"returncode": 1,
81+
"command": "add_rule",
82+
"stderr": str(e),
83+
"stdout": "",
84+
}
85+
)
86+
87+
return json.dumps(True)
88+
89+
90+
@error_wrapped
91+
def del_rule(_session, args):
92+
_LOGGER.info("Calling del_rule with args {}".format(args))
93+
return json.dumps(True)
94+
95+
96+
@error_wrapped
97+
def dump_flows(_session, args):
98+
_LOGGER.info("Calling check with args {}".format(args))
99+
ofctl_cmd = ["ovs-ofctl", "dump-flows"]
100+
bridge = args.get("bridge")
101+
102+
ofctl_cmd.append(bridge)
103+
cmd = run_command(ofctl_cmd, check=False)
104+
return json.dumps(cmd)
105+
106+
107+
_LOGGER = configure_logging("sdn-controller")
108+
if __name__ == "__main__":
109+
XenAPIPlugin.dispatch(
110+
{
111+
"add-rule": add_rule,
112+
"del-rule": del_rule,
113+
"dump-flows": dump_flows,
114+
}
115+
)

0 commit comments

Comments
 (0)