+
+ <p>
+ For a switch port attached to a logical router, this column is empty.
+ </p>
+ </column>
+
<group title="DHCP">
<column name="dhcpv4_options">
This column defines the DHCPv4 Options to be included by the
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index d4f61cb77..7a6529ee5 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -2419,6 +2419,11 @@ AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
router
])
+AT_CHECK([ovn-nbctl lsp-set-type lp0 switch])
+AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
+switch
+])
+
AT_CHECK([ovn-nbctl lsp-set-type lp0 localnet])
AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
localnet
@@ -2460,8 +2465,22 @@ ovn-nbctl: Logical switch port type 'eggs' is
unrecognized. Not setting type.
dnl Empty string should work too
AT_CHECK([ovn-nbctl lsp-set-type lp0 ""])
-AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
+AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [
+])
+dnl Only the 'switch' type should accept peer.
+AT_CHECK([ovn-nbctl lsp-set-type lp0 router peer=qwe], [1], [], [dnl
+ovn-nbctl: Peer can only be set for a logical switch port with type 'switch'.
+])
+AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [
+])
+dnl Check that peer can be set.
+AT_CHECK([ovn-nbctl lsp-set-type lp0 switch peer=my-peer])
+AT_CHECK([ovn-nbctl lsp-get-type lp0], [0], [dnl
+switch
+])
+AT_CHECK([ovn-nbctl get logical-switch-port lp0 peer], [0], [dnl
+my-peer
])])
dnl ---------------------------------------------------------------------
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 2b1791de0..5e108acd1 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -170,6 +170,23 @@ wait_row_count nb:Logical_Switch_Port 1 name=S1-vm1
'up=true'
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([check up state of switch-switch LSP])
+ovn_start
+
+check ovn-nbctl ls-add S1
+check ovn-nbctl lsp-add S1 S1-p1
+check ovn-nbctl --wait=sb lsp-set-type S1-p1 switch peer=S2-p2
+
+check ovn-nbctl ls-add S2
+check ovn-nbctl lsp-add S2 S2-p2
+check ovn-nbctl --wait=sb lsp-set-type S2-p2 switch peer=S1-p1
+AT_CHECK([test x`ovn-nbctl lsp-get-up S1-p1` = xup])
+AT_CHECK([test x`ovn-nbctl lsp-get-up S2-p2` = xup])
+
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD_NO_HV([
AT_SETUP([check up state of router LSP linked to a distributed LR])
ovn_start
@@ -7298,7 +7315,47 @@ AT_CHECK([grep -e "ls_in_l2_lkup.*S1-vm1" S1flows |
ovn_strip_lflows], [0], [dnl
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([implicit unknown addresses on switch-switch LSPs])
+ovn_start NORTHD_TYPE
+check ovn-nbctl ls-add S1
+check ovn-nbctl lsp-add S1 S1-vm
+check ovn-nbctl lsp-set-addresses S1-vm "50:54:00:00:00:01 192.168.0.1"
+
+check ovn-nbctl ls-add S2
+check ovn-nbctl lsp-add S2 S2-vm
+check ovn-nbctl lsp-set-addresses S2-vm "50:54:00:00:00:02 192.168.0.2"
+check ovn-nbctl lsp-add S1 S1-S2
+check ovn-nbctl lsp-add S2 S2-S1
+check ovn-nbctl lsp-set-type S1-S2 switch peer=S2-S1
+check ovn-nbctl lsp-set-type S2-S1 switch peer=S1-S2
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows S1 > S1flows
+AT_CAPTURE_FILE([S1flows])
+
+dnl Check that S2-vm address is not known on S1 and the forwarding to
+dnl _MC_unknown group is configured.
+AT_CHECK([grep -E "ls_in_l2_lkup.*S1-|unknown" S1flows | ovn_strip_lflows],
[0], [dnl
+ table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst == 50:54:00:00:00:01),
action=(outport = "S1-vm"; output;)
+ table=??(ls_in_l2_unknown ), priority=0 , match=(1), action=(output;)
+ table=??(ls_in_l2_unknown ), priority=50 , match=(outport == "none"),
action=(outport = "_MC_unknown"; output;)
+])
+
+ovn-sbctl dump-flows S2 > S2flows
+AT_CAPTURE_FILE([S2flows])
+
+dnl Check that S1-vm address is not known on S2 and the forwarding to
+dnl _MC_unknown group is configured.
+AT_CHECK([grep -E "ls_in_l2_lkup.*S2-|unknown" S2flows | ovn_strip_lflows],
[0], [dnl
+ table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst == 50:54:00:00:00:02),
action=(outport = "S2-vm"; output;)
+ table=??(ls_in_l2_unknown ), priority=0 , match=(1), action=(output;)
+ table=??(ls_in_l2_unknown ), priority=50 , match=(outport == "none"),
action=(outport = "_MC_unknown"; output;)
+])
+
+AT_CLEANUP
+])
# Duplicated datapaths shouldn't be created, but in case it is created because
# of bug or dirty data, it should be properly deleted instead of causing
diff --git a/tests/ovn.at b/tests/ovn.at
index e9144b0cd..5e26a0e45 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -7906,6 +7906,239 @@ OVN_CLEANUP([hv1],[hv2])
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([spine-leaf: 1 HV, 2 LSs, connected via spine switch])
+AT_KEYWORDS([spine leaf])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
+ovn_start
+
+# Logical network:
+# Single network 191.168.1.0/24. Two switches with VIF ports, connected
+# to a spine logical switch via 'switch' ports.
+
+check ovn-nbctl ls-add spine
+
+check ovn-nbctl ls-add ls1
+check ovn-nbctl ls-add ls2
+
+# Connect ls1 to spine.
+check ovn-nbctl lsp-add spine spine-to-ls1
+check ovn-nbctl lsp-add ls1 ls1-to-spine
+check ovn-nbctl lsp-set-type spine-to-ls1 switch peer=ls1-to-spine
+check ovn-nbctl lsp-set-type ls1-to-spine switch peer=spine-to-ls1
+
+# Connect ls2 to spine.
+check ovn-nbctl lsp-add spine spine-to-ls2
+check ovn-nbctl lsp-add ls2 ls2-to-spine
+check ovn-nbctl lsp-set-type spine-to-ls2 switch peer=ls2-to-spine
+check ovn-nbctl lsp-set-type ls2-to-spine switch peer=spine-to-ls2
+
+# Create logical port ls1-lp1 in ls1
+check ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "f0:00:00:01:02:01 172.16.1.1"
+# Create logical port ls1-lp2 in ls1
+check ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "f0:00:00:01:02:02 172.16.1.2"
+
+# Create logical port ls2-lp1 in ls2
+check ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "f0:00:00:01:02:03 172.16.1.3"
+# Create logical port ls2-lp2 in ls2
+check ovn-nbctl lsp-add ls2 ls2-lp2 \
+-- lsp-set-addresses ls2-lp2 "f0:00:00:01:02:04 172.16.1.4"
+
+# Create one hypervisor and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+ options:rxq_pcap=hv1/vif1-rx.pcap \
+ ofport-request=1
+ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+ options:rxq_pcap=hv1/vif2-rx.pcap \
+ ofport-request=2
+
+ovs-vsctl -- add-port br-int vif3 -- \
+ set interface vif3 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+ options:rxq_pcap=hv1/vif3-rx.pcap \
+ ofport-request=3
+ovs-vsctl -- add-port br-int vif4 -- \
+ set interface vif4 external-ids:iface-id=ls2-lp2 \
+ options:tx_pcap=hv1/vif4-tx.pcap \
+ options:rxq_pcap=hv1/vif4-rx.pcap \
+ ofport-request=4
+
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+ovn-sbctl dump-flows > sbflows
+AT_CAPTURE_FILE([sbflows])
+
+# Send ip packets between the two ports.
+src_mac="f0:00:00:01:02:01"
+dst_mac="f0:00:00:01:02:03"
+src_ip=172.16.1.1
+dst_ip=172.16.1.3
+packet=$(fmt_pkt "Ether(dst='${dst_mac}', src='${src_mac}')/ \
+ IP(src='${src_ip}', dst='${dst_ip}')/ \
+ UDP(sport=1538, dport=4369)")
+check as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
+
+# Check that datapath is not doing any extra work.
+AT_CHECK([as hv1 ovs-appctl ofproto/trace --names \
+ br-int in_port=vif1 $packet | tail -2], [0], [dnl
+Megaflow:
recirc_id=0,eth,ip,in_port=vif1,dl_src=f0:00:00:01:02:01,dl_dst=f0:00:00:01:02:03,nw_frag=no
+Datapath actions: vif3
+])
+
+# No modifications expected.
+AT_CHECK([echo $packet > expected])
+
+AT_CHECK([touch empty])
+
+# Check that it is delivered where needed and not delivered where not.
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [empty])
+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
+OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [empty])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([spine-leaf: 2 HVs, 2 LSs, connected via distributed spine switch])
+AT_KEYWORDS([spine leaf])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
+ovn_start
+
+# Logical network:
+# Single network 172.16.1.0/24. Two switches with VIF ports on two HVs,
+# connected to a spine distributed logical switch via 'switch' ports.
+
+check ovn-nbctl ls-add spine
+
+check ovn-nbctl ls-add ls1
+check ovn-nbctl ls-add ls2
+
+# Connect ls1 to spine.
+check ovn-nbctl lsp-add spine spine-to-ls1
+check ovn-nbctl lsp-add ls1 ls1-to-spine
+check ovn-nbctl lsp-set-type spine-to-ls1 switch peer=ls1-to-spine
+check ovn-nbctl lsp-set-type ls1-to-spine switch peer=spine-to-ls1
+
+# Connect ls2 to spine.
+check ovn-nbctl lsp-add spine spine-to-ls2
+check ovn-nbctl lsp-add ls2 ls2-to-spine
+check ovn-nbctl lsp-set-type spine-to-ls2 switch peer=ls2-to-spine
+check ovn-nbctl lsp-set-type ls2-to-spine switch peer=spine-to-ls2
+
+# Create logical port ls1-lp1 in ls1
+check ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "f0:00:00:01:02:01 172.16.1.1"
+# Create logical port ls1-lp2 in ls1
+check ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "f0:00:00:01:02:02 172.16.1.2"
+
+# Create logical port ls2-lp1 in ls2
+check ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "f0:00:00:01:02:03 172.16.1.3"
+# Create logical port ls2-lp2 in ls2
+check ovn-nbctl lsp-add ls2 ls2-lp2 \
+-- lsp-set-addresses ls2-lp2 "f0:00:00:01:02:04 172.16.1.4"
+
+# Create hypervisors and OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+ options:rxq_pcap=hv1/vif1-rx.pcap \
+ ofport-request=1
+ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+ options:rxq_pcap=hv1/vif2-rx.pcap \
+ ofport-request=2
+
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+ovs-vsctl -- add-port br-int vif3 -- \
+ set interface vif3 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif3-tx.pcap \
+ options:rxq_pcap=hv2/vif3-rx.pcap \
+ ofport-request=3
+ovs-vsctl -- add-port br-int vif4 -- \
+ set interface vif4 external-ids:iface-id=ls2-lp2 \
+ options:tx_pcap=hv2/vif4-tx.pcap \
+ options:rxq_pcap=hv2/vif4-rx.pcap \
+ ofport-request=4
+
+OVN_POPULATE_ARP
+
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+ovn-sbctl dump-flows > sbflows
+AT_CAPTURE_FILE([sbflows])
+
+# Send ip packets between the two ports.
+src_mac="f0:00:00:01:02:01"
+dst_mac="f0:00:00:01:02:03"
+src_ip=172.16.1.1
+dst_ip=172.16.1.3
+packet=$(fmt_pkt "Ether(dst='${dst_mac}', src='${src_mac}')/ \
+ IP(src='${src_ip}', dst='${dst_ip}')/ \
+ UDP(sport=1538, dport=4369)")
+check as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
+
+# Check that datapath is not doing any extra work and sends the packet out
+# through the tunnel.
+AT_CHECK([as hv1 ovs-appctl ofproto/trace --names \
+ br-int in_port=vif1 $packet > ofproto-trace-1])
+AT_CAPTURE_FILE([ofproto-trace-1])
+AT_CHECK([grep 'Megaflow:' ofproto-trace-1], [0], [dnl
+Megaflow:
recirc_id=0,eth,ip,in_port=vif1,dl_src=f0:00:00:01:02:01,dl_dst=f0:00:00:01:02:03,nw_ecn=0,nw_frag=no
+])
+AT_CHECK([grep -q \
+ 'Datapath actions: tnl_push(tnl_port(genev_sys_6081).*out_port(br-phys))' \
+ ofproto-trace-1])
+
+# It's a little problematic to trace the other side, but we can check
+# datapath actions.
+AT_CHECK([as hv2 ovs-appctl dpctl/dump-flows --names \
+ | grep actions | sed 's/.*\(actions:.*\)/\1/' | sort], [0], [dnl
+actions:tnl_pop(genev_sys_6081)
+actions:vif3
+])
+
+# No modifications expected.
+AT_CHECK([echo $packet > expected])
+
+AT_CHECK([touch empty])
+
+# Check that it is delivered where needed and not delivered where not.
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [empty])
+OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected])
+OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [empty])
+
+OVN_CLEANUP([hv1], [hv2])
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([icmp_reply: 1 HVs, 2 LSs, 1 lport/LS, 1 LR])
AT_KEYWORDS([router-icmp-reply])
diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
index 63cefd119..19f121142 100644
--- a/utilities/ovn-nbctl.8.xml
+++ b/utilities/ovn-nbctl.8.xml
@@ -738,7 +738,8 @@
or <code>disabled</code>.
</dd>
- <dt><code>lsp-set-type</code> <var>port</var> <var>type</var></dt>
+ <dt><code>lsp-set-type</code> <var>port</var> <var>type</var>
+ [<code>peer=</code><var>peer</var>]</dt>
<dd>
<p>
Set the type for the logical port. The type must be one of the
following:
@@ -755,6 +756,13 @@
A connection to a logical router.
</dd>
+ <dt><code>switch</code></dt>
+ <dd>
+ A connection to another logical switch. The optional argument
+ <code>peer</code> identifies a logical switch port that connects
+ to this one.
+ </dd>
+
<dt><code>localnet</code></dt>
<dd>
A connection to a locally accessible network from each
ovn-controller
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index f5277af7c..01985cb82 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -881,6 +881,9 @@ print_ls(const struct nbrec_logical_switch *ls, struct ds
*s)
if (router_port) {
ds_put_format(s, " router-port: %s\n", router_port);
}
+ if (lsp->peer) {
+ ds_put_format(s, " peer: %s\n", lsp->peer);
+ }
}
}
@@ -913,6 +916,7 @@ nbctl_pre_show(struct ctl_context *ctx)
ovsdb_idl_add_column(ctx->idl,
&nbrec_logical_switch_port_col_external_ids);
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
ovsdb_idl_add_column(ctx->idl,
&nbrec_logical_switch_port_col_parent_name);
+ ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_peer);
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_tag);
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_addresses);
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_options);
@@ -1731,6 +1735,7 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
{
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
+ ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_peer);
}
static void
@@ -1857,15 +1862,29 @@ nbctl_lsp_set_type(struct ctl_context *ctx)
{
const char *id = ctx->argv[1];
const char *type = ctx->argv[2];
+ const char *peer = (ctx->argc < 4) ? NULL : ctx->argv[3];
const struct nbrec_logical_switch_port *lsp = NULL;
+ if (peer && strncmp(peer, "peer=", 5)) {
+ ctl_error(ctx, "Unrecognized argument: %s\n", peer);
+ return;
+ }
+
char *error = lsp_by_name_or_uuid(ctx, id, true, &lsp);
if (error) {
ctx->error = error;
return;
}
if (ovn_is_known_nb_lsp_type(type)) {
+ if (peer && strcmp(type, "switch")) {
+ ctl_error(ctx, "Peer can only be set for a logical switch port "
+ "with type 'switch'.");
+ return;
+ }
nbrec_logical_switch_port_set_type(lsp, type);
+ if (peer) {
+ nbrec_logical_switch_port_set_peer(lsp, peer + 5);
+ } > } else {
ctl_error(ctx, "Logical switch port type '%s' is unrecognized. "
"Not setting type.", type);
@@ -8010,7 +8029,7 @@ static const struct ctl_command_syntax nbctl_commands[] =
{
nbctl_lsp_set_enabled, NULL, "", RW },
{ "lsp-get-enabled", 1, 1, "PORT", nbctl_pre_lsp_enabled,
nbctl_lsp_get_enabled, NULL, "", RO },
- { "lsp-set-type", 2, 2, "PORT TYPE", nbctl_pre_lsp_type,
+ { "lsp-set-type", 2, 3, "PORT TYPE [peer=PEER]", nbctl_pre_lsp_type,
nbctl_lsp_set_type, NULL, "", RW },
{ "lsp-get-type", 1, 1, "PORT", nbctl_pre_lsp_type,
nbctl_lsp_get_type, NULL, "", RO },