Merge lp:~thumper/juju-core/kvm-local-provider 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: 2115
Proposed branch: lp:~thumper/juju-core/kvm-local-provider
Merge into: lp:~go-bot/juju-core/trunk
Prerequisite: lp:~thumper/juju-core/container-factory
Diff against target: 597 lines (+238/-48)
11 files modified
container/kvm/initialisation.go (+46/-0)
container/kvm/libvirt.go (+4/-11)
provider/local/config.go (+8/-1)
provider/local/environ.go (+6/-2)
provider/local/environprovider.go (+16/-1)
provider/local/prereqs.go (+13/-13)
provider/local/prereqs_test.go (+8/-7)
utils/apt.go (+17/-0)
utils/apt_test.go (+49/-13)
utils/command.go (+19/-0)
utils/command_test.go (+52/-0)
To merge this branch: bzr merge lp:~thumper/juju-core/kvm-local-provider
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+196465@code.launchpad.net

Commit message

Allow the local provider to use kvm.

Add a 'container' config value for the local provider.
The allows a local provider to use kvm instead of lxc
if the host machine is kvm capable.

https://codereview.appspot.com/31870043/

Description of the change

Allow the local provider to use kvm.

Add a 'container' config value for the local provider.
The allows a local provider to use kvm instead of lxc
if the host machine is kvm capable.

TODO: prereqs needs to check for other needed packages.

https://codereview.appspot.com/31870043/

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

Reviewers: mp+196465_code.launchpad.net,

Message:
Please take a look.

Description:
Allow the local provider to use kvm.

Add a 'container' config value for the local provider.
The allows a local provider to use kvm instead of lxc
if the host machine is kvm capable.

TODO: prereqs needs to check for other needed packages.

https://code.launchpad.net/~thumper/juju-core/kvm-local-provider/+merge/196465

Requires:
https://code.launchpad.net/~thumper/juju-core/container-factory/+merge/196073

(do not edit description out of merge proposal)

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

Affected files (+91, -13 lines):
   A [revision details]
   M provider/local/config.go
   M provider/local/environ.go
   M provider/local/environprovider.go
   M provider/local/prereqs.go
   M provider/local/prereqs_test.go

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

LGTM with primarily the consolidation of the code to check kvm container
dependencies with the code to handle kvm initialisation. I also would
like the command code in apt moved. I'm not sure about the need for a
ContainerConfig interface but anytime we need to rely on UnknownAttrs()
seems a bit hacky.

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

https://codereview.appspot.com/31870043/diff/20001/container/kvm/libvirt.go#newcode36
container/kvm/libvirt.go:36: output, err = utils.RunCommand(command,
args...)
\o/

https://codereview.appspot.com/31870043/diff/20001/provider/local/config.go
File provider/local/config.go (right):

https://codereview.appspot.com/31870043/diff/20001/provider/local/config.go#newcode23
provider/local/config.go:23: // to explicitly get from the config
unknown params.
My brain found it hard to parse the 2nd line above

https://codereview.appspot.com/31870043/diff/20001/provider/local/environprovider.go
File provider/local/environprovider.go (right):

https://codereview.appspot.com/31870043/diff/20001/provider/local/environprovider.go#newcode49
provider/local/environprovider.go:49: containerType, ok :=
cfg.UnknownAttrs()[containerConfigKey].(string)
Part of me really doesn't like this UnknownAttrs() business and would
love to see a proper cfg.ContainerType() API available. Not sure if we
should introduce a ContainerConfig interface and cast to that?

