示例#1
0
 def test_masked_match_from_ovs(self):
     """ Test conversion of arbitrarily masked matches from ovs """
     self.assertEqual(match_from_ovs('nw_dst=192.168.2.0/24'),
                      Match([("IPV4_DST", 0xC0A80200, 0xFFFFFF00)]))
     self.assertEqual(match_from_ovs('nw_dst=192.168.2.0/245.255.255.0'),
                      Match([("IPV4_DST", 0xC0A80200, 0xF5FFFF00)]))
     self.assertEqual(
         match_from_ovs('dl_src=01:00:00:00:00:00/01:00:00:00:00:00'),
         Match([("ETH_SRC", 0x010000000000, 0x010000000000)]))
示例#2
0
 def test_exact_match_from_ovs(self):
     """ Test conversion of an exact match from ovs """
     self.assertEqual(match_from_ovs('in_port=1'),
                      Match([("IN_PORT", 1, None)]))
     self.assertEqual(match_from_ovs('metadata=0x3'),
                      Match([("METADATA", 3, None)]))
     self.assertEqual(match_from_ovs('dl_src=00:00:00:00:00:04'),
                      Match([("ETH_SRC", 4, None)]))
     self.assertEqual(match_from_ovs('mpls_bos=1'),
                      Match([("MPLS_BOS", 1, None)]))
示例#3
0
 def test_masked_match_from_ryu(self):
     """ Test conversion of arbitrarily masked matches from ryu """
     self.assertEqual(
         match_from_ryu(OFPMatch(ipv4_dst=("192.168.2.0",
                                           "255.255.255.0"))),
         Match([("IPV4_DST", 0xC0A80200, 0xFFFFFF00)]))
     self.assertEqual(
         match_from_ryu(
             OFPMatch(eth_src=("01:00:00:00:00:00", "01:00:00:00:00:00"))),
         Match([("ETH_SRC", 0x010000000000, 0x010000000000)]))
示例#4
0
    def test_merge_complex_match(self):
        """ Test merging """
        part1 = Rule(priority=0,
                     match=Match([("IPV4_SRC", 0x12340000, 0xFFFF0000)]))
        part2 = Rule(priority=0,
                     match=Match([("IPV4_SRC", 0x00005678, 0x0000FFFF)]))
        expct = Rule(priority=0,
                     match=Match([("IPV4_SRC", 0x12345678, 0xFFFFFFFF)]))

        self.assertEqual(part1 + part2, expct)
示例#5
0
 def test_exact_match_from_ryu(self):
     """ Test conversion of an exact match from ryu """
     self.assertEqual(match_from_ryu(OFPMatch(in_port=1)),
                      Match([("IN_PORT", 1, None)]))
     self.assertEqual(match_from_ryu(OFPMatch(in_phy_port=2)),
                      Match([("IN_PHY_PORT", 2, None)]))
     self.assertEqual(match_from_ryu(OFPMatch(metadata=3)),
                      Match([("METADATA", 3, None)]))
     self.assertEqual(match_from_ryu(OFPMatch(eth_src=4)),
                      Match([("ETH_SRC", 4, None)]))
     self.assertEqual(match_from_ryu(OFPMatch(mpls_bos=7)),
                      Match([("MPLS_BOS", 7, None)]))
示例#6
0
    def test_find_metadata_conflicting_paths(self):
        """ Metadata rewrite conflicting paths test

        As metadata can be set bitwise it uses a slightly different code
        path to a standard set field.

        * MD=0x10 GT=1      0x10 MD=0x1/0x1 GT=2   0x11 output1
                            *                      *
        vs. single table, note all traffic is different no set
        * drop
        """
        inst_a = Instructions()
        inst_a.goto_table = 1
        inst_a.write_metadata = (0x12, None)
        inst_b = Instructions()
        inst_b.goto_table = 2
        inst_b.write_metadata = (0x1, 0x3)
        # Note: Set VLAN applies the present bit mask so must included it
        ruleset_a = [
            Rule(priority=10, table=0,
                 instructions=inst_a),
            Rule(priority=10, table=1, match=Match([('METADATA', 0x12, None)]),
                 instructions=inst_b),
            Rule(priority=0, table=1),
            Rule(priority=10, table=2, match=Match([('METADATA', 0x11, None)]),
                 instructions=Instructions(dup=output1)),
            Rule(priority=0, table=2),
            ]
        ruleset_b = [
            Rule(priority=0, table=0)
            ]
        single_a = to_single_table(ruleset_a)
        single_b = to_single_table(ruleset_b)
        norm_a = normalise(single_a)
        norm_b = normalise(single_b)

        # Make sure the frozensets are made after to_single_table which changes
        # priorities which changes the Rule's hash in the frozenset
        result_ab = {
            (ruleset_a[0], ruleset_a[1], ruleset_a[3]): frozenset([(ruleset_b[0],)]),
            }
        result_ba = {
            (ruleset_b[0],): frozenset([(ruleset_a[0], ruleset_a[1], ruleset_a[3],)])
            }
        equal_ab, diff_ab = check_equal(norm_a, norm_b, diff=True)
        self.assertFalse(equal_ab)

        paths_ab = find_conflicting_paths(diff_ab, single_a, single_b)
        paths_ba = find_conflicting_paths(diff_ab, single_b, single_a)
        self.assertEqual(paths_ab, result_ab)
        self.assertNotEqual(paths_ab, result_ba)  # Sanity check
        self.assertEqual(paths_ba, result_ba)
