cisco

Home Shitty cloud provider
Log | Files | Refs | Submodules | git clone https://git.ne02ptzero.me/git/cisco

commit 2152b93f5b24996a40312c46353f621941873c17
parent e48f2368fb7d1eed05dec88a7822b7edf7527526
Author: Louis Solofrizzo <louis@ne02ptzero.me>
Date:   Sat, 30 Mar 2019 15:19:16 +0100

Slave: Add slave

Signed-off-by: Louis Solofrizzo <louis@ne02ptzero.me>

Diffstat:
Aslave/CMakeLists.txt | 13+++++++++++++
Aslave/config.go | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aslave/instance.go | 375+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aslave/main.go | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aslave/test.json | 6++++++
5 files changed, 535 insertions(+), 0 deletions(-)

diff --git a/slave/CMakeLists.txt b/slave/CMakeLists.txt @@ -0,0 +1,13 @@ +add_go_component(lf-slave + main.go + instance.go + config.go +) + +add_arm_go_component(lf-slave-arm + main.go + instance.go + config.go +) + +add_dependencies(lf-slave-arm lxc sqlite3) diff --git a/slave/config.go b/slave/config.go @@ -0,0 +1,68 @@ +package main + +import ( + "flag" + "github.com/go-xorm/xorm" + _ "github.com/mattn/go-sqlite3" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" + "io/ioutil" +) + +type SlaveConfig struct { + Db string `yaml:"db" binding:"required"` + Port string `yaml:"port" binding:"required"` +} + +var Database *xorm.Engine +var Binds *xorm.Engine +var Config SlaveConfig + +func setConfig() error { + var err error + filename := flag.String("config", "/etc/lf-slave.conf", "The yaml configuration file") + flag.Parse() + + source, err := ioutil.ReadFile(*filename) + if err != nil { + return err + } + err = yaml.Unmarshal(source, &Config) + if err != nil { + return err + } + + Database, err = xorm.NewEngine("sqlite3", Config.Db) + if err != nil { + log.Fatal(err) + return err + } + + if Database == nil { + log.Fatal("db is nil") + } + + err = Database.Sync2(new(Instance)) + if err != nil { + log.Fatal(err) + } + + Binds, err = xorm.NewEngine("sqlite3", Config.Db) + if err != nil { + log.Fatal(err) + return err + } + + if Binds == nil { + log.Fatal("db is nil") + } + + err = Binds.Sync2(new(InstanceBindEntry)) + if err != nil { + log.Fatal(err) + } + + log.Info("Db created") + + return nil +} diff --git a/slave/instance.go b/slave/instance.go @@ -0,0 +1,375 @@ +package main + +import ( + "net" + "os/exec" + "strconv" + + "github.com/kataras/iris" + "github.com/phayes/freeport" + log "github.com/sirupsen/logrus" + + "cisco/contrib/go-lxc" +) + +type InstanceAdd struct { + Distro string `json:"distro"` + Release string `json:"release"` + Name string `json:"name"` + Arch string `json:"arch"` +} + +type Instance struct { + Name string `json:"name xorm:"varchar(200)"` + Status lxc.State `json:"status" xorm:"int"` + IPv4 []string `json:"IPv4"` + IPv6 []string `json:"IPv6"` + Gateway string `json:"gateway"` +} + +func instance_create(ctx iris.Context) { + var instance InstanceAdd + + ctx.ReadJSON(&instance) + c, err := lxc.NewContainer(instance.Name, lxc.DefaultConfigPath()) + if err != nil { + log.Errorf("Error on new container: %s", err) + ctx.StatusCode(iris.StatusInternalServerError) + return + } + + defer c.Release() + c.SetVerbosity(lxc.Verbose) + + options := lxc.TemplateOptions{ + Template: "download", + Distro: instance.Distro, + Release: instance.Release, + Arch: instance.Arch, + Backend: lxc.RBD, + } + + log.Infof("Creating container %v", options) + + if err := c.Create(options); err != nil { + log.Errorf("Cannot create container: %s", err) + ctx.StatusCode(iris.StatusAlreadyReported) + } else { + Database.Insert(&Instance{ + Name: instance.Name, + Status: c.State(), + }) + ctx.StatusCode(iris.StatusCreated) + } +} + +func instance_get(ctx iris.Context) { + name := ctx.Params().Get("name") + c, err := lxc.NewContainer(name, lxc.DefaultConfigPath()) + + if err != nil { + log.Errorf("Error on new container: %s", err) + ctx.StatusCode(iris.StatusInternalServerError) + } + + defer c.Release() + if !c.Defined() { + ctx.StatusCode(iris.StatusNotFound) + return + } + + ip, _ := c.IPAddress("eth0") + ipv6, _ := c.IPv6Addresses() + _, gateway := get_local_ip(1) + + ret := Instance{ + Name: c.Name(), + Status: c.State(), + IPv4: ip, + IPv6: ipv6, + Gateway: gateway, + } + + ctx.JSON(&ret) +} + +func instance_delete(ctx iris.Context) { + var instance Instance + var instancebinds []InstanceBindEntry + + name := ctx.Params().Get("name") + c, err := lxc.NewContainer(name, lxc.DefaultConfigPath()) + + if err != nil { + log.Errorf("Error on new container: %s", err) + ctx.StatusCode(iris.StatusInternalServerError) + } + + defer c.Release() + if !c.Defined() { + ctx.StatusCode(iris.StatusNotFound) + return + } + + err = c.Stop() + if err != nil { + log.Errorf("Could not stop instance") + ctx.StatusCode(iris.StatusInternalServerError) + } + + err = c.Destroy() + if err != nil { + log.Errorf("Destroy failed: %s", err) + ctx.StatusCode(iris.StatusInternalServerError) + } + + Binds.Where("Name = ?", name).Find(&instancebinds) + for _, bind := range instancebinds { + run := exec.Command("/bin/bash", "-c", bind.IpTableDel) + run.Run() + Database.Delete(&bind) + } + + Database.Where("Name = ?", name).Delete(&instance) +} + +func instance_start(ctx iris.Context) { + name := ctx.Params().Get("name") + c, err := lxc.NewContainer(name, lxc.DefaultConfigPath()) + + if err != nil { + log.Errorf("Error on new container: %s", err) + ctx.StatusCode(iris.StatusInternalServerError) + } + + defer c.Release() + if !c.Defined() { + ctx.StatusCode(iris.StatusNotFound) + return + } + + err = c.Start() + if err != nil { + log.Errorf("Error starting the container: %s", err) + } + Database.Cols("Status").Update(&Instance{Name: name, Status: c.State()}) +} + +func instance_reboot(ctx iris.Context) { + name := ctx.Params().Get("name") + c, err := lxc.NewContainer(name, lxc.DefaultConfigPath()) + + if err != nil { + log.Errorf("Error on new container: %s", err) + ctx.StatusCode(iris.StatusInternalServerError) + } + + defer c.Release() + if !c.Defined() { + ctx.StatusCode(iris.StatusNotFound) + return + } + + c.Stop() + c.Start() + Database.Cols("Status").Update(&Instance{Name: name, Status: c.State()}) +} + +func instance_stop(ctx iris.Context) { + name := ctx.Params().Get("name") + c, err := lxc.NewContainer(name, lxc.DefaultConfigPath()) + + if err != nil { + log.Errorf("Error on new container: %s", err) + ctx.StatusCode(iris.StatusInternalServerError) + } + + defer c.Release() + if !c.Defined() { + ctx.StatusCode(iris.StatusNotFound) + return + } + + c.Stop() + Database.Cols("Status").Update(&Instance{Name: name, Status: c.State()}) +} + +type InstanceExec struct { + Cmd []string `json:"cmd"` +} + +func instance_exec(ctx iris.Context) { + var instance InstanceExec + + name := ctx.Params().Get("name") + ctx.ReadJSON(&instance) + + log.Printf("%v", instance) + + c, err := lxc.NewContainer(name, lxc.DefaultConfigPath()) + + if err != nil { + log.Errorf("Error on new container: %s", err) + ctx.StatusCode(iris.StatusInternalServerError) + } + + defer c.Release() + if !c.Defined() { + ctx.StatusCode(iris.StatusNotFound) + return + } + + c.SetVerbosity(lxc.Verbose) + + for _, cmd := range instance.Cmd { + log.Printf("Executing '%s'", cmd) + if _, err := c.RunCommand([]string{ + "bash", "-c", + cmd, + }, lxc.DefaultAttachOptions); err != nil { + log.Errorf("ERROR: %s\n", err.Error()) + ctx.StatusCode(iris.StatusInternalServerError) + } + } +} + +type InstanceBind struct { + IP string `json:"ip"` + Port int `json:"port"` +} + +func get_local_ip(id int) (string, string) { + ifaces, err := net.Interfaces() + + if err != nil { + log.Errorf("localAddresses: %+v", err) + } + + for _, i := range ifaces { + addrs, _ := i.Addrs() + if i.Name == "eth0" { + return "eth0", addrs[id].(*net.IPNet).IP.String() + } else if i.Name == "enp2s0" { + return "enp2s0", addrs[id].(*net.IPNet).IP.String() + } + } + + return "", "" +} + +type InstanceBindEntry struct { + Name string `xorm:"varchar(200)" json:"-"` + SourcePort int `xorm:"int" json:"source"` + NatPort int `xorm:"int" json:"nat"` + Iface string `json:"ip"` + IpTableDel string `json:"-"` + System bool `json:"system"` +} + +func instance_bind_local_port(ctx iris.Context) { + var ret InstanceBind + + name := ctx.Params().Get("name") + port, _ := ctx.Params().GetUint16("port") + c, err := lxc.NewContainer(name, lxc.DefaultConfigPath()) + + if err != nil { + log.Errorf("Error on new container: %s", err) + ctx.StatusCode(iris.StatusInternalServerError) + } + + defer c.Release() + if !c.Defined() { + ctx.StatusCode(iris.StatusNotFound) + return + } + + iface, ip := get_local_ip(0) + container_ip, _ := c.IPAddress("eth0") + to := container_ip[0] + ":" + strconv.Itoa(int(port)) + random_port, _ := freeport.GetFreePort() + + ret.IP = ip + ret.Port = random_port + + iptable_add_cmd := "iptables -t nat -I PREROUTING -p tcp -i " + iface + " --dport " + strconv.Itoa(random_port) + " -j DNAT --to " + to + + run := exec.Command("/bin/bash", "-c", iptable_add_cmd) + run.Run() + Binds.Insert(&InstanceBindEntry{ + Name: name, + SourcePort: int(port), + NatPort: random_port, + Iface: iface, + IpTableDel: "iptables -t nat -D PREROUTING -p tcp -i " + iface + " --dport " + strconv.Itoa(random_port) + " -j DNAT --to " + to, + }) + ctx.JSON(ret) +} + +func instance_bind_port(ctx iris.Context) { + var ret InstanceBind + + name := ctx.Params().Get("name") + port, _ := ctx.Params().GetUint16("port") + + c, err := lxc.NewContainer(name, lxc.DefaultConfigPath()) + + if err != nil { + log.Errorf("Error on new container: %s", err) + ctx.StatusCode(iris.StatusInternalServerError) + } + + defer c.Release() + if !c.Defined() { + ctx.StatusCode(iris.StatusNotFound) + return + } + + container_ip, _ := c.IPAddress("eth0") + to := container_ip[0] + ":" + strconv.Itoa(int(port)) + random_port, _ := freeport.GetFreePort() + + iface, _ := net.InterfaceByName("tun0") + ip, _ := iface.Addrs() + + ret.IP = ip[0].(*net.IPNet).IP.String() + ret.Port = random_port + + iptable_add_cmd := "iptables -t nat -I PREROUTING -p tcp -i tun0 --dport " + strconv.Itoa(random_port) + " -j DNAT --to " + to + + run := exec.Command("/bin/bash", "-c", iptable_add_cmd) + run.Run() + + Binds.Insert(&InstanceBindEntry{ + Name: name, + SourcePort: int(port), + NatPort: random_port, + Iface: "tun0", + IpTableDel: "iptables -t nat -D PREROUTING -p tcp -i tun0 --dport " + strconv.Itoa(random_port) + " -j DNAT --to " + to, + }) + ctx.JSON(ret) +} + +func instance_get_network(ctx iris.Context) { + var ports []InstanceBindEntry + + name := ctx.Params().Get("name") + Binds.Where("name = ?", name).Find(&ports) + for i, port := range ports { + if port.SourcePort == 80 || port.SourcePort == 22 { + ports[i].System = true + } else { + ports[i].System = false + } + + if port.Iface == "tun0" { + iface, _ := net.InterfaceByName("tun0") + ip, _ := iface.Addrs() + ports[i].Iface = ip[0].(*net.IPNet).IP.String() + } else { + _, ports[i].Iface = get_local_ip(0) + } + } + + ctx.JSON(ports) +} diff --git a/slave/main.go b/slave/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "github.com/kataras/iris" + log "github.com/sirupsen/logrus" + + "cisco/contrib/go-lxc" + "os" + "runtime" +) + +func startInstances() { + var instances []Instance + + Database.Where("status = 3").Find(&instances) + for _, inst := range instances { + log.Infof("Starting instance %s", inst.Name) + c, err := lxc.NewContainer(inst.Name, lxc.DefaultConfigPath()) + if err == nil { + c.Start() + c.Release() + } else { + log.Error(err) + } + } +} + +type Infos struct { + Name string `json:"name"` + Architecture string `json:"arch"` +} + +func get_infos(ctx iris.Context) { + hostname, _ := os.Hostname() + info := Infos{ + Name: hostname, + Architecture: runtime.GOARCH, + } + + ctx.JSON(&info) +} + +func main() { + app := iris.New() + app.Use(func(ctx iris.Context) { + log.WithFields(log.Fields{ + "ip": ctx.RemoteAddr(), + "path": ctx.Path(), + "method": ctx.Method(), + }).Info("api request") + ctx.Next() + }) + + app.Get("/api/info", get_infos) + app.Post("/api/instance", instance_create) + app.Get("/api/instance/{name:string}", instance_get) + app.Delete("/api/instance/{name:string}", instance_delete) + app.Put("/api/instance/{name:string}/start", instance_start) + app.Put("/api/instance/{name:string}/stop", instance_stop) + app.Put("/api/instance/{name:string}/reboot", instance_reboot) + app.Post("/api/instance/{name:string}/exec", instance_exec) + app.Put("/api/instance/{name:string}/bind_local/{port:uint16}", instance_bind_local_port) + app.Put("/api/instance/{name:string}/bind/{port:uint16}", instance_bind_port) + app.Get("/api/instance/{name:string}/network", instance_get_network) + + setConfig() + + startInstances() + + app.Run(iris.Addr(":"+Config.Port), iris.WithConfiguration(iris.Configuration{ + FireMethodNotAllowed: true, + })) +} diff --git a/slave/test.json b/slave/test.json @@ -0,0 +1,6 @@ +{ + "distro": "ubuntu", + "release": "bionic", + "name": "louis", + "arch": "amd64" +}