https://codereview.appspot.com/31870043/diff/20001/provider/local/environprovider.go#newcode148
provider/local/environprovider.go:148: if localConfig.container() !=
"lxc" && localConfig.container() != "kvm" {
Can we use the ContainerType consts here instead of "lxc", "kvm"

https://codereview.appspot.com/31870043/diff/20001/provider/local/prereqs.go
File provider/local/prereqs.go (right):

https://codereview.appspot.com/31870043/diff/20001/provider/local/prereqs.go#newcode50
provider/local/prereqs.go:50: const kvmNeedsUbuntu = `Sorry, but KVM
support with the local provider is only supported
s/but//

https://codereview.appspot.com/31870043/diff/20001/provider/local/prereqs.go#newcode154
provider/local/prereqs.go:154: packagesNeeded := []string{"libvirt-bin",
"uvtool-libvirt", "kvm"}
There's also code in the container.kvm package to deal with installing
the required packages - see initialisation.go

I'd prefer this verifyKvm() function to be moved there so it's all done
in one place.

https://codereview.appspot.com/31870043/diff/20001/utils/apt.go
File utils/apt.go (right):

https://codereview.appspot.com/31870043/diff/20001/utils/apt.go#newcode87
utils/apt.go:87: func RunCommand(command string, args ...string) (output
string, err error) {
I'd like to see this in a file called command.go since it's not really
anything to do with apt per se. And tests in command_test.go

https://codereview.appspot.com/31870043/

Revision history for this message
Jorge Castro (jorge) wrote :

Thumper please remember to submit an update to the local provider docs as you finish this up!

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

Please take a look.

https://codereview.appspot.com/31870043/diff/20001/provider/local/config.go
File provider/local/config.go (right):

https://codereview.appspot.com/31870043/diff/20001/provider/local/config.go#newcode23
provider/local/config.go:23: // to explicitly get from the config
unknown params.
On 2013/12/02 07:10:58, wallyworld wrote:
> My brain found it hard to parse the 2nd line above

tweaked slightly

https://codereview.appspot.com/31870043/diff/20001/provider/local/environprovider.go
File provider/local/environprovider.go (right):

https://codereview.appspot.com/31870043/diff/20001/provider/local/environprovider.go#newcode49
provider/local/environprovider.go:49: containerType, ok :=
cfg.UnknownAttrs()[containerConfigKey].(string)
On 2013/12/02 07:10:58, wallyworld wrote:
> Part of me really doesn't like this UnknownAttrs() business and would
love to
> see a proper cfg.ContainerType() API available. Not sure if we should
introduce
> a ContainerConfig interface and cast to that?

Refactored for better reading and understanding and taking the very
small overhead of creating the local config twice.

https://codereview.appspot.com/31870043/diff/20001/provider/local/environprovider.go#newcode148
provider/local/environprovider.go:148: if localConfig.container() !=
"lxc" && localConfig.container() != "kvm" {
On 2013/12/02 07:10:58, wallyworld wrote:
> Can we use the ContainerType consts here instead of "lxc", "kvm"

Yes.

https://codereview.appspot.com/31870043/diff/20001/provider/local/prereqs.go
File provider/local/prereqs.go (right):

https://codereview.appspot.com/31870043/diff/20001/provider/local/prereqs.go#newcode50
provider/local/prereqs.go:50: const kvmNeedsUbuntu = `Sorry, but KVM
support with the local provider is only supported
On 2013/12/02 07:10:58, wallyworld wrote:
> s/but//

Done.

https://codereview.appspot.com/31870043/diff/20001/provider/local/prereqs.go#newcode154
provider/local/prereqs.go:154: packagesNeeded := []string{"libvirt-bin",
"uvtool-libvirt", "kvm"}
On 2013/12/02 07:10:58, wallyworld wrote:
> There's also code in the container.kvm package to deal with installing
the
> required packages - see initialisation.go

> I'd prefer this verifyKvm() function to be moved there so it's all
done in one
> place.

Done.

https://codereview.appspot.com/31870043/diff/20001/utils/apt.go
File utils/apt.go (right):

https://codereview.appspot.com/31870043/diff/20001/utils/apt.go#newcode87
utils/apt.go:87: func RunCommand(command string, args ...string) (output
string, err error) {
On 2013/12/02 07:10:58, wallyworld wrote:
> I'd like to see this in a file called command.go since it's not really
anything
> to do with apt per se. And tests in command_test.go

Done.

https://codereview.appspot.com/31870043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'container/kvm/initialisation.go'
2--- container/kvm/initialisation.go 2013-11-15 06:36:39 +0000
3+++ container/kvm/initialisation.go 2013-12-02 21:28:17 +0000
4@@ -4,6 +4,9 @@
5 package kvm
6
7 import (
8+ "fmt"
9+ "strings"
10+
11 "launchpad.net/juju-core/container"
12 "launchpad.net/juju-core/utils"
13 )
14@@ -33,3 +36,46 @@
15 func ensureDependencies() error {
16 return utils.AptGetInstall(requiredPackages...)
17 }
18+
19+const kvmNeedsUbuntu = `Sorry, KVM support with the local provider is only supported
20+on the Ubuntu OS.`
21+
22+const kvmNotSupported = `KVM is not currently supported with the current settings.
23+You could try running 'kvm-ok' yourself as root to get the full rationale as to
24+why it isn't supported, or potentially some BIOS settings to change to enable
25+KVM support.`
26+
27+const neetToInstallKVMOk = `kvm-ok is not installed. Please install the cpu-checker package.
28+ sudo apt-get install cpu-checker`
29+
30+const missingKVMDeps = `Some required packages are missing for KVM to work:
31+
32+ sudo apt-get install %s
33+`
34+
35+// VerifyKVMEnabled makes sure that the host OS is Ubuntu, and that the required
36+// packages are installed, and that the host CPU is able to support KVM.
37+func VerifyKVMEnabled() error {
38+ if !utils.IsUbuntu() {
39+ return fmt.Errorf(kvmNeedsUbuntu)
40+ }
41+ supported, err := IsKVMSupported()
42+ if err != nil {
43+ // Missing the kvm-ok package.
44+ return fmt.Errorf(neetToInstallKVMOk)
45+ }
46+ if !supported {
47+ return fmt.Errorf(kvmNotSupported)
48+ }
49+ // Check for other packages needed.
50+ toInstall := []string{}
51+ for _, pkg := range requiredPackages {
52+ if !utils.IsPackageInstalled(pkg) {
53+ toInstall = append(toInstall, pkg)
54+ }
55+ }
56+ if len(toInstall) > 0 {
57+ return fmt.Errorf(missingKVMDeps, strings.Join(toInstall, " "))
58+ }
59+ return nil
60+}
61
62=== modified file 'container/kvm/libvirt.go'
63--- container/kvm/libvirt.go 2013-11-22 03:34:42 +0000
64+++ container/kvm/libvirt.go 2013-12-02 21:28:17 +0000
65@@ -16,9 +16,10 @@
66
67 import (
68 "fmt"
69- "os/exec"
70 "regexp"
71 "strings"
72+
73+ "launchpad.net/juju-core/utils"
74 )
75
76 var (
77@@ -32,17 +33,9 @@
78 // run the command and return the combined output.
79 func run(command string, args ...string) (output string, err error) {
80 logger.Tracef("%s %v", command, args)
81- cmd := exec.Command(command, args...)
82- out, err := cmd.CombinedOutput()
83- output = string(out)
84+ output, err = utils.RunCommand(command, args...)
85 logger.Tracef("output: %v", output)
86- if err != nil {
87- return output, err
88- }
89- if !cmd.ProcessState.Success() {
90- return output, fmt.Errorf("%s returned non-zero exit", command)
91- }
92- return output, nil
93+ return output, err
94 }
95
96 // SyncImages updates the local cached images by reading the simplestreams
97
98=== modified file 'provider/local/config.go'
99--- provider/local/config.go 2013-10-03 03:03:09 +0000
100+++ provider/local/config.go 2013-12-02 21:28:17 +0000
101@@ -9,6 +9,7 @@
102 "path/filepath"
103
104 "launchpad.net/juju-core/environs/config"
105+ "launchpad.net/juju-core/instance"
106 "launchpad.net/juju-core/schema"
107 "launchpad.net/juju-core/utils"
108 )
109@@ -22,6 +23,7 @@
110 "root-dir": schema.String(),
111 "bootstrap-ip": schema.String(),
112 "network-bridge": schema.String(),
113+ "container": schema.String(),
114 "storage-port": schema.ForceInt(),
115 "shared-storage-port": schema.ForceInt(),
116 }
117@@ -32,6 +34,7 @@
118 configDefaults = schema.Defaults{
119 "root-dir": "",
120 "network-bridge": "lxcbr0",
121+ "container": string(instance.LXC),
122 "bootstrap-ip": schema.Omit,
123 "storage-port": 8040,
124 "shared-storage-port": 8041,
125@@ -64,7 +67,7 @@
126
127 // Since it is technically possible for two different users on one machine to
128 // have the same local provider name, we need to have a simple way to
129-// namespace the file locations, but more importantly the lxc containers.
130+// namespace the file locations, but more importantly the containers.
131 func (c *environConfig) namespace() string {
132 return fmt.Sprintf("%s-%s", c.user, c.Name())
133 }
134@@ -73,6 +76,10 @@
135 return c.attrs["root-dir"].(string)
136 }
137
138+func (c *environConfig) container() instance.ContainerType {
139+ return instance.ContainerType(c.attrs["container"].(string))
140+}
141+
142 func (c *environConfig) networkBridge() string {
143 return c.attrs["network-bridge"].(string)
144 }
145
146=== modified file 'provider/local/environ.go'
147--- provider/local/environ.go 2013-11-26 12:24:48 +0000
148+++ provider/local/environ.go 2013-12-02 21:28:17 +0000
149@@ -17,7 +17,7 @@
150 agenttools "launchpad.net/juju-core/agent/tools"
151 "launchpad.net/juju-core/constraints"
152 "launchpad.net/juju-core/container"
153- "launchpad.net/juju-core/container/lxc"
154+ "launchpad.net/juju-core/container/factory"
155 "launchpad.net/juju-core/environs"
156 "launchpad.net/juju-core/environs/bootstrap"
157 "launchpad.net/juju-core/environs/cloudinit"
158@@ -194,11 +194,15 @@
159 env.config = ecfg
160 env.name = ecfg.Name()
161
162- env.containerManager = lxc.NewContainerManager(
163+ env.containerManager, err = factory.NewContainerManager(
164+ ecfg.container(),
165 container.ManagerConfig{
166 Name: env.config.namespace(),
167 LogDir: env.config.logDir(),
168 })
169+ if err != nil {
170+ return err
171+ }
172
173 // Here is the end of normal config setting.
174 if ecfg.bootstrapped() {
175
176=== modified file 'provider/local/environprovider.go'
177--- provider/local/environprovider.go 2013-10-14 15:01:07 +0000
178+++ provider/local/environprovider.go 2013-12-02 21:28:17 +0000
179@@ -12,6 +12,7 @@
180
181 "launchpad.net/juju-core/environs"
182 "launchpad.net/juju-core/environs/config"
183+ "launchpad.net/juju-core/instance"
184 "launchpad.net/juju-core/provider"
185 "launchpad.net/juju-core/utils"
186 "launchpad.net/juju-core/version"
187@@ -41,7 +42,12 @@
188 }
189 cfg = newCfg
190 }
191- if err := VerifyPrerequisites(); err != nil {
192+ // Do the initial validation on the config.
193+ localConfig, err := providerInstance.newConfig(cfg)
194+ if err != nil {
195+ return nil, err
196+ }
197+ if err := VerifyPrerequisites(localConfig.container()); err != nil {
198 logger.Errorf("failed verification of local provider prerequisites: %v", err)
199 return nil, err
200 }
201@@ -109,6 +115,11 @@
202 if err != nil {
203 return nil, fmt.Errorf("old config is not a valid local config: %v", old)
204 }
205+ if localConfig.container() != oldLocalConfig.container() {
206+ return nil, fmt.Errorf("cannot change container from %q to %q",
207+ oldLocalConfig.container(),
208+ localConfig.container())
209+ }
210 if localConfig.rootDir() != oldLocalConfig.rootDir() {
211 return nil, fmt.Errorf("cannot change root-dir from %q to %q",
212 oldLocalConfig.rootDir(),
213@@ -130,6 +141,10 @@
214 localConfig.sharedStoragePort())
215 }
216 }
217+ // Currently only supported containers are "lxc" and "kvm".
218+ if localConfig.container() != instance.LXC && localConfig.container() != instance.KVM {
219+ return nil, fmt.Errorf("unsupported container type: %q", localConfig.container())
220+ }
221 dir := utils.NormalizePath(localConfig.rootDir())
222 if dir == "." {
223 dir = config.JujuHomePath(cfg.Name())
224
225=== modified file 'provider/local/prereqs.go'
226--- provider/local/prereqs.go 2013-08-09 06:42:05 +0000
227+++ provider/local/prereqs.go 2013-12-02 21:28:17 +0000
228@@ -9,8 +9,10 @@
229 "os"
230 "os/exec"
231 "runtime"
232- "strings"
233
234+ "launchpad.net/juju-core/container/kvm"
235+ "launchpad.net/juju-core/instance"
236+ "launchpad.net/juju-core/utils"
237 "launchpad.net/juju-core/version"
238 )
239
240@@ -61,14 +63,20 @@
241 // VerifyPrerequisites verifies the prerequisites of
242 // the local machine (machine 0) for running the local
243 // provider.
244-func VerifyPrerequisites() error {
245+func VerifyPrerequisites(containerType instance.ContainerType) error {
246 if goos != "linux" {
247 return fmt.Errorf(errUnsupportedOS, goos)
248 }
249 if err := verifyMongod(); err != nil {
250 return err
251 }
252- return verifyLxc()
253+ switch containerType {
254+ case instance.LXC:
255+ return verifyLxc()
256+ case instance.KVM:
257+ return kvm.VerifyKVMEnabled()
258+ }
259+ return fmt.Errorf("Unknown container type specified in the config.")
260 }
261
262 func verifyMongod() error {
263@@ -91,16 +99,8 @@
264 return nil
265 }
266
267-func isUbuntu() bool {
268- out, err := exec.Command("lsb_release", "-i", "-s").CombinedOutput()
269- if err != nil {
270- return false
271- }
272- return strings.TrimSpace(string(out)) == "Ubuntu"
273-}
274-
275 func wrapMongodNotExist(err error) error {
276- if isUbuntu() {
277+ if utils.IsUbuntu() {
278 series := version.Current.Series
279 args := []interface{}{err, installMongodUbuntu}
280 format := "%v\n%s\n%s"
281@@ -115,7 +115,7 @@
282 }
283
284 func wrapLxcNotFound(err error) error {
285- if isUbuntu() {
286+ if utils.IsUbuntu() {
287 return fmt.Errorf("%v\n%s", err, installLxcUbuntu)
288 }
289 return fmt.Errorf("%v\n%s", err, installLxcGeneric)
290
291=== modified file 'provider/local/prereqs_test.go'
292--- provider/local/prereqs_test.go 2013-09-20 02:53:59 +0000
293+++ provider/local/prereqs_test.go 2013-12-02 21:28:17 +0000
294@@ -10,6 +10,7 @@
295
296 gc "launchpad.net/gocheck"
297
298+ "launchpad.net/juju-core/instance"
299 "launchpad.net/juju-core/testing/testbase"
300 )
301
302@@ -59,17 +60,17 @@
303 goos = old
304 }(goos)
305 goos = "windows"
306- err := VerifyPrerequisites()
307+ err := VerifyPrerequisites(instance.LXC)
308 c.Assert(err, gc.ErrorMatches, "Unsupported operating system: windows(.|\n)*")
309 }
310
311 func (s *prereqsSuite) TestMongoPrereq(c *gc.C) {
312- err := VerifyPrerequisites()
313+ err := VerifyPrerequisites(instance.LXC)
314 c.Assert(err, gc.ErrorMatches, "(.|\n)*MongoDB server must be installed(.|\n)*")
315 c.Assert(err, gc.ErrorMatches, "(.|\n)*apt-get install mongodb-server(.|\n)*")
316
317 os.Setenv("JUJUTEST_LSB_RELEASE_ID", "NotUbuntu")
318- err = VerifyPrerequisites()
319+ err = VerifyPrerequisites(instance.LXC)
320 c.Assert(err, gc.ErrorMatches, "(.|\n)*MongoDB server must be installed(.|\n)*")
321 c.Assert(err, gc.Not(gc.ErrorMatches), "(.|\n)*apt-get install(.|\n)*")
322
323@@ -77,7 +78,7 @@
324 c.Assert(err, gc.IsNil)
325 err = ioutil.WriteFile(filepath.Join(s.tmpdir, "lxc-ls"), nil, 0777)
326 c.Assert(err, gc.IsNil)
327- err = VerifyPrerequisites()
328+ err = VerifyPrerequisites(instance.LXC)
329 c.Assert(err, gc.IsNil)
330 }
331
332@@ -85,17 +86,17 @@
333 err := ioutil.WriteFile(mongodPath, nil, 0777)
334 c.Assert(err, gc.IsNil)
335
336- err = VerifyPrerequisites()
337+ err = VerifyPrerequisites(instance.LXC)
338 c.Assert(err, gc.ErrorMatches, "(.|\n)*Linux Containers \\(LXC\\) userspace tools must be\ninstalled(.|\n)*")
339 c.Assert(err, gc.ErrorMatches, "(.|\n)*apt-get install lxc(.|\n)*")
340
341 os.Setenv("JUJUTEST_LSB_RELEASE_ID", "NotUbuntu")
342- err = VerifyPrerequisites()
343+ err = VerifyPrerequisites(instance.LXC)
344 c.Assert(err, gc.ErrorMatches, "(.|\n)*Linux Containers \\(LXC\\) userspace tools must be installed(.|\n)*")
345 c.Assert(err, gc.Not(gc.ErrorMatches), "(.|\n)*apt-get install(.|\n)*")
346
347 err = ioutil.WriteFile(lxclsPath, nil, 0777)
348 c.Assert(err, gc.IsNil)
349- err = VerifyPrerequisites()
350+ err = VerifyPrerequisites(instance.LXC)
351 c.Assert(err, gc.IsNil)
352 }
353
354=== modified file 'utils/apt.go'
355--- utils/apt.go 2013-11-15 06:36:39 +0000
356+++ utils/apt.go 2013-12-02 21:28:17 +0000
357@@ -9,6 +9,7 @@
358 "os"
359 "os/exec"
360 "regexp"
361+ "strings"
362
363 "launchpad.net/loggo"
364 )
365@@ -72,3 +73,19 @@
366 }
367 return string(bytes.Join(aptProxyRE.FindAll(out, -1), []byte("\n"))), nil
368 }
369+
370+// IsUbuntu executes lxb_release to see if the host OS is Ubuntu.
371+func IsUbuntu() bool {
372+ out, err := RunCommand("lsb_release", "-i", "-s")
373+ if err != nil {
374+ return false
375+ }
376+ return strings.TrimSpace(out) == "Ubuntu"
377+}
378+
379+// IsPackageInstalled uses dpkg-query to determine if the `packageName`
380+// package is installed.
381+func IsPackageInstalled(packageName string) bool {
382+ _, err := RunCommand("dpkg-query", "--status", packageName)
383+ return err == nil
384+}
385
386=== modified file 'utils/apt_test.go'
387--- utils/apt_test.go 2013-11-15 06:36:39 +0000
388+++ utils/apt_test.go 2013-12-02 21:28:17 +0000
389@@ -1,14 +1,16 @@
390 // Copyright 2012, 2013 Canonical Ltd.
391 // Licensed under the AGPLv3, see LICENCE file for details.
392
393-package utils
394+package utils_test
395
396 import (
397 "fmt"
398
399 gc "launchpad.net/gocheck"
400
401+ jc "launchpad.net/juju-core/testing/checkers"
402 "launchpad.net/juju-core/testing/testbase"
403+ "launchpad.net/juju-core/utils"
404 )
405
406 type AptSuite struct {
407@@ -18,9 +20,9 @@
408 var _ = gc.Suite(&AptSuite{})
409
410 func (s *AptSuite) TestOnePackage(c *gc.C) {
411- cmdChan, cleanup := testbase.HookCommandOutput(&AptCommandOutput, []byte{}, nil)
412+ cmdChan, cleanup := testbase.HookCommandOutput(&utils.AptCommandOutput, []byte{}, nil)
413 defer cleanup()
414- err := AptGetInstall("test-package")
415+ err := utils.AptGetInstall("test-package")
416 c.Assert(err, gc.IsNil)
417 cmd := <-cmdChan
418 c.Assert(cmd.Args, gc.DeepEquals, []string{
419@@ -35,9 +37,9 @@
420 const expected = `E: frobnicator failure detected`
421 cmdError := fmt.Errorf("error")
422 cmdExpectedError := fmt.Errorf("apt-get failed: error")
423- cmdChan, cleanup := testbase.HookCommandOutput(&AptCommandOutput, []byte(expected), cmdError)
424+ cmdChan, cleanup := testbase.HookCommandOutput(&utils.AptCommandOutput, []byte(expected), cmdError)
425 defer cleanup()
426- err := AptGetInstall("foo")
427+ err := utils.AptGetInstall("foo")
428 c.Assert(err, gc.DeepEquals, cmdExpectedError)
429 cmd := <-cmdChan
430 c.Assert(cmd.Args, gc.DeepEquals, []string{
431@@ -48,9 +50,9 @@
432 }
433
434 func (s *AptSuite) TestConfigProxyEmpty(c *gc.C) {
435- cmdChan, cleanup := testbase.HookCommandOutput(&AptCommandOutput, []byte{}, nil)
436+ cmdChan, cleanup := testbase.HookCommandOutput(&utils.AptCommandOutput, []byte{}, nil)
437 defer cleanup()
438- out, err := AptConfigProxy()
439+ out, err := utils.AptConfigProxy()
440 c.Assert(err, gc.IsNil)
441 cmd := <-cmdChan
442 c.Assert(cmd.Args, gc.DeepEquals, []string{
443@@ -63,9 +65,9 @@
444 func (s *AptSuite) TestConfigProxyConfigured(c *gc.C) {
445 const expected = `Acquire::http::Proxy "10.0.3.1:3142";
446 Acquire::https::Proxy "false";`
447- cmdChan, cleanup := testbase.HookCommandOutput(&AptCommandOutput, []byte(expected), nil)
448+ cmdChan, cleanup := testbase.HookCommandOutput(&utils.AptCommandOutput, []byte(expected), nil)
449 defer cleanup()
450- out, err := AptConfigProxy()
451+ out, err := utils.AptConfigProxy()
452 c.Assert(err, gc.IsNil)
453 cmd := <-cmdChan
454 c.Assert(cmd.Args, gc.DeepEquals, []string{
455@@ -83,9 +85,9 @@
456 expected = `Acquire::http::Proxy "10.0.3.1:3142";
457 Acquire::https::Proxy "false";`
458 )
459- cmdChan, cleanup := testbase.HookCommandOutput(&AptCommandOutput, []byte(output), nil)
460+ cmdChan, cleanup := testbase.HookCommandOutput(&utils.AptCommandOutput, []byte(output), nil)
461 defer cleanup()
462- out, err := AptConfigProxy()
463+ out, err := utils.AptConfigProxy()
464 c.Assert(err, gc.IsNil)
465 cmd := <-cmdChan
466 c.Assert(cmd.Args, gc.DeepEquals, []string{
467@@ -99,9 +101,9 @@
468 const expected = `E: frobnicator failure detected`
469 cmdError := fmt.Errorf("error")
470 cmdExpectedError := fmt.Errorf("apt-config failed: error")
471- cmdChan, cleanup := testbase.HookCommandOutput(&AptCommandOutput, []byte(expected), cmdError)
472+ cmdChan, cleanup := testbase.HookCommandOutput(&utils.AptCommandOutput, []byte(expected), cmdError)
473 defer cleanup()
474- out, err := AptConfigProxy()
475+ out, err := utils.AptConfigProxy()
476 c.Assert(err, gc.DeepEquals, cmdExpectedError)
477 cmd := <-cmdChan
478 c.Assert(cmd.Args, gc.DeepEquals, []string{
479@@ -110,3 +112,37 @@
480 })
481 c.Assert(out, gc.Equals, "")
482 }
483+
484+func (s *AptSuite) patchLsbRelease(c *gc.C, name string) {
485+ content := fmt.Sprintf("#!/bin/bash --norc\necho %s", name)
486+ patchExecutable(s, c.MkDir(), "lsb_release", content)
487+}
488+
489+func (s *AptSuite) TestIsUbuntu(c *gc.C) {
490+ s.patchLsbRelease(c, "Ubuntu")
491+ c.Assert(utils.IsUbuntu(), jc.IsTrue)
492+}
493+
494+func (s *AptSuite) TestIsNotUbuntu(c *gc.C) {
495+ s.patchLsbRelease(c, "Windows NT")
496+ c.Assert(utils.IsUbuntu(), jc.IsFalse)
497+}
498+
499+func (s *AptSuite) patchDpkgQuery(c *gc.C, installed bool) {
500+ rc := 0
501+ if !installed {
502+ rc = 1
503+ }
504+ content := fmt.Sprintf("#!/bin/bash --norc\nexit %v", rc)
505+ patchExecutable(s, c.MkDir(), "dpkg-query", content)
506+}
507+
508+func (s *AptSuite) TestIsPackageInstalled(c *gc.C) {
509+ s.patchDpkgQuery(c, true)
510+ c.Assert(utils.IsPackageInstalled("foo-bar"), jc.IsTrue)
511+}
512+
513+func (s *AptSuite) TestIsPackageNotInstalled(c *gc.C) {
514+ s.patchDpkgQuery(c, false)
515+ c.Assert(utils.IsPackageInstalled("foo-bar"), jc.IsFalse)
516+}
517
518=== added file 'utils/command.go'
519--- utils/command.go 1970-01-01 00:00:00 +0000
520+++ utils/command.go 2013-12-02 21:28:17 +0000
521@@ -0,0 +1,19 @@
522+// Copyright 2013 Canonical Ltd.
523+// Licensed under the AGPLv3, see LICENCE file for details.
524+
525+package utils
526+
527+import (
528+ "os/exec"
529+)
530+
531+// RunCommand executes the command and return the combined output.
532+func RunCommand(command string, args ...string) (output string, err error) {
533+ cmd := exec.Command(command, args...)
534+ out, err := cmd.CombinedOutput()
535+ output = string(out)
536+ if err != nil {
537+ return output, err
538+ }
539+ return output, nil
540+}
541
542=== added file 'utils/command_test.go'
543--- utils/command_test.go 1970-01-01 00:00:00 +0000
544+++ utils/command_test.go 2013-12-02 21:28:17 +0000
545@@ -0,0 +1,52 @@
546+// Copyright 2012, 2013 Canonical Ltd.
547+// Licensed under the AGPLv3, see LICENCE file for details.
548+
549+package utils_test
550+
551+import (
552+ "io/ioutil"
553+ "path/filepath"
554+
555+ gc "launchpad.net/gocheck"
556+
557+ "launchpad.net/juju-core/testing/testbase"
558+ "launchpad.net/juju-core/utils"
559+)
560+
561+type EnvironmentPatcher interface {
562+ PatchEnvironment(name, value string)
563+}
564+
565+func patchExecutable(patcher EnvironmentPatcher, dir, execName, script string) {
566+ patcher.PatchEnvironment("PATH", dir)
567+ filename := filepath.Join(dir, execName)
568+ ioutil.WriteFile(filename, []byte(script), 0755)
569+}
570+
571+type commandSuite struct {
572+ testbase.LoggingSuite
573+}
574+
575+var _ = gc.Suite(&commandSuite{})
576+
577+func (s *commandSuite) TestRunCommandCombinesOutput(c *gc.C) {
578+ content := `#!/bin/bash --norc
579+echo stdout
580+echo stderr 1>&2
581+`
582+ patchExecutable(s, c.MkDir(), "test-output", content)
583+ output, err := utils.RunCommand("test-output")
584+ c.Assert(err, gc.IsNil)
585+ c.Assert(output, gc.Equals, "stdout\nstderr\n")
586+}
587+
588+func (s *commandSuite) TestRunCommandNonZeroExit(c *gc.C) {
589+ content := `#!/bin/bash --norc
590+echo stdout
591+exit 42
592+`
593+ patchExecutable(s, c.MkDir(), "test-output", content)
594+ output, err := utils.RunCommand("test-output")
595+ c.Assert(err, gc.ErrorMatches, `exit status 42`)
596+ c.Assert(output, gc.Equals, "stdout\n")
597+}

Subscribers

People subscribed via source and target branches

to status/vote changes: