Skip to content

Commit

Permalink
nad: Ensure active-network annotation at namespace
Browse files Browse the repository at this point in the history
This change ensure that there are no multiple primary network at the
same namespace, that there are no pods before activate the primary
network and update the namesapce with active-network if everything is
fine else it will annotate with "unknown", Also namespace is created
with active-network="default".

Signed-off-by: Enrique Llorente <ellorent@redhat.com>
  • Loading branch information
qinqon committed Jun 14, 2024
1 parent cb147b1 commit fbbe12b
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ func newSecondaryNetworkClusterManager(ovnClient *util.OVNClusterManagerClientse
if err != nil {
return nil, err
}
if util.IsNetworkSegmentationSupportEnabled() {
sncm.nadController.AddManager("network segmentation", nad.NewNetworkSegmentationManager(ovnClient.KubeClient, wf))
}
return sncm, nil
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package networkAttachDefController

import (
"fmt"

nettypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"
"k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
)

var _ NetAttachDefinitionManager = &NetworkSegmentationManager{}

type NetworkSegmentationManager struct {
kube kubernetes.Interface
watchFactory *factory.WatchFactory
}

func NewNetworkSegmentationManager(kube kubernetes.Interface, watchFactory *factory.WatchFactory) *NetworkSegmentationManager {
return &NetworkSegmentationManager{
kube: kube,
watchFactory: watchFactory,
}
}

func (m *NetworkSegmentationManager) OnAddNetAttachDef(nad *nettypes.NetworkAttachmentDefinition, network util.NetInfo) error {
return m.ensureNamespaceActiveNetwork(nad.Namespace, network)
}

func (m *NetworkSegmentationManager) OnDelNetAttachDef(nadName, netName string) error {
//TODO
return nil
}

func (m *NetworkSegmentationManager) ensureNamespaceActiveNetwork(namespace string, network util.NetInfo) error {
if m.watchFactory == nil || m.kube == nil {
return nil
}
if !network.IsPrimaryNetwork() {
return nil
}

networkNamespace, err := m.watchFactory.GetNamespace(namespace)
if err != nil {
return fmt.Errorf("failed looking for network namespace '%s': %w", namespace, err)
}

currentActiveNetwork, ok := networkNamespace.Annotations[util.ActiveNetworkAnnotation]
if !ok {
return fmt.Errorf("missing active-network annotation at namespace %s", namespace)
}

if currentActiveNetwork == network.GetNetworkName() {
return nil
}

if currentActiveNetwork != types.DefaultNetworkName {
//TODO: Event
klog.Warningf("Active primary network %s already configured at namespace %s, marking namespace active network to unknown", currentActiveNetwork, networkNamespace.Name)
if err := util.UpdateNamespaceActiveNetwork(m.kube, networkNamespace, types.UnknownNetworkName); err != nil {
return fmt.Errorf("failed annotating namespace with active-network=unknown when a primary network was already configured: %w", err)
}
return nil
}

pods, err := m.watchFactory.GetPods(networkNamespace.Name)
if err != nil {
return fmt.Errorf("failed ensuring namespace '%s' active network when listing pods: %w", networkNamespace.Name, err)
}

// At this point all those pods exist before configuring the primary network,
// so we should mark the namespace and send and event
if len(pods) > 0 {
//TODO: Event
klog.Warningf("Pods present at namesapace %s before configuring primary network, marking namespace active network to unknown", networkNamespace.Name)
if err := util.UpdateNamespaceActiveNetwork(m.kube, networkNamespace, types.UnknownNetworkName); err != nil {
return fmt.Errorf("failed annotating namespace with active-network=unknown when namespace contains pods before configuring a primary network: %w", err)
}
return nil
}

if err := util.UpdateNamespaceActiveNetwork(m.kube, networkNamespace, network.GetNetworkName()); err != nil {
return fmt.Errorf("failed annotating namespace with active-network=%s: %w", network.GetNetworkName(), err)
}
return nil
}
9 changes: 9 additions & 0 deletions go-controller/pkg/ovn/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ func (oc *DefaultNetworkController) AddNamespace(ns *kapi.Namespace) error {
klog.Infof("[%s] adding namespace", ns.Name)
// Keep track of how long syncs take.
start := time.Now()

if util.IsNetworkSegmentationSupportEnabled() {
if _, ok := ns.Annotations[util.ActiveNetworkAnnotation]; !ok {
if err := util.UpdateNamespaceActiveNetwork(oc.kube.KClient, ns, types.DefaultNetworkName); err != nil {
return fmt.Errorf("failed annotating namesspace with active-network=default: %w", err)
}
}
}

defer func() {
klog.Infof("[%s] adding namespace took %v", ns.Name, time.Since(start))
}()
Expand Down
1 change: 1 addition & 0 deletions go-controller/pkg/types/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "time"
const (
// Default network name
DefaultNetworkName = "default"
UnknownNetworkName = "unknown"
K8sPrefix = "k8s-"
HybridOverlayPrefix = "int-"
HybridOverlayGRSubfix = "-gr"
Expand Down
8 changes: 7 additions & 1 deletion test/e2e/multihoming_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type networkAttachmentConfigParams struct {
networkName string
vlanID int
allowPersistentIPs bool
primaryNetwork bool
}

type networkAttachmentConfig struct {
Expand Down Expand Up @@ -78,7 +79,8 @@ func generateNAD(config networkAttachmentConfig) *nadapi.NetworkAttachmentDefini
"mtu": 1300,
"netAttachDefName": %q,
"vlanID": %d,
"allowPersistentIPs": %t
"allowPersistentIPs": %t,
"primaryNetwork": %t
}
`,
config.networkName,
Expand All @@ -88,6 +90,7 @@ func generateNAD(config networkAttachmentConfig) *nadapi.NetworkAttachmentDefini
namespacedName(config.namespace, config.name),
config.vlanID,
config.allowPersistentIPs,
config.primaryNetwork,
)
return generateNetAttachDef(config.namespace, config.name, nadSpec)
}
Expand Down Expand Up @@ -126,6 +129,9 @@ func generatePodSpec(config podConfiguration) *v1.Pod {
}

func networkSelectionElements(elements ...nadapi.NetworkSelectionElement) map[string]string {
if len(elements) == 0 {
return map[string]string{}
}
marshalledElements, err := json.Marshal(elements)
if err != nil {
panic(fmt.Errorf("programmer error: you've provided wrong input to the test data: %v", err))
Expand Down
160 changes: 160 additions & 0 deletions test/e2e/network_segmentation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package e2e

import (
"context"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

nadclient "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned/typed/k8s.cni.cncf.io/v1"
)

var _ = Describe("Network Segmentation", func() {
const (
activeNetworkAnnotation = "k8s.ovn.org/active-network"
)

f := wrappedTestFramework("network-segmentation")

type activeNetworkTest struct {
nads []networkAttachmentConfigParams
podsBeforeNADs []podConfiguration
expectedActiveNetwork string
}
DescribeTable("should annotate namespace with proper active-network", func(td activeNetworkTest) {
nadClient, err := nadclient.NewForConfig(f.ClientConfig())
Expect(err).NotTo(HaveOccurred())

By("Create pods before network attachment definition")
podsBeforeNADs := []*corev1.Pod{}
for _, pod := range td.podsBeforeNADs {
pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(
context.Background(),
generatePodSpec(pod),
metav1.CreateOptions{},
)
Expect(err).NotTo(HaveOccurred())
podsBeforeNADs = append(podsBeforeNADs, pod)
}

By("Create network attachment definitions")
for _, nad := range td.nads {
netConfig := newNetworkAttachmentConfig(nad)
netConfig.namespace = f.Namespace.Name

_, err = nadClient.NetworkAttachmentDefinitions(netConfig.namespace).Create(
context.Background(),
generateNAD(netConfig),
metav1.CreateOptions{},
)
Expect(err).NotTo(HaveOccurred())
}

By("Wait for active-network annotation")
Eventually(thisNamespace(f.ClientSet, f.Namespace)).
WithPolling(time.Second / 2).
WithTimeout(5 * time.Second).
Should(WithTransform(getAnnotations,
HaveKeyWithValue(activeNetworkAnnotation, td.expectedActiveNetwork)))

},
Entry("without primary network nads to 'default'", activeNetworkTest{
nads: []networkAttachmentConfigParams{},
expectedActiveNetwork: "default",
}),
Entry("with one primaryNetwork nad on layer2 to network name", activeNetworkTest{
nads: []networkAttachmentConfigParams{{
name: "tenant-blue-l2",
networkName: "net-l2",
cidr: "10.128.0.0/24",
topology: "layer2",
primaryNetwork: true,
}},
expectedActiveNetwork: "net-l2",
}),
Entry("with one primaryNetwork nad on layer3 to network name", activeNetworkTest{
nads: []networkAttachmentConfigParams{{
name: "tenant-blue-l3",
networkName: "net-l3",
cidr: "10.128.0.0/16/24",
topology: "layer3",
primaryNetwork: true,
}},
expectedActiveNetwork: "net-l3",
}),
Entry("with two primaryNetwork nads on layer3 and same network with network name", activeNetworkTest{
nads: []networkAttachmentConfigParams{
{
name: "tenant-blue-l3",
networkName: "net-l3",
cidr: "10.128.0.0/16/24",
topology: "layer3",
primaryNetwork: true,
},
{
name: "tenant-red-l3",
networkName: "net-l3",
cidr: "10.128.0.0/16/24",
topology: "layer3",
primaryNetwork: true,
},
},
expectedActiveNetwork: "net-l3",
}),
Entry("with two primaryNetwork nads on layer2 and same network with network name", activeNetworkTest{
nads: []networkAttachmentConfigParams{
{
name: "tenant-blue-l2",
networkName: "net-l2",
cidr: "10.128.0.0/24",
topology: "layer2",
primaryNetwork: true,
},
{
name: "tenant-red-l2",
networkName: "net-l2",
cidr: "10.128.0.0/24",
topology: "layer2",
primaryNetwork: true,
},
},
expectedActiveNetwork: "net-l2",
}),
Entry("with two primaryNetwork nads and different network with 'unknown'", activeNetworkTest{
nads: []networkAttachmentConfigParams{
{
name: "tenant-blue-l3",
networkName: "net-l3",
cidr: "10.128.0.0/16/24",
topology: "layer3",
primaryNetwork: true,
},
{
name: "tenant-blue-l2",
networkName: "net-l2",
cidr: "10.128.0.0/24",
topology: "layer2",
primaryNetwork: true,
},
},
expectedActiveNetwork: "unknown",
}),
Entry("with one primaryNetwork nad pods at the namespace with 'unknown'", activeNetworkTest{
podsBeforeNADs: []podConfiguration{{
name: "pod1",
}},
nads: []networkAttachmentConfigParams{{
name: "tenant-blue-l2",
networkName: "net-l2",
cidr: "10.128.0.0/24",
topology: "layer2",
primaryNetwork: true,
}},
expectedActiveNetwork: "unknown",
}),
)
})
11 changes: 11 additions & 0 deletions test/e2e/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
testutils "k8s.io/kubernetes/test/utils"
admissionapi "k8s.io/pod-security-admission/api"
utilnet "k8s.io/utils/net"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
Expand Down Expand Up @@ -1240,3 +1241,13 @@ func getGatewayMTUSupport(node *v1.Node) bool {
}
return false
}

func thisNamespace(cli kubernetes.Interface, namespace *v1.Namespace) func() (*v1.Namespace, error) {
return func() (*v1.Namespace, error) {
return cli.CoreV1().Namespaces().Get(context.Background(), namespace.Name, metav1.GetOptions{})
}
}

func getAnnotations(obj client.Object) map[string]string {
return obj.GetAnnotations()
}

0 comments on commit fbbe12b

Please sign in to comment.