示例#7
0
    def test_find_rewrite_conflicting_paths(self):
        """ Rewritten conflicting paths test

        VLAN_VID=0 SetVLAN(1), goto1      VLAN_VID=0 output:1
        *                                 VLAN_VID=1 drop
                                          *
        vs. single table, note all traffic is different no set
        * output1
        """
        inst_a = Instructions()
        inst_a.goto_table = 1
        inst_a.apply_actions.append("SET_FIELD", ("VLAN_VID", 1))
        # Note: Set VLAN applies the present bit mask so must included it
        ruleset_a = [
            Rule(priority=10, table=0,
                 match=Match([('VLAN_VID', 0x1000 | 0, None)]),
                 instructions=Instructions(dup=inst_a)),
            Rule(priority=0, table=0),
            Rule(priority=20, table=1,
                 match=Match([('VLAN_VID', 0x1000 | 0, None)]),
                 instructions=Instructions(dup=output1)),
            Rule(priority=20, table=1,
                 match=Match([('VLAN_VID', 0x1000 | 1, None)])),
            Rule(priority=0, table=1)
            ]
        ruleset_b = [
            Rule(priority=0, table=0, instructions=Instructions(dup=output1))
            ]
        single_a = to_single_table(ruleset_a)
        single_b = to_single_table(ruleset_b)
        norm_a = normalise(single_a)
        norm_b = normalise(single_b)

        # Make sure the frozensets are made after to_single_table which changes
        # priorities which changes the Rule's hash in the frozenset
        result_ab = {
            (ruleset_a[0], ruleset_a[3]): frozenset([(ruleset_b[0],)]),
            (ruleset_a[1],): frozenset([(ruleset_b[0],)])
            }
        result_ba = {
            (ruleset_b[0],): frozenset([(ruleset_a[0], ruleset_a[3]),
                                        (ruleset_a[1],)])
            }

        equal_ab, diff_ab = check_equal(norm_a, norm_b, diff=True)
        self.assertFalse(equal_ab)

        paths_ab = find_conflicting_paths(diff_ab, single_a, single_b)
        paths_ba = find_conflicting_paths(diff_ab, single_b, single_a)
        self.assertEqual(paths_ab, result_ab)
        self.assertNotEqual(paths_ab, result_ba)  # Sanity check
        self.assertEqual(paths_ba, result_ba)
示例#8
0
    def setUp(self, _bdd):
        global wc_to_BDD
        global BDD
        global IS_DIFFERING
        global BDD_to_wcs
        wc_to_BDD = _bdd.wc_to_BDD
        BDD = _bdd.BDD
        BDD_to_wcs = _bdd.BDD_to_wcs

        self.IN_PORT0 = Match([('IN_PORT', 0, None)]).get_wildcard()
        self.IN_PORT1 = Match([('IN_PORT', 1, None)]).get_wildcard()
        self.IN_PORT0_ETH_SRC1 = Match([('IN_PORT', 0, None),
                                        ('ETH_SRC', 1, None)]).get_wildcard()
        self.IN_PORT0_ETH_DST1 = Match([('IN_PORT', 0, None),
                                        ('ETH_DST', 1, None)]).get_wildcard()
        self.IN_PORT0_ETH_DST1_ETH_SRC1 = Match([('IN_PORT', 0, None),
                                                 ('ETH_DST', 1, None),
                                                 ('ETH_SRC', 1, None)
                                                 ]).get_wildcard()
        self.IN_PORT0_ETH_SRC1 = Match([('IN_PORT', 0, None),
                                        ('ETH_SRC', 1, None)]).get_wildcard()
        self.IN_PORT0_ETH_DST2 = Match([('IN_PORT', 0, None),
                                        ('ETH_DST', 2, None)]).get_wildcard()
        self.IN_PORT0_ETH_DST2_ETH_SRC1 = Match([('IN_PORT', 0, None),
                                                 ('ETH_DST', 2, None),
                                                 ('ETH_SRC', 1, None)
                                                 ]).get_wildcard()
        self.zero_bdd = BDD()
        self.zero_bdd2 = BDD()
        self.DIFF = _bdd.IS_DIFFERING_TUPLE
        self.OF = OpenFlow1_3_5()
示例#9
0
    def test_action_independence_multiple(self):
        """ Testing messy combinations of mutliple set fields

            Combinations of multiple fields, some overlapping some not.
            And alternative values for different outputs.

            Base case:
            dst:0/31 -> dst:1, src:2, output:1, dst:2, src:1, output:2
        """
        DST1, DST2 = ('SET_FIELD', ('IPV4_DST', 0x1)), ('SET_FIELD', ('IPV4_DST', 0x2))
        SRC1, SRC2 = ('SET_FIELD', ('IPV4_SRC', 0x1)), ('SET_FIELD', ('IPV4_SRC', 0x2))
        OUT1, OUT2 = ('OUTPUT', 1), ('OUTPUT', 2)
        n1 = normalise([
            Rule(priority=10,
                 match=Match([('IPV4_DST', 0x0, 0xFFFFFFFE)]),
                 instructions=inst_from_acts([DST1, SRC2, OUT1, DST2, SRC1, OUT2])),
            Rule(priority=0)
            ], match_redundancy=True)
        """
            dst:1, src:2 -> output:1, dst:2, src:1, output:2
            dst:0/31 -> dst:1, src:2, output:1, dst:2, src:1, output:2
        """
        n2 = normalise([
            Rule(priority=10,
                 match=Match([('IPV4_DST', 1, None),
                              ('IPV4_SRC', 2, None)]),
                 instructions=inst_from_acts([OUT1, DST2, SRC1, OUT2])),
            Rule(priority=9,
                 match=Match([('IPV4_DST', 0x0, 0xFFFFFFFE)]),
                 instructions=inst_from_acts([DST1, SRC2, OUT1, DST2, SRC1, OUT2])),
            Rule(priority=0)
            ], match_redundancy=True)
        """
            dst:1 -> src:2, output:1, dst:2, src:1, output:2
            dst:0/31 -> dst:1, src:2, output:1, dst:2, src:1, output:2
        """
        n3 = normalise([
            Rule(priority=10,
                 match=Match([('IPV4_DST', 1, None)]),
                 instructions=inst_from_acts([SRC2, OUT1, DST2, SRC1, OUT2])),
            Rule(priority=9,
                 match=Match([('IPV4_DST', 0x0, 0xFFFFFFFE)]),
                 instructions=inst_from_acts([DST1, SRC2, OUT1, DST2, SRC1, OUT2])),
            Rule(priority=0)
            ], match_redundancy=True)

        self.assertTrue(check_equal(n1, n2))
        self.assertTrue(check_equal(n2, n3))
        self.assertTrue(check_equal(n1, n3))
示例#10
0
 def test_duplicate_empty(self):
     new = Match(self.empty_match)
     self.assertEqual(self.empty_match, new)
     # Check this works with sets (only one copy)
     self.assertEqual(len(set([self.empty_match, new])), 1)
     self.assertEqual(len(new), 0)
     self.assertEqual(new.required_mask, 0)
示例#11
0
 def test_duplicate_full(self):
     new = Match(self.fm_full)
     self.assertEqual(self.fm_full, new)
     # Check this works with sets (only one copy)
     self.assertEqual(len(set([self.fm_full, new])), 1)
     self.assertEqual(len(new), 2)
     self.assertEqual(bin(new.required_mask).count('1'), len(new))
示例#12
0
 def test_subset(self):
     self.assertTrue(Match([('VLAN_VID', 0x1, None)]).issubset(
                     Match([('VLAN_VID', 0x1, None)])))
     self.assertFalse(Match([('VLAN_VID', 0x2, None)]).issubset(
         Match([('VLAN_VID', 0x1, None)])))
     self.assertFalse(Match([('VLAN_VID', 0x1, None)]).issubset(
         Match([('VLAN_VID', 0x1, None), ('IN_PORT', 0x1, None)])))
     self.assertTrue(Match([('VLAN_VID', 0x1, None), ('IN_PORT', 0x1, None)]).issubset(
         Match([('VLAN_VID', 0x1, None)])))
示例#13
0
    def test_equal_matches(self):
        matcha = Match([("VLAN_VID", 0x1001, None)])
        matchb = Match([("VLAN_VID", 0x1001, None)])

        f1 = Rule(priority=100, match=matcha, instructions=self.inst1, table=1)
        f2 = Rule(priority=100, match=matcha, instructions=self.inst2, table=2)

        res = simulate_actions(f1, f2)
        self.assertEqual(res[0], None)
        # act_apply1 + act_apply2 + merge(act_set1, act_set2)
        self.assertEqual(res[1],
            ActionList(self.act_apply1+self.act_apply2+(self.act_set1+self.act_set2)))
        self.assertEqual(res[2], None)

        print(self.act_set1)
        print(self.act_set2)
        print(self.act_set1 + self.act_set2)
示例#14
0
    def test_push_vlan(self):
        """ Push and match, later pop """
        rule1 = Rule(priority=0)
        rule1.instructions.apply_actions.append("PUSH_VLAN", 0x800)
        rule1.instructions.apply_actions.append("SET_FIELD", ('VLAN_VID', 6))
        rule2 = Rule(priority=0,
                     match=Match([('VLAN_VID', 0x1006, None),
                                  ('VLAN_VID1', 0x1002, None)]))
        rule2.instructions.apply_actions.append("POP_VLAN", None)
        rule2.instructions.apply_actions.append("OUTPUT", 1)
        expt = Rule(priority=0, match=Match([('VLAN_VID', 0x1002, None)]))
        expt.instructions.apply_actions.append("PUSH_VLAN", 0x800)
        expt.instructions.apply_actions.append("SET_FIELD", ('VLAN_VID', 6))
        expt.instructions.apply_actions.append("POP_VLAN", None)
        expt.instructions.apply_actions.append("OUTPUT", 1)

        self.assertEqual(rule1.merge(rule2, False), expt)
        # The resulting rule is valid openflow
        self.assertEqual(rule1.merge(rule2, True), expt)
        self.assertEqual(rule1 + rule2, expt)
示例#15
0
    def test_special(self):
        # Attempt to a add a bad name and check this works correctly
        # assigning a unique yet constant numbering scheme
        full_mask = self.fm_full.required_mask
        match = Match()
        match.append('bad_field', 0x50, 0xfff0)
        self.fm_full.append('bad_field', 0x40, None)

        self.assertEqual(len(match), 1)
        self.assertEqual(bin(match.required_mask).count('1'), len(match))
        self.assertEqual(len(self.fm_full), 3)
        self.assertEqual(bin(self.fm_full.required_mask).count('1'),
                         len(self.fm_full))

        # Check that the same bit gets set for both
        self.assertEqual(match.required_mask,
                         self.fm_full.required_mask - full_mask)
        self.fm_full.append('bad_field2', 1, 1)
        self.assertEqual(len(self.fm_full), 4)
        self.assertEqual(bin(self.fm_full.required_mask).count('1'),
                         len(self.fm_full))
示例#16
0
 def test_stress_meld_16(self):
     """ Check that we can combine 0-15 to create a /4 mask """
     TA = "A"
     a = Match([('IN_PORT', 0, 0x1f)])
     u_bdd = wc_to_BDD(a.get_wildcard(), TA, TA)
     rng = [4, 14, 9, 2, 5, 10, 7, 8, 12, 13, 3, 6, 15, 1, 11]
     for i in rng:
         m = Match([('IN_PORT', i, 0x1f)])
         m_bdd = wc_to_BDD(m.get_wildcard(), TA, TA)
         n_bdd = u_bdd + m_bdd
         # Check the result of the opposite works, as these are
         # non-overlapping sections
         self.assertEqual(n_bdd, (m_bdd + u_bdd))
         u_bdd = n_bdd
     r = Match([('IN_PORT', 0, 0x10)])
     r_bdd = wc_to_BDD(r.get_wildcard(), TA, TA)
     # The 16 exact matches, should have combined into a single range
     self.assertEqual(u_bdd, r_bdd)
示例#17
0
 def test_stress_meld_66535(self):
     """ Check that we can combine 0-66535 to create a /16 mask """
     TA = "A"
     a = Match([('IN_PORT', 0, 0x1ffff)])
     u_bdd = wc_to_BDD(a.get_wildcard(), TA, TA)
     rng = range(1, 65536)
     random.seed(0)
     random.shuffle(list(rng))
     for i in rng:
         m = Match([('IN_PORT', i, 0x1ffff)])
         m_bdd = wc_to_BDD(m.get_wildcard(), TA, TA)
         n_bdd = u_bdd + m_bdd
         # Check the result of the opposite works, as these are
         # non-overlapping sections
         self.assertEqual(n_bdd, (m_bdd + u_bdd))
         u_bdd = n_bdd
     r = Match([('IN_PORT', 0, 0x10000)])
     r_bdd = wc_to_BDD(r.get_wildcard(), TA, TA)
     # The 16 exact matches, should have combined into a single range
     self.assertEqual(u_bdd, r_bdd)
