mirror of https://github.com/docker/docs.git
Merge pull request #1180 from abronan/clean_state
Cleanup state folder with local file persistence
This commit is contained in:
commit
2fb43773cf
|
@ -20,7 +20,6 @@ import (
|
|||
"github.com/docker/swarm/scheduler"
|
||||
"github.com/docker/swarm/scheduler/filter"
|
||||
"github.com/docker/swarm/scheduler/strategy"
|
||||
"github.com/docker/swarm/state"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
|
@ -219,11 +218,6 @@ func manage(c *cli.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
store := state.NewStore(path.Join(c.String("rootdir"), "state"))
|
||||
if err := store.Initialize(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
uri := getDiscovery(c)
|
||||
if uri == "" {
|
||||
log.Fatalf("discovery required to manage a cluster. See '%s manage --help'.", c.App.Name)
|
||||
|
@ -249,9 +243,9 @@ func manage(c *cli.Context) {
|
|||
switch c.String("cluster-driver") {
|
||||
case "mesos-experimental":
|
||||
log.Warn("WARNING: the mesos driver is currently experimental, use at your own risks")
|
||||
cl, err = mesos.NewCluster(sched, store, tlsConfig, uri, c.StringSlice("cluster-opt"))
|
||||
cl, err = mesos.NewCluster(sched, tlsConfig, uri, c.StringSlice("cluster-opt"))
|
||||
case "swarm":
|
||||
cl, err = swarm.NewCluster(sched, store, tlsConfig, discovery, c.StringSlice("cluster-opt"))
|
||||
cl, err = swarm.NewCluster(sched, tlsConfig, discovery, c.StringSlice("cluster-opt"))
|
||||
default:
|
||||
log.Fatalf("unsupported cluster %q", c.String("cluster-driver"))
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import (
|
|||
"github.com/docker/swarm/scheduler"
|
||||
"github.com/docker/swarm/scheduler/node"
|
||||
"github.com/docker/swarm/scheduler/strategy"
|
||||
"github.com/docker/swarm/state"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/mesos/mesos-go/mesosproto"
|
||||
mesosscheduler "github.com/mesos/mesos-go/scheduler"
|
||||
|
@ -34,7 +33,6 @@ type Cluster struct {
|
|||
master string
|
||||
slaves map[string]*slave
|
||||
scheduler *scheduler.Scheduler
|
||||
store *state.Store
|
||||
TLSConfig *tls.Config
|
||||
options *cluster.DriverOpts
|
||||
offerTimeout time.Duration
|
||||
|
@ -57,7 +55,7 @@ var (
|
|||
)
|
||||
|
||||
// NewCluster for mesos Cluster creation
|
||||
func NewCluster(scheduler *scheduler.Scheduler, store *state.Store, TLSConfig *tls.Config, master string, options cluster.DriverOpts) (cluster.Cluster, error) {
|
||||
func NewCluster(scheduler *scheduler.Scheduler, TLSConfig *tls.Config, master string, options cluster.DriverOpts) (cluster.Cluster, error) {
|
||||
log.WithFields(log.Fields{"name": "mesos"}).Debug("Initializing cluster")
|
||||
|
||||
cluster := &Cluster{
|
||||
|
@ -65,7 +63,6 @@ func NewCluster(scheduler *scheduler.Scheduler, store *state.Store, TLSConfig *t
|
|||
master: master,
|
||||
slaves: make(map[string]*slave),
|
||||
scheduler: scheduler,
|
||||
store: store,
|
||||
TLSConfig: TLSConfig,
|
||||
options: &options,
|
||||
offerTimeout: defaultOfferTimeout,
|
||||
|
|
|
@ -16,7 +16,6 @@ import (
|
|||
"github.com/docker/swarm/discovery"
|
||||
"github.com/docker/swarm/scheduler"
|
||||
"github.com/docker/swarm/scheduler/node"
|
||||
"github.com/docker/swarm/state"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
|
@ -27,7 +26,6 @@ type Cluster struct {
|
|||
eventHandler cluster.EventHandler
|
||||
engines map[string]*cluster.Engine
|
||||
scheduler *scheduler.Scheduler
|
||||
store *state.Store
|
||||
discovery discovery.Discovery
|
||||
|
||||
overcommitRatio float64
|
||||
|
@ -35,13 +33,12 @@ type Cluster struct {
|
|||
}
|
||||
|
||||
// NewCluster is exported
|
||||
func NewCluster(scheduler *scheduler.Scheduler, store *state.Store, TLSConfig *tls.Config, discovery discovery.Discovery, options cluster.DriverOpts) (cluster.Cluster, error) {
|
||||
func NewCluster(scheduler *scheduler.Scheduler, TLSConfig *tls.Config, discovery discovery.Discovery, options cluster.DriverOpts) (cluster.Cluster, error) {
|
||||
log.WithFields(log.Fields{"name": "swarm"}).Debug("Initializing cluster")
|
||||
|
||||
cluster := &Cluster{
|
||||
engines: make(map[string]*cluster.Engine),
|
||||
scheduler: scheduler,
|
||||
store: store,
|
||||
TLSConfig: TLSConfig,
|
||||
discovery: discovery,
|
||||
overcommitRatio: 0.05,
|
||||
|
@ -126,16 +123,7 @@ func (c *Cluster) createContainer(config *cluster.ContainerConfig, name string,
|
|||
|
||||
if nn, ok := c.engines[n.ID]; ok {
|
||||
container, err := nn.Create(config, name, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
st := &state.RequestedState{
|
||||
ID: container.Id,
|
||||
Name: name,
|
||||
Config: config,
|
||||
}
|
||||
return container, c.store.Add(container.Id, st)
|
||||
return container, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
@ -147,18 +135,8 @@ func (c *Cluster) RemoveContainer(container *cluster.Container, force bool) erro
|
|||
c.scheduler.Lock()
|
||||
defer c.scheduler.Unlock()
|
||||
|
||||
if err := container.Engine.RemoveContainer(container, force); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.store.Remove(container.Id); err != nil {
|
||||
if err == state.ErrNotFound {
|
||||
log.Debugf("Container %s not found in the store", container.Id)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
err := container.Engine.RemoveContainer(container, force)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Cluster) getEngineByAddr(addr string) *cluster.Engine {
|
||||
|
@ -586,17 +564,7 @@ func (c *Cluster) RenameContainer(container *cluster.Container, newName string)
|
|||
|
||||
// call engine rename
|
||||
err := container.Engine.RenameContainer(container, newName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update container name in store
|
||||
st, err := c.store.Get(container.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.Name = newName
|
||||
return c.store.Replace(container.Id, st)
|
||||
return err
|
||||
}
|
||||
|
||||
// BuildImage build an image
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package state
|
||||
|
||||
import "github.com/docker/swarm/cluster"
|
||||
|
||||
// RequestedState is exported
|
||||
type RequestedState struct {
|
||||
ID string
|
||||
Name string
|
||||
Config *cluster.ContainerConfig
|
||||
}
|
189
state/store.go
189
state/store.go
|
@ -1,189 +0,0 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotFound is exported
|
||||
ErrNotFound = errors.New("not found")
|
||||
// ErrAlreadyExists is exported
|
||||
ErrAlreadyExists = errors.New("already exists")
|
||||
// ErrInvalidKey is exported
|
||||
ErrInvalidKey = errors.New("invalid key")
|
||||
)
|
||||
|
||||
// Store is a simple key<->RequestedState store.
|
||||
type Store struct {
|
||||
RootDir string
|
||||
values map[string]*RequestedState
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewStore is exported
|
||||
func NewStore(rootdir string) *Store {
|
||||
return &Store{
|
||||
RootDir: rootdir,
|
||||
values: make(map[string]*RequestedState),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize must be called before performing any operation on the store. It
|
||||
// will attempt to restore the data from disk.
|
||||
func (s *Store) Initialize() error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if err := os.MkdirAll(s.RootDir, 0700); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.restore(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) path(key string) string {
|
||||
return path.Join(s.RootDir, key+".json")
|
||||
}
|
||||
|
||||
func (s *Store) restore() error {
|
||||
files, err := ioutil.ReadDir(s.RootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fileinfo := range files {
|
||||
file := fileinfo.Name()
|
||||
|
||||
// Verify the file extension.
|
||||
extension := filepath.Ext(file)
|
||||
if extension != ".json" {
|
||||
log.Errorf("invalid file extension for filename %s (%s)", file, extension)
|
||||
continue
|
||||
}
|
||||
|
||||
// Load the object back.
|
||||
value, err := s.load(path.Join(s.RootDir, file))
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract the key.
|
||||
key := file[0 : len(file)-len(extension)]
|
||||
if len(key) == 0 {
|
||||
log.Errorf("invalid filename %s", file)
|
||||
continue
|
||||
}
|
||||
|
||||
// Store it back.
|
||||
s.values[key] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) load(file string) (*RequestedState, error) {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load %s: %v", file, err)
|
||||
}
|
||||
value := &RequestedState{}
|
||||
if err := json.Unmarshal(data, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Get an object from the store keyed by `key`.
|
||||
func (s *Store) Get(key string) (*RequestedState, error) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
if value, ok := s.values[key]; ok {
|
||||
return value, nil
|
||||
}
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// All objects of the store are returned.
|
||||
func (s *Store) All() []*RequestedState {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
states := make([]*RequestedState, 0, len(s.values))
|
||||
for _, state := range s.values {
|
||||
states = append(states, state)
|
||||
}
|
||||
return states
|
||||
}
|
||||
|
||||
func (s *Store) set(key string, value *RequestedState) error {
|
||||
if len(key) == 0 {
|
||||
return ErrInvalidKey
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(value, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(s.path(key), data, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.values[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add a new object on the store. `key` must be unique.
|
||||
func (s *Store) Add(key string, value *RequestedState) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if _, exists := s.values[key]; exists {
|
||||
return ErrAlreadyExists
|
||||
}
|
||||
|
||||
return s.set(key, value)
|
||||
}
|
||||
|
||||
// Replace an already existing object from the store.
|
||||
func (s *Store) Replace(key string, value *RequestedState) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if _, exists := s.values[key]; !exists {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
return s.set(key, value)
|
||||
}
|
||||
|
||||
// Remove `key` from the store.
|
||||
func (s *Store) Remove(key string) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if _, exists := s.values[key]; !exists {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
if err := os.Remove(s.path(key)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(s.values, key)
|
||||
return nil
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "store-test")
|
||||
assert.NoError(t, err)
|
||||
store := NewStore(dir)
|
||||
assert.NoError(t, store.Initialize())
|
||||
|
||||
c1 := &RequestedState{}
|
||||
c1.Name = "foo"
|
||||
c2 := &RequestedState{}
|
||||
c2.Name = "bar"
|
||||
|
||||
var ret *RequestedState
|
||||
|
||||
// Add an invalid key
|
||||
assert.EqualError(t, store.Add("", c1), ErrInvalidKey.Error())
|
||||
|
||||
// Add "foo" into the store.
|
||||
assert.NoError(t, store.Add("foo", c1))
|
||||
|
||||
// Retrieve "foo" from the store.
|
||||
ret, err = store.Get("foo")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c1.Name, ret.Name)
|
||||
|
||||
// Try to add "foo" again.
|
||||
assert.EqualError(t, store.Add("foo", c1), ErrAlreadyExists.Error())
|
||||
|
||||
// Replace "foo" with c2.
|
||||
assert.NoError(t, store.Replace("foo", c2))
|
||||
ret, err = store.Get("foo")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c2.Name, ret.Name)
|
||||
|
||||
// Only one item in the store
|
||||
all := store.All()
|
||||
assert.Equal(t, 1, len(all))
|
||||
// The same name
|
||||
assert.Equal(t, c2.Name, all[0].Name)
|
||||
// It's actually the same pointer
|
||||
assert.Equal(t, c2, all[0])
|
||||
|
||||
// Initialize a brand new store and retrieve "foo" again.
|
||||
// This is to ensure data load on initialization works correctly.
|
||||
store = NewStore(dir)
|
||||
assert.NoError(t, store.Initialize())
|
||||
ret, err = store.Get("foo")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c2.Name, ret.Name)
|
||||
}
|
Loading…
Reference in New Issue