From: Dileep Ranganathan Date: Wed, 7 Aug 2019 07:14:07 +0000 (-0700) Subject: Collectd Global CRD X-Git-Tag: 1.5.0~28 X-Git-Url: https://gerrit.onap.org/r/gitweb?p=demo.git;a=commitdiff_plain;h=787f229616b7d26d4a712288018b83c8de6d900c Collectd Global CRD This patch introduces an enhancement to the existing Collectd Operator. It has now 2 CRDS CollectdGlobal and CollectdPlugin and corresponding controllers. CollectdGlobal is used to specify the global options of collectd conf and CollectdPlugin is used to load/unload plugins. Added synchronization when both controllers try to fetch the same resources. Refactored some of the common code to collectdutils. Issue-ID: ONAPARC-461 Signed-off-by: Dileep Ranganathan Change-Id: Id2edf6fa6de56d9d7216ebb258b3017661c6a3dd --- diff --git a/vnfs/DAaaS/microservices/collectd-operator/deploy/crds/collectdglobal.yaml b/vnfs/DAaaS/microservices/collectd-operator/deploy/crds/collectdglobal.yaml new file mode 100644 index 00000000..60dec7c4 --- /dev/null +++ b/vnfs/DAaaS/microservices/collectd-operator/deploy/crds/collectdglobal.yaml @@ -0,0 +1,15 @@ +apiVersion: onap.org/v1alpha1 +kind: CollectdGlobal +metadata: + name: example-collectdglobal +spec: + # Add fields here + globalOptions: | + BaseDir "/opt/collectd/var/lib/collectd" + PIDFile "/opt/collectd/var/run/collectd.pid" + PluginDir "/opt/collectd/lib/collectd" + TypesDB "/opt/collectd/share/collectd/types.db" + TypesDB "/opt/collectd/share/collectd/vcmts.types.db" + #Hostname "localhost" + Interval 1 + configMap: typesdb-configmap diff --git a/vnfs/DAaaS/microservices/collectd-operator/deploy/crds/cpu_collectdplugin_cr.yaml b/vnfs/DAaaS/microservices/collectd-operator/deploy/crds/cpu_collectdplugin_cr.yaml index fb168c7a..d10b82af 100644 --- a/vnfs/DAaaS/microservices/collectd-operator/deploy/crds/cpu_collectdplugin_cr.yaml +++ b/vnfs/DAaaS/microservices/collectd-operator/deploy/crds/cpu_collectdplugin_cr.yaml @@ -4,11 +4,11 @@ metadata: name: cpu spec: # Add fields here - pluginName: "cpu" + pluginName: cpu pluginConf: | Interval 5 ReportByState false ReportByCpu false - \ No newline at end of file + diff --git a/vnfs/DAaaS/microservices/collectd-operator/deploy/crds/onap_v1alpha1_collectdglobal_crd.yaml b/vnfs/DAaaS/microservices/collectd-operator/deploy/crds/onap_v1alpha1_collectdglobal_crd.yaml new file mode 100644 index 00000000..29b13ec1 --- /dev/null +++ b/vnfs/DAaaS/microservices/collectd-operator/deploy/crds/onap_v1alpha1_collectdglobal_crd.yaml @@ -0,0 +1,64 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: collectdglobals.onap.org +spec: + group: onap.org + names: + kind: CollectdGlobal + listKind: CollectdGlobalList + plural: collectdglobals + singular: collectdglobal + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + configMap: + type: string + globalOptions: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "operator-sdk generate k8s" to regenerate code after + modifying this file Add custom validation using kubebuilder tags: + https://book.kubebuilder.io/beyond_basics/generating_crd.html' + type: string + required: + - globalOptions + type: object + status: + properties: + collectdAgents: + description: 'INSERT ADDITIONAL STATUS FIELD - define observed state + of cluster Important: Run "operator-sdk generate k8s" to regenerate + code after modifying this file Add custom validation using kubebuilder + tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html + CollectdAgents are the collectd pods in the Daemonset Status can be + one of "", Created, Deleting, Applied, Deprecated' + items: + type: string + type: array + status: + type: string + required: + - status + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/vnfs/DAaaS/microservices/collectd-operator/deploy/role.yaml b/vnfs/DAaaS/microservices/collectd-operator/deploy/role.yaml index d7aba353..504a16cc 100644 --- a/vnfs/DAaaS/microservices/collectd-operator/deploy/role.yaml +++ b/vnfs/DAaaS/microservices/collectd-operator/deploy/role.yaml @@ -44,5 +44,6 @@ rules: - onap.org resources: - '*' + - collectdglobals verbs: - '*' diff --git a/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1/collectdglobal_types.go b/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1/collectdglobal_types.go new file mode 100644 index 00000000..1094271d --- /dev/null +++ b/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1/collectdglobal_types.go @@ -0,0 +1,55 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// CollectdGlobalSpec defines the desired state of CollectdGlobal +// +k8s:openapi-gen=true +type CollectdGlobalSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html + GlobalOptions string `json:"globalOptions"` + ConfigMap string `json:"configMap,omitempty"` +} + +// CollectdGlobalStatus defines the observed state of CollectdGlobal +// +k8s:openapi-gen=true +type CollectdGlobalStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html + // CollectdAgents are the collectd pods in the Daemonset + // Status can be one of "", Created, Deleting, Applied, Deprecated + CollectdAgents []string `json:"collectdAgents,omitempty"` + Status string `json:"status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// CollectdGlobal is the Schema for the collectdglobals API +// +k8s:openapi-gen=true +// +kubebuilder:subresource:status +type CollectdGlobal struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec CollectdGlobalSpec `json:"spec,omitempty"` + Status CollectdGlobalStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// CollectdGlobalList contains a list of CollectdGlobal +type CollectdGlobalList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CollectdGlobal `json:"items"` +} + +func init() { + SchemeBuilder.Register(&CollectdGlobal{}, &CollectdGlobalList{}) +} diff --git a/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1/zz_generated.deepcopy.go b/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1/zz_generated.deepcopy.go index b7e2b34e..5a3c06f7 100644 --- a/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1/zz_generated.deepcopy.go +++ b/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1/zz_generated.deepcopy.go @@ -8,6 +8,104 @@ import ( 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 *CollectdGlobal) DeepCopyInto(out *CollectdGlobal) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectdGlobal. +func (in *CollectdGlobal) DeepCopy() *CollectdGlobal { + if in == nil { + return nil + } + out := new(CollectdGlobal) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CollectdGlobal) 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 *CollectdGlobalList) DeepCopyInto(out *CollectdGlobalList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CollectdGlobal, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectdGlobalList. +func (in *CollectdGlobalList) DeepCopy() *CollectdGlobalList { + if in == nil { + return nil + } + out := new(CollectdGlobalList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CollectdGlobalList) 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 *CollectdGlobalSpec) DeepCopyInto(out *CollectdGlobalSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectdGlobalSpec. +func (in *CollectdGlobalSpec) DeepCopy() *CollectdGlobalSpec { + if in == nil { + return nil + } + out := new(CollectdGlobalSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CollectdGlobalStatus) DeepCopyInto(out *CollectdGlobalStatus) { + *out = *in + if in.CollectdAgents != nil { + in, out := &in.CollectdAgents, &out.CollectdAgents + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectdGlobalStatus. +func (in *CollectdGlobalStatus) DeepCopy() *CollectdGlobalStatus { + if in == nil { + return nil + } + out := new(CollectdGlobalStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CollectdPlugin) DeepCopyInto(out *CollectdPlugin) { *out = *in diff --git a/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1/zz_generated.openapi.go b/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1/zz_generated.openapi.go index c58baaaf..72a2d349 100644 --- a/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1/zz_generated.openapi.go +++ b/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1/zz_generated.openapi.go @@ -13,12 +13,119 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ + "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1.CollectdGlobal": schema_pkg_apis_onap_v1alpha1_CollectdGlobal(ref), + "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1.CollectdGlobalSpec": schema_pkg_apis_onap_v1alpha1_CollectdGlobalSpec(ref), + "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1.CollectdGlobalStatus": schema_pkg_apis_onap_v1alpha1_CollectdGlobalStatus(ref), "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1.CollectdPlugin": schema_pkg_apis_onap_v1alpha1_CollectdPlugin(ref), "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1.CollectdPluginSpec": schema_pkg_apis_onap_v1alpha1_CollectdPluginSpec(ref), "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1.CollectdPluginStatus": schema_pkg_apis_onap_v1alpha1_CollectdPluginStatus(ref), } } +func schema_pkg_apis_onap_v1alpha1_CollectdGlobal(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CollectdGlobal is the Schema for the collectdglobals API", + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Ref: ref("demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1.CollectdGlobalSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Ref: ref("demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1.CollectdGlobalStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1.CollectdGlobalSpec", "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1.CollectdGlobalStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_onap_v1alpha1_CollectdGlobalSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CollectdGlobalSpec defines the desired state of CollectdGlobal", + Properties: map[string]spec.Schema{ + "globalOptions": { + SchemaProps: spec.SchemaProps{ + Description: "INSERT ADDITIONAL SPEC FIELDS - desired state of cluster Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html", + Type: []string{"string"}, + Format: "", + }, + }, + "configMap": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"globalOptions"}, + }, + }, + Dependencies: []string{}, + } +} + +func schema_pkg_apis_onap_v1alpha1_CollectdGlobalStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CollectdGlobalStatus defines the observed state of CollectdGlobal", + Properties: map[string]spec.Schema{ + "collectdAgents": { + SchemaProps: spec.SchemaProps{ + Description: "INSERT ADDITIONAL STATUS FIELD - define observed state of cluster Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html CollectdAgents are the collectd pods in the Daemonset Status can be one of \"\", Created, Deleting, Applied, Deprecated", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"status"}, + }, + }, + Dependencies: []string{}, + } +} + func schema_pkg_apis_onap_v1alpha1_CollectdPlugin(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/add_collectdglobal.go b/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/add_collectdglobal.go new file mode 100644 index 00000000..19792089 --- /dev/null +++ b/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/add_collectdglobal.go @@ -0,0 +1,10 @@ +package controller + +import ( + "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/collectdglobal" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, collectdglobal.Add) +} diff --git a/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/collectdglobal/collectdglobal_controller.go b/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/collectdglobal/collectdglobal_controller.go new file mode 100644 index 00000000..701580b3 --- /dev/null +++ b/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/collectdglobal/collectdglobal_controller.go @@ -0,0 +1,324 @@ +package collectdglobal + +import ( + "context" + "fmt" + //"path/filepath" + "reflect" + "strings" + + "github.com/go-logr/logr" + "github.com/operator-framework/operator-sdk/pkg/predicate" + + onapv1alpha1 "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1" + collectdutils "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/utils" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +var log = logf.Log.WithName("controller_collectdglobal") + +// Add creates a new CollectdGlobal Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + return &ReconcileCollectdGlobal{client: mgr.GetClient(), scheme: mgr.GetScheme()} +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + log.V(1).Info("Creating a new controller for CollectdGlobal") + c, err := controller.New("collectdglobal-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource CollectdGlobal + log.V(1).Info("Add watcher for primary resource CollectdGlobal") + err = c.Watch(&source.Kind{Type: &onapv1alpha1.CollectdGlobal{}}, &handler.EnqueueRequestForObject{}, predicate.GenerationChangedPredicate{}) + if err != nil { + return err + } + + log.V(1).Info("Add watcher for secondary resource Collectd Daemonset") + err = c.Watch( + &source.Kind{Type: &appsv1.DaemonSet{}}, + &handler.EnqueueRequestsFromMapFunc{ + ToRequests: handler.ToRequestsFunc(func(a handler.MapObject) []reconcile.Request { + labelSelector, err := collectdutils.GetWatchLabels() + labels := strings.Split(labelSelector, "=") + if err != nil { + log.Error(err, "Failed to get watch labels, continuing with default label") + } + rcp := r.(*ReconcileCollectdGlobal) + // Select the Daemonset with labelSelector (Defautl is app=collectd) + if a.Meta.GetLabels()[labels[0]] == labels[1] { + var requests []reconcile.Request + cg, err := collectdutils.GetCollectdGlobal(rcp.client, a.Meta.GetNamespace()) + if err != nil || cg == nil { + return nil + } + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKey{Namespace: cg.Namespace, Name: cg.Name}}) + return requests + } + return nil + }), + }) + if err != nil { + return err + } + + return nil +} + +// blank assignment to verify that ReconcileCollectdGlobal implements reconcile.Reconciler +var _ reconcile.Reconciler = &ReconcileCollectdGlobal{} + +// ReconcileCollectdGlobal reconciles a CollectdGlobal object +type ReconcileCollectdGlobal struct { + // This client, initialized using mgr.Client() above, is a split client + // that reads objects from the cache and writes to the apiserver + client client.Client + scheme *runtime.Scheme +} + +// Reconcile reads that state of the cluster for a CollectdGlobal object and makes changes based on the state read +// and what is in the CollectdGlobal.Spec +// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates +// a Pod as an example +// Note: +// The Controller will requeue the Request to be processed again if the returned error is non-nil or +// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. +func (r *ReconcileCollectdGlobal) Reconcile(request reconcile.Request) (reconcile.Result, error) { + reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) + reqLogger.Info("Reconciling CollectdGlobal") + + // Fetch the CollectdGlobal instance + instance := &onapv1alpha1.CollectdGlobal{} + err := r.client.Get(context.TODO(), request.NamespacedName, instance) + if err != nil { + if errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + reqLogger.V(1).Info("CollectdGlobal object Not found") + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + reqLogger.V(1).Info("Error reading the CollectdGlobal object, Requeuing") + return reconcile.Result{}, err + } + + // Handle Delete CR for additional cleanup + isDelete, err := r.handleDelete(reqLogger, instance) + if isDelete { + return reconcile.Result{}, err + } + + // Add finalizer for this CR + if !collectdutils.Contains(instance.GetFinalizers(), collectdutils.CollectdFinalizer) { + if err := r.addFinalizer(reqLogger, instance); err != nil { + return reconcile.Result{}, err + } + return reconcile.Result{}, nil + } + // Handle the reconciliation for CollectdGlobal. + // At this stage the Status of the CollectdGlobal should NOT be "" + err = r.handleCollectdGlobal(reqLogger, instance, false) + return reconcile.Result{}, err +} + +// handleCollectdGlobal regenerates the collectd conf on CR Create, Update, Delete events +func (r *ReconcileCollectdGlobal) handleCollectdGlobal(reqLogger logr.Logger, cr *onapv1alpha1.CollectdGlobal, isDelete bool) error { + + rmap, err := collectdutils.FindResourceMapForCR(r.client, reqLogger, cr.Namespace) + if err != nil { + reqLogger.Info(":::: Skip current reconcile:::: Resources not found. Cache might be stale. Requeue") + return err + } + + cm := rmap.ConfigMap + reqLogger.V(1).Info("Found ResourceMap") + reqLogger.V(1).Info(":::: ConfigMap Info ::::", "ConfigMap.Namespace", cm.Namespace, "ConfigMap.Name", cm.Name) + + collectdConf, err := collectdutils.RebuildCollectdConf(r.client, cr.Namespace, isDelete, "") + if err != nil { + reqLogger.Error(err, "Skip reconcile: Rebuild conf failed") + return err + } + + cm.SetAnnotations(map[string]string{ + "daaas-random": collectdutils.ComputeSHA256([]byte(collectdConf)), + }) + + cm.Data["collectd.conf"] = collectdConf + retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { + // Update the ConfigMap with new Spec and reload DaemonSets + reqLogger.Info("Updating the ConfigMap", "ConfigMap.Namespace", cm.Namespace, "ConfigMap.Name", cm.Name) + log.V(1).Info("ConfigMap Data", "Map: ", cm.Data) + err = r.client.Update(context.TODO(), cm) + if err != nil { + reqLogger.Error(err, "Update the ConfigMap failed", "ConfigMap.Namespace", cm.Namespace, "ConfigMap.Name", cm.Name) + return err + } + return nil + }) + if retryErr != nil { + panic(fmt.Errorf("Update failed: %v", retryErr)) + } + + retryErr = retry.RetryOnConflict(retry.DefaultRetry, func() error { + // Retrieve the latest version of Daemonset before attempting update + // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver + // Select DaemonSets with label + dsList := &extensionsv1beta1.DaemonSetList{} + opts := &client.ListOptions{} + labelSelector, err := collectdutils.GetWatchLabels() + if err != nil { + reqLogger.Error(err, "Failed to get watch labels, continuing with default label") + } + opts.SetLabelSelector(labelSelector) + opts.InNamespace(cr.Namespace) + err = r.client.List(context.TODO(), opts, dsList) + if err != nil { + panic(fmt.Errorf("Failed to get latest version of DaemonSet: %v", err)) + } + + if dsList.Items == nil || len(dsList.Items) == 0 { + return errors.NewNotFound(corev1.Resource("daemonset"), "DaemonSet") + } + ds := &dsList.Items[0] + //Restart Collectd Pods + reqLogger.Info("Reloading the Daemonset", "DaemonSet.Namespace", ds.Namespace, "DaemonSet.Name", ds.Name) + //Restart only if hash of conf has changed. + ds.Spec.Template.SetAnnotations(map[string]string{ + "daaas-random": collectdutils.ComputeSHA256([]byte(collectdConf)), + }) + r.handleAdditonalConfigMap(reqLogger, cr, ds) + updateErr := r.client.Update(context.TODO(), ds) + return updateErr + }) + if retryErr != nil { + panic(fmt.Errorf("Update failed: %v", retryErr)) + } + + err = r.updateStatus(cr) + if err != nil { + reqLogger.Error(err, "Unable to update status") + return err + } + // Reconcile success + reqLogger.Info("Reconcile success!!") + return nil +} + +// Handle Delete CR event for additional cleanup +func (r *ReconcileCollectdGlobal) handleDelete(reqLogger logr.Logger, cr *onapv1alpha1.CollectdGlobal) (bool, error) { + // Check if the CollectdGlobal instance is marked to be deleted, which is + // indicated by the deletion timestamp being set. + isMarkedToBeDeleted := cr.GetDeletionTimestamp() != nil + if isMarkedToBeDeleted { + // Update status to Deleting state + cr.Status.Status = onapv1alpha1.Deleting + cr.Status.CollectdAgents = nil + _ = r.client.Status().Update(context.TODO(), cr) + + if collectdutils.Contains(cr.GetFinalizers(), collectdutils.CollectdFinalizer) { + // Run finalization logic for CollectdFinalizer. If the + // finalization logic fails, don't remove the finalizer so + // that we can retry during the next reconciliation. + if err := r.finalizeCollectdGlobal(reqLogger, cr); err != nil { + return isMarkedToBeDeleted, err + } + + // Remove CollectdFinalizer. Once all finalizers have been + // removed, the object will be deleted. + cr.SetFinalizers(collectdutils.Remove(cr.GetFinalizers(), collectdutils.CollectdFinalizer)) + err := r.client.Update(context.TODO(), cr) + if err != nil { + return isMarkedToBeDeleted, err + } + } + } + return isMarkedToBeDeleted, nil +} + +func (r *ReconcileCollectdGlobal) updateStatus(cr *onapv1alpha1.CollectdGlobal) error { + switch cr.Status.Status { + case onapv1alpha1.Initial: + cr.Status.Status = onapv1alpha1.Created + case onapv1alpha1.Created, onapv1alpha1.Enabled: + pods, err := collectdutils.GetPodList(r.client, cr.Namespace) + if err != nil { + return err + } + if !reflect.DeepEqual(pods, cr.Status.CollectdAgents) { + cr.Status.CollectdAgents = pods + cr.Status.Status = onapv1alpha1.Enabled + } + case onapv1alpha1.Deleting, onapv1alpha1.Deprecated: + return nil + } + err := r.client.Status().Update(context.TODO(), cr) + return err +} + +func (r *ReconcileCollectdGlobal) finalizeCollectdGlobal(reqLogger logr.Logger, cr *onapv1alpha1.CollectdGlobal) error { + // Cleanup by regenerating new collectd conf and rolling update of DaemonSet + if err := r.handleCollectdGlobal(reqLogger, cr, true); err != nil { + reqLogger.Error(err, "Finalize CollectdGlobal failed!!") + return err + } + reqLogger.Info("Successfully finalized CollectdGlobal!!") + return nil +} + +func (r *ReconcileCollectdGlobal) addFinalizer(reqLogger logr.Logger, cr *onapv1alpha1.CollectdGlobal) error { + reqLogger.Info("Adding Finalizer for the CollectdGlobal") + cr.SetFinalizers(append(cr.GetFinalizers(), collectdutils.CollectdFinalizer)) + // Update status from Initial to Created + // Since addFinalizer will be executed only once, + // the status will be changed from Initial state to Created + updateErr := r.updateStatus(cr) + if updateErr != nil { + reqLogger.Error(updateErr, "Failed to update status from Initial state") + } + // Update CR + err := r.client.Update(context.TODO(), cr) + if err != nil { + reqLogger.Error(err, "Failed to update CollectdGlobal with finalizer") + return err + } + return nil +} + +func (r *ReconcileCollectdGlobal) handleAdditonalConfigMap(reqLogger logr.Logger, cr *onapv1alpha1.CollectdGlobal, ds *extensionsv1beta1.DaemonSet) error { + cm := &corev1.ConfigMap{} + key := types.NamespacedName{Namespace: cr.Namespace, Name: cr.Spec.ConfigMap} + err := r.client.Get(context.TODO(), key, cm) + if err != nil { + reqLogger.Info("Error getting TypesDB") + return nil + } + // TODO: Implement Types.DB mounting + return nil +} diff --git a/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/collectdplugin/collectdplugin_controller.go b/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/collectdplugin/collectdplugin_controller.go index 11cf0bc1..4ce32eb2 100644 --- a/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/collectdplugin/collectdplugin_controller.go +++ b/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/collectdplugin/collectdplugin_controller.go @@ -2,15 +2,14 @@ package collectdplugin import ( "context" - "crypto/sha256" "fmt" - "os" "reflect" "strings" "github.com/go-logr/logr" onapv1alpha1 "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1" + collectdutils "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/utils" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -29,17 +28,6 @@ import ( var log = logf.Log.WithName("controller_collectdplugin") -// ResourceMap to hold objects to update/reload -type ResourceMap struct { - configMap *corev1.ConfigMap - daemonSet *extensionsv1beta1.DaemonSet - collectdPlugins *[]onapv1alpha1.CollectdPlugin -} - -/** -* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller -* business logic. Delete these comments after modifying this file.* - */ // Add creates a new CollectdPlugin Controller and adds it to the Manager. The Manager will set fields on the Controller // and Start it when the Manager is Started. @@ -73,7 +61,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { &source.Kind{Type: &appsv1.DaemonSet{}}, &handler.EnqueueRequestsFromMapFunc{ ToRequests: handler.ToRequestsFunc(func (a handler.MapObject) []reconcile.Request { - labelSelector, err := getWatchLabels() + labelSelector, err := collectdutils.GetWatchLabels() labels := strings.Split(labelSelector, "=") if err != nil { log.Error(err, "Failed to get watch labels, continuing with default label") @@ -82,7 +70,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { // Select the Daemonset with labelSelector (Defautl is app=collectd) if a.Meta.GetLabels()[labels[0]] == labels[1] { var requests []reconcile.Request - cpList, err := rcp.getCollectdPluginList(a.Meta.GetNamespace()) + cpList, err := collectdutils.GetCollectdPluginList(rcp.client, a.Meta.GetNamespace()) if err != nil { return nil } @@ -114,16 +102,6 @@ type ReconcileCollectdPlugin struct { scheme *runtime.Scheme } -// Define the collectdPlugin finalizer for handling deletion -const ( - defaultWatchLabel = "app=collectd" - collectdPluginFinalizer = "finalizer.collectdplugin.onap.org" - - // WatchLabelsEnvVar is the constant for env variable WATCH_LABELS - // which is the labels where the watch activity happens. - // this value is empty if the operator is running with clusterScope. - WatchLabelsEnvVar = "WATCH_LABELS" -) // Reconcile reads that state of the cluster for a CollectdPlugin object and makes changes based on the state read // and what is in the CollectdPlugin.Spec @@ -159,7 +137,7 @@ func (r *ReconcileCollectdPlugin) Reconcile(request reconcile.Request) (reconcil } // Add finalizer for this CR - if !contains(instance.GetFinalizers(), collectdPluginFinalizer) { + if !collectdutils.Contains(instance.GetFinalizers(), collectdutils.CollectdFinalizer) { if err := r.addFinalizer(reqLogger, instance); err != nil { return reconcile.Result{}, err } @@ -174,24 +152,27 @@ func (r *ReconcileCollectdPlugin) Reconcile(request reconcile.Request) (reconcil // handleCollectdPlugin regenerates the collectd conf on CR Create, Update, Delete events func (r *ReconcileCollectdPlugin) handleCollectdPlugin(reqLogger logr.Logger, cr *onapv1alpha1.CollectdPlugin, isDelete bool) error { - rmap, err := r.findResourceMapForCR(reqLogger, cr) + rmap, err := collectdutils.FindResourceMapForCR(r.client, reqLogger, cr.Namespace) if err != nil { reqLogger.Error(err, "Skip reconcile: Resources not found") return err } - cm := rmap.configMap - collectPlugins := rmap.collectdPlugins + cm := rmap.ConfigMap reqLogger.V(1).Info("Found ResourceMap") reqLogger.V(1).Info(":::: ConfigMap Info ::::", "ConfigMap.Namespace", cm.Namespace, "ConfigMap.Name", cm.Name) - collectdConf, err := rebuildCollectdConf(cr, collectPlugins, isDelete) + collectdConf, err := collectdutils.RebuildCollectdConf(r.client, cr.Namespace, isDelete, cr.Spec.PluginName) + if err != nil { + reqLogger.Error(err, "Skip reconcile: Rebuild conf failed") + return err + } cm.SetAnnotations(map[string]string{ - "daaas-random": ComputeSHA256([]byte(collectdConf)), + "daaas-random": collectdutils.ComputeSHA256([]byte(collectdConf)), }) - cm.Data["node-collectd.conf"] = collectdConf + cm.Data["collectd.conf"] = collectdConf // Update the ConfigMap with new Spec and reload DaemonSets reqLogger.Info("Updating the ConfigMap", "ConfigMap.Namespace", cm.Namespace, "ConfigMap.Name", cm.Name) @@ -208,7 +189,7 @@ func (r *ReconcileCollectdPlugin) handleCollectdPlugin(reqLogger logr.Logger, cr // Select DaemonSets with label dsList := &extensionsv1beta1.DaemonSetList{} opts := &client.ListOptions{} - labelSelector, err := getWatchLabels() + labelSelector, err := collectdutils.GetWatchLabels() if err != nil { reqLogger.Error(err, "Failed to get watch labels, continuing with default label") } @@ -227,7 +208,7 @@ func (r *ReconcileCollectdPlugin) handleCollectdPlugin(reqLogger logr.Logger, cr reqLogger.Info("Reloading the Daemonset", "DaemonSet.Namespace", ds.Namespace, "DaemonSet.Name", ds.Name) //Restart only if hash of conf has changed. ds.Spec.Template.SetAnnotations(map[string]string{ - "daaas-random": ComputeSHA256([]byte(collectdConf)), + "daaas-random": collectdutils.ComputeSHA256([]byte(collectdConf)), }) updateErr := r.client.Update(context.TODO(), ds) return updateErr @@ -246,89 +227,6 @@ func (r *ReconcileCollectdPlugin) handleCollectdPlugin(reqLogger logr.Logger, cr return nil } -// ComputeSHA256 returns hash of data as string -func ComputeSHA256(data []byte) string { - hash := sha256.Sum256(data) - return fmt.Sprintf("%x", hash) -} - -// findResourceMapForCR returns the configMap, collectd Daemonset and list of Collectd Plugins -func (r *ReconcileCollectdPlugin) findResourceMapForCR(reqLogger logr.Logger, cr *onapv1alpha1.CollectdPlugin) (ResourceMap, error) { - cmList := &corev1.ConfigMapList{} - opts := &client.ListOptions{} - rmap := ResourceMap{} - - // Select ConfigMaps with label - labelSelector, err := getWatchLabels() - if err != nil { - reqLogger.Error(err, "Failed to get watch labels, continuing with default label") - } - opts.SetLabelSelector(labelSelector) - opts.InNamespace(cr.Namespace) - - err = r.client.List(context.TODO(), opts, cmList) - if err != nil { - return rmap, err - } - - if cmList.Items == nil || len(cmList.Items) == 0 { - return rmap, errors.NewNotFound(corev1.Resource("configmap"), "ConfigMap") - } - - // Select DaemonSets with label - dsList := &extensionsv1beta1.DaemonSetList{} - err = r.client.List(context.TODO(), opts, dsList) - if err != nil { - return rmap, err - } - - if dsList.Items == nil || len(dsList.Items) == 0 { - return rmap, errors.NewNotFound(corev1.Resource("daemonset"), "DaemonSet") - } - - // Get all collectd plugins in the current namespace to rebuild conf. - cpList, err := r.getCollectdPluginList(cr.Namespace) - if err != nil { - return rmap, err - } - - rmap.configMap = &cmList.Items[0] - rmap.daemonSet = &dsList.Items[0] - rmap.collectdPlugins = &cpList.Items //will be nil if no plugins exist - return rmap, err -} - -// Get all collectd plugins and reconstruct, compute Hash and check for changes -func rebuildCollectdConf(cr *onapv1alpha1.CollectdPlugin, cpList *[]onapv1alpha1.CollectdPlugin, isDelete bool) (string, error) { - var collectdConf string - if *cpList == nil || len(*cpList) == 0 { - return "", errors.NewNotFound(corev1.Resource("collectdplugin"), "CollectdPlugin") - } - loadPlugin := make(map[string]string) - for _, cp := range *cpList { - if cp.Spec.PluginName == "global" { - collectdConf += cp.Spec.PluginConf + "\n" - } else { - loadPlugin[cp.Spec.PluginName] = cp.Spec.PluginConf - } - } - - if isDelete { - delete(loadPlugin, cr.Spec.PluginName) - } - - log.V(1).Info("::::::: Plugins Map ::::::: ", "PluginMap ", loadPlugin) - - for cpName, cpConf := range loadPlugin { - collectdConf += "LoadPlugin" + " " + cpName + "\n" - collectdConf += cpConf + "\n" - } - - collectdConf += "#Last line (collectd requires ‘\\n’ at the last line)\n" - - return collectdConf, nil -} - // Handle Delete CR event for additional cleanup func (r *ReconcileCollectdPlugin) handleDelete(reqLogger logr.Logger, cr *onapv1alpha1.CollectdPlugin) (bool, error) { // Check if the CollectdPlugin instance is marked to be deleted, which is @@ -340,7 +238,7 @@ func (r *ReconcileCollectdPlugin) handleDelete(reqLogger logr.Logger, cr *onapv1 cr.Status.CollectdAgents = nil _ = r.client.Status().Update(context.TODO(), cr) - if contains(cr.GetFinalizers(), collectdPluginFinalizer) { + if collectdutils.Contains(cr.GetFinalizers(), collectdutils.CollectdFinalizer) { // Run finalization logic for collectdPluginFinalizer. If the // finalization logic fails, don't remove the finalizer so // that we can retry during the next reconciliation. @@ -350,7 +248,7 @@ func (r *ReconcileCollectdPlugin) handleDelete(reqLogger logr.Logger, cr *onapv1 // Remove collectdPluginFinalizer. Once all finalizers have been // removed, the object will be deleted. - cr.SetFinalizers(remove(cr.GetFinalizers(), collectdPluginFinalizer)) + cr.SetFinalizers(collectdutils.Remove(cr.GetFinalizers(), collectdutils.CollectdFinalizer)) err := r.client.Update(context.TODO(), cr) if err != nil { return isMarkedToBeDeleted, err @@ -365,7 +263,7 @@ func (r *ReconcileCollectdPlugin) updateStatus(cr *onapv1alpha1.CollectdPlugin) case onapv1alpha1.Initial: cr.Status.Status = onapv1alpha1.Created case onapv1alpha1.Created, onapv1alpha1.Enabled: - pods, err := r.getPodList(cr.Namespace) + pods, err := collectdutils.GetPodList(r.client, cr.Namespace) if err != nil { return err } @@ -392,7 +290,7 @@ func (r *ReconcileCollectdPlugin) finalizeCollectdPlugin(reqLogger logr.Logger, func (r *ReconcileCollectdPlugin) addFinalizer(reqLogger logr.Logger, cr *onapv1alpha1.CollectdPlugin) error { reqLogger.Info("Adding Finalizer for the CollectdPlugin") - cr.SetFinalizers(append(cr.GetFinalizers(), collectdPluginFinalizer)) + cr.SetFinalizers(append(cr.GetFinalizers(), collectdutils.CollectdFinalizer)) // Update status from Initial to Created // Since addFinalizer will be executed only once, // the status will be changed from Initial state to Created @@ -408,65 +306,3 @@ func (r *ReconcileCollectdPlugin) addFinalizer(reqLogger logr.Logger, cr *onapv1 } return nil } - -func contains(list []string, s string) bool { - for _, v := range list { - if v == s { - return true - } - } - return false -} - -func remove(list []string, s string) []string { - for i, v := range list { - if v == s { - list = append(list[:i], list[i+1:]...) - } - } - return list -} - -// getWatchLabels returns the labels the operator should be watching for changes -func getWatchLabels() (string, error) { - labelSelector, found := os.LookupEnv(WatchLabelsEnvVar) - if !found { - return defaultWatchLabel, fmt.Errorf("%s must be set", WatchLabelsEnvVar) - } - return labelSelector, nil -} - -func (r *ReconcileCollectdPlugin) getPodList(ns string) ([]string, error) { - var pods []string - podList := &corev1.PodList{} - opts := &client.ListOptions{} - // Select ConfigMaps with label - labelSelector, _ := getWatchLabels() - opts.SetLabelSelector(labelSelector) - opts.InNamespace(ns) - err := r.client.List(context.TODO(), opts, podList) - if err != nil { - return nil, err - } - - if podList.Items == nil || len(podList.Items) == 0 { - return nil, err - } - - for _, pod := range podList.Items { - pods = append(pods, pod.Name) - } - return pods, nil -} - -func (r *ReconcileCollectdPlugin) getCollectdPluginList(ns string) (*onapv1alpha1.CollectdPluginList, error) { - // Get all collectd plugins in the current namespace to rebuild conf. - collectdPlugins := &onapv1alpha1.CollectdPluginList{} - cpOpts := &client.ListOptions{} - cpOpts.InNamespace(ns) - err := r.client.List(context.TODO(), cpOpts, collectdPlugins) - if err != nil { - return nil, err - } - return collectdPlugins, nil -} diff --git a/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/utils/collectdutils.go b/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/utils/collectdutils.go new file mode 100644 index 00000000..0eb4e322 --- /dev/null +++ b/vnfs/DAaaS/microservices/collectd-operator/pkg/controller/utils/collectdutils.go @@ -0,0 +1,218 @@ +package utils + +import ( + "context" + "crypto/sha256" + "fmt" + "os" + "sync" + + "github.com/go-logr/logr" + + onapv1alpha1 "demo/vnfs/DAaaS/microservices/collectd-operator/pkg/apis/onap/v1alpha1" + + corev1 "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// Define the collectdPlugin finalizer for handling deletion +const ( + defaultWatchLabel = "app=collectd" + CollectdFinalizer = "finalizer.collectd.onap.org" + + // WatchLabelsEnvVar is the constant for env variable WATCH_LABELS + // which is the labels where the watch activity happens. + // this value is empty if the operator is running with clusterScope. + WatchLabelsEnvVar = "WATCH_LABELS" +) + +var lock sync.Mutex + +// ResourceMap to hold objects to update/reload +type ResourceMap struct { + ConfigMap *corev1.ConfigMap + DaemonSet *extensionsv1beta1.DaemonSet + CollectdPlugins *[]onapv1alpha1.CollectdPlugin +} + +// ComputeSHA256 returns hash of data as string +func ComputeSHA256(data []byte) string { + hash := sha256.Sum256(data) + return fmt.Sprintf("%x", hash) +} + +// Contains checks if a string is contained in a list of strings +func Contains(list []string, s string) bool { + for _, v := range list { + if v == s { + return true + } + } + return false +} + +// Remove removes a string from a list of string +func Remove(list []string, s string) []string { + for i, v := range list { + if v == s { + list = append(list[:i], list[i+1:]...) + } + } + return list +} + +// GetWatchLabels returns the labels the operator should be watching for changes +func GetWatchLabels() (string, error) { + labelSelector, found := os.LookupEnv(WatchLabelsEnvVar) + if !found { + return defaultWatchLabel, fmt.Errorf("%s must be set", WatchLabelsEnvVar) + } + return labelSelector, nil +} + +// FindResourceMapForCR returns the configMap, collectd Daemonset and list of Collectd Plugins +func FindResourceMapForCR(rc client.Client, reqLogger logr.Logger, ns string) (*ResourceMap, error) { + lock.Lock() + defer lock.Unlock() + cmList := &corev1.ConfigMapList{} + opts := &client.ListOptions{} + rmap := &ResourceMap{} + + // Select ConfigMaps with label + labelSelector, err := GetWatchLabels() + if err != nil { + reqLogger.Error(err, "Failed to get watch labels, continuing with default label") + } + opts.SetLabelSelector(labelSelector) + opts.InNamespace(ns) + + err = rc.List(context.TODO(), opts, cmList) + if err != nil { + return rmap, err + } + + if cmList.Items == nil || len(cmList.Items) == 0 { + return rmap, errors.NewNotFound(corev1.Resource("configmap"), "ConfigMap") + } + + // Select DaemonSets with label + dsList := &extensionsv1beta1.DaemonSetList{} + err = rc.List(context.TODO(), opts, dsList) + if err != nil { + return rmap, err + } + + if dsList.Items == nil || len(dsList.Items) == 0 { + return rmap, errors.NewNotFound(corev1.Resource("daemonset"), "DaemonSet") + } + + rmap.ConfigMap = &cmList.Items[0] + rmap.DaemonSet = &dsList.Items[0] + + return rmap, err +} + +// GetCollectdPluginList returns the list of CollectdPlugin instances in the namespace ns +func GetCollectdPluginList(rc client.Client, ns string) (*onapv1alpha1.CollectdPluginList, error) { + // Get all collectd plugins in the current namespace to rebuild conf. + collectdPlugins := &onapv1alpha1.CollectdPluginList{} + cpOpts := &client.ListOptions{} + cpOpts.InNamespace(ns) + err := rc.List(context.TODO(), cpOpts, collectdPlugins) + if err != nil { + return nil, err + } + return collectdPlugins, nil +} + +// GetCollectdGlobal returns the CollectdGlobal instance in the namespace ns +func GetCollectdGlobal(rc client.Client, ns string) (*onapv1alpha1.CollectdGlobal, error) { + // Get the CollectdGlobal instance in current namespace to rebuild conf. + cgList := &onapv1alpha1.CollectdGlobalList{} + cpOpts := &client.ListOptions{} + cpOpts.InNamespace(ns) + err := rc.List(context.TODO(), cpOpts, cgList) + if err != nil { + return nil, err + } + if cgList.Items == nil || len(cgList.Items) == 0 { + return nil, err + } + collectdGlobals := &cgList.Items[0] + return collectdGlobals, nil +} + +// GetPodList returns the list of pods in the namespace ns +func GetPodList(rc client.Client, ns string) ([]string, error) { + var pods []string + podList := &corev1.PodList{} + opts := &client.ListOptions{} + // Select ConfigMaps with label + labelSelector, _ := GetWatchLabels() + opts.SetLabelSelector(labelSelector) + opts.InNamespace(ns) + err := rc.List(context.TODO(), opts, podList) + if err != nil { + return nil, err + } + + if podList.Items == nil || len(podList.Items) == 0 { + return nil, err + } + + for _, pod := range podList.Items { + pods = append(pods, pod.Name) + } + return pods, nil +} + +// RebuildCollectdConf Get all collectd plugins and reconstruct, compute Hash and check for changes +func RebuildCollectdConf(rc client.Client, ns string, isDelete bool, delPlugin string) (string, error) { + var collectdConf, collectdGlobalConf string + // Get the collectd global in the current namespace to rebuild conf. + cg, err := GetCollectdGlobal(rc, ns) + if err != nil { + return "", err + } + + if cg != nil { + collectdGlobalConf += cg.Spec.GlobalOptions + "\n" + } + + // Get all collectd plugins in the current namespace to rebuild conf. + cp, err := GetCollectdPluginList(rc, ns) + if err != nil { + return "", err + } + cpList := &cp.Items + loadPlugin := make(map[string]string) + if *cpList != nil && len(*cpList) > 0 { + for _, cp := range *cpList { + // using CollectdPlugin to set global options. If CollectdGlobal CRD is defined do not check for this + if cp.Spec.PluginName == "global" { + if cg == nil { + collectdGlobalConf += cp.Spec.PluginConf + "\n" + } + } else { + loadPlugin[cp.Spec.PluginName] = cp.Spec.PluginConf + } + } + } + + if isDelete { + delete(loadPlugin, delPlugin) + } else { + collectdConf += collectdGlobalConf + } + + for cpName, cpConf := range loadPlugin { + collectdConf += "LoadPlugin" + " " + cpName + "\n" + collectdConf += cpConf + "\n" + } + + collectdConf += "#Last line (collectd requires ‘\\n’ at the last line)\n" + + return collectdConf, nil +}