diff --git a/pkg/admit/netadmit.go b/pkg/admit/netadmit.go index c5443c07..cf267d0f 100644 --- a/pkg/admit/netadmit.go +++ b/pkg/admit/netadmit.go @@ -62,7 +62,7 @@ func (validator *Validator) ValidateNetwork(responseWriter http.ResponseWriter, return } origNewManifest := *newManifest - isManifestValid, err := validateNetworkByType(oldManifest, newManifest, admissionReview.Request.Operation) + isManifestValid, err := validateNetworkByType(oldManifest, newManifest, admissionReview.Request.Operation, validator.Client) if !isManifestValid { SendErroneousAdmissionResponse(responseWriter, admissionReview.Request, err) return @@ -99,13 +99,13 @@ func getNetworkManifest(objectToReview []byte) (*danmtypes.DanmNet,error) { return &networkManifest, nil } -func validateNetworkByType(oldManifest, newManifest *danmtypes.DanmNet, opType v1beta1.Operation) (bool,error) { +func validateNetworkByType(oldManifest, newManifest *danmtypes.DanmNet, opType v1beta1.Operation, client danmclientset.Interface) (bool,error) { validatorMapping, isTypeHandled := danmValidationConfig[newManifest.TypeMeta.Kind] if !isTypeHandled { return false, errors.New("K8s API type:" + newManifest.TypeMeta.Kind + " is not handled by DANM webhook") } for _, validator := range validatorMapping { - err := validator(oldManifest,newManifest,opType) + err := validator(oldManifest,newManifest,opType,client) if err != nil { return false, err } @@ -133,7 +133,7 @@ func mutateNetManifest(danmClient danmclientset.Interface, dnet *danmtypes.DanmN //Example is NetworkID related validations for TenantNetworks //TODO: make this also fancy when more post validation needs surface func postValidateManifest(dnet *danmtypes.DanmNet) error { - return validateNetworkId(nil, dnet, "") + return validateNetworkId(nil, dnet, "", nil) } func CreateAllocationArray(dnet *danmtypes.DanmNet) { diff --git a/pkg/admit/netdel.go b/pkg/admit/netdel.go index e6a8c4cf..9c13bd2b 100644 --- a/pkg/admit/netdel.go +++ b/pkg/admit/netdel.go @@ -5,6 +5,7 @@ import ( "net/http" "k8s.io/api/admission/v1beta1" "github.com/nokia/danm/pkg/confman" + "github.com/nokia/danm/pkg/danmep" ) //A GIGANTIC DISCLAIMER: THIS DOES NOT WORK BEFORE K8S 1.15! @@ -21,6 +22,17 @@ func (validator *Validator) DeleteNetwork(responseWriter http.ResponseWriter, re SendErroneousAdmissionResponse(responseWriter, admissionReview.Request, err) return } + isAnyPodConnectedToNetwork, connectedEp, err := danmep.ArePodsConnectedToNetwork(validator.Client, oldManifest) + if err != nil { + SendErroneousAdmissionResponse(responseWriter, admissionReview.Request, + errors.New("Network cannot be deleted because there is no way to tell if Pods are still using it due to:" + err.Error())) + return + } + if isAnyPodConnectedToNetwork { + SendErroneousAdmissionResponse(responseWriter, admissionReview.Request, + errors.New("Network cannot be deleted because there are Pods still connected to it e.g. Pod:" + connectedEp.Spec.Pod + " in namespace:" + connectedEp.ObjectMeta.Namespace)) + return + } if oldManifest.TypeMeta.Kind == "TenantNetwork" && IsTypeDynamic(oldManifest.Spec.NetworkType) { tconf, err := confman.GetTenantConfig(validator.Client) if err != nil { diff --git a/pkg/admit/validators.go b/pkg/admit/validators.go index cb84711a..b43a3fb4 100644 --- a/pkg/admit/validators.go +++ b/pkg/admit/validators.go @@ -7,6 +7,8 @@ import ( "encoding/binary" admissionv1 "k8s.io/api/admission/v1beta1" danmtypes "github.com/nokia/danm/crd/apis/danm/v1" + danmclientset "github.com/nokia/danm/crd/client/clientset/versioned" + "github.com/nokia/danm/pkg/danmep" "github.com/nokia/danm/pkg/ipam" "k8s.io/kubernetes/pkg/kubelet/cm/cpuset" ) @@ -16,8 +18,8 @@ const ( ) var ( - DanmNetMapping = []ValidatorFunc{validateIpv4Fields,validateIpv6Fields,validateAllocationPool,validateVids,validateNetworkId,validateAbsenceOfAllowedTenants,validateNeType} - ClusterNetMapping = []ValidatorFunc{validateIpv4Fields,validateIpv6Fields,validateAllocationPool,validateVids,validateNetworkId,validateNeType} + DanmNetMapping = []ValidatorFunc{validateIpv4Fields,validateIpv6Fields,validateAllocationPool,validateVids,validateNetworkId,validateAbsenceOfAllowedTenants,validateNeType,validateVniChange} + ClusterNetMapping = []ValidatorFunc{validateIpv4Fields,validateIpv6Fields,validateAllocationPool,validateVids,validateNetworkId,validateNeType,validateVniChange} TenantNetMapping = []ValidatorFunc{validateIpv4Fields,validateIpv6Fields,validateAllocationPool,validateAbsenceOfAllowedTenants,validateTenantNetRules,validateNeType} danmValidationConfig = map[string]ValidatorMapping { "DanmNet": DanmNetMapping, @@ -26,14 +28,14 @@ var ( } ) -type ValidatorFunc func(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation) error +type ValidatorFunc func(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation, client danmclientset.Interface) error type ValidatorMapping []ValidatorFunc -func validateIpv4Fields(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation) error { +func validateIpv4Fields(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation, client danmclientset.Interface) error { return validateIpFields(newManifest.Spec.Options.Cidr, newManifest.Spec.Options.Routes) } -func validateIpv6Fields(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation) error { +func validateIpv6Fields(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation, client danmclientset.Interface) error { return validateIpFields(newManifest.Spec.Options.Net6, newManifest.Spec.Options.Routes6) } @@ -56,7 +58,7 @@ func validateIpFields(cidr string, routes map[string]string) error { return nil } -func validateAllocationPool(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation) error { +func validateAllocationPool(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation, client danmclientset.Interface) error { if opType == admissionv1.Create && newManifest.Spec.Options.Alloc != "" { return errors.New("Allocation bitmask shall not be manually defined upon creation!") } @@ -90,7 +92,7 @@ func GetBroadcastAddress(subnet *net.IPNet) (net.IP) { return ip } -func validateVids(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation) error { +func validateVids(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation, client danmclientset.Interface) error { isVlanDefined := (newManifest.Spec.Options.Vlan!=0) isVxlanDefined := (newManifest.Spec.Options.Vxlan!=0) if isVlanDefined && isVxlanDefined { @@ -99,7 +101,7 @@ func validateVids(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv return nil } -func validateNetworkId(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation) error { +func validateNetworkId(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation, client danmclientset.Interface) error { if newManifest.Spec.NetworkID == "" { return errors.New("Spec.NetworkID mandatory parameter is missing!") } @@ -109,14 +111,14 @@ func validateNetworkId(oldManifest, newManifest *danmtypes.DanmNet, opType admis return nil } -func validateAbsenceOfAllowedTenants(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation) error { +func validateAbsenceOfAllowedTenants(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation, client danmclientset.Interface) error { if newManifest.Spec.AllowedTenants != nil { return errors.New("AllowedTenants attribute is only valid for the ClusterNetwork API!") } return nil } -func validateTenantNetRules(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation) error { +func validateTenantNetRules(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation, client danmclientset.Interface) error { if opType == admissionv1.Create && (newManifest.Spec.Options.Vxlan != 0 || newManifest.Spec.Options.Vlan != 0) { @@ -182,7 +184,7 @@ func validateIfaceConfig(ifaceConf danmtypes.IfaceProfile, opType admissionv1.Op return nil } -func validateNeType(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation) error { +func validateNeType(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation, client danmclientset.Interface) error { if newManifest.Spec.NetworkType == "sriov" { if newManifest.Spec.Options.DevicePool == "" || newManifest.Spec.Options.Device != "" { return errors.New("Spec.Options.device_pool must, and Spec.Options.host_device cannot be provided for SR-IOV networks!") @@ -191,4 +193,22 @@ func validateNeType(oldManifest, newManifest *danmtypes.DanmNet, opType admissio return errors.New("Spec.Options.device_pool and Spec.Options.host_device cannot be provided together!") } return nil +} + +func validateVniChange(oldManifest, newManifest *danmtypes.DanmNet, opType admissionv1.Operation, client danmclientset.Interface) error { + if opType != admissionv1.Update { + return nil + } + isAnyPodConnectedToNetwork, connectedEp, err := danmep.ArePodsConnectedToNetwork(client, newManifest) + if err != nil { + return errors.New("no way to tell if Pods are still using the network due to:" + err.Error()) + } + if !isAnyPodConnectedToNetwork { + return nil + } + if (oldManifest.Spec.Options.Vlan != 0 && (oldManifest.Spec.Options.Vlan != newManifest.Spec.Options.Vlan || oldManifest.Spec.Options.Device != newManifest.Spec.Options.Device)) || + (oldManifest.Spec.Options.Vxlan != 0 && (oldManifest.Spec.Options.Vxlan != newManifest.Spec.Options.Vxlan || oldManifest.Spec.Options.Device != newManifest.Spec.Options.Device)) { + return errors.New("cannot change VNI/host_device of a network which having any Pods connected to it e.g. Pod:" + connectedEp.Spec.Pod + " in namespace:" + connectedEp.ObjectMeta.Namespace) + } + return nil } \ No newline at end of file diff --git a/pkg/danmep/danmep.go b/pkg/danmep/danmep.go index 61a21582..4f0675c9 100644 --- a/pkg/danmep/danmep.go +++ b/pkg/danmep/danmep.go @@ -50,11 +50,13 @@ func DeleteIpvlanInterface(ep danmtypes.DanmEp) (error) { func FindByCid(client danmclientset.Interface, cid string)([]danmtypes.DanmEp, error) { result, err := client.DanmV1().DanmEps("").List(meta_v1.ListOptions{}) if err != nil { - log.Println("cannot get list of eps:" + err.Error()) - return nil, err + return nil, errors.New("cannot list DanmEps because:" + err.Error()) + } + ret := make([]danmtypes.DanmEp, 0) + if result == nil { + return ret, nil } eplist := result.Items - var ret = make([]danmtypes.DanmEp, 0) for _, ep := range eplist { if ep.Spec.CID == cid { ret = append(ret, ep) @@ -68,11 +70,13 @@ func FindByCid(client danmclientset.Interface, cid string)([]danmtypes.DanmEp, e func CidsByHost(client danmclientset.Interface, host string)(map[string]danmtypes.DanmEp, error) { result, err := client.DanmV1().DanmEps("").List(meta_v1.ListOptions{}) if err != nil { - log.Println("cannot get list of eps") - return nil, err + return nil, errors.New("cannot list DanmEps because:" + err.Error()) + } + ret := make(map[string]danmtypes.DanmEp, 0) + if result == nil { + return ret, nil } eplist := result.Items - var ret = make(map[string]danmtypes.DanmEp, 0) for _, ep := range eplist { if ep.Spec.Host == host { ret[ep.Spec.CID] = ep @@ -103,7 +107,7 @@ func DetermineHostDeviceName(dnet *danmtypes.DanmNet) string { return device } -func CreateRoutesInNetNs(ep danmtypes.DanmEp, dnet *danmtypes.DanmNet, ) error { +func CreateRoutesInNetNs(ep danmtypes.DanmEp, dnet *danmtypes.DanmNet) error { runtime.LockOSThread() defer runtime.UnlockOSThread() origNs, err := ns.GetCurrentNS() @@ -196,3 +200,22 @@ func PutDanmEp(danmClient danmclientset.Interface, ep danmtypes.DanmEp) error { } return errors.New("DanmEp creation was supposedly successful, but the object hasn't really appeared within 1 sec") } + +// ArePodsConnectedToNetwork checks if there are any Pods currently in the system using the particular network. +// If there is at least, it returns true, and the spec of the first matching DanmEp. +func ArePodsConnectedToNetwork(client danmclientset.Interface, dnet *danmtypes.DanmNet)(bool, danmtypes.DanmEp, error) { + result, err := client.DanmV1().DanmEps("").List(meta_v1.ListOptions{}) + if err != nil { + return false, danmtypes.DanmEp{}, errors.New("cannot list DanmEps because:" + err.Error()) + } + if result == nil { + return false, danmtypes.DanmEp{}, nil + } + eplist := result.Items + for _, ep := range eplist { + if ep.Spec.ApiType == dnet.TypeMeta.Kind && ep.Spec.NetworkName == dnet.ObjectMeta.Name { + return true, ep, nil + } + } + return false, danmtypes.DanmEp{}, nil +} \ No newline at end of file diff --git a/scm/build/build.sh b/scm/build/build.sh index 56933eb9..ebf54f1f 100755 --- a/scm/build/build.sh +++ b/scm/build/build.sh @@ -1,7 +1,7 @@ #!/bin/sh -ex export GOOS=linux cd $GOPATH/src/github.com/nokia/danm -glide install --strip-vendor +#glide install --strip-vendor go get -d github.com/vishvananda/netlink go get github.com/containernetworking/plugins/pkg/ns go get github.com/golang/groupcache/lru @@ -20,4 +20,4 @@ go install -a -ldflags '-extldflags "-static"' github.com/nokia/danm/cmd/netwatc go install -a -ldflags '-extldflags "-static"' github.com/nokia/danm/cmd/fakeipam go install -a -ldflags '-extldflags "-static"' github.com/nokia/danm/cmd/svcwatcher go install -a -ldflags '-extldflags "-static"' github.com/nokia/danm/cmd/cnitest -go install -a -ldflags '-extldflags "-static"' github.com/nokia/danm/cmd/webhook \ No newline at end of file +go install -a -ldflags '-extldflags "-static"' github.com/nokia/danm/cmd/webhook