Massive handlers refactoring
All checks were successful
continuous-integration/drone/push Build is passing

Taking logic from handler into glue module
Add tests for apps
Updated docker library
This commit is contained in:
Adam Štrauch 2022-02-06 00:01:05 +01:00
parent fdcdca237f
commit e58d6462a9
Signed by: cx
GPG key ID: 018304FFA8988F8D
14 changed files with 1173 additions and 855 deletions

View file

@ -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
}

189
apps/main_test.go Normal file
View file

@ -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")
}

View file

@ -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
}

View file

@ -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

652
glue/main.go Normal file
View file

@ -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
}

View file

@ -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())

15
glue/types.go Normal file
View file

@ -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

1
go.mod
View file

@ -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

3
go.sum
View file

@ -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=

View file

@ -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)
}

View file

@ -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)
}

26
main.go
View file

@ -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() {

View file

@ -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")
}

View file

@ -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