From 545f05525c710a1f7f5b34c47ee6396d223581bc Mon Sep 17 00:00:00 2001 From: nicklesimba Date: Wed, 15 Jun 2022 19:01:26 -0500 Subject: [PATCH] Moved IP Reconciler code into IP Control Loop Signed-off-by: nicklesimba --- .github/CODEOWNERS | 3 +- Dockerfile | 1 - Dockerfile.arm64 | 1 - Dockerfile.openshift | 4 +- README.md | 3 +- cmd/controlloop/{main.go => controlloop.go} | 53 +++++- cmd/reconciler/errors.go | 8 - cmd/reconciler/ip.go | 48 ----- doc/crds/ip-reconciler-job.yaml | 41 ---- doc/extended-configuration.md | 6 + hack/build-go.sh | 1 - hack/e2e-setup-kind-cluster.sh | 2 +- pkg/config/config.go | 81 ++++---- pkg/config/config_test.go | 28 +++ .../controlloop/dummy_controller.go | 0 .../controlloop/entity_generators.go | 0 pkg/{reconciler => }/controlloop/pod.go | 0 .../controlloop/pod_controller_test.go | 0 pkg/reconciler/ip.go | 45 +++++ {cmd => pkg}/reconciler/ip_test.go | 158 +++++++++++++++- pkg/reconciler/iploop_test.go | 153 --------------- {cmd => pkg}/reconciler/suite_test.go | 14 +- pkg/types/types.go | 177 +++++++++--------- script/install-cni.sh | 3 +- 24 files changed, 420 insertions(+), 410 deletions(-) rename cmd/controlloop/{main.go => controlloop.go} (74%) delete mode 100644 cmd/reconciler/errors.go delete mode 100644 cmd/reconciler/ip.go delete mode 100644 doc/crds/ip-reconciler-job.yaml rename pkg/{reconciler => }/controlloop/dummy_controller.go (100%) rename pkg/{reconciler => }/controlloop/entity_generators.go (100%) rename pkg/{reconciler => }/controlloop/pod.go (100%) rename pkg/{reconciler => }/controlloop/pod_controller_test.go (100%) create mode 100644 pkg/reconciler/ip.go rename {cmd => pkg}/reconciler/ip_test.go (71%) delete mode 100644 pkg/reconciler/iploop_test.go rename {cmd => pkg}/reconciler/suite_test.go (94%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3e6a020f6..037108fce 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,6 @@ # https://help.github.com/en/articles/about-code-owners * @dougbtv cmd/controlloop @maiqueb -cmd/reconciler @maiqueb e2e/ @maiqueb pkg/reconciler/ @maiqueb - +pkg/controlloop @maiqueb diff --git a/Dockerfile b/Dockerfile index 460d918be..e5660020a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,5 @@ FROM alpine:latest LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/whereabouts COPY --from=0 /go/src/github.com/k8snetworkplumbingwg/whereabouts/bin/whereabouts . COPY --from=0 /go/src/github.com/k8snetworkplumbingwg/whereabouts/bin/ip-control-loop . -COPY --from=0 /go/src/github.com/k8snetworkplumbingwg/whereabouts/bin/ip-reconciler . COPY script/install-cni.sh . CMD ["/install-cni.sh"] diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 7030a6284..e1713143a 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -13,6 +13,5 @@ FROM arm64v8/alpine:latest LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/whereabouts COPY --from=0 /go/src/github.com/k8snetworkplumbingwg/whereabouts/bin/whereabouts . COPY --from=0 /go/src/github.com/k8snetworkplumbingwg/whereabouts/bin/ip-control-loop . -COPY --from=0 /go/src/github.com/k8snetworkplumbingwg/whereabouts/bin/ip-reconciler . COPY script/install-cni.sh . CMD ["/install-cni.sh"] diff --git a/Dockerfile.openshift b/Dockerfile.openshift index bb08d83d5..16e5efe4e 100644 --- a/Dockerfile.openshift +++ b/Dockerfile.openshift @@ -5,15 +5,13 @@ WORKDIR /go/src/github.com/k8snetworkplumbingwg/whereabouts ENV CGO_ENABLED=1 ENV GO111MODULE=on RUN go build -mod vendor -o bin/whereabouts cmd/whereabouts.go -RUN go build -mod vendor -o bin/ip-reconciler cmd/reconciler/ip.go cmd/reconciler/errors.go -RUN go build -mod vendor -o bin/ip-control-loop cmd/controlloop/main.go +RUN go build -mod vendor -o bin/ip-control-loop cmd/controlloop/controlloop.go WORKDIR / FROM openshift/origin-base RUN mkdir -p /usr/src/whereabouts/images && \ mkdir -p /usr/src/whereabouts/bin COPY --from=builder /go/src/github.com/k8snetworkplumbingwg/whereabouts/bin/whereabouts /usr/src/whereabouts/bin -COPY --from=builder /go/src/github.com/k8snetworkplumbingwg/whereabouts/bin/ip-reconciler /usr/src/whereabouts/bin COPY --from=builder /go/src/github.com/k8snetworkplumbingwg/whereabouts/bin/ip-control-loop /usr/src/whereabouts/bin LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/whereabouts diff --git a/README.md b/README.md index 144fc68e5..28fbed65b 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,7 @@ git clone https://github.com/k8snetworkplumbingwg/whereabouts && cd whereabouts kubectl apply \ -f doc/crds/daemonset-install.yaml \ -f doc/crds/whereabouts.cni.cncf.io_ippools.yaml \ - -f doc/crds/whereabouts.cni.cncf.io_overlappingrangeipreservations.yaml \ - -f doc/crds/ip-reconciler-job.yaml + -f doc/crds/whereabouts.cni.cncf.io_overlappingrangeipreservations.yaml ``` The daemonset installation requires Kubernetes Version 1.16 or later. diff --git a/cmd/controlloop/main.go b/cmd/controlloop/controlloop.go similarity index 74% rename from cmd/controlloop/main.go rename to cmd/controlloop/controlloop.go index 30482f43b..257cb69b6 100644 --- a/cmd/controlloop/main.go +++ b/cmd/controlloop/controlloop.go @@ -5,7 +5,9 @@ import ( "fmt" "os" "os/signal" + "time" + gocron "github.com/go-co-op/gocron" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" @@ -21,17 +23,23 @@ import ( wbclient "github.com/k8snetworkplumbingwg/whereabouts/pkg/client/clientset/versioned" wbinformers "github.com/k8snetworkplumbingwg/whereabouts/pkg/client/informers/externalversions" + "github.com/k8snetworkplumbingwg/whereabouts/pkg/config" + "github.com/k8snetworkplumbingwg/whereabouts/pkg/controlloop" "github.com/k8snetworkplumbingwg/whereabouts/pkg/logging" - "github.com/k8snetworkplumbingwg/whereabouts/pkg/reconciler/controlloop" + "github.com/k8snetworkplumbingwg/whereabouts/pkg/reconciler" + "github.com/k8snetworkplumbingwg/whereabouts/pkg/types" ) const ( allNamespaces = "" - controllerName = "pod-ip-reconciler" + controllerName = "pod-ip-controlloop" ) const ( couldNotCreateController = 1 + couldNotReadFlatfile = 1 + couldNotGetFlatIPAM = 1 + cronExpressionError = 1 ) const ( @@ -46,7 +54,9 @@ func main() { logging.SetLogStderr(true) stopChan := make(chan struct{}) + errorChan := make(chan error) defer close(stopChan) + defer close(errorChan) handleSignals(stopChan, os.Interrupt) networkController, err := newPodController(stopChan) @@ -57,8 +67,34 @@ func main() { networkController.Start(stopChan) defer networkController.Shutdown() - <-stopChan - logging.Verbosef("shutting down network controller") + + s := gocron.NewScheduler(time.UTC) + schedule := cronExpressionFromFlatFile() + + _, err = s.Cron(schedule).Do(func() { // user configurable cron expression in install-cni.sh + reconciler.ReconcileIPs(errorChan) + }) + if err != nil { + _ = logging.Errorf("error with cron expression schedule: %v", err) + os.Exit(cronExpressionError) + } + + s.StartAsync() + + for { + select { + case <-stopChan: + logging.Verbosef("shutting down network controller") + s.Stop() + return + case err := <-errorChan: + if err == nil { + logging.Verbosef("reconciler success") + } else { + logging.Verbosef("reconciler failure: %s", err) + } + } + } } func handleSignals(stopChannel chan struct{}, signals ...os.Signal) { @@ -133,3 +169,12 @@ func newEventBroadcaster(k8sClientset kubernetes.Interface) record.EventBroadcas func newEventRecorder(broadcaster record.EventBroadcaster) record.EventRecorder { return broadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerName}) } + +func cronExpressionFromFlatFile() string { + flatipam, _, err := config.GetFlatIPAM(true, &types.IPAMConfig{}, "") + if err != nil { + _ = logging.Errorf("could not get flatipam: %v", err) + os.Exit(couldNotGetFlatIPAM) + } + return flatipam.IPAM.ReconcilerCronExpression +} diff --git a/cmd/reconciler/errors.go b/cmd/reconciler/errors.go deleted file mode 100644 index 1046e6519..000000000 --- a/cmd/reconciler/errors.go +++ /dev/null @@ -1,8 +0,0 @@ -package main - -const ( - kubeconfigNotFound = iota + 1 - couldNotStartOrphanedIPMonitor - failedToReconcileIPPools - failedToReconcileClusterWideIPs -) diff --git a/cmd/reconciler/ip.go b/cmd/reconciler/ip.go deleted file mode 100644 index 7407034bd..000000000 --- a/cmd/reconciler/ip.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "context" - "flag" - "os" - - "github.com/k8snetworkplumbingwg/whereabouts/pkg/logging" - "github.com/k8snetworkplumbingwg/whereabouts/pkg/reconciler" -) - -const defaultReconcilerTimeout = 30 - -func main() { - kubeConfigFile := flag.String("kubeconfig", "", "the path to the Kubernetes configuration file") - logLevel := flag.String("log-level", "error", "the logging level for the `ip-reconciler` app. Valid values are: \"debug\", \"verbose\", \"error\", and \"panic\".") - reconcilerTimeout := flag.Int("timeout", defaultReconcilerTimeout, "the value for a request timeout in seconds.") - flag.Parse() - - logging.SetLogLevel(*logLevel) - - var err error - var ipReconcileLoop *reconciler.ReconcileLooper - if kubeConfigFile == nil { - ipReconcileLoop, err = reconciler.NewReconcileLooper(context.Background(), *reconcilerTimeout) - } else { - ipReconcileLoop, err = reconciler.NewReconcileLooperWithKubeconfig(context.Background(), *kubeConfigFile, *reconcilerTimeout) - } - if err != nil { - _ = logging.Errorf("failed to create the reconcile looper: %v", err) - os.Exit(couldNotStartOrphanedIPMonitor) - } - - cleanedUpIps, err := ipReconcileLoop.ReconcileIPPools(context.Background()) - if err != nil { - _ = logging.Errorf("failed to clean up IP for allocations: %v", err) - os.Exit(failedToReconcileIPPools) - } - if len(cleanedUpIps) > 0 { - logging.Debugf("successfully cleanup IPs: %+v", cleanedUpIps) - } else { - logging.Debugf("no IP addresses to cleanup") - } - - if err := ipReconcileLoop.ReconcileOverlappingIPAddresses(context.Background()); err != nil { - os.Exit(failedToReconcileClusterWideIPs) - } -} diff --git a/doc/crds/ip-reconciler-job.yaml b/doc/crds/ip-reconciler-job.yaml deleted file mode 100644 index 247a6498d..000000000 --- a/doc/crds/ip-reconciler-job.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: ip-reconciler - namespace: kube-system - labels: - tier: node - app: whereabouts -spec: - concurrencyPolicy: Forbid - successfulJobsHistoryLimit: 0 - schedule: "*/5 * * * *" - jobTemplate: - spec: - backoffLimit: 0 - ttlSecondsAfterFinished: 300 - template: - metadata: - labels: - app: whereabouts - spec: - priorityClassName: "system-node-critical" - serviceAccountName: whereabouts - containers: - - name: whereabouts - image: ghcr.io/k8snetworkplumbingwg/whereabouts:latest-amd64 - resources: - requests: - cpu: "100m" - memory: "50Mi" - command: - - /ip-reconciler - - -log-level=verbose - volumeMounts: - - name: cni-net-dir - mountPath: /host/etc/cni/net.d - volumes: - - name: cni-net-dir - hostPath: - path: /etc/cni/net.d - restartPolicy: OnFailure diff --git a/doc/extended-configuration.md b/doc/extended-configuration.md index 7f20b631a..06d1d28e5 100644 --- a/doc/extended-configuration.md +++ b/doc/extended-configuration.md @@ -134,6 +134,12 @@ spec: You'll note that in the `ipam` section there's a lot less parameters than are used in the previous examples. +### Reconciler Cron Expression Configuration (optional) + +You may want to provide a cron expression to configure how frequently the ip-reconciler runs. This is done via the flatfile. + +Look for the following parameter `"reconciler_cron_expression"` located in `script/install-cni.sh` and change to your desired schedule. + ## Installing etcd. (optional) etcd installation is optional. By default, we recommend the custom resource backend (given in the first example configuration). diff --git a/hack/build-go.sh b/hack/build-go.sh index 1acc9734f..0d615169e 100755 --- a/hack/build-go.sh +++ b/hack/build-go.sh @@ -45,5 +45,4 @@ VERSION_LDFLAGS="-X github.com/k8snetworkplumbingwg/whereabouts/pkg/version.Vers GLDFLAGS="${GLDFLAGS} ${VERSION_LDFLAGS}" CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} ${GO} build ${GOFLAGS} -ldflags "${GLDFLAGS}" -o bin/${cmd} cmd/${cmd}.go -CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} ${GO} build ${GOFLAGS} -ldflags "${GLDFLAGS}" -o bin/ip-reconciler cmd/reconciler/*.go CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} ${GO} build ${GOFLAGS} -ldflags "${GLDFLAGS}" -o bin/ip-control-loop cmd/controlloop/*.go diff --git a/hack/e2e-setup-kind-cluster.sh b/hack/e2e-setup-kind-cluster.sh index 2a5f96b31..ac4b66252 100755 --- a/hack/e2e-setup-kind-cluster.sh +++ b/hack/e2e-setup-kind-cluster.sh @@ -98,7 +98,7 @@ trap "rm /tmp/whereabouts-img.tar || true" EXIT kind load image-archive --name "$KIND_CLUSTER_NAME" /tmp/whereabouts-img.tar echo "## install whereabouts" -for file in "daemonset-install.yaml" "ip-reconciler-job.yaml" "whereabouts.cni.cncf.io_ippools.yaml" "whereabouts.cni.cncf.io_overlappingrangeipreservations.yaml"; do +for file in "daemonset-install.yaml" "whereabouts.cni.cncf.io_ippools.yaml" "whereabouts.cni.cncf.io_overlappingrangeipreservations.yaml"; do retry kubectl apply -f "$ROOT/doc/crds/$file" done retry kubectl wait -n kube-system --for=condition=ready -l app=whereabouts pod --timeout=$TIMEOUT_K8 diff --git a/pkg/config/config.go b/pkg/config/config.go index 33e53f673..677d5eeff 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -53,41 +53,9 @@ func LoadIPAMConfig(bytes []byte, envArgs string, extraConfigPaths ...string) (* n.IPAM.PodName = string(args.K8S_POD_NAME) n.IPAM.PodNamespace = string(args.K8S_POD_NAMESPACE) - // Once we have our basics, let's look for our (optional) configuration file - confdirs := []string{"/etc/kubernetes/cni/net.d/whereabouts.d/whereabouts.conf", "/etc/cni/net.d/whereabouts.d/whereabouts.conf"} - confdirs = append(confdirs, extraConfigPaths...) - // We prefix the optional configuration path (so we look there first) - if n.IPAM.ConfigurationPath != "" { - confdirs = append([]string{n.IPAM.ConfigurationPath}, confdirs...) - } - - // Cycle through the path and parse the JSON config - flatipam := types.Net{} - foundflatfile := "" - for _, confpath := range confdirs { - if pathExists(confpath) { - - jsonFile, err := os.Open(confpath) - - if err != nil { - return nil, "", fmt.Errorf("error opening flat configuration file @ %s with: %s", confpath, err) - } - - defer jsonFile.Close() - - jsonBytes, err := ioutil.ReadAll(jsonFile) - if err != nil { - return nil, "", fmt.Errorf("LoadIPAMConfig Flatfile (%s) - ioutil.ReadAll error: %s", confpath, err) - } - - if err := json.Unmarshal(jsonBytes, &flatipam.IPAM); err != nil { - return nil, "", fmt.Errorf("LoadIPAMConfig Flatfile (%s) - JSON Parsing Error: %s / bytes: %s", confpath, err, jsonBytes) - } - - foundflatfile = confpath - - break - } + flatipam, foundflatfile, err := GetFlatIPAM(false, n.IPAM, extraConfigPaths...) + if err != nil { + return nil, "", err } // Now let's try to merge the configurations... @@ -139,7 +107,6 @@ func LoadIPAMConfig(bytes []byte, envArgs string, extraConfigPaths ...string) (* n.IPAM.Datastore = types.DatastoreETCD } - var err error storageError := "You have not configured the storage engine (looks like you're using an invalid `%s` parameter in your config)" switch n.IPAM.Datastore { case types.DatastoreKubernetes: @@ -164,7 +131,6 @@ func LoadIPAMConfig(bytes []byte, envArgs string, extraConfigPaths ...string) (* } n.IPAM.Gateway = gwip } - for i := range n.IPAM.OmitRanges { _, _, err := netutils.ParseCIDRSloppy(n.IPAM.OmitRanges[i]) if err != nil { @@ -252,6 +218,47 @@ func configureStatic(n *types.Net, args types.IPAMEnvArgs) error { } +func GetFlatIPAM(isControlLoop bool, IPAM *types.IPAMConfig, extraConfigPaths ...string) (types.Net, string, error) { + // Once we have our basics, let's look for our (optional) configuration file + confdirs := []string{"/etc/kubernetes/cni/net.d/whereabouts.d/whereabouts.conf", "/etc/cni/net.d/whereabouts.d/whereabouts.conf", "/host/etc/cni/net.d/whereabouts.d/whereabouts.conf"} + confdirs = append(confdirs, extraConfigPaths...) + // We prefix the optional configuration path (so we look there first) + + if !isControlLoop && IPAM != nil { + if IPAM.ConfigurationPath != "" { + confdirs = append([]string{IPAM.ConfigurationPath}, confdirs...) + } + } + + // Cycle through the path and parse the JSON config + flatipam := types.Net{} + foundflatfile := "" + for _, confpath := range confdirs { + if pathExists(confpath) { + jsonFile, err := os.Open(confpath) + if err != nil { + return flatipam, foundflatfile, fmt.Errorf("error opening flat configuration file @ %s with: %s", confpath, err) + } + + defer jsonFile.Close() + + jsonBytes, err := ioutil.ReadAll(jsonFile) + if err != nil { + return flatipam, foundflatfile, fmt.Errorf("LoadIPAMConfig Flatfile (%s) - ioutil.ReadAll error: %s", confpath, err) + } + + if err := json.Unmarshal(jsonBytes, &flatipam.IPAM); err != nil { + return flatipam, foundflatfile, fmt.Errorf("LoadIPAMConfig Flatfile (%s) - JSON Parsing Error: %s / bytes: %s", confpath, err, jsonBytes) + } + + foundflatfile = confpath + return flatipam, foundflatfile, err + } + } + var err error + return flatipam, foundflatfile, err +} + func handleEnvArgs(n *types.Net, numV6 int, numV4 int, args types.IPAMEnvArgs) (int, int, error) { if args.IP != "" { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 799e31b58..f4c838b15 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -243,4 +243,32 @@ var _ = Describe("Allocation operations", func() { Expect(ipamConfig.RangeStart).To(Equal(net.ParseIP("192.168.1.44"))) Expect(ipamConfig.RangeEnd).To(Equal(net.ParseIP("192.168.1.209"))) }) + + It("can unmarshall the cronjob expression", func() { + conf := `{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "whereabouts", + "log_file" : "/tmp/whereabouts.log", + "log_level" : "debug", + "etcd_host": "foo", + "range": "00192.00168.1.0/24", + "range_start": "00192.00168.1.44", + "range_end": "00192.00168.01.209", + "gateway": "192.168.10.1", + "reconciler_cron_expression": "30 4 * * *" + } + }` + + ipamConfig, _, err := LoadIPAMConfig([]byte(conf), "") + Expect(err).NotTo(HaveOccurred()) + Expect(ipamConfig.Range).To(Equal("192.168.1.0/24")) + Expect(ipamConfig.RangeStart).To(Equal(net.ParseIP("192.168.1.44"))) + Expect(ipamConfig.RangeEnd).To(Equal(net.ParseIP("192.168.1.209"))) + Expect(ipamConfig.RangeEnd).To(Equal(net.ParseIP("192.168.1.209"))) + Expect(ipamConfig.ReconcilerCronExpression).To(Equal("30 4 * * *")) + }) }) diff --git a/pkg/reconciler/controlloop/dummy_controller.go b/pkg/controlloop/dummy_controller.go similarity index 100% rename from pkg/reconciler/controlloop/dummy_controller.go rename to pkg/controlloop/dummy_controller.go diff --git a/pkg/reconciler/controlloop/entity_generators.go b/pkg/controlloop/entity_generators.go similarity index 100% rename from pkg/reconciler/controlloop/entity_generators.go rename to pkg/controlloop/entity_generators.go diff --git a/pkg/reconciler/controlloop/pod.go b/pkg/controlloop/pod.go similarity index 100% rename from pkg/reconciler/controlloop/pod.go rename to pkg/controlloop/pod.go diff --git a/pkg/reconciler/controlloop/pod_controller_test.go b/pkg/controlloop/pod_controller_test.go similarity index 100% rename from pkg/reconciler/controlloop/pod_controller_test.go rename to pkg/controlloop/pod_controller_test.go diff --git a/pkg/reconciler/ip.go b/pkg/reconciler/ip.go new file mode 100644 index 000000000..012bc0420 --- /dev/null +++ b/pkg/reconciler/ip.go @@ -0,0 +1,45 @@ +package reconciler + +import ( + "context" + "time" + + "github.com/k8snetworkplumbingwg/whereabouts/pkg/logging" +) + +const ( + defaultReconcilerTimeout = 30 +) + +func ReconcileIPs(errorChan chan error) { + logging.Verbosef("starting reconciler run") + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(defaultReconcilerTimeout*time.Second)) + defer cancel() + + ipReconcileLoop, err := NewReconcileLooper(ctx, defaultReconcilerTimeout) + if err != nil { + _ = logging.Errorf("failed to create the reconcile looper: %v", err) + errorChan <- err + return + } + + cleanedUpIps, err := ipReconcileLoop.ReconcileIPPools(ctx) + if err != nil { + _ = logging.Errorf("failed to clean up IP for allocations: %v", err) + errorChan <- err + return + } + + if len(cleanedUpIps) > 0 { + logging.Debugf("successfully cleanup IPs: %+v", cleanedUpIps) + } else { + logging.Debugf("no IP addresses to cleanup") + } + + if err := ipReconcileLoop.ReconcileOverlappingIPAddresses(ctx); err != nil { + errorChan <- err + return + } + + errorChan <- nil +} diff --git a/cmd/reconciler/ip_test.go b/pkg/reconciler/ip_test.go similarity index 71% rename from cmd/reconciler/ip_test.go rename to pkg/reconciler/ip_test.go index 83eaac84a..b43140697 100644 --- a/cmd/reconciler/ip_test.go +++ b/pkg/reconciler/ip_test.go @@ -1,4 +1,4 @@ -package main +package reconciler import ( "context" @@ -6,19 +6,25 @@ import ( "fmt" "net" "strings" + "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" multusv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" "github.com/k8snetworkplumbingwg/whereabouts/pkg/api/whereabouts.cni.cncf.io/v1alpha1" - "github.com/k8snetworkplumbingwg/whereabouts/pkg/reconciler" + "github.com/k8snetworkplumbingwg/whereabouts/pkg/types" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8stypes "k8s.io/apimachinery/pkg/types" ) +func TestIPReconciler(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Reconcile IP address allocation in the system") +} + var _ = Describe("Whereabouts IP reconciler", func() { const ( firstIPInRange = "10.10.10.1" @@ -30,7 +36,7 @@ var _ = Describe("Whereabouts IP reconciler", func() { ) var ( - reconcileLooper *reconciler.ReconcileLooper + reconcileLooper *ReconcileLooper ) Context("reconciling IP pools with a single running pod", func() { @@ -67,7 +73,7 @@ var _ = Describe("Whereabouts IP reconciler", func() { Context("reconciling the IPPool", func() { BeforeEach(func() { var err error - reconcileLooper, err = reconciler.NewReconcileLooperWithKubeconfig(context.TODO(), kubeConfigPath, timeout) + reconcileLooper, err = NewReconcileLooperWithKubeconfig(context.TODO(), kubeConfigPath, timeout) Expect(err).NotTo(HaveOccurred()) }) @@ -138,7 +144,7 @@ var _ = Describe("Whereabouts IP reconciler", func() { Context("reconciling the IPPool", func() { BeforeEach(func() { var err error - reconcileLooper, err = reconciler.NewReconcileLooperWithKubeconfig(context.TODO(), kubeConfigPath, timeout) + reconcileLooper, err = NewReconcileLooperWithKubeconfig(context.TODO(), kubeConfigPath, timeout) Expect(err).NotTo(HaveOccurred()) }) @@ -243,7 +249,7 @@ var _ = Describe("Whereabouts IP reconciler", func() { It("will delete an orphaned IP address", func() { Expect(k8sClientSet.CoreV1().Pods(namespace).Delete(context.TODO(), pods[podIndexToRemove].Name, metav1.DeleteOptions{})).NotTo(HaveOccurred()) - newReconciler, err := reconciler.NewReconcileLooperWithKubeconfig(context.TODO(), kubeConfigPath, timeout) + newReconciler, err := NewReconcileLooperWithKubeconfig(context.TODO(), kubeConfigPath, timeout) Expect(err).NotTo(HaveOccurred()) Expect(newReconciler.ReconcileOverlappingIPAddresses(context.TODO())).To(Succeed()) @@ -271,7 +277,7 @@ var _ = Describe("Whereabouts IP reconciler", func() { pool = generateIPPoolSpec(ipRange, namespace, poolName, pod.Name) Expect(k8sClient.Create(context.Background(), pool)).NotTo(HaveOccurred()) - reconcileLooper, err = reconciler.NewReconcileLooperWithKubeconfig(context.TODO(), kubeConfigPath, timeout) + reconcileLooper, err = NewReconcileLooperWithKubeconfig(context.TODO(), kubeConfigPath, timeout) Expect(err).NotTo(HaveOccurred()) }) @@ -286,6 +292,117 @@ var _ = Describe("Whereabouts IP reconciler", func() { }) }) +// mock the pool +type dummyPool struct { + orphans []types.IPReservation + pool v1alpha1.IPPool +} + +func (dp dummyPool) Allocations() []types.IPReservation { + return dp.orphans +} + +func (dp dummyPool) Update(context.Context, []types.IPReservation) error { + return nil +} + +var _ = Describe("IPReconciler", func() { + var ipReconciler *ReconcileLooper + + newIPReconciler := func(orphanedIPs ...OrphanedIPReservations) *ReconcileLooper { + reconciler := &ReconcileLooper{ + orphanedIPs: orphanedIPs, + } + + return reconciler + } + + When("there are no IP addresses to reconcile", func() { + BeforeEach(func() { + ipReconciler = newIPReconciler() + }) + + It("does not delete anything", func() { + reconciledIPs, err := ipReconciler.ReconcileIPPools(context.TODO()) + Expect(err).NotTo(HaveOccurred()) + Expect(reconciledIPs).To(BeEmpty()) + }) + }) + + When("there are IP addresses to reconcile", func() { + const ( + firstIPInRange = "192.168.14.1" + ipCIDR = "192.168.14.0/24" + namespace = "default" + podName = "pod1" + ) + + BeforeEach(func() { + podRef := "default/pod1" + reservations := generateIPReservation(firstIPInRange, podRef) + + pool := generateIPPool(ipCIDR, podRef) + orphanedIPAddr := OrphanedIPReservations{ + Pool: dummyPool{orphans: reservations, pool: pool}, + Allocations: reservations, + } + + ipReconciler = newIPReconciler(orphanedIPAddr) + }) + + It("does delete the orphaned IP address", func() { + reconciledIPs, err := ipReconciler.ReconcileIPPools(context.TODO()) + Expect(err).NotTo(HaveOccurred()) + Expect(reconciledIPs).To(Equal([]net.IP{net.ParseIP(firstIPInRange)})) + }) + + Context("and they are actually multiple IPs", func() { + BeforeEach(func() { + podRef := "default/pod2" + reservations := generateIPReservation("192.168.14.2", podRef) + + pool := generateIPPool(ipCIDR, podRef, "default/pod2", "default/pod3") + orphanedIPAddr := OrphanedIPReservations{ + Pool: dummyPool{orphans: reservations, pool: pool}, + Allocations: reservations, + } + + ipReconciler = newIPReconciler(orphanedIPAddr) + }) + + It("does delete *only the orphaned* the IP address", func() { + reconciledIPs, err := ipReconciler.ReconcileIPPools(context.TODO()) + Expect(err).NotTo(HaveOccurred()) + Expect(reconciledIPs).To(ConsistOf([]net.IP{net.ParseIP("192.168.14.2")})) + }) + }) + + Context("but the IP reservation owner does not match", func() { + var reservationPodRef string + BeforeEach(func() { + reservationPodRef = "default/pod2" + podRef := "default/pod1" + reservations := generateIPReservation(firstIPInRange, podRef) + erroredReservations := generateIPReservation(firstIPInRange, reservationPodRef) + + pool := generateIPPool(ipCIDR, podRef) + orphanedIPAddr := OrphanedIPReservations{ + Pool: dummyPool{orphans: reservations, pool: pool}, + Allocations: erroredReservations, + } + + ipReconciler = newIPReconciler(orphanedIPAddr) + }) + + It("errors when attempting to clean up the IP address", func() { + reconciledIPs, err := ipReconciler.ReconcileIPPools(context.TODO()) + Expect(err).To(MatchError(fmt.Sprintf("did not find reserved IP for container %s", reservationPodRef))) + Expect(reconciledIPs).To(BeEmpty()) + }) + }) + }) +}) + func generateIPPoolSpec(ipRange string, namespace string, poolName string, podNames ...string) *v1alpha1.IPPool { allocations := map[string]v1alpha1.IPAllocation{} for i, podName := range podNames { @@ -348,8 +465,8 @@ func generatePodAnnotations(ipNetworks ...ipInNetwork) map[string]string { networks = append(networks, ipNetworkInfo.networkName) } networkAnnotations := map[string]string{ - reconciler.MultusNetworkAnnotation: strings.Join(networks, ","), - reconciler.MultusNetworkStatusAnnotation: generatePodNetworkStatusAnnotation(ipNetworks...), + MultusNetworkAnnotation: strings.Join(networks, ","), + MultusNetworkStatusAnnotation: generatePodNetworkStatusAnnotation(ipNetworks...), } return networkAnnotations } @@ -370,3 +487,26 @@ func generatePodNetworkStatusAnnotation(ipNetworks ...ipInNetwork) string { return string(networkStatusStr) } + +func generateIPPool(cidr string, podRefs ...string) v1alpha1.IPPool { + allocations := map[string]v1alpha1.IPAllocation{} + for i, podRef := range podRefs { + allocations[fmt.Sprintf("%d", i)] = v1alpha1.IPAllocation{PodRef: podRef} + } + + return v1alpha1.IPPool{ + Spec: v1alpha1.IPPoolSpec{ + Range: cidr, + Allocations: allocations, + }, + } +} + +func generateIPReservation(ip string, podRef string) []types.IPReservation { + return []types.IPReservation{ + { + IP: net.ParseIP(ip), + PodRef: podRef, + }, + } +} diff --git a/pkg/reconciler/iploop_test.go b/pkg/reconciler/iploop_test.go deleted file mode 100644 index 1d8a5b1c2..000000000 --- a/pkg/reconciler/iploop_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package reconciler - -import ( - "context" - "fmt" - "net" - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - whereaboutsv1alpha1 "github.com/k8snetworkplumbingwg/whereabouts/pkg/api/whereabouts.cni.cncf.io/v1alpha1" - "github.com/k8snetworkplumbingwg/whereabouts/pkg/types" -) - -func TestIPReconciler(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Reconcile IP address allocation in the system") -} - -// mock the pool -type dummyPool struct { - orphans []types.IPReservation - pool whereaboutsv1alpha1.IPPool -} - -func (dp dummyPool) Allocations() []types.IPReservation { - return dp.orphans -} - -func (dp dummyPool) Update(context.Context, []types.IPReservation) error { - return nil -} - -var _ = Describe("IPReconciler", func() { - var ipReconciler *ReconcileLooper - - newIPReconciler := func(orphanedIPs ...OrphanedIPReservations) *ReconcileLooper { - reconciler := &ReconcileLooper{ - orphanedIPs: orphanedIPs, - } - - return reconciler - } - - When("there are no IP addresses to reconcile", func() { - BeforeEach(func() { - ipReconciler = newIPReconciler() - }) - - It("does not delete anything", func() { - reconciledIPs, err := ipReconciler.ReconcileIPPools(context.TODO()) - Expect(err).NotTo(HaveOccurred()) - Expect(reconciledIPs).To(BeEmpty()) - }) - }) - - When("there are IP addresses to reconcile", func() { - const ( - firstIPInRange = "192.168.14.1" - ipCIDR = "192.168.14.0/24" - namespace = "default" - podName = "pod1" - ) - - BeforeEach(func() { - podRef := "default/pod1" - reservations := generateIPReservation(firstIPInRange, podRef) - - pool := generateIPPool(ipCIDR, podRef) - orphanedIPAddr := OrphanedIPReservations{ - Pool: dummyPool{orphans: reservations, pool: pool}, - Allocations: reservations, - } - - ipReconciler = newIPReconciler(orphanedIPAddr) - }) - - It("does delete the orphaned IP address", func() { - reconciledIPs, err := ipReconciler.ReconcileIPPools(context.TODO()) - Expect(err).NotTo(HaveOccurred()) - Expect(reconciledIPs).To(Equal([]net.IP{net.ParseIP(firstIPInRange)})) - }) - - Context("and they are actually multiple IPs", func() { - BeforeEach(func() { - podRef := "default/pod2" - reservations := generateIPReservation("192.168.14.2", podRef) - - pool := generateIPPool(ipCIDR, podRef, "default/pod2", "default/pod3") - orphanedIPAddr := OrphanedIPReservations{ - Pool: dummyPool{orphans: reservations, pool: pool}, - Allocations: reservations, - } - - ipReconciler = newIPReconciler(orphanedIPAddr) - }) - - It("does delete *only the orphaned* the IP address", func() { - reconciledIPs, err := ipReconciler.ReconcileIPPools(context.TODO()) - Expect(err).NotTo(HaveOccurred()) - Expect(reconciledIPs).To(ConsistOf([]net.IP{net.ParseIP("192.168.14.2")})) - }) - }) - - Context("but the IP reservation owner does not match", func() { - var reservationPodRef string - BeforeEach(func() { - reservationPodRef = "default/pod2" - podRef := "default/pod1" - reservations := generateIPReservation(firstIPInRange, podRef) - erroredReservations := generateIPReservation(firstIPInRange, reservationPodRef) - - pool := generateIPPool(ipCIDR, podRef) - orphanedIPAddr := OrphanedIPReservations{ - Pool: dummyPool{orphans: reservations, pool: pool}, - Allocations: erroredReservations, - } - - ipReconciler = newIPReconciler(orphanedIPAddr) - }) - - It("errors when attempting to clean up the IP address", func() { - reconciledIPs, err := ipReconciler.ReconcileIPPools(context.TODO()) - Expect(err).To(MatchError(fmt.Sprintf("did not find reserved IP for container %s", reservationPodRef))) - Expect(reconciledIPs).To(BeEmpty()) - }) - }) - }) -}) - -func generateIPPool(cidr string, podRefs ...string) whereaboutsv1alpha1.IPPool { - allocations := map[string]whereaboutsv1alpha1.IPAllocation{} - for i, podRef := range podRefs { - allocations[fmt.Sprintf("%d", i)] = whereaboutsv1alpha1.IPAllocation{PodRef: podRef} - } - - return whereaboutsv1alpha1.IPPool{ - Spec: whereaboutsv1alpha1.IPPoolSpec{ - Range: cidr, - Allocations: allocations, - }, - } -} - -func generateIPReservation(ip string, podRef string) []types.IPReservation { - return []types.IPReservation{ - { - IP: net.ParseIP(ip), - PodRef: podRef, - }, - } -} diff --git a/cmd/reconciler/suite_test.go b/pkg/reconciler/suite_test.go similarity index 94% rename from cmd/reconciler/suite_test.go rename to pkg/reconciler/suite_test.go index 3af7d5331..202eac197 100644 --- a/cmd/reconciler/suite_test.go +++ b/pkg/reconciler/suite_test.go @@ -1,15 +1,15 @@ -package main +package reconciler import ( "fmt" "io/fs" "io/ioutil" - "k8s.io/client-go/kubernetes" "os" "path" "path/filepath" "strings" - "testing" + + "k8s.io/client-go/kubernetes" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -32,14 +32,6 @@ var ( tmpdir string ) -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecsWithDefaultAndCustomReporters(t, - "Whereabouts IP reconciler Suite", - []Reporter{}) -} - var _ = BeforeSuite(func(done Done) { zap.WriteTo(GinkgoWriter) logf.SetLogger(zap.New()) diff --git a/pkg/types/types.go b/pkg/types/types.go index 9ecc44f49..5d6608130 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -41,68 +41,70 @@ type NetConfList struct { // IPAMConfig describes the expected json configuration for this plugin type IPAMConfig struct { - Name string - Type string `json:"type"` - Routes []*cnitypes.Route `json:"routes"` - Datastore string `json:"datastore"` - Addresses []Address `json:"addresses,omitempty"` - OmitRanges []string `json:"exclude,omitempty"` - DNS cnitypes.DNS `json:"dns"` - Range string `json:"range"` - RangeStart net.IP `json:"range_start,omitempty"` - RangeEnd net.IP `json:"range_end,omitempty"` - GatewayStr string `json:"gateway"` - EtcdHost string `json:"etcd_host,omitempty"` - EtcdUsername string `json:"etcd_username,omitempty"` - EtcdPassword string `json:"etcd_password,omitempty"` - EtcdKeyFile string `json:"etcd_key_file,omitempty"` - EtcdCertFile string `json:"etcd_cert_file,omitempty"` - EtcdCACertFile string `json:"etcd_ca_cert_file,omitempty"` - LeaderLeaseDuration int `json:"leader_lease_duration,omitempty"` - LeaderRenewDeadline int `json:"leader_renew_deadline,omitempty"` - LeaderRetryPeriod int `json:"leader_retry_period,omitempty"` - LogFile string `json:"log_file"` - LogLevel string `json:"log_level"` - OverlappingRanges bool `json:"enable_overlapping_ranges,omitempty"` - SleepForRace int `json:"sleep_for_race,omitempty"` - Gateway net.IP - Kubernetes KubernetesConfig `json:"kubernetes,omitempty"` - ConfigurationPath string `json:"configuration_path"` - PodName string - PodNamespace string + Name string + Type string `json:"type"` + Routes []*cnitypes.Route `json:"routes"` + Datastore string `json:"datastore"` + Addresses []Address `json:"addresses,omitempty"` + OmitRanges []string `json:"exclude,omitempty"` + DNS cnitypes.DNS `json:"dns"` + Range string `json:"range"` + RangeStart net.IP `json:"range_start,omitempty"` + RangeEnd net.IP `json:"range_end,omitempty"` + GatewayStr string `json:"gateway"` + EtcdHost string `json:"etcd_host,omitempty"` + EtcdUsername string `json:"etcd_username,omitempty"` + EtcdPassword string `json:"etcd_password,omitempty"` + EtcdKeyFile string `json:"etcd_key_file,omitempty"` + EtcdCertFile string `json:"etcd_cert_file,omitempty"` + EtcdCACertFile string `json:"etcd_ca_cert_file,omitempty"` + LeaderLeaseDuration int `json:"leader_lease_duration,omitempty"` + LeaderRenewDeadline int `json:"leader_renew_deadline,omitempty"` + LeaderRetryPeriod int `json:"leader_retry_period,omitempty"` + LogFile string `json:"log_file"` + LogLevel string `json:"log_level"` + ReconcilerCronExpression string `json:"reconciler_cron_expression,omitempty"` + OverlappingRanges bool `json:"enable_overlapping_ranges,omitempty"` + SleepForRace int `json:"sleep_for_race,omitempty"` + Gateway net.IP + Kubernetes KubernetesConfig `json:"kubernetes,omitempty"` + ConfigurationPath string `json:"configuration_path"` + PodName string + PodNamespace string } func (ic *IPAMConfig) UnmarshalJSON(data []byte) error { type IPAMConfigAlias struct { - Name string - Type string `json:"type"` - Routes []*cnitypes.Route `json:"routes"` - Datastore string `json:"datastore"` - Addresses []Address `json:"addresses,omitempty"` - OmitRanges []string `json:"exclude,omitempty"` - DNS cnitypes.DNS `json:"dns"` - Range string `json:"range"` - RangeStart string `json:"range_start,omitempty"` - RangeEnd string `json:"range_end,omitempty"` - GatewayStr string `json:"gateway"` - EtcdHost string `json:"etcd_host,omitempty"` - EtcdUsername string `json:"etcd_username,omitempty"` - EtcdPassword string `json:"etcd_password,omitempty"` - EtcdKeyFile string `json:"etcd_key_file,omitempty"` - EtcdCertFile string `json:"etcd_cert_file,omitempty"` - EtcdCACertFile string `json:"etcd_ca_cert_file,omitempty"` - LeaderLeaseDuration int `json:"leader_lease_duration,omitempty"` - LeaderRenewDeadline int `json:"leader_renew_deadline,omitempty"` - LeaderRetryPeriod int `json:"leader_retry_period,omitempty"` - LogFile string `json:"log_file"` - LogLevel string `json:"log_level"` - OverlappingRanges bool `json:"enable_overlapping_ranges,omitempty"` - SleepForRace int `json:"sleep_for_race,omitempty"` - Gateway string - Kubernetes KubernetesConfig `json:"kubernetes,omitempty"` - ConfigurationPath string `json:"configuration_path"` - PodName string - PodNamespace string + Name string + Type string `json:"type"` + Routes []*cnitypes.Route `json:"routes"` + Datastore string `json:"datastore"` + Addresses []Address `json:"addresses,omitempty"` + OmitRanges []string `json:"exclude,omitempty"` + DNS cnitypes.DNS `json:"dns"` + Range string `json:"range"` + RangeStart string `json:"range_start,omitempty"` + RangeEnd string `json:"range_end,omitempty"` + GatewayStr string `json:"gateway"` + EtcdHost string `json:"etcd_host,omitempty"` + EtcdUsername string `json:"etcd_username,omitempty"` + EtcdPassword string `json:"etcd_password,omitempty"` + EtcdKeyFile string `json:"etcd_key_file,omitempty"` + EtcdCertFile string `json:"etcd_cert_file,omitempty"` + EtcdCACertFile string `json:"etcd_ca_cert_file,omitempty"` + LeaderLeaseDuration int `json:"leader_lease_duration,omitempty"` + LeaderRenewDeadline int `json:"leader_renew_deadline,omitempty"` + LeaderRetryPeriod int `json:"leader_retry_period,omitempty"` + LogFile string `json:"log_file"` + LogLevel string `json:"log_level"` + ReconcilerCronExpression string `json:"reconciler_cron_expression,omitempty"` + OverlappingRanges bool `json:"enable_overlapping_ranges,omitempty"` + SleepForRace int `json:"sleep_for_race,omitempty"` + Gateway string + Kubernetes KubernetesConfig `json:"kubernetes,omitempty"` + ConfigurationPath string `json:"configuration_path"` + PodName string + PodNamespace string } ipamConfigAlias := IPAMConfigAlias{ @@ -114,35 +116,36 @@ func (ic *IPAMConfig) UnmarshalJSON(data []byte) error { } *ic = IPAMConfig{ - Name: ipamConfigAlias.Name, - Type: ipamConfigAlias.Type, - Routes: ipamConfigAlias.Routes, - Datastore: ipamConfigAlias.Datastore, - Addresses: ipamConfigAlias.Addresses, - OmitRanges: ipamConfigAlias.OmitRanges, - DNS: ipamConfigAlias.DNS, - Range: ipamConfigAlias.Range, - RangeStart: backwardsCompatibleIPAddress(ipamConfigAlias.RangeStart), - RangeEnd: backwardsCompatibleIPAddress(ipamConfigAlias.RangeEnd), - GatewayStr: ipamConfigAlias.GatewayStr, - EtcdHost: ipamConfigAlias.EtcdHost, - EtcdUsername: ipamConfigAlias.EtcdUsername, - EtcdPassword: ipamConfigAlias.EtcdPassword, - EtcdKeyFile: ipamConfigAlias.EtcdKeyFile, - EtcdCertFile: ipamConfigAlias.EtcdCertFile, - EtcdCACertFile: ipamConfigAlias.EtcdCACertFile, - LeaderLeaseDuration: ipamConfigAlias.LeaderLeaseDuration, - LeaderRenewDeadline: ipamConfigAlias.LeaderRenewDeadline, - LeaderRetryPeriod: ipamConfigAlias.LeaderRetryPeriod, - LogFile: ipamConfigAlias.LogFile, - LogLevel: ipamConfigAlias.LogLevel, - OverlappingRanges: ipamConfigAlias.OverlappingRanges, - SleepForRace: ipamConfigAlias.SleepForRace, - Gateway: backwardsCompatibleIPAddress(ipamConfigAlias.Gateway), - Kubernetes: ipamConfigAlias.Kubernetes, - ConfigurationPath: ipamConfigAlias.ConfigurationPath, - PodName: ipamConfigAlias.PodName, - PodNamespace: ipamConfigAlias.PodNamespace, + Name: ipamConfigAlias.Name, + Type: ipamConfigAlias.Type, + Routes: ipamConfigAlias.Routes, + Datastore: ipamConfigAlias.Datastore, + Addresses: ipamConfigAlias.Addresses, + OmitRanges: ipamConfigAlias.OmitRanges, + DNS: ipamConfigAlias.DNS, + Range: ipamConfigAlias.Range, + RangeStart: backwardsCompatibleIPAddress(ipamConfigAlias.RangeStart), + RangeEnd: backwardsCompatibleIPAddress(ipamConfigAlias.RangeEnd), + GatewayStr: ipamConfigAlias.GatewayStr, + EtcdHost: ipamConfigAlias.EtcdHost, + EtcdUsername: ipamConfigAlias.EtcdUsername, + EtcdPassword: ipamConfigAlias.EtcdPassword, + EtcdKeyFile: ipamConfigAlias.EtcdKeyFile, + EtcdCertFile: ipamConfigAlias.EtcdCertFile, + EtcdCACertFile: ipamConfigAlias.EtcdCACertFile, + LeaderLeaseDuration: ipamConfigAlias.LeaderLeaseDuration, + LeaderRenewDeadline: ipamConfigAlias.LeaderRenewDeadline, + LeaderRetryPeriod: ipamConfigAlias.LeaderRetryPeriod, + LogFile: ipamConfigAlias.LogFile, + LogLevel: ipamConfigAlias.LogLevel, + OverlappingRanges: ipamConfigAlias.OverlappingRanges, + ReconcilerCronExpression: ipamConfigAlias.ReconcilerCronExpression, + SleepForRace: ipamConfigAlias.SleepForRace, + Gateway: backwardsCompatibleIPAddress(ipamConfigAlias.Gateway), + Kubernetes: ipamConfigAlias.Kubernetes, + ConfigurationPath: ipamConfigAlias.ConfigurationPath, + PodName: ipamConfigAlias.PodName, + PodNamespace: ipamConfigAlias.PodNamespace, } return nil } diff --git a/script/install-cni.sh b/script/install-cni.sh index 5e79957b5..6eab0ff56 100755 --- a/script/install-cni.sh +++ b/script/install-cni.sh @@ -102,7 +102,8 @@ EOF "datastore": "kubernetes", "kubernetes": { "kubeconfig": "${WHEREABOUTS_KUBECONFIG_LITERAL}" - } + }, + "reconciler_cron_expression": "30 4 * * *" } EOF