Skip to content

Commit

Permalink
Test subnet controller (#177)
Browse files Browse the repository at this point in the history
  • Loading branch information
LimKianAn authored Oct 28, 2021
1 parent df15dbf commit e83d486
Show file tree
Hide file tree
Showing 2 changed files with 269 additions and 0 deletions.
165 changes: 165 additions & 0 deletions controllers/network/subnet_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright (c) 2021 by the OnMetal authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package network

import (
"time"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"inet.af/netaddr"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/onmetal/onmetal-api/apis/common/v1alpha1"
networkv1alpha1 "github.com/onmetal/onmetal-api/apis/network/v1alpha1"
)

var _ = Describe("subnet controller", func() {
Context("Reconcile", func() {
It("sets the owner Subnet as a Controller OwnerReference on the controlled IPAMRange", func() {
subnet := newSubnet("owner")
ipamRng := newIPAMRange(subnet)
ipamRngKey := objectKey(ipamRng)

Expect(k8sClient.Create(ctx, subnet)).Should(Succeed())
Eventually(func() error {
return notFoundOrSucceed(k8sClient.Get(ctx, ipamRngKey, ipamRng))
}, timeout, interval).Should(Succeed())

Expect(ipamRng.OwnerReferences).To(ContainElement(controllerReference(subnet)))
})

It("reconciles a Subnet without parent", func() {
subnet := newSubnet("no-parant")
ipamRng := newIPAMRange(subnet)

subnetKey := objectKey(subnet)
ipamRngKey := objectKey(ipamRng)

By("creating a Subnet without parent")
Expect(k8sClient.Create(ctx, subnet)).Should(Succeed())
Eventually(func() error {
return notFoundOrSucceed(k8sClient.Get(ctx, ipamRngKey, ipamRng))
}, timeout, interval).Should(Succeed())

By("wating for the status of the owned IPAMRange to have an allocated CIDR")
Eventually(func() []v1alpha1.CIDR {
Expect(k8sClient.Get(ctx, ipamRngKey, ipamRng)).Should(Succeed())
return ipamRng.Spec.CIDRs
}, timeout, interval).Should(ContainElement(subnet.Spec.Ranges[0].CIDR))

By("waiting for the status of the Subnet to become up")
Eventually(func() networkv1alpha1.SubnetState {
Expect(k8sClient.Get(ctx, subnetKey, subnet)).Should(Succeed())
return subnet.Status.State
}, timeout, interval).Should(Equal(networkv1alpha1.SubnetStateUp))
})

It("reconciles a subnet with parent", func() {
parentNet := newSubnet("parent")
childNet := newSubnetWithParent("child", "parent")
childRng := newIPAMRange(childNet)

childNetKey := objectKey(childNet)
childRngKey := objectKey(childRng)

By("creating a pair of parent and child Subnet")
Expect(k8sClient.Create(ctx, parentNet)).Should(Succeed())
Expect(k8sClient.Create(ctx, childNet)).Should(Succeed())
Eventually(func() error {
return notFoundOrSucceed(k8sClient.Get(ctx, childRngKey, childRng))
}, timeout, interval).Should(Succeed())

By("wating for the spec of the child IPAMRange to be patched")
Eventually(func() *networkv1alpha1.IPAMRangeSpec {
Expect(k8sClient.Get(ctx, childRngKey, childRng)).Should(Succeed())
return &childRng.Spec
}, timeout, interval).Should(Equal(ipamRangeSpec(childNet)))

By("waiting for the status of the Subnet to be become up")
Eventually(func() networkv1alpha1.SubnetState {
Expect(k8sClient.Get(ctx, childNetKey, childNet)).Should(Succeed())
return childNet.Status.State
}, timeout, interval).Should(Equal(networkv1alpha1.SubnetStateUp))
})
})
})

const (
// test data
ipPrefix = "192.168.0.0/24"
ns = "default" // namespace

// ginkgo
interval = time.Millisecond * 250
timeout = time.Second * 10
)

var objectKey = client.ObjectKeyFromObject

func controllerReference(subnet *networkv1alpha1.Subnet) metav1.OwnerReference {
return metav1.OwnerReference{
APIVersion: networkv1alpha1.GroupVersion.String(),
Kind: networkv1alpha1.SubnetGK.Kind,
Name: subnet.Name,
UID: subnet.UID,
BlockOwnerDeletion: pointer.BoolPtr(true),
Controller: pointer.BoolPtr(true),
}
}

func ipamRangeSpec(subnet *networkv1alpha1.Subnet) *networkv1alpha1.IPAMRangeSpec {
rngSpec := &networkv1alpha1.IPAMRangeSpec{}
rngSpec.Parent = &corev1.LocalObjectReference{Name: networkv1alpha1.SubnetIPAMName(subnet.Spec.Parent.Name)}
rngSpec.Requests = []networkv1alpha1.IPAMRangeRequest{{CIDR: &subnet.Spec.Ranges[0].CIDR}}
return rngSpec
}

func newIPAMRange(sub *networkv1alpha1.Subnet) *networkv1alpha1.IPAMRange {
rng := &networkv1alpha1.IPAMRange{}
rng.Namespace = sub.Namespace
rng.Name = networkv1alpha1.SubnetIPAMName(sub.Name)
return rng
}

func newSubnet(name string) *networkv1alpha1.Subnet {
subnet := &networkv1alpha1.Subnet{}
subnet.APIVersion = networkv1alpha1.GroupVersion.String()
subnet.Kind = networkv1alpha1.SubnetGK.Kind
subnet.Namespace = ns
subnet.Name = name

ipPrefix, err := netaddr.ParseIPPrefix(ipPrefix)
Expect(err).ToNot(HaveOccurred())
subnet.Spec.Ranges = []networkv1alpha1.RangeType{{CIDR: v1alpha1.CIDR{IPPrefix: ipPrefix}}}
return subnet
}

func newSubnetWithParent(name, parentName string) *networkv1alpha1.Subnet {
subnet := newSubnet(name)
subnet.Spec.Parent = &corev1.LocalObjectReference{Name: parentName}
return subnet
}

func notFoundOrSucceed(err error) error {
Expect(apierrors.IsNotFound(err) || err == nil).To(BeTrue(), "error is `not found` or nil")
return err
}
104 changes: 104 additions & 0 deletions controllers/network/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2021 by the OnMetal authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package network

import (
"context"
"path/filepath"
"testing"

ctrl "sigs.k8s.io/controller-runtime"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

networkv1alpha1 "github.com/onmetal/onmetal-api/apis/network/v1alpha1"
//+kubebuilder:scaffold:imports
)

var (
cancel context.CancelFunc
ctx context.Context
k8sClient client.Client
testEnv *envtest.Environment
)

func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)

RunSpecs(t, "Newwork Controller Suite")
}

var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))

ctx, cancel = context.WithCancel(context.Background())

By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
ErrorIfCRDPathMissing: true,
}

cfg, err := testEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())

Expect(networkv1alpha1.AddToScheme(scheme.Scheme)).Should(Succeed())

// Init package-level k8sClient
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())

k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme.Scheme,
MetricsBindAddress: "0",
})
Expect(err).ToNot(HaveOccurred())

// Register reconcilers
err = (&SubnetReconciler{
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
}).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())

err = (&IPAMRangeReconciler{
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
}).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())

go func() {
defer GinkgoRecover()
err = k8sManager.Start(ctx)
Expect(err).ToNot(HaveOccurred())
}()
}, 60)

var _ = AfterSuite(func() {
cancel()
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).NotTo(HaveOccurred())
})

0 comments on commit e83d486

Please sign in to comment.