diff --git a/.zuul.yaml b/.zuul.yaml index 0bb95eba..6370d625 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -12,6 +12,8 @@ repository: vexxhost/memcached - context: images/memcached_exporter repository: vexxhost/memcached_exporter + - context: images/rabbitmq + repository: vexxhost/rabbitmq - context: . repository: vexxhost/openstack-operator @@ -65,4 +67,4 @@ - openstack-operator:images:upload promote: jobs: - - openstack-operator:images:promote + - openstack-operator:images:promote \ No newline at end of file diff --git a/Makefile b/Makefile index 9b834852..a0460600 100644 --- a/Makefile +++ b/Makefile @@ -86,4 +86,6 @@ images: docker build images/mcrouter -t vexxhost/mcrouter:latest docker build images/mcrouter_exporter -t vexxhost/mcrouter_exporter:latest docker build images/memcached -t vexxhost/memcached:latest - docker build images/memcached_exporter -t vexxhost/memcached_exporter:latest \ No newline at end of file + docker build images/memcached_exporter -t vexxhost/memcached_exporter:latest + docker build images/rabbitmq -t vexxhost/rabbitmq:latest + diff --git a/api/v1alpha1/rabbitmq_type.go b/api/v1alpha1/rabbitmq_type.go new file mode 100644 index 00000000..076db043 --- /dev/null +++ b/api/v1alpha1/rabbitmq_type.go @@ -0,0 +1,65 @@ +package v1alpha1 + +import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// RabbitmqPolicySpec defines the Rabbitmq Policy Spec for the Vhost +type RabbitmqPolicyDefinitionSpec struct { + Vhost string `json:"vhost,omitempty"` + Name string `json:"name"` + Pattern string `json:"pattern"` + Definition RabbitmqPolicyDefinition `json:"definition"` + Priority int64 `json:"priority"` + ApplyTo string `json:"apply-to"` +} + +// RabbitmqPolicyDefinition defines the Rabbitmq Policy content +type RabbitmqPolicyDefinition struct { + FederationUpstreamSet string `json:"federation-upstream-set,omitempty"` + HaMode string `json:"ha-mode,omitempty"` + HaParams int `json:"ha-params,omitempty"` + HaSyncMode string `json:"ha-sync-mode,omitempty"` + Expires int `json:"expires,omitempty"` + MessageTTL int `json:"message-ttl,omitempty"` + MaxLen int `json:"max-length,omitempty"` + MaxLenBytes int `json:"max-length-bytes,omitempty"` +} + +// RabbitmqSpec defines the desired state of Rabbitmq +type RabbitmqSpec struct { + AuthSecret string `json:"authSecret"` + Policies []RabbitmqPolicyDefinitionSpec `json:"policies,omitempty"` + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + Tolerations []v1.Toleration `json:"tolerations,omitempty"` +} + +// RabbitmqStatus defines the observed state of Rabbitmq +type RabbitmqStatus struct { + // +kubebuilder:validation:Default=Pending + Phase string `json:"phase"` +} + +// Rabbitmq is the Schema for the Rabbitmqs API +// +kubebuilder:object:root=true +type Rabbitmq struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RabbitmqSpec `json:"spec,omitempty"` + Status RabbitmqStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// RabbitmqList contains a list of Rabbitmq +type RabbitmqList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Rabbitmq `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Rabbitmq{}, &RabbitmqList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 503622ec..7b57f29d 100755 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -257,3 +257,142 @@ func (in *MemcachedStatus) DeepCopy() *MemcachedStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Rabbitmq) DeepCopyInto(out *Rabbitmq) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rabbitmq. +func (in *Rabbitmq) DeepCopy() *Rabbitmq { + if in == nil { + return nil + } + out := new(Rabbitmq) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Rabbitmq) 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 *RabbitmqList) DeepCopyInto(out *RabbitmqList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Rabbitmq, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RabbitmqList. +func (in *RabbitmqList) DeepCopy() *RabbitmqList { + if in == nil { + return nil + } + out := new(RabbitmqList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RabbitmqList) 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 *RabbitmqPolicyDefinition) DeepCopyInto(out *RabbitmqPolicyDefinition) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RabbitmqPolicyDefinition. +func (in *RabbitmqPolicyDefinition) DeepCopy() *RabbitmqPolicyDefinition { + if in == nil { + return nil + } + out := new(RabbitmqPolicyDefinition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RabbitmqPolicyDefinitionSpec) DeepCopyInto(out *RabbitmqPolicyDefinitionSpec) { + *out = *in + out.Definition = in.Definition +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RabbitmqPolicyDefinitionSpec. +func (in *RabbitmqPolicyDefinitionSpec) DeepCopy() *RabbitmqPolicyDefinitionSpec { + if in == nil { + return nil + } + out := new(RabbitmqPolicyDefinitionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RabbitmqSpec) DeepCopyInto(out *RabbitmqSpec) { + *out = *in + if in.Policies != nil { + in, out := &in.Policies, &out.Policies + *out = make([]RabbitmqPolicyDefinitionSpec, len(*in)) + copy(*out, *in) + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RabbitmqSpec. +func (in *RabbitmqSpec) DeepCopy() *RabbitmqSpec { + if in == nil { + return nil + } + out := new(RabbitmqSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RabbitmqStatus) DeepCopyInto(out *RabbitmqStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RabbitmqStatus. +func (in *RabbitmqStatus) DeepCopy() *RabbitmqStatus { + if in == nil { + return nil + } + out := new(RabbitmqStatus) + in.DeepCopyInto(out) + return out +} diff --git a/builders/container.go b/builders/container.go index 9880acd5..1ed8423c 100755 --- a/builders/container.go +++ b/builders/container.go @@ -133,6 +133,47 @@ func (c *ContainerBuilder) Probe(handler v1.Handler, readyInterval int32, liveIn return c } +// EnvVarFromString register one environment variable set from the string pair. +func (c *ContainerBuilder) EnvVarFromString(name string, value string) *ContainerBuilder { + c.obj.Env = append(c.obj.Env, corev1.EnvVar{ + Name: name, + Value: value, + }) + return c +} + +// EnvVarFromConfigMap register one environment variable set from the configMap. +func (c *ContainerBuilder) EnvVarFromConfigMap(name string, cfmName string, cfmKey string) *ContainerBuilder { + c.obj.Env = append(c.obj.Env, corev1.EnvVar{ + Name: name, + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: cfmName, + }, + Key: cfmKey, + }, + }, + }) + return c +} + +// EnvVarFromSecret register one environment variable set from the secret. +func (c *ContainerBuilder) EnvVarFromSecret(name string, scName string, scKey string) *ContainerBuilder { + c.obj.Env = append(c.obj.Env, corev1.EnvVar{ + Name: name, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: scName, + }, + Key: scKey, + }, + }, + }) + return c +} + // Build returns the object after making certain assertions func (c *ContainerBuilder) Build() (corev1.Container, error) { if c.securityContext == nil { diff --git a/builders/pvc.go b/builders/pvc.go new file mode 100644 index 00000000..688f5e89 --- /dev/null +++ b/builders/pvc.go @@ -0,0 +1,78 @@ +package builders + +import ( + "github.com/alecthomas/units" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// PersistentVolumeClaimBuilder defines the interface to build a PVC +type PersistentVolumeClaimBuilder struct { + obj *corev1.PersistentVolumeClaim +} + +// PVC returns a new PVC builder +func PersistentVolumeClaim(existing *corev1.PersistentVolumeClaim) *PersistentVolumeClaimBuilder { + + return &PersistentVolumeClaimBuilder{ + obj: existing, + } +} + +func (pvc *PersistentVolumeClaimBuilder) ReadWriteOnce() *PersistentVolumeClaimBuilder { + pvc.obj.Spec.AccessModes = []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"} + return pvc +} + +// Resources defines the resource configuration for the PV +func (pvc *PersistentVolumeClaimBuilder) Resources(storage int64) *PersistentVolumeClaimBuilder { + storage = storage * int64(units.Megabyte) + pvc.obj.Spec.Resources = v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceStorage: *resource.NewQuantity(storage, resource.DecimalSI), + }, + } + return pvc +} + +func (pvc *PersistentVolumeClaimBuilder) ReadOnlyMany() *PersistentVolumeClaimBuilder { + pvc.obj.Spec.AccessModes = []corev1.PersistentVolumeAccessMode{"ReadOnlyMany"} + return pvc +} + +func (pvc *PersistentVolumeClaimBuilder) ReadWriteMany() *PersistentVolumeClaimBuilder { + pvc.obj.Spec.AccessModes = []corev1.PersistentVolumeAccessMode{"ReadWriteMany"} + return pvc +} + +func (pvc *PersistentVolumeClaimBuilder) Selector(selector metav1.LabelSelector) *PersistentVolumeClaimBuilder { + pvc.obj.Spec.Selector = &selector + return pvc +} + +func (pvc *PersistentVolumeClaimBuilder) VolumeName(name string) *PersistentVolumeClaimBuilder { + pvc.obj.Spec.VolumeName = name + return pvc +} + +func (pvc *PersistentVolumeClaimBuilder) StorageClassName(name string) *PersistentVolumeClaimBuilder { + pvc.obj.Spec.StorageClassName = &name + return pvc +} + +func (pvc *PersistentVolumeClaimBuilder) Block() *PersistentVolumeClaimBuilder { + *pvc.obj.Spec.VolumeMode = corev1.PersistentVolumeBlock + return pvc +} + +func (pvc *PersistentVolumeClaimBuilder) Filesystem() *PersistentVolumeClaimBuilder { + *pvc.obj.Spec.VolumeMode = corev1.PersistentVolumeFilesystem + return pvc +} + +// Build returns a complete PVC object +func (pvc *PersistentVolumeClaimBuilder) Build() (corev1.PersistentVolumeClaim, error) { + return *pvc.obj, nil +} diff --git a/builders/secret.go b/builders/secret.go new file mode 100644 index 00000000..03e95fab --- /dev/null +++ b/builders/secret.go @@ -0,0 +1,50 @@ +package builders + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// SecretBuilder defines the interface to build a Secret +type SecretBuilder struct { + obj *corev1.Secret + owner metav1.Object + scheme *runtime.Scheme +} + +// Secret returns a new secret builder +func Secret(existing *corev1.Secret, owner metav1.Object, scheme *runtime.Scheme) *SecretBuilder { + existing.Data = map[string][]byte{} + existing.StringData = map[string]string{} + + return &SecretBuilder{ + obj: existing, + owner: owner, + scheme: scheme, + } +} + +// Data sets a key inside this Secret +func (cm *SecretBuilder) Data(key, value string) *SecretBuilder { + cm.obj.Data[key] = []byte(value) + return cm +} + +// StringData sets a key inside this Secret +func (cm *SecretBuilder) StringData(key, value string) *SecretBuilder { + cm.obj.StringData[key] = value + return cm +} + +// SecretType sets the secret type +func (cm *SecretBuilder) SecretType(value string) *SecretBuilder { + cm.obj.Type = corev1.SecretType(value) + return cm +} + +// Build returns a complete Secret object +func (cm *SecretBuilder) Build() error { + return controllerutil.SetControllerReference(cm.owner, cm.obj, cm.scheme) +} diff --git a/builders/statefulset.go b/builders/statefulset.go new file mode 100644 index 00000000..3233115d --- /dev/null +++ b/builders/statefulset.go @@ -0,0 +1,76 @@ +package builders + +import ( + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// StatefulSetBuilder defines the interface to build a StatefulSet +type StatefulSetBuilder struct { + obj *appsv1.StatefulSet + podTemplateSpec *PodTemplateSpecBuilder + pvcs []*PersistentVolumeClaimBuilder + scheme *runtime.Scheme + labels map[string]string + owner metav1.Object +} + +// StatefulSet returns a new StatefulSet builder +func StatefulSet(existing *appsv1.StatefulSet, owner metav1.Object, scheme *runtime.Scheme) *StatefulSetBuilder { + return &StatefulSetBuilder{ + obj: existing, + labels: map[string]string{}, + owner: owner, + scheme: scheme, + } +} + +// Labels specifies labels for the StatefulSet +func (d *StatefulSetBuilder) Labels(labels map[string]string) *StatefulSetBuilder { + d.labels = labels + d.obj.ObjectMeta.Labels = d.labels + return d +} + +// Replicas defines the number of replicas +func (d *StatefulSetBuilder) Replicas(replicas int32) *StatefulSetBuilder { + d.obj.Spec.Replicas = pointer.Int32Ptr(replicas) + return d +} + +// PodTemplateSpec defines a builder for the pod template spec +func (d *StatefulSetBuilder) PodTemplateSpec(podTemplateSpec *PodTemplateSpecBuilder) *StatefulSetBuilder { + d.podTemplateSpec = podTemplateSpec + return d +} + +// PVCs defines a builder array for the PVC spec +func (d *StatefulSetBuilder) PVCs(pvcs ...*PersistentVolumeClaimBuilder) *StatefulSetBuilder { + d.pvcs = pvcs + return d +} + +// Build creates a final StatefulSet objet +func (d *StatefulSetBuilder) Build() error { + podTemplateSpec, err := d.podTemplateSpec.Labels(d.labels).Build() + + if err != nil { + return err + } + + d.obj.Spec.Template = podTemplateSpec + + for _, c := range d.pvcs { + pvc, err := c.Build() + if err != nil { + return err + } + + d.obj.Spec.VolumeClaimTemplates = append(d.obj.Spec.VolumeClaimTemplates, pvc) + } + + return controllerutil.SetControllerReference(d.owner, d.obj, d.scheme) +} diff --git a/builders/volume.go b/builders/volume.go index 0672b858..a9bc9fd6 100755 --- a/builders/volume.go +++ b/builders/volume.go @@ -33,6 +33,28 @@ func (v *VolumeBuilder) FromConfigMap(name string) *VolumeBuilder { return v } +// FromSecret sets the source of the volume from a Secret +func (v *VolumeBuilder) FromSecret(name string) *VolumeBuilder { + v.obj.VolumeSource = corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: name, + DefaultMode: pointer.Int32Ptr(420), + }, + } + return v +} + +// FromPersistentVolumeClaim sets the source of the volume from a PVC +func (v *VolumeBuilder) FromPersistentVolumeClaim(name string) *VolumeBuilder { + v.obj.VolumeSource = corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: name, + ReadOnly: false, + }, + } + return v +} + // Build returns the object after checking assertions func (v *VolumeBuilder) Build() corev1.Volume { return *v.obj diff --git a/chart/crds/infrastructure.vexxhost.cloud_rabbitmqs.yaml b/chart/crds/infrastructure.vexxhost.cloud_rabbitmqs.yaml new file mode 100644 index 00000000..cb337f39 --- /dev/null +++ b/chart/crds/infrastructure.vexxhost.cloud_rabbitmqs.yaml @@ -0,0 +1,149 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.5 + creationTimestamp: null + name: rabbitmqs.infrastructure.vexxhost.cloud +spec: + group: infrastructure.vexxhost.cloud + names: + kind: Rabbitmq + listKind: RabbitmqList + plural: rabbitmqs + singular: rabbitmq + scope: Namespaced + validation: + openAPIV3Schema: + description: Rabbitmq is the Schema for the Rabbitmqs API + 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: RabbitmqSpec defines the desired state of Rabbitmq + properties: + authSecret: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + policies: + items: + description: RabbitmqPolicySpec defines the Rabbitmq Policy Spec for + the Vhost + properties: + apply-to: + type: string + definition: + description: RabbitmqPolicyDefinition defines the Rabbitmq Policy + content + properties: + expires: + type: integer + federation-upstream-set: + type: string + ha-mode: + type: string + ha-params: + type: integer + ha-sync-mode: + type: string + max-length: + type: integer + max-length-bytes: + type: integer + message-ttl: + type: integer + type: object + name: + type: string + pattern: + type: string + priority: + format: int64 + type: integer + vhost: + type: string + required: + - apply-to + - definition + - name + - pattern + - priority + type: object + type: array + tolerations: + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, operator + must be Exists; this combination means to match all values and + all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. Exists + is equivalent to wildcard for value, so that a pod can tolerate + all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the + toleration (which must be of effect NoExecute, otherwise this + field is ignored) tolerates the taint. By default, it is not + set, which means tolerate the taint forever (do not evict). + Zero and negative values will be treated as 0 (evict immediately) + by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise + just a regular string. + type: string + type: object + type: array + required: + - authSecret + type: object + status: + description: RabbitmqStatus defines the observed state of Rabbitmq + properties: + phase: + type: string + required: + - phase + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/chart/templates/clusterrole.yaml b/chart/templates/clusterrole.yaml index 127b2753..83d0a9a9 100644 --- a/chart/templates/clusterrole.yaml +++ b/chart/templates/clusterrole.yaml @@ -43,6 +43,19 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - secrets + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - infrastructure.vexxhost.cloud resources: @@ -83,6 +96,26 @@ rules: - get - patch - update +- apiGroups: + - infrastructure.vexxhost.cloud + resources: + - rabbitmqs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infrastructure.vexxhost.cloud + resources: + - rabbitmqs/status + verbs: + - get + - patch + - update - apiGroups: - monitoring.coreos.com resources: diff --git a/config/crd/bases/infrastructure.vexxhost.cloud_rabbitmqs.yaml b/config/crd/bases/infrastructure.vexxhost.cloud_rabbitmqs.yaml new file mode 100644 index 00000000..cb337f39 --- /dev/null +++ b/config/crd/bases/infrastructure.vexxhost.cloud_rabbitmqs.yaml @@ -0,0 +1,149 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.5 + creationTimestamp: null + name: rabbitmqs.infrastructure.vexxhost.cloud +spec: + group: infrastructure.vexxhost.cloud + names: + kind: Rabbitmq + listKind: RabbitmqList + plural: rabbitmqs + singular: rabbitmq + scope: Namespaced + validation: + openAPIV3Schema: + description: Rabbitmq is the Schema for the Rabbitmqs API + 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: RabbitmqSpec defines the desired state of Rabbitmq + properties: + authSecret: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + policies: + items: + description: RabbitmqPolicySpec defines the Rabbitmq Policy Spec for + the Vhost + properties: + apply-to: + type: string + definition: + description: RabbitmqPolicyDefinition defines the Rabbitmq Policy + content + properties: + expires: + type: integer + federation-upstream-set: + type: string + ha-mode: + type: string + ha-params: + type: integer + ha-sync-mode: + type: string + max-length: + type: integer + max-length-bytes: + type: integer + message-ttl: + type: integer + type: object + name: + type: string + pattern: + type: string + priority: + format: int64 + type: integer + vhost: + type: string + required: + - apply-to + - definition + - name + - pattern + - priority + type: object + type: array + tolerations: + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, operator + must be Exists; this combination means to match all values and + all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. Exists + is equivalent to wildcard for value, so that a pod can tolerate + all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the + toleration (which must be of effect NoExecute, otherwise this + field is ignored) tolerates the taint. By default, it is not + set, which means tolerate the taint forever (do not evict). + Zero and negative values will be treated as 0 (evict immediately) + by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise + just a regular string. + type: string + type: object + type: array + required: + - authSecret + type: object + status: + description: RabbitmqStatus defines the observed state of Rabbitmq + properties: + phase: + type: string + required: + - phase + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 156e9073..5b593421 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,6 +4,7 @@ resources: - bases/infrastructure.vexxhost.cloud_mcrouters.yaml - bases/infrastructure.vexxhost.cloud_memcacheds.yaml +- bases/infrastructure.vexxhost.cloud_rabbitmqs.yaml - bases/monitoring.coreos.com_podmonitors.yaml - bases/monitoring.coreos.com_prometheusrules.yaml # +kubebuilder:scaffold:crdkustomizeresource diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index e188c5b3..fcce9139 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -43,6 +43,19 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - secrets + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - infrastructure.vexxhost.cloud resources: @@ -83,6 +96,26 @@ rules: - get - patch - update +- apiGroups: + - infrastructure.vexxhost.cloud + resources: + - rabbitmqs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infrastructure.vexxhost.cloud + resources: + - rabbitmqs/status + verbs: + - get + - patch + - update - apiGroups: - monitoring.coreos.com resources: diff --git a/config/samples/infrastructure_v1alpha1_rabbitmq.yaml b/config/samples/infrastructure_v1alpha1_rabbitmq.yaml new file mode 100644 index 00000000..4b54a8cf --- /dev/null +++ b/config/samples/infrastructure_v1alpha1_rabbitmq.yaml @@ -0,0 +1,15 @@ +apiVersion: infrastructure.vexxhost.cloud/v1alpha1 +kind: Rabbitmq +metadata: + name: sample +spec: + authSecret: rabbitmq-sample +--- +apiVersion: v1 +metadata: + name: rabbitmq-sample + namespace: default +data: + password: Y0dGemMzZHZjbVE9 + username: ZFhObGNnPT0= +kind: Secret diff --git a/config/samples/rabbitmq_pv.yaml b/config/samples/rabbitmq_pv.yaml new file mode 100644 index 00000000..6a4a42dd --- /dev/null +++ b/config/samples/rabbitmq_pv.yaml @@ -0,0 +1,18 @@ +# When the default StorageClass is "", use this. +--- +kind: PersistentVolume +apiVersion: v1 +metadata: + name: data + labels: + app.kubernetes.io/instance: sample + app.kubernetes.io/managed-by: openstack-operator + app.kubernetes.io/name: rabbitmq +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + hostPath: + path: "/opt/pv/rabbitmq" + persistentVolumeReclaimPolicy: Delete diff --git a/controllers/rabbitmq_controller.go b/controllers/rabbitmq_controller.go new file mode 100644 index 00000000..034de62c --- /dev/null +++ b/controllers/rabbitmq_controller.go @@ -0,0 +1,208 @@ +package controllers + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + monitoringv1 "opendev.org/vexxhost/openstack-operator/api/monitoring/v1" + infrastructurev1alpha1 "opendev.org/vexxhost/openstack-operator/api/v1alpha1" + "opendev.org/vexxhost/openstack-operator/builders" + "opendev.org/vexxhost/openstack-operator/utils" +) + +// RabbitmqReconciler reconciles a Rabbitmq object +type RabbitmqReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +const ( + _rabbitmqDefaultUsernameCfgKey = "username" + _rabbitmqDefaultPasswordCfgKey = "password" + _rabbitmqBuiltinMetricPort = 15692 + _rabbitmqPort = 5672 + _rabbitmqRunAsUser = 999 + _rabbitmqRunAsGroup = 999 +) + +// +kubebuilder:rbac:groups=infrastructure.vexxhost.cloud,resources=rabbitmqs,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=infrastructure.vexxhost.cloud,resources=rabbitmqs/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=prometheusrules,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=podmonitors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=secrets;services,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete + +// Reconcile does the reconcilication of Rabbitmq instances +func (r *RabbitmqReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + ctx := context.Background() + log := r.Log.WithValues("rabbitmq", req.NamespacedName) + + var Rabbitmq infrastructurev1alpha1.Rabbitmq + if err := r.Get(ctx, req.NamespacedName, &Rabbitmq); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Labels + labels := map[string]string{ + "app.kubernetes.io/name": "rabbitmq", + "app.kubernetes.io/instance": req.Name, + "app.kubernetes.io/managed-by": "openstack-operator", + } + + // Deployment + deployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: req.Namespace, + Name: fmt.Sprintf("rabbitmq-%s", req.Name), + }, + } + op, err := utils.CreateOrUpdate(ctx, r, deployment, func() error { + return builders.Deployment(deployment, &Rabbitmq, r.Scheme). + Labels(labels). + Replicas(1). + PodTemplateSpec( + builders.PodTemplateSpec(). + PodSpec( + builders.PodSpec(). + NodeSelector(Rabbitmq.Spec.NodeSelector). + Tolerations(Rabbitmq.Spec.Tolerations). + Containers( + builders.Container("rabbitmq", "vexxhost/rabbitmq:latest"). + EnvVarFromSecret("RABBITMQ_DEFAULT_USER", Rabbitmq.Spec.AuthSecret, _rabbitmqDefaultUsernameCfgKey). + EnvVarFromSecret("RABBITMQ_DEFAULT_PASS", Rabbitmq.Spec.AuthSecret, _rabbitmqDefaultPasswordCfgKey). + Port("rabbitmq", _rabbitmqPort). + Port("metrics", _rabbitmqBuiltinMetricPort). + PortProbe("rabbitmq", 15, 30). + Resources(500, 512, 500, 2). + SecurityContext( + builders.SecurityContext(). + RunAsUser(_rabbitmqRunAsUser). + RunAsGroup(_rabbitmqRunAsGroup), + ), + ), + ), + ). + Build() + }) + if err != nil { + return ctrl.Result{}, err + } + log.WithValues("resource", "Deployment").WithValues("op", op).Info("Reconciled") + + // PodMonitor + podMonitor := &monitoringv1.PodMonitor{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "monitoring.coreos.com/v1", + Kind: "PodMonitor", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: req.Namespace, + Name: "rabbitmq-podmonitor", + Labels: map[string]string{ + "app.kubernetes.io/name": "rabbitmq", + "app.kubernetes.io/managed-by": "openstack-operator", + }, + }, + } + + op, err = utils.CreateOrUpdate(ctx, r, podMonitor, func() error { + return builders.PodMonitor(podMonitor, &Rabbitmq, r.Scheme). + Selector(map[string]string{ + "app.kubernetes.io/name": "rabbitmq", + }). + PodMetricsEndpoints( + builders.PodMetricsEndpoint(). + Port("metrics"). + Path("/metrics"). + Interval("15s"), + ).Build() + + }) + if err != nil { + return ctrl.Result{}, err + } + log.WithValues("resource", "rabbitmq-podmonitor").WithValues("op", op).Info("Reconciled") + + // Alertrule + alertRule := &monitoringv1.PrometheusRule{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: req.Namespace, + Name: "rabbitmq-alertrule", + }, + } + op, err = utils.CreateOrUpdate(ctx, r, alertRule, func() error { + return builders.PrometheusRule(alertRule, &Rabbitmq, r.Scheme). + RuleGroups(builders.RuleGroup(). + Name("rabbitmq-rule"). + Rules( + builders.Rule(). + Alert("RabbitmqDown"). + Message("Rabbitmq node down."). + Priority(1). + Expr("rabbitmq_up == 0"), + builders.Rule(). + Alert("RabbitmqTooManyConnections"). + Message("RabbitMQ instance has too many connections."). + Priority(1). + Expr("rabbitmq_connectionsTotal > 1000"), + builders.Rule(). + Alert("RabbitmqTooManyMessagesInQueue"). + Message("Queue is filling up."). + Priority(1). + Expr("rabbitmq_queue_messages_ready > 1000"), + builders.Rule(). + Alert("RabbitmqSlowQueueConsuming"). + Message("Queue messages are consumed slowly."). + Priority(1). + Expr("time() - rabbitmq_queue_head_message_timestamp > 60"), + ). + Interval("1m")). + Build() + }) + if err != nil { + return ctrl.Result{}, err + } + log.WithValues("resource", "rabbitmq-alertrule").WithValues("op", op).Info("Reconciled") + + // Service + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: req.Namespace, + Name: fmt.Sprintf("rabbitmq-%s", req.Name), + }, + } + op, err = utils.CreateOrUpdate(ctx, r, service, func() error { + return builders.Service(service, &Rabbitmq, r.Scheme). + Port("epmd", 4369). + Port("amqp", 5671). + Port("distport", 25672). + Selector(labels). + Build() + }) + if err != nil { + return ctrl.Result{}, err + } + log.WithValues("resource", "Service").WithValues("op", op).Info("Reconciled") + + return ctrl.Result{}, nil +} + +// SetupWithManager initializes the controller with primary manager +func (r *RabbitmqReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&infrastructurev1alpha1.Rabbitmq{}). + Owns(&appsv1.Deployment{}). + Owns(&corev1.Service{}). + Owns(&monitoringv1.PodMonitor{}). + Owns(&monitoringv1.PrometheusRule{}). + Complete(r) +} diff --git a/images/rabbitmq/Dockerfile b/images/rabbitmq/Dockerfile new file mode 100644 index 00000000..094319aa --- /dev/null +++ b/images/rabbitmq/Dockerfile @@ -0,0 +1,4 @@ +FROM rabbitmq:latest +RUN rabbitmq-plugins enable --offline rabbitmq_prometheus && \ + rabbitmq-plugins enable --offline rabbitmq_management && \ + rabbitmq-plugins enable --offline rabbitmq_peer_discovery_k8s diff --git a/main.go b/main.go index 9c4553c5..2a516dac 100755 --- a/main.go +++ b/main.go @@ -57,6 +57,7 @@ func main() { ctrl.SetLogger(zap.New(zap.UseDevMode(true))) + // Create manager mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, @@ -69,6 +70,7 @@ func main() { os.Exit(1) } + // Setup controllers with manager if err = (&controllers.McrouterReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("Mcrouter"), @@ -77,6 +79,7 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Mcrouter") os.Exit(1) } + if err = (&controllers.MemcachedReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("Memcached"), @@ -85,6 +88,15 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Memcached") os.Exit(1) } + + if err = (&controllers.RabbitmqReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Rabbitmq"), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Rabbitmq") + os.Exit(1) + } // +kubebuilder:scaffold:builder setupLog.Info("starting manager", "revision", version.Revision)