diff --git a/pkg/controller/manager/nad/controller.go b/pkg/controller/manager/nad/controller.go index da96ea7e..0b142729 100644 --- a/pkg/controller/manager/nad/controller.go +++ b/pkg/controller/manager/nad/controller.go @@ -12,11 +12,13 @@ import ( ctlcniv1 "github.com/harvester/harvester/pkg/generated/controllers/k8s.cni.cncf.io/v1" cniv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" ctlbatchv1 "github.com/rancher/wrangler/pkg/generated/controllers/batch/v1" + "github.com/tidwall/sjson" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/klog/v2" "github.com/harvester/harvester-network-controller/pkg/apis/network.harvesterhci.io" @@ -89,10 +91,65 @@ func Register(ctx context.Context, management *config.Management) error { nads.OnChange(ctx, ControllerName, handler.OnChange) nads.OnRemove(ctx, ControllerName, handler.OnRemove) - + cns.OnChange(ctx, ControllerName, handler.OnCNChange) return nil } +func (h Handler) OnCNChange(_ string, cn *networkv1.ClusterNetwork) (*networkv1.ClusterNetwork, error) { + if cn == nil || cn.DeletionTimestamp != nil { + return nil, nil + } + + // MTU label is not set + curLbMTU := cn.Labels[utils.KeyUplinkMTU] + if curLbMTU == "" { + return nil, nil + } + MTU, err := strconv.Atoi(curLbMTU) + if err != nil { + return nil, fmt.Errorf("cluster network %v has an invalid label %v/%v, error %w", cn.Name, utils.KeyUplinkMTU, curLbMTU, err) + } + // skip + if MTU <= 0 { + klog.Infof("cluster network %v has an unexpected label %v/%v, skip to sync with nad", cn.Name, utils.KeyUplinkMTU, curLbMTU) + return nil, nil + } + + nads, err := h.nadCache.List("", labels.Set(map[string]string{ + utils.KeyClusterNetworkLabel: cn.Name, + }).AsSelector()) + if err != nil { + return nil, fmt.Errorf("failed to list cluster network %v related nads, error %w", cn.Name, err) + } + + // sync with the possible new MTU + for _, nad := range nads { + netConf := &utils.NetConf{} + if err := json.Unmarshal([]byte(nad.Spec.Config), netConf); err != nil { + return nil, fmt.Errorf("failed to Unmarshal nad %v config %v error %w", nad.Name, nad.Spec.Config, err) + } + + // default value or equal + if (MTU == utils.MTUDefault && netConf.MTU == 0) || MTU == netConf.MTU { + continue + } + + // Don't modify the unmarshalled structure and marshal it again because some fields may be lost during unmarshalling. + newConfig, err := sjson.Set(nad.Spec.Config, "mtu", MTU) + if err != nil { + return nil, fmt.Errorf("failed to set nad %v with new MTU %v error %w", nad.Name, MTU, err) + } + nadCopy := nad.DeepCopy() + nadCopy.Spec.Config = newConfig + if _, err := h.nadClient.Update(nadCopy); err != nil { + return nil, err + } + klog.Infof("sync cluster network %v label mtu %v/%v to nad %v", cn.Name, utils.KeyUplinkMTU, curLbMTU, nad.Name) + } + + return nil, nil +} + func (h Handler) OnChange(_ string, nad *cniv1.NetworkAttachmentDefinition) (*cniv1.NetworkAttachmentDefinition, error) { if nad == nil || nad.DeletionTimestamp != nil { return nil, nil diff --git a/pkg/controller/manager/vlanconfig/controller.go b/pkg/controller/manager/vlanconfig/controller.go index d549099f..98500451 100644 --- a/pkg/controller/manager/vlanconfig/controller.go +++ b/pkg/controller/manager/vlanconfig/controller.go @@ -50,7 +50,7 @@ func (h Handler) EnsureClusterNetwork(_ string, vc *networkv1.VlanConfig) (*netw klog.Infof("vlan config %s has been changed, spec: %+v", vc.Name, vc.Spec) - if err := h.ensureClusterNetwork(vc.Spec.ClusterNetwork); err != nil { + if err := h.ensureClusterNetwork(vc); err != nil { return nil, err } return vc, nil @@ -83,16 +83,47 @@ func (h Handler) SetClusterNetworkUnready(_ string, vs *networkv1.VlanStatus) (* return vs, nil } -func (h Handler) ensureClusterNetwork(name string) error { - if _, err := h.cnCache.Get(name); err != nil && !apierrors.IsNotFound(err) { +func (h Handler) ensureClusterNetwork(vc *networkv1.VlanConfig) error { + name := vc.Spec.ClusterNetwork + curCn, err := h.cnCache.Get(name) + if err != nil && !apierrors.IsNotFound(err) { return err - } else if err == nil { + } + + MTU := utils.MTUDefault + if vc.Spec.Uplink.LinkAttrs.MTU != 0 && MTU != vc.Spec.Uplink.LinkAttrs.MTU { + MTU = vc.Spec.Uplink.LinkAttrs.MTU + } + targetLbMTU := fmt.Sprintf("%v", MTU) + + // check if the configured VC MTU value is updated to ClusterNetwork label + if curCn != nil { + curLbMTU := curCn.Labels[utils.KeyUplinkMTU] + if curLbMTU == targetLbMTU { + return nil + } + // update the new MTU + cnCopy := curCn.DeepCopy() + if cnCopy.Labels == nil { + cnCopy.Labels = make(map[string]string, 2) + } + cnCopy.Labels[utils.KeyUplinkMTU] = targetLbMTU + cnCopy.Labels[utils.KeyMTUSourceVlanConfig] = vc.Name + if _, err := h.cnClient.Update(cnCopy); err != nil { + return fmt.Errorf("failed to update cluster network %s label %s with MTU %s error %w", name, utils.KeyUplinkMTU, targetLbMTU, err) + } return nil } // if cn is not existing cn := &networkv1.ClusterNetwork{ - ObjectMeta: metav1.ObjectMeta{Name: name}, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + utils.KeyUplinkMTU: targetLbMTU, + utils.KeyMTUSourceVlanConfig: vc.Name, + }, + }, } if _, err := h.cnClient.Create(cn); err != nil { return err diff --git a/pkg/utils/consts.go b/pkg/utils/consts.go new file mode 100644 index 00000000..a0070474 --- /dev/null +++ b/pkg/utils/consts.go @@ -0,0 +1,7 @@ +package utils + +const ( + MTUDefault = 1500 + MTUMax = 9000 + MTUMin = 1280 // IPv4 does not define; IPv6 requires >= 1280 +) diff --git a/pkg/utils/labels.go b/pkg/utils/labels.go index 9da2b5c9..de15ce2d 100644 --- a/pkg/utils/labels.go +++ b/pkg/utils/labels.go @@ -15,6 +15,8 @@ const ( KeyLastNetworkType = network.GroupName + "/last-type" KeyNetworkReady = network.GroupName + "/ready" KeyNetworkRoute = network.GroupName + "/route" + KeyMTUSourceVlanConfig = network.GroupName + "/mtu-source-vc" // the VC which syncs MTU to CN + KeyUplinkMTU = network.GroupName + "/uplink-mtu" // configured MTU on the VC'uplink KeyMatchedNodes = network.GroupName + "/matched-nodes" diff --git a/pkg/webhook/vlanconfig/validator.go b/pkg/webhook/vlanconfig/validator.go index fd631505..288952f7 100644 --- a/pkg/webhook/vlanconfig/validator.go +++ b/pkg/webhook/vlanconfig/validator.go @@ -247,9 +247,14 @@ func (v *Validator) validateMTU(current *networkv1.VlanConfig) error { continue } if current.Spec.Uplink.LinkAttrs.MTU != vc.Spec.Uplink.LinkAttrs.MTU { - return fmt.Errorf("the MTU is different from network config %s", vc.Name) + return fmt.Errorf("the MTU %v is different from network config %s %v", current.Spec.Uplink.LinkAttrs.MTU, vc.Name, vc.Spec.Uplink.LinkAttrs.MTU) } } + // MTU can be 0, it means user does not input it and the default value is used + if (current.Spec.Uplink.LinkAttrs.MTU < utils.MTUMin && current.Spec.Uplink.LinkAttrs.MTU != 0) || current.Spec.Uplink.LinkAttrs.MTU > utils.MTUMax { + return fmt.Errorf("the MTU %v is out of range [0, %v..%v]", current.Spec.Uplink.LinkAttrs.MTU, utils.MTUMin, utils.MTUMax) + } + return nil }