add container creation support

Signed-off-by: Tycho Andersen <tycho.andersen@canonical.com>
Signed-off-by: Serge E. Hallyn <serge.hallyn@ubuntu.com>
This commit is contained in:
Tycho Andersen 2014-11-06 09:41:07 -06:00
parent 85e265a9f8
commit 07998bc386
7 changed files with 294 additions and 1 deletions

View File

@ -109,3 +109,16 @@ func (c *Client) Ping() error {
Debugf("pong received")
return nil
}
func (c *Client) Create(name string, distro string, release string, arch string) (string, error) {
data, err := c.getstr("/create", map[string]string{
"name": name,
"distro": distro,
"release": release,
"arch": arch,
})
if err != nil {
return "fail", err
}
return data, err
}

59
lxc/create.go Normal file
View File

@ -0,0 +1,59 @@
package main
import (
"fmt"
"github.com/lxc/lxd"
)
type createCmd struct{}
const createUsage = `
lxc create images:ubuntu/$release/$arch
Creates a container using the specified release and arch
`
func (c *createCmd) usage() string {
return createUsage
}
func (c *createCmd) flags() {}
func (c *createCmd) run(args []string) error {
if len(args) > 2 {
return errArgs
}
if len(args) < 1 {
return errArgs
}
if args[0] != "images:ubuntu" {
return fmt.Errorf("only the default ubuntu image is supported currently.")
}
var containerRef string
if len(args) == 2 {
containerRef = args[1]
} else {
// TODO: come up with a random name a. la. juju/maas
containerRef = "foo"
}
config, err := lxd.LoadConfig()
if err != nil {
return err
}
d, name, err := lxd.NewClient(config, containerRef)
if err != nil {
return err
}
// TODO: implement the syntax for supporting other image types/remotes
l, err := d.Create(name, "ubuntu", "trusty", "amd64")
if err == nil {
fmt.Println(l)
}
return err
}

View File

@ -58,6 +58,7 @@ var commands = map[string]command{
"version": &versionCmd{},
"help": &helpCmd{},
"ping": &pingCmd{},
"create": &createCmd{},
}
var errArgs = fmt.Errorf("too many subcommand arguments")

36
lxd/byname.go Normal file
View File

@ -0,0 +1,36 @@
package main
import (
"fmt"
"net/http"
"gopkg.in/lxc/go-lxc.v2"
"github.com/lxc/lxd"
)
type byname func(*lxc.Container) error
func buildByNameServe(function string, f byname, d *Daemon) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
lxd.Debugf("responding to %s", function)
name := r.FormValue("name")
if name == "" {
fmt.Fprintf(w, "failed parsing name")
return
}
c, err := lxc.NewContainer(name, d.lxcpath)
if err != nil {
fmt.Fprintf(w, "failed getting container")
return
}
err = f(c)
if err != nil {
fmt.Fprintf(w, "operation failed")
return
}
}
}

89
lxd/create.go Normal file
View File

@ -0,0 +1,89 @@
package main
import (
"fmt"
"net/http"
"gopkg.in/lxc/go-lxc.v2"
"github.com/lxc/lxd"
)
func (d *Daemon) serveCreate(w http.ResponseWriter, r *http.Request) {
lxd.Debugf("responding to create")
name := r.FormValue("name")
if name == "" {
fmt.Fprintf(w, "failed parsing name")
return
}
distro := r.FormValue("distro")
if distro == "" {
fmt.Fprintf(w, "failed parsing distro")
return
}
release := r.FormValue("release")
if release == "" {
fmt.Fprintf(w, "failed parsing release")
return
}
arch := r.FormValue("arch")
if arch == "" {
fmt.Fprintf(w, "failed parsing arch")
return
}
opts := lxc.TemplateOptions{
Template: "download",
Distro: distro,
Release: release,
Arch: arch,
}
c, err := lxc.NewContainer(name, d.lxcpath)
if err != nil {
return
}
/*
* Set the id mapping. This may not be how we want to do it, but it's a
* start. First, we remove any id_map lines in the config which might
* have come from ~/.config/lxc/default.conf. Then add id mapping based
* on Domain.id_map
*/
if d.id_map != nil {
lxd.Debugf("setting custom idmap")
err = c.SetConfigItem("lxc.id_map", "")
if err != nil {
fmt.Fprintf(w, "Failed to clear id mapping, continuing")
}
uidstr := fmt.Sprintf("u 0 %d %d\n", d.id_map.Uidmin, d.id_map.Uidrange)
lxd.Debugf("uidstr is %s\n", uidstr)
err = c.SetConfigItem("lxc.id_map", uidstr)
if err != nil {
fmt.Fprintf(w, "Failed to set uid mapping")
return
}
gidstr := fmt.Sprintf("g 0 %d %d\n", d.id_map.Gidmin, d.id_map.Gidrange)
err = c.SetConfigItem("lxc.id_map", gidstr)
if err != nil {
fmt.Fprintf(w, "Failed to set gid mapping")
return
}
c.SaveConfigFile("/tmp/c")
}
/*
* Actually create the container
*/
err = c.Create(opts)
if err != nil {
fmt.Fprintf(w, "fail!")
} else {
fmt.Fprintf(w, "success!")
}
}

View File

@ -15,6 +15,7 @@ type Daemon struct {
tomb tomb.Tomb
unixl net.Listener
tcpl net.Listener
id_map *Idmap
lxcpath string
mux *http.ServeMux
}
@ -24,8 +25,18 @@ func StartDaemon(listenAddr string) (*Daemon, error) {
d := &Daemon{}
d.mux = http.NewServeMux()
d.mux.HandleFunc("/ping", d.servePing)
d.mux.HandleFunc("/create", d.serveCreate)
var err error
d.id_map, err = NewIdmap()
if err != nil {
return nil, err
}
lxd.Debugf("idmap is %d %d %d %d\n",
d.id_map.Uidmin,
d.id_map.Uidrange,
d.id_map.Gidmin,
d.id_map.Gidrange)
d.lxcpath = lxd.VarPath("lxc")
err = os.MkdirAll(lxd.VarPath("/"), 0755)
@ -95,4 +106,3 @@ func (d *Daemon) Stop() error {
//
// Together, these ideas ensure that we have a proper daemon, and a proper client,
// which can both be used independently and also embedded into other applications.

85
lxd/idmap.go Normal file
View File

@ -0,0 +1,85 @@
package main
import (
"bufio"
"fmt"
"os"
"os/user"
"path"
"strconv"
"strings"
)
/*
* We'll flesh this out to be lists of ranges
* We will want a list of available ranges (all ranges
* which lxd may use) and taken range (parts of the
* available ranges which are already in use by containers)
*
* We also may want some way of deciding which containers may
* or perhaps must not share ranges
*
* For now, we simply have a single range, shared by all
* containers
*/
type Idmap struct {
Uidmin, Uidrange uint
Gidmin, Gidrange uint
}
func checkmap(fname string, username string) (uint, uint, error) {
f, err := os.Open(fname)
var min uint
var idrange uint
if err != nil {
return 0, 0, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
min = 0
idrange = 0
for scanner.Scan() {
s := strings.Split(scanner.Text(), ":")
fmt.Println(s)
if len(s) < 3 {
return 0, 0, fmt.Errorf("unexpected values in %q: %q", fname, s)
}
if strings.EqualFold(s[0], username) {
bigmin, err := strconv.ParseUint(s[1], 10, 32)
if err != nil {
continue
}
biGidrange, err := strconv.ParseUint(s[2], 10, 32)
if err != nil {
continue
}
min = uint(bigmin)
idrange = uint(biGidrange)
return min, idrange, nil
}
}
return 0, 0, fmt.Errorf("User %q has no %ss.", username, path.Base(fname))
}
func NewIdmap() (*Idmap, error) {
me, err := user.Current()
if err != nil {
return nil, err
}
m := new(Idmap)
umin, urange, err := checkmap("/etc/subuid", me.Username)
if err != nil {
return nil, err
}
gmin, grange, err := checkmap("/etc/subgid", me.Username)
if err != nil {
return nil, err
}
m.Uidmin = umin
m.Uidrange = urange
m.Gidmin = gmin
m.Gidrange = grange
return m, nil
}