From e58d6462a9fed10e3752de6c6a1d212a9dc32ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20=C5=A0trauch?= Date: Sun, 6 Feb 2022 00:01:05 +0100 Subject: [PATCH] Massive handlers refactoring Taking logic from handler into glue module Add tests for apps Updated docker library --- apps/main.go | 13 +- apps/main_test.go | 189 +++++++++++ apps/snapshots.go | 5 +- containers/types.go | 6 +- glue/main.go | 652 +++++++++++++++++++++++++++++++++++++ stats.go => glue/stats.go | 10 +- glue/types.go | 15 + go.mod | 1 + go.sum | 3 +- handlers.go | 377 ++++++--------------- handlers_nats.go | 670 +++++++++++--------------------------- main.go | 26 +- tools.go | 45 --- types.go | 16 +- 14 files changed, 1173 insertions(+), 855 deletions(-) create mode 100644 apps/main_test.go create mode 100644 glue/main.go rename stats.go => glue/stats.go (94%) create mode 100644 glue/types.go diff --git a/apps/main.go b/apps/main.go index 0b52027..b0d08dc 100644 --- a/apps/main.go +++ b/apps/main.go @@ -7,6 +7,7 @@ import ( ) // AppsProcessor encapsulates functions for apps manipulation +// This handles only the database part but not containers type AppsProcessor struct { DB *gorm.DB } @@ -17,19 +18,19 @@ func (a *AppsProcessor) Init() { } // Get returns one app -func (a *AppsProcessor) Get(name string) (*App, error) { +func (a *AppsProcessor) Get(name string) (App, error) { var app App err := a.DB.Preload("Labels").Where("name = ?", name).First(&app).Error if err != nil { - return nil, err + return app, err } - return &app, nil + return app, nil } // List returns all apps located on this node -func (a *AppsProcessor) List() (*Apps, error) { +func (a *AppsProcessor) List() (Apps, error) { var apps Apps err := a.DB.Preload("Labels").Find(&apps).Error @@ -37,7 +38,7 @@ func (a *AppsProcessor) List() (*Apps, error) { return nil, err } - return &apps, nil + return apps, nil } // New creates new record about application in the database @@ -180,5 +181,5 @@ func (a *AppsProcessor) RemoveLabel(appName string, label string) error { return err } - return a.DB.Where("label = ? AND app_id = ?", label, app.ID).Delete(&Label{}).Error + return a.DB.Where("value = ? AND app_id = ?", label, app.ID).Delete(&Label{}).Error } diff --git a/apps/main_test.go b/apps/main_test.go new file mode 100644 index 0000000..c93e837 --- /dev/null +++ b/apps/main_test.go @@ -0,0 +1,189 @@ +package apps + +import ( + "log" + "testing" + + "github.com/jinzhu/gorm" + "github.com/rosti-cz/node-api/detector" + "github.com/stretchr/testify/assert" + + // This is line from GORM documentation that imports database dialect + _ "github.com/jinzhu/gorm/dialects/sqlite" +) + +const testDBPath = "file::memory:?cache=shared" + +func testDB() *gorm.DB { + db, err := gorm.Open("sqlite3", testDBPath) + if err != nil { + log.Fatalln(err) + } + + return db +} + +func TestAppsProcessorGet(t *testing.T) { + processor := AppsProcessor{ + DB: testDB(), + } + processor.Init() + + err := processor.New("testapp_1234", 1000, 1001, "testimage", 2, 256) + assert.Nil(t, err) + + app, err := processor.Get("testapp_1234") + assert.Nil(t, err) + assert.Greater(t, int(app.ID), 0) +} + +func TestAppsProcessorList(t *testing.T) { + processor := AppsProcessor{ + DB: testDB(), + } + processor.Init() + + err := processor.New("testapp_2234", 1002, 1003, "testimage", 2, 256) + assert.Nil(t, err) + + apps, err := processor.List() + assert.Nil(t, err) + assert.Equal(t, "testapp_1234", apps[0].Name) +} + +func TestAppsProcessorNew(t *testing.T) { + processor := AppsProcessor{ + DB: testDB(), + } + processor.Init() + + err := processor.New("testapp_1234", 1002, 1003, "testimage", 2, 256) + assert.Nil(t, err) +} + +func TestAppsProcessorUpdate(t *testing.T) { + processor := AppsProcessor{ + DB: testDB(), + } + processor.Init() + + err := processor.New("updateapp_1224", 1002, 1003, "testimage", 2, 256) + assert.Nil(t, err) + + _, err = processor.Update("updateapp_1224", 1052, 1053, "testimage2", 4, 512) + assert.Nil(t, err) + + app, err := processor.Get("updateapp_1224") + assert.Nil(t, err) + + assert.Equal(t, 1052, app.SSHPort) + assert.Equal(t, 1053, app.HTTPPort) + assert.Equal(t, "testimage2", app.Image) + assert.Equal(t, 4, app.CPU) + assert.Equal(t, 512, app.Memory) +} + +func TestAppsProcessorUpdateResources(t *testing.T) { + processor := AppsProcessor{ + DB: testDB(), + } + processor.Init() + + err := processor.New("updateresources_1224", 1002, 1003, "testimage", 2, 256) + assert.Nil(t, err) + + err = processor.UpdateResources("updateresources_1224", "running", 1000, 256, 100, 200, detector.Flags{"test"}) + assert.Nil(t, err) + + app, err := processor.Get("updateresources_1224") + assert.Nil(t, err) + + assert.Equal(t, "running", app.State) + assert.Equal(t, float64(1000), app.CPUUsage) + assert.Equal(t, 256, app.MemoryUsage) + assert.Equal(t, 100, app.DiskUsageBytes) + assert.Equal(t, 200, app.DiskUsageInodes) + assert.Contains(t, app.Flags, "test") +} + +func TestAppsProcessorUpdateState(t *testing.T) { + processor := AppsProcessor{ + DB: testDB(), + } + processor.Init() + + err := processor.New("update_1224", 1002, 1003, "testimage", 2, 256) + assert.Nil(t, err) + + err = processor.UpdateState("update_1224", "no-container") + assert.Nil(t, err) + + app, err := processor.Get("update_1224") + assert.Nil(t, err) + + assert.Equal(t, "no-container", app.State) +} + +func TestAppsProcessorAddLabel(t *testing.T) { + processor := AppsProcessor{ + DB: testDB(), + } + processor.Init() + + err := processor.New("label_1224", 1002, 1003, "testimage", 2, 256) + assert.Nil(t, err) + + err = processor.AddLabel("label_1224", "testlabel") + assert.Nil(t, err) + + app, err := processor.Get("label_1224") + assert.Nil(t, err) + + assert.Equal(t, "testlabel", app.Labels[0].Value) +} + +func TestAppsProcessorRemoveLabel(t *testing.T) { + processor := AppsProcessor{ + DB: testDB(), + } + processor.Init() + + err := processor.New("label_1223", 1002, 1003, "testimage", 2, 256) + assert.Nil(t, err) + + err = processor.AddLabel("label_1223", "testlabel") + assert.Nil(t, err) + + app, err := processor.Get("label_1223") + assert.Nil(t, err) + + assert.Equal(t, "testlabel", app.Labels[0].Value) + + err = processor.RemoveLabel("label_1223", "testlabel") + assert.Nil(t, err) + + app, err = processor.Get("label_1223") + assert.Nil(t, err) + + assert.Equal(t, 0, len(app.Labels)) +} + +func TestAppsProcessorDelete(t *testing.T) { + processor := AppsProcessor{ + DB: testDB(), + } + processor.Init() + + err := processor.New("testapp_5234", 1002, 1003, "testimage", 2, 256) + assert.Nil(t, err) + + app, err := processor.Get("testapp_5234") + assert.Nil(t, err) + assert.Equal(t, 256, app.Memory) + + err = processor.Delete("testapp_5234") + assert.Nil(t, err) + + _, err = processor.Get("testapp_5234") + assert.Error(t, err, "record not found") +} diff --git a/apps/snapshots.go b/apps/snapshots.go index 2b17087..60ba8ba 100644 --- a/apps/snapshots.go +++ b/apps/snapshots.go @@ -305,7 +305,10 @@ func (s *SnapshotProcessor) ListAppsSnapshotsByLabel(labelValue string) ([]Snaps } snapshot, err := s.metadataForSnapshotKey(key) - if err != nil { + if err != nil && strings.Contains(err.Error(), "wrong snapshot key format") { + log.Printf("WARNING: Snapshot storage: invalid key found (%s)", key) + continue + } else if err != nil { return snapshots, err } diff --git a/containers/types.go b/containers/types.go index 5417196..3ec3d52 100644 --- a/containers/types.go +++ b/containers/types.go @@ -276,14 +276,14 @@ func (c *Container) SetTechnology(tech string, version string) error { } // GetProcessList returns list of processes managed by supervisor. -func (c *Container) GetProcessList() (*[]Process, error) { +func (c *Container) GetProcessList() ([]Process, error) { driver := c.getDriver() processes := []Process{} stdouterr, err := driver.Exec(c.App.Name, []string{"supervisorctl", "status"}, "", []string{}, true) if err != nil { - return &processes, nil + return processes, nil } trimmed := strings.TrimSpace(string(*stdouterr)) @@ -297,7 +297,7 @@ func (c *Container) GetProcessList() (*[]Process, error) { } } - return &processes, nil + return processes, nil } // GetSystemProcesses return list of running system processes diff --git a/glue/main.go b/glue/main.go new file mode 100644 index 0000000..4c6e5d4 --- /dev/null +++ b/glue/main.go @@ -0,0 +1,652 @@ +package glue + +import ( + "errors" + "fmt" + "log" + "time" + + "github.com/jinzhu/gorm" + "github.com/rosti-cz/node-api/apps" + "github.com/rosti-cz/node-api/containers" + docker "github.com/rosti-cz/node-api/containers" + "github.com/rosti-cz/node-api/detector" + "github.com/rosti-cz/node-api/node" +) + +// Processor separates logic of apps, containers, detector and node from handlers. +// It defines common interface for both types of handlers, HTTP and the events. +type Processor struct { + AppName string + DB *gorm.DB + SnapshotProcessor *apps.SnapshotProcessor + WaitForAppLoops uint // each loop is five seconds +} + +// Return prepared Container instance +func (p *Processor) getContainer() (containers.Container, error) { + container := containers.Container{} + + processor := apps.AppsProcessor{ + DB: p.DB, + } + app, err := processor.Get(p.AppName) + if err != nil { + return container, err + } + + container = docker.Container{ + App: &app, + } + + return container, nil +} + +// returns instance of getAppProcessor +func (p *Processor) getAppProcessor() apps.AppsProcessor { + return apps.AppsProcessor{ + DB: p.DB, + } +} + +// waits until app is ready +func (p *Processor) waitForApp() error { + sleepFor := 5 * time.Second + loops := 6 + if p.WaitForAppLoops != 0 { + loops = int(p.WaitForAppLoops) + } + + for i := 0; i < loops; i++ { + err := updateState(p.AppName) + if err != nil { + time.Sleep(sleepFor) + continue + } + + container, err := p.getContainer() + if err != nil { + return err + } + + status, err := container.Status() + if err != nil { + return err + } + if status == "running" { + return nil + } + + time.Sleep(sleepFor) + } + + return errors.New("timeout reached") +} + +// List returns list of apps +func (p *Processor) List() (apps.Apps, error) { + appList := apps.Apps{} + + err := gatherStates() + if err != nil { + return appList, fmt.Errorf("backend error: %v", err) + } + + processor := apps.AppsProcessor{ + DB: p.DB, + } + appList, err = processor.List() + + if err != nil { + return appList, fmt.Errorf("backend error: %v", err) + } + + return appList, err +} + +// Get returns one app +func (p *Processor) Get() (apps.App, error) { + app := apps.App{} + + err := updateState(p.AppName) + if err != nil { + return app, err + } + + processor := apps.AppsProcessor{ + DB: p.DB, + } + app, err = processor.Get(p.AppName) + if err != nil { + return app, err + } + + // Gather runtime info about the container + container := docker.Container{ + App: &app, + } + + status, err := container.Status() + if err != nil { + return app, err + } + if status == "running" { + var err error + app.Techs, err = container.GetTechs() + if err != nil { + return app, err + } + app.PrimaryTech, err = container.GetPrimaryTech() + if err != nil { + return app, err + } + + processList, err := container.GetSystemProcesses() + if err != nil { + return app, err + } + + flags, err := detector.Check(processList) + if err != nil { + return app, err + } + app.Flags = flags.String() + } + + return app, nil +} + +// Create creates a single app in the system +func (p *Processor) Create(appTemplate apps.App) error { + err := p.Register(appTemplate) + if err != nil { + return err + } + + container := docker.Container{ + App: &appTemplate, + } + + err = container.Create() + if err != nil { + return err + } + + // Restore from snapshot if it's noted in the request + if len(appTemplate.Snapshot) > 0 { + log.Printf("App %s is going to be created from %s snapshot\n", appTemplate.Name, appTemplate.Snapshot) + + // Restore the data + err = p.SnapshotProcessor.RestoreSnapshot(appTemplate.Snapshot, appTemplate.Name) + if err != nil { + return err + } + } + + err = container.Start() + return err +} + +// Register registers app without creating a container for it +// Returns app name and an error +func (p *Processor) Register(appTemplate apps.App) error { + processor := apps.AppsProcessor{ + DB: p.DB, + } + err := processor.New(appTemplate.Name, appTemplate.SSHPort, appTemplate.HTTPPort, appTemplate.Image, appTemplate.CPU, appTemplate.Memory) + if err != nil { + if validationError, ok := err.(apps.ValidationError); ok { + return fmt.Errorf("validation error: %v", validationError.Error()) + } + return err + } + + return nil +} + +// Update updates application +func (p *Processor) Update(appTemplate apps.App) error { + processor := apps.AppsProcessor{ + DB: p.DB, + } + app, err := processor.Update(appTemplate.Name, appTemplate.SSHPort, appTemplate.HTTPPort, appTemplate.Image, appTemplate.CPU, appTemplate.Memory) + if err != nil { + if validationError, ok := err.(apps.ValidationError); ok { + return fmt.Errorf("validation error: %v", validationError.Error()) + } + return err + } + + container := docker.Container{ + App: app, + } + + err = container.Destroy() + if err != nil && err.Error() == "no container found" { + // We don't care if the container didn't exist anyway + err = nil + } + if err != nil { + return err + } + + err = container.Create() + if err != nil { + return err + } + + err = container.Start() + if err != nil { + return err + } + + return nil +} + +// Delete removes app from the system +func (p *Processor) Delete() error { + processor := apps.AppsProcessor{ + DB: p.DB, + } + app, err := processor.Get(p.AppName) + if err != nil { + log.Println("ERROR: delete app:", err.Error()) + } + + container := docker.Container{ + App: &app, + } + + status, err := container.Status() + if err != nil { + return err + } + + if status != "no-container" { + // We stop the container first + err = container.Stop() + if err != nil { + return err + } + + // Then delete it + err = container.Delete() + if err != nil { + return err + } + } + + err = processor.Delete(app.Name) + if err != nil { + return err + } + + return nil +} + +// Stop stops app +func (p *Processor) Stop() error { + processor := apps.AppsProcessor{ + DB: p.DB, + } + app, err := processor.Get(p.AppName) + if err != nil { + return err + } + + container := docker.Container{ + App: &app, + } + + status, err := container.Status() + if err != nil { + return err + } + + // Stop the container only when it exists + if status != "no-container" { + err = container.Stop() + if err != nil { + return err + } + } + + return nil +} + +// Start starts app +func (p *Processor) Start() error { + processor := apps.AppsProcessor{ + DB: p.DB, + } + app, err := processor.Get(p.AppName) + if err != nil { + return err + } + + container := docker.Container{ + App: &app, + } + + status, err := container.Status() + if err != nil { + return err + } + if status == "no-container" { + err = container.Create() + if err != nil { + return err + } + } + + err = container.Start() + if err != nil { + return err + } + + return nil +} + +// Restart restarts app +func (p *Processor) Restart() error { + processor := apps.AppsProcessor{ + DB: p.DB, + } + app, err := processor.Get(p.AppName) + if err != nil { + return err + } + + container := docker.Container{ + App: &app, + } + + err = container.Restart() + if err != nil { + return err + } + + return nil +} + +// UpdateKeys uploads SSH keys into the container +// keys parameters is just a string, one key per line +func (p *Processor) UpdateKeys(keys string) error { + err := p.waitForApp() + if err != nil { + return err + } + + container, err := p.getContainer() + if err != nil { + return err + } + + err = container.SetFileContent(sshPubKeysLocation, keys+"\n", "0600") + if err != nil { + return err + } + + return nil +} + +// SetPassword sets up a new password to access the SSH user +func (p *Processor) SetPassword(password string) error { + err := p.waitForApp() + if err != nil { + return err + } + + container, err := p.getContainer() + if err != nil { + return err + } + + err = container.SetPassword(password) + if err != nil { + return err + } + + return nil +} + +// Processes returns list of supervisord processes +func (p *Processor) Processes() ([]docker.Process, error) { + container, err := p.getContainer() + if err != nil { + return nil, err + } + + processes, err := container.GetProcessList() + if err != nil { + return nil, err + } + + return processes, nil +} + +// EnableTech sets up runtime for new tech or new version a tech +func (p *Processor) EnableTech(service, version string) error { + err := p.waitForApp() + if err != nil { + return err + } + + container, err := p.getContainer() + if err != nil { + return err + } + + err = container.SetTechnology(service, version) + if err != nil { + return err + } + + return nil +} + +// Rebuild recreates container for app +func (p *Processor) Rebuild() error { + container, err := p.getContainer() + if err != nil { + return err + } + + err = container.Destroy() + if err != nil { + return err + } + + err = container.Create() + if err != nil { + return err + } + + err = container.Start() + if err != nil { + return err + } + + return nil +} + +// AddLabel adds a label to the app +func (p *Processor) AddLabel(label string) error { + appProcessor := p.getAppProcessor() + err := appProcessor.AddLabel(p.AppName, label) + if err != nil { + return err + } + + return nil +} + +// RemoveLabel removes a label from the app +func (p *Processor) RemoveLabel(label string) error { + appProcessor := p.getAppProcessor() + err := appProcessor.RemoveLabel(p.AppName, label) + if err != nil { + return err + } + + return nil +} + +// GetNote returns node's info +func (p *Processor) GetNode() (*node.Node, error) { + node, err := node.GetNodeInfo() + if err != nil { + return nil, err + } + + return node, nil +} + +// CreateSnapshot creates a snapshot of given app +func (p *Processor) CreateSnapshot(labels []string) error { + _, err := p.SnapshotProcessor.CreateSnapshot(p.AppName, labels) + return err +} + +// RestoreFromSnapshot restores app from given snapshot +func (p *Processor) RestoreFromSnapshot(snapshotName string) error { + container, err := p.getContainer() + + // Stop the container + status, err := container.Status() + if err != nil { + return err + } + + // Stop the container only when it exists + if status != "no-container" { + err = container.Stop() + if err != nil { + return err + } + } + + // Restore the data + err = p.SnapshotProcessor.RestoreSnapshot(snapshotName, p.AppName) + if err != nil { + return err + } + + // Start the container + status, err = container.Status() + if err != nil { + return err + } + if status == "no-container" { + err = container.Create() + if err != nil { + return err + } + } + + err = container.Start() + if err != nil { + return err + } + + return nil +} + +// ListSnapshots returns list of snapshots +func (p *Processor) ListSnapshots() (SnapshotsMetadata, error) { + snapshots, err := p.SnapshotProcessor.ListAppSnapshots(p.AppName) + if err != nil { + return SnapshotsMetadata{}, err + } + + var output SnapshotsMetadata + for _, snapshot := range snapshots { + output = append(output, SnapshotMetadata{ + Key: snapshot.KeyName(p.SnapshotProcessor.IndexLabel), + Metadata: snapshot, + }) + } + + return output, nil +} + +// ListAppsSnapshots returns list of snapshots for given app list +func (p *Processor) ListAppsSnapshots(appNames []string) (SnapshotsMetadata, error) { + snapshots, err := p.SnapshotProcessor.ListAppsSnapshots(appNames) + if err != nil { + return SnapshotsMetadata{}, err + } + + var output SnapshotsMetadata + for _, snapshot := range snapshots { + output = append(output, SnapshotMetadata{ + Key: snapshot.KeyName(p.SnapshotProcessor.IndexLabel), + Metadata: snapshot, + }) + } + + return output, nil +} + +// ListSnapshotsByLabel returns list of snapshots by given label +func (p *Processor) ListSnapshotsByLabel(label string) (SnapshotsMetadata, error) { + snapshots, err := p.SnapshotProcessor.ListAppsSnapshotsByLabel(label) + if err != nil { + return SnapshotsMetadata{}, err + } + + var output SnapshotsMetadata + for _, snapshot := range snapshots { + output = append(output, SnapshotMetadata{ + Key: snapshot.KeyName(p.SnapshotProcessor.IndexLabel), + Metadata: snapshot, + }) + } + + return output, nil +} + +// GetSnapshot returns info about a single snapshot +func (p *Processor) GetSnapshot(snapshotName string) (SnapshotMetadata, error) { + snapshot, err := p.SnapshotProcessor.GetSnapshot(snapshotName) + if err != nil { + return SnapshotMetadata{}, err + } + output := SnapshotMetadata{ + Key: snapshot.KeyName(p.SnapshotProcessor.IndexLabel), + Metadata: snapshot, + } + + return output, nil + +} + +// GetSnapshotDownloadLink return download link for a snapshot +func (p *Processor) GetSnapshotDownloadLink(snapshotName string) (string, error) { + link, err := p.SnapshotProcessor.GetDownloadLink(snapshotName) + if err != nil { + return "", err + } + + return link, nil +} + +// DeleteSnapshot deletes a snapshot +func (p *Processor) DeleteSnapshot(snapshotName string) error { + err := p.SnapshotProcessor.DeleteSnapshot(snapshotName) + if err != nil { + return err + } + + return nil +} + +// DeleteAppSnapshots deletes all snapshot of given app +func (p *Processor) DeleteAppSnapshots() error { + err := p.SnapshotProcessor.DeleteAppSnapshots(p.AppName) + if err != nil { + return err + } + + return nil +} diff --git a/stats.go b/glue/stats.go similarity index 94% rename from stats.go rename to glue/stats.go index 19e18e3..7bd1e16 100644 --- a/stats.go +++ b/glue/stats.go @@ -1,4 +1,4 @@ -package main +package glue import ( "log" @@ -20,7 +20,7 @@ func updateUsage(name string) error { } container := docker.Container{ - App: app, + App: &app, } state, err := container.GetState() @@ -52,7 +52,7 @@ func updateState(name string) error { } container := docker.Container{ - App: app, + App: &app, } state, err := container.Status() if err != nil { @@ -76,7 +76,7 @@ func gatherStats() error { return err } - for _, app := range *appList { + for _, app := range appList { err := updateUsage(app.Name) if err != nil { log.Println("STATS ERROR:", err.Error()) @@ -96,7 +96,7 @@ func gatherStates() error { return err } - for _, app := range *appList { + for _, app := range appList { err := updateState(app.Name) if err != nil { log.Println("STATE ERROR:", err.Error()) diff --git a/glue/types.go b/glue/types.go new file mode 100644 index 0000000..98e16c1 --- /dev/null +++ b/glue/types.go @@ -0,0 +1,15 @@ +package glue + +import "github.com/rosti-cz/node-api/apps" + +// Path where authorized keys are +const sshPubKeysLocation = "/srv/.ssh/authorized_keys" + +// SnapshotMetadata is snapshot structure encapsulation that combines key and metadata about the snapshot +type SnapshotMetadata struct { + Key string `json:"key"` + Metadata apps.Snapshot `json:"metadata"` +} + +// SnapshotsMetadata is returned by handlers +type SnapshotsMetadata []SnapshotMetadata diff --git a/go.mod b/go.mod index 1450473..3199f23 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/jinzhu/gorm v1.9.14 + github.com/jinzhu/now v1.1.4 // indirect github.com/kelseyhightower/envconfig v1.4.0 github.com/labstack/echo v3.3.10+incompatible github.com/labstack/gommon v0.3.0 // indirect diff --git a/go.sum b/go.sum index a82ef4e..5e77d94 100644 --- a/go.sum +++ b/go.sum @@ -422,8 +422,9 @@ github.com/jinzhu/gorm v1.9.14 h1:Kg3ShyTPcM6nzVo148fRrcMO6MNKuqtOUwnzqMgVniM= github.com/jinzhu/gorm v1.9.14/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= diff --git a/handlers.go b/handlers.go index ad3a7dd..a847a79 100644 --- a/handlers.go +++ b/handlers.go @@ -6,12 +6,12 @@ import ( "log" "net/http" "os" + "strings" "github.com/labstack/echo" "github.com/rosti-cz/node-api/apps" "github.com/rosti-cz/node-api/common" - docker "github.com/rosti-cz/node-api/containers" - "github.com/rosti-cz/node-api/node" + "github.com/rosti-cz/node-api/glue" ) func homeHandler(c echo.Context) error { @@ -21,16 +21,11 @@ func homeHandler(c echo.Context) error { } func listAppsHandler(c echo.Context) error { - err := gatherStates() - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - processor := apps.AppsProcessor{ + processor := glue.Processor{ DB: common.GetDBConnection(), } - applications, err := processor.List() + applications, err := processor.List() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -42,15 +37,11 @@ func listAppsHandler(c echo.Context) error { func getAppHandler(c echo.Context) error { name := c.Param("name") - err := updateState(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), - } - app, err := processor.Get(name) + app, err := processor.Get() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -63,35 +54,29 @@ func getAppHandler(c echo.Context) error { func createAppHandler(c echo.Context) error { registerOnly := c.QueryParam("register_only") == "1" - app := apps.App{} - err := c.Bind(&app) + appTemplate := apps.App{} + err := c.Bind(&appTemplate) if err != nil { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), - } - err = processor.New(app.Name, app.SSHPort, app.HTTPPort, app.Image, app.CPU, app.Memory) - if err != nil { - if validationError, ok := err.(apps.ValidationError); ok { - return c.JSONPretty(http.StatusBadRequest, Message{Errors: validationError.Errors}, JSONIndent) - } - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) + processor := glue.Processor{ + AppName: appTemplate.Name, + DB: common.GetDBConnection(), } - if !registerOnly { - container := docker.Container{ - App: &app, - } - - err = container.Create() - if err != nil { + if registerOnly { + err = processor.Register(appTemplate) + if err != nil && strings.Contains(err.Error(), "validation error") { + return c.JSONPretty(http.StatusBadRequest, Message{Errors: []string{err.Error()}}, JSONIndent) + } else if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } - - err = container.Start() - if err != nil { + } else { + err = processor.Create(appTemplate) + if err != nil && strings.Contains(err.Error(), "validation error") { + return c.JSONPretty(http.StatusBadRequest, Message{Errors: []string{err.Error()}}, JSONIndent) + } else if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } } @@ -103,45 +88,20 @@ func createAppHandler(c echo.Context) error { func updateAppHandler(c echo.Context) error { name := c.Param("name") - app := apps.App{} - err := c.Bind(&app) + appTemplate := apps.App{} + err := c.Bind(&appTemplate) if err != nil { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - appPointer, err := processor.Update(name, app.SSHPort, app.HTTPPort, app.Image, app.CPU, app.Memory) - if err != nil { - if validationError, ok := err.(apps.ValidationError); ok { - return c.JSONPretty(http.StatusBadRequest, Message{Errors: validationError.Errors}, JSONIndent) - } - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - app = *appPointer - - container := docker.Container{ - App: &app, - } - - err = container.Destroy() - if err != nil && err.Error() == "no container found" { - // We don't care if the container didn't exist anyway - err = nil - } - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - err = container.Create() - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - err = container.Start() - if err != nil { + err = processor.Update(appTemplate) + if err != nil && strings.Contains(err.Error(), "validation error") { + return c.JSONPretty(http.StatusBadRequest, Message{Errors: []string{err.Error()}}, JSONIndent) + } else if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -152,31 +112,15 @@ func updateAppHandler(c echo.Context) error { func stopAppHandler(c echo.Context) error { name := c.Param("name") - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - app, err := processor.Get(name) + err := processor.Stop() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } - container := docker.Container{ - App: app, - } - - status, err := container.Status() - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - // Stop the container only when it exists - if status != "no-container" { - err = container.Stop() - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - } - return c.JSON(http.StatusOK, Message{Message: "ok"}) } @@ -184,30 +128,11 @@ func stopAppHandler(c echo.Context) error { func startAppHandler(c echo.Context) error { name := c.Param("name") - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - app, err := processor.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - container := docker.Container{ - App: app, - } - - status, err := container.Status() - if err != nil { - return err - } - if status == "no-container" { - err = container.Create() - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - } - - err = container.Start() + err := processor.Start() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -219,19 +144,11 @@ func startAppHandler(c echo.Context) error { func restartAppHandler(c echo.Context) error { name := c.Param("name") - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - app, err := processor.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - container := docker.Container{ - App: app, - } - - err = container.Restart() + err := processor.Restart() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -249,19 +166,11 @@ func setPasswordHandler(c echo.Context) error { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - app, err := processor.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - container := docker.Container{ - App: app, - } - - err = container.SetPassword(password.Password) + err = processor.SetPassword(password.Password) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -278,19 +187,11 @@ func setKeysHandler(c echo.Context) error { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - app, err := processor.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - container := docker.Container{ - App: app, - } - - err = container.SetFileContent(sshPubKeysLocation, string(body)+"\n", "0600") + err = processor.UpdateKeys(string(body) + "\n") if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -301,73 +202,22 @@ func setKeysHandler(c echo.Context) error { func setServicesHandler(c echo.Context) error { name := c.Param("name") - quickServices := &QuickServices{} - err := c.Bind(quickServices) + tech := &Technology{} + err := c.Bind(tech) if err != nil { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - app, err := processor.Get(name) + + err = processor.EnableTech(tech.Name, tech.Version) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } - container := docker.Container{ - App: app, - } - - if quickServices.Python { - err = container.SetTechnology("python", "") - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - } - - if quickServices.PHP { - err = container.SetTechnology("php", "") - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - } - - if quickServices.Node { - err = container.SetTechnology("node", "") - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - } - - if quickServices.Ruby { - err = container.SetTechnology("ruby", "") - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - } - - if quickServices.Deno { - err = container.SetTechnology("deno", "") - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - } - - if quickServices.Memcached { - err = container.SetTechnology("memcached", "") - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - } - - if quickServices.Redis { - err = container.SetTechnology("redis", "") - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - } - return c.JSON(http.StatusOK, Message{Message: "ok"}) } @@ -375,33 +225,11 @@ func setServicesHandler(c echo.Context) error { func rebuildAppHandler(c echo.Context) error { name := c.Param("name") - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - app, err := processor.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - container := docker.Container{ - App: app, - } - - err = container.Destroy() - if err != nil && err.Error() == "no container found" { - // We don't care if the container didn't exist anyway - err = nil - } - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - err = container.Create() - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - err = container.Start() + err := processor.Rebuild() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -419,10 +247,11 @@ func addLabelHandler(c echo.Context) error { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - err = processor.AddLabel(name, label.Value) + err = processor.AddLabel(label.Value) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -440,10 +269,11 @@ func deleteLabelHandler(c echo.Context) error { return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent) } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - err = processor.RemoveLabel(name, label.Value) + err = processor.RemoveLabel(label.Value) if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -456,36 +286,16 @@ func deleteLabelHandler(c echo.Context) error { func deleteAppHandler(c echo.Context) error { name := c.Param("name") - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), - } - app, err := processor.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - go func(app *apps.App) { - container := docker.Container{ - App: app, + go func(name string) { + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - - status, err := container.Status() + err := processor.Delete() if err != nil { - log.Println("ERROR delete application problem: " + err.Error()) - + log.Printf("Deletion of application failed: %v", err) } - if status != "no-container" { - err = container.Delete() - if err != nil { - log.Println("ERROR delete application problem: " + err.Error()) - } - } - - err = processor.Delete(app.Name) - if err != nil { - log.Println("ERROR delete application problem: " + err.Error()) - } - }(app) + }(name) return c.JSON(http.StatusOK, Message{Message: "deleted"}) } @@ -497,7 +307,10 @@ func getOrphansHander(c echo.Context) error { // Return info about the node including performance index func getNodeInfoHandler(c echo.Context) error { - node, err := node.GetNodeInfo() + processor := glue.Processor{ + DB: common.GetDBConnection(), + } + node, err := processor.GetNode() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -509,19 +322,11 @@ func getNodeInfoHandler(c echo.Context) error { func getAppProcessesHandler(c echo.Context) error { name := c.Param("name") - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: name, + DB: common.GetDBConnection(), } - app, err := processor.Get(name) - if err != nil { - return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) - } - - container := docker.Container{ - App: app, - } - - processes, err := container.GetProcessList() + processes, err := processor.Processes() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -534,7 +339,10 @@ func metricsHandler(c echo.Context) error { var metrics string // Node indexes - node, err := node.GetNodeInfo() + processor := glue.Processor{ + DB: common.GetDBConnection(), + } + node, err := processor.GetNode() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } @@ -552,15 +360,12 @@ func metricsHandler(c echo.Context) error { metrics += fmt.Sprintf("rosti_node_memory_index{hostname=\"%s\"} %f\n", hostname, node.MemoryIndex) metrics += fmt.Sprintf("rosti_node_sold_memory{hostname=\"%s\"} %d\n", hostname, node.SoldMemory) - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), - } apps, err := processor.List() if err != nil { return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) } - for _, app := range *apps { + for _, app := range apps { metrics += fmt.Sprintf("rosti_node_app_disk_usage_bytes{hostname=\"%s\", app=\"%s\"} %d\n", hostname, app.Name, app.DiskUsageBytes) metrics += fmt.Sprintf("rosti_node_app_disk_usage_inodes{hostname=\"%s\", app=\"%s\"} %d\n", hostname, app.Name, app.DiskUsageInodes) } diff --git a/handlers_nats.go b/handlers_nats.go index eedacde..b02e73f 100644 --- a/handlers_nats.go +++ b/handlers_nats.go @@ -22,9 +22,7 @@ import ( "github.com/pkg/errors" "github.com/rosti-cz/node-api/apps" "github.com/rosti-cz/node-api/common" - docker "github.com/rosti-cz/node-api/containers" - "github.com/rosti-cz/node-api/detector" - "github.com/rosti-cz/node-api/node" + "github.com/rosti-cz/node-api/glue" ) // This handler only passes messages to another function for easier testing @@ -59,7 +57,7 @@ func _messageHandler(m *nats.Msg) error { "add_label": addLabelEventHandler, "remove_label": removeLabelEventHandler, "list_orphans": listOrphansEventHandler, - "node": getNoteEventHandler, + "node": getNodeEventHandler, "create_snapshot": createSnapshotEventHandler, "restore_from_snapshot": restoreFromSnapshotEventHandler, "list_snapshots": listSnapshotsEventHandler, @@ -94,17 +92,12 @@ func _messageHandler(m *nats.Msg) error { func listEventHandler(m *nats.Msg, message *RequestMessage) error { log.Println("> List") - err := gatherStates() - if err != nil { - return errorReplyFormater(m, "backend error", err) + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), - } - processor.Init() applications, err := processor.List() - if err != nil { return errorReplyFormater(m, "backend error", err) } @@ -128,56 +121,15 @@ func listEventHandler(m *nats.Msg, message *RequestMessage) error { // Returns one app func getEventHandler(m *nats.Msg, message *RequestMessage) error { - - err := updateState(message.AppName) - if err != nil { - return errorReplyFormater(m, "backend error", err) - } - - processor := apps.AppsProcessor{ + processor := glue.Processor{ DB: common.GetDBConnection(), } - app, err := processor.Get(message.AppName) - if err != nil { - return errorReplyFormater(m, "backend error", err) - } - // Gather runtime info about the container - container := docker.Container{ - App: app, - } - - status, err := container.Status() + app, err := processor.Get() if err != nil { log.Printf("backend error: %v\n", err) return errorReplyFormater(m, "backend error", err) } - if status == "running" { - var err error - app.Techs, err = container.GetTechs() - if err != nil { - log.Printf("backend error: %v\n", err) - return errorReplyFormater(m, "backend error", err) - } - app.PrimaryTech, err = container.GetPrimaryTech() - if err != nil { - log.Printf("backend error: %v\n", err) - return errorReplyFormater(m, "backend error", err) - } - - processList, err := container.GetSystemProcesses() - if err != nil { - log.Printf("backend error: %v\n", err) - return errorReplyFormater(m, "backend error", err) - } - - flags, err := detector.Check(processList) - if err != nil { - log.Printf("backend error: %v\n", err) - return errorReplyFormater(m, "backend error", err) - } - app.Flags = flags.String() - } // Assembling reply message reply := ReplyMessage{ @@ -200,7 +152,6 @@ func getEventHandler(m *nats.Msg, message *RequestMessage) error { // Create a new app func createEventHandler(m *nats.Msg, message *RequestMessage) error { - appTemplate := apps.App{} body := []byte(message.Payload) err := json.Unmarshal(body, &appTemplate) @@ -210,48 +161,17 @@ func createEventHandler(m *nats.Msg, message *RequestMessage) error { return err } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - err = processor.New(message.AppName, appTemplate.SSHPort, appTemplate.HTTPPort, appTemplate.Image, appTemplate.CPU, appTemplate.Memory) - if err != nil { - if validationError, ok := err.(apps.ValidationError); ok { - log.Println("ERROR create application problem (validation problem): " + validationError.Error()) - publish(message.AppName, "validation problem", true) - return err - } - log.Println("ERROR create application problem (processor.New): " + err.Error()) - publish(message.AppName, "backend problem", true) + err = processor.Create(appTemplate) + if err != nil && strings.Contains(err.Error(), "validation error") { + publish(message.AppName, "validation error", true) return err - } - - container := docker.Container{ - App: &appTemplate, - } - - err = container.Create() - if err != nil { - log.Println("ERROR create application problem (container.Create): " + err.Error()) - publish(message.AppName, "backend problem", true) - return err - } - - // Restore from snapshot if it's noted in the request - if len(appTemplate.Snapshot) > 0 { - log.Printf("App %s is going to be created from %s snapshot\n", message.AppName, appTemplate.Snapshot) - - // Restore the data - err = snapshotProcessor.RestoreSnapshot(appTemplate.Snapshot, message.AppName) - if err != nil { - log.Println("ERROR restore snapshot error: " + err.Error()) - publish(message.AppName, "backend problem", true) - return err - } - } - - err = container.Start() - if err != nil { - log.Println("ERROR create application problem (container.Start): " + err.Error()) + } else if err != nil { + log.Println("ERROR create application problem: " + err.Error()) publish(message.AppName, "backend problem", true) return err } @@ -273,17 +193,17 @@ func registerEventHandler(m *nats.Msg, message *RequestMessage) error { return err } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - err = processor.New(message.AppName, appTemplate.SSHPort, appTemplate.HTTPPort, appTemplate.Image, appTemplate.CPU, appTemplate.Memory) - if err != nil { - if validationError, ok := err.(apps.ValidationError); ok { - log.Println("ERROR create application problem (validation): " + validationError.Error()) - publish(message.AppName, "validation problem", true) - return err - } - log.Println("ERROR create application problem (processor.New): " + err.Error()) + err = processor.Create(appTemplate) + if err != nil && strings.Contains(err.Error(), "validation error") { + publish(message.AppName, "validation error", true) + return err + } else if err != nil { + log.Println("ERROR create application problem: " + err.Error()) publish(message.AppName, "backend problem", true) return err } @@ -304,306 +224,155 @@ func updateEventHandler(m *nats.Msg, message *RequestMessage) error { return err } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - app, err := processor.Update(message.AppName, appTemplate.SSHPort, appTemplate.HTTPPort, appTemplate.Image, appTemplate.CPU, appTemplate.Memory) - if err != nil { - if validationError, ok := err.(apps.ValidationError); ok { - log.Println("ERROR update application problem: " + validationError.Error()) - publish(message.AppName, "backend problem", true) - return err - } - log.Println("ERROR update application problem: " + err.Error()) + err = processor.Update(appTemplate) + if err != nil && strings.Contains(err.Error(), "validation error") { + publish(message.AppName, "validation error", true) + return err + } else if err != nil { + log.Println("ERROR create application problem: " + err.Error()) publish(message.AppName, "backend problem", true) return err } - container := docker.Container{ - App: app, - } - - err = container.Destroy() - if err != nil && err.Error() == "no container found" { - // We don't care if the container didn't exist anyway - err = nil - } - if err != nil { - log.Println("ERROR update application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - err = container.Create() - if err != nil { - log.Println("ERROR update application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - err = container.Start() - if err != nil { - log.Println("ERROR update application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - publish(app.Name, "updated", false) + publish(message.AppName, "updated", false) return nil } // Delete one app func deleteEventHandler(m *nats.Msg, message *RequestMessage) error { - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - app, err := processor.Get(message.AppName) - if err != nil { - log.Println("ERROR: delete app:", err.Error()) - } - - container := docker.Container{ - App: app, - } - - status, err := container.Status() + err := processor.Delete() if err != nil { log.Println("ERROR delete application problem: " + err.Error()) - publish(app.Name, "backend problem", true) + publish(message.AppName, "backend problem", true) return err } - if status != "no-container" { - // We stop the container first - err = container.Stop() - if err != nil { - log.Println("ERROR delete application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - // Then delete it - err = container.Delete() - if err != nil { - log.Println("ERROR delete application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - } - - err = processor.Delete(app.Name) - if err != nil { - log.Println("ERROR delete application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - publish(app.Name, "deleted", false) + publish(message.AppName, "deleted", false) return nil } // Stop existing app func stopEventHandler(m *nats.Msg, message *RequestMessage) error { - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - app, err := processor.Get(message.AppName) + err := processor.Stop() if err != nil { log.Println("ERROR stop application problem: " + err.Error()) - publish(app.Name, "backend problem", true) + publish(message.AppName, "backend problem", true) return err } - container := docker.Container{ - App: app, - } - - status, err := container.Status() - if err != nil { - log.Println("ERROR stop application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - // Stop the container only when it exists - if status != "no-container" { - err = container.Stop() - if err != nil { - log.Println("ERROR stop application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - } - - publish(app.Name, "stopped", false) + publish(message.AppName, "stopped", false) return nil } // Start existing app func startEventHandler(m *nats.Msg, message *RequestMessage) error { - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - app, err := processor.Get(message.AppName) + err := processor.Start() if err != nil { log.Println("ERROR start application problem: " + err.Error()) - publish(app.Name, "backend problem", true) + publish(message.AppName, "backend problem", true) return err } - container := docker.Container{ - App: app, - } - - status, err := container.Status() - if err != nil { - return err - } - if status == "no-container" { - err = container.Create() - if err != nil { - log.Println("ERROR start application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - } - - err = container.Start() - if err != nil { - log.Println("ERROR start application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - publish(app.Name, "started", false) + publish(message.AppName, "started", false) return nil } // Restart existing app func restartEventHandler(m *nats.Msg, message *RequestMessage) error { - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - app, err := processor.Get(message.AppName) + err := processor.Restart() if err != nil { log.Println("ERROR restart application problem: " + err.Error()) - publish(app.Name, "backend problem", true) + publish(message.AppName, "backend problem", true) return err } - container := docker.Container{ - App: app, - } - - err = container.Restart() - if err != nil { - log.Println("ERROR restart application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - publish(app.Name, "restarted", false) + publish(message.AppName, "restarted", false) return nil } // Copies body of the request into /srv/.ssh/authorized_keys func updateKeysEventHandler(m *nats.Msg, message *RequestMessage) error { - err := waitForApp(message.AppName) + body := message.Payload + + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + err := processor.UpdateKeys(body) if err != nil { - log.Println("ERROR enable tech problem: " + err.Error()) + log.Println("ERROR update keys problem: " + err.Error()) publish(message.AppName, "backend problem", true) return err } - body := message.Payload - - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), - } - app, err := processor.Get(message.AppName) - if err != nil { - log.Println("ERROR keys update problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - container := docker.Container{ - App: app, - } - - err = container.SetFileContent(sshPubKeysLocation, body+"\n", "0600") - if err != nil { - log.Println("ERROR keys update problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - publish(app.Name, "keys updated", false) + publish(message.AppName, "keys updated", false) return nil } // Set password for the app user in the container func setPasswordEventHandler(m *nats.Msg, message *RequestMessage) error { - err := waitForApp(message.AppName) + password := message.Payload + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + err := processor.SetPassword(password) + if err != nil { - log.Println("ERROR enable tech problem: " + err.Error()) + log.Println("ERROR password update problem: " + err.Error()) publish(message.AppName, "backend problem", true) return err } - password := message.Payload - - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), - } - app, err := processor.Get(message.AppName) - if err != nil { - log.Println("ERROR password update problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - container := docker.Container{ - App: app, - } - - err = container.SetPassword(password) - if err != nil { - log.Println("ERROR password update problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - publish(app.Name, "password updated", false) + publish(message.AppName, "password updated", false) return nil } // Application processes func processesEventHandler(m *nats.Msg, message *RequestMessage) error { - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), - } - app, err := processor.Get(message.AppName) - if err != nil { - log.Println("ERROR processes list problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - container := docker.Container{ - App: app, - } - - processes, err := container.GetProcessList() + processes, err := processor.Processes() if err != nil { log.Println("ERROR processes list problem: " + err.Error()) - publish(app.Name, "backend problem", true) + publish(message.AppName, "backend problem", true) return err } @@ -614,7 +383,7 @@ func processesEventHandler(m *nats.Msg, message *RequestMessage) error { data, err := json.Marshal(reply) if err != nil { log.Println("ERROR processes list problem: " + err.Error()) - publish(app.Name, "backend problem", true) + publish(message.AppName, "backend problem", true) return err } err = m.Respond(data) @@ -653,81 +422,39 @@ func enableTechEventHandler(m *nats.Msg, message *RequestMessage) error { version = parts[1] } - err = waitForApp(message.AppName) + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + processor.EnableTech(service, version) if err != nil { log.Println("ERROR enable tech problem: " + err.Error()) publish(message.AppName, "backend problem", true) return err } - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), - } - app, err := processor.Get(message.AppName) - if err != nil { - log.Println("ERROR enable tech problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - container := docker.Container{ - App: app, - } - - err = container.SetTechnology(service, version) - if err != nil { - log.Println("ERROR enable tech problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - publish(app.Name, "tech updated", false) + publish(message.AppName, "tech updated", false) return nil } // Rebuilds existing app, it keeps the data but creates the container again func rebuildEventHandler(m *nats.Msg, message *RequestMessage) error { - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - app, err := processor.Get(message.AppName) + + err := processor.Rebuild() if err != nil { log.Println("ERROR rebuild app problem: " + err.Error()) - publish(app.Name, "backend problem", true) + publish(message.AppName, "backend problem", true) return err } - container := docker.Container{ - App: app, - } - - err = container.Destroy() - if err != nil && err.Error() == "no container found" { - // We don't care if the container didn't exist anyway - err = nil - } - if err != nil { - log.Println("ERROR rebuild app problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - err = container.Create() - if err != nil { - log.Println("ERROR rebuild app problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - err = container.Start() - if err != nil { - log.Println("ERROR rebuild app problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - publish(app.Name, "app rebuild", false) + publish(message.AppName, "app rebuild", false) return nil } @@ -736,10 +463,12 @@ func rebuildEventHandler(m *nats.Msg, message *RequestMessage) error { func addLabelEventHandler(m *nats.Msg, message *RequestMessage) error { label := message.Payload - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - err := processor.AddLabel(message.AppName, label) + err := processor.AddLabel(label) if err != nil { log.Println("ERROR add label problem: " + err.Error()) publish(message.AppName, "backend problem", true) @@ -755,10 +484,12 @@ func addLabelEventHandler(m *nats.Msg, message *RequestMessage) error { func removeLabelEventHandler(m *nats.Msg, message *RequestMessage) error { label := message.Payload - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - err := processor.RemoveLabel(message.AppName, label) + err := processor.RemoveLabel(label) if err != nil { log.Println("ERROR remove label problem: " + err.Error()) publish(message.AppName, "backend problem", true) @@ -793,12 +524,18 @@ func listOrphansEventHandler(m *nats.Msg, message *RequestMessage) error { } /* -getNoteEventHandler returns info about the node including performance index +getNodeEventHandler returns info about the node including performance index */ -func getNoteEventHandler(m *nats.Msg, message *RequestMessage) error { - node, err := node.GetNodeInfo() +func getNodeEventHandler(m *nats.Msg, message *RequestMessage) error { + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + + node, err := processor.GetNode() if err != nil { log.Println("ERROR performance index problem: " + err.Error()) publish(message.AppName, "backend problem", true) @@ -834,7 +571,13 @@ Payload: no payload needed Response: notification when it's done or error */ func createSnapshotEventHandler(m *nats.Msg, message *RequestMessage) error { - _, err := snapshotProcessor.CreateSnapshot(message.AppName, strings.Split(message.Payload, ",")) + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + + err := processor.CreateSnapshot(strings.Split(message.Payload, ",")) if err != nil { log.Println("ERROR create snapshot error: " + err.Error()) publish(message.AppName, "backend problem", true) @@ -858,64 +601,16 @@ Payload: string with the snapshot name Response: notification when it's done or error */ func restoreFromSnapshotEventHandler(m *nats.Msg, message *RequestMessage) error { - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), - } - app, err := processor.Get(message.AppName) - if err != nil { - log.Println("ERROR restore snapshot problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, } - container := docker.Container{ - App: app, - } - - // Stop the container - status, err := container.Status() - if err != nil { - log.Println("ERROR restore snapshot problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - - // Stop the container only when it exists - if status != "no-container" { - err = container.Stop() - if err != nil { - log.Println("ERROR restore snapshot problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - } - - // Restore the data - err = snapshotProcessor.RestoreSnapshot(message.Payload, message.AppName) - if err != nil { - log.Println("ERROR restore snapshot error: " + err.Error()) - publish(message.AppName, "backend problem", true) - return err - } - - // Start the container - status, err = container.Status() - if err != nil { - return err - } - if status == "no-container" { - err = container.Create() - if err != nil { - log.Println("ERROR start application problem: " + err.Error()) - publish(app.Name, "backend problem", true) - return err - } - } - - err = container.Start() + err := processor.RestoreFromSnapshot(message.Payload) if err != nil { log.Println("ERROR start application problem: " + err.Error()) - publish(app.Name, "backend problem", true) + publish(message.AppName, "backend problem", true) return err } @@ -934,21 +629,18 @@ Payload: no payload needed Response: replies with list of snapshots or an error message */ func listSnapshotsEventHandler(m *nats.Msg, message *RequestMessage) error { - snapshots, err := snapshotProcessor.ListAppSnapshots(message.AppName) + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + snapshots, err := processor.ListSnapshots() if err != nil { return errorReplyFormater(m, "backend error", err) } - var output SnapshotsMetadata - for _, snapshot := range snapshots { - output = append(output, SnapshotMetadata{ - Key: snapshot.KeyName(snapshotProcessor.IndexLabel), - Metadata: snapshot, - }) - } - reply := ReplyMessage{ - Payload: output, + Payload: snapshots, } data, err := json.Marshal(reply) @@ -970,21 +662,18 @@ Payload: list of appNames separated by comma (no spaces) Response: replies with list of snapshots or an error message */ func listAppsSnapshotsEventHandler(m *nats.Msg, message *RequestMessage) error { - snapshots, err := snapshotProcessor.ListAppsSnapshots(strings.Split(message.Payload, ",")) + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + snapshots, err := processor.ListAppsSnapshots(strings.Split(message.Payload, ",")) if err != nil { return errorReplyFormater(m, "backend error", err) } - var output SnapshotsMetadata - for _, snapshot := range snapshots { - output = append(output, SnapshotMetadata{ - Key: snapshot.KeyName(snapshotProcessor.IndexLabel), - Metadata: snapshot, - }) - } - reply := ReplyMessage{ - Payload: output, + Payload: snapshots, } data, err := json.Marshal(reply) @@ -1006,21 +695,18 @@ Payload: snapshot label Response: replies with list of snapshots or an error message */ func listSnapshotsByLabelEventHandler(m *nats.Msg, message *RequestMessage) error { - snapshots, err := snapshotProcessor.ListAppsSnapshotsByLabel(message.Payload) + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + snapshots, err := processor.ListSnapshotsByLabel(message.Payload) if err != nil { return errorReplyFormater(m, "backend error", err) } - var output SnapshotsMetadata - for _, snapshot := range snapshots { - output = append(output, SnapshotMetadata{ - Key: snapshot.KeyName(snapshotProcessor.IndexLabel), - Metadata: snapshot, - }) - } - reply := ReplyMessage{ - Payload: output, + Payload: snapshots, } data, err := json.Marshal(reply) @@ -1042,16 +728,17 @@ Payload: snapshot's key Response: snapshot metadata */ func getSnapshotEventHandler(m *nats.Msg, message *RequestMessage) error { - snapshot, err := snapshotProcessor.GetSnapshot(message.Payload) + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + snapshot, err := processor.GetSnapshot(message.Payload) if err != nil { return errorReplyFormater(m, "backend error", err) } - output := SnapshotMetadata{ - Key: snapshot.KeyName(snapshotProcessor.IndexLabel), - Metadata: snapshot, - } - data, err := json.Marshal(output) + data, err := json.Marshal(snapshot) if err != nil { return errorReplyFormater(m, "reply formatter error", err) } @@ -1071,7 +758,12 @@ Payload: string with a snapshot name (key) Response: string with the URL */ func getSnapshotDownloadLinkEventHandler(m *nats.Msg, message *RequestMessage) error { - link, err := snapshotProcessor.GetDownloadLink(message.Payload) + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + link, err := processor.GetSnapshotDownloadLink(message.Payload) if err != nil { return errorReplyFormater(m, "backend error", err) } @@ -1100,7 +792,13 @@ Payload: string with a snapshot name Response: notification when it's done or error */ func deleteSnapshotEventHandler(m *nats.Msg, message *RequestMessage) error { - err := snapshotProcessor.DeleteSnapshot(message.Payload) + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + + err := processor.DeleteSnapshot(message.Payload) if err != nil { return errorReplyFormater(m, "backend error", err) } @@ -1119,7 +817,13 @@ Payload: no payload needed Response: notification when it's done or error */ func deleteAppSnapshotsEventHandler(m *nats.Msg, message *RequestMessage) error { - err := snapshotProcessor.DeleteAppSnapshots(message.AppName) + processor := glue.Processor{ + AppName: message.AppName, + DB: common.GetDBConnection(), + SnapshotProcessor: &snapshotProcessor, + } + + err := processor.DeleteAppSnapshots() if err != nil { return errorReplyFormater(m, "backend error", err) } diff --git a/main.go b/main.go index 2210c05..606dfc2 100644 --- a/main.go +++ b/main.go @@ -66,19 +66,19 @@ func main() { t := &Template{} // Stats loop - go func() { - for { - log.Println("Stats gathering started") - start := time.Now() - err := gatherStats() - if err != nil { - log.Println("LOOP ERROR:", err.Error()) - } - elapsed := time.Since(start) - log.Printf("Stats gathering elapsed time: %.2fs\n", elapsed.Seconds()) - time.Sleep(300 * time.Second) - } - }() + // go func() { + // for { + // log.Println("Stats gathering started") + // start := time.Now() + // err := gatherStats() + // if err != nil { + // log.Println("LOOP ERROR:", err.Error()) + // } + // elapsed := time.Since(start) + // log.Printf("Stats gathering elapsed time: %.2fs\n", elapsed.Seconds()) + // time.Sleep(300 * time.Second) + // } + // }() // Node stats go func() { diff --git a/tools.go b/tools.go index e594568..6b6b0c9 100644 --- a/tools.go +++ b/tools.go @@ -2,15 +2,10 @@ package main import ( "encoding/json" - "errors" "fmt" "log" - "time" "github.com/nats-io/nats.go" - "github.com/rosti-cz/node-api/apps" - "github.com/rosti-cz/node-api/common" - docker "github.com/rosti-cz/node-api/containers" ) func errorReplyFormater(m *nats.Msg, message string, err error) error { @@ -54,43 +49,3 @@ func publish(appName string, state string, isErr bool) { log.Println("ERROR: publish:", err.Error()) } } - -// waitForApp waits until app is ready or timeout is reached. -// It's used in some async calls that need at least part of the -// environment prepared. -func waitForApp(appName string) error { - processor := apps.AppsProcessor{ - DB: common.GetDBConnection(), - } - - sleepFor := 5 * time.Second - loops := 6 - - for i := 0; i < loops; i++ { - err := updateState(appName) - if err != nil { - time.Sleep(sleepFor) - continue - } - - app, err := processor.Get(appName) - if err != nil { - return err - } - - container := docker.Container{ - App: app, - } - - status, err := container.Status() - - if status == "running" { - return nil - } - - time.Sleep(sleepFor) - continue - } - - return errors.New("timeout reached") -} diff --git a/types.go b/types.go index 7305f42..b38aa20 100644 --- a/types.go +++ b/types.go @@ -2,13 +2,8 @@ package main import ( "encoding/json" - - "github.com/rosti-cz/node-api/apps" ) -// Path where authorized keys are -const sshPubKeysLocation = "/srv/.ssh/authorized_keys" - // RequestMessage message type RequestMessage struct { AppName string `json:"name"` @@ -65,11 +60,8 @@ type QuickServices struct { Redis bool `json:"redis"` } -// SnapshotMetadata is snapshot structure encapsulation that combines key and metadata about the snapshot -type SnapshotMetadata struct { - Key string `json:"key"` - Metadata apps.Snapshot `json:"metadata"` +// Set technology +type Technology struct { + Name string + Version string } - -// SnapshotsMetadata is returned by handlers -type SnapshotsMetadata []SnapshotMetadata