From e72216cee100805b9dbb7a02a9c665c478b60506 Mon Sep 17 00:00:00 2001 From: Levente Kale Date: Sat, 27 Jul 2019 16:58:37 +0200 Subject: [PATCH] Added UTs for admit/netdel.go The things I do for tests... for reservation check added to TconfClient stub. We check if the right VNI in the right iface profile was freed, and also check that nothing else was! --- pkg/confman/confman.go | 2 +- test/stubs/danm/client_stub.go | 31 ++--- test/stubs/danm/clientset_stub.go | 3 +- test/stubs/danm/netclient_stub.go | 8 +- test/stubs/danm/tconfclient_stub.go | 60 ++++++--- test/utils/utils.go | 151 +++++++++++++++++++++- test/uts/admit_tests/confadmit_test.go | 106 ++-------------- test/uts/admit_tests/netdel_test.go | 167 +++++++++++++++++++++++++ test/uts/cnidel_test/cnidel_test.go | 4 +- test/uts/confman_test/confman_test.go | 6 +- test/uts/ipam_test/ipam_test.go | 6 +- 11 files changed, 393 insertions(+), 151 deletions(-) create mode 100644 test/uts/admit_tests/netdel_test.go diff --git a/pkg/confman/confman.go b/pkg/confman/confman.go index eed0829a..b8b553fa 100644 --- a/pkg/confman/confman.go +++ b/pkg/confman/confman.go @@ -16,7 +16,7 @@ func GetTenantConfig(danmClient danmclientset.Interface) (*danmtypes.TenantConfi return nil, err } if reply == nil || len(reply.Items) == 0 { - return nil, errors.New("TenantNetworks cannot be created without provisioning a TenantConfig first!") + return nil, errors.New("no TenantConfigs exist int the cluster") } //TODO: do a namespace based selection later if one generic config does not suffice return &reply.Items[0], nil diff --git a/test/stubs/danm/client_stub.go b/test/stubs/danm/client_stub.go index e6b10453..18c804b4 100644 --- a/test/stubs/danm/client_stub.go +++ b/test/stubs/danm/client_stub.go @@ -2,37 +2,21 @@ package danm import ( client "github.com/nokia/danm/crd/client/clientset/versioned/typed/danm/v1" - danmtypes "github.com/nokia/danm/crd/apis/danm/v1" + "github.com/nokia/danm/test/utils" rest "k8s.io/client-go/rest" ) -type TestArtifacts struct { - TestNets []danmtypes.DanmNet - TestEps []danmtypes.DanmEp - ReservedIps []ReservedIpsList - TestTconfs []danmtypes.TenantConfig -} - -type ReservedIpsList struct { - NetworkId string - Reservations []Reservation -} - -type Reservation struct { - Ip string - Set bool -} - type ClientStub struct { - Objects TestArtifacts + Objects utils.TestArtifacts NetClient *NetClientStub + TconfClient *TconfClientStub } func (client *ClientStub) DanmNets(namespace string) client.DanmNetInterface { if client.NetClient == nil { client.NetClient = newNetClientStub(client.Objects.TestNets, client.Objects.ReservedIps) } - return client.NetClient + return client.NetClient } func (client *ClientStub) DanmEps(namespace string) client.DanmEpInterface { @@ -40,7 +24,10 @@ func (client *ClientStub) DanmEps(namespace string) client.DanmEpInterface { } func (client *ClientStub) TenantConfigs() client.TenantConfigInterface { - return newTconfClientStub(client.Objects.TestTconfs) + if client.TconfClient == nil { + client.TconfClient = newTconfClientStub(client.Objects.TestTconfs, client.Objects.ReservedVnis) + } + return client.TconfClient } func (client *ClientStub) TenantNetworks(namespace string) client.TenantNetworkInterface { @@ -55,7 +42,7 @@ func (c *ClientStub) RESTClient() rest.Interface { return nil } -func newClientStub(ta TestArtifacts) *ClientStub { +func newClientStub(ta utils.TestArtifacts) *ClientStub { return &ClientStub { Objects: ta, } diff --git a/test/stubs/danm/clientset_stub.go b/test/stubs/danm/clientset_stub.go index 8e60cc1b..dd701dd4 100644 --- a/test/stubs/danm/clientset_stub.go +++ b/test/stubs/danm/clientset_stub.go @@ -3,6 +3,7 @@ package danm import ( discovery "k8s.io/client-go/discovery" danmv1 "github.com/nokia/danm/crd/client/clientset/versioned/typed/danm/v1" + "github.com/nokia/danm/test/utils" ) type ClientSetStub struct { @@ -21,7 +22,7 @@ func (c *ClientSetStub) Discovery() discovery.DiscoveryInterface { return nil } -func NewClientSetStub(objects TestArtifacts) *ClientSetStub { +func NewClientSetStub(objects utils.TestArtifacts) *ClientSetStub { var clientSet ClientSetStub clientSet.DanmClient = newClientStub(objects) return &clientSet diff --git a/test/stubs/danm/netclient_stub.go b/test/stubs/danm/netclient_stub.go index 7dd7ec05..fd89e690 100644 --- a/test/stubs/danm/netclient_stub.go +++ b/test/stubs/danm/netclient_stub.go @@ -11,18 +11,20 @@ import ( "github.com/nokia/danm/pkg/bitarray" "github.com/nokia/danm/pkg/datastructs" "github.com/nokia/danm/pkg/ipam" + "github.com/nokia/danm/test/utils" ) + const ( magicVersion = "42" ) type NetClientStub struct{ TestNets []danmtypes.DanmNet - ReservedIpsList []ReservedIpsList + ReservedIpsList []utils.ReservedIpsList TimesUpdateWasCalled int } -func newNetClientStub(nets []danmtypes.DanmNet, ips []ReservedIpsList) *NetClientStub { +func newNetClientStub(nets []danmtypes.DanmNet, ips []utils.ReservedIpsList) *NetClientStub { return &NetClientStub{TestNets: nets, ReservedIpsList: ips} } @@ -102,6 +104,6 @@ func (netClient *NetClientStub) Patch(name string, pt types.PatchType, data []by return nil, nil } -func (netClient *NetClientStub) AddReservedIpsList(reservedIps []ReservedIpsList) { +func (netClient *NetClientStub) AddReservedIpsList(reservedIps []utils.ReservedIpsList) { netClient.ReservedIpsList = reservedIps } diff --git a/test/stubs/danm/tconfclient_stub.go b/test/stubs/danm/tconfclient_stub.go index 2920053f..482bffc5 100644 --- a/test/stubs/danm/tconfclient_stub.go +++ b/test/stubs/danm/tconfclient_stub.go @@ -2,42 +2,67 @@ package danm import ( "errors" + "strconv" "strings" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" danmtypes "github.com/nokia/danm/crd/apis/danm/v1" + "github.com/nokia/danm/pkg/bitarray" + "github.com/nokia/danm/test/utils" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" ) - + type TconfClientStub struct{ - testTconfs []danmtypes.TenantConfig + TestTconfs []danmtypes.TenantConfig + ReservedVnis []utils.ReservedVnisList + TimesUpdateWasCalled int } -func newTconfClientStub(tconfs []danmtypes.TenantConfig) TconfClientStub { - return TconfClientStub{testTconfs: tconfs} +func newTconfClientStub(tconfs []danmtypes.TenantConfig, vnis []utils.ReservedVnisList) *TconfClientStub { + return &TconfClientStub{TestTconfs: tconfs, ReservedVnis: vnis} } - -func (tconfClient TconfClientStub) Create(obj *danmtypes.TenantConfig) (*danmtypes.TenantConfig, error) { + +func (tconfClient *TconfClientStub) Create(obj *danmtypes.TenantConfig) (*danmtypes.TenantConfig, error) { return nil, nil } -func (tconfClient TconfClientStub) Update(obj *danmtypes.TenantConfig) (*danmtypes.TenantConfig, error) { +func (tconfClient *TconfClientStub) Update(obj *danmtypes.TenantConfig) (*danmtypes.TenantConfig, error) { + tconfClient.TimesUpdateWasCalled++ + for _, vniReservation := range tconfClient.ReservedVnis { + for index, hostProfile := range obj.HostDevices { + if hostProfile.Name == vniReservation.ProfileName && hostProfile.VniType == vniReservation.VniType { + ba := bitarray.NewBitArrayFromBase64(hostProfile.Alloc) + for _, reservation := range vniReservation.Reservations { + if !ba.Get(uint32(reservation.Vni)) && reservation.Set { + return nil, errors.New("Reservation failure, VNI:" + strconv.Itoa(reservation.Vni) + " should have been reserved in TenantConfig:" + obj.ObjectMeta.Name + " profile no:" + strconv.Itoa(index)) + } + if ba.Get(uint32(reservation.Vni)) && !reservation.Set { + return nil, errors.New("Reservation failure, VNI:" + strconv.Itoa(reservation.Vni) + " should have been free in TenantConfig:" + obj.ObjectMeta.Name + " profile no.:" + strconv.Itoa(index)) + } + } + } else { + if hostProfile.Alloc != utils.ExhaustedAllocFor5k { + return nil, errors.New("Unexpected VNI was freed in Interface profile named:" + hostProfile.Name) + } + } + } + } if strings.HasPrefix(obj.ObjectMeta.Name,"error") { return nil, errors.New("here you go") } return &danmtypes.TenantConfig{}, nil } -func (tconfClient TconfClientStub) Delete(name string, options *meta_v1.DeleteOptions) error { +func (tconfClient *TconfClientStub) Delete(name string, options *meta_v1.DeleteOptions) error { return nil } -func (tconfClient TconfClientStub) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error { +func (tconfClient *TconfClientStub) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error { return nil } -func (tconfClient TconfClientStub) Get(tconfNameName string, options meta_v1.GetOptions) (*danmtypes.TenantConfig, error) { - for _, tconf := range tconfClient.testTconfs { +func (tconfClient *TconfClientStub) Get(tconfNameName string, options meta_v1.GetOptions) (*danmtypes.TenantConfig, error) { + for _, tconf := range tconfClient.TestTconfs { if tconf.ObjectMeta.Name == tconfNameName { return &tconf, nil } @@ -45,23 +70,22 @@ func (tconfClient TconfClientStub) Get(tconfNameName string, options meta_v1.Get return nil, nil } -func (tconfClient TconfClientStub) Watch(opts meta_v1.ListOptions) (watch.Interface, error) { +func (tconfClient *TconfClientStub) Watch(opts meta_v1.ListOptions) (watch.Interface, error) { watch := watch.NewEmptyWatch() return watch, nil } -func (tconfClient TconfClientStub) List(opts meta_v1.ListOptions) (*danmtypes.TenantConfigList, error) { - if tconfClient.testTconfs == nil { +func (tconfClient *TconfClientStub) List(opts meta_v1.ListOptions) (*danmtypes.TenantConfigList, error) { + if tconfClient.TestTconfs == nil { return nil, nil } - if strings.HasPrefix(tconfClient.testTconfs[0].ObjectMeta.Name,"error") { + if strings.HasPrefix(tconfClient.TestTconfs[0].ObjectMeta.Name,"error") && !strings.Contains(tconfClient.TestTconfs[0].ObjectMeta.Name,"update"){ return nil, errors.New("error happened") } - tconfList := danmtypes.TenantConfigList{Items: tconfClient.testTconfs } + tconfList := danmtypes.TenantConfigList{Items: tconfClient.TestTconfs } return &tconfList, nil } -func (tconfClient TconfClientStub) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *danmtypes.TenantConfig, err error) { +func (tconfClient *TconfClientStub) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *danmtypes.TenantConfig, err error) { return nil, nil } - diff --git a/test/utils/utils.go b/test/utils/utils.go index 30a36ce7..a30ba1f7 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -1,13 +1,19 @@ package utils import ( + "bytes" + "errors" "net" "strings" + "encoding/json" + "io/ioutil" + "net/http" danmtypes "github.com/nokia/danm/crd/apis/danm/v1" "github.com/nokia/danm/pkg/bitarray" "github.com/nokia/danm/pkg/ipam" "github.com/nokia/danm/pkg/admit" - stubs "github.com/nokia/danm/test/stubs/danm" + httpstub "github.com/nokia/danm/test/stubs/http" + "k8s.io/api/admission/v1beta1" ) const ( @@ -21,6 +27,43 @@ const ( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" ) +var ( + ExhaustedAllocFor5k = exhaustAlloc(AllocFor5k) +) + +type TestArtifacts struct { + TestNets []danmtypes.DanmNet + TestEps []danmtypes.DanmEp + ReservedIps []ReservedIpsList + TestTconfs []danmtypes.TenantConfig + ReservedVnis []ReservedVnisList +} + +type ReservedIpsList struct { + NetworkId string + Reservations []Reservation +} + +type Reservation struct { + Ip string + Set bool +} + +type ReservedVnisList struct { + ProfileName string + VniType string + Reservations []VniReservation +} + +type VniReservation struct { + Vni int + Set bool +} + +type MalformedObject struct { + ExtraField string `json:"extraField,omitempty"` +} + func SetupAllocationPools(nets []danmtypes.DanmNet) error { for index, dnet := range nets { if dnet.Spec.Options.Cidr != "" { @@ -56,16 +99,26 @@ func GetTestNet(netId string, testNets []danmtypes.DanmNet) *danmtypes.DanmNet { return nil } -func CreateExpectedAllocationsList(ip string, isExpectedToBeSet bool, networkId string) []stubs.ReservedIpsList { - var ips []stubs.ReservedIpsList +func CreateExpectedAllocationsList(ip string, isExpectedToBeSet bool, networkId string) []ReservedIpsList { + var ips []ReservedIpsList if ip != "" { - reservation := stubs.Reservation {Ip: ip, Set: isExpectedToBeSet,} - expectedAllocation := stubs.ReservedIpsList{NetworkId: networkId, Reservations: []stubs.Reservation {reservation,},} + reservation := Reservation {Ip: ip, Set: isExpectedToBeSet,} + expectedAllocation := ReservedIpsList{NetworkId: networkId, Reservations: []Reservation {reservation,},} ips = append(ips, expectedAllocation) } return ips } +func CreateExpectedVniAllocationsList(vni int, vniType, ifaceName string, isExpectedToBeSet bool) []ReservedVnisList { + var vnis []ReservedVnisList + if vni != 0 { + reservation := VniReservation {Vni: vni, Set: isExpectedToBeSet,} + expectedAllocation := ReservedVnisList{ProfileName: ifaceName, VniType: vniType, Reservations: []VniReservation {reservation,},} + vnis = append(vnis, expectedAllocation) + } + return vnis +} + func exhaustNetwork(netInfo *danmtypes.DanmNet) { ba := bitarray.NewBitArrayFromBase64(netInfo.Spec.Options.Alloc) _, ipnet, _ := net.ParseCIDR(netInfo.Spec.Options.Cidr) @@ -86,3 +139,91 @@ func GetTconf(tconfName string, tconfSet []danmtypes.TenantConfig) *danmtypes.Te } return nil } + +func CreateHttpRequest(oldObj, newObj []byte, isOldMalformed, isNewMalformed bool, opType v1beta1.Operation) (*http.Request, error) { + request := v1beta1.AdmissionRequest{} + review := v1beta1.AdmissionReview{Request: &request} + if opType != "" { + review.Request.Operation = opType + } + var err error + if oldObj != nil { + review.Request.OldObject.Raw = canItMalform(oldObj, isOldMalformed) + } + if newObj != nil { + review.Request.Object.Raw = canItMalform(newObj, isNewMalformed) + } + httpRequest := http.Request{} + if oldObj != nil || newObj != nil { + rawReview, err := json.Marshal(review) + if err != nil { + errors.New("AdmissionReview couldn't be marshalled because:" + err.Error()) + } + reader := bytes.NewReader(rawReview) + httpRequest.Body = ioutil.NopCloser(reader) + } + return &httpRequest, err +} + +func canItMalform(obj []byte, shouldBeMalformed bool) []byte { + if shouldBeMalformed { + malformedObj := MalformedObject{ExtraField: "blupp"} + obj, _ = json.Marshal(malformedObj) + } + return obj +} + +func ValidateHttpResponse(writer *httpstub.ResponseWriterStub, isErrorExpected bool, expectedPatches string) error { + if writer.RespHeader.Get("Content-Type") != "application/json" { + return errors.New("Content-Type is not set to application/json in the HTTP Header") + } + response, err := writer.GetAdmissionResponse() + if err != nil { + return err + } + if isErrorExpected { + if response.Allowed { + return errors.New("request would have been admitted but we expected an error") + } + if response.Result.Message == "" { + return errors.New("a faulty response was sent without explanation") + } + } else { + if !response.Allowed { + return errors.New("request would have been denied but we expected it to pass through validation") + } + if response.Result != nil { + return errors.New("an unnecessary Result message is put into a successful response") + } + } + if expectedPatches != "" { + return validatePatches(response, expectedPatches) + } + return nil +} + +func validatePatches(response *v1beta1.AdmissionResponse, expectedPatches string) error { + if expectedPatches == "empty" { + if response.Patch != nil { + return errors.New("did not expect any patches but some were included in the admission response") + } + return nil + } + var patches []admit.Patch + err := json.Unmarshal(response.Patch, &patches) + if err != nil { + return err + } + if len(patches) != 1 { + return errors.New("received number of patches was not the expected 1") + } + return nil +} + +func exhaustAlloc(alloc string) string { + ba := bitarray.NewBitArrayFromBase64(alloc) + for i:=0; i