From 60467eed01720a1057cc9dad550d42f0737b3f1d Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 14 Dec 2022 11:59:52 -0500 Subject: [PATCH 01/33] Move POC rest impl. to quota plubins. --- .../quota-simple-rest}/quota_rest_manager.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/{controller/quota/quotamanager => quotaplugins/quota-simple-rest}/quota_rest_manager.go (100%) diff --git a/pkg/controller/quota/quotamanager/quota_rest_manager.go b/pkg/quotaplugins/quota-simple-rest/quota_rest_manager.go similarity index 100% rename from pkg/controller/quota/quotamanager/quota_rest_manager.go rename to pkg/quotaplugins/quota-simple-rest/quota_rest_manager.go From 8948ab7b294a5a2a0428de9c009ec3be135ec0ed Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 8 Feb 2023 12:44:32 -0500 Subject: [PATCH 02/33] Initial checkin of new quota management. --- CONTROLLER_VERSION | 2 +- Makefile | 1 + config/crd/bases/ibm.com_quotasubtree-v1.yaml | 64 ++ .../mcad-controller/templates/deployment.yaml | 65 +++ hack/boilerplate/boilerplate.go.txt | 2 +- kuttl-test.yaml | 4 + .../v1beta1/zz_generated.deepcopy.go | 4 +- pkg/apis/quotaplugins/quotasubtree/v1/doc.go | 2 + .../quotaplugins/quotasubtree/v1/register.go | 38 ++ .../quotaplugins/quotasubtree/v1/types.go | 88 +++ .../quotasubtree/v1/zz_generated.deepcopy.go | 250 ++++++++ .../clientset/versioned/clientset.go | 103 ++++ .../quotasubtree/clientset/versioned/doc.go | 20 + .../versioned/fake/clientset_generated.go | 82 +++ .../clientset/versioned/fake/doc.go | 20 + .../clientset/versioned/fake/register.go | 56 ++ .../clientset/versioned/scheme/doc.go | 20 + .../clientset/versioned/scheme/register.go | 56 ++ .../versioned/typed/quotasubtree/v1/doc.go | 20 + .../typed/quotasubtree/v1/fake/doc.go | 20 + .../quotasubtree/v1/fake/fake_quotasubtree.go | 142 +++++ .../v1/fake/fake_quotasubtree_client.go | 41 ++ .../quotasubtree/v1/generated_expansion.go | 21 + .../typed/quotasubtree/v1/quotasubtree.go | 195 +++++++ .../quotasubtree/v1/quotasubtree_client.go | 89 +++ .../informers/externalversions/factory.go | 180 ++++++ .../informers/externalversions/generic.go | 62 ++ .../internalinterfaces/factory_interfaces.go | 40 ++ .../quotasubtree/interface.go | 46 ++ .../quotasubtree/v1/interface.go | 45 ++ .../quotasubtree/v1/quotasubtree.go | 90 +++ .../quotasubtree/v1/expansion_generated.go | 27 + .../listers/quotasubtree/v1/quotasubtree.go | 99 ++++ .../queuejob/queuejob_controller_ex.go | 6 +- .../quota/quota_manager_interface.go | 2 +- .../qm_lib_backend_with_quotasubt_mgr.go} | 28 +- .../quotasubtmgr/event_handlers.go | 83 +++ .../quotasubtmgr/quota_subtree_manager.go | 295 ++++++++++ .../quotasubtmgr}/util/constants.go | 2 +- .../util/utils.go | 0 .../resplanmgr/event_handlers.go | 84 --- .../resplanmgr/resource_plan_manager.go | 295 ---------- .../quota-forest/quota-manager/.gitignore | 21 + .../quota-forest/quota-manager/README.md | 200 +++++++ .../quota-manager/demos/forest/demo.go | 92 +++ .../quota-manager/demos/incremental/demo.go | 88 +++ .../demos/manager/forest/demo.go | 116 ++++ .../quota-manager/demos/manager/tree/demo.go | 113 ++++ .../quota-manager/demos/tree/demo.go | 104 ++++ .../quota-manager/demos/updates/README.md | 10 + .../demos/updates/forest/demo.go | 196 +++++++ .../quota-manager/demos/updates/tree/demo.go | 199 +++++++ .../quota-manager/docs/forest-example.pdf | Bin 0 -> 139918 bytes .../quota-manager/docs/quota-algorithm.pdf | Bin 0 -> 207739 bytes .../quota-manager/docs/quota-manager.png | Bin 0 -> 35997 bytes .../quota-manager/docs/tree-cache-example.pdf | Bin 0 -> 70065 bytes .../quota-manager/docs/tree-example.pdf | Bin 0 -> 155768 bytes .../quota-manager/docs/tree-example.txt | 278 +++++++++ .../quota-forest/quota-manager/go-legacy.mod | 5 + .../quota-forest/quota-manager/go-sum.mod | 4 + .../quota-forest/quota-manager/main.go | 84 +++ .../quota-manager/quota/consumerinfo.go | 121 ++++ .../quota-manager/quota/core/allocation.go | 171 ++++++ .../quota/core/allocation_test.go | 330 +++++++++++ .../quota/core/allocationrecovery.go | 115 ++++ .../quota/core/allocationresponse.go | 115 ++++ .../quota/core/allocationresponse_test.go | 78 +++ .../quota-manager/quota/core/consumer.go | 141 +++++ .../quota/core/forestconsumer.go | 131 +++++ .../quota/core/forestcontroller.go | 393 +++++++++++++ .../quota-manager/quota/core/quotanode.go | 264 +++++++++ .../quota/core/quotanode_test.go | 121 ++++ .../quota-manager/quota/core/quotatree.go | 233 ++++++++ .../quota-manager/quota/core/treecache.go | 446 ++++++++++++++ .../quota/core/treecontroller.go | 301 ++++++++++ .../quota-manager/quota/quotamanager.go | 481 +++++++++++++++ .../quota-manager/quota/utils/defaults.go | 31 + .../quota-manager/quota/utils/types.go | 72 +++ .../samples/ExampleConsumer.json | 21 + .../quota-manager/samples/ExampleTree.json | 111 ++++ .../quota-manager/samples/TestConsumer.json | 22 + .../samples/TestForestConsumer.json | 32 + .../quota-manager/samples/TestTree.json | 50 ++ .../samples/forest/ContextTree.json | 69 +++ .../samples/forest/ServiceTree.json | 43 ++ .../quota-manager/samples/forest/job1.json | 28 + .../quota-manager/samples/forest/job2.json | 28 + .../quota-manager/samples/forest/job3.json | 28 + .../quota-manager/samples/forest/job4.json | 28 + .../quota-manager/samples/forest/job5.json | 28 + .../quota-manager/samples/tree/ca.json | 20 + .../quota-manager/samples/tree/cb.json | 20 + .../quota-manager/samples/tree/cc.json | 20 + .../quota-manager/samples/tree/cd.json | 20 + .../quota-manager/samples/tree/ce.json | 20 + .../quota-manager/samples/tree/cf.json | 20 + .../quota-manager/samples/tree/cg.json | 20 + .../quota-manager/samples/tree/ch.json | 20 + .../quota-manager/samples/tree/ci.json | 20 + .../quota-manager/samples/tree/cj.json | 20 + .../quota-manager/samples/tree/tree.json | 125 ++++ .../quota-forest/quota-manager/tree/node.go | 190 ++++++ .../quota-manager/tree/node_test.go | 546 ++++++++++++++++++ .../quota-forest/quota-manager/tree/tree.go | 131 +++++ .../quota-manager/tree/tree_test.go | 320 ++++++++++ .../quota-simple-rest/quota_rest_manager.go | 7 +- pkg/quotaplugins/util/utils.go | 62 ++ test/e2e-kuttl/quota-forest/00-assert.yaml | 24 + test/e2e-kuttl/quota-forest/01-assert.yaml | 15 + test/e2e-kuttl/quota-forest/01-install.yaml | 29 + test/e2e-kuttl/quota-forest/02-assert.yaml | 12 + test/e2e-kuttl/quota-forest/02-install.yaml | 50 ++ test/e2e-kuttl/quota-forest/03-assert.yaml | 11 + test/e2e-kuttl/quota-forest/03-install.yaml | 49 ++ test/e2e-kuttl/quota-forest/04-assert.yaml | 22 + test/e2e-kuttl/quota-forest/04-install.yaml | 50 ++ 116 files changed, 9769 insertions(+), 405 deletions(-) create mode 100644 config/crd/bases/ibm.com_quotasubtree-v1.yaml create mode 100644 kuttl-test.yaml create mode 100755 pkg/apis/quotaplugins/quotasubtree/v1/doc.go create mode 100755 pkg/apis/quotaplugins/quotasubtree/v1/register.go create mode 100755 pkg/apis/quotaplugins/quotasubtree/v1/types.go create mode 100755 pkg/apis/quotaplugins/quotasubtree/v1/zz_generated.deepcopy.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/clientset.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/doc.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/fake/clientset_generated.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/fake/doc.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/fake/register.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/scheme/doc.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/scheme/register.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/doc.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/doc.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/fake_quotasubtree.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/fake_quotasubtree_client.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/generated_expansion.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/quotasubtree.go create mode 100755 pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/quotasubtree_client.go create mode 100755 pkg/client/quotasubtree/informers/externalversions/factory.go create mode 100755 pkg/client/quotasubtree/informers/externalversions/generic.go create mode 100755 pkg/client/quotasubtree/informers/externalversions/internalinterfaces/factory_interfaces.go create mode 100755 pkg/client/quotasubtree/informers/externalversions/quotasubtree/interface.go create mode 100755 pkg/client/quotasubtree/informers/externalversions/quotasubtree/v1/interface.go create mode 100755 pkg/client/quotasubtree/informers/externalversions/quotasubtree/v1/quotasubtree.go create mode 100755 pkg/client/quotasubtree/listers/quotasubtree/v1/expansion_generated.go create mode 100755 pkg/client/quotasubtree/listers/quotasubtree/v1/quotasubtree.go rename pkg/controller/quota/{quotamanager/qm_lib_backend_with_resplan_mgr.go => quotaforestmanager/qm_lib_backend_with_quotasubt_mgr.go} (95%) create mode 100644 pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/event_handlers.go create mode 100644 pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/quota_subtree_manager.go rename pkg/controller/quota/{quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr => quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr}/util/constants.go (91%) rename pkg/controller/quota/{quotamanager => quotaforestmanager}/util/utils.go (100%) delete mode 100644 pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/event_handlers.go delete mode 100644 pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/resource_plan_manager.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/.gitignore create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/README.md create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/demos/forest/demo.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/demos/incremental/demo.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/demos/manager/forest/demo.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/demos/manager/tree/demo.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/demos/tree/demo.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/demos/updates/README.md create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/demos/updates/forest/demo.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/demos/updates/tree/demo.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/docs/forest-example.pdf create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/docs/quota-algorithm.pdf create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/docs/quota-manager.png create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/docs/tree-cache-example.pdf create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/docs/tree-example.pdf create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/docs/tree-example.txt create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/go-legacy.mod create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/go-sum.mod create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/main.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/consumerinfo.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocation.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocation_test.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationrecovery.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationresponse.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationresponse_test.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/consumer.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/forestconsumer.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/forestcontroller.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotanode.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotanode_test.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotatree.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/treecache.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/core/treecontroller.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/quotamanager.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/utils/defaults.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/quota/utils/types.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/ExampleConsumer.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/ExampleTree.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/TestConsumer.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/TestForestConsumer.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/TestTree.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/forest/ContextTree.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/forest/ServiceTree.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job1.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job2.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job3.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job4.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job5.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ca.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cb.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cc.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cd.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ce.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cf.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cg.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ch.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ci.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cj.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/samples/tree/tree.json create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/tree/node.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/tree/node_test.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/tree/tree.go create mode 100644 pkg/quotaplugins/quota-forest/quota-manager/tree/tree_test.go create mode 100644 pkg/quotaplugins/util/utils.go create mode 100644 test/e2e-kuttl/quota-forest/00-assert.yaml create mode 100644 test/e2e-kuttl/quota-forest/01-assert.yaml create mode 100644 test/e2e-kuttl/quota-forest/01-install.yaml create mode 100644 test/e2e-kuttl/quota-forest/02-assert.yaml create mode 100644 test/e2e-kuttl/quota-forest/02-install.yaml create mode 100644 test/e2e-kuttl/quota-forest/03-assert.yaml create mode 100644 test/e2e-kuttl/quota-forest/03-install.yaml create mode 100644 test/e2e-kuttl/quota-forest/04-assert.yaml create mode 100644 test/e2e-kuttl/quota-forest/04-install.yaml diff --git a/CONTROLLER_VERSION b/CONTROLLER_VERSION index eef04d64d..971333c39 100644 --- a/CONTROLLER_VERSION +++ b/CONTROLLER_VERSION @@ -1 +1 @@ -1.29.49 +1.29.51 diff --git a/Makefile b/Makefile index dd8346ee4..2208e016e 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,7 @@ generate-code: go build -o ${BIN_DIR}/deepcopy-gen ./cmd/deepcopy-gen/ $(info Generating deepcopy...) ${BIN_DIR}/deepcopy-gen -i ./pkg/apis/controller/v1beta1/ -O zz_generated.deepcopy + ${BIN_DIR}/deepcopy-gen -i ./pkg/apis/quotaplugins/quotasubtree/v1 -O zz_generated.deepcopy images: verify-tag-name $(info List executable directory) diff --git a/config/crd/bases/ibm.com_quotasubtree-v1.yaml b/config/crd/bases/ibm.com_quotasubtree-v1.yaml new file mode 100644 index 000000000..d9ba3e6d1 --- /dev/null +++ b/config/crd/bases/ibm.com_quotasubtree-v1.yaml @@ -0,0 +1,64 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: quotasubtrees.ibm.com + finalizers: [] +spec: + group: ibm.com + scope: Namespaced + names: + kind: QuotaSubtree + singular: quotasubtree + plural: quotasubtrees + shortNames: + - qst + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + properties: + status: + type: object + x-kubernetes-preserve-unknown-fields: true + spec: + type: object + properties: + parent: + type: string + parentNamespace: + type: string + children: + type: array + items: + type: object + properties: + name: + type: string + namespace: + type: string + share: + type: integer + quotas: + type: object + properties: + disabled: + type: boolean + hardLimit: + type: boolean + requests: + type: object + properties: + cpu: + x-kubernetes-int-or-string: true + pattern: '^[0-9]*(m)*$' + memory: + x-kubernetes-int-or-string: true + pattern: '^[0-9]*(Ei|Pi|Ti|Gi|Mi|Ki|E|P|T|G|M|K)*$' + nvidia.com/gpu: + x-kubernetes-int-or-string: true + pattern: '^[0-9]*$' diff --git a/deployment/mcad-controller/templates/deployment.yaml b/deployment/mcad-controller/templates/deployment.yaml index e8a6dfc96..f1b3def49 100644 --- a/deployment/mcad-controller/templates/deployment.yaml +++ b/deployment/mcad-controller/templates/deployment.yaml @@ -102,6 +102,71 @@ subjects: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + name: quotasubtrees.ibm.com + finalizers: [] +spec: + group: ibm.com + scope: Namespaced + names: + kind: QuotaSubtree + singular: quotasubtree + plural: quotasubtrees + shortNames: + - qst + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + properties: + status: + type: object + x-kubernetes-preserve-unknown-fields: true + spec: + type: object + properties: + parent: + type: string + parentNamespace: + type: string + children: + type: array + items: + type: object + properties: + name: + type: string + namespace: + type: string + share: + type: integer + quotas: + type: object + properties: + disabled: + type: boolean + hardLimit: + type: boolean + requests: + type: object + properties: + cpu: + x-kubernetes-int-or-string: true + pattern: '^[0-9]*(m)*$' + memory: + x-kubernetes-int-or-string: true + pattern: '^[0-9]*(Ei|Pi|Ti|Gi|Mi|Ki|E|P|T|G|M|K)*$' + nvidia.com/gpu: + x-kubernetes-int-or-string: true + pattern: '^[0-9]*$' +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.4.1 diff --git a/hack/boilerplate/boilerplate.go.txt b/hack/boilerplate/boilerplate.go.txt index 6f41861ce..941e3a5ad 100644 --- a/hack/boilerplate/boilerplate.go.txt +++ b/hack/boilerplate/boilerplate.go.txt @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ /* -Copyright 2019, 2021 The Multi-Cluster App Dispatcher Authors. +Copyright 2019, 2021, 2022, YEAR The Multi-Cluster App Dispatcher Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/kuttl-test.yaml b/kuttl-test.yaml new file mode 100644 index 000000000..d5776f0f7 --- /dev/null +++ b/kuttl-test.yaml @@ -0,0 +1,4 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestSuite +testDirs: + - ./test/e2e-kuttl/ diff --git a/pkg/apis/controller/v1beta1/zz_generated.deepcopy.go b/pkg/apis/controller/v1beta1/zz_generated.deepcopy.go index 5c3e01197..1394611ea 100644 --- a/pkg/apis/controller/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/controller/v1beta1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2022 The Kubernetes Authors. +Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ /* -Copyright 2019, 2021 The Multi-Cluster App Dispatcher Authors. +Copyright 2019, 2021, 2022, 2023 The Multi-Cluster App Dispatcher Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/apis/quotaplugins/quotasubtree/v1/doc.go b/pkg/apis/quotaplugins/quotasubtree/v1/doc.go new file mode 100755 index 000000000..632034959 --- /dev/null +++ b/pkg/apis/quotaplugins/quotasubtree/v1/doc.go @@ -0,0 +1,2 @@ +// +k8s:deepcopy-gen=package +package v1 diff --git a/pkg/apis/quotaplugins/quotasubtree/v1/register.go b/pkg/apis/quotaplugins/quotasubtree/v1/register.go new file mode 100755 index 000000000..09e66191d --- /dev/null +++ b/pkg/apis/quotaplugins/quotasubtree/v1/register.go @@ -0,0 +1,38 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +const ( + // GroupName is the group name used in this package. + GroupName = "ibm.com" + + // GroupVersion is the version of scheduling group + GroupVersion = "v1" +) + +// SchemeGroupVersion is the group version used to register these objects. +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion} + +// Resource takes an unqualified resource and returns a Group-qualified GroupResource. +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +// addKnownTypes adds the set of types defined in this package to the supplied scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &QuotaSubtree{}, + &QuotaSubtreeList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/quotaplugins/quotasubtree/v1/types.go b/pkg/apis/quotaplugins/quotasubtree/v1/types.go new file mode 100755 index 000000000..56d725741 --- /dev/null +++ b/pkg/apis/quotaplugins/quotasubtree/v1/types.go @@ -0,0 +1,88 @@ +package v1 +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import ( + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type ResourceName string +type ResourceList map[ResourceName]resource.Quantity + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// QuotaSubtree is a specification for a quota subtree resource +type QuotaSubtree struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec QuotaSubtreeSpec `json:"spec"` + Status QuotaSubtreeStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// QuotaSubtreeList is a collection of resource plan +type QuotaSubtreeList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []QuotaSubtree `json:"items"` +} + +// QuotaSubtreeSpec is the spec for a resource plan +type QuotaSubtreeSpec struct { + Parent string `json:"parent,omitempty" protobuf:"bytes,1,opt,name=parent"` + ParentNamespace string `json:"parentNamespace,omitempty" protobuf:"bytes,2,opt,name=parentNamespace"` + Children []Child `json:"children,omitempty" protobuf:"bytes,3,opt,name=children"` +} + +// Child is the spec for a QuotaSubtree resource +type Child struct { + Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` + Namespace string `json:"namespace,omitempty" protobuf:"bytes,2,opt,name=namespace"` + Share int32 `json:"share,omitempty" protobuf:"bytes,3,opt,name=share"` + Quotas Quota `json:"quotas,omitempty" protobuf:"bytes,4,opt,name=quotas"` + Path string `json:"path,omitempty" protobuf:"bytes,5,opt,name=path"` +} + +// Quota is the spec for a QuotaSubtree resource +type Quota struct { + Disabled bool `json:"disabled,omitempty" protobuf:"bytes,1,opt,name=disabled"` + Requests ResourceList `json:"requests,omitempty" protobuf:"bytes,2,rep,name=requests,casttype=ResourceList,castkey=ResourceName"` + HardLimit bool `json:"hardLimit,omitempty" protobuf:"bytes,4,opt,name=hardLimit"` +} + +// QuotaSubtreeStatus is the status for a QuotaSubtree resource +type QuotaSubtreeStatus struct { + TotalAllocation ResourceAllocation `json:"totalAllocation,omitempty protobuf:"bytes,1,opt,name=totalAllocation"` + Children []ResourceAllocation `json:"children,omitempty protobuf:"bytes,2,opt,name=children"` +} + +// ResourceAllocation is the spec for the child status +type ResourceAllocation struct { + Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` + Namespace string `json:"namespace,omitempty" protobuf:"bytes,2,opt,name=namespace"` + Path string `json:"path,omitempty" protobuf:"bytes,3,opt,name=path"` + Allocated ResourceAllocationStatus `json:"allocated,omitempty" protobuf:"bytes,5,opt,name=allocated"` +} + +// ResourceAllocationStatus is the spec for the child resource usage +type ResourceAllocationStatus struct { + Requests map[string]string `json:"requests,omitempty" protobuf:"bytes,2,opt,name=requests"` +} diff --git a/pkg/apis/quotaplugins/quotasubtree/v1/zz_generated.deepcopy.go b/pkg/apis/quotaplugins/quotasubtree/v1/zz_generated.deepcopy.go new file mode 100755 index 000000000..9fc997ba0 --- /dev/null +++ b/pkg/apis/quotaplugins/quotasubtree/v1/zz_generated.deepcopy.go @@ -0,0 +1,250 @@ +// +build !ignore_autogenerated + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +/* +Copyright 2019, 2021, 2022, 2023 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1 + +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 *Child) DeepCopyInto(out *Child) { + *out = *in + in.Quotas.DeepCopyInto(&out.Quotas) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Child. +func (in *Child) DeepCopy() *Child { + if in == nil { + return nil + } + out := new(Child) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Quota) DeepCopyInto(out *Quota) { + *out = *in + if in.Requests != nil { + in, out := &in.Requests, &out.Requests + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Quota. +func (in *Quota) DeepCopy() *Quota { + if in == nil { + return nil + } + out := new(Quota) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *QuotaSubtree) DeepCopyInto(out *QuotaSubtree) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuotaSubtree. +func (in *QuotaSubtree) DeepCopy() *QuotaSubtree { + if in == nil { + return nil + } + out := new(QuotaSubtree) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *QuotaSubtree) 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 *QuotaSubtreeList) DeepCopyInto(out *QuotaSubtreeList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]QuotaSubtree, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuotaSubtreeList. +func (in *QuotaSubtreeList) DeepCopy() *QuotaSubtreeList { + if in == nil { + return nil + } + out := new(QuotaSubtreeList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *QuotaSubtreeList) 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 *QuotaSubtreeSpec) DeepCopyInto(out *QuotaSubtreeSpec) { + *out = *in + if in.Children != nil { + in, out := &in.Children, &out.Children + *out = make([]Child, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuotaSubtreeSpec. +func (in *QuotaSubtreeSpec) DeepCopy() *QuotaSubtreeSpec { + if in == nil { + return nil + } + out := new(QuotaSubtreeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *QuotaSubtreeStatus) DeepCopyInto(out *QuotaSubtreeStatus) { + *out = *in + in.TotalAllocation.DeepCopyInto(&out.TotalAllocation) + if in.Children != nil { + in, out := &in.Children, &out.Children + *out = make([]ResourceAllocation, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuotaSubtreeStatus. +func (in *QuotaSubtreeStatus) DeepCopy() *QuotaSubtreeStatus { + if in == nil { + return nil + } + out := new(QuotaSubtreeStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceAllocation) DeepCopyInto(out *ResourceAllocation) { + *out = *in + in.Allocated.DeepCopyInto(&out.Allocated) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceAllocation. +func (in *ResourceAllocation) DeepCopy() *ResourceAllocation { + if in == nil { + return nil + } + out := new(ResourceAllocation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceAllocationStatus) DeepCopyInto(out *ResourceAllocationStatus) { + *out = *in + if in.Requests != nil { + in, out := &in.Requests, &out.Requests + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceAllocationStatus. +func (in *ResourceAllocationStatus) DeepCopy() *ResourceAllocationStatus { + if in == nil { + return nil + } + out := new(ResourceAllocationStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in ResourceList) DeepCopyInto(out *ResourceList) { + { + in := &in + *out = make(ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceList. +func (in ResourceList) DeepCopy() ResourceList { + if in == nil { + return nil + } + out := new(ResourceList) + in.DeepCopyInto(out) + return *out +} diff --git a/pkg/client/quotasubtree/clientset/versioned/clientset.go b/pkg/client/quotasubtree/clientset/versioned/clientset.go new file mode 100755 index 000000000..dce585637 --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/clientset.go @@ -0,0 +1,103 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package versioned + +import ( + "fmt" + + discovery "k8s.io/client-go/discovery" + rest "k8s.io/client-go/rest" + flowcontrol "k8s.io/client-go/util/flowcontrol" + quotasubtreev1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1" +) + +type Interface interface { + Discovery() discovery.DiscoveryInterface + QuotasubtreeV1() quotasubtreev1.QuotaSubtreeV1Interface +} + +// Clientset contains the clients for groups. Each group has exactly one +// version included in a Clientset. +type Clientset struct { + *discovery.DiscoveryClient + quotasubtreeV1 *quotasubtreev1.QuotasubtreeV1Client +} + +// QuotasubtreeV1 retrieves the QuotasubtreeV1Client +func (c *Clientset) QuotasubtreeV1() quotasubtreev1.QuotaSubtreeV1Interface { + return c.quotasubtreeV1 +} + +// Discovery retrieves the DiscoveryClient +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + if c == nil { + return nil + } + return c.DiscoveryClient +} + +// NewForConfig creates a new Clientset for the given config. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfig will generate a rate-limiter in configShallowCopy. +func NewForConfig(c *rest.Config) (*Clientset, error) { + configShallowCopy := *c + if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + if configShallowCopy.Burst <= 0 { + return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } + configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) + } + var cs Clientset + var err error + cs.quotasubtreeV1, err = quotasubtreev1.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } + + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) + if err != nil { + return nil, err + } + return &cs, nil +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) (*Clientset, error) { + var cs Clientset + var err error + err = nil + cs.quotasubtreeV1, err = quotasubtreev1.NewForConfigOrDie(c) + + if err != nil { + return nil, err + } else { + cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) + } + return &cs, err +} + +// New creates a new Clientset for the given RESTClient. +func New(c rest.Interface) *Clientset { + var cs Clientset + cs.quotasubtreeV1 = quotasubtreev1.New(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClient(c) + return &cs +} diff --git a/pkg/client/quotasubtree/clientset/versioned/doc.go b/pkg/client/quotasubtree/clientset/versioned/doc.go new file mode 100755 index 000000000..41721ca52 --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated clientset. +package versioned diff --git a/pkg/client/quotasubtree/clientset/versioned/fake/clientset_generated.go b/pkg/client/quotasubtree/clientset/versioned/fake/clientset_generated.go new file mode 100755 index 000000000..5798ab417 --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/fake/clientset_generated.go @@ -0,0 +1,82 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/discovery" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/testing" + clientset "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/clientset/versioned" + quotasubtreev1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1" + fakequotasubtreev1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake" +) + +// NewSimpleClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + testing.Fake + discovery *fakediscovery.FakeDiscovery + tracker testing.ObjectTracker +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return c.discovery +} + +func (c *Clientset) Tracker() testing.ObjectTracker { + return c.tracker +} + +var _ clientset.Interface = &Clientset{} + +// QuotasubtreeV1 retrieves the QuotasubtreeV1Client +func (c *Clientset) QuotasubtreeV1() quotasubtreev1.QuotaSubtreeV1Interface { + return &fakequotasubtreev1.FakeQuotasubtreeV1{Fake: &c.Fake} +} diff --git a/pkg/client/quotasubtree/clientset/versioned/fake/doc.go b/pkg/client/quotasubtree/clientset/versioned/fake/doc.go new file mode 100755 index 000000000..9b99e7167 --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated fake clientset. +package fake diff --git a/pkg/client/quotasubtree/clientset/versioned/fake/register.go b/pkg/client/quotasubtree/clientset/versioned/fake/register.go new file mode 100755 index 000000000..433c12c9a --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/fake/register.go @@ -0,0 +1,56 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + quotasubtreev1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/quotaplugins/quotasubtree/v1" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) + +var localSchemeBuilder = runtime.SchemeBuilder{ + quotasubtreev1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(scheme)) +} diff --git a/pkg/client/quotasubtree/clientset/versioned/scheme/doc.go b/pkg/client/quotasubtree/clientset/versioned/scheme/doc.go new file mode 100755 index 000000000..7dc375616 --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/scheme/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package contains the scheme of the automatically generated clientset. +package scheme diff --git a/pkg/client/quotasubtree/clientset/versioned/scheme/register.go b/pkg/client/quotasubtree/clientset/versioned/scheme/register.go new file mode 100755 index 000000000..ce0f5c042 --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/scheme/register.go @@ -0,0 +1,56 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package scheme + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + quotasubtreev1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/quotaplugins/quotasubtree/v1" +) + +var Scheme = runtime.NewScheme() +var Codecs = serializer.NewCodecFactory(Scheme) +var ParameterCodec = runtime.NewParameterCodec(Scheme) +var localSchemeBuilder = runtime.SchemeBuilder{ + quotasubtreev1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(Scheme)) +} diff --git a/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/doc.go b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/doc.go new file mode 100755 index 000000000..3af5d054f --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1 diff --git a/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/doc.go b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/doc.go new file mode 100755 index 000000000..16f443990 --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/fake_quotasubtree.go b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/fake_quotasubtree.go new file mode 100755 index 000000000..df8dea1b4 --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/fake_quotasubtree.go @@ -0,0 +1,142 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + quotasubtreev1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/quotaplugins/quotasubtree/v1" +) + +// FakeQuotaSubtrees implements QuotaSubtreeInterface +type FakeQuotaSubtrees struct { + Fake *FakeQuotasubtreeV1 + ns string +} + +var quotasubtreesResource = schema.GroupVersionResource{Group: "quotasubtree", Version: "v1", Resource: "quotasubtrees"} + +var quotasubtreesKind = schema.GroupVersionKind{Group: "quotasubtree", Version: "v1", Kind: "QuotaSubtree"} + +// Get takes name of the quotaSubtree, and returns the corresponding quotaSubtree object, and an error if there is any. +func (c *FakeQuotaSubtrees) Get(ctx context.Context, name string, options v1.GetOptions) (result *quotasubtreev1.QuotaSubtree, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(quotasubtreesResource, c.ns, name), "asubtreev1.QuotaSubtree{}) + + if obj == nil { + return nil, err + } + return obj.(*quotasubtreev1.QuotaSubtree), err +} + +// List takes label and field selectors, and returns the list of QuotaSubtrees that match those selectors. +func (c *FakeQuotaSubtrees) List(ctx context.Context, opts v1.ListOptions) (result *quotasubtreev1.QuotaSubtreeList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(quotasubtreesResource, quotasubtreesKind, c.ns, opts), "asubtreev1.QuotaSubtreeList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := "asubtreev1.QuotaSubtreeList{ListMeta: obj.(*quotasubtreev1.QuotaSubtreeList).ListMeta} + for _, item := range obj.(*quotasubtreev1.QuotaSubtreeList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested quotaSubtrees. +func (c *FakeQuotaSubtrees) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(quotasubtreesResource, c.ns, opts)) + +} + +// Create takes the representation of a quotaSubtree and creates it. Returns the server's representation of the quotaSubtree, and an error, if there is any. +func (c *FakeQuotaSubtrees) Create(ctx context.Context, quotaSubtree *quotasubtreev1.QuotaSubtree, opts v1.CreateOptions) (result *quotasubtreev1.QuotaSubtree, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(quotasubtreesResource, c.ns, quotaSubtree), "asubtreev1.QuotaSubtree{}) + + if obj == nil { + return nil, err + } + return obj.(*quotasubtreev1.QuotaSubtree), err +} + +// Update takes the representation of a quotaSubtree and updates it. Returns the server's representation of the quotaSubtree, and an error, if there is any. +func (c *FakeQuotaSubtrees) Update(ctx context.Context, quotaSubtree *quotasubtreev1.QuotaSubtree, opts v1.UpdateOptions) (result *quotasubtreev1.QuotaSubtree, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(quotasubtreesResource, c.ns, quotaSubtree), "asubtreev1.QuotaSubtree{}) + + if obj == nil { + return nil, err + } + return obj.(*quotasubtreev1.QuotaSubtree), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeQuotaSubtrees) UpdateStatus(ctx context.Context, quotaSubtree *quotasubtreev1.QuotaSubtree, opts v1.UpdateOptions) (*quotasubtreev1.QuotaSubtree, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(quotasubtreesResource, "status", c.ns, quotaSubtree), "asubtreev1.QuotaSubtree{}) + + if obj == nil { + return nil, err + } + return obj.(*quotasubtreev1.QuotaSubtree), err +} + +// Delete takes name of the quotaSubtree and deletes it. Returns an error if one occurs. +func (c *FakeQuotaSubtrees) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(quotasubtreesResource, c.ns, name), "asubtreev1.QuotaSubtree{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeQuotaSubtrees) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(quotasubtreesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, "asubtreev1.QuotaSubtreeList{}) + return err +} + +// Patch applies the patch and returns the patched quotaSubtree. +func (c *FakeQuotaSubtrees) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *quotasubtreev1.QuotaSubtree, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(quotasubtreesResource, c.ns, name, pt, data, subresources...), "asubtreev1.QuotaSubtree{}) + + if obj == nil { + return nil, err + } + return obj.(*quotasubtreev1.QuotaSubtree), err +} diff --git a/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/fake_quotasubtree_client.go b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/fake_quotasubtree_client.go new file mode 100755 index 000000000..da61491d5 --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/fake/fake_quotasubtree_client.go @@ -0,0 +1,41 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1" + +) + +type FakeQuotasubtreeV1 struct { + *testing.Fake +} + +func (c *FakeQuotasubtreeV1) QuotaSubtrees(namespace string) v1.QuotaSubtreeInterface { + return &FakeQuotaSubtrees{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeQuotasubtreeV1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/generated_expansion.go b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/generated_expansion.go new file mode 100755 index 000000000..4505b3f9a --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1 + +type QuotaSubtreeExpansion interface{} diff --git a/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/quotasubtree.go b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/quotasubtree.go new file mode 100755 index 000000000..2f06c18bc --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/quotasubtree.go @@ -0,0 +1,195 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1 + +import ( + "context" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + v1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/quotaplugins/quotasubtree/v1" + scheme "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/clientset/versioned/scheme" +) + +// QuotaSubtreesGetter has a method to return a QuotaSubtreeInterface. +// A group's client should implement this interface. +type QuotaSubtreesGetter interface { + QuotaSubtrees(namespace string) QuotaSubtreeInterface +} + +// QuotaSubtreeInterface has methods to work with QuotaSubtree resources. +type QuotaSubtreeInterface interface { + Create(ctx context.Context, quotaSubtree *v1.QuotaSubtree, opts metav1.CreateOptions) (*v1.QuotaSubtree, error) + Update(ctx context.Context, quotaSubtree *v1.QuotaSubtree, opts metav1.UpdateOptions) (*v1.QuotaSubtree, error) + UpdateStatus(ctx context.Context, quotaSubtree *v1.QuotaSubtree, opts metav1.UpdateOptions) (*v1.QuotaSubtree, error) + Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error + Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.QuotaSubtree, error) + List(ctx context.Context, opts metav1.ListOptions) (*v1.QuotaSubtreeList, error) + Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.QuotaSubtree, err error) + QuotaSubtreeExpansion +} + +// quotaSubtrees implements QuotaSubtreeInterface +type quotaSubtrees struct { + client rest.Interface + ns string +} + +// newQuotaSubtrees returns a QuotaSubtrees +func newQuotaSubtrees(c *QuotasubtreeV1Client, namespace string) *quotaSubtrees { + return "aSubtrees{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the quotaSubtree, and returns the corresponding quotaSubtree object, and an error if there is any. +func (c *quotaSubtrees) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.QuotaSubtree, err error) { + result = &v1.QuotaSubtree{} + err = c.client.Get(). + Namespace(c.ns). + Resource("quotasubtrees"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of QuotaSubtrees that match those selectors. +func (c *quotaSubtrees) List(ctx context.Context, opts metav1.ListOptions) (result *v1.QuotaSubtreeList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1.QuotaSubtreeList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("quotasubtrees"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested quotaSubtrees. +func (c *quotaSubtrees) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("quotasubtrees"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a quotaSubtree and creates it. Returns the server's representation of the quotaSubtree, and an error, if there is any. +func (c *quotaSubtrees) Create(ctx context.Context, quotaSubtree *v1.QuotaSubtree, opts metav1.CreateOptions) (result *v1.QuotaSubtree, err error) { + result = &v1.QuotaSubtree{} + err = c.client.Post(). + Namespace(c.ns). + Resource("quotasubtrees"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(quotaSubtree). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a quotaSubtree and updates it. Returns the server's representation of the quotaSubtree, and an error, if there is any. +func (c *quotaSubtrees) Update(ctx context.Context, quotaSubtree *v1.QuotaSubtree, opts metav1.UpdateOptions) (result *v1.QuotaSubtree, err error) { + result = &v1.QuotaSubtree{} + err = c.client.Put(). + Namespace(c.ns). + Resource("quotasubtrees"). + Name(quotaSubtree.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(quotaSubtree). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *quotaSubtrees) UpdateStatus(ctx context.Context, quotaSubtree *v1.QuotaSubtree, opts metav1.UpdateOptions) (result *v1.QuotaSubtree, err error) { + result = &v1.QuotaSubtree{} + err = c.client.Put(). + Namespace(c.ns). + Resource("quotasubtrees"). + Name(quotaSubtree.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(quotaSubtree). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the quotaSubtree and deletes it. Returns an error if one occurs. +func (c *quotaSubtrees) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("quotasubtrees"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *quotaSubtrees) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("quotasubtrees"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched quotaSubtree. +func (c *quotaSubtrees) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.QuotaSubtree, err error) { + result = &v1.QuotaSubtree{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("quotasubtrees"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/quotasubtree_client.go b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/quotasubtree_client.go new file mode 100755 index 000000000..626d1d16d --- /dev/null +++ b/pkg/client/quotasubtree/clientset/versioned/typed/quotasubtree/v1/quotasubtree_client.go @@ -0,0 +1,89 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1 + +import ( + rest "k8s.io/client-go/rest" + v1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/quotaplugins/quotasubtree/v1" + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/clientset/versioned/scheme" +) + +type QuotaSubtreeV1Interface interface { + RESTClient() rest.Interface + QuotaSubtreesGetter +} + +// QuotasubtreeV1Client is used to interact with features provided by the quotasubtree group. +type QuotasubtreeV1Client struct { + restClient rest.Interface +} + +func (c *QuotasubtreeV1Client) QuotaSubtrees(namespace string) QuotaSubtreeInterface { + return newQuotaSubtrees(c, namespace) +} + +// NewForConfig creates a new QuotasubtreeV1Client for the given config. +func NewForConfig(c *rest.Config) (*QuotasubtreeV1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &QuotasubtreeV1Client{client}, nil +} + +// NewForConfigOrDie creates a new QuotasubtreeV1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) (*QuotasubtreeV1Client, error) { + client, err := NewForConfig(c) + if err != nil { + return nil, err + } + return client, err +} + +// New creates a new QuotasubtreeV1Client for the given RESTClient. +func New(c rest.Interface) *QuotasubtreeV1Client { + return &QuotasubtreeV1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *QuotasubtreeV1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/quotasubtree/informers/externalversions/factory.go b/pkg/client/quotasubtree/informers/externalversions/factory.go new file mode 100755 index 000000000..0923eba3b --- /dev/null +++ b/pkg/client/quotasubtree/informers/externalversions/factory.go @@ -0,0 +1,180 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + reflect "reflect" + sync "sync" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" + versioned "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/clientset/versioned" + internalinterfaces "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/informers/externalversions/internalinterfaces" + quotasubtree "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/informers/externalversions/quotasubtree" +) + +// SharedInformerOption defines the functional option type for SharedInformerFactory. +type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory + +type sharedInformerFactory struct { + client versioned.Interface + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc + lock sync.Mutex + defaultResync time.Duration + customResync map[reflect.Type]time.Duration + + informers map[reflect.Type]cache.SharedIndexInformer + // startedInformers is used for tracking which informers have been started. + // This allows Start() to be called multiple times safely. + startedInformers map[reflect.Type]bool +} + +// WithCustomResyncConfig sets a custom resync period for the specified informer types. +func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + for k, v := range resyncConfig { + factory.customResync[reflect.TypeOf(k)] = v + } + return factory + } +} + +// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. +func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.tweakListOptions = tweakListOptions + return factory + } +} + +// WithNamespace limits the SharedInformerFactory to the specified namespace. +func WithNamespace(namespace string) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.namespace = namespace + return factory + } +} + +// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. +func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync) +} + +// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. +// Listers obtained via this SharedInformerFactory will be subject to the same filters +// as specified here. +// Deprecated: Please use NewSharedInformerFactoryWithOptions instead +func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) +} + +// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. +func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { + factory := &sharedInformerFactory{ + client: client, + namespace: v1.NamespaceAll, + defaultResync: defaultResync, + informers: make(map[reflect.Type]cache.SharedIndexInformer), + startedInformers: make(map[reflect.Type]bool), + customResync: make(map[reflect.Type]time.Duration), + } + + // Apply all options + for _, opt := range options { + factory = opt(factory) + } + + return factory +} + +// Start initializes all requested informers. +func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.lock.Lock() + defer f.lock.Unlock() + + for informerType, informer := range f.informers { + if !f.startedInformers[informerType] { + go informer.Run(stopCh) + f.startedInformers[informerType] = true + } + } +} + +// WaitForCacheSync waits for all started informers' cache were synced. +func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + informers := func() map[reflect.Type]cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informers := map[reflect.Type]cache.SharedIndexInformer{} + for informerType, informer := range f.informers { + if f.startedInformers[informerType] { + informers[informerType] = informer + } + } + return informers + }() + + res := map[reflect.Type]bool{} + for informType, informer := range informers { + res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) + } + return res +} + +// InternalInformerFor returns the SharedIndexInformer for obj using an internal +// client. +func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informerType := reflect.TypeOf(obj) + informer, exists := f.informers[informerType] + if exists { + return informer + } + + resyncPeriod, exists := f.customResync[informerType] + if !exists { + resyncPeriod = f.defaultResync + } + + informer = newFunc(f.client, resyncPeriod) + f.informers[informerType] = informer + + return informer +} + +// SharedInformerFactory provides shared informers for resources in all known +// API group versions. +type SharedInformerFactory interface { + internalinterfaces.SharedInformerFactory + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + + Quotasubtree() quotasubtree.Interface +} + +func (f *sharedInformerFactory) Quotasubtree() quotasubtree.Interface { + return quotasubtree.New(f, f.namespace, f.tweakListOptions) +} diff --git a/pkg/client/quotasubtree/informers/externalversions/generic.go b/pkg/client/quotasubtree/informers/externalversions/generic.go new file mode 100755 index 000000000..6809177ce --- /dev/null +++ b/pkg/client/quotasubtree/informers/externalversions/generic.go @@ -0,0 +1,62 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + "fmt" + + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" + v1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/quotaplugins/quotasubtree/v1" +) + +// GenericInformer is type of SharedIndexInformer which will locate and delegate to other +// sharedInformers based on type +type GenericInformer interface { + Informer() cache.SharedIndexInformer + Lister() cache.GenericLister +} + +type genericInformer struct { + informer cache.SharedIndexInformer + resource schema.GroupResource +} + +// Informer returns the SharedIndexInformer. +func (f *genericInformer) Informer() cache.SharedIndexInformer { + return f.informer +} + +// Lister returns the GenericLister. +func (f *genericInformer) Lister() cache.GenericLister { + return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) +} + +// ForResource gives generic access to a shared informer of the matching type +// TODO extend this to unknown resources with a client pool +func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { + switch resource { + // Group=quotasubtree, Version=v1 + case v1.SchemeGroupVersion.WithResource("quotasubtrees"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Quotasubtree().V1().QuotaSubtrees().Informer()}, nil + + } + + return nil, fmt.Errorf("no informer found for %v", resource) +} diff --git a/pkg/client/quotasubtree/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/client/quotasubtree/informers/externalversions/internalinterfaces/factory_interfaces.go new file mode 100755 index 000000000..88a577ca8 --- /dev/null +++ b/pkg/client/quotasubtree/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -0,0 +1,40 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package internalinterfaces + +import ( + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + cache "k8s.io/client-go/tools/cache" + versioned "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/clientset/versioned" +) + +// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. +type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer + +// SharedInformerFactory a small interface to allow for adding an informer without an import cycle +type SharedInformerFactory interface { + Start(stopCh <-chan struct{}) + InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer +} + +// TweakListOptionsFunc is a function that transforms a v1.ListOptions. +type TweakListOptionsFunc func(*v1.ListOptions) diff --git a/pkg/client/quotasubtree/informers/externalversions/quotasubtree/interface.go b/pkg/client/quotasubtree/informers/externalversions/quotasubtree/interface.go new file mode 100755 index 000000000..02ca19aac --- /dev/null +++ b/pkg/client/quotasubtree/informers/externalversions/quotasubtree/interface.go @@ -0,0 +1,46 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package quotasubtree + +import ( + internalinterfaces "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/informers/externalversions/internalinterfaces" + v1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/informers/externalversions/quotasubtree/v1" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1 provides access to shared informers for resources in V1. + V1() v1.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1 returns a new v1.Interface. +func (g *group) V1() v1.Interface { + return v1.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/client/quotasubtree/informers/externalversions/quotasubtree/v1/interface.go b/pkg/client/quotasubtree/informers/externalversions/quotasubtree/v1/interface.go new file mode 100755 index 000000000..6fae598d6 --- /dev/null +++ b/pkg/client/quotasubtree/informers/externalversions/quotasubtree/v1/interface.go @@ -0,0 +1,45 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1 + +import ( + internalinterfaces "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // QuotaSubtrees returns a QuotaSubtreeInformer. + QuotaSubtrees() QuotaSubtreeInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// QuotaSubtrees returns a QuotaSubtreeInformer. +func (v *version) QuotaSubtrees() QuotaSubtreeInformer { + return "aSubtreeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/quotasubtree/informers/externalversions/quotasubtree/v1/quotasubtree.go b/pkg/client/quotasubtree/informers/externalversions/quotasubtree/v1/quotasubtree.go new file mode 100755 index 000000000..9712089f5 --- /dev/null +++ b/pkg/client/quotasubtree/informers/externalversions/quotasubtree/v1/quotasubtree.go @@ -0,0 +1,90 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1 + +import ( + "context" + time "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + quotasubtreev1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/quotaplugins/quotasubtree/v1" + versioned "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/clientset/versioned" + internalinterfaces "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/informers/externalversions/internalinterfaces" + v1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/listers/quotasubtree/v1" +) + +// QuotaSubtreeInformer provides access to a shared informer and lister for +// QuotaSubtrees. +type QuotaSubtreeInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1.QuotaSubtreeLister +} + +type quotaSubtreeInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewQuotaSubtreeInformer constructs a new informer for QuotaSubtree type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewQuotaSubtreeInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredQuotaSubtreeInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredQuotaSubtreeInformer constructs a new informer for QuotaSubtree type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredQuotaSubtreeInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.QuotasubtreeV1().QuotaSubtrees(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.QuotasubtreeV1().QuotaSubtrees(namespace).Watch(context.TODO(), options) + }, + }, + "asubtreev1.QuotaSubtree{}, + resyncPeriod, + indexers, + ) +} + +func (f *quotaSubtreeInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredQuotaSubtreeInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *quotaSubtreeInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor("asubtreev1.QuotaSubtree{}, f.defaultInformer) +} + +func (f *quotaSubtreeInformer) Lister() v1.QuotaSubtreeLister { + return v1.NewQuotaSubtreeLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/quotasubtree/listers/quotasubtree/v1/expansion_generated.go b/pkg/client/quotasubtree/listers/quotasubtree/v1/expansion_generated.go new file mode 100755 index 000000000..7e71ef385 --- /dev/null +++ b/pkg/client/quotasubtree/listers/quotasubtree/v1/expansion_generated.go @@ -0,0 +1,27 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1 + +// QuotaSubtreeListerExpansion allows custom methods to be added to +// QuotaSubtreeLister. +type QuotaSubtreeListerExpansion interface{} + +// QuotaSubtreeNamespaceListerExpansion allows custom methods to be added to +// QuotaSubtreeNamespaceLister. +type QuotaSubtreeNamespaceListerExpansion interface{} diff --git a/pkg/client/quotasubtree/listers/quotasubtree/v1/quotasubtree.go b/pkg/client/quotasubtree/listers/quotasubtree/v1/quotasubtree.go new file mode 100755 index 000000000..227d6adca --- /dev/null +++ b/pkg/client/quotasubtree/listers/quotasubtree/v1/quotasubtree.go @@ -0,0 +1,99 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/quotaplugins/quotasubtree/v1" +) + +// QuotaSubtreeLister helps list QuotaSubtrees. +// All objects returned here must be treated as read-only. +type QuotaSubtreeLister interface { + // List lists all QuotaSubtrees in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1.QuotaSubtree, err error) + // QuotaSubtrees returns an object that can list and get QuotaSubtrees. + QuotaSubtrees(namespace string) QuotaSubtreeNamespaceLister + QuotaSubtreeListerExpansion +} + +// quotaSubtreeLister implements the QuotaSubtreeLister interface. +type quotaSubtreeLister struct { + indexer cache.Indexer +} + +// NewQuotaSubtreeLister returns a new QuotaSubtreeLister. +func NewQuotaSubtreeLister(indexer cache.Indexer) QuotaSubtreeLister { + return "aSubtreeLister{indexer: indexer} +} + +// List lists all QuotaSubtrees in the indexer. +func (s *quotaSubtreeLister) List(selector labels.Selector) (ret []*v1.QuotaSubtree, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1.QuotaSubtree)) + }) + return ret, err +} + +// QuotaSubtrees returns an object that can list and get QuotaSubtrees. +func (s *quotaSubtreeLister) QuotaSubtrees(namespace string) QuotaSubtreeNamespaceLister { + return quotaSubtreeNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// QuotaSubtreeNamespaceLister helps list and get QuotaSubtrees. +// All objects returned here must be treated as read-only. +type QuotaSubtreeNamespaceLister interface { + // List lists all QuotaSubtrees in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1.QuotaSubtree, err error) + // Get retrieves the QuotaSubtree from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1.QuotaSubtree, error) + QuotaSubtreeNamespaceListerExpansion +} + +// quotaSubtreeNamespaceLister implements the QuotaSubtreeNamespaceLister +// interface. +type quotaSubtreeNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all QuotaSubtrees in the indexer for a given namespace. +func (s quotaSubtreeNamespaceLister) List(selector labels.Selector) (ret []*v1.QuotaSubtree, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1.QuotaSubtree)) + }) + return ret, err +} + +// Get retrieves the QuotaSubtree from the indexer for a given namespace and name. +func (s quotaSubtreeNamespaceLister) Get(name string) (*v1.QuotaSubtree, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1.Resource("quotasubtree"), name) + } + return obj.(*v1.QuotaSubtree), nil +} diff --git a/pkg/controller/queuejob/queuejob_controller_ex.go b/pkg/controller/queuejob/queuejob_controller_ex.go index d9399656e..7501edd0b 100644 --- a/pkg/controller/queuejob/queuejob_controller_ex.go +++ b/pkg/controller/queuejob/queuejob_controller_ex.go @@ -32,6 +32,7 @@ package queuejob import ( "fmt" + qmutils "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/util" "math" "math/rand" "reflect" @@ -40,13 +41,12 @@ import ( "strings" "time" - "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota/quotamanager" + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota/quotaforestmanager" dto "github.com/prometheus/client_model/go" "github.com/project-codeflare/multi-cluster-app-dispatcher/cmd/kar-controllers/app/options" "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/metrics/adapter" "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota" - qmutils "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota/quotamanager/util" "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -384,7 +384,7 @@ func NewJobController(config *rest.Config, serverOption *options.ServerOption) * // Setup Quota if serverOption.QuotaEnabled { dispatchedAWDemands, dispatchedAWs := cc.getDispatchedAppWrappers() - cc.quotaManager, _ = quotamanager.NewQuotaManager(dispatchedAWDemands, dispatchedAWs, cc.queueJobLister, + cc.quotaManager, _ = quotaforestmanager.NewQuotaManager(dispatchedAWDemands, dispatchedAWs, cc.queueJobLister, config, serverOption) } else { cc.quotaManager = nil diff --git a/pkg/controller/quota/quota_manager_interface.go b/pkg/controller/quota/quota_manager_interface.go index 80224482c..1f6f51893 100644 --- a/pkg/controller/quota/quota_manager_interface.go +++ b/pkg/controller/quota/quota_manager_interface.go @@ -1,5 +1,5 @@ // ------------------------------------------------------ {COPYRIGHT-TOP} --- -// Copyright 2022 The Multi-Cluster App Dispatcher Authors. +// Copyright 2019, 2021, 2022, 2023 The Multi-Cluster App Dispatcher Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr.go b/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr.go similarity index 95% rename from pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr.go rename to pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr.go index 2a4e30f84..a3b483d00 100644 --- a/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr.go +++ b/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr.go @@ -1,6 +1,6 @@ -// +build private +// b private // ------------------------------------------------------ {COPYRIGHT-TOP} --- -// Copyright 2022 The Multi-Cluster App Dispatcher Authors. +// Copyright 2019, 2021, 2022, 2023 The Multi-Cluster App Dispatcher Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ // limitations under the License. // ------------------------------------------------------ {COPYRIGHT-END} --- -package quotamanager +package quotaforestmanager import ( "bytes" @@ -25,10 +25,10 @@ import ( listersv1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/listers/controller/v1" clusterstateapi "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/clusterstate/api" "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota" - rpmanager "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr" - "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota/quotamanager/util" - qmbackend "github.ibm.com/ai-foundation/quota-manager/quota" - qmbackendutils "github.ibm.com/ai-foundation/quota-manager/quota/utils" + qstmanager "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr" + qmbackend "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota" + qmbackendutils "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/utils" + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/util" "k8s.io/client-go/rest" "strings" @@ -57,7 +57,7 @@ type QuotaManager struct { appwrapperLister listersv1.AppWrapperLister preemptionEnabled bool quotaManagerBackend *qmbackend.Manager - resourcePlanManager *rpmanager.ResourcePlanManager + quotaSubtreeManager *qstmanager.QuotaSubtreeManager initializationDone bool } @@ -124,10 +124,10 @@ func NewQuotaManager(dispatchedAWDemands map[string]*clusterstateapi.Resource, d // Set the name of the forest in the backend qm.quotaManagerBackend.AddForest(QuotaManagerForestName) - klog.V(10).Infof("[NewQuotaManager] Before initialization ResourcePlan informer - %s", qm.quotaManagerBackend.String()) + klog.V(10).Infof("[NewQuotaManager] Before initialization QuotaSubtree informer - %s", qm.quotaManagerBackend.String()) // Create a resource plan manager - qm.resourcePlanManager, _ = rpmanager.NewResourcePlanManager(config, qm.quotaManagerBackend) + qm.quotaSubtreeManager, _ = qstmanager.NewQuotaSubtreeManager(config, qm.quotaManagerBackend) // Initialize Forest/Trees if new resource plan manager added to the cache err := qm.updateForestFromCache() @@ -529,10 +529,10 @@ func (qm *QuotaManager) Fits(aw *arbv1.AppWrapper, awResDemands *clusterstateapi return doesFit, nil, "Quota Manager backend in maintenance mode" } - // Refresh Quota Manager Backend Cache and Tree(s) if detected change in ResourcePlans - if qm.resourcePlanManager.IsResplanChanged() { - // Load ResourcePlan Cache into Quoto Management Backend Cache - qm.resourcePlanManager.LoadResourcePlansIntoBackend() + // Refresh Quota Manager Backend Cache and Tree(s) if detected change in QuotaSubtrees + if qm.quotaSubtreeManager.IsQuotasubtreeChanged() { + // Load QuotaSubtree Cache into Quoto Management Backend Cache + qm.quotaSubtreeManager.LoadQuotaSubtreesIntoBackend() // Realize new Quoto Management tree(s) from Backend Cache err := qm.updateForestFromCache() if err != nil { diff --git a/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/event_handlers.go b/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/event_handlers.go new file mode 100644 index 000000000..2b7836225 --- /dev/null +++ b/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/event_handlers.go @@ -0,0 +1,83 @@ +// b private +// ------------------------------------------------------ {COPYRIGHT-TOP} --- +// Copyright 2019, 2021, 2022, 2023 The Multi-Cluster App Dispatcher Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------ {COPYRIGHT-END} --- + +package quotasubtmgr + +import ( + qstv1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/quotaplugins/quotasubtree/v1" + "k8s.io/klog/v2" +) + +func (qstm *QuotaSubtreeManager) addQST(obj interface{}) { + qst, ok := obj.(*qstv1.QuotaSubtree) + if !ok { + return + } + + qstm.qstMutex.Lock() + qstm.qstMap[string(qst.UID)] = qst + qstm.qstMap[qst.Namespace+"/"+qst.Name] = qst + qstm.setQuotasubtreeChanged() + qstm.qstMutex.Unlock() + klog.V(10).Infof("[addQST] Add complete for: %s/%s", qst.Name, qst.Namespace) +} + +func (qstm *QuotaSubtreeManager) updateQST(oldObj, newObj interface{}) { + oldQST, ok := oldObj.(*qstv1.QuotaSubtree) + if !ok { + return + } + + newQST, ok := newObj.(*qstv1.QuotaSubtree) + if !ok { + return + } + + qstm.qstMutex.Lock() + delete(qstm.qstMap, string(oldQST.UID)) + delete(qstm.qstMap, oldQST.Namespace+"/"+oldQST.Name) + qstm.qstMap[string(newQST.UID)] = newQST + qstm.qstMap[newQST.Namespace+"/"+newQST.Name] = newQST + notify := false + // status change (updating running/pending pods) will not update the Generation, + // with this logic, we only need to handle necessary update. + if oldQST.ObjectMeta.Generation != newQST.ObjectMeta.Generation { + notify = true + } + qstm.qstMutex.Unlock() + + if notify { + qstm.mutex.Lock() + qstm.setQuotasubtreeChanged() + qstm.mutex.Unlock() + } + klog.V(10).Infof("[updateQST] Update complete for: %s/%s", newQST.Name, newQST.Namespace) +} + +func (qstm *QuotaSubtreeManager) deleteQST(obj interface{}) { + qst, ok := obj.(*qstv1.QuotaSubtree) + if !ok { + return + } + + qstm.qstMutex.Lock() + defer qstm.qstMutex.Unlock() + + delete(qstm.qstMap, string(qst.UID)) + delete(qstm.qstMap, qst.Namespace+"/"+qst.Name) + klog.V(10).Infof("[deleteQST] Delete complete for: %s/%s", qst.Name, qst.Namespace) +} diff --git a/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/quota_subtree_manager.go b/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/quota_subtree_manager.go new file mode 100644 index 000000000..95817e8a7 --- /dev/null +++ b/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/quota_subtree_manager.go @@ -0,0 +1,295 @@ +// b private +// ------------------------------------------------------ {COPYRIGHT-TOP} --- +// Copyright 2019, 2021, 2022, 2023 The Multi-Cluster App Dispatcher Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------ {COPYRIGHT-END} --- + +package quotasubtmgr + +import ( + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/core" + "k8s.io/klog/v2" + "strconv" + "strings" + "sync" + + qstv1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/quotaplugins/quotasubtree/v1" + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/util" + qmlib "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota" + qmlibutils "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + + qst "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/clientset/versioned" + "k8s.io/client-go/rest" + + qstinformer "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/informers/externalversions" + qstinformers "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/quotasubtree/informers/externalversions/quotasubtree/v1" +) + + +// New returns a new implementation. +func NewQuotaSubtreeManager(config *rest.Config, quotaManagerBackend *qmlib.Manager) (*QuotaSubtreeManager, error) { + return newQuotaSubtreeManager(config, quotaManagerBackend) +} + +type QuotaSubtreeManager struct { + mutex sync.Mutex + + kubeclient *kubernetes.Clientset + + beMutex sync.Mutex + quotaManagerBackend *qmlib.Manager + + /* Information about Quota Subtrees */ + quotaSubtreeInformer qstinformers.QuotaSubtreeInformer + qstMutex sync.Mutex + qstMap map[string]*qstv1.QuotaSubtree + + qstChanged bool + qstSynced func() bool +} + +func newQuotaSubtreeManager(config *rest.Config, quotaManagerBackend *qmlib.Manager) (*QuotaSubtreeManager, error) { + qstm := &QuotaSubtreeManager{ + quotaManagerBackend: quotaManagerBackend, + qstMap: make(map[string]*qstv1.QuotaSubtree), + } + // QuotaSubtree informer setup + qstClient, err := qst.NewForConfigOrDie(config) + if err != nil { + return nil, err + } + + qstInformerFactory := qstinformer.NewSharedInformerFactoryWithOptions(qstClient, 0, + qstinformer.WithTweakListOptions(func(opt *metav1.ListOptions) { + opt.LabelSelector = util.URMTreeLabel + })) + qstm.quotaSubtreeInformer = qstInformerFactory.Quotasubtree().V1().QuotaSubtrees() + + + // Add event handle for resource plans + qstm.quotaSubtreeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: qstm.addQST, + UpdateFunc: qstm.updateQST, + DeleteFunc: qstm.deleteQST, + }) + + // Start resource plan informers + neverStop := make(chan struct{}) + klog.V(10).Infof("[newQuotaSubtreeManager] Starting QuotaSubtree Informer.") + go qstm.quotaSubtreeInformer.Informer().Run(neverStop) + + // Wait for cache sync + klog.V(10).Infof("[newQuotaSubtreeManager] Waiting for QuotaSubtree informer cache sync. to complete.") + qstm.qstSynced = qstm.quotaSubtreeInformer.Informer().HasSynced + cache.WaitForCacheSync(neverStop, qstm.qstSynced) + + // Initialize Quota Trees + qstm.initializeQuotaTreeBackend() + klog.V(10).Infof("[newQuotaSubtreeManager] QuotaSubtree Manager initialization complete.") + return qstm, nil +} + +func appendIfNotPresent(candidate string, source []string) []string { + if source == nil { + var retval []string + return retval + } + + if len(candidate) <= 0 { + return source + } + + for _, val := range source { + if strings.Compare(val, candidate) == 0 { + return source + } + } + + return append(source, candidate) +} + +func (qstm *QuotaSubtreeManager) setQuotasubtreeChanged() { + qstm.qstChanged = true +} + +func (qstm *QuotaSubtreeManager) clearQuotasubtreeChanged() { + qstm.qstChanged = false +} + +func (qstm *QuotaSubtreeManager) IsQuotasubtreeChanged() bool { + return qstm.qstChanged +} + +func (qstm *QuotaSubtreeManager) createTreeNodesFromQST(qst *qstv1.QuotaSubtree) (map[string]*qmlibutils.JNodeSpec, []string) { + nodeSpecs := make(map[string]*qmlibutils.JNodeSpec) + var resourceTypes []string + + for _, qstChild := range qst.Spec.Children { + // Generate node key + child_key := qstChild.Name + + // Get the quota demands + quota := make(map[string]string) + reqs := qstChild.Quotas.Requests + for k, v := range reqs { + resourceName := string(k) + if len(resourceName) <= 0 { + klog.Errorf("[createTreeNodesFromQST] Resource Name can not be empty, QuotaSubtree %s request quota: %v will be ignored.", + qst.Name, v) + continue + } + resourceTypes = appendIfNotPresent(resourceName, resourceTypes) + amount, success := v.AsInt64() + if !success { + klog.Errorf("[createTreeNodesFromQST] Failure converting QuotaSubtree request demand quota to int64, QuotaSubtree %s request quota: %v will be ignored.", + qst.Name, v) + continue + } + + // Add new quota demand + quota[resourceName] = strconv.FormatInt(amount, 10) + } + + // Build a node + node := &qmlibutils.JNodeSpec{ + Parent: qst.Spec.Parent, + Quota: quota, + Hard: strconv.FormatBool(qstChild.Quotas.HardLimit), + } + klog.V(10).Infof("[createTreeNodesFromQST] Created node: %s=%#v for QuotaSubtree %s completed.", + child_key, *node, qst.Name) + + //Add to the list of nodes from this quotasubtree + nodeSpecs[child_key] = node + } + + return nodeSpecs, resourceTypes +} + +func (qstm *QuotaSubtreeManager) addQuotaSubtreesIntoBackend(qst *qstv1.QuotaSubtree, treeCache *core.TreeCache) { + treeNodes, resourceTypes := qstm.createTreeNodesFromQST(qst) + for childKey, nodeInfo := range treeNodes { + treeCache.AddNodeSpec(childKey, *nodeInfo) + } + klog.V(4).Infof("[addQuotaSubtreesIntoBackend] Processing QuotaSubtree %s completed.", + qst.Name) + + // Add all the resource names found in the QuotaSubtree to the tree cache + for _, resourceName := range resourceTypes { + treeCache.AddResourceName(resourceName) + } +} + +func (qstm *QuotaSubtreeManager) createTreeCache(forestName string, qst *qstv1.QuotaSubtree) *core.TreeCache { + // Create new tree in backend + qstTreeName := qst.Labels[util.URMTreeLabel] + _, err := qstm.quotaManagerBackend.AddTreeByName(qstTreeName) + if err != nil { + klog.Errorf("[createTreeCache] Failure adding tree name %s to quota tree backend err=%#v. QuotaSubtree %s will be ignored.", + qstTreeName, err, qst.Name) + return nil + } + + // Add new tree to forest in backend + err = qstm.quotaManagerBackend.AddTreeToForest(forestName, qstTreeName) + + if err != nil { + klog.Errorf("[createTreeCache] Failure adding tree name %s to forest %s in quota tree backend failed err=%#v, QuotaSubtree %s will be ignored.", + qstTreeName, forestName, err, qst.Name) + return nil + } + + // Add new tree to local cache + return qstm.quotaManagerBackend.GetTreeCache(qstTreeName) +} + +func (qstm *QuotaSubtreeManager) LoadQuotaSubtreesIntoBackend() { + + qstm.qstMutex.Lock() + defer qstm.qstMutex.Unlock() + + // Get the list of forests names (should be only one for MCAD + forestNames := qstm.quotaManagerBackend.GetForestNames() + if len(forestNames) != 1 { + klog.Errorf("[LoadQuotaSubtreesIntoBackend] QuotaSubtree initialization requires only one forest to be defined in quota tree backend, found %v defined.", len(forestNames)) + return + } + + forestName := forestNames[0] + + // Get the list of trees names in the forest + treeNames := qstm.quotaManagerBackend.GetTreeNames() + + // Function cache map of Tree Name to Tree Cache + treeNameToTreeCache := make(map[string]*core.TreeCache) + for _, treeName := range treeNames { + treeNameToTreeCache[treeName] = qstm.quotaManagerBackend.GetTreeCache(treeName) + } + + // Process all quotasubtrees to the tree caches + for _, qst := range qstm.qstMap { + klog.V(4).Infof("[LoadQuotaSubtreesIntoBackend] Processing QuotaSubtree %s.", + qst.Name) + qstTreeName := qst.Labels[util.URMTreeLabel] + if len(qstTreeName) <= 0 { + klog.Errorf("[LoadQuotaSubtreesIntoBackend] QuotaSubtree %s does not contain the proper 'tree' label will be ignored.", + qst.Name) + continue + } + + treeCache := treeNameToTreeCache[qstTreeName] + + // Handle new tree + if treeCache == nil { + // Add new tree to function cache + treeNameToTreeCache[qstTreeName] = qstm.createTreeCache(forestName, qst) + + // Validate cache exists in backend + treeCache = treeNameToTreeCache[qstTreeName] + if treeCache == nil { + klog.Errorf("[LoadQuotaSubtreesIntoBackend] Tree cache not found for tree: %s. QuotaSubtree %s will be ignored.", + qstTreeName, qst.Name) + continue + } + treeNames = append(treeNames, qstTreeName) + } + + // Add quotasubtree to quota tree backend + qstm.addQuotaSubtreesIntoBackend(qst, treeCache) + } + + for _, treeName := range treeNames { + klog.V(10).Infof("[LoadQuotaSubtreesIntoBackend] Processing Quota Manager Backend tree %s completed.", treeName) + } + + qstm.clearQuotasubtreeChanged() + +} + +func (qstm *QuotaSubtreeManager) initializeQuotaTreeBackend() { + if !qstm.IsQuotasubtreeChanged() { + klog.V(4).Infof("[initializeQuotaTreeBackend] No QuotaSubtrees to process.") + return + } + + if qstm.quotaManagerBackend.GetMode() != qmlib.Maintenance { + klog.Warningf("[initializeQuotaTreeBackend] Forcing Quota Manager into maintenance mode.") + qstm.quotaManagerBackend.SetMode(qmlib.Maintenance) + } + + qstm.LoadQuotaSubtreesIntoBackend() +} diff --git a/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/util/constants.go b/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/util/constants.go similarity index 91% rename from pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/util/constants.go rename to pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/util/constants.go index bc3f803b0..44a0d12ed 100644 --- a/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/util/constants.go +++ b/pkg/controller/quota/quotaforestmanager/qm_lib_backend_with_quotasubt_mgr/quotasubtmgr/util/constants.go @@ -1,5 +1,5 @@ // ------------------------------------------------------ {COPYRIGHT-TOP} --- -// Copyright 2022 The Multi-Cluster App Dispatcher Authors. +// Copyright 2019, 2021, 2022, 2023 The Multi-Cluster App Dispatcher Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/controller/quota/quotamanager/util/utils.go b/pkg/controller/quota/quotaforestmanager/util/utils.go similarity index 100% rename from pkg/controller/quota/quotamanager/util/utils.go rename to pkg/controller/quota/quotaforestmanager/util/utils.go diff --git a/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/event_handlers.go b/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/event_handlers.go deleted file mode 100644 index 79d926936..000000000 --- a/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/event_handlers.go +++ /dev/null @@ -1,84 +0,0 @@ -// +build private -// ------------------------------------------------------ {COPYRIGHT-TOP} --- -// Copyright 2022 The Multi-Cluster App Dispatcher Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ------------------------------------------------------ {COPYRIGHT-END} --- - -package resplanmgr - -import ( - "k8s.io/klog/v2" - rpv1 "sigs.k8s.io/scheduler-plugins/pkg/apis/resourceplan/v1" - //rpv1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/resourceplan/v1" -) - -func (rpm *ResourcePlanManager) addRP(obj interface{}) { - rp, ok := obj.(*rpv1.ResourcePlan) - if !ok { - return - } - - rpm.rpMutex.Lock() - rpm.rpMap[string(rp.UID)] = rp - rpm.rpMap[rp.Namespace+"/"+rp.Name] = rp - rpm.setResplanChanged() - rpm.rpMutex.Unlock() - klog.V(10).Infof("[addRP] Add complete for: %s/%s", rp.Name, rp.Namespace) -} - -func (rpm *ResourcePlanManager) updateRP(oldObj, newObj interface{}) { - oldRP, ok := oldObj.(*rpv1.ResourcePlan) - if !ok { - return - } - - newRP, ok := newObj.(*rpv1.ResourcePlan) - if !ok { - return - } - - rpm.rpMutex.Lock() - delete(rpm.rpMap, string(oldRP.UID)) - delete(rpm.rpMap, oldRP.Namespace+"/"+oldRP.Name) - rpm.rpMap[string(newRP.UID)] = newRP - rpm.rpMap[newRP.Namespace+"/"+newRP.Name] = newRP - notify := false - // status change (updating running/pending pods) will not update the Generation, - // with this logic, we only need to handle necessary update. - if oldRP.ObjectMeta.Generation != newRP.ObjectMeta.Generation { - notify = true - } - rpm.rpMutex.Unlock() - - if notify { - rpm.mutex.Lock() - rpm.setResplanChanged() - rpm.mutex.Unlock() - } - klog.V(10).Infof("[updateRP] Update complete for: %s/%s", newRP.Name, newRP.Namespace) -} - -func (rpm *ResourcePlanManager) deleteRP(obj interface{}) { - rp, ok := obj.(*rpv1.ResourcePlan) - if !ok { - return - } - - rpm.rpMutex.Lock() - defer rpm.rpMutex.Unlock() - - delete(rpm.rpMap, string(rp.UID)) - delete(rpm.rpMap, rp.Namespace+"/"+rp.Name) - klog.V(10).Infof("[deleteRP] Delete complete for: %s/%s", rp.Name, rp.Namespace) -} diff --git a/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/resource_plan_manager.go b/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/resource_plan_manager.go deleted file mode 100644 index a9cbb3d6a..000000000 --- a/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/resource_plan_manager.go +++ /dev/null @@ -1,295 +0,0 @@ -// +build private -// ------------------------------------------------------ {COPYRIGHT-TOP} --- -// Copyright 2022 The Multi-Cluster App Dispatcher Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ------------------------------------------------------ {COPYRIGHT-END} --- - -package resplanmgr - -import ( - "github.ibm.com/ai-foundation/quota-manager/quota/core" - "k8s.io/klog/v2" - "strconv" - "strings" - "sync" - - "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota/quotamanager/qm_lib_backend_with_resplan_mgr/resplanmgr/util" - qmlib "github.ibm.com/ai-foundation/quota-manager/quota" - qmlibutils "github.ibm.com/ai-foundation/quota-manager/quota/utils" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" - rpv1 "sigs.k8s.io/scheduler-plugins/pkg/apis/resourceplan/v1" - - "k8s.io/client-go/rest" - rp "sigs.k8s.io/scheduler-plugins/pkg/client/resourceplan/clientset/versioned" - - rpinformer "sigs.k8s.io/scheduler-plugins/pkg/client/resourceplan/informers/externalversions" - rpinformers "sigs.k8s.io/scheduler-plugins/pkg/client/resourceplan/informers/externalversions/resourceplan/v1" -) - - -// New returns a new implementation. -func NewResourcePlanManager(config *rest.Config, quotaManagerBackend *qmlib.Manager) (*ResourcePlanManager, error) { - return newResourcePlanManager(config, quotaManagerBackend) -} - -type ResourcePlanManager struct { - mutex sync.Mutex - - kubeclient *kubernetes.Clientset - - - beMutex sync.Mutex - quotaManagerBackend *qmlib.Manager - - /* Information about Resource Plans */ - resourcePlanInformer rpinformers.ResourcePlanInformer - rpMutex sync.Mutex - rpMap map[string]*rpv1.ResourcePlan - - rpChanged bool - rpSynced func() bool -} - -func newResourcePlanManager(config *rest.Config, quotaManagerBackend *qmlib.Manager) (*ResourcePlanManager, error) { - rpm := &ResourcePlanManager{ - quotaManagerBackend: quotaManagerBackend, - rpMap: make(map[string]*rpv1.ResourcePlan), - } - // ResourcePlan informer setup - rpClient := rp.NewForConfigOrDie(config) - - rpInformerFactory := rpinformer.NewSharedInformerFactoryWithOptions(rpClient, 0, - rpinformer.WithTweakListOptions(func(opt *metav1.ListOptions) { - opt.LabelSelector = util.URMTreeLabel - })) - rpm.resourcePlanInformer = rpInformerFactory.Resourceplan().V1().ResourcePlans() - - - // Add event handle for resource plans - rpm.resourcePlanInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: rpm.addRP, - UpdateFunc: rpm.updateRP, - DeleteFunc: rpm.deleteRP, - }) - - // Start resource plan informers - neverStop := make(chan struct{}) - klog.V(10).Infof("[newResourcePlanManager] Starting ResourcePlan Informer.") - go rpm.resourcePlanInformer.Informer().Run(neverStop) - - // Wait for cache sync - klog.V(10).Infof("[newResourcePlanManager] Waiting for ResourcePlan informer cache sync. to complete.") - rpm.rpSynced = rpm.resourcePlanInformer.Informer().HasSynced - cache.WaitForCacheSync(neverStop, rpm.rpSynced) - - // Initialize Quota Trees - rpm.initializeQuotaTreeBackend() - klog.V(10).Infof("[newResourcePlanManager] ResourcePlan Manager initialization complete.") - return rpm, nil -} - -func appendIfNotPresent(candidate string, source []string) []string { - if source == nil { - var retval []string - return retval - } - - if len(candidate) <= 0 { - return source - } - - for _, val := range source { - if strings.Compare(val, candidate) == 0 { - return source - } - } - - return append(source, candidate) -} - -func (rpm *ResourcePlanManager) setResplanChanged() { - rpm.rpChanged = true -} - -func (rpm *ResourcePlanManager) clearResplanChanged() { - rpm.rpChanged = false -} - -func (rpm *ResourcePlanManager) IsResplanChanged() bool { - return rpm.rpChanged -} - - - -func (rpm *ResourcePlanManager) createTreeNodesFromRP(rp *rpv1.ResourcePlan) (map[string]*qmlibutils.JNodeSpec, []string) { - nodeSpecs := make(map[string]*qmlibutils.JNodeSpec) - var resourceTypes []string - - for _, rpChild := range rp.Spec.Children { - // Generate node key - child_key := rpChild.Name - - // Get the quota demands - quota := make(map[string]string) - reqs := rpChild.RunPodQuotas.Requests - for k, v := range reqs { - resourceName := string(k) - if len(resourceName) <= 0 { - klog.Errorf("[createTreeNodesFromRP] Resource Name can not be empty, ResourcePlan %s request quota: %v will be ignored.", - rp.Name, v) - continue - } - resourceTypes = appendIfNotPresent(resourceName, resourceTypes) - amount, success := v.AsInt64() - if !success { - klog.Errorf("[createTreeNodesFromRP] Failure converting ResourcePlan request demand quota to int64, ResourcePlan %s request quota: %v will be ignored.", - rp.Name, v) - continue - } - - // Add new quota demand - quota[resourceName] = strconv.FormatInt(amount, 10) - } - - // Build a node - node := &qmlibutils.JNodeSpec{ - Parent: rp.Spec.Parent, - Quota: quota, - Hard: strconv.FormatBool(rpChild.RunPodQuotas.HardLimit), - } - klog.V(10).Infof("[createTreeNodesFromRP] Created node: %s=%#v for ResourcePlan %s completed.", - child_key, *node, rp.Name) - - //Add to the list of nodes from this resourceplan - nodeSpecs[child_key] = node - } - - return nodeSpecs, resourceTypes -} - -func (rpm *ResourcePlanManager) addResourcePlansIntoBackend(rp *rpv1.ResourcePlan, treeCache *core.TreeCache) { - treeNodes, resourceTypes := rpm.createTreeNodesFromRP(rp) - for childKey, nodeInfo := range treeNodes { - treeCache.AddNodeSpec(childKey, *nodeInfo) - } - klog.V(4).Infof("[addResourcePlansIntoBackend] Processing ResourcePlan %s completed.", - rp.Name) - - // Add all the resource names found in the ResourcePlan to the tree cache - for _, resourceName := range resourceTypes { - treeCache.AddResourceName(resourceName) - } -} - -func (rpm *ResourcePlanManager) createTreeCache(forestName string, rp *rpv1.ResourcePlan) *core.TreeCache { - // Create new tree in backend - rpTreeName := rp.Labels[util.URMTreeLabel] - _, err := rpm.quotaManagerBackend.AddTreeByName(rpTreeName) - if err != nil { - klog.Errorf("[LoadResourcePlansIntoBackend] Failure adding tree name %s to quota tree backend err=%#v. ResourcePlan %s will be ignored.", - rpTreeName, err, rp.Name) - return nil - } - - // Add new tree to forest in backend - err = rpm.quotaManagerBackend.AddTreeToForest(forestName, rpTreeName) - - if err != nil { - klog.Errorf("[LoadResourcePlansIntoBackend] Failure adding tree name %s to forest %s in quota tree backend failed err=%#v, ResourcePlan %s will be ignored.", - rpTreeName, forestName, err, rp.Name) - return nil - } - - // Add new tree to local cache - return rpm.quotaManagerBackend.GetTreeCache(rpTreeName) -} - -func (rpm *ResourcePlanManager) LoadResourcePlansIntoBackend() { - - rpm.rpMutex.Lock() - defer rpm.rpMutex.Unlock() - - // Get the list of forests names (should be only one for MCAD - forestNames := rpm.quotaManagerBackend.GetForestNames() - if len(forestNames) != 1 { - klog.Errorf("[LoadResourcePlansIntoBackend] ResourcePlan initialization requires only one forest to be defined in quota tree backend, found %v defined.", len(forestNames)) - return - } - - forestName := forestNames[0] - - // Get the list of trees names in the forest - treeNames := rpm.quotaManagerBackend.GetTreeNames() - - // Function cache map of Tree Name to Tree Cache - treeNameToTreeCache := make(map[string]*core.TreeCache) - for _, treeName := range treeNames { - treeNameToTreeCache[treeName] = rpm.quotaManagerBackend.GetTreeCache(treeName) - } - - // Process all resourceplans to the tree caches - for _, rp := range rpm.rpMap { - klog.V(4).Infof("[LoadResourcePlansIntoBackend] Processing ResourcePlan %s.", - rp.Name) - rpTreeName := rp.Labels[util.URMTreeLabel] - if len(rpTreeName) <= 0 { - klog.Errorf("[LoadResourcePlansIntoBackend] ResourcePlan %s does not contain the proper 'tree' label will be ignored.", - rp.Name) - continue - } - - treeCache := treeNameToTreeCache[rpTreeName] - - // Handle new tree - if treeCache == nil { - // Add new tree to function cache - treeNameToTreeCache[rpTreeName] = rpm.createTreeCache(forestName, rp) - - // Validate cache exists in backend - treeCache = treeNameToTreeCache[rpTreeName] - if treeCache == nil { - klog.Errorf("[LoadResourcePlansIntoBackend] Tree cache not found for tree: %s. ResourcePlan %s will be ignored.", - rpTreeName, rp.Name) - continue - } - treeNames = append(treeNames, rpTreeName) - } - - // Add resourceplan to quota tree backend - rpm.addResourcePlansIntoBackend(rp, treeCache) - } - - for _, treeName := range treeNames { - klog.V(10).Infof("[LoadResourcePlansIntoBackend] Processing Quota Manager Backend tree %s completed.", treeName) - } - - rpm.clearResplanChanged() - -} - -func (rpm *ResourcePlanManager) initializeQuotaTreeBackend() { - if !rpm.IsResplanChanged() { - klog.V(4).Infof("[initializeQuotaTreeBackend] No ResourcePlans to process.") - return - } - - if rpm.quotaManagerBackend.GetMode() != qmlib.Maintenance { - klog.Warningf("[initializeQuotaTreeBackend] Forcing Quota Manager into maintenance mode.") - rpm.quotaManagerBackend.SetMode(qmlib.Maintenance) - } - - rpm.LoadResourcePlansIntoBackend() -} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/.gitignore b/pkg/quotaplugins/quota-forest/quota-manager/.gitignore new file mode 100644 index 000000000..33bcbe688 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/.gitignore @@ -0,0 +1,21 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +vendor/ + +# Miscellaneous +.DS_Store +temp/ +out.txt +*.log diff --git a/pkg/quotaplugins/quota-forest/quota-manager/README.md b/pkg/quotaplugins/quota-forest/quota-manager/README.md new file mode 100644 index 000000000..e9f03ae73 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/README.md @@ -0,0 +1,200 @@ +# quota-manager: Hierarchical quota management + +This is concerned with a quota management library where a hierarchical quota tree is defined through a JSON file. Quotas are assigned to a multitude of resources (CPU, memory). A consumer requesting a specified amount of resources and a member of a leaf node in the quota tree is checked against the quota limitations and is either allocated successfully or rejected. Consumers have priorities and a preemptable option. The quotas may be applied with a Hard or Soft constraint. + +An outline of the [algorithm](docs/quota-algorithm.pdf) is provided. + +An [example](docs/tree-example.pdf) scenario is described along with its corresponding [output](docs/tree-example.txt). + +## Basic concepts + +**Allocation**, **Quota**, **Request**: An Allocation is a vector of integers, one for each member of a given set of resource types, e.g. [CPU, memory]. An Allocation of [5, 32] means that 5 CPU units and 32 memory units are allocated. A Quota is an upper limit (maximum) on an Allocation, e.g. [16, 512]. And, a Request is the amount requested by a consumer, e.g. [1, 16]. + +**Quota Tree** (or **Tree**): A Quota Tree (or simply referred to as a Tree) is a tree where each node represents some grouping of consumers in a hierarchy. Each tree has a unique name. A Quota is associated with each node, representing the maximun resources allowed for such a grouping. Leaf nodes are concrete groups that consumers belong to, such as teams and projects. The leaf node where a consumer is assigned is referred to as the consumer group node (or *gNode*). Internal (non-leaf) nodes correspond to groups of groups, such as departments and organizations. An quota indicator (*Soft/Hard*) at a node signifies whether a consumer can satisfy its request by seeking allocation from the parent node (*Soft*) or not (*Hard*), in case the request cannot be fulfilled at the node level. The node where a consumer receives its request is referred to as the consumer allocated node (or *aNode*), which could be any node along the path from the *gNode* to the *root*, assuming that all nodes along the path are designated as *Soft*. (The *root* node is *Hard* by definition.) An example specification of a tree follows. + +```json +{ + "kind": "QuotaTree", + "metadata": { + "name": "TestTree" + }, + "spec": { + "resourceNames": [ + "cpu", + "memory" + ], + "nodes": { + "A": { + "parent": "nil", + "hard": "true", + "quota": { + "cpu": "10", + "memory": "256" + } + }, + "B": { + "parent": "A", + "hard": "true", + "quota": { + "cpu": "2", + "memory": "64" + } + }, + "C": { + "parent": "A", + "hard": "false", + "quota": { + "cpu": "6", + "memory": "64" + } + }, + } + } +} +``` + +**Consumer**: A requestor of resources. A consumer specifies a tree, a leaf group node (*gNode*) in the tree, and the requested amount of resources. If allocated, the consumer will be assigned an allocation node (*aNode*) in the tree, otherwise the allocation request is rejected. Typically the *aNode* is the same as the *gNode*. However, if there is not enough resources available at the *gNode*, the requested resources may be fulfilled by sharing available resources at a node along the path from the *gNode* to the *root* of the tree. The *aNode* may change during the lifetime of the consumer depending on the departure (completion) of other consumers and arrivals of new consumers. An example of a consumer specification follows. + +```json +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "consumer-1", + "trees:": [ + { + "treeName": "ExampleTree", + "groupID": "C", + "request": { + "cpu": 4, + "memory": 16 + }, + "priority": 0, + "type": 0, + "unPreemptable": true + } + ] + } +} +``` + +**Preempted Consumers**: When attempting to allocate a consumer, other, already allocated consumers may be preempted, if such consumers are marked as *Preemptable*. Preemption may happen during the allocation of a higher priority consumer. It may also occur otherwise, in case a new consumer is claiming resources at a lower level than the level at which the preempted consumer is allocated shared resources. Preempted consumers are automatically de-allocated from the tree. A list of the preempted consumers is given in the `AllocationResponse` of the Allocate method. + +**Forest** (or **multi-trees**): A collection of (one or more) quota trees treated simultaneously when allocating a consumer, requesting resources from multiple trees. Each forest has a unique name. A consumer specifies a list of trees, each with a tree name, a *gNode*, and a request. (Indiviadual trees may have their own sets of resource types.) The allocation request is satisfied only if the individual requests are satisfied on *ALL* the trees in the forest, i.e. conjointly satisfied. An example of a forest consumer specification follows. + +```json +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "job-1", + "trees": [ + { + "treeName": "ContextTree", + "groupID": "Context-4", + "request": { + "cpu": 2 + }, + "priority": 0 + }, + { + "treeName": "ServiceTree", + "groupID": "Srvc-X", + "request": { + "cpu": 2, + "disk": 1 + }, + "priority": 0 + } + ] + } +} +``` + +Consumer specifications are kept in a `ConsumerInfo` object. A `Consumer` object is a realization instance for a given tree (also referred to as Tree Consumer). A `ForestConmsumer` object is a realization instance for a given forest; It consists of multiple tree `Consumer` objects. A client of the `QuotaManager` first creates a `ConsumerInfo`, then, using the `consumerID` of the `ConsumerInfo`, requests to allocate and deallocate the consumer on either a single tree or a forest of trees. + +## Dynamic Tree Updates + +Each [Tree](quota/core/quotatree.go) has a [Controller](quota/core/treeController.go) to manage the allocation and de-allocation of consumers on the tree. The Controller keeps the allocation state of the tree in memory. In order to allow for dynamic changes to the tree, such as tree topology, node names, and node quota values, a [Cache](quota/core/treecache.go) is provided where all updates are kept. Whenever updates in the Cache are to take effect, the method UpdateTree() is called. Then, the Controller will refresh the tree to match all updates in the cache. Examples are provided [here](demos/updates). + +## Quota Manager Interface + +A high-level interface is provided through a [Quota Manager](quota/quotamanager.go). A summary of the interaction between a client and the Quota Manager is depicted below. + +![quota-manager](docs/quota-manager.png) + +The Quota Manager is in one of two modes. + +1. **Maintenance**: Allocation requests are treated as forced allocation on consumer group nodes (gNodes). Tree updates are kept in the cache. This is the default initial mode. +2. **Normal**: Allocation and de-allocation requests are handled in the normal way. Tree updates are kept in the cache. + +A summary of the API interface to the Quota Manager follows. + +- Management + - create a new quota manager + - `NewManager()` + - get/set mode + - `GetMode()` + - `SetMode(mode)` + - get information + - `GetTreeNames()` + - `GetForestNames()` + - `GetForestTreeNames()` + - update all single trees and forests from caches + - `UpdateAll()` +- Consumers (first create a `ConsumerInfo` from JSON specifications) + - `AddConsumer(consumerInfo)` + - `RemoveConsumer(consumerID)` + - `GetAllConsumerIDs()` +- Trees + - add a tree + - `AddTreeByName(treeName)` + - `AddTreeFromString(treeJsonString)` + - `AddTreeFromStruct(treeJsonStruct)` + - delete a tree + - `DeleteTree(treeName)` +- Tree consumers allocation + - allocation (returns `AllocationResponse`) + - `Allocate(treeName, consumerID)` + - de-allocation + - `DeAllocate(treeName, consumerID)` + - check allocation + - `IsAllocated(treeName, consumerID)` +- Tree Updates + - tree cache (get a `TreeCache` object on which modifications can be made) + - `GetTreeCache(treeName)` + - refresh (effect) updates from cache + - `UpdateTree(treeName)` +- Forests + - add a forest + - `AddForest(forestName)` + - delete a forest + - `DeleteForest(forestName)` + - add a tree to a forest + - `AddTreeToForest(forestName, treeName)` + - delete a tree from a forest + - `DeleteTreeFromForest(forestName, treeName)` +- Forest consumers allocation + - allocation (returns `AllocationResponse`) + - `AllocateForest(forestName, consumerID)` + - de-allocation + - `DeAllocateForest(forestName, consumerID)` + - check allocation + - `IsAllocatedForest(forestName, consumerID)` +- Forest Updates + - refresh (effect) updates from caches + - `UpdateForest(forestName)` + +Examples of using the Quota Manager in the case of a [single tree](demos/manager/tree/demo.go) and a [forest](demos/manager/forest/demo.go) are provided. + +## Notes + +- Quota-manager is implemented as single threaded, thus all calls are blocking. +- All names (forest, tree, consumer) are assumed to be unique within their domain. +- Allocating a consumer on a forest will allocate on the trees in the intersection of the set of trees defined by the forest and the set of trees defined by the consumer specification. +- One could treat a tree as a forest of a single tree, however, for convenience, operations on a single tree (as well as on a forest) are provided. +- For simplicity, a tree is limited to belong to only one forest. diff --git a/pkg/quotaplugins/quota-forest/quota-manager/demos/forest/demo.go b/pkg/quotaplugins/quota-forest/quota-manager/demos/forest/demo.go new file mode 100644 index 000000000..d2141dff0 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/demos/forest/demo.go @@ -0,0 +1,92 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "flag" + "fmt" + "os" + + core "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/core" + "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + flag.Set("v", "4") + flag.Set("skip_headers", "true") + klog.SetOutput(os.Stdout) + flag.Parse() + defer klog.Flush() + + fmt.Println("Demo of multiple quota trees.") + fmt.Println() + prefix := "../../samples/forest/" + + treeNames := []string{"ContextTree", "ServiceTree"} + forestController := core.NewForestController() + resourceNames := make(map[string][]string) + treeCache := core.NewTreeCache() + + /* + * create multiple trees + */ + for _, treeName := range treeNames { + fName := prefix + treeName + ".json" + fmt.Printf("Tree file name: %s\n", fName) + + err := treeCache.FromFile(fName) + if err != nil { + fmt.Println("Error creating tree " + treeName) + return + } + tree, response := treeCache.CreateTree() + if !response.IsClean() { + fmt.Printf("Warning: cache not clean after tree created: %v \n", response.String()) + } + resourceNames[treeName] = treeCache.GetResourceNames() + forestController.AddTree(tree) + treeCache.Clear() + } + fmt.Println(forestController) + + /* + * create consumer jobs + */ + jobs := []string{"job1", "job2", "job3", "job4", "job5"} + ids := make([]string, len(jobs)) + for i, job := range jobs { + fName := prefix + job + ".json" + fmt.Printf("Consumer file name: %s\n", fName) + forestConsumer, err := core.NewForestConsumerFromFile(fName, resourceNames) + if err != nil { + fmt.Println("Error creating consumers from " + fName) + } + forestController.Allocate(forestConsumer) + ids[i] = forestConsumer.GetID() + } + + fmt.Println(forestController) + + for _, id := range ids { + if forestController.IsConsumerAllocated(id) { + forestController.DeAllocate(id) + } + } + + fmt.Println(forestController) +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/demos/incremental/demo.go b/pkg/quotaplugins/quota-forest/quota-manager/demos/incremental/demo.go new file mode 100644 index 000000000..8aa4d30f0 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/demos/incremental/demo.go @@ -0,0 +1,88 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import ( + "flag" + "fmt" + "os" + + core "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/core" + "k8s.io/klog/v2" +) + +var ( + treeInfo string = "{ \"name\": \"ExampleTree\", \"resourceNames\": [ \"cpu\" ] }" + + nodeInfo1 string = "{ \"Context-1\": {\"parent\": \"Org-A\", \"hard\": \"true\", \"quota\": { \"cpu\": \"1\" } } }" + + nodeInfo2 string = "{ \"Root\": { \"parent\": \"nil\", \"quota\": { \"cpu\": \"10\" } }, \"Org-A\": {\"parent\": \"Root\", \"quota\": { \"cpu\": \"4\" } } }" + + nodeInfo3 string = "{ \"Context-2\": {\"parent\": \"Org-B\", \"quota\": { \"cpu\": \"2\" } } }" + + nodeInfo4 string = "{ \"Org-B\": {\"parent\": \"Root\", \"quota\": { \"cpu\": \"3\" } } }" +) + +func main() { + klog.InitFlags(nil) + flag.Set("v", "4") + flag.Set("skip_headers", "true") + klog.SetOutput(os.Stdout) + flag.Parse() + defer klog.Flush() + + fmt.Println("Demo of building a quota tree incrementally.") + fmt.Println() + + treeCache := core.NewTreeCache() + + /* process tree information */ + fmt.Printf("treeInfo = %s\n\n", treeInfo) + err := treeCache.AddTreeInfoFromString(treeInfo) + if err != nil { + fmt.Fprintf(os.Stderr, "error creating tree: invalid tree info") + return + } + + /* process node information */ + fmt.Printf("nodeInfo1 = %s\n\n", nodeInfo1) + err1 := treeCache.AddNodeSpecsFromString(nodeInfo1) + fmt.Println() + + fmt.Printf("nodeInfo2 = %s\n\n", nodeInfo2) + err2 := treeCache.AddNodeSpecsFromString(nodeInfo2) + fmt.Println() + + fmt.Printf("nodeInfo3 = %s\n\n", nodeInfo3) + err3 := treeCache.AddNodeSpecsFromString(nodeInfo3) + fmt.Println() + + fmt.Printf("nodeInfo4 = %s\n\n", nodeInfo4) + err4 := treeCache.AddNodeSpecsFromString(nodeInfo4) + fmt.Println() + + if err1 != nil || err2 != nil || err3 != nil || err4 != nil { + fmt.Fprintf(os.Stderr, "error creating tree: invalid nodes info") + return + } + + /* create tree */ + tree, response := treeCache.CreateTree() + if !response.IsClean() { + fmt.Printf("Warning: cache not clean after tree created: %v \n", response.String()) + } + fmt.Println(tree) +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/demos/manager/forest/demo.go b/pkg/quotaplugins/quota-forest/quota-manager/demos/manager/forest/demo.go new file mode 100644 index 000000000..62ef22d7f --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/demos/manager/forest/demo.go @@ -0,0 +1,116 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota" + "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + flag.Set("v", "4") + flag.Set("skip_headers", "true") + klog.SetOutput(os.Stdout) + flag.Parse() + defer klog.Flush() + + fmt.Println("Demo of allocation and de-allocation of consumers on a forest using the quota manager.") + fmt.Println() + prefix := "../../../samples/forest/" + indent := "===> " + forestName := "Context-Service" + treeNames := []string{"ContextTree", "ServiceTree"} + + // create a quota manager + fmt.Println(indent + "Creating quota manager ... " + "\n") + quotaManager := quota.NewManager() + quotaManager.SetMode(quota.Normal) + fmt.Println(quotaManager.GetModeString()) + fmt.Println() + + // create multiple trees + fmt.Println(indent + "Creating multiple trees ..." + "\n") + for _, treeName := range treeNames { + fName := prefix + treeName + ".json" + fmt.Printf("Tree file name: %s\n", fName) + jsonTree, err := ioutil.ReadFile(fName) + if err != nil { + fmt.Printf("error reading quota tree file: %s", fName) + return + } + _, err = quotaManager.AddTreeFromString(string(jsonTree)) + if err != nil { + fmt.Printf("error adding tree %s: %v", treeName, err) + return + } + } + + // create forest + fmt.Println(indent + "Creating forest " + forestName + " ..." + "\n") + quotaManager.AddForest(forestName) + for _, treeName := range treeNames { + quotaManager.AddTreeToForest(forestName, treeName) + } + fmt.Println(quotaManager) + + // create consumer jobs + fmt.Println(indent + "Allocating consumers on forest ..." + "\n") + jobs := []string{"job1", "job2", "job3", "job4", "job5"} + for _, job := range jobs { + + // create consumer info + fName := prefix + job + ".json" + fmt.Printf("Consumer file name: %s\n", fName) + consumerInfo, err := quota.NewConsumerInfoFromFile(fName) + if err != nil { + fmt.Printf("error reading consumer file: %s \n", fName) + continue + } + consumerID := consumerInfo.GetID() + + // add consumer info to quota manager + quotaManager.AddConsumer(consumerInfo) + + // allocate forest consumer instance of the consumer info + allocResponse, err := quotaManager.AllocateForest(forestName, consumerID) + if err != nil { + fmt.Printf("error allocating consumer: %v \n", err) + quotaManager.RemoveConsumer((consumerID)) + continue + } + + // remove preemted consumers if any from quota manager (unless will be resubmitted) + preemted := allocResponse.GetPreemptedIds() + for _, pid := range preemted { + quotaManager.RemoveConsumer(pid) + } + } + + // de-allocate consumers from forest + fmt.Println(indent + "De-allocating consumers from forest ..." + "\n") + for _, id := range quotaManager.GetAllConsumerIDs() { + quotaManager.DeAllocateForest(forestName, id) + quotaManager.RemoveConsumer(id) + } + fmt.Println() + fmt.Println(quotaManager) +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/demos/manager/tree/demo.go b/pkg/quotaplugins/quota-forest/quota-manager/demos/manager/tree/demo.go new file mode 100644 index 000000000..6d5bf6452 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/demos/manager/tree/demo.go @@ -0,0 +1,113 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota" + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/core" + "k8s.io/klog/v2" +) + +const ( + prefix = "../../../samples/tree/" +) + +var ( + quotaManager *quota.Manager +) + +func main() { + klog.InitFlags(nil) + flag.Set("v", "4") + flag.Set("skip_headers", "true") + klog.SetOutput(os.Stdout) + flag.Parse() + defer klog.Flush() + + fmt.Println("Demo of allocation and de-allocation of consumers on a tree using the quota manager.") + fmt.Println() + treeFileName := prefix + "tree.json" + + // create a quota manager + quotaManager = quota.NewManager() + quotaManager.SetMode(quota.Normal) + fmt.Println(quotaManager.GetModeString()) + + // add a quota tree from file + jsonTree, err := ioutil.ReadFile(treeFileName) + if err != nil { + fmt.Printf("error reading quota tree file: %s", treeFileName) + return + } + treeName, err := quotaManager.AddTreeFromString(string(jsonTree)) + if err != nil { + fmt.Printf("error adding tree %s: %v", treeName, err) + return + } + + // allocate consumers from files + ar := createAndAllocateConsumer("ca", treeName) + caID := ar.GetConsumerID() + + createAndAllocateConsumer("cb", treeName) + createAndAllocateConsumer("cc", treeName) + + quotaManager.DeAllocate(treeName, caID) + quotaManager.RemoveConsumer(caID) + + createAndAllocateConsumer("cd", treeName) + createAndAllocateConsumer("ce", treeName) + createAndAllocateConsumer("cf", treeName) + createAndAllocateConsumer("cg", treeName) + createAndAllocateConsumer("ch", treeName) + createAndAllocateConsumer("ci", treeName) + createAndAllocateConsumer("cj", treeName) + + fmt.Println(quotaManager) +} + +func createAndAllocateConsumer(consumerFile string, treeName string) (allocResponse *core.AllocationResponse) { + // create consumer info + consumerFileName := prefix + consumerFile + ".json" + consumerInfo, err := quota.NewConsumerInfoFromFile(consumerFileName) + if err != nil { + fmt.Printf("error reading consumer file: %s \n", consumerFileName) + os.Exit(1) + } + consumerID := consumerInfo.GetID() + + // add consumer info to quota manager + quotaManager.AddConsumer(consumerInfo) + + // allocate a tree consumer instance of the consumer info + allocResponse, err = quotaManager.Allocate(treeName, consumerID) + if err != nil { + fmt.Printf("error allocating consumer: %v \n", err) + os.Exit(1) + } + + // remove preemted consumers if any from quota manager (unless will be resubmitted) + preemted := allocResponse.GetPreemptedIds() + for _, pid := range preemted { + quotaManager.RemoveConsumer(pid) + } + return allocResponse +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/demos/tree/demo.go b/pkg/quotaplugins/quota-forest/quota-manager/demos/tree/demo.go new file mode 100644 index 000000000..0bc0b0e71 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/demos/tree/demo.go @@ -0,0 +1,104 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import ( + "flag" + "fmt" + "os" + + core "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/core" + "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + flag.Set("v", "4") + flag.Set("skip_headers", "true") + klog.SetOutput(os.Stdout) + flag.Parse() + defer klog.Flush() + + fmt.Println("Demo of allocation and de-allocation of a sequence of consumers on a single quota tree.") + fmt.Println() + prefix := "../../samples/tree/" + + treeCache := core.NewTreeCache() + err := treeCache.FromFile(prefix + "tree.json") + if err != nil { + fmt.Println("error parsing file") + return + } + tree, response := treeCache.CreateTree() + if !response.IsClean() { + fmt.Printf("Warning: cache not clean after tree created: %v \n", response.String()) + } + treeName := tree.GetName() + rNames := treeCache.GetResourceNames() + controller := core.NewController(tree) + + /* + * run allocation/deallocation scenario + */ + + ca, _ := createConsumer(prefix+"ca.json", treeName, rNames) + controller.Allocate(ca) + + cb, _ := createConsumer(prefix+"cb.json", treeName, rNames) + controller.Allocate(cb) + + cc, _ := createConsumer(prefix+"cc.json", treeName, rNames) + controller.Allocate(cc) + + controller.DeAllocate(ca.GetID()) + + cd, _ := createConsumer(prefix+"cd.json", treeName, rNames) + controller.Allocate(cd) + + ce, _ := createConsumer(prefix+"ce.json", treeName, rNames) + controller.Allocate(ce) + + cf, _ := createConsumer(prefix+"cf.json", treeName, rNames) + controller.Allocate(cf) + + cg, _ := createConsumer(prefix+"cg.json", treeName, rNames) + controller.Allocate(cg) + + ch, _ := createConsumer(prefix+"ch.json", treeName, rNames) + controller.Allocate(ch) + + ci, _ := createConsumer(prefix+"ci.json", treeName, rNames) + controller.Allocate(ci) + + cj, _ := createConsumer(prefix+"cj.json", treeName, rNames) + controller.Allocate(cj) + + fmt.Println(controller) +} + +func createConsumer(fName string, treeName string, resourceNames []string) (*core.Consumer, error) { + rNames := make(map[string][]string) + rNames[treeName] = resourceNames + forestConsumer, err := core.NewForestConsumerFromFile(fName, rNames) + if err != nil { + return nil, err + } + consumer := forestConsumer.GetTreeConsumer(treeName) + if consumer == nil { + return nil, fmt.Errorf("undefined tree %v for consumer %v", treeName, forestConsumer.GetID()) + } + return consumer, nil +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/demos/updates/README.md b/pkg/quotaplugins/quota-forest/quota-manager/demos/updates/README.md new file mode 100644 index 000000000..c369ad2ff --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/demos/updates/README.md @@ -0,0 +1,10 @@ +# Testing dynamic tree updates + +Demonstrating various updates to single and multiple trees and refresh to the state of the tree. + +## Single tree + +Slides to follow the test is [here](../../docs/tree-cache-example.pdf). + +## Multiple trees + diff --git a/pkg/quotaplugins/quota-forest/quota-manager/demos/updates/forest/demo.go b/pkg/quotaplugins/quota-forest/quota-manager/demos/updates/forest/demo.go new file mode 100644 index 000000000..44edce9a6 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/demos/updates/forest/demo.go @@ -0,0 +1,196 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + core "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/core" + "k8s.io/klog/v2" +) + +var separator string = strings.Repeat("*", 32) + +func main() { + klog.InitFlags(nil) + flag.Set("v", "4") + flag.Set("skip_headers", "true") + klog.SetOutput(os.Stdout) + flag.Parse() + defer klog.Flush() + + fmt.Println(separator) + fmt.Println("===> Demonstrating dynamic multi-tree updates") + fmt.Println(separator) + fmt.Println() + + // create trees + prefix := "../../../samples/forest/" + treeNames := []string{"ContextTree", "ServiceTree"} + numTrees := len(treeNames) + treeCaches := make([]*core.TreeCache, numTrees) + + // create forest manager + for i, treeName := range treeNames { + fName := prefix + treeName + ".json" + treeCache := createTreeCache(fName) + if treeCache == nil { + fmt.Println("error parsing file") + return + } + treeCaches[i] = treeCache + } + forestController := createForestControllerFromCaches(treeCaches) + rNamesMap := forestController.GetResourceNames() + + // // allocate consumer(s) + job := "job1" + fName := prefix + job + ".json" + allocateConsumer(forestController, fName, rNamesMap) + + // update cache + fmt.Println(separator) + fmt.Println("===> Update cache") + fmt.Println(separator) + fmt.Println() + + fmt.Println("======> deleting node Srvc-Z") + fmt.Println() + treeCaches[1].DeleteNode("Srvc-Z") + refreshTreeFromCache(forestController, treeCaches) + + fmt.Println("======> renaming node Srvc-X to Srvc-XX") + fmt.Println() + treeCaches[1].RenameNode("Srvc-X", "Srvc-XX") + refreshTreeFromCache(forestController, treeCaches) + + fmt.Println("======> updating parent of Org-B to Org-A") + fmt.Println() + bStr := "{ \"Org-B\": {\"parent\": \"Org-A\", \"quota\": { \"cpu\": \"6\" } } }" + fmt.Println(bStr) + fmt.Println() + fmt.Println("======> updating quota of Org-A") + fmt.Println() + aStr := "{ \"Org-A\": {\"parent\": \"Root\", \"quota\": { \"cpu\": \"8\" } } }" + fmt.Println(aStr) + fmt.Println() + treeCaches[0].AddNodeSpecsFromString(bStr) + treeCaches[0].AddNodeSpecsFromString(aStr) + refreshTreeFromCache(forestController, treeCaches) + + fmt.Println("======> deleting node Context-4") + fmt.Println() + treeCaches[0].DeleteNode("Context-4") + refreshTreeFromCache(forestController, treeCaches) + + fmt.Println("======> deleting node Root") + fmt.Println() + treeCaches[1].DeleteNode("Root") + refreshTreeFromCache(forestController, treeCaches) + + // de-allocate consumer(s) + deallocateConsumer(forestController, "C-1") +} + +func createTreeCache(TreeSpecPath string) *core.TreeCache { + fmt.Println(separator) + fmt.Println("===> Create tree in cache") + fmt.Println(separator) + fmt.Println() + + treeCache := core.NewTreeCache() + err := treeCache.FromFile(TreeSpecPath) + if err != nil { + return nil + } + return treeCache +} + +func createForestControllerFromCaches(treeCaches []*core.TreeCache) *core.ForestController { + fmt.Println(separator) + fmt.Println("===> Create forest controller") + fmt.Println(separator) + fmt.Println() + + forestController := core.NewForestController() + for _, tc := range treeCaches { + tree, response := tc.CreateTree() + if !response.IsClean() { + fmt.Printf("Warning: cache not clean after tree created: %v \n", response.String()) + } + forestController.AddTree(tree) + } + fmt.Println(forestController) + return forestController +} + +func allocateConsumer(cm *core.ForestController, consumerSpecPath string, resourceNamesMap map[string][]string) bool { + fmt.Println(separator) + fmt.Println("===> Allocate consumer") + fmt.Println(separator) + fmt.Println() + + forestConsumer, err := core.NewForestConsumerFromFile(consumerSpecPath, resourceNamesMap) + if err != nil { + fmt.Println("Error creating consumer") + return false + } + fmt.Println(forestConsumer) + fmt.Println() + cm.Allocate(forestConsumer) + fmt.Println(forestConsumer) + fmt.Println() + return true +} + +func deallocateConsumer(cm *core.ForestController, consumerID string) bool { + fmt.Println(separator) + fmt.Println("===> De-Allocate consumer") + fmt.Println(separator) + fmt.Println() + + if !cm.IsConsumerAllocated(consumerID) { + fmt.Println("error retrieving consumer " + consumerID) + return false + } + cm.DeAllocate(consumerID) + return true +} + +func refreshTreeFromCache(cm *core.ForestController, treeCaches []*core.TreeCache) { + fmt.Println(separator) + fmt.Println("===> Refresh cache") + fmt.Println(separator) + fmt.Println() + + unAllocated, responseMap := cm.UpdateTrees(treeCaches) + for _, response := range responseMap { + if !response.IsClean() { + fmt.Printf("Warning: cache not clean after tree created: %v \n", response.String()) + } + } + + fmt.Println(separator) + fmt.Println("===> Updated tree") + fmt.Println(separator) + fmt.Println() + fmt.Println(cm) + fmt.Printf("unAllocated = %v \n", unAllocated) + fmt.Println() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/demos/updates/tree/demo.go b/pkg/quotaplugins/quota-forest/quota-manager/demos/updates/tree/demo.go new file mode 100644 index 000000000..d3e376034 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/demos/updates/tree/demo.go @@ -0,0 +1,199 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + core "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/core" + "k8s.io/klog/v2" +) + +var separator string = strings.Repeat("*", 32) + +func main() { + klog.InitFlags(nil) + flag.Set("v", "4") + flag.Set("skip_headers", "true") + klog.SetOutput(os.Stdout) + flag.Parse() + defer klog.Flush() + + fmt.Println(separator) + fmt.Println("===> Demonstrating dynamic tree updates") + fmt.Println(separator) + fmt.Println() + + // create tree + treeCache := createTreeCache("../../../samples/ExampleTree.json") + if treeCache == nil { + fmt.Println("error parsing file") + return + } + + // create tree controller + controller := createTreeControllerFromCache(treeCache) + rNames := controller.GetResourceNames() + + // allocate consumer(s) + allocateConsumer(controller, "../../../samples/ExampleConsumer.json", rNames) + + // update cache + fmt.Println(separator) + fmt.Println("===> Update cache") + fmt.Println(separator) + fmt.Println() + + fmt.Println("======> deleting node D") + fmt.Println() + treeCache.DeleteNode("D") + refreshTreeFromCache(controller, treeCache) + + fmt.Println("======> renaming node C to CC") + fmt.Println() + treeCache.RenameNode("C", "CC") + refreshTreeFromCache(controller, treeCache) + + fmt.Println("======> updating parent of G to B") + fmt.Println() + gStr := "{ \"G\": {\"parent\": \"B\", \"quota\": { \"cpu\": \"3\" } } }" + fmt.Println(gStr) + fmt.Println() + fmt.Println("======> updating parent of H to A") + fmt.Println() + hStr := "{ \"H\": {\"parent\": \"A\", \"quota\": { \"cpu\": \"3\" } } }" + fmt.Println(hStr) + fmt.Println() + fmt.Println("======> updating quota of B") + fmt.Println() + bStr := "{ \"B\": {\"parent\": \"A\", \"quota\": { \"cpu\": \"6\" } } }" + fmt.Println(bStr) + fmt.Println() + treeCache.AddNodeSpecsFromString(gStr) + treeCache.AddNodeSpecsFromString(hStr) + treeCache.AddNodeSpecsFromString(bStr) + refreshTreeFromCache(controller, treeCache) + + fmt.Println("======> deleting node K") + fmt.Println() + treeCache.DeleteNode("K") + refreshTreeFromCache(controller, treeCache) + + fmt.Println("======> deleting node A") + fmt.Println() + treeCache.DeleteNode("A") + refreshTreeFromCache(controller, treeCache) + + // de-allocate consumer(s) + deallocateConsumer(controller, "C-1") +} + +func createTreeCache(TreeSpecPath string) *core.TreeCache { + fmt.Println(separator) + fmt.Println("===> Create tree in cache") + fmt.Println(separator) + fmt.Println() + + treeCache := core.NewTreeCache() + err := treeCache.FromFile(TreeSpecPath) + if err != nil { + return nil + } + return treeCache +} + +func createTreeControllerFromCache(treeCache *core.TreeCache) *core.Controller { + fmt.Println(separator) + fmt.Println("===> Create tree controller") + fmt.Println(separator) + fmt.Println() + + tree, response := treeCache.CreateTree() + if !response.IsClean() { + fmt.Printf("Warning: cache not clean after tree created: %v \n", response.String()) + } + return core.NewController(tree) +} + +func allocateConsumer(controller *core.Controller, consumerSpecPath string, resourceNames []string) bool { + fmt.Println(separator) + fmt.Println("===> Allocate consumer") + fmt.Println(separator) + fmt.Println() + + consumer, err := createConsumer(consumerSpecPath, controller.GetTreeName(), resourceNames) + fmt.Println(consumer) + fmt.Println() + if err != nil { + fmt.Println("error creating consumer") + return false + } + controller.Allocate(consumer) + fmt.Println(consumer) + fmt.Println() + return true +} + +func deallocateConsumer(controller *core.Controller, consumerID string) bool { + fmt.Println(separator) + fmt.Println("===> De-Allocate consumer") + fmt.Println(separator) + fmt.Println() + + consumer := controller.GetConsumer(consumerID) + if consumer == nil { + fmt.Println("error retrieving consumer " + consumerID) + return false + } + controller.DeAllocate(consumerID) + return true +} + +func refreshTreeFromCache(controller *core.Controller, treeCache *core.TreeCache) { + fmt.Println(separator) + fmt.Println("===> Refresh cache") + fmt.Println(separator) + fmt.Println() + + unAllocated, response := controller.UpdateTree(treeCache) + if !response.IsClean() { + fmt.Printf("Warning: cache not clean after tree created: %v \n", response.String()) + } + fmt.Println(separator) + fmt.Println("===> Updated tree") + fmt.Println(separator) + fmt.Println() + fmt.Println(controller) + fmt.Printf("unAllocated = %v \n", unAllocated) + fmt.Println() +} + +func createConsumer(fName string, treeName string, resourceNames []string) (*core.Consumer, error) { + rNames := make(map[string][]string) + rNames[treeName] = resourceNames + forestConsumer, err := core.NewForestConsumerFromFile(fName, rNames) + if err != nil { + return nil, err + } + consumer := forestConsumer.GetTreeConsumer(treeName) + if consumer == nil { + return nil, fmt.Errorf("undefined tree %v for consumer %v", treeName, forestConsumer.GetID()) + } + return consumer, nil +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/docs/forest-example.pdf b/pkg/quotaplugins/quota-forest/quota-manager/docs/forest-example.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5deb30b847f90853b5d987fb8a34ef4905a429b7 GIT binary patch literal 139918 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 VBk~&GGsZs!?m8j<4c7u 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

~5xyKS*dNYHr88-X_=c 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

dw#Gpb5*qIuq0 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૨}`9oKZ(7kS=Jdmod*bT2CiSs!7 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

  • t)?Go6*JRYu~n#(ZnUkGe${N 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 literal 0 HcmV?d00001 diff --git a/pkg/quotaplugins/quota-forest/quota-manager/docs/quota-manager.png b/pkg/quotaplugins/quota-forest/quota-manager/docs/quota-manager.png new file mode 100644 index 0000000000000000000000000000000000000000..9fcc2996c555b9e3eee962ead155b6ee36bae1e8 GIT binary patch literal 35997 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 aeuNUaC@+cn%H2c+|LJKNY1XORNB%$2DKg~% literal 0 HcmV?d00001 diff --git a/pkg/quotaplugins/quota-forest/quota-manager/docs/tree-cache-example.pdf b/pkg/quotaplugins/quota-forest/quota-manager/docs/tree-cache-example.pdf new file mode 100644 index 0000000000000000000000000000000000000000..72bf42f07bb155eed729cfcce322d96f580ddc6e GIT binary patch literal 70065 zcmc%QRd5_zq9$rt%*-rVEQ?Ca7Be$5GqYq_%*4!UU zU#coHSFFs8%EyIBxqz{V`xN3CuwAD;%G_;U}9z>e^mOji$V>8Z+x7r~4j4TJAcb@3*d0w|-jm;Up!x zu=m{Og*Vf5yM^mX2o-mkf*op-RabR8F3KQI_jbgSK%6eBtsY;V_+!d?Edu$ zvBhfs)%e1|*lHAWKZ_vnzJC0e)xiE>d$`TZ6;kr3_wnFD#b&G*;eZaI_rc*q$`b3% zKtm{N@|iq&Dp0JJNCcef?RKnI0*ipvuv?M3p})nT_!6q+W@*6Sxx*lfpP#3?=FGJI zJ|EI6L?m?FwA9RiYMUtI%)hZ(;R%f=}MgBvpR<)4{V8(~HW3)S^)Qf7SuDrE@ylKx7M z%g&p@A{wRr26g+$Qm7Ir%P$7CGGn^}aE7q;do}lF&W(L=u4~VAe6v;zj4{j0e4*na zM&C?OJ!lxh35_gH`>GH{@k9icY{+^&i$L45mKreBbOpO%ibT?mVSiTHC_q{GVE+vX6nOP4X;3AhESLQ$uY3GR5@_}Ft z(0NeRmBXKL50Alu`X`GW^=2A?tx`Y%8C5Byk zh&&=`yis={c1(F;WbCq$DC=z2(Q17wZ8Y+@zCvAxWGF{1PJC=p$uOV0EDk}O=!n^R z0WU8_knp8b8gCiVxHz}hdM;*jjL?%g9S(0TqI_?j|=(m+m9&E?Ct+w6`2| z8LF#?dvXHb?vu206g%i}KBb(y%T<4BWP~M{?lRbp?g}v)wV2`>Kb-2K^p_*rp1Lfq zsFMOEknQXVwUgCSW3YOe!9zEIDPPmCMn47gm(aPT(6zmtJcJf!CtUM60%SzMnL zbMDaD*{^UiLz`P-1NPh{ix9mKacTVdr_~Z8f(x>KYoy7cRfX%%=Dzz1l^|s=vvy0( zzCM);(L#fRjmx7~KuTM^MT00FE? zqQt)+&NBopf*M2{Jxxx@iO7=$4nifTJ+(ajk(c zo9jS%yhiuWSBb~sh;|}{nT2SL`vfIZuI_m9DUK_ukAFUSIJL#$uH%BsLM_+ z(S|QYkq18wD+S){ZET=CyeTLtC%J6D1HnTep&7p~G(*Cl=eathI_--Isq zH7G=Jj%AQCQ7TbU2K%GK4VUkoqxJN~2knKc7Ky!gi`K|weCh0pWloIZceVW0W?$8P zFB2tpm#7EN@fMSX`{RkB@`s^Hq_IgyqN^Ro88G~EAXwWX;;EKmr>1%G=!Xl2te?>o zB8y0~r*qz+_;D30Q57cVV~J-rugi*~Mr;Sf&uHJXv_%#qhlEcA(hpF;5Gur2JG^Kt zKJ38jG*FH9{BcFDw?_ZO@c9ez0>{6eCP~Xe;CtTlB_T?g0J3$p)VS-f$xwqAq zy}Ach*YkC-Co$B>+ZR`t*a&gfv&h=V~H& zJ17g~0=hRuv^Xl{r#fh8ywe-#uq5UwWIxElWL2?TXm@&FHe|bka8SoK$JT>d?WkI1 z4&b*vFURqY4|t9mc{93RXX!@i)GMSCTN`aC#FIG(EAKx$(}FrPH>TencpJ-4)S->6 z4gU$W-@pE_j^S^-Q*^Wa?KxRJlfORgjjSCB0l#<46Vk~WIoLSa8yGndGW}zNfQ_}| z@BI#he|RYW$9?~t@6Y~!aZ%RZ#z4Wykx=9Jq=LePbc#l ze^e>pz(C0I&uM(0O%?ym>Mfm?0S6O~f^525??}p7tbWv1TMh}{7XKkNCCMG1k z!*+)voZ%Ej1C2quh*M5%13*!4vuh}b{~6~^zU{vy;3qU=DooS@MD zdbkDDWjXWhM(X>Z+UD|MlzCFF%;FRZ%ZayETzfM*ALtF%iGO ztA6R)HZ}$oc@hMkdu!)TNhuN?sC~Gg{bAU;`mIxz%wgfvCx)0+fCL-}T4<&+Wc_ElOw6dq8w4kss(?PX)DO<=V#WrnxPZlscK z=Z1r3YbKL!ndEvx7_}>kr2_3gQq0airAq?Qqo(ML9g<~Rh5&?6u`WVxfPo<6``Fv; z;lrNUO*4U93rzMxK|{p!3y>C*&GwImj9;#66`u7OsN;L3wu4Q&FULJUS6etReePV|#P?Qs=N@u@Rk|iSo6O`3&s5vAJS(GXM+bT!QBT9G z?MP4psF_1Xf|mM-^)>6K%}8e; z`(w)J#j7%B0S2${S1~k3Bcc6*!U74wu`yWqq)Ef~N5s$FE_So=;ofiQSzQ>{0Y^%5 zNAXbpA*QVXT)wK&w1&|BKJ(i?m@MQFSl$^HsHRRjKwbgtvQ1(TLpIz%0$)K$ta;sm zy-qa{8K8$c+Dyx1DjlK#?jMIQ1TdPR*4vVN#0m!wuu+Egq&eTPayR`gyMd9TAsl}} zGx^fe1Ao(n!~MZ%3$j57iX4L_EvIB_9G zdPcVdUD47ZFuGQ^#hcK1;2V1|wsEe2z6I#?#6f-^f&`fsR)xg57UYjsCHaCxWD!H0 z2V5+a8V#R^(;OihW48wa9+a(@jEO#gwHu@szOApUx1cXhKc8%ntSpIfz+#NZ===OL zUw^*3K$X0V*BOchDJ_DgPgyrgSEDMa8e~cDj1w1runTJk*VelUQxjm#TmjmMuo&L@ zTr%l6RIxx340#gDlsaEUF>_)qGH$YVqPSrW zLlJvSweQ+86(m`NI;04(yt45WWT-OYxI+BP((K~R-`&2u3FOPe6Ck48>k#>fe-ykm0n2rN?^f{5ty0bw z-mdH64iFPc%}LM?)=$+B(f8Y7?w2ENi}j6dM3yQ3eq1#}nooqyKb!A1(^Mx_$FX9x zLXRiHn)f;zciewRaMXSWWkhPEu}`s&yN|tJJ0cI>L$CY9D36sN9v9vk9(Buf&^)<2 zxyn*$rv2GQnea?yP34(DoM4>rB_XOrQpsi>bv~oSszhdv-ki)_&wSbZb)L2~VzO}3 zC37-Ehv{?gE$F1GCB(hy#r(eag!?28I~BVF8w*&Qu+y z9#nl_{jgq}J|X2|OwpLMHjF*HyyCo&<~In|OS#JpifUC&a;0l0P_$xcHCXu?H5(ON z_+=#MROxgo%h>mc*1!Q{}4X zat^Q=G}wh+^XzJnRfS}T#fn7?ODQbPZ7AxtOx8`pS{#^?F_kj64(3#?si~C-FZ^Qf z*NFc1Mw@_D+2^~kZVvo{Ns|NGexVv*F4hc(RS0s zb@(m>xd(amtXSb@hS~(>1l&X}3@pYK#vBc%{j}Y76Rv#>ts!l=j;n3v;A-}(~w@_W-OHG%{}j7ZpEd|})n_9TpUa8jj8j7nu=?4$mr`DGyw2Tur3 z1P@Awb4RHc-ILxs@VoQN`t9P?;mhKy3@96D8PpMU9;h&=G6W9{016q*3ADHSOLyx} z0=gL;63sbuVE#7&=KwA!W>_t>7Y9t*Z%k!mXKqbOuHLRLpfvhIf&zW5qESOLLn2`j z=o08I!owo{Lh~Y0LLNd%LJ~=;-_&aNgu~Ut2@AN<32@LnNSy79A7|Djf_Be#pm#55 z88s7Y&sOZ~_lrj-E24F})$FM7aR~4>5kEsvdW!cYEADifUG_m2<647WV-E+Y4Hgn&HU@0>Sp;# z^+KztwHUd{+v$3la_I$HM$G0flu&ZMPsd17IH(!7CP#iq-iwBlq>=im!PJ`9Y?z1` zWsS&HacbrC9 z5n72ZUyTZnMi}92T8D1T$^DY6>d-ZZ+-SzAADxm8WkSJPges@T#h-ygfl zxoK=wIqOi?)3DP;FpDybssj|&w2N1oi_90EepM_F6*5<%{gi5H1W~#5j+>Yj+qn*5Xm3xiE^6foLgiq zVvT3%iHIGj-nARFdEdGh@IWa&mSxOsegb79V!z%Qd?L8agymXx3-u~FCV$U*XqZwD zRnJ~nTL@{$V=?oP^w>Q9F`I!mfk(re_UU;S{*aPdJ7OX+88d;Fp2nW#;(fzE5O5t9 zf)T`_;>godw>OhfQB|=q>YG%lecfzdyxRWyrj%4_Qo*jp>6PKtejfN9v4G#2nW%H- zZFXFLY1MvgTl=24TOQc~=5_d@^-8z#(Bicp`I&(O5(fTqLEGW+slCR1?6vVe4N;7s z%{%7K_HO!aaJHL64lP5KS;AZXKL506T-<(l_G?jhvNqTxDzHg_?la-JQl=MdvUrkO zxK8*)q+Db&{5gExz51^IYBW4)b~m7t@}uD~VY)QS+vT<0M1Q#N%3}I7g;#u|sDu4w z;lb+A@Mv$=#q6@6Fm$7|gZIJrJ^R(*ZZPKfOy)@@AuEE{#S`l7#+A&<_+azV|MvCY zh5d)Ne?ERU>LV0BJGR zv&TD5D5{JEV>DF&Zn}SKalzfTxNN#n4n2;mHGPpm80WSac`-(PTpBH&voRbAU5YB{jCPU?m$0+6!8&0K6aaq(O?k=wV^VY1o5ez}98GZJ|9I7(-DDuc7> zKFTu}_q3irG)GoG67KOse>NbtXR$*f+VczoRhn z&{N~ZYE^dj9HOXRiYE17U*R2}g$`@s%GW@0;sq_xx3AHx~OY83Jn)AlsCBE6eTgu0A9jQ7q7u6lt| zZ&5%^ zi2Aom8KYo*8=(ljV&&xso~q&gk-4;ICMbGDEVvtwxT~cUgxXi)Of%}3us>|A9U=;^ z_nHwZ_PZ!SpzFhlzG!G(q6bB~`cEyYL!m%0Z#uTw)2fGBl(|aWOQ*xc759<=BOgl8 zvyL)>%XM7*lB&D!>tk_^~XUV(<#v155#nAWZ zT_jbshuT@d=IVtbS+i9uScAeWW;AO# z@4(%-OKy0wLiWazVQZW(s#W8$IDkzt7oMy6bT|t~u-#Q-C%ZM)kPtV^Rat1eg`2A= zD*6I4pU@Gr1#+e$M-*}bg{ELq)SJt2zjVrtt)b((ZJVQXY5i{Et( zNRdxh9HL@;&J^}cqGBigl!WTf9uSMj=t)y%MOK6r0Y6?A$0D1W26~I9gkupJ&~a?y z&SuH|{WoFE8X}jY;KTDdAUU#N5J76b1?vxd%G7^T>Wp1k*P?mt=h9|nAp2HmD4RA^ zXSv8LnZlpDMJuxUH53@E{**gYHUNUI=M&AKVylYrAuaQ~HKJdFN?x@n5sDmVw5!j; zJZAVnP;!=~UPWt z=SaM8*yP>3%#7sH#9X(38Rj@iL!90Ltl7f9^44%+nc=)?g@QL1^A(C3_{P#iskFP4 zGUGJtxUp{d8B@)N23n3JnBaCag2duI)Z&3n6`I!*J^nNN8`ySkj87f;wJnib+xf+X z(LG`^C(73j-AMu!uc-C>@6P)q;7y7+^v5XJ(x`>b#Vmo4o!O!GHYt_gC5nI)2zd&g zjr-W}OsQ4_Ncxd7h;iwMe3jJ%ls%3sp2j*9f`i(~mqeXO4I2VIyGWYssEWD=4Qujc zU2jte0nEjMgMLX%3ek-ejqnNz2VNDhtNOdef*T5@lE#T?WN}BXdzT`Ubu@m`FDH@? zN!KSDTzVz;rH`GRhLnayxug@07n%?1=5NJS*Ymqvb6NgsSei%(Y(Z8aeS${dk>U)* z5lhtu!O)cU#94-@4EM?|L?;m1|%pTKoW3F*vW%joK1 zZAD28RIf3!1H5=t*3)ulg#`+pxPOE>osHFl$|#l<^JXp&87J~lk*pjZUs#IIdN#f( z>9U&N%8@JXDpTDEz^sXOESb1&3V~EmVo8euMj3`>kvAO=2977j2PVrn`_PUD;Q!`YA)Ra%bSo+dhA>o$&Ee@Gjn z*H1+i4vQ#6e*2|n1gKqON)a67#AiNN%7{GcyO{axT@6mfS`qaMw&YQ`+DdioT`kNx zs4Ns{4U!`R?2+{Q5gd4hd`nFW^S~nQH+v&cyvt36xP`(<&Bt!E0WNX;vVGb_&ZXq6 zV|V53a=bVKGh#8A(5e1>5qT6~Z!G;NL#$Jv6+sr&sx=_Ae`~K+vxbCZ)@Q#G`SLzu z^G$-^jR?b|{RU?=wrJbdm(B>3OVB%DzcDd@gB0s0Ni;4Yc(cd2%YM}<99W{H7}UaoJb1zMH!&6@2ip?ub?j@|rgNaT`@>5%A)gHLrJ!co+KEBFLI-`&lFJ zE7!Gl-`lQhn;A_Q^(~oCwS9Tv90BGgm%@zw9s`ivJ9~bT%*-T>Grd!@xzp}R+7eSm zGPc($iAg2swZ=nI(FU&w)e_;D8&*00p3t>s&86t-ZFulol}E%4?v_3LiO&HMjCBEb z$6oHYgP2%955kurpO0yy$Er*p4&H}(18>a@ut!;{$)m4XS&C)n)9>Tl@7HTJZrnkl zq{QG&Q{M2SI!_=IXJu z=|yn^ZqOWa*H>%es!JC#W`LL`;r6n%M(toOY&<73wK>e4jgUcR8v>oQF?ZF zXyAR+P*p(jR=&{-s#*S5IitF=G|Nv~Igkme03sX#2nl+tsp-k84&jL|NVA z_j>VPNgLuetX!)~DkGN#H@u%XaBYZ||88=IzsJ=7N$kz=kAV7jbp6lV`!BKoJ^21> z#Q#6ZH~WA3^1wvT_+Kj;QMh9NdU*hsvhr3Tv+Y@dK@Lg~SpgX>?uS;x&+CWy^vXP{ z2rj$8npBM9D;sdIRdO^IG&J^XLsH{yzFd=;;N(raI>AlD%xPo@8s}^)p2jbbsxzXCn#gsCUK=7%pJ0zEjwN-EpB@iTc4514 zpsQ}0OkWL}rR?yagXr0Ob+Nx2^wU|1^(qp%IcFbUIyouq<=)l5aZ@|!L3hXSgf1Ts zfrt#Yiu+RSPlW)H1mGa?exSw3l5c>TeX(9!TAmG7Ow3niTi(<3q7!UB=tYAR@k;IExVa?>3L^6xZ}0BxwzQya&W;| zo@ymnAE(=BX+^kL<=lRs`T(~=e(f!nr#O5lWMal)v5F+EmaXsX;(67avdfpOGIz2z zTUIqY!g4TLF&}*CsOI@vd`+2TW}sa9EtgP&8L*-nY^R>YVAU5QbHPBr`bFz1#TN;- zqo->weXeV%B?&v(Lsxf}85}*4o~}cQnf{R!By9rjEQu3tOiy>mul?Z&`2yX;+H&I; zsU|(*f=9-6w~!|F7`p`5ofCZZ0^jzWK#GTHU#`;}s_u%o6fI>TpW6QRnIHO;UWg&= z?%pWw4I*oPg6+~#U2NdWh_P&7RR$SL!HN2WOn+R^T*Fg^5$Z!C+h@#MP~>#-o(fs}JItt6IE?H2eeC`kNi7Nk;VdvAtY zXq`jNV-n<@`zt?y!k+qn(OA)L#Dz+vVzdny{j`V>3alM~M5l*tL$}LC4QJU$4_@lH zjk5=gP$6_;=0bBs4}s_I9HMgGs@TP%$)JWPm&DHm?u;DSJBh9yWBM(&>@B9OF>(q~ zP+vG7I{-D^w|88hZsT}(^25U?b@_{@P@A}{$Xtiu<`R;9{|;nTKTqlG+G~y`hP*gU z1wJX;8f0Kzt+fAC#+bL(Ra=iE-G>!ka72U#;GY<$50Xgmj2=P?2f`G|2OZqp#u%{239$2aCq~3u;HPm|?ok)wrA)mXuU3;tB`hX4&8u+9J-z2T~>vO)AO?o$WmX_S!(@)mTdj zgceKzMaaS22@TzDH-5}XhZOJ z;LbtP90PO8Ri!W~sKYvOvramWG$wjy^>Yd-HPxaFUcjv$qs0W)fgxIc&Wz=02In7$ zap0R61S4VF6?by-rV21KC*R_YiNk0rH6 zjanSN-NB9lei&Zc$jAy|O!{+k_-krfnv`$e{xufA40D*IXV{(qTN3e;d6yPA9U<`o zobevrgTD+-xVOV=9mgAzToP6}bJCx1(}y}#Ue^jByocMm(lbHp&m>Yhv0mSQu)o+_ zBvHgS8z0yon2%3##2>E4`r`Y4@s#?-V%(7jC&qYi!#c}o()zEacuVtmGD_hhrvX?cCE!)bc`YHb}Z zCSjG>Z9C|>ycO=^03sCSnfE5G^*OFh^6=|Z;MV2E1zlk!WDgP%E}vnZjF1=H8l4gc z-cMqcR80O$hO22QOST*)(FEZq=G1X+F9!vbMfF=o~}sE;Ss*ydWfE|`5|fq#Q~|*A@cY>-#q)fkU1Xr zl{)y{{R3&gfng-(&$Pr@80X_P8s=LaBFlegoS~)1`bj1XzE+D-Qg|YLbXD<+xPuD} z;pm z@oM$uEi8QdRn7jW8MRPzC`Zc-OiE*6@+{aB)p!I8hgK>fmPt^Djhp!a+ zg1!iOWkj`{k{fS!^q&{J=9e&S|BXEVR2u%p#Q#K|fPeNL{+B%eaPRN({A$&17I_ObacS!y<9scl z>T1w&DRQUTb*c{q*vJ?jq|iZMJ^y^=y9@ zdK3&*Mx|xX`gya1c{Kg-vHi5$PSb_Q<1#{i0xq1Jt%xv~N=MgDTXqzy;{hor+|h~vXBQu~Lv4M#EH`U{D4FYZ z4>!U$pHFH0_w8;vt6`owzo!R9!>X3oVF!>Io!9S_G@)s%Px`@D($d$Ih_bgu!lY- zuh|_g{X9$WrY1X;?(%VfcLmpGV(M9 zTLiG1gama=5G`vV_y)&GPMAU%-z?tVZrpU}UgK zLGJN@{(pE=Rc3&@g%qLE zdMMSfCOK%bGU z(sRdf-+JrkQ^|{zIO2FTm)1M>?`tpGn@`VI5!^gP2>(az$!~A+{llB6*A0rDdMyD*shj~RUy8ew4N`}dN0VWP z$_5h|p>7o^<5D)v%V(pHE}S7W(_a4W&9*<@B>e5o<=@@}o|L~@H zY5Z?*x}#*z1RBP6^9~jzk!BT4tG@~vj}qjQHxB*Zq1q*UTSBwI zgQsVR1`1de)x<*(iKr>H#U3p zO~QZlfuPAuC7g*5abX{|i$UJRi*y@sQKgB8-l+}&K*UH}@bOJ^b@k=cp+P7av%I$e z%kES$ZY2rBk`4lZkP4AfeR8|%;+Blu5Yx$C?wKN2r!Z;L!n!lmbDu>%%8qUf5Dx6Z1Lw09Y+V(*t3bNBn`)7)v%*UWust|bWHQFv!I`WxS>9YF|KZhE`ZTnEE zX;`ATmy6;lFGOR0Uv2~Lts>bXCjJ22xM~bC_ai!WBYsP!SaP9yiX@r};W`wq+xprC zMS*NX5(>y`^>m`ZgU`?WJVc7JuMa|DRY1(u1Y$o%(D#2D}vyvev;O}0%1 z1nDopmJAYc;y!38UWRNG`${-0@yCk=nXJlSdN>KjUZ_J*P1$C>ZKR(}bnY ze}ZLcvAwzfmTHk54;=>Z?OR7tkn=zw zFF%@RtvU`(cEfBrek*d+f%z~_e*|NDu`f)b$ZhsNIQgwqnU^G!d*nI4DW9qI(LQJH zC&s~8AUZ`u`)`#XKEI#a?RXW$hRl5rEB9Z!Y4B*A> z&cD**>&tIMm3 z%dN-f!g}DIb>2r)KA7Dt&a=jvR6^(c`q2-!cdwYv<@GU}!d_S6Ynt92jyF%t-p_-J z4@b{uLW=ER&{t)Owqg+Rd;ZGd@?WF!Kk&6x$lq(7#?T4%dglmoNJh(bX<5kAi_ke zKZ`(V)sFP<)Lox_t(+>BOWWG+{9(10pgU+*3wOyIXJv+@uY#GUaF4$(VcU~@Wxik! z%gro!tsb56g0zG8&A{cIS&J5^rc>NlbNzlPm&6M3^vn;4dD(jp@5p$}D|4d_z;fbJ z`P~+SBvrXt(+I&q>{-nwx&!9pcHkY8-wbXBin8}}x~f+B*?8DxhVVu>o)uCapcze= z@82ny#!%t3UEQjx9SUbcKG!JHM{T}JO9m@;Hio_uCF0yQO zY=>ktc@oXq`F@l8%R3zh3Fj+xn~G6J>iYKWfeXmnst?FdGW3dn=PBS%v-V$>{EvA0 zUkky1`S5?^>EE-~|NA^;{o4ink33~&qyH~FmG~2e4qo0<{%KN3vTq`MKdb50j%F+4 zLWUSK>w_-0{2hk+czMey#i*?_S}%sH@MFKyuTQI84}}l%5}%6PW;{=;8kmgo9$Hz` zq$aLt)`(4~etwL;t-lSnb-Ztly+iCKeiB@`33_{VYH+mA(iQ$r_H{oe-c~(3A-&Vj zL08+Le~@Rf1WqdtHsbY4BLzBF3^$nXJ~6>zbF=~m<-Hgxp4lOAraEWa0up~-n?=%c z+O*X=P|l}B8{0@oC={-xVID51n;9tGIKYsKEcIuv z2saamJEfj*vr!tUx^$j7NbsaYrLQ8KQf_p_fNX!JX|TWSZ%NQhx3opuNQ-5ZtMjgR znq(lbBm&4Arm#)?N}b?6w}O{g)VAVAZ?-r5gW-L%%0q?&5nn-OAKD!ZOcv3I5@H$C zUwRo<6Ju}IJM42GhVW0=B$?LmJ5Gm zpZG+2H85oF3a98Q-D(OfyA@j?x$IYuN8P_pymE+T$TUlGHm(>tLDwkeO5+4NN~dAX zN~ibjAwisS{sPZp!yMj%!IfMW!wWC>sPIdHfM?7135uo5=W?c~*)u0sTXC}p- z{3{*YC{CmbAypt~rx-IMPO3IcU=5X}=Uuumf@w~f#Sh?*=U9DRv|_)bPe&RQlXoDG z!#?IAJ~)l~5Voy1fq}ds((_OTEsDddT%Zj)l#fo z1CJvXsfDv*qDna(%t|%Qp+7fTV|L(4e*eXU8SO^V2fr7Ws&|?IJ}gF(#v~D=mtvs5GPBz@n^W6yU$YTsQn4V z!{Q0@E3%qc85%)#es&D5R%D%HEBMQl05t$>{y6;FLS*0ooroze6cjrf7Yc^B*+ly9 zB;u$Yh>ZRRh~@ma69Q=O0UF=Q!Va2W;plh|gQ5;pDDwyp4lI%KmZdcw88U1}!4?q- zG=F%#06Yp{pHP+<)gQ8gx-Wqogu)7%d5m3wnByzynJu5r9G|Vp98zE?&Jd_w$(-uE zl^pI2&GIlOChYsP^?2AfWTMrSvNywDucb)P7+#V;th4zsR=ZH*KNRzc9iiWq8gwI( zafyI+(U|18i1>cjl})KT5TSL;Nn$N*ne@ESY>@^bb|}7*meuT~Sb@!dC8e(cqVNren zR&{cUzc*0C6i^WBOM~YeR2jbr=_$ktRwtieR_zeplnyMQZp*a*lFeBtjn(8(Chy3> zW(B0&fByYqN^H@wo-<7vs|Ws|vKqpCG$gDYH5w=6f<)>LYDlY!a{Uz#Zs<_`p;5)A zGO+VGRg|B=2ysWgVp%0nII>tFyQ(=?09mEjHfc%CQQvr5}UJ+=Y;aXBR zo<8I+-b4Uj(dxjx#*xY;FFe(6y%~&sACSR?o|96*Ec7fCdAq)}K%+AfYzUfAdMNg| zeHhUexvN}5Px?|ZRg?ZyUrDdvqk9;j^lcy_jQc>yCCOB^@^4&E4-lt0Ss}N=`*PBb-UkpKCh(JhXQfmxubFkB&*bGh!byuC@3=~v*+=O~ zR45#R{uIyzw<~Rv-e#XKnN_qi%1e~wG=t!lR4eu>R6EQ6ZkURtZee3h4B}xdFBr4N zShdVFVXwnqfv>YSpoar_cs{UA39|jYH_qWClg!odtJ7!j4>=CN3*CL4C^;H-NQ1)n zkva`cj6CTyii$1Us@vcB>iY7d(mEdp`F(crs6r^A!=EExPy7j39j9jRhV?d|6Bh1( zPmBy{El^zIo3OVoY}?#iwarJY3yxBt+7`YL1-??;Vc8jOkH>?WT|dLyThyOx|OqW=Yb^G?Ic>z!Wh&L6i?TB`&0H60?8h`Q@a zKJSsZn`!(IUVt#8ha&XnN_n1iq*2GD7vjT|&?s^5CLhHZi98y7Q)u?Cj_!5kY)qI5vp50~f1<8%0FKH1?Q-kAk z7g^_=OgfThT|DeqERK0vR>b(bnlUW!7)$DzmBkqRCYH{%m3V=hKg+$zVxy-07IFA7 zu#Jx5E$edYtw$Y%lsiUonR7^dfrFdWZbrzdh%tJkIL&3>U6e^Bhie5dH>+{raLD*t zhA*&ZZXYK$=0PdVC@5&{QM^Rrqc?tbHcSB`nm&&JWAl;{w#|p*%7>bnC|5^Eu9z>r z(EgBiQD(`jyke@;p1_LJ2C6y_z&ze#;3J?QVGfF3n3;C{`F9?gV>(m{XZ5+3TW8)Z zV>I7V*#z@Ra4p+BelQU<>`weVpa0L3H`~imEg=)W+OSCs?(_;E0C?jx1+RnRiLSML&b|O9^J%F1Jh7E6$D&BH z7(qWz9rInv;-=2&+ zGW{!UccKnu4jnzLKFlsYS~|j=wDSUe4J-3Kt7=iUDaoJm!LecY?vv>lujo#_HEX8u zYNxIzHo!F?W3mRg;H};7-a54ArBAc&zwbG+ZQo0FcJ$}8 z*$VtOJpNai_fJpvKjQIUE7<-ldHZK?0Px4B{~M409ti(m=P~2o*ZcjCJZ5D4pVC{9 z)U~${AZb<2KRTYlr!PdOHXJ9U;d#2e6;$z7Tnt~`uU7tbjk|`HfrgdxEZm0^^B(u2 z{hk*NShzsG7;Z=8fDLQ+;+znCLTOAyq+Rx_Ir1Egh z`QUq(cz4Zwa;t|ipXK<%@jQnLuJBC;CqRH0I)Zk3PQT}^P7;e{wM>APmRci2dc*aM z0ALa=RH-@FO?DbzD}1O~b|c3bXQ3Imm&ZR6uq+v= z5Hrpfw9}IeKbyM86oFl1{Wv#A>fls(%d>ct^U~Msr7ClM&dHAWvC*E8%S+%%%Z`~g zvC}FsExW4HnU0Tj*apRZI)sjG6w#P(I{A;L0PPA1f%FK3<&~$EV(`E#y|(Jg?bN_T z#aGWUc2cH@Bmn^PKt&_BU6IvDz8EadlJ>ePrN%Y)SU1&3RG{jk%1d zbgNjUk7w<=tiiI+TC&^=f_az6$4I#LPr3EC9p!Z&S)XQ%b(w2cqp&T6#v2yxoyt)T z%O$4s9FuH3pF}SfB7K9nZ$(sM7+I_o^~^vj+7@gVlQ?&9au7O;HI};b2iV95C+Kj? znnP1_iPe?`C|O*EMGV1OM&`Fe?>uT^yKGSZ>6-X-*+8Sj;@>qA%8PfQAH-nVrg~B1^9eYgrND*UNch3R90_wIAus?h@T+1El-ZA=4)UQse zCvjxub|fl@X{*^?Wt`5qN>7##oRi$xSMX%MhSms4bfPI{LxhI;9lbHRM$?fgP!hL& zf-M5NZ(%P7)+7X;ik{?XWJhQNei=EGR5xuzMQb*??%6hsok}0X&5|lq*h9swQ;J;7 zEFMA&JWsf~K2GIAOIrGIWd@r}d5L$@&4P;Py{gr=<=-zh>zW|3_N+1{2A1+u>g z#e_8mCmp2+6rw7+eCC%&7Zx^E+g>>%Re^L`312Q<#TII5lGikrC&kpVB7{^QR$y5i z1c4YNQ9K`eyi6X5Oz07bpsUtEB`(shQFNU)=@3)~RHyDhjBq|bPy``Ay}M&FM!gp6 zIb7z28Mh(TVdDp{{?&_MacI~d(-`Dzb zPQDzv8w`Dc`=;zpTsVRvhmqp!T~Uhq)UbO4tD6)6bpx!04NPM+VlUlaL6T&aV9a-| z`G`chSz%D5pQmx0J9!gZrX`{T39$eO3P2EK9q*Yje>5qo4rw<=q)hjfRH%%7^nkEU%pbn<}c>j>% zIWg}5T{RDJfL$fd4{alBGP9As_ECVo$d7q^9KBEd)BsDLR1c*UQQxalaAeIYcv^e{ zolZ_`jToVj{PArj`W2obonUjqe0y+mY^*a5%heSOJDZC#JbooM;#{Xli*0ix$0aIp zd0Z3%j=miB<}lK8uEMKN{qyd$X&%G66n-GrR!^~(gFNgS zjd?AYpwLTBPiMm%rxfIkzC}%9FKVSem6gMtg0++xPO6Y`v(@A@p1$~MA$@oE+ARG1 zgZ`JEDY@>A=#%cSdm9K%KKf0&Z5UNCKP0Li@qFQ1F2UC0vC{@i7pi6l!8##PlH39m zf=Mdp6wFE15YDVxvz0C+GKhi?hp=6)7F*QG&J410#!HQ5qB2)CiGy}~wU9s#owiay zp6cQ%uEJi(m&DOmYQ)JGggO~BIijz{V>5wx?P{Ukh*ByU!c~gk)iZO`@9N|!Pl{gKxddEUq}jO*rNa7 z0%n+n7o50+$#1Q40SG~$_xyChxU`A1Ub{`*zMA$1#8U*zow(wpnPKWujdfBLk=~dfCME}jhP`%_qzX_}2 zwg)?_ob@;6`GR_7e;^U5hePA>T_xX4s@#s{c$+MPeNPl-_LILhNa+Yb6C%s?Row$0 zeF#Szyp468R@OeugETuh9rc_yav_3q&E1r^baUx?>|M0GimGLf!hL3(`aTQY82guG zT@27d_GGueeT4n%RtjD2`oCmLN;x0R7Agk{BIf z|L*Dh8fy?Tk-#|D?0u@G#kPFJ`e9`rIU zmKh{+(K|HExr6<6&G=V*6xKnN7;QX!7BbC~#t#owp=|2|CopYqYbFR~2(ESQk5*7;?b z3$^v0@jI2A08;`6{im3TjOP7(Vf0SIf$U?82{Jkt;7w-3h_4bIK_5rv&6 znL+J^=vQs}UIIS?E4i43Wy{8(Rf=FN@$4?$gwMkezt_^wr^UxOa1FvibCa^8loykQ z&-vYm9HiI^gn5%J8@|3mpFK!a3S0W-qm%V*Nv|W-ziKAtBff6Z#`zP|$vFtTibYNT znKI+MJH2pv8ErF@&;+scF0I6ux@zgtKy^4Q3xWO-C5JrVGBEK6m`jAQHCe-}<712A zxi@DObjW||_Ufjk{fcg_Yf zUPx%=*~>k*1J{J$`}h5JXR#kmJ3H`RVP|d++vIf-s<3Eo?V?Bc++O*EhtF%$lQW!l zHB#D<5u`zDZzL`HnkOGt{EhM5IuV*NQhfQseS7&*eC>=bjwAWDh1;huF3m5w^2XN# zI2V+A+*RbA{W9E^ZfiC{Yng%4w#Y*9S-dFg<9jS0~nIh~IWjyW@IUSks>_=?-xtcOH2=p5xF{ zZ>ZT0yynFmj$3XVG$+6|hX}C42F@wZsgS&P)(S6(XzkNUvK0J*PEGr2>8s$kpXM&K zu$$J|A&q)pMB`?39W)6|cPqH~3Gm0kPfm8T^h@mSL5hmutH_z-u)EZl^>Z#&(t zYS4uVS}NK~$Jv+HPJTWA)K}FWBl<7=(mzkd{7)WVIsS9nJI8Ow@E`orKR*%wKlMw0 zXB_=jeBmME_%qSzpVeRfgW?P5fBGy70{us2!g7^inzt;);rj4*8q#;WGj+_OY;YT8=O!vHGy?YcsPVgynVU*JHebo4O3tSv`h@f%tEH zRyI(44DZj$lF*}e1V)ypO77K47t>>eI$adjU2nhK@2n43ey%Jg6_)iOh7K6+vVyy9 zoPt{DPh94Sy3XD^rj)E^3&N0yX&^6kk3`2Q=u8OY75yHqoRT0=+H=3Jc=LTO1?=A1 zud!dM0W0h8GYW|JnXYLNXfXnGT3k2hzrItyaR^&e6xveBhq`!UWZ(HY90tp<_Nk{4 zg9hco+50<*2Jn$`IHC$x!n}q!Doa0f<>sDYD#m95#I(ol|nX+r(uh`(cnzb{Y5 zqN$}#26~xZeJSKif&MR+JNw^66`j9J^KVVr{+`BtIq9E8{kNtZeu}=gXKU_X}T&{RM)c7h4W68yPnk zOa=nKG!76L`9GlVpSzR4lw~Job1F{l=e!O}3nXWd4?UB@7xKia(I~FZptX2LO{0PDJ)mxyg_RNCtvU+-a)@# zEGhgi9PZx@;~zudzp?iZl>Lc1c?0Vg-L-$gqm_Z#Zw_uQ0K5fnT1O4D2LL zEX>TEUZD80Q^m=|TK)GCntyIZ264SyO~l3Q4-~&Zk?qBq8o2$r#>T<^;yQnq{$(WG zgq;lZpU39^*zk8x@FyDCUSQ10^>Wx>;ACUtc`5&Y3(r|E_jm7RAAdI8jJ2`yp`xguGmwGOj+7rNosB9(+)RvlOnrG@3 zwd+1uS=G(boeA!xfdjwa-9MiDtd19+8fUm?)Oeo?tktX$RGUoC*%(p@h>>IQ&bwHV z{ZMnEFRFH2!X-sVU_eq5syFN&s^d>XU`No97}M|UHd(`nvC*7ene6R;WuRP*jKL3- zgNf9sa~Xb3!ki1AAU5aPH$ChVb{1Gi%(ppDYfJe@_~8a zxn;woVfe+wj${rKr!*Thk>0`Rmy(7cgUOcDB9?YBork#(QxBy9`(fJ~#=^BLN6hOY z2-c%P^@u1A9kEJ36eVyU!!Q%z8T6g9$A;VmsY)gEGUz*bj|+J{Qk7aL6T}MFFc&}+ zsL9g9OFoB;fw9R?K8IMP9jXQ?!!`^8tU#QQCh2<|$aw?J@eGpzGC}58hMxdoLFPC) z;-RMGV>pJ<00Z)rKutQaTmTwm2Bk_k^cQ3V%kV3}88U;cMA`F(+!RHLNh}@^4^c&A zlL<|R1P0RK83qIP5ZQD?Eg-TuIkKV3$Maw~{tP$gnCS)dkTEI@)B6|x^Rfs~B{p$ybUj0b!MR0CoFRLpr80mLW+udox+n2Pe=4*cDPg-{{n^RIwY#PfQ9Ez)@bz!vcwfm{!7lapKzXA_6qhoxs2 z@*p3Y80e-HiWlgn7rKUgPDJj*)Z+-@XYApCJV=M?Bilnl^%2i&0SZX=??P#V+#sR- zL2k04Z-aPfdX&iFgLo)xBk((Ij~@9? z#44RoUC0mYO)qj6#46oTYlsz&VHhAa=sQ)91UVOCm0qYbqzs3GqQ@OlhGAF-C=Jx4 z??EPi$*;!DQ3>6FI3Z3l_pp;EAWzctV3OmaOfrat0Y)Kdj6D?OT@Vmbm13v_`C*VI zS&tX_F0vA>SUG?bGJ;`P5156hA}P^}i3_bTp%3BpO_{k>nSIIsCVcO{|55W!HPFM& zoV2jD$_b0&=4`@7{u|Hnwv!Lgnx^_kXug_Xz2jn^1*5`JmuM>3dNZ2H$f8E^WhC@E zZSEXs2^Qx`$CD|CjB%!^J8Jh@P#5A<%}5&`>}E5|r>7-29<-R%l-`OP z8tkU0PGBb;liuC_k`EN7`=mk<>16M4A&EfCM@yto<4}Dz@JilPo+iU|+ju>1&w^K8 zTRuh8LE!7xFVx(As%h9H^p{aRpIBogc+hHqQvh`D{sSHPLia^({wvNxU;8`S!V1F^ zn$xT+laW@y6*|csUm*)m`lf~NR`xhJefx?);XZDfmj9w6O>p-LqY&Laq5W(Ef&a>k zEMcP}peGGYYN5b_PQW@aee50zK-Yh|RC3$pvkEm(bw39ujq-pOe zv5~u}*5#pb@Jjde$*vP#As=|9^T0*E!JfD0K;7mCuFa0AJokAm5qPEJz(ZbW3-0Sy zwk24%``|-{3r@p@ioDQ_<+CriWUX=xubMz?btd0l=p+QOYQ zUYj3IdJm1{59i?6 zXo)K?jmBWJ+}X?o&6&rQf5W&(0#dS(4Nfz?95_sJWSJ^49Fq}_XWJ806dzfDO~I+r z2rzX(wrAJ_QJVI*3G;@86!cp$ADhERil|I2;pBELVtH`- zt=vO_n_${Lt6D=WLoQx&90!K=-pgM&7d&uHRp)6AO;s-_PlHLTHsm_Yw@-UmHl=Au z?u_`=h{ICoq{keg!a$2&h%G+hy$$%qe|oh0Y4xSsPoZ4Z7=b=L^-s+9HGA!$ z52N==Px$UK^uFA^{Dym?J=o{Ys(!MDyRU`bfq;K?yI*Uef1%&RfW4O> z53xZJ|v=VNw7b346w=_eA6Ly`O!< zjn5Xzslk@ssdqqhr8iLhy(sdfF)Y_IAh*ufZY~pFG;*G0kn`ax;*sPIkk!=NA=w-QP zZ4Vc)SMpMDSFToRlC5G;11S6Nx!tEqJF{0(N9GPAHm*0&Lgqu}%QcDg0*Kz}HnX=J zo(-LSp89Hcv+>o=?BK%gt%awXmW7Vn!AAXA%+%c3W_S5U!qo0r(?EMsE&Ut8W$c$! z2!IX$GC)s}SBRhV{8IX;%MpD($Wmr6(DL;-(l~SZYmMv+s@ar78MU%2_}#)y`Ovp1 zoSj$~EqmY{B@Bfb$K~AbO!TYR;Q1J!8c;G}j`mchjY6xcQQv8p+|@>}j{2ZCPl)D` zNoog?dIwiER?ISoMah1$nR?7J$N5MBSh3A-&t6)}JxN+r-$)fUd=yjqv>U@r^Laa# zib6}|^J^1$s>B9u_cuzn)!~v;@p<%`+(ncT&PuwEn?+6*AWQN`RL)un&O`~RMrV7+ zOsdDme1#asl$^2VV0DtV{dJKTu2SZd`bu!Y8{Rms(tPGcQD?fIE5zAXTG}6FxNTS7 zSiMdcwK`ppU%;M^ox>}y?xoNIQsRP*J4baeTaefv@ud_YNLYocIwN{DvO*Fg(uM=! z_=iJQD&8t}z5AR)8xC7Axo_ALG*m;ve_@q8muEcIW;_gaGRbfwkT<+qAj-zIU_{#t z!fxTV6-j!6C6_fHLT`8D+SaKjU@;9&ecqsWCe4$4CSBh0{l)YX12uX#nerm&miZE@ zlC%+OANnV>r@ugl&3n8`EFRMD&{hcb&^rE19j5OoE1@;f%V6hVFxL6sqg4`W!j-*J zhguQFn?M30@cOyDC#b|WhfRPA^H7w!tCqtF@9nla6Hfm?-8v0a4MmY z{0LZ~B|2Vii8u=m9Trwt2mpKMM|TD|BRhk#6SBi^L~q2q1>2$9aoge9(b^%{@!1jC zAvWSP!Zi{!A~j+(LN}7!64(*jG1;NnaoXXWsjSm>#CPCyMEVDG>~xq2vz$?`E3ad& z8?IAKan`(T1|0r(zcW@P#&@VPggIzSe?&WyN_b7QG8lCjMn9V@l>Ju;u*AZAm8hD? zwa_wty~2zWFd*1Hm@q%qiC4f^g#I4yp;<|jP-dX(gwa?LlF(FP)r6@PMVg<63lE-u zE?NGvZSQ=(xTkc|;c8~fw&qGe(;b)PI48KAKvMq zGUS=92D5TwHZ92FS!(U;nf9LX2a;wg1Vg|In+gMo*^o$S|ZV^}Q( zu4pD>#)V*CqAc!yQIItyiY@ihxh(=X^9R+p0h?;tr>6AY9}+|IxZhyuJqHDAN8pTw zR6e?oKs%5(A(p!!HN`z3GX3n%CXLuVq)v<;o*WaR(TF0QLKv5Big_pH1M&I%WXIVQ z{7%Lv*vFo->AjluxEK_rf!?}0{<6|dlJ7S2}LBUzDrlbGG!MF zV^Se^2f{a{Jy@fYBu~ftqK2PcM767}=N_ki&p#vStC&vS9=5^`NVQHA*&=)}QQ((w z+mxOOKjOH~6U=dL+${6>jQOksEN8o(mOD#4L%m+83;njSX4t=rUs^%UZS-n2Sm98u z#3p3-P`#kPZ;d_W1|ah-U|H$}qO&YoggIh#BC#y6y@$Va6WzV-^T3ze&yaCP=I&Sb zF77HC|9}?OeDLi62K&I~b75@=tE*SLtnaeA)y$BRLpc9ul8aa1FUh+W4_IuGlqZ93 zKadi_LNaOE$!Im1Q|>@_a~w(8K5{;yJ~Db#?PB|IKYsxsEYXRBr-Mf^1c$EM6}N9F zSc-+nkc?@*Joir!a!2Ri-aMb>%ab!Zm~S6NA5-cxIu0NgBM}YUPfEjXJMbrKy6#TJ zB;u?3QpmHjUMQF#@tEz)U6*Vuy?w&{(0ZH0l*( zZbLmNBo&v7YlbCB$ks;JdNgfeRxKW<#x!`yERDl^ai)5To;nr<<>Jx-Mr-RKS+8N* zv@R(EzE!?dsTBLi_+`n%>~WiUmOu&e?cT^>p@A;~1#h2N?OX5A5`40uh`iY zyEJz6)DFU=kK0Pjuj|!X)sEwTxhrQ*DsEKlb8YWM3kG0JA>xdl+wR9Ym`chHfYns) zjdM7rhb7|6O1^wG_ZD?j%crHx4eE_XHf1a+4*ihKw01~$lKgJ>Xqa!+^H)|p&=x(V z`9YUhCM8!I0r{Fit+h2#%(~P3Ydd?&w@X;=H{XdzFE!M~1ALY>b+`86@``we4{xr0 z#O*GhVly|s_V+mT7=g6rDiG!J)ZRF&s2&1R)>rL9iu5JmTN8)jMUBBQlNrILBtN5f zGVqab;B|5w_~gLM%I^$PrYMyq!W!;378rchJRe%E5W5=>6ZiWpE1+KMpE z^;erdJG^zRXvgFXWRX&F1gRon9ES4Ml-NSZFI;PLF-wby2vc%{p3syEeQ|HNDBe?* zez@*3RS)b}7f;7gnp!1b!C@+cXgNif=q<8d!51_6ie4%y^Izz#QDqjkC&^MUT?yuV z!ZD8%R?uy$#pb(G|s-x}7uSo`g9Bk1#48veK5QV=PgZ zy9i9L(E3ToR9gqwDD4aJopYkn%q1krW8$@JuF$#wM5AeZ+RHiw@*&b`;W#Sdhv(op z@)PVY|F{-fjtS9#YQUI{SdB&I&K7i3c;FRsv*hP)&3>n^=DL$a#6L_2^8*TY#rCB7Q8pXyS+C!AxL$U!+toJW6s#@t)zTv`P21Y=V4zo`_6QcXJ)?4`v~{$ zU`uUo@mz1pNjG{fORQMWPI)Ru|5r4Y5OnFwt@)m!!z?B^|R?pOLuFYnl(rvD+ z`&ASVSj(3iWg{3*Su(4;iV)SlR3arO$7P(>B$TSTHTTp0AST>@;rS&P@xY*%0k^X& zJpj|do*!$IY6?tu$-_82kM|xAdBc{L7eS4mnhID1DBQ9a;CGehZ+K0wp5hSxp1iYL zXqm(JoYtQ_fHx22K=g&_+?`)5Wf%$$Zt?hZl}c5!eGniw12uUo-K;=*@x~;lJ-Cf$4bQ87O;;)Gl-85 zeq`N~ zc&lvo4PEaosa9y!2p$UTaY*PvUs?sS69+#3%j+ac^Nk5cgNxdr;Li&dHK79BEOpkg zwcT->sWPDF>m?!@b35=g`cOLfBulbyKHg9_x~E*ze<(1B0^Pa3KPluMWzH9Om`PKc z_MeSNwU=EW|BBFIGOPE`J)ovt>|E8(mS1^6Ehf!!;K|^TqIdqQ>p26ba z(MQb5o>fTU=24R)?BsYh`PmWrgeBcQt{6u(FP7Qz{o;nE@1@!#+?@Ur?fBc*l|79O zbCKe5V>ulXZ#&Nh&z$Qf-Zk!ca3%iorNfd9H_aIaajaUVOSn>l0-bOnP^YW=Lp1@V z5&^T4U;9j1OQGJMLJKwYx zp!hzDCrn3<=?Rt9)gf#k=~T4NYs`hwH=>n>FibQR5zFA{MthYN^YJ5gR&au5U_|+I z1;b3YMg3Ms7vJlravjN{5{FG*i)3i^mu-#_rsiJf0EiSo^L_7C{wXs!TZCUkD*{m=OL{Y=?G_8AhYkOcbpWgE; zQ3F2qUyC^totJ74K9)#NWqZk4pU38c#@se4L z;~Th6VRmT!fT<|0y`vv{6puRPcQ$({0(mBh_O?gm+DOKH^z25B2|bPGycHF?OAOJ@ z=^il?D}gQ)B+B4jh-Sv%katv1<2`Rdsh}NM5>zEtXVGZSrl0f57sxA} zCc1hIMu%I~!;ZdEUt%}<*LL&1c;{|^WgBPLgX+OXh;Dp* zv)3_}GMr6U$lg4!SDh|Kb&>I9bX=UnbM#YIIz7%c6t_4YvVv0`H&yvQ~IbeJxtDRaeNm6?B!NmX! zeEUQM6+Of@MSL%EM2=1*EFCJH;E2v-GRr#o2%#F`j5K08dS>Pna)k`+Gc%e2ppEii z9D%gGQgay@ZDca}bY))4gKlZ{dr8XE>qHg?^}<5UG}8!edFQ3eLNhHq=CKsLr~gySvlFKwC!DXxj>P&~!pvq>7K} z#2`b2DeFo)zmK-WC`DLz2|I6Dy;%C34abfuGePtBcxmgr=6B0;6a08pl-0 zRKlwTK0?xc5nlZ7tEC+u1MD&I-+(#ai-jv8KEQ9O(gjJFXdM(Ld#8LiqkB7)Gw3d1 zvC891b|e!!xjiO(Ni?FIGbARf9{UjWEcG1b(`$Q?`okjPXC<%f(D3lFiAhk9wRL?S zJ5gTwcaQ1;b$Yv~jOv3(8n1jjX@aZQ%w>jJ`5}i%5)5Dl%Y7C^x1< z`Ygl&pi8DWHenONPleI|w zsN`{%CZ_OFdNSo|s&-@V%CuEFeO*emHynvqd{QeMA;cw{do5ClAzQyNmh1`YMt`Ou z8@pnOVf0d)>8d*%y#a|-&<(krmKkv;uMr~Y8WeTnKyYck%bx;{G#$%Le)-yB4^6K-X{Nn(U;B4ebcr@vQf|V$}?{Z{XZ$LGtv7j-(OcSXXdY#{dh7wr&q6r8w+3uqS)E)hY@8pizEC)>qKPAdni}~ z{8h$+WeJEm#$Lq>QF5>@a24nAz>0Q=BPcXe$T37c`VbL=zi=p1yUXA?*_09Zu+npL zOCeh|HP_Iw@i;zgQ>%fyMd|a)>#DGDBB%#1YfLle&$;Cn%s``+y(AhYqo=d~mmWgjY8@E43DDT`$KV9;c0 z$SX?*E$hzB%Xb)g6WBboR0_pxAfigU!(#Mz4VH*lc=;Mv`*dZg0>ZMW-nhbieEyui;AvDm|^nvH>MpV$=!zMun&Gn=cu*P7+qa^$7Z`x*S@6DTu`@|tHQ{T zu9T=)`+n=AIKsCA*^?HB^OjMxSfF-*1lZ7d2`jB>A)|n=z42H5DT(&xMZ^sikMfT( z)eymNtlthd3tdhMpq~d(j9MLMJkVUp!GpT6*zUVQK{Xr#8xe zh%Dev9T5wLpvy=OW#yqHd%{ee`&}suBfkO-2E@>t+6=&7TS$1mU+R;y(lIcGe!panLhn&V9(wVgU^slMoNkAqeFd!KDGw~PXNuM~BykgHE5r zE6jput*b{CsXP^a0;rTn|fApWoH6Oe!Z&`J*M2*Cgxhp z&o9tkcEWX>)*6vBV{3p87_=7GU8%KA8jM|tVd0Bg$tXA0+0=vKVDVqTe^tXaFr_rb zPKSqvEgwT;AWHzx&M}N^#?L@dnaC>HR8<`$A70r82^ZsfU6nK;C+sERnO}a~vmYnX znkZ$>kC`no!$EdWdC1ZM@qZJzx^R@dtS^UOskQ}BHs`1UR1kq#H{ z>Et6cX*-e!e&Uj{l36E9prJ+)n)g=M%`Rxr2E3SBwqk)J%v2UG%&Vv_UKh68N z^yk!jD(Go_YyZa3o27jy>9`LA=l`o!p={0jp=+oXbnq}@Zw{3{mhyqrCGXV~`$|Fm zy@IZH_ksD_&qCbP9ILQu*P8PvgCVul@I$oHJ}S$t?|1e3%rRMy0)fOQoRh&)a)f4i zO#k>eQ3gbRrIMoB?z+Hom_RQ@6Cy<U#OSe071U@v zbk#@D4pWygRZLvd2+oY7{^C-2zLmgYAtk2?zvI~XC9N-s-~rBL{iTkQTFbGLsu086 zbM6wQTjG(<(Vf2Oco=+_9bLskI+1l?+^&f+ihnVbw zKL31QML3gPPoEbsKi6v+re9I*(~DmRSF`w0%UX9|16It|!U;{f^VW$4M{ryxeQs>3 zC|fEQtI1rw%@^NVrn%~CcU~DOZ*&~vqP)i^o1Viok`R*V%YYo`wpKY?nS93Y0~T@s zjti#?!qnulV-Bv7s8K8$DjZxwILz0r8eMdO9qlnBh-4>Y=zaq`q5Gn08@qx8>Ep4y=*PC9WfcCz@$ zTgVNj@6L`53B$={96gYBCEVFqz?2ZFUfP@3byxpI6YhAFfBfOV(C9OFRLk6^eUb!1 z$cLQ<4NEj_@gQBeFBJ1Ag{3DXLSu0;Tfa`;?VMbl4@$IBj`l7=nV}JZLzd;1vV@il z?fX{7@V^?P+J01+)Wt*Tj*#E|=_D#5g5m5LP2?W7JfLYT{*~hJCq+uH^pp${8eD>v z8oMssLBhvhq0L~yZ)xWa+i?EY>Y}SBZKPe#JxU`d$4H;DpHNw7=N60Q?XqG; z``WCpHpl8$Yqh}i?mM0P6(Nbl`FjU;G^wW5Y%^Nt`UV_ko)%#r7&CM$8P+${Q@jq! z=k((Suy?@g5Bdx=iCb6vP(G0-?+=VQguhWN#gY44&#eoy>Zp%f%}EGm!&O>v1djwE zDMK#au10n8_lFT^lGx2KWXcB~L$|t*^|XjcO&4aqb^RvNJOV!stfFO&osN(i*p_>u z>q6rx3Zle#dn~pyjmUYy%9&HBUU{9~zsFjC^EjTWUyt|G%#bKeu=!fet6Dpizx8WV z<<{cKAP2YJ8m3iI+K)L)<8KQw0s{Qb_if>dOIOw6J0e)e`3*}k*1OwfAWw6R=ee+q zW$$C6oGosUw1RGQ~9Uex4j&r+}I_lG*?6q;3x?@f>tINAGd z@%z72^#c~-buqKv0?Hk{T`m-XtsbJHMWqE3d7K}xwI&H=aII0x+s&qD4v5g5zcv*J zJlpI%9Pa^>qd&1lr7JZUDfu(@=F3UerxN4FvMn4ZRXN)2CFJ6%s@b$vEmS=per#G% z*++I1_&NSE;xnh;()qBbNy)mI zixA&;e|vLqq$q9ZBbeQ9-R3Se0xEI*pj>M0IO}*YB9bX38MHrP5;VuM|2ltP;Eo_L zgpG-)e|NXWo|q%O~G7OFxIxmh&ptGr@4_tt z(zVPR3ZDv$-UD9CDkucXDzI!Gk?J5VPz*@Ga80efDMmMNRS|-%LDiA!AK@ASc3JhB z$eLxoJx@KLFzg*s)CjW>#bO^XOIJ$Q*Nzq9c9;D&QHZk{u8XTLdLGKsro|wNRn>lV zb~USIU`(d0!56C~6Pji%4|?PMfvw=2HG6uxUV^Ux?U{*UwdiXMW_{yDp%nAAjJYHz zOLT9f6iB!9E??BsFnyM0R}DMPx)^)3tg4WPxT=c@5! zWp%ri#ZKey*@TJEo7{Ir966oW5-UH?KE`z=gRvO<`Np))bDPfXoe<0Q#Nf3`b=*p) z3s^F#BFvaPT!u3?nTAu553IKOv=?J-YAINRkSj$ylP>+Pt}z5ZP8HXP20tvG4{m)B zb6glUSNT-%$<|?$Y5QQwO4M=e)4 zxwpbSnJ(M*R%_CbXzQ`c;GBZI@${V5`Jdlh8hvp88fTQ~V6EH%gp*YkriH7WauXkl z&%e4&fFHTzOp;T1&vjYj1QO;=KbuBDi|LeV0LdRJ)9UJ^xip_=2MoBQ`iA>L*qnQ9kO|UM%cW?vN1HY3UEcVop!pWKqZr!X*gR{4iS!_<~|JW%eZ^6SoIR zZLa#sXZIlH;$&tN-AdbczB#6P^u`um!F__mX!K*sY&=261t-dGS@D&Wua@i8X0w(j z0++vK=QqorEG((DR?jVqkHjXi2`7=`{1*aedTdQecQ)_4jp_XY_)}5F-t2G1a#Cob z*G@F`k_f#syd#@oqk_AXQkNtxL(_cMMu?Z5!zMD_CVunAiaM`#Xd2I$04`S(#8W^3l){?#ywiXOX-B#|aGU zB?d~atzYELmhfoTK5O`-l<8|}+mM#6&wV(_{7!e*69xy_qO+p?O(8!S$JrhpkpyD! z0b~V$%_<-XI^6q;<*O(uE}RyQ-m~KJ)j6A`_bLV73%+9(wsTJBj+M(jB;A1APV*uJ zvKumLr$$KxYA%^LW01Gd`3{Emkxb<1-DnLtya>s;N=MDQw_HrwT<>%+OjXivtM)(9 z5gD5v2pY>C6^-%s$P)_8_&kdgN%&;D9X$QQ8Y@qUGFP++*EXv3QaZRDqEK)t5?lrh zjae_x^4N^cyh~K?EHR(h=aBh;+#lkg3(oF4>r$+bQ^XPRm6mq-a>;v!I=*%J{gGPB zUkbcBvK}rzP3tP2ElD3=hLa?=7#SqR*MvF>c2)Q1DQYf5ZPL?T3VjXTTMgYyiFB2LPJ&fA$K%+ zuG{m*M6-jOniDgRNSEJDcc_lm4aMJBzu_W-+`0L`(l#XP`utw8kM)^ptsZXP|`j4pLjZLK?wTs5lMlX zZ2gtyL*!UFOrS;fn&6#WqI-uUm?MuP!O|hZYTB_X+Ywd%4 zRT`gkz&AzjjJ(RQ@AlEcdQus{Jt+Bx+aeDzWOZy@?OD^R+*Nh9= zKOb?uq5K>zS{7p7NU6N>@@1DOVNop(dCpIYb(m`1ij^DMs(y$vlpmD4F{K1OL}&+< z%P4;+&@F5s-)fJVU7D=t=^v^Xp0cc7jgH>z6Q$TuFQ2|xX}vmPiD&L4Bb_-4;R=g# z_}W`MOH{{0yzVdeS&zlHh}#Ib=|2^v6KG642X3MA$_>|-+{o{A1sceKVEeHchdR)j`<<}@0lNe(p~bwy_K(e9c_TgZ-naBE~ z4l8!qw}ILDD!F)C-b9I<%D%LMcnX+Upfbc!F|mQiC1 zJI*m-3v(X5M$QsAB0{y|nQ0e}xO^za|R(-|#X1872R}=3{XFW{>?dGw+WLe>aW4`50_G z>@PIczahp02LGm)SXeokIJ_*a44h2FO#Yxc{WDE{*32$?~@ACiPH zS||!bBgTTUJv23;Vw|q(vhXrqNqv2-OLBd^OI^ARhf9-~&Ls2V7u~{p-f8Q_K z%f#gR=|zV2(G0 zZbIMPN6~b;xsJJdIxG;;J*v;XQRUly zA@${&-~4~@6Oh$-tEp7-+v5G`!9gSXIFEPQek)V?PUJb*hhdo6 z(&EiUU%#(bv7$Kn$?Xu`VUS`_Ctcn&g=rX|RT1YUUBv|jLqLzX(9_s`|9e$K(2AF- z{mizlSd5md(8EE(UcQ@=t-=v{;kZ+Xw=;;I^?k??C2SPPRrP(q5H{=@I9IiASd}178^#G#uhHx^ zqz=OYE@}bIs=|Afajk&~mF6asT{+3(SYZ;lXuv})plMa8IB{4I5Vx|(M5()L+)yyg z9q0ut(Hb#f3lYZ&lg6#l8Zl&x?M21K0S;-!RneKSrHdnl8RCiqk+rZZllr=bDKdLi zaaA;BP1!#6is8Bg9kgWMvt{)1;DUjD)%e|ppfDw1wN|)sbT370P)IK#ZnqYyQFJFo zx;SB2_KR>paHXWlhT(=WTaq|(m@)2a+yG6gDoNw$4vGx%H({E%Y``3??^@qA#i~M$ z4NNdPySgaS#PLx$P_V)jamj(fO}A zU8}rJ=@rs?2a8J-h_DQKk`pS$8_R^BG{u4z8R_r3)_b#0CV1&z<4 z!-T5bGJ8FNt0w2K!y2?6qI*d-TY5L;aBVBy(t9m|dM4+{VToETJ)7dVER}8TkjBT;uf39y1 z>95gW8Q-bn0jEl%MqA@6vk>d^)Rx-R zW%N_yd$Qoy>ve(Cq|s2tqaRrQ)ScIrT63oNlj5hcST|(!TOdTE;P0{w>!s9ZjDtg^ zIisoZ!w`38+;Ur)IlRkpCPst1UGvC38eN09AmNv&iBWJmI0eR@}@ zB$bL53^5md!JYtPtsiNXDqwWCYUD!Pyl#82Hh{To7zOMF-VNTdm^G4VOeu}EMOJs9 zY(dgC7__Iir?#iHrz~eMP-+A&%_V+Mu!UAY15?09<0Z^1eAK781($@}PFQ=C;550a@bU_++gthl?@;Ox?4888X(7Ry)_t(wMx zed;@4L2YpkcUKpntG<{D(8V-XX^hzCD)(X17X_qa8Y?ws?}H_bV1Iovr>w?mjJCC* ztj3#12cyEcis`Ke3y|^+-%6}_Px67QX8lpWFDo@NVJ^hHCnnb(=hzBo%65DP#9+yJ5@@u%{R zN^MXh@h9^fm^{#o2UU4;TZQA+ z{F`O+UAYEip#u9uLN#P`n5I13(%>wb@+$=}f0Ah70$0)Bzw7e~L4lc~%d-QRfc?Q` z!L}jTK zVT_rBA9#R_OlB2QqkTc2uo$V#4BtgDMA1aiMNvf+BY4@H(b(Xs(`EfVCXBf0GdC>`sec()O-AUgnQ!G zK$tg)Mcvv?xDF^WM3wG0qI0HUm7Z7Ob7aUfZPOs#c&E${L2;N@m}jDU%nppMK$tbe zwU8IKd$NsBkZPHta^#z{zO3*%T&MY`&mB(6pF0rRP+p?uw*85`k-f2Y*}LxAJj1T; zCMQ0KqrSv=W?n%Uzdg-)zP;pmx^W!aC*eLn7;n<^TZ(}_PDsBz1>_xp(qz%6YNAR`QOcp!R)^L6CFcuwceQSAV+21VLxxl&Ixi|Qxqu@{)tld5-yx4Rqkv{PoZ~6O= zk#o;(9rLk$^Ert*!&gba3M6dt8uDde*w@h`Q# zoXv2@tJCJ+Q!6j3E~~_9hnwm>!<>nPnt47))|_-si()a*($)G#$oEhHG88G;P= z3_bG3n>d>0n>Hk>YUmfvvg04G+$sV(VCOnoTC_U^5zGA>+-)ALyT!Sidib{`!fHgC zW_7*tX8A>%`gCKuqdywfGcTGYlVzmFF(1S*)h*e@l`z}gRJ=6l0Xw=qSF48@XkUR{vlLaTr zt*G=?>pHU^gfeE-BB9APKmBJ|)mgg=-?xt5|2yh)dz71xat#G2TVjP<;p}7t?vMJ>*J0M7Okv;PUZEl5UC92>kfAP^zoQc>FoB3-&|(O$ z2+w%;NcX7cHXBkKup6pfPhEb0C%Pcx6BNPXP|ncKFwT&_2Wp^eU}~V)VcDVCVc4N2 zU^@|WU~}MepmPv%U~=G8pgLi5pvV#9U*@d%~q#3DMjYsuJ$Lqt%|8PH}|L0W9NtfHd$6=}@ zP$%ElFvBVdYJXf255i8h$lS+Wy>F2_ z#?pO@uR5_2_9Jg}rMWb4aCkhk1~ z6^@%C(PmrYVWvqesbhD>=bRbK;XjYTj-RCXcAEXZZm$JBp9qxnx0;b#-FktGM^#kM z#ReG-L&(SbQVEF;a(^+o(+;@w`)u#vzlqn7){V|64-(kO{BdOW=YP(=TOr;%Y~Ql3 z>{>o&YIr|)GFj*}chmA}h;VqiF!F`<6!Y_z$qH+s1^tBa`6}ya_tI;X0Oe`_gzBS_ zbkSY()3tC(qzM)#Vn5Z&P(+{kRLRrZ&w9k#&(CdR-DZ_t5-BuWrQU?xJDjeh!eco1 z<`V_yWT?KX``-t@KqlrvlMX}E3>-~4ztC?$_wC*$ZL~R|H{jQYca`b=6u^4Xe0v9?v-h`#fY_-cTpnTX*r>R_t zIx_VhW{U7ikoaHq`=X>?)`{`IMtt)TE<8Ctc`@~NU~hH#WHXR8SD{mFYue#xx12Pe z_UBiB^dUZ5Y1PG3`QCV0IY`vuPfXNh{qzA{zjPS?2@)EKY6()h3k-cREtRB0#OTn)qBh&9cfYFE1w4^hp9 zUmmNqJ#xy@sJgBehKY)rhsON|L#;n@wjRNiN<|CUCi`rT$P>f3?VI>=s40A-?X9{* zeA-iCLY3YYHSb4teDuc>bXF!SwRIk%qe;ukcw1F*59=ByCC3T}+*s!(ZCuML_^-St ztOEF^^v)t}z4-ZAt{uV=cn&#n@2-#MkI6qZ|CA%?I4A(n)=H0f5a2nG^9$*4+HAl? z;U`(U|Mcy^+HZTBwb{QB%n8q9;2FD(O$c)X!rD!zAk4!ANMUex%xW#BD?YV>vBKw# zcNv!Hh{>Qg$8KkB?b}%d$LU~AlqSMSUx57B!+qqe(HzgqEK?=!R& z2|AoSvx(3`4L_B09ll{{5`bA&==#1%~+5UQV^n4&I%W6V6sBnEUwBIpyTW;K>*l_dSL^!PC_>nvJ zXPEGZwF39g=ki`PnlpUTYHa^zRQiIh;;87qo)!`Lz4sxC*q=ey(JsLa+ z1$O3TGP%UOkaRwDP-ndJ=w@-YDI=BADMmSwtkc)zR9U4vvj8jNq_TDX>^I*2MS7|8xI32EGJodW zZ`AbctKGTWt*;S~BfZV%O|B+ksXZE-9Nt}iKR z(IuST(ek=}--^W4)+m#GQs(LqOa8h1*ha^?Mjue9vp`^RlV-N(k%%>}_DrFk@ z*;2J;VY#8Tx#JV{ymb7}%9a7q1#u#N-F2iNLVQCVfM}E=a4S^4mipw84qNr%&3l50 zsvC+rrS_un+#xyVzvDU6j5p(zucd8{N7pnk@Vod8{6JZjR^>yOM3pJ()kYqwJhN>30rriH>OyNa z@kjAc48P13-{CC#+TVhd|5C3FeN z_olAx@!elQE0tt6qHQ=IWw#xyLE#^v9j`xENp|rEYAThQL|%5DapGw7O2Nf@d+48n z8JK6D-D|Wz-(6=^y?Me8r!NY1aiJhg)5i1Gf?OW`-SI|X=Z)7Y#wF2e!S1RpBoSFz z^udT^uxYC06)iIJlIhGh?V~S>shAddNg~BJ8=^ntFUtT1j0>!U-gmZ&_Hw638$hF<2=RaM`Gsx+-6%F#47j>vDbF z?8g&8!}!4}RCS=9IXU#sKK^IEw3y_!Z*@3+`zPeq3LDwm3H9S&22LNGt=2f%p3m5) z7{#-ESAaUYXCLT~MW;lo+sGZeJq1K#4wSDBUA2aAhKkk?5fE8+zAG8BW`|E}OvSi_+25G$*R(fU+Ylf>z|k4kICvTprC%l?xh+kLCcjwB^x|oe+=W)K$>Gb zW)ZUMK5u5b$t06QF<8b%0xPJ4> zA9zC=&lRLLQPLv!=cq0n1X=kiF#+@lI!yVtbtl5s7z^x0MQqhNp3a*mMG^e;bCWz7 z*BcQ#1&741UNZ?ACfWku3$Z_?B)zOfoCVrG9ZLI|-2TQeeRJXYl1wf<=-W)DJkWyC z6we)cYRCWk%F54K&#yz5_ry*%q0fIJk3f{&+Rx;gGR9_hpSVBAegTW5EK8{_|CEYm z&Xwq3dmxUCTbnFB`IH>H?63oLfm2NUD~_m=SS?vZQ`?R?TsN?icT^0g+{t}!f`o+2 zTTQEymj|G%5;L9!vd;cV`RQ^HH@O?v9*HwMT$zzjW6DHi=TovM9h(^4l4G1oWV~Ws z_IMi(w$CF4RiZd9@p54EFH9jj^H;Urn@#IELgFQU0ZzNV!r6Ksbxytlx?k4u{Mq0W ztllX6oEAnl{a|g4%Ygxi)dU&5mz|Y3?d0;m;o=jq>W76-KOL* zDD~QV&KLVse=iA&FZaS)3w(9~adEHkG4wH9UZpxtL_DUq70AvBq92a;J^Syl$2OQx zo7INW?Evzm1o^Jn7_f|o>!cS`hy(+MJKGYeX=oG}v3lquIMn0dwYzgk0iV82 zNnBDbT3h7W-~L*{$-x!;LFzeuK`On&C@4r%61%TSx>lMV!6+3@sHqail6&dYr&KUF zE}qJ6qqoZR^Vz7O6y05!;sJRA+}h@tT|KZ9Oa8rdeDk{jb_ytzVcDvU%D3y@ixUvZ zPyJ^wCHaJAgr>M{nQfcide8527u2hZOHQ<;S;D}Mi^C=wup%&&;ms9cIj|ek2S<|e zQATiROKBR1fJEGD^d33f->}{?f!l3bTYE;hqQ1Dg-R2u%$uN$&JgYfwsT zwPGz@xg#V6PwLSA8hnHMnye-cUWM41*L5=_POP5jCz`WToV($wvLoU$$PH<6_ZPuh4F= zEcZ(Y{jq8bIkx~$+Bt(ahGCrMN$8^ z=Wkyo)Z&*(lfPz~##gR|IzP|U@r!{c{B2%o`?m&@uSD5N^+y`0QhnUQ_l6E~?>N4& zvH-j~bFPlf%Da8bJ!NQCXw&EXAL|5`lq2ZLnBdv0q{xV>1<#v+MX=(xJ4O8VgyS>9Ffs<=qyBJCrs zc`pZXoo8jPp!1*cwO`*c&azC5N)42BS_<@(ZKC$Zb61-C4I2xr6t00iQ-e-q^*=G5 zq7Y3(K(h_P)PDZ2_uAWNoNPP%zrrei^%Yt#CDbcrt}12TDg{JzgC1VHsVB(D=R$VG zfLS@DmJ~%~l@qqBGTa=WIAt==W@krBe#Yt&&uz=>S`}FE(A#uoo8)-ci@A9g5)*lG z>F17ncCT6~BRVr%u3bU)cJccv+X*cdld0NNGhQ#+WOlpOCGL&8^RijCePLG$4q%g2}x4DE5<_$@-LZu5FO z*xv{791^*QzFzkBplZJRVg7UxZj%}>M{JM=8Lrp);$b3oDoj!2k>77^KHt!;^75SSuVmEO*AL9lt{%M zr(j?+7mERrlkRktf^TS&lg_mAFj5GYkv0Ni6H{%6a`4RwYGn;gJE=SgOZvpdu?w9D zAC^ySDpPDKGcO-;WK610tZ0;YHI_GZBsRD1wio>klLBS=N^C$UKO8D*1quV;962l= zr7+brZjEtqN6k;m2k3}vE6dD6C0d8F$9WHBi9+7lW=FJ^``gEKpj=otZsMq|2}fk} z%fPHv!$=x_kc>2<)Abk{4%;{&`7u8uoustU6wV@27g&8lW{Pan^oa$ zV16~n8UuZl;>S~TGy<>BIvo5$mV;&iJ;66KS~j$liYjeDIEQ(r&vsEaT9OC0$Aj(k z#E+`qWQD5BydYpk`%X;%cvq{F^?e;Wbstb*I52> z(KCD45J}8f%qv0H*!)LJc)Sv$-EFaBbQC(L;h3S%K>!PdD7Zi@heOod)~!;ImZh(m z(hA7R#llksc26TdASUJ>bn=}EHTYGFg!Gh`kzd`g=^5SI(<@1hlUU%eGi2-i(d?U0 zoe(RLTaneEbeg#g8`t$WA*t%sca94f$Teg~V?;*vW}+b*XVWGa|DG<^QpkGQBZ=hkp1Bw3sBVWWpFRT{DJR!<&YwqomRy8mdMr3 zaetWIm)xa(@`oBO!DTS?14_U1Z6D9W`&(STM)ps~dXH~VogT2a|2;F}{O>mPUpN&a zuKtf6vF!h`O5^>fSN&hAG)QRnkBua-F8hy)4Pp}iE!ZF~G0I=dn41G~J%~L0_w9me#ay+#tA=YL__I6trMw`6@6z2*)5V8s zdG=?qjCtQq>2cd8PNYRqR^~?ARyNL66m+@??WI{l?AhrmBvKg@v?qsCD!VV!=A$ZU z=Qc}wKjL}(=>3S6mablwq&=xX!Iz+&r$gsZ+twEMULXFO@(?l0**JD0uio0X*bnnA z6BYOHKJcd7@{rrrDoSblTHe8(OsqARiN~S#Q*nMPC*8T9>t&VrB=z>q()8z1eQo(| z@^5?x;Rw!vXW1*Bir@=G#<3l(|NhYV$14B#I{43^^Z!!l{ntm#|J#M$U(fu1DfIpg zod0L_2@#X|xdb6O>A%HfUJi)!{69Vj{zFXud>deD7DPlyC>b6QLCayT7?e=^5nE0Y zCiobqfuwmXb<3nPdSC{fgs()Tjju0w$-1H1IpE20&vhOrkhNej-sQUYK^- zo9{+VT30oZ6>wSGnD-QUvz}FjS^iwDSghDydK!@BHP`xGzozYK`HD6tF+o^dBMDma zBUlZaI8*Tv7}ZEENw1>;V1uZzOuCe@uQUK-9j}PfY#av`_cs}h#>%vaHSv3#_a-f_ z9xpbzDlz+FlRDI;QfSYJyF5)Q&rz5!C%FNS2PdryOH z`7a?_Bc0Uh59u4ZA%TfIy4^LD&s_I55S5JmD+R1L$``n2j!5jHXJRJAG?*wm-WUj) zJQ5V;_(2()Gw&~&yj_S)h~Ndw5A}X00N3s_SSAu~M=|cGO~cU4NQ@d{=d$5j0RCy* zN=cI9ZUtdJg+Yv4c*$ct2yXjt=oZuBC+2dffGV&G!teg3?z5)iL#|T?qDfW~SbW3m zm3)U>rvv;zg%GMo(`Qfh7^X+pr$D8HG{pd@!5j(GqX%dKizKg+rdS~b%Qs0DEQ>l! zGD#L}urZL2${pj@}hFWfxyWfOnt19{!+l~ux5E+4eA9il@sn(h2#olpKfTg3GgVi zSqJD4dS8#Ji|U$+sf)t-zArtrSrRxB)~p0n3~L4eX(UPUw=Ae+LYw7)s9^%MeIKaM z|4Q8fl17veM|qsUA|CT+*l)T%m47?RAcj62Dho+REQP8W5m)A0GlN(@C=6L#*x-$wuU<_kEI7 z_()UKfCfxuNgncowaaFIdFq03j9kIQO&GF2`O!E;-;rX^C#6AbN*@Wae1H+?ZbDsl z;w1r**AIhkAE?{08q;SUK(~-Lghd*oXH>{rXcE_gV3q|xZF`VU)Te63%%lMfxZRxo zG)B(IsE`O$sTT55jR=tw|KaJgV=eih0t=dVF&G=Uxte^h!Yo*|F*{I}KI5h`A|Nfu zx;xujmaR!pqN1WG!1`yl+YYP5OXU-?Df4N5>P$~)4O%`t$N;;;D>MtILpqOMB^E^N zz2tAg)>D>~Ua`#+KVy{Rj$UG5(iX1CTk>8d74+WQY8N!@%~}K@l<}^HqXh6S-QYo& zf?n37YsdLdlvz=~wQ<8!6*VvB~k8_SBx7F{wR6*>c8T z6C#qcWm^RVeF`C%Y;tUgzM#H$VPofd4edCe97OjMFC&^~wopR-#6%GpbtaD7BBp>X$2v+)U^ znGv~UQc?Zu5;F+AB<%TvBEEDT@RSUAFBO2uHxn`3eStF}Ao2~Kard09{A7r(EHr+g z*JB)?twOuP{J|2m^%qR0C^>U)O`lO$xuadlxG6fjFz6ULaLVx_C^>U!O`d5I42Yf~ z13B$^kklOOn2hiXuJ+7ybRR$fW!Fa`_H$nH*2EcI2&6pI(c{pPGvg_8VxY3%^2k+E zA!yw1ZZafQ0P5&FKv79|Z)Mpe?rS&Aa;bucFv@a0-^ufPL2N3`V5C@DBR$8$XzXf* z$uJQ5#TKGZ3oPiC!KinzKAxa6XvRck&wZ)aj=#jX?eJUMJ-W&=PBqnReXOn*`9#a# zm5Zte0z#58P~`3w{v(1vyF5%#l-~9UP(Y!0u9*@)mE$osl->s6Iu8f#*Q3gO!@aCW>#+*pE-n14%hVT z`2m$*9$H19m04Q_rG4J$recL`C`PU5GoBP0yQb&bBg0TgtrFJe4gD7GuVQYp=&0h; zR<93o6ATmBrFEQDSOk4-8)q@Gp|4@-a=3sj_T6d0BG*e-h24~-+S>vZ=bl#L9eKTl zpcRl22m~Sp9k&I=64YMGp)znY;4sik*lh^jh-eJRkI47RkGYq7tI)-3xg-h7gXBIA z*UzhR+7Vr<>V?1LN(CbLqdeod=2D(gbgc(>LQ~MZ)^wRB++y`mzyu<^!ht?KBX&K> z`C)8|!<@mKK?QO+pTDiOO}z<2I(+A#CagRB`-Q(iDbn8>AJXD0ZV(i+zUF2tJlA|R zs92^A2Z5=Pf=zH|z_-quq@4n`W`{D}yr0i#XS( zbMfGMaX>&ktT+_4X350^;W^_u&^6-Cd|TyB>X-2=V(iZs45;lcroLP_70MHV={PDj z=oo0%jW4}oZvo*iuV*Cak7vB3mlV84`<;mQ^p79jTuR&3>->Qujvug8i0$L-MS9 z)VbZM5ZLslAszRIE&crssC6PvN)~D2??LOUeyw)hQxE|TmrB}9I8%GdhmD9M_dQYT ztDdKbIgKY5Xccc|Z{_#xo@$>$+#MP8eV86x*?*9|1El(a{JhT8{o7=PT4n8*Xz^)c|`K2dix`BOZm^by>ByJb2~(+VsH*~ljnrx(gCYp^M_ z&`X?Io&>akN)8`PWeZ-^$c2mwpiiipE~E~MY^WS(88;<5ix!D2X-v_ibsJl()}8vr z6q?r3eMTB{7Mu^tTrU}1Oe2x7#W=&(w{_R z&XNP&z3&c<%?DS_Z==<68zT@dLU5l?mtr|y5G6AvQ{J9?8>rT^jPK3o&JW+*6W=g2 z3g0lQFFK#G2av+$&Yqbbuti2hU`P?z5M+X)gHVH|x;|_`pxxhyMo`D_mULkNPDf_W7Gbd>`n*lS7k5CFlD5N#681NvJR zwi)LnGz-j9Fkc>?9ufwnE!y1 z-mZAK~p4}wI3 z1HqOOFodvMT`ye$U3bBLkVa6n>n`XF=5GhM4t)yOf##3e4BJfBjMj{IPw#?yj<#XF zLBDa-wGc!STmy*$O`)q1t6{6*tD!sI8KD`$8zINQn8H*;u_3a-veETk$rdW+#X&^` zM+akVP;W?XV7g!mKpi9ALK8sw1z$nh!GF-%|Ae#mpbdiwYvF&tbA;i8Aql$a5;7ws zgqep4>4N&}Dqq7^1f_PNq!1IO2~Z{tRSFRDw7z6BGqTFT zy21-39qwApU^<%3?ZCV%8LCmzXBe3C0^3+AUn-WK)J*rymgu?7_3>!S_g?N!;@>J$ z*g1V`3%opacgjolNW}X{r!W0@p|V7I8kpfon{AfKN-Ogt=}K>3r0`RaN&F=7PS17E z^`g=8pjUk?#7Z6_cN2U46tR~2ZmN-tr7hyPzY}#(tUi~N5^*ShXU_!R9h2!v{}^7b zCBY~(ttErhT7e^8P?C)*9X!Xzpqs#+-~Z~quQ<~pGnBgXzVRx3h<8W)pkhM4CCx~& znr5Lud_|%|!HL#Xw(}mLWf!&1)bfHq*>LF7K6hiEE|xoqCVx^*V#HAL4qQw8imD2i zA$s?Z73vJ21 z^vl8XZj3}UEUv7lF-Hw|@}B@_n_2Fsnko0|76)XS!mBWT$RldmtH9>$H##rtxXeeH zOXZj@;dA*ag}J7@me$yV%!?+Kz4mWK0og0^9U(uOv(@M_23EqFe-3@-{BcGlJJf#v zS!Y5^aDN~--eLTitA;1t<`68EN)?$0Oy>F;$9`2j=obep`48f@L_ZIhNklH0osKsf- z)0NcNlf<{&zqaK)l5r+Kc1J9(AmI+2 z8kbz*!sWWQSd;b)sCh$huFp-`;5|ekIj+T`cBvE*lk`_NXL^vS0xRDwnxeqO%O_}PWAY~hk0#jWM_;L=*k zKpjQ~;f&oop-(!|LwO5!y!NRY&~GB0g1@;ife{E>!)h*@Jut-R=+qW2GJO&vL8WQ# zSADRJE0js&Lqs3OZ%hY?HU(*B1nC^i*fI?hC~VOYwkCMtVYjEFs!jGQvcCod-xC!J zXXu3Mb%jb;(<{JbwUcDSxY|)pk~#|pZrff-XnyG6v7Y2dAXzv-y&TF|gR%>Xnm}=) zNVcAnR~l@{av1ms=AEjEWp>L#jM)BW+&-qlp0+3 zJX4?>YdEwNT*b0HoCH&gS}dfC$Kc6%0d)2~oD%BS7_qEW_7>@MWY9*Bxg2y}tsuZzseui!Vq zT_OLBK+^mjD_FrTi~r`jo?7IG;HhIwa&Z2~GH-S6VQ@50{B(Fef!q;_*NvFOaPb;3 zGsb;fYNFe;Yz@t+6QCe8OV%%9Vc-R}!~A;UCfLN(T4WmW>IU9#;buw)#bDdVdGqKz z1mRhldSd&Jo4Oc5MZAZ^(VyO!9w5a3OL%x#neZ#VT)D=t(`kt;hkCnyA)zVW$Z|^D!#Pg6bUeST*BGKxRxC# zhI?>a6f5;ZhlB1?B}-dSSV;(K zXTr++B+j#h@u{gzPViv9@ghGxXTE(~J$nc33PDT_8Y1_BC;m>nT_mUbfdq|=GphBt z*@zj38CqiudYo!(B^g;XK&FWw!?CiznGo#~tb?c=ymkydWMredMt-X8N}s%ZXBHB>b__VA$XdP3U(<3~bP}+Q#ZGXI zES+b;NK`Z|+C$WO-nK^6EO9oHAVBypD{>pFhhLJ%(bV_Lk@>E4s*ynA6yVHdGggkD z4p$RC#8bYc?3Vo6D{jR7w#g8T)&3qFyH72%NZVW3D31;F!SwOO7PL-LN^*YZxrL4y zD?4r33Os|>!HM@(BmSU5%>Q*MdGVuU!&oVj>5#Jsio)6l59u>bnh!6>ID*9c7{!=p zGS{3eJY&J;OK<46pGe8}3mOnz3u?A>qZGS^u+u|4n=QU`R%28<07<^0@pR<;TDbD& zja`5v*-A2$w2Zf$CL=?#p#GI{*uNbo_D@h*#mwK{-p=`tdk1$wCgD{@5MH~hF(K-= z_a$f#_-gMceLwhrv`sKUjI>}Y&{REAqd@psim7j~Ei{{_I3GmHTKSI5nUN>7BOHBb zIGz%d;vo7i>>&Nt39O5Yq__Riyq2#T{X$5a>QP!cpy1YwiL7Oicwx^-MLxoTMzmez zR|I9pJ7$!2npiAdFo57w z=P;u$*Wg86y984H-9~f^&+T0_AhJ$$PpM+IRDms->bV|TyF1Ns9Fw{K;pv8$P=93Z z*Jn!O7uz?Q4nz($`SpDQnl0vD_=hF-Us-zB`=#^OXQ(cIcFHqK4&AZurmWDEgh`K_ zgRNV;5SD-K^bbO(5yt<3uPYrA#C7q&B^1jH-W1D1O=3i7fJK%!wU#N2MR1mKoNg+kEHoOch zN03(*IsX3ZzNg1z%sY7B1#m;J2rFy>Yr%a|*Cb`slvu`d@x9NcdwE}T0tdP|rhVAG zoQ+u?I|tN)*5TpU=(Rb|SJ)%O?ZgepKDI&^FZU;XNpQrZMaH1(O~n9LH2mV5kfX1e ze^upyV~DD}J|2N~5gUgfyX{DA`@vd^qNmoOD0KZ~b7Mp3P;&bTCgezO5#NZd0li(! z)E(2(hpakniR><{^Of4+U|`Y~^ZN}(s5$a-X#kHJcN9}IMMVS`PopE$Usf5WvJ5c* zsk^$0XEq|rTgDiDY%l_ktrkL9@j<9 zSb&k8(c!OAgr^xFlwX>=jXiBx9-oA03B}k;^FY|ao2-#?a{)VFk*_EHELZ5gJ_OfAzUYh5L{5X+0=HYs{O`lc|;h zqdg7U@BGCuC=Bu%UBwJm?VV#cZ8y0{NUT7z0m?Y7;Ju|d8FCU!lCmU{gX?>1I;KH$ zDJKk->W|0p%6*C&kz`lp@gFh86*42q191+i?Mj)*0&PpFoMvSM$!X%{Ia_qT$kzc5 zv|ja6^ILV>wF~B-CH(-pT-u-V2!2v=FO?A2O_gygO{S#<*{@z}y;B zB+i`=KW+;4KJiLhId?vLA+O=O6lbaQt2I)4zt_oEGbPjfeQ5UpWYeKY3( z^)P}yO5c=vleUT`bQ#l>N_SC1ZbpW+h|2IwQ%`K)?udgmn{q$de^mS%pQsB5RpE-WlN*hfV@K7Gm|hJekZ zzQwaTIvOltPY;O#CZy-mTxvy}d)vQ9g>2@x@$i&&ab#F++en)X#|&6>6U#W5zv}0! zS*7-64;3K4=SuZ!OyV6ln{eGvS*Cxj)zAM7H#60l~lsHgPF1fojV#8ZRal zvztu@`w_?#CNyF&wYY0GY%!w>p3(`YQIxB@Tst$P4?ON-V<1)TJ;;|B!$ktFVgWE& zp~I6Z0|_|Ko17o5W1crUd(n!YPdRs3NYbmYW=_Iq5Ec`%o?PW4>HUm){H%?4x0B43 z8d7F+%OGI0%=^Q@?8Ub0T@DD?9O?i8o8@v1lAc`OY3tdM4Oq^zp;gggN%0a2+&B)- zPI7MZFo$I7=c5?P(;Zh+`;oM&&xZ&YF4IwF z*3##Q17_0axutmeLY0JHwAgx&_fw-dvcn6XLknad5_qz;5-iGGl$zcpPmI_w7E(L0 z9aX4ySy*P8w5@rwq#Yo|Dn1NY#^s>wHuF5`RZ8lLu;I~LFe3MgLeGdoz-H2t%n9~g z$3JSC4O5w+1>O2 z9~b9(weh4Ds*iuqX**5r&qZnK)T|zlj@+=eUllm7T|B&07zHniHPFnRD!K2wMZ0CQ zKXrG*#vAd7YvF1vb0JwHFQwOfF#Tcq>0an)=-?(ZDdbia@gx_Wfn~S0R|%cnamO2Y ztC}?YWMw}Mj!DEAeWc5{8JUgvNHWh@^HC||r~kHwty2FJxOQU%yngfaYm}ql!h3Y3 z2rP)h`O)en_A~DTsJb|Fs;*l9_8TN$e8xG_5nKCnEymapLn<>UgZwzp%AbEiH9Ud+dXJS`_mlO2~S^sR1)vtKA{P$y^NH_lJ*1&ar z&2+cLbjch-DyePhvSJqFtDn3z=H1P@je~rCmSe(nU1{%TLHMl!7FoQ#Y z;1=B7J;9w2EWv|2!F6yaK!D)x?(Xh(Cu^U*&si(y-gEnzKV4I8)z#lf_4`)zZb-Lh zj*`2dlJx{Zu{xP%#b)~Xe#*74{o=xF+!(fb8)*`=E_;Fnm*!@d$+^{NVxgO>%BSPB zhT6lE#x0d*hZ?HIOjO(;yhy>u!47uW(yjePu!psZivWn*=bo=_- z@f!mxb~aFG#H?q9lCx1RBc<?4%p7cVZ$b>jSOv;Kjwa$5J)d6=bM(yyv0>Y#hGLI= zLlZKIEc$sI^!ABL*3mK{_uk!RuEg%Q1OREf&e=1sL&o-A&-!$QmkaKn<|!T!h`Cx0 zL{g*r0^t3&0>bei;g0i+IYQ`E> zHhsYg1>-D;#aJq+;}`mdN19@`O6IL>Vfxp?uPMqTPD5R80E3}YYPk(evosXzOo()7 z6C{i3d!H4jRA|x8mx3&~Vxok8`Ax-VsiN_7jx1dpRkj3({o@8g+_hh(8R{Rm_L3cl2!fMj=O5 zICE($I^5D*`L)Mdsl+xKs@U{wiJJ?yQ}Xhh&Uq6Fy4Q<&nQ%glscMB2n&C=Pw`X@N zzt}o&-Y+HWt24+>_Sb@jmdkUdEYXH_b%3|?2Tq;2prxd`Nj`JfDhJR^{$}T@7)+$x>nn3yF=^8mcf|ewF_z%%CLx1V3|um*sa|q84q3 zS6J&~@fZ9ngCDOuau=rgfi31T5AC$zQ4^l(TLDk4`D>>@&F&$mogA)VUxS=XyXD^I zkniOy+rF9~30-EJgAwwh5+j=WjuamP6@N=ooAr!xgM7by_Dh@+mVnce*Y6m@qM8E^*c9bUpdT- z1)0%I_3L{|T7L<~>j!%A{eC0dMq_GZZL$f?#3#Ly@b-xksV2`n2{&7(b zevu~BdJDBSo}flNO=;a)qqfECBqTSJugs3f?&R8eXc;_g%~>=lz8-QHpBjI8j&Ez< zZ^>|6a*_)_UNeOwBkf_uzW9x-`NJ-D`sceJ@##Ud7kjuC*`L-3xqm2(;}OYpr1~by z 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

    wYp_O=M(BavxIO4Sq%OwVWU|bEv6b7M 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ⅆ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

    2zjS?5pRjZWWnEIUjE?<7jkkK`VtdHcFo{ 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 literal 0 HcmV?d00001 diff --git a/pkg/quotaplugins/quota-forest/quota-manager/docs/tree-example.txt b/pkg/quotaplugins/quota-forest/quota-manager/docs/tree-example.txt new file mode 100644 index 000000000..ac16bd2d4 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/docs/tree-example.txt @@ -0,0 +1,278 @@ +Using tree input file name: ExampleTree.json + +kind=QuotaTree +numResources=1 + +node=A; parent=nil; hard=false; cpu=10; +node=B; parent=A; hard=false; cpu=2; +node=C; parent=A; hard=false; cpu=6; +node=D; parent=A; hard=false; cpu=2; +node=E; parent=B; hard=false; cpu=1; +node=F; parent=B; hard=false; cpu=1; +node=G; parent=C; hard=false; cpu=3; +node=H; parent=C; hard=false; cpu=3; +node=I; parent=D; hard=false; cpu=1; +node=J; parent=D; hard=false; cpu=1; +node=K; parent=G; hard=false; cpu=1; +node=L; parent=G; hard=false; cpu=2; +node=M; parent=H; hard=false; cpu=1; +node=N; parent=H; hard=false; cpu=2; + +Tree: A -> ( B -> ( E F ) C -> ( G -> ( K L ) H -> ( M N ) ) D -> ( I J ) ) + +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 0 ]; consumers={ } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 0 ]; consumers={ } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 0 ]; consumers={ } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 0 ]; consumers={ } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } + +Allocating consumer a: +*** Consumer a allocated on node N +Consumer: id=a; groupId=N; priority=0; unPreemptable=false; request=[ 1 ]; aNode=N; +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 1 ]; consumers={ } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 1 ]; consumers={ } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 0 ]; consumers={ } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 1 ]; consumers={ } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 1 ]; consumers={ a } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +Status = Success +Preempted Consumers: [] + +Allocating consumer b: +*** Consumer b allocated on node N +Consumer: id=b; groupId=N; priority=0; unPreemptable=false; request=[ 1 ]; aNode=N; +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 2 ]; consumers={ } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 2 ]; consumers={ } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 0 ]; consumers={ } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 2 ]; consumers={ } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 2 ]; consumers={ a b } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +Status = Success +Preempted Consumers: [] + +Allocating consumer c: +*** Consumer c allocated on node H +Consumer: id=c; groupId=N; priority=0; unPreemptable=false; request=[ 1 ]; aNode=H; +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 3 ]; consumers={ } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 3 ]; consumers={ } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 0 ]; consumers={ } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 3 ]; consumers={ c } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 2 ]; consumers={ a b } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +Status = Success +Preempted Consumers: [] + +Deallocating consumer a: +*** Consumer c slid down to node N +Consumer: id=a; groupId=N; priority=0; unPreemptable=false; request=[ 1 ]; aNode=null; +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 2 ]; consumers={ } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 2 ]; consumers={ } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 0 ]; consumers={ } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 2 ]; consumers={ } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 2 ]; consumers={ b c } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +Status = Success +Preempted Consumers: [] + +Allocating consumer d: +*** Consumer b slid up to node H +*** Consumer c slid up to node H +*** Consumer b slid up to node C +*** Consumer d allocated on node N +Consumer: id=d; groupId=N; priority=1; unPreemptable=false; request=[ 2 ]; aNode=N; +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 4 ]; consumers={ } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 4 ]; consumers={ b } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 0 ]; consumers={ } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 3 ]; consumers={ c } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 2 ]; consumers={ d } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +Status = Success +Preempted Consumers: [] + +Allocating consumer e: +*** Consumer b slid up to node A +*** Consumer e allocated on node G +Consumer: id=e; groupId=L; priority=0; unPreemptable=false; request=[ 3 ]; aNode=G; +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 7 ]; consumers={ b } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 6 ]; consumers={ } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 3 ]; consumers={ e } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 3 ]; consumers={ c } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 2 ]; consumers={ d } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +Status = Success +Preempted Consumers: [] + +Allocating consumer f: +*** Consumer f allocated on node A +Consumer: id=f; groupId=E; priority=0; unPreemptable=false; request=[ 3 ]; aNode=A; +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 10 ]; consumers={ b f } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 6 ]; consumers={ } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 3 ]; consumers={ e } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 3 ]; consumers={ c } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 2 ]; consumers={ d } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +Status = Success +Preempted Consumers: [] + +Allocating consumer g: +*** Consumer b is preempted! +*** Consumer g allocated on node J +Consumer: id=g; groupId=J; priority=0; unPreemptable=false; request=[ 1 ]; aNode=J; +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 10 ]; consumers={ f } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 6 ]; consumers={ } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 3 ]; consumers={ e } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 3 ]; consumers={ c } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 2 ]; consumers={ d } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 1 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 1 ]; consumers={ g } +Status = Success +Preempted Consumers: [b] + +Allocating consumer h: +*** Consumer e slid up to node C +*** Consumer e slid up to node A +*** Consumer f is preempted! +*** Consumer h allocated on node K +Consumer: id=h; groupId=K; priority=0; unPreemptable=false; request=[ 1 ]; aNode=K; +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 8 ]; consumers={ e } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 4 ]; consumers={ } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 1 ]; consumers={ } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 1 ]; consumers={ h } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 3 ]; consumers={ c } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 2 ]; consumers={ d } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 1 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 1 ]; consumers={ g } +Status = Success +Preempted Consumers: [f] + +Allocating consumer i: +*** Consumer e is preempted! +*** Consumer i allocated on node A +Consumer: id=i; groupId=I; priority=1; unPreemptable=false; request=[ 3 ]; aNode=A; +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 8 ]; consumers={ i } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 4 ]; consumers={ } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 1 ]; consumers={ } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 1 ]; consumers={ h } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 3 ]; consumers={ c } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 2 ]; consumers={ d } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 1 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 1 ]; consumers={ g } +Status = Success +Preempted Consumers: [e] + +Allocating consumer j: +*** Consumer j allocated on node B +Consumer: id=j; groupId=F; priority=0; unPreemptable=false; request=[ 2 ]; aNode=B; +QuotaTree: +|A: isHard=false; quota=[ 10 ]; allocated=[ 10 ]; consumers={ i } +--|B: isHard=false; quota=[ 2 ]; allocated=[ 2 ]; consumers={ j } +----|E: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|F: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +--|C: isHard=false; quota=[ 6 ]; allocated=[ 4 ]; consumers={ } +----|G: isHard=false; quota=[ 3 ]; allocated=[ 1 ]; consumers={ } +------|K: isHard=false; quota=[ 1 ]; allocated=[ 1 ]; consumers={ h } +------|L: isHard=false; quota=[ 2 ]; allocated=[ 0 ]; consumers={ } +----|H: isHard=false; quota=[ 3 ]; allocated=[ 3 ]; consumers={ c } +------|M: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +------|N: isHard=false; quota=[ 2 ]; allocated=[ 2 ]; consumers={ d } +--|D: isHard=false; quota=[ 2 ]; allocated=[ 1 ]; consumers={ } +----|I: isHard=false; quota=[ 1 ]; allocated=[ 0 ]; consumers={ } +----|J: isHard=false; quota=[ 1 ]; allocated=[ 1 ]; consumers={ g } +Status = Success +Preempted Consumers: [] + diff --git a/pkg/quotaplugins/quota-forest/quota-manager/go-legacy.mod b/pkg/quotaplugins/quota-forest/quota-manager/go-legacy.mod new file mode 100644 index 000000000..2a8b837b5 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/go-legacy.mod @@ -0,0 +1,5 @@ +module github.ibm.com/ai-foundation/quota-manager + +go 1.16 + +require k8s.io/klog/v2 v2.9.0 diff --git a/pkg/quotaplugins/quota-forest/quota-manager/go-sum.mod b/pkg/quotaplugins/quota-forest/quota-manager/go-sum.mod new file mode 100644 index 000000000..4ddc26962 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/go-sum.mod @@ -0,0 +1,4 @@ +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= diff --git a/pkg/quotaplugins/quota-forest/quota-manager/main.go b/pkg/quotaplugins/quota-forest/quota-manager/main.go new file mode 100644 index 000000000..63ad97066 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/main.go @@ -0,0 +1,84 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota" + klog "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + flag.Set("v", "4") + flag.Set("skip_headers", "true") + klog.SetOutput(os.Stdout) + flag.Parse() + defer klog.Flush() + + treeFileName := "samples/TestTree.json" + consumerFileName := "samples/TestConsumer.json" + + // create a quota manager + fmt.Println("==> Creating Quota Manager") + fmt.Println("**************************") + quotaManager := quota.NewManager() + treeJsonString, err := ioutil.ReadFile(treeFileName) + if err != nil { + fmt.Printf("error reading quota tree file: %s", treeFileName) + return + } + + // set mode of quota manager + quotaManager.SetMode(quota.Normal) + + // add a quota tree from file + treeName, err := quotaManager.AddTreeFromString(string(treeJsonString)) + if err != nil { + fmt.Printf("error adding tree %s: %v", treeName, err) + return + } + + // allocate a consumer from file + fmt.Println("==> Allocating a consumer") + fmt.Println("**************************") + consumerInfo, err := quota.NewConsumerInfoFromFile(consumerFileName) + if err != nil { + fmt.Printf("error reading consumer file: %s", consumerFileName) + return + } + consumerID := consumerInfo.GetID() + quotaManager.AddConsumer(consumerInfo) + allocResponse, err := quotaManager.Allocate(treeName, consumerID) + if err != nil { + fmt.Printf("error allocating consumer: %v", err) + return + } + fmt.Println(allocResponse) + fmt.Println(quotaManager) + + // deallocate consumer + fmt.Println("==> DeAllocating consumer") + fmt.Println("**************************") + quotaManager.DeAllocate(treeName, consumerID) + quotaManager.RemoveConsumer(consumerID) + + fmt.Println(quotaManager) +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/consumerinfo.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/consumerinfo.go new file mode 100644 index 000000000..d32521328 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/consumerinfo.go @@ -0,0 +1,121 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package quota + +import ( + "encoding/json" + "fmt" + "io/ioutil" + + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/core" + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/utils" +) + +// ConsumerInfo : A consumer model including specifications +type ConsumerInfo struct { + // consumer specifications + spec utils.JConsumerSpec +} + +// NewConsumerInfo : create a new ConsumerInfo from Json struct +func NewConsumerInfo(consumerStruct utils.JConsumer) (*ConsumerInfo, error) { + if consumerStruct.Kind != utils.DefaultConsumerKind { + err := fmt.Errorf("consumer invalid kind: %s", consumerStruct.Kind) + return nil, err + } + if len(consumerStruct.Spec.ID) == 0 { + err := fmt.Errorf("missing consumer ID") + return nil, err + } + consumerInfo := &ConsumerInfo{ + spec: consumerStruct.Spec, + } + return consumerInfo, nil +} + +// NewConsumerInfoFromFile : create a new ConsumerInfo from Json file +func NewConsumerInfoFromFile(consumerFileName string) (*ConsumerInfo, error) { + byteValue, err := ioutil.ReadFile(consumerFileName) + if err != nil { + return nil, err + } + var consumerStruct utils.JConsumer + if err := json.Unmarshal(byteValue, &consumerStruct); err != nil { + return nil, err + } + return NewConsumerInfo(consumerStruct) +} + +// NewConsumerInfoFromString : create a new ConsumerInfo from Json string +func NewConsumerInfoFromString(consumerString string) (*ConsumerInfo, error) { + byteValue := []byte(consumerString) + var consumerStruct utils.JConsumer + if err := json.Unmarshal(byteValue, &consumerStruct); err != nil { + return nil, err + } + return NewConsumerInfo(consumerStruct) +} + +// GetID : get the ID of the consumer +func (ci *ConsumerInfo) GetID() string { + return ci.spec.ID +} + +// CreateTreeConsumer : create a tree consumer for a given tree +func (ci *ConsumerInfo) CreateTreeConsumer(treeName string, resourceNames []string) (*core.Consumer, error) { + consumerID := ci.spec.ID + for _, spec := range ci.spec.Trees { + if spec.TreeName == treeName { + req := make([]int, len(resourceNames)) + for i, r := range resourceNames { + req[i] = spec.Request[r] + } + alloc, _ := core.NewAllocationCopy(req) + consumer := core.NewConsumer(consumerID, treeName, spec.GroupID, alloc, spec.Priority, + spec.CType, spec.UnPreemptable) + return consumer, nil + } + } + return nil, fmt.Errorf("tree %s not specified in ConsumerInfo for consumer id %s", treeName, consumerID) +} + +// CreateForestConsumer : create a forest consumer for a given forest +func (ci *ConsumerInfo) CreateForestConsumer(forestName string, resourceNames map[string][]string) (*core.ForestConsumer, error) { + consumerID := ci.spec.ID + consumers := make(map[string]*core.Consumer) + for _, spec := range ci.spec.Trees { + treeName := spec.TreeName + if len(treeName) == 0 { + continue + } + req := make([]int, len(resourceNames[treeName])) + for i, r := range resourceNames[treeName] { + req[i] = spec.Request[r] + } + alloc, _ := core.NewAllocationCopy(req) + consumer := core.NewConsumer(consumerID, treeName, spec.GroupID, alloc, spec.Priority, + spec.CType, spec.UnPreemptable) + consumers[treeName] = consumer + } + fc := core.NewForestConsumer(consumerID, consumers) + return fc, nil +} + +// String : a print out of the consumer info +func (ci *ConsumerInfo) String() string { + return fmt.Sprintf("Consumer: ID=%s; Spec={%v}", ci.spec.ID, ci.spec) +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocation.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocation.go new file mode 100644 index 000000000..f39951588 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocation.go @@ -0,0 +1,171 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package core + +import ( + "bytes" + "fmt" +) + +// Allocation : an allocation of an (ordered) array of resources +// (names of resources are left out for efficiency) +type Allocation struct { + // values of the allocation + x []int +} + +// NewAllocation : create an empty allocation of a given size (length) +func NewAllocation(size int) (*Allocation, error) { + if size < 0 { + return nil, fmt.Errorf("invalid size %d", size) + } + return &Allocation{ + x: make([]int, size), + }, nil +} + +// NewAllocationCopy : create an allocation given an array of values +func NewAllocationCopy(value []int) (*Allocation, error) { + a, err := NewAllocation(len(value)) + if err != nil { + return nil, err + } + copy(a.x, value) + return a, nil +} + +// GetSize : get the size (length) of the values array +func (a *Allocation) GetSize() int { + return len(a.x) +} + +// GetValue : get the array of values +func (a *Allocation) GetValue() []int { + return a.x +} + +// SetValue : set the array of values (overwites previous values) +func (a *Allocation) SetValue(value []int) { + a.x = make([]int, len(value)) + copy(a.x, value) +} + +// Clone : create a copy +func (a *Allocation) Clone() *Allocation { + alloc, _ := NewAllocationCopy(a.x) + return alloc +} + +// Add : add another allocation to this one (false if unequal lengths) +func (a *Allocation) Add(other *Allocation) bool { + if !a.SameSize(other) { + return false + } + v := other.GetValue() + for i := 0; i < len(a.x); i++ { + a.x[i] += v[i] + } + return true +} + +// Subtract : subtract another allocation to this one (false if unequal lengths) +func (a *Allocation) Subtract(other *Allocation) bool { + if !a.SameSize(other) { + return false + } + v := other.GetValue() + for i := 0; i < len(a.x); i++ { + a.x[i] -= v[i] + } + return true +} + +// Fit : check if this allocation fits on an entity with a given capacity +// and already allocated values (false if unequal lengths) +func (a *Allocation) Fit(allocated *Allocation, capacity *Allocation) bool { + available := capacity.Clone() + if available.Subtract(allocated) { + return a.LessOrEqual(available) + } + return false +} + +// SameSize : check if same size (length) as another allocation +func (a *Allocation) SameSize(other *Allocation) bool { + return a.GetSize() == other.GetSize() +} + +// IsZero : check if values are zeros (all element values) +func (a *Allocation) IsZero() bool { + for i := 0; i < len(a.x); i++ { + if a.x[i] != 0 { + return false + } + } + return true +} + +// Equal : check if equals another allocation (false if unequal lengths) +func (a *Allocation) Equal(other *Allocation) bool { + return a.comp(other, false) +} + +// LessOrEqual : check if less or equal to another allocation (false if unequal lengths) +func (a *Allocation) LessOrEqual(other *Allocation) bool { + return a.comp(other, true) +} + +// comp : compare this to another allocation (false if unequal lengths); +// lessOrEqual: true => 'less or equal'; false => 'equal' +func (a *Allocation) comp(other *Allocation, lessOrEqual bool) bool { + if a.SameSize(other) { + v := other.GetValue() + condition := true + for i := 0; i < len(a.x) && condition; i++ { + if lessOrEqual { + condition = a.x[i] <= v[i] + } else { + condition = a.x[i] == v[i] + } + } + return condition + } + return false +} + +// String : a print out of the allocation +func (a *Allocation) String() string { + return fmt.Sprint(a.x) +} + +// StringPretty : a print out of the allocation with resource names (empty if unequal lengths) +func (a *Allocation) StringPretty(resourceNames []string) string { + n := len(a.x) + if len(resourceNames) != n { + return "" + } + var b bytes.Buffer + b.WriteString("[") + for i := 0; i < n; i++ { + fmt.Fprintf(&b, "%s:%d", resourceNames[i], a.x[i]) + if i < n-1 { + b.WriteString(", ") + } + } + b.WriteString("]") + return b.String() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocation_test.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocation_test.go new file mode 100644 index 000000000..339b59c4f --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocation_test.go @@ -0,0 +1,330 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package core + +import ( + "reflect" + "testing" +) + +func TestNewAllocation(t *testing.T) { + type args struct { + size int + } + tests := []struct { + name string + args args + want *Allocation + wantErr bool + }{ + {name: "test1", + args: args{ + size: 3, + }, + want: &Allocation{ + x: []int{0, 0, 0}, + }, + wantErr: false, + }, + {name: "test2", + args: args{ + size: 0, + }, + want: &Allocation{ + x: []int{}, + }, + wantErr: false, + }, + {name: "test3", + args: args{ + size: -2, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewAllocation(tt.args.size) + if (err != nil) != tt.wantErr { + t.Errorf("NewAllocation() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewAllocation() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewAllocationCopy(t *testing.T) { + type args struct { + value []int + } + tests := []struct { + name string + args args + want *Allocation + wantErr bool + }{ + {name: "test1", + args: args{ + value: []int{1, 2, 3}, + }, + want: &Allocation{ + x: []int{1, 2, 3}, + }, + wantErr: false, + }, + {name: "test2", + args: args{ + value: []int{}, + }, + want: &Allocation{ + x: []int{}, + }, + wantErr: false, + }, + {name: "test3", + args: args{ + value: nil, + }, + want: &Allocation{ + x: []int{}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewAllocationCopy(tt.args.value) + if (err != nil) != tt.wantErr { + t.Errorf("NewAllocationCopy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewAllocationCopy() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAllocation_SetValue(t *testing.T) { + type fields struct { + x []int + } + type args struct { + value []int + } + tests := []struct { + name string + fields fields + args args + want *Allocation + }{ + {name: "test1", + fields: fields{ + x: []int{1, 2, 3}, + }, + args: args{ + value: []int{4, 5}, + }, + want: &Allocation{ + x: []int{4, 5}, + }, + }, + {name: "test2", + fields: fields{ + x: []int{1, 2, 3}, + }, + args: args{ + value: []int{}, + }, + want: &Allocation{ + x: []int{}, + }, + }, + {name: "test3", + fields: fields{ + x: []int{1, 2, 3}, + }, + args: args{ + value: nil, + }, + want: &Allocation{ + x: []int{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &Allocation{ + x: tt.fields.x, + } + a.SetValue(tt.args.value) + got := a + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Allocation_SetValue() = %v, want %v", got, tt.want) + } + + }) + } +} + +func TestAllocation_Fit(t *testing.T) { + type fields struct { + x []int + } + type args struct { + allocated *Allocation + capacity *Allocation + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + {name: "test1", + fields: fields{ + x: []int{1, 2, 3}, + }, + args: args{ + allocated: &Allocation{ + x: []int{1, 1, 0}, + }, + capacity: &Allocation{ + x: []int{5, 4, 3}, + }, + }, + want: true, + }, + {name: "test2", + fields: fields{ + x: []int{1, 2, 3}, + }, + args: args{ + allocated: &Allocation{ + x: []int{1, 1, 0}, + }, + capacity: &Allocation{ + x: []int{2, 3, 3}, + }, + }, + want: true, + }, + {name: "test3", + fields: fields{ + x: []int{1, 2, 3}, + }, + args: args{ + allocated: &Allocation{ + x: []int{1, 1, 0}, + }, + capacity: &Allocation{ + x: []int{5, 2, 3}, + }, + }, + want: false, + }, + {name: "test4", + fields: fields{ + x: []int{1, 2, 3}, + }, + args: args{ + allocated: &Allocation{ + x: []int{0, 0}, + }, + capacity: &Allocation{ + x: []int{5, 5, 5}, + }, + }, + want: false, + }, + {name: "test5", + fields: fields{ + x: []int{1, 2, 3}, + }, + args: args{ + allocated: &Allocation{ + x: []int{5, 5, 0}, + }, + capacity: &Allocation{ + x: []int{5, 5, 5}, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &Allocation{ + x: tt.fields.x, + } + if got := a.Fit(tt.args.allocated, tt.args.capacity); got != tt.want { + t.Errorf("Allocation.Fit() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAllocation_StringPretty(t *testing.T) { + type fields struct { + x []int + } + type args struct { + resourceNames []string + } + tests := []struct { + name string + fields fields + args args + want string + }{ + { + name: "test1", + fields: fields{ + x: []int{1, 2, 3}, + }, + args: args{ + resourceNames: []string{"cpu", "memory", "gpu"}, + }, + want: "[cpu:1, memory:2, gpu:3]", + }, + { + name: "test2", + fields: fields{ + x: []int{1, 2, 3}, + }, + args: args{ + resourceNames: []string{"cpu", "memory"}, + }, + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &Allocation{ + x: tt.fields.x, + } + if got := a.StringPretty(tt.args.resourceNames); got != tt.want { + t.Errorf("Allocation.StringPretty() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationrecovery.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationrecovery.go new file mode 100644 index 000000000..822b2e652 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationrecovery.go @@ -0,0 +1,115 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package core + +import ( + "unsafe" + + "k8s.io/klog/v2" +) + +// AllocationRecovery : Used to recover from failure after partial allocation +type AllocationRecovery struct { + // consumer : subject to partial allocation + consumer *Consumer + // alteredNodes : nodes with altered allocations during partial allocation + alteredNodes []*QuotaNode + // alteredConsumers : consumers altered during partial allocation + alteredConsumers map[string]*Consumer + // originalConsumersNode : original allocation nodes of consumers altered during partial allocation + originalConsumersNode map[string]*QuotaNode +} + +// NewAllocationRecovery : create an allocation recovery for a consumer +func NewAllocationRecovery(consumer *Consumer) *AllocationRecovery { + ar := &AllocationRecovery{ + consumer: consumer, + } + ar.Reset() + return ar +} + +// AlteredNode : mark a node as altered +func (ar *AllocationRecovery) AlteredNode(qn *QuotaNode) { + ar.alteredNodes = append(ar.alteredNodes, qn) +} + +// AlteredConsumer : mark a consumer as altered +func (ar *AllocationRecovery) AlteredConsumer(consumer *Consumer) { + cid := consumer.GetID() + if ar.alteredConsumers[cid] == nil { + ar.alteredConsumers[cid] = consumer + ar.originalConsumersNode[cid] = consumer.GetNode() + } +} + +// Recover : perform recovery actions +func (ar *AllocationRecovery) Recover() { + + for _, qn := range ar.alteredNodes { + qn.SubtractRequest(ar.consumer) + } + consumerNode := ar.consumer.GetNode() + if consumerNode != nil { + consumerNode.RemoveConsumer(ar.consumer) + ar.consumer.SetNode(nil) + } + + if len(ar.alteredConsumers) > 0 { + klog.V(4).Infoln("*** Recovering ...") + } + for cid, ci := range ar.alteredConsumers { + ni := ar.originalConsumersNode[cid] + if ci != nil && ni != nil { + + curNode := ci.GetNode() + if curNode == ni { + // nothing to do + continue + } + + var curNodeID string + if curNode != nil { + curNodeID = curNode.GetID() + } else { + curNodeID = "null" + } + klog.V(4).Infof("*** Restating consumer %s from node %s to %s \n", cid, curNodeID, ni.GetID()) + + if curNode != nil { + curNode.RemoveConsumer(ci) + } + ni.AddConsumer(ci) + ci.SetNode(ni) + pi := ni.GetPathToRoot() + for _, p := range pi { + pqn := (*QuotaNode)(unsafe.Pointer(p)) + if pqn == curNode { + break + } + pqn.AddRequest(ci) + } + } + } +} + +// Reset : reset the allocation recovery +func (ar *AllocationRecovery) Reset() { + ar.alteredNodes = make([]*QuotaNode, 0) + ar.alteredConsumers = make(map[string]*Consumer) + ar.originalConsumersNode = make(map[string]*QuotaNode) +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationresponse.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationresponse.go new file mode 100644 index 000000000..d636764bd --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationresponse.go @@ -0,0 +1,115 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package core + +import ( + "bytes" + "fmt" +) + +// AllocationResponse : A response object to a consumer allocation request +type AllocationResponse struct { + // ID of subject consumer + consumerID string + // result of allocation request + allocated bool + // any message in relation to the allocation request + message string + // resulting set of (unique) IDs of preempted consumers due to the allocation request + preemptedIDs map[string]bool +} + +// NewAllocationResponse : create an allocation response for a consumer +func NewAllocationResponse(consumerID string) *AllocationResponse { + return &AllocationResponse{ + consumerID: consumerID, + allocated: true, + message: "", + preemptedIDs: make(map[string]bool), + } +} + +// Append : append to the response +func (ar *AllocationResponse) Append(allocated bool, message string, preemptedIds *[]string) { + ar.allocated = ar.allocated && allocated + ar.message += " " + message + for _, id := range *preemptedIds { + ar.preemptedIDs[id] = true + } +} + +// Merge : merge another response into this +func (ar *AllocationResponse) Merge(other *AllocationResponse) { + if ar.consumerID != other.GetConsumerID() { + return + } + pids := other.GetPreemptedIds() + ar.Append( + other.IsAllocated(), + other.GetMessage(), + &pids, + ) +} + +// GetConsumerID : +func (ar *AllocationResponse) GetConsumerID() string { + return ar.consumerID +} + +// IsAllocated : +func (ar *AllocationResponse) IsAllocated() bool { + return ar.allocated +} + +// SetAllocated : +func (ar *AllocationResponse) SetAllocated(allocated bool) { + ar.allocated = allocated +} + +// GetMessage : +func (ar *AllocationResponse) GetMessage() string { + return ar.message +} + +// SetMessage : +func (ar *AllocationResponse) SetMessage(message string) { + ar.message = message +} + +// GetPreemptedIds : +func (ar *AllocationResponse) GetPreemptedIds() []string { + pids := make([]string, len(ar.preemptedIDs)) + i := 0 + for k := range ar.preemptedIDs { + pids[i] = k + i++ + } + return pids +} + +// String +func (ar *AllocationResponse) String() string { + var b bytes.Buffer + b.WriteString("AllocationResponse: ") + fmt.Fprintf(&b, "consumer=%s; ", ar.consumerID) + fmt.Fprintf(&b, "allocated=%v; ", ar.allocated) + fmt.Fprintf(&b, "message=\"%s\"; ", ar.message) + fmt.Fprintf(&b, "preemptedIds=") + fmt.Fprintf(&b, "%v", ar.GetPreemptedIds()) + fmt.Fprintf(&b, "\n") + return b.String() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationresponse_test.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationresponse_test.go new file mode 100644 index 000000000..a819469b5 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/allocationresponse_test.go @@ -0,0 +1,78 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package core + +import ( + "reflect" + "testing" +) + +func TestAllocationResponse_Merge(t *testing.T) { + type fields struct { + consumerID string + allocated bool + message string + preemptedIDs map[string]bool + } + type args struct { + other *AllocationResponse + } + tests := []struct { + name string + fields fields + args args + want *AllocationResponse + }{ + { + name: "test1", + fields: fields{ + consumerID: "C10", + allocated: true, + message: "success", + preemptedIDs: map[string]bool{"C2": true, "C5": true}, + }, + args: args{ + other: &AllocationResponse{ + consumerID: "C10", + allocated: true, + message: "again", + preemptedIDs: map[string]bool{"C5": true, "C3": true}, + }, + }, + want: &AllocationResponse{ + consumerID: "C10", + allocated: true, + message: "success again", + preemptedIDs: map[string]bool{"C2": true, "C3": true, "C5": true}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ar := &AllocationResponse{ + consumerID: tt.fields.consumerID, + allocated: tt.fields.allocated, + message: tt.fields.message, + preemptedIDs: tt.fields.preemptedIDs, + } + ar.Merge(tt.args.other) + got := ar + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("AllocationResponse_Merge = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/consumer.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/consumer.go new file mode 100644 index 000000000..9a24ad497 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/consumer.go @@ -0,0 +1,141 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package core + +import ( + "bytes" + "fmt" +) + +// Consumer : A tree consumer +type Consumer struct { + // a unique id for the consumer + id string + // the tree ID for the consumer + treeID string + // the group ID for the consumer (ID of the leaf node) + groupID string + // resources requested by the consumer + request *Allocation + // priority of the consumer + priority int + // type of the consumer + cType int + // cannot preempt this consumer + unPreemptable bool + // node the consumer is assigned to + aNode *QuotaNode +} + +// NewConsumer : create a consumer +func NewConsumer(id string, treeID string, groupID string, request *Allocation, priority int, + cType int, unPreemptable bool) *Consumer { + return &Consumer{ + id: id, + treeID: treeID, + groupID: groupID, + request: request, + priority: priority, + cType: cType, + unPreemptable: unPreemptable, + } +} + +// GetID : get the id of the consumer +func (c *Consumer) GetID() string { + return c.id +} + +// GetTreeID : get the treeID of the consumer +func (c *Consumer) GetTreeID() string { + return c.treeID +} + +// SetTreeID : set the groupID of the consumer +func (c *Consumer) SetTreeID(treeID string) { + c.treeID = treeID +} + +// GetGroupID : get the treeID of the consumer +func (c *Consumer) GetGroupID() string { + return c.groupID +} + +// SetGroupID : set the groupID of the consumer +func (c *Consumer) SetGroupID(groupID string) { + c.groupID = groupID +} + +// GetRequest : get the request demand of the consumer +func (c *Consumer) GetRequest() *Allocation { + return c.request +} + +// GetPriority : get the priority of the consumer +func (c *Consumer) GetPriority() int { + return c.priority +} + +// GetType : get the type of the consumer +func (c *Consumer) GetType() int { + return c.cType +} + +// IsUnPreemptable : is the consumer preemptable +func (c *Consumer) IsUnPreemptable() bool { + return c.unPreemptable +} + +// SetUnPreemptable : set the consumer unpreemptability +func (c *Consumer) SetUnPreemptable(unPreemptable bool) { + c.unPreemptable = unPreemptable +} + +// GetNode : get the allocated node for the consumer +func (c *Consumer) GetNode() *QuotaNode { + return c.aNode +} + +// SetNode : set the allocated node for the consumer +func (c *Consumer) SetNode(aNode *QuotaNode) { + c.aNode = aNode +} + +// IsAllocated : is consumer allocated on tree +func (c *Consumer) IsAllocated() bool { + return c.aNode != nil +} + +// String : a print out of the consumer +func (c *Consumer) String() string { + var b bytes.Buffer + b.WriteString("Consumer: ") + fmt.Fprintf(&b, "ID=%s; ", c.id) + fmt.Fprintf(&b, "treeID=%s; ", c.treeID) + fmt.Fprintf(&b, "groupID=%s; ", c.groupID) + fmt.Fprintf(&b, "priority=%d; ", c.priority) + fmt.Fprintf(&b, "type=%d; ", c.cType) + fmt.Fprintf(&b, "unPreemptable=%v; ", c.unPreemptable) + fmt.Fprintf(&b, "request=%s; ", c.request) + var nodeID string + if c.aNode != nil { + nodeID = c.aNode.GetID() + } else { + nodeID = "null" + } + fmt.Fprintf(&b, "aNode=%s; ", nodeID) + return b.String() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/forestconsumer.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/forestconsumer.go new file mode 100644 index 000000000..16b232c48 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/forestconsumer.go @@ -0,0 +1,131 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + + utils "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/utils" +) + +// ForestConsumer : A forest consumer (multiple tree consumers) +type ForestConsumer struct { + // a unique id for the consumer + id string + // map of tree consumers: treeName -> consumer + consumers map[string]*Consumer +} + +// NewForestConsumer: create a forest consumer +func NewForestConsumer(id string, consumers map[string]*Consumer) *ForestConsumer { + return &ForestConsumer{ + id: id, + consumers: consumers, + } +} + +// NewForestConsumerFromFile : create a forest consumer from JSON file +func NewForestConsumerFromFile(consumerFileName string, resourceNames map[string][]string) (*ForestConsumer, error) { + byteValue, err := ioutil.ReadFile(consumerFileName) + if err != nil { + return nil, err + } + return NewForestConsumerFromString(string(byteValue), resourceNames) +} + +// NewForestConsumerFromString : create a forest consumer from JSON string spec +func NewForestConsumerFromString(consumerString string, resourceNames map[string][]string) (*ForestConsumer, error) { + byteValue := []byte(consumerString) + var jConsumerMulti utils.JConsumer + if err := json.Unmarshal(byteValue, &jConsumerMulti); err != nil { + return nil, err + } + return NewForestConsumerFromStruct(jConsumerMulti, resourceNames) +} + +// NewForestConsumerFromStruct : create a forest consumer from JSON struct +func NewForestConsumerFromStruct(consumerJson utils.JConsumer, resourceNames map[string][]string) (*ForestConsumer, error) { + if consumerJson.Kind != utils.DefaultConsumerKind { + err := fmt.Errorf("consumer multi invalid kind: %s", consumerJson.Kind) + return nil, err + } + + consumerID := consumerJson.Spec.ID + if len(consumerID) == 0 { + err := fmt.Errorf("missing consumer multi ID") + return nil, err + } + + consumers := make(map[string]*Consumer) + for _, spec := range consumerJson.Spec.Trees { + treeName := spec.TreeName + if len(treeName) == 0 { + continue + } + req := make([]int, len(resourceNames[treeName])) + for i, r := range resourceNames[treeName] { + req[i] = spec.Request[r] + } + alloc, _ := NewAllocationCopy(req) + consumer := NewConsumer(consumerID, treeName, spec.GroupID, alloc, spec.Priority, + spec.CType, spec.UnPreemptable) + consumers[treeName] = consumer + } + return &ForestConsumer{ + id: consumerID, + consumers: consumers, + }, nil +} + +// GetID : get the id of the consumer +func (fc *ForestConsumer) GetID() string { + return fc.id +} + +// GetConsumers : get the tree consumers +func (fc *ForestConsumer) GetConsumers() map[string]*Consumer { + return fc.consumers +} + +// GetTreeConsumer : get the consumer of a given tree +func (fc *ForestConsumer) GetTreeConsumer(treeName string) *Consumer { + return fc.consumers[treeName] +} + +//IsAllocated : is consumer allocated on all trees in the forest +func (fc *ForestConsumer) IsAllocated() bool { + for _, consumer := range fc.consumers { + if !consumer.IsAllocated() { + return false + } + } + return true +} + +// String : a print out of the forest consumer +func (fc *ForestConsumer) String() string { + var b bytes.Buffer + fmt.Fprintf(&b, "ForestConsumer: ID=%s {\n", fc.id) + for treeName, consumer := range fc.consumers { + fmt.Fprintf(&b, "treeName=%s; ", treeName) + fmt.Fprintf(&b, "%s\n", consumer.String()) + } + fmt.Fprint(&b, "}") + return b.String() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/forestcontroller.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/forestcontroller.go new file mode 100644 index 000000000..134ec8fbd --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/forestcontroller.go @@ -0,0 +1,393 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package core + +import ( + "bytes" + "fmt" + + "k8s.io/klog/v2" +) + +// ForestController : controller for multiple quota trees +type ForestController struct { + // map of tree controllers, one per tree + controllers map[string]*Controller +} + +// NewForestController : create a multiple tree controller +func NewForestController() *ForestController { + return &ForestController{ + controllers: make(map[string]*Controller), + } +} + +// AddController : add a quota tree controller +func (fc *ForestController) AddController(controller *Controller) bool { + if controller != nil { + treeName := controller.GetTreeName() + if _, exists := fc.controllers[treeName]; !exists { + fc.controllers[treeName] = controller + return true + } + } + return false +} + +// DeleteController : delete a quota tree controller +func (fc *ForestController) DeleteController(treeName string) bool { + return fc.DeleteTree(treeName) +} + +// AddTree : add a quota tree +func (fc *ForestController) AddTree(tree *QuotaTree) bool { + treeName := tree.GetName() + if fc.controllers[treeName] != nil { + return false + } + fc.controllers[treeName] = NewController(tree) + return true +} + +// DeleteTree : delete a quota tree +func (fc *ForestController) DeleteTree(treeName string) bool { + if fc.controllers[treeName] == nil { + return false + } + delete(fc.controllers, treeName) + return true +} + +// GetResourceNames : get resource names (null if no tree) +func (fc *ForestController) GetResourceNames() map[string][]string { + rNmaesMap := make(map[string][]string) + for treeName, controller := range fc.controllers { + rNmaesMap[treeName] = controller.tree.GetResourceNames() + } + return rNmaesMap +} + +// IsConsumerAllocated : check if a consumer is already allocated +func (fc *ForestController) IsConsumerAllocated(id string) bool { + for _, controller := range fc.controllers { + if !controller.IsConsumerAllocated(id) { + return false + } + } + return true +} + +// IsAllocated : check if there are consumers already allocated +func (fc *ForestController) IsAllocated() bool { + for _, controller := range fc.controllers { + if controller.IsAllocated() { + return true + } + } + return false +} + +// Allocate : allocate a consumer +func (fc *ForestController) Allocate(forestConsumer *ForestConsumer) *AllocationResponse { + + consumerID := forestConsumer.GetID() + consumers := forestConsumer.GetConsumers() + var b bytes.Buffer + + fmt.Fprintf(&b, "Multi-quota allocation for consumer %s:\n", consumerID) + for treeName, consumer := range consumers { + fmt.Fprintf(&b, treeName+": {") + fmt.Fprintf(&b, "groupID=%s; ", consumer.GetGroupID()) + fmt.Fprintf(&b, "request=%v; ", consumer.GetRequest().GetValue()) + fmt.Fprintf(&b, "priority=%d; ", consumer.GetPriority()) + fmt.Fprintf(&b, "type=%d; ", consumer.GetType()) + fmt.Fprintf(&b, "unPreemptable=%v; ", consumer.IsUnPreemptable()) + fmt.Fprintln(&b, "}") + } + fmt.Fprintln(&b) + klog.V(4).Info(b.String()) + + allocResponse := NewAllocationResponse(consumerID) + + /* + * holders of progress through processing the trees + */ + processedTrees := make([]string, 0) + deletedConsumers := make([]([]*Consumer), 0) + preemptedConsumers := make([]([]string), 0) + + /* + * process trees sequentially + */ + for treeName, consumer := range consumers { + + controller := fc.controllers[treeName] + groupID := consumer.GetGroupID() + allocRequested := consumer.GetRequest() + if controller == nil || len(groupID) == 0 || allocRequested.GetSize() != controller.GetQuotaSize() { + var msg bytes.Buffer + if controller == nil { + fmt.Fprintf(&msg, "Unknown error processing quota designation '%s'", treeName) + } else if len(groupID) == 0 { + fmt.Fprintf(&msg, "No quota designations provided for '%s'", treeName) + } else { + fmt.Fprintf(&msg, "Explected %d resources for quota designations '%s', received %d", + controller.GetQuotaSize(), treeName, allocRequested.GetSize()) + } + return fc.failureRecover(consumerID, processedTrees, deletedConsumers, msg.String()) + } + + /* + * delete preempted consumers in previously processed trees from this tree + */ + treeDeletedConsumers := make([]*Consumer, 0) + numProcessedTrees := len(processedTrees) + if numProcessedTrees > 0 { + for _, cj := range deletedConsumers[numProcessedTrees-1] { + cjID := cj.GetID() + c := controller.GetConsumer(cjID) + if c != nil { + treeDeletedConsumers = append(treeDeletedConsumers, c) + controller.DeAllocate(cjID) + } + } + } + + /* + * allocate consumer on this tree + */ + treeAllocResponse := controller.Allocate(consumer) + + if treeAllocResponse.IsAllocated() { + + /* + * allocation succeeded + */ + processedTrees = append(processedTrees, treeName) + treeDeletedConsumers = append(treeDeletedConsumers, controller.GetPreemptedConsumersArray()...) + deletedConsumers = append(deletedConsumers, treeDeletedConsumers) + preemptedConsumers = append(preemptedConsumers, treeAllocResponse.GetPreemptedIds()) + allocResponse.Merge(treeAllocResponse) + + } else { + + /* + * allocation failed - undo deletions of prior preempted consumers and recover + */ + for _, c := range treeDeletedConsumers { + controller.Allocate(c) + } + return fc.failureRecover(consumerID, processedTrees, deletedConsumers, treeAllocResponse.GetMessage()) + } + } + + /* + * delete preempted consumers (which had not been deleted) from all trees + */ + for i, treeName := range processedTrees { + treeQM := fc.controllers[treeName] + if treeQM == nil { + continue + } + for j := i + 1; j < len(preemptedConsumers); j++ { + pcs := preemptedConsumers[j] + for _, pc := range pcs { + treeQM.DeAllocate(pc) + } + } + } + + b.Reset() + fmt.Fprintf(&b, "Multi-quota allocation for consumer: %s\n", consumerID) + fmt.Fprint(&b, allocResponse.String()) + fmt.Fprintln(&b) + klog.V(4).Info(b.String()) + return allocResponse +} + +// failureRecover : undo alterations to trees in case of allocation failure +func (fc *ForestController) failureRecover(consumerID string, processedTrees []string, + deletedConsumers []([]*Consumer), msg string) *AllocationResponse { + + for i, treeName := range processedTrees { + controller := fc.controllers[treeName] + if controller != nil { + controller.DeAllocate(consumerID) + treeDeletedConsumers := deletedConsumers[i] + for _, consumer := range treeDeletedConsumers { + controller.Allocate(consumer) + } + } + } + + failedResponse := NewAllocationResponse(consumerID) + failedResponse.SetAllocated(false) + + // Pickup message from the allocation request. + failedResponse.SetMessage(msg) + + klog.V(4).Infof("Multi-quota allocation for consumer: %s\n", consumerID) + klog.V(4).Infoln(failedResponse) + + return failedResponse +} + +// ForceAllocate : force allocate a consumer on a given set of nodes on trees; +// no recovery if not allocated on some trees; partial allocation allowed +func (fc *ForestController) ForceAllocate(forestConsumer *ForestConsumer, nodeIDs map[string]string) *AllocationResponse { + consumerID := forestConsumer.GetID() + consumers := forestConsumer.GetConsumers() + allocResponse := NewAllocationResponse(consumerID) + for treeName, controller := range fc.controllers { + if consumer, consumerExists := consumers[treeName]; consumerExists { + if nodeID, nodeExists := nodeIDs[treeName]; nodeExists { + resp := controller.ForceAllocate(consumer, nodeID) + allocResponse.Merge(resp) + } + } + } + return allocResponse +} + +// DeAllocate : deallocate a consumer +func (fc *ForestController) DeAllocate(consumerID string) bool { + status := true + for _, controller := range fc.controllers { + if !controller.DeAllocate(consumerID) { + status = false + } + } + return status +} + +// GetControllers : get a map of tree controllers of the trees +func (fc *ForestController) GetControllers() map[string]*Controller { + return fc.controllers +} + +// GetTreeNames : get the names of the trees +func (fc *ForestController) GetTreeNames() []string { + names := make([]string, 0, len(fc.controllers)) + for name := range fc.controllers { + names = append(names, name) + } + return names +} + +// GetQuotaTrees : get a list of the quota trees +func (fc *ForestController) GetQuotaTrees() []*QuotaTree { + trees := make([]*QuotaTree, 0, len(fc.controllers)) + for _, controller := range fc.controllers { + trees = append(trees, controller.GetTree()) + } + return trees +} + +// IsReady : check if tree(s) have been created +func (fc *ForestController) IsReady() bool { + if len(fc.controllers) == 0 { + return false + } + trees := fc.GetQuotaTrees() + for _, tree := range trees { + if tree == nil { + return false + } + } + return true +} + +// UpdateTrees : update all trees from caches; +// returns nil if able to allocate all consumers onto updated trees, +// otherwise a list of the IDs of unallocated consumers is returned +func (fc *ForestController) UpdateTrees(treeCacheList []*TreeCache) (unAllocatedConsumerIDs []string, + responseMap map[string]*TreeCacheCreateResponse) { + + // keep track of consumers unable to allocate on new tree + unAllocatedConsumerIDs = make([]string, 0) + mapUnAllocatedConsumerIDs := make(map[string]bool) + noteUnAllocatedConsumer := func(cID string) { + mapUnAllocatedConsumerIDs[cID] = true + unAllocatedConsumerIDs = append(unAllocatedConsumerIDs, cID) + } + + responseMap = make(map[string]*TreeCacheCreateResponse) + + // create map of tree caches (for ease of reference by tree name) + treeCacheMap := make(map[string]*TreeCache) + for _, treeCache := range treeCacheList { + treeCacheMap[treeCache.GetTreeName()] = treeCache + } + + // delete controllers with obsolete trees + for _, treeName := range fc.GetTreeNames() { + if _, exists := treeCacheMap[treeName]; !exists { + fc.DeleteTree(treeName) + } + } + + // add controllers for new trees + for treeName, treeCache := range treeCacheMap { + if _, exists := fc.controllers[treeName]; !exists { + tree, response := treeCache.CreateTree() + fc.AddTree(tree) + responseMap[treeName] = response + } + } + + // update controllers + for _, controller := range fc.controllers { + treeName := controller.GetTreeName() + cache := treeCacheMap[treeName] + if cache != nil { + cIDs, response := controller.UpdateTree(cache) + for _, id := range cIDs { + if !mapUnAllocatedConsumerIDs[id] { + noteUnAllocatedConsumer(id) + } + } + responseMap[treeName] = response + } + } + + if len(unAllocatedConsumerIDs) == 0 { + return nil, responseMap + } + + // remove unallocated consumers + for _, controller := range fc.controllers { + for _, id := range unAllocatedConsumerIDs { + controller.DeAllocate(id) + } + } + return unAllocatedConsumerIDs, responseMap +} + +// String : printout +func (fc *ForestController) String() string { + var b bytes.Buffer + b.WriteString("ForestController: \n") + for treeName, controller := range fc.controllers { + tree := controller.GetTree() + if tree != nil { + b.WriteString(tree.String()) + } else { + b.WriteString("Tree " + treeName + " is null") + } + b.WriteString("\n") + } + return b.String() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotanode.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotanode.go new file mode 100644 index 000000000..dae2b1d59 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotanode.go @@ -0,0 +1,264 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package core + +import ( + "bytes" + "fmt" + "sort" + "strings" + "unsafe" + + tree "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/tree" + "k8s.io/klog/v2" +) + +// QuotaNode : a node in the quota tree +type QuotaNode struct { + //extends Node + tree.Node + // quota associated with the node + quota *Allocation + // quota limit is hard + isHard bool + // amount allocated on the node + allocated *Allocation + // list of consumers allocated on the node + consumers []*Consumer +} + +// NewQuotaNode : create a quota node +func NewQuotaNode(id string, quota *Allocation) (*QuotaNode, error) { + if len(id) == 0 || quota == nil { + return nil, fmt.Errorf("invalid parameters") + } + alloc, _ := NewAllocation(quota.GetSize()) + qn := &QuotaNode{ + Node: *tree.NewNode(id), + quota: quota, + isHard: false, + allocated: alloc, + consumers: make([]*Consumer, 0), + } + return qn, nil +} + +// NewQuotaNodeHard : create a quota node +func NewQuotaNodeHard(id string, quota *Allocation, isHard bool) (*QuotaNode, error) { + qn, err := NewQuotaNode(id, quota) + if err != nil { + return nil, err + } + qn.isHard = isHard + return qn, nil +} + +// CanFit : check if a consumer request can fit on this node +func (qn *QuotaNode) CanFit(c *Consumer) bool { + return c.GetRequest().Fit(qn.allocated, qn.quota) +} + +// AddRequest : add request of consumer to allocated amount on this node (acquire) +func (qn *QuotaNode) AddRequest(c *Consumer) { + qn.allocated.Add(c.GetRequest()) +} + +// SubtractRequest : subtract request of consumer from allocated amount on this node (release) +func (qn *QuotaNode) SubtractRequest(c *Consumer) { + qn.allocated.Subtract(c.GetRequest()) +} + +// AddConsumer : add a consumer to the consumers list +func (qn *QuotaNode) AddConsumer(c *Consumer) bool { + // TODO: need more efficient data structure + cid := c.GetID() + for _, ci := range qn.consumers { + if ci.GetID() == cid { + return false + } + } + qn.consumers = append(qn.consumers, c) + return true +} + +// RemoveConsumer : remove a consumer from the consumers list +func (qn *QuotaNode) RemoveConsumer(c *Consumer) bool { + // TODO: need more efficient data structure + cid := c.GetID() + for i, ci := range qn.consumers { + if ci.GetID() == cid { + qn.consumers = append(qn.consumers[:i], qn.consumers[i+1:]...) + return true + } + } + return false +} + +// Allocate : allocate a consumer on this node, assuming consumer request fits +func (qn *QuotaNode) Allocate(c *Consumer) { + qn.AddRequest(c) + qn.AddConsumer(c) + c.SetNode(qn) +} + +// SlideDown : slide down potential consumers from parent node +func (qn *QuotaNode) SlideDown() { + parent := qn.GetParent() + if parent != nil { + p := (*QuotaNode)(unsafe.Pointer(parent)) + // make copy of parent consumers list, as we may make changes to it + list := make([]*Consumer, len(p.GetConsumers())) + copy(list, p.GetConsumers()) + qnID := qn.GetID() + for _, c := range list { + if qn.HasLeaf(c) && qn.CanFit(c) { + p.RemoveConsumer(c) + qn.Allocate(c) + klog.V(4).Infof("*** Consumer %s slid down to node %s \n", c.GetID(), qnID) + } + } + } +} + +// SlideUp : slide up potential consumers to parent node; return true if able to fit given +// consumer on this node after sliding up other consumers +func (qn *QuotaNode) SlideUp(c *Consumer, applyPriority bool, allocationRecovery *AllocationRecovery, + preemptedConsumers *[]string) bool { + + if qn.isHard { + return false + } + + success := false + requested := c.GetRequest() + priority := c.GetPriority() + cType := c.GetType() + candidates := make([]*Consumer, 0) + scratch := qn.allocated.Clone() + + // TODO: ordering of consumers to slide up + for _, consumer := range qn.consumers { + if !applyPriority || priority > consumer.GetPriority() { + + if (consumer.IsUnPreemptable() || consumer.GetType() != cType) && qn.IsRoot() { + continue + } + + scratch.Subtract(consumer.GetRequest()) + candidates = append(candidates, consumer) + if requested.Fit(scratch, qn.quota) { + success = true + break + } + } + } + + if success { + p := qn.GetParent() + parent := (*QuotaNode)(unsafe.Pointer(p)) + for _, consumer := range candidates { + allocationRecovery.AlteredConsumer(consumer) + qn.SubtractRequest(consumer) + qn.RemoveConsumer(consumer) + consumer.SetNode(parent) + if parent != nil { + parent.AddConsumer(consumer) + klog.V(4).Infof("*** Consumer %s slid up to node %s \n", consumer.GetID(), parent.GetID()) + } else { + consumerID := consumer.GetID() + *preemptedConsumers = append(*preemptedConsumers, consumerID) + klog.V(4).Infof("*** Consumer %s is preempted! \n", consumerID) + } + } + } + return success +} + +// HasLeaf : check if the leaf node of a consumer is also a leaf of the subtree formed from this node as a root +func (qn *QuotaNode) HasLeaf(c *Consumer) bool { + groupID := c.GetGroupID() + for _, leaf := range qn.GetLeaves() { + if leaf.GetID() == groupID { + return true + } + } + return false +} + +// IsHard : +func (qn *QuotaNode) IsHard() bool { + return qn.isHard +} + +// GetQuota : +func (qn *QuotaNode) GetQuota() *Allocation { + return qn.quota +} + +// SetQuota : +func (qn *QuotaNode) SetQuota(quota *Allocation) { + qn.quota = quota +} + +// GetAllocated : +func (qn *QuotaNode) GetAllocated() *Allocation { + return qn.allocated +} + +// GetConsumers : +func (qn *QuotaNode) GetConsumers() []*Consumer { + return qn.consumers +} + +// String : print node with a specified level of indentation +func (qn *QuotaNode) String(level int) string { + var b bytes.Buffer + prefix := strings.Repeat("--", level) + prefix += "|" + fmt.Fprintf(&b, "%s: ", prefix+qn.ID) + fmt.Fprintf(&b, "isHard=%v; ", qn.isHard) + fmt.Fprintf(&b, "quota=%s; ", qn.quota) + fmt.Fprintf(&b, "allocated=%s; ", qn.allocated) + + fmt.Fprintf(&b, "consumers={ ") + ids := make([]string, 0, len(qn.consumers)) + for _, c := range qn.consumers { + ids = append(ids, c.GetID()) + } + sort.Strings(ids) + for _, id := range ids { + fmt.Fprintf(&b, "%s ", id) + } + fmt.Fprintf(&b, "}") + fmt.Fprintf(&b, "\n") + + if !qn.IsLeaf() { + children := qn.GetChildren() + list := make([]string, 0, len(children)) + childrenMap := make(map[string]*QuotaNode) + for _, cn := range children { + id := cn.GetID() + qcn := (*QuotaNode)(unsafe.Pointer(cn)) + list = append(list, id) + childrenMap[id] = qcn + } + sort.Strings(list) + for _, id := range list { + fmt.Fprintf(&b, "%s", childrenMap[id].String(level+1)) + } + } + return b.String() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotanode_test.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotanode_test.go new file mode 100644 index 000000000..1dfce6ebd --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotanode_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package core + +import ( + "reflect" + "testing" + + tree "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/tree" +) + +var ( + nodeA *tree.Node = tree.NewNode("A") + alloc1 Allocation = Allocation{ + x: []int{5, 10, 20}, + } + quotaNodeA QuotaNode = QuotaNode{ + Node: *nodeA, + quota: &alloc1, + isHard: false, + allocated: &Allocation{ + x: make([]int, len(alloc1.x)), + }, + consumers: make([]*Consumer, 0), + } +) + +func TestNewQuotaNode(t *testing.T) { + type args struct { + id string + quota *Allocation + } + tests := []struct { + name string + args args + want *QuotaNode + wantErr bool + }{ + {name: "testA", + args: args{ + id: "A", + quota: &alloc1, + }, + want: "aNodeA, + wantErr: false, + }, + {name: "test empty ID", + args: args{ + id: "", + quota: &alloc1, + }, + want: nil, + wantErr: true, + }, + {name: "test empty quota", + args: args{ + id: "A", + quota: nil, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got, err := NewQuotaNode(tt.args.id, tt.args.quota); !reflect.DeepEqual(got, tt.want) { + if (err != nil) != tt.wantErr { + t.Errorf("NewQuotaNode() error = %v, wantErr %v", err, tt.wantErr) + return + } + t.Errorf("NewQuotaNode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestQuotaNode_String(t *testing.T) { + type fields struct { + quotaNode QuotaNode + } + type args struct { + level int + } + tests := []struct { + name string + fields fields + args args + want string + }{ + {name: "testA", + fields: fields{ + quotaNode: quotaNodeA, + }, + args: args{ + level: 1, + }, + want: "--|A: isHard=false; quota=[5 10 20]; allocated=[0 0 0]; consumers={ }\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + qn := &tt.fields.quotaNode + if got := qn.String(tt.args.level); got != tt.want { + t.Errorf("QuotaNode.String() = %s, want %s", got, tt.want) + } + }) + } +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotatree.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotatree.go new file mode 100644 index 000000000..7aaa9eae2 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotatree.go @@ -0,0 +1,233 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package core + +import ( + "bytes" + "fmt" + "unsafe" + + tree "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/tree" + "k8s.io/klog/v2" +) + +// QuotaTree : a quota tree +type QuotaTree struct { + // extends tree + tree.Tree + // unique name of the tree + name string + // names of quota resources + resourceNames []string +} + +// NewQuotaTree : create a quota tree +func NewQuotaTree(name string, root *QuotaNode, resourceNames []string) *QuotaTree { + rootNode := (*tree.Node)(unsafe.Pointer(root)) + qt := &QuotaTree{ + Tree: *tree.NewTree(rootNode), + name: name, + resourceNames: resourceNames, + } + return qt +} + +// Allocate : allocate a consumer request +func (qt *QuotaTree) Allocate(c *Consumer, preemptedConsumers *[]string) bool { + groupID := c.GetGroupID() + leafNode := qt.GetLeafNode(groupID) + if leafNode == nil { + klog.V(4).Infof("Consumer %s member of unknown group %s \n", c.GetID(), groupID) + return false + } + + allocationRecovery := NewAllocationRecovery(c) + + path := leafNode.GetPathToRoot() + allocated := false + hitHard := false + attemptedNode := (*QuotaNode)(unsafe.Pointer(leafNode)) + for _, n := range path { + node := (*QuotaNode)(unsafe.Pointer(n)) + attemptedNode = node + hitHard = hitHard || node.IsHard() + + if !allocated { + if node.CanFit(c) || node.SlideUp(c, true, allocationRecovery, preemptedConsumers) { + node.Allocate(c) + allocationRecovery.AlteredNode(node) + allocated = true + } else { + if node.IsHard() { + break + } + } + } else { + if node.CanFit(c) || node.SlideUp(c, false, allocationRecovery, preemptedConsumers) { + node.AddRequest(c) + allocationRecovery.AlteredNode(node) + } else { + allocationRecovery.Recover() + *preemptedConsumers = make([]string, 0) + allocated = false + + if hitHard { + break + } else { + continue + } + } + } + } + + if allocated { + klog.V(4).Infof("*** Consumer %s allocated on node %s \n", c.GetID(), c.GetNode().GetID()) + } else { + klog.V(4).Infof("*** Consumer %s failed allocating on node %s \n", c.GetID(), attemptedNode.GetID()) + } + + /* + * If not allocated, attempt preempting lower priority consumers + */ + + priority := c.GetPriority() + cType := c.GetType() + if !allocated && priority > 0 { + klog.V(4).Infoln("*** Attempting to preempt lower priority consumers ...") + allocationRecovery.Reset() + n := len(path) + foundit := false + // traverse nodes from root down to leaf node + for i := n - 1; i >= 0; i-- { + node := (*QuotaNode)(unsafe.Pointer(path[i])) + if !foundit { + if node == attemptedNode { + foundit = true + } else { + continue + } + } + + // make copy of consumers list, as we may make changes to it + list := make([]*Consumer, len(node.GetConsumers())) + copy(list, node.GetConsumers()) + for _, consumer := range list { + if priority > consumer.GetPriority() && !consumer.IsUnPreemptable() && + consumer.GetType() == cType { + + node.RemoveConsumer(consumer) + for j := i; j < n; j++ { + qn := (*QuotaNode)(unsafe.Pointer(path[j])) + qn.SubtractRequest(consumer) + } + allocationRecovery.AlteredConsumer(consumer) + consumer.SetNode(nil) + consumerId := consumer.GetID() + *preemptedConsumers = append(*preemptedConsumers, consumerId) + klog.V(4).Infof("*** Consumer %s is preempted! \n", consumerId) + + if attemptedNode.CanFit(c) { + return qt.Allocate(c, preemptedConsumers) + } + } + } + } + + allocationRecovery.Recover() + *preemptedConsumers = make([]string, 0) + allocated = false + } + + return allocated +} + +// ForceAllocate : force allocate a consumer request on a given node +func (qt *QuotaTree) ForceAllocate(c *Consumer, nodeID string) bool { + node := qt.GetNode(nodeID) + if node == nil { + klog.V(4).Infof("Attemting to force allocate consumer %s on unkown node %s \n", c.GetID(), nodeID) + return false + } + + // place consumer on node + qNode := (*QuotaNode)(unsafe.Pointer(node)) + qNode.AddConsumer(c) + c.SetNode(qNode) + + // update allocated resources from node to root + path := node.GetPathToRoot() + for _, n := range path { + qn := (*QuotaNode)(unsafe.Pointer(n)) + qn.AddRequest(c) + } + return true +} + +// DeAllocate : deallocate a consumer request +func (qt *QuotaTree) DeAllocate(c *Consumer) bool { + node := c.GetNode() + if node == nil || !node.RemoveConsumer(c) { + klog.V(4).Infof("Consumer %s has no or inconsistent allocation node assignment \n", c.GetID()) + return false + } + + path := node.GetPathToRoot() + for _, n := range path { + qn := (*QuotaNode)(unsafe.Pointer(n)) + qn.SubtractRequest(c) + qn.SlideDown() + } + c.SetNode(nil) + return true +} + +// GetName : +func (qt *QuotaTree) GetName() string { + return qt.name +} + +// GetResourceNames : +func (qt *QuotaTree) GetResourceNames() []string { + return qt.resourceNames +} + +// GetQuotaSize : +func (qt *QuotaTree) GetQuotaSize() int { + return len(qt.resourceNames) +} + +// StringSimply : +func (qt *QuotaTree) StringSimply() string { + var b bytes.Buffer + fmt.Fprintf(&b, "Tree %s: ", qt.name) + t := (*tree.Tree)(unsafe.Pointer(qt)) + b.WriteString(t.String()) + return b.String() +} + +// String : +func (qt *QuotaTree) String() string { + var b bytes.Buffer + fmt.Fprintf(&b, "QuotaTree %s: \n", qt.name) + t := (*tree.Tree)(unsafe.Pointer(qt)) + r := (*QuotaNode)(unsafe.Pointer(t.GetRoot())) + if r != nil { + b.WriteString(r.String(0)) + } else { + b.WriteString("null") + } + return b.String() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/treecache.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/treecache.go new file mode 100644 index 000000000..823f1468e --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/treecache.go @@ -0,0 +1,446 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "reflect" + "sort" + "strconv" + + utils "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/utils" + "k8s.io/klog/v2" +) + +// TreeCache : cache area for the parts of the quota tree (resources, nodes, topology) +// which may be updated; a quota tree may be created from the cache at any point +type TreeCache struct { + // the unique name of the tree + treeName string + // map of resource names: resourceName -> bool + resourceNamesMap map[string]bool + // map of node specs: nodeName -> nodeSpec + nodeSpecMap map[string]utils.JNodeSpec + + // map of renamed nodes: oldName -> newName + renamedNodesMap map[string]string +} + +// NewTreeCache : create a tree cache +func NewTreeCache() *TreeCache { + tc := &TreeCache{} + tc.Clear() + return tc +} + +// SetTreeName : set the name of the tree; overrides earlier name +func (tc *TreeCache) SetTreeName(name string) { + tc.treeName = name +} + +// GetTreeName : get the name of the tree +func (tc *TreeCache) GetTreeName() string { + return tc.treeName +} + +// SetDefaultTreeName : set the name of the tree to the default name +func (tc *TreeCache) SetDefaultTreeName() { + tc.SetTreeName(utils.DefaultTreeName) +} + +// clearTreeName : clear the name of the tree +func (tc *TreeCache) clearTreeName() { + tc.treeName = "" +} + +// AddResourceName : add a resource name; overrides earlier name +func (tc *TreeCache) AddResourceName(name string) { + if len(name) != 0 { + tc.resourceNamesMap[name] = true + } +} + +// AddResourceNames : add a list of resource names +func (tc *TreeCache) AddResourceNames(names []string) { + for _, name := range names { + tc.AddResourceName(name) + } +} + +// DeleteResourceName : delete a resource name +func (tc *TreeCache) DeleteResourceName(name string) { + delete(tc.resourceNamesMap, name) +} + +// GetResourceNames : get a sorted list of resource names +func (tc *TreeCache) GetResourceNames() []string { + names := make([]string, 0, len(tc.resourceNamesMap)) + for name := range tc.resourceNamesMap { + names = append(names, name) + } + sort.Strings(names) + return names +} + +// GetNumResourceNames : the number of resource names +func (tc *TreeCache) GetNumResourceNames() int { + return len(tc.resourceNamesMap) +} + +// clearResourceNames : delete all resource names +func (tc *TreeCache) clearResourceNames() { + tc.resourceNamesMap = make(map[string]bool) +} + +// SetDefaultResourceNames : set resource names to the default ones +func (tc *TreeCache) SetDefaultResourceNames() { + for _, name := range utils.DefaultResourceNames { + tc.resourceNamesMap[name] = true + } +} + +// AddTreeInfoFromString : add tree name and resource names by parsing tree information string; +/* { "name": "ExampleTree", "resourceNames": [ "cpu", "memory" ] } */ +func (tc *TreeCache) AddTreeInfoFromString(treeInfo string) error { + var jTreeInfo utils.JTreeInfo + err := json.Unmarshal([]byte(treeInfo), &jTreeInfo) + if err != nil { + return err + } + tc.SetTreeName(jTreeInfo.Name) + tc.AddResourceNames(jTreeInfo.ResourceNames) + return nil +} + +// AddNodeSpec : add a node spec to the cache; overrides earlier node spec with same name +/* {"parent": "Org-A", "hard": "true", "quota": { "cpu": "1" } } */ +func (tc *TreeCache) AddNodeSpec(nodeName string, nodeSpec utils.JNodeSpec) error { + if reflect.DeepEqual(nodeSpec, utils.JNodeSpec{}) { + return fmt.Errorf("node " + nodeName + ": node spec is invalid; not added") + } + if nodeSpec.Quota == nil { + return fmt.Errorf("node " + nodeName + ": quota is missing; not added") + } + tc.removeRenamedNode(nodeName) + tc.nodeSpecMap[nodeName] = nodeSpec + return nil +} + +// AddNodeSpecs : add a map of node specs to the cache; invalid node specs are not added +func (tc *TreeCache) AddNodeSpecs(nodeSpecs map[string]utils.JNodeSpec) error { + errString := "" + for name, spec := range nodeSpecs { + errNode := tc.AddNodeSpec(name, spec) + if errNode != nil { + errString += errNode.Error() + } + } + if len(errString) > 0 { + return fmt.Errorf(errString) + } + return nil +} + +// AddNodeSpecsFromString : add node specs by parsing string with one or more node information +/* { "Root": { "parent": "nil", "quota": { "cpu": "10" } }, "Org-A": {"parent": "Root", "quota": { "cpu": "4" } } } */ +/* { "Context-1": {"parent": "Org-A", "hard": "true", "quota": { "cpu": "1" } } } */ +func (tc *TreeCache) AddNodeSpecsFromString(nodesInfo string) error { + jNodes := make(map[string]utils.JNodeSpec) + err := json.Unmarshal([]byte(nodesInfo), &jNodes) + if err != nil { + return err + } + return tc.AddNodeSpecs(jNodes) +} + +// clearNodeSpecs : clear all node specs +func (tc *TreeCache) clearNodeSpecs() { + tc.nodeSpecMap = make(map[string]utils.JNodeSpec) +} + +// RenameNode : rename a node in the cache +func (tc *TreeCache) RenameNode(nodeName string, newNodeName string) error { + if len(nodeName) == 0 || len(newNodeName) == 0 || nodeName == newNodeName { + return fmt.Errorf("invalid nodeName %s and/or newNodeName %s", nodeName, newNodeName) + } + var nodeSpec utils.JNodeSpec + var exists bool + if nodeSpec, exists = tc.nodeSpecMap[nodeName]; !exists { + return fmt.Errorf("node with name %s does not exist", nodeName) + } + delete(tc.nodeSpecMap, nodeName) + tc.nodeSpecMap[newNodeName] = nodeSpec + + // modify nodes which have the renamed node as parent + nodeNames := tc.GetNodeNames() + for _, n := range nodeNames { + spec := tc.nodeSpecMap[n] + if spec.Parent == nodeName { + spec.Parent = newNodeName + tc.nodeSpecMap[n] = spec + } + } + + // make a note of the renaming + tc.setRenamedNode(nodeName, newNodeName) + return nil +} + +// GetRenamedNode : get new name of node if renamed, otherwise emty +func (tc *TreeCache) GetRenamedNode(nodeName string) string { + return tc.renamedNodesMap[nodeName] +} + +// setRenamedNode : set the new name of node +func (tc *TreeCache) setRenamedNode(nodeName string, newNodeName string) { + tc.removeRenamedNode(newNodeName) + tc.renamedNodesMap[nodeName] = newNodeName +} + +// removeRenamedNode : remove a node from the renamed nodes map +func (tc *TreeCache) removeRenamedNode(nodeName string) { + delete(tc.renamedNodesMap, nodeName) +} + +// clearRenamedNodes : delete map of renamed nodes +func (tc *TreeCache) clearRenamedNodes() { + tc.renamedNodesMap = make(map[string]string) +} + +// DeleteNode : delete a node from the cache +func (tc *TreeCache) DeleteNode(nodeName string) { + delete(tc.nodeSpecMap, nodeName) +} + +// GetNodeNames : get a sorted list of node names in the cache +func (tc *TreeCache) GetNodeNames() []string { + names := make([]string, 0, len(tc.nodeSpecMap)) + for name := range tc.nodeSpecMap { + names = append(names, name) + } + sort.Strings(names) + return names +} + +// FromFile : fill cache from a JSON file +func (tc *TreeCache) FromFile(treeFileName string) error { + /* + * parse JSON file + */ + byteValue, err := ioutil.ReadFile(treeFileName) + if err != nil { + return fmt.Errorf("error reading file: %s", err.Error()) + } + return tc.FromString(string(byteValue)) +} + +// FromString : fill cache from the JSON string representation of the tree +func (tc *TreeCache) FromString(treeString string) error { + var jQuotaTree utils.JQuotaTree + err := json.Unmarshal([]byte(treeString), &jQuotaTree) + if err != nil { + return fmt.Errorf("error parsing tree: %s", err.Error()) + } + return tc.FromStruct(jQuotaTree) +} + +// FromStruct : fill cache from the JSON struct of the tree +func (tc *TreeCache) FromStruct(jQuotaTree utils.JQuotaTree) error { + /* + * process all fields + */ + klog.V(4).Infoln("kind=" + jQuotaTree.Kind) + if jQuotaTree.Kind != utils.DefaultTreeKind { + return fmt.Errorf("invalid kind: %s", jQuotaTree.Kind) + } + + klog.V(4).Info("treeName=" + jQuotaTree.MetaData.Name + "; ") + tc.SetTreeName(jQuotaTree.MetaData.Name) + + tc.AddResourceNames(jQuotaTree.Spec.ResourceNames) + resourceNames := tc.GetResourceNames() + klog.V(4).Infof("numResources=%d\n", len(resourceNames)) + klog.V(4).Infof("resourceNames = %s\n", resourceNames) + klog.V(4).Infoln() + + errNodes := tc.AddNodeSpecs(jQuotaTree.Spec.Nodes) + if errNodes != nil { + return fmt.Errorf("invalid specs for some nodes") + } + return nil +} + +// TreeCacheCreateResponse : response information of creating a tree from the cache +type TreeCacheCreateResponse struct { + // the unique name of the tree + TreeName string + // name of root node, empty if missing root + RootNodeName string + // names of dangling nodes (nodes in the cache but not connected in the tree) + DanglingNodeNames []string +} + +// IsClean : tree created from cache has a root and no dangling nodes +func (response *TreeCacheCreateResponse) IsClean() bool { + return len(response.RootNodeName) > 0 && len(response.DanglingNodeNames) == 0 +} + +// String : printout of TreeCacheCreateResponse +func (response *TreeCacheCreateResponse) String() string { + var b bytes.Buffer + b.WriteString("Response: {") + fmt.Fprintf(&b, "TreeName=%s; ", response.TreeName) + fmt.Fprintf(&b, "RootNodeName=\"%s\"; ", response.RootNodeName) + fmt.Fprintf(&b, "DanglingNodeNames=%s", response.DanglingNodeNames) + b.WriteString("}") + return b.String() +} + +// CreateTree : create tree from cache +func (tc *TreeCache) CreateTree() (*QuotaTree, *TreeCacheCreateResponse) { + + // names of resources + resourceNames := make([]string, 0) + // map of all nodes in the tree + nodeMap := make(map[string]*QuotaNode) + // map from node ID to parent ID for non-root nodes + parentMap := make(map[string]string) + // the name of the root node + var rootNodeName string + // tree create response + response := &TreeCacheCreateResponse{ + TreeName: tc.treeName, + RootNodeName: "", + DanglingNodeNames: make([]string, 0), + } + var b bytes.Buffer + + /* + * set default values, if missing + */ + if len(tc.GetTreeName()) == 0 { + tc.SetDefaultTreeName() + } + if tc.GetNumResourceNames() == 0 { + tc.SetDefaultResourceNames() + } + + /* + * process all nodes + */ + nodeNames := tc.GetNodeNames() + for _, nodeName := range nodeNames { + + fmt.Fprintf(&b, "node=%s; ", nodeName) + nodeSpec := tc.nodeSpecMap[nodeName] + parent := nodeSpec.Parent + if len(parent) == 0 { + parent = "nil" + } + fmt.Fprintf(&b, "parent="+parent+"; ") + + if parent == "nil" { + if len(rootNodeName) > 0 { + klog.Errorf("node " + nodeName + ": duplicate root(" + rootNodeName + "); skipping node \n") + continue + } + rootNodeName = nodeName + } else { + parentMap[nodeName] = parent + } + + hard, err := strconv.ParseBool(nodeSpec.Hard) + if err != nil { + hard = false + } + fmt.Fprintf(&b, "hard="+strconv.FormatBool(hard)+"; ") + + resourceNames = tc.GetResourceNames() + numResources := tc.GetNumResourceNames() + values := make([]int, numResources) + for i, res := range resourceNames { + values[i], err = strconv.Atoi(nodeSpec.Quota[res]) + if err != nil { + klog.Errorf("node " + nodeName + ": error converting " + + nodeSpec.Quota[res] + "; assuming 0 \n") + } + fmt.Fprintf(&b, res+"="+strconv.Itoa(values[i])+"; ") + } + + /* + * create node and add to map + */ + alloc, _ := NewAllocationCopy(values) + node, _ := NewQuotaNodeHard(nodeName, alloc, hard) + nodeMap[nodeName] = node + fmt.Fprintln(&b) + } + + /* + * set parents and children for all nodes + */ + if len(rootNodeName) > 0 { + for nodeID, qn := range nodeMap { + parentID := parentMap[nodeID] + if len(parentID) > 0 { + parentNode := nodeMap[parentID] + if parentNode != nil { + parentNode.AddChild(&qn.Node) + } else { + klog.Errorf("node " + nodeID + ": parent node " + parentID + " unknown; dropping node \n") + } + } + } + } else { + klog.Errorf("root node is missing; creating empty tree \n") + } + + /* + * create tree + */ + klog.V(4).Infoln(b.String()) + tree := NewQuotaTree(tc.GetTreeName(), nodeMap[rootNodeName], resourceNames) + klog.V(4).Infoln(tree.StringSimply()) + klog.V(4).Infoln() + klog.V(4).Infoln(tree) + + /* + * prepare response + */ + response.RootNodeName = rootNodeName + treeNodeNames := make(map[string]bool) + for _, name := range tree.GetNodeIDs() { + treeNodeNames[name] = true + } + for _, name := range nodeNames { + if !treeNodeNames[name] { + response.DanglingNodeNames = append(response.DanglingNodeNames, name) + } + } + + return tree, response +} + +// Clear : clear the cache +func (tc *TreeCache) Clear() { + tc.clearTreeName() + tc.clearResourceNames() + tc.clearNodeSpecs() + tc.clearRenamedNodes() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/core/treecontroller.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/treecontroller.go new file mode 100644 index 000000000..466a8b4dd --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/core/treecontroller.go @@ -0,0 +1,301 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package core + +import ( + "bytes" + "fmt" + "sort" + + tree "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/tree" + "k8s.io/klog/v2" +) + +// Controller : controller of a quota tree +type Controller struct { + // the quota tree + tree *QuotaTree + // consumers already allocated (running) + consumers map[string]*Consumer + + // list of IDs of preempted consumers due to previous allocation request + preemptedConsumers []string + // list of preempted consumer objects due to previous allocation request + preemptedConsumersArray []*Consumer +} + +// NewController : create a quota controller +func NewController(tree *QuotaTree) *Controller { + return &Controller{ + tree: tree, + consumers: make(map[string]*Consumer), + preemptedConsumers: make([]string, 0), + preemptedConsumersArray: make([]*Consumer, 0), + } +} + +// Allocate : allocate a consumer +func (controller *Controller) Allocate(consumer *Consumer) *AllocationResponse { + klog.V(4).Infof("Allocating consumer %s:\n", consumer.GetID()) + controller.preemptedConsumers = make([]string, 0) + controller.preemptedConsumersArray = make([]*Consumer, 0) + var allocationMessage bytes.Buffer + fmt.Fprintf(&allocationMessage, "") + allocated := controller.tree.Allocate(consumer, &controller.preemptedConsumers) + if allocated { + controller.consumers[consumer.GetID()] = consumer + for _, id := range controller.preemptedConsumers { + c := controller.consumers[id] + if c != nil { + controller.preemptedConsumersArray = append(controller.preemptedConsumersArray, c) + controller.Remove(id) + } + } + } else { + fmt.Fprintf(&allocationMessage, "Failed to allocate quota on quota designation '%s'", controller.GetTreeName()) + + } + controller.PrintState(consumer, allocated) + preemptedIds := make([]string, len(controller.preemptedConsumers)) + copy(preemptedIds, controller.preemptedConsumers) + allocResponse := NewAllocationResponse(consumer.GetID()) + allocResponse.Append(allocated, allocationMessage.String(), &preemptedIds) + return allocResponse +} + +// ForceAllocate : force allocate a consumer on a given node +func (controller *Controller) ForceAllocate(consumer *Consumer, nodeID string) *AllocationResponse { + consumerID := consumer.GetID() + klog.V(4).Infof("Force allocating consumer %s on node %s:\n", consumerID, nodeID) + allocResponse := NewAllocationResponse(consumerID) + allocated := controller.GetTree().ForceAllocate(consumer, nodeID) + controller.PrintState(consumer, allocated) + if !allocated { + allocResponse.SetAllocated(false) + msg := fmt.Sprintf("failed force allocate consumer %v on node %v", + consumerID, nodeID) + allocResponse.SetMessage(msg) + return allocResponse + } + controller.consumers[consumer.GetID()] = consumer + return allocResponse +} + +// DeAllocate : deallocate a consumer +func (controller *Controller) DeAllocate(consumerID string) bool { + klog.V(4).Infof("Deallocating consumer %s:\n", consumerID) + controller.preemptedConsumers = make([]string, 0) + consumer := controller.consumers[consumerID] + if consumer != nil { + delete(controller.consumers, consumerID) + deallocated := controller.tree.DeAllocate(consumer) + controller.PrintState(consumer, deallocated) + return deallocated + } + klog.V(4).Infoln("Consumer " + consumerID + " is not allocated") + return false +} + +// GetPreemptedConsumers : get a list of the preempted consumer IDs +func (controller *Controller) GetPreemptedConsumers() []string { + return controller.preemptedConsumers +} + +// GetPreemptedConsumersArray : get a list of the preempted consumer objects +func (controller *Controller) GetPreemptedConsumersArray() []*Consumer { + return controller.preemptedConsumersArray +} + +// Remove : remove a consumer +func (controller *Controller) Remove(id string) bool { + _, exists := controller.consumers[id] + if exists { + delete(controller.consumers, id) + } + return exists +} + +// GetTree : the quota tree +func (controller *Controller) GetTree() *QuotaTree { + return controller.tree +} + +// GetTreeName : the name of the quota tree, empty if nil +func (controller *Controller) GetTreeName() string { + if controller.tree == nil { + return "" + } + return controller.tree.GetName() +} + +// GetConsumer : retrieve a consumer by id +func (controller *Controller) GetConsumer(id string) *Consumer { + return controller.consumers[id] +} + +// IsConsumerAllocated : check if a consumer is already allocated +func (controller *Controller) IsConsumerAllocated(id string) bool { + return controller.consumers[id] != nil +} + +// IsAllocated : check if there are consumers already allocated +func (controller *Controller) IsAllocated() bool { + return len(controller.consumers) > 0 +} + +// GetConsumerIDs : get IDs of all consumers allocated +func (controller *Controller) GetConsumerIDs() []string { + allIDs := make([]string, len(controller.consumers)) + i := 0 + for id := range controller.consumers { + allIDs[i] = id + i++ + } + return allIDs +} + +// GetQuotaSize : get dimension of quota array (number of resources) +func (controller *Controller) GetQuotaSize() int { + if controller.tree == nil { + return 0 + } + return controller.tree.GetQuotaSize() +} + +// GetResourceNames : get resource names (null if no tree) +func (controller *Controller) GetResourceNames() []string { + if controller.tree == nil { + return nil + } + return controller.tree.GetResourceNames() +} + +// UpdateTree : update tree from cache; +// returns nil if able to allocate all consumers onto updated tree, +// otherwise a list of the IDs of unallocated consumers is returned +func (controller *Controller) UpdateTree(treeCache *TreeCache) (unAllocatedConsumerIDs []string, response *TreeCacheCreateResponse) { + + // get new tree from cache + var newTree *QuotaTree + newTree, response = treeCache.CreateTree() + + // keep track of consumers unable to allocate on new tree + unAllocatedConsumerIDs = make([]string, 0) + noteUnAllocatedConsumer := func(cID string) { + unAllocatedConsumerIDs = append(unAllocatedConsumerIDs, cID) + } + + // move all consumers to new tree + for _, c := range controller.consumers { + groupID := c.GetGroupID() + // update name if changed + renamedGroupID := treeCache.GetRenamedNode(groupID) + if len(renamedGroupID) > 0 { + groupID = renamedGroupID + c.SetGroupID(groupID) + } + newGroupNode := newTree.GetNode(groupID) + + var newANode *tree.Node + if aNode := c.GetNode(); aNode != nil { + aNodeID := aNode.GetID() + // update name if changed + renamedANodeID := treeCache.GetRenamedNode(aNodeID) + if len(renamedANodeID) > 0 { + aNodeID = renamedANodeID + } + newANode = newTree.GetNode(aNodeID) + } + + // newNode : the node to allocate the consumer + var newNode *tree.Node + if newGroupNode != nil { + if newANode != nil && newANode.HasLeaf(groupID) { + newNode = newANode + } else { + // aNode does not exist in the new tree, use group node instead + newNode = newGroupNode + } + } else { + // group node does not exist in the new tree, use root node + newNode = newTree.GetRoot() + } + + cID := c.GetID() + if newNode == nil { + // we should not have an empty tree + noteUnAllocatedConsumer(cID) + continue + } + + if !newTree.ForceAllocate(c, newNode.GetID()) { + noteUnAllocatedConsumer(cID) + continue + } + } + + // set the tree as the new tree + controller.tree = newTree + + // remove unallocated consumers + if len(unAllocatedConsumerIDs) > 0 { + for _, id := range unAllocatedConsumerIDs { + delete(controller.consumers, id) + } + return unAllocatedConsumerIDs, response + } + return nil, response +} + +// PrintState : print state of quota tree after consumer (de)allocation step +func (controller *Controller) PrintState(c *Consumer, status bool) { + klog.V(4).Infoln(c) + klog.V(4).Info(controller.tree) + if status { + klog.V(4).Infoln("Status = Success") + } else { + klog.V(4).Infoln("Status = Failure") + } + klog.V(4).Infof("Preempted Consumers: %v\n", controller.GetPreemptedConsumers()) + klog.V(4).Infoln() +} + +// String : printout +func (controller *Controller) String() string { + var b bytes.Buffer + b.WriteString("TreeController: \n") + + if controller.tree != nil { + b.WriteString(controller.tree.String()) + } else { + b.WriteString("null") + } + + // print consumers sorted list + b.WriteString("Consumers: \n") + ids := make([]string, len(controller.consumers)) + i := 0 + for id := range controller.consumers { + ids[i] = id + i++ + } + sort.Strings(ids) + for _, id := range ids { + b.WriteString(controller.consumers[id].String() + "\n") + } + + return b.String() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/quotamanager.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/quotamanager.go new file mode 100644 index 000000000..1e96d8738 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/quotamanager.go @@ -0,0 +1,481 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package quota + +import ( + "bytes" + "fmt" + "strings" + + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/core" + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/utils" + "k8s.io/klog/v2" +) + +// Manager : A quota manager of quota trees; +// This should be the main interface to quota management +type Manager struct { + // all managed agents: treeName -> agent(treeController, treeCache) + agents map[string]*agent + // all forests: forestName -> forestController + forests map[string]*core.ForestController + // mapping tree name to forest name: treeName -> forestName + treeToForest map[string]string + // all consumer information: consumerID -> ConsumerInfo + consumerInfos map[string]*ConsumerInfo + // mode of the quota mnager; initially in maintenance mode + mode Mode +} + +// agent : a pair of controller and its tree cache +type agent struct { + // the tree controller; maintains a tree instance + controller *core.Controller + // the tree cache from which a tree instance is derived + cache *core.TreeCache +} + +// Mode : the mode of a quota tree +type Mode int + +const ( + // Normal operation: consumers allocated/deallocated; tree updates in cache + Normal = iota + // Maintenance operation: consumers forced allocated; tree updates in cache + Maintenance +) + +// NewManager : create a new quota manager +func NewManager() *Manager { + return &Manager{ + agents: make(map[string]*agent), + forests: make(map[string]*core.ForestController), + treeToForest: make(map[string]string), + consumerInfos: map[string]*ConsumerInfo{}, + mode: Maintenance, + } +} + +// newAgent : create a new treeAgent(treeController, treeCache) +func newAgentByName(treeName string) (*agent, error) { + treeCache := core.NewTreeCache() + treeCache.SetTreeName(treeName) + return newAgentFromCache(treeCache) +} + +// newAgentFromCache : create a new tree agent from tree cache +func newAgentFromCache(treeCache *core.TreeCache) (*agent, error) { + if treeName := treeCache.GetTreeName(); len(treeName) == 0 { + return nil, fmt.Errorf("invalid tree name %v", treeName) + } + tree, response := treeCache.CreateTree() + if !response.IsClean() { + klog.Warningf("Warning: cache not clean after tree created: %v \n", response.String()) + } + return &agent{ + controller: core.NewController(tree), + cache: treeCache, + }, nil +} + +// GetMode : get the mode of the quota manager +func (m *Manager) GetMode() Mode { + return m.mode +} + +// SetMode : set the mode of the quota manager +func (m *Manager) SetMode(mode Mode) bool { + m.mode = mode + // in the future we may restrict some mode transitions + return true +} + +// GetMode : get the mode of the quota manager +func (m *Manager) GetModeString() string { + modeString := "mode set to " + switch m.mode { + case Normal: + modeString += "Normal" + case Maintenance: + modeString += "Maintenance" + default: + modeString += "Unknown" + } + return modeString +} + +// AddTreeByName : add an empty tree with a given name +func (m *Manager) AddTreeByName(treeName string) (string, error) { + if _, exists := m.agents[treeName]; exists { + return treeName, fmt.Errorf("tree %v already exists; delete first", treeName) + } + agent, err := newAgentByName(treeName) + if err != nil { + return treeName, err + } + m.agents[treeName] = agent + return treeName, nil +} + +// AddTreeFromString : add a quota tree from the string JSON representation of the tree +func (m *Manager) AddTreeFromString(treeSring string) (string, error) { + treeCache := core.NewTreeCache() + if err := treeCache.FromString(treeSring); err != nil { + return "", err + } + return m.addTree(treeCache) +} + +// AddTreeFromStruct : add a quota tree from the JSON struct of the tree +func (m *Manager) AddTreeFromStruct(jQuotaTree utils.JQuotaTree) (string, error) { + treeCache := core.NewTreeCache() + if err := treeCache.FromStruct(jQuotaTree); err != nil { + return "", err + } + return m.addTree(treeCache) +} + +// addTree : add a tree from tree cache; returns name of tree added +func (m *Manager) addTree(treeCache *core.TreeCache) (string, error) { + treeName := treeCache.GetTreeName() + if _, exists := m.agents[treeName]; exists { + return treeName, fmt.Errorf("tree %v already exists; delete first", treeName) + } + agent, err := newAgentFromCache(treeCache) + if err != nil { + return treeName, err + } + m.agents[treeName] = agent + return treeName, nil +} + +// DeleteTree : delete a quota tree +func (m *Manager) DeleteTree(treeName string) error { + agent := m.agents[treeName] + if agent == nil { + return fmt.Errorf("tree %v does not exist", treeName) + } + if agent.controller.IsAllocated() { + return fmt.Errorf("consumer(s) allocated in tree %v", treeName) + } + delete(m.agents, treeName) + return nil +} + +// GetTreeCache : get the tree cache corresponding to a tree; nil if does not exist +func (m *Manager) GetTreeCache(treeName string) *core.TreeCache { + if agent, exists := m.agents[treeName]; exists { + return agent.cache + } + return nil +} + +// GetTreeNames : get the names of all the trees +func (m *Manager) GetTreeNames() []string { + names := make([]string, len(m.agents)) + i := 0 + for name := range m.agents { + names[i] = name + i++ + } + return names +} + +// UpdateTree : update tree from cache +func (m *Manager) UpdateTree(treeName string) (unallocatedConsumerIDs []string, response *core.TreeCacheCreateResponse, err error) { + if agent, exists := m.agents[treeName]; exists { + unallocatedConsumerIDs, response = agent.controller.UpdateTree(agent.cache) + return unallocatedConsumerIDs, response, nil + } + return nil, nil, fmt.Errorf("tree %v does not exist", treeName) +} + +// AddConsumer : add a consumer info +func (m *Manager) AddConsumer(consumerInfo *ConsumerInfo) (bool, error) { + consumerID := consumerInfo.GetID() + if _, exists := m.consumerInfos[consumerID]; exists { + return false, fmt.Errorf("consumer %s already exists", consumerID) + } + m.consumerInfos[consumerID] = consumerInfo + return true, nil +} + +// RemoveConsumer : remove a consumer info, does not de-allocate any currently allocated consumers +func (m *Manager) RemoveConsumer(consumerID string) (bool, error) { + if _, exists := m.consumerInfos[consumerID]; !exists { + return false, fmt.Errorf("consumer %s does not exist", consumerID) + } + delete(m.consumerInfos, consumerID) + return true, nil +} + +// GetAllConsumerIDs : get IDs of all consumers added +func (m *Manager) GetAllConsumerIDs() []string { + allIDs := make([]string, len(m.consumerInfos)) + i := 0 + for id := range m.consumerInfos { + allIDs[i] = id + i++ + } + return allIDs +} + +// Allocate : allocate a consumer on a tree +func (m *Manager) Allocate(treeName string, consumerID string) (response *core.AllocationResponse, err error) { + agent := m.agents[treeName] + if agent == nil { + return nil, fmt.Errorf("invalid tree name %s", treeName) + } + consumerInfo := m.consumerInfos[consumerID] + if consumerInfo == nil { + return nil, fmt.Errorf("consumer %s does not exist, create and add first", consumerID) + } + if agent.controller.IsConsumerAllocated(consumerID) { + return nil, fmt.Errorf("consumer %s already allocated on tree %s", consumerID, treeName) + } + resourceNames := agent.cache.GetResourceNames() + consumer, err := consumerInfo.CreateTreeConsumer(treeName, resourceNames) + if err != nil { + return nil, fmt.Errorf("failure creating consumer %s on tree %s", consumerID, treeName) + } + + klog.V(4).Infoln(consumer) + if m.mode == Normal { + response = agent.controller.Allocate(consumer) + } else { + response = agent.controller.ForceAllocate(consumer, consumer.GetGroupID()) + } + if !response.IsAllocated() { + return nil, fmt.Errorf(response.GetMessage()) + } + return response, err +} + +// IsAllocated : check if a consumer is allocated on a tree +func (m *Manager) IsAllocated(treeName string, consumerID string) bool { + if agent, exists := m.agents[treeName]; exists { + return agent.controller.IsConsumerAllocated(consumerID) + } + return false +} + +// DeAllocate : de-allocate a consumer from a tree +func (m *Manager) DeAllocate(treeName string, consumerID string) bool { + if agent, exists := m.agents[treeName]; exists { + return agent.controller.DeAllocate(consumerID) + } + return false +} + +// AddForest : add a new forest +func (m *Manager) AddForest(forestName string) error { + if _, exists := m.forests[forestName]; exists { + return fmt.Errorf("duplicate forest name %v", forestName) + } + m.forests[forestName] = core.NewForestController() + return nil +} + +// DeleteForest : delete a forest +func (m *Manager) DeleteForest(forestName string) error { + if forestController, exists := m.forests[forestName]; exists { + treeNames := forestController.GetTreeNames() + for i := 0; i < len(treeNames); i++ { + delete(m.treeToForest, treeNames[i]) + } + delete(m.forests, forestName) + return nil + } + return fmt.Errorf("forest %v does not exist", forestName) +} + +// AddTreeToForest : add an already defined tree to a forest +func (m *Manager) AddTreeToForest(forestName string, treeName string) error { + if forestController, exists := m.forests[forestName]; exists { + if agent, exists := m.agents[treeName]; exists { + forestController.AddController(agent.controller) + m.treeToForest[treeName] = forestName + return nil + } + return fmt.Errorf("unknown tree name %v", treeName) + } + return fmt.Errorf("unknown forest name %v", forestName) +} + +// DeleteTreeFromForest : delete a tree from a forest +func (m *Manager) DeleteTreeFromForest(forestName string, treeName string) error { + if len(forestName) == 0 || m.treeToForest[treeName] != forestName { + return fmt.Errorf("forest %v does not include tree %v", forestName, treeName) + } + delete(m.treeToForest, treeName) + m.forests[forestName].DeleteController(treeName) + return nil +} + +// GetForestNames : get the names of all forests +func (m *Manager) GetForestNames() []string { + names := make([]string, len(m.forests)) + i := 0 + for name := range m.forests { + names[i] = name + i++ + } + return names +} + +// GetForestTreeNames : get the tree names for all forests +func (m *Manager) GetForestTreeNames() map[string][]string { + forestTreeNames := make(map[string][]string) + for forestName, controller := range m.forests { + forestTreeNames[forestName] = controller.GetTreeNames() + } + return forestTreeNames +} + +// UpdateForest : update forest from cache +func (m *Manager) UpdateForest(forestName string) (unallocatedConsumerIDs []string, + response map[string]*core.TreeCacheCreateResponse, err error) { + + if forestController, exists := m.forests[forestName]; exists { + treeNames := forestController.GetTreeNames() + treeCaches := make([]*core.TreeCache, len(treeNames)) + for i := 0; i < len(treeNames); i++ { + treeCaches[i] = m.agents[treeNames[i]].cache + } + unallocatedConsumerIDs, response = forestController.UpdateTrees(treeCaches) + return unallocatedConsumerIDs, response, nil + } + return nil, nil, fmt.Errorf("forest %v does not exist", forestName) +} + +// AllocateForest : allocate a consumer on a forest +func (m *Manager) AllocateForest(forestName string, consumerID string) (response *core.AllocationResponse, err error) { + forestController := m.forests[forestName] + if forestController == nil { + return nil, fmt.Errorf("invalid forest name %s", forestName) + } + consumerInfo := m.consumerInfos[consumerID] + if consumerInfo == nil { + return nil, fmt.Errorf("consumer %s does not exist, create and add first", consumerID) + } + if forestController.IsConsumerAllocated(consumerID) { + return nil, fmt.Errorf("consumer %s already allocated on forest %s", consumerID, forestName) + } + resourceNames := forestController.GetResourceNames() + forestConsumer, err := consumerInfo.CreateForestConsumer(forestName, resourceNames) + if err != nil { + return nil, fmt.Errorf("failure creating forest consumer %s in forest %s", consumerID, forestName) + } + + if m.mode == Normal { + response = forestController.Allocate(forestConsumer) + } else { + groupIDs := make(map[string]string) + for treeName, consumer := range forestConsumer.GetConsumers() { + groupIDs[treeName] = consumer.GetGroupID() + } + response = forestController.ForceAllocate(forestConsumer, groupIDs) + } + if !response.IsAllocated() { + return nil, fmt.Errorf(response.GetMessage()) + } + return response, nil +} + +// IsAllocatedForest : check if a consumer is allocated on a forest +func (m *Manager) IsAllocatedForest(forestName string, consumerID string) bool { + if forestController, exists := m.forests[forestName]; exists { + return forestController.IsConsumerAllocated(consumerID) + } + return false +} + +// DeAllocateForest : de-allocate a consumer from a forest +func (m *Manager) DeAllocateForest(forestName string, consumerID string) bool { + if forestController, exists := m.forests[forestName]; exists { + return forestController.DeAllocate(consumerID) + } + return false +} + +// UpdateAll : update all trees and forests from caches +func (m *Manager) UpdateAll() (treeUnallocatedConsumerIDs map[string][]string, + treeResponse map[string]*core.TreeCacheCreateResponse, + forestUnallocatedConsumerIDs map[string][]string, + forestResponse map[string]map[string]*core.TreeCacheCreateResponse, + err error) { + + treeUnallocatedConsumerIDs = make(map[string][]string) + treeResponse = make(map[string]*core.TreeCacheCreateResponse) + forestUnallocatedConsumerIDs = make(map[string][]string) + forestResponse = make(map[string]map[string]*core.TreeCacheCreateResponse) + var erStr []string + + // update all single trees + for treeName := range m.agents { + if _, exists := m.treeToForest[treeName]; !exists { + ids, resp, er := m.UpdateTree(treeName) + treeUnallocatedConsumerIDs[treeName] = ids + treeResponse[treeName] = resp + if er != nil { + erStr = append(erStr, er.Error()) + } + } + } + // update all forests + for forestName := range m.forests { + ids, resp, er := m.UpdateForest(forestName) + forestUnallocatedConsumerIDs[forestName] = ids + forestResponse[forestName] = resp + if er != nil { + erStr = append(erStr, er.Error()) + } + } + + if len(erStr) > 0 { + err = fmt.Errorf(strings.Join(erStr, "\n")) + } + return treeUnallocatedConsumerIDs, treeResponse, forestUnallocatedConsumerIDs, forestResponse, err +} + +// String : printout +func (m *Manager) String() string { + var b bytes.Buffer + b.WriteString("QuotaManger: \n") + b.WriteString("Mode: " + m.GetModeString() + "\n") + b.WriteString("\n") + + b.WriteString("TreeControllers: \n") + for _, agent := range m.agents { + b.WriteString(agent.controller.String()) + } + b.WriteString("\n") + + b.WriteString("ForestControllers: \n") + for _, controller := range m.forests { + b.WriteString(controller.String()) + } + + b.WriteString("Consumers: \n") + b.WriteString("[ ") + for _, consumerInfo := range m.consumerInfos { + b.WriteString(consumerInfo.GetID() + " ") + } + b.WriteString("]") + + return b.String() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/utils/defaults.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/utils/defaults.go new file mode 100644 index 000000000..97bf0b8b7 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/utils/defaults.go @@ -0,0 +1,31 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +var ( + // DefaultTreeName : the default name of the tree (if unspecified) + DefaultTreeName string = "default" + + // DefaultResourceNames : the default resource names + DefaultResourceNames []string = []string{"cpu", "memory"} + + // DefaultTreeKind : the default kind attribute of the tree + DefaultTreeKind string = "QuotaTree" + + // DefaultConsumerKind : the kind attribute for a consumer + DefaultConsumerKind string = "Consumer" +) diff --git a/pkg/quotaplugins/quota-forest/quota-manager/quota/utils/types.go b/pkg/quotaplugins/quota-forest/quota-manager/quota/utils/types.go new file mode 100644 index 000000000..ba72f2ba7 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/quota/utils/types.go @@ -0,0 +1,72 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +// JQuotaTree : JSON quota tree +type JQuotaTree struct { + Kind string `json:"kind"` + MetaData JMetaData `json:"metadata"` + Spec JTreeSpec `json:"spec"` +} + +// JMetaData : common metada +type JMetaData struct { + Name string `json:"name"` +} + +// JTreeSpec : spec of quota tree +type JTreeSpec struct { + ResourceNames []string `json:"resourceNames"` + Nodes map[string]JNodeSpec `json:"nodes"` +} + +// JNodeSpec : spec for a node in the quota tree +type JNodeSpec struct { + Parent string `json:"parent"` + Quota map[string]string `json:"quota"` + Hard string `json:"hard"` +} + +// JTreeInfo : data about tree name and resource names +type JTreeInfo struct { + Name string `json:"name"` + ResourceNames []string `json:"resourceNames"` +} + +// JConsumer : JSON consumer +type JConsumer struct { + Kind string `json:"kind"` + MetaData JMetaData `json:"metadata"` + Spec JConsumerSpec `json:"spec"` +} + +// JConsumerSpec : spec of consumer of multiple trees +type JConsumerSpec struct { + ID string `json:"id"` + Trees []JConsumerTreeSpec `json:"trees"` +} + +// JConsumerTreeSpec : consumer spec for a tree +type JConsumerTreeSpec struct { + ID string `json:"id"` + TreeName string `json:"treeName"` + GroupID string `json:"groupID"` + Request map[string]int `json:"request"` + Priority int `json:"priority"` + CType int `json:"type"` + UnPreemptable bool `json:"unPreemptable"` +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/ExampleConsumer.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/ExampleConsumer.json new file mode 100644 index 000000000..391f42c07 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/ExampleConsumer.json @@ -0,0 +1,21 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "C-1", + "trees": [ + { + "treeName": "ExampleTree", + "groupID": "K", + "request": { + "cpu": 4 + }, + "priority": 0, + "type": 0, + "unPreemptable": true + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/ExampleTree.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/ExampleTree.json new file mode 100644 index 000000000..a1dc47e3d --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/ExampleTree.json @@ -0,0 +1,111 @@ +{ + "kind": "QuotaTree", + "metadata": { + "name": "ExampleTree" + }, + "spec": { + "resourceNames": [ + "cpu" + ], + "nodes": { + "A": { + "parent": "nil", + "hard": "false", + "quota": { + "cpu": "10" + } + }, + "B": { + "parent": "A", + "hard": "false", + "quota": { + "cpu": "2" + } + }, + "C": { + "parent": "A", + "hard": "false", + "quota": { + "cpu": "6" + } + }, + "D": { + "parent": "A", + "hard": "false", + "quota": { + "cpu": "2" + } + }, + "E": { + "parent": "B", + "hard": "false", + "quota": { + "cpu": "1" + } + }, + "F": { + "parent": "B", + "hard": "false", + "quota": { + "cpu": "1" + } + }, + "G": { + "parent": "C", + "hard": "false", + "quota": { + "cpu": "3" + } + }, + "H": { + "parent": "C", + "hard": "false", + "quota": { + "cpu": "3" + } + }, + "K": { + "parent": "G", + "hard": "false", + "quota": { + "cpu": "1" + } + }, + "L": { + "parent": "G", + "hard": "false", + "quota": { + "cpu": "2" + } + }, + "M": { + "parent": "H", + "hard": "false", + "quota": { + "cpu": "1" + } + }, + "N": { + "parent": "H", + "hard": "false", + "quota": { + "cpu": "2" + } + }, + "I": { + "parent": "D", + "hard": "false", + "quota": { + "cpu": "1" + } + }, + "J": { + "parent": "D", + "hard": "false", + "quota": { + "cpu": "1" + } + } + } + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/TestConsumer.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/TestConsumer.json new file mode 100644 index 000000000..7f42dc78b --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/TestConsumer.json @@ -0,0 +1,22 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "C-1", + "trees": [ + { + "treeName": "TestTree", + "groupID": "D", + "request": { + "cpu": 4, + "memory": 16 + }, + "priority": 0, + "type": 0, + "unPreemptable": true + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/TestForestConsumer.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/TestForestConsumer.json new file mode 100644 index 000000000..4be8374f2 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/TestForestConsumer.json @@ -0,0 +1,32 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "C-1", + "trees": [ + { + "treeName": "ContextTree", + "groupID": "Context-1", + "request": { + "cpu": 2 + }, + "priority": 0, + "type": 0, + "unPreemptable": false + }, + { + "treeName": "ServiceTree", + "groupID": "Srvc-X", + "request": { + "cpu": 8, + "count": 1 + }, + "priority": 0, + "type": 0, + "unPreemptable": false + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/TestTree.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/TestTree.json new file mode 100644 index 000000000..c2c927e28 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/TestTree.json @@ -0,0 +1,50 @@ +{ + "kind": "QuotaTree", + "metadata": { + "name": "TestTree" + }, + "spec": { + "resourceNames": [ + "cpu", + "memory" + ], + "nodes": { + + "A": { + "parent": "nil", + "hard": "true", + "quota": { + "cpu": "10", + "memory": "256" + } + }, + + "B": { + "parent": "A", + "hard": "true", + "quota": { + "cpu": "2", + "memory": "64" + } + }, + + "C": { + "parent": "A", + "quota": { + "cpu": "6", + "memory": "64" + } + }, + + "D": { + "parent": "A", + "hard": "false", + "quota": { + "cpu": "2", + "memory": "128" + } + } + + } + } +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/ContextTree.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/ContextTree.json new file mode 100644 index 000000000..ad4b57f70 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/ContextTree.json @@ -0,0 +1,69 @@ +{ + "kind": "QuotaTree", + "metadata": { + "name": "ContextTree" + }, + "spec": { + "resourceNames": [ + "cpu" + ], + "nodes": { + "Root": { + "parent": "nil", + "quota": { + "cpu": "10" + } + }, + "Org-A": { + "parent": "Root", + "quota": { + "cpu": "4" + } + }, + "Org-B": { + "parent": "Root", + "hard": "true", + "quota": { + "cpu": "6" + } + }, + "Org-C": { + "parent": "Root", + "quota": { + "cpu": "4" + } + }, + "Context-1": { + "parent": "Org-A", + "quota": { + "cpu": "1" + } + }, + "Context-2": { + "parent": "Org-A", + "quota": { + "cpu": "1" + } + }, + "Context-3": { + "parent": "Org-B", + "quota": { + "cpu": "2" + } + }, + "Context-4": { + "parent": "Org-B", + "hard": "true", + "quota": { + "cpu": "2" + } + }, + "Context-5": { + "parent": "Org-C", + "quota": { + "cpu": "4" + } + } + } + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/ServiceTree.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/ServiceTree.json new file mode 100644 index 000000000..835a243a5 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/ServiceTree.json @@ -0,0 +1,43 @@ +{ + "kind": "QuotaTree", + "metadata": { + "name": "ServiceTree" + }, + "spec": { + "resourceNames": [ + "cpu", + "disk" + ], + "nodes": { + "Root": { + "parent": "nil", + "quota": { + "cpu": "16", + "disk": "12" + } + }, + "Srvc-X": { + "parent": "Root", + "hard": "true", + "quota": { + "cpu": "3", + "disk": "4" + } + }, + "Srvc-Y": { + "parent": "Root", + "quota": { + "cpu": "4", + "disk": "4" + } + }, + "Srvc-Z": { + "parent": "Root", + "quota": { + "cpu": "5", + "disk": "4" + } + } + } + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job1.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job1.json new file mode 100644 index 000000000..075acd5ba --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job1.json @@ -0,0 +1,28 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "job-1", + "trees": [ + { + "treeName": "ContextTree", + "groupID": "Context-4", + "request": { + "cpu": 2 + }, + "priority": 0 + }, + { + "treeName": "ServiceTree", + "groupID": "Srvc-X", + "request": { + "cpu": 2, + "disk": 1 + }, + "priority": 0 + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job2.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job2.json new file mode 100644 index 000000000..895bf9142 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job2.json @@ -0,0 +1,28 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "job-2", + "trees": [ + { + "treeName": "ContextTree", + "groupID": "Context-2", + "request": { + "cpu": 3 + }, + "priority": 0 + }, + { + "treeName": "ServiceTree", + "groupID": "Srvc-Y", + "request": { + "cpu": 1, + "disk": 1 + }, + "priority": 0 + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job3.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job3.json new file mode 100644 index 000000000..3088bec5e --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job3.json @@ -0,0 +1,28 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "job-3", + "trees": [ + { + "treeName": "ContextTree", + "groupID": "Context-3", + "request": { + "cpu": 4 + }, + "priority": 0 + }, + { + "treeName": "ServiceTree", + "groupID": "Srvc-Z", + "request": { + "cpu": 4, + "disk": 2 + }, + "priority": 0 + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job4.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job4.json new file mode 100644 index 000000000..fb84b939c --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job4.json @@ -0,0 +1,28 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "job-4", + "trees": [ + { + "treeName": "ContextTree", + "groupID": "Context-2", + "request": { + "cpu": 4 + }, + "priority": 1 + }, + { + "treeName": "ServiceTree", + "groupID": "Srvc-X", + "request": { + "cpu": 3, + "disk": 4 + }, + "priority": 1 + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job5.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job5.json new file mode 100644 index 000000000..8bcba2509 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/forest/job5.json @@ -0,0 +1,28 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "job-5", + "trees": [ + { + "treeName": "ContextTree", + "groupID": "Context-4", + "request": { + "cpu": 4 + }, + "priority": 1 + }, + { + "treeName": "ServiceTree", + "groupID": "Srvc-Z", + "request": { + "cpu": 2, + "disk": 8 + }, + "priority": 1 + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ca.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ca.json new file mode 100644 index 000000000..616547768 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ca.json @@ -0,0 +1,20 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "a", + "trees": [ + { + "treeName": "ExampleTree", + "groupID": "N", + "request": { + "cpu": 1 + }, + "priority": 0, + "unPreemptable": false + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cb.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cb.json new file mode 100644 index 000000000..80baa3368 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cb.json @@ -0,0 +1,20 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "b", + "trees": [ + { + "treeName": "ExampleTree", + "groupID": "N", + "request": { + "cpu": 1 + }, + "priority": 0, + "unPreemptable": false + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cc.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cc.json new file mode 100644 index 000000000..00ef74b5f --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cc.json @@ -0,0 +1,20 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "c", + "trees": [ + { + "treeName": "ExampleTree", + "groupID": "N", + "request": { + "cpu": 1 + }, + "priority": 0, + "unPreemptable": false + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cd.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cd.json new file mode 100644 index 000000000..d2b233c9b --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cd.json @@ -0,0 +1,20 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "d", + "trees": [ + { + "treeName": "ExampleTree", + "groupID": "N", + "request": { + "cpu": 2 + }, + "priority": 1, + "unPreemptable": false + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ce.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ce.json new file mode 100644 index 000000000..947f3f73f --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ce.json @@ -0,0 +1,20 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "e", + "trees": [ + { + "treeName": "ExampleTree", + "groupID": "L", + "request": { + "cpu": 3 + }, + "priority": 0, + "unPreemptable": false + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cf.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cf.json new file mode 100644 index 000000000..20ac5527e --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cf.json @@ -0,0 +1,20 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "f", + "trees": [ + { + "treeName": "ExampleTree", + "groupID": "E", + "request": { + "cpu": 3 + }, + "priority": 0, + "unPreemptable": false + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cg.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cg.json new file mode 100644 index 000000000..acec9cac3 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cg.json @@ -0,0 +1,20 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "g", + "trees": [ + { + "treeName": "ExampleTree", + "groupID": "J", + "request": { + "cpu": 1 + }, + "priority": 0, + "unPreemptable": false + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ch.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ch.json new file mode 100644 index 000000000..3a025571f --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ch.json @@ -0,0 +1,20 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "h", + "trees": [ + { + "treeName": "ExampleTree", + "groupID": "K", + "request": { + "cpu": 1 + }, + "priority": 0, + "unPreemptable": false + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ci.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ci.json new file mode 100644 index 000000000..d7b457374 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/ci.json @@ -0,0 +1,20 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "i", + "trees": [ + { + "treeName": "ExampleTree", + "groupID": "I", + "request": { + "cpu": 3 + }, + "priority": 1, + "unPreemptable": false + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cj.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cj.json new file mode 100644 index 000000000..69ebb0875 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/cj.json @@ -0,0 +1,20 @@ +{ + "kind": "Consumer", + "metadata": { + "name": "consumer-data" + }, + "spec": { + "id": "j", + "trees": [ + { + "treeName": "ExampleTree", + "groupID": "F", + "request": { + "cpu": 2 + }, + "priority": 0, + "unPreemptable": false + } + ] + } +} \ No newline at end of file diff --git a/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/tree.json b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/tree.json new file mode 100644 index 000000000..faf6b73d1 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/samples/tree/tree.json @@ -0,0 +1,125 @@ +{ + "kind": "QuotaTree", + "metadata": { + "name": "ExampleTree" + }, + "spec": { + "resourceNames": [ + "cpu" + ], + "nodes": { + + "A": { + "parent": "nil", + "hard": "false", + "quota": { + "cpu": "10" + } + }, + + "B": { + "parent": "A", + "hard": "false", + "quota": { + "cpu": "2" + } + }, + + "C": { + "parent": "A", + "hard": "false", + "quota": { + "cpu": "6" + } + }, + + "D": { + "parent": "A", + "hard": "false", + "quota": { + "cpu": "2" + } + }, + + "E": { + "parent": "B", + "hard": "false", + "quota": { + "cpu": "1" + } + }, + + "F": { + "parent": "B", + "hard": "false", + "quota": { + "cpu": "1" + } + }, + + "G": { + "parent": "C", + "hard": "false", + "quota": { + "cpu": "3" + } + }, + + "H": { + "parent": "C", + "hard": "false", + "quota": { + "cpu": "3" + } + }, + + "K": { + "parent": "G", + "hard": "false", + "quota": { + "cpu": "1" + } + }, + + "L": { + "parent": "G", + "hard": "false", + "quota": { + "cpu": "2" + } + }, + + "M": { + "parent": "H", + "hard": "false", + "quota": { + "cpu": "1" + } + }, + + "N": { + "parent": "H", + "hard": "false", + "quota": { + "cpu": "2" + } + }, + + "I": { + "parent": "D", + "hard": "false", + "quota": { + "cpu": "1" + } + }, + + "J": { + "parent": "D", + "hard": "false", + "quota": { + "cpu": "1" + } + } + } + } +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/tree/node.go b/pkg/quotaplugins/quota-forest/quota-manager/tree/node.go new file mode 100644 index 000000000..1bf5734af --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/tree/node.go @@ -0,0 +1,190 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tree + +import ( + "bytes" + "fmt" + "sort" +) + +// Node : a basic node in a tree +type Node struct { + // unique id for the node + ID string + // value associated with the node + value int + // the parent of this node in the tree + parent *Node + // set of children of this node in the tree (node ID -> node) + children map[string]*Node +} + +// NewNode : create a node +func NewNode(id string) *Node { + return &Node{ + ID: id, + value: 0, + parent: nil, + children: make(map[string]*Node), + } +} + +// GetID : the unique ID of this node +func (n *Node) GetID() string { + return n.ID +} + +// GetValue : the value of this node +func (n *Node) GetValue() int { + return n.value +} + +// SetValue : set the value of this node +func (n *Node) SetValue(value int) { + n.value = value +} + +// GetParent : the parent of this node +func (n *Node) GetParent() *Node { + return n.parent +} + +// setParent : set the parent of this node +func (n *Node) setParent(parent *Node) { + n.parent = parent +} + +// GetChildren : the children of this node +func (n *Node) GetChildren() []*Node { + children := make([]*Node, 0, len(n.children)) + for _, c := range n.children { + children = append(children, c) + } + return children +} + +// IsRoot : is this node the root of the tree +func (n *Node) IsRoot() bool { + return n.parent == nil +} + +// IsLeaf : is this node a leaf in the tree +func (n *Node) IsLeaf() bool { + return len(n.children) == 0 +} + +// HasLeaf : does the subtree from this node has a given node as a leaf +func (n *Node) HasLeaf(leafID string) bool { + for _, leaf := range n.GetLeaves() { + if leaf.GetID() == leafID { + return true + } + } + return false +} + +// AddChild : add a child to this node; +// return false if child already exists +func (n *Node) AddChild(child *Node) bool { + if child != nil { + cid := child.GetID() + if _, exists := n.children[cid]; !exists { + n.children[cid] = child + child.setParent(n) + return true + } + } + return false +} + +// RemoveChild : remove a child from this node +func (n *Node) RemoveChild(child *Node) bool { + if child != nil { + cid := child.GetID() + if _, exists := n.children[cid]; exists { + delete(n.children, cid) + child.setParent(nil) + return true + } + } + return false +} + +// GetNumChildren : the number of children of this node +func (n *Node) GetNumChildren() int { + return len(n.children) +} + +// GetHeight : the height of this node in the tree +func (n *Node) GetHeight() int { + h := 0 + for _, c := range n.children { + ch := c.GetHeight() + 1 + if ch > h { + h = ch + } + } + return h +} + +// GetLeaves : the leaf nodes in the subtree consisting of this node as a root +func (n *Node) GetLeaves() []*Node { + list := make([]*Node, 0) + if n.IsLeaf() { + list = append(list, n) + } else { + for _, c := range n.children { + list = append(list, c.GetLeaves()...) + } + } + return list +} + +// GetPathToRoot : the path from this node to the root of the tree; +// node (root) is first (last) in list +func (n *Node) GetPathToRoot() []*Node { + path := make([]*Node, 0) + node := n + for !node.IsRoot() { + path = append(path, node) + node = node.GetParent() + } + path = append(path, node) + return path +} + +// String : a print out of the node; +// e.g. A -> ( C -> ( D ) B ) +func (n *Node) String() string { + var b bytes.Buffer + b.WriteString(n.ID) + if !n.IsLeaf() { + b.WriteString(" -> ( ") + // order children by name + ids := make([]string, 0, len(n.children)) + for id := range n.children { + ids = append(ids, id) + } + sort.Strings(ids) + for _, id := range ids { + fmt.Fprintf(&b, "%s ", n.children[id]) + } + b.WriteString(")") + } + return b.String() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/tree/node_test.go b/pkg/quotaplugins/quota-forest/quota-manager/tree/node_test.go new file mode 100644 index 000000000..06c54e3a3 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/tree/node_test.go @@ -0,0 +1,546 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tree + +import ( + "reflect" + "testing" +) + +var ( + nodeA *Node = &Node{ + ID: "A", + value: 0, + parent: nil, + children: make(map[string]*Node), + } + + nodeB *Node = &Node{ + ID: "B", + value: 0, + parent: nil, + children: make(map[string]*Node), + } + + nodeC *Node = &Node{ + ID: "C", + value: 0, + parent: nil, + children: make(map[string]*Node), + } + + nodeD *Node = &Node{ + ID: "D", + value: 0, + parent: nil, + children: make(map[string]*Node), + } +) + +func resetNodes() { + nodes := []*Node{nodeA, nodeB, nodeC, nodeD} + for _, n := range nodes { + n.parent = nil + n.children = make(map[string]*Node) + } +} + +func connectNodes() { + //A -> ( B C -> ( D ) ) + nodeD.parent = nodeC + nodeC.children["D"] = nodeD + nodeC.parent = nodeA + nodeA.children["C"] = nodeC + nodeB.parent = nodeA + nodeA.children["B"] = nodeB +} + +func TestNewNode(t *testing.T) { + type args struct { + id string + } + tests := []struct { + name string + args args + want *Node + }{ + { + name: "node-A", + args: args{ + id: "A", + }, + want: nodeA, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewNode(tt.args.id); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewNode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNode_SetValue(t *testing.T) { + type fields struct { + ID string + value int + } + type args struct { + value int + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "value-X", + fields: fields{ + ID: "X", + value: 5, + }, + args: args{ + value: 10, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NewNode(tt.fields.ID) + n.SetValue(tt.fields.value) + n.SetValue(tt.args.value) + if got := n.GetValue(); got != tt.args.value { + t.Errorf("SetValue() = %v, want %v", got, tt.args.value) + } + }) + } +} + +func TestNode_GetChildren(t *testing.T) { + type fields struct { + node *Node + } + tests := []struct { + name string + fields fields + want [][]*Node + }{ + { + name: "children-A", + fields: fields{ + node: nodeA, + }, + want: [][]*Node{{nodeC, nodeB}, {nodeB, nodeC}}, + }, + { + name: "children-B", + fields: fields{ + node: nodeB, + }, + want: [][]*Node{{}}, + }, + { + name: "children-C", + fields: fields{ + node: nodeC, + }, + want: [][]*Node{{nodeD}}, + }, + { + name: "children-D", + fields: fields{ + node: nodeD, + }, + want: [][]*Node{{}}, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := tt.fields.node + got := n.GetChildren() + ok := false + for _, w := range tt.want { + if reflect.DeepEqual(got, w) { + ok = true + break + } + } + if !ok { + t.Errorf("Node.GetChildren() = %v, want %v", got, tt.want) + } + }) + } +} +func TestNode_AddChild(t *testing.T) { + type fields struct { + node *Node + } + type args struct { + child *Node + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "add first", + fields: fields{ + node: nodeA, + }, + args: args{ + child: nodeB, + }, + want: true, + }, + { + name: "add again", + fields: fields{ + node: nodeA, + }, + args: args{ + child: nodeB, + }, + want: false, + }, + } + resetNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := tt.fields.node + if got := n.AddChild(tt.args.child); got != tt.want { + t.Errorf("Node.AddChild() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNode_RemoveChild(t *testing.T) { + type fields struct { + node *Node + } + type args struct { + child *Node + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "remove first", + fields: fields{ + node: nodeA, + }, + args: args{ + child: nodeB, + }, + want: true, + }, + { + name: "remove again", + fields: fields{ + node: nodeA, + }, + args: args{ + child: nodeB, + }, + want: false, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := tt.fields.node + if got := n.RemoveChild(tt.args.child); got != tt.want { + t.Errorf("Node.RemoveChild() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNode_GetNumChildren(t *testing.T) { + type fields struct { + node *Node + } + tests := []struct { + name string + fields fields + want int + }{ + { + name: "children-A", + fields: fields{ + node: nodeA, + }, + want: 2, + }, + { + name: "children-B", + fields: fields{ + node: nodeB, + }, + want: 0, + }, + { + name: "children-C", + fields: fields{ + node: nodeC, + }, + want: 1, + }, + { + name: "children-D", + fields: fields{ + node: nodeD, + }, + want: 0, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := tt.fields.node + if got := n.GetNumChildren(); got != tt.want { + t.Errorf("Node.GetNumChildren() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNode_GetHeight(t *testing.T) { + type fields struct { + node *Node + } + tests := []struct { + name string + fields fields + want int + }{ + { + name: "height-A", + fields: fields{ + node: nodeA, + }, + want: 2, + }, + { + name: "height-B", + fields: fields{ + node: nodeB, + }, + want: 0, + }, + { + name: "height-C", + fields: fields{ + node: nodeC, + }, + want: 1, + }, + { + name: "height-D", + fields: fields{ + node: nodeD, + }, + want: 0, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := tt.fields.node + if got := n.GetHeight(); got != tt.want { + t.Errorf("Node.GetHeight() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNode_GetLeaves(t *testing.T) { + type fields struct { + node *Node + } + tests := []struct { + name string + fields fields + want [][]*Node + }{ + { + name: "leaves-A", + fields: fields{ + node: nodeA, + }, + want: [][]*Node{{nodeD, nodeB}, {nodeB, nodeD}}, + }, + { + name: "leaves-B", + fields: fields{ + node: nodeB, + }, + want: [][]*Node{{nodeB}}, + }, + { + name: "leaves-C", + fields: fields{ + node: nodeC, + }, + want: [][]*Node{{nodeD}}, + }, + { + name: "leaves-D", + fields: fields{ + node: nodeD, + }, + want: [][]*Node{{nodeD}}, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := tt.fields.node + got := n.GetLeaves() + ok := false + for _, w := range tt.want { + if reflect.DeepEqual(got, w) { + ok = true + break + } + } + if !ok { + t.Errorf("Node.GetLeaves() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNode_GetPathToRoot(t *testing.T) { + type fields struct { + node *Node + } + tests := []struct { + name string + fields fields + want []*Node + }{ + { + name: "path-A", + fields: fields{ + node: nodeA, + }, + want: []*Node{nodeA}, + }, + { + name: "path-B", + fields: fields{ + node: nodeB, + }, + want: []*Node{nodeB, nodeA}, + }, + { + name: "path-C", + fields: fields{ + node: nodeC, + }, + want: []*Node{nodeC, nodeA}, + }, + { + name: "path-D", + fields: fields{ + node: nodeD, + }, + want: []*Node{nodeD, nodeC, nodeA}, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := tt.fields.node + if got := n.GetPathToRoot(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Node.GetPathToRoot() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNode_String(t *testing.T) { + type fields struct { + node *Node + } + tests := []struct { + name string + fields fields + want []string + }{ + { + name: "print-A", + fields: fields{ + node: nodeA, + }, + want: []string{"A -> ( C -> ( D ) B )", "A -> ( B C -> ( D ) )"}, + }, + { + name: "print-B", + fields: fields{ + node: nodeB, + }, + want: []string{"B"}, + }, + { + name: "print-C", + fields: fields{ + node: nodeC, + }, + want: []string{"C -> ( D )"}, + }, + { + name: "print-D", + fields: fields{ + node: nodeD, + }, + want: []string{"D"}, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := tt.fields.node + got := n.String() + ok := false + for _, w := range tt.want { + if reflect.DeepEqual(got, w) { + ok = true + break + } + } + if !ok { + t.Errorf("Node.ToString() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/tree/tree.go b/pkg/quotaplugins/quota-forest/quota-manager/tree/tree.go new file mode 100644 index 000000000..d78bc1a40 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/tree/tree.go @@ -0,0 +1,131 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tree + +import ( + "bytes" + "fmt" + "sort" +) + +// Tree : a basic tree +type Tree struct { + // the root of the tree (could be null) + root *Node + // a map to help find leaf nodes (leaf ID -> leaf node) + leafNodeMap map[string]*Node +} + +// NewTree : create a tree +func NewTree(root *Node) *Tree { + t := &Tree{ + root: root, + leafNodeMap: make(map[string]*Node), + } + for _, n := range t.GetLeaves() { + t.leafNodeMap[n.GetID()] = n + } + return t +} + +// GetHeight : the height of the tree +func (t *Tree) GetHeight() int { + if t.root == nil { + return 0 + } + return t.root.GetHeight() +} + +// GetRoot : the root of the tree +func (t *Tree) GetRoot() *Node { + return t.root +} + +// GetLeaves : the leaves of the tree +func (t *Tree) GetLeaves() []*Node { + if t.root == nil { + return make([]*Node, 0) + } + return t.root.GetLeaves() +} + +// GetLeafIDs : the IDs of the leaves of the tree +func (t *Tree) GetLeafIDs() []string { + leafIDs := make([]string, 0) + if t.root != nil { + for _, n := range t.root.GetLeaves() { + leafIDs = append(leafIDs, n.GetID()) + } + } + return leafIDs +} + +// GetLeafNode : get a leaf node by ID; null if not found +func (t *Tree) GetLeafNode(nodeID string) *Node { + return t.leafNodeMap[nodeID] +} + +// GetNode : find node in tree with a given ID; null if not found +func (t *Tree) GetNode(nodeID string) *Node { + allNodes := t.GetNodeListBFS() + for _, n := range allNodes { + if n.GetID() == nodeID { + return n + } + } + return nil +} + +// GetNodeListBFS : list of nodes in BFS order +func (t *Tree) GetNodeListBFS() []*Node { + allNodes := make([]*Node, 0) + + nodeList := make([]*Node, 0) + if t.root != nil { + nodeList = append(nodeList, t.root) + } + + for len(nodeList) > 0 { + n := nodeList[0] + nodeList = nodeList[1:] + allNodes = append(allNodes, n) + nodeList = append(nodeList, n.GetChildren()...) + } + return allNodes +} + +// GetNodeIDs : get (sorted) IDs of all nodes in the tree +func (t *Tree) GetNodeIDs() []string { + nodes := t.GetNodeListBFS() + nodeIDs := make([]string, len(nodes)) + for i, node := range nodes { + nodeIDs[i] = node.GetID() + } + sort.Strings(nodeIDs) + return nodeIDs +} + +// String : a print out of the tree +func (t *Tree) String() string { + var b bytes.Buffer + if t.root != nil { + fmt.Fprintf(&b, "%s", t.root) + } else { + b.WriteString("null") + } + return b.String() +} diff --git a/pkg/quotaplugins/quota-forest/quota-manager/tree/tree_test.go b/pkg/quotaplugins/quota-forest/quota-manager/tree/tree_test.go new file mode 100644 index 000000000..e333b4fe8 --- /dev/null +++ b/pkg/quotaplugins/quota-forest/quota-manager/tree/tree_test.go @@ -0,0 +1,320 @@ +/* +Copyright 2022 The Multi-Cluster App Dispatcher Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tree + +import ( + "reflect" + "testing" +) + +var ( + treeA *Tree = &Tree{ + root: nodeA, + leafNodeMap: map[string]*Node{ + "B": nodeB, + "D": nodeD, + }, + } + treeNil *Tree = &Tree{ + root: nil, + leafNodeMap: make(map[string]*Node), + } +) + +func TestNewTree(t *testing.T) { + type args struct { + root *Node + } + tests := []struct { + name string + args args + want *Tree + }{ + { + name: "good tree", + args: args{ + root: nodeA, + }, + want: treeA, + }, + { + name: "empty tree", + args: args{ + root: nil, + }, + want: treeNil, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewTree(tt.args.root); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewTree() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTree_GetLeafIDs(t *testing.T) { + type fields struct { + tree *Tree + } + tests := []struct { + name string + fields fields + want [][]string + }{ + { + name: "good tree", + fields: fields{ + tree: treeA, + }, + want: [][]string{{"B", "D"}, {"D", "B"}}, + }, + { + name: "empty tree", + fields: fields{ + tree: treeNil, + }, + want: [][]string{{}}, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := tt.fields.tree + got := tr.GetLeafIDs() + ok := false + for _, w := range tt.want { + if reflect.DeepEqual(got, w) { + ok = true + break + } + } + if !ok { + t.Errorf("Tree.GetLeafIDs() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTree_GetLeafNode(t *testing.T) { + type fields struct { + tree *Tree + } + type args struct { + nodeID string + } + tests := []struct { + name string + fields fields + args args + want *Node + }{ + { + name: "node exists", + fields: fields{ + tree: treeA, + }, + args: args{ + nodeID: "B", + }, + want: nodeB, + }, + { + name: "node missing", + fields: fields{ + tree: treeA, + }, + args: args{ + nodeID: "C", + }, + want: nil, + }, + { + name: "empty tree", + fields: fields{ + tree: treeNil, + }, + args: args{ + nodeID: "X", + }, + want: nil, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := tt.fields.tree + if got := tr.GetLeafNode(tt.args.nodeID); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Tree.GetLeafNode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTree_GetNode(t *testing.T) { + type fields struct { + tree *Tree + } + type args struct { + nodeID string + } + tests := []struct { + name string + fields fields + args args + want *Node + }{ + { + name: "leaf node", + fields: fields{ + tree: treeA, + }, + args: args{ + nodeID: "B", + }, + want: nodeB, + }, + { + name: "internal node", + fields: fields{ + tree: treeA, + }, + args: args{ + nodeID: "C", + }, + want: nodeC, + }, + { + name: "root node", + fields: fields{ + tree: treeA, + }, + args: args{ + nodeID: "A", + }, + want: nodeA, + }, + { + name: "missing node", + fields: fields{ + tree: treeA, + }, + args: args{ + nodeID: "X", + }, + want: nil, + }, + { + name: "empty tree", + fields: fields{ + tree: treeNil, + }, + args: args{ + nodeID: "X", + }, + want: nil, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := tt.fields.tree + if got := tr.GetNode(tt.args.nodeID); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Tree.GetNode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTree_GetHeight(t *testing.T) { + type fields struct { + tree *Tree + } + tests := []struct { + name string + fields fields + want int + }{ + { + name: "good tree", + fields: fields{ + tree: treeA, + }, + want: 2, + }, + { + name: "empty tree", + fields: fields{ + tree: treeNil, + }, + want: 0, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := tt.fields.tree + if got := tr.GetHeight(); got != tt.want { + t.Errorf("Tree.GetHeight() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTree_GetNodeIDs(t *testing.T) { + type fields struct { + tree *Tree + } + tests := []struct { + name string + fields fields + want []string + }{ + { + name: "good tree", + fields: fields{ + tree: treeA, + }, + want: []string{"A", "B", "C", "D"}, + }, + { + name: "empty tree", + fields: fields{ + tree: treeNil, + }, + want: []string{}, + }, + } + resetNodes() + connectNodes() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := tt.fields.tree + got := tr.GetNodeIDs() + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Tree.GetNodeIDs() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/quotaplugins/quota-simple-rest/quota_rest_manager.go b/pkg/quotaplugins/quota-simple-rest/quota_rest_manager.go index d34fb3687..488730362 100644 --- a/pkg/quotaplugins/quota-simple-rest/quota_rest_manager.go +++ b/pkg/quotaplugins/quota-simple-rest/quota_rest_manager.go @@ -1,6 +1,6 @@ // +build !private // ------------------------------------------------------ {COPYRIGHT-TOP} --- -// Copyright 2022 The Multi-Cluster App Dispatcher Authors. +// Copyright 2022, 2023 The Multi-Cluster App Dispatcher Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,7 +26,8 @@ import ( listersv1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/listers/controller/v1" clusterstateapi "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/clusterstate/api" "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota" - "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/controller/quota/quotamanager/util" + "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/util" + "io/ioutil" "k8s.io/client-go/rest" "k8s.io/klog/v2" @@ -306,7 +307,7 @@ func (qm *QuotaManager) Fits(aw *arbv1.AppWrapper, awResDemands *clusterstateapi klog.V(10).Infof("[getQuotaTreeIDs] POST Response dump: %q", dump) if err != nil { - klog.Errorf("[Fits] Fail to add access quotamanager: %s, err=%#v.", uri, err) + klog.Errorf("[Fits] Fail to add access quotaforestmanager: %s, err=%#v.", uri, err) preemptIds = nil } else { body, err := ioutil.ReadAll(response.Body) diff --git a/pkg/quotaplugins/util/utils.go b/pkg/quotaplugins/util/utils.go new file mode 100644 index 000000000..beb0cf80d --- /dev/null +++ b/pkg/quotaplugins/util/utils.go @@ -0,0 +1,62 @@ +// ------------------------------------------------------ {COPYRIGHT-TOP} --- +// Copyright 2022, 2023 The Multi-Cluster App Dispatcher Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------ {COPYRIGHT-END} --- + +package util + +import ( + "fmt" + "strings" +) + +const ( + // AW Namespace used for building unique name for AW job + NamespacePrefix string = "NAMESPACE_" + + // AW Name used for building unique name for AW job + AppWrapperNamePrefix string = "_AWNAME_" +) + +func ParseId(id string) (string, string) { + ns := "" + n := "" + + // Extract the namespace seperator + nspSplit := strings.Split(id, NamespacePrefix) + if len(nspSplit) == 2 { + // Extract the appwrapper seperator + awnpSplit := strings.Split(nspSplit[1], AppWrapperNamePrefix) + if len(awnpSplit) == 2 { + // What is left if the namespace value in the first slice + if len(awnpSplit[0]) > 0 { + ns = awnpSplit[0] + } + // And the names value in the second slice + if len(awnpSplit[1]) > 0 { + n = awnpSplit[1] + } + } + } + return ns, n +} + +func CreateId(ns string, n string) string { + id := "" + if len(ns) > 0 && len(n) > 0 { + id = fmt.Sprintf("%s%s%s%s", NamespacePrefix, ns, AppWrapperNamePrefix, n) + } + return id +} + diff --git a/test/e2e-kuttl/quota-forest/00-assert.yaml b/test/e2e-kuttl/quota-forest/00-assert.yaml new file mode 100644 index 000000000..c03368ecc --- /dev/null +++ b/test/e2e-kuttl/quota-forest/00-assert.yaml @@ -0,0 +1,24 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: appwrappers.mcad.ibm.com +status: + acceptedNames: + kind: AppWrapper + listKind: AppWrapperList + plural: appwrappers + singular: appwrapper + storedVersions: + - v1beta1 +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: quotasubtrees.ibm.com +status: + acceptedNames: + kind: QuotaSubtree + singular: quotasubtree + plural: quotasubtrees + storedVersions: + - v1 diff --git a/test/e2e-kuttl/quota-forest/01-assert.yaml b/test/e2e-kuttl/quota-forest/01-assert.yaml new file mode 100644 index 000000000..b10473519 --- /dev/null +++ b/test/e2e-kuttl/quota-forest/01-assert.yaml @@ -0,0 +1,15 @@ +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: context-root + namespace: kube-system + labels: + tree: quota_context +--- +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: service-root + namespace: kube-system + labels: + tree: quota_service diff --git a/test/e2e-kuttl/quota-forest/01-install.yaml b/test/e2e-kuttl/quota-forest/01-install.yaml new file mode 100644 index 000000000..0366f92ea --- /dev/null +++ b/test/e2e-kuttl/quota-forest/01-install.yaml @@ -0,0 +1,29 @@ +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: context-root + namespace: kube-system + labels: + tree: quota_context +spec: + children: + - name: context-root + quotas: + requests: + cpu: 1075 + memory: 1045 +--- +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: service-root + namespace: kube-system + labels: + tree: quota_service +spec: + children: + - name: service-root + quotas: + requests: + cpu: 1075 + memory: 1045 diff --git a/test/e2e-kuttl/quota-forest/02-assert.yaml b/test/e2e-kuttl/quota-forest/02-assert.yaml new file mode 100644 index 000000000..f771f890a --- /dev/null +++ b/test/e2e-kuttl/quota-forest/02-assert.yaml @@ -0,0 +1,12 @@ +# Verify that quota management is enabled by checking the queuing is happening +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job2-1replica + namespace: test +--- +apiVersion: v1 +kind: Pod +metadata: + name: job2-1replica-0 + namespace: test \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/02-install.yaml b/test/e2e-kuttl/quota-forest/02-install.yaml new file mode 100644 index 000000000..4ad556b7e --- /dev/null +++ b/test/e2e-kuttl/quota-forest/02-install.yaml @@ -0,0 +1,50 @@ +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job2-1replica + namespace: test + labels: + quota_context: "context-root" + quota_service: "service-root" +spec: + service: + spec: {} + resources: + metadata: {} + Items: [] + GenericItems: + - metadata: {} + replicas: 1 + generictemplate: + apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 + kind: StatefulSet + metadata: + name: job2-1replica + namespace: test + labels: + app: job2-1replica + spec: + selector: + matchLabels: + app: job2-1replica + replicas: 1 + template: + metadata: + labels: + app: job2-1replica + size: "1" + spec: + containers: + - name: job2-1replica + image: registry.access.redhat.com/ubi8/ubi:latest + command: + - /bin/sh + - -c + - while true; do sleep 10; done + resources: + requests: + cpu: "0.6" + memory: "95Mi" + limits: + cpu: "0.6" + memory: "95Mi" diff --git a/test/e2e-kuttl/quota-forest/03-assert.yaml b/test/e2e-kuttl/quota-forest/03-assert.yaml new file mode 100644 index 000000000..f5d0487c8 --- /dev/null +++ b/test/e2e-kuttl/quota-forest/03-assert.yaml @@ -0,0 +1,11 @@ +# Verify that quota management is enabled by checking the queuing is happening +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job3-10replica + namespace: test + labels: + quota_context: "context-root2" + quota_service: "service-root2" +status: + state: Pending diff --git a/test/e2e-kuttl/quota-forest/03-install.yaml b/test/e2e-kuttl/quota-forest/03-install.yaml new file mode 100644 index 000000000..a4ad655c9 --- /dev/null +++ b/test/e2e-kuttl/quota-forest/03-install.yaml @@ -0,0 +1,49 @@ +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job3-10replica + namespace: test + labels: + quota_context: "context-root2" + quota_service: "service-root2" +spec: + service: + spec: {} + resources: + metadata: {} + GenericItems: + - metadata: {} + replicas: 1 + generictemplate: + apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 + kind: StatefulSet + metadata: + name: job3-10replica + namespace: test + labels: + app: job3-10replica + spec: + selector: + matchLabels: + app: job3-10replica + replicas: 10 + template: + metadata: + labels: + app: job3-10replica + size: "1" + spec: + containers: + - name: job3-10replica + image: registry.access.redhat.com/ubi8/ubi:latest + command: + - /bin/sh + - -c + - while true; do sleep 10; done + resources: + requests: + cpu: "0.1" + memory: "95Mi" + limits: + cpu: "0.1" + memory: "95Mi" diff --git a/test/e2e-kuttl/quota-forest/04-assert.yaml b/test/e2e-kuttl/quota-forest/04-assert.yaml new file mode 100644 index 000000000..7c30be2c0 --- /dev/null +++ b/test/e2e-kuttl/quota-forest/04-assert.yaml @@ -0,0 +1,22 @@ +# Verify that quota management is enabled by checking the queuing is happening +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job4-1replica + namespace: test +status: + state: Running +--- +apiVersion: v1 +kind: Pod +metadata: + name: job4-1replica-0 + namespace: test +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job2-1replica + namespace: test +status: + state: Pending \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/04-install.yaml b/test/e2e-kuttl/quota-forest/04-install.yaml new file mode 100644 index 000000000..8669f3705 --- /dev/null +++ b/test/e2e-kuttl/quota-forest/04-install.yaml @@ -0,0 +1,50 @@ +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job4-1replica + namespace: test + labels: + quota_context: "context-root" + quota_service: "service-root" +spec: + service: + spec: {} + priority: 1000 + resources: + metadata: {} + GenericItems: + - metadata: {} + replicas: 1 + generictemplate: + apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 + kind: StatefulSet + metadata: + name: job4-1replica + namespace: test + labels: + app: job4-1replica + spec: + selector: + matchLabels: + app: job4-1replica + replicas: 1 + template: + metadata: + labels: + app: job4-1replica + size: "1" + spec: + containers: + - name: job4-1replica + image: registry.access.redhat.com/ubi8/ubi:latest + command: + - /bin/sh + - -c + - while true; do sleep 10; done + resources: + requests: + cpu: "0.6" + memory: "95Mi" + limits: + cpu: "0.6" + memory: "95Mi" From 037885514553d00199f4a6d16178d5334cb0fa87 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 8 Feb 2023 13:09:11 -0500 Subject: [PATCH 03/33] Partial update to e2e script to enable instatiate 2 differently configured mcad controllers. --- hack/run-e2e-kind.sh | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index 93ff4eb7c..ceb34bc73 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -301,8 +301,14 @@ function kube-test-env-up { kubectl describe pod ${tiller_pod} -n kube-system - helm version + helm version + # Turn off master taints + kubectl taint nodes --all node-role.kubernetes.io/master- + +} + +function mcad-up { cd deployment # start mcad controller @@ -311,6 +317,9 @@ function kube-test-env-up { helm install mcad-controller --namespace kube-system --wait --set loglevel=2 --set resources.requests.cpu=1000m --set resources.requests.memory=1024Mi --set resources.limits.cpu=4000m --set resources.limits.memory=4096Mi --set image.repository=$IMAGE_REPOSITORY_MCAD --set image.tag=$IMAGE_TAG_MCAD --set image.pullPolicy=$MCAD_IMAGE_PULL_POLICY sleep 10 +} + +function mcad-env-status { echo "Listing MCAD Controller Helm Chart and Pod YAML..." helm list mcad_pod=$(kubectl get pods -n kube-system | grep mcad-controller | awk '{print $1}') @@ -319,7 +328,6 @@ function kube-test-env-up { kubectl get pod ${mcad_pod} -n kube-system -o yaml fi - sleep 10 echo "Listing MCAD Controller Helm Chart and Pod YAML..." helm list @@ -329,10 +337,6 @@ function kube-test-env-up { kubectl get pod ${mcad_pod} -n kube-system -o yaml fi - # Turn off master taints - kubectl taint nodes --all node-role.kubernetes.io/master- - - # This is meant to orchestrate initial cluster configuration such that accounting tests can be consistent echo "---" echo "Orchestrate cluster..." @@ -360,6 +364,9 @@ kind-up-cluster kube-test-env-up +mcad-up + +mcad-env-status cd ${ROOT_DIR} echo "==========================>>>>> Running E2E tests... <<<<<==========================" From 5bc0778bda07b4871b8ccf07c15d03a926bbd6b5 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Mon, 27 Feb 2023 10:33:08 -0800 Subject: [PATCH 04/33] Added kuttl installation to e2e test. --- hack/run-e2e-kind.sh | 16 +++++++++++- test/e2e-kuttl/quota-forest/01-install.yaml | 28 +++++++++++++++++++++ test/e2e-kuttl/quota-forest/02-install.yaml | 3 ++- test/e2e-kuttl/quota-forest/04-install.yaml | 3 ++- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index ceb34bc73..4d9be50ce 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -41,6 +41,7 @@ export IMAGE_REPOSITORY_MCAD="${1}" export IMAGE_TAG_MCAD="${2}" export MCAD_IMAGE_PULL_POLICY="${3-Always}" export IMAGE_MCAD="${IMAGE_REPOSITORY_MCAD}:${IMAGE_TAG_MCAD}" +export KUTTL_VERSION=0.15.0 sudo apt-get update && sudo apt-get install -y apt-transport-https curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - @@ -53,6 +54,9 @@ sudo apt-get install -y kubectl=1.17.0-00 sudo curl -o /usr/local/bin/kind -L https://github.com/kubernetes-sigs/kind/releases/download/v0.11.0/kind-linux-amd64 sudo chmod +x /usr/local/bin/kind +# Download kuttl plugin +sudo curl -sSLf --output /tmp/kubectl-kuttl https://github.com/kudobuilder/kuttl/releases/download/v${KUTTL_VERSION}/kubectl-kuttl_${KUTTL_VERSION}_linux_x86_64 && mv /tmp/kubectl-kuttl /usr/local/bin && chmod a+x /usr/local/bin/kubectl-kuttl + # check if kind installed function check-prerequisites { echo "checking prerequisites" @@ -71,7 +75,15 @@ function check-prerequisites { else echo -n "found kubectl, " && kubectl version --short --client fi - + + kubectl kuttl version >/dev/null 2>&1 + if [[ $? -ne 0 ]]; then + echo "kuttl plugin for kubectl not installed, exiting." + exit 1 + else + echo -n "found kuttl plugin for kubectl, " && kubectl kuttl version + fi + if [[ $IMAGE_REPOSITORY_MCAD == "" ]] then echo "No MCAD image was provided." @@ -367,6 +379,8 @@ kube-test-env-up mcad-up mcad-env-status + + cd ${ROOT_DIR} echo "==========================>>>>> Running E2E tests... <<<<<==========================" diff --git a/test/e2e-kuttl/quota-forest/01-install.yaml b/test/e2e-kuttl/quota-forest/01-install.yaml index 0366f92ea..415d44626 100644 --- a/test/e2e-kuttl/quota-forest/01-install.yaml +++ b/test/e2e-kuttl/quota-forest/01-install.yaml @@ -27,3 +27,31 @@ spec: requests: cpu: 1075 memory: 1045 +--- +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: context-root-children + namespace: kube-system + labels: + tree: quota_context +spec: + parent: context-root + children: + - name: gold + quotas: + requests: + cpu: 1075 + memory: 450 + - name: silver + quotas: + hardLimit: false + requests: + cpu: 1075 + memory: 400 + - name: bronze + quotas: + hardLimit: true + requests: + cpu: 900 + memory: 300 \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/02-install.yaml b/test/e2e-kuttl/quota-forest/02-install.yaml index 4ad556b7e..a4b2d74d8 100644 --- a/test/e2e-kuttl/quota-forest/02-install.yaml +++ b/test/e2e-kuttl/quota-forest/02-install.yaml @@ -4,7 +4,8 @@ metadata: name: job2-1replica namespace: test labels: - quota_context: "context-root" + #quota_context: "context-root" + quota_context: "gold" quota_service: "service-root" spec: service: diff --git a/test/e2e-kuttl/quota-forest/04-install.yaml b/test/e2e-kuttl/quota-forest/04-install.yaml index 8669f3705..f474578ba 100644 --- a/test/e2e-kuttl/quota-forest/04-install.yaml +++ b/test/e2e-kuttl/quota-forest/04-install.yaml @@ -4,7 +4,8 @@ metadata: name: job4-1replica namespace: test labels: - quota_context: "context-root" + #quota_context: "context-root" + quota_context: "gold" quota_service: "service-root" spec: service: From cbd3d75cd35b65ad5814375069a8e94166abe524 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Mon, 27 Feb 2023 10:39:08 -0800 Subject: [PATCH 05/33] Synced version file. --- CONTROLLER_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTROLLER_VERSION b/CONTROLLER_VERSION index 971333c39..5d9b3ce52 100644 --- a/CONTROLLER_VERSION +++ b/CONTROLLER_VERSION @@ -1 +1 @@ -1.29.51 +1.29.52 From b13e19c6dbbb98fa5db9199e53347508b5a62359 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Mon, 27 Feb 2023 10:47:30 -0800 Subject: [PATCH 06/33] Modified kuttl installation to include sudo for commands. Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index 4d9be50ce..4d5274f8a 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -55,7 +55,7 @@ sudo curl -o /usr/local/bin/kind -L https://github.com/kubernetes-sigs/kind/rele sudo chmod +x /usr/local/bin/kind # Download kuttl plugin -sudo curl -sSLf --output /tmp/kubectl-kuttl https://github.com/kudobuilder/kuttl/releases/download/v${KUTTL_VERSION}/kubectl-kuttl_${KUTTL_VERSION}_linux_x86_64 && mv /tmp/kubectl-kuttl /usr/local/bin && chmod a+x /usr/local/bin/kubectl-kuttl +sudo curl -sSLf --output /tmp/kubectl-kuttl https://github.com/kudobuilder/kuttl/releases/download/v${KUTTL_VERSION}/kubectl-kuttl_${KUTTL_VERSION}_linux_x86_64 && sudo mv /tmp/kubectl-kuttl /usr/local/bin && sudo chmod a+x /usr/local/bin/kubectl-kuttl # check if kind installed function check-prerequisites { From bf63654f64852474da2dcde6421ed0dea65cbe70 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Mon, 27 Feb 2023 11:08:15 -0800 Subject: [PATCH 07/33] Added startup of MCAD with Quota Management enabled Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index 4d5274f8a..6fdb1eeae 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -320,8 +320,30 @@ function kube-test-env-up { } +function mcad-quota-management-up { + cd $ROOT_DIR/deployment + + # start mcad controller + echo "Starting MCAD Controller for Quota Management Testing..." + echo "helm install mcad-controller namespace kube-system wait set loglevel=2 set resources.requests.cpu=1000m set resources.requests.memory=1024Mi set resources.limits.cpu=4000m set resources.limits.memory=4096Mi set image.repository=$IMAGE_REPOSITORY_MCAD set image.tag=$IMAGE_TAG_MCAD set image.pullPolicy=$MCAD_IMAGE_PULL_POLICY set configMap.quotaEnabled='true' set quotaManagement.rbac.apiGroup=ibm.com set quotaManagement.rbac.resource=quotasubtrees set configMap.name=mcad-controller-configmap set configMap.preemptionEnabled='true'" + helm install mcad-controller --namespace kube-system --wait --set loglevel=10 --set resources.requests.cpu=1000m --set resources.requests.memory=1024Mi --set resources.limits.cpu=4000m --set resources.limits.memory=4096Mi --set image.repository=$IMAGE_REPOSITORY_MCAD --set image.tag=$IMAGE_TAG_MCAD --set image.pullPolicy=$MCAD_IMAGE_PULL_POLICY --set configMap.quotaEnabled='"true"' --set quotaManagement.rbac.apiGroup=ibm.com --set quotaManagement.rbac.resource=quotasubtrees --set configMap.name=mcad-controller-configmap --set configMap.preemptionEnabled='"true"' + sleep 10 +} + +function mcad-quota-management-down { + + # Helm chart install name + helm_chart_name=$(helm list --short) + + # start mcad controller + echo "Stopping MCAD Controller for Quota Management Testing..." + echo "helm delete ${helm_chart_name}" + helm delete ${helm_chart_name} + sleep 20 +} + function mcad-up { - cd deployment + cd $ROOT_DIR/deployment # start mcad controller echo "Starting MCAD Controller..." @@ -376,6 +398,14 @@ kind-up-cluster kube-test-env-up +# Quota management testing +mcad-quota-management-up + +mcad-env-status + +mcad-quota-management-down + +# Non-quota mangement testing mcad-up mcad-env-status From 1c78b033be5409275dc42e515905250c93988800 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Mon, 27 Feb 2023 11:23:36 -0800 Subject: [PATCH 08/33] Added kuttl test command to e2e testing. Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index 6fdb1eeae..bade867a9 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -403,9 +403,12 @@ mcad-quota-management-up mcad-env-status +echo "==========================>>>>> Running Quota Management Kuttl E2E tests... <<<<<==========================" +kubectl kuttl test + mcad-quota-management-down -# Non-quota mangement testing +# Non-quota management testing mcad-up mcad-env-status @@ -414,4 +417,4 @@ mcad-env-status cd ${ROOT_DIR} echo "==========================>>>>> Running E2E tests... <<<<<==========================" -go test ./test/e2e -v -timeout 55m +#go test ./test/e2e -v -timeout 55m From ac23d9b309558f3e8e88c61cba7962d974aceaeb Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Mon, 27 Feb 2023 12:13:06 -0800 Subject: [PATCH 09/33] Provided an alternative to the travis_wait command. Signed-off-by: dmatch01 --- .travis.yml | 7 ++- test/e2e-kuttl/quota-forest/05-assert.yaml | 22 +++++++++ test/e2e-kuttl/quota-forest/05-install.yaml | 50 +++++++++++++++++++++ test/e2e-kuttl/quota-forest/06-assert.yaml | 16 +++++++ test/e2e-kuttl/quota-forest/06-install.yaml | 50 +++++++++++++++++++++ 5 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 test/e2e-kuttl/quota-forest/05-assert.yaml create mode 100644 test/e2e-kuttl/quota-forest/05-install.yaml create mode 100644 test/e2e-kuttl/quota-forest/06-assert.yaml create mode 100644 test/e2e-kuttl/quota-forest/06-install.yaml diff --git a/.travis.yml b/.travis.yml index 1c56b48b6..0fb15552e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,4 +29,9 @@ script: - 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then unset quay_repository && make images; fi' # Process 'make push-images' when NOT a pull request - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then make push-images; fi' - - travis_wait 60 make run-e2e + # Run the e2e and handle Travis condition where Travis fails on no output for 10 minutes. No output for 10 minutes + # is fine for our test cases that waits for 10 minutes before requeuing a job. The other workaround was to + # use 'travis_wait n' which unfortunately does not stream output until the very end so monitoring the Travis log + # during runtime is not possible. + - make run-e2e & + - while [ -e /proc/$! ]; do echo -n "." && sleep 120; done diff --git a/test/e2e-kuttl/quota-forest/05-assert.yaml b/test/e2e-kuttl/quota-forest/05-assert.yaml new file mode 100644 index 000000000..80b64ebd5 --- /dev/null +++ b/test/e2e-kuttl/quota-forest/05-assert.yaml @@ -0,0 +1,22 @@ +# Verify that quota management is enabled by checking the queuing is happening +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job5-1replica + namespace: test +status: + state: Running +--- +apiVersion: v1 +kind: Pod +metadata: + name: job5-1replica-0 + namespace: test +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job2-1replica + namespace: test +status: + state: Pending \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/05-install.yaml b/test/e2e-kuttl/quota-forest/05-install.yaml new file mode 100644 index 000000000..4261559f8 --- /dev/null +++ b/test/e2e-kuttl/quota-forest/05-install.yaml @@ -0,0 +1,50 @@ +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job5-1replica + namespace: test + labels: + quota_context: "bronze" + quota_service: "service-root" +spec: + service: + spec: {} + priority: 1000 + resources: + metadata: {} + GenericItems: + - metadata: {} + replicas: 1 + generictemplate: + apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 + kind: StatefulSet + metadata: + name: job5-1replica + namespace: test + labels: + app: job5-1replica + spec: + selector: + matchLabels: + app: job5-1replica + replicas: 1 + template: + metadata: + labels: + app: job5-1replica + size: "1" + spec: + containers: + - name: job5-1replica + image: registry.access.redhat.com/ubi8/ubi:latest + command: + - /bin/sh + - -c + - while true; do sleep 10; done + resources: + requests: + cpu: "0.4" + memory: "95Mi" + limits: + cpu: "0.4" + memory: "95Mi" diff --git a/test/e2e-kuttl/quota-forest/06-assert.yaml b/test/e2e-kuttl/quota-forest/06-assert.yaml new file mode 100644 index 000000000..4513dc88f --- /dev/null +++ b/test/e2e-kuttl/quota-forest/06-assert.yaml @@ -0,0 +1,16 @@ +# Verify that quota management is enabled by checking the queuing is happening +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job6-1replica + namespace: test +status: + state: Pending +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job2-1replica + namespace: test +status: + state: Pending \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/06-install.yaml b/test/e2e-kuttl/quota-forest/06-install.yaml new file mode 100644 index 000000000..14c3adb84 --- /dev/null +++ b/test/e2e-kuttl/quota-forest/06-install.yaml @@ -0,0 +1,50 @@ +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job6-1replica + namespace: test + labels: + quota_context: "bronze" + quota_service: "service-root" +spec: + service: + spec: {} + priority: 1000 + resources: + metadata: {} + GenericItems: + - metadata: {} + replicas: 1 + generictemplate: + apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 + kind: StatefulSet + metadata: + name: job6-1replica + namespace: test + labels: + app: job6-1replica + spec: + selector: + matchLabels: + app: job6-1replica + replicas: 1 + template: + metadata: + labels: + app: job6-1replica + size: "1" + spec: + containers: + - name: job6-1replica + image: registry.access.redhat.com/ubi8/ubi:latest + command: + - /bin/sh + - -c + - while true; do sleep 10; done + resources: + requests: + cpu: "0.4" + memory: "95Mi" + limits: + cpu: "0.4" + memory: "95Mi" From 2d41a5dccffc357b9dc8bec0cb8ae7ddfdf606c9 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Mon, 27 Feb 2023 12:22:47 -0800 Subject: [PATCH 10/33] Added full e2e testing back. Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index bade867a9..2088e98e9 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -417,4 +417,4 @@ mcad-env-status cd ${ROOT_DIR} echo "==========================>>>>> Running E2E tests... <<<<<==========================" -#go test ./test/e2e -v -timeout 55m +go test ./test/e2e -v -timeout 55m From 2c69afaaae368d1742de1a98bff5401fcd1c8747 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Mon, 27 Feb 2023 13:20:36 -0800 Subject: [PATCH 11/33] Added explicit kuttl config file. Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index 2088e98e9..bd0dfcfaf 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -42,6 +42,7 @@ export IMAGE_TAG_MCAD="${2}" export MCAD_IMAGE_PULL_POLICY="${3-Always}" export IMAGE_MCAD="${IMAGE_REPOSITORY_MCAD}:${IMAGE_TAG_MCAD}" export KUTTL_VERSION=0.15.0 +export KUTTL_TEST_OPT="--config ${ROOT_DIR}/kuttl-test.yaml" sudo apt-get update && sudo apt-get install -y apt-transport-https curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - @@ -399,14 +400,18 @@ kind-up-cluster kube-test-env-up # Quota management testing +kubectl create namespace test mcad-quota-management-up mcad-env-status -echo "==========================>>>>> Running Quota Management Kuttl E2E tests... <<<<<==========================" -kubectl kuttl test +cd ${ROOT_DIR} +echo "==============>>>>> Running Quota Management Kuttl E2E tests... <<<<<==============" +echo "kubectl kuttl test ${KUTTL_TEST_OPT}" +kubectl kuttl test ${KUTTL_TEST_OPT} mcad-quota-management-down +kubectl delete namespace test # Non-quota management testing mcad-up From e92994a81f5bc426c5b6235f006f349dc342ff66 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Tue, 28 Feb 2023 14:35:42 -0800 Subject: [PATCH 12/33] Set e2e test to above if kuttl test fails. Signed-off-by: dmatch01 --- config/crd/bases/ibm.com_quotasubtree-v1.yaml | 2 -- .../mcad-controller/templates/deployment.yaml | 2 -- hack/run-e2e-kind.sh | 29 +++++++++++-------- .../quotaplugins/quotasubtree/v1/types.go | 1 - 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/config/crd/bases/ibm.com_quotasubtree-v1.yaml b/config/crd/bases/ibm.com_quotasubtree-v1.yaml index d9ba3e6d1..2fa6d6808 100644 --- a/config/crd/bases/ibm.com_quotasubtree-v1.yaml +++ b/config/crd/bases/ibm.com_quotasubtree-v1.yaml @@ -41,8 +41,6 @@ spec: type: string namespace: type: string - share: - type: integer quotas: type: object properties: diff --git a/deployment/mcad-controller/templates/deployment.yaml b/deployment/mcad-controller/templates/deployment.yaml index f1b3def49..b5904f387 100644 --- a/deployment/mcad-controller/templates/deployment.yaml +++ b/deployment/mcad-controller/templates/deployment.yaml @@ -143,8 +143,6 @@ spec: type: string namespace: type: string - share: - type: integer quotas: type: object properties: diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index bd0dfcfaf..354dac7eb 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -392,6 +392,20 @@ function mcad-env-status { kubectl describe nodes } +function kuttl-tests { + kubectl create namespace test + mcad-quota-management-up + mcad-env-status + cd ${ROOT_DIR} + echo "==============>>>>> Running Quota Management Kuttl E2E tests... <<<<<==============" + echo "kubectl kuttl test ${KUTTL_TEST_OPT}" + kubectl kuttl test ${KUTTL_TEST_OPT} + if [[ $? -ne 0 ]]; then + echo "quota management kuttl e2e tests failure, exiting." + exit 1 + mcad-quota-management-down + kubectl delete namespace test +} trap cleanup EXIT @@ -400,18 +414,7 @@ kind-up-cluster kube-test-env-up # Quota management testing -kubectl create namespace test -mcad-quota-management-up - -mcad-env-status - -cd ${ROOT_DIR} -echo "==============>>>>> Running Quota Management Kuttl E2E tests... <<<<<==============" -echo "kubectl kuttl test ${KUTTL_TEST_OPT}" -kubectl kuttl test ${KUTTL_TEST_OPT} - -mcad-quota-management-down -kubectl delete namespace test +kuttl-tests # Non-quota management testing mcad-up @@ -423,3 +426,5 @@ cd ${ROOT_DIR} echo "==========================>>>>> Running E2E tests... <<<<<==========================" go test ./test/e2e -v -timeout 55m + +} \ No newline at end of file diff --git a/pkg/apis/quotaplugins/quotasubtree/v1/types.go b/pkg/apis/quotaplugins/quotasubtree/v1/types.go index 56d725741..3b0df3846 100755 --- a/pkg/apis/quotaplugins/quotasubtree/v1/types.go +++ b/pkg/apis/quotaplugins/quotasubtree/v1/types.go @@ -56,7 +56,6 @@ type QuotaSubtreeSpec struct { type Child struct { Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` Namespace string `json:"namespace,omitempty" protobuf:"bytes,2,opt,name=namespace"` - Share int32 `json:"share,omitempty" protobuf:"bytes,3,opt,name=share"` Quotas Quota `json:"quotas,omitempty" protobuf:"bytes,4,opt,name=quotas"` Path string `json:"path,omitempty" protobuf:"bytes,5,opt,name=path"` } From d6a6bbd2c5f04715954253f10b02f5a730084c9f Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Tue, 28 Feb 2023 14:44:38 -0800 Subject: [PATCH 13/33] Uptick of release number. Signed-off-by: dmatch01 --- CONTROLLER_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTROLLER_VERSION b/CONTROLLER_VERSION index 5d9b3ce52..e33c41baa 100644 --- a/CONTROLLER_VERSION +++ b/CONTROLLER_VERSION @@ -1 +1 @@ -1.29.52 +1.29.53 From e7d6013bdaee56d9148b890cbd1976efbc76ed00 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Tue, 28 Feb 2023 23:43:24 -0800 Subject: [PATCH 14/33] Fix syntax error. Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index 354dac7eb..42ac6bfc2 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -403,6 +403,7 @@ function kuttl-tests { if [[ $? -ne 0 ]]; then echo "quota management kuttl e2e tests failure, exiting." exit 1 + fi mcad-quota-management-down kubectl delete namespace test } From 8357cd56dcf55cf5def39ef867bb0a9876b5d1f9 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 00:45:20 -0800 Subject: [PATCH 15/33] Added creation of test namespace to kuttl testing steps. Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 31 +++--- test/e2e-kuttl/quota-forest/01-assert.yaml | 18 +--- test/e2e-kuttl/quota-forest/01-install.yaml | 59 +---------- test/e2e-kuttl/quota-forest/02-assert.yaml | 21 ++-- test/e2e-kuttl/quota-forest/02-install.yaml | 102 +++++++++++--------- test/e2e-kuttl/quota-forest/03-assert.yaml | 13 +-- test/e2e-kuttl/quota-forest/03-install.yaml | 24 ++--- test/e2e-kuttl/quota-forest/04-assert.yaml | 21 +--- test/e2e-kuttl/quota-forest/04-install.yaml | 24 +++-- test/e2e-kuttl/quota-forest/05-assert.yaml | 4 +- test/e2e-kuttl/quota-forest/05-install.yaml | 19 ++-- test/e2e-kuttl/quota-forest/06-assert.yaml | 10 +- test/e2e-kuttl/quota-forest/06-install.yaml | 12 +-- test/e2e-kuttl/quota-forest/07-assert.yaml | 16 +++ test/e2e-kuttl/quota-forest/07-install.yaml | 50 ++++++++++ 15 files changed, 218 insertions(+), 206 deletions(-) create mode 100644 test/e2e-kuttl/quota-forest/07-assert.yaml create mode 100644 test/e2e-kuttl/quota-forest/07-install.yaml diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index ef8a9f9a0..a64eaef05 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -393,19 +393,9 @@ function mcad-env-status { } function kuttl-tests { - kubectl create namespace test mcad-quota-management-up mcad-env-status cd ${ROOT_DIR} - echo "==============>>>>> Running Quota Management Kuttl E2E tests... <<<<<==============" - echo "kubectl kuttl test ${KUTTL_TEST_OPT}" - kubectl kuttl test ${KUTTL_TEST_OPT} - if [[ $? -ne 0 ]]; then - echo "quota management kuttl e2e tests failure, exiting." - exit 1 - fi - mcad-quota-management-down - kubectl delete namespace test } trap cleanup EXIT @@ -414,18 +404,31 @@ kind-up-cluster kube-test-env-up +### # Quota management testing +### kuttl-tests - +echo "==============>>>>> Running Quota Management Kuttl E2E tests... <<<<<==============" +echo "kubectl kuttl test ${KUTTL_TEST_OPT}" +kubectl kuttl test ${KUTTL_TEST_OPT} +if [[ $? -ne 0 ]]; then + echo "quota management kuttl e2e tests failure, exiting." + exit 1 +else + # Takes about 50 seconds for namespace created in kuttl testing to completely delete. + sleep 50 +fi +mcad-quota-management-down + + +### # Non-quota management testing +### mcad-up mcad-env-status - cd ${ROOT_DIR} echo "==========================>>>>> Running E2E tests... <<<<<==========================" go test ./test/e2e -v -timeout 55m - -} \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/01-assert.yaml b/test/e2e-kuttl/quota-forest/01-assert.yaml index b10473519..c67a17241 100644 --- a/test/e2e-kuttl/quota-forest/01-assert.yaml +++ b/test/e2e-kuttl/quota-forest/01-assert.yaml @@ -1,15 +1,5 @@ -apiVersion: ibm.com/v1 -kind: QuotaSubtree +# Verify test namespace existance +apiVersion: v1 +kind: Namespace metadata: - name: context-root - namespace: kube-system - labels: - tree: quota_context ---- -apiVersion: ibm.com/v1 -kind: QuotaSubtree -metadata: - name: service-root - namespace: kube-system - labels: - tree: quota_service + name: test \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/01-install.yaml b/test/e2e-kuttl/quota-forest/01-install.yaml index 415d44626..5e0105d10 100644 --- a/test/e2e-kuttl/quota-forest/01-install.yaml +++ b/test/e2e-kuttl/quota-forest/01-install.yaml @@ -1,57 +1,4 @@ -apiVersion: ibm.com/v1 -kind: QuotaSubtree +apiVersion: v1 +kind: Namespace metadata: - name: context-root - namespace: kube-system - labels: - tree: quota_context -spec: - children: - - name: context-root - quotas: - requests: - cpu: 1075 - memory: 1045 ---- -apiVersion: ibm.com/v1 -kind: QuotaSubtree -metadata: - name: service-root - namespace: kube-system - labels: - tree: quota_service -spec: - children: - - name: service-root - quotas: - requests: - cpu: 1075 - memory: 1045 ---- -apiVersion: ibm.com/v1 -kind: QuotaSubtree -metadata: - name: context-root-children - namespace: kube-system - labels: - tree: quota_context -spec: - parent: context-root - children: - - name: gold - quotas: - requests: - cpu: 1075 - memory: 450 - - name: silver - quotas: - hardLimit: false - requests: - cpu: 1075 - memory: 400 - - name: bronze - quotas: - hardLimit: true - requests: - cpu: 900 - memory: 300 \ No newline at end of file + name: test \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/02-assert.yaml b/test/e2e-kuttl/quota-forest/02-assert.yaml index f771f890a..b10473519 100644 --- a/test/e2e-kuttl/quota-forest/02-assert.yaml +++ b/test/e2e-kuttl/quota-forest/02-assert.yaml @@ -1,12 +1,15 @@ -# Verify that quota management is enabled by checking the queuing is happening -apiVersion: mcad.ibm.com/v1beta1 -kind: AppWrapper +apiVersion: ibm.com/v1 +kind: QuotaSubtree metadata: - name: job2-1replica - namespace: test + name: context-root + namespace: kube-system + labels: + tree: quota_context --- -apiVersion: v1 -kind: Pod +apiVersion: ibm.com/v1 +kind: QuotaSubtree metadata: - name: job2-1replica-0 - namespace: test \ No newline at end of file + name: service-root + namespace: kube-system + labels: + tree: quota_service diff --git a/test/e2e-kuttl/quota-forest/02-install.yaml b/test/e2e-kuttl/quota-forest/02-install.yaml index a4b2d74d8..415d44626 100644 --- a/test/e2e-kuttl/quota-forest/02-install.yaml +++ b/test/e2e-kuttl/quota-forest/02-install.yaml @@ -1,51 +1,57 @@ -apiVersion: mcad.ibm.com/v1beta1 -kind: AppWrapper +apiVersion: ibm.com/v1 +kind: QuotaSubtree metadata: - name: job2-1replica - namespace: test + name: context-root + namespace: kube-system labels: - #quota_context: "context-root" - quota_context: "gold" - quota_service: "service-root" + tree: quota_context spec: - service: - spec: {} - resources: - metadata: {} - Items: [] - GenericItems: - - metadata: {} - replicas: 1 - generictemplate: - apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 - kind: StatefulSet - metadata: - name: job2-1replica - namespace: test - labels: - app: job2-1replica - spec: - selector: - matchLabels: - app: job2-1replica - replicas: 1 - template: - metadata: - labels: - app: job2-1replica - size: "1" - spec: - containers: - - name: job2-1replica - image: registry.access.redhat.com/ubi8/ubi:latest - command: - - /bin/sh - - -c - - while true; do sleep 10; done - resources: - requests: - cpu: "0.6" - memory: "95Mi" - limits: - cpu: "0.6" - memory: "95Mi" + children: + - name: context-root + quotas: + requests: + cpu: 1075 + memory: 1045 +--- +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: service-root + namespace: kube-system + labels: + tree: quota_service +spec: + children: + - name: service-root + quotas: + requests: + cpu: 1075 + memory: 1045 +--- +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: context-root-children + namespace: kube-system + labels: + tree: quota_context +spec: + parent: context-root + children: + - name: gold + quotas: + requests: + cpu: 1075 + memory: 450 + - name: silver + quotas: + hardLimit: false + requests: + cpu: 1075 + memory: 400 + - name: bronze + quotas: + hardLimit: true + requests: + cpu: 900 + memory: 300 \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/03-assert.yaml b/test/e2e-kuttl/quota-forest/03-assert.yaml index f5d0487c8..f771f890a 100644 --- a/test/e2e-kuttl/quota-forest/03-assert.yaml +++ b/test/e2e-kuttl/quota-forest/03-assert.yaml @@ -2,10 +2,11 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job3-10replica + name: job2-1replica namespace: test - labels: - quota_context: "context-root2" - quota_service: "service-root2" -status: - state: Pending +--- +apiVersion: v1 +kind: Pod +metadata: + name: job2-1replica-0 + namespace: test \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/03-install.yaml b/test/e2e-kuttl/quota-forest/03-install.yaml index a4ad655c9..a4b2d74d8 100644 --- a/test/e2e-kuttl/quota-forest/03-install.yaml +++ b/test/e2e-kuttl/quota-forest/03-install.yaml @@ -1,16 +1,18 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job3-10replica + name: job2-1replica namespace: test labels: - quota_context: "context-root2" - quota_service: "service-root2" + #quota_context: "context-root" + quota_context: "gold" + quota_service: "service-root" spec: service: spec: {} resources: metadata: {} + Items: [] GenericItems: - metadata: {} replicas: 1 @@ -18,23 +20,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job3-10replica + name: job2-1replica namespace: test labels: - app: job3-10replica + app: job2-1replica spec: selector: matchLabels: - app: job3-10replica - replicas: 10 + app: job2-1replica + replicas: 1 template: metadata: labels: - app: job3-10replica + app: job2-1replica size: "1" spec: containers: - - name: job3-10replica + - name: job2-1replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh @@ -42,8 +44,8 @@ spec: - while true; do sleep 10; done resources: requests: - cpu: "0.1" + cpu: "0.6" memory: "95Mi" limits: - cpu: "0.1" + cpu: "0.6" memory: "95Mi" diff --git a/test/e2e-kuttl/quota-forest/04-assert.yaml b/test/e2e-kuttl/quota-forest/04-assert.yaml index 7c30be2c0..f5d0487c8 100644 --- a/test/e2e-kuttl/quota-forest/04-assert.yaml +++ b/test/e2e-kuttl/quota-forest/04-assert.yaml @@ -2,21 +2,10 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job4-1replica + name: job3-10replica namespace: test + labels: + quota_context: "context-root2" + quota_service: "service-root2" status: - state: Running ---- -apiVersion: v1 -kind: Pod -metadata: - name: job4-1replica-0 - namespace: test ---- -apiVersion: mcad.ibm.com/v1beta1 -kind: AppWrapper -metadata: - name: job2-1replica - namespace: test -status: - state: Pending \ No newline at end of file + state: Pending diff --git a/test/e2e-kuttl/quota-forest/04-install.yaml b/test/e2e-kuttl/quota-forest/04-install.yaml index f474578ba..a4ad655c9 100644 --- a/test/e2e-kuttl/quota-forest/04-install.yaml +++ b/test/e2e-kuttl/quota-forest/04-install.yaml @@ -1,16 +1,14 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job4-1replica + name: job3-10replica namespace: test labels: - #quota_context: "context-root" - quota_context: "gold" - quota_service: "service-root" + quota_context: "context-root2" + quota_service: "service-root2" spec: service: spec: {} - priority: 1000 resources: metadata: {} GenericItems: @@ -20,23 +18,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job4-1replica + name: job3-10replica namespace: test labels: - app: job4-1replica + app: job3-10replica spec: selector: matchLabels: - app: job4-1replica - replicas: 1 + app: job3-10replica + replicas: 10 template: metadata: labels: - app: job4-1replica + app: job3-10replica size: "1" spec: containers: - - name: job4-1replica + - name: job3-10replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh @@ -44,8 +42,8 @@ spec: - while true; do sleep 10; done resources: requests: - cpu: "0.6" + cpu: "0.1" memory: "95Mi" limits: - cpu: "0.6" + cpu: "0.1" memory: "95Mi" diff --git a/test/e2e-kuttl/quota-forest/05-assert.yaml b/test/e2e-kuttl/quota-forest/05-assert.yaml index 80b64ebd5..7c30be2c0 100644 --- a/test/e2e-kuttl/quota-forest/05-assert.yaml +++ b/test/e2e-kuttl/quota-forest/05-assert.yaml @@ -2,7 +2,7 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job5-1replica + name: job4-1replica namespace: test status: state: Running @@ -10,7 +10,7 @@ status: apiVersion: v1 kind: Pod metadata: - name: job5-1replica-0 + name: job4-1replica-0 namespace: test --- apiVersion: mcad.ibm.com/v1beta1 diff --git a/test/e2e-kuttl/quota-forest/05-install.yaml b/test/e2e-kuttl/quota-forest/05-install.yaml index 4261559f8..f474578ba 100644 --- a/test/e2e-kuttl/quota-forest/05-install.yaml +++ b/test/e2e-kuttl/quota-forest/05-install.yaml @@ -1,10 +1,11 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job5-1replica + name: job4-1replica namespace: test labels: - quota_context: "bronze" + #quota_context: "context-root" + quota_context: "gold" quota_service: "service-root" spec: service: @@ -19,23 +20,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job5-1replica + name: job4-1replica namespace: test labels: - app: job5-1replica + app: job4-1replica spec: selector: matchLabels: - app: job5-1replica + app: job4-1replica replicas: 1 template: metadata: labels: - app: job5-1replica + app: job4-1replica size: "1" spec: containers: - - name: job5-1replica + - name: job4-1replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh @@ -43,8 +44,8 @@ spec: - while true; do sleep 10; done resources: requests: - cpu: "0.4" + cpu: "0.6" memory: "95Mi" limits: - cpu: "0.4" + cpu: "0.6" memory: "95Mi" diff --git a/test/e2e-kuttl/quota-forest/06-assert.yaml b/test/e2e-kuttl/quota-forest/06-assert.yaml index 4513dc88f..80b64ebd5 100644 --- a/test/e2e-kuttl/quota-forest/06-assert.yaml +++ b/test/e2e-kuttl/quota-forest/06-assert.yaml @@ -2,10 +2,16 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job6-1replica + name: job5-1replica namespace: test status: - state: Pending + state: Running +--- +apiVersion: v1 +kind: Pod +metadata: + name: job5-1replica-0 + namespace: test --- apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper diff --git a/test/e2e-kuttl/quota-forest/06-install.yaml b/test/e2e-kuttl/quota-forest/06-install.yaml index 14c3adb84..4261559f8 100644 --- a/test/e2e-kuttl/quota-forest/06-install.yaml +++ b/test/e2e-kuttl/quota-forest/06-install.yaml @@ -1,7 +1,7 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job6-1replica + name: job5-1replica namespace: test labels: quota_context: "bronze" @@ -19,23 +19,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job6-1replica + name: job5-1replica namespace: test labels: - app: job6-1replica + app: job5-1replica spec: selector: matchLabels: - app: job6-1replica + app: job5-1replica replicas: 1 template: metadata: labels: - app: job6-1replica + app: job5-1replica size: "1" spec: containers: - - name: job6-1replica + - name: job5-1replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh diff --git a/test/e2e-kuttl/quota-forest/07-assert.yaml b/test/e2e-kuttl/quota-forest/07-assert.yaml new file mode 100644 index 000000000..4513dc88f --- /dev/null +++ b/test/e2e-kuttl/quota-forest/07-assert.yaml @@ -0,0 +1,16 @@ +# Verify that quota management is enabled by checking the queuing is happening +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job6-1replica + namespace: test +status: + state: Pending +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job2-1replica + namespace: test +status: + state: Pending \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/07-install.yaml b/test/e2e-kuttl/quota-forest/07-install.yaml new file mode 100644 index 000000000..14c3adb84 --- /dev/null +++ b/test/e2e-kuttl/quota-forest/07-install.yaml @@ -0,0 +1,50 @@ +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job6-1replica + namespace: test + labels: + quota_context: "bronze" + quota_service: "service-root" +spec: + service: + spec: {} + priority: 1000 + resources: + metadata: {} + GenericItems: + - metadata: {} + replicas: 1 + generictemplate: + apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 + kind: StatefulSet + metadata: + name: job6-1replica + namespace: test + labels: + app: job6-1replica + spec: + selector: + matchLabels: + app: job6-1replica + replicas: 1 + template: + metadata: + labels: + app: job6-1replica + size: "1" + spec: + containers: + - name: job6-1replica + image: registry.access.redhat.com/ubi8/ubi:latest + command: + - /bin/sh + - -c + - while true; do sleep 10; done + resources: + requests: + cpu: "0.4" + memory: "95Mi" + limits: + cpu: "0.4" + memory: "95Mi" From 7282c642d5f7a6a5bfb518e1bed99a06150f71e7 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 01:05:58 -0800 Subject: [PATCH 16/33] Test kuttl failure in Travis run. Signed-off-by: dmatch01 --- test/e2e-kuttl/quota-forest/01-assert.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e-kuttl/quota-forest/01-assert.yaml b/test/e2e-kuttl/quota-forest/01-assert.yaml index c67a17241..eb1e652f0 100644 --- a/test/e2e-kuttl/quota-forest/01-assert.yaml +++ b/test/e2e-kuttl/quota-forest/01-assert.yaml @@ -2,4 +2,4 @@ apiVersion: v1 kind: Namespace metadata: - name: test \ No newline at end of file + name: test2 \ No newline at end of file From 499a7c23d1a6ce5773cbcb3543beaa4ee01ddbc3 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 01:38:40 -0800 Subject: [PATCH 17/33] Test fix to make e2e test failure in background mode. Signed-off-by: dmatch01 --- .travis.yml | 6 +++++- hack/run-e2e-kind.sh | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0fb15552e..a27d0d979 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,4 +34,8 @@ script: # use 'travis_wait n' which unfortunately does not stream output until the very end so monitoring the Travis log # during runtime is not possible. - make run-e2e & - - while [ -e /proc/$! ]; do echo -n "." && sleep 120; done + - PID=$! + - while [ -e /proc/${PID} ]; do echo -n "." && sleep 30; done + - wait $PID + - CODE=$? + - return ${CODE} diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index a64eaef05..618163e15 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -61,7 +61,7 @@ sudo curl -sSLf --output /tmp/kubectl-kuttl https://github.com/kudobuilder/kuttl # check if kind installed function check-prerequisites { echo "checking prerequisites" - which kind >/dev/null 2>&1 + which kind2 >/dev/null 2>&1 if [[ $? -ne 0 ]]; then echo "kind not installed, exiting." exit 1 From f23f33398b77da1da069ec366711ff0286bdd5bd Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 01:55:28 -0800 Subject: [PATCH 18/33] Test fix to make e2e test failure in background mode. Signed-off-by: dmatch01 --- .travis.yml | 6 +++--- hack/run-e2e-kind.sh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index a27d0d979..5fa4d172f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,8 +34,8 @@ script: # use 'travis_wait n' which unfortunately does not stream output until the very end so monitoring the Travis log # during runtime is not possible. - make run-e2e & - - PID=$! + - PID=$! && echo "make run e2e pid=${PID}" - while [ -e /proc/${PID} ]; do echo -n "." && sleep 30; done - wait $PID - - CODE=$? - - return ${CODE} + - CODE=$? && echo "make run e2e pid=${PID} return code=${CODE}" + - exit ${CODE} diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index 618163e15..a64eaef05 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -61,7 +61,7 @@ sudo curl -sSLf --output /tmp/kubectl-kuttl https://github.com/kudobuilder/kuttl # check if kind installed function check-prerequisites { echo "checking prerequisites" - which kind2 >/dev/null 2>&1 + which kind >/dev/null 2>&1 if [[ $? -ne 0 ]]; then echo "kind not installed, exiting." exit 1 From 3b67951e1cd48e8639309ed11d376535dc653123 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 02:10:13 -0800 Subject: [PATCH 19/33] Test fix to make e2e test kuttle success run in background mode. Signed-off-by: dmatch01 --- .travis.yml | 6 +++--- hack/run-e2e-kind.sh | 2 +- test/e2e-kuttl/quota-forest/01-assert.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fa4d172f..d0b8588d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,8 +34,8 @@ script: # use 'travis_wait n' which unfortunately does not stream output until the very end so monitoring the Travis log # during runtime is not possible. - make run-e2e & - - PID=$! && echo "make run e2e pid=${PID}" + - PID=$! && echo -e "\033[1;93mmake run e2e pid=${PID}" - while [ -e /proc/${PID} ]; do echo -n "." && sleep 30; done - - wait $PID - - CODE=$? && echo "make run e2e pid=${PID} return code=${CODE}" + - wait ${PID} + - CODE=$? && echo -e "\033[1;93mmake run e2e pid=${PID} return code=${CODE}" - exit ${CODE} diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index a64eaef05..cb1a3e0e5 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -431,4 +431,4 @@ mcad-env-status cd ${ROOT_DIR} echo "==========================>>>>> Running E2E tests... <<<<<==========================" -go test ./test/e2e -v -timeout 55m +#go test ./test/e2e -v -timeout 55m diff --git a/test/e2e-kuttl/quota-forest/01-assert.yaml b/test/e2e-kuttl/quota-forest/01-assert.yaml index eb1e652f0..c67a17241 100644 --- a/test/e2e-kuttl/quota-forest/01-assert.yaml +++ b/test/e2e-kuttl/quota-forest/01-assert.yaml @@ -2,4 +2,4 @@ apiVersion: v1 kind: Namespace metadata: - name: test2 \ No newline at end of file + name: test \ No newline at end of file From f2930b1602dffd65a277d881bc9766d49b00da65 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 02:53:33 -0800 Subject: [PATCH 20/33] Fix job names to match file names and increased timeout for preemption to happen. Signed-off-by: dmatch01 --- .travis.yml | 6 +-- kuttl-test.yaml | 1 + test/e2e-kuttl/quota-forest/00-assert.yaml | 1 + test/e2e-kuttl/quota-forest/01-assert.yaml | 2 +- test/e2e-kuttl/quota-forest/02-assert.yaml | 9 ++++ test/e2e-kuttl/quota-forest/03-assert.yaml | 11 +++-- test/e2e-kuttl/quota-forest/03-install.yaml | 13 +++-- test/e2e-kuttl/quota-forest/04-assert.yaml | 4 +- test/e2e-kuttl/quota-forest/04-install.yaml | 12 ++--- test/e2e-kuttl/quota-forest/05-assert.yaml | 25 ++++++++-- test/e2e-kuttl/quota-forest/05-install.yaml | 13 +++-- test/e2e-kuttl/quota-forest/06-assert.yaml | 38 +++++++++++++- test/e2e-kuttl/quota-forest/06-install.yaml | 12 ++--- test/e2e-kuttl/quota-forest/07-assert.yaml | 55 ++++++++++++++++++++- test/e2e-kuttl/quota-forest/07-install.yaml | 12 ++--- 15 files changed, 165 insertions(+), 49 deletions(-) diff --git a/.travis.yml b/.travis.yml index d0b8588d1..05a15c873 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,8 +34,8 @@ script: # use 'travis_wait n' which unfortunately does not stream output until the very end so monitoring the Travis log # during runtime is not possible. - make run-e2e & - - PID=$! && echo -e "\033[1;93mmake run e2e pid=${PID}" + - PID=$! && echo -e "\033[1;92mmake run e2e pid=${PID}" - while [ -e /proc/${PID} ]; do echo -n "." && sleep 30; done - wait ${PID} - - CODE=$? && echo -e "\033[1;93mmake run e2e pid=${PID} return code=${CODE}" - - exit ${CODE} + - CODE=$? && echo -e "\033[1;92mmake run e2e pid=${PID} return code=${CODE}" + - return ${CODE} diff --git a/kuttl-test.yaml b/kuttl-test.yaml index d5776f0f7..00f64f816 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -2,3 +2,4 @@ apiVersion: kuttl.dev/v1beta1 kind: TestSuite testDirs: - ./test/e2e-kuttl/ +timeout: 120 diff --git a/test/e2e-kuttl/quota-forest/00-assert.yaml b/test/e2e-kuttl/quota-forest/00-assert.yaml index c03368ecc..853505926 100644 --- a/test/e2e-kuttl/quota-forest/00-assert.yaml +++ b/test/e2e-kuttl/quota-forest/00-assert.yaml @@ -1,3 +1,4 @@ +# Verify CRDs existence apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/test/e2e-kuttl/quota-forest/01-assert.yaml b/test/e2e-kuttl/quota-forest/01-assert.yaml index c67a17241..672b198f5 100644 --- a/test/e2e-kuttl/quota-forest/01-assert.yaml +++ b/test/e2e-kuttl/quota-forest/01-assert.yaml @@ -1,4 +1,4 @@ -# Verify test namespace existance +# Verify test namespace existence apiVersion: v1 kind: Namespace metadata: diff --git a/test/e2e-kuttl/quota-forest/02-assert.yaml b/test/e2e-kuttl/quota-forest/02-assert.yaml index b10473519..13ef4b424 100644 --- a/test/e2e-kuttl/quota-forest/02-assert.yaml +++ b/test/e2e-kuttl/quota-forest/02-assert.yaml @@ -1,3 +1,4 @@ +# Verify subtree creations apiVersion: ibm.com/v1 kind: QuotaSubtree metadata: @@ -13,3 +14,11 @@ metadata: namespace: kube-system labels: tree: quota_service +--- +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: context-root-children + namespace: kube-system + labels: + tree: quota_context diff --git a/test/e2e-kuttl/quota-forest/03-assert.yaml b/test/e2e-kuttl/quota-forest/03-assert.yaml index f771f890a..5ce8c94dd 100644 --- a/test/e2e-kuttl/quota-forest/03-assert.yaml +++ b/test/e2e-kuttl/quota-forest/03-assert.yaml @@ -1,12 +1,17 @@ -# Verify that quota management is enabled by checking the queuing is happening +# Verify AppWrapper was dispatched and pod was created apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job2-1replica + name: job3-1replica namespace: test + labels: + quota_context: "gold" + quota_service: "service-root" +status: + state: Running --- apiVersion: v1 kind: Pod metadata: - name: job2-1replica-0 + name: job3-1replica-0 namespace: test \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/03-install.yaml b/test/e2e-kuttl/quota-forest/03-install.yaml index a4b2d74d8..08f45716f 100644 --- a/test/e2e-kuttl/quota-forest/03-install.yaml +++ b/test/e2e-kuttl/quota-forest/03-install.yaml @@ -1,10 +1,9 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job2-1replica + name: job3-1replica namespace: test labels: - #quota_context: "context-root" quota_context: "gold" quota_service: "service-root" spec: @@ -20,23 +19,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job2-1replica + name: job3-1replica namespace: test labels: - app: job2-1replica + app: job3-1replica spec: selector: matchLabels: - app: job2-1replica + app: job3-1replica replicas: 1 template: metadata: labels: - app: job2-1replica + app: job3-1replica size: "1" spec: containers: - - name: job2-1replica + - name: job3-1replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh diff --git a/test/e2e-kuttl/quota-forest/04-assert.yaml b/test/e2e-kuttl/quota-forest/04-assert.yaml index f5d0487c8..60df988cb 100644 --- a/test/e2e-kuttl/quota-forest/04-assert.yaml +++ b/test/e2e-kuttl/quota-forest/04-assert.yaml @@ -1,8 +1,8 @@ -# Verify that quota management is enabled by checking the queuing is happening +# Verify that quota management is enabled by checking the queuing is happening (e.g. no available quota for quota ids provided in AW) apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job3-10replica + name: job4-10replica namespace: test labels: quota_context: "context-root2" diff --git a/test/e2e-kuttl/quota-forest/04-install.yaml b/test/e2e-kuttl/quota-forest/04-install.yaml index a4ad655c9..aa70e3176 100644 --- a/test/e2e-kuttl/quota-forest/04-install.yaml +++ b/test/e2e-kuttl/quota-forest/04-install.yaml @@ -1,7 +1,7 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job3-10replica + name: job4-10replica namespace: test labels: quota_context: "context-root2" @@ -18,23 +18,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job3-10replica + name: job4-10replica namespace: test labels: - app: job3-10replica + app: job4-10replica spec: selector: matchLabels: - app: job3-10replica + app: job4-10replica replicas: 10 template: metadata: labels: - app: job3-10replica + app: job4-10replica size: "1" spec: containers: - - name: job3-10replica + - name: job4-10replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh diff --git a/test/e2e-kuttl/quota-forest/05-assert.yaml b/test/e2e-kuttl/quota-forest/05-assert.yaml index 7c30be2c0..15ec8e324 100644 --- a/test/e2e-kuttl/quota-forest/05-assert.yaml +++ b/test/e2e-kuttl/quota-forest/05-assert.yaml @@ -1,22 +1,39 @@ -# Verify that quota management is enabled by checking the queuing is happening +# Verify that quota management preempted lower priority job apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job4-1replica + name: job5-1replica namespace: test + labels: + quota_context: "gold" + quota_service: "service-root" status: state: Running --- apiVersion: v1 kind: Pod metadata: - name: job4-1replica-0 + name: job5-1replica-0 namespace: test --- apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job2-1replica + name: job4-10replica namespace: test + labels: + quota_context: "context-root2" + quota_service: "service-root2" +status: + state: Pending +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job3-1replica + namespace: test + labels: + quota_context: "gold" + quota_service: "service-root" status: state: Pending \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/05-install.yaml b/test/e2e-kuttl/quota-forest/05-install.yaml index f474578ba..e772f9d65 100644 --- a/test/e2e-kuttl/quota-forest/05-install.yaml +++ b/test/e2e-kuttl/quota-forest/05-install.yaml @@ -1,10 +1,9 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job4-1replica + name: job5-1replica namespace: test labels: - #quota_context: "context-root" quota_context: "gold" quota_service: "service-root" spec: @@ -20,23 +19,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job4-1replica + name: job5-1replica namespace: test labels: - app: job4-1replica + app: job5-1replica spec: selector: matchLabels: - app: job4-1replica + app: job5-1replica replicas: 1 template: metadata: labels: - app: job4-1replica + app: job5-1replica size: "1" spec: containers: - - name: job4-1replica + - name: job5-1replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh diff --git a/test/e2e-kuttl/quota-forest/06-assert.yaml b/test/e2e-kuttl/quota-forest/06-assert.yaml index 80b64ebd5..a77e13f3a 100644 --- a/test/e2e-kuttl/quota-forest/06-assert.yaml +++ b/test/e2e-kuttl/quota-forest/06-assert.yaml @@ -1,9 +1,29 @@ -# Verify that quota management is enabled by checking the queuing is happening +# Verify that quota management is enabled using different quota ids +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job6-1replica + namespace: test + labels: + quota_context: "bronze" + quota_service: "service-root" +status: + state: Running +--- +apiVersion: v1 +kind: Pod +metadata: + name: job6-1replica-0 + namespace: test +--- apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: name: job5-1replica namespace: test + labels: + quota_context: "gold" + quota_service: "service-root" status: state: Running --- @@ -16,7 +36,21 @@ metadata: apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job2-1replica + name: job4-10replica + namespace: test + labels: + quota_context: "context-root2" + quota_service: "service-root2" +status: + state: Pending +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job3-1replica namespace: test + labels: + quota_context: "gold" + quota_service: "service-root" status: state: Pending \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/06-install.yaml b/test/e2e-kuttl/quota-forest/06-install.yaml index 4261559f8..14c3adb84 100644 --- a/test/e2e-kuttl/quota-forest/06-install.yaml +++ b/test/e2e-kuttl/quota-forest/06-install.yaml @@ -1,7 +1,7 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job5-1replica + name: job6-1replica namespace: test labels: quota_context: "bronze" @@ -19,23 +19,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job5-1replica + name: job6-1replica namespace: test labels: - app: job5-1replica + app: job6-1replica spec: selector: matchLabels: - app: job5-1replica + app: job6-1replica replicas: 1 template: metadata: labels: - app: job5-1replica + app: job6-1replica size: "1" spec: containers: - - name: job5-1replica + - name: job6-1replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh diff --git a/test/e2e-kuttl/quota-forest/07-assert.yaml b/test/e2e-kuttl/quota-forest/07-assert.yaml index 4513dc88f..6c592bafd 100644 --- a/test/e2e-kuttl/quota-forest/07-assert.yaml +++ b/test/e2e-kuttl/quota-forest/07-assert.yaml @@ -1,16 +1,67 @@ -# Verify that quota management is enabled by checking the queuing is happening +# Verify that quota management hard limit +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job7-1replica + namespace: test + labels: + quota_context: "bronze" + quota_service: "service-root" +status: + state: Pending +--- apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: name: job6-1replica namespace: test + labels: + quota_context: "bronze" + quota_service: "service-root" +status: + state: Running +--- +apiVersion: v1 +kind: Pod +metadata: + name: job6-1replica-0 + namespace: test +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job5-1replica + namespace: test + labels: + quota_context: "gold" + quota_service: "service-root" +status: + state: Running +--- +apiVersion: v1 +kind: Pod +metadata: + name: job5-1replica-0 + namespace: test +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job4-10replica + namespace: test + labels: + quota_context: "context-root2" + quota_service: "service-root2" status: state: Pending --- apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job2-1replica + name: job3-1replica namespace: test + labels: + quota_context: "gold" + quota_service: "service-root" status: state: Pending \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/07-install.yaml b/test/e2e-kuttl/quota-forest/07-install.yaml index 14c3adb84..0f1946a76 100644 --- a/test/e2e-kuttl/quota-forest/07-install.yaml +++ b/test/e2e-kuttl/quota-forest/07-install.yaml @@ -1,7 +1,7 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job6-1replica + name: job7-1replica namespace: test labels: quota_context: "bronze" @@ -19,23 +19,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job6-1replica + name: job7-1replica namespace: test labels: - app: job6-1replica + app: job7-1replica spec: selector: matchLabels: - app: job6-1replica + app: job7-1replica replicas: 1 template: metadata: labels: - app: job6-1replica + app: job7-1replica size: "1" spec: containers: - - name: job6-1replica + - name: job7-1replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh From 5a38984229b5b13792436f687c2150f9e011cf86 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 03:10:02 -0800 Subject: [PATCH 21/33] Increase kuttle timeout. Signed-off-by: dmatch01 --- .travis.yml | 6 +++--- kuttl-test.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 05a15c873..ec4b8456f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ before_script: - export TEST_LOG_LEVEL=4 script: -# - make + - YELLOW='\033[1;93m' - make mcad-controller - make run-test # Process 'make images' when NOT a pull request @@ -34,8 +34,8 @@ script: # use 'travis_wait n' which unfortunately does not stream output until the very end so monitoring the Travis log # during runtime is not possible. - make run-e2e & - - PID=$! && echo -e "\033[1;92mmake run e2e pid=${PID}" + - PID=$! && echo -e "${YELLOW}make run e2e pid=${PID}" - while [ -e /proc/${PID} ]; do echo -n "." && sleep 30; done - wait ${PID} - - CODE=$? && echo -e "\033[1;92mmake run e2e pid=${PID} return code=${CODE}" + - CODE=$? && echo -e "${YELLOW}make run e2e pid=${PID} return code=${CODE}" - return ${CODE} diff --git a/kuttl-test.yaml b/kuttl-test.yaml index 00f64f816..8c15ef902 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -2,4 +2,4 @@ apiVersion: kuttl.dev/v1beta1 kind: TestSuite testDirs: - ./test/e2e-kuttl/ -timeout: 120 +timeout: 600 From 92e79e3b53604c222a49c0ac02a00153c3c160bf Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 03:26:06 -0800 Subject: [PATCH 22/33] Test fix to make e2e test failure in background mode. Signed-off-by: dmatch01 --- .travis.yml | 2 +- test/e2e-kuttl/quota-forest/01-assert.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ec4b8456f..a2de2a682 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ before_script: - export TEST_LOG_LEVEL=4 script: - - YELLOW='\033[1;93m' + - YELLOW='\033[43m' - make mcad-controller - make run-test # Process 'make images' when NOT a pull request diff --git a/test/e2e-kuttl/quota-forest/01-assert.yaml b/test/e2e-kuttl/quota-forest/01-assert.yaml index 672b198f5..075c2a884 100644 --- a/test/e2e-kuttl/quota-forest/01-assert.yaml +++ b/test/e2e-kuttl/quota-forest/01-assert.yaml @@ -2,4 +2,4 @@ apiVersion: v1 kind: Namespace metadata: - name: test \ No newline at end of file + name: test2 \ No newline at end of file From 854a5988f9add24a8dcd897f7ab004e2f8bff4ef Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 03:49:00 -0800 Subject: [PATCH 23/33] Test fix to make e2e test failure in background mode. Signed-off-by: dmatch01 --- .travis.yml | 6 ++---- kuttl-test.yaml | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index a2de2a682..c654a62cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ before_script: - export TEST_LOG_LEVEL=4 script: - - YELLOW='\033[43m' + - BLUE='\033[44m' - make mcad-controller - make run-test # Process 'make images' when NOT a pull request @@ -34,8 +34,6 @@ script: # use 'travis_wait n' which unfortunately does not stream output until the very end so monitoring the Travis log # during runtime is not possible. - make run-e2e & - - PID=$! && echo -e "${YELLOW}make run e2e pid=${PID}" + - PID=$! && echo -e "${BLUE}make run e2e pid=${PID}" - while [ -e /proc/${PID} ]; do echo -n "." && sleep 30; done - wait ${PID} - - CODE=$? && echo -e "${YELLOW}make run e2e pid=${PID} return code=${CODE}" - - return ${CODE} diff --git a/kuttl-test.yaml b/kuttl-test.yaml index 8c15ef902..3520d6f88 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -2,4 +2,4 @@ apiVersion: kuttl.dev/v1beta1 kind: TestSuite testDirs: - ./test/e2e-kuttl/ -timeout: 600 +timeout: 180 From bf8af829a69d0a69f90ac97ccc174bfe54dc6f77 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 04:02:15 -0800 Subject: [PATCH 24/33] Test fix to make e2e test kuttle success run in background mode. Signed-off-by: dmatch01 --- .travis.yml | 2 +- test/e2e-kuttl/quota-forest/01-assert.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c654a62cf..cd8972116 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ before_script: - export TEST_LOG_LEVEL=4 script: - - BLUE='\033[44m' + - BLUE='\033[0;94m' - make mcad-controller - make run-test # Process 'make images' when NOT a pull request diff --git a/test/e2e-kuttl/quota-forest/01-assert.yaml b/test/e2e-kuttl/quota-forest/01-assert.yaml index 075c2a884..672b198f5 100644 --- a/test/e2e-kuttl/quota-forest/01-assert.yaml +++ b/test/e2e-kuttl/quota-forest/01-assert.yaml @@ -2,4 +2,4 @@ apiVersion: v1 kind: Namespace metadata: - name: test2 \ No newline at end of file + name: test \ No newline at end of file From 879c47e761f9167669cce54bc30bc8f160dc8753 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 04:17:10 -0800 Subject: [PATCH 25/33] Use different color code for e2e PID message. Signed-off-by: dmatch01 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cd8972116..797fc920d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ before_script: - export TEST_LOG_LEVEL=4 script: - - BLUE='\033[0;94m' + - BLUE='\033[34m' - make mcad-controller - make run-test # Process 'make images' when NOT a pull request From 1e969c8e568e4dcb94230e2ce862f707b7e6febe Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 10:27:36 -0800 Subject: [PATCH 26/33] Debugging kuttl failure. Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index cb1a3e0e5..76cf41936 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -42,7 +42,8 @@ export IMAGE_TAG_MCAD="${2}" export MCAD_IMAGE_PULL_POLICY="${3-Always}" export IMAGE_MCAD="${IMAGE_REPOSITORY_MCAD}:${IMAGE_TAG_MCAD}" export KUTTL_VERSION=0.15.0 -export KUTTL_TEST_OPT="--config ${ROOT_DIR}/kuttl-test.yaml" +export KUTTL_TEST_OPT="--config ${ROOT_DIR}/kuttl-test.yaml --skip-delete" +#export KUTTL_TEST_OPT="--config ${ROOT_DIR}/kuttl-test.yaml" sudo apt-get update && sudo apt-get install -y apt-transport-https curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - From 42afea93e7668d62fdd6d11b23bf07b35d218de5 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 12:24:09 -0800 Subject: [PATCH 27/33] Rearrange setup of kuttl test for more stability of test. Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 7 +-- test/e2e-kuttl/quota-forest/01-assert.yaml | 27 ++++++++-- test/e2e-kuttl/quota-forest/01-install.yaml | 59 +++++++++++++++++++-- test/e2e-kuttl/quota-forest/02-assert.yaml | 27 ++-------- test/e2e-kuttl/quota-forest/02-install.yaml | 59 ++------------------- 5 files changed, 90 insertions(+), 89 deletions(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index 76cf41936..083cb0b60 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -42,8 +42,9 @@ export IMAGE_TAG_MCAD="${2}" export MCAD_IMAGE_PULL_POLICY="${3-Always}" export IMAGE_MCAD="${IMAGE_REPOSITORY_MCAD}:${IMAGE_TAG_MCAD}" export KUTTL_VERSION=0.15.0 -export KUTTL_TEST_OPT="--config ${ROOT_DIR}/kuttl-test.yaml --skip-delete" -#export KUTTL_TEST_OPT="--config ${ROOT_DIR}/kuttl-test.yaml" +export KUTTL_TEST_OPT="--config ${ROOT_DIR}/kuttl-test.yaml" +# FOR DEBUGGING +#export KUTTL_TEST_OPT="--config ${ROOT_DIR}/kuttl-test.yaml --skip-delete" sudo apt-get update && sudo apt-get install -y apt-transport-https curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - @@ -432,4 +433,4 @@ mcad-env-status cd ${ROOT_DIR} echo "==========================>>>>> Running E2E tests... <<<<<==========================" -#go test ./test/e2e -v -timeout 55m +go test ./test/e2e -v -timeout 55m diff --git a/test/e2e-kuttl/quota-forest/01-assert.yaml b/test/e2e-kuttl/quota-forest/01-assert.yaml index 672b198f5..13ef4b424 100644 --- a/test/e2e-kuttl/quota-forest/01-assert.yaml +++ b/test/e2e-kuttl/quota-forest/01-assert.yaml @@ -1,5 +1,24 @@ -# Verify test namespace existence -apiVersion: v1 -kind: Namespace +# Verify subtree creations +apiVersion: ibm.com/v1 +kind: QuotaSubtree metadata: - name: test \ No newline at end of file + name: context-root + namespace: kube-system + labels: + tree: quota_context +--- +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: service-root + namespace: kube-system + labels: + tree: quota_service +--- +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: context-root-children + namespace: kube-system + labels: + tree: quota_context diff --git a/test/e2e-kuttl/quota-forest/01-install.yaml b/test/e2e-kuttl/quota-forest/01-install.yaml index 5e0105d10..415d44626 100644 --- a/test/e2e-kuttl/quota-forest/01-install.yaml +++ b/test/e2e-kuttl/quota-forest/01-install.yaml @@ -1,4 +1,57 @@ -apiVersion: v1 -kind: Namespace +apiVersion: ibm.com/v1 +kind: QuotaSubtree metadata: - name: test \ No newline at end of file + name: context-root + namespace: kube-system + labels: + tree: quota_context +spec: + children: + - name: context-root + quotas: + requests: + cpu: 1075 + memory: 1045 +--- +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: service-root + namespace: kube-system + labels: + tree: quota_service +spec: + children: + - name: service-root + quotas: + requests: + cpu: 1075 + memory: 1045 +--- +apiVersion: ibm.com/v1 +kind: QuotaSubtree +metadata: + name: context-root-children + namespace: kube-system + labels: + tree: quota_context +spec: + parent: context-root + children: + - name: gold + quotas: + requests: + cpu: 1075 + memory: 450 + - name: silver + quotas: + hardLimit: false + requests: + cpu: 1075 + memory: 400 + - name: bronze + quotas: + hardLimit: true + requests: + cpu: 900 + memory: 300 \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/02-assert.yaml b/test/e2e-kuttl/quota-forest/02-assert.yaml index 13ef4b424..672b198f5 100644 --- a/test/e2e-kuttl/quota-forest/02-assert.yaml +++ b/test/e2e-kuttl/quota-forest/02-assert.yaml @@ -1,24 +1,5 @@ -# Verify subtree creations -apiVersion: ibm.com/v1 -kind: QuotaSubtree +# Verify test namespace existence +apiVersion: v1 +kind: Namespace metadata: - name: context-root - namespace: kube-system - labels: - tree: quota_context ---- -apiVersion: ibm.com/v1 -kind: QuotaSubtree -metadata: - name: service-root - namespace: kube-system - labels: - tree: quota_service ---- -apiVersion: ibm.com/v1 -kind: QuotaSubtree -metadata: - name: context-root-children - namespace: kube-system - labels: - tree: quota_context + name: test \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/02-install.yaml b/test/e2e-kuttl/quota-forest/02-install.yaml index 415d44626..5e0105d10 100644 --- a/test/e2e-kuttl/quota-forest/02-install.yaml +++ b/test/e2e-kuttl/quota-forest/02-install.yaml @@ -1,57 +1,4 @@ -apiVersion: ibm.com/v1 -kind: QuotaSubtree +apiVersion: v1 +kind: Namespace metadata: - name: context-root - namespace: kube-system - labels: - tree: quota_context -spec: - children: - - name: context-root - quotas: - requests: - cpu: 1075 - memory: 1045 ---- -apiVersion: ibm.com/v1 -kind: QuotaSubtree -metadata: - name: service-root - namespace: kube-system - labels: - tree: quota_service -spec: - children: - - name: service-root - quotas: - requests: - cpu: 1075 - memory: 1045 ---- -apiVersion: ibm.com/v1 -kind: QuotaSubtree -metadata: - name: context-root-children - namespace: kube-system - labels: - tree: quota_context -spec: - parent: context-root - children: - - name: gold - quotas: - requests: - cpu: 1075 - memory: 450 - - name: silver - quotas: - hardLimit: false - requests: - cpu: 1075 - memory: 400 - - name: bronze - quotas: - hardLimit: true - requests: - cpu: 900 - memory: 300 \ No newline at end of file + name: test \ No newline at end of file From 293c8ad17642f23c100503249a03a1469a73218f Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Wed, 1 Mar 2023 18:44:52 -0800 Subject: [PATCH 28/33] Introduced forced error test to validate new Travis workaround for long running Travis job. Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 35 +++++++++++++++++++++++------------ test/e2e/queue.go | 3 ++- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index 083cb0b60..e683364a6 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -51,7 +51,7 @@ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee -a /etc/apt/sources.list.d/kubernetes.list sudo apt-get update # Using older version due to older version of kubernetes cluster" -sudo apt-get install -y --allow-unauthenticated kubectl=1.17.0-00 +sudo apt-get install -y --allow-unauthenticated kubectl=1.17.0-00 # Download kind binary (0.6.1) sudo curl -o /usr/local/bin/kind -L https://github.com/kubernetes-sigs/kind/releases/download/v0.11.0/kind-linux-amd64 @@ -398,6 +398,17 @@ function kuttl-tests { mcad-quota-management-up mcad-env-status cd ${ROOT_DIR} + echo "==============>>>>> Running Quota Management Kuttl E2E tests... <<<<<==============" + echo "kubectl kuttl test ${KUTTL_TEST_OPT}" + kubectl kuttl test ${KUTTL_TEST_OPT} + if [[ $? -ne 0 ]]; then + echo "quota management kuttl e2e tests failure, exiting." + exit 1 + else + # Takes about 50 seconds for namespace created in kuttl testing to completely delete. + sleep 50 + fi + mcad-quota-management-down } trap cleanup EXIT @@ -410,17 +421,17 @@ kube-test-env-up # Quota management testing ### kuttl-tests -echo "==============>>>>> Running Quota Management Kuttl E2E tests... <<<<<==============" -echo "kubectl kuttl test ${KUTTL_TEST_OPT}" -kubectl kuttl test ${KUTTL_TEST_OPT} -if [[ $? -ne 0 ]]; then - echo "quota management kuttl e2e tests failure, exiting." - exit 1 -else - # Takes about 50 seconds for namespace created in kuttl testing to completely delete. - sleep 50 -fi -mcad-quota-management-down +#echo "==============>>>>> Running Quota Management Kuttl E2E tests... <<<<<==============" +#echo "kubectl kuttl test ${KUTTL_TEST_OPT}" +#kubectl kuttl test ${KUTTL_TEST_OPT} +#if [[ $? -ne 0 ]]; then +# echo "quota management kuttl e2e tests failure, exiting." +# exit 1 +#else +# # Takes about 50 seconds for namespace created in kuttl testing to completely delete. +# sleep 50 +#fi +#mcad-quota-management-down ### diff --git a/test/e2e/queue.go b/test/e2e/queue.go index 65cdb8275..d501265a1 100644 --- a/test/e2e/queue.go +++ b/test/e2e/queue.go @@ -96,7 +96,8 @@ var _ = Describe("AppWrapper E2E Test", func() { appwrappers = append(appwrappers, aw) err := waitAWPodsReady(context, aw) - Expect(err).NotTo(HaveOccurred()) + Expect(err).To(HaveOccurred()) +// Orig Expect(err).NotTo(HaveOccurred()) // This should fill up the master node aw2 := createDeploymentAWwith350CPU(context, "aw-deployment-2-350cpu") From 612b4103d95d5f0636327260f7f4804f830d95c5 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Thu, 2 Mar 2023 04:13:55 -0800 Subject: [PATCH 29/33] Force final failure test for e2e kuttl test. Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 16 ++-------------- test/e2e-kuttl/quota-forest/02-assert.yaml | 2 +- test/e2e/queue.go | 3 +-- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index e683364a6..3ca7be0b3 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -405,8 +405,8 @@ function kuttl-tests { echo "quota management kuttl e2e tests failure, exiting." exit 1 else - # Takes about 50 seconds for namespace created in kuttl testing to completely delete. - sleep 50 + # Takes a bit of time for namespace created in kuttl testing to completely delete. + sleep 40 fi mcad-quota-management-down } @@ -421,18 +421,6 @@ kube-test-env-up # Quota management testing ### kuttl-tests -#echo "==============>>>>> Running Quota Management Kuttl E2E tests... <<<<<==============" -#echo "kubectl kuttl test ${KUTTL_TEST_OPT}" -#kubectl kuttl test ${KUTTL_TEST_OPT} -#if [[ $? -ne 0 ]]; then -# echo "quota management kuttl e2e tests failure, exiting." -# exit 1 -#else -# # Takes about 50 seconds for namespace created in kuttl testing to completely delete. -# sleep 50 -#fi -#mcad-quota-management-down - ### # Non-quota management testing diff --git a/test/e2e-kuttl/quota-forest/02-assert.yaml b/test/e2e-kuttl/quota-forest/02-assert.yaml index 672b198f5..29259a073 100644 --- a/test/e2e-kuttl/quota-forest/02-assert.yaml +++ b/test/e2e-kuttl/quota-forest/02-assert.yaml @@ -2,4 +2,4 @@ apiVersion: v1 kind: Namespace metadata: - name: test \ No newline at end of file + name: test3 \ No newline at end of file diff --git a/test/e2e/queue.go b/test/e2e/queue.go index d501265a1..65cdb8275 100644 --- a/test/e2e/queue.go +++ b/test/e2e/queue.go @@ -96,8 +96,7 @@ var _ = Describe("AppWrapper E2E Test", func() { appwrappers = append(appwrappers, aw) err := waitAWPodsReady(context, aw) - Expect(err).To(HaveOccurred()) -// Orig Expect(err).NotTo(HaveOccurred()) + Expect(err).NotTo(HaveOccurred()) // This should fill up the master node aw2 := createDeploymentAWwith350CPU(context, "aw-deployment-2-350cpu") From 140c13b79083515be4f91a95316ebf99beb8f3d7 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Thu, 2 Mar 2023 05:17:49 -0800 Subject: [PATCH 30/33] Removed forced failure for Travis testing. Signed-off-by: dmatch01 --- test/e2e-kuttl/quota-forest/02-assert.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e-kuttl/quota-forest/02-assert.yaml b/test/e2e-kuttl/quota-forest/02-assert.yaml index 29259a073..672b198f5 100644 --- a/test/e2e-kuttl/quota-forest/02-assert.yaml +++ b/test/e2e-kuttl/quota-forest/02-assert.yaml @@ -2,4 +2,4 @@ apiVersion: v1 kind: Namespace metadata: - name: test3 \ No newline at end of file + name: test \ No newline at end of file From 07e57df20ca2182ecaaa5fa8d677dceb44d8c258 Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Mon, 13 Mar 2023 07:08:02 -0700 Subject: [PATCH 31/33] Added delay step 3 to give time for MCAD to process subtrees. Signed-off-by: dmatch01 --- test/e2e-kuttl/quota-forest/03-assert.yaml | 19 ++---- test/e2e-kuttl/quota-forest/03-install.yaml | 59 ++++-------------- test/e2e-kuttl/quota-forest/04-assert.yaml | 16 +++-- test/e2e-kuttl/quota-forest/04-install.yaml | 23 +++---- test/e2e-kuttl/quota-forest/05-assert.yaml | 32 +--------- test/e2e-kuttl/quota-forest/05-install.yaml | 23 ++++--- test/e2e-kuttl/quota-forest/06-assert.yaml | 27 ++------- test/e2e-kuttl/quota-forest/06-install.yaml | 18 +++--- test/e2e-kuttl/quota-forest/07-assert.yaml | 25 +++----- test/e2e-kuttl/quota-forest/07-install.yaml | 12 ++-- test/e2e-kuttl/quota-forest/08-assert.yaml | 67 +++++++++++++++++++++ test/e2e-kuttl/quota-forest/08-install.yaml | 50 +++++++++++++++ 12 files changed, 197 insertions(+), 174 deletions(-) create mode 100644 test/e2e-kuttl/quota-forest/08-assert.yaml create mode 100644 test/e2e-kuttl/quota-forest/08-install.yaml diff --git a/test/e2e-kuttl/quota-forest/03-assert.yaml b/test/e2e-kuttl/quota-forest/03-assert.yaml index 5ce8c94dd..f03795cb4 100644 --- a/test/e2e-kuttl/quota-forest/03-assert.yaml +++ b/test/e2e-kuttl/quota-forest/03-assert.yaml @@ -1,17 +1,8 @@ -# Verify AppWrapper was dispatched and pod was created -apiVersion: mcad.ibm.com/v1beta1 -kind: AppWrapper +# Verify delay step +apiVersion: batch/v1 +kind: Job metadata: - name: job3-1replica + name: job-delay-step namespace: test - labels: - quota_context: "gold" - quota_service: "service-root" status: - state: Running ---- -apiVersion: v1 -kind: Pod -metadata: - name: job3-1replica-0 - namespace: test \ No newline at end of file + succeeded: 1 \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/03-install.yaml b/test/e2e-kuttl/quota-forest/03-install.yaml index 08f45716f..8fc9c4ff6 100644 --- a/test/e2e-kuttl/quota-forest/03-install.yaml +++ b/test/e2e-kuttl/quota-forest/03-install.yaml @@ -1,50 +1,15 @@ -apiVersion: mcad.ibm.com/v1beta1 -kind: AppWrapper +apiVersion: batch/v1 +kind: Job metadata: - name: job3-1replica + name: job-delay-step namespace: test - labels: - quota_context: "gold" - quota_service: "service-root" spec: - service: - spec: {} - resources: - metadata: {} - Items: [] - GenericItems: - - metadata: {} - replicas: 1 - generictemplate: - apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 - kind: StatefulSet - metadata: - name: job3-1replica - namespace: test - labels: - app: job3-1replica - spec: - selector: - matchLabels: - app: job3-1replica - replicas: 1 - template: - metadata: - labels: - app: job3-1replica - size: "1" - spec: - containers: - - name: job3-1replica - image: registry.access.redhat.com/ubi8/ubi:latest - command: - - /bin/sh - - -c - - while true; do sleep 10; done - resources: - requests: - cpu: "0.6" - memory: "95Mi" - limits: - cpu: "0.6" - memory: "95Mi" + template: + spec: + containers: + - name: job-delay-step + image: ubuntu:latest + command: [ "/bin/bash", "-c", "--" ] + args: [ "sleep 10;" ] + restartPolicy: Never + backoffLimit: 4 diff --git a/test/e2e-kuttl/quota-forest/04-assert.yaml b/test/e2e-kuttl/quota-forest/04-assert.yaml index 60df988cb..c88808094 100644 --- a/test/e2e-kuttl/quota-forest/04-assert.yaml +++ b/test/e2e-kuttl/quota-forest/04-assert.yaml @@ -1,11 +1,17 @@ -# Verify that quota management is enabled by checking the queuing is happening (e.g. no available quota for quota ids provided in AW) +# Verify AppWrapper was dispatched and pod was created apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job4-10replica + name: job-gold-lo-pri-1replica namespace: test labels: - quota_context: "context-root2" - quota_service: "service-root2" + quota_context: "gold" + quota_service: "service-root" status: - state: Pending + state: Running +--- +apiVersion: v1 +kind: Pod +metadata: + name: job-gold-lo-pri-1replica-0 + namespace: test \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/04-install.yaml b/test/e2e-kuttl/quota-forest/04-install.yaml index aa70e3176..e1b2c2298 100644 --- a/test/e2e-kuttl/quota-forest/04-install.yaml +++ b/test/e2e-kuttl/quota-forest/04-install.yaml @@ -1,16 +1,17 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job4-10replica + name: job-gold-lo-pri-1replica namespace: test labels: - quota_context: "context-root2" - quota_service: "service-root2" + quota_context: "gold" + quota_service: "service-root" spec: service: spec: {} resources: metadata: {} + Items: [] GenericItems: - metadata: {} replicas: 1 @@ -18,23 +19,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job4-10replica + name: job-gold-lo-pri-1replica namespace: test labels: - app: job4-10replica + app: job-gold-lo-pri-1replica spec: selector: matchLabels: - app: job4-10replica - replicas: 10 + app: job-gold-lo-pri-1replica + replicas: 1 template: metadata: labels: - app: job4-10replica + app: job-gold-lo-pri-1replica size: "1" spec: containers: - - name: job4-10replica + - name: job-gold-lo-pri-1replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh @@ -42,8 +43,8 @@ spec: - while true; do sleep 10; done resources: requests: - cpu: "0.1" + cpu: "0.6" memory: "95Mi" limits: - cpu: "0.1" + cpu: "0.6" memory: "95Mi" diff --git a/test/e2e-kuttl/quota-forest/05-assert.yaml b/test/e2e-kuttl/quota-forest/05-assert.yaml index 15ec8e324..df07213d2 100644 --- a/test/e2e-kuttl/quota-forest/05-assert.yaml +++ b/test/e2e-kuttl/quota-forest/05-assert.yaml @@ -1,39 +1,11 @@ -# Verify that quota management preempted lower priority job +# Verify that quota management is enabled by checking the queuing is happening (e.g. no available quota for quota ids provided in AW) apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job5-1replica - namespace: test - labels: - quota_context: "gold" - quota_service: "service-root" -status: - state: Running ---- -apiVersion: v1 -kind: Pod -metadata: - name: job5-1replica-0 - namespace: test ---- -apiVersion: mcad.ibm.com/v1beta1 -kind: AppWrapper -metadata: - name: job4-10replica + name: job-bad-quota-id-10replica namespace: test labels: quota_context: "context-root2" quota_service: "service-root2" status: state: Pending ---- -apiVersion: mcad.ibm.com/v1beta1 -kind: AppWrapper -metadata: - name: job3-1replica - namespace: test - labels: - quota_context: "gold" - quota_service: "service-root" -status: - state: Pending \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/05-install.yaml b/test/e2e-kuttl/quota-forest/05-install.yaml index e772f9d65..421444351 100644 --- a/test/e2e-kuttl/quota-forest/05-install.yaml +++ b/test/e2e-kuttl/quota-forest/05-install.yaml @@ -1,15 +1,14 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job5-1replica + name: job-bad-quota-id-10replica namespace: test labels: - quota_context: "gold" - quota_service: "service-root" + quota_context: "context-root2" + quota_service: "service-root2" spec: service: spec: {} - priority: 1000 resources: metadata: {} GenericItems: @@ -19,23 +18,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job5-1replica + name: job-bad-quota-id-10replica namespace: test labels: - app: job5-1replica + app: job-bad-quota-id-10replica spec: selector: matchLabels: - app: job5-1replica - replicas: 1 + app: job-bad-quota-id-10replica + replicas: 10 template: metadata: labels: - app: job5-1replica + app: job-bad-quota-id-10replica size: "1" spec: containers: - - name: job5-1replica + - name: job-bad-quota-id-10replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh @@ -43,8 +42,8 @@ spec: - while true; do sleep 10; done resources: requests: - cpu: "0.6" + cpu: "0.1" memory: "95Mi" limits: - cpu: "0.6" + cpu: "0.1" memory: "95Mi" diff --git a/test/e2e-kuttl/quota-forest/06-assert.yaml b/test/e2e-kuttl/quota-forest/06-assert.yaml index a77e13f3a..20defa59d 100644 --- a/test/e2e-kuttl/quota-forest/06-assert.yaml +++ b/test/e2e-kuttl/quota-forest/06-assert.yaml @@ -1,25 +1,8 @@ -# Verify that quota management is enabled using different quota ids +# Verify that quota management preempted lower priority job apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job6-1replica - namespace: test - labels: - quota_context: "bronze" - quota_service: "service-root" -status: - state: Running ---- -apiVersion: v1 -kind: Pod -metadata: - name: job6-1replica-0 - namespace: test ---- -apiVersion: mcad.ibm.com/v1beta1 -kind: AppWrapper -metadata: - name: job5-1replica + name: job-gold-high-pri-1replica namespace: test labels: quota_context: "gold" @@ -30,13 +13,13 @@ status: apiVersion: v1 kind: Pod metadata: - name: job5-1replica-0 + name: job-gold-high-pri-1replica-0 namespace: test --- apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job4-10replica + name: job-bad-quota-id-10replica namespace: test labels: quota_context: "context-root2" @@ -47,7 +30,7 @@ status: apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job3-1replica + name: job-gold-lo-pri-1replica namespace: test labels: quota_context: "gold" diff --git a/test/e2e-kuttl/quota-forest/06-install.yaml b/test/e2e-kuttl/quota-forest/06-install.yaml index 14c3adb84..0f86e3dff 100644 --- a/test/e2e-kuttl/quota-forest/06-install.yaml +++ b/test/e2e-kuttl/quota-forest/06-install.yaml @@ -1,10 +1,10 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job6-1replica + name: job-gold-high-pri-1replica namespace: test labels: - quota_context: "bronze" + quota_context: "gold" quota_service: "service-root" spec: service: @@ -19,23 +19,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job6-1replica + name: job-gold-high-pri-1replica namespace: test labels: - app: job6-1replica + app: job-gold-high-pri-1replica spec: selector: matchLabels: - app: job6-1replica + app: job-gold-high-pri-1replica replicas: 1 template: metadata: labels: - app: job6-1replica + app: job-gold-high-pri-1replica size: "1" spec: containers: - - name: job6-1replica + - name: job-gold-high-pri-1replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh @@ -43,8 +43,8 @@ spec: - while true; do sleep 10; done resources: requests: - cpu: "0.4" + cpu: "0.6" memory: "95Mi" limits: - cpu: "0.4" + cpu: "0.6" memory: "95Mi" diff --git a/test/e2e-kuttl/quota-forest/07-assert.yaml b/test/e2e-kuttl/quota-forest/07-assert.yaml index 6c592bafd..51836365f 100644 --- a/test/e2e-kuttl/quota-forest/07-assert.yaml +++ b/test/e2e-kuttl/quota-forest/07-assert.yaml @@ -1,19 +1,8 @@ -# Verify that quota management hard limit +# Verify that quota management is enabled using different quota ids apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job7-1replica - namespace: test - labels: - quota_context: "bronze" - quota_service: "service-root" -status: - state: Pending ---- -apiVersion: mcad.ibm.com/v1beta1 -kind: AppWrapper -metadata: - name: job6-1replica + name: job-1-bronze-1replica namespace: test labels: quota_context: "bronze" @@ -24,13 +13,13 @@ status: apiVersion: v1 kind: Pod metadata: - name: job6-1replica-0 + name: job-1-bronze-1replica-0 namespace: test --- apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job5-1replica + name: job-gold-high-pri-1replica namespace: test labels: quota_context: "gold" @@ -41,13 +30,13 @@ status: apiVersion: v1 kind: Pod metadata: - name: job5-1replica-0 + name: job-gold-high-pri-1replica-0 namespace: test --- apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job4-10replica + name: job-bad-quota-id-10replica namespace: test labels: quota_context: "context-root2" @@ -58,7 +47,7 @@ status: apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job3-1replica + name: job-gold-lo-pri-1replica namespace: test labels: quota_context: "gold" diff --git a/test/e2e-kuttl/quota-forest/07-install.yaml b/test/e2e-kuttl/quota-forest/07-install.yaml index 0f1946a76..c400c65e5 100644 --- a/test/e2e-kuttl/quota-forest/07-install.yaml +++ b/test/e2e-kuttl/quota-forest/07-install.yaml @@ -1,7 +1,7 @@ apiVersion: mcad.ibm.com/v1beta1 kind: AppWrapper metadata: - name: job7-1replica + name: job-1-bronze-1replica namespace: test labels: quota_context: "bronze" @@ -19,23 +19,23 @@ spec: apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: StatefulSet metadata: - name: job7-1replica + name: job-1-bronze-1replica namespace: test labels: - app: job7-1replica + app: job-1-bronze-1replica spec: selector: matchLabels: - app: job7-1replica + app: job-1-bronze-1replica replicas: 1 template: metadata: labels: - app: job7-1replica + app: job-1-bronze-1replica size: "1" spec: containers: - - name: job7-1replica + - name: job-1-bronze-1replica image: registry.access.redhat.com/ubi8/ubi:latest command: - /bin/sh diff --git a/test/e2e-kuttl/quota-forest/08-assert.yaml b/test/e2e-kuttl/quota-forest/08-assert.yaml new file mode 100644 index 000000000..a3e53333b --- /dev/null +++ b/test/e2e-kuttl/quota-forest/08-assert.yaml @@ -0,0 +1,67 @@ +# Verify that quota management hard limit +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job-2-bronze-1replica + namespace: test + labels: + quota_context: "bronze" + quota_service: "service-root" +status: + state: Pending +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job-1-bronze-1replica + namespace: test + labels: + quota_context: "bronze" + quota_service: "service-root" +status: + state: Running +--- +apiVersion: v1 +kind: Pod +metadata: + name: job-1-bronze-1replica-0 + namespace: test +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job-gold-high-pri-1replica + namespace: test + labels: + quota_context: "gold" + quota_service: "service-root" +status: + state: Running +--- +apiVersion: v1 +kind: Pod +metadata: + name: job-gold-high-pri-1replica-0 + namespace: test +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job-bad-quota-id-10replica + namespace: test + labels: + quota_context: "context-root2" + quota_service: "service-root2" +status: + state: Pending +--- +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job-gold-lo-pri-1replica + namespace: test + labels: + quota_context: "gold" + quota_service: "service-root" +status: + state: Pending \ No newline at end of file diff --git a/test/e2e-kuttl/quota-forest/08-install.yaml b/test/e2e-kuttl/quota-forest/08-install.yaml new file mode 100644 index 000000000..bf3d31f39 --- /dev/null +++ b/test/e2e-kuttl/quota-forest/08-install.yaml @@ -0,0 +1,50 @@ +apiVersion: mcad.ibm.com/v1beta1 +kind: AppWrapper +metadata: + name: job-2-bronze-1replica + namespace: test + labels: + quota_context: "bronze" + quota_service: "service-root" +spec: + service: + spec: {} + priority: 1000 + resources: + metadata: {} + GenericItems: + - metadata: {} + replicas: 1 + generictemplate: + apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 + kind: StatefulSet + metadata: + name: job-2-bronze-1replica + namespace: test + labels: + app: job-2-bronze-1replica + spec: + selector: + matchLabels: + app: job-2-bronze-1replica + replicas: 1 + template: + metadata: + labels: + app: job-2-bronze-1replica + size: "1" + spec: + containers: + - name: job-2-bronze-1replica + image: registry.access.redhat.com/ubi8/ubi:latest + command: + - /bin/sh + - -c + - while true; do sleep 10; done + resources: + requests: + cpu: "0.4" + memory: "95Mi" + limits: + cpu: "0.4" + memory: "95Mi" From a2e6e300e7a3b91a260474928e71a3c7d7592afe Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Thu, 23 Mar 2023 17:50:34 -0700 Subject: [PATCH 32/33] Updated QM test to use Helm3 Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index f49b3b23f..b84a1f80e 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -316,8 +316,8 @@ function mcad-quota-management-up { # start mcad controller echo "Starting MCAD Controller for Quota Management Testing..." - echo "helm install mcad-controller namespace kube-system wait set loglevel=2 set resources.requests.cpu=1000m set resources.requests.memory=1024Mi set resources.limits.cpu=4000m set resources.limits.memory=4096Mi set image.repository=$IMAGE_REPOSITORY_MCAD set image.tag=$IMAGE_TAG_MCAD set image.pullPolicy=$MCAD_IMAGE_PULL_POLICY set configMap.quotaEnabled='true' set quotaManagement.rbac.apiGroup=ibm.com set quotaManagement.rbac.resource=quotasubtrees set configMap.name=mcad-controller-configmap set configMap.preemptionEnabled='true'" - helm install mcad-controller --namespace kube-system --wait --set loglevel=10 --set resources.requests.cpu=1000m --set resources.requests.memory=1024Mi --set resources.limits.cpu=4000m --set resources.limits.memory=4096Mi --set image.repository=$IMAGE_REPOSITORY_MCAD --set image.tag=$IMAGE_TAG_MCAD --set image.pullPolicy=$MCAD_IMAGE_PULL_POLICY --set configMap.quotaEnabled='"true"' --set quotaManagement.rbac.apiGroup=ibm.com --set quotaManagement.rbac.resource=quotasubtrees --set configMap.name=mcad-controller-configmap --set configMap.preemptionEnabled='"true"' + echo "helm upgrade --install mcad-controller . namespace kube-system wait set loglevel=10 set resources.requests.cpu=1000m set resources.requests.memory=1024Mi set resources.limits.cpu=4000m set resources.limits.memory=4096Mi set image.repository=$IMAGE_REPOSITORY_MCAD set image.tag=$IMAGE_TAG_MCAD set image.pullPolicy=$MCAD_IMAGE_PULL_POLICY set configMap.quotaEnabled='true' set quotaManagement.rbac.apiGroup=ibm.com set quotaManagement.rbac.resource=quotasubtrees set configMap.name=mcad-controller-configmap set configMap.preemptionEnabled='true'" + helm upgrade --install mcad-controller . --namespace kube-system --wait --set loglevel=10 --set resources.requests.cpu=1000m --set resources.requests.memory=1024Mi --set resources.limits.cpu=4000m --set resources.limits.memory=4096Mi --set image.repository=$IMAGE_REPOSITORY_MCAD --set image.tag=$IMAGE_TAG_MCAD --set image.pullPolicy=$MCAD_IMAGE_PULL_POLICY --set configMap.quotaEnabled='"true"' --set quotaManagement.rbac.apiGroup=ibm.com --set quotaManagement.rbac.resource=quotasubtrees --set configMap.name=mcad-controller-configmap --set configMap.preemptionEnabled='"true"' sleep 10 } @@ -334,7 +334,6 @@ function mcad-quota-management-down { } function mcad-up { - cd $ROOT_DIR/deployment kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/scheduler-plugins/277b6bdec18f8a9e9ccd1bfeaf4b66495bfc6f92/config/crd/bases/scheduling.sigs.k8s.io_podgroups.yaml cd $ROOT_DIR/deployment/mcad-controller From a31841ad2790f574ab71ae6a3de65374b46221eb Mon Sep 17 00:00:00 2001 From: dmatch01 Date: Fri, 24 Mar 2023 06:57:28 -0700 Subject: [PATCH 33/33] Added the new path QM test using new Helm3 setup. Signed-off-by: dmatch01 --- hack/run-e2e-kind.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/run-e2e-kind.sh b/hack/run-e2e-kind.sh index b84a1f80e..00874511a 100755 --- a/hack/run-e2e-kind.sh +++ b/hack/run-e2e-kind.sh @@ -312,7 +312,7 @@ function kube-test-env-up { } function mcad-quota-management-up { - cd $ROOT_DIR/deployment + cd $ROOT_DIR/deployment/mcad-controller # start mcad controller echo "Starting MCAD Controller for Quota Management Testing..."