Merge lp:~thumper/juju-core/kvm-manager-start-stop into lp:~go-bot/juju-core/trunk
- kvm-manager-start-stop
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Tim Penhey |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2097 |
Proposed branch: | lp:~thumper/juju-core/kvm-manager-start-stop |
Merge into: | lp:~go-bot/juju-core/trunk |
Prerequisite: | lp:~thumper/juju-core/local-provider-syslog |
Diff against target: |
681 lines (+434/-30) 11 files modified
container/kvm/container.go (+56/-5) container/kvm/containerfactory.go (+22/-6) container/kvm/interface.go (+11/-2) container/kvm/kvm.go (+34/-3) container/kvm/kvm_test.go (+10/-4) container/kvm/libvirt.go (+128/-0) container/kvm/live_test.go (+139/-0) container/kvm/mock/mock-kvm.go (+7/-1) container/kvm/mock/mock-kvm_test.go (+18/-8) container/userdata.go (+1/-1) log/log.go (+8/-0) |
To merge this branch: | bzr merge lp:~thumper/juju-core/kvm-manager-start-stop |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+196072@code.launchpad.net |
Commit message
Implement kvm start, stop and list.
The implementation here uses a mix of:
* uvt-simplestrea
* uvt-kvm
* virsh
These are provided by the uvtool package and the
dependencies. The implementation of the low level
calls out to the shell are in container/
These low level calls are then used by the container
factory and container instances in container/kvm.
These are used by the container manager instance.
There are some live tests for the kvm calls.
cd container/kvm
sudo -E go test
This will take quite some time the first time it is
run on any particular machine as it is syncing the
kvm machine instances. But it does work \o/
The machines won't actually boot functional juju
machines in the test because the tools location is
fake.
Description of the change
Implement kvm start, stop and list.
The implementation here uses a mix of:
* uvt-simplestrea
* uvt-kvm
* virsh
These are provided by the uvtool package and the
dependencies. The implementation of the low level
calls out to the shell are in container/
These low level calls are then used by the container
factory and container instances in container/kvm.
These are used by the container manager instance.
There are some live tests for the kvm calls.
cd container/kvm
sudo -E go test
This will take quite some time the first time it is
run on any particular machine as it is syncing the
kvm machine instances. But it does work \o/
The machines won't actually boot functional juju
machines in the test because the tools location is
fake.
Tim Penhey (thumper) wrote : | # |
Ian Booth (wallyworld) wrote : | # |
LTGM with some suggestions.
https:/
File container/
https:/
container/
Perhaps a comment explaining why a pointer? ie inband signalling of
unknown state
https:/
container/
I'd prefer var bridge string here because "" is not a valid value
https:/
container/
Perhaps
id !c.isRunning() {
logger.
return nil
}
other stuff....
because the other stuff is more lines of code and I think it reads
better that way
https:/
File container/
https:/
container/
Generally, inside these sorts of methods implementing an interface, I
think we have been doing stuff like
return nil, fmt.Errorf("doing blah blah: %v", err)
to give some content to the final error message, rather that just return
nil, err
https:/
container/
ditto
https:/
File container/
https:/
container/
Aren't there tests we can do for start, stop etc when using mocks?
https:/
File container/
https:/
container/
Could we add some docstring saying what this file does and also what
packages it depends on being installed?
https:/
container/
(output string, err error) {
This looks like pretty generic boilerplate. Shouldn't we put this in a
utils package somewhere?
https:/
container/
Could we rename this to UserDataFile cause that's what it is and it
could be confused for the actual user data content otherwise. That's
what I thought till I read the rest of the code.
https:/
container/
way to match the lines.
s/Perhaps//
https:/
container/
machineListPatt
machineStatus is confusing as a variable name, since it could look like
is is being used to store the value of "status" from "id hostname
stat...
Tim Penhey (thumper) wrote : | # |
I'll push the diff once I have fixed the first one. It seems a bit
screwed up right now.
https:/
File container/
https:/
container/
On 2013/11/21 23:57:26, wallyworld wrote:
> Perhaps a comment explaining why a pointer? ie inband signalling of
unknown
> state
Done.
https:/
container/
On 2013/11/21 23:57:26, wallyworld wrote:
> I'd prefer var bridge string here because "" is not a valid value
Changed it, but "" is a technically valid value. If it isn't set, we
don't pass the --bridge value down to the underlying uvt-kvm call.
https:/
container/
On 2013/11/21 23:57:26, wallyworld wrote:
> Perhaps
> id !c.isRunning() {
> logger.Debugf(...)
> return nil
> }
> other stuff....
> because the other stuff is more lines of code and I think it reads
better that
> way
Done.
https:/
File container/
https:/
container/
On 2013/11/21 23:57:26, wallyworld wrote:
> Generally, inside these sorts of methods implementing an interface, I
think we
> have been doing stuff like
> return nil, fmt.Errorf("doing blah blah: %v", err)
> to give some content to the final error message, rather that just
return nil,
> err
Done.
https:/
container/
On 2013/11/21 23:57:26, wallyworld wrote:
> ditto
Done.
https:/
File container/
https:/
container/
On 2013/11/21 23:57:26, wallyworld wrote:
> Aren't there tests we can do for start, stop etc when using mocks?
Yes, and we actually do already. This comment was to remind myself to
implement the live tests, which I did.
https:/
File container/
https:/
container/
On 2013/11/21 23:57:26, wallyworld wrote:
> Could we add some docstring saying what this file does and also what
packages it
> depends on being installed?
Done.
https:/
container/
(output string, err error) {
On 2013/11/21 23:57:26, wallyworld wrote:
> This looks like pretty generic boilerplate. Shouldn't we put this in a
utils
> package somewhere?
Possibly, but it did provide me a very place to ramp up the logging to
test what was going on. I'd be a little loathed to rem...
Tim Penhey (thumper) wrote : | # |
Please take a look.
Preview Diff
1 | === modified file 'container/kvm/container.go' |
2 | --- container/kvm/container.go 2013-11-12 21:00:12 +0000 |
3 | +++ container/kvm/container.go 2013-11-25 04:11:41 +0000 |
4 | @@ -5,12 +5,18 @@ |
5 | |
6 | import ( |
7 | "fmt" |
8 | + |
9 | + "launchpad.net/juju-core/container" |
10 | + "launchpad.net/juju-core/log" |
11 | ) |
12 | |
13 | type kvmContainer struct { |
14 | factory *containerFactory |
15 | name string |
16 | - started bool |
17 | + // started is a three state boolean, true, false, or unknown |
18 | + // this allows for checking when we don't know, but using a |
19 | + // value if we already know it (like in the list situation). |
20 | + started *bool |
21 | } |
22 | |
23 | var _ Container = (*kvmContainer)(nil) |
24 | @@ -19,16 +25,61 @@ |
25 | return c.name |
26 | } |
27 | |
28 | -func (c *kvmContainer) Start() error { |
29 | - return fmt.Errorf("not implemented") |
30 | +func (c *kvmContainer) Start( |
31 | + series string, |
32 | + arch string, |
33 | + userDataFile string, |
34 | + network *container.NetworkConfig, |
35 | +) error { |
36 | + // TODO: handle memory, cpu, disk etc. |
37 | + logger.Debugf("Synchronise images for %s %s", series, arch) |
38 | + if err := SyncImages(series, arch); err != nil { |
39 | + return err |
40 | + } |
41 | + var bridge string |
42 | + if network != nil { |
43 | + if network.NetworkType == container.BridgeNetwork { |
44 | + bridge = network.Device |
45 | + } else { |
46 | + return log.LoggedErrorf(logger, "Non-bridge network devices not yet supported") |
47 | + } |
48 | + } |
49 | + logger.Debugf("Create the machine %s", c.name) |
50 | + if err := CreateMachine(CreateMachineParams{ |
51 | + Hostname: c.name, |
52 | + Series: series, |
53 | + Arch: arch, |
54 | + UserDataFile: userDataFile, |
55 | + NetworkBridge: bridge, |
56 | + }); err != nil { |
57 | + return err |
58 | + } |
59 | + |
60 | + logger.Debugf("Set machine %s to autostart", c.name) |
61 | + return AutostartMachine(c.name) |
62 | } |
63 | |
64 | func (c *kvmContainer) Stop() error { |
65 | - return fmt.Errorf("not implemented") |
66 | + if !c.IsRunning() { |
67 | + logger.Debugf("%s is already stopped", c.name) |
68 | + return nil |
69 | + } |
70 | + // Make started state unknown again. |
71 | + c.started = nil |
72 | + logger.Debugf("Stop %s", c.name) |
73 | + return DestroyMachine(c.name) |
74 | } |
75 | |
76 | func (c *kvmContainer) IsRunning() bool { |
77 | - return c.started |
78 | + if c.started != nil { |
79 | + return *c.started |
80 | + } |
81 | + machines, err := ListMachines() |
82 | + if err != nil { |
83 | + return false |
84 | + } |
85 | + c.started = isRunning(machines[c.name]) |
86 | + return *c.started |
87 | } |
88 | |
89 | func (c *kvmContainer) String() string { |
90 | |
91 | === modified file 'container/kvm/containerfactory.go' |
92 | --- container/kvm/containerfactory.go 2013-11-12 21:00:12 +0000 |
93 | +++ container/kvm/containerfactory.go 2013-11-25 04:11:41 +0000 |
94 | @@ -3,10 +3,6 @@ |
95 | |
96 | package kvm |
97 | |
98 | -import ( |
99 | - "fmt" |
100 | -) |
101 | - |
102 | type containerFactory struct { |
103 | } |
104 | |
105 | @@ -19,6 +15,26 @@ |
106 | } |
107 | } |
108 | |
109 | -func (factory *containerFactory) List() ([]Container, error) { |
110 | - return nil, fmt.Errorf("Not yet implemented") |
111 | +func isRunning(value string) *bool { |
112 | + var result *bool = new(bool) |
113 | + if value == "running" { |
114 | + *result = true |
115 | + } |
116 | + return result |
117 | +} |
118 | + |
119 | +func (factory *containerFactory) List() (result []Container, err error) { |
120 | + machines, err := ListMachines() |
121 | + if err != nil { |
122 | + return nil, err |
123 | + } |
124 | + for hostname, status := range machines { |
125 | + result = append(result, &kvmContainer{ |
126 | + factory: factory, |
127 | + name: hostname, |
128 | + started: isRunning(status), |
129 | + }) |
130 | + |
131 | + } |
132 | + return result, nil |
133 | } |
134 | |
135 | === modified file 'container/kvm/interface.go' |
136 | --- container/kvm/interface.go 2013-11-12 21:00:12 +0000 |
137 | +++ container/kvm/interface.go 2013-11-25 04:11:41 +0000 |
138 | @@ -3,6 +3,10 @@ |
139 | |
140 | package kvm |
141 | |
142 | +import ( |
143 | + "launchpad.net/juju-core/container" |
144 | +) |
145 | + |
146 | // Container represents a virtualized container instance and provides |
147 | // operations to create, maintain and destroy the container. |
148 | type Container interface { |
149 | @@ -11,8 +15,13 @@ |
150 | Name() string |
151 | |
152 | // Start runs the container as a daemon. |
153 | - // TODO: determine parameters |
154 | - Start() error |
155 | + // TODO: add constraints. |
156 | + Start( |
157 | + series string, |
158 | + arch string, |
159 | + userDataFile string, |
160 | + network *container.NetworkConfig, |
161 | + ) error |
162 | |
163 | // Stop terminates the running container. |
164 | Stop() error |
165 | |
166 | === modified file 'container/kvm/kvm.go' |
167 | --- container/kvm/kvm.go 2013-11-13 01:35:46 +0000 |
168 | +++ container/kvm/kvm.go 2013-11-25 04:11:41 +0000 |
169 | @@ -13,6 +13,9 @@ |
170 | "launchpad.net/juju-core/container" |
171 | "launchpad.net/juju-core/environs/cloudinit" |
172 | "launchpad.net/juju-core/instance" |
173 | + "launchpad.net/juju-core/log" |
174 | + "launchpad.net/juju-core/names" |
175 | + "launchpad.net/juju-core/version" |
176 | ) |
177 | |
178 | var ( |
179 | @@ -60,11 +63,39 @@ |
180 | machineConfig *cloudinit.MachineConfig, |
181 | series string, |
182 | network *container.NetworkConfig) (instance.Instance, error) { |
183 | - return nil, fmt.Errorf("not yet implemented") |
184 | + |
185 | + name := names.MachineTag(machineConfig.MachineId) |
186 | + if manager.name != "" { |
187 | + name = fmt.Sprintf("%s-%s", manager.name, name) |
188 | + } |
189 | + // Note here that the kvmObjectFacotry only returns a valid container |
190 | + // object, and doesn't actually construct the underlying kvm container on |
191 | + // disk. |
192 | + kvmContainer := KvmObjectFactory.New(name) |
193 | + |
194 | + // Create the cloud-init. |
195 | + directory, err := container.NewDirectory(name) |
196 | + if err != nil { |
197 | + return nil, fmt.Errorf("failed to create container directory: %v", err) |
198 | + } |
199 | + logger.Tracef("write cloud-init") |
200 | + userDataFilename, err := container.WriteUserData(machineConfig, directory) |
201 | + if err != nil { |
202 | + return nil, log.LoggedErrorf(logger, "failed to write user data: %v", err) |
203 | + } |
204 | + // Create the container. |
205 | + logger.Tracef("create the container") |
206 | + if err := kvmContainer.Start(series, version.Current.Arch, userDataFilename, network); err != nil { |
207 | + return nil, log.LoggedErrorf(logger, "kvm container creation failed: %v", err) |
208 | + } |
209 | + logger.Tracef("kvm container created") |
210 | + return &kvmInstance{kvmContainer, name}, nil |
211 | } |
212 | |
213 | -func (manager *containerManager) StopContainer(instance.Instance) error { |
214 | - return fmt.Errorf("not yet implemented") |
215 | +func (manager *containerManager) StopContainer(instance instance.Instance) error { |
216 | + name := string(instance.Id()) |
217 | + kvmContainer := KvmObjectFactory.New(name) |
218 | + return kvmContainer.Stop() |
219 | } |
220 | |
221 | func (manager *containerManager) ListContainers() (result []instance.Instance, err error) { |
222 | |
223 | === modified file 'container/kvm/kvm_test.go' |
224 | --- container/kvm/kvm_test.go 2013-11-12 21:00:12 +0000 |
225 | +++ container/kvm/kvm_test.go 2013-11-25 04:11:41 +0000 |
226 | @@ -11,6 +11,7 @@ |
227 | kvmtesting "launchpad.net/juju-core/container/kvm/testing" |
228 | "launchpad.net/juju-core/instance" |
229 | jc "launchpad.net/juju-core/testing/checkers" |
230 | + "launchpad.net/juju-core/version" |
231 | ) |
232 | |
233 | type KVMSuite struct { |
234 | @@ -19,7 +20,11 @@ |
235 | |
236 | var _ = gc.Suite(&KVMSuite{}) |
237 | |
238 | -// TODO: work out how to test the actual kvm implementations. |
239 | +func (*KVMSuite) TestManagerNameNeeded(c *gc.C) { |
240 | + manager, err := kvm.NewContainerManager(container.ManagerConfig{}) |
241 | + c.Assert(err, gc.ErrorMatches, "name is required") |
242 | + c.Assert(manager, gc.IsNil) |
243 | +} |
244 | |
245 | func (*KVMSuite) TestListInitiallyEmpty(c *gc.C) { |
246 | manager, err := kvm.NewContainerManager(container.ManagerConfig{Name: "test"}) |
247 | @@ -30,9 +35,10 @@ |
248 | } |
249 | |
250 | func (s *KVMSuite) createRunningContainer(c *gc.C, name string) kvm.Container { |
251 | - container := s.Factory.New(name) |
252 | - c.Assert(container.Start(), gc.IsNil) |
253 | - return container |
254 | + kvmContainer := s.Factory.New(name) |
255 | + network := container.BridgeNetworkConfig("testbr0") |
256 | + c.Assert(kvmContainer.Start("quantal", version.Current.Arch, "userdata.txt", network), gc.IsNil) |
257 | + return kvmContainer |
258 | } |
259 | |
260 | func (s *KVMSuite) TestListMatchesManagerName(c *gc.C) { |
261 | |
262 | === added file 'container/kvm/libvirt.go' |
263 | --- container/kvm/libvirt.go 1970-01-01 00:00:00 +0000 |
264 | +++ container/kvm/libvirt.go 2013-11-25 04:11:41 +0000 |
265 | @@ -0,0 +1,128 @@ |
266 | +// Copyright 2013 Canonical Ltd. |
267 | +// Licensed under the AGPLv3, see LICENCE file for details. |
268 | + |
269 | +package kvm |
270 | + |
271 | +// This file contains wrappers around the following executables: |
272 | +// uvt-simplestreams-libvirt |
273 | +// uvt-kvm |
274 | +// virsh |
275 | +// Those executables are found in the following packages: |
276 | +// uvtool-libvirt |
277 | +// libvirt-bin |
278 | +// |
279 | +// These executables provide Juju's interface to dealing with kvm containers. |
280 | +// The define how we start, stop and list running containers on the host |
281 | + |
282 | +import ( |
283 | + "fmt" |
284 | + "os/exec" |
285 | + "regexp" |
286 | + "strings" |
287 | +) |
288 | + |
289 | +var ( |
290 | + // The regular expression for breaking up the results of 'virsh list' |
291 | + // (?m) - specify that this is a multiline regex |
292 | + // first part is the opaque identifier we don't care about |
293 | + // then the hostname, and lastly the status. |
294 | + machineListPattern = regexp.MustCompile(`(?m)^\s+\d+\s+(?P<hostname>[-\w]+)\s+(?P<status>.+)\s*$`) |
295 | +) |
296 | + |
297 | +// run the command and return the combined output. |
298 | +func run(command string, args ...string) (output string, err error) { |
299 | + logger.Tracef("%s %v", command, args) |
300 | + cmd := exec.Command(command, args...) |
301 | + out, err := cmd.CombinedOutput() |
302 | + output = string(out) |
303 | + logger.Tracef("output: %v", output) |
304 | + if err != nil { |
305 | + return output, err |
306 | + } |
307 | + if !cmd.ProcessState.Success() { |
308 | + return output, fmt.Errorf("%s returned non-zero exit", command) |
309 | + } |
310 | + return output, nil |
311 | +} |
312 | + |
313 | +// SyncImages updates the local cached images by reading the simplestreams |
314 | +// data and downloading the cloud images to the uvtool pool (used by libvirt). |
315 | +func SyncImages(series string, arch string) error { |
316 | + args := []string{ |
317 | + "sync", |
318 | + fmt.Sprintf("arch=%s", arch), |
319 | + fmt.Sprintf("release=%s", series), |
320 | + } |
321 | + _, err := run("uvt-simplestreams-libvirt", args...) |
322 | + return err |
323 | +} |
324 | + |
325 | +type CreateMachineParams struct { |
326 | + Hostname string |
327 | + Series string |
328 | + Arch string |
329 | + UserDataFile string |
330 | + NetworkBridge string |
331 | + // TODO memory, cpu and disk |
332 | +} |
333 | + |
334 | +// CreateMachine creates a virtual machine and starts it. |
335 | +func CreateMachine(params CreateMachineParams) error { |
336 | + if params.Hostname == "" { |
337 | + return fmt.Errorf("Hostname is required") |
338 | + } |
339 | + args := []string{ |
340 | + "create", |
341 | + "--log-console-output", // do wonder where this goes... |
342 | + } |
343 | + if params.UserDataFile != "" { |
344 | + args = append(args, "--user-data", params.UserDataFile) |
345 | + } |
346 | + if params.NetworkBridge != "" { |
347 | + args = append(args, "--bridge", params.NetworkBridge) |
348 | + } |
349 | + // TODO add memory, cpu and disk prior to hostname |
350 | + args = append(args, params.Hostname) |
351 | + if params.Series != "" { |
352 | + args = append(args, fmt.Sprintf("release=%s", params.Series)) |
353 | + } |
354 | + if params.Arch != "" { |
355 | + args = append(args, fmt.Sprintf("arch=%s", params.Arch)) |
356 | + } |
357 | + output, err := run("uvt-kvm", args...) |
358 | + logger.Debugf("is this the logged output?:\n%s", output) |
359 | + return err |
360 | +} |
361 | + |
362 | +// DestroyMachine destroys the virtual machine identified by hostname. |
363 | +func DestroyMachine(hostname string) error { |
364 | + _, err := run("uvt-kvm", "destroy", hostname) |
365 | + return err |
366 | +} |
367 | + |
368 | +// AutostartMachine indicates that the virtual machines should automatically |
369 | +// restart when the host restarts. |
370 | +func AutostartMachine(hostname string) error { |
371 | + _, err := run("virsh", "autostart", hostname) |
372 | + return err |
373 | +} |
374 | + |
375 | +// ListMachines returns a map of machine name to state, where state is one of: |
376 | +// running, idle, paused, shutdown, shut off, crashed, dying, pmsuspended. |
377 | +func ListMachines() (map[string]string, error) { |
378 | + output, err := run("virsh", "-q", "list", "--all") |
379 | + if err != nil { |
380 | + return nil, err |
381 | + } |
382 | + // Split the output into lines. |
383 | + // Regex matching is the easiest way to match the lines. |
384 | + // id hostname status |
385 | + // separated by whitespace, with whitespace at the start too. |
386 | + result := make(map[string]string) |
387 | + for _, s := range machineListPattern.FindAllStringSubmatchIndex(output, -1) { |
388 | + hostnameAndStatus := machineListPattern.ExpandString(nil, "$hostname $status", output, s) |
389 | + parts := strings.SplitN(string(hostnameAndStatus), " ", 2) |
390 | + result[parts[0]] = parts[1] |
391 | + } |
392 | + return result, nil |
393 | +} |
394 | |
395 | === added file 'container/kvm/live_test.go' |
396 | --- container/kvm/live_test.go 1970-01-01 00:00:00 +0000 |
397 | +++ container/kvm/live_test.go 2013-11-25 04:11:41 +0000 |
398 | @@ -0,0 +1,139 @@ |
399 | +// Copyright 2013 Canonical Ltd. |
400 | +// Licensed under the AGPLv3, see LICENCE file for details. |
401 | + |
402 | +package kvm_test |
403 | + |
404 | +import ( |
405 | + "os" |
406 | + "runtime" |
407 | + |
408 | + gc "launchpad.net/gocheck" |
409 | + "launchpad.net/loggo" |
410 | + |
411 | + "launchpad.net/juju-core/constraints" |
412 | + "launchpad.net/juju-core/container" |
413 | + "launchpad.net/juju-core/container/kvm" |
414 | + "launchpad.net/juju-core/environs" |
415 | + "launchpad.net/juju-core/environs/config" |
416 | + "launchpad.net/juju-core/instance" |
417 | + jujutesting "launchpad.net/juju-core/juju/testing" |
418 | + coretesting "launchpad.net/juju-core/testing" |
419 | + "launchpad.net/juju-core/testing/testbase" |
420 | + "launchpad.net/juju-core/tools" |
421 | + "launchpad.net/juju-core/version" |
422 | +) |
423 | + |
424 | +type LiveSuite struct { |
425 | + testbase.LoggingSuite |
426 | + ContainerDir string |
427 | + RemovedDir string |
428 | +} |
429 | + |
430 | +var _ = gc.Suite(&LiveSuite{}) |
431 | + |
432 | +func (s *LiveSuite) SetUpTest(c *gc.C) { |
433 | + s.LoggingSuite.SetUpTest(c) |
434 | + // Skip if not linux |
435 | + if runtime.GOOS != "linux" { |
436 | + c.Skip("not running linux") |
437 | + } |
438 | + // Skip if not running as root. |
439 | + if os.Getuid() != 0 { |
440 | + c.Skip("not running as root") |
441 | + } |
442 | + s.ContainerDir = c.MkDir() |
443 | + s.PatchValue(&container.ContainerDir, s.ContainerDir) |
444 | + s.RemovedDir = c.MkDir() |
445 | + s.PatchValue(&container.RemovedContainerDir, s.RemovedDir) |
446 | + loggo.GetLogger("juju.container").SetLogLevel(loggo.TRACE) |
447 | +} |
448 | + |
449 | +func (s *LiveSuite) newManager(c *gc.C, name string) container.Manager { |
450 | + manager, err := kvm.NewContainerManager( |
451 | + container.ManagerConfig{ |
452 | + Name: name, |
453 | + LogDir: c.MkDir(), |
454 | + }) |
455 | + c.Assert(err, gc.IsNil) |
456 | + return manager |
457 | +} |
458 | + |
459 | +func assertNumberOfContainers(c *gc.C, manager container.Manager, count int) { |
460 | + containers, err := manager.ListContainers() |
461 | + c.Assert(err, gc.IsNil) |
462 | + c.Assert(containers, gc.HasLen, count) |
463 | +} |
464 | + |
465 | +func (s *LiveSuite) TestNoInitialContainers(c *gc.C) { |
466 | + manager := s.newManager(c, "test") |
467 | + assertNumberOfContainers(c, manager, 0) |
468 | +} |
469 | + |
470 | +func shutdownMachines(manager container.Manager) func(*gc.C) { |
471 | + return func(c *gc.C) { |
472 | + instances, err := manager.ListContainers() |
473 | + c.Assert(err, gc.IsNil) |
474 | + for _, instance := range instances { |
475 | + err := manager.StopContainer(instance) |
476 | + c.Check(err, gc.IsNil) |
477 | + } |
478 | + } |
479 | +} |
480 | + |
481 | +func startContainer(c *gc.C, manager container.Manager, machineId string) instance.Instance { |
482 | + machineNonce := "fake-nonce" |
483 | + stateInfo := jujutesting.FakeStateInfo(machineId) |
484 | + apiInfo := jujutesting.FakeAPIInfo(machineId) |
485 | + machineConfig := environs.NewMachineConfig(machineId, machineNonce, stateInfo, apiInfo) |
486 | + network := container.BridgeNetworkConfig("virbr0") |
487 | + |
488 | + machineConfig.Tools = &tools.Tools{ |
489 | + Version: version.MustParseBinary("2.3.4-foo-bar"), |
490 | + URL: "http://tools.testing.invalid/2.3.4-foo-bar.tgz", |
491 | + } |
492 | + environConfig := dummyConfig(c) |
493 | + err := environs.FinishMachineConfig(machineConfig, environConfig, constraints.Value{}) |
494 | + c.Assert(err, gc.IsNil) |
495 | + |
496 | + instance, err := manager.StartContainer(machineConfig, "precise", network) |
497 | + c.Assert(err, gc.IsNil) |
498 | + return instance |
499 | +} |
500 | + |
501 | +func (s *LiveSuite) TestShutdownMachines(c *gc.C) { |
502 | + manager := s.newManager(c, "test") |
503 | + startContainer(c, manager, "1/kvm/0") |
504 | + startContainer(c, manager, "1/kvm/1") |
505 | + assertNumberOfContainers(c, manager, 2) |
506 | + |
507 | + shutdownMachines(manager)(c) |
508 | + assertNumberOfContainers(c, manager, 0) |
509 | +} |
510 | + |
511 | +func (s *LiveSuite) TestManagerIsolation(c *gc.C) { |
512 | + firstManager := s.newManager(c, "first") |
513 | + s.AddCleanup(shutdownMachines(firstManager)) |
514 | + |
515 | + startContainer(c, firstManager, "1/kvm/0") |
516 | + startContainer(c, firstManager, "1/kvm/1") |
517 | + |
518 | + secondManager := s.newManager(c, "second") |
519 | + s.AddCleanup(shutdownMachines(secondManager)) |
520 | + |
521 | + startContainer(c, secondManager, "1/kvm/0") |
522 | + |
523 | + assertNumberOfContainers(c, firstManager, 2) |
524 | + assertNumberOfContainers(c, secondManager, 1) |
525 | +} |
526 | + |
527 | +func dummyConfig(c *gc.C) *config.Config { |
528 | + testConfig, err := config.New(config.UseDefaults, coretesting.FakeConfig()) |
529 | + c.Assert(err, gc.IsNil) |
530 | + testConfig, err = testConfig.Apply(map[string]interface{}{ |
531 | + "type": "dummy", |
532 | + "state-server": false, |
533 | + "agent-version": version.Current.Number.String(), |
534 | + }) |
535 | + c.Assert(err, gc.IsNil) |
536 | + return testConfig |
537 | +} |
538 | |
539 | === modified file 'container/kvm/mock/mock-kvm.go' |
540 | --- container/kvm/mock/mock-kvm.go 2013-11-12 21:00:12 +0000 |
541 | +++ container/kvm/mock/mock-kvm.go 2013-11-25 04:11:41 +0000 |
542 | @@ -6,6 +6,7 @@ |
543 | import ( |
544 | "fmt" |
545 | |
546 | + "launchpad.net/juju-core/container" |
547 | "launchpad.net/juju-core/container/kvm" |
548 | ) |
549 | |
550 | @@ -66,7 +67,12 @@ |
551 | return mock.name |
552 | } |
553 | |
554 | -func (mock *mockContainer) Start() error { |
555 | +func (mock *mockContainer) Start( |
556 | + series string, |
557 | + arch string, |
558 | + userDataFile string, |
559 | + network *container.NetworkConfig, |
560 | +) error { |
561 | if mock.started { |
562 | return fmt.Errorf("container is already running") |
563 | } |
564 | |
565 | === modified file 'container/kvm/mock/mock-kvm_test.go' |
566 | --- container/kvm/mock/mock-kvm_test.go 2013-11-12 21:13:21 +0000 |
567 | +++ container/kvm/mock/mock-kvm_test.go 2013-11-25 04:11:41 +0000 |
568 | @@ -6,17 +6,27 @@ |
569 | import ( |
570 | gc "launchpad.net/gocheck" |
571 | |
572 | + "launchpad.net/juju-core/container" |
573 | "launchpad.net/juju-core/container/kvm" |
574 | "launchpad.net/juju-core/container/kvm/mock" |
575 | jc "launchpad.net/juju-core/testing/checkers" |
576 | "launchpad.net/juju-core/testing/testbase" |
577 | + "launchpad.net/juju-core/version" |
578 | ) |
579 | |
580 | type MockSuite struct { |
581 | testbase.LoggingSuite |
582 | } |
583 | |
584 | -var _ = gc.Suite(&MockSuite{}) |
585 | +var ( |
586 | + _ = gc.Suite(&MockSuite{}) |
587 | + |
588 | + // Start args |
589 | + series = "quantal" |
590 | + arch = version.Current.Arch |
591 | + userData = "/some/path/to/userdata.txt" |
592 | + network = container.BridgeNetworkConfig("testbr0") |
593 | +) |
594 | |
595 | func (*MockSuite) TestListInitiallyEmpty(c *gc.C) { |
596 | factory := mock.MockFactory() |
597 | @@ -52,7 +62,7 @@ |
598 | func (*MockSuite) TestContainerStartStarts(c *gc.C) { |
599 | factory := mock.MockFactory() |
600 | container := factory.New("first") |
601 | - err := container.Start() |
602 | + err := container.Start(series, arch, userData, network) |
603 | c.Assert(err, gc.IsNil) |
604 | c.Assert(container.IsRunning(), jc.IsTrue) |
605 | } |
606 | @@ -60,16 +70,16 @@ |
607 | func (*MockSuite) TestContainerStartingRunningErrors(c *gc.C) { |
608 | factory := mock.MockFactory() |
609 | container := factory.New("first") |
610 | - err := container.Start() |
611 | + err := container.Start(series, arch, userData, network) |
612 | c.Assert(err, gc.IsNil) |
613 | - err = container.Start() |
614 | + err = container.Start(series, arch, userData, network) |
615 | c.Assert(err, gc.ErrorMatches, "container is already running") |
616 | } |
617 | |
618 | func (*MockSuite) TestContainerStoppingRunningStops(c *gc.C) { |
619 | factory := mock.MockFactory() |
620 | container := factory.New("first") |
621 | - err := container.Start() |
622 | + err := container.Start(series, arch, userData, network) |
623 | c.Assert(err, gc.IsNil) |
624 | err = container.Stop() |
625 | c.Assert(err, gc.IsNil) |
626 | @@ -132,8 +142,8 @@ |
627 | |
628 | first := factory.New("first") |
629 | second := factory.New("second") |
630 | - first.Start() |
631 | - second.Start() |
632 | + first.Start(series, arch, userData, network) |
633 | + second.Start(series, arch, userData, network) |
634 | second.Stop() |
635 | first.Stop() |
636 | |
637 | @@ -151,7 +161,7 @@ |
638 | factory.AddListener(second) |
639 | |
640 | container := factory.New("container") |
641 | - container.Start() |
642 | + container.Start(series, arch, userData, network) |
643 | container.Stop() |
644 | |
645 | c.Assert(<-first, gc.Equals, mock.Event{mock.Started, "container"}) |
646 | |
647 | === modified file 'container/userdata.go' |
648 | --- container/userdata.go 2013-11-14 08:27:15 +0000 |
649 | +++ container/userdata.go 2013-11-25 04:11:41 +0000 |
650 | @@ -17,7 +17,7 @@ |
651 | ) |
652 | |
653 | var ( |
654 | - logger = loggo.GetLogger("juju.container.lxc") |
655 | + logger = loggo.GetLogger("juju.container") |
656 | aptHTTPProxyRE = regexp.MustCompile(`(?i)^Acquire::HTTP::Proxy\s+"([^"]+)";$`) |
657 | ) |
658 | |
659 | |
660 | === modified file 'log/log.go' |
661 | --- log/log.go 2013-06-10 04:25:51 +0000 |
662 | +++ log/log.go 2013-11-25 04:11:41 +0000 |
663 | @@ -4,6 +4,8 @@ |
664 | package log |
665 | |
666 | import ( |
667 | + "fmt" |
668 | + |
669 | "launchpad.net/loggo" |
670 | ) |
671 | |
672 | @@ -41,3 +43,9 @@ |
673 | logger.Logf(loggo.DEBUG, format, a...) |
674 | return nil |
675 | } |
676 | + |
677 | +// Log the error and return an error with the same text. |
678 | +func LoggedErrorf(logger loggo.Logger, format string, a ...interface{}) error { |
679 | + logger.Logf(loggo.ERROR, format, a...) |
680 | + return fmt.Errorf(format, a...) |
681 | +} |
Reviewers: mp+196072_ code.launchpad. net,
Message:
Please take a look.
Description:
Implement kvm start, stop and list.
The implementation here uses a mix of: m-libvirt
* uvt-simplestrea
* uvt-kvm
* virsh
These are provided by the uvtool package and the kvm/libvirt. go
dependencies. The implementation of the low level
calls out to the shell are in container/
These low level calls are then used by the container
factory and container instances in container/kvm.
These are used by the container manager instance.
There are some live tests for the kvm calls.
cd container/kvm
sudo -E go test
This will take quite some time the first time it is
run on any particular machine as it is syncing the
kvm machine instances. But it does work \o/
The machines won't actually boot functional juju
machines in the test because the tools location is
fake.
https:/ /code.launchpad .net/~thumper/ juju-core/ kvm-manager- start-stop/ +merge/ 196072
Requires: /code.launchpad .net/~thumper/ juju-core/ local-provider- syslog/ +merge/ 196048
https:/
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/30020044/
Affected files (+424, -29 lines): kvm/container. go kvm/containerfa ctory.go kvm/interface. go kvm/kvm. go kvm/kvm_ test.go kvm/libvirt. go kvm/live_ test.go kvm/mock/ mock-kvm. go kvm/mock/ mock-kvm_ test.go userdata. go
A [revision details]
M container/
M container/
M container/
M container/
M container/
A container/
A container/
M container/
M container/
M container/
M log/log.go