Hey, I tested this patch with multi node devstack environment - more detailed information: [1]
I verified that those things are working: * DHCP and metadata for instances in all segments (VM1, VM2, VM3) * traffic from VM1 to VM3 (same segment, different chassis) * traffic from host1 to VM1 (from provider network segment-1 to VM using OVN and vice versa) * traffic from host2 to VM2 (from provider network segment-2 to VM using OVN and vice versa) * Security groups for traffic within OVN (VM1<>VM3) and outside (VM1<>host1, VM2<>host2). * ARP responder (tested between VM1 and VM3). Things to check: * possibility of binding localnet ports on bridge mappings change or recomputation (only) Tested-By: Maciej Jozefczyk <[email protected]> [1] https://github.com/mjozefcz/vagrants/tree/master/ovn-routed-provider-networks-devstack On Wed, Apr 15, 2020 at 3:45 AM Ihar Hrachyshka <[email protected]> wrote: > Assuming only a single localnet port is actually plugged mapped on > each chassis, this allows to maintain disjoint networks plugged to the > same switch. This is useful to simplify resource management for > OpenStack "routed provider networks" feature [1] where a single > "network" (which traditionally maps to logical switches in OVN) is > comprised of multiple L2 segments and assumes external L3 routing > implemented between the segments. > > TODO: consider E-W routing between localnet vlan tagged LSs > (ovn-chassis-mac-mappings). > > Note: the test requires [2] to actually validate packets. > > [1]: > https://docs.openstack.org/ocata/networking-guide/config-routed-networks.html > [2]: https://patchwork.ozlabs.org/project/openvswitch/list/?series=169291 > > Signed-off-by: Ihar Hrachyshka <[email protected]> > --- > controller/binding.c | 16 ++ > controller/patch.c | 24 ++- > northd/ovn-northd.c | 351 ++++++++++++++++++++++------------------- > ovn-architecture.7.xml | 25 ++- > ovn-nb.xml | 21 +-- > ovn-sb.xml | 21 ++- > tests/ovn.at | 112 +++++++++++++ > 7 files changed, 373 insertions(+), 197 deletions(-) > > diff --git a/controller/binding.c b/controller/binding.c > index 5ea12a8be..f4ae42806 100644 > --- a/controller/binding.c > +++ b/controller/binding.c > @@ -680,12 +680,28 @@ add_localnet_egress_interface_mappings( > } > } > > +static bool > +is_network_plugged(const struct sbrec_port_binding *binding_rec, > + struct shash *bridge_mappings) > +{ > + const char *network = smap_get(&binding_rec->options, "network_name"); > + if (!network) { > + return false; > + } > + return shash_find_data(bridge_mappings, network); > +} > + > static void > consider_localnet_port(const struct sbrec_port_binding *binding_rec, > struct shash *bridge_mappings, > struct sset *egress_ifaces, > struct hmap *local_datapaths) > { > + /* Ignore localnet ports for unplugged networks. */ > + if (!is_network_plugged(binding_rec, bridge_mappings)) { > + return; > + } > + > add_localnet_egress_interface_mappings(binding_rec, > bridge_mappings, egress_ifaces); > > diff --git a/controller/patch.c b/controller/patch.c > index 349faae17..52255cc3a 100644 > --- a/controller/patch.c > +++ b/controller/patch.c > @@ -198,9 +198,9 @@ add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn, > continue; > } > > - const char *patch_port_id; > + bool is_localnet = false; > if (!strcmp(binding->type, "localnet")) { > - patch_port_id = "ovn-localnet-port"; > + is_localnet = true; > } else if (!strcmp(binding->type, "l2gateway")) { > if (!binding->chassis > || strcmp(chassis->name, binding->chassis->name)) { > @@ -208,7 +208,6 @@ add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn, > * so we should not create any patch ports for it. */ > continue; > } > - patch_port_id = "ovn-l2gateway-port"; > } else { > /* not a localnet or L2 gateway port. */ > continue; > @@ -224,12 +223,25 @@ add_bridge_mappings(struct ovsdb_idl_txn > *ovs_idl_txn, > struct ovsrec_bridge *br_ln = shash_find_data(&bridge_mappings, > network); > if (!br_ln) { > static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); > - VLOG_ERR_RL(&rl, "bridge not found for %s port '%s' " > - "with network name '%s'", > - binding->type, binding->logical_port, network); > + if (!is_localnet) { > + VLOG_ERR_RL(&rl, "bridge not found for %s port '%s' " > + "with network name '%s'", > + binding->type, binding->logical_port, network); > + } else { > + VLOG_INFO_RL(&rl, "bridge not found for localnet port > '%s' " > + "with network name '%s'; skipping", > + binding->logical_port, network); > + } > continue; > } > > + const char *patch_port_id; > + if (is_localnet) { > + patch_port_id = "ovn-localnet-port"; > + } else { > + patch_port_id = "ovn-l2gateway-port"; > + } > + > char *name1 = patch_port_name(br_int->name, > binding->logical_port); > char *name2 = patch_port_name(binding->logical_port, > br_int->name); > create_patch_port(ovs_idl_txn, patch_port_id, > binding->logical_port, > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c > index 076278197..91730f7f3 100644 > --- a/northd/ovn-northd.c > +++ b/northd/ovn-northd.c > @@ -543,7 +543,9 @@ struct ovn_datapath { > /* The "derived" OVN port representing the instance of l3dgw_port on > * the "redirect-chassis". */ > struct ovn_port *l3redirect_port; > - struct ovn_port *localnet_port; > + > + struct ovn_port **localnet_ports; > + size_t n_localnet_ports; > > struct ovs_list lr_list; /* In list of logical router datapaths. */ > /* The logical router group to which this datapath belongs. > @@ -611,6 +613,7 @@ ovn_datapath_destroy(struct hmap *datapaths, struct > ovn_datapath *od) > ovn_destroy_tnlids(&od->port_tnlids); > bitmap_free(od->ipam_info.allocated_ipv4s); > free(od->router_ports); > + free(od->localnet_ports); > ovn_ls_port_group_destroy(&od->nb_pgs); > destroy_mcast_info_for_datapath(od); > > @@ -2053,7 +2056,11 @@ join_logical_ports(struct northd_context *ctx, > } > > if (!strcmp(nbsp->type, "localnet")) { > - od->localnet_port = op; > + od->localnet_ports = xrealloc( > + od->localnet_ports, > + sizeof *od->localnet_ports * (od->n_localnet_ports > + 1) > + ); > + od->localnet_ports[od->n_localnet_ports++] = op; > } > > op->lsp_addrs > @@ -2974,7 +2981,7 @@ ovn_port_update_sbrec(struct northd_context *ctx, > "reside-on-redirect-chassis", false) || > op->peer == op->peer->od->l3dgw_port)) { > add_router_port_garp = true; > - } else if (chassis && op->od->localnet_port) { > + } else if (chassis && op->od->localnet_ports) { > add_router_port_garp = true; > } > > @@ -4662,23 +4669,25 @@ build_pre_acls(struct ovn_datapath *od, struct > hmap *lflows) > ds_destroy(&match_in); > ds_destroy(&match_out); > } > - if (od->localnet_port) { > - struct ds match_in = DS_EMPTY_INITIALIZER; > - struct ds match_out = DS_EMPTY_INITIALIZER; > + if (od->localnet_ports) { > + for (size_t i = 0; i < od->n_localnet_ports; i++) { > + struct ds match_in = DS_EMPTY_INITIALIZER; > + struct ds match_out = DS_EMPTY_INITIALIZER; > > - ds_put_format(&match_in, "ip && inport == %s", > - od->localnet_port->json_key); > - ds_put_format(&match_out, "ip && outport == %s", > - od->localnet_port->json_key); > - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_ACL, 110, > - ds_cstr(&match_in), "next;", > - &od->localnet_port->nbsp->header_); > - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, > - ds_cstr(&match_out), "next;", > - &od->localnet_port->nbsp->header_); > + ds_put_format(&match_in, "ip && inport == %s", > + od->localnet_ports[i]->json_key); > + ds_put_format(&match_out, "ip && outport == %s", > + od->localnet_ports[i]->json_key); > + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_ACL, > 110, > + ds_cstr(&match_in), "next;", > + > &od->localnet_ports[i]->nbsp->header_); > + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_OUT_PRE_ACL, > 110, > + ds_cstr(&match_out), "next;", > + > &od->localnet_ports[i]->nbsp->header_); > > - ds_destroy(&match_in); > - ds_destroy(&match_out); > + ds_destroy(&match_in); > + ds_destroy(&match_out); > + } > } > > /* Ingress and Egress Pre-ACL Table (Priority 110). > @@ -5919,9 +5928,11 @@ build_lswitch_rport_arp_req_flow_for_ip(struct sset > *ips, > /* Send a the packet only to the router pipeline and skip flooding it > * in the broadcast domain (except for the localnet port). > */ > - if (od->localnet_port) { > - ds_put_format(&actions, "clone { outport = %s; output; }; ", > - od->localnet_port->json_key); > + if (od->localnet_ports) { > + for (size_t i = 0; i < od->n_localnet_ports; i++) { > + ds_put_format(&actions, "clone { outport = %s; output; }; ", > + od->localnet_ports[i]->json_key); > + } > } > ds_put_format(&actions, "outport = %s; output;", patch_op->json_key); > ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP, priority, > @@ -6323,9 +6334,9 @@ build_lswitch_flows(struct hmap *datapaths, struct > hmap *ports, > } > > bool is_external = lsp_is_external(op->nbsp); > - if (is_external && (!op->od->localnet_port || > + if (is_external && (!op->od->localnet_ports || > !op->nbsp->ha_chassis_group)) { > - /* If it's an external port and there is no localnet port > + /* If it's an external port and there are no localnet ports > * and if it doesn't belong to an HA chassis group ignore it. > */ > continue; > } > @@ -6339,84 +6350,93 @@ build_lswitch_flows(struct hmap *datapaths, struct > hmap *ports, > stage_hint = NULL; > } > > - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { > - struct ds options_action = DS_EMPTY_INITIALIZER; > - struct ds response_action = DS_EMPTY_INITIALIZER; > - struct ds ipv4_addr_match = DS_EMPTY_INITIALIZER; > - if (build_dhcpv4_action( > - op, op->lsp_addrs[i].ipv4_addrs[j].addr, > - &options_action, &response_action, > &ipv4_addr_match)) { > - ds_clear(&match); > - ds_put_format( > - &match, "inport == %s && eth.src == %s && " > - "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 > && " > - "udp.src == 68 && udp.dst == 67", > - is_external ? op->od->localnet_port->json_key : > - op->json_key, > - op->lsp_addrs[i].ea_s); > - > - if (is_external) { > - ds_put_format(&match, " && > is_chassis_resident(%s)", > - op->json_key); > - } > + size_t jmax = is_external? op->od->n_localnet_ports: 1; > + for (size_t j = 0; j < jmax; j++) { > + for (size_t k = 0; k < op->lsp_addrs[i].n_ipv4_addrs; > k++) { > + struct ds options_action = DS_EMPTY_INITIALIZER; > + struct ds response_action = DS_EMPTY_INITIALIZER; > + struct ds ipv4_addr_match = DS_EMPTY_INITIALIZER; > + if (build_dhcpv4_action( > + op, op->lsp_addrs[i].ipv4_addrs[k].addr, > + &options_action, &response_action, > + &ipv4_addr_match)) { > + ds_clear(&match); > + ds_put_format( > + &match, > + "inport == %s && eth.src == %s && " > + "ip4.src == 0.0.0.0 && " > + "ip4.dst == 255.255.255.255 && " > + "udp.src == 68 && udp.dst == 67", > + is_external ? > op->od->localnet_ports[j]->json_key : > + op->json_key, > + op->lsp_addrs[i].ea_s); > > - ovn_lflow_add_with_hint(lflows, op->od, > - S_SWITCH_IN_DHCP_OPTIONS, 100, > - ds_cstr(&match), > - ds_cstr(&options_action), > - stage_hint); > - ds_clear(&match); > - /* Allow ip4.src = OFFER_IP and > - * ip4.dst = {SERVER_IP, 255.255.255.255} for the > below > - * cases > - * - When the client wants to renew the IP by > sending > - * the DHCPREQUEST to the server ip. > - * - When the client wants to renew the IP by > - * broadcasting the DHCPREQUEST. > - */ > - ds_put_format( > - &match, "inport == %s && eth.src == %s && " > - "%s && udp.src == 68 && udp.dst == 67", > - is_external ? op->od->localnet_port->json_key : > - op->json_key, > - op->lsp_addrs[i].ea_s, ds_cstr(&ipv4_addr_match)); > + if (is_external) { > + ds_put_format( > + &match, " && is_chassis_resident(%s)", > + op->json_key); > + } > > - if (is_external) { > - ds_put_format(&match, " && > is_chassis_resident(%s)", > - op->json_key); > - } > + ovn_lflow_add_with_hint(lflows, op->od, > + S_SWITCH_IN_DHCP_OPTIONS, > 100, > + ds_cstr(&match), > + ds_cstr(&options_action), > + stage_hint); > + ds_clear(&match); > + /* Allow ip4.src = OFFER_IP and > + * ip4.dst = {SERVER_IP, 255.255.255.255} for the > below > + * cases > + * - When the client wants to renew the IP by > sending > + * the DHCPREQUEST to the server ip. > + * - When the client wants to renew the IP by > + * broadcasting the DHCPREQUEST. > + */ > + ds_put_format( > + &match, "inport == %s && eth.src == %s && " > + "%s && udp.src == 68 && udp.dst == 67", > + is_external ? > op->od->localnet_ports[j]->json_key : > + op->json_key, > + op->lsp_addrs[i].ea_s, > ds_cstr(&ipv4_addr_match)); > + > + if (is_external) { > + ds_put_format( > + &match, " && is_chassis_resident(%s)", > + op->json_key); > + } > > - ovn_lflow_add_with_hint(lflows, op->od, > - S_SWITCH_IN_DHCP_OPTIONS, 100, > - ds_cstr(&match), > - ds_cstr(&options_action), > - stage_hint); > - ds_clear(&match); > + ovn_lflow_add_with_hint(lflows, op->od, > + S_SWITCH_IN_DHCP_OPTIONS, > 100, > + ds_cstr(&match), > + ds_cstr(&options_action), > + stage_hint); > + ds_clear(&match); > > - /* If REGBIT_DHCP_OPTS_RESULT is set, it means the > - * put_dhcp_opts action is successful. */ > - ds_put_format( > - &match, "inport == %s && eth.src == %s && " > - "ip4 && udp.src == 68 && udp.dst == 67" > - " && "REGBIT_DHCP_OPTS_RESULT, > - is_external ? op->od->localnet_port->json_key : > - op->json_key, > - op->lsp_addrs[i].ea_s); > - > - if (is_external) { > - ds_put_format(&match, " && > is_chassis_resident(%s)", > - op->json_key); > - } > + /* If REGBIT_DHCP_OPTS_RESULT is set, it means the > + * put_dhcp_opts action is successful. */ > + ds_put_format( > + &match, "inport == %s && eth.src == %s && " > + "ip4 && udp.src == 68 && udp.dst == 67" > + " && "REGBIT_DHCP_OPTS_RESULT, > + is_external ? > op->od->localnet_ports[j]->json_key : > + op->json_key, > + op->lsp_addrs[i].ea_s); > > - ovn_lflow_add_with_hint(lflows, op->od, > - S_SWITCH_IN_DHCP_RESPONSE, > 100, > - ds_cstr(&match), > - ds_cstr(&response_action), > - stage_hint); > - ds_destroy(&options_action); > - ds_destroy(&response_action); > - ds_destroy(&ipv4_addr_match); > - break; > + if (is_external) { > + ds_put_format( > + &match, " && is_chassis_resident(%s)", > + op->json_key); > + } > + > + ovn_lflow_add_with_hint(lflows, op->od, > + > S_SWITCH_IN_DHCP_RESPONSE, 100, > + ds_cstr(&match), > + ds_cstr(&response_action), > + stage_hint); > + ds_destroy(&options_action); > + ds_destroy(&response_action); > + ds_destroy(&ipv4_addr_match); > + break; > + } > } > } > > @@ -6426,43 +6446,46 @@ build_lswitch_flows(struct hmap *datapaths, struct > hmap *ports, > stage_hint = NULL; > } > > - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { > - struct ds options_action = DS_EMPTY_INITIALIZER; > - struct ds response_action = DS_EMPTY_INITIALIZER; > - if (build_dhcpv6_action( > - op, &op->lsp_addrs[i].ipv6_addrs[j].addr, > - &options_action, &response_action)) { > - ds_clear(&match); > - ds_put_format( > - &match, "inport == %s && eth.src == %s" > - " && ip6.dst == ff02::1:2 && udp.src == 546 &&" > - " udp.dst == 547", > - is_external ? op->od->localnet_port->json_key : > - op->json_key, > - op->lsp_addrs[i].ea_s); > - > - if (is_external) { > - ds_put_format(&match, " && > is_chassis_resident(%s)", > - op->json_key); > - } > + for (size_t j = 0; j < jmax; j++) { > + for (size_t k = 0; k < op->lsp_addrs[i].n_ipv6_addrs; > k++) { > + struct ds options_action = DS_EMPTY_INITIALIZER; > + struct ds response_action = DS_EMPTY_INITIALIZER; > + if (build_dhcpv6_action( > + op, &op->lsp_addrs[i].ipv6_addrs[k].addr, > + &options_action, &response_action)) { > + ds_clear(&match); > + ds_put_format( > + &match, "inport == %s && eth.src == %s" > + " && ip6.dst == ff02::1:2 && udp.src == 546 > &&" > + " udp.dst == 547", > + is_external ? > op->od->localnet_ports[j]->json_key : > + op->json_key, > + op->lsp_addrs[i].ea_s); > > - ovn_lflow_add_with_hint(lflows, op->od, > - S_SWITCH_IN_DHCP_OPTIONS, 100, > - ds_cstr(&match), > - ds_cstr(&options_action), > - stage_hint); > + if (is_external) { > + ds_put_format( > + &match, " && is_chassis_resident(%s)", > + op->json_key); > + } > > - /* If REGBIT_DHCP_OPTS_RESULT is set to 1, it means > the > - * put_dhcpv6_opts action is successful */ > - ds_put_cstr(&match, " && "REGBIT_DHCP_OPTS_RESULT); > - ovn_lflow_add_with_hint(lflows, op->od, > - S_SWITCH_IN_DHCP_RESPONSE, > 100, > - ds_cstr(&match), > - ds_cstr(&response_action), > - stage_hint); > - ds_destroy(&options_action); > - ds_destroy(&response_action); > - break; > + ovn_lflow_add_with_hint(lflows, op->od, > + S_SWITCH_IN_DHCP_OPTIONS, > 100, > + ds_cstr(&match), > + ds_cstr(&options_action), > + stage_hint); > + > + /* If REGBIT_DHCP_OPTS_RESULT is set to 1, it > means the > + * put_dhcpv6_opts action is successful */ > + ds_put_cstr(&match, " && > "REGBIT_DHCP_OPTS_RESULT); > + ovn_lflow_add_with_hint(lflows, op->od, > + > S_SWITCH_IN_DHCP_RESPONSE, 100, > + ds_cstr(&match), > + ds_cstr(&response_action), > + stage_hint); > + ds_destroy(&options_action); > + ds_destroy(&response_action); > + break; > + } > } > } > } > @@ -6521,7 +6544,7 @@ build_lswitch_flows(struct hmap *datapaths, struct > hmap *ports, > > HMAP_FOR_EACH (op, key_node, ports) { > if (!op->nbsp || !lsp_is_external(op->nbsp) || > - !op->od->localnet_port) { > + !op->od->localnet_ports) { > continue; > } > > @@ -6536,36 +6559,42 @@ build_lswitch_flows(struct hmap *datapaths, struct > hmap *ports, > for (size_t k = 0; k < rp->n_lsp_addrs; k++) { > for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv4_addrs; > l++) { > - ds_clear(&match); > - ds_put_format( > - &match, "inport == %s && eth.src == %s" > - " && !is_chassis_resident(%s)" > - " && arp.tpa == %s && arp.op == 1", > - op->od->localnet_port->json_key, > - op->lsp_addrs[i].ea_s, op->json_key, > - rp->lsp_addrs[k].ipv4_addrs[l].addr_s); > - ovn_lflow_add_with_hint(lflows, op->od, > - S_SWITCH_IN_EXTERNAL_PORT, > - 100, ds_cstr(&match), > "drop;", > - &op->nbsp->header_); > + for (size_t m = 0; m < op->od->n_localnet_ports; > m++) { > + ds_clear(&match); > + ds_put_format( > + &match, "inport == %s && eth.src == %s" > + " && !is_chassis_resident(%s)" > + " && arp.tpa == %s && arp.op == 1", > + op->od->localnet_ports[m]->json_key, > + op->lsp_addrs[i].ea_s, op->json_key, > + rp->lsp_addrs[k].ipv4_addrs[l].addr_s); > + ovn_lflow_add_with_hint( > + lflows, op->od, > + S_SWITCH_IN_EXTERNAL_PORT, 100, > + ds_cstr(&match), "drop;", > + &op->nbsp->header_); > + } > } > for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv6_addrs; > l++) { > - ds_clear(&match); > - ds_put_format( > - &match, "inport == %s && eth.src == %s" > - " && !is_chassis_resident(%s)" > - " && nd_ns && ip6.dst == {%s, %s} && " > - "nd.target == %s", > - op->od->localnet_port->json_key, > - op->lsp_addrs[i].ea_s, op->json_key, > - rp->lsp_addrs[k].ipv6_addrs[l].addr_s, > - rp->lsp_addrs[k].ipv6_addrs[l].sn_addr_s, > - rp->lsp_addrs[k].ipv6_addrs[l].addr_s); > - ovn_lflow_add_with_hint(lflows, op->od, > - > S_SWITCH_IN_EXTERNAL_PORT, 100, > - ds_cstr(&match), "drop;", > - &op->nbsp->header_); > + for (size_t m = 0; m < op->od->n_localnet_ports; > m++) { > + ds_clear(&match); > + ds_put_format( > + &match, "inport == %s && eth.src == %s" > + " && !is_chassis_resident(%s)" > + " && nd_ns && ip6.dst == {%s, %s} && " > + "nd.target == %s", > + op->od->localnet_ports[m]->json_key, > + op->lsp_addrs[i].ea_s, op->json_key, > + rp->lsp_addrs[k].ipv6_addrs[l].addr_s, > + rp->lsp_addrs[k].ipv6_addrs[l].sn_addr_s, > + rp->lsp_addrs[k].ipv6_addrs[l].addr_s); > + ovn_lflow_add_with_hint( > + lflows, op->od, > + S_SWITCH_IN_EXTERNAL_PORT, 100, > + ds_cstr(&match), "drop;", > + &op->nbsp->header_); > + } > } > } > } > @@ -6787,7 +6816,7 @@ build_lswitch_flows(struct hmap *datapaths, struct > hmap *ports, > ETH_ADDR_ARGS(mac)); > if (op->peer->od->l3dgw_port > && op->peer->od->l3redirect_port > - && op->od->localnet_port) { > + && op->od->localnet_ports) { > bool add_chassis_resident_check = false; > if (op->peer == op->peer->od->l3dgw_port) { > /* The peer of this port represents a distributed > @@ -8084,7 +8113,7 @@ build_lrouter_flows(struct hmap *datapaths, struct > hmap *ports, > op->lrp_networks.ipv4_addrs[i].addr_s); > > if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer > - && op->peer->od->localnet_port) { > + && op->peer->od->localnet_ports) { > bool add_chassis_resident_check = false; > if (op == op->od->l3dgw_port) { > /* Traffic with eth.src = > l3dgw_port->lrp_networks.ea_s > diff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml > index 533ae716d..88edb6f32 100644 > --- a/ovn-architecture.7.xml > +++ b/ovn-architecture.7.xml > @@ -441,9 +441,8 @@ > > <p> > A <code>localnet</code> logical switch port bridges a logical switch > to a > - physical VLAN. Any given logical switch should have no more than one > - <code>localnet</code> port. Such a logical switch is used in two > - scenarios: > + physical VLAN. A logical switch may have one or more > <code>localnet</code> > + ports. Such a logical switch is used in two scenarios: > </p> > > <ul> > @@ -1895,13 +1894,13 @@ > <ol> > <li> > The packet first enters the ingress pipeline, and then egress > pipeline of > - the source localnet logical switch datapath and is sent out via the > + the source localnet logical switch datapath and is sent out via a > localnet port of the source localnet logical switch (instead of > sending > it to router pipeline). > </li> > > <li> > - The gateway chassis receives the packet via the localnet port of the > + The gateway chassis receives the packet via a localnet port of the > source localnet logical switch and sends it to the integration > bridge. > The packet then enters the ingress pipeline, and then egress > pipeline of > the source localnet logical switch datapath and enters the ingress > @@ -1916,11 +1915,11 @@ > From the router datapath, packet enters the ingress pipeline and > then > egress pipeline of the destination localnet logical switch datapath. > It then goes out of the integration bridge to the provider bridge ( > - belonging to the destination logical switch) via the localnet port. > + belonging to the destination logical switch) via a localnet port. > </li> > > <li> > - The destination chassis receives the packet via the localnet port > and > + The destination chassis receives the packet via a localnet port and > sends it to the integration bridge. The packet enters the > ingress pipeline and then egress pipeline of the destination > localnet > logical switch and finally delivered to the destination VM port. > @@ -1935,13 +1934,13 @@ > <ol> > <li> > The packet first enters the ingress pipeline, and then egress > pipeline of > - the source localnet logical switch datapath and is sent out via the > + the source localnet logical switch datapath and is sent out via a > localnet port of the source localnet logical switch (instead of > sending > it to router pipeline). > </li> > > <li> > - The gateway chassis receives the packet via the localnet port of the > + The gateway chassis receives the packet via a localnet port of the > source localnet logical switch and sends it to the integration > bridge. > The packet then enters the ingress pipeline, and then egress > pipeline of > the source localnet logical switch datapath and enters the ingress > @@ -1957,7 +1956,7 @@ > egress pipeline of the localnet logical switch datapath which > provides > external connectivity. It then goes out of the integration bridge > to the > provider bridge (belonging to the logical switch which provides > external > - connectivity) via the localnet port. > + connectivity) via a localnet port. > </li> > </ol> > > @@ -1967,7 +1966,7 @@ > > <ol> > <li> > - The gateway chassis receives the packet from the localnet port of > + The gateway chassis receives the packet from a localnet port of > the logical switch which provides external connectivity. The packet > then > enters the ingress pipeline and then egress pipeline of the localnet > logical switch (which provides external connectivity). The packet > then > @@ -1978,12 +1977,12 @@ > The ingress pipeline of the logical router datapath applies the > unNATting > rules. The packet then enters the ingress pipeline and then egress > pipeline of the source localnet logical switch. Since the source VM > - doesn't reside in the gateway chassis, the packet is sent out via > the > + doesn't reside in the gateway chassis, the packet is sent out via a > localnet port of the source logical switch. > </li> > > <li> > - The source chassis receives the packet via the localnet port and > + The source chassis receives the packet via a localnet port and > sends it to the integration bridge. The packet enters the > ingress pipeline and then egress pipeline of the source localnet > logical switch and finally gets delivered to the source VM port. > diff --git a/ovn-nb.xml b/ovn-nb.xml > index 541ec20c1..6f84c427c 100644 > --- a/ovn-nb.xml > +++ b/ovn-nb.xml > @@ -244,14 +244,14 @@ > <p> > There are two kinds of logical switches, that is, ones that fully > virtualize the network (overlay logical switches) and ones that > provide > - simple connectivity to a physical network (bridged logical > switches). > + simple connectivity to physical networks (bridged logical switches). > They work in the same way when providing connectivity between > logical > - ports on same chasis, but differently when connecting remote logical > + ports on same chassis, but differently when connecting remote > logical > ports. Overlay logical switches connect remote logical ports by > tunnels, > while bridged logical switches provide connectivity to remote ports > by > - bridging the packets to directly connected physical L2 segment with > the > + bridging the packets to directly connected physical L2 segments > with the > help of <code>localnet</code> ports. Each bridged logical switch > has > - one and only one <code>localnet</code> port, which has only one > special > + one or more <code>localnet</code> ports, which have only one special > address <code>unknown</code>. > </p> > > @@ -527,10 +527,13 @@ > > <dt><code>localnet</code></dt> > <dd> > - A connection to a locally accessible network from each > - <code>ovn-controller</code> instance. A logical switch can > only > - have a single <code>localnet</code> port attached. This is > used > - to model direct connectivity to an existing network. > + A connection to a locally accessible network from > + <code>ovn-controller</code> instances that have corresponding > + bridge mapping. A logical switch can have multiple > + <code>localnet</code> ports attached, as long as each > + <code>ovn-controller</code> is plugged to a single local > network > + only. In this case, each hypervisor implements part of switch > + external network connectivity. > </dd> > > <dt><code>localport</code></dt> > @@ -721,7 +724,7 @@ > Required. The name of the network to which the > <code>localnet</code> > port is connected. Each hypervisor, via > <code>ovn-controller</code>, > uses its local configuration to determine exactly how to > connect to > - this locally accessible network. > + this locally accessible network, if at all. > </column> > </group> > > diff --git a/ovn-sb.xml b/ovn-sb.xml > index 3ae9d4f92..2d2d08027 100644 > --- a/ovn-sb.xml > +++ b/ovn-sb.xml > @@ -2606,10 +2606,13 @@ tcp.flags = RST; > > <dt><code>localnet</code></dt> > <dd> > - A connection to a locally accessible network from each > - <code>ovn-controller</code> instance. A logical switch can > only > - have a single <code>localnet</code> port attached. This is > used > - to model direct connectivity to an existing network. > + A connection to a locally accessible network from some or all > + <code>ovn-controller</code> instances. This is used > + to model direct connectivity to existing networks. A logical > + switch can have multiple <code>localnet</code> ports > attached, as > + long as each <code>ovn-controller</code> is plugged to a > single > + local network only. In this case, each hypervisor implements > part > + of switch external network connectivity. > </dd> > > <dt><code>localport</code></dt> > @@ -2754,10 +2757,12 @@ tcp.flags = RST; > <p> > When a logical switch has a <code>localnet</code> port attached, > every chassis that may have a local vif attached to that logical > - switch must have a bridge mapping configured to reach that > - <code>localnet</code>. Traffic that arrives on a > - <code>localnet</code> port is never forwarded over a tunnel to > - another chassis. > + switch that needs this external connectivity must have a bridge > + mapping configured to reach that <code>localnet</code>. If the > + mapping is missing, the vif won't be plugged to this network. > It may > + still reach the other network if routing is implemented by > fabric. > + Traffic that arrives on a <code>localnet</code> port is never > + forwarded over a tunnel to another chassis. > </p> > </column> > > diff --git a/tests/ovn.at b/tests/ovn.at > index 013583826..42089763a 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -2438,6 +2438,118 @@ OVN_CLEANUP([hv1],[hv2]) > > AT_CLEANUP > > +AT_SETUP([ovn -- 2 HVs, multiple localnet ports]) > +ovn_start > + > +# In this test case we create a single switch connected to two physical > +# networks via multiple localnet ports. Then we create two hypervisors, > with 2 > +# ports on each. Each pair of adjecent ports belong to the same network > segment > +# and assume interconnectivity. There is no direct interconnectivity > between > +# ports located on chassis attached to different segments. (It is assumed > that > +# in real life external fabric L3 routing will deliver packets between the > +# segments as needed.) > +ovn-nbctl ls-add ls1 > +for tag in 10 20; do > + ln_port_name=ln-$tag > + ovn-nbctl lsp-add ls1 $ln_port_name "" $tag > + ovn-nbctl lsp-set-addresses $ln_port_name unknown > + ovn-nbctl lsp-set-type $ln_port_name localnet > + ovn-nbctl lsp-set-options $ln_port_name network_name=phys-$tag > +done > + > +for tag in 10 20; do > + net_add n-$tag > +done > + > +for tag in 10 20; do > + for i in 1 2; do > + sim_add hv-$tag-$i > + as hv-$tag-$i > + ovs-vsctl add-br br-phys > + ovs-vsctl set open . > external-ids:ovn-bridge-mappings=phys-$tag:br-phys > + ovn_attach n-$tag br-phys 192.168.$i.$tag > + > + ovs-vsctl add-port br-int vif-$tag-$i -- \ > + set Interface vif-$tag-$i external-ids:iface-id=lp-$tag-$i \ > + > options:tx_pcap=hv-$tag-$i/vif-$tag-$i-tx.pcap \ > + > options:rxq_pcap=hv-$tag-$i/vif-$tag-$i-rx.pcap \ > + ofport-request=$tag$i > + > + lsp_name=lp-$tag-$i > + ovn-nbctl lsp-add ls1 $lsp_name > + ovn-nbctl lsp-set-addresses $lsp_name f0:00:00:00:0$i:$tag > + ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:0$i:$tag > + > + OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup]) > + done > +done > +ovn-nbctl --wait=sb sync > +ovn-sbctl dump-flows > + > +for tag in 10 20; do > + for i in 1 2; do > + : > $tag-$i.expected > + done > +done > + > +vif_to_hv() { > + echo hv-$1 > +} > + > +test_packet() { > + local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6 > + > + # First try tracing the packet. > + uflow="inport==\"lp-$inport\" && eth.dst==$dst && eth.src==$src && > eth.type==0x$eth" > + echo "output(\"$lout\");" > expout > + AT_CAPTURE_FILE([trace]) > + AT_CHECK([ovn-trace --all ls1 "$uflow" | tee trace | sed '1,/Minimal > trace/d'], [0], [expout]) > + > + # Then actually send a packet, for an end-to-end test. > + local packet=$(echo $dst$src | sed 's/://g')${eth} > + hv=`vif_to_hv $inport` > + vif=vif-$inport > + as $hv ovs-appctl netdev-dummy/receive $vif $packet > + if test $eth = 1002 -o $eth = 2002; then > + echo $packet >> ${eout#lp-}.expected > + fi > +} > + > +# should fail > +test_packet 10-1 f0:00:00:00:01:20 f0:00:00:00:01:10 1001 lp-20-1 lp-20-1 > +test_packet 20-1 f0:00:00:00:01:10 f0:00:00:00:01:20 2001 lp-10-1 lp-10-1 > + > +# should pass > +test_packet 10-1 f0:00:00:00:02:10 f0:00:00:00:01:10 1002 lp-10-2 lp-10-2 > +test_packet 20-1 f0:00:00:00:02:20 f0:00:00:00:01:20 2002 lp-20-2 lp-20-2 > + > +# Dump a bunch of info helpful for debugging if there's a failure. > + > +echo "------ OVN dump ------" > +ovn-nbctl show > +ovn-sbctl show > + > +for tag in 10 20; do > + for i in 1 2; do > + hv=hv-$tag-$i > + echo "------ $hv dump ------" > + as $hv ovs-vsctl show > + as $hv ovs-ofctl -O OpenFlow13 dump-flows br-int > + done > +done > + > +# Now check the packets actually received against the ones expected. > +for tag in 10 20; do > + for i in 1 2; do > + echo "hv = $tag-$i" > + > OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv-$tag-$i/vif-$tag-$i-tx.pcap], > [$tag-$i.expected]) > + done > +done > + > +OVN_CLEANUP([hv-10-1],[hv-10-2],[hv-20-1],[hv-20-2]) > + > +AT_CLEANUP > + > AT_SETUP([ovn -- vtep: 3 HVs, 1 VIFs/HV, 1 GW, 1 LS]) > AT_KEYWORDS([vtep]) > ovn_start > -- > 2.25.2 > > > _______________________________________________ > dev mailing list > [email protected] > https://mail.openvswitch.org/mailman/listinfo/ovs-dev > > -- Best regards, Maciej Józefczyk _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