示例#18
0
    def test_convert_flow1(self):
        """ Test converting OFPFlowMod and OFPFlowStats from ovs """

        expected_insts = Instructions()
        expected_insts.apply_actions.append("POP_VLAN", None)
        expected_insts.apply_actions.append("SET_FIELD",
                                            ("ETH_DST", 0x101010101010))
        expected_insts.apply_actions.append("OUTPUT", 7)
        expected_insts.goto_table = 4

        expected = Rule(priority=789,
                        cookie=0xABCD,
                        table=3,
                        match=Match([("ETH_TYPE", 0x0800, None),
                                     ("IPV4_SRC", 1, None),
                                     ("VLAN_VID", 0x1100, None)]),
                        instructions=expected_insts)

        handcrafted = (
            "priority=789 cookie=0xABCD table=3 eth_type=0x800,ip_src=0.0.0.1,vlan_vid=0x1100 actions=pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7,goto_table:4"
        )

        # After going in and out of OVS, ovs-ofctl -O OpenFlowXX dumpflows br0
        ofctl10 = " cookie=0xabcd, duration=30.907s, table=3, n_packets=0, n_bytes=0, priority=789,ip,dl_vlan=256,nw_src=0.0.0.1 actions=strip_vlan,mod_dl_dst:10:10:10:10:10:10,output:7,resubmit(,4)"
        ofctl13 = " cookie=0xabcd, duration=24.104s, table=3, n_packets=0, n_bytes=0, priority=789,ip,dl_vlan=256,nw_src=0.0.0.1 actions=pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7,goto_table:4"
        ofctl13_nostats = " cookie=0xabcd, table=3, priority=789,ip,dl_vlan=256,nw_src=0.0.0.1 actions=pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7,goto_table:4"
        ofctl15 = " cookie=0xabcd, duration=28.969s, table=3, n_packets=0, n_bytes=0, idle_age=28, priority=789,ip,dl_vlan=256,nw_src=0.0.0.1 actions=pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7,goto_table:4"
        # From ovs-appctl bridge/dumpflows
        appctl = "table_id=3, duration=51s, n_packets=0, n_bytes=0, priority=789,ip,dl_vlan=256,nw_src=0.0.0.1,actions=pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7,goto_table:4"

        rule_hand = rule_from_ovs(handcrafted, {})
        rule_10 = rule_from_ovs(ofctl10, {})
        rule_13 = rule_from_ovs(ofctl13, {})
        rule_13nostats = rule_from_ovs(ofctl13_nostats, {})
        rule_15 = rule_from_ovs(ofctl15, {})
        rule_appctl = rule_from_ovs(appctl, {})

        self.assertEqual(rule_hand, expected)
        self.assertEqual(rule_10, expected)
        self.assertEqual(rule_13, expected)
        self.assertEqual(rule_13nostats, expected)
        self.assertEqual(rule_15, expected)

        # appctl output doesn't display cookies
        expected.cookie = None
        self.assertEqual(rule_appctl, expected)
示例#19
0
    def test_convert_flow2(self):
        """ Test converting OFPFlowMod and OFPFlowStats from ovs """

        match = Match([("IPV4_SRC", 1, None), ("ETH_TYPE", 0x800, None),
                       ("VLAN_VID", 0x1100, None)])
        instructions = Instructions()
        instructions.goto_table = 9
        instructions.clear_actions = True
        instructions.write_actions.append("POP_VLAN", None)
        instructions.write_actions.append("SET_FIELD",
                                          ("ETH_DST", 0x101010101010))
        instructions.write_actions.append("OUTPUT", 7)
        instructions.write_metadata = (0x99, 0xff)

        expected = Rule(priority=0x8000,
                        cookie=0xABCD,
                        table=0,
                        match=match,
                        instructions=instructions)

        # Now for write actions and clear actions etc.
        # Note priority=0x8000 is the default priority and ovs omits it,
        # Similarly table 0 is often omitted
        handcrafted = (
            "priority=0x8000 cookie=0xABCD table=0 eth_type=0x800,ip_src=0.0.0.1,vlan_vid=0x1100 actions=clear_actions,write_actions(pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7),write_metadata:0x99/0xff,goto_table:9"
        )

        ofctl11 = " cookie=0xabcd, duration=10.778s, table=0, n_packets=0, n_bytes=0, ip,dl_vlan=256,nw_src=0.0.0.1 actions=clear_actions,write_actions(pop_vlan,mod_dl_dst:10:10:10:10:10:10,output:7),write_metadata:0x99/0xff,goto_table:9"
        # Also the same for of12 to of15
        ofctl13 = " cookie=0xabcd, duration=24.097s, table=0, n_packets=0, n_bytes=0, ip,dl_vlan=256,nw_src=0.0.0.1 actions=clear_actions,write_actions(pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7),write_metadata:0x99/0xff,goto_table:9"
        # From ovs-appctl bridge/dumpflows
        appctl = "duration=369s, n_packets=0, n_bytes=0, ip,dl_vlan=256,nw_src=0.0.0.1,actions=clear_actions,write_actions(pop_vlan,set_field:10:10:10:10:10:10->eth_dst,output:7),write_metadata:0x99/0xff,goto_table:9"

        rule_hand = rule_from_ovs(handcrafted, {})
        rule_11 = rule_from_ovs(ofctl11, {})
        rule_13 = rule_from_ovs(ofctl13, {})
        rule_appctl = rule_from_ovs(appctl, {})

        self.assertEqual(rule_hand, expected)
        self.assertEqual(rule_11, expected)
        self.assertEqual(rule_13, expected)

        # appctl output doesn't display cookies
        expected.cookie = None
        self.assertEqual(rule_appctl, expected)
示例#20
0
    def test_convert_flow(self):
        """ Test converting OFPFlowMod and OFPFlowStats from ryu """
        ryu_match = OFPMatch(ipv4_src=1, eth_type=0x8100, vlan_vid=0x1100)
        ryu_write_actions = OFPInstructionActions(OFPIT_WRITE_ACTIONS, [
            OFPActionPopVlan(),
            OFPActionSetField(eth_dst="10:10:10:10:10:10"),
            OFPActionOutput(7)
        ])
        ryu_instructions = [
            OFPInstructionActions(OFPIT_CLEAR_ACTIONS, []), ryu_write_actions,
            OFPInstructionGotoTable(9),
            OFPInstructionWriteMetadata(0x99, 0xff)
        ]
        ryu_flow_mod = OFPFlowMod(datapath=None,
                                  cookie=0xABCD,
                                  table_id=3,
                                  command=OFPFC_ADD,
                                  priority=789,
                                  match=ryu_match,
                                  instructions=ryu_instructions)
        ryu_flow_stats = OFPFlowStats(cookie=0xABCD,
                                      table_id=3,
                                      priority=789,
                                      match=ryu_match,
                                      instructions=ryu_instructions)

        match = Match([("IPV4_SRC", 1, None), ("ETH_TYPE", 0x8100, None),
                       ("VLAN_VID", 0x1100, None)])
        instructions = Instructions()
        instructions.goto_table = 9
        instructions.clear_actions = True
        instructions.write_actions.append("POP_VLAN", None)
        instructions.write_actions.append("SET_FIELD",
                                          ("ETH_DST", 0x101010101010))
        instructions.write_actions.append("OUTPUT", 7)
        instructions.write_metadata = (0x99, 0xff)

        rule = Rule(priority=789,
                    cookie=0xABCD,
                    table=3,
                    match=match,
                    instructions=instructions)

        self.assertEqual(rule_from_ryu(ryu_flow_stats), rule)
        self.assertEqual(rule_from_ryu(ryu_flow_mod), rule)
