Merge lp:~thumper/juju-core/kvm-manager-start-stop 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: 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
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-simplestream-libvirt
 * 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/kvm/libvirt.go

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://codereview.appspot.com/30020044/

Description of the change

Implement kvm start, stop and list.

The implementation here uses a mix of:
 * uvt-simplestream-libvirt
 * 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/kvm/libvirt.go

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://codereview.appspot.com/30020044/

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

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:
  * uvt-simplestream-libvirt
  * 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/kvm/libvirt.go

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:
https://code.launchpad.net/~thumper/juju-core/local-provider-syslog/+merge/196048

(do not edit description out of merge proposal)

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

Affected files (+424, -29 lines):
   A [revision details]
   M container/kvm/container.go
   M container/kvm/containerfactory.go
   M container/kvm/interface.go
   M container/kvm/kvm.go
   M container/kvm/kvm_test.go
   A container/kvm/libvirt.go
   A container/kvm/live_test.go
   M container/kvm/mock/mock-kvm.go
   M container/kvm/mock/mock-kvm_test.go
   M container/userdata.go
   M log/log.go

Revision history for this message
Ian Booth (wallyworld) wrote :
Download full text (3.3 KiB)

LTGM with some suggestions.

https://codereview.appspot.com/30020044/diff/1/container/kvm/container.go
File container/kvm/container.go (right):

https://codereview.appspot.com/30020044/diff/1/container/kvm/container.go#newcode16
container/kvm/container.go:16: started *bool
Perhaps a comment explaining why a pointer? ie inband signalling of
unknown state

https://codereview.appspot.com/30020044/diff/1/container/kvm/container.go#newcode36
container/kvm/container.go:36: bridge := ""
I'd prefer var bridge string here because "" is not a valid value

https://codereview.appspot.com/30020044/diff/1/container/kvm/container.go#newcode60
container/kvm/container.go:60: if c.IsRunning() {
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

https://codereview.appspot.com/30020044/diff/1/container/kvm/kvm.go
File container/kvm/kvm.go (right):

https://codereview.appspot.com/30020044/diff/1/container/kvm/kvm.go#newcode78
container/kvm/kvm.go:78: return nil, err
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://codereview.appspot.com/30020044/diff/1/container/kvm/kvm.go#newcode84
container/kvm/kvm.go:84: return nil, err
ditto

https://codereview.appspot.com/30020044/diff/1/container/kvm/kvm_test.go
File container/kvm/kvm_test.go (right):

https://codereview.appspot.com/30020044/diff/1/container/kvm/kvm_test.go#newcode24
container/kvm/kvm_test.go:24:
Aren't there tests we can do for start, stop etc when using mocks?

https://codereview.appspot.com/30020044/diff/1/container/kvm/libvirt.go
File container/kvm/libvirt.go (right):

https://codereview.appspot.com/30020044/diff/1/container/kvm/libvirt.go#newcode5
container/kvm/libvirt.go:5:
Could we add some docstring saying what this file does and also what
packages it depends on being installed?

https://codereview.appspot.com/30020044/diff/1/container/kvm/libvirt.go#newcode22
container/kvm/libvirt.go:22: func run(command string, args ...string)
(output string, err error) {
This looks like pretty generic boilerplate. Shouldn't we put this in a
utils package somewhere?

https://codereview.appspot.com/30020044/diff/1/container/kvm/libvirt.go#newcode53
container/kvm/libvirt.go:53: UserData string
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://codereview.appspot.com/30020044/diff/1/container/kvm/libvirt.go#newcode107
container/kvm/libvirt.go:107: // Perhaps regex matching is the easiest
way to match the lines.
s/Perhaps//

https://codereview.appspot.com/30020044/diff/1/container/kvm/libvirt.go#newcode112
container/kvm/libvirt.go:112: machineStatus :=
machineListPattern.ExpandString(nil, "$hostname $status", output, s)
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...

Read more...

Revision history for this message
Tim Penhey (thumper) wrote :
Download full text (4.3 KiB)

I'll push the diff once I have fixed the first one. It seems a bit
screwed up right now.

https://codereview.appspot.com/30020044/diff/1/container/kvm/container.go
File container/kvm/container.go (right):

https://codereview.appspot.com/30020044/diff/1/container/kvm/container.go#newcode16
container/kvm/container.go:16: started *bool
On 2013/11/21 23:57:26, wallyworld wrote:
> Perhaps a comment explaining why a pointer? ie inband signalling of
unknown
> state

Done.

https://codereview.appspot.com/30020044/diff/1/container/kvm/container.go#newcode36
container/kvm/container.go:36: bridge := ""
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://codereview.appspot.com/30020044/diff/1/container/kvm/container.go#newcode60
container/kvm/container.go:60: if c.IsRunning() {
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://codereview.appspot.com/30020044/diff/1/container/kvm/kvm.go
File container/kvm/kvm.go (right):

https://codereview.appspot.com/30020044/diff/1/container/kvm/kvm.go#newcode78
container/kvm/kvm.go:78: return nil, err
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://codereview.appspot.com/30020044/diff/1/container/kvm/kvm.go#newcode84
container/kvm/kvm.go:84: return nil, err
On 2013/11/21 23:57:26, wallyworld wrote:
> ditto

Done.

https://codereview.appspot.com/30020044/diff/1/container/kvm/kvm_test.go
File container/kvm/kvm_test.go (right):

https://codereview.appspot.com/30020044/diff/1/container/kvm/kvm_test.go#newcode24
container/kvm/kvm_test.go:24:
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://codereview.appspot.com/30020044/diff/1/container/kvm/libvirt.go
File container/kvm/libvirt.go (right):

https://codereview.appspot.com/30020044/diff/1/container/kvm/libvirt.go#newcode5
container/kvm/libvirt.go:5:
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://codereview.appspot.com/30020044/diff/1/container/kvm/libvirt.go#newcode22
container/kvm/libvirt.go:22: func run(command string, args ...string)
(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...

Read more...

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+}

Subscribers

People subscribed via source and target branches

to status/vote changes: