Browse code

+ Support for data volumes

Solomon Hykes authored on 2013/05/04 05:03:47
Showing 5 changed files
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"log"
11 11
 	"net/http"
12 12
 	"net/url"
13
+	"path/filepath"
13 14
 	"runtime"
14 15
 	"strconv"
15 16
 	"strings"
... ...
@@ -400,7 +401,8 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str
400 400
 }
401 401
 
402 402
 func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
403
-	cmd := rcli.Subcmd(stdout, "rm", "CONTAINER [CONTAINER...]", "Remove a container")
403
+	cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove a container")
404
+	v := cmd.Bool("v", false, "Remove the volumes associated to the container")
404 405
 	if err := cmd.Parse(args); err != nil {
405 406
 		return nil
406 407
 	}
... ...
@@ -408,15 +410,40 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string)
408 408
 		cmd.Usage()
409 409
 		return nil
410 410
 	}
411
+	volumes := make(map[string]struct{})
411 412
 	for _, name := range cmd.Args() {
412 413
 		container := srv.runtime.Get(name)
413 414
 		if container == nil {
414 415
 			return fmt.Errorf("No such container: %s", name)
415 416
 		}
417
+		// Store all the deleted containers volumes
418
+		for _, volumeId := range container.Volumes {
419
+			volumes[volumeId] = struct{}{}
420
+		}
416 421
 		if err := srv.runtime.Destroy(container); err != nil {
417 422
 			fmt.Fprintln(stdout, "Error destroying container "+name+": "+err.Error())
418 423
 		}
419 424
 	}
425
+	if *v {
426
+		// Retrieve all volumes from all remaining containers
427
+		usedVolumes := make(map[string]*Container)
428
+		for _, container := range srv.runtime.List() {
429
+			for _, containerVolumeId := range container.Volumes {
430
+				usedVolumes[containerVolumeId] = container
431
+			}
432
+		}
433
+
434
+		for volumeId := range volumes {
435
+			// If the requested volu
436
+			if c, exists := usedVolumes[volumeId]; exists {
437
+				fmt.Fprintf(stdout, "The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.Id)
438
+				continue
439
+			}
440
+			if err := srv.runtime.volumes.Delete(volumeId); err != nil {
441
+				return err
442
+			}
443
+		}
444
+	}
420 445
 	return nil
421 446
 }
422 447
 
... ...
@@ -913,6 +940,25 @@ func (opts AttachOpts) Get(val string) bool {
913 913
 	return false
914 914
 }
915 915
 
916
+// PathOpts stores a unique set of absolute paths
917
+type PathOpts map[string]struct{}
918
+
919
+func NewPathOpts() PathOpts {
920
+	return make(PathOpts)
921
+}
922
+
923
+func (opts PathOpts) String() string {
924
+	return fmt.Sprintf("%v", map[string]struct{}(opts))
925
+}
926
+
927
+func (opts PathOpts) Set(val string) error {
928
+	if !filepath.IsAbs(val) {
929
+		return fmt.Errorf("%s is not an absolute path", val)
930
+	}
931
+	opts[filepath.Clean(val)] = struct{}{}
932
+	return nil
933
+}
934
+
916 935
 func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
917 936
 	cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
918 937
 	force := cmd.Bool("f", false, "Force")
... ...
@@ -48,6 +48,7 @@ type Container struct {
48 48
 	runtime *Runtime
49 49
 
50 50
 	waitLock chan struct{}
51
+	Volumes  map[string]string
51 52
 }
52 53
 
53 54
 type Config struct {
... ...
@@ -66,6 +67,8 @@ type Config struct {
66 66
 	Cmd          []string
67 67
 	Dns          []string
68 68
 	Image        string // Name of the image as it was passed by the operator (eg. could be symbolic)
69
+	Volumes      map[string]struct{}
70
+	VolumesFrom  string
69 71
 }
70 72
 
71 73
 func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Config, error) {
... ...
@@ -97,6 +100,11 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con
97 97
 	var flDns ListOpts
98 98
 	cmd.Var(&flDns, "dns", "Set custom dns servers")
99 99
 
100
+	flVolumes := NewPathOpts()
101
+	cmd.Var(flVolumes, "v", "Attach a data volume")
102
+
103
+	flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
104
+
100 105
 	if err := cmd.Parse(args); err != nil {
101 106
 		return nil, err
102 107
 	}
... ...
@@ -136,6 +144,8 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con
136 136
 		Cmd:          runCmd,
137 137
 		Dns:          flDns,
138 138
 		Image:        image,
139
+		Volumes:      flVolumes,
140
+		VolumesFrom:  *flVolumesFrom,
139 141
 	}
140 142
 
141 143
 	if *flMemory > 0 && !capabilities.SwapLimit {
... ...
@@ -394,10 +404,40 @@ func (container *Container) Start() error {
394 394
 		log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
395 395
 		container.Config.MemorySwap = -1
396 396
 	}
397
+	container.Volumes = make(map[string]string)
398
+
399
+	// Create the requested volumes volumes
400
+	for volPath := range container.Config.Volumes {
401
+		if c, err := container.runtime.volumes.Create(nil, container, "", "", nil); err != nil {
402
+			return err
403
+		} else {
404
+			if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
405
+				return nil
406
+			}
407
+			container.Volumes[volPath] = c.Id
408
+		}
409
+	}
410
+
411
+	if container.Config.VolumesFrom != "" {
412
+		c := container.runtime.Get(container.Config.VolumesFrom)
413
+		if c == nil {
414
+			return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.Id)
415
+		}
416
+		for volPath, id := range c.Volumes {
417
+			if _, exists := container.Volumes[volPath]; exists {
418
+				return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.Id)
419
+			}
420
+			if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
421
+				return nil
422
+			}
423
+			container.Volumes[volPath] = id
424
+		}
425
+	}
397 426
 
398 427
 	if err := container.generateLXCConfig(); err != nil {
399 428
 		return err
400 429
 	}
430
+
401 431
 	params := []string{
402 432
 		"-n", container.Id,
403 433
 		"-f", container.lxcConfigPath(),
... ...
@@ -456,6 +496,7 @@ func (container *Container) Start() error {
456 456
 
457 457
 	// Init the lock
458 458
 	container.waitLock = make(chan struct{})
459
+
459 460
 	container.ToDisk()
460 461
 	go container.monitor()
461 462
 	return nil
... ...
@@ -787,6 +828,22 @@ func (container *Container) RootfsPath() string {
787 787
 	return path.Join(container.root, "rootfs")
788 788
 }
789 789
 
790
+func (container *Container) GetVolumes() (map[string]string, error) {
791
+	ret := make(map[string]string)
792
+	for volPath, id := range container.Volumes {
793
+		volume, err := container.runtime.volumes.Get(id)
794
+		if err != nil {
795
+			return nil, err
796
+		}
797
+		root, err := volume.root()
798
+		if err != nil {
799
+			return nil, err
800
+		}
801
+		ret[volPath] = path.Join(root, "layer")
802
+	}
803
+	return ret, nil
804
+}
805
+
790 806
 func (container *Container) rwPath() string {
791 807
 	return path.Join(container.root, "rw")
792 808
 }
... ...
@@ -17,3 +17,5 @@
17 17
       -p=[]: Map a network port to the container
18 18
       -t=false: Allocate a pseudo-tty
19 19
       -u="": Username or UID
20
+      -d=[]: Set custom dns servers for the container
21
+      -v=[]: Creates a new volumes and mount it at the specified path. A container ID can be passed instead of a path in order to mount all volumes from the given container.
... ...
@@ -79,7 +79,11 @@ lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0
79 79
 
80 80
 # In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
81 81
 lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0
82
-
82
+{{if .Volumes}}
83
+{{range $virtualPath, $realPath := .GetVolumes}}
84
+lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,rw 0 0
85
+{{end}}
86
+{{end}}
83 87
 
84 88
 # drop linux capabilities (apply mainly to the user root in the container)
85 89
 lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config
... ...
@@ -32,6 +32,7 @@ type Runtime struct {
32 32
 	capabilities   *Capabilities
33 33
 	kernelVersion  *KernelVersionInfo
34 34
 	autoRestart    bool
35
+	volumes        *Graph
35 36
 }
36 37
 
37 38
 var sysInitPath string
... ...
@@ -405,6 +406,10 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) {
405 405
 	if err != nil {
406 406
 		return nil, err
407 407
 	}
408
+	volumes, err := NewGraph(path.Join(root, "volumes"))
409
+	if err != nil {
410
+		return nil, err
411
+	}
408 412
 	repositories, err := NewTagStore(path.Join(root, "repositories"), g)
409 413
 	if err != nil {
410 414
 		return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
... ...
@@ -432,6 +437,7 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) {
432 432
 		idIndex:        NewTruncIndex(),
433 433
 		capabilities:   &Capabilities{},
434 434
 		autoRestart:    autoRestart,
435
+		volumes:        volumes,
435 436
 	}
436 437
 
437 438
 	if err := runtime.restore(); err != nil {