b1757dc4f6693dd5f531696214c76690b0206f60 — Louis Solofrizzo 2 months ago a800477
csc, sdk, api: Add resize and re-type to the instances API

Signed-off-by: Louis Solofrizzo <lsolofrizzo@online.net>
M api/config.go => api/config.go +14 -13
@@ 7,19 7,20 @@ )
  
  type CiscoConfig struct {
- 	Driver       string `yaml:"driver"`
- 	Database     string `yaml:"database"`
- 	Address      string `yaml:"address"`
- 	SMTPHost     string `yaml:"smtp_host"`
- 	SMTPUser     string `yaml:"smtp_user"`
- 	SMTPPassword string `yaml:"smtp_password"`
- 	Host         string `yaml:"host"`
- 	Salt         string `yaml:"salt"`
- 	LXDCert      string `yaml:"lxd_cert"`
- 	LXDKey       string `yaml:"lxd_key"`
- 	LXDCA        string `yaml:"lxd_ca"`
- 	LXDUrl       string `yaml:"lxd_url"`
- 	HTTPHost     string
+ 	Driver         string `yaml:"driver"`
+ 	Database       string `yaml:"database"`
+ 	Address        string `yaml:"address"`
+ 	SMTPHost       string `yaml:"smtp_host"`
+ 	SMTPUser       string `yaml:"smtp_user"`
+ 	SMTPPassword   string `yaml:"smtp_password"`
+ 	Host           string `yaml:"host"`
+ 	Salt           string `yaml:"salt"`
+ 	LXDCert        string `yaml:"lxd_cert"`
+ 	LXDKey         string `yaml:"lxd_key"`
+ 	LXDCA          string `yaml:"lxd_ca"`
+ 	LXDUrl         string `yaml:"lxd_url"`
+ 	LXDStoragePool string `yaml:"lxd_storage_pool"`
+ 	HTTPHost       string
  }
  
  type CiscoContext struct {

M api/instance.go => api/instance.go +159 -3
@@ 51,6 51,40 @@ ctx.JSON(&result)
  }
  
+ func instance_get(ctx iris.Context) {
+ 	var entry DbInstance
+ 	var result cisco.Instance
+ 	var ipv6 string
+ 
+ 	user := ctx.Values().Get("User")
+ 	instance := ctx.Params().Get("name")
+ 	DB.First(&entry, "user = ? AND name = ?", user, instance)
+ 
+ 	inst, _, _ := Context.LXD.GetInstanceState("LF" + entry.Uuid)
+ 
+ 	for _, net := range inst.Network {
+ 		for _, addr := range net.Addresses {
+ 			if addr.Family == "inet6" && !strings.HasPrefix(addr.Address, "fe80") && addr.Scope != "local" {
+ 				ipv6 = addr.Address
+ 				break
+ 			}
+ 		}
+ 	}
+ 
+ 	result = cisco.Instance{
+ 		Uuid:         entry.Uuid,
+ 		Name:         entry.Name,
+ 		Size:         entry.Size,
+ 		OS:           entry.OS,
+ 		Architecture: entry.Architecture,
+ 		Status:       inst.Status,
+ 		Ipv6:         ipv6,
+ 		Type:         entry.Type,
+ 	}
+ 
+ 	ctx.JSON(&result)
+ }
+ 
  func ArchitectureIsValid(ctx iris.Context, name string) error {
  	var Architectures []string = []string{
  		"x86_64",


@@ 98,6 132,14 @@ "limits.cpu":    strconv.Itoa(instanceType.CPU),
  				"limits.memory": strconv.Itoa(instanceType.Memory) + "MB",
  			},
+ 			Devices: map[string]map[string]string{
+ 				"root": map[string]string{
+ 					"path": "/",
+ 					"pool": Config.LXDStoragePool,
+ 					"size": strconv.Itoa(int(instance.Size)) + "GB",
+ 					"type": "disk",
+ 				},
+ 			},
  		},
  		Source: api.ContainerSource{
  			Type:  "image",


@@ 159,11 201,17 @@ return
  	}
  
