Skip to content
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

Refactored state machine to use strings instead of integers #335

Merged
merged 1 commit into from
Oct 18, 2016
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
29 changes: 24 additions & 5 deletions api/rpc/stack/stack-state.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,30 @@ import (

// StackRuleSet defines possible transitions for stack states
var StackRuleSet = state.RuleSet{
// | Stopped | Starting | Running | Redeploying
[]bool{false /**/, true /* */, false /**/, true /* */}, // Stopped (initial state)
[]bool{false /**/, false /**/, true /* */, false /**/}, // Starting
[]bool{true /* */, false /**/, false /**/, true /* */}, // Running
[]bool{true /* */, true /* */, false /**/, false /**/}, // Redeploying
StackState_Stopped.String(): {
StackState_Stopped.String(): false,
StackState_Starting.String(): true,
StackState_Running.String(): false,
StackState_Redeploying.String(): true,
},
StackState_Starting.String(): {
StackState_Stopped.String(): false,
StackState_Starting.String(): false,
StackState_Running.String(): true,
StackState_Redeploying.String(): false,
},
StackState_Running.String(): {
StackState_Stopped.String(): true,
StackState_Starting.String(): false,
StackState_Running.String(): false,
StackState_Redeploying.String(): true,
},
StackState_Redeploying.String(): {
StackState_Stopped.String(): true,
StackState_Starting.String(): true,
StackState_Running.String(): false,
StackState_Redeploying.String(): false,
},
}

var stackStateMachine = state.NewMachine(StackRuleSet, runtime.Store)
28 changes: 9 additions & 19 deletions api/rpc/stack/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ func (s *Server) Start(ctx context.Context, in *StackRequest) (*StackReply, erro
if stack.Services == nil || len(stack.Services) == 0 {
return nil, fmt.Errorf("No services found for the stack %s \n", in.StackIdent)
}
if err := stackStateMachine.TransitionTo(stack.Id, int32(StackState_Starting)); err != nil {
if err := stackStateMachine.TransitionTo(stack.Id, StackState_Starting.String()); err != nil {
return nil, err
}
fmt.Printf("Starting stack %s\n", in.StackIdent)
Expand Down Expand Up @@ -417,7 +417,7 @@ func (s *Server) Start(ctx context.Context, in *StackRequest) (*StackReply, erro
return nil, createErr
}
}
if err := stackStateMachine.TransitionTo(stack.Id, int32(StackState_Running)); err != nil {
if err := stackStateMachine.TransitionTo(stack.Id, StackState_Running.String()); err != nil {
return nil, err
}
reply := StackReply{
Expand All @@ -433,7 +433,7 @@ func (s *Server) Stop(ctx context.Context, in *StackRequest) (*StackReply, error
if errIdent != nil {
return nil, errIdent
}
if running, err := stackStateMachine.Is(stack.Id, int32(StackState_Running)); err != nil {
if running, err := stackStateMachine.Is(stack.Id, StackState_Running.String()); err != nil {
return nil, err
} else if !running {
return nil, errors.New("Stack is not running")
Expand All @@ -448,7 +448,7 @@ func (s *Server) Stop(ctx context.Context, in *StackRequest) (*StackReply, error
if err := s.removeCustomNetworks(ctx, stack, false); err != nil {
fmt.Printf("catch error during remove custom networks: %v", err)
}
if err := stackStateMachine.TransitionTo(stack.Id, int32(StackState_Stopped)); err != nil {
if err := stackStateMachine.TransitionTo(stack.Id, StackState_Stopped.String()); err != nil {
fmt.Printf("catch error during stack state transition: %v", err)
}
reply := StackReply{
Expand Down Expand Up @@ -583,7 +583,7 @@ func (s *Server) Remove(ctx context.Context, in *RemoveRequest) (*StackReply, er
return nil, errIdent
}
if !in.Force {
if stopped, err := stackStateMachine.Is(stack.Id, int32(StackState_Stopped)); err != nil {
if stopped, err := stackStateMachine.Is(stack.Id, StackState_Stopped.String()); err != nil {
return nil, err
} else if !stopped {
return nil, errors.New("The stack is not stopped")
Expand Down Expand Up @@ -640,19 +640,9 @@ func (s *Server) getStackInfo(ctx context.Context, ID string) *StackInfo {
info.Name = stack.Name
info.Id = stack.Id
}
state, errGet := stackStateMachine.GetState(stack.Id)
info.State = "nc"
if errGet == nil {
switch state {
case 0:
info.State = "Stopped"
case 1:
info.State = "Starting"
case 2:
info.State = "Running"
case 3:
info.State = "Redeploying"
}
info.State, err = stackStateMachine.GetState(stack.Id)
if err != nil {
info.State = "N/A"
}
return &info
}
Expand All @@ -665,7 +655,7 @@ func newStackFromYaml(ctx context.Context, config string) (stack *Stack, err err
}

// Create stack state
if err = stackStateMachine.CreateState(stack.Id, int32(StackState_Stopped)); err != nil {
if err = stackStateMachine.CreateState(stack.Id, StackState_Stopped.String()); err != nil {
return
}

Expand Down
64 changes: 32 additions & 32 deletions api/rpc/stack/stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,82 +162,82 @@ func TestTransitionsFromStopped(t *testing.T) {
machine := state.NewMachine(stack.StackRuleSet, runtime.Store)

id := stringid.GenerateNonCryptoID()
machine.CreateState(id, int32(stack.StackState_Stopped))
assert.Error(t, machine.TransitionTo(id, int32(stack.StackState_Stopped)))
machine.CreateState(id, stack.StackState_Stopped.String())
assert.Error(t, machine.TransitionTo(id, stack.StackState_Stopped.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Stopped))
assert.NoError(t, machine.TransitionTo(id, int32(stack.StackState_Starting)))
machine.CreateState(id, stack.StackState_Stopped.String())
assert.NoError(t, machine.TransitionTo(id, stack.StackState_Starting.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Stopped))
assert.Error(t, machine.TransitionTo(id, int32(stack.StackState_Running)))
machine.CreateState(id, stack.StackState_Stopped.String())
assert.Error(t, machine.TransitionTo(id, stack.StackState_Running.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Stopped))
assert.NoError(t, machine.TransitionTo(id, int32(stack.StackState_Redeploying)))
machine.CreateState(id, stack.StackState_Stopped.String())
assert.NoError(t, machine.TransitionTo(id, stack.StackState_Redeploying.String()))
machine.DeleteState(id)
}

func TestTransitionsFromStarting(t *testing.T) {
machine := state.NewMachine(stack.StackRuleSet, runtime.Store)
id := stringid.GenerateNonCryptoID()

machine.CreateState(id, int32(stack.StackState_Starting))
assert.Error(t, machine.TransitionTo(id, int32(stack.StackState_Stopped)))
machine.CreateState(id, stack.StackState_Starting.String())
assert.Error(t, machine.TransitionTo(id, stack.StackState_Stopped.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Starting))
assert.Error(t, machine.TransitionTo(id, int32(stack.StackState_Starting)))
machine.CreateState(id, stack.StackState_Starting.String())
assert.Error(t, machine.TransitionTo(id, stack.StackState_Starting.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Starting))
assert.NoError(t, machine.TransitionTo(id, int32(stack.StackState_Running)))
machine.CreateState(id, stack.StackState_Starting.String())
assert.NoError(t, machine.TransitionTo(id, stack.StackState_Running.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Starting))
assert.Error(t, machine.TransitionTo(id, int32(stack.StackState_Redeploying)))
machine.CreateState(id, stack.StackState_Starting.String())
assert.Error(t, machine.TransitionTo(id, stack.StackState_Redeploying.String()))
machine.DeleteState(id)
}

func TestTransitionsFromRunning(t *testing.T) {
machine := state.NewMachine(stack.StackRuleSet, runtime.Store)
id := stringid.GenerateNonCryptoID()

machine.CreateState(id, int32(stack.StackState_Running))
assert.NoError(t, machine.TransitionTo(id, int32(stack.StackState_Stopped)))
machine.CreateState(id, stack.StackState_Running.String())
assert.NoError(t, machine.TransitionTo(id, stack.StackState_Stopped.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Running))
assert.Error(t, machine.TransitionTo(id, int32(stack.StackState_Starting)))
machine.CreateState(id, stack.StackState_Running.String())
assert.Error(t, machine.TransitionTo(id, stack.StackState_Starting.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Running))
assert.Error(t, machine.TransitionTo(id, int32(stack.StackState_Running)))
machine.CreateState(id, stack.StackState_Running.String())
assert.Error(t, machine.TransitionTo(id, stack.StackState_Running.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Running))
assert.NoError(t, machine.TransitionTo(id, int32(stack.StackState_Redeploying)))
machine.CreateState(id, stack.StackState_Running.String())
assert.NoError(t, machine.TransitionTo(id, stack.StackState_Redeploying.String()))
machine.DeleteState(id)
}

func TestTransitionsFromRedeploying(t *testing.T) {
machine := state.NewMachine(stack.StackRuleSet, runtime.Store)
id := stringid.GenerateNonCryptoID()

machine.CreateState(id, int32(stack.StackState_Redeploying))
assert.NoError(t, machine.TransitionTo(id, int32(stack.StackState_Stopped)))
machine.CreateState(id, stack.StackState_Redeploying.String())
assert.NoError(t, machine.TransitionTo(id, stack.StackState_Stopped.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Redeploying))
assert.NoError(t, machine.TransitionTo(id, int32(stack.StackState_Starting)))
machine.CreateState(id, stack.StackState_Redeploying.String())
assert.NoError(t, machine.TransitionTo(id, stack.StackState_Starting.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Redeploying))
assert.Error(t, machine.TransitionTo(id, int32(stack.StackState_Running)))
machine.CreateState(id, stack.StackState_Redeploying.String())
assert.Error(t, machine.TransitionTo(id, stack.StackState_Running.String()))
machine.DeleteState(id)

machine.CreateState(id, int32(stack.StackState_Redeploying))
assert.Error(t, machine.TransitionTo(id, int32(stack.StackState_Redeploying)))
machine.CreateState(id, stack.StackState_Redeploying.String())
assert.Error(t, machine.TransitionTo(id, stack.StackState_Redeploying.String()))
machine.DeleteState(id)
}
18 changes: 9 additions & 9 deletions api/state/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
const statesRootKey = "states"

// RuleSet describe allowed state transitions
type RuleSet [][]bool
type RuleSet map[string]map[string]bool

// Machine StateMachine is the state machine
type Machine struct {
Expand All @@ -25,38 +25,38 @@ func NewMachine(ruleSet RuleSet, store storage.Interface) Machine {
}

// canTransition return whether or not you can transition between states
func (s *Machine) canTransition(from int32, to int32) bool {
func (s *Machine) canTransition(from string, to string) bool {
return s.ruleSet[from][to]
}

// GetState get state
func (s *Machine) GetState(id string) (int32, error) {
func (s *Machine) GetState(id string) (string, error) {
state := &State{}
if err := runtime.Store.Get(context.Background(), path.Join(statesRootKey, id), state, true); err != nil {
return -1, err
return "", err
}
return state.Value, nil
}

// TransitionTo transitionTo
func (s *Machine) TransitionTo(id string, to int32) error {
func (s *Machine) TransitionTo(id string, to string) error {
current, err := s.GetState(id)
if err != nil {
return err
}
if !s.canTransition(current, to) {
return fmt.Errorf("Cannot transition to state %v", to)
return fmt.Errorf("Cannot transition from state %s to state %s", current, to)
}
expect := &State{Value: current}
update := &State{Value: to}
if err = runtime.Store.CompareAndSet(context.Background(), path.Join(statesRootKey, id), expect, update); err != nil {
return err
return fmt.Errorf("Cannot transition from state %s to state %s", current, to)
}
return nil
}

// Is is
func (s *Machine) Is(id string, expected int32) (bool, error) {
func (s *Machine) Is(id string, expected string) (bool, error) {
state, err := s.GetState(id)
if err != nil {
return false, err
Expand All @@ -65,7 +65,7 @@ func (s *Machine) Is(id string, expected int32) (bool, error) {
}

// CreateState createstate
func (s *Machine) CreateState(id string, initial int32) error {
func (s *Machine) CreateState(id string, initial string) error {
state := &State{Value: initial}
if err := runtime.Store.Create(context.Background(), path.Join(statesRootKey, id), state, nil, 0); err != nil {
return err
Expand Down
6 changes: 3 additions & 3 deletions api/state/state.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/state/state.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ syntax = "proto3";
package state;

message State {
int32 value = 1;
string value = 1;
}
8 changes: 4 additions & 4 deletions data/storage/etcd/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ func TestCompareAndSet(t *testing.T) {
defer cancel()

key := "state"
expect := &state.State{Value: 0}
update := &state.State{Value: 42}
expect := &state.State{Value: "hello"}
update := &state.State{Value: "world"}

store.Delete(ctx, key, false, &state.State{})
store.Create(ctx, key, expect, nil, 0)
Expand All @@ -261,8 +261,8 @@ func TestWatch(t *testing.T) {
defer cancel()

key := "watch"
expect := &state.State{Value: 0}
update := &state.State{Value: 42}
expect := &state.State{Value: "hello"}
update := &state.State{Value: "world"}

watch, err := store.Watch(ctx, key, 0, storage.Everything)
if err != nil {
Expand Down