b98475b4284bbc1465f8604d28abca068e88fd2f — Louis Solofrizzo 10 months ago f793954
csc, sdk, api: Add routes and command to create, start, stop and delete instances

Signed-off-by: Louis Solofrizzo <lsolofrizzo@online.net>
M api/cron.go => api/cron.go +5 -3
@@ 22,11 22,12 @@   		if !found {
  			op, err := Context.LXD.ExecContainer("LF"+ip.Instance, api.ContainerExecPost{
- 				Command: []string{"/usr/bin/ip", "addr", "add", ip.IP + "/32", "dev", "eth0"},
+ 				Command: []string{"/usr/bin/env", "ip", "addr", "add", ip.IP + "/32", "dev", "eth0"},
  			}, nil)
  
  			if err != nil {
  				fmt.Printf("Error: %s", err.Error())
+ 				continue
  			}
  
  			err = op.Wait()


@@ 42,18 43,19 @@   	DB.Find(&entries)
  	for _, instance := range entries {
+ 		fmt.Printf("Setting hostname '%s' to instance 'LF%s'\n", instance.Name, instance.Uuid)
  		op, err := Context.LXD.ExecContainer("LF"+instance.Uuid, api.ContainerExecPost{
- 			Command: []string{"/usr/bin/hostname", instance.Name},
+ 			Command: []string{"/usr/bin/env", "hostname", instance.Name},
  		}, nil)
  
  		if err != nil {
  			fmt.Printf("Error: %s", err.Error())
+ 			continue
  		}
  
  		err = op.Wait()
  		if err != nil {
  			fmt.Printf("Error: %s", err.Error())
  		}
- 
  	}
  }

M api/instance.go => api/instance.go +294 -0
@@ 2,7 2,11 @@   import (
  	"cisco/sdk"
+ 	"errors"
+ 	"fmt"
  	"github.com/kataras/iris"
+ 	"github.com/lxc/lxd/shared/api"
+ 	"github.com/satori/go.uuid"
  	"strings"
  )
  


@@ 16,6 20,7 @@   	for _, instance := range entries {
  		var ipv4 DbIpv4
+ 		ipv6 = ""
  
  		DB.Find(&ipv4, "instance = ?", instance.Uuid)
  		inst, _, _ := Context.LXD.GetInstanceState("LF" + instance.Uuid)


@@ 44,6 49,295 @@ ctx.JSON(&result)
  }
  