- 	if !CheckStorageQuotas(ctx, 10) {
+ 	if !CheckSSHKeys(ctx) {
  		return
  	}
  
- 	if !CheckSSHKeys(ctx) {
+ 	if instance.Size == 0 {
+ 		entry.Size = 10
+ 	} else {
+ 		entry.Size = instance.Size
+ 	}
+ 
+ 	if !CheckStorageQuotas(ctx, entry.Size) {
  		return
  	}
  


@@ 171,7 219,6 @@ entry.Name = instance.Name
  	entry.Type = instance.Type
  	entry.OS = instance.OS
- 	entry.Size = 10
  	entry.User, _ = ctx.Values().GetUint("User")
  
  	if createBackendInstance(entry) != nil {


@@ 366,6 413,113 @@ ctx.JSON(&result)
  }
  
+ func instance_size(ctx iris.Context) {
+ 	var entry DbInstance
+ 	var size cisco.InstanceSizeUpdate
+ 
+ 	instance := ctx.Params().Get("name")
+ 	user := ctx.Values().Get("User")
+ 
+ 	ctx.ReadJSON(&size)
+ 
+ 	if size.Size == 0 {
+ 		APIErrorField(ctx, "size")
+ 		return
+ 	}
+ 
+ 	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
+ 		}
+ 	}
+ 
+ 	if size.Size <= entry.Size {
+ 		APIError(ctx, 400, "You cannot shrink a disk.")
+ 		return
+ 	}
+ 
+ 	inst, _, err := Context.LXD.GetContainer("LF" + entry.Uuid)
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	inst.ContainerPut.Devices["root"]["size"] = strconv.Itoa(int(size.Size)) + "GB"
+ 
+ 	op, err := Context.LXD.UpdateContainer("LF"+entry.Uuid, inst.ContainerPut, "")
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	err = op.Wait()
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	DB.Model(&entry).Update("size", size.Size)
+ }
+ 
+ func instance_type(ctx iris.Context) {
+ 	var instanceType DbInstanceType
+ 	var entry DbInstance
+ 	var inst_type cisco.InstanceTypeUpdate
+ 
+ 	instance := ctx.Params().Get("name")
+ 	user := ctx.Values().Get("User")
+ 
+ 	ctx.ReadJSON(&inst_type)
+ 
+ 	if inst_type.Type == "" {
+ 		APIErrorField(ctx, "type")
+ 		return
+ 	}
+ 
+ 	DB.First(&instanceType, "name = ?", inst_type.Type)
+ 	if instanceType.ID == 0 {
+ 		APIError(ctx, 400, "The specified Instance Type is not valid")
+ 		return
+ 	}
+ 
+ 	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.GetContainer("LF" + entry.Uuid)
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	inst.ContainerPut.Config["limits.cpu"] = strconv.Itoa(instanceType.CPU)
+ 	inst.ContainerPut.Config["limits.memory"] = strconv.Itoa(instanceType.Memory) + "MB"
+ 
+ 	op, err := Context.LXD.UpdateContainer("LF"+entry.Uuid, inst.ContainerPut, "")
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	err = op.Wait()
+ 	if err != nil {
+ 		APIError(ctx, 503, "Error: "+err.Error())
+ 		return
+ 	}
+ 
+ 	DB.Model(&entry).Update("type", instanceType.Name)
+ 
+ }
+ 
  func InitInstanceRoutes(app *iris.Application) {
  	app.Get("/instance", APIAuth, instance_list)
  	app.Post("/instance", APIAuth, instance_new)


@@ 375,4 529,6 @@ app.Get("/instance/keys/{name:string}", instance_keys)
  	app.Get("/instance/name/{name:string}", instance_name)
  	app.Get("/instance/types", APIAuth, instance_types)
+ 	app.Put("/instance/size/{name:string}", APIAuth, instance_size)
+ 	app.Put("/instance/type/{name:string}", APIAuth, instance_type)
  }

M csc/cmd/instance/new.go => csc/cmd/instance/new.go +3 -0
@@ 15,11 15,13 @@ name := args[0]
  		os := args[1]
  		model := args[2]
+ 		size, _ := cmd.Flags().GetUint("size")
  
  		err := cisco.NewInstance(cisco.InstanceNew{
  			Name: name,
  			OS:   os,
  			Type: model,
+ 			Size: size,
  		})
  
  		if err != nil {


@@ 29,5 31,6 @@ }
  
  func init() {
+ 	instanceNew.Flags().Uint("size", 0, "Instance disk size (GB)")
  	cmd.InstanceRootCmd.AddCommand(instanceNew)
  }

