Skip to content
Merged
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: 1 addition & 1 deletion config/samples/multinicnetwork/ipvlanl2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ kind: MultiNicNetwork
metadata:
name: multinic-ipvlanl2
spec:
subnet: ""
subnet: "192.168.0.0/16"
ipam: |
{ "type": "whereabouts", "range": "192.168.0.0/18" }
multiNICIPAM: false
Expand Down
18 changes: 18 additions & 0 deletions config/samples/multinicnetwork/ipvlanl2_unmanaged.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: multinic.fms.io/v1
kind: MultiNicNetwork
metadata:
name: multinic-unmanaged
spec:
ipam: |
{
"type": "multi-nic-ipam",
"hostBlock": 0,
"interfaceBlock": 0,
"vlanMode": "l2"
}
multiNICIPAM: true
plugin:
cniVersion: "0.3.0"
type: ipvlan
args:
mode: l2
12 changes: 10 additions & 2 deletions controllers/cidr_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,11 @@ func (h *CIDRHandler) getCurrentAllocationMap(cidrMap map[string]multinicv1.CIDR

// addNewHost finds new available host index
func (h *CIDRHandler) addNewHost(hosts []multinicv1.HostInterfaceInfo, maxHostIndex int, vlanCIDR string, nodeBlock int, excludes []string) (string, int, error) {
if maxHostIndex == 0 {
// pods use the same cidr with vlan
// podCIDR = vlanCIDR
return vlanCIDR, 0, nil
}
nodeIndex := 0
// excludedIndexes = previously-assigned host indexes
excludedIndexes := []int{}
Expand Down Expand Up @@ -991,9 +996,12 @@ func (h *CIDRHandler) GenerateCIDRFromHostSubnet(def multinicv1.PluginConfig) (m
vlanIndexMap := make(map[string]int)
entryMap := make(map[string]multinicv1.CIDREntry)
lastIndex := 0
maxHostIndex := 0

// maxHostIndex = 2^(host bits) - 1
maxHostIndex := int(math.Pow(2, float64(def.HostBlock)) - 1)
if def.HostBlock > 0 {
// maxHostIndex = 2^(host bits) - 1
maxHostIndex = int(math.Pow(2, float64(def.HostBlock)) - 1)
}

snapshot := h.HostInterfaceHandler.ListCache()
for hostName, hif := range snapshot {
Expand Down
36 changes: 36 additions & 0 deletions controllers/cidr_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ var _ = Describe("Test CIDR Handler ", func() {
Expect(len(hostIPs)).To(BeEquivalentTo(len(interfaceNames) * handler.HostInterfaceHandler.GetSize()))

By("Testing with no interfaces")
originalHif := handler.HostInterfaceHandler.ListCache()
// Clear the HostInterface cache to simulate no interfaces
handler.HostInterfaceHandler.SafeCache.Clear()

Expand All @@ -128,6 +129,41 @@ var _ = Describe("Test CIDR Handler ", func() {
Expect(len(cidrSpec.CIDRs)).To(Equal(0))
hostIPs = handler.GetHostAddressesToExclude()
Expect(len(hostIPs)).To(BeZero())

By("Setting original hosts")
for k, v := range originalHif {
handler.HostInterfaceHandler.SafeCache.SetCache(k, v)
}
})

It("Empty subnet with unmanaged hostinterface", func() {
unmanagedMultinicnetwork := GetMultiNicCNINetwork("unmanaged", cniVersion, cniType, cniArgs)
unmanagedMultinicnetwork.Spec.Subnet = ""
ipamConfig, err := MultiNicnetworkReconcilerInstance.GetIPAMConfig(unmanagedMultinicnetwork)
Expect(err).NotTo(HaveOccurred())
ipamConfig.InterfaceBlock = 0
ipamConfig.HostBlock = 0
cidrSpec, err := handler.GenerateCIDRFromHostSubnet(*ipamConfig)
Expect(err).NotTo(HaveOccurred())
Expect(len(cidrSpec.CIDRs)).To(Equal(len(interfaceNames)))
hifSnapshot := handler.HostInterfaceHandler.ListCache()
for _, cidr := range cidrSpec.CIDRs {
for _, hostInfo := range cidr.Hosts {
hif, found := hifSnapshot[hostInfo.HostName]
Expect(found).To(Equal(true))
found = false
for _, iface := range hif.Spec.Interfaces {
if hostInfo.InterfaceName == iface.InterfaceName {
netAddr := iface.NetAddress
Expect(cidr.VlanCIDR).To(Equal(netAddr))
Expect(hostInfo.PodCIDR).To(Equal(netAddr))
found = true
break
}
}
Expect(found).To(Equal(true))
}
}
})
})

Expand Down
13 changes: 13 additions & 0 deletions controllers/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package controllers
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

//+kubebuilder:scaffold:imports

"github.com/foundation-model-stack/multi-nic-cni/internal/vars"
)

func cleanCache() {
Expand Down Expand Up @@ -33,4 +36,14 @@ var _ = Describe("Daemon Test", func() {
daemonPods := MultiNicnetworkReconcilerInstance.CIDRHandler.DaemonCacheHandler.ListCache()
Expect(len(daemonPods)).To(Equal(0))
})

It("Test IsUnmanaged function", func() {
newHostName := "unmanagedHost"
newHif := GenerateNewHostInterface(newHostName, interfaceNames, networkPrefixes, 0)
Expect(vars.IsUnmanaged(newHif.ObjectMeta)).To(Equal(false))
newHif.ObjectMeta.Labels[vars.UnmanagedLabelName] = "true"
Expect(vars.IsUnmanaged(newHif.ObjectMeta)).To(Equal(true))
newHif.ObjectMeta.Labels[vars.UnmanagedLabelName] = "false"
Expect(vars.IsUnmanaged(newHif.ObjectMeta)).To(Equal(false))
})
})
10 changes: 7 additions & 3 deletions controllers/daemon_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,13 @@ func (w *DaemonWatcher) ProcessPodQueue() {
if errors.IsNotFound(err) {
// deleted, delete HostInterface
w.DaemonCacheHandler.SafeCache.UnsetCache(nodeName)
err := w.HostInterfaceHandler.DeleteHostInterface(nodeName)
if err != nil {
vars.DaemonLog.V(4).Info(fmt.Sprintf("Failed to delete HostInterface %s: %v", nodeName, err))
hif, err := w.HostInterfaceHandler.GetHostInterface(nodeName)
if err == nil && !vars.IsUnmanaged(hif.ObjectMeta) {
// deleted, delete HostInterface
err := w.HostInterfaceHandler.DeleteHostInterface(nodeName)
if err != nil {
vars.DaemonLog.V(4).Info(fmt.Sprintf("Failed to delete HostInterface %s: %v", nodeName, err))
}
}
}
}
Expand Down
25 changes: 19 additions & 6 deletions controllers/hostinterface_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ func InitHostInterfaceCache(clientset *kubernetes.Clientset, hostInterfaceHandle
if _, foundErr := daemonCacheHandler.GetCache(name); foundErr != nil {
// not found, check whether node is still there.
if _, foundErr = clientset.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{}); foundErr != nil {
// delete HostInterface and do not add
err = hostInterfaceHandler.DeleteHostInterface(name)
if err != nil {
vars.HifLog.V(4).Info(fmt.Sprintf("Failed to delete HostInterface %s: %v", name, err))
if !vars.IsUnmanaged(instance.ObjectMeta) {
// delete HostInterface and do not add
err = hostInterfaceHandler.DeleteHostInterface(name)
if err != nil {
vars.HifLog.V(4).Info(fmt.Sprintf("Failed to delete HostInterface %s: %v", name, err))
}
continue
}
continue
}
}
hostInterfaceHandler.SetCache(name, instance)
Expand Down Expand Up @@ -92,6 +94,14 @@ func (r *HostInterfaceReconciler) Reconcile(ctx context.Context, req ctrl.Reques
return ctrl.Result{RequeueAfter: vars.UrgentReconcileTime}, nil
}

hifName := instance.GetName()
if vars.IsUnmanaged(instance.ObjectMeta) {
// handle unmanaged hostinterface before adding finalizer
r.HostInterfaceHandler.SetCache(hifName, *instance.DeepCopy())
r.CIDRHandler.UpdateCIDRs()
return ctrl.Result{}, nil
}

// Add finalizer to instance
if !controllerutil.ContainsFinalizer(instance, hifFinalizer) {
controllerutil.AddFinalizer(instance, hifFinalizer)
Expand Down Expand Up @@ -125,7 +135,6 @@ func (r *HostInterfaceReconciler) Reconcile(ctx context.Context, req ctrl.Reques
return ctrl.Result{}, nil
}

hifName := instance.GetName()
if !r.HostInterfaceHandler.SafeCache.Contains(hifName) && len(instance.Spec.Interfaces) > 0 {
r.HostInterfaceHandler.SetCache(hifName, *instance.DeepCopy())
}
Expand Down Expand Up @@ -153,6 +162,10 @@ func (r *HostInterfaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
}

func (r *HostInterfaceReconciler) UpdateInterfaces(instance multinicv1.HostInterface) error {
if vars.IsUnmanaged(instance.ObjectMeta) {
r.CIDRHandler.UpdateCIDRs()
return nil
}
nodeName := instance.Spec.HostName
hifName := instance.GetName()
pod, err := r.DaemonWatcher.TryGetDaemonPod(nodeName)
Expand Down
31 changes: 31 additions & 0 deletions controllers/hostinterface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
package controllers_test

import (
"context"

multinicv1 "github.com/foundation-model-stack/multi-nic-cni/api/v1"
"github.com/foundation-model-stack/multi-nic-cni/controllers"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
)

var _ = Describe("Host Interface Test", func() {
Expand Down Expand Up @@ -101,6 +106,32 @@ var _ = Describe("Host Interface Test", func() {
})
})

Context("unmanaged host", func() {
It("can create/delete unmanaged host", func() {
ctx := context.Background()
hostName := "unmanagned"
By("creating")
unmanaged := controllers.GenerateNewUnmanagedHostInterface(hostName)
err := controllers.K8sClient.Create(ctx, &unmanaged)
Expect(err).To(BeNil())
By("reconciling")
namespacedName := types.NamespacedName{Name: hostName, Namespace: metav1.NamespaceAll}
req := ctrl.Request{
NamespacedName: namespacedName,
}
_, err = controllers.HostInterfaceReconcilerInstance.Reconcile(ctx, req)
Expect(err).To(BeNil())
// finalizer must not be added
hif := &multinicv1.HostInterface{}
err = controllers.K8sClient.Get(ctx, namespacedName, hif)
Expect(err).To(BeNil())
Expect(hif.GetFinalizers()).To(HaveLen(0))
By("deleting")
err = controllers.K8sClient.Delete(ctx, &unmanaged)
Expect(err).To(BeNil())
})
})

})

func genInterfaceInfo(devName, netAddress string) multinicv1.InterfaceInfoType {
Expand Down
13 changes: 11 additions & 2 deletions controllers/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ var mellanoxPlugin *plugin.MellanoxPlugin

var MultiNicnetworkReconcilerInstance *MultiNicNetworkReconciler
var ConfigReconcilerInstance *ConfigReconciler
var HostInterfaceReconcilerInstance *HostInterfaceReconciler
var daemonWatcher *DaemonWatcher

// Multi-NIC IPAM
Expand Down Expand Up @@ -148,13 +149,14 @@ var _ = BeforeSuite(func() {
}).SetupWithManager(mgr)
Expect(err).ToNot(HaveOccurred())

err = (&HostInterfaceReconciler{
HostInterfaceReconcilerInstance = &HostInterfaceReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
HostInterfaceHandler: hostInterfaceHandler,
CIDRHandler: cidrHandler,
DaemonWatcher: daemonWatcher,
}).SetupWithManager(mgr)
}
err = HostInterfaceReconcilerInstance.SetupWithManager(mgr)
Expect(err).ToNot(HaveOccurred())

err = (&IPPoolReconciler{
Expand Down Expand Up @@ -352,6 +354,13 @@ func GenerateNewHostInterface(hostName string, interfaceNames []string, networkP
return hif
}

// GenerateNewUnmanagedHostInterface generates empty HostInterface with unmanaged label
func GenerateNewUnmanagedHostInterface(hostName string) multinicv1.HostInterface {
hif := GenerateNewHostInterface(hostName, []string{}, []string{}, 0)
hif.Labels[vars.UnmanagedLabelName] = "true"
return hif
}

// GetMultiNicCNINetwork returns MultiNicNetwork object
func GetMultiNicCNINetwork(name string, cniVersion string, cniType string, cniArgs map[string]string) *multinicv1.MultiNicNetwork {
return &multinicv1.MultiNicNetwork{
Expand Down
37 changes: 31 additions & 6 deletions daemon/src/backend/hostinterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package backend

import (
"github.com/foundation-model-stack/multi-nic-cni/daemon/iface"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"

Expand All @@ -17,11 +16,24 @@ import (
"k8s.io/client-go/rest"
)

type InterfaceInfoType struct {
InterfaceName string `json:"interfaceName"`
NetAddress string `json:"netAddress"`
HostIP string `json:"hostIP"`
Vendor string `json:"vendor"`
Product string `json:"product"`
PciAddress string `json:"pciAddress"`
}

const (
HOSTINTERFACE_RESOURCE = "hostinterfaces.v1.multinic.fms.io"
HOSTINTERFACE_KIND = "hostinterfaces"
)

var (
unmanagedLabelName = "multi-nic-unmanaged"
)

type HostInterfaceHandler struct {
*DynamicHandler
hostName string
Expand All @@ -43,14 +55,14 @@ func NewHostInterfaceHandler(config *rest.Config, hostName string) *HostInterfac
return handler
}

func (h *HostInterfaceHandler) parse(uobj *unstructured.Unstructured) ([]iface.InterfaceInfoType, error) {
var infos []iface.InterfaceInfoType
func (h *HostInterfaceHandler) parse(uobj *unstructured.Unstructured) ([]InterfaceInfoType, error) {
var infos []InterfaceInfoType
var err error
spec := uobj.Object["spec"].(map[string]interface{})
if v, found := spec["interfaces"]; found {
if vals, ok := v.([]interface{}); ok {
for _, v := range vals {
var info iface.InterfaceInfoType
var info InterfaceInfoType
h.DynamicHandler.Parse(v.(map[string]interface{}), &info)
infos = append(infos, info)
}
Expand All @@ -63,11 +75,24 @@ func (h *HostInterfaceHandler) parse(uobj *unstructured.Unstructured) ([]iface.I
return infos, err
}

func (h *HostInterfaceHandler) GetHostInterfaces() ([]iface.InterfaceInfoType, error) {
func (h *HostInterfaceHandler) GetHostInterfaces() ([]InterfaceInfoType, error) {
hifobj, err := h.DynamicHandler.Get(h.hostName, metav1.NamespaceAll, metav1.GetOptions{})
if err == nil {
infos, err := h.parse(hifobj)
return infos, err
}
return []iface.InterfaceInfoType{}, err
return []InterfaceInfoType{}, err
}

func (h *HostInterfaceHandler) GetUnmanagedHostInterfaces() ([]InterfaceInfoType, error) {
hifobj, err := h.DynamicHandler.Get(h.hostName, metav1.NamespaceAll, metav1.GetOptions{})
if err == nil {
if labels := hifobj.GetLabels(); labels != nil {
if labels[unmanagedLabelName] == "true" {
infos, err := h.parse(hifobj)
return infos, err
}
}
}
return []InterfaceInfoType{}, err
}
Loading
Loading