+ func OSIsValid(ctx iris.Context, name string) error {
+ 	var validOs []string = []string{
+ 		"alpine/3.10",
+ 		"alpine/3.7",
+ 		"alpine/3.8",
+ 		"alpine/3.9",
+ 		"alpine/edge",
+ 		"alt/p8",
+ 		"alt/p9",
+ 		"alt/Sisyphus",
+ 		"apertis/17.12",
+ 		"apertis/18.03",
+ 		"apertis/18.06",
+ 		"apertis/18.09",
+ 		"apertis/18.12",
+ 		"archlinux/current",
+ 		"centos/6",
+ 		"centos/7",
+ 		"centos/8",
+ 		"debian/bullseye",
+ 		"debian/buster",
+ 		"debian/jessie",
+ 		"debian/sid",
+ 		"debian/stretch",
+ 		"devuan/ascii",
+ 		"fedora/29",
+ 		"fedora/30",
+ 		"fedora/31",
+ 		"funtoo/1.3",
+ 		"gentoo/current",
+ 		"kali/current",
+ 		"mint/sarah",
+ 		"mint/serena",
+ 		"mint/sonya",
+ 		"mint/sylvia",
+ 		"mint/tara",
+ 		"mint/tessa",
+ 		"mint/tina",
+ 		"opensuse/15.0",
+ 		"opensuse/15.1",
+ 		"opensuse/tumbleweed",
+ 		"openwrt/18.06",
+ 		"openwrt/current",
+ 		"openwrt/snapshot",
+ 		"oracle/6",
+ 		"oracle/7",
+ 		"plamo/6.x",
+ 		"plamo/7.x",
+ 		"sabayon/current",
+ 		"ubuntu/bionic",
+ 		"ubuntu-core/16",
+ 		"ubuntu/cosmic",
+ 		"ubuntu/disco",
+ 		"ubuntu/eoan",
+ 		"ubuntu/trusty",
+ 		"ubuntu/xenial",
+ 		"voidlinux/current",
+ 	}
+ 
+ 	for _, v := range validOs {
+ 		if v == name {
+ 			return nil
+ 		}
+ 	}
+ 
+ 	APIError(ctx, 400, "The specified OS is not valid")
+ 	return errors.New("The specified OS is not valid")
+ }
+ 
+ func ArchitectureIsValid(ctx iris.Context, name string) error {
+ 	var Architectures []string = []string{
+ 		"intel",
+ 	}
+ 
+ 	for _, v := range Architectures {
+ 		if v == name {
+ 			return nil
+ 		}
+ 	}
+ 
+ 	APIError(ctx, 400, "The specified Architecture is not valid")
+ 	return errors.New("The specified Architecture is not valid")
+ }
+ 
+ func createBackendInstance(instance DbInstance) error {
+ 	op, err := Context.LXD.CreateContainer(api.ContainersPost{
+ 		Name: "LF" + instance.Uuid,
+ 		Source: api.ContainerSource{
+ 			Type:     "image",
+ 			Alias:    instance.OS,
+ 			Server:   "https://images.linuxcontainers.org",
+ 			Protocol: "simplestreams",
+ 		},
+ 	})
+ 
+ 	if err != nil {
+ 		fmt.Printf("Error: %s\n", err.Error())
+ 		return err
+ 	}
+ 
+ 	err = op.Wait()
+ 	if err != nil {
+ 		fmt.Printf("Error: %s\n", err.Error())
+ 		return err
+ 	}
+ 
+ 	return nil
+ }
+ 
+ func instance_new(ctx iris.Context) {
+ 	var instance cisco.InstanceNew
+ 	var entry DbInstance
+ 
+ 	ctx.ReadJSON(&instance)
+ 	if instance.Name == "" {
+ 		APIErrorField(ctx, "name")
+ 		return
+ 	}
+ 
+ 	if instance.OS == "" {
+ 		APIErrorField(ctx, "os")
+ 		return
+ 	}
+ 
+ 	if instance.Architecture == "" {
+ 		APIErrorField(ctx, "architecture")
+ 		return
+ 	}
+ 
+ 	if OSIsValid(ctx, instance.OS) != nil {
+ 		return
+ 	}
+ 
+ 	if ArchitectureIsValid(ctx, instance.Architecture) != nil {
+ 		return
+ 	}
+ 
+ 	user := ctx.Values().Get("User")
+ 
+ 	DB.First(&entry, "name = ? AND user = ?", instance.Name, user)
+ 	if entry.ID != 0 {
+ 		APIError(ctx, 409, "The specified instance name already exists")
+ 		return
+ 	}
+ 
+ 	// XXX: quota
+ 
+ 	entry.Uuid = uuid.NewV4().String()
+ 	entry.Name = instance.Name
+ 	entry.Architecture = instance.Architecture
+ 	entry.OS = instance.OS
+ 	entry.Size = 10
+ 	entry.Type = "LXC"
+ 	entry.User, _ = ctx.Values().GetUint("User")
+ 
+ 	if createBackendInstance(entry) != nil {
+ 		APIError(ctx, 500, "Cannot create instance")
+ 		return
+ 	}
+ 
+ 	DB.Create(&entry)
+ 	ctx.StatusCode(iris.StatusCreated)
+ }
+ 
+ func instance_start(ctx iris.Context) {
+ 	var entry DbInstance
+ 
+ 	instance := ctx.Params().Get("name")
+ 	user := ctx.Values().Get("User")
+ 
+ 	DB.First(&entry, "name = ? AND user = ?", instance, user)
+ 
+ 	if entry.ID == 0 {
+ 		DB.First(&entry, "uuid = ? AND user = ?", instance, user)
+ 		if entry.ID == 0 {
+ 			APIError(ctx, 404, "Can't find the specified instance")
+ 			return
+ 		}
+ 	}
+ 
+ 	inst, _, err := Context.LXD.GetInstanceState("LF" + entry.Uuid)
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	if inst.Status == "Running" {
+ 		APIError(ctx, 400, "The instance is already started")
+ 		return
+ 	}
+ 
+ 	reqState := api.ContainerStatePut{
+ 		Action:  "start",
+ 		Timeout: -1,
+ 	}
+ 
+ 	op, err := Context.LXD.UpdateContainerState("LF"+entry.Uuid, reqState, "")
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	err = op.Wait()
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ }
+ 
+ func instance_stop(ctx iris.Context) {
+ 	var entry DbInstance
+ 
+ 	instance := ctx.Params().Get("name")
+ 	user := ctx.Values().Get("User")
+ 
+ 	DB.First(&entry, "name = ? AND user = ?", instance, user)
+ 
+ 	if entry.ID == 0 {
+ 		DB.First(&entry, "uuid = ? AND user = ?", instance, user)
+ 		if entry.ID == 0 {
+ 			APIError(ctx, 404, "Can't find the specified instance")
+ 			return
+ 		}
+ 	}
+ 
+ 	inst, _, err := Context.LXD.GetInstanceState("LF" + entry.Uuid)
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	if inst.Status == "Stopped" {
+ 		APIError(ctx, 400, "The instance is already stopped")
+ 		return
+ 	}
+ 
+ 	reqState := api.ContainerStatePut{
+ 		Action:  "stop",
+ 		Timeout: -1,
+ 	}
+ 
+ 	op, err := Context.LXD.UpdateContainerState("LF"+entry.Uuid, reqState, "")
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	err = op.Wait()
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ }
+ 
+ func instance_delete(ctx iris.Context) {
+ 	var entry DbInstance
+ 
+ 	instance := ctx.Params().Get("name")
+ 	user := ctx.Values().Get("User")
+ 
+ 	DB.First(&entry, "name = ? AND user = ?", instance, user)
+ 
+ 	if entry.ID == 0 {
+ 		DB.First(&entry, "uuid = ? AND user = ?", instance, user)
+ 		if entry.ID == 0 {
+ 			APIError(ctx, 404, "Can't find the specified instance")
+ 			return
+ 		}
+ 	}
+ 
+ 	op, err := Context.LXD.DeleteContainer("LF" + entry.Uuid)
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	err = op.Wait()
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	DB.Delete(&entry)
+ }
+ 
  func InitInstanceRoutes(app *iris.Application) {
  	app.Get("/instance", APIAuth, instance_list)
+ 	app.Post("/instance", APIAuth, instance_new)
+ 	app.Put("/instance/start/{name:string}", APIAuth, instance_start)
+ 	app.Put("/instance/stop/{name:string}", APIAuth, instance_stop)
+ 	app.Delete("/instance/{name:string}", APIAuth, instance_delete)
  }

