Skip to content

Add support for try and undo allocate #508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pkg/quotaplugins/quota-forest/quota-manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ A summary of the API interface to the Quota Manager follows.
- Forest Updates
- refresh (effect) updates from caches
- `UpdateForest(forestName)`
- Undo consumer allocation: Two calls are provided to try to allocate a consumer, and if unaccepted, to undo the effect of the allocation trial. If the trial is accepted, no further action is needed. Otherwise, the undo has to be called right after the try allocation, without making any calls to change the trees or allocate/deallocate consumers. These operations are intended only during Normal mode.
(Note: This design pattern puts the burden on the caller of the quota manager to make sure that TryAllocate() and UndoAllocate() are run in an atomic fashion. So, a lock is needed for that purpose. An example is provided in the TestQuotaManagerParallelTryUndoAllocation() in [quotamanagerundo_test.go](quota/quotamanagerundo_test.go))
- `TryAllocate(treeName, consumerID)`
- `UndoAllocate(treeName, consumerID)`
- `TryAllocateForest(forestName, consumerID)`
- `UndoAllocateForest(forestName, consumerID)`

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.

Expand Down
122 changes: 122 additions & 0 deletions pkg/quotaplugins/quota-forest/quota-manager/demos/undo/forest/demo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
Copyright 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.
*/
package main

import (
"flag"
"fmt"
"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 := os.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
if job == "job4" {
_, err = quotaManager.TryAllocateForest(forestName, consumerID)
if err != nil {
fmt.Printf("error allocating consumer: %v \n", err)
}
err = quotaManager.UndoAllocateForest(forestName, "job-4")
if err != nil {
fmt.Printf("error undoing allocation consumer: %v \n", err)
}
_, err = quotaManager.AllocateForest(forestName, consumerID)
} else {
_, err = quotaManager.AllocateForest(forestName, consumerID)
}

if err != nil {
fmt.Printf("error allocating consumer: %v \n", err)
quotaManager.RemoveConsumer((consumerID))
continue
}
}

// 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)
}
124 changes: 124 additions & 0 deletions pkg/quotaplugins/quota-forest/quota-manager/demos/undo/tree/demo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
Copyright 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.
*/
package main

import (
"flag"
"fmt"
"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"
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()

prefix := "../../../samples/tree/"
treeFileName := prefix + "tree.json"
caFileName := prefix + "ca.json"
cbFileName := prefix + "cb.json"
ccFileName := prefix + "cc.json"
cdFileName := prefix + "cd.json"
ceFileName := prefix + "ce.json"

// create a quota manager
fmt.Println("==> Creating Quota Manager")
fmt.Println("**************************")
quotaManager := quota.NewManager()
treeJsonString, err := os.ReadFile(treeFileName)
if err != nil {
fmt.Printf("error reading quota tree file: %s", treeFileName)
return
}
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 consumers
allocate(quotaManager, treeName, caFileName, false)
allocate(quotaManager, treeName, cbFileName, false)
allocate(quotaManager, treeName, ccFileName, false)

// try and undo allocation
allocate(quotaManager, treeName, cdFileName, true)
undoAllocate(quotaManager, treeName, cdFileName)

// allocate consumers
allocate(quotaManager, treeName, ceFileName, false)
}

// allocate consumer from file
func allocate(quotaManager *quota.Manager, treeName string, consumerFileName string, try bool) {
consumerInfo := getConsumerInfo(consumerFileName)
if consumerInfo == nil {
fmt.Printf("error reading consumer file: %s", consumerFileName)
return
}
consumerID := consumerInfo.GetID()
fmt.Println("==> Allocating consumer " + consumerID)
fmt.Println("**************************")
quotaManager.AddConsumer(consumerInfo)

var allocResponse *core.AllocationResponse
var err error
if try {
allocResponse, err = quotaManager.TryAllocate(treeName, consumerID)
} else {
allocResponse, err = quotaManager.Allocate(treeName, consumerID)
}
if err != nil {
fmt.Printf("error allocating consumer: %v", err)
return
}
fmt.Println(allocResponse)
fmt.Println(quotaManager)
}

// undo most recent consumer allocation
func undoAllocate(quotaManager *quota.Manager, treeName string, consumerFileName string) {
consumerInfo := getConsumerInfo(consumerFileName)
if consumerInfo == nil {
fmt.Printf("error reading consumer file: %s", consumerFileName)
return
}
consumerID := consumerInfo.GetID()
fmt.Println("==> Undo allocating consumer " + consumerID)
fmt.Println("**************************")
quotaManager.UndoAllocate(treeName, consumerID)
fmt.Println(quotaManager)
}

// get consumer info from yaml file
func getConsumerInfo(consumerFileName string) *quota.ConsumerInfo {
consumerInfo, err := quota.NewConsumerInfoFromFile(consumerFileName)
if err != nil {
fmt.Printf("error reading consumer file: %s", consumerFileName)
return nil
}
return consumerInfo
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
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.
Expand All @@ -19,7 +19,7 @@ package quota
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"

"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"
Expand Down Expand Up @@ -49,7 +49,7 @@ func NewConsumerInfo(consumerStruct utils.JConsumer) (*ConsumerInfo, error) {

// NewConsumerInfoFromFile : create a new ConsumerInfo from Json file
func NewConsumerInfoFromFile(consumerFileName string) (*ConsumerInfo, error) {
byteValue, err := ioutil.ReadFile(consumerFileName)
byteValue, err := os.ReadFile(consumerFileName)
if err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
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.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0
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,
Expand Down Expand Up @@ -119,6 +119,13 @@ func (c *Consumer) IsAllocated() bool {
return c.aNode != nil
}

// ByID implements sort.Interface based on the ID field.
type ByID []*Consumer

func (c ByID) Len() int { return len(c) }
func (c ByID) Less(i, j int) bool { return c[i].id < c[j].id }
func (c ByID) Swap(i, j int) { c[i], c[j] = c[j], c[i] }

// String : a print out of the consumer
func (c *Consumer) String() string {
var b bytes.Buffer
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
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.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0
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,
Expand All @@ -19,7 +19,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"

utils "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/utils"
)
Expand All @@ -42,7 +42,7 @@ func NewForestConsumer(id string, consumers map[string]*Consumer) *ForestConsume

// NewForestConsumerFromFile : create a forest consumer from JSON file
func NewForestConsumerFromFile(consumerFileName string, resourceNames map[string][]string) (*ForestConsumer, error) {
byteValue, err := ioutil.ReadFile(consumerFileName)
byteValue, err := os.ReadFile(consumerFileName)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -108,7 +108,7 @@ func (fc *ForestConsumer) GetTreeConsumer(treeName string) *Consumer {
return fc.consumers[treeName]
}

//IsAllocated : is consumer allocated on all trees in the forest
// IsAllocated : is consumer allocated on all trees in the forest
func (fc *ForestConsumer) IsAllocated() bool {
for _, consumer := range fc.consumers {
if !consumer.IsAllocated() {
Expand Down
Loading