Files
openflow/datapath/table_t.c
T
Justin Pettit d1d2f3f321 - On flow entries with wildcards, match priority field when doing a "strict" delete.
- Remove group_id from flow entries.
- Show priority when print flow mod messages.
2008-04-29 15:00:28 -07:00

875 lines
22 KiB
C

/*
* Distributed under the terms of the GNU GPL version 2.
* Copyright (c) 2007, 2008 The Board of Trustees of The Leland
* Stanford Junior University
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/random.h>
#include <linux/rcupdate.h>
#include "flow.h"
#include "table.h"
#include "openflow.h"
#include "unit.h"
static const char *
table_name(struct sw_table *table)
{
struct sw_table_stats stats;
table->stats(table, &stats);
return stats.name;
}
static unsigned long int
table_max_flows(struct sw_table *table)
{
struct sw_table_stats stats;
table->stats(table, &stats);
return stats.max_flows;
}
static struct sw_flow *flow_zalloc(int n_actions, gfp_t flags)
{
struct sw_flow *flow = flow_alloc(n_actions, flags);
if (flow) {
struct ofp_action *actions = flow->actions;
memset(flow, 0, sizeof *flow);
flow->actions = actions;
}
return flow;
}
static void
simple_insert_delete(struct sw_table *swt, uint16_t wildcards)
{
struct sw_flow *a_flow = flow_zalloc(0, GFP_KERNEL);
struct sw_flow *b_flow = flow_zalloc(0, GFP_KERNEL);
struct sw_flow *found;
if (!swt) {
unit_fail("table creation failed");
return;
}
printk("simple_insert_delete: testing %s table\n", table_name(swt));
*((uint32_t*)a_flow->key.dl_src) = 0x12345678;
*((uint32_t*)b_flow->key.dl_src) = 0x87654321;
a_flow->key.nw_src = 0xdeadbeef;
b_flow->key.nw_src = 0x001dd0d0;
a_flow->key.wildcards = wildcards;
b_flow->key.wildcards = wildcards;
if (!(swt->insert(swt, a_flow)))
unit_fail("insert failed");
found = swt->lookup(swt, &a_flow->key);
if(found != a_flow)
unit_fail("%p != %p", found, a_flow);
if (swt->lookup(swt, &b_flow->key))
unit_fail("lookup should not succeed (1)");
swt->delete(swt, &a_flow->key, 0, 0);
if (swt->lookup(swt, &a_flow->key))
unit_fail("lookup should not succeed (3)");
flow_free(b_flow);
swt->destroy(swt);
}
static void
multiple_insert_destroy(struct sw_table *swt, int inserts, uint16_t wildcards,
int min_collisions, int max_collisions)
{
int i;
int col = 0;
if (!swt) {
unit_fail("table creation failed");
return;
}
printk("inserting %d flows into %s table with max %lu flows: ",
inserts, table_name(swt), table_max_flows(swt));
for(i = 0; i < inserts; ++i){
struct sw_flow *a_flow = flow_zalloc(0, GFP_KERNEL);
*((uint32_t*)&(a_flow->key.dl_src[0])) = random32();
a_flow->key.nw_src = random32();
a_flow->key.wildcards = wildcards;
if(!swt->insert(swt, a_flow)) {
col++;
flow_free(a_flow);
}
}
printk("%d failures\n", col);
if (min_collisions <= col && col <= max_collisions)
printk("\tmin = %d <= %d <= %d = max, OK.\n",
min_collisions, col, max_collisions);
else {
if (col < min_collisions)
unit_fail("too few collisions (%d < %d)",
col, min_collisions);
else if (col > max_collisions)
unit_fail("too many collisions (%d > %d)",
col, max_collisions);
printk("(This is statistically possible "
"but should not occur often.)\n");
}
swt->destroy(swt);
}
static void
set_random_key(struct sw_flow_key *key, uint16_t wildcards)
{
key->nw_src = random32();
key->nw_dst = random32();
key->in_port = (uint16_t) random32();
key->dl_vlan = (uint16_t) random32();
key->dl_type = (uint16_t) random32();
key->tp_src = (uint16_t) random32();
key->tp_dst = (uint16_t) random32();
key->wildcards = wildcards;
*((uint32_t*)key->dl_src) = random32();
*(((uint32_t*)key->dl_src) + 1) = random32();
*((uint32_t*)key->dl_dst) = random32();
*(((uint32_t*)key->dl_dst) + 1) = random32();
key->nw_proto = (uint8_t) random32();
}
struct flow_key_entry {
struct sw_flow_key key;
struct list_head node;
};
/*
* Allocates memory for 'n_keys' flow_key_entrys. Initializes the allocated
* keys with random values, setting their wildcard values to 'wildcards', and
* places them all in a list. Returns a pointer to a flow_key_entry that
* serves solely as the list's head (its key has not been set). If allocation
* fails, returns NULL. Returned pointer should be freed with vfree (which
* frees the memory associated with the keys as well.)
*/
static struct flow_key_entry *
allocate_random_keys(int n_keys, uint16_t wildcards)
{
struct flow_key_entry *entries, *pos;
struct list_head *keys;
if (n_keys < 0)
return NULL;
entries = vmalloc((n_keys+1) * sizeof *entries);
if (entries == NULL) {
unit_fail("cannot allocate memory for %u keys",
n_keys);
return NULL;
}
keys = &entries->node;
INIT_LIST_HEAD(keys);
for(pos = entries+1; pos < (entries + n_keys + 1); pos++) {
set_random_key(&pos->key, wildcards);
list_add(&pos->node, keys);
}
return entries;
}
/*
* Attempts to insert the first 'n_flows' flow keys in list 'keys' into table
* 'swt', where 'keys' is a list of flow_key_entrys. key_entrys that are
* inserted into the table are removed from the 'keys' list and placed in
* 'added' list. Returns -1 if flow memory allocation fails, else returns the
* number of flows that were actually inserted (some attempts might fail due to
* collisions).
*/
static int
insert_flows(struct sw_table *swt, struct list_head *keys, struct list_head *added, int n_flows)
{
struct flow_key_entry *pos, *next;
int cnt;
cnt = 0;
list_for_each_entry_safe (pos, next, keys, node) {
struct sw_flow *flow = flow_zalloc(0, GFP_KERNEL);
if (flow == NULL) {
unit_fail("Could only allocate %u flows", cnt);
return -1;
}
flow->key = pos->key;
if (!swt->insert(swt, flow)) {
flow_free(flow);
list_del(&pos->node);
} else {
list_del(&pos->node);
list_add(&pos->node, added);
cnt++;
if (n_flows != -1 && cnt == n_flows)
break;
}
}
return cnt;
}
/*
* Finds and returns the flow_key_entry in list 'keys' matching the passed in
* flow's key. If not found, returns NULL.
*/
static struct flow_key_entry *
find_flow(struct list_head *keys, struct sw_flow *flow)
{
struct flow_key_entry *pos;
list_for_each_entry(pos, keys, node) {
if(!memcmp(&pos->key, &flow->key, sizeof(struct sw_flow_key)))
return pos;
}
return NULL;
}
/*
* Checks that all flow_key_entrys in list 'keys' return successful lookups on
* the table 'swt'.
*/
static int
check_lookup(struct sw_table *swt, struct list_head *keys)
{
struct flow_key_entry *pos;
list_for_each_entry(pos, keys, node) {
if(swt->lookup(swt, &pos->key) == NULL)
return -1;
}
return 0;
}
/*
* Checks that all flow_key_entrys in list 'keys' DO NOT return successful
* lookups in the 'swt' table.
*/
static int
check_no_lookup(struct sw_table *swt, struct list_head *keys)
{
struct flow_key_entry *pos;
list_for_each_entry(pos, keys, node) {
if(swt->lookup(swt, &pos->key) != NULL)
return -1;
}
return 0;
}
struct check_iteration_state
{
int n_found;
struct list_head *to_find;
struct list_head *found;
};
static int
check_iteration_callback(struct sw_flow *flow, void *private)
{
struct check_iteration_state *s = private;
struct flow_key_entry *entry;
entry = find_flow(s->to_find, flow);
if (entry == NULL) {
unit_fail("UNKNOWN ITERATOR FLOW %p", flow);
rcu_read_unlock();
return 1;
}
s->n_found++;
list_del(&entry->node);
list_add(&entry->node, s->found);
return 0;
}
/*
* Compares an iterator's view of the 'swt' table to the list of
* flow_key_entrys in 'to_find'. flow_key_entrys that are matched are removed
* from the 'to_find' list and placed in the 'found' list. Returns -1 if the
* iterator cannot be initialized or it encounters a flow with a key not in
* 'to_find'. Else returns the number of flows found by the iterator
* (i.e. there might still be flow keys in the 'to_find' list that were not
* encountered by the iterator. it is up to the caller to determine if that is
* acceptable behavior)
*/
static int
check_iteration(struct sw_table *swt, struct list_head *to_find, struct list_head *found)
{
struct sw_flow_key key;
struct sw_table_position position;
struct check_iteration_state state;
memset(&key, 0, sizeof key);
key.wildcards = -1;
memset(&position, 0, sizeof position);
state.n_found = 0;
state.to_find = to_find;
state.found = found;
rcu_read_lock();
swt->iterate(swt, &key, &position, check_iteration_callback, &state);
rcu_read_unlock();
return state.n_found;
}
/*
* Deletes from table 'swt' keys from the list of flow_key_entrys 'keys'.
* Removes flow_key_entrys of deleted flows from 'keys' and places them in the
* 'deleted' list. If 'del_all' == 1, all flows in 'keys' will be deleted,
* else only every third key will be deleted. Returns the number flows deleted
* from the table.
*/
static int
delete_flows(struct sw_table *swt, struct list_head *keys,
struct list_head *deleted, uint8_t del_all)
{
struct flow_key_entry *pos, *next;
int i, n_del, total_del;
total_del = 0;
i = 0;
list_for_each_entry_safe (pos, next, keys, node) {
if (del_all == 1 || i % 3 == 0) {
n_del = swt->delete(swt, &pos->key, 0, 0);
if (n_del > 1) {
unit_fail("%d flows deleted for one entry", n_del);
unit_fail("\tfuture 'errors' could just be product duplicate flow_key_entries");
unit_fail("THIS IS VERY UNLIKELY...SHOULDN'T HAPPEN OFTEN");
}
total_del += n_del;
list_del(&pos->node);
list_add(&pos->node, deleted);
}
i++;
}
return total_del;
}
/*
* Checks that both iteration and lookups are consistent with the caller's view
* of the table. In particular, checks that all keys in flow_key_entry list
* 'deleted' do not show up in lookup or iteration, and keys in flow_key_entry
* list 'added' do show up. 'tmp' should be an empty list that can be used for
* iteration. References to list_head pointers are needed for 'added' and 'tmp'
* because iteration will cause the list_heads to change. Function thus
* switches 'added' to point to the list of added keys after the iteration.
*/
static int
check_lookup_and_iter(struct sw_table *swt, struct list_head *deleted,
struct list_head **added, struct list_head **tmp)
{
struct list_head *tmp2;
int ret;
if (check_no_lookup(swt, deleted) < 0) {
unit_fail("Uninserted flows returning lookup");
return -1;
}
if (check_lookup(swt, *added) < 0) {
unit_fail("Inserted flows not returning lookup");
return -1;
}
ret = check_iteration(swt, *added, *tmp);
tmp2 = *added;
*added = *tmp;
*tmp = tmp2;
if ((*tmp)->next != *tmp) {
unit_fail("WARNING: not all flows in 'added' found by iterator");
unit_fail("\tcould be a product of duplicate flow_key_entrys, though should be VERY rare.");
/* To avoid reoccurence */
(*tmp)->next = (*tmp)->prev = *tmp;
}
return ret;
}
/*
* Verifies iteration and lookup after inserting 'n_flows', then after deleting
* some flows, and once again after deleting all flows in table 'swt'.
*/
static int
iterator_test(struct sw_table *swt, int n_flows, uint16_t wildcards)
{
struct flow_key_entry *allocated, h1, h2;
struct list_head *added, *deleted, *tmp;
int ret, n_del, success;
INIT_LIST_HEAD(&h1.node);
INIT_LIST_HEAD(&h2.node);
success = -1;
allocated = allocate_random_keys(n_flows, wildcards);
if(allocated == NULL)
return success;
deleted = &allocated->node;
added = &h1.node;
tmp = &h2.node;
ret = insert_flows(swt, deleted, added, -1);
if (ret < 0)
goto iterator_test_destr;
n_flows = ret;
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
unit_fail("Bad lookup after insertion");
goto iterator_test_destr;
} else if (ret != n_flows) {
unit_fail("Iterator only found %d of %d flows",
ret, n_flows);
goto iterator_test_destr;
}
n_del = delete_flows(swt, added, deleted, 0);
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
unit_fail("Bad lookup after some deletion");
goto iterator_test_destr;
} else if (ret + n_del != n_flows) {
unit_fail("iterator after deletion inconsistent");
unit_fail("\tn_del = %d, n_found = %d, n_flows = %d",
n_del, ret, n_flows);
goto iterator_test_destr;
}
n_flows -= n_del;
n_del = delete_flows(swt, added, deleted, 1);
if (n_del != n_flows) {
unit_fail("Not all flows deleted - only %d of %d",
n_del, n_flows);
goto iterator_test_destr;
}
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
unit_fail("Bad lookup after all deletion");
goto iterator_test_destr;
} else if (ret != 0) {
unit_fail("Empty table iterator failed. %d flows found",
ret);
goto iterator_test_destr;
}
success = 0;
iterator_test_destr:
allocated->key.wildcards = OFPFW_ALL;
swt->delete(swt, &allocated->key, 0, 0);
vfree(allocated);
return success;
}
/*
* Checks lookup and iteration consistency after adding one flow, adding the
* flow again, and then deleting the flow from table 'swt'.
*/
static int
add_test(struct sw_table *swt, uint16_t wildcards)
{
struct flow_key_entry *allocated, h1, h2;
struct list_head *added, *deleted, *tmp, *tmp2;
int ret, success = -1;
INIT_LIST_HEAD(&h1.node);
INIT_LIST_HEAD(&h2.node);
allocated = allocate_random_keys(2, wildcards);
if (allocated == NULL)
return success;
deleted = &allocated->node;
added = &h1.node;
tmp = &h2.node;
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
unit_fail("Bad lookup before table modification");
goto add_test_destr;
} else if (ret != 0) {
unit_fail("Iterator on empty table found %d flows",
ret);
goto add_test_destr;
}
if (insert_flows(swt, deleted, added, 1) != 1) {
unit_fail("Cannot add one flow to table");
goto add_test_destr;
}
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
unit_fail("Bad lookup after single add");
goto add_test_destr;
} else if (ret != 1) {
unit_fail("Iterator on single add found %d flows",
ret);
goto add_test_destr;
}
/* Re-adding flow */
if (insert_flows(swt, added, tmp, 1) != 1) {
unit_fail("Cannot insert same flow twice");
goto add_test_destr;
}
tmp2 = added;
added = tmp;
tmp = tmp2;
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
unit_fail("Bad lookup after double add");
goto add_test_destr;
} else if (ret != 1) {
unit_fail("Iterator on double add found %d flows",
ret);
goto add_test_destr;
}
ret = delete_flows(swt, added, deleted, 1);
if (ret != 1) {
unit_fail("Unexpected %d flows deleted", ret);
goto add_test_destr;
}
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
unit_fail("Bad lookup after delete.");
goto add_test_destr;
} else if (ret != 0) {
unit_fail("unexpected %d flows found delete", ret);
goto add_test_destr;
}
success = 0;
add_test_destr:
allocated->key.wildcards = OFPFW_ALL;
swt->delete(swt, &allocated->key, 0, 0);
vfree(allocated);
return success;
}
/*
* Checks lookup and iteration consistency after each deleting a non-existent
* flow, adding and then deleting a flow, adding the flow again, and then
* deleting the flow twice in table 'swt'.
*/
static int
delete_test(struct sw_table *swt, uint16_t wildcards)
{
struct flow_key_entry *allocated, h1, h2;
struct list_head *added, *deleted, *tmp, *tmp2;
int i, ret, success = -1;
INIT_LIST_HEAD(&h1.node);
INIT_LIST_HEAD(&h2.node);
allocated = allocate_random_keys(2, wildcards);
if (allocated == NULL)
return success;
/* Not really added...*/
added = &allocated->node;
deleted = &h1.node;
tmp = &h2.node;
ret = delete_flows(swt, added, deleted, 1);
if (ret != 0) {
unit_fail("Deleting non-existent keys from table returned unexpected value %d",
ret);
goto delete_test_destr;
}
for (i = 0; i < 3; i++) {
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
if (i == 0)
unit_fail("Loop %d. Bad lookup before modification.", i);
else
unit_fail("Loop %d. Bad lookup after delete.", i);
goto delete_test_destr;
} else if (ret != 0) {
if(i == 0)
unit_fail("Loop %d. Unexpected %d flows found before modification",
i, ret);
else
unit_fail("Loop %d. Unexpected %d flows found after delete",
i, ret);
goto delete_test_destr;
}
if(i == 2)
break;
if (insert_flows(swt, deleted, added, 1) != 1) {
unit_fail("loop %d: cannot add flow to table", i);
goto delete_test_destr;
}
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
unit_fail("loop %d: bad lookup after single add.", i);
goto delete_test_destr;
} else if (ret != 1) {
unit_fail("loop %d: unexpected %d flows found after single add",
i, ret);
goto delete_test_destr;
}
ret = delete_flows(swt, added, deleted, 1);
if (ret != 1) {
unit_fail("loop %d: deleting inserted key from table returned unexpected value %d",
i, ret);
goto delete_test_destr;
}
}
ret = delete_flows(swt, deleted, tmp, 1);
tmp2 = deleted;
deleted = tmp2;
tmp = tmp2;
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
unit_fail("Bad lookup after double delete.");
goto delete_test_destr;
} else if (ret != 0) {
unit_fail("Unexpected %d flows found after double delete", ret);
goto delete_test_destr;
}
success = 0;
delete_test_destr:
allocated->key.wildcards = OFPFW_ALL;
swt->delete(swt, &allocated->key, 0, 0);
vfree(allocated);
return success;
}
/*
* Randomly adds and deletes from a set of size 'n_flows', looping for 'i'
* iterations.
*/
static int
complex_add_delete_test(struct sw_table *swt, int n_flows, int i, uint16_t wildcards)
{
struct flow_key_entry *allocated, h1, h2;
struct list_head *added, *deleted, *tmp;
int cnt, ret, n_added, n_deleted, success = -1;
uint8_t del_all;
INIT_LIST_HEAD(&h1.node);
INIT_LIST_HEAD(&h2.node);
allocated = allocate_random_keys(n_flows, wildcards);
if (allocated == NULL)
return success;
deleted = &allocated->node;
added = &h1.node;
tmp = &h2.node;
n_deleted = n_flows;
n_added = 0;
for (;i > 0; i--) {
if (n_deleted != 0 && random32() % 2 == 0) {
cnt = random32() % n_deleted;
cnt = insert_flows(swt, deleted, added, cnt);
if (cnt < 0)
goto complex_test_destr;
n_deleted -= cnt;
n_added += cnt;
} else {
if (random32() % 7 == 0)
del_all = 1;
else
del_all = 0;
cnt = delete_flows(swt, added, deleted, del_all);
n_deleted += cnt;
n_added -= cnt;
}
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
unit_fail("Bad lookup on iteration %d.", i);
goto complex_test_destr;
}
}
delete_flows(swt, added, deleted, 1);
ret = check_lookup_and_iter(swt, deleted, &added, &tmp);
if (ret < 0) {
unit_fail("Bad lookup on end deletion.");
goto complex_test_destr;
} else if (ret != 0) {
unit_fail("Unexpected %d flows found on end deletion", ret);
goto complex_test_destr;
}
success = 0;
complex_test_destr:
allocated->key.wildcards = OFPFW_ALL;
swt->delete(swt, &allocated->key, 0, 0);
vfree(allocated);
return success;
}
void run_table_t(void)
{
int linear_max, hash_buckets, hash2_buckets1;
int hash2_buckets2, num_flows, num_iterations;
int i;
struct sw_table *swt;
/* Most basic operations. */
simple_insert_delete(table_linear_create(2048), 0);
simple_insert_delete(table_hash_create(0x04C11DB7, 2048), 0);
simple_insert_delete(table_hash2_create(0x04C11DB7, 2048,
0x1EDC6F41, 2048), 0);
/* Linear table operations. */
multiple_insert_destroy(table_linear_create(2048), 1024, 0, 0, 0);
multiple_insert_destroy(table_linear_create(2048), 2048, 0, 0, 0);
multiple_insert_destroy(table_linear_create(2048), 8192, 0,
8192 - 2048, 8192 - 2048);
/* Hash table operations. */
multiple_insert_destroy(table_hash_create(0x04C11DB7, 2048), 1024, 0,
100, 300);
multiple_insert_destroy(table_hash_create(0x04C11DB7, 2048), 2048, 0,
500, 1000);
multiple_insert_destroy(table_hash_create(0x04C11DB7, 1 << 20), 8192, 0,
0, 50);
multiple_insert_destroy(table_hash_create(0x04C11DB7, 1 << 20), 65536, 0,
1500, 3000);
/* Hash table 2, two hash functions. */
multiple_insert_destroy(table_hash2_create(0x04C11DB7, 2048,
0x1EDC6F41, 2048), 1024, 0, 0, 20);
multiple_insert_destroy(table_hash2_create(0x04C11DB7, 2048,
0x1EDC6F41, 2048), 2048, 0, 50, 200);
multiple_insert_destroy(table_hash2_create(0x04C11DB7, 1<<20,
0x1EDC6F41, 1<<20), 8192, 0, 0, 20);
multiple_insert_destroy(table_hash2_create(0x04C11DB7, 1<<20,
0x1EDC6F41, 1<<20), 65536, 0, 0, 20);
/* Hash table 2, one hash function. */
multiple_insert_destroy(table_hash2_create(0x04C11DB7, 2048,
0x04C11DB7, 2048), 1024, 0, 0, 50);
multiple_insert_destroy(table_hash2_create(0x04C11DB7, 2048,
0x04C11DB7, 2048), 2048, 0, 100, 300);
multiple_insert_destroy(table_hash2_create(0x04C11DB7, 1<<20,
0x04C11DB7, 1<<20), 8192, 0, 0, 20);
multiple_insert_destroy(table_hash2_create(0x04C11DB7, 1<<20,
0x04C11DB7, 1<<20), 65536, 0, 0, 100);
multiple_insert_destroy(table_hash2_create(0x04C11DB7, 1<<20,
0x04C11DB7, 1<<20), 1<<16, 0, 0, 100);
linear_max = 2048;
hash_buckets = 2048;
hash2_buckets1 = 1024;
hash2_buckets2 = 1024;
num_flows = 2300;
num_iterations = 100;
printk("\nTesting on each table type:\n");
printk(" iteration_test on 0 flows\n");
printk(" iteration_test on %d flows\n", num_flows);
printk(" add_test\n");
printk(" delete_test\n");
printk(" complex_add_delete_test with %d flows and %d iterations\n\n",
num_flows, num_iterations);
for (i = 0; i < 3; i++) {
unsigned int mask = i == 0 ? : 0;
if (unit_failed())
return;
mask = 0;
switch (i) {
case 0:
swt = table_linear_create(linear_max);
break;
case 1:
swt = table_hash_create (0x04C11DB7, hash_buckets);
break;
case 2:
swt = table_hash2_create(0x04C11DB7, hash2_buckets1,
0x1EDC6F41, hash2_buckets2);
break;
default:
BUG();
return;
}
if (swt == NULL) {
unit_fail("failed to allocate table %d", i);
return;
}
printk("Testing %s table with %d buckets and %d max flows...\n",
table_name(swt), hash_buckets, num_flows);
iterator_test(swt, 0, mask);
iterator_test(swt, num_flows, mask);
add_test(swt, mask);
delete_test(swt, mask);
complex_add_delete_test(swt, num_flows, num_iterations, mask);
swt->destroy(swt);
}
}