A csc/cmd/instance/delete.go => csc/cmd/instance/delete.go +27 -0
@@ 0,0 1,27 @@
+ package instance
+ 
+ import (
+ 	"cisco/csc/cmd"
+ 	"cisco/sdk"
+ 	"fmt"
+ 	"github.com/spf13/cobra"
+ )
+ 
+ var instanceDelete = &cobra.Command{
+ 	Use:   "delete",
+ 	Short: "Delete an instance",
+ 	Args:  cobra.ExactArgs(1),
+ 	Run: func(cmd *cobra.Command, args []string) {
+ 		name := args[0]
+ 
+ 		err := cisco.DeleteInstance(name)
+ 
+ 		if err != nil {
+ 			fmt.Printf("Error: %s\n", err.Error())
+ 		}
+ 	},
+ }
+ 
+ func init() {
+ 	cmd.InstanceRootCmd.AddCommand(instanceDelete)
+ }

A csc/cmd/instance/new.go => csc/cmd/instance/new.go +32 -0
@@ 0,0 1,32 @@
+ package instance
+ 
+ import (
+ 	"cisco/csc/cmd"
+ 	"cisco/sdk"
+ 	"fmt"
+ 	"github.com/spf13/cobra"
+ )
+ 
+ var instanceNew = &cobra.Command{
+ 	Use:   "new",
+ 	Short: "Create a new instance",
+ 	Args:  cobra.ExactArgs(2),
+ 	Run: func(cmd *cobra.Command, args []string) {
+ 		name := args[0]
+ 		os := args[1]
+ 
+ 		err := cisco.NewInstance(cisco.InstanceNew{
+ 			Name:         name,
+ 			OS:           os,
+ 			Architecture: "intel",
+ 		})
+ 
+ 		if err != nil {
+ 			fmt.Printf("Error: %s\n", err.Error())
+ 		}
+ 	},
+ }
+ 
+ func init() {
+ 	cmd.InstanceRootCmd.AddCommand(instanceNew)
+ }

