Skip to content

Commit d86a394

Browse files
committed
feat: unmanaged hostinterface
Signed-off-by: Sunyanan Choochotkaew <sunyanan.choochotkaew1@ibm.com>
1 parent fe15ab7 commit d86a394

File tree

18 files changed

+355
-203
lines changed

18 files changed

+355
-203
lines changed

config/samples/multinicnetwork/ipvlanl2.yaml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ apiVersion: multinic.fms.io/v1
22
kind: MultiNicNetwork
33
metadata:
44
name: multinic-ipvlanl2
5-
namespace: default
65
spec:
7-
subnet: "172.31.49.0/24"
6+
subnet: "192.168.0.0/16"
87
ipam: |
98
{ "type": "whereabouts" }
109
multiNICIPAM: false
@@ -13,5 +12,3 @@ spec:
1312
type: ipvlan
1413
args:
1514
mode: l2
16-
masterNets:
17-
- "172.31.32.0/20"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: multinic.fms.io/v1
2+
kind: MultiNicNetwork
3+
metadata:
4+
name: multinic-unmanaged
5+
spec:
6+
ipam: |
7+
{
8+
"type": "multi-nic-ipam",
9+
"hostBlock": 0,
10+
"interfaceBlock": 0,
11+
"vlanMode": "l2"
12+
}
13+
multiNICIPAM: true
14+
plugin:
15+
cniVersion: "0.3.0"
16+
type: ipvlan
17+
args:
18+
mode: l2

