Merge lp:~thumper/juju-core/fast-lxc-no-apt-upgrade into lp:~go-bot/juju-core/trunk

Proposed by Tim Penhey
Status: Merged
Approved by: Tim Penhey
Approved revision: no longer in the source branch.
Merged at revision: 2412
Proposed branch: lp:~thumper/juju-core/fast-lxc-no-apt-upgrade
Merge into: lp:~go-bot/juju-core/trunk
Prerequisite: lp:~thumper/juju-core/update-golxc-version
Diff against target: 983 lines (+526/-64)
10 files modified
container/interface.go (+16/-0)
container/kvm/kvm.go (+3/-8)
container/kvm/kvm_test.go (+1/-1)
container/lxc/clonetemplate.go (+219/-0)
container/lxc/lxc.go (+75/-30)
container/lxc/lxc_test.go (+110/-3)
container/lxc/mock/mock-lxc.go (+87/-19)
container/userdata.go (+9/-3)
provider/local/environ.go (+2/-0)
worker/provisioner/lxc-broker_test.go (+4/-0)
To merge this branch: bzr merge lp:~thumper/juju-core/fast-lxc-no-apt-upgrade
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+210536@code.launchpad.net

Commit message

Use lxc clone on trusty

This branch speeds up lxc containers for the local
provider only by creating a template as needed that
persists between environments, and this template is
then used as a basis for the machines created in the
environment using lxc-clone.

If /var/lib/lxc is a btrfs filesystem, the containers
use btrfs snapshots. If not, the containers are
created using aufs snapshots.

In addition to this, the apt-get update/upgrade step
is skipped during cloud-init for the cloned machines.

https://codereview.appspot.com/74370044/

Description of the change

Use lxc clone on trusty

This branch speeds up lxc containers for the local
provider only by creating a template as needed that
persists between environments, and this template is
then used as a basis for the machines created in the
environment using lxc-clone.

If /var/lib/lxc is a btrfs filesystem, the containers
use btrfs snapshots. If not, the containers are
created using aufs snapshots.

In addition to this, the apt-get update/upgrade step
is skipped during cloud-init for the cloned machines.

Since the testing required a go routine to shut down
the template container, the mock containers had to be
made go routine safe for the state checking.

Certain extra info was also added for the mock containers,
being the config file and console log.

https://codereview.appspot.com/74370044/

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

Reviewers: mp+210536_code.launchpad.net,

Message:
Please take a look.

Description:
Use lxc clone on trusty

This branch speeds up lxc containers for the local
provider only by creating a template as needed that
persists between environments, and this template is
then used as a basis for the machines created in the
environment using lxc-clone.

If /var/lib/lxc is a btrfs filesystem, the containers
use btrfs snapshots. If not, the containers are
created using aufs snapshots.

In addition to this, the apt-get update/upgrade step
is skipped during cloud-init for the cloned machines.

Since the testing required a go routine to shut down
the template container, the mock containers had to be
made go routine safe for the state checking.

Certain extra info was also added for the mock containers,
being the config file and console log.

https://code.launchpad.net/~thumper/juju-core/fast-lxc-no-apt-upgrade/+merge/210536

Requires:
https://code.launchpad.net/~thumper/juju-core/update-golxc-version/+merge/210492

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/74370044/

Affected files (+485, -46 lines):
   A [revision details]
   A container/lxc/clonetemplate.go
   M container/lxc/lxc.go
   M container/lxc/lxc_test.go
   M container/lxc/mock/mock-lxc.go
   M container/userdata.go
   M provider/local/environ.go
   M worker/provisioner/lxc-broker_test.go

Revision history for this message
Ian Booth (wallyworld) wrote :

I'm not fully across the finer details about lxc cloning. I think this
looks good. Perhaps a little more test coverage though. Specifically:
1. EnsureCloneTemplate behaves correctly if called when a template
already exists
2. The cloud init contents are devoid of apt stuff

https://codereview.appspot.com/74370044/diff/1/container/lxc/lxc.go
File container/lxc/lxc.go (right):

