diff --git a/README.md b/README.md index f23bfc6..8461e8a 100644 --- a/README.md +++ b/README.md @@ -350,9 +350,9 @@ Parameters for adding a rule: - *priority* (optional): A number between 0 and 65535 for the rule priority. - *mac* (optional): The MAC address of the VIF to create the rule for, if not specified, a network-wide rule will be created. -- *iprange*: An IP or range of IPs in CIDR notation, for example `192.168.1.0/24`. +- *ipRange*: An IP or range of IPs in CIDR notation, for example `192.168.1.0/24`. - *direction*: can be **from**, **to** or **from/to** - - *to*: means the parameters for **port** and **iprange** are to be used as destination + - *to*: means the parameters for **port** and **ipRange** are to be used as destination - *from*: means they will be use as source - *from/to*: 2 rules will be created, one per direction - *protocol*: IP, TCP, UDP, ICMP or ARP @@ -365,7 +365,7 @@ $ xe host-call-plugin host-uuid plugin=sdncontroller.py \ args:bridge="xenbr0" \ args:priority="100" \ args:mac="6e:0b:9e:72:ab:c6" \ - args:iprange="192.168.1.0/24" \ + args:ipRange="192.168.1.0/24" \ args:direction="from/to" \ args:protocol="tcp" \ args:port="22" \ @@ -377,9 +377,9 @@ $ xe host-call-plugin host-uuid plugin=sdncontroller.py \ Parameters for removing a rule: - *bridge* : The name of the bridge to delete the rule from. - *mac* (optional): The MAC address of the VIF to delete the rule for. -- *iprange*: An IP or range of IPs in CIDR notation, for example `192.168.1.0/24`. +- *ipRange*: An IP or range of IPs in CIDR notation, for example `192.168.1.0/24`. - *direction*: can be **from**, **to** or **from/to** - - *to*: means the parameters for **port** and **iprange** are to be used as destination + - *to*: means the parameters for **port** and **ipRange** are to be used as destination - *from*: means they will be use as source - *from/to*: 2 rules will be created, one per direction - *protocol*: IP, TCP, UDP, ICMP or ARP @@ -390,7 +390,7 @@ $ xe host-call-plugin host-uuid plugin=sdncontroller.py \ fn=del-rule \ args:bridge="xenbr0" \ args:mac="6e:0b:9e:72:ab:c6" \ - args:iprange="192.168.1.0/24" \ + args:ipRange="192.168.1.0/24" \ args:direction="from/to" \ args:protocol="tcp" \ args:port="22" diff --git a/SOURCES/etc/xapi.d/plugins/sdncontroller.py b/SOURCES/etc/xapi.d/plugins/sdncontroller.py index 4f24ce7..6efe672 100755 --- a/SOURCES/etc/xapi.d/plugins/sdncontroller.py +++ b/SOURCES/etc/xapi.d/plugins/sdncontroller.py @@ -9,19 +9,27 @@ OPENFLOW_PROTOCOL = "OpenFlow11" OVS_OFCTL_CMD = "ovs-ofctl" +OVS_VSCTL_CMD = "ovs-vsctl" VALID_PROTOCOLS = {"tcp", "udp", "icmp", "ip", "arp"} PROTOCOLS_WITH_PORTS = {"tcp", "udp"} +OFPVID_NONE = "0xffff" # error codes E_PARSER = 1 E_PARAMS = 2 -E_BUILDER = 3 -E_OVSCALL = 4 +E_OVSCALL = 3 +E_PORTS = 4 + +# rules names per direction +TO = 0 +FROM = 1 + def log_and_raise_error(code, desc): _LOGGER.error(desc) raise_plugin_error(code, desc) + # All functions starting with `parse_` are helper functions compatible with the `Parser` class. # Each should accept `args` as input and return either (result, None) on success, # or (None, error_message) on failure. @@ -39,7 +47,9 @@ def parse_bridge(self): log_and_raise_error(E_PARSER, "bridge parameter is missing") if not BRIDGE_REGEX.match(bridge): - log_and_raise_error(E_PARSER, "'{}' is not a valid bridge name".format(bridge)) + log_and_raise_error( + E_PARSER, "'{}' is not a valid bridge name".format(bridge) + ) return bridge @@ -50,7 +60,10 @@ def parse_mac(self): if mac_addr is None: return None - if not MAC_REGEX.match(mac_addr) or MAC_REGEX.match(mac_addr).group(0) != mac_addr: + if ( + not MAC_REGEX.match(mac_addr) + or MAC_REGEX.match(mac_addr).group(0) != mac_addr + ): log_and_raise_error(E_PARSER, "'{}' is not a valid MAC".format(mac_addr)) return mac_addr @@ -60,13 +73,15 @@ def parse_iprange(self): r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}" r"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/\d{1,2})?$" ) - ip_range = self.args.get("iprange") + ip_range = self.args.get("ipRange") if ip_range is None: - log_and_raise_error(E_PARSER, "ip range parameter is missing") + log_and_raise_error(E_PARSER, "ipRange parameter is missing") if not IPRANGE_REGEX.match(ip_range): - log_and_raise_error(E_PARSER, "'{}' is not a valid IP range".format(ip_range)) + log_and_raise_error( + E_PARSER, "'{}' is not a valid IP range".format(ip_range) + ) return ip_range @@ -81,7 +96,9 @@ def parse_direction(self): has_from = "from" in dir if not (has_to or has_from): - log_and_raise_error(E_PARSER, "'{}' is not a valid direction".format(direction)) + log_and_raise_error( + E_PARSER, "'{}' is not a valid direction".format(direction) + ) return (has_to, has_from) @@ -94,7 +111,9 @@ def parse_protocol(self): protocol = protocol.lower() if protocol not in VALID_PROTOCOLS: - log_and_raise_error(E_PARSER, "'{}' is not a supported protocol".format(protocol)) + log_and_raise_error( + E_PARSER, "'{}' is not a supported protocol".format(protocol) + ) return protocol @@ -117,7 +136,10 @@ def parse_allow(self): log_and_raise_error(E_PARSER, "allow parameter is missing") if allow.lower() not in ["true", "false"]: - log_and_raise_error(E_PARSER, "allow parameter should be true or false, not '{}'".format(allow)) + log_and_raise_error( + E_PARSER, + "allow parameter should be true or false, not '{}'".format(allow), + ) if allow.lower() == "true": return True @@ -135,7 +157,9 @@ def parse_priority(self): raise ValueError return priority except ValueError: - log_and_raise_error(E_PARSER, "'{}' is not a valid priority".format(priority)) + log_and_raise_error( + E_PARSER, "'{}' is not a valid priority".format(priority) + ) def read(self, key, parse_fn, dests=None): # parse_fn can return a single value or a tuple of values. @@ -147,13 +171,17 @@ def read(self, key, parse_fn, dests=None): if not isinstance(val, tuple): log_and_raise_error( E_PARSER, - "Internal error: parse {}: doesn't return a tuple while dests is set".format(key) + "Internal error: parse {}: doesn't return a tuple while dests is set".format( + key + ), ) if len(dests) != len(val): log_and_raise_error( E_PARSER, - "Internal error: parse {}: dests is {} while {} was expected".format(key, len(dests), len(val)) + "Internal error: parse {}: dests is {} while {} was expected".format( + key, len(dests), len(val) + ), ) for d, v in zip(dests, val): @@ -163,6 +191,7 @@ def read(self, key, parse_fn, dests=None): self.values[key] = val + def json_error(name, desc): return json.dumps( { @@ -173,40 +202,147 @@ def json_error(name, desc): } ) -def build_rules_string(args): + +def run_vsctl_cmd(args): + vsctl_cmd = [OVS_VSCTL_CMD] + args + cmd = run_command(vsctl_cmd, check=False) + if cmd["returncode"] != 0: + log_and_raise_error( + E_OVSCALL, + "Error running ovs-vsctl command: %s: %s" + % (format(vsctl_cmd), cmd["stderr"]), + ) + return cmd["stdout"] + + +def update_args_from_ovs(args): + # get parent bridge to apply rules to + args["parent-bridge"] = run_vsctl_cmd(["br-to-parent", args["bridge"]]).rstrip() + + # get ports names for our actual bridge, be it fake (vlan) or real (no vlan) + ifs_in_bridge = run_vsctl_cmd(["list-ports", args["bridge"]]).split() + ifs_in_parent_bridge = run_vsctl_cmd(["list-ports", args["parent-bridge"]]).split() + + # get vlanid if any + vlanid = run_vsctl_cmd(["get", "port", args["bridge"], "tag"]).rstrip() + if vlanid != "[]": + args["vlanid"] = vlanid + + # get the list of all ports and filter them with the ports names to get all interfaces + ports_j = json.loads(run_vsctl_cmd(["--format=json", "list", "port"])) + ports = [dict(zip(ports_j["headings"], row)) for row in ports_j["data"]] + interfaces = [] + parent_ifaces = [] + for port in ports: + name = port["name"] + if name in ifs_in_bridge: + interfaces.append(port["interfaces"]) + if name in ifs_in_parent_bridge: + parent_ifaces.append(port["interfaces"]) + if len(interfaces) == 0: + return + + # get the list of all interfaces, filter with what we found previously and get their ofports number + if_j = json.loads(run_vsctl_cmd(["--format=json", "list", "interface"])) + ifs = [dict(zip(if_j["headings"], row)) for row in if_j["data"]] + ofports = [] + for interface in ifs: + # port 65534 is the internal port for the bridge, we don't want to use it + if interface["_uuid"] in interfaces and interface["ofport"] != 65534: + ofports.append(interface["ofport"]) + + # second pass on interfaces, find uplinks ofports, that can be: + # - physical port ethX + # - physical ports ethX and Y of a bond + # - the port of a tunnel + uplinks = [] + for interface in ifs: + if interface["_uuid"] not in parent_ifaces: + continue + name = interface["name"] + # ignore irrelevant interfaces + if name.startswith("vif") or name.startswith("xen") or name.startswith("tap"): + continue + if ( + interface["type"] == "" + or interface["type"] == "gre" + or interface["type"] == "vxlan" + ): + uplinks.append(interface["ofport"]) + if interface["ofport"] in ofports: + ofports.remove(interface["ofport"]) + + args["uplinks"] = uplinks + args["ofports"] = ofports + + +def build_rules_strings(args): rules = [] + if args.get("has_to"): + if args.get("mac"): + rules.append(build_rule_string(TO, None, args)) + elif args.get("ofports"): + for ofport in args["ofports"]: + rules.append(build_rule_string(TO, ofport, args)) + for ofport in args["uplinks"]: + rules.append(build_rule_string(TO, ofport, args, uplink=True)) + + if args.get("has_from"): + if args.get("mac"): + rules.append(build_rule_string(FROM, None, args)) + elif args.get("ofports"): + for ofport in args["ofports"]: + rules.append(build_rule_string(FROM, ofport, args)) + for ofport in args["uplinks"]: + rules.append(build_rule_string(FROM, ofport, args, uplink=True)) + return rules - # tcp,dl_dst=26:bf:26:f0:4f:50,nw_src=192.168.38.0/24,tp_src=22 actions=NORMA - if args["has_from"]: - rule = "" - if "priority" in args and args["priority"]: - rule += "priority=" + args["priority"] + "," - rule += args["protocol"].lower() - if "mac" in args and args["mac"]: - rule += ",dl_dst=" + args["mac"] - rule += ",nw_src=" + args["iprange"] - if "protocol" in args and args["protocol"] in PROTOCOLS_WITH_PORTS: - rule += ",tp_src=" + args["port"] - if "allow" in args: - rule += ",actions=" + ("normal" if args["allow"] else "drop") - rules += [rule] - - # tcp,in_port=3,dl_src=26:bf:26:f0:4f:50,nw_dst=192.168.38.0/24,tp_dst=2 - if args["has_to"]: - rule = "" - if "priority" in args and args["priority"]: - rule += "priority=" + args["priority"] + "," - rule += args["protocol"].lower() - if "mac" in args and args["mac"]: - rule += ",dl_src=" + args["mac"] - rule += ",nw_dst=" + args["iprange"] - if "protocol" in args and args["protocol"] in PROTOCOLS_WITH_PORTS: - rule += ",tp_dst=" + args["port"] - if "allow" in args: - rule += ",actions=" + ("normal" if args["allow"] else "drop") - rules += [rule] - return rules +def build_rule_string(direction, ofport, args, uplink=False): + # To / From + rule_parts = { + "priority": ("priority", "priority"), + "protocol": (None, None), + "ofport": ("in_port", "in_port"), + "mac": ("dl_src", "dl_dst"), + "iprange": ("nw_dst", "nw_src"), + "port": ("tp_dst", "tp_src"), + "allow": ("actions", "actions"), + } + + rule = "" + vlanid = OFPVID_NONE + if args.get("vlanid"): + vlanid = args["vlanid"] + + if args.get("priority"): + rule += "priority={}".format(args["priority"]) + "," + rule += args["protocol"] + if uplink: + rule += ",dl_vlan={}".format(vlanid) + if ofport: + rule += "," + rule_parts["ofport"][direction] + "={}".format(ofport) + if args.get("mac"): + rule += "," + rule_parts["mac"][direction] + "={}".format(args["mac"]) + rule += "," + rule_parts["iprange"][direction] + "={}".format(args["iprange"]) + if args.get("port"): + rule += "," + rule_parts["port"][direction] + "={}".format(args["port"]) + if "allow" in args: # optional in case of del_rule + rule += ",actions={}".format("normal" if args["allow"] else "drop") + return rule + + +def run_ofctl_cmd(cmd, bridge, rule): + ofctl_cmd = [OVS_OFCTL_CMD, "-O", OPENFLOW_PROTOCOL, cmd, bridge, rule] + cmd = run_command(ofctl_cmd, check=False) + if cmd["returncode"] != 0: + log_and_raise_error( + E_OVSCALL, + "Error running ovs-ofctl command: %s: %s" + % (format(ofctl_cmd), cmd["stderr"]), + ) + _LOGGER.info("Applied rule: {}".format(ofctl_cmd)) + @error_wrapped def add_rule(_session, args): @@ -223,31 +359,39 @@ def add_rule(_session, args): parser.read("allow", parser.parse_allow) parser.read("priority", parser.parse_priority) except XenAPIPlugin.Failure as e: - log_and_raise_error(E_PARSER, "add_rule: Failed to get parameters: {}".format(e.params[1])) + log_and_raise_error( + E_PARSER, "add_rule: Failed to get parameters: {}".format(e.params[1]) + ) if parser.errors: - log_and_raise_error(E_PARSER, "add_rule: Failed to get parameters: {}".format(parser.errors)) + log_and_raise_error( + E_PARSER, "add_rule: Failed to get parameters: {}".format(parser.errors) + ) rule_args = parser.values - _LOGGER.info("successfully parsed: {}".format(rule_args)) # sanity check if rule_args["protocol"] in PROTOCOLS_WITH_PORTS and not rule_args["port"]: - log_and_raise_error(E_PARAMS, "add_rule: No port provided, tcp and udp requires one") + log_and_raise_error( + E_PARAMS, "add_rule: No port provided, tcp and udp requires one" + ) - ofctl_cmd = [OVS_OFCTL_CMD, "-O", OPENFLOW_PROTOCOL, "add-flow", rule_args["bridge"]] + # update vlanid first, as bridge could be replaced by parent bridge + update_args_from_ovs(rule_args) + if rule_args["ofports"] is None: + log_and_raise_error( + E_PORTS, "No ports found for bridge: {}".format(rule_args["bridge"]) + ) # We can now build the open flow rule - rules = build_rules_string(rule_args) + rules = build_rules_strings(rule_args) + _LOGGER.info("Built rules: {}".format(rules)) if not rules: - log_and_raise_error(E_BUILDER, "add_rule: No rules were build") + log_and_raise_error(E_PORTS, "add_rule: No rules were build") for rule in rules: - cmd = run_command(ofctl_cmd + [rule], check=False) - if cmd['returncode'] != 0: - log_and_raise_error(E_OVSCALL, "Error adding rule: %s: %s" % (format(ofctl_cmd + [rule]), cmd['stderr'])) - _LOGGER.info("Added rule: {}".format(ofctl_cmd + [rule])) + run_ofctl_cmd("add-flow", rule_args["parent-bridge"], rule) return json.dumps(True) @@ -265,28 +409,33 @@ def del_rule(_session, args): parser.read("iprange", parser.parse_iprange) parser.read("port", parser.parse_port) except XenAPIPlugin.Failure as e: - log_and_raise_error(E_PARSER, "del_rule: Failed to get parameters: {}".format(e.params[1])) + log_and_raise_error( + E_PARSER, "del_rule: Failed to get parameters: {}".format(e.params[1]) + ) if parser.errors: - log_and_raise_error(E_PARSER, "del_rule: Failed to get parameters: {}".format(parser.errors)) + log_and_raise_error( + E_PARSER, "del_rule: Failed to get parameters: {}".format(parser.errors) + ) rule_args = parser.values _LOGGER.info("successfully parsed: {}".format(rule_args)) # sanity check if rule_args["protocol"] in PROTOCOLS_WITH_PORTS and not rule_args["port"]: - log_and_raise_error(E_PARAMS, "del_rule: No port provided, tcp and udp requires one") + log_and_raise_error( + E_PARAMS, "del_rule: No port provided, tcp and udp requires one" + ) - ofctl_cmd = [OVS_OFCTL_CMD, "-O", OPENFLOW_PROTOCOL, "del-flows", rule_args["bridge"]] + update_args_from_ovs(rule_args) # We can now build the open flow rule - rules = build_rules_string(rule_args) + rules = build_rules_strings(rule_args) + _LOGGER.info("Built rules: {}".format(rules)) for rule in rules: - cmd = run_command(ofctl_cmd + [rule], check=False) - if cmd['returncode']: - log_and_raise_error(E_OVSCALL, "Error deleting rule: %s: %s" % (format(ofctl_cmd + [rule]), cmd['stderr'])) - _LOGGER.info("Deleted rule: {}".format(ofctl_cmd + [rule])) + run_ofctl_cmd("del-flows", rule_args["parent-bridge"], rule) + return json.dumps(True) @@ -298,12 +447,17 @@ def dump_flows(_session, args): try: bridge = parser.parse_bridge() except XenAPIPlugin.Failure as e: - log_and_raise_error(E_PARSER, "dump_flows: Failed to get parameters: {}".format(e.params[1])) + log_and_raise_error( + E_PARSER, "dump_flows: Failed to get parameters: {}".format(e.params[1]) + ) ofctl_cmd = [OVS_OFCTL_CMD, "-O", OPENFLOW_PROTOCOL, "dump-flows", bridge] cmd = run_command(ofctl_cmd, check=False) - if cmd['returncode']: - log_and_raise_error(E_OVSCALL, "Error dumping flows: %s: %s" % (format(ofctl_cmd), cmd['stderr'])) + if cmd["returncode"]: + log_and_raise_error( + E_OVSCALL, + "Error dumping flows: %s: %s" % (format(ofctl_cmd), cmd["stderr"]), + ) return json.dumps(cmd) diff --git a/tests/sdncontroller_test_cases/functions.py b/tests/sdncontroller_test_cases/functions.py index 205a30c..9381485 100644 --- a/tests/sdncontroller_test_cases/functions.py +++ b/tests/sdncontroller_test_cases/functions.py @@ -2,215 +2,504 @@ # PARAMS are in lists of test cases # IDS are just a description of the test for pytest, manual matching is needed import XenAPIPlugin +from mock import call -ADD_RULE_PARAMS = [ - { # no args - 'args': {}, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "add_rule: Failed to get parameters: bridge parameter is missing" - }, - 'cmd': { - 'returncode': 0, - 'stdout': '', - 'stderr': '' - }, - }, - { # ip drop - 'args': { - 'bridge': 'xenbr0', - 'direction': 'to', - 'protocol': 'ip', - 'iprange': '1.1.1.1', - 'allow': 'false', - }, - 'exception': None, - 'cmd': { - 'returncode': 0, - 'stdout': '', - 'stderr': '' - }, - }, - { # subnet tcp 4242 allow - 'args': { - 'bridge': 'xenbr0', - 'direction': 'to', - 'protocol': 'tcp', - 'port': '4242', - 'iprange': '1.1.1.1/24', - 'allow': 'false', - }, - 'exception': None, - 'cmd': { - 'returncode': 0, - 'stdout': '', - 'stderr': '' - }, - }, - { # tcp no port - 'args': { - 'bridge': 'xenbr0', - 'direction': 'to', - 'protocol': 'tcp', - 'iprange': '1.1.1.1/24', - 'allow': 'false', - }, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '2', - 'text': "add_rule: No port provided, tcp and udp requires one" - }, - 'cmd': { - 'returncode': 0, - 'stdout': '', - 'stderr': '' - }, - }, - { # failed ovs call - 'args': { - 'bridge': 'xenbr0', - 'direction': 'to', - 'protocol': 'ip', - 'iprange': '1.1.1.1/24', - 'allow': 'false', - }, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '4', - 'text': "Error adding rule: ['ovs-ofctl', '-O', 'OpenFlow11', 'add-flow', 'xenbr0', " - "'ip,nw_dst=1.1.1.1/24,actions=drop']: fake error" - }, - 'cmd': { - 'returncode': 1, - 'stdout': '', - 'stderr': 'fake error' +# List of ports in JSON format, as output by the `ovs-vsctl --format=json list port` command. +PORT_LIST_JSON = """ +{"data":[[["uuid","0de54408-a136-4d67-a258-dc6e6b894f72"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","e1b02bec-caec-45ba-a548-536a64806423"],["set",[]],["set",[]],"xapi31_port15",["map",[["xo:sdn-controller:private-network-uuid","364a15c4-3138-4ef6-930f-704c1711e92c"]]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],["set",[]],["set",[]],["set",[]]],[["uuid","f245a8f8-3845-4870-b2de-57e102bb4d9a"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","15472a6b-4aa9-4dcf-b300-715b79377bf1"],["set",[]],["set",[]],"vif2.0",["map",[]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],["set",[]],["set",[]],["set",[]]],[["uuid","0e077dd0-df09-4864-a8c0-a6f485892921"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","fefec25f-1262-4acd-9d84-fe20f41d54b5"],["set",[]],["set",[]],"xapi9",["map",[]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],["set",[]],["set",[]],["set",[]]],[["uuid","fef17539-5477-4914-b68e-60381c36f79d"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","23c506aa-a061-4c8e-b6ce-35a46265c2aa"],["set",[]],["set",[]],"xapi16_port13",["map",[["xo:sdn-controller:private-network-uuid","25ea1e51-b662-4679-a71f-d0a9624b4832"]]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],["set",[]],["set",[]],["set",[]]],[["uuid","473301b2-7ca8-4741-bf66-236122e1176d"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","1d12f735-b1ef-458a-a7c8-a6f6a5f86172"],["set",[]],["set",[]],"eth0",["map",[]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],["set",[]],["set",[]],["set",[]]],[["uuid","ec7b8cb2-9b65-4af0-9c67-f9bd8f5bef29"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","26ddebea-49d0-465e-991d-1b22a968f40e"],["set",[]],["set",[]],"xapi16",["map",[]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],["set",[]],["set",[]],["set",[]]],[["uuid","6a737693-f9aa-42a6-a05d-e254d743c3fc"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","72e590be-14ba-4f64-8ede-1a0f751c6c56"],["set",[]],["set",[]],"xapi31",["map",[]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],["set",[]],["set",[]],["set",[]]],[["uuid","7b52cff3-8b41-4e20-80db-62b927249b91"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","00314263-3096-4820-9a2f-509504fe9c1e"],["set",[]],["set",[]],"xapi9_port14",["map",[["xo:sdn-controller:private-network-uuid","b14c3cb2-2189-4086-b1c7-42d99d55f005"]]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],["set",[]],["set",[]],["set",[]]],[["uuid","7f6cd65c-3382-4f2e-99c7-21637b198ffb"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","d76bc9ba-22c5-4fd6-9eaf-9502b59940ea"],["set",[]],["set",[]],"vif2.2",["map",[]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],["set",[]],["set",[]],["set",[]]],[["uuid","01409acd-750e-4f37-a091-1006ab9acfb0"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],true,["uuid","c3b73f81-0810-4fbe-b2b4-ff797f8abb22"],["set",[]],["set",[]],"xapi5",["map",[]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],100,["set",[]],["set",[]]],[["uuid","bc222a76-b79c-4a43-b753-af91c9d10ff1"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","3a8c2af0-88ca-4a31-bbe3-b7b83668e52d"],["set",[]],["set",[]],"vif1.0",["map",[]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],["set",[]],["set",[]],["set",[]]],[["uuid","4bb3cc76-5a2e-41eb-b018-26d9ef934f21"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","e0d981dd-ae2c-4e30-a7b0-e96f1a6ae4bc"],["set",[]],["set",[]],"vif2.1",["map",[]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],100,["set",[]],["set",[]]],[["uuid","6cda9ca7-f0f3-47dd-8fff-93016be3497e"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],false,["uuid","e60820dd-c660-4853-a78f-51a67a4c7b07"],["set",[]],["set",[]],"xenbr0",["map",[]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],["set",[]],["set",[]],["set",[]]],[["uuid","7517d2d7-3679-4e90-8c79-4a52b585a582"],["set",[]],0,false,["set",[]],0,["set",[]],["map",[]],true,["uuid","191baed0-c6f5-470d-9aa6-e0c9ce960185"],["set",[]],["set",[]],"xapi0",["map",[]],false,["set",[]],["map",[]],["map",[]],["map",[]],["map",[]],42,["set",[]],["set",[]]]],"headings":["_uuid","bond_active_slave","bond_downdelay","bond_fake_iface","bond_mode","bond_updelay","cvlans","external_ids","fake_bridge","interfaces","lacp","mac","name","other_config","protected","qos","rstp_statistics","rstp_status","statistics","status","tag","trunks","vlan_mode"]} + +""" + +# List of interfaces in JSON format, as output by the `ovs-vsctl --format=json list interface` command. +INTERFACE_LIST_JSON = """ +{"data":[[["uuid","191baed0-c6f5-470d-9aa6-e0c9ce960185"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[]],22,0,0,0,0,["set",[]],1,["set",[]],"up",["map",[]],"f8:0d:ac:1a:da:de","f8:0d:ac:1a:da:de",1500,1500,"xapi0",9,["set",[]],["map",[]],["map",[]],["map",[["collisions",0],["rx_bytes",0],["rx_crc_err",0],["rx_dropped",0],["rx_errors",0],["rx_frame_err",0],["rx_missed_errors",0],["rx_multicast_packets",0],["rx_over_err",0],["rx_packets",0],["tx_bytes",0],["tx_dropped",0],["tx_errors",0],["tx_packets",0]]],["map",[["driver_name","openvswitch"]]],"internal"],[["uuid","3a8c2af0-88ca-4a31-bbe3-b7b83668e52d"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[["attached-mac","56:55:a4:a4:ce:71"],["xs-network-uuid","9334aa83-6960-62e5-a463-5acc05295af4"],["xs-vif-uuid","e41c690e-c0a1-f130-4e95-b9a505db2f3c"],["xs-vm-uuid","0ec00fdc-8127-8817-40d4-79d61797f87a"]]],5,0,0,0,0,["set",[]],3,["set",[]],"up",["map",[]],["set",[]],"fe:ff:ff:ff:ff:ff",1500,["set",[]],"vif1.0",2,["set",[]],["map",[]],["map",[]],["map",[["collisions",0],["rx_bytes",950026692],["rx_crc_err",0],["rx_dropped",0],["rx_errors",0],["rx_frame_err",0],["rx_missed_errors",0],["rx_multicast_packets",0],["rx_over_err",0],["rx_packets",8366385],["tx_bytes",101259026443],["tx_dropped",5],["tx_errors",0],["tx_packets",13563793]]],["map",[["driver_name","vif"],["driver_version",""],["firmware_version",""]]],""],[["uuid","c3b73f81-0810-4fbe-b2b4-ff797f8abb22"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[]],15,0,0,0,0,["set",[]],1,["set",[]],"up",["map",[]],"f8:0d:ac:1a:da:de","f8:0d:ac:1a:da:de",1500,1500,"xapi5",4,["set",[]],["map",[]],["map",[]],["map",[["collisions",0],["rx_bytes",32756],["rx_crc_err",0],["rx_dropped",0],["rx_errors",0],["rx_frame_err",0],["rx_missed_errors",0],["rx_multicast_packets",0],["rx_over_err",0],["rx_packets",732],["tx_bytes",0],["tx_dropped",0],["tx_errors",0],["tx_packets",0]]],["map",[["driver_name","openvswitch"]]],"internal"],[["uuid","23c506aa-a061-4c8e-b6ce-35a46265c2aa"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[]],11,0,0,0,0,["set",[]],0,["set",[]],"up",["map",[]],["set",[]],"2a:b8:2c:93:bc:fd",["set",[]],["set",[]],"xapi16_iface13",1,["set",[]],["map",[["key","12"],["remote_ip","192.168.1.221"]]],["map",[]],["map",[["rx_bytes",0],["rx_packets",0],["tx_bytes",0],["tx_packets",0]]],["map",[["tunnel_egress_iface","xenbr0"],["tunnel_egress_iface_carrier","up"]]],"gre"],[["uuid","26ddebea-49d0-465e-991d-1b22a968f40e"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[]],7,0,0,0,0,["set",[]],1,["set",[]],"up",["map",[]],["set",[]],"6e:54:d3:eb:1e:a4",1500,1500,"xapi16",65534,["set",[]],["map",[]],["map",[]],["map",[["collisions",0],["rx_bytes",0],["rx_crc_err",0],["rx_dropped",0],["rx_errors",0],["rx_frame_err",0],["rx_missed_errors",0],["rx_multicast_packets",0],["rx_over_err",0],["rx_packets",0],["tx_bytes",0],["tx_dropped",0],["tx_errors",0],["tx_packets",0]]],["map",[["driver_name","openvswitch"]]],"internal"],[["uuid","d76bc9ba-22c5-4fd6-9eaf-9502b59940ea"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[["attached-mac","0a:27:cf:e6:d2:9c"],["xs-network-uuid","3d809de7-f11b-db02-40ca-0f51330aae19"],["xs-vif-uuid","547e321d-88f2-c698-7f8d-d26630165078"],["xs-vm-uuid","e07d8464-9a05-e391-b4c3-5dc38be4c792"]]],16,0,0,0,0,["set",[]],3,["set",[]],"up",["map",[]],["set",[]],"fe:ff:ff:ff:ff:ff",1500,["set",[]],"vif2.2",2,["set",[]],["map",[]],["map",[]],["map",[["collisions",0],["rx_bytes",12136],["rx_crc_err",0],["rx_dropped",0],["rx_errors",0],["rx_frame_err",0],["rx_missed_errors",0],["rx_multicast_packets",0],["rx_over_err",0],["rx_packets",215],["tx_bytes",15146],["tx_dropped",0],["tx_errors",0],["tx_packets",215]]],["map",[["driver_name","vif"],["driver_version",""],["firmware_version",""]]],""],[["uuid","15472a6b-4aa9-4dcf-b300-715b79377bf1"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[["attached-mac","4e:01:39:a6:46:69"],["xs-network-uuid","9334aa83-6960-62e5-a463-5acc05295af4"],["xs-vif-uuid","e9a7914a-9518-82e2-7052-42cb16cc9724"],["xs-vm-uuid","e07d8464-9a05-e391-b4c3-5dc38be4c792"]]],17,0,0,0,0,["set",[]],1,["set",[]],"up",["map",[]],["set",[]],"fe:ff:ff:ff:ff:ff",1500,["set",[]],"vif2.0",6,["set",[]],["map",[]],["map",[]],["map",[["collisions",0],["rx_bytes",1080132],["rx_crc_err",0],["rx_dropped",0],["rx_errors",0],["rx_frame_err",0],["rx_missed_errors",0],["rx_multicast_packets",0],["rx_over_err",0],["rx_packets",32439],["tx_bytes",257625162],["tx_dropped",0],["tx_errors",0],["tx_packets",2379022]]],["map",[["driver_name","vif"],["driver_version",""],["firmware_version",""]]],""],[["uuid","00314263-3096-4820-9a2f-509504fe9c1e"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[]],13,0,0,0,0,["set",[]],0,["set",[]],"up",["map",[]],["set",[]],"ce:5a:a4:99:b0:e5",["set",[]],["set",[]],"xapi9_iface14",1,["set",[]],["map",[["key","5"],["remote_ip","192.168.1.221"]]],["map",[]],["map",[["rx_bytes",15146],["rx_packets",215],["tx_bytes",15146],["tx_packets",215]]],["map",[["tunnel_egress_iface","xenbr0"],["tunnel_egress_iface_carrier","up"]]],"vxlan"],[["uuid","e1b02bec-caec-45ba-a548-536a64806423"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[]],11,0,0,0,0,["set",[]],0,["set",[]],"up",["map",[]],["set",[]],"22:2f:77:14:8b:ea",["set",[]],["set",[]],"xapi31_iface15",1,["set",[]],["map",[["key","14"],["remote_ip","192.168.1.221"]]],["map",[]],["map",[["rx_bytes",0],["rx_packets",0],["tx_bytes",0],["tx_packets",0]]],["map",[["tunnel_egress_iface","xenbr0"],["tunnel_egress_iface_carrier","up"]]],"gre"],[["uuid","e0d981dd-ae2c-4e30-a7b0-e96f1a6ae4bc"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[["attached-mac","62:16:6e:15:38:c3"],["xs-network-uuid","3878b6de-84e8-a988-5341-d09cf8c821ba"],["xs-vif-uuid","75980193-aa29-3359-087c-2bd11cb04b24"],["xs-vm-uuid","e07d8464-9a05-e391-b4c3-5dc38be4c792"]]],18,0,0,0,0,["set",[]],3,["set",[]],"up",["map",[]],["set",[]],"fe:ff:ff:ff:ff:ff",1500,["set",[]],"vif2.1",5,["set",[]],["map",[]],["map",[]],["map",[["collisions",0],["rx_bytes",20732],["rx_crc_err",0],["rx_dropped",0],["rx_errors",0],["rx_frame_err",0],["rx_missed_errors",0],["rx_multicast_packets",0],["rx_over_err",0],["rx_packets",519],["tx_bytes",15364],["tx_dropped",0],["tx_errors",0],["tx_packets",218]]],["map",[["driver_name","vif"],["driver_version",""],["firmware_version",""]]],""],[["uuid","1d12f735-b1ef-458a-a7c8-a6f6a5f86172"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],"full",["set",[]],["map",[]],2,0,0,0,0,["set",[]],1,1000000000,"up",["map",[]],["set",[]],"f8:0d:ac:1a:da:de",1500,1500,"eth0",1,["set",[]],["map",[]],["map",[]],["map",[["collisions",0],["rx_bytes",143676807514],["rx_crc_err",0],["rx_dropped",0],["rx_errors",0],["rx_frame_err",0],["rx_missed_errors",0],["rx_multicast_packets",1130962],["rx_over_err",0],["rx_packets",198109882],["tx_bytes",50188460190],["tx_dropped",0],["tx_errors",0],["tx_packets",142255313]]],["map",[["driver_name","r8169"],["driver_version",""],["firmware_version",""]]],""],[["uuid","e60820dd-c660-4853-a78f-51a67a4c7b07"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[]],4,0,0,0,0,["set",[]],1,["set",[]],"up",["map",[]],["set",[]],"f8:0d:ac:1a:da:de",1500,1500,"xenbr0",65534,["set",[]],["map",[]],["map",[]],["map",[["collisions",0],["rx_bytes",36745505785],["rx_crc_err",0],["rx_dropped",602217],["rx_errors",0],["rx_frame_err",0],["rx_missed_errors",0],["rx_multicast_packets",0],["rx_over_err",0],["rx_packets",122886392],["tx_bytes",48280983892],["tx_dropped",0],["tx_errors",0],["tx_packets",121147512]]],["map",[["driver_name","openvswitch"]]],"internal"],[["uuid","72e590be-14ba-4f64-8ede-1a0f751c6c56"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[]],14,0,0,0,0,["set",[]],1,["set",[]],"up",["map",[]],["set",[]],"8a:7e:24:58:c9:21",1500,1500,"xapi31",65534,["set",[]],["map",[]],["map",[]],["map",[["collisions",0],["rx_bytes",0],["rx_crc_err",0],["rx_dropped",0],["rx_errors",0],["rx_frame_err",0],["rx_missed_errors",0],["rx_multicast_packets",0],["rx_over_err",0],["rx_packets",0],["tx_bytes",0],["tx_dropped",0],["tx_errors",0],["tx_packets",0]]],["map",[["driver_name","openvswitch"]]],"internal"],[["uuid","fefec25f-1262-4acd-9d84-fe20f41d54b5"],"up",["map",[]],["map",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["set",[]],["map",[]],12,0,0,0,0,["set",[]],1,["set",[]],"up",["map",[]],["set",[]],"62:98:0c:6c:2e:d1",1500,1500,"xapi9",65534,["set",[]],["map",[]],["map",[]],["map",[["collisions",0],["rx_bytes",24272],["rx_crc_err",0],["rx_dropped",0],["rx_errors",0],["rx_frame_err",0],["rx_missed_errors",0],["rx_multicast_packets",0],["rx_over_err",0],["rx_packets",430],["tx_bytes",0],["tx_dropped",0],["tx_errors",0],["tx_packets",0]]],["map",[["driver_name","openvswitch"]]],"internal"]],"headings":["_uuid","admin_state","bfd","bfd_status","cfm_fault","cfm_fault_status","cfm_flap_count","cfm_health","cfm_mpid","cfm_remote_mpids","cfm_remote_opstate","duplex","error","external_ids","ifindex","ingress_policing_burst","ingress_policing_kpkts_burst","ingress_policing_kpkts_rate","ingress_policing_rate","lacp_current","link_resets","link_speed","link_state","lldp","mac","mac_in_use","mtu","mtu_request","name","ofport","ofport_request","options","other_config","statistics","status","type"]} +""" + +UPDATE_ARGS_PARAMS = [ + { # no vlan + "args": {"bridge": "xenbr0"}, + "exception": None, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + { + "returncode": 0, + "stdout": "eth0\nvif1.0\nvif2.0\n", + "stderr": "", + }, # list-ports + { + "returncode": 0, + "stdout": "eth0\nvif1.0\nvif2.0\n", + "stderr": "", + }, # list-ports parent + {"returncode": 0, "stdout": "[]", "stderr": ""}, # get port tag + {"returncode": 0, "stdout": PORT_LIST_JSON, "stderr": ""}, # list port + { + "returncode": 0, + "stdout": INTERFACE_LIST_JSON, + "stderr": "", + }, # list-interfaces + ], + "ofports": [2, 6], + "uplinks": [1], + }, + { # with vlan + "args": {"bridge": "xapi5"}, + "exception": None, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + { + "returncode": 0, + "stdout": "eth0\nvif1.0\nvif2.0\n", + "stderr": "", + }, # list-ports parent + {"returncode": 0, "stdout": "100", "stderr": ""}, # get port tag + {"returncode": 0, "stdout": PORT_LIST_JSON, "stderr": ""}, # list port + { + "returncode": 0, + "stdout": INTERFACE_LIST_JSON, + "stderr": "", + }, # list-interfaces + ], + "ofports": [5], + "uplinks": [1], + }, + { # invalid bridge + "args": {"bridge": "abcd"}, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "3", + "text": "Error running ovs-vsctl command: ['ovs-vsctl', " + "'br-to-parent', 'abcd']: ovs-vsctl: no bridge named abcd", + }, + "cmd": [ + { + "returncode": 1, + "stdout": "", + "stderr": "ovs-vsctl: no bridge named abcd", + }, + ], + }, + { # error listing ports in bridge`` + "args": {"bridge": "xenbr0"}, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "3", + "text": "Error running ovs-vsctl command: ['ovs-vsctl', 'list-ports', 'xenbr0']: fake error", + }, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 1, "stdout": "", "stderr": "fake error"}, + ], + }, + { # error getting bridge tag + "args": {"bridge": "xenbr0"}, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "3", + "text": "Error running ovs-vsctl command: ['ovs-vsctl', 'get', 'port', 'xenbr0', 'tag']: fake error", + }, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + { + "returncode": 0, + "stdout": "eth0\nvif1.0\nvif2.0\n", + "stderr": "", + }, # list-ports parent + {"returncode": 1, "stdout": "", "stderr": "fake error"}, + ], + }, + { # error listing all ports + "args": {"bridge": "xenbr0"}, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "3", + "text": "Error running ovs-vsctl command: ['ovs-vsctl', '--format=json', 'list', 'port']: fake error", + }, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + { + "returncode": 0, + "stdout": "eth0\nvif1.0\nvif2.0\n", + "stderr": "", + }, # list-ports parent + {"returncode": 0, "stdout": "[]", "stderr": ""}, # get port tag + {"returncode": 1, "stdout": "", "stderr": "fake error"}, + ], + }, + { # error listing all interfaces + "args": {"bridge": "xenbr0"}, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "3", + "text": "Error running ovs-vsctl command: ['ovs-vsctl', '--format=json', 'list', 'interface']: fake error", }, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + { + "returncode": 0, + "stdout": "eth0\nvif1.0\nvif2.0\n", + "stderr": "", + }, # list-ports parent + {"returncode": 0, "stdout": "[]", "stderr": ""}, # get port tag + {"returncode": 0, "stdout": PORT_LIST_JSON, "stderr": ""}, # list port + {"returncode": 1, "stdout": "", "stderr": "fake error"}, + ], }, ] -ADD_RULE_IDS = ["no args", "ip drop", "subnet tcp 4242 allow", "tcp no port", - "failed ovs call"] +UPDATE_ARGS_IDS = [ + "no vlan", + "with vlan", + "invalid bridge", + "error listing ports in bridge", + "error getting bridge tag", + "error listing all ports", + "error listing all interfaces", +] +ADD_RULE_PARAMS = [ + { # no args + "args": {}, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "add_rule: Failed to get parameters: bridge parameter is missing", + }, + "cmd": [], + }, + { # ip drop + "args": { + "bridge": "xenbr0", + "direction": "to", + "protocol": "ip", + "ipRange": "1.1.1.1", + "allow": "false", + }, + "exception": None, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports parent + {"returncode": 0, "stdout": "[]", "stderr": ""}, # get port tag + {"returncode": 0, "stdout": PORT_LIST_JSON, "stderr": ""}, # list port + { + "returncode": 0, + "stdout": INTERFACE_LIST_JSON, + "stderr": "", + }, # list-interfaces + ], + "calls": [ + call("add-flow", "xenbr0", "ip,in_port=5,nw_dst=1.1.1.1,actions=drop"), + ], + }, + { # subnet tcp 4242 allow + "args": { + "bridge": "xenbr0", + "direction": "to", + "protocol": "tcp", + "port": "4242", + "ipRange": "1.1.1.1/24", + "allow": "true", + }, + "exception": None, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports parent + {"returncode": 0, "stdout": "[]", "stderr": ""}, # get port tag + {"returncode": 0, "stdout": PORT_LIST_JSON, "stderr": ""}, # list port + { + "returncode": 0, + "stdout": INTERFACE_LIST_JSON, + "stderr": "", + }, # list-interfaces + ], + "calls": [ + call( + "add-flow", + "xenbr0", + "tcp,in_port=5,nw_dst=1.1.1.1/24,tp_dst=4242,actions=normal", + ) + ], + }, + { # udp port from vif drop + "args": { + "bridge": "xenbr0", + "direction": "from", + "mac": "DE:AD:BE:EF:CA:FE", + "protocol": "udp", + "port": "2121", + "ipRange": "4.4.4.4", + "allow": "false", + }, + "exception": None, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports parent + {"returncode": 0, "stdout": "[]", "stderr": ""}, # get port tag + {"returncode": 0, "stdout": PORT_LIST_JSON, "stderr": ""}, # list port + { + "returncode": 0, + "stdout": INTERFACE_LIST_JSON, + "stderr": "", + }, # list-interfaces + ], + "calls": [ + call( + "add-flow", + "xenbr0", + "udp,dl_dst=DE:AD:BE:EF:CA:FE,nw_src=4.4.4.4,tp_src=2121,actions=drop", + ), + ], + }, + { # tcp no port + "args": { + "bridge": "xenbr0", + "direction": "to", + "protocol": "tcp", + "ipRange": "1.1.1.1/24", + "allow": "false", + }, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "2", + "text": "add_rule: No port provided, tcp and udp requires one", + }, + "cmd": {"returncode": 0, "stdout": "", "stderr": ""}, + }, + { # failed ovs call + "args": { + "bridge": "xenbr0", + "direction": "to", + "protocol": "ip", + "ipRange": "1.1.1.1/24", + "allow": "false", + }, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "3", + "text": "Error running ovs-ofctl command: ['ovs-ofctl', '-O', 'OpenFlow11', 'add-flow', 'xenbr0', " + "'ip,in_port=5,nw_dst=1.1.1.1/24,actions=drop']: fake error", + }, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports parent + {"returncode": 0, "stdout": "[]", "stderr": ""}, # get port tag + {"returncode": 0, "stdout": PORT_LIST_JSON, "stderr": ""}, # list port + { + "returncode": 0, + "stdout": INTERFACE_LIST_JSON, + "stderr": "", + }, # list-interfaces + {"returncode": 1, "stdout": "", "stderr": "fake error"}, # ofctl error + ], + }, +] +ADD_RULE_IDS = [ + "no args", + "ip drop", + "subnet tcp 4242 allow", + "udp port from vif drop", + "tcp no port", + "failed ovs call", +] DEL_RULE_PARAMS = [ { - 'args': {}, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "del_rule: Failed to get parameters: bridge parameter is missing" - }, - 'cmd': { - 'returncode': 0, - 'stdout': '', - 'stderr': '' - }, - }, - { # ip drop - 'args': { - 'bridge': 'xenbr0', - 'direction': 'to', - 'protocol': 'ip', - 'iprange': '1.1.1.1', - }, - 'exception': None, - 'cmd': { - 'returncode': 0, - 'stdout': '', - 'stderr': '' - }, - }, - { # tcp no port - 'args': { - 'bridge': 'xenbr0', - 'direction': 'to', - 'protocol': 'tcp', - 'iprange': '1.1.1.1/24', - 'allow': 'false', - }, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '2', - 'text': "del_rule: No port provided, tcp and udp requires one" - }, - 'cmd': { - 'returncode': 0, - 'stdout': '', - 'stderr': '' - }, - }, - { # failed ovs call - 'args': { - 'bridge': 'xenbr0', - 'direction': 'to', - 'protocol': 'ip', - 'iprange': '1.1.1.1/24', - }, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '4', - 'text': "Error deleting rule: ['ovs-ofctl', '-O', 'OpenFlow11', 'del-flows', 'xenbr0', " - "'ip,nw_dst=1.1.1.1/24']: fake error" - }, - 'cmd': { - 'returncode': 1, - 'stdout': '', - 'stderr': 'fake error' - }, - } + "args": {}, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "del_rule: Failed to get parameters: bridge parameter is missing", + }, + "cmd": {"returncode": 0, "stdout": "", "stderr": ""}, + }, + { # ip drop + "args": { + "bridge": "xenbr0", + "direction": "to", + "protocol": "ip", + "ipRange": "1.1.1.1", + "allow": "false", + }, + "exception": None, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports parent + {"returncode": 0, "stdout": "[]", "stderr": ""}, # get port tag + {"returncode": 0, "stdout": PORT_LIST_JSON, "stderr": ""}, # list port + { + "returncode": 0, + "stdout": INTERFACE_LIST_JSON, + "stderr": "", + }, # list-interfaces + ], + "calls": [ + call("del-flows", "xenbr0", "ip,in_port=5,nw_dst=1.1.1.1"), + ], + }, + { # subnet tcp 4242 allow + "args": { + "bridge": "xenbr0", + "direction": "to", + "protocol": "tcp", + "port": "4242", + "ipRange": "1.1.1.1/24", + "allow": "true", + }, + "exception": None, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports parent + {"returncode": 0, "stdout": "[]", "stderr": ""}, # get port tag + {"returncode": 0, "stdout": PORT_LIST_JSON, "stderr": ""}, # list port + { + "returncode": 0, + "stdout": INTERFACE_LIST_JSON, + "stderr": "", + }, # list-interfaces + ], + "calls": [ + call( + "del-flows", + "xenbr0", + "tcp,in_port=5,nw_dst=1.1.1.1/24,tp_dst=4242", + ) + ], + }, + { # udp port from vif drop + "args": { + "bridge": "xenbr0", + "direction": "from", + "mac": "DE:AD:BE:EF:CA:FE", + "protocol": "udp", + "port": "2121", + "ipRange": "4.4.4.4", + "allow": "false", + }, + "exception": None, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports parent + {"returncode": 0, "stdout": "[]", "stderr": ""}, # get port tag + {"returncode": 0, "stdout": PORT_LIST_JSON, "stderr": ""}, # list port + { + "returncode": 0, + "stdout": INTERFACE_LIST_JSON, + "stderr": "", + }, # list-interfaces + ], + "calls": [ + call( + "del-flows", + "xenbr0", + "udp,dl_dst=DE:AD:BE:EF:CA:FE,nw_src=4.4.4.4,tp_src=2121", + ), + ], + }, + { # tcp no port + "args": { + "bridge": "xenbr0", + "direction": "to", + "protocol": "tcp", + "ipRange": "1.1.1.1/24", + "allow": "false", + }, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "2", + "text": "del_rule: No port provided, tcp and udp requires one", + }, + "cmd": {"returncode": 0, "stdout": "", "stderr": ""}, + }, + { # failed ovs call + "args": { + "bridge": "xenbr0", + "direction": "to", + "protocol": "ip", + "ipRange": "1.1.1.1/24", + "allow": "false", + }, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "3", + "text": "Error running ovs-ofctl command: ['ovs-ofctl', '-O', 'OpenFlow11', 'del-flows', 'xenbr0', " + "'ip,in_port=5,nw_dst=1.1.1.1/24']: fake error", + }, + "cmd": [ + {"returncode": 0, "stdout": "xenbr0", "stderr": ""}, # br-to-parent + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports + {"returncode": 0, "stdout": "vif2.1\n", "stderr": ""}, # list-ports parent + {"returncode": 0, "stdout": "[]", "stderr": ""}, # get port tag + {"returncode": 0, "stdout": PORT_LIST_JSON, "stderr": ""}, # list port + { + "returncode": 0, + "stdout": INTERFACE_LIST_JSON, + "stderr": "", + }, # list-interfaces + {"returncode": 1, "stdout": "", "stderr": "fake error"}, # ofctl error + ], + }, +] +DEL_RULE_IDS = [ + "no args", + "ip drop", + "subnet tcp 4242 allow", + "udp port from vif drop", + "tcp no port", + "failed ovs call", ] -DEL_RULE_IDS = ["no args", "ip drop", "tcp no port", "failed ovs call"] DUMP_FLOWS_PARAMS = [ - { # no args - 'args': {}, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "dump_flows: Failed to get parameters: bridge parameter is missing" - }, - 'cmd': { - 'returncode': 0, - 'stdout': '', - 'stderr': '' - }, - }, - { # invalid bridge - 'args': {'bridge': ''}, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "dump_flows: Failed to get parameters: '' is not a valid bridge name" - }, - 'cmd': { - 'returncode': 0, - 'stdout': '', - 'stderr': '' - }, - }, - { # xenbr0 - 'args': {'bridge': 'xenbr0'}, - 'exception': None, - 'cmd': { - 'returncode': 0, - 'stdout': '"OFPST_FLOW reply (OF1.1) (xid=0x2):\n cookie=0x0, duration=10553.194s, table=0, ' + { # no args + "args": {}, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "dump_flows: Failed to get parameters: bridge parameter is missing", + }, + "cmd": {"returncode": 0, "stdout": "", "stderr": ""}, + }, + { # invalid bridge + "args": {"bridge": ""}, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "dump_flows: Failed to get parameters: '' is not a valid bridge name", + }, + "cmd": {"returncode": 0, "stdout": "", "stderr": ""}, + }, + { # xenbr0 + "args": {"bridge": "xenbr0"}, + "exception": None, + "cmd": { + "returncode": 0, + "stdout": '"OFPST_FLOW reply (OF1.1) (xid=0x2):\n cookie=0x0, duration=10553.194s, table=0, ' 'n_packets=221808, n_bytes=84062096, priority=0 actions=NORMAL\n"', - 'stderr': '' + "stderr": "", }, }, - { # non-exsting bridge - 'args': {'bridge': 'xapi42'}, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '4', - 'text': "Error dumping flows: ['ovs-ofctl', '-O', 'OpenFlow11', 'dump-flows', 'xapi42']: " - "ovs-ofctl: xapi42 is not a bridge or a socket\n" + { # non-exsting bridge + "args": {"bridge": "xapi42"}, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "3", + "text": "Error dumping flows: ['ovs-ofctl', '-O', 'OpenFlow11', 'dump-flows', 'xapi42']: " + "ovs-ofctl: xapi42 is not a bridge or a socket\n", }, - 'cmd': { - 'returncode': 1, - 'stdout': '', - 'stderr': 'ovs-ofctl: xapi42 is not a bridge or a socket\n' + "cmd": { + "returncode": 1, + "stdout": "", + "stderr": "ovs-ofctl: xapi42 is not a bridge or a socket\n", }, }, ] diff --git a/tests/sdncontroller_test_cases/parser.py b/tests/sdncontroller_test_cases/parser.py index 7a82635..a5fdd42 100644 --- a/tests/sdncontroller_test_cases/parser.py +++ b/tests/sdncontroller_test_cases/parser.py @@ -4,463 +4,369 @@ import XenAPIPlugin BRIDGE_PARAMS = [ - { - 'input': {'bridge': 'xenbr0'}, - 'result': 'xenbr0', - 'exception': None - }, - { - 'input': {'bridge': 'xapi0'}, - 'result': 'xapi0', - 'exception': None + {"input": {"bridge": "xenbr0"}, "result": "xenbr0", "exception": None}, + {"input": {"bridge": "xapi0"}, "result": "xapi0", "exception": None}, + { + "input": {"bridge": ""}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'' is not a valid bridge name", + }, }, { - 'input': {'bridge': ''}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'' is not a valid bridge name" + "input": {"bridge": "test"}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'test' is not a valid bridge name", }, }, { - 'input': {'bridge': 'test'}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'test' is not a valid bridge name" + "input": {"bridge": "xenbr 0"}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'xenbr 0' is not a valid bridge name", }, }, { - 'input': {'bridge': 'xenbr 0'}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'xenbr 0' is not a valid bridge name" + "input": {}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "bridge parameter is missing", }, }, - { - 'input': {}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "bridge parameter is missing" - } - } ] -BRIDGE_IDS = ["xenbr0", "xapi0", "empty string", "no number", "with space", "no parameters"] +BRIDGE_IDS = [ + "xenbr0", + "xapi0", + "empty string", + "no number", + "with space", + "no parameters", +] MAC_PARAMS = [ { - 'input': {'mac': '72:7a:c0:ae:1b:a5'}, - 'result': '72:7a:c0:ae:1b:a5', - 'exception': None + "input": {"mac": "72:7a:c0:ae:1b:a5"}, + "result": "72:7a:c0:ae:1b:a5", + "exception": None, }, { - 'input': {'mac': '72:7A:C0:AE:1B:A5'}, - 'result': '72:7A:C0:AE:1B:A5', - 'exception': None + "input": {"mac": "72:7A:C0:AE:1B:A5"}, + "result": "72:7A:C0:AE:1B:A5", + "exception": None, }, { - 'input': {'mac': '72:7z:C0:AE:1B:A5'}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'72:7z:C0:AE:1B:A5' is not a valid MAC" + "input": {"mac": "72:7z:C0:AE:1B:A5"}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'72:7z:C0:AE:1B:A5' is not a valid MAC", }, }, { - 'input': {'mac': ''}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'' is not a valid MAC" + "input": {"mac": ""}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'' is not a valid MAC", }, }, - { - 'input': {}, - 'result': None, - 'exception': None - } + {"input": {}, "result": None, "exception": None}, ] MAC_IDS = ["mac", "MAC", "non hexa mac", "empty mac", "no parameters"] IPRANGE_PARAMS = [ - { - 'input': {'iprange': '1.1.1.1'}, - 'result': '1.1.1.1', - 'exception': None - }, - { - 'input': {'iprange': '1.1.1.0/24'}, - 'result': '1.1.1.0/24', - 'exception': None + {"input": {"ipRange": "1.1.1.1"}, "result": "1.1.1.1", "exception": None}, + {"input": {"ipRange": "1.1.1.0/24"}, "result": "1.1.1.0/24", "exception": None}, + { + "input": {"ipRange": "256.256.256.256"}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'256.256.256.256' is not a valid IP range", + }, }, { - 'input': {'iprange': '256.256.256.256'}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'256.256.256.256' is not a valid IP range" + "input": {"ipRange": ""}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'' is not a valid IP range", }, }, { - 'input': {'iprange': ''}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'' is not a valid IP range", + "input": {}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "ipRange parameter is missing", }, }, - { - 'input': {}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "ip range parameter is missing", - } - } ] IPRANGE_IDS = ["ip addr", "ip subnet", "invalid ip", "empty ip", "no parameters"] DIRECTION_PARAMS = [ - { - 'input': {'direction': 'to'}, - 'result': (True, False), - 'exception': None - }, - { - 'input': {'direction': 'TO'}, - 'result': (True, False), - 'exception': None - }, - { - 'input': {'direction': 'from'}, - 'result': (False, True), - 'exception': None - }, - { - 'input': {'direction': 'FROM'}, - 'result': (False, True), - 'exception': None - }, - { - 'input': {'direction': 'to/from'}, - 'result': (True, True), - 'exception': None - }, - { - 'input': {'direction': 'TO/from'}, - 'result': (True, True), - 'exception': None - }, - { - 'input': {'direction': 'to/FROM'}, - 'result': (True, True), - 'exception': None - }, - { - 'input': {'direction': 'from/to'}, - 'result': (True, True), - 'exception': None + {"input": {"direction": "to"}, "result": (True, False), "exception": None}, + {"input": {"direction": "TO"}, "result": (True, False), "exception": None}, + {"input": {"direction": "from"}, "result": (False, True), "exception": None}, + {"input": {"direction": "FROM"}, "result": (False, True), "exception": None}, + {"input": {"direction": "to/from"}, "result": (True, True), "exception": None}, + {"input": {"direction": "TO/from"}, "result": (True, True), "exception": None}, + {"input": {"direction": "to/FROM"}, "result": (True, True), "exception": None}, + {"input": {"direction": "from/to"}, "result": (True, True), "exception": None}, + { + "input": {"direction": ""}, + "result": (None, None), + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'' is not a valid direction", + }, }, { - 'input': {'direction': ''}, - 'result': (None, None), - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'' is not a valid direction" - } + "input": {"direction": "aoeui"}, + "result": (None, None), + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'aoeui' is not a valid direction", + }, }, { - 'input': {'direction': 'aoeui'}, - 'result': (None, None), - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'aoeui' is not a valid direction" - } + "input": {}, + "result": (None, None), + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "direction parameter is missing", + }, }, - { - 'input': {}, - 'result': (None, None), - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "direction parameter is missing" - } - } ] -DIRECTION_IDS = ["to", "TO", "from", "FROM", "to/from", "TO/from", "to/FROM", "from/to", - "empty direction", "invalid direction", "no parameters"] +DIRECTION_IDS = [ + "to", + "TO", + "from", + "FROM", + "to/from", + "TO/from", + "to/FROM", + "from/to", + "empty direction", + "invalid direction", + "no parameters", +] PROTOCOL_PARAMS = [ - { - 'input': {'protocol': 'ip'}, - 'result': 'ip', - 'exception': None - }, - { - 'input': {'protocol': 'IP'}, - 'result': 'ip', - 'exception': None - }, - { - 'input': {'protocol': 'tcp'}, - 'result': 'tcp', - 'exception': None - }, - { - 'input': {'protocol': 'TCP'}, - 'result': 'tcp', - 'exception': None - }, - { - 'input': {'protocol': 'udp'}, - 'result': 'udp', - 'exception': None - }, - { - 'input': {'protocol': 'UDP'}, - 'result': 'udp', - 'exception': None - }, - { - 'input': {'protocol': 'arp'}, - 'result': 'arp', - 'exception': None - }, - { - 'input': {'protocol': 'ARP'}, - 'result': 'arp', - 'exception': None - }, - { - 'input': {'protocol': 'icmp'}, - 'result': 'icmp', - 'exception': None - }, - { - 'input': {'protocol': 'ICMP'}, - 'result': 'icmp', - 'exception': None + {"input": {"protocol": "ip"}, "result": "ip", "exception": None}, + {"input": {"protocol": "IP"}, "result": "ip", "exception": None}, + {"input": {"protocol": "tcp"}, "result": "tcp", "exception": None}, + {"input": {"protocol": "TCP"}, "result": "tcp", "exception": None}, + {"input": {"protocol": "udp"}, "result": "udp", "exception": None}, + {"input": {"protocol": "UDP"}, "result": "udp", "exception": None}, + {"input": {"protocol": "arp"}, "result": "arp", "exception": None}, + {"input": {"protocol": "ARP"}, "result": "arp", "exception": None}, + {"input": {"protocol": "icmp"}, "result": "icmp", "exception": None}, + {"input": {"protocol": "ICMP"}, "result": "icmp", "exception": None}, + { + "input": {"protocol": ""}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'' is not a supported protocol", + }, }, { - 'input': {'protocol': ''}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'' is not a supported protocol", - } + "input": {"protocol": "aoeui"}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'aoeui' is not a supported protocol", + }, }, { - 'input': {'protocol': 'aoeui'}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'aoeui' is not a supported protocol", - } + "input": {}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "protocol parameter is missing", + }, }, - { - 'input': {}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "protocol parameter is missing", - } - } ] -PROTOCOL_IDS = ["ip", "IP", "tcp", "TCP", "udp", "UDP", "arp", "ARP", "icmp", - "ICMP", "empty string", "invalid protocol", "no parameters"] +PROTOCOL_IDS = [ + "ip", + "IP", + "tcp", + "TCP", + "udp", + "UDP", + "arp", + "ARP", + "icmp", + "ICMP", + "empty string", + "invalid protocol", + "no parameters", +] PORT_PARAMS = [ - { - 'input': {'port': '4242'}, - 'result': '4242', - 'exception': None - }, - { - 'input': {'port': '0'}, - 'result': '0', - 'exception': None - }, - { - 'input': {'port': '65535'}, - 'result': '65535', - 'exception': None - }, - { - 'input': {'port': '65536'}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'65536' is not a valid port", - } + {"input": {"port": "4242"}, "result": "4242", "exception": None}, + {"input": {"port": "0"}, "result": "0", "exception": None}, + {"input": {"port": "65535"}, "result": "65535", "exception": None}, + { + "input": {"port": "65536"}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'65536' is not a valid port", + }, }, { - 'input': {'port': '92142'}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'92142' is not a valid port", - } + "input": {"port": "92142"}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'92142' is not a valid port", + }, }, { - 'input': {'port': 'foo'}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'foo' is not a valid port", - } + "input": {"port": "foo"}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'foo' is not a valid port", + }, }, { - 'input': {'port': ''}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'' is not a valid port", - } + "input": {"port": ""}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'' is not a valid port", + }, }, - { - 'input': {}, - 'result': None, - 'exception': None - } + {"input": {}, "result": None, "exception": None}, +] +PORT_IDS = [ + "4242", + "0", + "65535", + "65536", + "92142", + "string", + "empty string", + "no parameters", ] -PORT_IDS = ["4242", "0", "65535", "65536", "92142", "string", "empty string", "no parameters"] ALLOW_PARAMS = [ - { - 'input': {'allow': 'true'}, - 'result': True, - 'exception': None - }, - { - 'input': {'allow': 'True'}, - 'result': True, - 'exception': None - }, - { - 'input': {'allow': 'TRUE'}, - 'result': True, - 'exception': None - }, - { - 'input': {'allow': 'false'}, - 'result': False, - 'exception': None - }, - { - 'input': {'allow': 'False'}, - 'result': False, - 'exception': None - }, - { - 'input': {'allow': 'FALSE'}, - 'result': False, - 'exception': None + {"input": {"allow": "true"}, "result": True, "exception": None}, + {"input": {"allow": "True"}, "result": True, "exception": None}, + {"input": {"allow": "TRUE"}, "result": True, "exception": None}, + {"input": {"allow": "false"}, "result": False, "exception": None}, + {"input": {"allow": "False"}, "result": False, "exception": None}, + {"input": {"allow": "FALSE"}, "result": False, "exception": None}, + { + "input": {"allow": "bar"}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "allow parameter should be true or false, not 'bar'", + }, }, { - 'input': {'allow': 'bar'}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "allow parameter should be true or false, not 'bar'", - } + "input": {"allow": ""}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "allow parameter should be true or false, not ''", + }, }, { - 'input': {'allow': ''}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "allow parameter should be true or false, not ''", - } + "input": {}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "allow parameter is missing", + }, }, - { - 'input': {}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "allow parameter is missing", - } - } ] -ALLOW_IDS = ["true", "True", "TRUE", "false", "False", "FALSE", "bar", "empty string", "no parameters"] +ALLOW_IDS = [ + "true", + "True", + "TRUE", + "false", + "False", + "FALSE", + "bar", + "empty string", + "no parameters", +] PRIORITY_PARAMS = [ - { - 'input': {'priority': '100'}, - 'result': '100', - 'exception': None - }, - { - 'input': {'priority': '0'}, - 'result': '0', - 'exception': None - }, - { - 'input': {'priority': '65535'}, - 'result': '65535', - 'exception': None + {"input": {"priority": "100"}, "result": "100", "exception": None}, + {"input": {"priority": "0"}, "result": "0", "exception": None}, + {"input": {"priority": "65535"}, "result": "65535", "exception": None}, + { + "input": {"priority": "65536"}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'65536' is not a valid priority", + }, }, { - 'input': {'priority': '65536'}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'65536' is not a valid priority", - } + "input": {"priority": "aoeui"}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'aoeui' is not a valid priority", + }, }, { - 'input': {'priority': 'aoeui'}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'aoeui' is not a valid priority", - } + "input": {"priority": ""}, + "result": None, + "exception": { + "type": XenAPIPlugin.Failure, + "code": "1", + "text": "'' is not a valid priority", + }, }, { - 'input': {'priority': ''}, - 'result': None, - 'exception': { - 'type': XenAPIPlugin.Failure, - 'code': '1', - 'text': "'' is not a valid priority", - } + "input": {}, + "result": None, + "exception": None, }, - { - 'input': {}, - 'result': None, - 'exception': None, - } ] PRIORITY_IDS = ["100", "0", "65535", "65536", "aoeui", "empty string", "no parameters"] diff --git a/tests/test_sdn-controller.py b/tests/test_sdn-controller.py index 53210aa..46a35d5 100644 --- a/tests/test_sdn-controller.py +++ b/tests/test_sdn-controller.py @@ -2,40 +2,50 @@ import pytest import XenAPIPlugin -from sdncontroller import ( - Parser, - add_rule, - del_rule, - dump_flows -) +from sdncontroller import Parser, update_args_from_ovs, add_rule, del_rule, dump_flows from sdncontroller_test_cases.parser import ( - BRIDGE_PARAMS, BRIDGE_IDS, - MAC_PARAMS, MAC_IDS, - IPRANGE_PARAMS, IPRANGE_IDS, - DIRECTION_PARAMS, DIRECTION_IDS, - PROTOCOL_PARAMS, PROTOCOL_IDS, - PORT_PARAMS, PORT_IDS, - ALLOW_PARAMS, ALLOW_IDS, - PRIORITY_PARAMS, PRIORITY_IDS + BRIDGE_PARAMS, + BRIDGE_IDS, + MAC_PARAMS, + MAC_IDS, + IPRANGE_PARAMS, + IPRANGE_IDS, + DIRECTION_PARAMS, + DIRECTION_IDS, + PROTOCOL_PARAMS, + PROTOCOL_IDS, + PORT_PARAMS, + PORT_IDS, + ALLOW_PARAMS, + ALLOW_IDS, + PRIORITY_PARAMS, + PRIORITY_IDS, ) from sdncontroller_test_cases.functions import ( - ADD_RULE_PARAMS, ADD_RULE_IDS, - DEL_RULE_PARAMS, DEL_RULE_IDS, - DUMP_FLOWS_PARAMS, DUMP_FLOWS_IDS + UPDATE_ARGS_PARAMS, + UPDATE_ARGS_IDS, + ADD_RULE_PARAMS, + ADD_RULE_IDS, + DEL_RULE_PARAMS, + DEL_RULE_IDS, + DUMP_FLOWS_PARAMS, + DUMP_FLOWS_IDS, ) + def parser_test(method, params): - exc = params['exception'] + exc = params["exception"] if exc: - with pytest.raises(exc['type']) as e: + with pytest.raises(exc["type"]) as e: ret = method() - assert e.value.params[0] == exc['code'] - assert e.value.params[1] == exc['text'] + assert e.value.params[0] == exc["code"] + assert e.value.params[1] == exc["text"] else: ret = method() - assert ret == params['result'] + assert ret == params["result"] + class TestSdnControllerParser: @pytest.fixture(params=BRIDGE_PARAMS, ids=BRIDGE_IDS) @@ -43,7 +53,7 @@ def bridge(self, request): return request.param def test_parse_bridge(self, bridge): - p = Parser(bridge['input']) + p = Parser(bridge["input"]) parser_test(p.parse_bridge, bridge) @pytest.fixture(params=MAC_PARAMS, ids=MAC_IDS) @@ -51,7 +61,7 @@ def mac(self, request): return request.param def test_parse_mac(self, mac): - p = Parser(mac['input']) + p = Parser(mac["input"]) parser_test(p.parse_mac, mac) @pytest.fixture(params=IPRANGE_PARAMS, ids=IPRANGE_IDS) @@ -59,7 +69,7 @@ def iprange(self, request): return request.param def test_parse_iprange(self, iprange): - p = Parser(iprange['input']) + p = Parser(iprange["input"]) parser_test(p.parse_iprange, iprange) @pytest.fixture(params=DIRECTION_PARAMS, ids=DIRECTION_IDS) @@ -67,7 +77,7 @@ def direction(self, request): return request.param def test_parse_direction(self, direction): - p = Parser(direction['input']) + p = Parser(direction["input"]) parser_test(p.parse_direction, direction) @pytest.fixture(params=PROTOCOL_PARAMS, ids=PROTOCOL_IDS) @@ -75,7 +85,7 @@ def protocol(self, request): return request.param def test_parse_protocol(self, protocol): - p = Parser(protocol['input']) + p = Parser(protocol["input"]) parser_test(p.parse_protocol, protocol) @pytest.fixture(params=PORT_PARAMS, ids=PORT_IDS) @@ -83,7 +93,7 @@ def port(self, request): return request.param def test_parse_port(self, port): - p = Parser(port['input']) + p = Parser(port["input"]) parser_test(p.parse_port, port) @pytest.fixture(params=ALLOW_PARAMS, ids=ALLOW_IDS) @@ -91,7 +101,7 @@ def allow(self, request): return request.param def test_parse_allow(self, allow): - p = Parser(allow['input']) + p = Parser(allow["input"]) parser_test(p.parse_allow, allow) @pytest.fixture(params=PRIORITY_PARAMS, ids=PRIORITY_IDS) @@ -99,52 +109,76 @@ def priority(self, request): return request.param def test_parse_priority(self, priority): - p = Parser(priority['input']) + p = Parser(priority["input"]) parser_test(p.parse_priority, priority) + @mock.patch("sdncontroller.run_command", autospec=True) class TestSdnControllerFunctions: + @pytest.fixture(params=UPDATE_ARGS_PARAMS, ids=UPDATE_ARGS_IDS) + def args_update(self, request): + return request.param + + def test_update_args_from_ovs(self, run_command, args_update): + run_command.side_effect = args_update["cmd"] + exc = args_update["exception"] + if exc: + with pytest.raises(exc["type"]) as e: + update_args_from_ovs(args_update["args"]) + assert e.value.params[0] == exc["code"] + assert e.value.params[1] == exc["text"] + else: + update_args_from_ovs(args_update["args"]) + assert args_update["args"]["ofports"] == args_update["ofports"] + assert args_update["args"]["uplinks"] == args_update["uplinks"] + @pytest.fixture(params=ADD_RULE_PARAMS, ids=ADD_RULE_IDS) def rule_to_add(self, request): return request.param def test_add_rule(self, run_command, rule_to_add): - run_command.return_value = rule_to_add['cmd'] - exc = rule_to_add['exception'] + run_command.side_effect = rule_to_add["cmd"] + exc = rule_to_add["exception"] if exc: - with pytest.raises(exc['type']) as e: - add_rule(None, rule_to_add['args']) - assert e.value.params[0] == exc['code'] - assert e.value.params[1] == exc['text'] + with pytest.raises(exc["type"]) as e: + add_rule(None, rule_to_add["args"]) + assert e.value.params[0] == exc["code"] + assert e.value.params[1] == exc["text"] else: - add_rule(None, rule_to_add['args']) + with mock.patch("sdncontroller.run_ofctl_cmd") as mock_func: + add_rule(None, rule_to_add["args"]) + assert mock_func.call_count == len(rule_to_add["calls"]) + mock_func.assert_has_calls(rule_to_add["calls"]) @pytest.fixture(params=DEL_RULE_PARAMS, ids=DEL_RULE_IDS) def rule_to_del(self, request): return request.param def test_del_rule(self, run_command, rule_to_del): - run_command.return_value = rule_to_del['cmd'] - exc = rule_to_del['exception'] + run_command.side_effect = rule_to_del["cmd"] + exc = rule_to_del["exception"] if exc: - with pytest.raises(exc['type']) as e: - del_rule(None, rule_to_del['args']) - assert e.value.params[0] == exc['code'] - assert e.value.params[1] == exc['text'] + with pytest.raises(exc["type"]) as e: + del_rule(None, rule_to_del["args"]) + assert e.value.params[0] == exc["code"] + assert e.value.params[1] == exc["text"] else: - del_rule(None, rule_to_del['args']) + with mock.patch("sdncontroller.run_ofctl_cmd") as mock_func: + del_rule(None, rule_to_del["args"]) + assert mock_func.call_count == len(rule_to_del["calls"]) + mock_func.assert_has_calls(rule_to_del["calls"]) @pytest.fixture(params=DUMP_FLOWS_PARAMS, ids=DUMP_FLOWS_IDS) def bridge_to_dump(self, request): return request.param def test_dump_flow(self, run_command, bridge_to_dump): - run_command.return_value = bridge_to_dump['cmd'] - exc = bridge_to_dump['exception'] + run_command.return_value = bridge_to_dump["cmd"] + exc = bridge_to_dump["exception"] if exc: - with pytest.raises(exc['type']) as e: - dump_flows(None, bridge_to_dump['args']) - assert e.value.params[0] == exc['code'] - assert e.value.params[1] == exc['text'] + with pytest.raises(exc["type"]) as e: + dump_flows(None, bridge_to_dump["args"]) + assert e.value.params[0] == exc["code"] + assert e.value.params[1] == exc["text"] else: - dump_flows(None, bridge_to_dump['args']) + dump_flows(None, bridge_to_dump["args"])