示例#21
0
    def test_pop_vlan(self):
        """
        Test the POP_VLAN, MATCH VLAN_VID case
        """

        # Test single pop, i.e. match 2nd header
        rule1 = Rule(priority=0, match=self.vlan_vid_1)
        rule1.instructions.apply_actions.append("POP_VLAN", None)
        rule2 = Rule(priority=0, match=self.vlan_vid_2)

        expt_m = Match([("VLAN_VID", 0x1001, None),
                        ("VLAN_VID1", 0x1002, None)])
        expt = Rule(priority=0, match=expt_m)
        expt.instructions.apply_actions.append("POP_VLAN", None)
        self.assertEqual(rule1.merge(rule2, False), expt)
        # Merging fails to return a valid openflow rule
        self.assertRaises(MergeException, lambda: rule1.merge(rule2, True))
        self.assertRaises(MergeException, lambda: rule1 + rule2)
示例#22
0
 def test_action_independence_single(self):
     """ Test cases where the match cancels a set field """
     SF1, OUT = ('SET_FIELD', ('IPV4_DST', 0x01010101)), ('OUTPUT', 6)
     DEC_TTL = ('DEC_NW_TTL', None)
     # 0.1.1.0/30 -> ip:1.1.1.1, output:1
     n1 = normalise([
         Rule(priority=10,
              match=Match([('IPV4_DST', 0x01010100, 0xFFFFFFFE)]),
              instructions=inst_from_acts([SF1, OUT])),
         Rule(priority=0)
         ])
     # 1.1.1.1/32 -> output:1
     # 1.1.1.0/31 -> ip:1.1.1.1, output:1
     n2 = normalise([
         Rule(priority=10,
              match=Match([('IPV4_DST', 0x01010101, None)]),
              instructions=inst_from_acts([OUT])),
         Rule(priority=9,
              match=Match([('IPV4_DST', 0x01010100, 0xFFFFFFFE)]),
              instructions=inst_from_acts([SF1, OUT])),
         Rule(priority=0)
         ])
     # 1.1.1.0/32 -> ip:1.1.1.1, output1
     # 1.1.1.0/31 -> output:1
     n3 = normalise([
         Rule(priority=10,
              match=Match([('IPV4_DST', 0x01010100, None)]),
              instructions=inst_from_acts([SF1, OUT])),
         Rule(priority=9,
              match=Match([('IPV4_DST', 0x01010100, 0xFFFFFFFE)]),
              instructions=inst_from_acts([OUT])),
         Rule(priority=0)
         ])
     n4 = normalise([
         Rule(priority=10,
              match=Match([('IPV4_DST', 0x01010101, None)]),
              instructions=inst_from_acts([OUT])),
         Rule(priority=9,
              match=Match([('IPV4_DST', 0x01010100, 0xFFFFFFFE)]),
              instructions=inst_from_acts([DEC_TTL, SF1, OUT])),
         Rule(priority=0)
         ])
     self.assertTrue(check_equal(n1, n2))
     self.assertFalse(check_equal(n1, n4))
     self.assertTrue(check_equal(n2, n3))
     self.assertTrue(check_equal(n1, n3))
示例#23
0
    def test_append(self):
        match = Match()
        match.append('in_port', 1, None)
        self.assertEqual(len(match), 1)
        self.assertEqual(bin(match.required_mask).count('1'), len(match))
        match.append('vlan_vid', 0x1000, 0x1000)
        self.assertEqual(len(match), 2)
        self.assertEqual(bin(match.required_mask).count('1'), len(match))

        count = 0
        for _ in match:
            count += 1
        self.assertEqual(count, 2)

        self.assertEqual(match['in_port'][0:2], (1, None))
        self.assertEqual(match['vlan_vid'][0:2], (0x1000, 0x1000))

        # Ensure two uniqually copied versions work
        self.assertEqual(match, self.fm_full)
        self.assertEqual(len(set([match, self.fm_full])), 1)
示例#24
0
 def setUp(self):
     self.ipv4_src1 = Match([("IPV4_SRC", 1, None)])
     self.ipv4_src2 = Match([("IPV4_SRC", 2, None)])
     self.ipv4_dst2 = Match([("IPV4_DST", 2, None)])
     self.ipv4_src2_dst2 = Match([("IPV4_SRC", 2, None),
                                  ("IPV4_DST", 2, None)])
     self.ipv4_src1m = Match([("IPV4_SRC", 1, 0x1)])
     self.ipv4_src2m = Match([("IPV4_SRC", 2, 0x2)])
     self.meta_1 = Match([("METADATA", 1, None)])
     self.meta_2m = Match([("METADATA", 2, 0x2)])
     self.vlan_vid_1 = Match([("VLAN_VID", 0x1001, None)])
     self.vlan_vid_2 = Match([("VLAN_VID", 0x1002, None)])
     self.vlan_vid_6 = Match([("VLAN_VID", 0x1006, None)])
     self.vlan_vid1_2 = Match([("VLAN_VID1", 0x1002, None)])