controllers/cidr_handler.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,11 @@ func (h *CIDRHandler) getCurrentAllocationMap(cidrMap map[string]multinicv1.CIDR
809809

810810
// addNewHost finds new available host index
811811
func (h *CIDRHandler) addNewHost(hosts []multinicv1.HostInterfaceInfo, maxHostIndex int, vlanCIDR string, nodeBlock int, excludes []string) (string, int, error) {
812+
if maxHostIndex == 0 {
813+
// pods use the same cidr with vlan
814+
// podCIDR = vlanCIDR
815+
return vlanCIDR, 0, nil
816+
}
812817
nodeIndex := 0
813818
// excludedIndexes = previously-assigned host indexes
814819
excludedIndexes := []int{}
@@ -991,9 +996,12 @@ func (h *CIDRHandler) GenerateCIDRFromHostSubnet(def multinicv1.PluginConfig) (m
991996
vlanIndexMap := make(map[string]int)
992997
entryMap := make(map[string]multinicv1.CIDREntry)
993998
lastIndex := 0
999+
maxHostIndex := 0
9941000

995-
// maxHostIndex = 2^(host bits) - 1
996-
maxHostIndex := int(math.Pow(2, float64(def.HostBlock)) - 1)
1001+
if def.HostBlock > 0 {
1002+
// maxHostIndex = 2^(host bits) - 1
1003+
maxHostIndex = int(math.Pow(2, float64(def.HostBlock)) - 1)
1004+
}
9971005

9981006
snapshot := h.HostInterfaceHandler.ListCache()
9991007
for hostName, hif := range snapshot {

controllers/cidr_handler_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ var _ = Describe("Test CIDR Handler ", func() {
120120
Expect(len(hostIPs)).To(BeEquivalentTo(len(interfaceNames) * handler.HostInterfaceHandler.GetSize()))
121121

122122
By("Testing with no interfaces")
123+
originalHif := handler.HostInterfaceHandler.ListCache()
123124
// Clear the HostInterface cache to simulate no interfaces
124125
handler.HostInterfaceHandler.SafeCache.Clear()
125126

@@ -128,6 +129,41 @@ var _ = Describe("Test CIDR Handler ", func() {
128129
Expect(len(cidrSpec.CIDRs)).To(Equal(0))
129130
hostIPs = handler.GetHostAddressesToExclude()
130131
Expect(len(hostIPs)).To(BeZero())
132+
133+
By("Setting original hosts")
134+
for k, v := range originalHif {
135+
handler.HostInterfaceHandler.SafeCache.SetCache(k, v)
136+
}
137+
})
138+
139+
It("Empty subnet with unmanaged hostinterface", func() {
140+
unmanagedMultinicnetwork := GetMultiNicCNINetwork("unmanaged", cniVersion, cniType, cniArgs)
141+
unmanagedMultinicnetwork.Spec.Subnet = ""
142+
ipamConfig, err := MultiNicnetworkReconcilerInstance.GetIPAMConfig(unmanagedMultinicnetwork)
143+
Expect(err).NotTo(HaveOccurred())
144+
ipamConfig.InterfaceBlock = 0
145+
ipamConfig.HostBlock = 0
146+
cidrSpec, err := handler.GenerateCIDRFromHostSubnet(*ipamConfig)
147+
Expect(err).NotTo(HaveOccurred())
148+
Expect(len(cidrSpec.CIDRs)).To(Equal(len(interfaceNames)))
149+
hifSnapshot := handler.HostInterfaceHandler.ListCache()
150+
for _, cidr := range cidrSpec.CIDRs {
151+
for _, hostInfo := range cidr.Hosts {
152+
hif, found := hifSnapshot[hostInfo.HostName]
153+
Expect(found).To(Equal(true))
154+
found = false
155+
for _, iface := range hif.Spec.Interfaces {
156+
if hostInfo.InterfaceName == iface.InterfaceName {
157+
netAddr := iface.NetAddress
158+
Expect(cidr.VlanCIDR).To(Equal(netAddr))
159+
Expect(hostInfo.PodCIDR).To(Equal(netAddr))
160+
found = true
161+
break
162+
}
163+
}
164+
Expect(found).To(Equal(true))
165+
}
166+
}
131167
})
132168
})
133169

controllers/daemon_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package controllers
33
import (
44
. "github.com/onsi/ginkgo/v2"
55
. "github.com/onsi/gomega"
6+
67
//+kubebuilder:scaffold:imports
8+
9+
"github.com/foundation-model-stack/multi-nic-cni/internal/vars"
710
)
811

912
func cleanCache() {
@@ -33,4 +36,14 @@ var _ = Describe("Daemon Test", func() {
3336
daemonPods := MultiNicnetworkReconcilerInstance.CIDRHandler.DaemonCacheHandler.ListCache()
3437
Expect(len(daemonPods)).To(Equal(0))
3538
})
39+
40+
It("Test IsUnmanaged function", func() {
41+
newHostName := "unmanagedHost"
42+
newHif := GenerateNewHostInterface(newHostName, interfaceNames, networkPrefixes, 0)
43+
Expect(vars.IsUnmanaged(newHif.ObjectMeta)).To(Equal(false))
44+
newHif.ObjectMeta.Labels[vars.UnmanagedLabelName] = "true"
45+
Expect(vars.IsUnmanaged(newHif.ObjectMeta)).To(Equal(true))
46+
newHif.ObjectMeta.Labels[vars.UnmanagedLabelName] = "false"
47+
Expect(vars.IsUnmanaged(newHif.ObjectMeta)).To(Equal(false))
48+
})
3649
})

controllers/daemon_watcher.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,13 @@ func (w *DaemonWatcher) ProcessPodQueue() {
246246
if errors.IsNotFound(err) {
247247
// deleted, delete HostInterface
248248
w.DaemonCacheHandler.SafeCache.UnsetCache(nodeName)
249-
err := w.HostInterfaceHandler.DeleteHostInterface(nodeName)
250-
if err != nil {
251-
vars.DaemonLog.V(4).Info(fmt.Sprintf("Failed to delete HostInterface %s: %v", nodeName, err))
249+
hif, err := w.HostInterfaceHandler.GetHostInterface(nodeName)
250+
if err == nil && !vars.IsUnmanaged(hif.ObjectMeta) {
251+
// deleted, delete HostInterface
252+
err := w.HostInterfaceHandler.DeleteHostInterface(nodeName)
253+
if err != nil {
254+
vars.DaemonLog.V(4).Info(fmt.Sprintf("Failed to delete HostInterface %s: %v", nodeName, err))
255+
}
252256
}
253257
}
254258
}

controllers/hostinterface_controller.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,14 @@ func InitHostInterfaceCache(clientset *kubernetes.Clientset, hostInterfaceHandle
4848
if _, foundErr := daemonCacheHandler.GetCache(name); foundErr != nil {
4949
// not found, check whether node is still there.
5050
if _, foundErr = clientset.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{}); foundErr != nil {
51-
// delete HostInterface and do not add
52-
err = hostInterfaceHandler.DeleteHostInterface(name)
53-
if err != nil {
54-
vars.HifLog.V(4).Info(fmt.Sprintf("Failed to delete HostInterface %s: %v", name, err))
51+
if !vars.IsUnmanaged(instance.ObjectMeta) {
52+
// delete HostInterface and do not add
53+
err = hostInterfaceHandler.DeleteHostInterface(name)
54+
if err != nil {
55+
vars.HifLog.V(4).Info(fmt.Sprintf("Failed to delete HostInterface %s: %v", name, err))
56+
}
57+
continue
5558
}
56-
continue
5759
}
5860
}
5961
hostInterfaceHandler.SetCache(name, instance)
@@ -92,6 +94,14 @@ func (r *HostInterfaceReconciler) Reconcile(ctx context.Context, req ctrl.Reques
9294
return ctrl.Result{RequeueAfter: vars.UrgentReconcileTime}, nil
9395
}
9496

97+
hifName := instance.GetName()
98+
if vars.IsUnmanaged(instance.ObjectMeta) {
99+
// handle unmanaged hostinterface before adding finalizer
100+
r.HostInterfaceHandler.SetCache(hifName, *instance.DeepCopy())
101+
r.CIDRHandler.UpdateCIDRs()
102+
return ctrl.Result{}, nil
103+
}
104+
95105
// Add finalizer to instance
96106
if !controllerutil.ContainsFinalizer(instance, hifFinalizer) {
97107
controllerutil.AddFinalizer(instance, hifFinalizer)
@@ -125,7 +135,6 @@ func (r *HostInterfaceReconciler) Reconcile(ctx context.Context, req ctrl.Reques
125135
return ctrl.Result{}, nil
126136
}
127137

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

155164
func (r *HostInterfaceReconciler) UpdateInterfaces(instance multinicv1.HostInterface) error {
165+
if vars.IsUnmanaged(instance.ObjectMeta) {
166+
r.CIDRHandler.UpdateCIDRs()
167+
return nil
168+
}
156169
nodeName := instance.Spec.HostName
157170
hifName := instance.GetName()
158171
pod, err := r.DaemonWatcher.TryGetDaemonPod(nodeName)

controllers/hostinterface_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@
66
package controllers_test
77

88
import (
9+
"context"
10+
911
multinicv1 "github.com/foundation-model-stack/multi-nic-cni/api/v1"
1012
"github.com/foundation-model-stack/multi-nic-cni/controllers"
1113
. "github.com/onsi/ginkgo/v2"
1214
. "github.com/onsi/gomega"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/types"
17+
ctrl "sigs.k8s.io/controller-runtime"
1318
)
1419

1520
var _ = Describe("Host Interface Test", func() {
@@ -101,6 +106,32 @@ var _ = Describe("Host Interface Test", func() {
101106
})
102107
})
103108

109+
Context("unmanaged host", func() {
110+
It("can create/delete unmanaged host", func() {
111+
ctx := context.Background()
112+
hostName := "unmanagned"
113+
By("creating")
114+
unmanaged := controllers.GenerateNewUnmanagedHostInterface(hostName)
115+
err := controllers.K8sClient.Create(ctx, &unmanaged)
116+
Expect(err).To(BeNil())
117+
By("reconciling")
118+
namespacedName := types.NamespacedName{Name: hostName, Namespace: metav1.NamespaceAll}
119+
req := ctrl.Request{
120+
NamespacedName: namespacedName,
121+
}
122+
_, err = controllers.HostInterfaceReconcilerInstance.Reconcile(ctx, req)
123+
Expect(err).To(BeNil())
124+
// finalizer must not be added
125+
hif := &multinicv1.HostInterface{}
126+
err = controllers.K8sClient.Get(ctx, namespacedName, hif)
127+
Expect(err).To(BeNil())
128+
Expect(hif.GetFinalizers()).To(HaveLen(0))
129+
By("deleting")
130+
err = controllers.K8sClient.Delete(ctx, &unmanaged)
131+
Expect(err).To(BeNil())
132+
})
133+
})
134+
104135
})
105136

106137
func genInterfaceInfo(devName, netAddress string) multinicv1.InterfaceInfoType {

controllers/suite_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ var mellanoxPlugin *plugin.MellanoxPlugin
5858

5959
var MultiNicnetworkReconcilerInstance *MultiNicNetworkReconciler
6060
var ConfigReconcilerInstance *ConfigReconciler
61+
var HostInterfaceReconcilerInstance *HostInterfaceReconciler
6162
var daemonWatcher *DaemonWatcher
6263

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

151-
err = (&HostInterfaceReconciler{
152+
HostInterfaceReconcilerInstance = &HostInterfaceReconciler{
152153
Client: mgr.GetClient(),
153154
Scheme: mgr.GetScheme(),
154155
HostInterfaceHandler: hostInterfaceHandler,
155156
CIDRHandler: cidrHandler,
156157
DaemonWatcher: daemonWatcher,
157-
}).SetupWithManager(mgr)
158+
}
159+
err = HostInterfaceReconcilerInstance.SetupWithManager(mgr)
158160
Expect(err).ToNot(HaveOccurred())
159161

160162
err = (&IPPoolReconciler{
@@ -352,6 +354,13 @@ func GenerateNewHostInterface(hostName string, interfaceNames []string, networkP
352354
return hif
353355
}
354356

357+
// GenerateNewUnmanagedHostInterface generates empty HostInterface with unmanaged label
358+
func GenerateNewUnmanagedHostInterface(hostName string) multinicv1.HostInterface {
359+
hif := GenerateNewHostInterface(hostName, []string{}, []string{}, 0)
360+
hif.Labels[vars.UnmanagedLabelName] = "true"
361+
return hif
362+
}
363+
355364
// GetMultiNicCNINetwork returns MultiNicNetwork object
356365
func GetMultiNicCNINetwork(name string, cniVersion string, cniType string, cniArgs map[string]string) *multinicv1.MultiNicNetwork {
357366
return &multinicv1.MultiNicNetwork{

daemon/src/backend/hostinterface.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package backend
77

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

@@ -17,11 +16,24 @@ import (
1716
"k8s.io/client-go/rest"
1817
)
1918

19+
type InterfaceInfoType struct {
20+
InterfaceName string `json:"interfaceName"`
21+
NetAddress string `json:"netAddress"`
22+
HostIP string `json:"hostIP"`
23+
Vendor string `json:"vendor"`
24+
Product string `json:"product"`
25+
PciAddress string `json:"pciAddress"`
26+
}
27+
2028
const (
2129
HOSTINTERFACE_RESOURCE = "hostinterfaces.v1.multinic.fms.io"
2230
HOSTINTERFACE_KIND = "hostinterfaces"
2331
)
2432

33+
var (
34+
unmanagedLabelName = "multi-nic-unmanaged"
35+
)
36+
2537
type HostInterfaceHandler struct {
2638
*DynamicHandler
2739
hostName string
@@ -43,14 +55,14 @@ func NewHostInterfaceHandler(config *rest.Config, hostName string) *HostInterfac
4355
return handler
4456
}
4557

46-
func (h *HostInterfaceHandler) parse(uobj *unstructured.Unstructured) ([]iface.InterfaceInfoType, error) {
47-
var infos []iface.InterfaceInfoType
58+
func (h *HostInterfaceHandler) parse(uobj *unstructured.Unstructured) ([]InterfaceInfoType, error) {
59+
var infos []InterfaceInfoType
4860
var err error
4961
spec := uobj.Object["spec"].(map[string]interface{})
5062
if v, found := spec["interfaces"]; found {
5163
if vals, ok := v.([]interface{}); ok {
5264
for _, v := range vals {
53-
var info iface.InterfaceInfoType
65+
var info InterfaceInfoType
5466
h.DynamicHandler.Parse(v.(map[string]interface{}), &info)
5567
infos = append(infos, info)
5668
}
@@ -63,11 +75,24 @@ func (h *HostInterfaceHandler) parse(uobj *unstructured.Unstructured) ([]iface.I
6375
return infos, err
6476
}
6577

66-
func (h *HostInterfaceHandler) GetHostInterfaces() ([]iface.InterfaceInfoType, error) {
78+
func (h *HostInterfaceHandler) GetHostInterfaces() ([]InterfaceInfoType, error) {
6779
hifobj, err := h.DynamicHandler.Get(h.hostName, metav1.NamespaceAll, metav1.GetOptions{})
6880
if err == nil {
6981
infos, err := h.parse(hifobj)
7082
return infos, err
7183
}
72-
return []iface.InterfaceInfoType{}, err
84+
return []InterfaceInfoType{}, err
85+
}
86+
87+
func (h *HostInterfaceHandler) GetUnmanagedHostInterfaces() ([]InterfaceInfoType, error) {
88+
hifobj, err := h.DynamicHandler.Get(h.hostName, metav1.NamespaceAll, metav1.GetOptions{})
89+
if err == nil {
90+
if labels := hifobj.GetLabels(); labels != nil {
91+
if labels[unmanagedLabelName] == "true" {
92+
infos, err := h.parse(hifobj)
93+
return infos, err
94+
}
95+
}
96+
}
97+
return []InterfaceInfoType{}, err
7398
}

0 commit comments

Comments
 (0)