A csc/cmd/instance/resize.go => csc/cmd/instance/resize.go +37 -0
@@ 0,0 1,37 @@
+ package instance
+ 
+ import (
+ 	"cisco/csc/cmd"
+ 	"cisco/sdk"
+ 	"fmt"
+ 	"github.com/spf13/cobra"
+ 	"strconv"
+ )
+ 
+ var instanceResize = &cobra.Command{
+ 	Use:   "resize",
+ 	Short: "Resize an instance",
+ 	Args:  cobra.ExactArgs(2),
+ 	Run: func(cmd *cobra.Command, args []string) {
+ 		name := args[0]
+ 		size, err := strconv.Atoi(args[1])
+ 
+ 		if err != nil {
+ 			fmt.Printf("Error: %s\n", err.Error())
+ 			return
+ 		}
+ 
+ 		err = cisco.ResizeInstance(name, uint(size))
+ 
+ 		if err != nil {
+ 			fmt.Printf("Error: %s\n", err.Error())
+ 			return
+ 		}
+ 
+ 		fmt.Printf("Done! You need to restart the instance in order to use the new disk space.\n")
+ 	},
+ }
+ 
+ func init() {
+ 	cmd.InstanceRootCmd.AddCommand(instanceResize)
+ }

A csc/cmd/instance/set-type.go => csc/cmd/instance/set-type.go +28 -0
@@ 0,0 1,28 @@
+ package instance
+ 
+ import (
+ 	"cisco/csc/cmd"
+ 	"cisco/sdk"
+ 	"fmt"
+ 	"github.com/spf13/cobra"
+ )
+ 
+ var instanceSetType = &cobra.Command{
+ 	Use:   "set-type",
+ 	Short: "Changes the type of an instance",
+ 	Args:  cobra.ExactArgs(2),
+ 	Run: func(cmd *cobra.Command, args []string) {
+ 		err := cisco.SetTypeInstance(args[0], args[1])
+ 
+ 		if err != nil {
+ 			fmt.Printf("Error: %s\n", err.Error())
+ 			return
+ 		}
+ 
+ 		//fmt.Printf("Done! You need to restart the instance in order to use the new disk space.\n")
+ 	},
+ }
+ 
+ func init() {
+ 	cmd.InstanceRootCmd.AddCommand(instanceSetType)
+ }

M sdk/instance.go => sdk/instance.go +31 -0
@@ 44,6 44,7 @@ Name string `json:"name"`
  	OS   string `json:"os"`
  	Type string `json:"type"`
+ 	Size uint   `json:"size"`
  }
  
  func NewInstance(instance InstanceNew) error {


@@ 209,3 210,33 @@ err = json.Unmarshal(data.([]byte), &result)
  	return result, err
  }
+ 
+ type InstanceSizeUpdate struct {
+ 	Size uint `json:"size"`
+ }
+ 
+ func ResizeInstance(name string, size uint) error {
+ 	_, err := SendRequest(InstanceResizeRoute, Request{
+ 		Arg: name,
+ 		Data: &InstanceSizeUpdate{
+ 			Size: size,
+ 		},
+ 	})
+ 
+ 	return err
+ }
+ 
+ type InstanceTypeUpdate struct {
+ 	Type string `json:"type"`
+ }
+ 
+ func SetTypeInstance(name string, Itype string) error {
+ 	_, err := SendRequest(InstanceTypeRoute, Request{
+ 		Arg: name,
+ 		Data: &InstanceTypeUpdate{
+ 			Type: Itype,
+ 		},
+ 	})
+ 
+ 	return err
+ }

M sdk/request.go => sdk/request.go +12 -0
@@ 164,6 164,18 @@ Protected: true,
  }
  
+ var InstanceResizeRoute Route = Route{
+ 	Path:      "/instance/size",
+ 	Method:    "PUT",
+ 	Protected: true,
+ }
+ 
+ var InstanceTypeRoute Route = Route{
+ 	Path:      "/instance/type",
+ 	Method:    "PUT",
+ 	Protected: true,
+ }
+ 
  var InstanceTypesRoute Route = Route{
  	Path:      "/instance/types",
  	Method:    "GET",