diff --git a/api.go b/api.go index ebc11bc..2c948b6 100644 --- a/api.go +++ b/api.go @@ -32,7 +32,7 @@ func main() { task.NewCheckPorts(e, f), task.NewCheckSwarmPorts(e, f), task.NewCheckSwarmStatus(e, f), - task.NewCollectStats(e, f), + task.NewCollectStats(e, f, s), } sch, err := scheduler.NewScheduler(tasks, s, e, core) if err != nil { diff --git a/docker/factory.go b/docker/factory.go index 8294648..f230d51 100644 --- a/docker/factory.go +++ b/docker/factory.go @@ -16,7 +16,7 @@ import ( ) type FactoryApi interface { - GetForSession(sessionId string) (DockerApi, error) + GetForSession(session *types.Session) (DockerApi, error) GetForInstance(instance *types.Instance) (DockerApi, error) } diff --git a/docker/factory_mock.go b/docker/factory_mock.go index 934c081..69c3c07 100644 --- a/docker/factory_mock.go +++ b/docker/factory_mock.go @@ -9,8 +9,8 @@ type FactoryMock struct { mock.Mock } -func (m *FactoryMock) GetForSession(sessionId string) (DockerApi, error) { - args := m.Called(sessionId) +func (m *FactoryMock) GetForSession(session *types.Session) (DockerApi, error) { + args := m.Called(session) return args.Get(0).(DockerApi), args.Error(1) } diff --git a/docker/local_cached_factory.go b/docker/local_cached_factory.go index 9fa407c..89bcde6 100644 --- a/docker/local_cached_factory.go +++ b/docker/local_cached_factory.go @@ -25,7 +25,7 @@ type instanceEntry struct { client DockerApi } -func (f *localCachedFactory) GetForSession(sessionId string) (DockerApi, error) { +func (f *localCachedFactory) GetForSession(session *types.Session) (DockerApi, error) { f.rw.Lock() defer f.rw.Unlock() diff --git a/provisioner/dind.go b/provisioner/dind.go index 1813e43..40d6159 100644 --- a/provisioner/dind.go +++ b/provisioner/dind.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" + lru "github.com/hashicorp/golang-lru" "github.com/play-with-docker/play-with-docker/config" "github.com/play-with-docker/play-with-docker/docker" "github.com/play-with-docker/play-with-docker/id" @@ -22,10 +23,12 @@ type DinD struct { factory docker.FactoryApi storage storage.StorageApi generator id.Generator + cache *lru.Cache } func NewDinD(generator id.Generator, f docker.FactoryApi, s storage.StorageApi) *DinD { - return &DinD{generator: generator, factory: f, storage: s} + c, _ := lru.New(5000) + return &DinD{generator: generator, factory: f, storage: s, cache: c} } func checkHostnameExists(sessionId, hostname string, instances []*types.Instance) bool { @@ -73,7 +76,7 @@ func (d *DinD) InstanceNew(session *types.Session, conf types.InstanceConfig) (* Networks: []string{session.Id}, } - dockerClient, err := d.factory.GetForSession(session.Id) + dockerClient, err := d.factory.GetForSession(session) if err != nil { return nil, err } @@ -105,8 +108,23 @@ func (d *DinD) InstanceNew(session *types.Session, conf types.InstanceConfig) (* return instance, nil } +func (d *DinD) getSession(sessionId string) (*types.Session, error) { + var session *types.Session + if s, found := d.cache.Get(sessionId); !found { + s, err := d.storage.SessionGet(sessionId) + if err != nil { + return nil, err + } + session = s + d.cache.Add(sessionId, s) + } else { + session = s.(*types.Session) + } + return session, nil +} + func (d *DinD) InstanceDelete(session *types.Session, instance *types.Instance) error { - dockerClient, err := d.factory.GetForSession(session.Id) + dockerClient, err := d.factory.GetForSession(session) if err != nil { return err } @@ -118,7 +136,11 @@ func (d *DinD) InstanceDelete(session *types.Session, instance *types.Instance) } func (d *DinD) InstanceExec(instance *types.Instance, cmd []string) (int, error) { - dockerClient, err := d.factory.GetForSession(instance.SessionId) + session, err := d.getSession(instance.SessionId) + if err != nil { + return -1, err + } + dockerClient, err := d.factory.GetForSession(session) if err != nil { return -1, err } @@ -126,7 +148,11 @@ func (d *DinD) InstanceExec(instance *types.Instance, cmd []string) (int, error) } func (d *DinD) InstanceResizeTerminal(instance *types.Instance, rows, cols uint) error { - dockerClient, err := d.factory.GetForSession(instance.SessionId) + session, err := d.getSession(instance.SessionId) + if err != nil { + return err + } + dockerClient, err := d.factory.GetForSession(session) if err != nil { return err } @@ -134,7 +160,11 @@ func (d *DinD) InstanceResizeTerminal(instance *types.Instance, rows, cols uint) } func (d *DinD) InstanceGetTerminal(instance *types.Instance) (net.Conn, error) { - dockerClient, err := d.factory.GetForSession(instance.SessionId) + session, err := d.getSession(instance.SessionId) + if err != nil { + return nil, err + } + dockerClient, err := d.factory.GetForSession(session) if err != nil { return nil, err } @@ -151,7 +181,11 @@ func (d *DinD) InstanceUploadFromUrl(instance *types.Instance, fileName, dest, u if resp.StatusCode != 200 { return fmt.Errorf("Could not download file [%s]. Status code: %d\n", url, resp.StatusCode) } - dockerClient, err := d.factory.GetForSession(instance.SessionId) + session, err := d.getSession(instance.SessionId) + if err != nil { + return err + } + dockerClient, err := d.factory.GetForSession(session) if err != nil { return err } @@ -166,7 +200,11 @@ func (d *DinD) InstanceUploadFromUrl(instance *types.Instance, fileName, dest, u } func (d *DinD) getInstanceCWD(instance *types.Instance) (string, error) { - dockerClient, err := d.factory.GetForSession(instance.SessionId) + session, err := d.getSession(instance.SessionId) + if err != nil { + return "", err + } + dockerClient, err := d.factory.GetForSession(session) if err != nil { return "", err } @@ -184,7 +222,11 @@ func (d *DinD) getInstanceCWD(instance *types.Instance) (string, error) { } func (d *DinD) InstanceUploadFromReader(instance *types.Instance, fileName, dest string, reader io.Reader) error { - dockerClient, err := d.factory.GetForSession(instance.SessionId) + session, err := d.getSession(instance.SessionId) + if err != nil { + return err + } + dockerClient, err := d.factory.GetForSession(session) if err != nil { return err } diff --git a/provisioner/overlay.go b/provisioner/overlay.go index 4e39659..23d415e 100644 --- a/provisioner/overlay.go +++ b/provisioner/overlay.go @@ -21,7 +21,7 @@ func NewOverlaySessionProvisioner(df docker.FactoryApi) SessionProvisionerApi { } func (p *overlaySessionProvisioner) SessionNew(s *types.Session) error { - dockerClient, err := p.dockerFactory.GetForSession(s.Id) + dockerClient, err := p.dockerFactory.GetForSession(s) if err != nil { // We assume we are out of capacity return fmt.Errorf("Out of capacity") @@ -52,7 +52,7 @@ func (p *overlaySessionProvisioner) SessionNew(s *types.Session) error { } func (p *overlaySessionProvisioner) SessionClose(s *types.Session) error { // Disconnect L2 router from the network - dockerClient, err := p.dockerFactory.GetForSession(s.Id) + dockerClient, err := p.dockerFactory.GetForSession(s) if err != nil { log.Println(err) return err diff --git a/provisioner/windows.go b/provisioner/windows.go index 6450cdb..a533e3e 100644 --- a/provisioner/windows.go +++ b/provisioner/windows.go @@ -62,7 +62,7 @@ func (d *windows) InstanceNew(session *types.Session, conf types.InstanceConfig) } instanceName := fmt.Sprintf("%s_%s", session.Id[:8], winfo.id) - dockerClient, err := d.factory.GetForSession(session.Id) + dockerClient, err := d.factory.GetForSession(session) if err != nil { d.releaseInstance(winfo.id) return nil, err @@ -94,7 +94,7 @@ func (d *windows) InstanceNew(session *types.Session, conf types.InstanceConfig) } func (d *windows) InstanceDelete(session *types.Session, instance *types.Instance) error { - dockerClient, err := d.factory.GetForSession(session.Id) + dockerClient, err := d.factory.GetForSession(session) if err != nil { return err } diff --git a/pwd/client_test.go b/pwd/client_test.go index f39af24..f661567 100644 --- a/pwd/client_test.go +++ b/pwd/client_test.go @@ -27,7 +27,7 @@ func TestClientNew(t *testing.T) { sp := provisioner.NewOverlaySessionProvisioner(_f) _g.On("NewId").Return("aaaabbbbcccc") - _f.On("GetForSession", "aaaabbbbcccc").Return(_d, nil) + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) _d.On("CreateNetwork", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) _d.On("GetDaemonHost").Return("localhost") _d.On("ConnectNetwork", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) @@ -67,7 +67,7 @@ func TestClientCount(t *testing.T) { sp := provisioner.NewOverlaySessionProvisioner(_f) _g.On("NewId").Return("aaaabbbbcccc") - _f.On("GetForSession", "aaaabbbbcccc").Return(_d, nil) + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) _d.On("CreateNetwork", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) _d.On("GetDaemonHost").Return("localhost") _d.On("ConnectNetwork", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) @@ -106,7 +106,7 @@ func TestClientResizeViewPort(t *testing.T) { sp := provisioner.NewOverlaySessionProvisioner(_f) _g.On("NewId").Return("aaaabbbbcccc") - _f.On("GetForSession", "aaaabbbbcccc").Return(_d, nil) + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) _d.On("CreateNetwork", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) _d.On("GetDaemonHost").Return("localhost") _d.On("ConnectNetwork", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) diff --git a/pwd/instance_test.go b/pwd/instance_test.go index 37b3a8a..3e848e6 100644 --- a/pwd/instance_test.go +++ b/pwd/instance_test.go @@ -27,8 +27,10 @@ func TestInstanceResizeTerminal(t *testing.T) { ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) sp := provisioner.NewOverlaySessionProvisioner(_f) + s := &types.Session{Id: "aaaabbbbcccc"} _d.On("ContainerResize", "foobar", uint(24), uint(80)).Return(nil) - _f.On("GetForSession", "aaaabbbbcccc").Return(_d, nil) + _s.On("SessionGet", "aaaabbbbcccc").Return(s, nil) + _f.On("GetForSession", s).Return(_d, nil) p := NewPWD(_f, _e, _s, sp, ipf) @@ -52,7 +54,7 @@ func TestInstanceNew(t *testing.T) { sp := provisioner.NewOverlaySessionProvisioner(_f) _g.On("NewId").Return("aaaabbbbcccc") - _f.On("GetForSession", "aaaabbbbcccc").Return(_d, nil) + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) _d.On("CreateNetwork", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) _d.On("GetDaemonHost").Return("localhost") _d.On("ConnectNetwork", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) @@ -120,7 +122,7 @@ func TestInstanceNew_WithNotAllowedImage(t *testing.T) { sp := provisioner.NewOverlaySessionProvisioner(_f) _g.On("NewId").Return("aaaabbbbcccc") - _f.On("GetForSession", "aaaabbbbcccc").Return(_d, nil) + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) _d.On("CreateNetwork", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) _d.On("GetDaemonHost").Return("localhost") _d.On("ConnectNetwork", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) @@ -189,7 +191,7 @@ func TestInstanceNew_WithCustomHostname(t *testing.T) { sp := provisioner.NewOverlaySessionProvisioner(_f) _g.On("NewId").Return("aaaabbbbcccc") - _f.On("GetForSession", "aaaabbbbcccc").Return(_d, nil) + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) _d.On("CreateNetwork", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) _d.On("GetDaemonHost").Return("localhost") _d.On("ConnectNetwork", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) diff --git a/pwd/session.go b/pwd/session.go index f02dfea..a4c8d7a 100644 --- a/pwd/session.go +++ b/pwd/session.go @@ -181,7 +181,7 @@ func (p *pwd) SessionDeployStack(s *types.Session) error { w := sessionBuilderWriter{sessionId: s.Id, event: p.event} - dockerClient, err := p.dockerFactory.GetForSession(s.Id) + dockerClient, err := p.dockerFactory.GetForSession(s) if err != nil { log.Println(err) return err diff --git a/pwd/session_test.go b/pwd/session_test.go index b49c04e..c3bc528 100644 --- a/pwd/session_test.go +++ b/pwd/session_test.go @@ -28,7 +28,7 @@ func TestSessionNew(t *testing.T) { sp := provisioner.NewOverlaySessionProvisioner(_f) _g.On("NewId").Return("aaaabbbbcccc") - _f.On("GetForSession", "aaaabbbbcccc").Return(_d, nil) + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) _d.On("CreateNetwork", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) _d.On("GetDaemonHost").Return("localhost") _d.On("ConnectNetwork", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) diff --git a/scheduler/task/check_swarm_ports_test.go b/scheduler/task/check_swarm_ports_test.go index 62963ce..52bf716 100644 --- a/scheduler/task/check_swarm_ports_test.go +++ b/scheduler/task/check_swarm_ports_test.go @@ -32,6 +32,7 @@ func TestCheckSwarmPorts_RunWhenManager(t *testing.T) { IP: "10.0.0.1", Name: "aaaabbbb_node1", SessionId: "aaaabbbbcccc", + Hostname: "node1", } info := dockerTypes.Info{ Swarm: swarm.Info{ @@ -43,7 +44,7 @@ func TestCheckSwarmPorts_RunWhenManager(t *testing.T) { f.On("GetForInstance", i).Return(d, nil) d.On("GetDaemonInfo").Return(info, nil) d.On("GetSwarmPorts").Return([]string{"node1", "node2"}, []uint16{8080, 9090}, nil) - e.M.On("Emit", CheckSwarmPortsEvent, "aaaabbbbcccc", []interface{}{DockerSwarmPorts{Manager: i.Name, Instances: []string{i.Name, "aaaabbbb_node2"}, Ports: []int{8080, 9090}}}).Return() + e.M.On("Emit", CheckSwarmPortsEvent, "aaaabbbbcccc", []interface{}{DockerSwarmPorts{Manager: i.Name, Instances: []string{i.Hostname, "node2"}, Ports: []int{8080, 9090}}}).Return() task := NewCheckSwarmPorts(e, f) ctx := context.Background() diff --git a/scheduler/task/collect_stats.go b/scheduler/task/collect_stats.go index 993c6ef..4fb3249 100644 --- a/scheduler/task/collect_stats.go +++ b/scheduler/task/collect_stats.go @@ -12,10 +12,12 @@ import ( dockerTypes "github.com/docker/docker/api/types" units "github.com/docker/go-units" + lru "github.com/hashicorp/golang-lru" "github.com/play-with-docker/play-with-docker/docker" "github.com/play-with-docker/play-with-docker/event" "github.com/play-with-docker/play-with-docker/pwd/types" "github.com/play-with-docker/play-with-docker/router" + "github.com/play-with-docker/play-with-docker/storage" ) type InstanceStats struct { @@ -28,6 +30,8 @@ type collectStats struct { event event.EventApi factory docker.FactoryApi cli *http.Client + cache *lru.Cache + storage storage.StorageApi } var CollectStatsEvent event.EventType @@ -71,7 +75,18 @@ func (t *collectStats) Run(ctx context.Context, instance *types.Instance) error t.event.Emit(CollectStatsEvent, instance.SessionId, stats) return nil } - dockerClient, err := t.factory.GetForSession(instance.SessionId) + var session *types.Session + if sess, found := t.cache.Get(instance.SessionId); !found { + s, err := t.storage.SessionGet(instance.SessionId) + if err != nil { + return err + } + t.cache.Add(s.Id, s) + session = s + } else { + session = sess.(*types.Session) + } + dockerClient, err := t.factory.GetForSession(session) if err != nil { log.Println(err) return err @@ -119,7 +134,7 @@ func proxyHost(r *http.Request) (*url.URL, error) { return u, nil } -func NewCollectStats(e event.EventApi, f docker.FactoryApi) *collectStats { +func NewCollectStats(e event.EventApi, f docker.FactoryApi, s storage.StorageApi) *collectStats { transport := &http.Transport{ DialContext: (&net.Dialer{ Timeout: 1 * time.Second, @@ -131,7 +146,8 @@ func NewCollectStats(e event.EventApi, f docker.FactoryApi) *collectStats { cli := &http.Client{ Transport: transport, } - return &collectStats{event: e, factory: f, cli: cli} + c, _ := lru.New(5000) + return &collectStats{event: e, factory: f, cli: cli, cache: c, storage: s} } func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *dockerTypes.StatsJSON) float64 { diff --git a/scheduler/task/collect_stats_test.go b/scheduler/task/collect_stats_test.go index ccbd188..443a1b6 100644 --- a/scheduler/task/collect_stats_test.go +++ b/scheduler/task/collect_stats_test.go @@ -11,6 +11,7 @@ import ( "github.com/play-with-docker/play-with-docker/docker" "github.com/play-with-docker/play-with-docker/event" "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -19,8 +20,8 @@ type mockSessionProvider struct { mock.Mock } -func (m *mockSessionProvider) GetDocker(sessionId string) (docker.DockerApi, error) { - args := m.Called(sessionId) +func (m *mockSessionProvider) GetDocker(session *types.Session) (docker.DockerApi, error) { + args := m.Called(session) return args.Get(0).(docker.DockerApi), args.Error(1) } @@ -34,8 +35,9 @@ func (nopCloser) Close() error { return nil } func TestCollectStats_Name(t *testing.T) { e := &event.Mock{} f := &docker.FactoryMock{} + s := &storage.Mock{} - task := NewCollectStats(e, f) + task := NewCollectStats(e, f, s) assert.Equal(t, "CollectStats", task.Name()) e.M.AssertExpectations(t) @@ -46,6 +48,7 @@ func TestCollectStats_Run(t *testing.T) { d := &docker.Mock{} e := &event.Mock{} f := &docker.FactoryMock{} + s := &storage.Mock{} stats := dockerTypes.StatsJSON{} b, _ := json.Marshal(stats) @@ -53,13 +56,19 @@ func TestCollectStats_Run(t *testing.T) { IP: "10.0.0.1", Name: "aaaabbbb_node1", SessionId: "aaaabbbbcccc", + Hostname: "node1", } - f.On("GetForSession", i.SessionId).Return(d, nil) + sess := &types.Session{ + Id: "aaaabbbbcccc", + } + + s.On("SessionGet", i.SessionId).Return(sess, nil) + f.On("GetForSession", sess).Return(d, nil) d.On("GetContainerStats", i.Name).Return(nopCloser{bytes.NewReader(b)}, nil) e.M.On("Emit", CollectStatsEvent, "aaaabbbbcccc", []interface{}{InstanceStats{Instance: i.Name, Mem: "0.00% (0B / 0B)", Cpu: "0.00%"}}).Return() - task := NewCollectStats(e, f) + task := NewCollectStats(e, f, s) ctx := context.Background() err := task.Run(ctx, i)