https://codereview.appspot.com/74370044/diff/1/container/lxc/lxc.go#newcode92
container/lxc/lxc.go:92: if conf["use-clone"] == "true" {
Is there a helper function for str -> bool?

https://codereview.appspot.com/74370044/diff/1/container/lxc/lxc_test.go
File container/lxc/lxc_test.go (right):

https://codereview.appspot.com/74370044/diff/1/container/lxc/lxc_test.go#newcode165
container/lxc/lxc_test.go:165: func (s *LxcSuite) AssertEvent(c *gc.C,
event mock.Event, expected mock.Action, id string) {
does this need to be exported?

https://codereview.appspot.com/74370044/diff/1/container/lxc/lxc_test.go#newcode203
container/lxc/lxc_test.go:203: c.Assert(template.Name(), gc.Equals,
name)
Should we be checking (some key aspects of) the contents of the
template?

https://codereview.appspot.com/74370044/

Revision history for this message
Sidnei da Silva (sidnei) wrote :

https://codereview.appspot.com/74370044/diff/1/container/lxc/clonetemplate.go
File container/lxc/clonetemplate.go (right):

https://codereview.appspot.com/74370044/diff/1/container/lxc/clonetemplate.go#newcode142
container/lxc/clonetemplate.go:142: if backingFilesystem == Btrfs {
I find it odd that backingFilesystem is special-cased for btrfs, since
the version of lxc in trusty has support for picking the right backing
filesystem if -Bbest is specified, which would work for either btrfs or
lvm thin provisioning.

https://codereview.appspot.com/74370044/

Revision history for this message
Tim Penhey (thumper) wrote :

Please take a look.

https://codereview.appspot.com/74370044/diff/1/container/lxc/clonetemplate.go
File container/lxc/clonetemplate.go (right):

https://codereview.appspot.com/74370044/diff/1/container/lxc/clonetemplate.go#newcode142
container/lxc/clonetemplate.go:142: if backingFilesystem == Btrfs {
On 2014/03/12 08:31:38, sidnei.da.silva wrote:
> I find it odd that backingFilesystem is special-cased for btrfs, since
the
> version of lxc in trusty has support for picking the right backing
filesystem if
> -Bbest is specified, which would work for either btrfs or lvm thin
provisioning.

man lxc-create doesn't show this.

Post chat: yes -B best does exist, but not documented. However, it
looks for the following in order: "btrfs", "zfs", "lvm", "dir". Since
we have no easy way to determine when to use "aufs" if we are allowing
"best", it is easier to be explicit in our support with btrfs now, and
have all others use aufs.
I'll talk with hallyn

https://codereview.appspot.com/74370044/diff/1/container/lxc/lxc.go
File container/lxc/lxc.go (right):

https://codereview.appspot.com/74370044/diff/1/container/lxc/lxc.go#newcode92
container/lxc/lxc.go:92: if conf["use-clone"] == "true" {
On 2014/03/12 05:42:25, wallyworld wrote:
> Is there a helper function for str -> bool?

Yes there is. Using it now.

https://codereview.appspot.com/74370044/diff/1/container/lxc/lxc_test.go
File container/lxc/lxc_test.go (right):

https://codereview.appspot.com/74370044/diff/1/container/lxc/lxc_test.go#newcode165
container/lxc/lxc_test.go:165: func (s *LxcSuite) AssertEvent(c *gc.C,
event mock.Event, expected mock.Action, id string) {
On 2014/03/12 05:42:25, wallyworld wrote:
> does this need to be exported?

No it doesn't, but it fits with the other Assert statements.

Since "exporting" a test function has no meaning, I went with
consistency.

https://codereview.appspot.com/74370044/diff/1/container/lxc/lxc_test.go#newcode203
container/lxc/lxc_test.go:203: c.Assert(template.Name(), gc.Equals,
name)
On 2014/03/12 05:42:25, wallyworld wrote:
> Should we be checking (some key aspects of) the contents of the
template?

We could make sure that it isn't autostarting nor mounting the logdir by
looking at the config file. Worth it?

https://codereview.appspot.com/74370044/

Revision history for this message
Ian Booth (wallyworld) wrote :

LGTM. Let's get this landed, but I'd still like to see tests that the
template doesn't start etc.

https://codereview.appspot.com/74370044/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'container/interface.go'
--- container/interface.go 2014-03-03 02:02:25 +0000
+++ container/interface.go 2014-03-13 02:11:05 +0000
@@ -39,3 +39,19 @@
39 // that the host machine can run containers.39 // that the host machine can run containers.
40 Initialise() error40 Initialise() error
41}41}
42
43// PopValue returns the requested key from the config map. If the value
44// doesn't exist, the function returns the empty string. If the value does
45// exist, the value is returned, and the element removed from the map.
46func (m ManagerConfig) PopValue(key string) string {
47 value := m[key]
48 delete(m, key)
49 return value
50}
51
52// WarnAboutUnused emits a warning about each value in the map.
53func (m ManagerConfig) WarnAboutUnused() {
54 for key, value := range m {
55 logger.Warningf("unused config option: %q -> %q", key, value)
56 }
57}
4258
=== modified file 'container/kvm/kvm.go'
--- container/kvm/kvm.go 2014-03-11 01:17:39 +0000
+++ container/kvm/kvm.go 2014-03-13 02:11:05 +0000
@@ -56,20 +56,15 @@
56// containers. The containers that are created are namespaced by the name56// containers. The containers that are created are namespaced by the name
57// parameter.57// parameter.
58func NewContainerManager(conf container.ManagerConfig) (container.Manager, error) {58func NewContainerManager(conf container.ManagerConfig) (container.Manager, error) {
59 name := conf[container.ConfigName]59 name := conf.PopValue(container.ConfigName)
60 delete(conf, container.ConfigName)
61 if name == "" {60 if name == "" {
62 return nil, fmt.Errorf("name is required")61 return nil, fmt.Errorf("name is required")
63 }62 }
64 logDir := conf[container.ConfigLogDir]63 logDir := conf.PopValue(container.ConfigLogDir)
65 delete(conf, container.ConfigLogDir)
66 if logDir == "" {64 if logDir == "" {
67 logDir = agent.DefaultLogDir65 logDir = agent.DefaultLogDir
68 }66 }
69 for k, v := range conf {67 conf.WarnAboutUnused()
70 logger.Warningf(`Found unused config option with key: "%v" and value: "%v"`, k, v)
71 }
72
73 return &containerManager{name: name, logdir: logDir}, nil68 return &containerManager{name: name, logdir: logDir}, nil
74}69}
7570
7671
=== modified file 'container/kvm/kvm_test.go'
--- container/kvm/kvm_test.go 2014-03-11 01:17:39 +0000
+++ container/kvm/kvm_test.go 2014-03-13 02:11:05 +0000
@@ -46,7 +46,7 @@
46 "shazam": "Captain Marvel",46 "shazam": "Captain Marvel",
47 })47 })
48 c.Assert(err, gc.IsNil)48 c.Assert(err, gc.IsNil)
49 c.Assert(c.GetTestLog(), gc.Matches, `^.*WARNING juju.container.kvm Found unused config option with key: "shazam" and value: "Captain Marvel"\n*`)49 c.Assert(c.GetTestLog(), jc.Contains, `WARNING juju.container unused config option: "shazam" -> "Captain Marvel"`)
50}50}
5151
52func (s *KVMSuite) TestListInitiallyEmpty(c *gc.C) {52func (s *KVMSuite) TestListInitiallyEmpty(c *gc.C) {
5353
=== added file 'container/lxc/clonetemplate.go'
--- container/lxc/clonetemplate.go 1970-01-01 00:00:00 +0000
+++ container/lxc/clonetemplate.go 2014-03-13 02:11:05 +0000
@@ -0,0 +1,219 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package lxc
5
6import (
7 "fmt"
8 "io"
9 "os"
10 "path/filepath"
11 "sync"
12 "time"
13
14 "launchpad.net/golxc"
15
16 coreCloudinit "launchpad.net/juju-core/cloudinit"
17 "launchpad.net/juju-core/container"
18 "launchpad.net/juju-core/environs/cloudinit"
19 "launchpad.net/juju-core/juju/osenv"
20 "launchpad.net/juju-core/utils"
21 "launchpad.net/juju-core/utils/fslock"
22 "launchpad.net/juju-core/utils/tailer"
23)
24
25const (
26 templateShutdownUpstartFilename = "/etc/init/juju-template-restart.conf"
27 templateShutdownUpstartScript = `
28description "Juju lxc template shutdown job"
29author "Juju Team <juju@lists.ubuntu.com>"
30start on stopped cloud-final
31
32script
33 shutdown -h now
34end script
35
36post-stop script
37 rm ` + templateShutdownUpstartFilename + `
38end script
39`
40)
41
42var (
43 TemplateLockDir = "/var/lib/juju/locks"
44
45 TemplateStopTimeout = 5 * time.Minute
46)
47
48// templateUserData returns a minimal user data necessary for the template.
49// This should have the authorized keys, base packages, the cloud archive if
50// necessary, initial apt proxy config, and it should do the apt-get
51// update/upgrade initially.
52func templateUserData(
53 series string,
54 authorizedKeys string,
55 aptProxy osenv.ProxySettings,
56) ([]byte, error) {
57 config := coreCloudinit.New()
58 config.AddScripts(
59 "set -xe", // ensure we run all the scripts or abort.
60 )
61 config.AddSSHAuthorizedKeys(authorizedKeys)
62 cloudinit.MaybeAddCloudArchiveCloudTools(config, series)
63 cloudinit.AddAptCommands(aptProxy, config)
64 config.AddScripts(
65 fmt.Sprintf(
66 "printf '%%s\n' %s > %s",
67 utils.ShQuote(templateShutdownUpstartScript),
68 templateShutdownUpstartFilename,
69 ))
70 data, err := config.Render()
71 if err != nil {
72 return nil, err
73 }
74 return data, nil
75}
76
77func AcquireTemplateLock(name, message string) (*fslock.Lock, error) {
78 logger.Infof("wait for fslock on %v", name)
79 lock, err := fslock.NewLock(TemplateLockDir, name)
80 if err != nil {
81 logger.Tracef("failed to create fslock for template: %v", err)
82 return nil, err
83 }
84 err = lock.Lock(message)
85 if err != nil {
86 logger.Tracef("failed to acquire lock for template: %v", err)
87 return nil, err
88 }
89 return lock, nil
90}
91
92// Make sure a template exists that we can clone from.
93func EnsureCloneTemplate(
94 backingFilesystem string,
95 series string,
96 network *container.NetworkConfig,
97 authorizedKeys string,
98 aptProxy osenv.ProxySettings,
99) (golxc.Container, error) {
100 name := fmt.Sprintf("juju-%s-template", series)
101 containerDirectory, err := container.NewDirectory(name)
102 if err != nil {
103 return nil, err
104 }
105
106 lock, err := AcquireTemplateLock(name, "ensure clone exists")
107 if err != nil {
108 return nil, err
109 }
110 defer lock.Unlock()
111
112 lxcContainer := LxcObjectFactory.New(name)
113 // Early exit if the container has been constructed before.
114 if lxcContainer.IsConstructed() {
115 logger.Infof("template exists, continuing")
116 return lxcContainer, nil
117 }
118 logger.Infof("template does not exist, creating")
119
120 userData, err := templateUserData(series, authorizedKeys, aptProxy)
121 if err != nil {
122 logger.Tracef("filed to create tempalte user data for template: %v", err)
123 return nil, err
124 }
125 userDataFilename, err := container.WriteCloudInitFile(containerDirectory, userData)
126 if err != nil {
127 return nil, err
128 }
129
130 configFile, err := writeLxcConfig(network, containerDirectory)
131 if err != nil {
132 logger.Errorf("failed to write config file: %v", err)
133 return nil, err
134 }
135 templateParams := []string{
136 "--debug", // Debug errors in the cloud image
137 "--userdata", userDataFilename, // Our groovey cloud-init
138 "--hostid", name, // Use the container name as the hostid
139 "-r", series,
140 }
141 var extraCreateArgs []string
142 if backingFilesystem == Btrfs {
143 extraCreateArgs = append(extraCreateArgs, "-B", Btrfs)
144 }
145 // Create the container.
146 logger.Tracef("create the container")
147 if err := lxcContainer.Create(configFile, defaultTemplate, extraCreateArgs, templateParams); err != nil {
148 logger.Errorf("lxc container creation failed: %v", err)
149 return nil, err
150 }
151 // Make sure that the mount dir has been created.
152 logger.Tracef("make the mount dir for the shared logs")
153 if err := os.MkdirAll(internalLogDir(name), 0755); err != nil {
154 logger.Tracef("failed to create internal /var/log/juju mount dir: %v", err)
155 return nil, err
156 }
157
158 // Start the lxc container with the appropriate settings for grabbing the
159 // console output and a log file.
160 consoleFile := filepath.Join(containerDirectory, "console.log")
161 lxcContainer.SetLogFile(filepath.Join(containerDirectory, "container.log"), golxc.LogDebug)
162 logger.Tracef("start the container")
163 // We explicitly don't pass through the config file to the container.Start
164 // method as we have passed it through at container creation time. This
165 // is necessary to get the appropriate rootfs reference without explicitly
166 // setting it ourselves.
167 if err = lxcContainer.Start("", consoleFile); err != nil {
168 logger.Errorf("container failed to start: %v", err)
169 return nil, err
170 }
171 logger.Infof("template container started, now wait for it to stop")
172 // Perhaps we should wait for it to finish, and the question becomes "how
173 // long do we wait for it to complete?"
174
175 console, err := os.Open(consoleFile)
176 if err != nil {
177 // can't listen
178 return nil, err
179 }
180
181 tailWriter := &logTail{tick: time.Now()}
182 consoleTailer := tailer.NewTailer(console, tailWriter, 0, nil)
183 defer consoleTailer.Stop()
184
185 // We should wait maybe 1 minute between output?
186 // if no output check to see if stopped
187 // If we have no output and still running, something has probably gone wrong
188 for lxcContainer.IsRunning() {
189 if tailWriter.lastTick().Before(time.Now().Add(-TemplateStopTimeout)) {
190 logger.Infof("not heard anything from the template log for five minutes")
191 return nil, fmt.Errorf("template container %q did not stop", name)
192 }
193 time.Sleep(time.Second)
194 }
195
196 return lxcContainer, nil
197}
198
199type logTail struct {
200 tick time.Time
201 mutex sync.Mutex
202}
203
204var _ io.Writer = (*logTail)(nil)
205
206func (t *logTail) Write(data []byte) (int, error) {
207 logger.Tracef(string(data))
208 t.mutex.Lock()
209 defer t.mutex.Unlock()
210 t.tick = time.Now()
211 return len(data), nil
212}
213
214func (t *logTail) lastTick() time.Time {
215 t.mutex.Lock()
216 defer t.mutex.Unlock()
217 tick := t.tick
218 return tick
219}
0220
=== modified file 'container/lxc/lxc.go'
--- container/lxc/lxc.go 2014-03-12 02:13:38 +0000
+++ container/lxc/lxc.go 2014-03-13 02:11:05 +0000
@@ -9,7 +9,9 @@
9 "os"9 "os"
10 "os/exec"10 "os/exec"
11 "path/filepath"11 "path/filepath"
12 "strconv"
12 "strings"13 "strings"
14 "time"
1315
14 "github.com/juju/loggo"16 "github.com/juju/loggo"
15 "launchpad.net/golxc"17 "launchpad.net/golxc"
@@ -66,6 +68,7 @@
66type containerManager struct {68type containerManager struct {
67 name string69 name string
68 logdir string70 logdir string
71 createWithClone bool
69 backingFilesystem string72 backingFilesystem string
70}73}
7174
@@ -76,16 +79,18 @@
76// containers. The containers that are created are namespaced by the name79// containers. The containers that are created are namespaced by the name
77// parameter.80// parameter.
78func NewContainerManager(conf container.ManagerConfig) (container.Manager, error) {81func NewContainerManager(conf container.ManagerConfig) (container.Manager, error) {
79 name := conf[container.ConfigName]82 name := conf.PopValue(container.ConfigName)
80 delete(conf, container.ConfigName)
81 if name == "" {83 if name == "" {
82 return nil, fmt.Errorf("name is required")84 return nil, fmt.Errorf("name is required")
83 }85 }
84 logDir := conf[container.ConfigLogDir]86 logDir := conf.PopValue(container.ConfigLogDir)
85 delete(conf, container.ConfigLogDir)
86 if logDir == "" {87 if logDir == "" {
87 logDir = agent.DefaultLogDir88 logDir = agent.DefaultLogDir
88 }89 }
90 // Explicitly ignore the error result from ParseBool.
91 // If it fails to parse, the value is false, and this suits
92 // us fine.
93 useClone, _ := strconv.ParseBool(conf.PopValue("use-clone"))
89 backingFS, err := containerDirFilesystem()94 backingFS, err := containerDirFilesystem()
90 if err != nil {95 if err != nil {
91 // Especially in tests, or a bot, the lxc dir may not exist96 // Especially in tests, or a bot, the lxc dir may not exist
@@ -95,12 +100,11 @@
95 backingFS = "unknown"100 backingFS = "unknown"
96 }101 }
97 logger.Tracef("backing filesystem: %q", backingFS)102 logger.Tracef("backing filesystem: %q", backingFS)
98 for k, v := range conf {103 conf.WarnAboutUnused()
99 logger.Warningf(`Found unused config option with key: "%v" and value: "%v"`, k, v)
100 }
101 return &containerManager{104 return &containerManager{
102 name: name,105 name: name,
103 logdir: logDir,106 logdir: logDir,
107 createWithClone: useClone,
104 backingFilesystem: backingFS,108 backingFilesystem: backingFS,
105 }, nil109 }, nil
106}110}
@@ -108,23 +112,23 @@
108func (manager *containerManager) StartContainer(112func (manager *containerManager) StartContainer(
109 machineConfig *cloudinit.MachineConfig,113 machineConfig *cloudinit.MachineConfig,
110 series string,114 series string,
111 network *container.NetworkConfig) (instance.Instance, *instance.HardwareCharacteristics, error) {115 network *container.NetworkConfig,
112116) (instance.Instance, *instance.HardwareCharacteristics, error) {
117 start := time.Now()
113 name := names.MachineTag(machineConfig.MachineId)118 name := names.MachineTag(machineConfig.MachineId)
114 if manager.name != "" {119 if manager.name != "" {
115 name = fmt.Sprintf("%s-%s", manager.name, name)120 name = fmt.Sprintf("%s-%s", manager.name, name)
116 }121 }
117 // Note here that the lxcObjectFacotry only returns a valid container
118 // object, and doesn't actually construct the underlying lxc container on
119 // disk.
120 lxcContainer := LxcObjectFactory.New(name)
121
122 // Create the cloud-init.122 // Create the cloud-init.
123 directory, err := container.NewDirectory(name)123 directory, err := container.NewDirectory(name)
124 if err != nil {124 if err != nil {
125 return nil, nil, err125 return nil, nil, err
126 }126 }
127 logger.Tracef("write cloud-init")127 logger.Tracef("write cloud-init")
128 if manager.createWithClone {
129 // If we are using clone, disable the apt-get steps
130 machineConfig.DisablePackageCommands = true
131 }
128 userDataFilename, err := container.WriteUserData(machineConfig, directory)132 userDataFilename, err := container.WriteUserData(machineConfig, directory)
129 if err != nil {133 if err != nil {
130 logger.Errorf("failed to write user data: %v", err)134 logger.Errorf("failed to write user data: %v", err)
@@ -136,20 +140,58 @@
136 logger.Errorf("failed to write config file: %v", err)140 logger.Errorf("failed to write config file: %v", err)
137 return nil, nil, err141 return nil, nil, err
138 }142 }
139 templateParams := []string{143
140 "--debug", // Debug errors in the cloud image144 var lxcContainer golxc.Container
141 "--userdata", userDataFilename, // Our groovey cloud-init145 if manager.createWithClone {
142 "--hostid", name, // Use the container name as the hostid146 templateContainer, err := EnsureCloneTemplate(
143 "-r", series,147 manager.backingFilesystem,
144 }148 series,
145 // Create the container.149 network,
146 logger.Tracef("create the container")150 machineConfig.AuthorizedKeys,
147 if err := lxcContainer.Create(configFile, defaultTemplate, nil, templateParams); err != nil {151 machineConfig.AptProxySettings,
148 logger.Errorf("lxc container creation failed: %v", err)152 )
149 return nil, nil, err153 if err != nil {
150 }154 return nil, nil, err
151 logger.Tracef("lxc container created")155 }
152156 templateParams := []string{
157 "--debug", // Debug errors in the cloud image
158 "--userdata", userDataFilename, // Our groovey cloud-init
159 "--hostid", name, // Use the container name as the hostid
160 }
161 extraCloneArgs := []string{"--snapshot"}
162 if manager.backingFilesystem != Btrfs {
163 extraCloneArgs = append(extraCloneArgs, "--backingstore", "aufs")
164 }
165
166 lock, err := AcquireTemplateLock(templateContainer.Name(), "clone")
167 if err != nil {
168 return nil, nil, fmt.Errorf("failed to acquire lock on template: %v", err)
169 }
170 defer lock.Unlock()
171 lxcContainer, err = templateContainer.Clone(name, extraCloneArgs, templateParams)
172 if err != nil {
173 logger.Errorf("lxc container cloning failed: %v", err)
174 return nil, nil, err
175 }
176 } else {
177 // Note here that the lxcObjectFacotry only returns a valid container
178 // object, and doesn't actually construct the underlying lxc container on
179 // disk.
180 lxcContainer = LxcObjectFactory.New(name)
181 templateParams := []string{
182 "--debug", // Debug errors in the cloud image
183 "--userdata", userDataFilename, // Our groovey cloud-init
184 "--hostid", name, // Use the container name as the hostid
185 "-r", series,
186 }
187 // Create the container.
188 logger.Tracef("create the container")
189 if err := lxcContainer.Create(configFile, defaultTemplate, nil, templateParams); err != nil {
190 logger.Errorf("lxc container creation failed: %v", err)
191 return nil, nil, err
192 }
193 logger.Tracef("lxc container created")
194 }
153 if err := autostartContainer(name); err != nil {195 if err := autostartContainer(name); err != nil {
154 return nil, nil, err196 return nil, nil, err
155 }197 }
@@ -173,7 +215,7 @@
173 hardware := &instance.HardwareCharacteristics{215 hardware := &instance.HardwareCharacteristics{
174 Arch: &arch,216 Arch: &arch,
175 }217 }
176 logger.Tracef("container started")218 logger.Tracef("container %q started: %v", name, time.Now().Sub(start))
177 return &lxcInstance{lxcContainer, name}, hardware, nil219 return &lxcInstance{lxcContainer, name}, hardware, nil
178}220}
179221
@@ -222,6 +264,7 @@
222}264}
223265
224func (manager *containerManager) StopContainer(instance instance.Instance) error {266func (manager *containerManager) StopContainer(instance instance.Instance) error {
267 start := time.Now()
225 name := string(instance.Id())268 name := string(instance.Id())
226 lxcContainer := LxcObjectFactory.New(name)269 lxcContainer := LxcObjectFactory.New(name)
227 if useRestartDir() {270 if useRestartDir() {
@@ -236,7 +279,9 @@
236 return err279 return err
237 }280 }
238281
239 return container.RemoveDirectory(name)282 err := container.RemoveDirectory(name)
283 logger.Tracef("container %q stopped: %v", name, time.Now().Sub(start))
284 return err
240}285}
241286
242func (manager *containerManager) ListContainers() (result []instance.Instance, err error) {287func (manager *containerManager) ListContainers() (result []instance.Instance, err error) {
243288
=== modified file 'container/lxc/lxc_test.go'
--- container/lxc/lxc_test.go 2014-03-12 01:27:57 +0000
+++ container/lxc/lxc_test.go 2014-03-13 02:11:05 +0000
@@ -10,6 +10,7 @@
10 "path/filepath"10 "path/filepath"
11 "strings"11 "strings"
12 stdtesting "testing"12 stdtesting "testing"
13 "time"
1314
14 "github.com/juju/loggo"15 "github.com/juju/loggo"
15 gc "launchpad.net/gocheck"16 gc "launchpad.net/gocheck"
@@ -19,9 +20,11 @@
19 "launchpad.net/juju-core/agent"20 "launchpad.net/juju-core/agent"
20 "launchpad.net/juju-core/container"21 "launchpad.net/juju-core/container"
21 "launchpad.net/juju-core/container/lxc"22 "launchpad.net/juju-core/container/lxc"
23 "launchpad.net/juju-core/container/lxc/mock"
22 lxctesting "launchpad.net/juju-core/container/lxc/testing"24 lxctesting "launchpad.net/juju-core/container/lxc/testing"
23 containertesting "launchpad.net/juju-core/container/testing"25 containertesting "launchpad.net/juju-core/container/testing"
24 instancetest "launchpad.net/juju-core/instance/testing"26 instancetest "launchpad.net/juju-core/instance/testing"
27 "launchpad.net/juju-core/juju/osenv"
25 jc "launchpad.net/juju-core/testing/checkers"28 jc "launchpad.net/juju-core/testing/checkers"
26 "launchpad.net/juju-core/testing/testbase"29 "launchpad.net/juju-core/testing/testbase"
27)30)
@@ -32,6 +35,9 @@
3235
33type LxcSuite struct {36type LxcSuite struct {
34 lxctesting.TestSuite37 lxctesting.TestSuite
38
39 events chan mock.Event
40 useClone bool
35}41}
3642
37var _ = gc.Suite(&LxcSuite{})43var _ = gc.Suite(&LxcSuite{})
@@ -39,6 +45,16 @@
39func (s *LxcSuite) SetUpTest(c *gc.C) {45func (s *LxcSuite) SetUpTest(c *gc.C) {
40 s.TestSuite.SetUpTest(c)46 s.TestSuite.SetUpTest(c)
41 loggo.GetLogger("juju.container.lxc").SetLogLevel(loggo.TRACE)47 loggo.GetLogger("juju.container.lxc").SetLogLevel(loggo.TRACE)
48 s.events = make(chan mock.Event, 25)
49 s.TestSuite.Factory.AddListener(s.events)
50 s.PatchValue(&lxc.TemplateLockDir, c.MkDir())
51 s.PatchValue(&lxc.TemplateStopTimeout, 500*time.Millisecond)
52}
53
54func (s *LxcSuite) TearDownTest(c *gc.C) {
55 s.TestSuite.Factory.RemoveListener(s.events)
56 close(s.events)
57 s.TestSuite.TearDownTest(c)
42}58}
4359
44func (s *LxcSuite) TestContainerDirFilesystem(c *gc.C) {60func (s *LxcSuite) TestContainerDirFilesystem(c *gc.C) {
@@ -73,9 +89,13 @@
73}89}
7490
75func (s *LxcSuite) makeManager(c *gc.C, name string) container.Manager {91func (s *LxcSuite) makeManager(c *gc.C, name string) container.Manager {
76 manager, err := lxc.NewContainerManager(container.ManagerConfig{92 params := container.ManagerConfig{
77 container.ConfigName: name,93 container.ConfigName: name,
78 })94 }
95 if s.useClone {
96 params["use-clone"] = "true"
97 }
98 manager, err := lxc.NewContainerManager(params)
79 c.Assert(err, gc.IsNil)99 c.Assert(err, gc.IsNil)
80 return manager100 return manager
81}101}
@@ -86,7 +106,7 @@
86 "shazam": "Captain Marvel",106 "shazam": "Captain Marvel",
87 })107 })
88 c.Assert(err, gc.IsNil)108 c.Assert(err, gc.IsNil)
89 c.Assert(c.GetTestLog(), jc.Contains, `WARNING juju.container.lxc Found unused config option with key: "shazam" and value: "Captain Marvel"`)109 c.Assert(c.GetTestLog(), jc.Contains, `WARNING juju.container unused config option: "shazam" -> "Captain Marvel"`)
90}110}
91111
92func (s *LxcSuite) TestStartContainer(c *gc.C) {112func (s *LxcSuite) TestStartContainer(c *gc.C) {
@@ -130,6 +150,93 @@
130 c.Assert(location, gc.Equals, expectedTarget)150 c.Assert(location, gc.Equals, expectedTarget)
131}151}
132152
153func (s *LxcSuite) ensureTemplateStopped(name string) {
154 go func() {
155 for {
156 template := s.Factory.New(name)
157 if template.IsRunning() {
158 template.Stop()
159 }
160 time.Sleep(50 * time.Millisecond)
161 }
162 }()
163}
164
165func (s *LxcSuite) AssertEvent(c *gc.C, event mock.Event, expected mock.Action, id string) {
166 c.Assert(event.Action, gc.Equals, expected)
167 c.Assert(event.InstanceId, gc.Equals, id)
168}
169
170func (s *LxcSuite) TestStartContainerEvents(c *gc.C) {
171 manager := s.makeManager(c, "test")
172 instance := containertesting.StartContainer(c, manager, "1")
173 id := string(instance.Id())
174 s.AssertEvent(c, <-s.events, mock.Created, id)
175 s.AssertEvent(c, <-s.events, mock.Started, id)
176}
177
178func (s *LxcSuite) TestStartContainerEventsWithClone(c *gc.C) {
179 s.PatchValue(&s.useClone, true)
180 // The template containers are created with an upstart job that
181 // stops them once cloud init has finished. We emulate that here.
182 template := "juju-series-template"
183 s.ensureTemplateStopped(template)
184 manager := s.makeManager(c, "test")
185 instance := containertesting.StartContainer(c, manager, "1")
186 id := string(instance.Id())
187 s.AssertEvent(c, <-s.events, mock.Created, template)
188 s.AssertEvent(c, <-s.events, mock.Started, template)
189 s.AssertEvent(c, <-s.events, mock.Stopped, template)
190 s.AssertEvent(c, <-s.events, mock.Cloned, template)
191 s.AssertEvent(c, <-s.events, mock.Started, id)
192}
193
194func (s *LxcSuite) createTemplate(c *gc.C) golxc.Container {
195 name := "juju-series-template"
196 s.ensureTemplateStopped(name)
197 network := container.BridgeNetworkConfig("nic42")
198 authorizedKeys := "authorized keys list"
199 aptProxy := osenv.ProxySettings{}
200 template, err := lxc.EnsureCloneTemplate(
201 "ext4", "series", network, authorizedKeys, aptProxy)
202 c.Assert(err, gc.IsNil)
203 c.Assert(template.Name(), gc.Equals, name)
204 s.AssertEvent(c, <-s.events, mock.Created, name)
205 s.AssertEvent(c, <-s.events, mock.Started, name)
206 s.AssertEvent(c, <-s.events, mock.Stopped, name)
207
208 autostartLink := lxc.RestartSymlink(name)
209 config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name))
210 c.Assert(err, gc.IsNil)
211 expected := `
212lxc.network.type = veth
213lxc.network.link = nic42
214lxc.network.flags = up
215`
216 // NOTE: no autostart, no mounting the log dir
217 c.Assert(string(config), gc.Equals, expected)
218 c.Assert(autostartLink, jc.DoesNotExist)
219
220 return template
221}
222
223func (s *LxcSuite) TestStartContainerEventsWithCloneExistingTemplate(c *gc.C) {
224 s.createTemplate(c)
225 s.PatchValue(&s.useClone, true)
226 manager := s.makeManager(c, "test")
227 instance := containertesting.StartContainer(c, manager, "1")
228 name := string(instance.Id())
229 s.AssertEvent(c, <-s.events, mock.Cloned, "juju-series-template")
230 s.AssertEvent(c, <-s.events, mock.Started, name)
231
232 autostartLink := lxc.RestartSymlink(name)
233 config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name))
234 c.Assert(err, gc.IsNil)
235 mountLine := "lxc.mount.entry=/var/log/juju var/log/juju none defaults,bind 0 0"
236 c.Assert(string(config), jc.Contains, mountLine)
237 c.Assert(autostartLink, jc.IsSymlink)
238}
239
133func (s *LxcSuite) TestContainerState(c *gc.C) {240func (s *LxcSuite) TestContainerState(c *gc.C) {
134 manager := s.makeManager(c, "test")241 manager := s.makeManager(c, "test")
135 c.Logf("%#v", manager)242 c.Logf("%#v", manager)
136243
=== modified file 'container/lxc/mock/mock-lxc.go'
--- container/lxc/mock/mock-lxc.go 2014-03-11 03:49:20 +0000
+++ container/lxc/mock/mock-lxc.go 2014-03-13 02:11:05 +0000
@@ -5,17 +5,23 @@
55
6import (6import (
7 "fmt"7 "fmt"
8 "io/ioutil"
8 "os"9 "os"
9 "path/filepath"10 "path/filepath"
11 "sync"
1012
13 "github.com/juju/loggo"
11 "launchpad.net/golxc"14 "launchpad.net/golxc"
1215
16 "launchpad.net/juju-core/container"
13 "launchpad.net/juju-core/utils"17 "launchpad.net/juju-core/utils"
14)18)
1519
16// This file provides a mock implementation of the golxc interfaces20// This file provides a mock implementation of the golxc interfaces
17// ContainerFactory and Container.21// ContainerFactory and Container.
1822
23var logger = loggo.GetLogger("juju.container.lxc.mock")
24
19type Action int25type Action int
2026
21const (27const (
@@ -23,6 +29,12 @@
23 Started Action = iota29 Started Action = iota
24 // A container has been stopped.30 // A container has been stopped.
25 Stopped31 Stopped
32 // A container has been created.
33 Created
34 // A container has been destroyed.
35 Destroyed
36 // A container has been cloned.
37 Cloned
26)38)
2739
28func (action Action) String() string {40func (action Action) String() string {
@@ -31,6 +43,12 @@
31 return "Started"43 return "Started"
32 case Stopped:44 case Stopped:
33 return "Stopped"45 return "Stopped"
46 case Created:
47 return "Created"
48 case Destroyed:
49 return "Destroyed"
50 case Cloned:
51 return "Cloned"
34 }52 }
35 return "unknown"53 return "unknown"
36}54}
@@ -51,6 +69,7 @@
51 containerDir string69 containerDir string
52 instances map[string]golxc.Container70 instances map[string]golxc.Container
53 listeners []chan<- Event71 listeners []chan<- Event
72 mutex sync.Mutex
54}73}
5574
56func MockFactory(containerDir string) ContainerFactory {75func MockFactory(containerDir string) ContainerFactory {
@@ -68,53 +87,85 @@
68 logLevel golxc.LogLevel87 logLevel golxc.LogLevel
69}88}
7089
90func (mock *mockContainer) getState() golxc.State {
91 mock.factory.mutex.Lock()
92 defer mock.factory.mutex.Unlock()
93 return mock.state
94}
95
96func (mock *mockContainer) setState(newState golxc.State) {
97 mock.factory.mutex.Lock()
98 defer mock.factory.mutex.Unlock()
99 mock.state = newState
100 logger.Debugf("container %q state change to %s", mock.name, string(newState))
101}
102
71// Name returns the name of the container.103// Name returns the name of the container.
72func (mock *mockContainer) Name() string {104func (mock *mockContainer) Name() string {
73 return mock.name105 return mock.name
74}106}
75107
108func (mock *mockContainer) configFilename() string {
109 return filepath.Join(mock.factory.containerDir, mock.name, "config")
110}
111
76// Create creates a new container based on the given template.112// Create creates a new container based on the given template.
77func (mock *mockContainer) Create(configFile, template string, extraArgs []string, templateArgs []string) error {113func (mock *mockContainer) Create(configFile, template string, extraArgs []string, templateArgs []string) error {
78 if mock.state != golxc.StateUnknown {114 if mock.getState() != golxc.StateUnknown {
79 return fmt.Errorf("container is already created")115 return fmt.Errorf("container is already created")
80 }116 }
81 mock.state = golxc.StateStopped
82 mock.factory.instances[mock.name] = mock117 mock.factory.instances[mock.name] = mock
83 // Create the container directory.118 // Create the container directory.
84 containerDir := filepath.Join(mock.factory.containerDir, mock.name)119 containerDir := filepath.Join(mock.factory.containerDir, mock.name)
85 if err := os.MkdirAll(containerDir, 0755); err != nil {120 if err := os.MkdirAll(containerDir, 0755); err != nil {
86 return err121 return err
87 }122 }
88 containerConfig := filepath.Join(containerDir, "config")123 if err := utils.CopyFile(mock.configFilename(), configFile); err != nil {
89 return utils.CopyFile(containerConfig, configFile)124 return err
125 }
126 mock.setState(golxc.StateStopped)
127 mock.factory.notify(Created, mock.name)
128 return nil
90}129}
91130
92// Start runs the container as a daemon.131// Start runs the container as a daemon.
93func (mock *mockContainer) Start(configFile, consoleFile string) error {132func (mock *mockContainer) Start(configFile, consoleFile string) error {
94 if mock.state == golxc.StateUnknown {133 state := mock.getState()
134 if state == golxc.StateUnknown {
95 return fmt.Errorf("container has not been created")135 return fmt.Errorf("container has not been created")
96 } else if mock.state == golxc.StateRunning {136 } else if state == golxc.StateRunning {
97 return fmt.Errorf("container is already running")137 return fmt.Errorf("container is already running")
98 }138 }
99 mock.state = golxc.StateRunning139 ioutil.WriteFile(
140 filepath.Join(container.ContainerDir, mock.name, "console.log"),
141 []byte("fake console.log"), 0644)
142 mock.setState(golxc.StateRunning)
100 mock.factory.notify(Started, mock.name)143 mock.factory.notify(Started, mock.name)
101 return nil144 return nil
102}145}
103146
104// Stop terminates the running container.147// Stop terminates the running container.
105func (mock *mockContainer) Stop() error {148func (mock *mockContainer) Stop() error {
106 if mock.state == golxc.StateUnknown {149 state := mock.getState()
150 if state == golxc.StateUnknown {
107 return fmt.Errorf("container has not been created")151 return fmt.Errorf("container has not been created")
108 } else if mock.state == golxc.StateStopped {152 } else if state == golxc.StateStopped {
109 return fmt.Errorf("container is already stopped")153 return fmt.Errorf("container is already stopped")
110 }154 }
111 mock.state = golxc.StateStopped155 mock.setState(golxc.StateStopped)
112 mock.factory.notify(Stopped, mock.name)156 mock.factory.notify(Stopped, mock.name)
113 return nil157 return nil
114}158}
115159
116// Clone creates a copy of the container, giving the copy the specified name.160// Clone creates a copy of the container, giving the copy the specified name.
117func (mock *mockContainer) Clone(name string, extraArgs []string, templateArgs []string) (golxc.Container, error) {161func (mock *mockContainer) Clone(name string, extraArgs []string, templateArgs []string) (golxc.Container, error) {
162 state := mock.getState()
163 if state == golxc.StateUnknown {
164 return nil, fmt.Errorf("container has not been created")
165 } else if state == golxc.StateRunning {
166 return nil, fmt.Errorf("container is running, clone not possible")
167 }
168
118 container := &mockContainer{169 container := &mockContainer{
119 factory: mock.factory,170 factory: mock.factory,
120 name: name,171 name: name,
@@ -122,6 +173,17 @@
122 logLevel: golxc.LogWarning,173 logLevel: golxc.LogWarning,
123 }174 }
124 mock.factory.instances[name] = container175 mock.factory.instances[name] = container
176
177 // Create the container directory.
178 containerDir := filepath.Join(mock.factory.containerDir, name)
179 if err := os.MkdirAll(containerDir, 0755); err != nil {
180 return nil, err
181 }
182 if err := utils.CopyFile(container.configFilename(), mock.configFilename()); err != nil {
183 return nil, err
184 }
185
186 mock.factory.notify(Cloned, mock.name)
125 return container, nil187 return container, nil
126}188}
127189
@@ -137,15 +199,17 @@
137199
138// Destroy stops and removes the container.200// Destroy stops and removes the container.
139func (mock *mockContainer) Destroy() error {201func (mock *mockContainer) Destroy() error {
202 state := mock.getState()
140 // golxc destroy will stop the machine if it is running.203 // golxc destroy will stop the machine if it is running.
141 if mock.state == golxc.StateRunning {204 if state == golxc.StateRunning {
142 mock.Stop()205 mock.Stop()
143 }206 }
144 if mock.state == golxc.StateUnknown {207 if state == golxc.StateUnknown {
145 return fmt.Errorf("container has not been created")208 return fmt.Errorf("container has not been created")
146 }209 }
147 mock.state = golxc.StateUnknown
148 delete(mock.factory.instances, mock.name)210 delete(mock.factory.instances, mock.name)
211 mock.setState(golxc.StateUnknown)
212 mock.factory.notify(Destroyed, mock.name)
149 return nil213 return nil
150}214}
151215
@@ -157,27 +221,28 @@
157// Info returns the status and the process id of the container.221// Info returns the status and the process id of the container.
158func (mock *mockContainer) Info() (golxc.State, int, error) {222func (mock *mockContainer) Info() (golxc.State, int, error) {
159 pid := -1223 pid := -1
160 if mock.state == golxc.StateRunning {224 state := mock.getState()
225 if state == golxc.StateRunning {
161 pid = 42226 pid = 42
162 }227 }
163 return mock.state, pid, nil228 return state, pid, nil
164}229}
165230
166// IsConstructed checks if the container image exists.231// IsConstructed checks if the container image exists.
167func (mock *mockContainer) IsConstructed() bool {232func (mock *mockContainer) IsConstructed() bool {
168 return mock.state != golxc.StateUnknown233 return mock.getState() != golxc.StateUnknown
169}234}
170235
171// IsRunning checks if the state of the container is 'RUNNING'.236// IsRunning checks if the state of the container is 'RUNNING'.
172func (mock *mockContainer) IsRunning() bool {237func (mock *mockContainer) IsRunning() bool {
173 return mock.state == golxc.StateRunning238 return mock.getState() == golxc.StateRunning
174}239}
175240
176// String returns information about the container, like the name, state,241// String returns information about the container, like the name, state,
177// and process id.242// and process id.
178func (mock *mockContainer) String() string {243func (mock *mockContainer) String() string {
179 _, pid, _ := mock.Info()244 state, pid, _ := mock.Info()
180 return fmt.Sprintf("<MockContainer %q, state: %s, pid %d>", mock.name, string(mock.state), pid)245 return fmt.Sprintf("<MockContainer %q, state: %s, pid %d>", mock.name, string(state), pid)
181}246}
182247
183// LogFile returns the current filename used for the LogFile.248// LogFile returns the current filename used for the LogFile.
@@ -202,6 +267,8 @@
202}267}
203268
204func (mock *mockFactory) New(name string) golxc.Container {269func (mock *mockFactory) New(name string) golxc.Container {
270 mock.mutex.Lock()
271 defer mock.mutex.Unlock()
205 container, ok := mock.instances[name]272 container, ok := mock.instances[name]
206 if ok {273 if ok {
207 return container274 return container
@@ -212,6 +279,7 @@
212 state: golxc.StateUnknown,279 state: golxc.StateUnknown,
213 logLevel: golxc.LogWarning,280 logLevel: golxc.LogWarning,
214 }281 }
282 mock.instances[name] = container
215 return container283 return container
216}284}
217285
218286
=== modified file 'container/userdata.go'
--- container/userdata.go 2014-03-12 02:28:30 +0000
+++ container/userdata.go 2014-03-13 02:11:05 +0000
@@ -9,7 +9,6 @@
99
10 "github.com/juju/loggo"10 "github.com/juju/loggo"
1111
12 "launchpad.net/juju-core/agent"
13 coreCloudinit "launchpad.net/juju-core/cloudinit"12 coreCloudinit "launchpad.net/juju-core/cloudinit"
14 "launchpad.net/juju-core/environs/cloudinit"13 "launchpad.net/juju-core/environs/cloudinit"
15)14)
@@ -18,12 +17,21 @@
18 logger = loggo.GetLogger("juju.container")17 logger = loggo.GetLogger("juju.container")
19)18)
2019
20// WriteUserData generates the cloud init for the specified machine config,
21// and writes the serialized form out to a cloud-init file in the directory
22// specified.
21func WriteUserData(machineConfig *cloudinit.MachineConfig, directory string) (string, error) {23func WriteUserData(machineConfig *cloudinit.MachineConfig, directory string) (string, error) {
22 userData, err := cloudInitUserData(machineConfig)24 userData, err := cloudInitUserData(machineConfig)
23 if err != nil {25 if err != nil {
24 logger.Errorf("failed to create user data: %v", err)26 logger.Errorf("failed to create user data: %v", err)
25 return "", err27 return "", err
26 }28 }
29 return WriteCloudInitFile(directory, userData)
30}
31
32// WriteCloudInitFile writes the data out to a cloud-init file in the
33// directory specified, and returns the filename.
34func WriteCloudInitFile(directory string, userData []byte) (string, error) {
27 userDataFilename := filepath.Join(directory, "cloud-init")35 userDataFilename := filepath.Join(directory, "cloud-init")
28 if err := ioutil.WriteFile(userDataFilename, userData, 0644); err != nil {36 if err := ioutil.WriteFile(userDataFilename, userData, 0644); err != nil {
29 logger.Errorf("failed to write user data: %v", err)37 logger.Errorf("failed to write user data: %v", err)
@@ -33,8 +41,6 @@
33}41}
3442
35func cloudInitUserData(machineConfig *cloudinit.MachineConfig) ([]byte, error) {43func cloudInitUserData(machineConfig *cloudinit.MachineConfig) ([]byte, error) {
36 // consider not having this line hardcoded...
37 machineConfig.DataDir = agent.DefaultDataDir
38 cloudConfig := coreCloudinit.New()44 cloudConfig := coreCloudinit.New()
39 err := cloudinit.Configure(machineConfig, cloudConfig)45 err := cloudinit.Configure(machineConfig, cloudConfig)
40 if err != nil {46 if err != nil {
4147
=== modified file 'provider/local/environ.go'
--- provider/local/environ.go 2014-03-09 20:53:17 +0000
+++ provider/local/environ.go 2014-03-13 02:11:05 +0000
@@ -10,6 +10,7 @@
10 "os/exec"10 "os/exec"
11 "path/filepath"11 "path/filepath"
12 "regexp"12 "regexp"
13 "strconv"
13 "strings"14 "strings"
14 "sync"15 "sync"
15 "syscall"16 "syscall"
@@ -219,6 +220,7 @@
219 container.ManagerConfig{220 container.ManagerConfig{
220 container.ConfigName: env.config.namespace(),221 container.ConfigName: env.config.namespace(),
221 container.ConfigLogDir: env.config.logDir(),222 container.ConfigLogDir: env.config.logDir(),
223 "use-clone": strconv.FormatBool(env.fastLXC),
222 })224 })
223 if err != nil {225 if err != nil {
224 return err226 return err
225227
=== modified file 'worker/provisioner/lxc-broker_test.go'
--- worker/provisioner/lxc-broker_test.go 2014-03-07 03:00:24 +0000
+++ worker/provisioner/lxc-broker_test.go 2014-03-13 02:11:05 +0000
@@ -204,6 +204,8 @@
204func (s *lxcProvisionerSuite) expectStarted(c *gc.C, machine *state.Machine) string {204func (s *lxcProvisionerSuite) expectStarted(c *gc.C, machine *state.Machine) string {
205 s.State.StartSync()205 s.State.StartSync()
206 event := <-s.events206 event := <-s.events
207 c.Assert(event.Action, gc.Equals, mock.Created)
208 event = <-s.events
207 c.Assert(event.Action, gc.Equals, mock.Started)209 c.Assert(event.Action, gc.Equals, mock.Started)
208 err := machine.Refresh()210 err := machine.Refresh()
209 c.Assert(err, gc.IsNil)211 c.Assert(err, gc.IsNil)
@@ -215,6 +217,8 @@
215 s.State.StartSync()217 s.State.StartSync()
216 event := <-s.events218 event := <-s.events
217 c.Assert(event.Action, gc.Equals, mock.Stopped)219 c.Assert(event.Action, gc.Equals, mock.Stopped)
220 event = <-s.events
221 c.Assert(event.Action, gc.Equals, mock.Destroyed)
218 c.Assert(event.InstanceId, gc.Equals, instId)222 c.Assert(event.InstanceId, gc.Equals, instId)
219}223}
220224

Subscribers

People subscribed via source and target branches

to status/vote changes: