Permalink
Browse files

Persist gonit states.

1) Fill out 'gonit reload' command that reloads configs.
   The API user is responsible for stopping/starting processes
   before/after 'gonit reload'. A config will only be reloaded if
   it is a valid config.
2) Persist ProcessState structs after start/monitor/unmonitor.
3) Load persisted state before starting a process.

Change-Id: Iaac0b81eda1ccac127059f8633a65a9b9eeac526
  • Loading branch information...
1 parent 5c3a59e commit 670884098c19aecc52f9dea144791ee995dc384d @lisbakke lisbakke committed Sep 25, 2012
Showing with 226 additions and 70 deletions.
  1. +19 −3 api.go
  2. +6 −6 cli_test.go
  3. +44 −18 configmanager.go
  4. +21 −18 configmanager_test.go
  5. +67 −19 control.go
  6. +55 −3 control_test.go
  7. +4 −2 eventmonitor.go
  8. +6 −1 gonit/main.go
  9. +4 −0 test/config/expected_persist_file.yml
View
22 api.go
@@ -6,6 +6,7 @@ import (
"errors"
"sort"
"github.com/cloudfoundry/gosigar"
+ "log"
)
// until stubs are implemented
@@ -18,7 +19,7 @@ type API struct {
type ProcessSummary struct {
Name string
Running bool
- ControlState processState
+ ControlState ProcessState
}
type ProcessStatus struct {
@@ -89,7 +90,7 @@ func (e *ActionError) Error() string {
func NewAPI(config *ConfigManager) *API {
return &API{
- Control: &Control{configManager: config},
+ Control: &Control{ConfigManager: config},
}
}
@@ -287,7 +288,22 @@ func (a *API) About(unused interface{}, about *About) error {
// reload server configuration
func (a *API) Reload(unused interface{}, r *ActionResult) error {
- return notimpl
+ log.Printf("Starting config reload.")
+ control := a.Control
+ path := control.ConfigManager.path
+ newConfigManager := ConfigManager{}
+ if err := newConfigManager.LoadConfig(path); err != nil {
+ return err
+ }
+ control.EventMonitor.Stop()
+ // TODO: Do we have a concurrency issue with this?
+ *control.ConfigManager = newConfigManager
+ if err := control.EventMonitor.Start(control.ConfigManager,
+ control); err != nil {
+ return err
+ }
+ log.Printf("Finished config reload.")
+ return nil
}
// quit server daemon
View
@@ -46,27 +46,27 @@ func (m *MockAPI) About(args interface{}, about *About) error {
// run tests via RPC or local reflection
func runTests(t *testing.T, client CliClient) {
method, name := RpcArgs("stop", "foo", false)
- reply, err := client.Call(method, name)
+ _, err := client.Call(method, name)
assert.Equal(t, nil, err)
method, name = RpcArgs("stop", "bar", false)
- reply, err = client.Call(method, name)
+ _, err = client.Call(method, name)
assert.NotEqual(t, nil, err)
method, name = RpcArgs("unmonitor", "all", false)
- reply, err = client.Call(method, name)
+ _, err = client.Call(method, name)
assert.Equal(t, nil, err)
method, name = RpcArgs("start", "vcap", true)
- reply, err = client.Call(method, name)
+ _, err = client.Call(method, name)
assert.Equal(t, nil, err)
method, name = RpcArgs("start", "bar", true)
- reply, err = client.Call(method, name)
+ _, err = client.Call(method, name)
assert.NotEqual(t, nil, err)
method, name = RpcArgs("about", "", false)
- reply, err = client.Call(method, name)
+ reply, err := client.Call(method, name)
assert.Equal(t, nil, err)
about, ok := reply.(*About)
View
@@ -21,6 +21,7 @@ import (
type ConfigManager struct {
ProcessGroups map[string]*ProcessGroup
Settings *Settings
+ path string
}
type Settings struct {
@@ -29,6 +30,7 @@ type Settings struct {
RpcServerUrl string
PollInterval int
Daemon *Process
+ PersistFile string
}
type ProcessGroup struct {
@@ -84,8 +86,8 @@ const (
// Given an action string name, returns the events associated with it.
func (pg *ProcessGroup) EventByName(eventName string) *Event {
- event, has_key := pg.Events[eventName]
- if has_key {
+ event, hasKey := pg.Events[eventName]
+ if hasKey {
return event
}
return nil
@@ -197,6 +199,24 @@ func (c *ConfigManager) ApplyDefaultSettings() {
defaultPath := "." + daemon.Name + ".sock"
settings.RpcServerUrl = filepath.Join(daemon.Dir, defaultPath)
}
+
+ if settings.PersistFile == "" {
+ settings.PersistFile = filepath.Join(daemon.Dir, ".gonit.persist.yml")
+ }
+}
+
+func (s *Settings) validatePersistFile() error {
+ _, err := os.Stat(s.PersistFile)
+ if err != nil {
+ // The file doesn't exist. See if we can create it.
+ if file, err := os.Create(s.PersistFile); err != nil {
+ return err
+ } else {
+ file.Close()
+ os.Remove(s.PersistFile)
+ }
+ }
+ return nil
}
// Parses a file.
@@ -218,26 +238,29 @@ func (c *ConfigManager) parseFile(path string) error {
}
// Main function to call, parses a path for gonit config file(s).
-func (c *ConfigManager) Parse(paths ...string) error {
+func (c *ConfigManager) LoadConfig(path string) error {
+ c.path = path
+ if path == "" {
+ return fmt.Errorf("No config given.")
+ }
+
c.ProcessGroups = map[string]*ProcessGroup{}
- for _, path := range paths {
- fileInfo, err := os.Stat(path)
- if err != nil {
- return fmt.Errorf("Error stating path '%+v'.\n", path)
+ c.Settings = &Settings{}
+ fileInfo, err := os.Stat(path)
+ if err != nil {
+ return fmt.Errorf("Error stating path '%+v'.", path)
+ }
+ if fileInfo.IsDir() {
+ if err = c.parseDir(path); err != nil {
+ return err
}
- if fileInfo.IsDir() {
- if err = c.parseDir(path); err != nil {
- return err
- }
- } else {
- if err := c.parseFile(path); err != nil {
- return err
- }
+ } else {
+ if err := c.parseFile(path); err != nil {
+ return err
}
- c.fillInNames()
}
-
- if c.Settings == nil {
+ c.fillInNames()
+ if (*c.Settings == Settings{}) {
log.Printf("No settings found, using defaults.")
}
c.ApplyDefaultSettings()
@@ -301,6 +324,9 @@ func (s *Settings) validate() error {
return fmt.Errorf("Settings uses '%v' alerts transport, but has no socket"+
" file.", UNIX_SOCKET_TRANSPORT)
}
+ if err := s.validatePersistFile(); err != nil {
+ return err
+ }
return nil
}
View
@@ -53,38 +53,28 @@ func TestGetPid(t *testing.T) {
}
func TestParseDir(t *testing.T) {
- configManager := ConfigManager{}
- err := configManager.Parse("test/config/")
+ configManager := &ConfigManager{}
+ err := configManager.LoadConfig("test/config/")
if err != nil {
t.Fatal(err)
}
- assertFileParsed(t, &configManager)
-}
-
-func TestParseFileList(t *testing.T) {
- configManager := ConfigManager{}
- err := configManager.Parse("test/config/dashboard-gonit.yml",
- "test/config/gonit.yml")
- if err != nil {
- t.Fatal(err)
- }
- assertFileParsed(t, &configManager)
+ assertFileParsed(t, configManager)
}
func TestNoSettingsLoadsDefaults(t *testing.T) {
- configManager := ConfigManager{}
- err := configManager.Parse("test/config/dashboard-gonit.yml")
+ configManager := &ConfigManager{}
+ err := configManager.LoadConfig("test/config/dashboard-gonit.yml")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "none", configManager.Settings.AlertTransport)
}
func TestLoadBadDir(t *testing.T) {
- configManager := ConfigManager{}
- err := configManager.Parse("Bad/Dir")
+ configManager := &ConfigManager{}
+ err := configManager.LoadConfig("Bad/Dir")
assert.NotEqual(t, nil, err)
- assert.Equal(t, "Error stating path 'Bad/Dir'.\n", err.Error())
+ assert.Equal(t, "Error stating path 'Bad/Dir'.", err.Error())
}
func TestRequiredFieldsExist(t *testing.T) {
@@ -134,3 +124,16 @@ func TestRequiredFieldsExist(t *testing.T) {
err = pg.validateRequiredFieldsExist()
assert.Equal(t, nil, nil)
}
+
+func TestValidatePersistErr(t *testing.T) {
+ s := &Settings{PersistFile: "/does/not/exist"}
+ err := s.validatePersistFile()
+ assert.NotEqual(t, nil, err)
+}
+
+func TestValidatePersistGood(t *testing.T) {
+ persistFile := os.Getenv("PWD") + "/test/config/expected_persist_file.yml"
+ s := &Settings{PersistFile: persistFile}
+ err := s.validatePersistFile()
+ assert.Equal(t, nil, err)
+}
Oops, something went wrong.

0 comments on commit 6708840

Please sign in to comment.