Skip to content

Commit

Permalink
[State] load state and use desired state to resume resources
Browse files Browse the repository at this point in the history
  • Loading branch information
MeNsaaH committed Jan 12, 2021
1 parent 9bcfd2a commit 3940e74
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 41 deletions.
89 changes: 58 additions & 31 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ import (
)

var (
cfgFile string
cfg *config.Config
providers []*provider.Provider
backend state.Backender
currentState *state.State
cfgFile string
cfg *config.Config
providers []*provider.Provider
backend state.Backender
activeState *state.State
)

// rootCmd represents the base command when called without any subcommands
Expand All @@ -51,7 +51,26 @@ var rootCmd = &cobra.Command{
// Uncomment the following line if your bare application
// has an action associated with it:
Run: func(cmd *cobra.Command, args []string) {
// RefreshResources on every execution
refreshResources(providers)
for _, p := range providers {
res := activeState.Current[p.Name]

stoppableResources := p.GetStoppableResources(res)
fmt.Println("Stoppable Resources: ", stoppableResources)
errs := p.StopResources(stoppableResources)
fmt.Println("Errors Stopping Resources: ", errs)

resumableResources := p.GetResumableResources(res)
fmt.Println("Resumable Resources: ", resumableResources)
errs = p.ResumeResources(resumableResources)
fmt.Println("Errors Resuming Resources: ", errs)

destroyableResources := p.GetDestroyableResources(res)
fmt.Println("Destroyable Resources: ", destroyableResources)
errs = p.DestroyResources(destroyableResources)
fmt.Println("Errors Destroying Resources: ", errs)
}
},
}

Expand All @@ -67,6 +86,7 @@ func Execute() {
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.reka.yaml)")
rootCmd.Flags().BoolP("unused-only", "t", false, "Reap only unused resources")
}

// initConfig reads in config file and ENV variables if set.
Expand Down Expand Up @@ -135,41 +155,48 @@ func initProviders() []*provider.Provider {
// Refresh current status of resources from Providers
// TODO Reconcile state so that new resources are added to desired states and former resources removed
func refreshResources(providers []*provider.Provider) {
// currentState is the state stored in backend
currentState = backend.GetState()
// activeState is the current state stored in backend
activeState = backend.GetState()

currentState.Current = make(state.ProvidersState)
activeState.Current = make(state.ProvidersState)
for _, provider := range providers {
allResources := provider.GetAllResources()
currentState.Current[provider.Name] = allResources
res := provider.GetAllResources()
activeState.Current[provider.Name] = res
}

// Add new resources to desired state if they don't already exists
for k, v := range currentState.Current {
if m, ok := currentState.Desired[k]; ok || currentState.Desired[k] == nil {
log.Error(m)
// TODO Return difference between two Resources object
continue
// this is to ensure all new resources created are also added to reka's desired state
// too many for loops 😫
// TODO check for attribute changes not in desired state for instance, if node pool was scaled
for currentProvider, currentProviderResources := range activeState.Current {
if _, ok := activeState.Desired[currentProvider]; ok {
for u, w := range currentProviderResources {
if _, ok := activeState.Desired[currentProvider][u]; ok {
for _, res := range w {
if !containsResource(activeState.Desired[currentProvider][u], res) {
activeState.Desired[currentProvider][u] = append(activeState.Desired[currentProvider][u], res)
}
}
} else {
activeState.Desired[currentProvider][u] = activeState.Current[currentProvider][u]
}
}
} else {
activeState.Desired[currentProvider] = currentProviderResources
}
currentState.Desired[k] = v
}

backend.WriteState(currentState)
backend.WriteState(activeState)
}

func reapResources() {
// stoppableResources := provider.GetStoppableResources(allResources)
// fmt.Println("Stoppable Resources: ", stoppableResources)
// errs := provider.StopResources(stoppableResources)
// fmt.Println("Errors Stopping Resources: ", errs)

// resumableResources := provider.GetResumableResources(allResources)
// fmt.Println("Resumable Resources: ", resumableResources)
// errs = provider.ResumeResources(resumableResources)
// fmt.Println("Errors Resuming Resources: ", errs)

// destroyableResources := provider.GetDestroyableResources(allResources)
// fmt.Println("Destroyable Resources: ", destroyableResources)
// errs = provider.DestroyResources(destroyableResources)
// fmt.Println("Errors Destroying Resources: ", errs)
}

func containsResource(res []*resource.Resource, r *resource.Resource) bool {
for _, rs := range res {
if rs.UUID == r.UUID {
return true
}
}
return false
}
4 changes: 4 additions & 0 deletions config/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ exclude:
# Rules for stopping, resuming and terminating instances
rules:
- name: Pause all staging instances on a weekend (staging)
# Target specific resource types
target:
- aws.ec2
- aws.ebs
tags:
env: staging
condition:
Expand Down
8 changes: 6 additions & 2 deletions provider/aws/eks.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,13 @@ func ResumeEKSClusters(cfg aws.Config, clusters []*resource.Resource) error {
return nil
}
for _, clsr := range clusters {
desired, err := utils.GetResourceFromDesiredState(providerName, eksName, clsr.UUID)
if err != nil {
eksLogger.Error(err.Error())
continue
}
eksLogger.Debugf("Stopping EKS Clusters %s ...", clsr)
for _, ng := range clsr.SubResources[nodegroupName] {
// TODO get value of desired size from state
for _, ng := range desired.SubResources[nodegroupName] {
desiredSize, _ := ng.Attributes["DesiredSize"].(int32)
err := resizeNodeGroup(svc, clsr.UUID, ng.UUID, desiredSize)
if err != nil {
Expand Down
14 changes: 14 additions & 0 deletions provider/aws/utils/state.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package utils

import (
"fmt"

"github.com/mensaah/reka/resource"
"github.com/mensaah/reka/state"
)

// GetResourceStatus Get the current status of Resource: Pending, Running, ... Stopped
Expand Down Expand Up @@ -43,3 +46,14 @@ func GetEksResourceStatus(s string) resource.Status {
}
return resource.Stopped
}

func GetResourceFromDesiredState(providerName, resMgr, uid string) (*resource.Resource, error) {
activeState := (state.GetBackend()).GetState()

for _, w := range activeState.Desired[providerName][resMgr] {
if w.UUID == uid {
return w, nil
}
}
return &resource.Resource{}, fmt.Errorf("%s Resource %s not found in state", resMgr, uid)
}
12 changes: 8 additions & 4 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@ func (p *Provider) GetStoppableResources(resources Resources) Resources {
}
}
}
count += len(stoppableResList)
stoppableResources[mgrName] = stoppableResList
if p.getManager(mgrName).Stop != nil && p.getManager(mgrName).Resume != nil {
count += len(stoppableResList)
stoppableResources[mgrName] = stoppableResList
}
}
p.Logger.Infof("Found %d resources to be stopped", count)
return stoppableResources
Expand All @@ -118,9 +120,11 @@ func (p *Provider) GetResumableResources(resources Resources) Resources {
}
}
}
resumableResource[mgrName] = resumableResList
if p.getManager(mgrName).Stop != nil && p.getManager(mgrName).Resume != nil {
resumableResource[mgrName] = resumableResList
count += len(resumableResList)
}
}
count += len(resumableResource)
p.Logger.Infof("Found %d resources to be resumed", count)
return resumableResource
}
Expand Down
5 changes: 5 additions & 0 deletions state/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@ func InitBackend() Backender {
}
return backend
}

// GetBackend gets the current active backend
func GetBackend() Backender {
return backend
}
6 changes: 2 additions & 4 deletions state/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package state

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"

Expand Down Expand Up @@ -40,12 +39,11 @@ func (s LocalBackend) GetState() *State {
// WriteState writes state to local path
func (s LocalBackend) WriteState(st *State) error {
log.Debugf("Writing state to %s\n", s.Path)
file, err := json.MarshalIndent(st, "", " ")
data, err := json.MarshalIndent(st, "", " ")
if err != nil {
log.Fatal("Failed to Load State for Writing")
}
fmt.Println(string(file))
err = ioutil.WriteFile(s.Path, file, 0644)
err = ioutil.WriteFile(s.Path, data, 0644)
if err != nil {
log.Fatal("Failed to write state to file")
}
Expand Down

0 comments on commit 3940e74

Please sign in to comment.