]>
Commit | Line | Data |
---|---|---|
064af421 | 1 | /* |
da9cfca6 | 2 | * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc. |
064af421 | 3 | * |
a14bc59f BP |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
064af421 | 7 | * |
a14bc59f BP |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
064af421 BP |
15 | */ |
16 | ||
8205fbc8 BP |
17 | /* Tests for classifier, written with knowledge of and to advantage of the |
18 | * classifier's internal structure. | |
064af421 BP |
19 | * |
20 | * With very few exceptions, these tests obtain complete coverage of every | |
21 | * basic block and every branch in the classifier implementation, e.g. a clean | |
22 | * report from "gcov -b". (Covering the exceptions would require finding | |
23 | * collisions in the hash function used for flow data, etc.) | |
24 | * | |
25 | * This test should receive a clean report from "valgrind --leak-check=full": | |
26 | * it frees every heap block that it allocates. | |
27 | */ | |
28 | ||
29 | #include <config.h> | |
38c449e0 | 30 | #undef NDEBUG |
3f636c7e | 31 | #include "classifier.h" |
38c449e0 | 32 | #include <assert.h> |
064af421 BP |
33 | #include <errno.h> |
34 | #include <limits.h> | |
10a24935 | 35 | #include "byte-order.h" |
38c449e0 | 36 | #include "classifier-private.h" |
3223e977 | 37 | #include "command-line.h" |
1add079e | 38 | #include "fatal-signal.h" |
064af421 | 39 | #include "flow.h" |
3f636c7e | 40 | #include "ovstest.h" |
1add079e JR |
41 | #include "ovs-atomic.h" |
42 | #include "ovs-thread.h" | |
064af421 | 43 | #include "packets.h" |
b028db44 | 44 | #include "random.h" |
1add079e | 45 | #include "timeval.h" |
c0a56d9f | 46 | #include "unaligned.h" |
38c449e0 | 47 | #include "util.h" |
3d91d909 | 48 | |
3bbe9a1f JR |
49 | static bool versioned = false; |
50 | ||
b5d97350 | 51 | /* Fields in a rule. */ |
efe2037e WT |
52 | #define CLS_FIELDS \ |
53 | /* struct flow all-caps */ \ | |
54 | /* member name name */ \ | |
55 | /* ----------- -------- */ \ | |
56 | CLS_FIELD(tunnel.tun_id, TUN_ID) \ | |
57 | CLS_FIELD(metadata, METADATA) \ | |
58 | CLS_FIELD(nw_src, NW_SRC) \ | |
59 | CLS_FIELD(nw_dst, NW_DST) \ | |
60 | CLS_FIELD(in_port.ofp_port, IN_PORT) \ | |
f0fb825a | 61 | CLS_FIELD(vlans[0].tci, VLAN_TCI) \ |
efe2037e WT |
62 | CLS_FIELD(dl_type, DL_TYPE) \ |
63 | CLS_FIELD(tp_src, TP_SRC) \ | |
64 | CLS_FIELD(tp_dst, TP_DST) \ | |
65 | CLS_FIELD(dl_src, DL_SRC) \ | |
66 | CLS_FIELD(dl_dst, DL_DST) \ | |
67 | CLS_FIELD(nw_proto, NW_PROTO) \ | |
68 | CLS_FIELD(nw_tos, NW_DSCP) | |
b5d97350 BP |
69 | |
70 | /* Field indexes. | |
71 | * | |
72 | * (These are also indexed into struct classifier's 'tables' array.) */ | |
73 | enum { | |
0bdc4bec | 74 | #define CLS_FIELD(MEMBER, NAME) CLS_F_IDX_##NAME, |
b5d97350 BP |
75 | CLS_FIELDS |
76 | #undef CLS_FIELD | |
77 | CLS_N_FIELDS | |
78 | }; | |
79 | ||
80 | /* Field information. */ | |
81 | struct cls_field { | |
82 | int ofs; /* Offset in struct flow. */ | |
83 | int len; /* Length in bytes. */ | |
b5d97350 BP |
84 | const char *name; /* Name (for debugging). */ |
85 | }; | |
86 | ||
87 | static const struct cls_field cls_fields[CLS_N_FIELDS] = { | |
0bdc4bec | 88 | #define CLS_FIELD(MEMBER, NAME) \ |
b5d97350 BP |
89 | { offsetof(struct flow, MEMBER), \ |
90 | sizeof ((struct flow *)0)->MEMBER, \ | |
b5d97350 BP |
91 | #NAME }, |
92 | CLS_FIELDS | |
93 | #undef CLS_FIELD | |
94 | }; | |
95 | ||
064af421 | 96 | struct test_rule { |
3bbe9a1f | 97 | struct ovs_list list_node; |
064af421 BP |
98 | int aux; /* Auxiliary data. */ |
99 | struct cls_rule cls_rule; /* Classifier rule data. */ | |
100 | }; | |
101 | ||
102 | static struct test_rule * | |
103 | test_rule_from_cls_rule(const struct cls_rule *rule) | |
104 | { | |
105 | return rule ? CONTAINER_OF(rule, struct test_rule, cls_rule) : NULL; | |
106 | } | |
107 | ||
f2f3f5cb BP |
108 | static void |
109 | test_rule_destroy(struct test_rule *rule) | |
110 | { | |
111 | if (rule) { | |
112 | cls_rule_destroy(&rule->cls_rule); | |
113 | free(rule); | |
114 | } | |
115 | } | |
116 | ||
bd53aa17 | 117 | static struct test_rule *make_rule(int wc_fields, int priority, int value_pat); |
48d28ac1 BP |
118 | static void free_rule(struct test_rule *); |
119 | static struct test_rule *clone_rule(const struct test_rule *); | |
120 | ||
064af421 BP |
121 | /* Trivial (linear) classifier. */ |
122 | struct tcls { | |
123 | size_t n_rules; | |
124 | size_t allocated_rules; | |
125 | struct test_rule **rules; | |
126 | }; | |
127 | ||
128 | static void | |
129 | tcls_init(struct tcls *tcls) | |
130 | { | |
131 | tcls->n_rules = 0; | |
132 | tcls->allocated_rules = 0; | |
133 | tcls->rules = NULL; | |
134 | } | |
135 | ||
136 | static void | |
137 | tcls_destroy(struct tcls *tcls) | |
138 | { | |
139 | if (tcls) { | |
140 | size_t i; | |
141 | ||
142 | for (i = 0; i < tcls->n_rules; i++) { | |
f2f3f5cb | 143 | test_rule_destroy(tcls->rules[i]); |
064af421 BP |
144 | } |
145 | free(tcls->rules); | |
146 | } | |
147 | } | |
148 | ||
064af421 BP |
149 | static bool |
150 | tcls_is_empty(const struct tcls *tcls) | |
151 | { | |
152 | return tcls->n_rules == 0; | |
153 | } | |
154 | ||
155 | static struct test_rule * | |
156 | tcls_insert(struct tcls *tcls, const struct test_rule *rule) | |
157 | { | |
158 | size_t i; | |
159 | ||
064af421 BP |
160 | for (i = 0; i < tcls->n_rules; i++) { |
161 | const struct cls_rule *pos = &tcls->rules[i]->cls_rule; | |
193eb874 BP |
162 | if (cls_rule_equal(pos, &rule->cls_rule)) { |
163 | /* Exact match. */ | |
42767cce | 164 | ovsrcu_postpone(free_rule, tcls->rules[i]); |
48d28ac1 | 165 | tcls->rules[i] = clone_rule(rule); |
064af421 | 166 | return tcls->rules[i]; |
af7b73f4 | 167 | } else if (pos->priority < rule->cls_rule.priority) { |
064af421 BP |
168 | break; |
169 | } | |
170 | } | |
171 | ||
172 | if (tcls->n_rules >= tcls->allocated_rules) { | |
173 | tcls->rules = x2nrealloc(tcls->rules, &tcls->allocated_rules, | |
174 | sizeof *tcls->rules); | |
175 | } | |
176 | if (i != tcls->n_rules) { | |
177 | memmove(&tcls->rules[i + 1], &tcls->rules[i], | |
178 | sizeof *tcls->rules * (tcls->n_rules - i)); | |
179 | } | |
48d28ac1 | 180 | tcls->rules[i] = clone_rule(rule); |
064af421 BP |
181 | tcls->n_rules++; |
182 | return tcls->rules[i]; | |
183 | } | |
184 | ||
185 | static void | |
186 | tcls_remove(struct tcls *cls, const struct test_rule *rule) | |
187 | { | |
188 | size_t i; | |
189 | ||
190 | for (i = 0; i < cls->n_rules; i++) { | |
191 | struct test_rule *pos = cls->rules[i]; | |
192 | if (pos == rule) { | |
f2f3f5cb BP |
193 | test_rule_destroy(pos); |
194 | ||
064af421 BP |
195 | memmove(&cls->rules[i], &cls->rules[i + 1], |
196 | sizeof *cls->rules * (cls->n_rules - i - 1)); | |
f2f3f5cb | 197 | |
064af421 BP |
198 | cls->n_rules--; |
199 | return; | |
200 | } | |
201 | } | |
428b2edd | 202 | OVS_NOT_REACHED(); |
064af421 BP |
203 | } |
204 | ||
064af421 | 205 | static bool |
5cb7a798 | 206 | match(const struct cls_rule *wild_, const struct flow *fixed) |
064af421 | 207 | { |
5cb7a798 | 208 | struct match wild; |
064af421 BP |
209 | int f_idx; |
210 | ||
5cb7a798 | 211 | minimatch_expand(&wild_->match, &wild); |
064af421 | 212 | for (f_idx = 0; f_idx < CLS_N_FIELDS; f_idx++) { |
d8ae4d67 BP |
213 | bool eq; |
214 | ||
0bdc4bec | 215 | if (f_idx == CLS_F_IDX_NW_SRC) { |
5cb7a798 BP |
216 | eq = !((fixed->nw_src ^ wild.flow.nw_src) |
217 | & wild.wc.masks.nw_src); | |
d8ae4d67 | 218 | } else if (f_idx == CLS_F_IDX_NW_DST) { |
5cb7a798 BP |
219 | eq = !((fixed->nw_dst ^ wild.flow.nw_dst) |
220 | & wild.wc.masks.nw_dst); | |
73f33563 | 221 | } else if (f_idx == CLS_F_IDX_TP_SRC) { |
5cb7a798 BP |
222 | eq = !((fixed->tp_src ^ wild.flow.tp_src) |
223 | & wild.wc.masks.tp_src); | |
73f33563 | 224 | } else if (f_idx == CLS_F_IDX_TP_DST) { |
5cb7a798 BP |
225 | eq = !((fixed->tp_dst ^ wild.flow.tp_dst) |
226 | & wild.wc.masks.tp_dst); | |
73c0ce34 | 227 | } else if (f_idx == CLS_F_IDX_DL_SRC) { |
5cb7a798 BP |
228 | eq = eth_addr_equal_except(fixed->dl_src, wild.flow.dl_src, |
229 | wild.wc.masks.dl_src); | |
73c0ce34 | 230 | } else if (f_idx == CLS_F_IDX_DL_DST) { |
5cb7a798 BP |
231 | eq = eth_addr_equal_except(fixed->dl_dst, wild.flow.dl_dst, |
232 | wild.wc.masks.dl_dst); | |
66642cb4 | 233 | } else if (f_idx == CLS_F_IDX_VLAN_TCI) { |
f0fb825a EG |
234 | eq = !((fixed->vlans[0].tci ^ wild.flow.vlans[0].tci) |
235 | & wild.wc.masks.vlans[0].tci); | |
8368c090 | 236 | } else if (f_idx == CLS_F_IDX_TUN_ID) { |
296e07ac JG |
237 | eq = !((fixed->tunnel.tun_id ^ wild.flow.tunnel.tun_id) |
238 | & wild.wc.masks.tunnel.tun_id); | |
7525e578 | 239 | } else if (f_idx == CLS_F_IDX_METADATA) { |
5cb7a798 BP |
240 | eq = !((fixed->metadata ^ wild.flow.metadata) |
241 | & wild.wc.masks.metadata); | |
2486e66a | 242 | } else if (f_idx == CLS_F_IDX_NW_DSCP) { |
5cb7a798 BP |
243 | eq = !((fixed->nw_tos ^ wild.flow.nw_tos) & |
244 | (wild.wc.masks.nw_tos & IP_DSCP_MASK)); | |
851d3105 | 245 | } else if (f_idx == CLS_F_IDX_NW_PROTO) { |
5cb7a798 BP |
246 | eq = !((fixed->nw_proto ^ wild.flow.nw_proto) |
247 | & wild.wc.masks.nw_proto); | |
e2170cff | 248 | } else if (f_idx == CLS_F_IDX_DL_TYPE) { |
5cb7a798 BP |
249 | eq = !((fixed->dl_type ^ wild.flow.dl_type) |
250 | & wild.wc.masks.dl_type); | |
0bdc4bec | 251 | } else if (f_idx == CLS_F_IDX_IN_PORT) { |
4e022ec0 AW |
252 | eq = !((fixed->in_port.ofp_port |
253 | ^ wild.flow.in_port.ofp_port) | |
254 | & wild.wc.masks.in_port.ofp_port); | |
d8ae4d67 | 255 | } else { |
428b2edd | 256 | OVS_NOT_REACHED(); |
064af421 BP |
257 | } |
258 | ||
d8ae4d67 BP |
259 | if (!eq) { |
260 | return false; | |
064af421 | 261 | } |
064af421 BP |
262 | } |
263 | return true; | |
264 | } | |
265 | ||
266 | static struct cls_rule * | |
3c4486a5 | 267 | tcls_lookup(const struct tcls *cls, const struct flow *flow) |
064af421 BP |
268 | { |
269 | size_t i; | |
270 | ||
271 | for (i = 0; i < cls->n_rules; i++) { | |
272 | struct test_rule *pos = cls->rules[i]; | |
3c4486a5 | 273 | if (match(&pos->cls_rule, flow)) { |
064af421 BP |
274 | return &pos->cls_rule; |
275 | } | |
276 | } | |
277 | return NULL; | |
278 | } | |
279 | ||
280 | static void | |
3c4486a5 | 281 | tcls_delete_matches(struct tcls *cls, const struct cls_rule *target) |
064af421 BP |
282 | { |
283 | size_t i; | |
284 | ||
285 | for (i = 0; i < cls->n_rules; ) { | |
286 | struct test_rule *pos = cls->rules[i]; | |
8fd47924 JR |
287 | if (!minimask_has_extra(pos->cls_rule.match.mask, |
288 | target->match.mask)) { | |
5cb7a798 BP |
289 | struct flow flow; |
290 | ||
8fd47924 | 291 | miniflow_expand(pos->cls_rule.match.flow, &flow); |
5cb7a798 BP |
292 | if (match(target, &flow)) { |
293 | tcls_remove(cls, pos); | |
294 | continue; | |
295 | } | |
064af421 | 296 | } |
5cb7a798 | 297 | i++; |
064af421 BP |
298 | } |
299 | } | |
300 | \f | |
3c4486a5 | 301 | static ovs_be32 nw_src_values[] = { CONSTANT_HTONL(0xc0a80001), |
965f03d8 | 302 | CONSTANT_HTONL(0xc0a04455) }; |
3c4486a5 | 303 | static ovs_be32 nw_dst_values[] = { CONSTANT_HTONL(0xc0a80002), |
965f03d8 | 304 | CONSTANT_HTONL(0xc0a04455) }; |
b9298d3f BP |
305 | static ovs_be64 tun_id_values[] = { |
306 | 0, | |
307 | CONSTANT_HTONLL(UINT64_C(0xfedcba9876543210)) }; | |
7525e578 JS |
308 | static ovs_be64 metadata_values[] = { |
309 | 0, | |
310 | CONSTANT_HTONLL(UINT64_C(0xfedcba9876543210)) }; | |
4e022ec0 | 311 | static ofp_port_t in_port_values[] = { OFP_PORT_C(1), OFPP_LOCAL }; |
66642cb4 | 312 | static ovs_be16 vlan_tci_values[] = { CONSTANT_HTONS(101), CONSTANT_HTONS(0) }; |
3c4486a5 | 313 | static ovs_be16 dl_type_values[] |
965f03d8 | 314 | = { CONSTANT_HTONS(ETH_TYPE_IP), CONSTANT_HTONS(ETH_TYPE_ARP) }; |
3c4486a5 | 315 | static ovs_be16 tp_src_values[] = { CONSTANT_HTONS(49362), |
965f03d8 | 316 | CONSTANT_HTONS(80) }; |
3c4486a5 | 317 | static ovs_be16 tp_dst_values[] = { CONSTANT_HTONS(6667), CONSTANT_HTONS(22) }; |
74ff3298 | 318 | static struct eth_addr dl_src_values[] = { |
134fefa4 BP |
319 | ETH_ADDR_C(00,02,e3,0f,80,a4), |
320 | ETH_ADDR_C(5e,33,7f,5f,1e,99) | |
321 | }; | |
74ff3298 | 322 | static struct eth_addr dl_dst_values[] = { |
134fefa4 BP |
323 | ETH_ADDR_C(4a,27,71,ae,64,c1), |
324 | ETH_ADDR_C(ff,ff,ff,ff,ff,ff) | |
325 | }; | |
6767a2cc | 326 | static uint8_t nw_proto_values[] = { IPPROTO_TCP, IPPROTO_ICMP }; |
2486e66a | 327 | static uint8_t nw_dscp_values[] = { 48, 0 }; |
064af421 BP |
328 | |
329 | static void *values[CLS_N_FIELDS][2]; | |
330 | ||
331 | static void | |
332 | init_values(void) | |
333 | { | |
659586ef JG |
334 | values[CLS_F_IDX_TUN_ID][0] = &tun_id_values[0]; |
335 | values[CLS_F_IDX_TUN_ID][1] = &tun_id_values[1]; | |
336 | ||
7525e578 JS |
337 | values[CLS_F_IDX_METADATA][0] = &metadata_values[0]; |
338 | values[CLS_F_IDX_METADATA][1] = &metadata_values[1]; | |
339 | ||
064af421 BP |
340 | values[CLS_F_IDX_IN_PORT][0] = &in_port_values[0]; |
341 | values[CLS_F_IDX_IN_PORT][1] = &in_port_values[1]; | |
342 | ||
66642cb4 BP |
343 | values[CLS_F_IDX_VLAN_TCI][0] = &vlan_tci_values[0]; |
344 | values[CLS_F_IDX_VLAN_TCI][1] = &vlan_tci_values[1]; | |
959a2ecd | 345 | |
74ff3298 JR |
346 | values[CLS_F_IDX_DL_SRC][0] = &dl_src_values[0]; |
347 | values[CLS_F_IDX_DL_SRC][1] = &dl_src_values[1]; | |
064af421 | 348 | |
74ff3298 JR |
349 | values[CLS_F_IDX_DL_DST][0] = &dl_dst_values[0]; |
350 | values[CLS_F_IDX_DL_DST][1] = &dl_dst_values[1]; | |
064af421 BP |
351 | |
352 | values[CLS_F_IDX_DL_TYPE][0] = &dl_type_values[0]; | |
353 | values[CLS_F_IDX_DL_TYPE][1] = &dl_type_values[1]; | |
354 | ||
355 | values[CLS_F_IDX_NW_SRC][0] = &nw_src_values[0]; | |
356 | values[CLS_F_IDX_NW_SRC][1] = &nw_src_values[1]; | |
357 | ||
358 | values[CLS_F_IDX_NW_DST][0] = &nw_dst_values[0]; | |
359 | values[CLS_F_IDX_NW_DST][1] = &nw_dst_values[1]; | |
360 | ||
361 | values[CLS_F_IDX_NW_PROTO][0] = &nw_proto_values[0]; | |
362 | values[CLS_F_IDX_NW_PROTO][1] = &nw_proto_values[1]; | |
363 | ||
2486e66a JP |
364 | values[CLS_F_IDX_NW_DSCP][0] = &nw_dscp_values[0]; |
365 | values[CLS_F_IDX_NW_DSCP][1] = &nw_dscp_values[1]; | |
834377ea | 366 | |
064af421 BP |
367 | values[CLS_F_IDX_TP_SRC][0] = &tp_src_values[0]; |
368 | values[CLS_F_IDX_TP_SRC][1] = &tp_src_values[1]; | |
369 | ||
370 | values[CLS_F_IDX_TP_DST][0] = &tp_dst_values[0]; | |
371 | values[CLS_F_IDX_TP_DST][1] = &tp_dst_values[1]; | |
372 | } | |
373 | ||
374 | #define N_NW_SRC_VALUES ARRAY_SIZE(nw_src_values) | |
375 | #define N_NW_DST_VALUES ARRAY_SIZE(nw_dst_values) | |
659586ef | 376 | #define N_TUN_ID_VALUES ARRAY_SIZE(tun_id_values) |
7525e578 | 377 | #define N_METADATA_VALUES ARRAY_SIZE(metadata_values) |
064af421 | 378 | #define N_IN_PORT_VALUES ARRAY_SIZE(in_port_values) |
66642cb4 | 379 | #define N_VLAN_TCI_VALUES ARRAY_SIZE(vlan_tci_values) |
064af421 BP |
380 | #define N_DL_TYPE_VALUES ARRAY_SIZE(dl_type_values) |
381 | #define N_TP_SRC_VALUES ARRAY_SIZE(tp_src_values) | |
382 | #define N_TP_DST_VALUES ARRAY_SIZE(tp_dst_values) | |
383 | #define N_DL_SRC_VALUES ARRAY_SIZE(dl_src_values) | |
384 | #define N_DL_DST_VALUES ARRAY_SIZE(dl_dst_values) | |
385 | #define N_NW_PROTO_VALUES ARRAY_SIZE(nw_proto_values) | |
2486e66a | 386 | #define N_NW_DSCP_VALUES ARRAY_SIZE(nw_dscp_values) |
064af421 BP |
387 | |
388 | #define N_FLOW_VALUES (N_NW_SRC_VALUES * \ | |
389 | N_NW_DST_VALUES * \ | |
659586ef | 390 | N_TUN_ID_VALUES * \ |
064af421 | 391 | N_IN_PORT_VALUES * \ |
66642cb4 | 392 | N_VLAN_TCI_VALUES * \ |
064af421 BP |
393 | N_DL_TYPE_VALUES * \ |
394 | N_TP_SRC_VALUES * \ | |
395 | N_TP_DST_VALUES * \ | |
396 | N_DL_SRC_VALUES * \ | |
397 | N_DL_DST_VALUES * \ | |
834377ea | 398 | N_NW_PROTO_VALUES * \ |
2486e66a | 399 | N_NW_DSCP_VALUES) |
064af421 BP |
400 | |
401 | static unsigned int | |
402 | get_value(unsigned int *x, unsigned n_values) | |
403 | { | |
404 | unsigned int rem = *x % n_values; | |
405 | *x /= n_values; | |
406 | return rem; | |
407 | } | |
408 | ||
064af421 | 409 | static void |
3bbe9a1f | 410 | compare_classifiers(struct classifier *cls, size_t n_invisible_rules, |
44e0c35d | 411 | ovs_version_t version, struct tcls *tcls) |
064af421 | 412 | { |
70d3fbe7 | 413 | static const int confidence = 500; |
064af421 BP |
414 | unsigned int i; |
415 | ||
3bbe9a1f | 416 | assert(classifier_count(cls) == tcls->n_rules + n_invisible_rules); |
70d3fbe7 | 417 | for (i = 0; i < confidence; i++) { |
dfea28b3 | 418 | const struct cls_rule *cr0, *cr1, *cr2; |
ae412e7d | 419 | struct flow flow; |
476f36e8 | 420 | struct flow_wildcards wc; |
064af421 | 421 | unsigned int x; |
064af421 | 422 | |
476f36e8 | 423 | flow_wildcards_init_catchall(&wc); |
b028db44 | 424 | x = random_range(N_FLOW_VALUES); |
51c14ddd | 425 | memset(&flow, 0, sizeof flow); |
064af421 BP |
426 | flow.nw_src = nw_src_values[get_value(&x, N_NW_SRC_VALUES)]; |
427 | flow.nw_dst = nw_dst_values[get_value(&x, N_NW_DST_VALUES)]; | |
296e07ac | 428 | flow.tunnel.tun_id = tun_id_values[get_value(&x, N_TUN_ID_VALUES)]; |
7525e578 | 429 | flow.metadata = metadata_values[get_value(&x, N_METADATA_VALUES)]; |
4e022ec0 AW |
430 | flow.in_port.ofp_port = in_port_values[get_value(&x, |
431 | N_IN_PORT_VALUES)]; | |
f0fb825a | 432 | flow.vlans[0].tci = vlan_tci_values[get_value(&x, N_VLAN_TCI_VALUES)]; |
064af421 BP |
433 | flow.dl_type = dl_type_values[get_value(&x, N_DL_TYPE_VALUES)]; |
434 | flow.tp_src = tp_src_values[get_value(&x, N_TP_SRC_VALUES)]; | |
435 | flow.tp_dst = tp_dst_values[get_value(&x, N_TP_DST_VALUES)]; | |
74ff3298 JR |
436 | flow.dl_src = dl_src_values[get_value(&x, N_DL_SRC_VALUES)]; |
437 | flow.dl_dst = dl_dst_values[get_value(&x, N_DL_DST_VALUES)]; | |
064af421 | 438 | flow.nw_proto = nw_proto_values[get_value(&x, N_NW_PROTO_VALUES)]; |
2486e66a | 439 | flow.nw_tos = nw_dscp_values[get_value(&x, N_NW_DSCP_VALUES)]; |
064af421 | 440 | |
c424c2f7 | 441 | /* This assertion is here to suppress a GCC 4.9 array-bounds warning */ |
e48eccd1 | 442 | ovs_assert(cls->n_tries <= CLS_MAX_TRIES); |
c424c2f7 | 443 | |
3bbe9a1f | 444 | cr0 = classifier_lookup(cls, version, &flow, &wc); |
3c4486a5 BP |
445 | cr1 = tcls_lookup(tcls, &flow); |
446 | assert((cr0 == NULL) == (cr1 == NULL)); | |
447 | if (cr0 != NULL) { | |
448 | const struct test_rule *tr0 = test_rule_from_cls_rule(cr0); | |
449 | const struct test_rule *tr1 = test_rule_from_cls_rule(cr1); | |
450 | ||
193eb874 | 451 | assert(cls_rule_equal(cr0, cr1)); |
3c4486a5 | 452 | assert(tr0->aux == tr1->aux); |
3bbe9a1f JR |
453 | |
454 | /* Make sure the rule should have been visible. */ | |
5e27fe97 | 455 | assert(cls_rule_visible_in_version(cr0, version)); |
064af421 | 456 | } |
3bbe9a1f | 457 | cr2 = classifier_lookup(cls, version, &flow, NULL); |
476f36e8 | 458 | assert(cr2 == cr0); |
064af421 BP |
459 | } |
460 | } | |
461 | ||
064af421 BP |
462 | static void |
463 | destroy_classifier(struct classifier *cls) | |
464 | { | |
78c8df12 | 465 | struct test_rule *rule; |
5ecc9d81 | 466 | |
802f84ff | 467 | classifier_defer(cls); |
de4ad4a2 | 468 | CLS_FOR_EACH (rule, cls_rule, cls) { |
46ab60bf BP |
469 | classifier_remove_assert(cls, &rule->cls_rule); |
470 | ovsrcu_postpone(free_rule, rule); | |
5ecc9d81 | 471 | } |
064af421 BP |
472 | classifier_destroy(cls); |
473 | } | |
474 | ||
fe7cfa5c | 475 | static void |
da9cfca6 | 476 | pvector_verify(const struct pvector *pvec) |
fe7cfa5c JR |
477 | { |
478 | void *ptr OVS_UNUSED; | |
eb391b76 | 479 | int prev_priority = INT_MAX; |
fe7cfa5c | 480 | |
da9cfca6 | 481 | PVECTOR_FOR_EACH (ptr, pvec) { |
eb391b76 | 482 | int priority = cursor__.vector[cursor__.entry_idx].priority; |
fe7cfa5c | 483 | if (priority > prev_priority) { |
38c449e0 JR |
484 | ovs_abort(0, "Priority vector is out of order (%u > %u)", |
485 | priority, prev_priority); | |
fe7cfa5c JR |
486 | } |
487 | prev_priority = priority; | |
488 | } | |
489 | } | |
490 | ||
f358a2cb JR |
491 | static unsigned int |
492 | trie_verify(const rcu_trie_ptr *trie, unsigned int ofs, unsigned int n_bits) | |
493 | { | |
494 | const struct trie_node *node = ovsrcu_get(struct trie_node *, trie); | |
495 | ||
496 | if (node) { | |
497 | assert(node->n_rules == 0 || node->n_bits > 0); | |
498 | ofs += node->n_bits; | |
499 | assert((ofs > 0 || (ofs == 0 && node->n_bits == 0)) && ofs <= n_bits); | |
500 | ||
501 | return node->n_rules | |
502 | + trie_verify(&node->edges[0], ofs, n_bits) | |
503 | + trie_verify(&node->edges[1], ofs, n_bits); | |
504 | } | |
505 | return 0; | |
506 | } | |
507 | ||
508 | static void | |
e48eccd1 | 509 | verify_tries(struct classifier *cls) |
fccd7c09 | 510 | OVS_NO_THREAD_SAFETY_ANALYSIS |
f358a2cb | 511 | { |
f358a2cb JR |
512 | unsigned int n_rules = 0; |
513 | int i; | |
514 | ||
515 | for (i = 0; i < cls->n_tries; i++) { | |
a6117059 ET |
516 | const struct mf_field * cls_field |
517 | = ovsrcu_get(struct mf_field *, &cls->tries[i].field); | |
518 | n_rules += trie_verify(&cls->tries[i].root, 0, cls_field->n_bits); | |
f358a2cb | 519 | } |
f358a2cb | 520 | assert(n_rules <= cls->n_rules); |
f358a2cb JR |
521 | } |
522 | ||
064af421 | 523 | static void |
0b4f2078 | 524 | check_tables(const struct classifier *cls, int n_tables, int n_rules, |
44e0c35d | 525 | int n_dups, int n_invisible, ovs_version_t version) |
fccd7c09 | 526 | OVS_NO_THREAD_SAFETY_ANALYSIS |
064af421 | 527 | { |
03868246 | 528 | const struct cls_subtable *table; |
955f579d | 529 | struct test_rule *test_rule; |
064af421 | 530 | int found_tables = 0; |
3bbe9a1f | 531 | int found_tables_with_visible_rules = 0; |
064af421 | 532 | int found_rules = 0; |
b5d97350 | 533 | int found_dups = 0; |
3bbe9a1f JR |
534 | int found_invisible = 0; |
535 | int found_visible_but_removable = 0; | |
955f579d | 536 | int found_rules2 = 0; |
064af421 | 537 | |
da9cfca6 | 538 | pvector_verify(&cls->subtables); |
e48eccd1 | 539 | CMAP_FOR_EACH (table, cmap_node, &cls->subtables_map) { |
627fb667 | 540 | const struct cls_match *head; |
eb391b76 | 541 | int max_priority = INT_MIN; |
4d935a6b | 542 | unsigned int max_count = 0; |
fe7cfa5c | 543 | bool found = false; |
3bbe9a1f | 544 | bool found_visible_rules = false; |
fe7cfa5c JR |
545 | const struct cls_subtable *iter; |
546 | ||
547 | /* Locate the subtable from 'subtables'. */ | |
da9cfca6 | 548 | PVECTOR_FOR_EACH (iter, &cls->subtables) { |
fe7cfa5c JR |
549 | if (iter == table) { |
550 | if (found) { | |
38c449e0 JR |
551 | ovs_abort(0, "Subtable %p duplicated in 'subtables'.", |
552 | table); | |
fe7cfa5c JR |
553 | } |
554 | found = true; | |
555 | } | |
556 | } | |
557 | if (!found) { | |
38c449e0 | 558 | ovs_abort(0, "Subtable %p not found from 'subtables'.", table); |
fe7cfa5c | 559 | } |
b5d97350 | 560 | |
f2c21402 | 561 | assert(!cmap_is_empty(&table->rules)); |
f358a2cb | 562 | assert(trie_verify(&table->ports_trie, 0, table->ports_mask_len) |
f47eef15 | 563 | == (table->ports_mask_len ? cmap_count(&table->rules) : 0)); |
f358a2cb | 564 | |
064af421 | 565 | found_tables++; |
3bbe9a1f | 566 | |
f2c21402 | 567 | CMAP_FOR_EACH (head, cmap_node, &table->rules) { |
eb391b76 | 568 | int prev_priority = INT_MAX; |
44e0c35d | 569 | ovs_version_t prev_version = 0; |
3bbe9a1f JR |
570 | const struct cls_match *rule, *prev; |
571 | bool found_visible_rules_in_list = false; | |
572 | ||
573 | assert(head->priority <= table->max_priority); | |
b5d97350 | 574 | |
4d935a6b JR |
575 | if (head->priority > max_priority) { |
576 | max_priority = head->priority; | |
3bbe9a1f | 577 | max_count = 0; |
4d935a6b JR |
578 | } |
579 | ||
3bbe9a1f | 580 | FOR_EACH_RULE_IN_LIST_PROTECTED(rule, prev, head) { |
44e0c35d | 581 | ovs_version_t rule_version; |
3bbe9a1f JR |
582 | const struct cls_rule *found_rule; |
583 | ||
584 | /* Priority may not increase. */ | |
585 | assert(rule->priority <= prev_priority); | |
586 | ||
587 | if (rule->priority == max_priority) { | |
588 | ++max_count; | |
589 | } | |
590 | ||
591 | /* Count invisible rules and visible duplicates. */ | |
592 | if (!cls_match_visible_in_version(rule, version)) { | |
593 | found_invisible++; | |
594 | } else { | |
595 | if (cls_match_is_eventually_invisible(rule)) { | |
596 | found_visible_but_removable++; | |
597 | } | |
598 | if (found_visible_rules_in_list) { | |
599 | found_dups++; | |
600 | } | |
601 | found_visible_rules_in_list = true; | |
602 | found_visible_rules = true; | |
603 | } | |
604 | ||
605 | /* Rule must be visible in the version it was inserted. */ | |
44e0c35d | 606 | rule_version = rule->versions.add_version; |
3bbe9a1f JR |
607 | assert(cls_match_visible_in_version(rule, rule_version)); |
608 | ||
609 | /* We should always find the latest version of the rule, | |
610 | * unless all rules have been marked for removal. | |
611 | * Later versions must always be later in the list. */ | |
bd53aa17 JR |
612 | found_rule = classifier_find_rule_exactly(cls, rule->cls_rule, |
613 | rule_version); | |
3bbe9a1f | 614 | if (found_rule && found_rule != rule->cls_rule) { |
5e27fe97 JR |
615 | struct cls_match *cls_match; |
616 | cls_match = get_cls_match_protected(found_rule); | |
3bbe9a1f JR |
617 | |
618 | assert(found_rule->priority == rule->priority); | |
619 | ||
620 | /* Found rule may not have a lower version. */ | |
44e0c35d | 621 | assert(cls_match->versions.add_version >= rule_version); |
3bbe9a1f JR |
622 | |
623 | /* This rule must not be visible in the found rule's | |
624 | * version. */ | |
bd53aa17 | 625 | assert(!cls_match_visible_in_version( |
44e0c35d | 626 | rule, cls_match->versions.add_version)); |
3bbe9a1f JR |
627 | } |
628 | ||
629 | if (rule->priority == prev_priority) { | |
630 | /* Exact duplicate rule may not have a lower version. */ | |
631 | assert(rule_version >= prev_version); | |
632 | ||
633 | /* Previous rule must not be visible in rule's version. */ | |
634 | assert(!cls_match_visible_in_version(prev, rule_version)); | |
635 | } | |
4d935a6b | 636 | |
b5d97350 | 637 | prev_priority = rule->priority; |
3bbe9a1f | 638 | prev_version = rule_version; |
b5d97350 | 639 | found_rules++; |
b5d97350 BP |
640 | } |
641 | } | |
3bbe9a1f JR |
642 | |
643 | if (found_visible_rules) { | |
644 | found_tables_with_visible_rules++; | |
645 | } | |
646 | ||
4d935a6b JR |
647 | assert(table->max_priority == max_priority); |
648 | assert(table->max_count == max_count); | |
064af421 BP |
649 | } |
650 | ||
e48eccd1 | 651 | assert(found_tables == cmap_count(&cls->subtables_map)); |
da9cfca6 | 652 | assert(found_tables == pvector_count(&cls->subtables)); |
3bbe9a1f JR |
653 | assert(n_tables == -1 || n_tables == found_tables_with_visible_rules); |
654 | assert(n_rules == -1 || found_rules == n_rules + found_invisible); | |
b5d97350 | 655 | assert(n_dups == -1 || found_dups == n_dups); |
3bbe9a1f | 656 | assert(found_invisible == n_invisible); |
955f579d | 657 | |
5f0476ce | 658 | CLS_FOR_EACH (test_rule, cls_rule, cls) { |
955f579d BP |
659 | found_rules2++; |
660 | } | |
3bbe9a1f JR |
661 | /* Iteration does not see removable rules. */ |
662 | assert(found_rules | |
663 | == found_rules2 + found_visible_but_removable + found_invisible); | |
064af421 BP |
664 | } |
665 | ||
666 | static struct test_rule * | |
bd53aa17 | 667 | make_rule(int wc_fields, int priority, int value_pat) |
064af421 BP |
668 | { |
669 | const struct cls_field *f; | |
670 | struct test_rule *rule; | |
81a76618 | 671 | struct match match; |
064af421 | 672 | |
81a76618 | 673 | match_init_catchall(&match); |
064af421 BP |
674 | for (f = &cls_fields[0]; f < &cls_fields[CLS_N_FIELDS]; f++) { |
675 | int f_idx = f - cls_fields; | |
d8ae4d67 | 676 | int value_idx = (value_pat & (1u << f_idx)) != 0; |
81a76618 | 677 | memcpy((char *) &match.flow + f->ofs, |
d8ae4d67 BP |
678 | values[f_idx][value_idx], f->len); |
679 | ||
0bdc4bec | 680 | if (f_idx == CLS_F_IDX_NW_SRC) { |
b8266395 | 681 | match.wc.masks.nw_src = OVS_BE32_MAX; |
d8ae4d67 | 682 | } else if (f_idx == CLS_F_IDX_NW_DST) { |
b8266395 | 683 | match.wc.masks.nw_dst = OVS_BE32_MAX; |
73f33563 | 684 | } else if (f_idx == CLS_F_IDX_TP_SRC) { |
b8266395 | 685 | match.wc.masks.tp_src = OVS_BE16_MAX; |
73f33563 | 686 | } else if (f_idx == CLS_F_IDX_TP_DST) { |
b8266395 | 687 | match.wc.masks.tp_dst = OVS_BE16_MAX; |
73c0ce34 | 688 | } else if (f_idx == CLS_F_IDX_DL_SRC) { |
74ff3298 | 689 | WC_MASK_FIELD(&match.wc, dl_src); |
73c0ce34 | 690 | } else if (f_idx == CLS_F_IDX_DL_DST) { |
74ff3298 | 691 | WC_MASK_FIELD(&match.wc, dl_dst); |
66642cb4 | 692 | } else if (f_idx == CLS_F_IDX_VLAN_TCI) { |
f0fb825a | 693 | match.wc.masks.vlans[0].tci = OVS_BE16_MAX; |
8368c090 | 694 | } else if (f_idx == CLS_F_IDX_TUN_ID) { |
b8266395 | 695 | match.wc.masks.tunnel.tun_id = OVS_BE64_MAX; |
7525e578 | 696 | } else if (f_idx == CLS_F_IDX_METADATA) { |
b8266395 | 697 | match.wc.masks.metadata = OVS_BE64_MAX; |
5d9499c4 | 698 | } else if (f_idx == CLS_F_IDX_NW_DSCP) { |
81a76618 | 699 | match.wc.masks.nw_tos |= IP_DSCP_MASK; |
851d3105 | 700 | } else if (f_idx == CLS_F_IDX_NW_PROTO) { |
81a76618 | 701 | match.wc.masks.nw_proto = UINT8_MAX; |
e2170cff | 702 | } else if (f_idx == CLS_F_IDX_DL_TYPE) { |
b8266395 | 703 | match.wc.masks.dl_type = OVS_BE16_MAX; |
0bdc4bec | 704 | } else if (f_idx == CLS_F_IDX_IN_PORT) { |
e2711da9 | 705 | match.wc.masks.in_port.ofp_port = u16_to_ofp(UINT16_MAX); |
064af421 | 706 | } else { |
428b2edd | 707 | OVS_NOT_REACHED(); |
064af421 BP |
708 | } |
709 | } | |
81a76618 BP |
710 | |
711 | rule = xzalloc(sizeof *rule); | |
712b4d24 | 712 | cls_rule_init(&rule->cls_rule, &match, wc_fields |
3bbe9a1f JR |
713 | ? (priority == INT_MIN ? priority + 1 : |
714 | priority == INT_MAX ? priority - 1 : priority) | |
bd53aa17 | 715 | : 0); |
064af421 BP |
716 | return rule; |
717 | } | |
718 | ||
48d28ac1 BP |
719 | static struct test_rule * |
720 | clone_rule(const struct test_rule *src) | |
721 | { | |
722 | struct test_rule *dst; | |
723 | ||
724 | dst = xmalloc(sizeof *dst); | |
725 | dst->aux = src->aux; | |
726 | cls_rule_clone(&dst->cls_rule, &src->cls_rule); | |
727 | return dst; | |
728 | } | |
729 | ||
730 | static void | |
731 | free_rule(struct test_rule *rule) | |
732 | { | |
733 | cls_rule_destroy(&rule->cls_rule); | |
734 | free(rule); | |
735 | } | |
736 | ||
064af421 | 737 | static void |
eb391b76 | 738 | shuffle(int *p, size_t n) |
064af421 BP |
739 | { |
740 | for (; n > 1; n--, p++) { | |
eb391b76 BP |
741 | int *q = &p[random_range(n)]; |
742 | int tmp = *p; | |
064af421 BP |
743 | *p = *q; |
744 | *q = tmp; | |
745 | } | |
746 | } | |
5cb7a798 BP |
747 | |
748 | static void | |
749 | shuffle_u32s(uint32_t *p, size_t n) | |
750 | { | |
751 | for (; n > 1; n--, p++) { | |
b028db44 | 752 | uint32_t *q = &p[random_range(n)]; |
5cb7a798 BP |
753 | uint32_t tmp = *p; |
754 | *p = *q; | |
755 | *q = tmp; | |
756 | } | |
757 | } | |
064af421 | 758 | \f |
5cb7a798 BP |
759 | /* Classifier tests. */ |
760 | ||
13751fd8 JR |
761 | static enum mf_field_id trie_fields[2] = { |
762 | MFF_IPV4_DST, MFF_IPV4_SRC | |
763 | }; | |
764 | ||
f358a2cb JR |
765 | static void |
766 | set_prefix_fields(struct classifier *cls) | |
f358a2cb JR |
767 | { |
768 | verify_tries(cls); | |
769 | classifier_set_prefix_fields(cls, trie_fields, ARRAY_SIZE(trie_fields)); | |
770 | verify_tries(cls); | |
771 | } | |
772 | ||
064af421 BP |
773 | /* Tests an empty classifier. */ |
774 | static void | |
1636c761 | 775 | test_empty(struct ovs_cmdl_context *ctx OVS_UNUSED) |
064af421 BP |
776 | { |
777 | struct classifier cls; | |
778 | struct tcls tcls; | |
779 | ||
d70e8c28 | 780 | classifier_init(&cls, flow_segment_u64s); |
f358a2cb | 781 | set_prefix_fields(&cls); |
064af421 BP |
782 | tcls_init(&tcls); |
783 | assert(classifier_is_empty(&cls)); | |
784 | assert(tcls_is_empty(&tcls)); | |
44e0c35d | 785 | compare_classifiers(&cls, 0, OVS_VERSION_MIN, &tcls); |
064af421 BP |
786 | classifier_destroy(&cls); |
787 | tcls_destroy(&tcls); | |
788 | } | |
789 | ||
790 | /* Destroys a null classifier. */ | |
791 | static void | |
1636c761 | 792 | test_destroy_null(struct ovs_cmdl_context *ctx OVS_UNUSED) |
064af421 BP |
793 | { |
794 | classifier_destroy(NULL); | |
795 | } | |
796 | ||
797 | /* Tests classification with one rule at a time. */ | |
798 | static void | |
1636c761 | 799 | test_single_rule(struct ovs_cmdl_context *ctx OVS_UNUSED) |
064af421 BP |
800 | { |
801 | unsigned int wc_fields; /* Hilarious. */ | |
802 | ||
803 | for (wc_fields = 0; wc_fields < (1u << CLS_N_FIELDS); wc_fields++) { | |
804 | struct classifier cls; | |
805 | struct test_rule *rule, *tcls_rule; | |
806 | struct tcls tcls; | |
807 | ||
808 | rule = make_rule(wc_fields, | |
bd53aa17 | 809 | hash_bytes(&wc_fields, sizeof wc_fields, 0), 0); |
d70e8c28 | 810 | classifier_init(&cls, flow_segment_u64s); |
f358a2cb | 811 | set_prefix_fields(&cls); |
064af421 | 812 | tcls_init(&tcls); |
064af421 | 813 | tcls_rule = tcls_insert(&tcls, rule); |
3bbe9a1f | 814 | |
44e0c35d JR |
815 | classifier_insert(&cls, &rule->cls_rule, OVS_VERSION_MIN, NULL, 0); |
816 | compare_classifiers(&cls, 0, OVS_VERSION_MIN, &tcls); | |
817 | check_tables(&cls, 1, 1, 0, 0, OVS_VERSION_MIN); | |
064af421 | 818 | |
46ab60bf | 819 | classifier_remove_assert(&cls, &rule->cls_rule); |
064af421 BP |
820 | tcls_remove(&tcls, tcls_rule); |
821 | assert(classifier_is_empty(&cls)); | |
822 | assert(tcls_is_empty(&tcls)); | |
44e0c35d | 823 | compare_classifiers(&cls, 0, OVS_VERSION_MIN, &tcls); |
064af421 | 824 | |
42767cce | 825 | ovsrcu_postpone(free_rule, rule); |
064af421 BP |
826 | classifier_destroy(&cls); |
827 | tcls_destroy(&tcls); | |
828 | } | |
829 | } | |
830 | ||
831 | /* Tests replacing one rule by another. */ | |
832 | static void | |
1636c761 | 833 | test_rule_replacement(struct ovs_cmdl_context *ctx OVS_UNUSED) |
064af421 BP |
834 | { |
835 | unsigned int wc_fields; | |
836 | ||
837 | for (wc_fields = 0; wc_fields < (1u << CLS_N_FIELDS); wc_fields++) { | |
838 | struct classifier cls; | |
f0f4410a BP |
839 | struct test_rule *rule1; |
840 | struct test_rule *rule2; | |
064af421 BP |
841 | struct tcls tcls; |
842 | ||
bd53aa17 JR |
843 | rule1 = make_rule(wc_fields, OFP_DEFAULT_PRIORITY, UINT_MAX); |
844 | rule2 = make_rule(wc_fields, OFP_DEFAULT_PRIORITY, UINT_MAX); | |
064af421 BP |
845 | rule2->aux += 5; |
846 | rule2->aux += 5; | |
847 | ||
d70e8c28 | 848 | classifier_init(&cls, flow_segment_u64s); |
f358a2cb | 849 | set_prefix_fields(&cls); |
064af421 | 850 | tcls_init(&tcls); |
f0f4410a | 851 | tcls_insert(&tcls, rule1); |
44e0c35d JR |
852 | classifier_insert(&cls, &rule1->cls_rule, OVS_VERSION_MIN, NULL, 0); |
853 | compare_classifiers(&cls, 0, OVS_VERSION_MIN, &tcls); | |
854 | check_tables(&cls, 1, 1, 0, 0, OVS_VERSION_MIN); | |
064af421 BP |
855 | tcls_destroy(&tcls); |
856 | ||
857 | tcls_init(&tcls); | |
f0f4410a | 858 | tcls_insert(&tcls, rule2); |
5f0476ce | 859 | |
064af421 | 860 | assert(test_rule_from_cls_rule( |
44e0c35d | 861 | classifier_replace(&cls, &rule2->cls_rule, OVS_VERSION_MIN, |
18080541 | 862 | NULL, 0)) == rule1); |
42767cce | 863 | ovsrcu_postpone(free_rule, rule1); |
44e0c35d JR |
864 | compare_classifiers(&cls, 0, OVS_VERSION_MIN, &tcls); |
865 | check_tables(&cls, 1, 1, 0, 0, OVS_VERSION_MIN); | |
802f84ff | 866 | classifier_defer(&cls); |
46ab60bf | 867 | classifier_remove_assert(&cls, &rule2->cls_rule); |
5f0476ce JR |
868 | |
869 | tcls_destroy(&tcls); | |
064af421 BP |
870 | destroy_classifier(&cls); |
871 | } | |
872 | } | |
873 | ||
874 | static int | |
b5d97350 | 875 | factorial(int n_items) |
064af421 | 876 | { |
b5d97350 BP |
877 | int n, i; |
878 | ||
879 | n = 1; | |
880 | for (i = 2; i <= n_items; i++) { | |
881 | n *= i; | |
882 | } | |
883 | return n; | |
064af421 BP |
884 | } |
885 | ||
b5d97350 BP |
886 | static void |
887 | swap(int *a, int *b) | |
064af421 | 888 | { |
b5d97350 BP |
889 | int tmp = *a; |
890 | *a = *b; | |
891 | *b = tmp; | |
064af421 BP |
892 | } |
893 | ||
064af421 | 894 | static void |
b5d97350 | 895 | reverse(int *a, int n) |
064af421 | 896 | { |
b5d97350 | 897 | int i; |
064af421 | 898 | |
b5d97350 BP |
899 | for (i = 0; i < n / 2; i++) { |
900 | int j = n - (i + 1); | |
901 | swap(&a[i], &a[j]); | |
902 | } | |
903 | } | |
064af421 | 904 | |
b5d97350 BP |
905 | static bool |
906 | next_permutation(int *a, int n) | |
907 | { | |
908 | int k; | |
064af421 | 909 | |
b5d97350 BP |
910 | for (k = n - 2; k >= 0; k--) { |
911 | if (a[k] < a[k + 1]) { | |
912 | int l; | |
064af421 | 913 | |
b5d97350 BP |
914 | for (l = n - 1; ; l--) { |
915 | if (a[l] > a[k]) { | |
916 | swap(&a[k], &a[l]); | |
917 | reverse(a + (k + 1), n - (k + 1)); | |
918 | return true; | |
064af421 BP |
919 | } |
920 | } | |
921 | } | |
922 | } | |
b5d97350 | 923 | return false; |
064af421 BP |
924 | } |
925 | ||
b5d97350 | 926 | /* Tests classification with rules that have the same matching criteria. */ |
064af421 | 927 | static void |
1636c761 | 928 | test_many_rules_in_one_list (struct ovs_cmdl_context *ctx OVS_UNUSED) |
b5d97350 BP |
929 | { |
930 | enum { N_RULES = 3 }; | |
931 | int n_pris; | |
064af421 | 932 | |
b5d97350 BP |
933 | for (n_pris = N_RULES; n_pris >= 1; n_pris--) { |
934 | int ops[N_RULES * 2]; | |
935 | int pris[N_RULES]; | |
936 | int n_permutations; | |
937 | int i; | |
064af421 | 938 | |
b5d97350 BP |
939 | pris[0] = 0; |
940 | for (i = 1; i < N_RULES; i++) { | |
941 | pris[i] = pris[i - 1] + (n_pris > i); | |
942 | } | |
064af421 | 943 | |
b5d97350 BP |
944 | for (i = 0; i < N_RULES * 2; i++) { |
945 | ops[i] = i / 2; | |
064af421 | 946 | } |
064af421 | 947 | |
b5d97350 BP |
948 | n_permutations = 0; |
949 | do { | |
950 | struct test_rule *rules[N_RULES]; | |
951 | struct test_rule *tcls_rules[N_RULES]; | |
952 | int pri_rules[N_RULES]; | |
953 | struct classifier cls; | |
954 | struct tcls tcls; | |
44e0c35d | 955 | ovs_version_t version = OVS_VERSION_MIN; |
3bbe9a1f | 956 | size_t n_invisible_rules = 0; |
064af421 | 957 | |
b5d97350 | 958 | n_permutations++; |
064af421 | 959 | |
b5d97350 | 960 | for (i = 0; i < N_RULES; i++) { |
bd53aa17 | 961 | rules[i] = make_rule(456, pris[i], 0); |
b5d97350 BP |
962 | tcls_rules[i] = NULL; |
963 | pri_rules[i] = -1; | |
964 | } | |
064af421 | 965 | |
d70e8c28 | 966 | classifier_init(&cls, flow_segment_u64s); |
f358a2cb | 967 | set_prefix_fields(&cls); |
b5d97350 | 968 | tcls_init(&tcls); |
064af421 | 969 | |
b5d97350 | 970 | for (i = 0; i < ARRAY_SIZE(ops); i++) { |
3bbe9a1f JR |
971 | struct test_rule *displaced_rule = NULL; |
972 | struct cls_rule *removable_rule = NULL; | |
b5d97350 BP |
973 | int j = ops[i]; |
974 | int m, n; | |
064af421 | 975 | |
b5d97350 | 976 | if (!tcls_rules[j]) { |
b5d97350 | 977 | tcls_rules[j] = tcls_insert(&tcls, rules[j]); |
3bbe9a1f JR |
978 | if (versioned) { |
979 | /* Insert the new rule in the next version. */ | |
bd53aa17 | 980 | ++version; |
3bbe9a1f JR |
981 | |
982 | displaced_rule = test_rule_from_cls_rule( | |
983 | classifier_find_rule_exactly(&cls, | |
bd53aa17 JR |
984 | &rules[j]->cls_rule, |
985 | version)); | |
3bbe9a1f JR |
986 | if (displaced_rule) { |
987 | /* Mark the old rule for removal after the current | |
988 | * version. */ | |
989 | cls_rule_make_invisible_in_version( | |
18721c4a | 990 | &displaced_rule->cls_rule, version); |
3bbe9a1f JR |
991 | n_invisible_rules++; |
992 | removable_rule = &displaced_rule->cls_rule; | |
993 | } | |
bd53aa17 JR |
994 | classifier_insert(&cls, &rules[j]->cls_rule, version, |
995 | NULL, 0); | |
3bbe9a1f JR |
996 | } else { |
997 | displaced_rule = test_rule_from_cls_rule( | |
998 | classifier_replace(&cls, &rules[j]->cls_rule, | |
bd53aa17 | 999 | version, NULL, 0)); |
3bbe9a1f | 1000 | } |
b5d97350 BP |
1001 | if (pri_rules[pris[j]] >= 0) { |
1002 | int k = pri_rules[pris[j]]; | |
1003 | assert(displaced_rule != NULL); | |
1004 | assert(displaced_rule != rules[j]); | |
1005 | assert(pris[j] == displaced_rule->cls_rule.priority); | |
1006 | tcls_rules[k] = NULL; | |
1007 | } else { | |
1008 | assert(displaced_rule == NULL); | |
1009 | } | |
1010 | pri_rules[pris[j]] = j; | |
1011 | } else { | |
3bbe9a1f JR |
1012 | if (versioned) { |
1013 | /* Mark the rule for removal after the current | |
1014 | * version. */ | |
3bbe9a1f | 1015 | ++version; |
bd53aa17 JR |
1016 | cls_rule_make_invisible_in_version( |
1017 | &rules[j]->cls_rule, version); | |
3bbe9a1f JR |
1018 | n_invisible_rules++; |
1019 | removable_rule = &rules[j]->cls_rule; | |
1020 | } else { | |
46ab60bf | 1021 | classifier_remove_assert(&cls, &rules[j]->cls_rule); |
3bbe9a1f | 1022 | } |
b5d97350 BP |
1023 | tcls_remove(&tcls, tcls_rules[j]); |
1024 | tcls_rules[j] = NULL; | |
1025 | pri_rules[pris[j]] = -1; | |
1026 | } | |
3bbe9a1f | 1027 | compare_classifiers(&cls, n_invisible_rules, version, &tcls); |
b5d97350 BP |
1028 | n = 0; |
1029 | for (m = 0; m < N_RULES; m++) { | |
1030 | n += tcls_rules[m] != NULL; | |
064af421 | 1031 | } |
3bbe9a1f JR |
1032 | check_tables(&cls, n > 0, n, n - 1, n_invisible_rules, |
1033 | version); | |
1034 | ||
1035 | if (versioned && removable_rule) { | |
5e27fe97 JR |
1036 | struct cls_match *cls_match = |
1037 | get_cls_match_protected(removable_rule); | |
1038 | ||
3bbe9a1f | 1039 | /* Removable rule is no longer visible. */ |
5e27fe97 JR |
1040 | assert(cls_match); |
1041 | assert(!cls_match_visible_in_version(cls_match, version)); | |
46ab60bf | 1042 | classifier_remove_assert(&cls, removable_rule); |
3bbe9a1f JR |
1043 | n_invisible_rules--; |
1044 | } | |
064af421 | 1045 | } |
b5d97350 | 1046 | |
802f84ff | 1047 | classifier_defer(&cls); |
b5d97350 | 1048 | for (i = 0; i < N_RULES; i++) { |
42767cce JR |
1049 | if (classifier_remove(&cls, &rules[i]->cls_rule)) { |
1050 | ovsrcu_postpone(free_rule, rules[i]); | |
627fb667 | 1051 | } |
b5d97350 | 1052 | } |
627fb667 JR |
1053 | classifier_destroy(&cls); |
1054 | tcls_destroy(&tcls); | |
b5d97350 BP |
1055 | } while (next_permutation(ops, ARRAY_SIZE(ops))); |
1056 | assert(n_permutations == (factorial(N_RULES * 2) >> N_RULES)); | |
064af421 BP |
1057 | } |
1058 | } | |
1059 | ||
b5d97350 BP |
1060 | static int |
1061 | count_ones(unsigned long int x) | |
064af421 | 1062 | { |
b5d97350 | 1063 | int n = 0; |
064af421 | 1064 | |
b5d97350 | 1065 | while (x) { |
8472a3ce | 1066 | x = zero_rightmost_1bit(x); |
b5d97350 BP |
1067 | n++; |
1068 | } | |
064af421 | 1069 | |
b5d97350 BP |
1070 | return n; |
1071 | } | |
064af421 | 1072 | |
b5d97350 BP |
1073 | static bool |
1074 | array_contains(int *array, int n, int value) | |
1075 | { | |
1076 | int i; | |
064af421 | 1077 | |
b5d97350 BP |
1078 | for (i = 0; i < n; i++) { |
1079 | if (array[i] == value) { | |
1080 | return true; | |
064af421 BP |
1081 | } |
1082 | } | |
b5d97350 BP |
1083 | |
1084 | return false; | |
064af421 BP |
1085 | } |
1086 | ||
b5d97350 BP |
1087 | /* Tests classification with two rules at a time that fall into the same |
1088 | * table but different lists. */ | |
064af421 | 1089 | static void |
1636c761 | 1090 | test_many_rules_in_one_table(struct ovs_cmdl_context *ctx OVS_UNUSED) |
064af421 | 1091 | { |
b5d97350 | 1092 | int iteration; |
064af421 | 1093 | |
b5d97350 BP |
1094 | for (iteration = 0; iteration < 50; iteration++) { |
1095 | enum { N_RULES = 20 }; | |
1096 | struct test_rule *rules[N_RULES]; | |
1097 | struct test_rule *tcls_rules[N_RULES]; | |
1098 | struct classifier cls; | |
1099 | struct tcls tcls; | |
44e0c35d | 1100 | ovs_version_t version = OVS_VERSION_MIN; |
3bbe9a1f | 1101 | size_t n_invisible_rules = 0; |
b5d97350 BP |
1102 | int value_pats[N_RULES]; |
1103 | int value_mask; | |
1104 | int wcf; | |
1105 | int i; | |
064af421 | 1106 | |
b5d97350 | 1107 | do { |
b028db44 | 1108 | wcf = random_uint32() & ((1u << CLS_N_FIELDS) - 1); |
b5d97350 BP |
1109 | value_mask = ~wcf & ((1u << CLS_N_FIELDS) - 1); |
1110 | } while ((1 << count_ones(value_mask)) < N_RULES); | |
064af421 | 1111 | |
d70e8c28 | 1112 | classifier_init(&cls, flow_segment_u64s); |
f358a2cb | 1113 | set_prefix_fields(&cls); |
b5d97350 | 1114 | tcls_init(&tcls); |
064af421 | 1115 | |
b5d97350 | 1116 | for (i = 0; i < N_RULES; i++) { |
eb391b76 | 1117 | int priority = random_range(INT_MAX); |
064af421 | 1118 | |
b5d97350 | 1119 | do { |
b028db44 | 1120 | value_pats[i] = random_uint32() & value_mask; |
b5d97350 | 1121 | } while (array_contains(value_pats, i, value_pats[i])); |
064af421 | 1122 | |
3bbe9a1f | 1123 | ++version; |
bd53aa17 | 1124 | rules[i] = make_rule(wcf, priority, value_pats[i]); |
b5d97350 | 1125 | tcls_rules[i] = tcls_insert(&tcls, rules[i]); |
5f0476ce | 1126 | |
bd53aa17 | 1127 | classifier_insert(&cls, &rules[i]->cls_rule, version, NULL, 0); |
3bbe9a1f | 1128 | compare_classifiers(&cls, n_invisible_rules, version, &tcls); |
b5d97350 | 1129 | |
3bbe9a1f | 1130 | check_tables(&cls, 1, i + 1, 0, n_invisible_rules, version); |
b5d97350 BP |
1131 | } |
1132 | ||
1133 | for (i = 0; i < N_RULES; i++) { | |
1134 | tcls_remove(&tcls, tcls_rules[i]); | |
3bbe9a1f JR |
1135 | if (versioned) { |
1136 | /* Mark the rule for removal after the current version. */ | |
3bbe9a1f | 1137 | ++version; |
bd53aa17 JR |
1138 | cls_rule_make_invisible_in_version(&rules[i]->cls_rule, |
1139 | version); | |
3bbe9a1f JR |
1140 | n_invisible_rules++; |
1141 | } else { | |
46ab60bf | 1142 | classifier_remove_assert(&cls, &rules[i]->cls_rule); |
3bbe9a1f JR |
1143 | } |
1144 | compare_classifiers(&cls, n_invisible_rules, version, &tcls); | |
1145 | check_tables(&cls, i < N_RULES - 1, N_RULES - (i + 1), 0, | |
1146 | n_invisible_rules, version); | |
1147 | if (!versioned) { | |
1148 | ovsrcu_postpone(free_rule, rules[i]); | |
1149 | } | |
1150 | } | |
b5d97350 | 1151 | |
3bbe9a1f JR |
1152 | if (versioned) { |
1153 | for (i = 0; i < N_RULES; i++) { | |
46ab60bf | 1154 | classifier_remove_assert(&cls, &rules[i]->cls_rule); |
3bbe9a1f JR |
1155 | n_invisible_rules--; |
1156 | ||
1157 | compare_classifiers(&cls, n_invisible_rules, version, &tcls); | |
1158 | check_tables(&cls, 0, 0, 0, n_invisible_rules, version); | |
1159 | ovsrcu_postpone(free_rule, rules[i]); | |
1160 | } | |
064af421 | 1161 | } |
b5d97350 BP |
1162 | |
1163 | classifier_destroy(&cls); | |
1164 | tcls_destroy(&tcls); | |
064af421 BP |
1165 | } |
1166 | } | |
1167 | ||
b5d97350 BP |
1168 | /* Tests classification with many rules at a time that fall into random lists |
1169 | * in 'n' tables. */ | |
064af421 | 1170 | static void |
b5d97350 | 1171 | test_many_rules_in_n_tables(int n_tables) |
064af421 BP |
1172 | { |
1173 | enum { MAX_RULES = 50 }; | |
b5d97350 | 1174 | int wcfs[10]; |
064af421 | 1175 | int iteration; |
b5d97350 BP |
1176 | int i; |
1177 | ||
1178 | assert(n_tables < 10); | |
1179 | for (i = 0; i < n_tables; i++) { | |
1180 | do { | |
b028db44 | 1181 | wcfs[i] = random_uint32() & ((1u << CLS_N_FIELDS) - 1); |
b5d97350 BP |
1182 | } while (array_contains(wcfs, i, wcfs[i])); |
1183 | } | |
064af421 BP |
1184 | |
1185 | for (iteration = 0; iteration < 30; iteration++) { | |
eb391b76 | 1186 | int priorities[MAX_RULES]; |
064af421 BP |
1187 | struct classifier cls; |
1188 | struct tcls tcls; | |
44e0c35d | 1189 | ovs_version_t version = OVS_VERSION_MIN; |
3bbe9a1f JR |
1190 | size_t n_invisible_rules = 0; |
1191 | struct ovs_list list = OVS_LIST_INITIALIZER(&list); | |
064af421 | 1192 | |
b028db44 | 1193 | random_set_seed(iteration + 1); |
064af421 | 1194 | for (i = 0; i < MAX_RULES; i++) { |
eb391b76 | 1195 | priorities[i] = (i * 129) & INT_MAX; |
064af421 BP |
1196 | } |
1197 | shuffle(priorities, ARRAY_SIZE(priorities)); | |
1198 | ||
d70e8c28 | 1199 | classifier_init(&cls, flow_segment_u64s); |
f358a2cb | 1200 | set_prefix_fields(&cls); |
064af421 BP |
1201 | tcls_init(&tcls); |
1202 | ||
1203 | for (i = 0; i < MAX_RULES; i++) { | |
1204 | struct test_rule *rule; | |
eb391b76 | 1205 | int priority = priorities[i]; |
b028db44 BP |
1206 | int wcf = wcfs[random_range(n_tables)]; |
1207 | int value_pat = random_uint32() & ((1u << CLS_N_FIELDS) - 1); | |
bd53aa17 | 1208 | rule = make_rule(wcf, priority, value_pat); |
064af421 | 1209 | tcls_insert(&tcls, rule); |
bd53aa17 | 1210 | classifier_insert(&cls, &rule->cls_rule, version, NULL, 0); |
3bbe9a1f JR |
1211 | compare_classifiers(&cls, n_invisible_rules, version, &tcls); |
1212 | check_tables(&cls, -1, i + 1, -1, n_invisible_rules, version); | |
064af421 BP |
1213 | } |
1214 | ||
3bbe9a1f | 1215 | while (classifier_count(&cls) - n_invisible_rules > 0) { |
5ecc9d81 | 1216 | struct test_rule *target; |
78c8df12 | 1217 | struct test_rule *rule; |
3bbe9a1f | 1218 | size_t n_removable_rules = 0; |
5ecc9d81 | 1219 | |
b028db44 | 1220 | target = clone_rule(tcls.rules[random_range(tcls.n_rules)]); |
5ecc9d81 | 1221 | |
bd53aa17 JR |
1222 | CLS_FOR_EACH_TARGET (rule, cls_rule, &cls, &target->cls_rule, |
1223 | version) { | |
3bbe9a1f JR |
1224 | if (versioned) { |
1225 | /* Mark the rule for removal after the current version. */ | |
1226 | cls_rule_make_invisible_in_version(&rule->cls_rule, | |
18721c4a | 1227 | version + 1); |
3bbe9a1f JR |
1228 | n_removable_rules++; |
1229 | compare_classifiers(&cls, n_invisible_rules, version, | |
1230 | &tcls); | |
1231 | check_tables(&cls, -1, -1, -1, n_invisible_rules, version); | |
1232 | ||
417e7e66 | 1233 | ovs_list_push_back(&list, &rule->list_node); |
3bbe9a1f | 1234 | } else if (classifier_remove(&cls, &rule->cls_rule)) { |
42767cce JR |
1235 | ovsrcu_postpone(free_rule, rule); |
1236 | } | |
5ecc9d81 | 1237 | } |
5f0476ce | 1238 | |
3bbe9a1f JR |
1239 | ++version; |
1240 | n_invisible_rules += n_removable_rules; | |
1241 | ||
5ecc9d81 | 1242 | tcls_delete_matches(&tcls, &target->cls_rule); |
48d28ac1 | 1243 | free_rule(target); |
3bbe9a1f JR |
1244 | |
1245 | compare_classifiers(&cls, n_invisible_rules, version, &tcls); | |
1246 | check_tables(&cls, -1, -1, -1, n_invisible_rules, version); | |
1247 | } | |
1248 | if (versioned) { | |
1249 | struct test_rule *rule; | |
1250 | ||
1251 | /* Remove rules that are no longer visible. */ | |
1252 | LIST_FOR_EACH_POP (rule, list_node, &list) { | |
46ab60bf | 1253 | classifier_remove_assert(&cls, &rule->cls_rule); |
3bbe9a1f JR |
1254 | n_invisible_rules--; |
1255 | ||
1256 | compare_classifiers(&cls, n_invisible_rules, version, | |
1257 | &tcls); | |
1258 | check_tables(&cls, -1, -1, -1, n_invisible_rules, version); | |
1259 | } | |
064af421 | 1260 | } |
064af421 BP |
1261 | |
1262 | destroy_classifier(&cls); | |
1263 | tcls_destroy(&tcls); | |
1264 | } | |
1265 | } | |
b5d97350 BP |
1266 | |
1267 | static void | |
1636c761 | 1268 | test_many_rules_in_two_tables(struct ovs_cmdl_context *ctx OVS_UNUSED) |
b5d97350 BP |
1269 | { |
1270 | test_many_rules_in_n_tables(2); | |
1271 | } | |
1272 | ||
1273 | static void | |
1636c761 | 1274 | test_many_rules_in_five_tables(struct ovs_cmdl_context *ctx OVS_UNUSED) |
b5d97350 BP |
1275 | { |
1276 | test_many_rules_in_n_tables(5); | |
1277 | } | |
064af421 | 1278 | \f |
1add079e JR |
1279 | /* Classifier benchmarks. */ |
1280 | ||
1281 | static int n_rules; /* Number of rules to insert. */ | |
1282 | static int n_priorities; /* Number of priorities to use. */ | |
1283 | static int n_tables; /* Number of subtables. */ | |
1284 | static int n_threads; /* Number of threads to search and mutate. */ | |
1285 | static int n_lookups; /* Number of lookups each thread performs. */ | |
1286 | ||
1287 | static void benchmark(bool use_wc); | |
1288 | ||
1289 | static int | |
1290 | elapsed(const struct timeval *start) | |
1291 | { | |
1292 | struct timeval end; | |
1293 | ||
1294 | xgettimeofday(&end); | |
1295 | return timeval_to_msec(&end) - timeval_to_msec(start); | |
1296 | } | |
1297 | ||
1298 | static void | |
1299 | run_benchmarks(struct ovs_cmdl_context *ctx) | |
1300 | { | |
1301 | if (ctx->argc < 5 | |
1302 | || (ctx->argc > 1 && !strcmp(ctx->argv[1], "--help"))) { | |
1303 | printf( | |
1304 | "usage: ovstest %s benchmark <n_rules> <n_priorities> <n_subtables> <n_threads> <n_lookups>\n" | |
1305 | "\n" | |
1306 | "where:\n" | |
1307 | "\n" | |
1308 | "<n_rules> - The number of rules to install for lookups. More rules\n" | |
1309 | " makes misses less likely.\n" | |
1310 | "<n_priorities> - How many different priorities to use. Using only 1\n" | |
1311 | " priority will force lookups to continue through all\n" | |
1312 | " subtables.\n" | |
1313 | "<n_subtables> - Number of subtables to use. Normally a classifier has\n" | |
1314 | " rules with different kinds of masks, resulting in\n" | |
1315 | " multiple subtables (one per mask). However, in some\n" | |
1316 | " special cases a table may consist of only one kind of\n" | |
1317 | " rules, so there will be only one subtable.\n" | |
1318 | "<n_threads> - How many lookup threads to use. Using one thread should\n" | |
1319 | " give less variance accross runs, but classifier\n" | |
1320 | " scaling can be tested with multiple threads.\n" | |
1321 | "<n_lookups> - How many lookups each thread should perform.\n" | |
1322 | "\n", program_name); | |
1323 | return; | |
1324 | } | |
1325 | ||
1326 | n_rules = strtol(ctx->argv[1], NULL, 10); | |
1327 | n_priorities = strtol(ctx->argv[2], NULL, 10); | |
1328 | n_tables = strtol(ctx->argv[3], NULL, 10); | |
1329 | n_threads = strtol(ctx->argv[4], NULL, 10); | |
1330 | n_lookups = strtol(ctx->argv[5], NULL, 10); | |
1331 | ||
1332 | printf("\nBenchmarking with:\n" | |
1333 | "%d rules with %d priorities in %d tables, " | |
1334 | "%d threads doing %d lookups each\n", | |
1335 | n_rules, n_priorities, n_tables, n_threads, n_lookups); | |
1336 | ||
1337 | puts("\nWithout wildcards: \n"); | |
1338 | benchmark(false); | |
1339 | puts("\nWith wildcards: \n"); | |
1340 | benchmark(true); | |
1341 | } | |
1342 | ||
1343 | struct cls_aux { | |
1344 | const struct classifier *cls; | |
1345 | size_t n_lookup_flows; | |
1346 | struct flow *lookup_flows; | |
1347 | bool use_wc; | |
1348 | atomic_int hits; | |
1349 | atomic_int misses; | |
1350 | }; | |
1351 | ||
1352 | static void * | |
1353 | lookup_classifier(void *aux_) | |
1354 | { | |
1355 | struct cls_aux *aux = aux_; | |
44e0c35d | 1356 | ovs_version_t version = OVS_VERSION_MIN; |
1add079e JR |
1357 | int hits = 0, old_hits; |
1358 | int misses = 0, old_misses; | |
1359 | size_t i; | |
1360 | ||
1361 | random_set_seed(1); | |
1362 | ||
1363 | for (i = 0; i < n_lookups; i++) { | |
1364 | const struct cls_rule *cr; | |
1365 | struct flow_wildcards wc; | |
1366 | unsigned int x; | |
1367 | ||
1368 | x = random_range(aux->n_lookup_flows); | |
1369 | ||
1370 | if (aux->use_wc) { | |
1371 | flow_wildcards_init_catchall(&wc); | |
1372 | cr = classifier_lookup(aux->cls, version, &aux->lookup_flows[x], | |
1373 | &wc); | |
1374 | } else { | |
1375 | cr = classifier_lookup(aux->cls, version, &aux->lookup_flows[x], | |
1376 | NULL); | |
1377 | } | |
1378 | if (cr) { | |
1379 | hits++; | |
1380 | } else { | |
1381 | misses++; | |
1382 | } | |
1383 | } | |
1384 | atomic_add(&aux->hits, hits, &old_hits); | |
1385 | atomic_add(&aux->misses, misses, &old_misses); | |
1386 | return NULL; | |
1387 | } | |
1388 | ||
1389 | /* Benchmark classification. */ | |
1390 | static void | |
1391 | benchmark(bool use_wc) | |
1392 | { | |
1393 | struct classifier cls; | |
44e0c35d | 1394 | ovs_version_t version = OVS_VERSION_MIN; |
1add079e JR |
1395 | struct cls_aux aux; |
1396 | int *wcfs = xmalloc(n_tables * sizeof *wcfs); | |
1397 | int *priorities = xmalloc(n_priorities * sizeof *priorities); | |
1398 | struct timeval start; | |
1399 | pthread_t *threads; | |
1400 | int i; | |
1401 | ||
1402 | fatal_signal_init(); | |
1403 | ||
1404 | random_set_seed(1); | |
1405 | ||
1406 | for (i = 0; i < n_tables; i++) { | |
1407 | do { | |
1408 | wcfs[i] = random_uint32() & ((1u << CLS_N_FIELDS) - 1); | |
1409 | } while (array_contains(wcfs, i, wcfs[i])); | |
1410 | } | |
1411 | ||
1412 | for (i = 0; i < n_priorities; i++) { | |
1413 | priorities[i] = (i * 129) & INT_MAX; | |
1414 | } | |
1415 | shuffle(priorities, n_priorities); | |
1416 | ||
1417 | classifier_init(&cls, flow_segment_u64s); | |
1418 | set_prefix_fields(&cls); | |
1419 | ||
1420 | /* Create lookup flows. */ | |
1421 | aux.use_wc = use_wc; | |
1422 | aux.cls = &cls; | |
1423 | aux.n_lookup_flows = 2 * N_FLOW_VALUES; | |
1424 | aux.lookup_flows = xzalloc(aux.n_lookup_flows * sizeof *aux.lookup_flows); | |
1425 | for (i = 0; i < aux.n_lookup_flows; i++) { | |
1426 | struct flow *flow = &aux.lookup_flows[i]; | |
1427 | unsigned int x; | |
1428 | ||
1429 | x = random_range(N_FLOW_VALUES); | |
1430 | flow->nw_src = nw_src_values[get_value(&x, N_NW_SRC_VALUES)]; | |
1431 | flow->nw_dst = nw_dst_values[get_value(&x, N_NW_DST_VALUES)]; | |
1432 | flow->tunnel.tun_id = tun_id_values[get_value(&x, N_TUN_ID_VALUES)]; | |
1433 | flow->metadata = metadata_values[get_value(&x, N_METADATA_VALUES)]; | |
1434 | flow->in_port.ofp_port = in_port_values[get_value(&x, | |
1435 | N_IN_PORT_VALUES)]; | |
f0fb825a | 1436 | flow->vlans[0].tci = vlan_tci_values[get_value(&x, N_VLAN_TCI_VALUES)]; |
1add079e JR |
1437 | flow->dl_type = dl_type_values[get_value(&x, N_DL_TYPE_VALUES)]; |
1438 | flow->tp_src = tp_src_values[get_value(&x, N_TP_SRC_VALUES)]; | |
1439 | flow->tp_dst = tp_dst_values[get_value(&x, N_TP_DST_VALUES)]; | |
74ff3298 JR |
1440 | flow->dl_src = dl_src_values[get_value(&x, N_DL_SRC_VALUES)]; |
1441 | flow->dl_dst = dl_dst_values[get_value(&x, N_DL_DST_VALUES)]; | |
1add079e JR |
1442 | flow->nw_proto = nw_proto_values[get_value(&x, N_NW_PROTO_VALUES)]; |
1443 | flow->nw_tos = nw_dscp_values[get_value(&x, N_NW_DSCP_VALUES)]; | |
1444 | } | |
1445 | atomic_init(&aux.hits, 0); | |
1446 | atomic_init(&aux.misses, 0); | |
1447 | ||
1448 | /* Rule insertion. */ | |
1449 | for (i = 0; i < n_rules; i++) { | |
1450 | struct test_rule *rule; | |
1451 | const struct cls_rule *old_cr; | |
1452 | ||
1453 | int priority = priorities[random_range(n_priorities)]; | |
1454 | int wcf = wcfs[random_range(n_tables)]; | |
1455 | int value_pat = random_uint32() & ((1u << CLS_N_FIELDS) - 1); | |
1456 | ||
1457 | rule = make_rule(wcf, priority, value_pat); | |
1458 | old_cr = classifier_find_rule_exactly(&cls, &rule->cls_rule, version); | |
1459 | if (!old_cr) { | |
1460 | classifier_insert(&cls, &rule->cls_rule, version, NULL, 0); | |
1461 | } else { | |
1462 | free_rule(rule); | |
1463 | } | |
1464 | } | |
1465 | ||
1466 | /* Lookup. */ | |
1467 | xgettimeofday(&start); | |
1468 | threads = xmalloc(n_threads * sizeof *threads); | |
1469 | for (i = 0; i < n_threads; i++) { | |
1470 | threads[i] = ovs_thread_create("lookups", lookup_classifier, &aux); | |
1471 | } | |
1472 | for (i = 0; i < n_threads; i++) { | |
1473 | xpthread_join(threads[i], NULL); | |
1474 | } | |
1475 | ||
1476 | int elapsed_msec = elapsed(&start); | |
1477 | ||
1478 | free(threads); | |
1479 | ||
1480 | int hits, misses; | |
1481 | atomic_read(&aux.hits, &hits); | |
1482 | atomic_read(&aux.misses, &misses); | |
1483 | printf("hits: %d, misses: %d\n", hits, misses); | |
1484 | ||
1485 | printf("classifier lookups: %5d ms, %"PRId64" lookups/sec\n", | |
1486 | elapsed_msec, | |
1487 | (((uint64_t)hits + misses) * 1000) / elapsed_msec); | |
1488 | ||
1489 | destroy_classifier(&cls); | |
1490 | free(aux.lookup_flows); | |
1491 | free(priorities); | |
1492 | free(wcfs); | |
1493 | } | |
1494 | \f | |
5cb7a798 BP |
1495 | /* Miniflow tests. */ |
1496 | ||
1497 | static uint32_t | |
1498 | random_value(void) | |
1499 | { | |
396d492c | 1500 | static const uint32_t values_[] = |
5cb7a798 BP |
1501 | { 0xffffffff, 0xaaaaaaaa, 0x55555555, 0x80000000, |
1502 | 0x00000001, 0xface0000, 0x00d00d1e, 0xdeadbeef }; | |
1503 | ||
396d492c | 1504 | return values_[random_range(ARRAY_SIZE(values_))]; |
5cb7a798 BP |
1505 | } |
1506 | ||
1507 | static bool | |
1508 | choose(unsigned int n, unsigned int *idxp) | |
1509 | { | |
1510 | if (*idxp < n) { | |
1511 | return true; | |
1512 | } else { | |
1513 | *idxp -= n; | |
1514 | return false; | |
1515 | } | |
1516 | } | |
1517 | ||
d70e8c28 JR |
1518 | #define FLOW_U32S (FLOW_U64S * 2) |
1519 | ||
5cb7a798 BP |
1520 | static bool |
1521 | init_consecutive_values(int n_consecutive, struct flow *flow, | |
1522 | unsigned int *idxp) | |
1523 | { | |
1524 | uint32_t *flow_u32 = (uint32_t *) flow; | |
1525 | ||
1526 | if (choose(FLOW_U32S - n_consecutive + 1, idxp)) { | |
1527 | int i; | |
1528 | ||
1529 | for (i = 0; i < n_consecutive; i++) { | |
1530 | flow_u32[*idxp + i] = random_value(); | |
1531 | } | |
1532 | return true; | |
1533 | } else { | |
1534 | return false; | |
1535 | } | |
1536 | } | |
1537 | ||
1538 | static bool | |
1539 | next_random_flow(struct flow *flow, unsigned int idx) | |
1540 | { | |
1541 | uint32_t *flow_u32 = (uint32_t *) flow; | |
5cb7a798 BP |
1542 | |
1543 | memset(flow, 0, sizeof *flow); | |
1544 | ||
1545 | /* Empty flow. */ | |
1546 | if (choose(1, &idx)) { | |
1547 | return true; | |
1548 | } | |
1549 | ||
1550 | /* All flows with a small number of consecutive nonzero values. */ | |
71f21279 | 1551 | for (int i = 1; i <= 4; i++) { |
5cb7a798 BP |
1552 | if (init_consecutive_values(i, flow, &idx)) { |
1553 | return true; | |
1554 | } | |
1555 | } | |
1556 | ||
1557 | /* All flows with a large number of consecutive nonzero values. */ | |
71f21279 | 1558 | for (int i = FLOW_U32S - 4; i <= FLOW_U32S; i++) { |
5cb7a798 BP |
1559 | if (init_consecutive_values(i, flow, &idx)) { |
1560 | return true; | |
1561 | } | |
1562 | } | |
1563 | ||
1564 | /* All flows with exactly two nonconsecutive nonzero values. */ | |
1565 | if (choose((FLOW_U32S - 1) * (FLOW_U32S - 2) / 2, &idx)) { | |
1566 | int ofs1; | |
1567 | ||
1568 | for (ofs1 = 0; ofs1 < FLOW_U32S - 2; ofs1++) { | |
1569 | int ofs2; | |
1570 | ||
1571 | for (ofs2 = ofs1 + 2; ofs2 < FLOW_U32S; ofs2++) { | |
1572 | if (choose(1, &idx)) { | |
1573 | flow_u32[ofs1] = random_value(); | |
1574 | flow_u32[ofs2] = random_value(); | |
1575 | return true; | |
1576 | } | |
1577 | } | |
1578 | } | |
428b2edd | 1579 | OVS_NOT_REACHED(); |
5cb7a798 BP |
1580 | } |
1581 | ||
1582 | /* 16 randomly chosen flows with N >= 3 nonzero values. */ | |
1583 | if (choose(16 * (FLOW_U32S - 4), &idx)) { | |
1584 | int n = idx / 16 + 3; | |
5cb7a798 | 1585 | |
71f21279 | 1586 | for (int i = 0; i < n; i++) { |
5cb7a798 BP |
1587 | flow_u32[i] = random_value(); |
1588 | } | |
1589 | shuffle_u32s(flow_u32, FLOW_U32S); | |
1590 | ||
1591 | return true; | |
1592 | } | |
1593 | ||
1594 | return false; | |
1595 | } | |
1596 | ||
1597 | static void | |
1598 | any_random_flow(struct flow *flow) | |
1599 | { | |
1600 | static unsigned int max; | |
1601 | if (!max) { | |
1602 | while (next_random_flow(flow, max)) { | |
1603 | max++; | |
1604 | } | |
1605 | } | |
1606 | ||
1607 | next_random_flow(flow, random_range(max)); | |
1608 | } | |
1609 | ||
1610 | static void | |
1611 | toggle_masked_flow_bits(struct flow *flow, const struct flow_wildcards *mask) | |
1612 | { | |
1613 | const uint32_t *mask_u32 = (const uint32_t *) &mask->masks; | |
1614 | uint32_t *flow_u32 = (uint32_t *) flow; | |
1615 | int i; | |
1616 | ||
1617 | for (i = 0; i < FLOW_U32S; i++) { | |
1618 | if (mask_u32[i] != 0) { | |
1619 | uint32_t bit; | |
1620 | ||
1621 | do { | |
1622 | bit = 1u << random_range(32); | |
1623 | } while (!(bit & mask_u32[i])); | |
1624 | flow_u32[i] ^= bit; | |
1625 | } | |
1626 | } | |
1627 | } | |
1628 | ||
1629 | static void | |
1630 | wildcard_extra_bits(struct flow_wildcards *mask) | |
1631 | { | |
1632 | uint32_t *mask_u32 = (uint32_t *) &mask->masks; | |
1633 | int i; | |
1634 | ||
1635 | for (i = 0; i < FLOW_U32S; i++) { | |
1636 | if (mask_u32[i] != 0) { | |
1637 | uint32_t bit; | |
1638 | ||
1639 | do { | |
1640 | bit = 1u << random_range(32); | |
1641 | } while (!(bit & mask_u32[i])); | |
1642 | mask_u32[i] &= ~bit; | |
1643 | } | |
1644 | } | |
1645 | } | |
1646 | ||
a851eb94 JR |
1647 | /* Returns a copy of 'src'. The caller must eventually free the returned |
1648 | * miniflow with free(). */ | |
1649 | static struct miniflow * | |
1650 | miniflow_clone__(const struct miniflow *src) | |
1651 | { | |
1652 | struct miniflow *dst; | |
1653 | size_t data_size; | |
1654 | ||
1655 | data_size = miniflow_alloc(&dst, 1, src); | |
1656 | miniflow_clone(dst, src, data_size / sizeof(uint64_t)); | |
1657 | return dst; | |
1658 | } | |
1659 | ||
b30001c7 JR |
1660 | /* Returns a hash value for 'flow', given 'basis'. */ |
1661 | static inline uint32_t | |
1662 | miniflow_hash__(const struct miniflow *flow, uint32_t basis) | |
1663 | { | |
5fcff47b JR |
1664 | const uint64_t *p = miniflow_get_values(flow); |
1665 | size_t n_values = miniflow_n_values(flow); | |
1666 | struct flowmap hash_map = FLOWMAP_EMPTY_INITIALIZER; | |
b30001c7 | 1667 | uint32_t hash = basis; |
5fcff47b | 1668 | size_t idx; |
b30001c7 | 1669 | |
5fcff47b JR |
1670 | FLOWMAP_FOR_EACH_INDEX(idx, flow->map) { |
1671 | uint64_t value = *p++; | |
1672 | ||
1673 | if (value) { | |
1674 | hash = hash_add64(hash, value); | |
1675 | flowmap_set(&hash_map, idx, 1); | |
b30001c7 | 1676 | } |
b30001c7 | 1677 | } |
5fcff47b JR |
1678 | map_t map; |
1679 | FLOWMAP_FOR_EACH_MAP (map, hash_map) { | |
1680 | hash = hash_add64(hash, map); | |
b30001c7 | 1681 | } |
b30001c7 | 1682 | |
5fcff47b | 1683 | return hash_finish(hash, n_values); |
b30001c7 JR |
1684 | } |
1685 | ||
5cb7a798 | 1686 | static void |
1636c761 | 1687 | test_miniflow(struct ovs_cmdl_context *ctx OVS_UNUSED) |
5cb7a798 BP |
1688 | { |
1689 | struct flow flow; | |
1690 | unsigned int idx; | |
1691 | ||
1692 | random_set_seed(0xb3faca38); | |
1693 | for (idx = 0; next_random_flow(&flow, idx); idx++) { | |
d70e8c28 | 1694 | const uint64_t *flow_u64 = (const uint64_t *) &flow; |
8fd47924 | 1695 | struct miniflow *miniflow, *miniflow2, *miniflow3; |
5cb7a798 BP |
1696 | struct flow flow2, flow3; |
1697 | struct flow_wildcards mask; | |
8fd47924 | 1698 | struct minimask *minimask; |
5cb7a798 BP |
1699 | int i; |
1700 | ||
1701 | /* Convert flow to miniflow. */ | |
8fd47924 | 1702 | miniflow = miniflow_create(&flow); |
5cb7a798 BP |
1703 | |
1704 | /* Check that the flow equals its miniflow. */ | |
f0fb825a EG |
1705 | for (i = 0; i < FLOW_MAX_VLAN_HEADERS; i++) { |
1706 | assert(miniflow_get_vid(miniflow, i) == | |
1707 | vlan_tci_to_vid(flow.vlans[i].tci)); | |
1708 | } | |
d70e8c28 | 1709 | for (i = 0; i < FLOW_U64S; i++) { |
8fd47924 | 1710 | assert(miniflow_get(miniflow, i) == flow_u64[i]); |
5cb7a798 BP |
1711 | } |
1712 | ||
1713 | /* Check that the miniflow equals itself. */ | |
8fd47924 | 1714 | assert(miniflow_equal(miniflow, miniflow)); |
5cb7a798 BP |
1715 | |
1716 | /* Convert miniflow back to flow and verify that it's the same. */ | |
8fd47924 | 1717 | miniflow_expand(miniflow, &flow2); |
5cb7a798 BP |
1718 | assert(flow_equal(&flow, &flow2)); |
1719 | ||
1720 | /* Check that copying a miniflow works properly. */ | |
a851eb94 | 1721 | miniflow2 = miniflow_clone__(miniflow); |
8fd47924 | 1722 | assert(miniflow_equal(miniflow, miniflow2)); |
b30001c7 | 1723 | assert(miniflow_hash__(miniflow, 0) == miniflow_hash__(miniflow2, 0)); |
8fd47924 | 1724 | miniflow_expand(miniflow2, &flow3); |
5cb7a798 BP |
1725 | assert(flow_equal(&flow, &flow3)); |
1726 | ||
1727 | /* Check that masked matches work as expected for identical flows and | |
1728 | * miniflows. */ | |
1729 | do { | |
1730 | next_random_flow(&mask.masks, 1); | |
1731 | } while (flow_wildcards_is_catchall(&mask)); | |
8fd47924 JR |
1732 | minimask = minimask_create(&mask); |
1733 | assert(minimask_is_catchall(minimask) | |
5cb7a798 | 1734 | == flow_wildcards_is_catchall(&mask)); |
8fd47924 JR |
1735 | assert(miniflow_equal_in_minimask(miniflow, miniflow2, minimask)); |
1736 | assert(miniflow_equal_flow_in_minimask(miniflow, &flow2, minimask)); | |
1737 | assert(miniflow_hash_in_minimask(miniflow, minimask, 0x12345678) == | |
1738 | flow_hash_in_minimask(&flow, minimask, 0x12345678)); | |
b30001c7 JR |
1739 | assert(minimask_hash(minimask, 0) == |
1740 | miniflow_hash__(&minimask->masks, 0)); | |
5cb7a798 BP |
1741 | |
1742 | /* Check that masked matches work as expected for differing flows and | |
1743 | * miniflows. */ | |
1744 | toggle_masked_flow_bits(&flow2, &mask); | |
8fd47924 JR |
1745 | assert(!miniflow_equal_flow_in_minimask(miniflow, &flow2, minimask)); |
1746 | miniflow3 = miniflow_create(&flow2); | |
1747 | assert(!miniflow_equal_in_minimask(miniflow, miniflow3, minimask)); | |
5cb7a798 BP |
1748 | |
1749 | /* Clean up. */ | |
8fd47924 JR |
1750 | free(miniflow); |
1751 | free(miniflow2); | |
1752 | free(miniflow3); | |
1753 | free(minimask); | |
5cb7a798 BP |
1754 | } |
1755 | } | |
1756 | ||
1757 | static void | |
1636c761 | 1758 | test_minimask_has_extra(struct ovs_cmdl_context *ctx OVS_UNUSED) |
5cb7a798 BP |
1759 | { |
1760 | struct flow_wildcards catchall; | |
8fd47924 | 1761 | struct minimask *minicatchall; |
5cb7a798 BP |
1762 | struct flow flow; |
1763 | unsigned int idx; | |
1764 | ||
1765 | flow_wildcards_init_catchall(&catchall); | |
8fd47924 JR |
1766 | minicatchall = minimask_create(&catchall); |
1767 | assert(minimask_is_catchall(minicatchall)); | |
5cb7a798 BP |
1768 | |
1769 | random_set_seed(0x2ec7905b); | |
1770 | for (idx = 0; next_random_flow(&flow, idx); idx++) { | |
1771 | struct flow_wildcards mask; | |
8fd47924 | 1772 | struct minimask *minimask; |
5cb7a798 BP |
1773 | |
1774 | mask.masks = flow; | |
8fd47924 JR |
1775 | minimask = minimask_create(&mask); |
1776 | assert(!minimask_has_extra(minimask, minimask)); | |
1777 | assert(minimask_has_extra(minicatchall, minimask) | |
1778 | == !minimask_is_catchall(minimask)); | |
1779 | if (!minimask_is_catchall(minimask)) { | |
1780 | struct minimask *minimask2; | |
5cb7a798 BP |
1781 | |
1782 | wildcard_extra_bits(&mask); | |
8fd47924 JR |
1783 | minimask2 = minimask_create(&mask); |
1784 | assert(minimask_has_extra(minimask2, minimask)); | |
1785 | assert(!minimask_has_extra(minimask, minimask2)); | |
1786 | free(minimask2); | |
5cb7a798 BP |
1787 | } |
1788 | ||
8fd47924 | 1789 | free(minimask); |
5cb7a798 | 1790 | } |
f2f3f5cb | 1791 | |
8fd47924 | 1792 | free(minicatchall); |
5cb7a798 BP |
1793 | } |
1794 | ||
1795 | static void | |
1636c761 | 1796 | test_minimask_combine(struct ovs_cmdl_context *ctx OVS_UNUSED) |
5cb7a798 BP |
1797 | { |
1798 | struct flow_wildcards catchall; | |
8fd47924 | 1799 | struct minimask *minicatchall; |
5cb7a798 BP |
1800 | struct flow flow; |
1801 | unsigned int idx; | |
1802 | ||
1803 | flow_wildcards_init_catchall(&catchall); | |
8fd47924 JR |
1804 | minicatchall = minimask_create(&catchall); |
1805 | assert(minimask_is_catchall(minicatchall)); | |
5cb7a798 BP |
1806 | |
1807 | random_set_seed(0x181bf0cd); | |
1808 | for (idx = 0; next_random_flow(&flow, idx); idx++) { | |
8fd47924 | 1809 | struct minimask *minimask, *minimask2; |
5cb7a798 | 1810 | struct flow_wildcards mask, mask2, combined, combined2; |
8fd47924 JR |
1811 | struct { |
1812 | struct minimask minicombined; | |
1813 | uint64_t storage[FLOW_U64S]; | |
1814 | } m; | |
5cb7a798 BP |
1815 | struct flow flow2; |
1816 | ||
1817 | mask.masks = flow; | |
8fd47924 | 1818 | minimask = minimask_create(&mask); |
5cb7a798 | 1819 | |
8fd47924 JR |
1820 | minimask_combine(&m.minicombined, minimask, minicatchall, m.storage); |
1821 | assert(minimask_is_catchall(&m.minicombined)); | |
5cb7a798 BP |
1822 | |
1823 | any_random_flow(&flow2); | |
1824 | mask2.masks = flow2; | |
8fd47924 | 1825 | minimask2 = minimask_create(&mask2); |
5cb7a798 | 1826 | |
8fd47924 | 1827 | minimask_combine(&m.minicombined, minimask, minimask2, m.storage); |
368eefac | 1828 | flow_wildcards_and(&combined, &mask, &mask2); |
8fd47924 | 1829 | minimask_expand(&m.minicombined, &combined2); |
5cb7a798 BP |
1830 | assert(flow_wildcards_equal(&combined, &combined2)); |
1831 | ||
8fd47924 JR |
1832 | free(minimask); |
1833 | free(minimask2); | |
5cb7a798 | 1834 | } |
f2f3f5cb | 1835 | |
8fd47924 | 1836 | free(minicatchall); |
5cb7a798 BP |
1837 | } |
1838 | \f | |
1add079e JR |
1839 | |
1840 | static void help(struct ovs_cmdl_context *ctx); | |
1841 | ||
5f383751 | 1842 | static const struct ovs_cmdl_command commands[] = { |
5cb7a798 | 1843 | /* Classifier tests. */ |
1f4a7252 RM |
1844 | {"empty", NULL, 0, 0, test_empty, OVS_RO }, |
1845 | {"destroy-null", NULL, 0, 0, test_destroy_null, OVS_RO }, | |
1846 | {"single-rule", NULL, 0, 0, test_single_rule, OVS_RO }, | |
1847 | {"rule-replacement", NULL, 0, 0, test_rule_replacement, OVS_RO }, | |
1848 | {"many-rules-in-one-list", NULL, 0, 1, test_many_rules_in_one_list, OVS_RO }, | |
1849 | {"many-rules-in-one-table", NULL, 0, 1, test_many_rules_in_one_table, OVS_RO }, | |
1850 | {"many-rules-in-two-tables", NULL, 0, 0, test_many_rules_in_two_tables, OVS_RO }, | |
1851 | {"many-rules-in-five-tables", NULL, 0, 0, test_many_rules_in_five_tables, OVS_RO }, | |
1852 | {"benchmark", NULL, 0, 5, run_benchmarks, OVS_RO }, | |
5cb7a798 BP |
1853 | |
1854 | /* Miniflow and minimask tests. */ | |
1f4a7252 RM |
1855 | {"miniflow", NULL, 0, 0, test_miniflow, OVS_RO }, |
1856 | {"minimask_has_extra", NULL, 0, 0, test_minimask_has_extra, OVS_RO }, | |
1857 | {"minimask_combine", NULL, 0, 0, test_minimask_combine, OVS_RO }, | |
5cb7a798 | 1858 | |
1f4a7252 RM |
1859 | {"--help", NULL, 0, 0, help, OVS_RO }, |
1860 | {NULL, NULL, 0, 0, NULL, OVS_RO }, | |
3223e977 | 1861 | }; |
064af421 | 1862 | |
1add079e JR |
1863 | static void |
1864 | help(struct ovs_cmdl_context *ctx OVS_UNUSED) | |
1865 | { | |
1866 | const struct ovs_cmdl_command *p; | |
1867 | struct ds test_names = DS_EMPTY_INITIALIZER; | |
1868 | const int linesize = 80; | |
1869 | ||
1870 | printf("usage: ovstest %s TEST [TESTARGS]\n" | |
1871 | "where TEST is one of the following:\n\n", | |
1872 | program_name); | |
1873 | ||
1874 | for (p = commands; p->name != NULL; p++) { | |
1875 | if (*p->name != '-') { /* Skip internal commands */ | |
1876 | if (test_names.length > 1 | |
1877 | && test_names.length + strlen(p->name) + 1 >= linesize) { | |
1878 | test_names.length -= 1; | |
1879 | printf ("%s\n", ds_cstr(&test_names)); | |
1880 | ds_clear(&test_names); | |
1881 | } | |
1882 | ds_put_format(&test_names, "%s, ", p->name); | |
1883 | } | |
1884 | } | |
1885 | if (test_names.length > 2) { | |
1886 | test_names.length -= 2; | |
1887 | printf("%s\n", ds_cstr(&test_names)); | |
1888 | } | |
1889 | ds_destroy(&test_names); | |
1890 | } | |
1891 | ||
eadd1644 AZ |
1892 | static void |
1893 | test_classifier_main(int argc, char *argv[]) | |
064af421 | 1894 | { |
1636c761 RB |
1895 | struct ovs_cmdl_context ctx = { |
1896 | .argc = argc - 1, | |
1897 | .argv = argv + 1, | |
1898 | }; | |
b5d97350 | 1899 | set_program_name(argv[0]); |
3bbe9a1f JR |
1900 | |
1901 | if (argc > 1 && !strcmp(argv[1], "--versioned")) { | |
1902 | versioned = true; | |
1903 | ctx.argc--; | |
1904 | ctx.argv++; | |
1905 | } | |
1906 | ||
064af421 | 1907 | init_values(); |
1636c761 | 1908 | ovs_cmdl_run_command(&ctx, commands); |
064af421 | 1909 | } |
eadd1644 AZ |
1910 | |
1911 | OVSTEST_REGISTER("test-classifier", test_classifier_main); |