The "learn" action is useful for MAC learning, but until now there has been no way to find out through OpenFlow how much time remains before a MAC learning entry (a learned flow) expires. This commit adds that ability.
Feature #7193. Signed-off-by: Ben Pfaff <b...@nicira.com> --- NEWS | 2 + include/openflow/nicira-ext.h | 29 +++++++++++++++++++-- lib/learning-switch.c | 1 + lib/ofp-print.c | 11 +++++++- lib/ofp-util.c | 34 +++++++++++++++++++++++- lib/ofp-util.h | 8 ++++- ofproto/ofproto.c | 24 ++++++++++++++++++ tests/ofp-print.at | 16 ++++++------ tests/ofproto-dpif.at | 55 +++++++++++++++++++++++++++++++++++++++++ tests/ofproto-macros.at | 2 + utilities/ovs-ofctl.8.in | 41 +++++++++++++++++++----------- utilities/ovs-ofctl.c | 2 +- 12 files changed, 193 insertions(+), 32 deletions(-) diff --git a/NEWS b/NEWS index 592e013..c3cf232 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,8 @@ post-v1.5.0 table, with configurable policy for evicting flows upon overflow. See the Flow_Table table in ovs-vswitch.conf.db(5) for more information. + - OpenFlow: + - NXM flow dumps now include time left before hard and idle timeouts. - ofproto-provider interface: - "struct rule" has a new member "used" that ofproto implementations should maintain by updating with ofproto_rule_update_used(). diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h index 6921803..d850bb0 100644 --- a/include/openflow/nicira-ext.h +++ b/include/openflow/nicira-ext.h @@ -108,7 +108,12 @@ enum nicira_type { /* Alternative PACKET_IN message formats. */ NXT_SET_PACKET_IN_FORMAT = 16, /* Set Packet In format. */ - NXT_PACKET_IN = 17 /* Nicira Packet In. */ + NXT_PACKET_IN = 17, /* Nicira Packet In. */ + + /* Are the idle_left and hard_left members in struct nx_flow_stats + * supported? If so, the switch does not reply to this message. If not, + * the switch sends an error reply. */ + NXT_FLOW_LEFT = 18, }; /* Header for Nicira vendor stats request and reply messages. */ @@ -1763,7 +1768,24 @@ struct nx_flow_stats_request { OFP_ASSERT(sizeof(struct nx_flow_stats_request) == 32); /* Body for Nicira vendor stats reply of type NXST_FLOW (analogous to - * OFPST_FLOW reply). */ + * OFPST_FLOW reply). + * + * The value of 'idle_left' is only meaningful when talking to a switch that + * implements the NXT_FLOW_LEFT extension. There are three possibilities: + * + * - idle_left == 0: The amount of idle time before the flow expires is + * unknown, or 'idle_timeout' is 0 (OFP_FLOW_PERMANENT). This is also the + * value that one should ordinarily expect to see talking to a switch that + * does not implement NXT_FLOW_ELAPSED, since those switches zero the + * padding bytes that 'idle_left' replaced. + * + * - 0 < idle_left <= idle_timeout: There are approximately 'idle_left' + * seconds until the flow expires, if no (more) packets arrive in the flow. + * + * - idle_left > idle_timeout: Invalid. + * + * 'hard_left' may be interpreted similarly with respect to hard_timeout. + */ struct nx_flow_stats { ovs_be16 length; /* Length of this entry. */ uint8_t table_id; /* ID of table flow came from. */ @@ -1776,7 +1798,8 @@ struct nx_flow_stats { ovs_be16 idle_timeout; /* Number of seconds idle before expiration. */ ovs_be16 hard_timeout; /* Number of seconds before expiration. */ ovs_be16 match_len; /* Length of nx_match. */ - uint8_t pad2[4]; /* Align to 64 bits. */ + ovs_be16 idle_left; /* Remaining seconds idle before expiration. */ + ovs_be16 hard_left; /* Remaining seconds before hard expiration. */ ovs_be64 cookie; /* Opaque controller-issued identifier. */ ovs_be64 packet_count; /* Number of packets, UINT64_MAX if unknown. */ ovs_be64 byte_count; /* Number of bytes, UINT64_MAX if unknown. */ diff --git a/lib/learning-switch.c b/lib/learning-switch.c index 60cf731..e578395 100644 --- a/lib/learning-switch.c +++ b/lib/learning-switch.c @@ -264,6 +264,7 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn, case OFPUTIL_NXT_PACKET_IN: case OFPUTIL_NXT_FLOW_MOD: case OFPUTIL_NXT_FLOW_REMOVED: + case OFPUTIL_NXT_FLOW_LEFT: case OFPUTIL_NXST_FLOW_REQUEST: case OFPUTIL_NXST_AGGREGATE_REQUEST: case OFPUTIL_NXST_FLOW_REPLY: diff --git a/lib/ofp-print.c b/lib/ofp-print.c index 3c5c34a..f4051f6 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -1022,7 +1022,7 @@ ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh) struct ofputil_flow_stats fs; int retval; - retval = ofputil_decode_flow_stats_reply(&fs, &b); + retval = ofputil_decode_flow_stats_reply(&fs, &b, true); if (retval) { if (retval != EOF) { ds_put_cstr(string, " ***parse error***"); @@ -1044,6 +1044,12 @@ ofp_print_flow_stats_reply(struct ds *string, const struct ofp_header *oh) if (fs.hard_timeout != OFP_FLOW_PERMANENT) { ds_put_format(string, "hard_timeout=%"PRIu16",", fs.hard_timeout); } + if (fs.idle_left) { + ds_put_format(string, "idle_left=%"PRIu16",", fs.idle_left); + } + if (fs.hard_left) { + ds_put_format(string, "hard_left=%"PRIu16",", fs.hard_left); + } cls_rule_format(&fs.rule, string); if (string->string[string->length - 1] != ' ') { @@ -1456,6 +1462,9 @@ ofp_to_string__(const struct ofp_header *oh, ofp_print_flow_mod(string, msg, code, verbosity); break; + case OFPUTIL_NXT_FLOW_LEFT: + break; + case OFPUTIL_NXST_AGGREGATE_REPLY: ofp_print_stats_reply(string, oh); ofp_print_nxst_aggregate_reply(string, msg); diff --git a/lib/ofp-util.c b/lib/ofp-util.c index 2565ec7..a705277 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -385,6 +385,10 @@ ofputil_decode_vendor(const struct ofp_header *oh, size_t length, { OFPUTIL_NXT_FLOW_MOD_TABLE_ID, OFP10_VERSION, NXT_FLOW_MOD_TABLE_ID, "NXT_FLOW_MOD_TABLE_ID", sizeof(struct nx_flow_mod_table_id), 0 }, + + { OFPUTIL_NXT_FLOW_LEFT, OFP10_VERSION, + NXT_FLOW_LEFT, "NXT_FLOW_LEFT", + sizeof(struct nicira_header), 0 }, }; static const struct ofputil_msg_category nxt_category = { @@ -1290,11 +1294,18 @@ ofputil_encode_flow_stats_request(const struct ofputil_flow_stats_request *fsr, * iterates through the replies. The caller must initially leave 'msg''s layer * pointers null and not modify them between calls. * + * Most switches don't send the values needed to populate fs->idle_left and + * fs->hard_left, so those members will usually be set to 0. If the switch + * from which 'fs' implements For NXST_FLOW + * replies when 'flow_left_extension' true, the contents of 'msg' determine the + * 'idle_left' and 'hard_left' members in 'fs'. + * * Returns 0 if successful, EOF if no replies were left in this 'msg', * otherwise a positive errno value. */ int ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs, - struct ofpbuf *msg) + struct ofpbuf *msg, + bool flow_left_extension) { const struct ofputil_msg_type *type; int code; @@ -1345,6 +1356,8 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs, fs->duration_nsec = ntohl(ofs->duration_nsec); fs->idle_timeout = ntohs(ofs->idle_timeout); fs->hard_timeout = ntohs(ofs->hard_timeout); + fs->idle_left = 0; + fs->hard_left = 0; fs->packet_count = ntohll(get_32aligned_be64(&ofs->packet_count)); fs->byte_count = ntohll(get_32aligned_be64(&ofs->byte_count)); } else if (code == OFPUTIL_NXST_FLOW_REPLY) { @@ -1382,6 +1395,22 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs, fs->duration_nsec = ntohl(nfs->duration_nsec); fs->idle_timeout = ntohs(nfs->idle_timeout); fs->hard_timeout = ntohs(nfs->hard_timeout); + if (flow_left_extension) { + fs->idle_left = ntohs(nfs->idle_left); + if (fs->idle_left > fs->idle_timeout) { + VLOG_WARN_RL(&bad_ofmsg_rl, "invalid idle_left in NXST_FLOW"); + fs->idle_left = 0; + } + + fs->hard_left = ntohs(nfs->hard_left); + if (fs->hard_left > fs->hard_timeout) { + VLOG_WARN_RL(&bad_ofmsg_rl, "invalid hard_left in NXST_FLOW"); + fs->hard_left = 0; + } + } else { + fs->idle_left = 0; + fs->hard_left = 0; + } fs->packet_count = ntohll(nfs->packet_count); fs->byte_count = ntohll(nfs->byte_count); } else { @@ -1450,8 +1479,9 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs, nfs->priority = htons(fs->rule.priority); nfs->idle_timeout = htons(fs->idle_timeout); nfs->hard_timeout = htons(fs->hard_timeout); + nfs->idle_left = htons(fs->idle_left); + nfs->hard_left = htons(fs->hard_left); nfs->match_len = htons(nx_put_match(msg, &fs->rule, 0, 0)); - memset(nfs->pad2, 0, sizeof nfs->pad2); nfs->cookie = fs->cookie; nfs->packet_count = htonll(fs->packet_count); nfs->byte_count = htonll(fs->byte_count); diff --git a/lib/ofp-util.h b/lib/ofp-util.h index 422c14a..149cf51 100644 --- a/lib/ofp-util.h +++ b/lib/ofp-util.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2009, 2010, 2011 Nicira Networks. + * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira Networks. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,6 +79,7 @@ enum ofputil_msg_code { OFPUTIL_NXT_FLOW_REMOVED, OFPUTIL_NXT_SET_PACKET_IN_FORMAT, OFPUTIL_NXT_PACKET_IN, + OFPUTIL_NXT_FLOW_LEFT, /* NXST_* stat requests. */ OFPUTIL_NXST_FLOW_REQUEST, @@ -183,6 +184,8 @@ struct ofputil_flow_stats { uint32_t duration_nsec; uint16_t idle_timeout; uint16_t hard_timeout; + uint16_t idle_left; /* Seconds to idle expiration, 0 if unknown. */ + uint16_t hard_left; /* Seconds to hard expiration, 0 if unknown. */ uint64_t packet_count; /* Packet count, UINT64_MAX if unknown. */ uint64_t byte_count; /* Byte count, UINT64_MAX if unknown. */ union ofp_action *actions; @@ -190,7 +193,8 @@ struct ofputil_flow_stats { }; int ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *, - struct ofpbuf *msg); + struct ofpbuf *msg, + bool flow_left_extension); void ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *, struct list *replies); diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index c35d440..cf041ba 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -2337,6 +2337,24 @@ collect_rules_strict(struct ofproto *ofproto, uint8_t table_id, return 0; } +/* If 'timeout' is 0, returns 0. Otherwise, returns a value between 1 and + * 'timeout', inclusive, that reports the number of seconds until a + * 'timeout'-second timer started at 'start' (in ms, as returned by + * time_msec()) expires. */ +static uint16_t +time_left(long long int start, uint16_t timeout) +{ + if (!timeout) { + return 0; + } else { + /* mecs in which the flow expires, plus .5 to round to nearest. */ + long long int ms_left = (start + timeout * 1000) - time_msec() + 500; + return (ms_left < 1000 ? 1 + : ms_left >= 1000 * timeout ? timeout + : (unsigned int) ms_left / 1000); + } +} + static enum ofperr handle_flow_stats_request(struct ofconn *ofconn, const struct ofp_stats_msg *osm) @@ -2371,6 +2389,8 @@ handle_flow_stats_request(struct ofconn *ofconn, &fs.duration_nsec); fs.idle_timeout = rule->idle_timeout; fs.hard_timeout = rule->hard_timeout; + fs.idle_left = time_left(rule->used, rule->idle_timeout); + fs.hard_left = time_left(rule->modified, rule->hard_timeout); ofproto->ofproto_class->rule_get_stats(rule, &fs.packet_count, &fs.byte_count); fs.actions = rule->actions; @@ -3199,6 +3219,10 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg) case OFPUTIL_NXT_FLOW_MOD: return handle_flow_mod(ofconn, oh); + case OFPUTIL_NXT_FLOW_LEFT: + /* Nothing to do. */ + return 0; + /* Statistics requests. */ case OFPUTIL_OFPST_DESC_REQUEST: return handle_desc_stats_request(ofconn, msg->data); diff --git a/tests/ofp-print.at b/tests/ofp-print.at index 85562b6..9b6ae5e 100644 --- a/tests/ofp-print.at +++ b/tests/ofp-print.at @@ -798,7 +798,7 @@ AT_CHECK([ovs-ofctl ofp-print "\ a8 00 02 00 00 0c 01 06 00 00 12 02 09 e7 00 00 \ 14 02 00 00 00 00 00 00 00 00 00 08 00 01 00 00 \ 00 88 00 00 00 00 00 03 32 11 62 00 ff ff 00 05 \ -00 00 00 4c 00 00 00 00 00 00 00 00 00 00 00 00 \ +00 00 00 4c 00 03 00 00 00 00 00 00 00 00 00 00 \ 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 3c \ 00 00 00 02 00 03 00 00 02 06 50 54 00 00 00 06 \ 00 00 04 06 50 54 00 00 00 05 00 00 06 02 08 00 \ @@ -806,7 +806,7 @@ a8 00 02 00 00 0c 01 06 00 00 12 02 09 e7 00 00 \ a8 00 01 00 00 10 04 c0 a8 00 02 00 00 0c 01 06 \ 00 00 12 02 09 e4 00 00 14 02 00 00 00 00 00 00 \ 00 00 00 08 00 01 00 00 00 88 00 00 00 00 00 02 \ -33 f9 aa 00 ff ff 00 05 00 00 00 4c 00 00 00 00 \ +33 f9 aa 00 ff ff 00 05 00 00 00 4c 00 05 00 00 \ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 \ 00 00 00 00 00 00 00 3c 00 00 00 02 00 01 00 00 \ 02 06 50 54 00 00 00 05 00 00 04 06 50 54 00 00 \ @@ -815,7 +815,7 @@ a8 00 01 00 00 10 04 c0 a8 00 02 00 00 0c 01 06 \ a8 00 01 00 00 0c 01 06 00 00 12 02 00 00 00 00 \ 14 02 09 e5 00 00 00 00 00 00 00 08 00 03 00 00 \ 00 88 00 00 00 00 00 04 2d 0f a5 00 ff ff 00 05 \ -00 00 00 4c 00 00 00 00 00 00 00 00 00 00 00 00 \ +00 00 00 4c 00 01 00 00 00 00 00 00 00 00 00 00 \ 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 3c \ 00 00 00 02 00 03 00 00 02 06 50 54 00 00 00 06 \ 00 00 04 06 50 54 00 00 00 05 00 00 06 02 08 00 \ @@ -823,7 +823,7 @@ a8 00 01 00 00 0c 01 06 00 00 12 02 00 00 00 00 \ a8 00 01 00 00 10 04 c0 a8 00 02 00 00 0c 01 06 \ 00 00 12 02 09 e3 00 00 14 02 00 00 00 00 00 00 \ 00 00 00 08 00 01 00 00 00 88 00 00 00 00 00 02 \ -34 73 bc 00 ff ff 00 05 00 00 00 4c 00 00 00 00 \ +34 73 bc 00 ff ff 00 05 00 0a 00 4c 00 03 00 08 \ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 \ 00 00 00 00 00 00 00 3c 00 00 00 02 00 03 00 00 \ 02 06 50 54 00 00 00 06 00 00 04 06 50 54 00 00 \ @@ -920,10 +920,10 @@ ff ff 00 18 00 00 23 20 00 07 00 1f 00 01 00 04 \ "], [0], [[NXST_FLOW reply (xid=0x4): cookie=0x0, duration=1.048s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2535,tp_dst=0 actions=output:1 - cookie=0x0, duration=3.84s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2532,tp_dst=0 actions=output:1 - cookie=0x0, duration=2.872s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2533 actions=output:3 - cookie=0x0, duration=4.756s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2531,tp_dst=0 actions=output:1 - cookie=0x0, duration=2.88s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2533,tp_dst=0 actions=output:1 + cookie=0x0, duration=3.84s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,idle_left=3,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2532,tp_dst=0 actions=output:1 + cookie=0x0, duration=2.872s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,idle_left=5,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2533 actions=output:3 + cookie=0x0, duration=4.756s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,idle_left=1,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2531,tp_dst=0 actions=output:1 + cookie=0x0, duration=2.88s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,hard_timeout=10,idle_left=3,hard_left=8,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2533,tp_dst=0 actions=output:1 cookie=0x0, duration=5.672s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=3,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:06,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=2530,tp_dst=0 actions=output:1 cookie=0x0, duration=1.04s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2535 actions=output:3 cookie=0x0, duration=1.952s, table=0, n_packets=1, n_bytes=60, idle_timeout=5,priority=65535,tcp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=0,tp_dst=2534 actions=output:3 diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index 6b4c216..fdcd9ed 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -1035,3 +1035,58 @@ echo $n_recs AT_CHECK([test $n_recs -ge 13]) AT_CLEANUP + +AT_SETUP([idle_left and hard_left decline over time]) +OVS_VSWITCHD_START + +AT_CHECK([ovs-ofctl add-flow br0 hard_timeout=99,idle_timeout=88,actions=drop]) + +# Fetch the hard and idle time left into variables whose names are +# given in $1 and $2, respectively, checking that each value is a +# positive integer. +get_time_left () { + AT_CHECK([ovs-ofctl dump-flows br0], [0], [stdout]) + + hard=`sed -n 's/.*hard_left=\([[0-9]]*\),.*/\1/p' stdout` + AT_CHECK([[expr X"$hard" : 'X[1-9][0-9]*$']], [0], [ignore]) + AS_VAR_COPY([$1], [hard]) + + idle=`sed -n 's/.*idle_left=\([[0-9]]*\),.*/\1/p' stdout` + AT_CHECK([[expr X"$idle" : 'X[1-9][0-9]*$']], [0], [ignore]) + AS_VAR_COPY([$2], [idle]) +} + +# Get the initial hard and idle time left. This should presumably be +# 99 and 88, respectively, but if we're running slowly could be less, +# so we don't bother to check +get_time_left hard1 idle1 + +# Warp time forward by 10 seconds. +ovs-appctl time/warp 10000 +get_time_left hard2 idle2 + +# Warp time forward 10 more seconds, then pass some packets through the flow, +# then warp forward a few more times because idle times are only updated +# occasionally. +ovs-appctl time/warp 10000 +ovs-appctl netdev-dummy/receive br0 'in_port(0),eth(src=50:54:00:00:00:07,dst=50:54:00:00:00:05),eth_type(0x0800),ipv4(src=192.168.0.2,dst=192.168.0.1,proto=6,tos=0,ttl=64,frag=no),tcp(src=80,dst=1234)' +ovs-ofctl dump-flows br0 +get_time_left hard3 idle3 +ovs-appctl time/warp 1000 +ovs-appctl time/warp 1000 +ovs-appctl time/warp 1000 + +echo "hard_left: $hard1 => $hard2 => $hard3" +echo "idle_left: $idle1 => $idle2 => $idle3" + +# Hard time left should have decreased from 1 to 2 to 3. It's hard to predict +# how much, but at the very least it should be less than before +AT_CHECK([test $hard2 -lt $hard1]) +AT_CHECK([test $hard3 -lt $hard2]) + +# Idle time left should have decreased from 1 to 2, then increased to 3. +AT_CHECK([test $idle2 -lt $idle1]) +AT_CHECK([test $idle3 -gt $idle2]) + +OVS_VSWITCHD_STOP +AT_CLEANUP diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at index 3656ecf..6fb6106 100644 --- a/tests/ofproto-macros.at +++ b/tests/ofproto-macros.at @@ -10,6 +10,8 @@ s/ cookie=0x0,// s/ table=0,// s/ n_packets=0,// s/ n_bytes=0,// +s/idle_left=[0-9]*,// +s/hard_left=[0-9]*,// ' }] m4_divert_pop([PREPARE_TESTS]) diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index 0699d69..ea66ef7 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -1009,19 +1009,13 @@ If set, a matching flow must include an output action to \fIport\fR. . The \fBdump\-tables\fR and \fBdump\-aggregate\fR commands print information about the entries in a datapath's tables. Each line of output is a -unique flow entry, which begins with some common information: +flow entry as described in \fBFlow Syntax\fR, above, plus some +additional fields: . -.IP \fBduration\fR -The number of seconds the entry has been in the table. -. -.IP \fBtable\fR -The table that contains the flow. When a packet arrives, the switch -begins searching for an entry at the lowest numbered table. Tables are -numbered as shown by the \fBdump\-tables\fR command. -. -.IP \fBpriority\fR -The priority of the entry in relation to other entries within the same -table. A higher value will match before a lower one. +.IP \fBduration=\fIsecs\fR +The time, in seconds, that the entry has been in the table. +\fIsecs\fR includes as much precision as the switch provides, possibly +to nanosecond resolution. . .IP \fBn_packets\fR The number of packets that have matched the entry. @@ -1030,9 +1024,26 @@ The number of packets that have matched the entry. The total number of bytes from packets that have matched the entry. . .PP -The rest of the line consists of a description of the flow entry as -described in \fBFlow Syntax\fR, above. -. +The following additional fields are included only if the flow has the +respective timeout, the switch is Open vSwitch 1.6 or later, and the +NXM flow format (see the description of the \fB\-\-flow-format\fR +option below) is used to dump the flow. The values of these +additional fields are approximations only and in particular +\fBidle_left\fR will sometimes drop below \fBidle_timeout\fR even for +a very busy flow. +. +.IP \fBhard_left=\fIsecs\fR +The number of seconds that remain before the flow expires because the +\fBhard_timeout\fR has been reached, as an integer number of seconds. +(This is separate from \fBduration\fR because \fBmod\-flows\fR restarts +the \fBhard_timeout\fR timer without zeroing \fBduration\fR.) +. +.IP \fBidle_left=\fIsecs\fR +The number of seconds that remain before the flow expires because +\fBidle_timeout\fR seconds have passed without any packets passing +through the flow, as an integer number of seconds. (If a packet +passes through the flow this duration elapses, the idle timeout will +not trigger.) . .SH OPTIONS .TP diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c index 3d3a7b7..d64964c 100644 --- a/utilities/ovs-ofctl.c +++ b/utilities/ovs-ofctl.c @@ -1353,7 +1353,7 @@ read_flows_from_switch(struct vconn *vconn, enum nx_flow_format flow_format, struct ofputil_flow_stats fs; int retval; - retval = ofputil_decode_flow_stats_reply(&fs, reply); + retval = ofputil_decode_flow_stats_reply(&fs, reply, false); if (retval) { if (retval != EOF) { ovs_fatal(0, "parse error in reply"); -- 1.7.2.5 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev