From 8ba5462aaf52d0d1d8c228592baae7f42426e290 Mon Sep 17 00:00:00 2001 From: Jason Parraga Date: Thu, 2 Jan 2025 10:57:45 -0800 Subject: [PATCH] Implement queue controller logic Signed-off-by: Jason Parraga --- Makefile | 4 +- api/core/v1alpha1/queue_types.go | 23 +- api/core/v1alpha1/zz_generated.deepcopy.go | 54 ++- .../bases/core.armadaproject.io_queues.yaml | 32 +- go.mod | 12 +- go.sum | 86 ++++ internal/controller/common/helpers.go | 29 ++ internal/controller/core/constants.go | 5 + internal/controller/core/queue_controller.go | 156 ++++++- .../controller/core/queue_controller_test.go | 430 +++++++++++++++++- .../install/armadaserver_controller.go | 4 +- .../install/binoculars_controller.go | 4 +- internal/controller/install/common_helpers.go | 21 - .../controller/install/common_helpers_test.go | 4 +- .../install/eventingester_controller.go | 4 +- .../controller/install/executor_controller.go | 4 +- .../controller/install/lookout_controller.go | 4 +- .../install/lookoutingester_controller.go | 4 +- .../install/scheduler_controller.go | 4 +- .../install/scheduleringester_controller.go | 4 +- test/armadaclient/mock.go | 11 + test/armadaclient/mock_queue_client.go | 258 +++++++++++ 22 files changed, 1096 insertions(+), 61 deletions(-) create mode 100644 internal/controller/common/helpers.go create mode 100644 internal/controller/core/constants.go create mode 100644 test/armadaclient/mock.go create mode 100644 test/armadaclient/mock_queue_client.go diff --git a/Makefile b/Makefile index fbaaec15..df4ae994 100644 --- a/Makefile +++ b/Makefile @@ -104,9 +104,11 @@ generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." .PHONY: mock -mock: mockgen ## Generate mock client for k8sclient +mock: mockgen ## Generate mock clients $(RM) test/k8sclient/mock_client.go mockgen -destination=test/k8sclient/mock_client.go -package=k8sclient "github.com/armadaproject/armada-operator/test/k8sclient" Client + $(RM) test/armadaclient/mock_queue_client.go + mockgen -destination=test/armadaclient/mock_queue_client.go -package=armadaclient "github.com/armadaproject/armada-operator/test/armadaclient" QueueClient .PHONY: generate-helm-chart generate-helm-chart: manifests kustomize helmify ## Generate Helm chart from Kustomize manifests diff --git a/api/core/v1alpha1/queue_types.go b/api/core/v1alpha1/queue_types.go index 84d91d81..2f3e1ffa 100644 --- a/api/core/v1alpha1/queue_types.go +++ b/api/core/v1alpha1/queue_types.go @@ -17,25 +17,30 @@ limitations under the License. package v1alpha1 import ( + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +type PermissionSubject struct { + Kind string `json:"kind,omitempty"` + Name string `json:"name,omitempty"` +} + +type QueuePermissions struct { + Subjects []PermissionSubject `json:"subjects,omitempty"` + Verbs []string `json:"verbs,omitempty"` +} // QueueSpec defines the desired state of Queue type QueueSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // Foo is an example field of Queue. Edit queue_types.go to remove/update - Foo string `json:"foo,omitempty"` + // PriorityFactor is a multiplicative constant which is applied to the priority. + PriorityFactor *resource.Quantity `json:"priorityFactor,omitempty"` + // Permissions describe who can perform what operations on queue related resources. + Permissions []QueuePermissions `json:"permissions,omitempty"` } // QueueStatus defines the observed state of Queue type QueueStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file } //+kubebuilder:object:root=true diff --git a/api/core/v1alpha1/zz_generated.deepcopy.go b/api/core/v1alpha1/zz_generated.deepcopy.go index ec148e67..27fcdfed 100644 --- a/api/core/v1alpha1/zz_generated.deepcopy.go +++ b/api/core/v1alpha1/zz_generated.deepcopy.go @@ -24,12 +24,27 @@ 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 *PermissionSubject) DeepCopyInto(out *PermissionSubject) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PermissionSubject. +func (in *PermissionSubject) DeepCopy() *PermissionSubject { + if in == nil { + return nil + } + out := new(PermissionSubject) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Queue) DeepCopyInto(out *Queue) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -83,9 +98,46 @@ func (in *QueueList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *QueuePermissions) DeepCopyInto(out *QueuePermissions) { + *out = *in + if in.Subjects != nil { + in, out := &in.Subjects, &out.Subjects + *out = make([]PermissionSubject, len(*in)) + copy(*out, *in) + } + if in.Verbs != nil { + in, out := &in.Verbs, &out.Verbs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QueuePermissions. +func (in *QueuePermissions) DeepCopy() *QueuePermissions { + if in == nil { + return nil + } + out := new(QueuePermissions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *QueueSpec) DeepCopyInto(out *QueueSpec) { *out = *in + if in.PriorityFactor != nil { + in, out := &in.PriorityFactor, &out.PriorityFactor + x := (*in).DeepCopy() + *out = &x + } + if in.Permissions != nil { + in, out := &in.Permissions, &out.Permissions + *out = make([]QueuePermissions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QueueSpec. diff --git a/config/crd/bases/core.armadaproject.io_queues.yaml b/config/crd/bases/core.armadaproject.io_queues.yaml index 6fb34a14..5b295c13 100644 --- a/config/crd/bases/core.armadaproject.io_queues.yaml +++ b/config/crd/bases/core.armadaproject.io_queues.yaml @@ -39,10 +39,34 @@ spec: spec: description: QueueSpec defines the desired state of Queue properties: - foo: - description: Foo is an example field of Queue. Edit queue_types.go - to remove/update - type: string + permissions: + description: Permissions describe who can perform what operations + on queue related resources. + items: + properties: + subjects: + items: + properties: + kind: + type: string + name: + type: string + type: object + type: array + verbs: + items: + type: string + type: array + type: object + type: array + priorityFactor: + anyOf: + - type: integer + - type: string + description: PriorityFactor is a multiplicative constant which is + applied to the priority. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true type: object status: description: QueueStatus defines the observed state of Queue diff --git a/go.mod b/go.mod index 430b7297..6850a4fd 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module github.com/armadaproject/armada-operator go 1.23.2 require ( + github.com/armadaproject/armada v0.15.11 github.com/go-logr/logr v1.4.2 + github.com/gogo/protobuf v1.3.2 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 github.com/onsi/ginkgo/v2 v2.19.0 @@ -11,6 +13,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.77.2 github.com/stretchr/testify v1.9.0 + google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 k8s.io/api v0.31.2 k8s.io/apimachinery v0.31.2 @@ -25,7 +28,6 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect @@ -34,18 +36,21 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect + github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a // indirect + github.com/gogo/status v1.1.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/minio/highwayhash v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -67,6 +72,9 @@ require ( golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index d416d239..c9c13938 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,28 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armadaproject/armada v0.15.11 h1:syQ4apZWpknrZr32NHxShvU1OwVrFMC22iARxHC2BSg= +github.com/armadaproject/armada v0.15.11/go.mod h1:MBopeoZMf/rEpmMw9Ag0le/CdxqhDocbZ01e0sgEjIA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= @@ -16,6 +31,7 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -28,16 +44,27 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a h1:dR8+Q0uO5S2ZBcs2IH6VBKYwSxPo2vYCYq0ot0mu7xA= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= +github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -46,8 +73,11 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -66,6 +96,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -86,12 +118,14 @@ github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.77.2 h github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.77.2/go.mod h1:D0KY8md81DQKdaR/cXwnhoWB3MYYyc/UjvqE8GFkIvA= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -119,21 +153,46 @@ golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -150,6 +209,10 @@ golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -162,6 +225,26 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -171,11 +254,14 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= diff --git a/internal/controller/common/helpers.go b/internal/controller/common/helpers.go new file mode 100644 index 00000000..e34f4fcf --- /dev/null +++ b/internal/controller/common/helpers.go @@ -0,0 +1,29 @@ +package common + +import ( + "context" + + "github.com/go-logr/logr" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// GetObject will get the object from Kubernetes and return if it is missing or an error. +func GetObject( + ctx context.Context, + client client.Client, + object client.Object, + namespacedName types.NamespacedName, + logger logr.Logger, +) (miss bool, err error) { + logger.Info("Fetching object from cache") + if err := client.Get(ctx, namespacedName, object); err != nil { + if k8serrors.IsNotFound(err) { + logger.Info("Object not found in cache, ending reconcile...") + return true, nil + } + return true, err + } + return false, nil +} diff --git a/internal/controller/core/constants.go b/internal/controller/core/constants.go new file mode 100644 index 00000000..bc6487f5 --- /dev/null +++ b/internal/controller/core/constants.go @@ -0,0 +1,5 @@ +package core + +const ( + operatorFinalizer = "core.armadaproject.io/finalizer" +) diff --git a/internal/controller/core/queue_controller.go b/internal/controller/core/queue_controller.go index 4cc2507a..c333f1dc 100644 --- a/internal/controller/core/queue_controller.go +++ b/internal/controller/core/queue_controller.go @@ -18,6 +18,17 @@ package core import ( "context" + "fmt" + "time" + + "github.com/armadaproject/armada-operator/internal/controller/common" + + "github.com/armadaproject/armada/pkg/api" + "github.com/google/go-cmp/cmp" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/testing/protocmp" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -30,7 +41,8 @@ import ( // QueueReconciler reconciles a Queue object type QueueReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + QueueClient api.QueueServiceClient } //+kubebuilder:rbac:groups=core.armadaproject.io,resources=queues,verbs=get;list;watch;create;update;patch;delete @@ -39,21 +51,145 @@ type QueueReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Queue object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile func (r *QueueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + logger := log.FromContext(ctx).WithValues("namespace", req.Namespace, "name", req.Name) + + started := time.Now() + + logger.Info("Reconciling object") + + var queue corev1alpha1.Queue + if miss, err := common.GetObject(ctx, r.Client, &queue, req.NamespacedName, logger); err != nil || miss { + return ctrl.Result{}, err + } + + // examine DeletionTimestamp to determine if object is under deletion + deletionTimestamp := queue.ObjectMeta.DeletionTimestamp + // examine DeletionTimestamp to determine if object is under deletion + if deletionTimestamp.IsZero() { + // The object is not being deleted, so if it does not have our finalizer, + // then lets add the finalizer and update the object. This is equivalent + // registering our finalizer. + if !controllerutil.ContainsFinalizer(&queue, operatorFinalizer) { + logger.Info("Attaching finalizer to Lookout object", "finalizer", operatorFinalizer) + controllerutil.AddFinalizer(&queue, operatorFinalizer) + if err := r.Update(ctx, &queue); err != nil { + return ctrl.Result{}, err + } + } + } else { + logger.Info("Queue object is being deleted", "finalizer", operatorFinalizer) + logger.Info("Namespace-scoped resources will be deleted by Kubernetes based on their OwnerReference") + // The object is being deleted + if controllerutil.ContainsFinalizer(&queue, operatorFinalizer) { + // remove our finalizer from the list and update it. + logger.Info("Removing finalizer from Queue object", "finalizer", operatorFinalizer) + controllerutil.RemoveFinalizer(&queue, operatorFinalizer) + if err := r.Update(ctx, &queue); err != nil { + return ctrl.Result{}, err + } + } + + if err := r.deleteQueue(ctx, queue); err != nil { + return ctrl.Result{}, err + } + + // Stop reconciliation as the item is being deleted + return ctrl.Result{}, nil + } - // TODO(user): your logic here + err := r.upsertQueue(ctx, queue) + if err != nil { + return ctrl.Result{}, err + } + + logger.Info("Successfully reconciled Queue object", "durationMillis", time.Since(started).Milliseconds()) return ctrl.Result{}, nil } +func (r *QueueReconciler) upsertQueue(ctx context.Context, queue corev1alpha1.Queue) error { + + createQueueRequest := queueToProto(queue) + + existingQueue, getErr := r.QueueClient.GetQueue(ctx, &api.QueueGetRequest{Name: queue.Name}) + if getErr != nil { + // See if this is a gRPC error + e, ok := status.FromError(getErr) + if !ok { + return fmt.Errorf("getting queue: %w", getErr) + } + + // Check if not found + if e.Code() == codes.NotFound { + // Queue not found so create it + _, createErr := r.QueueClient.CreateQueue(ctx, createQueueRequest) + if createErr != nil { + return fmt.Errorf("creating queue: %w", createErr) + } + + return nil + } + + return fmt.Errorf("getting queue: %w", getErr) + } + + if cmp.Equal(existingQueue, createQueueRequest, protocmp.Transform()) { + // nothing to do + return nil + } + + _, getErr = r.QueueClient.UpdateQueue(ctx, createQueueRequest) + return getErr +} + +func queueToProto(queue corev1alpha1.Queue) *api.Queue { + queueRequest := &api.Queue{ + Name: queue.Name, + } + + if queue.Spec.PriorityFactor != nil { + queueRequest.PriorityFactor = queue.Spec.PriorityFactor.AsApproximateFloat64() + } else { + // Value must be at least 1 or the API will get upset + queueRequest.PriorityFactor = 1 + } + + protoPermissions := []*api.Queue_Permissions{} + for _, p := range queue.Spec.Permissions { + protoSubjects := []*api.Queue_Permissions_Subject{} + + for _, s := range p.Subjects { + protoSubjects = append(protoSubjects, &api.Queue_Permissions_Subject{ + Name: s.Name, + Kind: s.Kind, + }) + } + + protoPermission := &api.Queue_Permissions{ + Subjects: protoSubjects, + Verbs: p.Verbs, + } + + protoPermissions = append(protoPermissions, protoPermission) + } + + if len(protoPermissions) > 0 { + queueRequest.Permissions = protoPermissions + } + + return queueRequest +} + +func (r *QueueReconciler) deleteQueue(ctx context.Context, queue corev1alpha1.Queue) error { + _, err := r.QueueClient.DeleteQueue(ctx, &api.QueueDeleteRequest{Name: queue.Name}) + if err != nil { + return fmt.Errorf("deleting queue: %w", err) + } + + return nil +} + // SetupWithManager sets up the controller with the Manager. func (r *QueueReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). diff --git a/internal/controller/core/queue_controller_test.go b/internal/controller/core/queue_controller_test.go index a64315e4..a2a201d4 100644 --- a/internal/controller/core/queue_controller_test.go +++ b/internal/controller/core/queue_controller_test.go @@ -3,7 +3,20 @@ package core import ( "context" "testing" + "time" + "github.com/stretchr/testify/require" + + "github.com/armadaproject/armada/pkg/api" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + corev1alpha1 "github.com/armadaproject/armada-operator/api/core/v1alpha1" + "github.com/armadaproject/armada-operator/test/armadaclient" "github.com/armadaproject/armada-operator/test/k8sclient" "github.com/golang/mock/gomock" @@ -13,7 +26,11 @@ import ( "github.com/armadaproject/armada-operator/api/install/v1alpha1" ) -func TestQueueReconciler_Reconcile(t *testing.T) { +const ( + queueName = "test" +) + +func TestQueueReconciler_ReconcileNoQueue(t *testing.T) { t.Parallel() mockCtrl := gomock.NewController(t) @@ -22,15 +39,420 @@ func TestQueueReconciler_Reconcile(t *testing.T) { if err != nil { t.Fatalf("should not return error when building schema") } + + expectedNamespacedName := types.NamespacedName{Namespace: "default", Name: queueName} + mockK8sClient := k8sclient.NewMockClient(mockCtrl) + // queue + mockK8sClient. + EXPECT(). + Get(gomock.Any(), expectedNamespacedName, gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(errors.NewNotFound(schema.GroupResource{}, "executor")) + + mockQueueClient := armadaclient.NewMockQueueClient(mockCtrl) + + r := QueueReconciler{ + Client: mockK8sClient, + Scheme: scheme, + QueueClient: mockQueueClient, + } + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: queueName}, + } + + _, err = r.Reconcile(context.Background(), req) + if err != nil { + t.Fatalf("reconcile should not return error") + } +} + +func TestQueueReconciler_ReconcileNewQueueSuccess(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + scheme, err := v1alpha1.SchemeBuilder.Build() + if err != nil { + t.Fatalf("should not return error when building schema") + } + + expectedNamespacedName := types.NamespacedName{Namespace: "default", Name: queueName} + expectedQueue := corev1alpha1.Queue{ + TypeMeta: metav1.TypeMeta{ + Kind: "Queue", + APIVersion: "core.armadaproject.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: queueName}, + Spec: corev1alpha1.QueueSpec{}, + } + + mockK8sClient := k8sclient.NewMockClient(mockCtrl) + // queue + mockK8sClient. + EXPECT(). + Get(gomock.Any(), expectedNamespacedName, gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil). + SetArg(2, expectedQueue) + // queue finalizer + mockK8sClient. + EXPECT(). + Update(gomock.Any(), gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil) + + createQueueRequest := &api.Queue{ + Name: queueName, + PriorityFactor: 1, + } + + mockQueueClient := armadaclient.NewMockQueueClient(mockCtrl) + mockQueueClient. + EXPECT(). + GetQueue(gomock.Any(), &api.QueueGetRequest{Name: queueName}). + Return(nil, status.Error(codes.NotFound, "")) + + mockQueueClient. + EXPECT(). + CreateQueue(gomock.Any(), createQueueRequest).Return(nil, nil) + + r := QueueReconciler{ + Client: mockK8sClient, + Scheme: scheme, + QueueClient: mockQueueClient, + } + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: queueName}, + } + + _, err = r.Reconcile(context.Background(), req) + if err != nil { + t.Fatalf("reconcile should not return error") + } +} + +func TestQueueReconciler_ReconcileNewQueueServerErrorOnCreate(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + scheme, err := v1alpha1.SchemeBuilder.Build() + if err != nil { + t.Fatalf("should not return error when building schema") + } + + expectedNamespacedName := types.NamespacedName{Namespace: "default", Name: queueName} + expectedQueue := corev1alpha1.Queue{ + TypeMeta: metav1.TypeMeta{ + Kind: "Queue", + APIVersion: "core.armadaproject.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: queueName}, + Spec: corev1alpha1.QueueSpec{}, + } + + mockK8sClient := k8sclient.NewMockClient(mockCtrl) + // queue + mockK8sClient. + EXPECT(). + Get(gomock.Any(), expectedNamespacedName, gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil). + SetArg(2, expectedQueue) + // queue finalizer + mockK8sClient. + EXPECT(). + Update(gomock.Any(), gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil) + + createQueueRequest := &api.Queue{ + Name: queueName, + PriorityFactor: 1, + } + + mockQueueClient := armadaclient.NewMockQueueClient(mockCtrl) + mockQueueClient. + EXPECT(). + GetQueue(gomock.Any(), &api.QueueGetRequest{Name: queueName}). + Return(nil, status.Error(codes.NotFound, "")) + + mockQueueClient. + EXPECT(). + CreateQueue(gomock.Any(), createQueueRequest).Return(nil, status.Error(codes.Internal, "")) + + r := QueueReconciler{ + Client: mockK8sClient, + Scheme: scheme, + QueueClient: mockQueueClient, + } + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: queueName}, + } + + _, err = r.Reconcile(context.Background(), req) + require.ErrorContains(t, err, "creating queue") +} + +func TestQueueReconciler_ReconcileNewQueueServerErrorOnGet(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + scheme, err := v1alpha1.SchemeBuilder.Build() + if err != nil { + t.Fatalf("should not return error when building schema") + } + + expectedNamespacedName := types.NamespacedName{Namespace: "default", Name: queueName} + expectedQueue := corev1alpha1.Queue{ + TypeMeta: metav1.TypeMeta{ + Kind: "Queue", + APIVersion: "core.armadaproject.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: queueName}, + Spec: corev1alpha1.QueueSpec{}, + } + + mockK8sClient := k8sclient.NewMockClient(mockCtrl) + // queue + mockK8sClient. + EXPECT(). + Get(gomock.Any(), expectedNamespacedName, gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil). + SetArg(2, expectedQueue) + // queue finalizer + mockK8sClient. + EXPECT(). + Update(gomock.Any(), gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil) + + mockQueueClient := armadaclient.NewMockQueueClient(mockCtrl) + mockQueueClient. + EXPECT(). + GetQueue(gomock.Any(), &api.QueueGetRequest{Name: queueName}). + Return(nil, status.Error(codes.Internal, "")) + + r := QueueReconciler{ + Client: mockK8sClient, + Scheme: scheme, + QueueClient: mockQueueClient, + } + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: queueName}, + } + + _, err = r.Reconcile(context.Background(), req) + require.ErrorContains(t, err, "getting queue") +} + +func TestQueueReconciler_ReconcileExistingQueueNoopSuccess(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + scheme, err := v1alpha1.SchemeBuilder.Build() + if err != nil { + t.Fatalf("should not return error when building schema") + } + + expectedNamespacedName := types.NamespacedName{Namespace: "default", Name: queueName} + expectedQueue := corev1alpha1.Queue{ + TypeMeta: metav1.TypeMeta{ + Kind: "Queue", + APIVersion: "core.armadaproject.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: queueName}, + Spec: corev1alpha1.QueueSpec{}, + } + + mockK8sClient := k8sclient.NewMockClient(mockCtrl) + // queue + mockK8sClient. + EXPECT(). + Get(gomock.Any(), expectedNamespacedName, gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil). + SetArg(2, expectedQueue) + // queue finalizer + mockK8sClient. + EXPECT(). + Update(gomock.Any(), gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil) + + existingQueue := &api.Queue{ + Name: queueName, + PriorityFactor: 1, + } + + mockQueueClient := armadaclient.NewMockQueueClient(mockCtrl) + mockQueueClient. + EXPECT(). + GetQueue(gomock.Any(), &api.QueueGetRequest{Name: queueName}). + Return(existingQueue, nil) + + r := QueueReconciler{ + Client: mockK8sClient, + Scheme: scheme, + QueueClient: mockQueueClient, + } + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: queueName}, + } + + _, err = r.Reconcile(context.Background(), req) + if err != nil { + t.Fatalf("reconcile should not return error") + } +} + +func TestQueueReconciler_ReconcileExistingQueueUpdateSuccess(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + scheme, err := v1alpha1.SchemeBuilder.Build() + if err != nil { + t.Fatalf("should not return error when building schema") + } + + priorityFactor := resource.MustParse("5") + expectedNamespacedName := types.NamespacedName{Namespace: "default", Name: queueName} + expectedQueue := corev1alpha1.Queue{ + TypeMeta: metav1.TypeMeta{ + Kind: "Queue", + APIVersion: "core.armadaproject.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: queueName}, + Spec: corev1alpha1.QueueSpec{ + PriorityFactor: &priorityFactor, + Permissions: []corev1alpha1.QueuePermissions{ + { + Subjects: []corev1alpha1.PermissionSubject{ + { + Kind: "Group", + Name: "Admin", + }, + }, + Verbs: []string{"submit"}, + }, + }, + }, + } + + mockK8sClient := k8sclient.NewMockClient(mockCtrl) + // queue + mockK8sClient. + EXPECT(). + Get(gomock.Any(), expectedNamespacedName, gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil). + SetArg(2, expectedQueue) + // queue finalizer + mockK8sClient. + EXPECT(). + Update(gomock.Any(), gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil) + + existingQueue := &api.Queue{ + Name: queueName, + PriorityFactor: 1, + } + + updateQueueRequest := &api.Queue{ + Name: queueName, + PriorityFactor: 5, + Permissions: []*api.Queue_Permissions{ + { + Subjects: []*api.Queue_Permissions_Subject{ + { + Kind: "Group", + Name: "Admin", + }, + }, + Verbs: []string{ + "submit", + }, + }, + }, + } + + mockQueueClient := armadaclient.NewMockQueueClient(mockCtrl) + mockQueueClient. + EXPECT(). + GetQueue(gomock.Any(), &api.QueueGetRequest{Name: queueName}). + Return(existingQueue, nil) + mockQueueClient. + EXPECT(). + UpdateQueue(gomock.Any(), updateQueueRequest). + Return(nil, nil) + + r := QueueReconciler{ + Client: mockK8sClient, + Scheme: scheme, + QueueClient: mockQueueClient, + } + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: queueName}, + } + + _, err = r.Reconcile(context.Background(), req) + if err != nil { + t.Fatalf("reconcile should not return error") + } +} + +func TestQueueReconciler_ReconcileDeleteQueueSuccess(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + scheme, err := v1alpha1.SchemeBuilder.Build() + if err != nil { + t.Fatalf("should not return error when building schema") + } + + expectedNamespacedName := types.NamespacedName{Namespace: "default", Name: queueName} + expectedQueue := corev1alpha1.Queue{ + TypeMeta: metav1.TypeMeta{ + Kind: "Queue", + APIVersion: "core.armadaproject.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: queueName, + DeletionTimestamp: &metav1.Time{Time: time.Now()}, + Finalizers: []string{operatorFinalizer}}, + Spec: corev1alpha1.QueueSpec{}, + } + + mockK8sClient := k8sclient.NewMockClient(mockCtrl) + // queue + mockK8sClient. + EXPECT(). + Get(gomock.Any(), expectedNamespacedName, gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil). + SetArg(2, expectedQueue) + // remove finalizer + mockK8sClient. + EXPECT(). + Update(gomock.Any(), gomock.AssignableToTypeOf(&corev1alpha1.Queue{})). + Return(nil) + + mockQueueClient := armadaclient.NewMockQueueClient(mockCtrl) + mockQueueClient. + EXPECT(). + DeleteQueue(gomock.Any(), &api.QueueDeleteRequest{Name: queueName}). + Return(nil, nil) r := QueueReconciler{ - Client: mockK8sClient, - Scheme: scheme, + Client: mockK8sClient, + Scheme: scheme, + QueueClient: mockQueueClient, } req := ctrl.Request{ - NamespacedName: types.NamespacedName{Namespace: "default", Name: "server"}, + NamespacedName: types.NamespacedName{Namespace: "default", Name: queueName}, } _, err = r.Reconcile(context.Background(), req) diff --git a/internal/controller/install/armadaserver_controller.go b/internal/controller/install/armadaserver_controller.go index 81384545..f7dc393c 100644 --- a/internal/controller/install/armadaserver_controller.go +++ b/internal/controller/install/armadaserver_controller.go @@ -21,6 +21,8 @@ import ( "fmt" "time" + "github.com/armadaproject/armada-operator/internal/controller/common" + "k8s.io/utils/ptr" "github.com/go-logr/logr" @@ -69,7 +71,7 @@ func (r *ArmadaServerReconciler) Reconcile(ctx context.Context, req ctrl.Request logger.Info("Reconciling object") var server installv1alpha1.ArmadaServer - if miss, err := getObject(ctx, r.Client, &server, req.NamespacedName, logger); err != nil || miss { + if miss, err := common.GetObject(ctx, r.Client, &server, req.NamespacedName, logger); err != nil || miss { return ctrl.Result{}, err } diff --git a/internal/controller/install/binoculars_controller.go b/internal/controller/install/binoculars_controller.go index 95b328c7..1161b526 100644 --- a/internal/controller/install/binoculars_controller.go +++ b/internal/controller/install/binoculars_controller.go @@ -20,6 +20,8 @@ import ( "context" "time" + "github.com/armadaproject/armada-operator/internal/controller/common" + "github.com/pkg/errors" installv1alpha1 "github.com/armadaproject/armada-operator/api/install/v1alpha1" @@ -65,7 +67,7 @@ func (r *BinocularsReconciler) Reconcile(ctx context.Context, req ctrl.Request) logger.Info("Reconciling Binoculars object") var binoculars installv1alpha1.Binoculars - if miss, err := getObject(ctx, r.Client, &binoculars, req.NamespacedName, logger); err != nil || miss { + if miss, err := common.GetObject(ctx, r.Client, &binoculars, req.NamespacedName, logger); err != nil || miss { return ctrl.Result{}, err } diff --git a/internal/controller/install/common_helpers.go b/internal/controller/install/common_helpers.go index e7e6c7f8..f1c6cf2c 100644 --- a/internal/controller/install/common_helpers.go +++ b/internal/controller/install/common_helpers.go @@ -14,8 +14,6 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "github.com/go-logr/logr" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" @@ -679,25 +677,6 @@ func deleteObjectIfNeeded( return nil } -// getObject will get the object from Kubernetes and return if it is missing or an error. -func getObject( - ctx context.Context, - client client.Client, - object client.Object, - namespacedName types.NamespacedName, - logger logr.Logger, -) (miss bool, err error) { - logger.Info("Fetching object from cache") - if err := client.Get(ctx, namespacedName, object); err != nil { - if k8serrors.IsNotFound(err) { - logger.Info("Object not found in cache, ending reconcile...") - return true, nil - } - return true, err - } - return false, nil -} - func newProfilingComponents( object metav1.Object, scheme *runtime.Scheme, diff --git a/internal/controller/install/common_helpers_test.go b/internal/controller/install/common_helpers_test.go index 29833943..17b78495 100644 --- a/internal/controller/install/common_helpers_test.go +++ b/internal/controller/install/common_helpers_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/armadaproject/armada-operator/internal/controller/common" + "github.com/go-logr/logr" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -1013,7 +1015,7 @@ func TestGetObjectFromCache(t *testing.T) { // Call the function under test namespacedName := types.NamespacedName{Name: "test-resource", Namespace: "default"} - miss, err := getObject(ctx, fakeClient, object, namespacedName, logger) + miss, err := common.GetObject(ctx, fakeClient, object, namespacedName, logger) if tc.expectError { assert.ErrorIs(t, err, tc.returnError) } else { diff --git a/internal/controller/install/eventingester_controller.go b/internal/controller/install/eventingester_controller.go index 3e15e439..f5f8bd14 100644 --- a/internal/controller/install/eventingester_controller.go +++ b/internal/controller/install/eventingester_controller.go @@ -20,6 +20,8 @@ import ( "context" "time" + "github.com/armadaproject/armada-operator/internal/controller/common" + "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" @@ -59,7 +61,7 @@ func (r *EventIngesterReconciler) Reconcile(ctx context.Context, req ctrl.Reques logger.Info("Reconciling object") var eventIngester installv1alpha1.EventIngester - if miss, err := getObject(ctx, r.Client, &eventIngester, req.NamespacedName, logger); err != nil || miss { + if miss, err := common.GetObject(ctx, r.Client, &eventIngester, req.NamespacedName, logger); err != nil || miss { return ctrl.Result{}, err } diff --git a/internal/controller/install/executor_controller.go b/internal/controller/install/executor_controller.go index 3e79c0ea..6966fcc5 100644 --- a/internal/controller/install/executor_controller.go +++ b/internal/controller/install/executor_controller.go @@ -21,6 +21,8 @@ import ( "fmt" "time" + "github.com/armadaproject/armada-operator/internal/controller/common" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/ptr" @@ -85,7 +87,7 @@ func (r *ExecutorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c logger.Info("Reconciling object") var executor installv1alpha1.Executor - if miss, err := getObject(ctx, r.Client, &executor, req.NamespacedName, logger); err != nil || miss { + if miss, err := common.GetObject(ctx, r.Client, &executor, req.NamespacedName, logger); err != nil || miss { return ctrl.Result{}, err } diff --git a/internal/controller/install/lookout_controller.go b/internal/controller/install/lookout_controller.go index 1b65d161..54fc8e6f 100644 --- a/internal/controller/install/lookout_controller.go +++ b/internal/controller/install/lookout_controller.go @@ -17,6 +17,8 @@ import ( "context" "time" + "github.com/armadaproject/armada-operator/internal/controller/common" + "github.com/pkg/errors" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" @@ -60,7 +62,7 @@ func (r *LookoutReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct logger.Info("Reconciling object") var lookout installv1alpha1.Lookout - if miss, err := getObject(ctx, r.Client, &lookout, req.NamespacedName, logger); err != nil || miss { + if miss, err := common.GetObject(ctx, r.Client, &lookout, req.NamespacedName, logger); err != nil || miss { return ctrl.Result{}, err } diff --git a/internal/controller/install/lookoutingester_controller.go b/internal/controller/install/lookoutingester_controller.go index b7973ed3..08aeb117 100644 --- a/internal/controller/install/lookoutingester_controller.go +++ b/internal/controller/install/lookoutingester_controller.go @@ -20,6 +20,8 @@ import ( "context" "time" + "github.com/armadaproject/armada-operator/internal/controller/common" + "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" @@ -56,7 +58,7 @@ func (r *LookoutIngesterReconciler) Reconcile(ctx context.Context, req ctrl.Requ logger.Info("Reconciling object") var lookoutIngester installv1alpha1.LookoutIngester - if miss, err := getObject(ctx, r.Client, &lookoutIngester, req.NamespacedName, logger); err != nil || miss { + if miss, err := common.GetObject(ctx, r.Client, &lookoutIngester, req.NamespacedName, logger); err != nil || miss { return ctrl.Result{}, err } diff --git a/internal/controller/install/scheduler_controller.go b/internal/controller/install/scheduler_controller.go index cba65878..ee44c1e1 100644 --- a/internal/controller/install/scheduler_controller.go +++ b/internal/controller/install/scheduler_controller.go @@ -18,6 +18,8 @@ import ( "fmt" "time" + "github.com/armadaproject/armada-operator/internal/controller/common" + "k8s.io/utils/ptr" "k8s.io/apimachinery/pkg/util/duration" @@ -70,7 +72,7 @@ func (r *SchedulerReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( logger.Info("Reconciling object") var scheduler installv1alpha1.Scheduler - if miss, err := getObject(ctx, r.Client, &scheduler, req.NamespacedName, logger); err != nil || miss { + if miss, err := common.GetObject(ctx, r.Client, &scheduler, req.NamespacedName, logger); err != nil || miss { return ctrl.Result{}, err } diff --git a/internal/controller/install/scheduleringester_controller.go b/internal/controller/install/scheduleringester_controller.go index d2f88fbc..bbe2a2dd 100644 --- a/internal/controller/install/scheduleringester_controller.go +++ b/internal/controller/install/scheduleringester_controller.go @@ -20,6 +20,8 @@ import ( "context" "time" + "github.com/armadaproject/armada-operator/internal/controller/common" + "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" @@ -59,7 +61,7 @@ func (r *SchedulerIngesterReconciler) Reconcile(ctx context.Context, req ctrl.Re logger.Info("Reconciling object") var schedulerIngester installv1alpha1.SchedulerIngester - if miss, err := getObject(ctx, r.Client, &schedulerIngester, req.NamespacedName, logger); err != nil || miss { + if miss, err := common.GetObject(ctx, r.Client, &schedulerIngester, req.NamespacedName, logger); err != nil || miss { return ctrl.Result{}, err } diff --git a/test/armadaclient/mock.go b/test/armadaclient/mock.go new file mode 100644 index 00000000..13fba435 --- /dev/null +++ b/test/armadaclient/mock.go @@ -0,0 +1,11 @@ +package armadaclient + +import ( + "github.com/armadaproject/armada/pkg/api" +) + +//go:generate mockgen -destination=./mock_queue_client.go -package=armadaclient "github.com/armadaproject/armada-operator/test/armadaclient" QueueClient + +type QueueClient interface { + api.QueueServiceClient +} diff --git a/test/armadaclient/mock_queue_client.go b/test/armadaclient/mock_queue_client.go new file mode 100644 index 00000000..39ccdcd7 --- /dev/null +++ b/test/armadaclient/mock_queue_client.go @@ -0,0 +1,258 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/armadaproject/armada-operator/test/armadaclient (interfaces: QueueClient) + +// Package armadaclient is a generated GoMock package. +package armadaclient + +import ( + context "context" + reflect "reflect" + + api "github.com/armadaproject/armada/pkg/api" + types "github.com/gogo/protobuf/types" + gomock "github.com/golang/mock/gomock" + grpc "google.golang.org/grpc" +) + +// MockQueueClient is a mock of QueueClient interface. +type MockQueueClient struct { + ctrl *gomock.Controller + recorder *MockQueueClientMockRecorder +} + +// MockQueueClientMockRecorder is the mock recorder for MockQueueClient. +type MockQueueClientMockRecorder struct { + mock *MockQueueClient +} + +// NewMockQueueClient creates a new mock instance. +func NewMockQueueClient(ctrl *gomock.Controller) *MockQueueClient { + mock := &MockQueueClient{ctrl: ctrl} + mock.recorder = &MockQueueClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockQueueClient) EXPECT() *MockQueueClientMockRecorder { + return m.recorder +} + +// CancelOnQueue mocks base method. +func (m *MockQueueClient) CancelOnQueue(arg0 context.Context, arg1 *api.QueueCancelRequest, arg2 ...grpc.CallOption) (*types.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CancelOnQueue", varargs...) + ret0, _ := ret[0].(*types.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CancelOnQueue indicates an expected call of CancelOnQueue. +func (mr *MockQueueClientMockRecorder) CancelOnQueue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelOnQueue", reflect.TypeOf((*MockQueueClient)(nil).CancelOnQueue), varargs...) +} + +// CordonQueue mocks base method. +func (m *MockQueueClient) CordonQueue(arg0 context.Context, arg1 *api.QueueCordonRequest, arg2 ...grpc.CallOption) (*types.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CordonQueue", varargs...) + ret0, _ := ret[0].(*types.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CordonQueue indicates an expected call of CordonQueue. +func (mr *MockQueueClientMockRecorder) CordonQueue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CordonQueue", reflect.TypeOf((*MockQueueClient)(nil).CordonQueue), varargs...) +} + +// CreateQueue mocks base method. +func (m *MockQueueClient) CreateQueue(arg0 context.Context, arg1 *api.Queue, arg2 ...grpc.CallOption) (*types.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateQueue", varargs...) + ret0, _ := ret[0].(*types.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateQueue indicates an expected call of CreateQueue. +func (mr *MockQueueClientMockRecorder) CreateQueue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateQueue", reflect.TypeOf((*MockQueueClient)(nil).CreateQueue), varargs...) +} + +// CreateQueues mocks base method. +func (m *MockQueueClient) CreateQueues(arg0 context.Context, arg1 *api.QueueList, arg2 ...grpc.CallOption) (*api.BatchQueueCreateResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateQueues", varargs...) + ret0, _ := ret[0].(*api.BatchQueueCreateResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateQueues indicates an expected call of CreateQueues. +func (mr *MockQueueClientMockRecorder) CreateQueues(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateQueues", reflect.TypeOf((*MockQueueClient)(nil).CreateQueues), varargs...) +} + +// DeleteQueue mocks base method. +func (m *MockQueueClient) DeleteQueue(arg0 context.Context, arg1 *api.QueueDeleteRequest, arg2 ...grpc.CallOption) (*types.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteQueue", varargs...) + ret0, _ := ret[0].(*types.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteQueue indicates an expected call of DeleteQueue. +func (mr *MockQueueClientMockRecorder) DeleteQueue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteQueue", reflect.TypeOf((*MockQueueClient)(nil).DeleteQueue), varargs...) +} + +// GetQueue mocks base method. +func (m *MockQueueClient) GetQueue(arg0 context.Context, arg1 *api.QueueGetRequest, arg2 ...grpc.CallOption) (*api.Queue, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetQueue", varargs...) + ret0, _ := ret[0].(*api.Queue) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetQueue indicates an expected call of GetQueue. +func (mr *MockQueueClientMockRecorder) GetQueue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueue", reflect.TypeOf((*MockQueueClient)(nil).GetQueue), varargs...) +} + +// GetQueues mocks base method. +func (m *MockQueueClient) GetQueues(arg0 context.Context, arg1 *api.StreamingQueueGetRequest, arg2 ...grpc.CallOption) (api.QueueService_GetQueuesClient, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetQueues", varargs...) + ret0, _ := ret[0].(api.QueueService_GetQueuesClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetQueues indicates an expected call of GetQueues. +func (mr *MockQueueClientMockRecorder) GetQueues(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueues", reflect.TypeOf((*MockQueueClient)(nil).GetQueues), varargs...) +} + +// PreemptOnQueue mocks base method. +func (m *MockQueueClient) PreemptOnQueue(arg0 context.Context, arg1 *api.QueuePreemptRequest, arg2 ...grpc.CallOption) (*types.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PreemptOnQueue", varargs...) + ret0, _ := ret[0].(*types.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PreemptOnQueue indicates an expected call of PreemptOnQueue. +func (mr *MockQueueClientMockRecorder) PreemptOnQueue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PreemptOnQueue", reflect.TypeOf((*MockQueueClient)(nil).PreemptOnQueue), varargs...) +} + +// UncordonQueue mocks base method. +func (m *MockQueueClient) UncordonQueue(arg0 context.Context, arg1 *api.QueueUncordonRequest, arg2 ...grpc.CallOption) (*types.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UncordonQueue", varargs...) + ret0, _ := ret[0].(*types.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UncordonQueue indicates an expected call of UncordonQueue. +func (mr *MockQueueClientMockRecorder) UncordonQueue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UncordonQueue", reflect.TypeOf((*MockQueueClient)(nil).UncordonQueue), varargs...) +} + +// UpdateQueue mocks base method. +func (m *MockQueueClient) UpdateQueue(arg0 context.Context, arg1 *api.Queue, arg2 ...grpc.CallOption) (*types.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateQueue", varargs...) + ret0, _ := ret[0].(*types.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateQueue indicates an expected call of UpdateQueue. +func (mr *MockQueueClientMockRecorder) UpdateQueue(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateQueue", reflect.TypeOf((*MockQueueClient)(nil).UpdateQueue), varargs...) +} + +// UpdateQueues mocks base method. +func (m *MockQueueClient) UpdateQueues(arg0 context.Context, arg1 *api.QueueList, arg2 ...grpc.CallOption) (*api.BatchQueueUpdateResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateQueues", varargs...) + ret0, _ := ret[0].(*api.BatchQueueUpdateResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateQueues indicates an expected call of UpdateQueues. +func (mr *MockQueueClientMockRecorder) UpdateQueues(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateQueues", reflect.TypeOf((*MockQueueClient)(nil).UpdateQueues), varargs...) +}