@@ -27,6 +27,8 @@ OVN v22.06.0 - XX XXX XXXX
- Added support for setting the Next server IP in the DHCP header
using the private DHCP option - 253 in native OVN DHCPv4 responder.
- Support list of chassis for Logical_Switch_Port:options:requested-chassis.
+ - Support Logical_Switch_Port:options:activation-strategy for live migration
+ scenarios.
OVN v22.03.0 - 11 Mar 2022
--------------------------
@@ -40,7 +40,9 @@
#include "lib/mcast-group-index.h"
#include "lib/ovn-sb-idl.h"
#include "lib/ovn-util.h"
+#include "ovn/actions.h"
#include "physical.h"
+#include "pinctrl.h"
#include "openvswitch/shash.h"
#include "simap.h"
#include "smap.h"
@@ -984,6 +986,92 @@ enum access_type {
PORT_HA_REMOTE,
};
+static void
+setup_rarp_activation_strategy(const struct sbrec_port_binding *binding,
+ ofp_port_t ofport, struct zone_ids *zone_ids,
+ struct ovn_desired_flow_table *flow_table,
+ struct ofpbuf *ofpacts_p)
+{
+ struct match match = MATCH_CATCHALL_INITIALIZER;
+
+ /* Unblock the port on ingress RARP. */
+ match_set_dl_type(&match, htons(ETH_TYPE_RARP));
+ match_set_in_port(&match, ofport);
+ ofpbuf_clear(ofpacts_p);
+
+ load_logical_ingress_metadata(binding, zone_ids, ofpacts_p);
+
+ size_t ofs = ofpacts_p->size;
+ struct ofpact_controller *oc = ofpact_put_CONTROLLER(ofpacts_p);
+ oc->max_len = UINT16_MAX;
+ oc->reason = OFPR_ACTION;
+
+ struct action_header ah = {
+ .opcode = htonl(ACTION_OPCODE_ACTIVATION_STRATEGY_RARP)
+ };
+ ofpbuf_put(ofpacts_p, &ah, sizeof ah);
+
+ ofpacts_p->header = oc;
+ oc->userdata_len = ofpacts_p->size - (ofs + sizeof *oc);
+ ofpact_finish_CONTROLLER(ofpacts_p, &oc);
+
+ ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 1010,
+ binding->header_.uuid.parts[0],
+ &match, ofpacts_p, &binding->header_.uuid);
+ ofpbuf_clear(ofpacts_p);
+
+ /* Block all non-RARP traffic for the port, both directions. */
+ match_init_catchall(&match);
+ match_set_in_port(&match, ofport);
+
+ ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 1000,
+ binding->header_.uuid.parts[0],
+ &match, ofpacts_p, &binding->header_.uuid);
+
+ match_init_catchall(&match);
+ uint32_t dp_key = binding->datapath->tunnel_key;
+ uint32_t port_key = binding->tunnel_key;
+ match_set_metadata(&match, htonll(dp_key));
+ match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
+
+ ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 1000,
+ binding->header_.uuid.parts[0],
+ &match, ofpacts_p, &binding->header_.uuid);
+}
+
+static void
+setup_activation_strategy(const struct sbrec_port_binding *binding,
+ const struct sbrec_chassis *chassis,
+ uint32_t dp_key, uint32_t port_key,
+ ofp_port_t ofport, struct zone_ids *zone_ids,
+ struct ovn_desired_flow_table *flow_table,
+ struct ofpbuf *ofpacts_p)
+{
+ for (int i = 0; i < binding->n_additional_chassis; i++) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+ if (binding->additional_chassis[i] == chassis) {
+ const char *strategy = smap_get(&binding->options,
+ "activation-strategy");
+ if (strategy
+ && !db_is_port_activated(binding, chassis)
+ && !pinctrl_is_port_activated(dp_key, port_key)) {
+ if (!strcmp(strategy, "rarp")) {
+ setup_rarp_activation_strategy(binding, ofport,
+ zone_ids, flow_table,
+ ofpacts_p);
+ } else {
+ VLOG_WARN_RL(&rl,
+ "Unknown activation strategy defined for "
+ "port %s: %s",
+ binding->logical_port, strategy);
+ return;
+ }
+ }
+ return;
+ }
+ }
+}
+
static void
consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
enum mf_field_id mff_ovn_geneve,
@@ -1240,6 +1328,10 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
}
}
+ setup_activation_strategy(binding, chassis, dp_key, port_key,
+ ofport, &zone_ids, flow_table,
+ ofpacts_p);
+
/* Remember the size with just strip vlan added so far,
* as we're going to remove this with ofpbuf_pull() later. */
uint32_t ofpacts_orig_size = ofpacts_p->size;
@@ -29,10 +29,12 @@
#include "lport.h"
#include "mac-learn.h"
#include "nx-match.h"
+#include "ofctrl.h"
#include "latch.h"
#include "lib/packets.h"
#include "lib/sset.h"
#include "openvswitch/ofp-actions.h"
+#include "openvswitch/ofp-flow.h"
#include "openvswitch/ofp-msgs.h"
#include "openvswitch/ofp-packet.h"
#include "openvswitch/ofp-print.h"
@@ -152,8 +154,8 @@ VLOG_DEFINE_THIS_MODULE(pinctrl);
* and pinctrl_run().
* 'pinctrl_handler_seq' is used by pinctrl_run() to
* wake up pinctrl_handler thread from poll_block() if any changes happened
- * in 'send_garp_rarp_data', 'ipv6_ras' and 'buffered_mac_bindings'
- * structures.
+ * in 'send_garp_rarp_data', 'ipv6_ras', 'activated_ports' and
+ * 'buffered_mac_bindings' structures.
*
* 'pinctrl_main_seq' is used by pinctrl_handler() thread to wake up
* the main thread from poll_block() when mac bindings/igmp groups need to
@@ -198,6 +200,20 @@ static void wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn);
static void send_mac_binding_buffered_pkts(struct rconn *swconn)
OVS_REQUIRES(pinctrl_mutex);
+static void pinctrl_rarp_activation_strategy_handler(struct rconn *swconn,
+ const struct match *md,
+ struct dp_packet *pkt_in);
+
+static void init_activated_ports(void);
+static void destroy_activated_ports(void);
+static void wait_activated_ports(struct ovsdb_idl_txn *ovnsb_idl_txn);
+static void run_activated_ports(
+ struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+ struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ const struct sbrec_chassis *chassis)
+ OVS_REQUIRES(pinctrl_mutex);
+
static void init_send_garps_rarps(void);
static void destroy_send_garps_rarps(void);
static void send_garp_rarp_wait(long long int send_garp_rarp_time);
@@ -522,6 +538,7 @@ pinctrl_init(void)
init_ipv6_ras();
init_ipv6_prefixd();
init_buffered_packets_map();
+ init_activated_ports();
init_event_table();
ip_mcast_snoop_init();
init_put_vport_bindings();
@@ -3269,6 +3286,13 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
ovs_mutex_unlock(&pinctrl_mutex);
break;
+ case ACTION_OPCODE_ACTIVATION_STRATEGY_RARP:
+ ovs_mutex_lock(&pinctrl_mutex);
+ pinctrl_rarp_activation_strategy_handler(swconn, &pin.flow_metadata,
+ &packet);
+ ovs_mutex_unlock(&pinctrl_mutex);
+ break;
+
default:
VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
ntohl(ah->opcode));
@@ -3533,6 +3557,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name,
chassis, active_tunnels);
run_put_fdbs(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac);
+ run_activated_ports(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
+ sbrec_port_binding_by_key, chassis);
ovs_mutex_unlock(&pinctrl_mutex);
}
@@ -4036,6 +4062,7 @@ pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn)
int64_t new_seq = seq_read(pinctrl_main_seq);
seq_wait(pinctrl_main_seq, new_seq);
wait_put_fdbs(ovnsb_idl_txn);
+ wait_activated_ports(ovnsb_idl_txn);
}
/* Called by ovn-controller. */
@@ -4050,6 +4077,7 @@ pinctrl_destroy(void)
destroy_ipv6_ras();
destroy_ipv6_prefixd();
destroy_buffered_packets_map();
+ destroy_activated_ports();
event_table_destroy();
destroy_put_mac_bindings();
destroy_put_vport_bindings();
@@ -7727,6 +7755,200 @@ pinctrl_handle_svc_check(struct rconn *swconn, const struct flow *ip_flow,
}
}
+static struct ofpbuf *
+encode_flow_mod(struct ofputil_flow_mod *fm)
+{
+ fm->buffer_id = UINT32_MAX;
+ fm->out_port = OFPP_ANY;
+ fm->out_group = OFPG_ANY;
+ return ofputil_encode_flow_mod(fm, OFPUTIL_P_OF15_OXM);
+}
+
+struct port_pair {
+ uint32_t dp_key;
+ uint32_t port_key;
+ struct ovs_list list;
+};
+
+static struct ovs_list activated_ports;
+
+static void
+init_activated_ports(void)
+{
+ ovs_list_init(&activated_ports);
+}
+
+static void
+destroy_activated_ports(void)
+{
+ struct port_pair *pp;
+ LIST_FOR_EACH_POP (pp, list, &activated_ports) {
+ free(pp);
+ }
+}
+
+static void
+wait_activated_ports(struct ovsdb_idl_txn *ovnsb_idl_txn)
+{
+ if (ovnsb_idl_txn && !ovs_list_is_empty(&activated_ports)) {
+ poll_immediate_wake();
+ }
+}
+
+bool
+db_is_port_activated(const struct sbrec_port_binding *pb,
+ const struct sbrec_chassis *chassis)
+{
+ const char *activated_chassis = smap_get(&pb->options,
+ "additional-chassis-activated");
+ if (activated_chassis) {
+ char *save_ptr;
+ char *tokstr = xstrdup(activated_chassis);
+ for (const char *chassis_name = strtok_r(tokstr, ",", &save_ptr);
+ chassis_name != NULL;
+ chassis_name = strtok_r(NULL, ",", &save_ptr)) {
+ if (!strcmp(chassis_name, chassis->name)) {
+ free(tokstr);
+ return true;
+ }
+ }
+ free(tokstr);
+ }
+ return false;
+}
+
+static void
+run_activated_ports(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+ struct ovsdb_idl_index *sbrec_port_binding_by_key,
+ const struct sbrec_chassis *chassis)
+ OVS_REQUIRES(pinctrl_mutex)
+{
+ if (!ovnsb_idl_txn) {
+ return;
+ }
+
+ const struct port_pair *pp;
+ LIST_FOR_EACH (pp, list, &activated_ports) {
+ const struct sbrec_port_binding *pb = lport_lookup_by_key(
+ sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
+ pp->dp_key, pp->port_key);
+ if (pb) {
+ if (!db_is_port_activated(pb, chassis)) {
+ const char *activated_chassis = smap_get(
+ &pb->options, "additional-chassis-activated");
+ char *activated_str;
+ if (activated_chassis) {
+ activated_str = xasprintf(
+ "%s,%s", activated_chassis, chassis->name);
+ sbrec_port_binding_update_options_setkey(
+ pb, "additional-chassis-activated", activated_str);
+ free(activated_str);
+ } else {
+ sbrec_port_binding_update_options_setkey(
+ pb, "additional-chassis-activated", chassis->name);
+ }
+ }
+ }
+ }
+ destroy_activated_ports();
+}
+
+bool pinctrl_is_port_activated(int64_t dp_key, int64_t port_key)
+ OVS_REQUIRES(pinctrl_mutex)
+{
+ const struct port_pair *pp;
+ LIST_FOR_EACH (pp, list, &activated_ports) {
+ if (pp->dp_key == dp_key && pp->port_key == port_key) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void
+pinctrl_rarp_activation_strategy_handler(struct rconn *swconn,
+ const struct match *md,
+ struct dp_packet *pkt_in)
+ OVS_REQUIRES(pinctrl_mutex)
+{
+ /* Admitted; send RARP back to the pipeline. */
+ uint32_t port_key = md->flow.regs[MFF_LOG_INPORT - MFF_REG0];
+ uint32_t dp_key = ntohll(md->flow.metadata);
+
+ uint64_t ofpacts_stub[4096 / 8];
+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
+ enum ofp_version version = rconn_get_version(swconn);
+ put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
+ put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
+ struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
+ resubmit->in_port = OFPP_CONTROLLER;
+ resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE;
+
+ struct ofputil_packet_out po = {
+ .packet = dp_packet_data(pkt_in),
+ .packet_len = dp_packet_size(pkt_in),
+ .buffer_id = UINT32_MAX,
+ .ofpacts = ofpacts.data,
+ .ofpacts_len = ofpacts.size,
+ };
+ match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
+
+ enum ofputil_protocol proto;
+ proto = ofputil_protocol_from_ofp_version(version);
+ queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
+ ofpbuf_uninit(&ofpacts);
+
+ /* Delete ingress/egress drop flows to unblock the port. */
+ struct match match;
+ struct minimatch mmatch;
+
+ match_init_catchall(&match);
+ match_set_in_port(&match, md->flow.in_port.ofp_port);
+ minimatch_init(&mmatch, &match);
+
+ struct ofputil_flow_mod fm = {
+ .match = mmatch,
+ .priority = 1000,
+ .table_id = OFTABLE_PHY_TO_LOG,
+ .command = OFPFC_DELETE_STRICT,
+ };
+ queue_msg(swconn, encode_flow_mod(&fm));
+ minimatch_destroy(&mmatch);
+
+ match_init_catchall(&match);
+ match_set_metadata(&match, md->flow.metadata);
+ match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0,
+ md->flow.regs[MFF_LOG_INPORT - MFF_REG0]);
+ minimatch_init(&mmatch, &match);
+ fm.match = mmatch;
+
+ fm.table_id = OFTABLE_LOG_TO_PHY;
+ queue_msg(swconn, encode_flow_mod(&fm));
+ minimatch_destroy(&mmatch);
+
+ /* Delete inport controller flow (the one that got us here) */
+ match_init_catchall(&match);
+ match_set_in_port(&match, md->flow.in_port.ofp_port);
+ match_set_dl_type(&match, htons(ETH_TYPE_RARP));
+ minimatch_init(&mmatch, &match);
+ fm.match = mmatch;
+
+ fm.priority = 1010;
+ fm.table_id = OFTABLE_PHY_TO_LOG;
+ queue_msg(swconn, encode_flow_mod(&fm));
+ minimatch_destroy(&mmatch);
+
+ /* Tag the port as activated in-memory. */
+ struct port_pair *pp = xmalloc(sizeof *pp);
+ pp->port_key = port_key;
+ pp->dp_key = dp_key;
+ ovs_list_push_front(&activated_ports, &pp->list);
+
+ /* Notify main thread on pending additional-chassis-activated updates. */
+ notify_pinctrl_main();
+}
+
static struct hmap put_fdbs;
/* MAC learning (fdb) related functions. Runs within the main
@@ -33,6 +33,7 @@ struct sbrec_dns_table;
struct sbrec_controller_event_table;
struct sbrec_service_monitor_table;
struct sbrec_bfd_table;
+struct sbrec_port_binding;
void pinctrl_init(void);
void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
@@ -56,4 +57,8 @@ void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
void pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn);
void pinctrl_destroy(void);
void pinctrl_set_br_int_name(char *br_int_name);
+
+bool pinctrl_is_port_activated(int64_t dp_key, int64_t port_key);
+bool db_is_port_activated(const struct sbrec_port_binding *pb,
+ const struct sbrec_chassis *chassis);
#endif /* controller/pinctrl.h */
@@ -683,6 +683,9 @@ enum action_opcode {
/* put_fdb(inport, eth.src).
*/
ACTION_OPCODE_PUT_FDB,
+
+ /* activation_strategy_rarp() */
+ ACTION_OPCODE_ACTIVATION_STRATEGY_RARP,
};
/* Header. */
@@ -3453,6 +3453,16 @@ ovn_port_update_sbrec(struct northd_input *input_data,
smap_add(&options, "vlan-passthru", "true");
}
+ /* Retain activated chassis flags. */
+ if (op->sb->requested_additional_chassis) {
+ const char *activated_str = smap_get(
+ &op->sb->options, "additional-chassis-activated");
+ if (activated_str) {
+ smap_add(&options, "additional-chassis-activated",
+ activated_str);
+ }
+ }
+
sbrec_port_binding_set_options(op->sb, &options);
smap_destroy(&options);
if (ovn_is_known_nb_lsp_type(op->nbsp->type)) {
@@ -107,7 +107,10 @@ static const char *rbac_port_binding_auth[] =
static const char *rbac_port_binding_update[] =
{"chassis", "additional_chassis",
"encap", "additional_encap",
- "up", "virtual_parent"};
+ "up", "virtual_parent",
+ /* NOTE: we only need to update the additional-chassis-activated key,
+ * but RBAC_Role doesn't support mutate operation for subkeys. */
+ "options"};
static const char *rbac_mac_binding_auth[] =
{""};
@@ -1045,6 +1045,17 @@
</p>
</column>
+ <column name="options" key="activation-strategy">
+ If used with multiple chassis set in
+ <ref column="requested-chassis"/>, specifies an activation strategy
+ for all additional chassis. By default, no activation strategy is
+ used, meaning additional port locations are immediately available for
+ use. When set to "rarp", the port is blocked for ingress and egress
+ communication until a RARP packet is sent from a new location. The
+ "rarp" strategy is useful in live migration scenarios for virtual
+ machines.
+ </column>
+
<column name="options" key="iface-id-ver">
If set, this port will be bound by <code>ovn-controller</code>
only if this same key and value is configured in the
@@ -3354,6 +3354,21 @@ tcp.flags = RST;
</p>
</column>
+ <column name="options" key="activation-strategy">
+ If used with multiple chassis set in <ref column="requested-chassis"/>,
+ specifies an activation strategy for all additional chassis. By
+ default, no activation strategy is used, meaning additional port
+ locations are immediately available for use. When set to "rarp", the
+ port is blocked for ingress and egress communication until a RARP
+ packet is sent from a new location. The "rarp" strategy is useful
+ in live migration scenarios for virtual machines.
+ </column>
+
+ <column name="options" key="additional-chassis-activated">
+ When <ref column="activation-strategy"/> is set, this option indicates
+ that the port was activated using the strategy specified.
+ </column>
+
<column name="options" key="iface-id-ver">
If set, this port will be bound by <code>ovn-controller</code>
only if this same key and value is configured in the
@@ -14911,6 +14911,371 @@ OVN_CLEANUP([hv1],[hv2],[hv3])
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([options:activation-strategy for logical port])
+ovn_start
+
+net_add n1
+
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.11
+
+sim_add hv2
+as hv2
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.12
+
+sim_add hv3
+as hv3
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.13
+
+# Disable local ARP responder to pass ARP requests through tunnels
+check ovn-nbctl ls-add ls0 -- add Logical_Switch ls0 other_config vlan-passthru=true
+
+check ovn-nbctl lsp-add ls0 migrator
+check ovn-nbctl lsp-set-options migrator requested-chassis=hv1,hv2 \
+ activation-strategy=rarp
+
+check ovn-nbctl lsp-add ls0 first
+check ovn-nbctl lsp-set-options first requested-chassis=hv1
+check ovn-nbctl lsp-add ls0 second
+check ovn-nbctl lsp-set-options second requested-chassis=hv2
+check ovn-nbctl lsp-add ls0 outside
+check ovn-nbctl lsp-set-options outside requested-chassis=hv3
+
+check ovn-nbctl lsp-set-addresses migrator "00:00:00:00:00:10 10.0.0.10"
+check ovn-nbctl lsp-set-addresses first "00:00:00:00:00:01 10.0.0.1"
+check ovn-nbctl lsp-set-addresses second "00:00:00:00:00:02 10.0.0.2"
+check ovn-nbctl lsp-set-addresses outside "00:00:00:00:00:03 10.0.0.3"
+
+for hv in hv1 hv2; do
+ as $hv check ovs-vsctl -- add-port br-int migrator -- \
+ set Interface migrator external-ids:iface-id=migrator \
+ options:tx_pcap=$hv/migrator-tx.pcap \
+ options:rxq_pcap=$hv/migrator-rx.pcap
+done
+
+as hv1 check ovs-vsctl -- add-port br-int first -- \
+ set Interface first external-ids:iface-id=first
+as hv2 check ovs-vsctl -- add-port br-int second -- \
+ set Interface second external-ids:iface-id=second
+as hv3 check ovs-vsctl -- add-port br-int outside -- \
+ set Interface outside external-ids:iface-id=outside
+
+for hv in hv1 hv2 hv3; do
+ wait_row_count Chassis 1 name=$hv
+done
+hv1_uuid=$(fetch_column Chassis _uuid name=hv1)
+hv2_uuid=$(fetch_column Chassis _uuid name=hv2)
+hv3_uuid=$(fetch_column Chassis _uuid name=hv3)
+
+wait_column "$hv1_uuid" Port_Binding chassis logical_port=migrator
+wait_column "$hv1_uuid" Port_Binding requested_chassis logical_port=migrator
+wait_column "$hv2_uuid" Port_Binding additional_chassis logical_port=migrator
+wait_column "$hv2_uuid" Port_Binding requested_additional_chassis logical_port=migrator
+
+wait_column "$hv1_uuid" Port_Binding chassis logical_port=first
+wait_column "$hv2_uuid" Port_Binding chassis logical_port=second
+wait_column "$hv3_uuid" Port_Binding chassis logical_port=outside
+
+OVN_POPULATE_ARP
+
+send_arp() {
+ local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
+ local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa}
+ as ${hv} ovs-appctl netdev-dummy/receive $inport $request
+ echo "${request}"
+}
+
+send_rarp() {
+ local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
+ local request=${eth_dst}${eth_src}80350001080006040001${eth_src}${spa}${eth_dst}${tpa}
+ as ${hv} ovs-appctl netdev-dummy/receive $inport $request
+ echo "${request}"
+}
+
+reset_pcap_file() {
+ local hv=$1
+ local iface=$2
+ local pcap_file=$3
+ as $hv check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+ options:rxq_pcap=dummy-rx.pcap
+ check rm -f ${pcap_file}*.pcap
+ as $hv check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+ options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+reset_env() {
+ reset_pcap_file hv1 migrator hv1/migrator
+ reset_pcap_file hv2 migrator hv2/migrator
+ reset_pcap_file hv1 first hv1/first
+ reset_pcap_file hv2 second hv2/second
+ reset_pcap_file hv3 outside hv3/outside
+
+ for port in hv1/migrator hv2/migrator hv1/first hv2/second hv3/outside; do
+ : > $port.expected
+ done
+}
+
+check_packets() {
+ OVN_CHECK_PACKETS([hv1/migrator-tx.pcap], [hv1/migrator.expected])
+ OVN_CHECK_PACKETS([hv2/migrator-tx.pcap], [hv2/migrator.expected])
+ OVN_CHECK_PACKETS([hv3/outside-tx.pcap], [hv3/outside.expected])
+ OVN_CHECK_PACKETS([hv1/first-tx.pcap], [hv1/first.expected])
+ OVN_CHECK_PACKETS([hv2/second-tx.pcap], [hv2/second.expected])
+}
+
+migrator_spa=$(ip_to_hex 10 0 0 10)
+first_spa=$(ip_to_hex 10 0 0 1)
+second_spa=$(ip_to_hex 10 0 0 2)
+outside_spa=$(ip_to_hex 10 0 0 3)
+
+reset_env
+
+# Packet from hv3:Outside arrives to hv1:Migrator
+# hv3:Outside cannot reach hv2:Migrator because it is blocked by RARP strategy
+request=$(send_arp hv3 outside 000000000003 000000000010 $outside_spa $migrator_spa)
+echo $request >> hv1/migrator.expected
+
+# Packet from hv1:First arrives to hv1:Migrator
+# hv1:First cannot reach hv2:Migrator because it is blocked by RARP strategy
+request=$(send_arp hv1 first 000000000001 000000000010 $first_spa $migrator_spa)
+echo $request >> hv1/migrator.expected
+
+# Packet from hv2:Second arrives to hv1:Migrator
+# hv2:Second cannot reach hv2:Migrator because it is blocked by RARP strategy
+request=$(send_arp hv2 second 000000000002 000000000010 $second_spa $migrator_spa)
+echo $request >> hv1/migrator.expected
+
+check_packets
+reset_env
+
+# Packet from hv1:Migrator arrives to hv3:Outside
+request=$(send_arp hv1 migrator 000000000010 000000000003 $migrator_spa $outside_spa)
+echo $request >> hv3/outside.expected
+
+# Packet from hv1:Migrator arrives to hv1:First
+request=$(send_arp hv1 migrator 000000000010 000000000001 $migrator_spa $first_spa)
+echo $request >> hv1/first.expected
+
+# Packet from hv1:Migrator arrives to hv2:Second
+request=$(send_arp hv1 migrator 000000000010 000000000002 $migrator_spa $second_spa)
+echo $request >> hv2/second.expected
+
+check_packets
+reset_env
+
+# hv2:Migrator cannot reach to hv3:Outside because it is blocked by RARP strategy
+request=$(send_arp hv2 migrator 000000000010 000000000003 $migrator_spa $outside_spa)
+
+check_packets
+reset_env
+
+AT_CHECK([ovn-sbctl find port_binding logical_port=migrator | grep -q additional-chassis-activated], [1])
+
+# Now activate hv2:Migrator location
+request=$(send_rarp hv2 migrator 000000000010 ffffffffffff $migrator_spa $migrator_spa)
+
+# RARP was reinjected into the pipeline
+echo $request >> hv3/outside.expected
+echo $request >> hv1/first.expected
+echo $request >> hv2/second.expected
+
+check_packets
+reset_env
+
+pb_uuid=$(ovn-sbctl --bare --columns _uuid find Port_Binding logical_port=migrator)
+OVS_WAIT_UNTIL([test xhv2 = x$(ovn-sbctl get Port_Binding $pb_uuid options:additional-chassis-activated | tr -d '""')])
+
+# Now packet arrives to both locations
+request=$(send_arp hv3 outside 000000000003 000000000010 $outside_spa $migrator_spa)
+echo $request >> hv1/migrator.expected
+echo $request >> hv2/migrator.expected
+
+check_packets
+reset_env
+
+# Packet from hv1:Migrator still arrives to hv3:Outside
+request=$(send_arp hv1 migrator 000000000010 000000000003 $migrator_spa $outside_spa)
+echo $request >> hv3/outside.expected
+
+check_packets
+reset_env
+
+# hv2:Migrator can now reach to hv3:Outside because RARP strategy activated it
+request=$(send_arp hv2 migrator 000000000010 000000000003 $migrator_spa $outside_spa)
+echo $request >> hv3/outside.expected
+
+check_packets
+
+# complete port migration and check that -activated flag is reset
+check ovn-nbctl lsp-set-options migrator requested-chassis=hv2
+OVS_WAIT_UNTIL([test x = x$(ovn-sbctl get Port_Binding $pb_uuid options:additional-chassis-activated)])
+
+OVN_CLEANUP([hv1],[hv2],[hv3])
+
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([options:activation-strategy=rarp is not waiting for southbound db])
+# TODO: remove it when we find a way to make vswitchd forward packets to
+# controller() handler when ovsdb-server is down
+AT_SKIP_IF([true])
+ovn_start
+
+net_add n1
+
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.11
+
+sim_add hv2
+as hv2
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.12
+
+# Disable local ARP responder to pass ARP requests through tunnels
+check ovn-nbctl ls-add ls0 -- add Logical_Switch ls0 other_config vlan-passthru=true
+
+check ovn-nbctl lsp-add ls0 migrator
+check ovn-nbctl lsp-set-options migrator requested-chassis=hv1,hv2 \
+ activation-strategy=rarp
+
+check ovn-nbctl lsp-add ls0 first
+check ovn-nbctl lsp-set-options first requested-chassis=hv1
+
+check ovn-nbctl lsp-set-addresses migrator "00:00:00:00:00:10 10.0.0.10"
+check ovn-nbctl lsp-set-addresses first "00:00:00:00:00:01 10.0.0.1"
+
+for hv in hv1 hv2; do
+ as $hv check ovs-vsctl -- add-port br-int migrator -- \
+ set Interface migrator external-ids:iface-id=migrator \
+ options:tx_pcap=$hv/migrator-tx.pcap \
+ options:rxq_pcap=$hv/migrator-rx.pcap
+done
+
+as hv1 check ovs-vsctl -- add-port br-int first -- \
+ set Interface first external-ids:iface-id=first
+
+for hv in hv1 hv2; do
+ wait_row_count Chassis 1 name=$hv
+done
+hv1_uuid=$(fetch_column Chassis _uuid name=hv1)
+hv2_uuid=$(fetch_column Chassis _uuid name=hv2)
+
+wait_column "$hv1_uuid" Port_Binding chassis logical_port=migrator
+wait_column "$hv1_uuid" Port_Binding requested_chassis logical_port=migrator
+wait_column "$hv2_uuid" Port_Binding additional_chassis logical_port=migrator
+wait_column "$hv2_uuid" Port_Binding requested_additional_chassis logical_port=migrator
+
+wait_column "$hv1_uuid" Port_Binding chassis logical_port=first
+
+OVN_POPULATE_ARP
+
+send_arp() {
+ local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
+ local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa}
+ as ${hv} ovs-appctl netdev-dummy/receive $inport $request
+ echo "${request}"
+}
+
+send_rarp() {
+ local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
+ local request=${eth_dst}${eth_src}80350001080006040001${eth_src}${spa}${eth_dst}${tpa}
+ as ${hv} ovs-appctl netdev-dummy/receive $inport $request
+ echo "${request}"
+}
+
+reset_pcap_file() {
+ local hv=$1
+ local iface=$2
+ local pcap_file=$3
+ as $hv check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+ options:rxq_pcap=dummy-rx.pcap
+ check rm -f ${pcap_file}*.pcap
+ as $hv check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+ options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+reset_env() {
+ reset_pcap_file hv1 migrator hv1/migrator
+ reset_pcap_file hv2 migrator hv2/migrator
+ reset_pcap_file hv1 first hv1/first
+
+ for port in hv1/migrator hv2/migrator hv1/first; do
+ : > $port.expected
+ done
+}
+
+check_packets() {
+ OVN_CHECK_PACKETS([hv1/migrator-tx.pcap], [hv1/migrator.expected])
+ OVN_CHECK_PACKETS([hv2/migrator-tx.pcap], [hv2/migrator.expected])
+ OVN_CHECK_PACKETS([hv1/first-tx.pcap], [hv1/first.expected])
+}
+
+migrator_spa=$(ip_to_hex 10 0 0 10)
+first_spa=$(ip_to_hex 10 0 0 1)
+
+reset_env
+
+# Packet from hv1:First arrives to hv1:Migrator
+# hv1:First cannot reach hv2:Migrator because it is blocked by RARP strategy
+request=$(send_arp hv1 first 000000000001 000000000010 $first_spa $migrator_spa)
+echo $request >> hv1/migrator.expected
+
+check_packets
+reset_env
+
+# Packet from hv1:Migrator arrives to hv1:First
+request=$(send_arp hv1 migrator 000000000010 000000000001 $migrator_spa $first_spa)
+echo $request >> hv1/first.expected
+
+check_packets
+reset_env
+
+# hv2:Migrator cannot reach to hv1:First because it is blocked by RARP strategy
+request=$(send_arp hv2 migrator 000000000010 000000000001 $migrator_spa $first_spa)
+
+check_packets
+reset_env
+
+# Before proceeding, stop ovsdb-server to make sure we test in the environment
+# that can't remove flows triggered by updates to database
+as hv2
+SVCPID=$(cat $OVS_RUNDIR/ovsdb-server.pid)
+kill -9 $SVCPID
+
+# Now activate hv2:Migrator location
+request=$(send_rarp hv2 migrator 000000000010 ffffffffffff $migrator_spa $migrator_spa)
+
+# RARP was reinjected into the pipeline
+echo $request >> hv1/first.expected
+
+# Now packet from hv1:First arrives to both locations
+request=$(send_arp hv1 first 000000000001 000000000010 $first_spa $migrator_spa)
+echo $request >> hv1/migrator.expected
+echo $request >> hv2/migrator.expected
+
+# Packet from hv1:Migrator still arrives to hv1:First
+request=$(send_arp hv1 migrator 000000000010 000000000001 $migrator_spa $first_spa)
+echo $request >> hv1/first.expected
+
+# hv2:Migrator can now reach to hv1:First because RARP strategy activated it
+request=$(send_arp hv2 migrator 000000000010 000000000001 $migrator_spa $first_spa)
+echo $request >> hv1/first.expected
+
+check_packets
+
+OVN_CLEANUP([hv1],[hv2])
+
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([options:requested-chassis for logical port])
ovn_start
When options:activation-strategy is set to "rarp" for LSP, when used in combination with multiple chassis names listed in options:requested-chassis, additional chassis will install special flows that would block all ingress and egress traffic for the port until a special activation event happens. For "rarp" strategy, an observation of a RARP packet sent from the port on the additional chassis is such an event. When it occurs, a special flow passes control to a controller() action handler that removes the installed blocking flows and also marks the port as options:additional-chassis-activated in southbound db. Once vswitchd processes the flow mod request, the port is ready to communicate from a new location. This feature is useful in live migration scenarios where it's not advisable to unlock the destination port location prematurily to avoid duplicate packets originating from the port. Signed-off-by: Ihar Hrachyshka <ihrachys@redhat.com> --- NEWS | 2 + controller/physical.c | 92 +++++++++++ controller/pinctrl.c | 226 +++++++++++++++++++++++++- controller/pinctrl.h | 5 + include/ovn/actions.h | 3 + northd/northd.c | 10 ++ northd/ovn-northd.c | 5 +- ovn-nb.xml | 11 ++ ovn-sb.xml | 15 ++ tests/ovn.at | 365 ++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 731 insertions(+), 3 deletions(-)