示例#25
0
    def test_metadata(self):
        rule1 = Rule(priority=0, match=self.ipv4_src2)
        rule1.instructions.write_metadata = (0xFEFCFDFA, None)
        rule2 = Rule(priority=0, match=self.ipv4_src2)
        rule2.instructions.write_metadata = (0x0000FFFF, None)
        # This is half the match as these are 64 byte
        rule3 = Rule(priority=0, match=self.ipv4_src2)
        rule3.instructions.write_metadata = (0xFEFCFDFA, 0xFFFFFFFF)
        rule4 = Rule(priority=0, match=self.ipv4_src2)
        rule4.instructions.write_metadata = (0x0000FFFF, 0xFFFFFFFF)
        rule5 = Rule(priority=0, match=self.ipv4_src2)
        rule5.instructions.write_metadata = (0xFEFCFDFA00000000,
                                             0xFFFFFFFF00000000)
        rule6 = Rule(priority=0, match=self.ipv4_src2)
        rule6.instructions.write_metadata = (0xFEFCFDFA0000FFFF,
                                             0xFFFFFFFFFFFFFFFF)
        rule7 = Rule(priority=0, match=self.ipv4_src2)
        rule7.instructions.write_metadata = (0x0, 0xFFFFFFFFFFFFFFF0)

        # Test cases
        self.assertEqual(rule1 + rule2, rule2)
        self.assertEqual(rule2 + rule1, rule1)
        self.assertEqual(rule1 + rule4, rule2)
        self.assertEqual(rule1 + rule3, rule1)
        self.assertEqual(rule3 + rule4, rule4)
        self.assertEqual(rule4 + rule5, rule6)
        self.assertEqual(rule5 + rule4, rule6)

        # Now check masking and matching
        # Check an invalid match will fail
        mrule1 = Rule(priority=0, match=self.meta_1)
        self.assertRaises(MergeException, lambda: rule1 + mrule1)

        # Merging partial metadata sets and matches
        #
        # The correct behaviour is to remove matching set values.
        # More correctly one should remove set bits, in the case of
        # metadata the result is a partial metadata match.
        #
        # Match:  *                         + Metadata=(1, None)
        # Action: WriteMeta:(0x0, 0xFF..F0) +
        #
        # Expecting:
        # Match:  Meta=(1,0xF)
        # Action: WriteMeta:(0x0, 0xFF..F0)

        expected = rule7.copy()
        expected.match.append('METADATA', 0x1, 0xF)
        self.assertEqual(rule7 + mrule1, expected)

        # Try a complex case
        d1 = Rule(priority=0, match=Match())
        d1.instructions.write_metadata = (0x10, 0x10)
        d2 = Rule(priority=0, match=Match())
        d2.instructions.write_metadata = (0x1, 0x1)
        d3 = Rule(priority=0, match=Match([('METADATA', 0x10, 0x10)]))
        d3.instructions.write_metadata = (0x100, 0x100)
        d4 = Rule(priority=0, match=Match([('METADATA', 0x111, 0x111)]))

        expected = Rule(priority=0, match=Match())
        expected.instructions.write_metadata = (0x111, 0x111)

        self.assertEqual(d1 + d2 + d3 + d4, expected)
        self.assertEqual((d1 + d2) + (d3 + d4), expected)
        self.assertEqual(d1 + (d2 + d3) + d4, expected)
        self.assertEqual(d1 + (d2 + (d3 + d4)), expected)

        # This should fail if table the first is set to 0
        d1.instructions.write_metadata = (0x00, 0x10)
        self.assertRaises(MergeException, lambda: d1 + d2 + d3 + d4)
        self.assertRaises(MergeException, lambda: (d1 + d2) + (d3 + d4))
        self.assertRaises(MergeException, lambda: d1 + (d2 + d3) + d4)
        self.assertRaises(MergeException, lambda: d1 + (d2 + (d3 + d4)))

        # Check original metadata match + set's intersection is calculated
        # correctly
        rule8 = Rule(priority=0, match=Match([("METADATA", 0x1, 0x1)]))
        rule8.instructions.write_metadata = (0x0, 0x2)
        rule9 = Rule(priority=0, match=Match([("METADATA", 0x0, None)]))
        self.assertRaises(MergeException, lambda: rule8 + rule9)

        rule10 = Rule(priority=0, match=Match([("METADATA", 0x1, None)]))
        expected = Rule(priority=0,
                        match=Match([("METADATA", 0x1, 0xFFFFFFFFFFFFFFFD)]))
        expected.instructions.write_metadata = (0x0, 0x2)
        self.assertEqual(rule8 + rule10, expected)
示例#26
0
    if write_actions:
        inst.write_actions = ActionSet(write_actions)
    if goto_table:
        inst.goto_table = goto_table
    return inst


def priority_scale(first, second):
    """ This is how we scale priorities for a two table pipeline,
        if this changes we will need to update this code.
    """
    return first * (MAX_PRIORITY) + second


FORWARD_DROP = [
    Rule(priority=10, table=0, match=Match([("IPV4_DST", 0x1, 0x3)]),
         instructions=build_inst(write_actions=[("OUTPUT", 1)],
                                 goto_table=1)),
    Rule(priority=9, table=0, match=Match([("IPV4_DST", 0x2, 0x7)]),
         instructions=build_inst(write_actions=[("OUTPUT", 2)],
                                 goto_table=1)),
    Rule(priority=0, table=0, match=Match()),

    Rule(priority=100, table=1, match=Match([("IPV4_DST", 0x8, 0x8)]),
         instructions=build_inst(clear_actions=True)),
    Rule(priority=0, table=1, match=Match()),
    ]

# The expected single table
FORWARD_DROP_SINGLE = [
    Rule(priority=priority_scale(10, 100), table=0,  # A + D
示例#27
0
    def test_find_vlans_conflicting_paths(self):
        """ VLAN rewrite paths test, check push/pop combinations
            Also due to internals of how this can be structured

        IN_PORT:1,VLAN:1         TCP:80: drop     VLAN:1 output:10
        IN_PORT:2 pushVLAN:2     * ->             VLAN:2 pop output:11
        IN_PORT:3 pushVLAN:1
        IN_PORT:4,VLAN:2
        * drop

        vs. A vlan can do things:

        IN_PORT:1,VLAN:1         VLAN:1 pop output:11
        IN_PORT:2 pushVLAN:2     VLAN:2 output:10
        IN_PORT:3 pushVLAN:1
        IN_PORT:4,VLAN:2
        * drop

        vs. single table, note all traffic is different no set
        * drop
        """
        push_vlan2 = Instructions()
        push_vlan2.goto_table = 1
        push_vlan2.apply_actions.append("PUSH_VLAN", 0x8100)
        push_vlan2.apply_actions.append("SET_FIELD", ("VLAN_VID", 2))
        push_vlan1 = Instructions()
        push_vlan1.goto_table = 1
        push_vlan1.apply_actions.append("PUSH_VLAN", 0x8100)
        push_vlan1.apply_actions.append("SET_FIELD", ("VLAN_VID", 1))
        trunk10 = Instructions()
        trunk10.write_actions.append("OUTPUT", 11)
        access11 = Instructions()
        access11.write_actions.append("POP_VLAN", None)
        access11.write_actions.append("OUTPUT", 11)
        # Note: Set VLAN applies the present bit mask so must included it
        ruleset_a = [
            Rule(priority=10, table=0,
                 match=Match([('IN_PORT', 1, None), ('VLAN_VID', 0x1001, None)]),
                 instructions=goto1),
            Rule(priority=10, table=0,
                 match=Match([('IN_PORT', 2, None)]),
                 instructions=push_vlan2),
            Rule(priority=10, table=0,
                 match=Match([('IN_PORT', 3, None)]),
                 instructions=push_vlan1),
            Rule(priority=10, table=0,
                 match=Match([('IN_PORT', 4, None), ('VLAN_VID', 0x1002, None)]),
                 instructions=goto1),
            Rule(priority=0, table=0),
            Rule(priority=10, table=1, match=Match([('TCP_SRC', 80, None)])),
            Rule(priority=0, table=1, instructions=goto2),
            Rule(priority=10, table=2, match=Match([('VLAN_VID', 0x1001, None)]),
                 instructions=Instructions(dup=trunk10)),
            Rule(priority=10, table=2, match=Match([('VLAN_VID', 0x1002, None)]),
                 instructions=Instructions(dup=access11)),
            Rule(priority=0, table=2),
            ]
        ruleset_b = [
            Rule(priority=10, table=0,
                 match=Match([('IN_PORT', 1, None), ('VLAN_VID', 0x1001, None)]),
                 instructions=goto1),
            Rule(priority=10, table=0,
                 match=Match([('IN_PORT', 2, None)]),
                 instructions=push_vlan2),
            Rule(priority=10, table=0,
                 match=Match([('IN_PORT', 3, None)]),
                 instructions=push_vlan1),
            Rule(priority=10, table=0,
                 match=Match([('IN_PORT', 4, None), ('VLAN_VID', 0x1002, None)]),
                 instructions=goto1),
            Rule(priority=0, table=0),
            Rule(priority=10, table=1, match=Match([('VLAN_VID', 0x1001, None)]),
                 instructions=Instructions(dup=access11)),
            Rule(priority=10, table=1, match=Match([('VLAN_VID', 0x1002, None)]),
                 instructions=Instructions(dup=trunk10)),
            Rule(priority=0, table=1)]
        ruleset_c = [
            Rule(priority=0, table=0)
            ]
        single_a = to_single_table(ruleset_a)
        single_b = to_single_table(ruleset_b)
        single_c = to_single_table(ruleset_c)
        norm_a = normalise(single_a)
        norm_b = normalise(single_b)
        norm_c = normalise(single_c)

        # Make sure the frozensets are made after to_single_table which changes
        # priorities which changes the Rule's hash in the frozenset
        result_ab = {
            (ruleset_a[0], ruleset_a[5]):
                frozenset([(ruleset_b[0], ruleset_b[5])]),
            (ruleset_a[0], ruleset_a[6], ruleset_a[7]):
                frozenset([(ruleset_b[0], ruleset_b[5])]),
            (ruleset_a[1], ruleset_a[5]):
                frozenset([(ruleset_b[1], ruleset_b[6])]),
            (ruleset_a[1], ruleset_a[6], ruleset_a[8]):
                frozenset([(ruleset_b[1], ruleset_b[6])]),
            (ruleset_a[2], ruleset_a[5]):
                frozenset([(ruleset_b[2], ruleset_b[5])]),
            (ruleset_a[2], ruleset_a[6], ruleset_a[7]):
                frozenset([(ruleset_b[2], ruleset_b[5])]),
            (ruleset_a[3], ruleset_a[5]):
                frozenset([(ruleset_b[3], ruleset_b[6])]),
            (ruleset_a[3], ruleset_a[6], ruleset_a[8]):
                frozenset([(ruleset_b[3], ruleset_b[6])]),
            }
        result_ba = {
            (ruleset_b[0], ruleset_b[5]):
                frozenset([(ruleset_a[0], ruleset_a[5]),
                           (ruleset_a[0], ruleset_a[6], ruleset_a[7])]),
            (ruleset_b[1], ruleset_b[6]):
                frozenset([(ruleset_a[1], ruleset_a[5]),
                           (ruleset_a[1], ruleset_a[6], ruleset_a[8])]),
            (ruleset_b[2], ruleset_b[5]):
                frozenset([(ruleset_a[2], ruleset_a[5]),
                           (ruleset_a[2], ruleset_a[6], ruleset_a[7])]),
            (ruleset_b[3], ruleset_b[6]):
                frozenset([(ruleset_a[3], ruleset_a[5]),
                           (ruleset_a[3], ruleset_a[6], ruleset_a[8])]),
            }
        result_ca = {
            (ruleset_c[0],):
                frozenset([(ruleset_a[0], ruleset_a[6], ruleset_a[7]),
                           (ruleset_a[1], ruleset_a[6], ruleset_a[8]),
                           (ruleset_a[2], ruleset_a[6], ruleset_a[7]),
                           (ruleset_a[3], ruleset_a[6], ruleset_a[8])])
            }
        equal_ab, diff_ab = check_equal(norm_a, norm_b, diff=True)
        equal_ca, diff_ca = check_equal(norm_c, norm_a, diff=True)
        self.assertFalse(equal_ab)
        self.assertFalse(equal_ca)

        paths_ab = find_conflicting_paths(diff_ab, single_a, single_b)
        paths_ba = find_conflicting_paths(diff_ab, single_b, single_a)
        paths_ca = find_conflicting_paths(diff_ca, single_c, single_a)
        self.assertEqual(paths_ab, result_ab)
        self.assertNotEqual(paths_ab, result_ba)  # Sanity check
        self.assertEqual(paths_ba, result_ba)
        self.assertEqual(paths_ca, result_ca)
示例#28
0
    def test_difference(self):
        """ Difference should return packet-space in A, which is either not
            present in B or results in a difference action.
            We return a special IS_DIFFERING when found, otherwise None.
            In the case of an empty space in A not matching empty in B we
            report this as a difference (XXX this might be the wrong thing to
            do).
        """
        TA = "A"
        TB = "B"
        TC = "C"
        TD = "D"
        bdd_a = wc_to_BDD(self.IN_PORT0, TA, TA)
        bdd_b = wc_to_BDD(self.IN_PORT0, TB, TB)
        bdd_diff = wc_to_BDD(Match().get_wildcard(), *self.DIFF)
        port0_diff = wc_to_BDD(self.IN_PORT0, *self.DIFF)

        # The difference between the same is empty
        self.assertEqual(bdd_a.difference(bdd_a), self.zero_bdd)
        self.assertEqual(bdd_b.difference(bdd_b), self.zero_bdd)
        self.assertEqual(self.zero_bdd.difference(self.zero_bdd),
                         self.zero_bdd)

        # The difference between a and b in the whole IN_PORT0 path
        # So we expect IN_PORT0 -> IS_DIFFERING back
        self.assertEqual(bdd_a.difference(bdd_b), port0_diff)
        # Difference is not symmetrical
        self.assertEqual(bdd_a.difference(self.zero_bdd), port0_diff)

        # Difference is not symmetrical
        # XXX Think more about this case, this might be the wrong behaviour
        # then again our solver code probably wont hit it.
        self.assertEqual(self.zero_bdd.difference(bdd_a), bdd_diff)

        # Lets merge up some rule combinations with small differences and see
        # if they are detected
        # Lets Always compare to this:
        # 1) in_port:0,eth_dst:1,eth_src:1 -> A
        # 2) in_port:0,eth_dst:2,eth_src:1 -> B
        # 3) in_port:0,eth_dst:1 -> C
        # 4) in_port:0 -> D

        bdd_comp_to = (wc_to_BDD(self.IN_PORT0_ETH_DST1_ETH_SRC1, TA, TA) +
                       wc_to_BDD(self.IN_PORT0_ETH_DST2_ETH_SRC1, TB, TB) +
                       wc_to_BDD(self.IN_PORT0_ETH_DST1, TC, TC) +
                       wc_to_BDD(self.IN_PORT0, TD, TD))
        # Lets change 1) A -> B, we expect A -> DIFF as a result
        bdd_test = (wc_to_BDD(self.IN_PORT0_ETH_DST1_ETH_SRC1, TB, TB) +
                    wc_to_BDD(self.IN_PORT0_ETH_DST2_ETH_SRC1, TB, TB) +
                    wc_to_BDD(self.IN_PORT0_ETH_DST1, TC, TC) +
                    wc_to_BDD(self.IN_PORT0, TD, TD))
        self.assertEqual(
            bdd_test.difference(bdd_comp_to),
            wc_to_BDD(self.IN_PORT0_ETH_DST1_ETH_SRC1, *self.DIFF))

        # Lets try BOTH 1 and 2 and 3 to -> D i.e. in_port 0 -> D
        bdd_test = wc_to_BDD(self.IN_PORT0, TD, TD)
        expt_diff = (wc_to_BDD(self.IN_PORT0_ETH_DST1_ETH_SRC1, *self.DIFF) +
                     wc_to_BDD(self.IN_PORT0_ETH_DST2_ETH_SRC1, *self.DIFF) +
                     wc_to_BDD(self.IN_PORT0_ETH_DST1, *self.DIFF))
        self.assertEqual(bdd_test.difference(bdd_comp_to), expt_diff)
示例#29
0
    def test_bit_order(self):
        """ Matches within a field will be prefix matching.
            As such ensure that we are encoding the MSB in the highest node.

            This will result in smaller tables, and faster build times.
        """

        # Adding 0101/4 -> A and 1000/2 -> B
        #      *
        #   0/   \1
        #   *     *
        #    \1 0/
        #     *  B
        #   0/
        #   *
        #    \1
        #     A
        # Total of 7 nodes inc. terminals
        # We expect this ordering
        a = Match([("IPV4_SRC", 0x5, 0xF)])
        b = Match([("IPV4_SRC", 0x8, 0xC)])
        TA = "A"
        TB = "B"
        bdd_a = wc_to_BDD(a.get_wildcard(), TA, TA)
        bdd_b = wc_to_BDD(b.get_wildcard(), TB, TB)

        res = bdd_a + bdd_b
        self.assertEqual(len(res), 7)
        print(len(res))

        # Adding the opposite 1010/4 -> A and 0001/1100
        # Results in 8 nodes.
        a = Match([("IPV4_SRC", 0xA, 0xF)])
        b = Match([("IPV4_SRC", 0x1, 0x3)])
        TA = "A"
        TB = "B"
        bdd_a = wc_to_BDD(a.get_wildcard(), TA, TA)
        bdd_b = wc_to_BDD(b.get_wildcard(), TB, TB)

        res = bdd_a + bdd_b
        self.assertEqual(len(res), 8)
        print(len(res))
示例#30
0
    def test_conversion_to_from_BDD(self):
        a = Match([("IPV4_SRC", 0x5, 0xF)])
        TA = "A"
        bdd_a = wc_to_BDD(a.get_wildcard(), TA, TA)
        res = list(BDD_to_wcs(bdd_a))
        self.assertEqual(len(res), 1)
        self.assertEqual(res[0][0], a.get_wildcard())

        # Check the first and last fields for off by 1 issues
        # Purposely checking 0xF on the ends as this ensures no off by one
        # error missing a bit

        # Check this first field
        a = Match([(self.OF.ordered_oxm_fields[0], 0xF445FA82FF38F92F, None)])
        TA = "A"
        bdd_a = wc_to_BDD(a.get_wildcard(), TA, TA)
        res = list(BDD_to_wcs(bdd_a))
        self.assertEqual(len(res), 1)
        self.assertEqual(res[0][0], a.get_wildcard())

        # Check the last field
        a = Match([(self.OF.ordered_oxm_fields[-1], 0xF445FA82FF38F92F, None)])
        TA = "A"
        bdd_a = wc_to_BDD(a.get_wildcard(), TA, TA)
        res = list(BDD_to_wcs(bdd_a))
        self.assertEqual(len(res), 1)
        self.assertEqual(res[0][0], a.get_wildcard())