Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OCPBUGS-31679: localnet, multi-homing: introduce localnet alias #4320

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go-controller/pkg/cni/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ type NetConf struct {
// restart.
AllowPersistentIPs bool `json:"allowPersistentIPs,omitempty"`

Alias string `json:"localnetPortAlias,omitempty"`

// PciAddrs in case of using sriov or Auxiliry device name in case of SF
DeviceID string `json:"deviceID,omitempty"`
// LogFile to log all the messages from cni shim binary to
Expand Down
14 changes: 11 additions & 3 deletions go-controller/pkg/ovn/secondary_localnet_network_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,7 @@ func (oc *SecondaryLocalnetNetworkController) Init() error {
Name: oc.GetNetworkScopedName(types.OVNLocalnetPort),
Addresses: []string{"unknown"},
Type: "localnet",
Options: map[string]string{
"network_name": oc.GetNetworkName(),
},
Options: oc.localnetPortNetworkNameOptions(),
}
intVlanID := int(oc.Vlan())
if intVlanID != 0 {
Expand Down Expand Up @@ -342,3 +340,13 @@ func (oc *SecondaryLocalnetNetworkController) newRetryFramework(
resourceHandler,
)
}

func (oc *SecondaryLocalnetNetworkController) localnetPortNetworkNameOptions() map[string]string {
localnetLSPOptions := map[string]string{
"network_name": oc.GetNetworkName(),
}
if oc.PhysicalNetworkNameAlias() != "" {
localnetLSPOptions["network_name"] = oc.PhysicalNetworkNameAlias()
}
return localnetLSPOptions
}
13 changes: 13 additions & 0 deletions go-controller/pkg/util/multi_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type BasicNetInfo interface {
JoinSubnets() []*net.IPNet
Vlan() uint
AllowsPersistentIPs() bool
PhysicalNetworkNameAlias() string

// utility methods
Equals(BasicNetInfo) bool
Expand Down Expand Up @@ -258,6 +259,10 @@ func (nInfo *DefaultNetInfo) AllowsPersistentIPs() bool {
return false
}

func (nInfo *DefaultNetInfo) PhysicalNetworkNameAlias() string {
return ""
}

// SecondaryNetInfo holds the network name information for secondary network if non-nil
type secondaryNetInfo struct {
netName string
Expand All @@ -278,6 +283,8 @@ type secondaryNetInfo struct {
// to be plumbed for this network
sync.Mutex
nadNames sets.Set[string]

localnetAlias string
}

// GetNetworkName returns the network name
Expand Down Expand Up @@ -416,6 +423,10 @@ func (nInfo *secondaryNetInfo) AllowsPersistentIPs() bool {
return nInfo.allowPersistentIPs
}

func (nInfo *secondaryNetInfo) PhysicalNetworkNameAlias() string {
return nInfo.localnetAlias
}

// IPMode returns the ipv4/ipv6 mode
func (nInfo *secondaryNetInfo) IPMode() (bool, bool) {
return nInfo.ipv4mode, nInfo.ipv6mode
Expand Down Expand Up @@ -513,6 +524,7 @@ func (nInfo *secondaryNetInfo) copy() *secondaryNetInfo {
excludeSubnets: nInfo.excludeSubnets,
joinSubnets: nInfo.joinSubnets,
nadNames: nInfo.nadNames.Clone(),
localnetAlias: nInfo.localnetAlias,
}

return c
Expand Down Expand Up @@ -579,6 +591,7 @@ func newLocalnetNetConfInfo(netconf *ovncnitypes.NetConf) (NetInfo, error) {
vlan: uint(netconf.VLANID),
allowPersistentIPs: netconf.AllowPersistentIPs,
nadNames: sets.Set[string]{},
localnetAlias: netconf.Alias,
}
ni.ipv4mode, ni.ipv6mode = getIPMode(subnets)
return ni, nil
Expand Down
37 changes: 37 additions & 0 deletions test/e2e/localnet-underlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package e2e
import (
"context"
"fmt"
"os"
"os/exec"
"strings"

v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -161,3 +163,38 @@ func bridgeMapping(physnet, ovsBridge string) BridgeMapping {
ovsBridge: ovsBridge,
}
}

func createVLANInterface(deviceName string, vlanID string, ipAddress *string) error {
vlanName := fmt.Sprintf("%s.%s", deviceName, vlanID)
cmd := exec.Command("sudo", "ip", "link", "add", "link", deviceName, "name", vlanName, "type", "vlan", "id", vlanID)
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to create vlan interface %s: %v", vlanName, err)
}

cmd = exec.Command("sudo", "ip", "link", "set", "dev", vlanName, "up")
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to enable vlan interface %s: %v", vlanName, err)
}

if ipAddress != nil {
cmd = exec.Command("sudo", "ip", "addr", "add", *ipAddress, "dev", vlanName)
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to define the vlan interface %q IP Address %s: %v", vlanName, ipAddress, err)
}
}
return nil
}

func deleteVLANInterface(deviceName string, vlanID string) error {
vlanName := fmt.Sprintf("%s.%s", deviceName, vlanID)
cmd := exec.Command("sudo", "ip", "link", "del", deviceName)
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to create vlan interface %s: %v", vlanName, err)
}
return nil
}
131 changes: 125 additions & 6 deletions test/e2e/multihoming.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package e2e
import (
"context"
"fmt"
"net"
"os"
"os/exec"
"strconv"
Expand Down Expand Up @@ -656,6 +657,7 @@ var _ = Describe("Multi Homing", func() {
var underlayBridgeName string
var cmdWebServer *exec.Cmd

underlayIP := underlayServiceIP + "/24"
Context("with a service running on the underlay", func() {
BeforeEach(func() {
netConfig = newNetworkAttachmentConfig(
Expand Down Expand Up @@ -685,18 +687,15 @@ var _ = Describe("Multi Homing", func() {
underlayBridgeName, err = findInterfaceByIP(gatewayIP)
Expect(err).NotTo(HaveOccurred())

cmd := exec.Command("sudo", "ip", "addr", "add", underlayServiceIP+"/24", "dev", underlayBridgeName)
cmd := exec.Command("sudo", "ip", "addr", "add", underlayIP, "dev", underlayBridgeName)
cmd.Stderr = os.Stderr
err = cmd.Run()
Expect(err).NotTo(HaveOccurred())
})

BeforeEach(func() {
By("starting a service, connected to the underlay")
cmdWebServer = exec.Command("python3", "-m", "http.server", "--bind", underlayServiceIP, strconv.Itoa(servicePort))
cmdWebServer.Stderr = os.Stderr
err := cmdWebServer.Start()
Expect(err).NotTo(HaveOccurred(), "failed to create web server, port might be busy")
Expect(startServer(underlayServiceIP, servicePort)).NotTo(HaveOccurred(), "failed to create web server, port might be busy")
})

BeforeEach(func() {
Expand All @@ -715,7 +714,7 @@ var _ = Describe("Multi Homing", func() {
})

AfterEach(func() {
cmd := exec.Command("sudo", "ip", "addr", "del", underlayServiceIP+"/24", "dev", underlayBridgeName)
cmd := exec.Command("sudo", "ip", "addr", "del", underlayIP, "dev", underlayBridgeName)
cmd.Stderr = os.Stderr
err := cmd.Run()
Expect(err).NotTo(HaveOccurred())
Expand Down Expand Up @@ -899,6 +898,90 @@ var _ = Describe("Multi Homing", func() {
)
})
})

Context("with a trunked configuration", func() {
const vlanID = 20
BeforeEach(func() {
nodes := ovsPods(cs)
Expect(nodes).NotTo(BeEmpty())

Expect(
setupUnderlayForTrunkedConfig(
f.Namespace.Name,
secondaryLocalnetNetworkCIDR,
secondaryInterfaceName,
underlayServiceIP,
),
).To(Succeed())

By("creating a VLAN interface with an IP on top of the bridge connecting the cluster nodes")
cli, err := client.NewClientWithOpts(client.FromEnv)
Expect(err).NotTo(HaveOccurred())

gatewayIP, err := getNetworkGateway(cli, dockerNetworkName)
Expect(err).NotTo(HaveOccurred())

underlayBridgeName, err = findInterfaceByIP(gatewayIP)
Expect(err).NotTo(HaveOccurred())

Expect(createVLANInterface(underlayBridgeName, strconv.Itoa(vlanID), &underlayIP)).To(
Succeed(),
"create a VLAN interface on the bridge interconnecting the cluster nodes",
)
})

BeforeEach(func() {
By("starting a service, connected to the underlay")
Expect(startServer(underlayServiceIP, servicePort)).NotTo(HaveOccurred(), "failed to create web server, port might be busy")
})

BeforeEach(func() {
_, err := nadClient.NetworkAttachmentDefinitions(netConfig.namespace).Create(
context.Background(),
generateNAD(netConfig),
metav1.CreateOptions{},
)
Expect(err).NotTo(HaveOccurred(), "create the attachment configuration")
})

AfterEach(func() {
Expect(cmdWebServer.Process.Kill()).NotTo(HaveOccurred(), "kill the python webserver")
Expect(deleteVLANInterface(underlayBridgeName, strconv.Itoa(vlanID))).NotTo(HaveOccurred(), "remove the underlay physical configuration")
Expect(teardownUnderlay(nodes)).To(Succeed(), "tear down the localnet underlay")
})

It("the same localnet network mapping can be shared on a separate VLAN by using the localnet alias attribute", func() {
const otherNetworkName = "different-network"
vlan20NetConfig := newNetworkAttachmentConfig(
networkAttachmentConfigParams{
name: otherNetworkName,
localnetPortNameAlias: netConfig.networkName,
namespace: f.Namespace.Name,
vlanID: vlanID,
topology: "localnet",
cidr: secondaryLocalnetNetworkCIDR,
excludeCIDRs: []string{underlayServiceIP + "/32"},
})

By("creating the attachment configuration for a separate VLAN")
_, err := nadClient.NetworkAttachmentDefinitions(netConfig.namespace).Create(
context.Background(),
generateNAD(vlan20NetConfig),
metav1.CreateOptions{},
)
Expect(err).NotTo(HaveOccurred())

clientPodConfig := podConfiguration{
name: clientPodName,
namespace: f.Namespace.Name,
attachments: []nadapi.NetworkSelectionElement{{Name: otherNetworkName}},
}
kickstartPod(cs, clientPodConfig)
time.Sleep(10 * time.Minute)
By("asserting the *client* pod can contact the underlay service on the separate vlan")
Expect(connectToServer(clientPodConfig, underlayServiceIP, servicePort)).To(Succeed())
})
})
})

Context("multi-network policies", func() {
Expand Down Expand Up @@ -1599,6 +1682,30 @@ var _ = Describe("Multi Homing", func() {
})
})

func setupUnderlayForTrunkedConfig(namespace string, subnet string, underlayServiceIP string, secondaryInterfaceName string, ovsNodes ...v1.Pod) error {
netConfig := newNetworkAttachmentConfig(
// if we omit the vlan tag, we'll have a trunked configuration in the ovs bridge
networkAttachmentConfigParams{
name: secondaryNetworkName,
namespace: namespace,
topology: "localnet",
cidr: subnet,
excludeCIDRs: []string{underlayServiceIP + "/32"},
})

By("setting up the localnet underlay")
if err := setupUnderlay(ovsNodes, secondaryInterfaceName, netConfig); err != nil {
return err
}
return nil
}

func startServer(ip string, port int) error {
cmdWebServer := exec.Command("python3", "-m", "http.server", "--bind", ip, strconv.Itoa(port))
cmdWebServer.Stderr = os.Stderr
return cmdWebServer.Start()
}

func kickstartPod(cs clientset.Interface, configuration podConfiguration) *v1.Pod {
By(fmt.Sprintf("instantiating the %q pod", fmt.Sprintf("%s/%s", configuration.namespace, configuration.name)))
createdPod, err := cs.CoreV1().Pods(configuration.namespace).Create(
Expand Down Expand Up @@ -1666,3 +1773,15 @@ func createMultiNetworkPolicy(mnpClient mnpclient.K8sCniCncfIoV1beta1Interface,
)
return err
}

func MustParseIPNet(cidrStr string) *net.IPNet {
ip, ipNet, err := net.ParseCIDR(cidrStr)
if err != nil {
panic(fmt.Sprintf("Could not parse %q as a CIDR: %v", cidrStr, err))
}

if !ipNet.IP.Equal(ip) {
ipNet.IP = ip
}
return ipNet
}
21 changes: 12 additions & 9 deletions test/e2e/multihoming_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,16 @@ func getNetCIDRSubnet(netCIDR string) (string, error) {
}

type networkAttachmentConfigParams struct {
cidr string
excludeCIDRs []string
namespace string
name string
topology string
networkName string
vlanID int
allowPersistentIPs bool
role string
cidr string
excludeCIDRs []string
namespace string
name string
topology string
networkName string
vlanID int
allowPersistentIPs bool
role string
localnetPortNameAlias string
}

type networkAttachmentConfig struct {
Expand Down Expand Up @@ -96,6 +97,7 @@ func generateNAD(config networkAttachmentConfig) *nadapi.NetworkAttachmentDefini
"netAttachDefName": %q,
"vlanID": %d,
"allowPersistentIPs": %t,
"localnetPortAlias": %q,
"role": %q
}
`,
Expand All @@ -106,6 +108,7 @@ func generateNAD(config networkAttachmentConfig) *nadapi.NetworkAttachmentDefini
namespacedName(config.namespace, config.name),
config.vlanID,
config.allowPersistentIPs,
config.localnetPortNameAlias,
config.role,
)
return generateNetAttachDef(config.namespace, config.name, nadSpec)
Expand Down
Loading