diff --git a/go.work b/go.work new file mode 100644 index 00000000..96825b12 --- /dev/null +++ b/go.work @@ -0,0 +1,3 @@ +go 1.21.5 + +use . diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 00000000..c82a680e --- /dev/null +++ b/go.work.sum @@ -0,0 +1,4 @@ +github.com/emicklei/go-restful v2.10.0+incompatible h1:l6Soi8WCOOVAeCo4W98iBFC6Og7/X8bpRt51oNLZ2C8= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= diff --git a/hack/minikube-start.sh b/hack/minikube-start.sh new file mode 100755 index 00000000..e4069b68 --- /dev/null +++ b/hack/minikube-start.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +if command -v fedmanctl &>/dev/null; then + echo "fedmanctl is installed. Creating clusters." + + # cluster_names=("cluster-worker-paris" "cluster-worker-munich" "cluster-worker-milan" "cluster-federator-eu") + # cluster_nodes=(2 1 2 1) + # cluster_federators=(0 0 0 1) + + cluster_names=("cluster-worker-paris") + cluster_nodes=(1) + cluster_federators=(0) + + # Create 3 worker 1 federator cluster, this also updates the KUBECONFIG file and adds different contexts. + + for ((i=0; i<${#cluster_names[@]}; i++)); do + echo "> Creating ${cluster_names[i]} with ${cluster_nodes[i]} node(s)" + minikube start --memory 2g --cpus 2 -n ${cluster_nodes[i]} --network bridge -p ${cluster_names[i]} &>/dev/null + done + + # # Setup cert-manager and edgenet + echo "Clusters are created, deploying cert-manager and edgenet" + for ((i=0; i<${#cluster_names[@]}; i++)); do + echo "> Deploying to ${cluster_names[i]}" + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml --context ${cluster_names[i]} + done + + for ((i=0; i<${#cluster_names[@]}; i++)); do + # wait for cert-manager's webhook to run, there can be more sophisticated waiting mechanism + sleep 25 + kubectl apply -f build/yamls/kubernetes/multi-tenancy.yaml --context ${cluster_names[i]} + + # For now delete this. + kubectl delete validatingwebhookconfigurations.admissionregistration.k8s.io edgenet-admission-control --context ${cluster_names[i]} + + # If the cluster is a federator + if [ "${cluster_federators[i]}" = "1" ]; then + kubectl apply -f build/yamls/kubernetes/federation-manager.yaml --context ${cluster_names[i]} + else + kubectl apply -f build/yamls/kubernetes/federation-workload.yaml --context ${cluster_names[i]} + fi + done + + echo "DONE!" +else + echo "fedmanctl is not installed or not in the system's PATH. Exitting..." +fi + + + diff --git a/pkg/apis/apps/v1alpha1/types.go b/pkg/apis/apps/v1alpha1/types.go index 44216fb9..d8f2ba43 100644 --- a/pkg/apis/apps/v1alpha1/types.go +++ b/pkg/apis/apps/v1alpha1/types.go @@ -66,7 +66,9 @@ type Workloads struct { CronJob []batchv1beta.CronJob `json:"cronjob"` } -// Selector to define desired node filtering parameters +// Selector to define desired node filtering parameters. +// TODO: In the future we might want to add custom sectorization? this is already doable +// with LabelSelector type Selector struct { // Name of the selector. This can be City, State, Country, Continent, or Polygon Name string `json:"name"` diff --git a/pkg/apis/apps/v1alpha2/types.go b/pkg/apis/apps/v1alpha2/types.go index 6d8b51f2..3684b1c4 100644 --- a/pkg/apis/apps/v1alpha2/types.go +++ b/pkg/apis/apps/v1alpha2/types.go @@ -87,6 +87,7 @@ type SelectiveDeploymentStatus struct { Failed int `json:"failed"` } +// Denotes the status of an individual cluster type WorkloadClusterStatus struct { Server string `json:"server"` Location string `json:"location"` diff --git a/pkg/apis/apps/v1alpha3/doc.go b/pkg/apis/apps/v1alpha3/doc.go new file mode 100644 index 00000000..c0070383 --- /dev/null +++ b/pkg/apis/apps/v1alpha3/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// +k8s:deepcopy-gen=package +// +groupName=apps.edgenet.io + +package v1alpha3 // import "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha3" diff --git a/pkg/apis/apps/v1alpha3/register.go b/pkg/apis/apps/v1alpha3/register.go new file mode 100644 index 00000000..46564e49 --- /dev/null +++ b/pkg/apis/apps/v1alpha3/register.go @@ -0,0 +1,58 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +Old Credits: +Copyright 2017 The Kubernetes 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 v1alpha3 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/EdgeNet-project/edgenet/pkg/apis/apps" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: apps.GroupName, Version: "v1alpha3"} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // SchemeBuilder initializes a scheme builder + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + // AddToScheme is a global function that registers this API group & version to a scheme + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &SelectiveDeployment{}, + &SelectiveDeploymentList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/apps/v1alpha3/types.go b/pkg/apis/apps/v1alpha3/types.go new file mode 100644 index 00000000..f1319dde --- /dev/null +++ b/pkg/apis/apps/v1alpha3/types.go @@ -0,0 +1,107 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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 v1alpha3 + +import ( + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + batchv1beta "k8s.io/api/batch/v1beta1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// SelectiveDeployment describes a SelectiveDeployment resource +type SelectiveDeployment struct { + // TypeMeta is the metadata for the resource, like kind and apiversion + metav1.TypeMeta `json:",inline"` + // ObjectMeta contains the metadata for the particular object, including + metav1.ObjectMeta `json:"metadata,omitempty"` + // Spec is the selectivedeployment resource spec + Spec SelectiveDeploymentSpec `json:"spec"` + // Status is the selectivedeployment resource status + Status SelectiveDeploymentStatus `json:"status,omitempty"` +} + +// SelectiveDeploymentSpec is the spec for a SelectiveDeployment resource. +// Selectors filter the nodes to be used for specified workloads. +type SelectiveDeploymentSpec struct { + // Workload can be Deployment, Deamonset, StatefulSet, Job, or CronJob. + Workloads Workloads `json:"workloads"` + // List of Selector resources. Each selector filters the nodes with the + // requested method. + Selector []Selector `json:"selector"` + // If true, selective deployment tries to find another suitable + // node to run the workload in case of a node goes down. + Recovery bool `json:"recovery"` +} + +// Workloads indicates deployments, daemonsets, statefulsets, jobs, or cronjobs. +type Workloads struct { + // Workload can have a list of Deployments. + Deployment []appsv1.Deployment `json:"deployment"` + // Workload can have a list of DaemonSets. + DaemonSet []appsv1.DaemonSet `json:"daemonset"` + // Workload can have a list of StatefulSets. + StatefulSet []appsv1.StatefulSet `json:"statefulset"` + // Workload can have a list of Jobs. + Job []batchv1.Job `json:"job"` + // Workload can have a list of CronJobs. + CronJob []batchv1beta.CronJob `json:"cronjob"` +} + +// Selector to define desired node filtering parameters +type Selector struct { + // Name of the selector. This can be City, State, Country, Continent, or Polygon + Name string `json:"name"` + // Value of the selector. For example; if the name of the selector is 'City' + // then the value can be the city name. For example; if the name of + // the selector is 'Polygon' then the value can be the GeoJSON representation of the polygon. + Value []string `json:"value"` + // Operator means basic mathematical operators such as 'In', 'NotIn', 'Exists', 'NotExsists' etc... + Operator corev1.NodeSelectorOperator `json:"operator"` + // Quantity represents number of nodes on which the workloads will be running. + Quantity int `json:"quantity"` +} + +// SelectiveDeploymentStatus is the status for a SelectiveDeployment resource +type SelectiveDeploymentStatus struct { + // Ready string denotes number of workloads running filtered by the SelectiveDeployments. + // The string is 'x/y' if x instances are running and y instances are requested. + Ready string `json:"ready"` + // Represents state of the selective deployment. This can be 'Failure' if none + // of the workloads are deployed. 'Partial' if some of the the workloads + // are deployed. 'Success' if all of the workloads are deployed. + State string `json:"state"` + // There can be multiple display messages for state description. + Message string `json:"message"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// SelectiveDeploymentList is a list of SelectiveDeployment resources +type SelectiveDeploymentList struct { + // TypeMeta is the metadata for the resource, like kind and apiversion + metav1.TypeMeta `json:",inline"` + // ObjectMeta contains the metadata for the particular object, including + metav1.ListMeta `json:"metadata"` + // SelectiveDeploymentList is a list of SelectiveDeployment resources thus, + // SelectiveDeployments are contained here. + Items []SelectiveDeployment `json:"items"` +} diff --git a/pkg/apis/apps/v1alpha3/zz_generated.deepcopy.go b/pkg/apis/apps/v1alpha3/zz_generated.deepcopy.go new file mode 100644 index 00000000..5c4aa913 --- /dev/null +++ b/pkg/apis/apps/v1alpha3/zz_generated.deepcopy.go @@ -0,0 +1,202 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + v1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + v1beta1 "k8s.io/api/batch/v1beta1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SelectiveDeployment) DeepCopyInto(out *SelectiveDeployment) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelectiveDeployment. +func (in *SelectiveDeployment) DeepCopy() *SelectiveDeployment { + if in == nil { + return nil + } + out := new(SelectiveDeployment) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SelectiveDeployment) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SelectiveDeploymentList) DeepCopyInto(out *SelectiveDeploymentList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SelectiveDeployment, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelectiveDeploymentList. +func (in *SelectiveDeploymentList) DeepCopy() *SelectiveDeploymentList { + if in == nil { + return nil + } + out := new(SelectiveDeploymentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SelectiveDeploymentList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SelectiveDeploymentSpec) DeepCopyInto(out *SelectiveDeploymentSpec) { + *out = *in + in.Workloads.DeepCopyInto(&out.Workloads) + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = make([]Selector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelectiveDeploymentSpec. +func (in *SelectiveDeploymentSpec) DeepCopy() *SelectiveDeploymentSpec { + if in == nil { + return nil + } + out := new(SelectiveDeploymentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SelectiveDeploymentStatus) DeepCopyInto(out *SelectiveDeploymentStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelectiveDeploymentStatus. +func (in *SelectiveDeploymentStatus) DeepCopy() *SelectiveDeploymentStatus { + if in == nil { + return nil + } + out := new(SelectiveDeploymentStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Selector) DeepCopyInto(out *Selector) { + *out = *in + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Selector. +func (in *Selector) DeepCopy() *Selector { + if in == nil { + return nil + } + out := new(Selector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Workloads) DeepCopyInto(out *Workloads) { + *out = *in + if in.Deployment != nil { + in, out := &in.Deployment, &out.Deployment + *out = make([]v1.Deployment, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DaemonSet != nil { + in, out := &in.DaemonSet, &out.DaemonSet + *out = make([]v1.DaemonSet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.StatefulSet != nil { + in, out := &in.StatefulSet, &out.StatefulSet + *out = make([]v1.StatefulSet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Job != nil { + in, out := &in.Job, &out.Job + *out = make([]batchv1.Job, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.CronJob != nil { + in, out := &in.CronJob, &out.CronJob + *out = make([]v1beta1.CronJob, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Workloads. +func (in *Workloads) DeepCopy() *Workloads { + if in == nil { + return nil + } + out := new(Workloads) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/controller/apps/v1alpha1/selectivedeployment/controller.go b/pkg/controller/apps/v1alpha1/selectivedeployment/controller.go index c7d36c81..57f3144c 100644 --- a/pkg/controller/apps/v1alpha1/selectivedeployment/controller.go +++ b/pkg/controller/apps/v1alpha1/selectivedeployment/controller.go @@ -182,66 +182,8 @@ func NewController( DeleteFunc: controller.recoverSelectiveDeployments, }) - /*deploymentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: controller.handleObject, - UpdateFunc: func(old, new interface{}) { - newDeployment := new.(*appsv1.Deployment) - oldDeployment := old.(*appsv1.Deployment) - if newDeployment.ResourceVersion == oldDeployment.ResourceVersion { - return - } - controller.handleObject(new) - }, - DeleteFunc: controller.handleObject, - }) - daemonsetInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: controller.handleObject, - UpdateFunc: func(old, new interface{}) { - newDaemonSet := new.(*appsv1.DaemonSet) - oldDaemonSet := old.(*appsv1.DaemonSet) - if newDaemonSet.ResourceVersion == oldDaemonSet.ResourceVersion { - return - } - controller.handleObject(new) - }, - DeleteFunc: controller.handleObject, - }) - statefulsetInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: controller.handleObject, - UpdateFunc: func(old, new interface{}) { - newStatefulSet := new.(*appsv1.StatefulSet) - oldStatefulSet := old.(*appsv1.StatefulSet) - if newStatefulSet.ResourceVersion == oldStatefulSet.ResourceVersion { - return - } - controller.handleObject(new) - }, - DeleteFunc: controller.handleObject, - }) - jobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: controller.handleObject, - UpdateFunc: func(old, new interface{}) { - newJob := new.(*batchv1.Job) - oldJob := old.(*batchv1.Job) - if newJob.ResourceVersion == oldJob.ResourceVersion { - return - } - controller.handleObject(new) - }, - DeleteFunc: controller.handleObject, - }) - cronjobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: controller.handleObject, - UpdateFunc: func(old, new interface{}) { - newCronJob := new.(*batchv1beta1.CronJob) - oldCronJob := old.(*batchv1beta1.CronJob) - if newCronJob.ResourceVersion == oldCronJob.ResourceVersion { - return - } - controller.handleObject(new) - }, - DeleteFunc: controller.handleObject, - })*/ + // TODO: There used to be the informers of the workloads. Do we want to implement the functionality: + // if the Deployment is deleted, recreate it? return controller } @@ -521,6 +463,7 @@ func (c *Controller) getByNode(nodeName string) ([][]string, bool) { func (c *Controller) applyCriteria(selectivedeploymentCopy *appsv1alpha1.SelectiveDeployment) { oldStatus := selectivedeploymentCopy.Status statusUpdate := func() { + // If the status is different then update it afte this function.s if !reflect.DeepEqual(oldStatus, selectivedeploymentCopy.Status) { c.edgenetclientset.AppsV1alpha1().SelectiveDeployments(selectivedeploymentCopy.GetNamespace()).UpdateStatus(context.TODO(), selectivedeploymentCopy, metav1.UpdateOptions{}) } diff --git a/pkg/controller/apps/v1alpha3/selectivedeployment/controller.go b/pkg/controller/apps/v1alpha3/selectivedeployment/controller.go new file mode 100644 index 00000000..69fb7663 --- /dev/null +++ b/pkg/controller/apps/v1alpha3/selectivedeployment/controller.go @@ -0,0 +1,881 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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 selectivedeployment + +import ( + "context" + "encoding/json" + "fmt" + "math/rand" + "reflect" + "strconv" + "strings" + "time" + + appsv1alpha3 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha3" + clientset "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned" + edgenetscheme "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/scheme" + informers "github.com/EdgeNet-project/edgenet/pkg/generated/informers/externalversions/apps/v1alpha3" + listers "github.com/EdgeNet-project/edgenet/pkg/generated/listers/apps/v1alpha3" + "github.com/EdgeNet-project/edgenet/pkg/multiprovider" + "github.com/EdgeNet-project/edgenet/pkg/util" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + selection "k8s.io/apimachinery/pkg/selection" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + appsinformers "k8s.io/client-go/informers/apps/v1" + batchinformers "k8s.io/client-go/informers/batch/v1" + batchv1beta1informers "k8s.io/client-go/informers/batch/v1beta1" + coreinformers "k8s.io/client-go/informers/core/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + appslisters "k8s.io/client-go/listers/apps/v1" + batchlisters "k8s.io/client-go/listers/batch/v1" + batchv1beta1listers "k8s.io/client-go/listers/batch/v1beta1" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog" +) + +const controllerAgentName = "selectivedeployment-controller" + +// Definitions of the state of the selectivedeployment resource +const ( + successSynced = "Synced" + messageResourceSynced = "Selective Deployment synced successfully" + messageWorkloadCreated = "The desired workload(s) are created successfully" + failureCreation = "Creation Failed" + messageWorkloadFailed = "A workload defined in the spec could not be created" + messageExtendedWorkloadFailed = "Workload could not be created: %s %s" + messageExtendedWorkloadInUse = "Workload is owned by another selective deployment: %s %s" + failureGeoJSON = "GeoJSON Error" + messageGeoJSONError = "GeoJSON has a format error" + failureFewerNodes = "Fewer nodes issue" + messageFewerNodes = "The number of nodes found is lower than desired" + failure = "Failure" + partial = "Running Partially" + success = "Running" + noSchedule = "NoSchedule" + trueStr = "True" +) + +// Controller is the controller implementation for Selective Deployment resources +type Controller struct { + // kubeclientset is a standard kubernetes clientset + kubeclientset kubernetes.Interface + // edgenetclientset is a clientset for the EdgeNet API groups + edgenetclientset clientset.Interface + + nodesLister corelisters.NodeLister + nodesSynced cache.InformerSynced + + deploymentsLister appslisters.DeploymentLister + deploymentsSynced cache.InformerSynced + daemonsetsLister appslisters.DaemonSetLister + daemonsetsSynced cache.InformerSynced + statefulsetsLister appslisters.StatefulSetLister + statefulsetsSynced cache.InformerSynced + jobsLister batchlisters.JobLister + jobsSynced cache.InformerSynced + cronjobsLister batchv1beta1listers.CronJobLister + cronjobsSynced cache.InformerSynced + + selectivedeploymentsLister listers.SelectiveDeploymentLister + selectivedeploymentsSynced cache.InformerSynced + + // workqueue is a rate limited work queue. This is used to queue work to be + // processed instead of performing it as soon as a change happens. This + // means we can ensure we only process a fixed amount of resources at a + // time, and makes it easy to ensure we are never processing the same item + // simultaneously in two different workers. + workqueue workqueue.RateLimitingInterface + // recorder is an event recorder for recording Event resources to the + // Kubernetes API. + recorder record.EventRecorder +} + +// NewController returns a new controller +func NewController( + kubeclientset kubernetes.Interface, + edgenetclientset clientset.Interface, + nodeInformer coreinformers.NodeInformer, + deploymentInformer appsinformers.DeploymentInformer, + daemonsetInformer appsinformers.DaemonSetInformer, + statefulsetInformer appsinformers.StatefulSetInformer, + jobInformer batchinformers.JobInformer, + cronjobInformer batchv1beta1informers.CronJobInformer, + selectivedeploymentInformer informers.SelectiveDeploymentInformer) *Controller { + + utilruntime.Must(edgenetscheme.AddToScheme(scheme.Scheme)) + klog.Info("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartStructuredLogging(0) + eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")}) + recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName}) + + controller := &Controller{ + kubeclientset: kubeclientset, + edgenetclientset: edgenetclientset, + nodesLister: nodeInformer.Lister(), + nodesSynced: nodeInformer.Informer().HasSynced, + deploymentsLister: deploymentInformer.Lister(), + deploymentsSynced: deploymentInformer.Informer().HasSynced, + daemonsetsLister: daemonsetInformer.Lister(), + daemonsetsSynced: daemonsetInformer.Informer().HasSynced, + statefulsetsLister: statefulsetInformer.Lister(), + statefulsetsSynced: statefulsetInformer.Informer().HasSynced, + jobsLister: jobInformer.Lister(), + jobsSynced: jobInformer.Informer().HasSynced, + cronjobsLister: cronjobInformer.Lister(), + cronjobsSynced: cronjobInformer.Informer().HasSynced, + selectivedeploymentsLister: selectivedeploymentInformer.Lister(), + selectivedeploymentsSynced: selectivedeploymentInformer.Informer().HasSynced, + workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "SelectiveDeployments"), + recorder: recorder, + } + + klog.Infoln("Setting up event handlers") + // Set up an event handler for when Selective Deployment resources change + selectivedeploymentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: controller.enqueueSelectiveDeployment, + UpdateFunc: func(old, new interface{}) { + newSelectiveDeployment := new.(*appsv1alpha3.SelectiveDeployment) + oldSelectiveDeployment := old.(*appsv1alpha3.SelectiveDeployment) + if newSelectiveDeployment.ResourceVersion == oldSelectiveDeployment.ResourceVersion { + return + } + controller.enqueueSelectiveDeployment(new) + }, + }) + + nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: controller.recoverSelectiveDeployments, + UpdateFunc: func(old, new interface{}) { + newNode := new.(*corev1.Node) + oldNode := old.(*corev1.Node) + if newNode.ResourceVersion == oldNode.ResourceVersion { + return + } + controller.recoverSelectiveDeployments(new) + }, + DeleteFunc: controller.recoverSelectiveDeployments, + }) + + return controller +} + +// Run will set up the event handlers for the types of selective deployment and node, as well +// as syncing informer caches and starting workers. It will block until stopCh +// is closed, at which point it will shutdown the workqueue and wait for +// workers to finish processing their current work items. +func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error { + defer utilruntime.HandleCrash() + defer c.workqueue.ShutDown() + + klog.Infoln("Starting Selective Deployment controller") + + klog.Infoln("Waiting for informer caches to sync") + if ok := cache.WaitForCacheSync(stopCh, + c.selectivedeploymentsSynced, + c.nodesSynced, + c.deploymentsSynced, + c.daemonsetsSynced, + c.statefulsetsSynced, + c.jobsSynced, + c.cronjobsSynced); !ok { + return fmt.Errorf("failed to wait for caches to sync") + } + + klog.Infoln("Starting workers") + for i := 0; i < threadiness; i++ { + go wait.Until(c.runWorker, time.Second, stopCh) + } + + klog.Infoln("Started workers") + <-stopCh + klog.Infoln("Shutting down workers") + + return nil +} + +// runWorker is a long-running function that will continually call the +// processNextWorkItem function in order to read and process a message on the +// workqueue. +func (c *Controller) runWorker() { + for c.processNextWorkItem() { + } +} + +// processNextWorkItem will read a single work item off the workqueue and +// attempt to process it, by calling the syncHandler. +func (c *Controller) processNextWorkItem() bool { + obj, shutdown := c.workqueue.Get() + + if shutdown { + return false + } + + err := func(obj interface{}) error { + defer c.workqueue.Done(obj) + var key string + var ok bool + + if key, ok = obj.(string); !ok { + c.workqueue.Forget(obj) + utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj)) + return nil + } + if err := c.syncHandler(key); err != nil { + c.workqueue.AddRateLimited(key) + return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error()) + } + c.workqueue.Forget(obj) + klog.Infof("Successfully synced '%s'", key) + return nil + }(obj) + + if err != nil { + utilruntime.HandleError(err) + return true + } + + return true +} + +// syncHandler compares the actual state with the desired, and attempts to +// converge the two. It then updates the Status block of the Selective Deployment +// resource with the current status of the resource. +func (c *Controller) syncHandler(key string) error { + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key)) + return nil + } + + selectivedeployment, err := c.selectivedeploymentsLister.SelectiveDeployments(namespace).Get(name) + if err != nil { + if errors.IsNotFound(err) { + utilruntime.HandleError(fmt.Errorf("selectivedeployment '%s' in work queue no longer exists", key)) + return nil + } + + return err + } + + c.applyCriteria(selectivedeployment) + + c.recorder.Event(selectivedeployment, corev1.EventTypeNormal, successSynced, messageResourceSynced) + return nil +} + +// enqueueSelectiveDeployment takes a SelectiveDeployment resource and converts it into a namespace/name +// string which is then put onto the work queue. This method should *not* be +// passed resources of any type other than SelectiveDeployment. +func (c *Controller) enqueueSelectiveDeployment(obj interface{}) { + var key string + var err error + if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil { + utilruntime.HandleError(err) + return + } + c.workqueue.Add(key) +} + +func (c *Controller) recoverSelectiveDeployments(obj interface{}) { + nodeCopy := obj.(*corev1.Node).DeepCopy() + if nodeCopy.GetDeletionTimestamp() != nil || multiprovider.GetConditionReadyStatus(nodeCopy) != trueStr || nodeCopy.Spec.Unschedulable { + ownerRaw, status := c.getByNode(nodeCopy.GetName()) + if status { + for _, ownerRow := range ownerRaw { + selectivedeployment, err := c.selectivedeploymentsLister.SelectiveDeployments(ownerRow[0]).Get(ownerRow[1]) + if err != nil { + klog.Infoln(err.Error()) + continue + } + if selectivedeployment.Spec.Recovery { + c.enqueueSelectiveDeployment(selectivedeployment) + } + } + } + } else if multiprovider.GetConditionReadyStatus(nodeCopy) == trueStr { + selectivedeploymentRaw, _ := c.selectivedeploymentsLister.SelectiveDeployments("").List(labels.Everything()) + for _, selectivedeploymentRow := range selectivedeploymentRaw { + if selectivedeploymentRow.Spec.Recovery { + if selectivedeploymentRow.Status.State == partial || selectivedeploymentRow.Status.State == failure { + c.enqueueSelectiveDeployment(selectivedeploymentRow) + } + } + } + } + +} + +// getByNode generates selectivedeployment list from the owner references of workloads which contains the node that has an event (add/update/delete) +func (c *Controller) getByNode(nodeName string) ([][]string, bool) { + ownerList := [][]string{} + status := false + setList := func(ctlPodSpec corev1.PodSpec, ownerReferences []metav1.OwnerReference, namespace string) { + podSpec := ctlPodSpec + if podSpec.Affinity != nil && podSpec.Affinity.NodeAffinity != nil && podSpec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { + nodeSelectorLoop: + for _, nodeSelectorTerm := range podSpec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms { + for _, matchExpression := range nodeSelectorTerm.MatchExpressions { + if matchExpression.Key == "kubernetes.io/hostname" { + for _, expressionNodeName := range matchExpression.Values { + if nodeName == expressionNodeName { + for _, owner := range ownerReferences { + if owner.Kind == "SelectiveDeployment" { + ownerDet := []string{namespace, owner.Name} + if exists, _ := util.SliceContains(ownerList, ownerDet); !exists { + ownerList = append(ownerList, ownerDet) + } + status = true + } + } + break nodeSelectorLoop + } + } + } + } + } + } + } + deploymentRaw, err := c.deploymentsLister.Deployments("").List(labels.Everything()) + if err != nil { + klog.Infoln(err.Error()) + panic(err.Error()) + } + for _, deploymentRow := range deploymentRaw { + setList(deploymentRow.Spec.Template.Spec, deploymentRow.GetOwnerReferences(), deploymentRow.GetNamespace()) + } + daemonsetRaw, err := c.daemonsetsLister.DaemonSets("").List(labels.Everything()) + if err != nil { + klog.Infoln(err.Error()) + panic(err.Error()) + } + for _, daemonsetRow := range daemonsetRaw { + setList(daemonsetRow.Spec.Template.Spec, daemonsetRow.GetOwnerReferences(), daemonsetRow.GetNamespace()) + } + statefulsetRaw, err := c.statefulsetsLister.StatefulSets("").List(labels.Everything()) + if err != nil { + klog.Infoln(err.Error()) + panic(err.Error()) + } + for _, statefulsetRow := range statefulsetRaw { + setList(statefulsetRow.Spec.Template.Spec, statefulsetRow.GetOwnerReferences(), statefulsetRow.GetNamespace()) + } + jobRaw, err := c.jobsLister.Jobs("").List(labels.Everything()) + if err != nil { + klog.Infoln(err.Error()) + panic(err.Error()) + } + for _, jobRow := range jobRaw { + setList(jobRow.Spec.Template.Spec, jobRow.GetOwnerReferences(), jobRow.GetNamespace()) + } + cronjobRaw, err := c.cronjobsLister.CronJobs("").List(labels.Everything()) + if err != nil { + klog.Infoln(err.Error()) + panic(err.Error()) + } + for _, cronjobRow := range cronjobRaw { + setList(cronjobRow.Spec.JobTemplate.Spec.Template.Spec, cronjobRow.GetOwnerReferences(), cronjobRow.GetNamespace()) + } + return ownerList, status +} + +// applyCriteria picks the nodes according to the selector +func (c *Controller) applyCriteria(selectivedeploymentCopy *appsv1alpha3.SelectiveDeployment) { + oldStatus := selectivedeploymentCopy.Status + statusUpdate := func() { + // If the status is different then update it afte this function.s + if !reflect.DeepEqual(oldStatus, selectivedeploymentCopy.Status) { + c.edgenetclientset.AppsV1alpha3().SelectiveDeployments(selectivedeploymentCopy.GetNamespace()).UpdateStatus(context.TODO(), selectivedeploymentCopy, metav1.UpdateOptions{}) + } + } + defer statusUpdate() + + ownerReferences := SetAsOwnerReference(selectivedeploymentCopy) + workloadCounter := 0 + failureCounter := 0 + if selectivedeploymentCopy.Spec.Workloads.Deployment != nil { + workloadCounter += len(selectivedeploymentCopy.Spec.Workloads.Deployment) + for _, deployment := range selectivedeploymentCopy.Spec.Workloads.Deployment { + if actualDeployment, err := c.deploymentsLister.Deployments(selectivedeploymentCopy.GetNamespace()).Get(deployment.GetName()); errors.IsNotFound(err) { + desiredPodTemplate, isFailed := c.configureWorkload(selectivedeploymentCopy, deployment.Spec.Template, deployment.Spec.Template, ownerReferences) + desiredDeployment := deployment.DeepCopy() + desiredDeployment.Spec.Template = desiredPodTemplate + desiredDeployment.SetOwnerReferences(ownerReferences) + if _, err = c.kubeclientset.AppsV1().Deployments(selectivedeploymentCopy.GetNamespace()).Create(context.TODO(), desiredDeployment, metav1.CreateOptions{}); err != nil { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadFailed, desiredDeployment.GetObjectKind().GroupVersionKind().Kind, desiredDeployment.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + klog.Infoln(err) + failureCounter++ + } else { + if isFailed { + failureCounter++ + } + } + } else { + if hasOwner := checkOwnerReferences(selectivedeploymentCopy, actualDeployment.GetOwnerReferences()); !hasOwner { + desiredPodTemplate, isFailed := c.configureWorkload(selectivedeploymentCopy, actualDeployment.Spec.Template, deployment.Spec.Template, ownerReferences) + if isFailed { + failureCounter++ + } + desiredDeployment := actualDeployment.DeepCopy() + desiredDeployment.Spec.Template = desiredPodTemplate + desiredDeployment.SetOwnerReferences(ownerReferences) + if actualDeployment.Status.Replicas != actualDeployment.Status.AvailableReplicas || !reflect.DeepEqual(actualDeployment.Spec.Template, desiredDeployment.Spec.Template) { + if _, err = c.kubeclientset.AppsV1().Deployments(selectivedeploymentCopy.GetNamespace()).Update(context.TODO(), desiredDeployment, metav1.UpdateOptions{}); err != nil { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadFailed, desiredDeployment.GetObjectKind().GroupVersionKind().Kind, desiredDeployment.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + klog.Infoln(err) + if !isFailed { + failureCounter++ + } + } + } + } else { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadInUse, deployment.GetObjectKind().GroupVersionKind().Kind, deployment.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + failureCounter++ + } + } + } + } + if selectivedeploymentCopy.Spec.Workloads.DaemonSet != nil { + workloadCounter += len(selectivedeploymentCopy.Spec.Workloads.DaemonSet) + for _, daemonset := range selectivedeploymentCopy.Spec.Workloads.DaemonSet { + if actualDaemonset, err := c.daemonsetsLister.DaemonSets(selectivedeploymentCopy.GetNamespace()).Get(daemonset.GetName()); errors.IsNotFound(err) { + desiredPodTemplate, isFailed := c.configureWorkload(selectivedeploymentCopy, daemonset.Spec.Template, daemonset.Spec.Template, ownerReferences) + desiredDaemonset := daemonset.DeepCopy() + desiredDaemonset.Spec.Template = desiredPodTemplate + desiredDaemonset.SetOwnerReferences(ownerReferences) + if _, err = c.kubeclientset.AppsV1().DaemonSets(selectivedeploymentCopy.GetNamespace()).Create(context.TODO(), desiredDaemonset, metav1.CreateOptions{}); err != nil { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadFailed, desiredDaemonset.GetObjectKind().GroupVersionKind().Kind, desiredDaemonset.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + klog.Infoln(err) + failureCounter++ + } else { + if isFailed { + failureCounter++ + } + } + } else { + if hasOwner := checkOwnerReferences(selectivedeploymentCopy, actualDaemonset.GetOwnerReferences()); !hasOwner { + desiredPodTemplate, isFailed := c.configureWorkload(selectivedeploymentCopy, actualDaemonset.Spec.Template, daemonset.Spec.Template, ownerReferences) + if isFailed { + failureCounter++ + } + desiredDaemonset := daemonset.DeepCopy() + desiredDaemonset.Spec.Template = desiredPodTemplate + desiredDaemonset.SetOwnerReferences(ownerReferences) + if actualDaemonset.Status.DesiredNumberScheduled != actualDaemonset.Status.NumberReady || !reflect.DeepEqual(actualDaemonset.Spec.Template, desiredDaemonset.Spec.Template) { + if _, err = c.kubeclientset.AppsV1().DaemonSets(selectivedeploymentCopy.GetNamespace()).Update(context.TODO(), desiredDaemonset, metav1.UpdateOptions{}); err != nil { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadFailed, desiredDaemonset.GetObjectKind().GroupVersionKind().Kind, desiredDaemonset.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + klog.Infoln(err) + if !isFailed { + failureCounter++ + } + } + } + } else { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadInUse, daemonset.GetObjectKind().GroupVersionKind().Kind, daemonset.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + failureCounter++ + } + } + } + } + if selectivedeploymentCopy.Spec.Workloads.StatefulSet != nil { + workloadCounter += len(selectivedeploymentCopy.Spec.Workloads.StatefulSet) + for _, statefulset := range selectivedeploymentCopy.Spec.Workloads.StatefulSet { + if actualStatefulset, err := c.statefulsetsLister.StatefulSets(selectivedeploymentCopy.GetNamespace()).Get(statefulset.GetName()); errors.IsNotFound(err) { + desiredPodTemplate, isFailed := c.configureWorkload(selectivedeploymentCopy, statefulset.Spec.Template, statefulset.Spec.Template, ownerReferences) + desiredStatefulset := statefulset.DeepCopy() + desiredStatefulset.Spec.Template = desiredPodTemplate + desiredStatefulset.SetOwnerReferences(ownerReferences) + if _, err = c.kubeclientset.AppsV1().StatefulSets(selectivedeploymentCopy.GetNamespace()).Create(context.TODO(), desiredStatefulset, metav1.CreateOptions{}); err != nil { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadFailed, desiredStatefulset.GetObjectKind().GroupVersionKind().Kind, desiredStatefulset.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + klog.Infoln(err) + failureCounter++ + } else { + if isFailed { + failureCounter++ + } + } + } else { + if hasOwner := checkOwnerReferences(selectivedeploymentCopy, actualStatefulset.GetOwnerReferences()); !hasOwner { + desiredPodTemplate, isFailed := c.configureWorkload(selectivedeploymentCopy, actualStatefulset.Spec.Template, statefulset.Spec.Template, ownerReferences) + if isFailed { + failureCounter++ + } + desiredStatefulset := statefulset.DeepCopy() + desiredStatefulset.Spec.Template = desiredPodTemplate + desiredStatefulset.SetOwnerReferences(ownerReferences) + if !reflect.DeepEqual(actualStatefulset.Spec.Template, desiredStatefulset.Spec.Template) { + if _, err = c.kubeclientset.AppsV1().StatefulSets(selectivedeploymentCopy.GetNamespace()).Update(context.TODO(), desiredStatefulset, metav1.UpdateOptions{}); err != nil { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadFailed, desiredStatefulset.GetObjectKind().GroupVersionKind().Kind, desiredStatefulset.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + klog.Infoln(err) + if !isFailed { + failureCounter++ + } + } + } + } else { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadInUse, statefulset.GetObjectKind().GroupVersionKind().Kind, statefulset.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + failureCounter++ + } + } + } + } + if selectivedeploymentCopy.Spec.Workloads.Job != nil { + workloadCounter += len(selectivedeploymentCopy.Spec.Workloads.Job) + for _, job := range selectivedeploymentCopy.Spec.Workloads.Job { + if actualJob, err := c.jobsLister.Jobs(selectivedeploymentCopy.GetNamespace()).Get(job.GetName()); errors.IsNotFound(err) { + desiredPodTemplate, isFailed := c.configureWorkload(selectivedeploymentCopy, job.Spec.Template, job.Spec.Template, ownerReferences) + desiredJob := job.DeepCopy() + desiredJob.Spec.Template = desiredPodTemplate + desiredJob.SetOwnerReferences(ownerReferences) + if _, err = c.kubeclientset.BatchV1().Jobs(selectivedeploymentCopy.GetNamespace()).Create(context.TODO(), desiredJob, metav1.CreateOptions{}); err != nil { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadFailed, desiredJob.GetObjectKind().GroupVersionKind().Kind, desiredJob.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + klog.Infoln(err) + failureCounter++ + } else { + if isFailed { + failureCounter++ + } + } + } else { + if hasOwner := checkOwnerReferences(selectivedeploymentCopy, actualJob.GetOwnerReferences()); !hasOwner { + desiredPodTemplate, isFailed := c.configureWorkload(selectivedeploymentCopy, actualJob.Spec.Template, job.Spec.Template, ownerReferences) + if isFailed { + failureCounter++ + } + desiredJob := job.DeepCopy() + desiredJob.Spec.Template = desiredPodTemplate + desiredJob.SetOwnerReferences(ownerReferences) + if !reflect.DeepEqual(actualJob.Spec.Template, desiredJob.Spec.Template) { + if _, err = c.kubeclientset.BatchV1().Jobs(selectivedeploymentCopy.GetNamespace()).Update(context.TODO(), desiredJob, metav1.UpdateOptions{}); err != nil { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadFailed, desiredJob.GetObjectKind().GroupVersionKind().Kind, desiredJob.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + klog.Infoln(err) + if !isFailed { + failureCounter++ + } + } + } + } else { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadInUse, job.GetObjectKind().GroupVersionKind().Kind, job.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + failureCounter++ + } + } + } + } + if selectivedeploymentCopy.Spec.Workloads.CronJob != nil { + workloadCounter += len(selectivedeploymentCopy.Spec.Workloads.CronJob) + for _, cronjob := range selectivedeploymentCopy.Spec.Workloads.CronJob { + if actualCronjob, err := c.cronjobsLister.CronJobs(selectivedeploymentCopy.GetNamespace()).Get(cronjob.GetName()); errors.IsNotFound(err) { + desiredPodTemplate, isFailed := c.configureWorkload(selectivedeploymentCopy, cronjob.Spec.JobTemplate.Spec.Template, cronjob.Spec.JobTemplate.Spec.Template, ownerReferences) + desiredCronjob := cronjob.DeepCopy() + desiredCronjob.Spec.JobTemplate.Spec.Template = desiredPodTemplate + desiredCronjob.SetOwnerReferences(ownerReferences) + if _, err = c.kubeclientset.BatchV1beta1().CronJobs(selectivedeploymentCopy.GetNamespace()).Create(context.TODO(), desiredCronjob, metav1.CreateOptions{}); err != nil { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadFailed, desiredCronjob.GetObjectKind().GroupVersionKind().Kind, desiredCronjob.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + klog.Infoln(err) + failureCounter++ + } else { + if isFailed { + failureCounter++ + } + } + } else { + if hasOwner := checkOwnerReferences(selectivedeploymentCopy, actualCronjob.GetOwnerReferences()); !hasOwner { + desiredPodTemplate, isFailed := c.configureWorkload(selectivedeploymentCopy, actualCronjob.Spec.JobTemplate.Spec.Template, cronjob.Spec.JobTemplate.Spec.Template, ownerReferences) + if isFailed { + failureCounter++ + } + desiredCronjob := cronjob.DeepCopy() + desiredCronjob.Spec.JobTemplate.Spec.Template = desiredPodTemplate + desiredCronjob.SetOwnerReferences(ownerReferences) + if !reflect.DeepEqual(actualCronjob.Spec.JobTemplate.Spec.Template, desiredCronjob.Spec.JobTemplate.Spec.Template) { + if _, err = c.kubeclientset.BatchV1beta1().CronJobs(selectivedeploymentCopy.GetNamespace()).Update(context.TODO(), desiredCronjob, metav1.UpdateOptions{}); err != nil { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadFailed, desiredCronjob.GetObjectKind().GroupVersionKind().Kind, desiredCronjob.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + klog.Infoln(err) + if !isFailed { + failureCounter++ + } + } + } + } else { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureCreation, fmt.Sprintf(messageExtendedWorkloadInUse, cronjob.GetObjectKind().GroupVersionKind().Kind, cronjob.GetName())) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageWorkloadFailed + failureCounter++ + } + } + } + } + + if failureCounter == 0 && workloadCounter != 0 { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeNormal, success, messageWorkloadCreated) + selectivedeploymentCopy.Status.State = success + selectivedeploymentCopy.Status.Message = messageWorkloadCreated + } else if workloadCounter == failureCounter { + selectivedeploymentCopy.Status.State = failure + } else { + selectivedeploymentCopy.Status.State = partial + } + selectivedeploymentCopy.Status.Ready = fmt.Sprintf("%d/%d", (workloadCounter - failureCounter), workloadCounter) +} + +// configureWorkload converges the actual state to the desired state of the workloads defined in selective deployment +func (c *Controller) configureWorkload(selectivedeploymentCopy *appsv1alpha3.SelectiveDeployment, actualPodTemplate, desiredPodTemplate corev1.PodTemplateSpec, ownerReferences []metav1.OwnerReference) (corev1.PodTemplateSpec, bool) { + klog.Infoln("configureWorkload: start") + + actualNodeSelectorTermList := corev1.NodeSelectorTerm{} + + if actualPodTemplate.Spec.Affinity != nil && len(actualPodTemplate.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms) > 0 { + actualNodeSelectorTermList = actualPodTemplate.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0] + } + desiredNodeSelectorTermList, isFailed := c.setFilter(selectivedeploymentCopy, actualNodeSelectorTermList, "addOrUpdate") + // Set the new node affinity configuration for the workload and update that + nodeAffinity := &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: desiredNodeSelectorTermList, + }, + } + if len(desiredNodeSelectorTermList) <= 0 { + affinity := &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: desiredNodeSelectorTermList, + }, + }, + } + affinity.Reset() + } + + if len(desiredNodeSelectorTermList) <= 0 && desiredPodTemplate.Spec.Affinity != nil { + desiredPodTemplate.Spec.Affinity.Reset() + } else if desiredPodTemplate.Spec.Affinity != nil { + desiredPodTemplate.Spec.Affinity.NodeAffinity = nodeAffinity + } else { + desiredPodTemplate.Spec.Affinity = &corev1.Affinity{ + NodeAffinity: nodeAffinity, + } + } + + return desiredPodTemplate, isFailed +} + +// setFilter generates the values in the predefined form and puts those into the node selection fields of the selectivedeployment object +func (c *Controller) setFilter(selectivedeploymentCopy *appsv1alpha3.SelectiveDeployment, actualNodeSelectorTerm corev1.NodeSelectorTerm, event string) ([]corev1.NodeSelectorTerm, bool) { + var nodeSelectorTermList []corev1.NodeSelectorTerm + isFailed := false + + for _, selectorRow := range selectivedeploymentCopy.Spec.Selector { + var matchExpression corev1.NodeSelectorRequirement + matchExpression.Values = []string{} + matchExpression.Operator = selectorRow.Operator + matchExpression.Key = "kubernetes.io/hostname" + selectorName := strings.ToLower(selectorRow.Name) + + // If the event type is delete then we don't need to run the part below + if event != "delete" { + labelKeySuffix := "" + if selectorName == "state" || selectorName == "country" { + labelKeySuffix = "-iso" + } + labelKey := strings.ToLower(fmt.Sprintf("edge-net.io/%s%s", selectorName, labelKeySuffix)) + // This gets the node list which includes the EdgeNet geolabels + scheduleReq, _ := labels.NewRequirement("spec.unschedulable", selection.NotEquals, []string{"true"}) + selector := labels.NewSelector() + selector = selector.Add(*scheduleReq) + nodesRaw, err := c.nodesLister.List(selector) + if err != nil { + klog.Infoln(err.Error()) + panic(err.Error()) + } + // This loop allows us to process each value defined at the object of selectivedeployment resource + + matchNodeList := []string{} + desiredNodeList := []string{} + checkActualList := func(nodeName string) bool { + if len(actualNodeSelectorTerm.MatchExpressions) > 0 { + if exists, _ := util.Contains(actualNodeSelectorTerm.MatchExpressions[0].Values, nodeName); exists { + desiredNodeList = append(desiredNodeList, nodeName) + return true + } + } + return false + } + for _, selectorValue := range selectorRow.Value { + // The loop to process each node separately + for _, nodeRow := range nodesRaw { + taintBlock := false + for _, taint := range nodeRow.Spec.Taints { + if (taint.Key == "node-role.kubernetes.io/master" && taint.Effect == noSchedule) || + (taint.Key == "node.kubernetes.io/unschedulable" && taint.Effect == noSchedule) { + taintBlock = true + } + } + conditionBlock := false + if multiprovider.GetConditionReadyStatus(nodeRow.DeepCopy()) != trueStr { + conditionBlock = true + } + + if !conditionBlock && !taintBlock { + // Turn the key into the predefined form which is determined at the custom resource definition of selectivedeployment + switch selectorName { + case "city", "state", "country", "continent": + if selectorValue == nodeRow.Labels[labelKey] && selectorRow.Operator == "In" { + if !checkActualList(nodeRow.Labels["kubernetes.io/hostname"]) { + matchNodeList = append(matchNodeList, nodeRow.Labels["kubernetes.io/hostname"]) + } + } else if selectorValue != nodeRow.Labels[labelKey] && selectorRow.Operator == "NotIn" { + if !checkActualList(nodeRow.Labels["kubernetes.io/hostname"]) { + matchNodeList = append(matchNodeList, nodeRow.Labels["kubernetes.io/hostname"]) + } + } + case "polygon": + var polygon [][]float64 + err = json.Unmarshal([]byte(selectorValue), &polygon) + if err != nil { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureGeoJSON, messageGeoJSONError) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageGeoJSONError + isFailed = true + continue + } + if nodeRow.Labels["edge-net.io/lon"] != "" && nodeRow.Labels["edge-net.io/lat"] != "" { + // Because of alphanumeric limitations of Kubernetes on the labels we use "w", "e", "n", and "s" prefixes + // at the labels of latitude and longitude. Here is the place those prefixes are dropped away. + lonStr := nodeRow.Labels["edge-net.io/lon"][1:] + latStr := nodeRow.Labels["edge-net.io/lat"][1:] + if lon, err := strconv.ParseFloat(lonStr, 64); err == nil { + if lat, err := strconv.ParseFloat(latStr, 64); err == nil { + // boundbox is a rectangle which provides to check whether the point is inside polygon + // without taking all point of the polygon into consideration + boundbox := multiprovider.Boundbox(polygon) + status := multiprovider.GeoFence(boundbox, polygon, lon, lat) + if status && selectorRow.Operator == "In" { + if !checkActualList(nodeRow.Labels["kubernetes.io/hostname"]) { + matchNodeList = append(matchNodeList, nodeRow.Labels["kubernetes.io/hostname"]) + } + } else if !status && selectorRow.Operator == "NotIn" { + if !checkActualList(nodeRow.Labels["kubernetes.io/hostname"]) { + matchNodeList = append(matchNodeList, nodeRow.Labels["kubernetes.io/hostname"]) + } + } + } + } + } + } + } + } + } + + prePickedNodeCount := len(desiredNodeList) + if selectorRow.Quantity == 0 { + desiredNodeList = append(desiredNodeList, matchNodeList...) + } else { + if selectorRow.Quantity > prePickedNodeCount { + if len(matchNodeList) > 0 { + for i := 0; i < (selectorRow.Quantity - prePickedNodeCount); i++ { + rand.Seed(time.Now().UnixNano()) + randomSelect := rand.Intn(len(matchNodeList)) + desiredNodeList = append(desiredNodeList, matchNodeList[randomSelect]) + matchNodeList[randomSelect] = matchNodeList[len(matchNodeList)-1] + matchNodeList = matchNodeList[:len(matchNodeList)-1] + if len(matchNodeList) == 0 { + break + } + } + } + + if selectorRow.Quantity != len(desiredNodeList) { + c.recorder.Event(selectivedeploymentCopy, corev1.EventTypeWarning, failureFewerNodes, messageFewerNodes) + selectivedeploymentCopy.Status.State = failure + selectivedeploymentCopy.Status.Message = messageFewerNodes + isFailed = true + } + + } else { + for i := 0; i < (prePickedNodeCount - selectorRow.Quantity); i++ { + desiredNodeList = desiredNodeList[:len(desiredNodeList)-1] + } + } + } + + matchExpression.Values = desiredNodeList + var nodeSelectorTerm corev1.NodeSelectorTerm + nodeSelectorTerm.MatchExpressions = append(nodeSelectorTerm.MatchExpressions, matchExpression) + nodeSelectorTermList = append(nodeSelectorTermList, nodeSelectorTerm) + } + } + return nodeSelectorTermList, isFailed +} + +// SetAsOwnerReference returns the selectivedeployment as owner +func SetAsOwnerReference(selectivedeploymentCopy *appsv1alpha3.SelectiveDeployment) []metav1.OwnerReference { + // The following section makes selectivedeployment become the owner + ownerReferences := []metav1.OwnerReference{} + newRef := *metav1.NewControllerRef(selectivedeploymentCopy, appsv1alpha3.SchemeGroupVersion.WithKind("SelectiveDeployment")) + takeControl := true + newRef.Controller = &takeControl + ownerReferences = append(ownerReferences, newRef) + return ownerReferences +} + +func checkOwnerReferences(selectivedeploymentCopy *appsv1alpha3.SelectiveDeployment, ownerReferences []metav1.OwnerReference) bool { + underControl := false + for _, reference := range ownerReferences { + if reference.Kind == "SelectiveDeployment" && reference.UID != selectivedeploymentCopy.GetUID() { + underControl = true + } + } + return underControl +} diff --git a/pkg/generated/clientset/versioned/clientset.go b/pkg/generated/clientset/versioned/clientset.go index 3111e637..1afe734d 100644 --- a/pkg/generated/clientset/versioned/clientset.go +++ b/pkg/generated/clientset/versioned/clientset.go @@ -24,6 +24,7 @@ import ( appsv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/apps/v1alpha1" appsv1alpha2 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/apps/v1alpha2" + appsv1alpha3 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/apps/v1alpha3" corev1alpha1 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/core/v1alpha1" federationv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/federation/v1alpha1" networkingv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/networking/v1alpha1" @@ -37,6 +38,7 @@ type Interface interface { Discovery() discovery.DiscoveryInterface AppsV1alpha1() appsv1alpha1.AppsV1alpha1Interface AppsV1alpha2() appsv1alpha2.AppsV1alpha2Interface + AppsV1alpha3() appsv1alpha3.AppsV1alpha3Interface CoreV1alpha1() corev1alpha1.CoreV1alpha1Interface FederationV1alpha1() federationv1alpha1.FederationV1alpha1Interface NetworkingV1alpha1() networkingv1alpha1.NetworkingV1alpha1Interface @@ -48,6 +50,7 @@ type Clientset struct { *discovery.DiscoveryClient appsV1alpha1 *appsv1alpha1.AppsV1alpha1Client appsV1alpha2 *appsv1alpha2.AppsV1alpha2Client + appsV1alpha3 *appsv1alpha3.AppsV1alpha3Client coreV1alpha1 *corev1alpha1.CoreV1alpha1Client federationV1alpha1 *federationv1alpha1.FederationV1alpha1Client networkingV1alpha1 *networkingv1alpha1.NetworkingV1alpha1Client @@ -64,6 +67,11 @@ func (c *Clientset) AppsV1alpha2() appsv1alpha2.AppsV1alpha2Interface { return c.appsV1alpha2 } +// AppsV1alpha3 retrieves the AppsV1alpha3Client +func (c *Clientset) AppsV1alpha3() appsv1alpha3.AppsV1alpha3Interface { + return c.appsV1alpha3 +} + // CoreV1alpha1 retrieves the CoreV1alpha1Client func (c *Clientset) CoreV1alpha1() corev1alpha1.CoreV1alpha1Interface { return c.coreV1alpha1 @@ -136,6 +144,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, if err != nil { return nil, err } + cs.appsV1alpha3, err = appsv1alpha3.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } cs.coreV1alpha1, err = corev1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err @@ -175,6 +187,7 @@ func New(c rest.Interface) *Clientset { var cs Clientset cs.appsV1alpha1 = appsv1alpha1.New(c) cs.appsV1alpha2 = appsv1alpha2.New(c) + cs.appsV1alpha3 = appsv1alpha3.New(c) cs.coreV1alpha1 = corev1alpha1.New(c) cs.federationV1alpha1 = federationv1alpha1.New(c) cs.networkingV1alpha1 = networkingv1alpha1.New(c) diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go index 2e77978c..a5199e75 100644 --- a/pkg/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go @@ -24,6 +24,8 @@ import ( fakeappsv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake" appsv1alpha2 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/apps/v1alpha2" fakeappsv1alpha2 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/apps/v1alpha2/fake" + appsv1alpha3 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/apps/v1alpha3" + fakeappsv1alpha3 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/apps/v1alpha3/fake" corev1alpha1 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/core/v1alpha1" fakecorev1alpha1 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/core/v1alpha1/fake" federationv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/federation/v1alpha1" @@ -99,6 +101,11 @@ func (c *Clientset) AppsV1alpha2() appsv1alpha2.AppsV1alpha2Interface { return &fakeappsv1alpha2.FakeAppsV1alpha2{Fake: &c.Fake} } +// AppsV1alpha3 retrieves the AppsV1alpha3Client +func (c *Clientset) AppsV1alpha3() appsv1alpha3.AppsV1alpha3Interface { + return &fakeappsv1alpha3.FakeAppsV1alpha3{Fake: &c.Fake} +} + // CoreV1alpha1 retrieves the CoreV1alpha1Client func (c *Clientset) CoreV1alpha1() corev1alpha1.CoreV1alpha1Interface { return &fakecorev1alpha1.FakeCoreV1alpha1{Fake: &c.Fake} diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go index 7b63ad4a..c66e7925 100644 --- a/pkg/generated/clientset/versioned/fake/register.go +++ b/pkg/generated/clientset/versioned/fake/register.go @@ -21,6 +21,7 @@ package fake import ( appsv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha1" appsv1alpha2 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha2" + appsv1alpha3 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha3" corev1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/core/v1alpha1" federationv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/federation/v1alpha1" networkingv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/networking/v1alpha1" @@ -38,6 +39,7 @@ var codecs = serializer.NewCodecFactory(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ appsv1alpha1.AddToScheme, appsv1alpha2.AddToScheme, + appsv1alpha3.AddToScheme, corev1alpha1.AddToScheme, federationv1alpha1.AddToScheme, networkingv1alpha1.AddToScheme, diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go index 3cfe2ae1..0bcd51a7 100644 --- a/pkg/generated/clientset/versioned/scheme/register.go +++ b/pkg/generated/clientset/versioned/scheme/register.go @@ -21,6 +21,7 @@ package scheme import ( appsv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha1" appsv1alpha2 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha2" + appsv1alpha3 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha3" corev1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/core/v1alpha1" federationv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/federation/v1alpha1" networkingv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/networking/v1alpha1" @@ -38,6 +39,7 @@ var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ appsv1alpha1.AddToScheme, appsv1alpha2.AddToScheme, + appsv1alpha3.AddToScheme, corev1alpha1.AddToScheme, federationv1alpha1.AddToScheme, networkingv1alpha1.AddToScheme, diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha3/apps_client.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/apps_client.go new file mode 100644 index 00000000..d2d195ba --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/apps_client.go @@ -0,0 +1,107 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + "net/http" + + v1alpha3 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha3" + "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/scheme" + rest "k8s.io/client-go/rest" +) + +type AppsV1alpha3Interface interface { + RESTClient() rest.Interface + SelectiveDeploymentsGetter +} + +// AppsV1alpha3Client is used to interact with features provided by the apps.edgenet.io group. +type AppsV1alpha3Client struct { + restClient rest.Interface +} + +func (c *AppsV1alpha3Client) SelectiveDeployments(namespace string) SelectiveDeploymentInterface { + return newSelectiveDeployments(c, namespace) +} + +// NewForConfig creates a new AppsV1alpha3Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*AppsV1alpha3Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new AppsV1alpha3Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AppsV1alpha3Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &AppsV1alpha3Client{client}, nil +} + +// NewForConfigOrDie creates a new AppsV1alpha3Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *AppsV1alpha3Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new AppsV1alpha3Client for the given RESTClient. +func New(c rest.Interface) *AppsV1alpha3Client { + return &AppsV1alpha3Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha3.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *AppsV1alpha3Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha3/doc.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/doc.go new file mode 100644 index 00000000..42d9c3ca --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha3 diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha3/fake/doc.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/fake/doc.go new file mode 100644 index 00000000..f8b3a309 --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha3/fake/fake_apps_client.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/fake/fake_apps_client.go new file mode 100644 index 00000000..ebe80173 --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/fake/fake_apps_client.go @@ -0,0 +1,40 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha3 "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/typed/apps/v1alpha3" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeAppsV1alpha3 struct { + *testing.Fake +} + +func (c *FakeAppsV1alpha3) SelectiveDeployments(namespace string) v1alpha3.SelectiveDeploymentInterface { + return &FakeSelectiveDeployments{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeAppsV1alpha3) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha3/fake/fake_selectivedeployment.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/fake/fake_selectivedeployment.go new file mode 100644 index 00000000..caf0bafa --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/fake/fake_selectivedeployment.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha3 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeSelectiveDeployments implements SelectiveDeploymentInterface +type FakeSelectiveDeployments struct { + Fake *FakeAppsV1alpha3 + ns string +} + +var selectivedeploymentsResource = v1alpha3.SchemeGroupVersion.WithResource("selectivedeployments") + +var selectivedeploymentsKind = v1alpha3.SchemeGroupVersion.WithKind("SelectiveDeployment") + +// Get takes name of the selectiveDeployment, and returns the corresponding selectiveDeployment object, and an error if there is any. +func (c *FakeSelectiveDeployments) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.SelectiveDeployment, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(selectivedeploymentsResource, c.ns, name), &v1alpha3.SelectiveDeployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.SelectiveDeployment), err +} + +// List takes label and field selectors, and returns the list of SelectiveDeployments that match those selectors. +func (c *FakeSelectiveDeployments) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.SelectiveDeploymentList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(selectivedeploymentsResource, selectivedeploymentsKind, c.ns, opts), &v1alpha3.SelectiveDeploymentList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha3.SelectiveDeploymentList{ListMeta: obj.(*v1alpha3.SelectiveDeploymentList).ListMeta} + for _, item := range obj.(*v1alpha3.SelectiveDeploymentList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested selectiveDeployments. +func (c *FakeSelectiveDeployments) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(selectivedeploymentsResource, c.ns, opts)) + +} + +// Create takes the representation of a selectiveDeployment and creates it. Returns the server's representation of the selectiveDeployment, and an error, if there is any. +func (c *FakeSelectiveDeployments) Create(ctx context.Context, selectiveDeployment *v1alpha3.SelectiveDeployment, opts v1.CreateOptions) (result *v1alpha3.SelectiveDeployment, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(selectivedeploymentsResource, c.ns, selectiveDeployment), &v1alpha3.SelectiveDeployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.SelectiveDeployment), err +} + +// Update takes the representation of a selectiveDeployment and updates it. Returns the server's representation of the selectiveDeployment, and an error, if there is any. +func (c *FakeSelectiveDeployments) Update(ctx context.Context, selectiveDeployment *v1alpha3.SelectiveDeployment, opts v1.UpdateOptions) (result *v1alpha3.SelectiveDeployment, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(selectivedeploymentsResource, c.ns, selectiveDeployment), &v1alpha3.SelectiveDeployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.SelectiveDeployment), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeSelectiveDeployments) UpdateStatus(ctx context.Context, selectiveDeployment *v1alpha3.SelectiveDeployment, opts v1.UpdateOptions) (*v1alpha3.SelectiveDeployment, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(selectivedeploymentsResource, "status", c.ns, selectiveDeployment), &v1alpha3.SelectiveDeployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.SelectiveDeployment), err +} + +// Delete takes name of the selectiveDeployment and deletes it. Returns an error if one occurs. +func (c *FakeSelectiveDeployments) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(selectivedeploymentsResource, c.ns, name, opts), &v1alpha3.SelectiveDeployment{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeSelectiveDeployments) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(selectivedeploymentsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha3.SelectiveDeploymentList{}) + return err +} + +// Patch applies the patch and returns the patched selectiveDeployment. +func (c *FakeSelectiveDeployments) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.SelectiveDeployment, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(selectivedeploymentsResource, c.ns, name, pt, data, subresources...), &v1alpha3.SelectiveDeployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha3.SelectiveDeployment), err +} diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha3/generated_expansion.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/generated_expansion.go new file mode 100644 index 00000000..b30474cb --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/generated_expansion.go @@ -0,0 +1,21 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha3 + +type SelectiveDeploymentExpansion interface{} diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha3/selectivedeployment.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/selectivedeployment.go new file mode 100644 index 00000000..98c50a3d --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha3/selectivedeployment.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + "context" + "time" + + v1alpha3 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha3" + scheme "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// SelectiveDeploymentsGetter has a method to return a SelectiveDeploymentInterface. +// A group's client should implement this interface. +type SelectiveDeploymentsGetter interface { + SelectiveDeployments(namespace string) SelectiveDeploymentInterface +} + +// SelectiveDeploymentInterface has methods to work with SelectiveDeployment resources. +type SelectiveDeploymentInterface interface { + Create(ctx context.Context, selectiveDeployment *v1alpha3.SelectiveDeployment, opts v1.CreateOptions) (*v1alpha3.SelectiveDeployment, error) + Update(ctx context.Context, selectiveDeployment *v1alpha3.SelectiveDeployment, opts v1.UpdateOptions) (*v1alpha3.SelectiveDeployment, error) + UpdateStatus(ctx context.Context, selectiveDeployment *v1alpha3.SelectiveDeployment, opts v1.UpdateOptions) (*v1alpha3.SelectiveDeployment, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha3.SelectiveDeployment, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha3.SelectiveDeploymentList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.SelectiveDeployment, err error) + SelectiveDeploymentExpansion +} + +// selectiveDeployments implements SelectiveDeploymentInterface +type selectiveDeployments struct { + client rest.Interface + ns string +} + +// newSelectiveDeployments returns a SelectiveDeployments +func newSelectiveDeployments(c *AppsV1alpha3Client, namespace string) *selectiveDeployments { + return &selectiveDeployments{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the selectiveDeployment, and returns the corresponding selectiveDeployment object, and an error if there is any. +func (c *selectiveDeployments) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha3.SelectiveDeployment, err error) { + result = &v1alpha3.SelectiveDeployment{} + err = c.client.Get(). + Namespace(c.ns). + Resource("selectivedeployments"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of SelectiveDeployments that match those selectors. +func (c *selectiveDeployments) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha3.SelectiveDeploymentList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha3.SelectiveDeploymentList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("selectivedeployments"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested selectiveDeployments. +func (c *selectiveDeployments) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("selectivedeployments"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a selectiveDeployment and creates it. Returns the server's representation of the selectiveDeployment, and an error, if there is any. +func (c *selectiveDeployments) Create(ctx context.Context, selectiveDeployment *v1alpha3.SelectiveDeployment, opts v1.CreateOptions) (result *v1alpha3.SelectiveDeployment, err error) { + result = &v1alpha3.SelectiveDeployment{} + err = c.client.Post(). + Namespace(c.ns). + Resource("selectivedeployments"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(selectiveDeployment). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a selectiveDeployment and updates it. Returns the server's representation of the selectiveDeployment, and an error, if there is any. +func (c *selectiveDeployments) Update(ctx context.Context, selectiveDeployment *v1alpha3.SelectiveDeployment, opts v1.UpdateOptions) (result *v1alpha3.SelectiveDeployment, err error) { + result = &v1alpha3.SelectiveDeployment{} + err = c.client.Put(). + Namespace(c.ns). + Resource("selectivedeployments"). + Name(selectiveDeployment.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(selectiveDeployment). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *selectiveDeployments) UpdateStatus(ctx context.Context, selectiveDeployment *v1alpha3.SelectiveDeployment, opts v1.UpdateOptions) (result *v1alpha3.SelectiveDeployment, err error) { + result = &v1alpha3.SelectiveDeployment{} + err = c.client.Put(). + Namespace(c.ns). + Resource("selectivedeployments"). + Name(selectiveDeployment.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(selectiveDeployment). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the selectiveDeployment and deletes it. Returns an error if one occurs. +func (c *selectiveDeployments) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("selectivedeployments"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *selectiveDeployments) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("selectivedeployments"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched selectiveDeployment. +func (c *selectiveDeployments) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha3.SelectiveDeployment, err error) { + result = &v1alpha3.SelectiveDeployment{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("selectivedeployments"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/generated/informers/externalversions/apps/interface.go b/pkg/generated/informers/externalversions/apps/interface.go index 8d9b50d7..35a5cff3 100644 --- a/pkg/generated/informers/externalversions/apps/interface.go +++ b/pkg/generated/informers/externalversions/apps/interface.go @@ -21,6 +21,7 @@ package apps import ( v1alpha1 "github.com/EdgeNet-project/edgenet/pkg/generated/informers/externalversions/apps/v1alpha1" v1alpha2 "github.com/EdgeNet-project/edgenet/pkg/generated/informers/externalversions/apps/v1alpha2" + v1alpha3 "github.com/EdgeNet-project/edgenet/pkg/generated/informers/externalversions/apps/v1alpha3" internalinterfaces "github.com/EdgeNet-project/edgenet/pkg/generated/informers/externalversions/internalinterfaces" ) @@ -30,6 +31,8 @@ type Interface interface { V1alpha1() v1alpha1.Interface // V1alpha2 provides access to shared informers for resources in V1alpha2. V1alpha2() v1alpha2.Interface + // V1alpha3 provides access to shared informers for resources in V1alpha3. + V1alpha3() v1alpha3.Interface } type group struct { @@ -52,3 +55,8 @@ func (g *group) V1alpha1() v1alpha1.Interface { func (g *group) V1alpha2() v1alpha2.Interface { return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions) } + +// V1alpha3 returns a new v1alpha3.Interface. +func (g *group) V1alpha3() v1alpha3.Interface { + return v1alpha3.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/generated/informers/externalversions/apps/v1alpha3/interface.go b/pkg/generated/informers/externalversions/apps/v1alpha3/interface.go new file mode 100644 index 00000000..c9803443 --- /dev/null +++ b/pkg/generated/informers/externalversions/apps/v1alpha3/interface.go @@ -0,0 +1,45 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + internalinterfaces "github.com/EdgeNet-project/edgenet/pkg/generated/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // SelectiveDeployments returns a SelectiveDeploymentInformer. + SelectiveDeployments() SelectiveDeploymentInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// SelectiveDeployments returns a SelectiveDeploymentInformer. +func (v *version) SelectiveDeployments() SelectiveDeploymentInformer { + return &selectiveDeploymentInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/generated/informers/externalversions/apps/v1alpha3/selectivedeployment.go b/pkg/generated/informers/externalversions/apps/v1alpha3/selectivedeployment.go new file mode 100644 index 00000000..fa7cf416 --- /dev/null +++ b/pkg/generated/informers/externalversions/apps/v1alpha3/selectivedeployment.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + "context" + time "time" + + appsv1alpha3 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha3" + versioned "github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned" + internalinterfaces "github.com/EdgeNet-project/edgenet/pkg/generated/informers/externalversions/internalinterfaces" + v1alpha3 "github.com/EdgeNet-project/edgenet/pkg/generated/listers/apps/v1alpha3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// SelectiveDeploymentInformer provides access to a shared informer and lister for +// SelectiveDeployments. +type SelectiveDeploymentInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha3.SelectiveDeploymentLister +} + +type selectiveDeploymentInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSelectiveDeploymentInformer constructs a new informer for SelectiveDeployment type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewSelectiveDeploymentInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSelectiveDeploymentInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSelectiveDeploymentInformer constructs a new informer for SelectiveDeployment type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredSelectiveDeploymentInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AppsV1alpha3().SelectiveDeployments(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AppsV1alpha3().SelectiveDeployments(namespace).Watch(context.TODO(), options) + }, + }, + &appsv1alpha3.SelectiveDeployment{}, + resyncPeriod, + indexers, + ) +} + +func (f *selectiveDeploymentInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSelectiveDeploymentInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *selectiveDeploymentInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&appsv1alpha3.SelectiveDeployment{}, f.defaultInformer) +} + +func (f *selectiveDeploymentInformer) Lister() v1alpha3.SelectiveDeploymentLister { + return v1alpha3.NewSelectiveDeploymentLister(f.Informer().GetIndexer()) +} diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go index 8cd31696..32547c38 100644 --- a/pkg/generated/informers/externalversions/generic.go +++ b/pkg/generated/informers/externalversions/generic.go @@ -23,6 +23,7 @@ import ( v1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha1" v1alpha2 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha2" + v1alpha3 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha3" corev1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/core/v1alpha1" federationv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/federation/v1alpha1" networkingv1alpha1 "github.com/EdgeNet-project/edgenet/pkg/apis/networking/v1alpha1" @@ -65,6 +66,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case v1alpha2.SchemeGroupVersion.WithResource("selectivedeployments"): return &genericInformer{resource: resource.GroupResource(), informer: f.Apps().V1alpha2().SelectiveDeployments().Informer()}, nil + // Group=apps.edgenet.io, Version=v1alpha3 + case v1alpha3.SchemeGroupVersion.WithResource("selectivedeployments"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Apps().V1alpha3().SelectiveDeployments().Informer()}, nil + // Group=core.edgenet.io, Version=v1alpha1 case corev1alpha1.SchemeGroupVersion.WithResource("nodecontributions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().V1alpha1().NodeContributions().Informer()}, nil diff --git a/pkg/generated/listers/apps/v1alpha3/expansion_generated.go b/pkg/generated/listers/apps/v1alpha3/expansion_generated.go new file mode 100644 index 00000000..b7cdb53f --- /dev/null +++ b/pkg/generated/listers/apps/v1alpha3/expansion_generated.go @@ -0,0 +1,27 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha3 + +// SelectiveDeploymentListerExpansion allows custom methods to be added to +// SelectiveDeploymentLister. +type SelectiveDeploymentListerExpansion interface{} + +// SelectiveDeploymentNamespaceListerExpansion allows custom methods to be added to +// SelectiveDeploymentNamespaceLister. +type SelectiveDeploymentNamespaceListerExpansion interface{} diff --git a/pkg/generated/listers/apps/v1alpha3/selectivedeployment.go b/pkg/generated/listers/apps/v1alpha3/selectivedeployment.go new file mode 100644 index 00000000..76fb9dcb --- /dev/null +++ b/pkg/generated/listers/apps/v1alpha3/selectivedeployment.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 Contributors to the EdgeNet project. + +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + v1alpha3 "github.com/EdgeNet-project/edgenet/pkg/apis/apps/v1alpha3" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// SelectiveDeploymentLister helps list SelectiveDeployments. +// All objects returned here must be treated as read-only. +type SelectiveDeploymentLister interface { + // List lists all SelectiveDeployments in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha3.SelectiveDeployment, err error) + // SelectiveDeployments returns an object that can list and get SelectiveDeployments. + SelectiveDeployments(namespace string) SelectiveDeploymentNamespaceLister + SelectiveDeploymentListerExpansion +} + +// selectiveDeploymentLister implements the SelectiveDeploymentLister interface. +type selectiveDeploymentLister struct { + indexer cache.Indexer +} + +// NewSelectiveDeploymentLister returns a new SelectiveDeploymentLister. +func NewSelectiveDeploymentLister(indexer cache.Indexer) SelectiveDeploymentLister { + return &selectiveDeploymentLister{indexer: indexer} +} + +// List lists all SelectiveDeployments in the indexer. +func (s *selectiveDeploymentLister) List(selector labels.Selector) (ret []*v1alpha3.SelectiveDeployment, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha3.SelectiveDeployment)) + }) + return ret, err +} + +// SelectiveDeployments returns an object that can list and get SelectiveDeployments. +func (s *selectiveDeploymentLister) SelectiveDeployments(namespace string) SelectiveDeploymentNamespaceLister { + return selectiveDeploymentNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// SelectiveDeploymentNamespaceLister helps list and get SelectiveDeployments. +// All objects returned here must be treated as read-only. +type SelectiveDeploymentNamespaceLister interface { + // List lists all SelectiveDeployments in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha3.SelectiveDeployment, err error) + // Get retrieves the SelectiveDeployment from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha3.SelectiveDeployment, error) + SelectiveDeploymentNamespaceListerExpansion +} + +// selectiveDeploymentNamespaceLister implements the SelectiveDeploymentNamespaceLister +// interface. +type selectiveDeploymentNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all SelectiveDeployments in the indexer for a given namespace. +func (s selectiveDeploymentNamespaceLister) List(selector labels.Selector) (ret []*v1alpha3.SelectiveDeployment, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha3.SelectiveDeployment)) + }) + return ret, err +} + +// Get retrieves the SelectiveDeployment from the indexer for a given namespace and name. +func (s selectiveDeploymentNamespaceLister) Get(name string) (*v1alpha3.SelectiveDeployment, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha3.Resource("selectivedeployment"), name) + } + return obj.(*v1alpha3.SelectiveDeployment), nil +} diff --git a/pkg/multiprovider/federation.go b/pkg/multiprovider/federation.go index 4baaa267..3ddfe892 100644 --- a/pkg/multiprovider/federation.go +++ b/pkg/multiprovider/federation.go @@ -109,19 +109,26 @@ func (m *Manager) PrepareSecretForRemoteCluster(name, namespace, clusterUID, rem return remoteSecret, false, nil } -// GetServerAddress retrieves the server address of the cluster from a control plane node +// GetServerAddress retrieves the server address of the cluster from a control plane node. The +// node can be any node that responds with the location and ip address. Returns empty if cannot find. +// address example "192.168.0.1:8443" +// location example "France/Paris" func (m *Manager) GetClusterAddressWithLocation() (string, string) { // TODO: This part needs to be changed to support multiple control plane nodes var address string var location string + + // Retrieve all the control-plane nodes nodeRaw, _ := m.kubeclientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{LabelSelector: "node-role.kubernetes.io/control-plane"}) + + // Get the node for _, node := range nodeRaw.Items { if internal, external := GetNodeIPAddresses(node.DeepCopy()); external == "" && internal == "" { continue } else if external != "" { - address = external + ":8443" + address = external + ":8443" // TODO: The API server address might be different than this } else { - address = internal + ":8443" + address = internal + ":8443" // TODO: The API server address might be different than this } labels := node.GetLabels() location = fmt.Sprintf("%s/%s", labels["edge-net.io/country"], labels["edge-net.io/city"])