A csc/cmd/instance/start.go => csc/cmd/instance/start.go +27 -0
@@ 0,0 1,27 @@
+ package instance
+ 
+ import (
+ 	"cisco/csc/cmd"
+ 	"cisco/sdk"
+ 	"fmt"
+ 	"github.com/spf13/cobra"
+ )
+ 
+ var instanceStart = &cobra.Command{
+ 	Use:   "start",
+ 	Short: "Start an instance",
+ 	Args:  cobra.ExactArgs(1),
+ 	Run: func(cmd *cobra.Command, args []string) {
+ 		name := args[0]
+ 
+ 		err := cisco.StartInstance(name)
+ 
+ 		if err != nil {
+ 			fmt.Printf("Error: %s\n", err.Error())
+ 		}
+ 	},
+ }
+ 
+ func init() {
+ 	cmd.InstanceRootCmd.AddCommand(instanceStart)
+ }

A csc/cmd/instance/stop.go => csc/cmd/instance/stop.go +27 -0
@@ 0,0 1,27 @@
+ package instance
+ 
+ import (
+ 	"cisco/csc/cmd"
+ 	"cisco/sdk"
+ 	"fmt"
+ 	"github.com/spf13/cobra"
+ )
+ 
+ var instanceStop = &cobra.Command{
+ 	Use:   "stop",
+ 	Short: "Stop an instance",
+ 	Args:  cobra.ExactArgs(1),
+ 	Run: func(cmd *cobra.Command, args []string) {
+ 		name := args[0]
+ 
+ 		err := cisco.StopInstance(name)
+ 
+ 		if err != nil {
+ 			fmt.Printf("Error: %s\n", err.Error())
+ 		}
+ 	},
+ }
+ 
+ func init() {
+ 	cmd.InstanceRootCmd.AddCommand(instanceStop)
+ }

M sdk/instance.go => sdk/instance.go +38 -0
@@ 30,3 30,41 @@ err = json.Unmarshal(data.([]byte), &result)
  	return result, err
  }
+ 
+ type InstanceNew struct {
+ 	Name         string `json:"name"`
+ 	OS           string `json:"os"`
+ 	Architecture string `json:"architecture"`
+ }
+ 
+ func NewInstance(instance InstanceNew) error {
+ 	_, err := SendRequest(InstanceNewRoute, Request{
+ 		Data: instance,
+ 	})
+ 
+ 	return err
+ }
+ 
+ func StartInstance(name string) error {
+ 	_, err := SendRequest(InstanceStartRoute, Request{
+ 		Arg: name,
+ 	})
+ 
+ 	return err
+ }
+ 
+ func StopInstance(name string) error {
+ 	_, err := SendRequest(InstanceStopRoute, Request{
+ 		Arg: name,
+ 	})
+ 
+ 	return err
+ }
+ 
+ func DeleteInstance(name string) error {
+ 	_, err := SendRequest(InstanceDeleteRoute, Request{
+ 		Arg: name,
+ 	})
+ 
+ 	return err
+ }

M sdk/request.go => sdk/request.go +24 -0
@@ 140,6 140,30 @@ Protected: true,
  }
  
+ var InstanceNewRoute Route = Route{
+ 	Path:      "/instance",
+ 	Method:    "POST",
+ 	Protected: true,
+ }
+ 
+ var InstanceStartRoute Route = Route{
+ 	Path:      "/instance/start",
+ 	Method:    "PUT",
+ 	Protected: true,
+ }
+ 
+ var InstanceStopRoute Route = Route{
+ 	Path:      "/instance/stop",
+ 	Method:    "PUT",
+ 	Protected: true,
+ }
+ 
+ var InstanceDeleteRoute Route = Route{
+ 	Path:      "/instance",
+ 	Method:    "DELETE",
+ 	Protected: true,
+ }
+ 
  type Request struct {
  	Arg  string
  	Data interface{}