Rest of the endpoints finished

This commit is contained in:
Adam Štrauch 2020-07-11 23:14:45 +02:00
parent 0181236e23
commit 4f9dac7f5c
Signed by: cx
GPG Key ID: 018304FFA8988F8D
5 changed files with 208 additions and 39 deletions

View File

@ -3,8 +3,8 @@ Content-type: application/json
{ {
"name": "test_1234", "name": "test_1234",
"ssh_port": 10000, "ssh_port": 46500,
"http_port": 10001, "http_port": 46501,
"image": "docker.io/rosti/runtime:2020.04-1", "image": "docker.io/rosti/runtime:2020.04-1",
"cpu": 100, "cpu": 100,
"memory": 128000 "memory": 128000
@ -16,8 +16,8 @@ PUT http://localhost:1323/v1/apps/test_1234
Content-type: application/json Content-type: application/json
{ {
"ssh_port": 36500, "ssh_port": 46500,
"http_port": 36501 "http_port": 46501
} }
@ -25,3 +25,27 @@ Content-type: application/json
PUT http://localhost:1323/v1/apps/test_1234/start PUT http://localhost:1323/v1/apps/test_1234/start
Content-type: application/json Content-type: application/json
###
PUT http://localhost:1323/v1/apps/test_1234/stop
Content-type: application/json
###
DELETE http://localhost:1323/v1/apps/test_1234
Content-type: application/json
###
GET http://localhost:1323/v1/apps/test_1234/stats
Content-type: application/json
###
GET http://localhost:1323/v1/apps
Content-type: application/json

View File

@ -23,6 +23,13 @@ type Label struct {
Value string `json:"value"` Value string `json:"value"`
} }
// AppState contains info about runnint application, it's not saved in the database
type AppState struct {
State string `json:"state"`
CPUUsage int `json:"cpu_usage"`
MemoryUsage int `json:"memory_usage"`
}
// App keeps info about hosted application // App keeps info about hosted application
type App struct { type App struct {
gorm.Model gorm.Model
@ -34,6 +41,8 @@ type App struct {
CPU int `json:"cpu"` // percentage, 200 means two CPU CPU int `json:"cpu"` // percentage, 200 means two CPU
Memory int `json:"memory"` // Limit in MB Memory int `json:"memory"` // Limit in MB
// Labels []Label `json:"labels"` // username:cx or user_id:1 // Labels []Label `json:"labels"` // username:cx or user_id:1
State AppState `json:"state" gorm:"-"`
} }
// Validate do basic checks of the struct values // Validate do basic checks of the struct values

View File

@ -7,7 +7,6 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
@ -25,7 +24,7 @@ const dockerTimeout = 10
const dockerSock = "unix:///var/run/docker.sock" const dockerSock = "unix:///var/run/docker.sock"
// DOCKER_API_VERSION set API version of Docker, 1.40 belongs to Docker 19.03.11 // DOCKER_API_VERSION set API version of Docker, 1.40 belongs to Docker 19.03.11
const dockerAPIVersion = "1.40" const dockerAPIVersion = "1.24"
// Driver keeps everything for connection to Docker // Driver keeps everything for connection to Docker
type Driver struct{} type Driver struct{}
@ -76,8 +75,10 @@ func (d *Driver) Status(name string) (string, error) {
} }
containerID, err := d.nameToID(name) containerID, err := d.nameToID(name)
if err != nil && err.Error() == "no container found" {
return "no-container", err
}
if err != nil { if err != nil {
status = "no-container"
return status, err return status, err
} }
@ -117,7 +118,9 @@ func (d *Driver) Stats(name string) (float64, int, error) {
if err != nil { if err != nil {
return 0.0, 0, err return 0.0, 0, err
} }
log.Println(data) // It returns one JSON:
// {"read":"2020-07-11T20:42:31.486726241Z","preread":"2020-07-11T20:42:30.484048602Z","pids_stats":{"current":7},"blkio_stats":{"io_service_bytes_recursive":[{"major":253,"minor":0,"op":"Read","value":0},{"major":253,"minor":0,"op":"Write","value":20480},{"major":253,"minor":0,"op":"Sync","value":12288},{"major":253,"minor":0,"op":"Async","value":8192},{"major":253,"minor":0,"op":"Discard","value":0},{"major":253,"minor":0,"op":"Total","value":20480}],"io_serviced_recursive":[{"major":253,"minor":0,"op":"Read","value":0},{"major":253,"minor":0,"op":"Write","value":5},{"major":253,"minor":0,"op":"Sync","value":3},{"major":253,"minor":0,"op":"Async","value":2},{"major":253,"minor":0,"op":"Discard","value":0},{"major":253,"minor":0,"op":"Total","value":5}],"io_queue_recursive":[],"io_service_time_recursive":[],"io_wait_time_recursive":[],"io_merged_recursive":[],"io_time_recursive":[],"sectors_recursive":[]},"num_procs":0,"storage_stats":{},"cpu_stats":{"cpu_usage":{"total_usage":758392753,"percpu_usage":[302688474,0,11507116,124238500,222136766,5656446,3009320,0,19406386,1397028,6201423,62151294,0,0,0,0],"usage_in_kernelmode":100000000,"usage_in_usermode":640000000},"system_cpu_usage":119385810000000,"online_cpus":12,"throttling_data":{"periods":21,"throttled_periods":1,"throttled_time":2995938}},"precpu_stats":{"cpu_usage":{"total_usage":758282347,"percpu_usage":[302688474,0,11507116,124238500,222026360,5656446,3009320,0,19406386,1397028,6201423,62151294,0,0,0,0],"usage_in_kernelmode":100000000,"usage_in_usermode":640000000},"system_cpu_usage":119373720000000,"online_cpus":12,"throttling_data":{"periods":21,"throttled_periods":1,"throttled_time":2995938}},"memory_stats":{"usage":21626880,"max_usage":22630400,"stats":{"active_anon":15949824,"active_file":0,"cache":0,"dirty":0,"hierarchical_memory_limit":144179200,"hierarchical_memsw_limit":288358400,"inactive_anon":0,"inactive_file":0,"mapped_file":0,"pgfault":13167,"pgmajfault":0,"pgpgin":7293,"pgpgout":3406,"rss":15900672,"rss_huge":0,"total_active_anon":15949824,"total_active_file":0,"total_cache":0,"total_dirty":0,"total_inactive_anon":0,"total_inactive_file":0,"total_mapped_file":0,"total_pgfault":13167,"total_pgmajfault":0,"total_pgpgin":7293,"total_pgpgout":3406,"total_rss":15900672,"total_rss_huge":0,"total_unevictable":0,"total_writeback":0,"unevictable":0,"writeback":0},"limit":144179200},"name":"/test_1234","id":"576878d645efecc8e5e2a57b88351f7b5c551e3fc72dc8473fd965d10dfddbec","networks":{"eth0":{"rx_bytes":6150,"rx_packets":37,"rx_errors":0,"rx_dropped":0,"tx_bytes":0,"tx_packets":0,"tx_errors":0,"tx_dropped":0}}}
log.Println(string(data))
return 0.0, 0, nil return 0.0, 0, nil
} }
@ -248,24 +251,26 @@ func (d *Driver) Create(name string, image string, volumePath string, HTTPPort i
return "", err return "", err
} }
portmaps := make(nat.PortMap, 1) portmaps := nat.PortMap{}
portbindingsHTTP := make([]nat.PortBinding, 1) // portbindingsHTTP := make([]nat.PortBinding, 1)
portbindingsHTTP[0] = nat.PortBinding{ // portbindingsHTTP[0] = nat.PortBinding{
HostPort: strconv.Itoa(HTTPPort) + "/tcp", // HostIP: "0.0.0.0",
} // HostPort: strconv.Itoa(HTTPPort) + "/tcp",
portmaps["8000/tcp"] = portbindingsHTTP // }
// portmaps["8000/tcp"] = portbindingsHTTP
if SSHPort != 0 { // if SSHPort != 0 {
portbindingsSSH := make([]nat.PortBinding, 1) // portbindingsSSH := make([]nat.PortBinding, 1)
portbindingsSSH[0] = nat.PortBinding{ // portbindingsSSH[0] = nat.PortBinding{
HostPort: strconv.Itoa(SSHPort) + "/tcp", // HostIP: "0.0.0.0",
} // HostPort: strconv.Itoa(SSHPort) + "/tcp",
portmaps["22/tcp"] = portbindingsSSH // }
} // portmaps["22/tcp"] = portbindingsSSH
// }
createdContainer, err := cli.ContainerCreate( createdContainer, err := cli.ContainerCreate(
context.TODO(), context.Background(),
&container.Config{ &container.Config{
Hostname: name, Hostname: name,
Env: []string{}, Env: []string{},

View File

@ -1,6 +1,7 @@
package docker package docker
import ( import (
"log"
"path" "path"
"github.com/rosti-cz/apps-api/apps" "github.com/rosti-cz/apps-api/apps"
@ -8,7 +9,7 @@ import (
// Container extends App struct from App // Container extends App struct from App
type Container struct { type Container struct {
App apps.App `json:"app"` App *apps.App `json:"app"`
} }
func (c *Container) getDriver() *Driver { func (c *Container) getDriver() *Driver {
@ -21,8 +22,26 @@ func (c *Container) volumeHostPath() string {
return path.Join("/srv", c.App.Name) return path.Join("/srv", c.App.Name)
} }
// GetApp app object with populated state fields
func (c *Container) GetApp() (*apps.App, error) {
status, err := c.Status()
if err != nil {
return nil, err
}
state := apps.AppState{
State: status,
CPUUsage: 0,
MemoryUsage: 0,
}
c.App.State = state
return c.App, nil
}
// Status returns state of the container // Status returns state of the container
// Possible values: running, stopped, data-only, unknown // Possible values: running, stopped, no-container, unknown
func (c *Container) Status() (string, error) { func (c *Container) Status() (string, error) {
status := "unknown" status := "unknown"
@ -32,15 +51,14 @@ func (c *Container) Status() (string, error) {
driver := c.getDriver() driver := c.getDriver()
containerStatus, err := driver.Status(c.App.Name) containerStatus, err := driver.Status(c.App.Name)
if err != nil && err.Error() == "no container found" {
return "no-container", nil
}
if err != nil { if err != nil {
return status, err return status, err
} }
if containerStatus == "no-container" {
status = "data-only"
} else {
status = containerStatus status = containerStatus
}
return status, nil return status, nil
} }
@ -121,6 +139,9 @@ func (c *Container) Delete() error {
volumePath := path.Join("/srv", c.App.Name) volumePath := path.Join("/srv", c.App.Name)
err = removeDirectory(volumePath) err = removeDirectory(volumePath)
if err != nil {
log.Println(err)
}
return err return nil
} }

128
main.go
View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"log"
"net/http" "net/http"
"github.com/labstack/echo" "github.com/labstack/echo"
@ -25,13 +26,25 @@ func main() {
// Returns list of apps // Returns list of apps
e.GET("/v1/apps", func(c echo.Context) error { e.GET("/v1/apps", func(c echo.Context) error {
apps, err := apps.List() applications, err := apps.List()
if err != nil { if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
} }
return c.JSON(http.StatusOK, apps) populatedApps := []*apps.App{}
for _, app := range *applications {
container := docker.Container{
App: &app,
}
populatedApp, err := container.GetApp()
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
populatedApps = append(populatedApps, populatedApp)
}
return c.JSON(http.StatusOK, populatedApps)
}) })
// Returns one app // Returns one app
@ -43,6 +56,14 @@ func main() {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
} }
container := docker.Container{
App: app,
}
_, err = container.GetApp()
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSON(http.StatusOK, app) return c.JSON(http.StatusOK, app)
}) })
@ -63,7 +84,7 @@ func main() {
} }
container := docker.Container{ container := docker.Container{
App: app, App: &app,
} }
err = container.Create() err = container.Create()
@ -100,7 +121,7 @@ func main() {
app = *appPointer app = *appPointer
container := docker.Container{ container := docker.Container{
App: app, App: &app,
} }
err = container.Destroy() err = container.Destroy()
@ -123,7 +144,23 @@ func main() {
// Stop existing app // Stop existing app
e.PUT("/v1/apps/:name/stop", func(c echo.Context) error { e.PUT("/v1/apps/:name/stop", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{}) name := c.Param("name")
app, err := apps.Get(name)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
container := docker.Container{
App: app,
}
err = container.Stop()
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSON(http.StatusOK, Message{Message: "ok"})
}) })
// Start existing app // Start existing app
@ -136,7 +173,7 @@ func main() {
} }
container := docker.Container{ container := docker.Container{
App: *app, App: app,
} }
err = container.Start() err = container.Start()
@ -149,19 +186,92 @@ func main() {
// Stop existing app // Stop existing app
e.PUT("/v1/apps/:name/restart", func(c echo.Context) error { e.PUT("/v1/apps/:name/restart", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{}) name := c.Param("name")
app, err := apps.Get(name)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
container := docker.Container{
App: app,
}
err = container.Restart()
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSON(http.StatusOK, Message{Message: "ok"})
})
// Stats for existing app
e.GET("/v1/apps/:name/stats", func(c echo.Context) error {
name := c.Param("name")
app, err := apps.Get(name)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
container := docker.Container{
App: app,
}
cpu, memory, err := container.ResourceUsage()
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
log.Println("DEBUG", cpu, memory)
return c.JSON(http.StatusOK, Message{Message: "ok"})
}) })
// Rebuilds existing app, it keeps the data but created the container again // Rebuilds existing app, it keeps the data but created the container again
e.PUT("/v1/apps/:name/rebuild", func(c echo.Context) error { e.PUT("/v1/apps/:name/rebuild", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{}) name := c.Param("name")
app, err := apps.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 {
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)
}
return c.JSON(http.StatusOK, Message{Message: "ok"})
}) })
// Delete one app // Delete one app
e.DELETE("/v1/apps/:name", func(c echo.Context) error { e.DELETE("/v1/apps/:name", func(c echo.Context) error {
name := c.Param("name") name := c.Param("name")
err := apps.Delete(name) app, err := apps.Get(name)
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
container := docker.Container{
App: app,
}
err = container.Delete()
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
err = apps.Delete(name)
if err != nil { if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent) return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
} }