diff -Nru juju-core-1.17.2/debian/changelog juju-core-1.17.3/debian/changelog --- juju-core-1.17.2/debian/changelog 2014-02-17 17:30:06.000000000 +0000 +++ juju-core-1.17.3/debian/changelog 2014-02-24 09:19:56.000000000 +0000 @@ -1,3 +1,10 @@ +juju-core (1.17.3-0ubuntu1) trusty; urgency=medium + + * New upstream point release (LP: #1271941, #834930, #1240667, #1274210): + - https://launchpad.net/juju-core/trunk/1.17.3 + + -- James Page Mon, 24 Feb 2014 09:19:55 +0000 + juju-core (1.17.2-0ubuntu4) trusty; urgency=medium * No change rebuild with gccgo-4.9 as default gccgo. diff -Nru juju-core-1.17.2/src/github.com/errgo/errgo/errgo.go juju-core-1.17.3/src/github.com/errgo/errgo/errgo.go --- juju-core-1.17.2/src/github.com/errgo/errgo/errgo.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/github.com/errgo/errgo/errgo.go 2014-02-20 16:11:14.000000000 +0000 @@ -0,0 +1,220 @@ +// errgo is a package that provides an easy way to annotate errors without +// losing the orginal error context. +package errgo + +import ( + "errors" + "fmt" + "path/filepath" + "runtime" + "strings" +) + +type annotation struct { + message string + err error + function string + file string + line int +} + +type annotatedError struct { + stack []annotation +} + +var _ error = (*annotatedError)(nil) + +func errorString(annotations []annotation) string { + parts := []string{} + err := annotations[0].err + var origin string + for i, a := range annotations { + if a.err != err { + origin = fmt.Sprintf(" (%s)", errorString(annotations[i:])) + break + } + if a.message != "" { + parts = append(parts, a.message) + } + } + message := strings.Join(parts, ", ") + if message == "" { + return fmt.Sprintf("%v%s", err, origin) + } + return fmt.Sprintf("%s: %v%s", message, err, origin) +} + +// Error returns the annotation where the annotations are joined by commas, +// and separated by the original error by a colon. If there are translated +// errors in the stack, the Error representation of the previous annotations +// are in brackets. +func (a *annotatedError) Error() string { + return errorString(a.stack) +} + +func (a *annotatedError) addAnnotation(an annotation) *annotatedError { + a.stack = append( + []annotation{an}, + a.stack...) + return a +} + +func partialPath(filename string, elements int) string { + if filename == "" { + return "" + } + if elements < 1 { + return filename + } + base := filepath.Base(filename) + if elements == 1 { + return base + } + dir := filepath.Dir(filename) + if dir != "." { + dir = partialPath(dir, elements-1) + } + return filepath.Join(dir, base) +} + +func newAnnotation(message string) annotation { + result := annotation{message: message} + pc, file, line, ok := runtime.Caller(3) + if ok { + result.file = file + result.line = line + result.function = runtime.FuncForPC(pc).Name() + } + return result +} + +func annotate(err error, message string) error { + a := newAnnotation(message) + if annotated, ok := err.(*annotatedError); ok { + a.err = annotated.stack[0].err + return annotated.addAnnotation(a) + } + a.err = err + return &annotatedError{[]annotation{a}} +} + +func New(format string, args ...interface{}) error { + return annotate(errors.New(fmt.Sprintf(format, args...)), "") +} + +// Trace records the location of the Trace call, and adds it to the annotation +// stack. +func Trace(err error) error { + return annotate(err, "") +} + +// Annotate is used to add extra context to an existing error. The location of +// the Annotate call is recorded with the annotations. The file, line and +// function are also recorded. +func Annotate(err error, message string) error { + return annotate(err, message) +} + +// Annotatef operates like Annotate, but uses the a format and args that match +// the fmt package. +func Annotatef(err error, format string, args ...interface{}) error { + return annotate(err, fmt.Sprintf(format, args...)) +} + +func translate(err, newError error, message string) error { + a := newAnnotation(message) + a.err = newError + if annotated, ok := err.(*annotatedError); ok { + return annotated.addAnnotation(a) + } + return &annotatedError{ + []annotation{ + a, + {err: err}, + }, + } +} + +// Translate records the newError along with the message in the annotation +// stack. +func Translate(err, newError error, message string) error { + return translate(err, newError, message) +} + +// Translatef operates like Translate, but uses the a format and args that +// match the fmt package. +func Translatef(err, newError error, format string, args ...interface{}) error { + return translate(err, newError, fmt.Sprintf(format, args)) +} + +// Check looks at the underling error to see if it matches the checker +// function. +func Check(err error, checker func(error) bool) bool { + if annotated, ok := err.(*annotatedError); ok { + return checker(annotated.stack[0].err) + } + return checker(err) +} + +// GetErrorStack returns a slice of errors stored in the annotated errors. If +// the error isn't an annotated error, a slice with a single value is +// returned. +func GetErrorStack(err error) []error { + if annotated, ok := err.(*annotatedError); ok { + result := []error{} + var last error + for _, a := range annotated.stack { + if a.err != last { + last = a.err + result = append(result, last) + } + } + return result + } + return []error{err} +} + +// OutputParams are used to control the look of the DetailedErrorStack. +type OutputParams struct { + Prefix string + FileDepth int +} + +// Default is a simple pre-defined params for the DetailedErrorStack method +// that has no prefix, and shows files to a depth of one. +var Default = OutputParams{FileDepth: 2} + +// DetailedErrorStack gives a slice containing the detailed error stack, +// annotation and original error, along with the location if it was recorded. +func DetailedErrorStack(err error, params OutputParams) string { + if annotated, ok := err.(*annotatedError); ok { + result := []string{} + size := len(annotated.stack) + for i, a := range annotated.stack { + errText := "" + if i == (size-1) || a.err != annotated.stack[i+1].err { + format := ": %v" + if a.message == "" { + format = "%v" + } + errText = fmt.Sprintf(format, a.err) + } + line := fmt.Sprintf( + "%s%s%s%s", + params.Prefix, + a.message, + errText, + a.location(params.FileDepth)) + result = append(result, line) + } + return strings.Join(result, "\n") + } + return err.Error() +} + +func (a *annotation) location(depth int) string { + if a.file != "" { + return fmt.Sprintf(" [%s:%v, %s]", partialPath(a.file, depth), a.line, a.function) + } + return "" +} diff -Nru juju-core-1.17.2/src/github.com/errgo/errgo/errgo_internal_test.go juju-core-1.17.3/src/github.com/errgo/errgo/errgo_internal_test.go --- juju-core-1.17.2/src/github.com/errgo/errgo/errgo_internal_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/github.com/errgo/errgo/errgo_internal_test.go 2014-02-20 16:11:14.000000000 +0000 @@ -0,0 +1,173 @@ +package errgo + +import ( + "fmt" + + gc "launchpad.net/gocheck" +) + +type internalSuite struct{} + +var _ = gc.Suite(&internalSuite{}) + +func (*internalSuite) TestPartialPath(c *gc.C) { + for i, test := range []struct { + filename string + count int + expected string + }{ + { + filename: "foo/bar/baz", + count: -1, + expected: "foo/bar/baz", + }, { + filename: "foo/bar/baz", + count: 0, + expected: "foo/bar/baz", + }, { + filename: "foo/bar/baz", + count: 1, + expected: "baz", + }, { + filename: "foo/bar/baz", + count: 2, + expected: "bar/baz", + }, { + filename: "foo/bar/baz", + count: 5, + expected: "foo/bar/baz", + }, { + filename: "", + count: 2, + expected: "", + }, + } { + c.Logf("Test %v", i) + c.Check(partialPath(test.filename, test.count), gc.Equals, test.expected) + } +} + +func (*internalSuite) TestInternalAnnotate(c *gc.C) { + for i, test := range []struct { + message string + errFunc func() error + expected []annotation + }{ + { + message: "structure of a simple annotation", + errFunc: two, + expected: []annotation{ + { + message: "two", + file: "errgo/test_functions_test.go", + line: 16, + function: "github.com/errgo/errgo.two", + err: fmt.Errorf("one"), + }, + }, + }, { + message: "structure of a stacked annotation", + errFunc: three, + expected: []annotation{ + { + message: "three", + file: "errgo/test_functions_test.go", + line: 20, + function: "github.com/errgo/errgo.three", + err: fmt.Errorf("one"), + }, { + message: "two", + file: "errgo/test_functions_test.go", + line: 16, + function: "github.com/errgo/errgo.two", + err: fmt.Errorf("one"), + }, + }, + }, { + message: "structure of a simple translation", + errFunc: transtwo, + expected: []annotation{ + { + message: "transtwo", + file: "errgo/test_functions_test.go", + line: 27, + function: "github.com/errgo/errgo.transtwo", + err: fmt.Errorf("translated"), + }, { + err: fmt.Errorf("one"), + }, + }, + }, { + message: "structure of a simple annotation then translated", + errFunc: transthree, + expected: []annotation{ + { + message: "transthree", + file: "errgo/test_functions_test.go", + line: 34, + function: "github.com/errgo/errgo.transthree", + err: fmt.Errorf("translated"), + }, { + message: "two", + file: "errgo/test_functions_test.go", + line: 16, + function: "github.com/errgo/errgo.two", + err: fmt.Errorf("one"), + }, + }, + }, { + message: "structure of an annotationed, translated annotation", + errFunc: four, + expected: []annotation{ + { + message: "four", + file: "errgo/test_functions_test.go", + line: 38, + function: "github.com/errgo/errgo.four", + err: fmt.Errorf("translated"), + }, { + message: "transthree", + file: "errgo/test_functions_test.go", + line: 34, + function: "github.com/errgo/errgo.transthree", + err: fmt.Errorf("translated"), + }, { + message: "two", + file: "errgo/test_functions_test.go", + line: 16, + function: "github.com/errgo/errgo.two", + err: fmt.Errorf("one"), + }, + }, + }, { + message: "test new", + errFunc: test_new, + expected: []annotation{ + { + message: "", + file: "errgo/test_functions_test.go", + line: 42, + function: "github.com/errgo/errgo.test_new", + err: fmt.Errorf("get location"), + }, + }, + }, + } { + c.Logf("%v: %s", i, test.message) + err := test.errFunc() + annotated, ok := err.(*annotatedError) + c.Assert(ok, gc.Equals, true) + c.Assert(annotated.stack, gc.HasLen, len(test.expected)) + for i, annotation := range test.expected { + c.Logf(" %v: %v", i, annotation) + stacked := annotated.stack[i] + c.Assert(stacked.message, gc.Equals, annotation.message) + c.Assert(partialPath(stacked.file, 2), gc.Equals, annotation.file) + c.Assert(stacked.function, gc.Equals, annotation.function) + c.Assert(stacked.line, gc.Equals, annotation.line) + // We use ErrorMatches here as we can't easily test identity. + c.Assert(stacked.err, gc.ErrorMatches, fmt.Sprint(annotation.err)) + } + + } +} diff -Nru juju-core-1.17.2/src/github.com/errgo/errgo/errgo_test.go juju-core-1.17.3/src/github.com/errgo/errgo/errgo_test.go --- juju-core-1.17.2/src/github.com/errgo/errgo/errgo_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/github.com/errgo/errgo/errgo_test.go 2014-02-20 16:11:14.000000000 +0000 @@ -0,0 +1,151 @@ +package errgo + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + gc "launchpad.net/gocheck" +) + +func Test(t *testing.T) { gc.TestingT(t) } + +type errgoSuite struct{} + +var _ = gc.Suite(&errgoSuite{}) + +func (*errgoSuite) TestErrorStringOneAnnotation(c *gc.C) { + first := fmt.Errorf("first error") + err := Annotate(first, "annotation") + c.Assert(err, gc.ErrorMatches, `annotation: first error`) +} + +func (*errgoSuite) TestErrorStringTwoAnnotations(c *gc.C) { + first := fmt.Errorf("first error") + err := Annotate(first, "annotation") + err = Annotate(err, "another") + c.Assert(err, gc.ErrorMatches, `another, annotation: first error`) +} + +func (*errgoSuite) TestErrorStringThreeAnnotations(c *gc.C) { + first := fmt.Errorf("first error") + err := Annotate(first, "annotation") + err = Annotate(err, "another") + err = Annotate(err, "third") + c.Assert(err, gc.ErrorMatches, `third, another, annotation: first error`) +} + +func (*errgoSuite) TestExampleAnnotations(c *gc.C) { + for _, test := range []struct { + err error + expected string + }{ + { + err: two(), + expected: "two: one", + }, { + err: three(), + expected: "three, two: one", + }, { + err: transtwo(), + expected: "transtwo: translated (one)", + }, { + err: transthree(), + expected: "transthree: translated (two: one)", + }, { + err: four(), + expected: "four, transthree: translated (two: one)", + }, + } { + c.Assert(test.err.Error(), gc.Equals, test.expected) + } +} + +func (*errgoSuite) TestAnnotatedErrorCheck(c *gc.C) { + // Look for a file that we know isn't there. + dir := c.MkDir() + _, err := os.Stat(filepath.Join(dir, "not-there")) + c.Assert(os.IsNotExist(err), gc.Equals, true) + c.Assert(Check(err, os.IsNotExist), gc.Equals, true) + + err = Annotate(err, "wrap it") + // Now the error itself isn't a 'IsNotExist'. + c.Assert(os.IsNotExist(err), gc.Equals, false) + // However if we use the Check method, it is. + c.Assert(Check(err, os.IsNotExist), gc.Equals, true) +} + +func (*errgoSuite) TestGetErrorStack(c *gc.C) { + for _, test := range []struct { + err error + matches []string + }{ + { + err: fmt.Errorf("testing"), + matches: []string{"testing"}, + }, { + err: Annotate(fmt.Errorf("testing"), "annotation"), + matches: []string{"testing"}, + }, { + err: one(), + matches: []string{"one"}, + }, { + err: two(), + matches: []string{"one"}, + }, { + err: three(), + matches: []string{"one"}, + }, { + err: transtwo(), + matches: []string{"translated", "one"}, + }, { + err: transthree(), + matches: []string{"translated", "one"}, + }, { + err: four(), + matches: []string{"translated", "one"}, + }, + } { + stack := GetErrorStack(test.err) + c.Assert(stack, gc.HasLen, len(test.matches)) + for i, match := range test.matches { + c.Assert(stack[i], gc.ErrorMatches, match) + } + } +} + +func (*errgoSuite) TestDetailedErrorStack(c *gc.C) { + for _, test := range []struct { + err error + expected string + }{ + { + err: one(), + expected: "one", + }, { + err: two(), + expected: "two: one [errgo/test_functions_test.go:16, github.com/errgo/errgo.two]", + }, { + err: three(), + expected: "three [errgo/test_functions_test.go:20, github.com/errgo/errgo.three]\n" + + "two: one [errgo/test_functions_test.go:16, github.com/errgo/errgo.two]", + }, { + err: transtwo(), + expected: "transtwo: translated [errgo/test_functions_test.go:27, github.com/errgo/errgo.transtwo]\n" + + "one", + }, { + err: transthree(), + expected: "transthree: translated [errgo/test_functions_test.go:34, github.com/errgo/errgo.transthree]\n" + + "two: one [errgo/test_functions_test.go:16, github.com/errgo/errgo.two]", + }, { + err: four(), + expected: "four [errgo/test_functions_test.go:38, github.com/errgo/errgo.four]\n" + + "transthree: translated [errgo/test_functions_test.go:34, github.com/errgo/errgo.transthree]\n" + + "two: one [errgo/test_functions_test.go:16, github.com/errgo/errgo.two]", + }, + } { + stack := DetailedErrorStack(test.err, Default) + c.Check(stack, gc.DeepEquals, test.expected) + } +} diff -Nru juju-core-1.17.2/src/github.com/errgo/errgo/.gitignore juju-core-1.17.3/src/github.com/errgo/errgo/.gitignore --- juju-core-1.17.2/src/github.com/errgo/errgo/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/github.com/errgo/errgo/.gitignore 2014-02-20 16:10:27.000000000 +0000 @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff -Nru juju-core-1.17.2/src/github.com/errgo/errgo/LICENSE juju-core-1.17.3/src/github.com/errgo/errgo/LICENSE --- juju-core-1.17.2/src/github.com/errgo/errgo/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/github.com/errgo/errgo/LICENSE 2014-02-20 16:10:27.000000000 +0000 @@ -0,0 +1,165 @@ +GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff -Nru juju-core-1.17.2/src/github.com/errgo/errgo/README.md juju-core-1.17.3/src/github.com/errgo/errgo/README.md --- juju-core-1.17.2/src/github.com/errgo/errgo/README.md 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/github.com/errgo/errgo/README.md 2014-02-20 16:10:27.000000000 +0000 @@ -0,0 +1,4 @@ +errgo +===== + +A package for the Go language to provide extra information for errors. diff -Nru juju-core-1.17.2/src/github.com/errgo/errgo/test_functions_test.go juju-core-1.17.3/src/github.com/errgo/errgo/test_functions_test.go --- juju-core-1.17.2/src/github.com/errgo/errgo/test_functions_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/github.com/errgo/errgo/test_functions_test.go 2014-02-20 16:10:27.000000000 +0000 @@ -0,0 +1,43 @@ +package errgo + +// Functions in this file are used in the tests and the names of the functions +// along with the file and line numbers are used, so please don't mess around +// with them too much. + +import ( + "errors" +) + +func one() error { + return errors.New("one") +} + +func two() error { + return Annotate(one(), "two") +} + +func three() error { + return Annotate(two(), "three") +} + +func transtwo() error { + return Translate( + one(), + errors.New("translated"), + "transtwo") +} + +func transthree() error { + return Translate( + two(), + errors.New("translated"), + "transthree") +} + +func four() error { + return Annotate(transthree(), "four") +} + +func test_new() error { + return New("get location") +} diff -Nru juju-core-1.17.2/src/launchpad.net/godeps/godeps.go juju-core-1.17.3/src/launchpad.net/godeps/godeps.go --- juju-core-1.17.2/src/launchpad.net/godeps/godeps.go 2014-01-31 06:11:13.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/godeps/godeps.go 2014-02-20 16:11:07.000000000 +0000 @@ -15,11 +15,13 @@ "regexp" "sort" "strings" + "sync" ) -var revFile = flag.String("u", "", "file containing desired revisions") +var revFile = flag.String("u", "", "update dependencies") var testDeps = flag.Bool("t", false, "include testing dependencies in output") var printCommands = flag.Bool("x", false, "show executed commands") +var dryRun = flag.Bool("n", false, "print but do not execute update commands") var exitCode = 0 @@ -27,8 +29,8 @@ var usage = ` Usage: - godeps [-t] [pkg ...] - godeps -u file + godeps [-x] [-t] [pkg ...] + godeps -u file [-x] [-n] In the first form of usage, godeps prints to standard output a list of all the source dependencies of the named packages (or the package in @@ -40,7 +42,8 @@ In the second form, godeps updates source to versions specified by the -u file argument, which should hold version information in the same form printed by godeps. It is an error if the file contains more than -one line for the same package root. +one line for the same package root. If the -n flag is specified, +update commands will be printed but not executed. `[1:] func main() { @@ -480,7 +483,7 @@ } func (gitVCS) Update(dir string, revid string) error { - _, err := runCmd(dir, "git", "checkout", revid) + _, err := runUpdateCmd(dir, "git", "checkout", revid) return err } @@ -524,7 +527,7 @@ } func (bzrVCS) Update(dir string, revid string) error { - _, err := runCmd(dir, "bzr", "update", "-r", "revid:"+revid) + _, err := runUpdateCmd(dir, "bzr", "update", "-r", "revid:"+revid) return err } @@ -558,10 +561,18 @@ } func (hgVCS) Update(dir string, revid string) error { - _, err := runCmd(dir, "hg", "update", revid) + _, err := runUpdateCmd(dir, "hg", "update", revid) return err } +func runUpdateCmd(dir string, name string, args ...string) (string, error) { + if *dryRun { + printShellCommand(dir, name, args) + return "", nil + } + return runCmd(dir, name, args...) +} + func runCmd(dir string, name string, args ...string) (string, error) { var outData, errData bytes.Buffer if *printCommands { @@ -587,9 +598,19 @@ exitCode = 1 } +var ( + outputDirMutex sync.Mutex + outputDir string +) + func printShellCommand(dir, name string, args []string) { + outputDirMutex.Lock() + defer outputDirMutex.Unlock() + if dir != outputDir { + fmt.Fprintf(os.Stderr, "cd %s\n", shquote(dir)) + outputDir = dir + } var buf bytes.Buffer - fmt.Fprintf(&buf, "[%s] ", dir) buf.WriteString(name) for _, arg := range args { buf.WriteString(" ") diff -Nru juju-core-1.17.2/src/launchpad.net/gwacl/rolesizes.go juju-core-1.17.3/src/launchpad.net/gwacl/rolesizes.go --- juju-core-1.17.2/src/launchpad.net/gwacl/rolesizes.go 2014-01-31 06:11:04.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/gwacl/rolesizes.go 2014-02-20 16:10:59.000000000 +0000 @@ -1,23 +1,26 @@ -// Copyright 2013 Canonical Ltd. This software is licensed under the +// Copyright 2013-2014 Canonical Ltd. This software is licensed under the // GNU Lesser General Public License version 3 (see the file COPYING). -// Define the various role sizes and their attributes. +// Define the role sizes available in Azure. package gwacl -// RoleSize is a representation of the data available in the Azure +// RoleSize is a representation of the machine specs available in the Azure // documentation here: // http://msdn.microsoft.com/en-us/library/windowsazure/dn197896.aspx and // // Pricing from here: -// http://www.windowsazure.com/en-us/pricing/details/virtual-machines/ +// http://www.windowsazure.com/en-us/pricing/details/cloud-services/ +// +// Detailed specifications here: +// http://msdn.microsoft.com/en-us/library/windowsazure/dn197896.aspx // // Our specifications may be inaccurate or out of date. When in doubt, check! // // The Disk Space values are only the maxumim permitted; actual space is // determined by the OS image being used. // -// Sizes and costs last updated 2013-09-24. +// Sizes and costs last updated 2014-01-31. type RoleSize struct { Name string CpuCores uint64 @@ -52,7 +55,7 @@ OSDiskSpaceCloud: 224 * GB, OSDiskSpaceVirt: 70 * GB, MaxDataDisks: 2, - Cost: 9, + Cost: 8, }, { Name: "Medium", CpuCores: 2, @@ -60,7 +63,7 @@ OSDiskSpaceCloud: 489 * GB, OSDiskSpaceVirt: 135 * GB, MaxDataDisks: 4, - Cost: 18, + Cost: 16, }, { Name: "Large", CpuCores: 4, @@ -68,23 +71,23 @@ OSDiskSpaceCloud: 999 * GB, OSDiskSpaceVirt: 285 * GB, MaxDataDisks: 8, - Cost: 36, + Cost: 32, }, { Name: "ExtraLarge", CpuCores: 8, Mem: 14 * GB, OSDiskSpaceCloud: 2039 * GB, - OSDiskSpaceVirt: 65 * GB, + OSDiskSpaceVirt: 605 * GB, MaxDataDisks: 16, - Cost: 72, + Cost: 64, }, { Name: "A5", CpuCores: 2, Mem: 14 * GB, - OSDiskSpaceCloud: 489 * GB, // Estimate; not yet announced. - OSDiskSpaceVirt: 285 * GB, // Estimate; not yet announced. - MaxDataDisks: 2, // Estimate; not yet announced. - Cost: 51, + OSDiskSpaceCloud: 489 * GB, + OSDiskSpaceVirt: 135 * GB, + MaxDataDisks: 4, + Cost: 35, }, { Name: "A6", CpuCores: 4, @@ -92,15 +95,31 @@ OSDiskSpaceCloud: 999 * GB, OSDiskSpaceVirt: 285 * GB, MaxDataDisks: 8, - Cost: 102, + Cost: 71, }, { Name: "A7", CpuCores: 8, Mem: 56 * GB, OSDiskSpaceCloud: 2039 * GB, - OSDiskSpaceVirt: 65 * GB, + OSDiskSpaceVirt: 605 * GB, MaxDataDisks: 16, - Cost: 204, + Cost: 141, + }, { + Name: "A8", + CpuCores: 8, + Mem: 56 * GB, + OSDiskSpaceCloud: 2039 * GB, // Estimate; not yet announced. + OSDiskSpaceVirt: 605 * GB, // Estimate; not yet announced. + MaxDataDisks: 16, // Estimate; not yet announced. + Cost: 245, + }, { + Name: "A9", + CpuCores: 16, + Mem: 112 * GB, + OSDiskSpaceCloud: 2039 * GB, // Estimate; not yet announced. + OSDiskSpaceVirt: 605 * GB, // Estimate; not yet announced. + MaxDataDisks: 16, // Estimate; not yet announced. + Cost: 490, }, } diff -Nru juju-core-1.17.2/src/launchpad.net/gwacl/x509dispatcher.go juju-core-1.17.3/src/launchpad.net/gwacl/x509dispatcher.go --- juju-core-1.17.2/src/launchpad.net/gwacl/x509dispatcher.go 2014-01-31 06:11:04.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/gwacl/x509dispatcher.go 2014-02-20 16:10:59.000000000 +0000 @@ -5,9 +5,11 @@ import ( "bytes" + "fmt" "io/ioutil" "launchpad.net/gwacl/fork/http" . "launchpad.net/gwacl/logging" + "net/url" ) type X509Request struct { @@ -84,7 +86,7 @@ httpRequest.Header.Set("Content-Type", request.ContentType) httpRequest.Header.Set("x-ms-version", request.APIVersion) retrier := session.retryPolicy.getForkedHttpRetrier(session.client) - httpResponse, err := retrier.RetryRequest(httpRequest) + httpResponse, err := handleRequestRedirects(retrier, httpRequest) if err != nil { return nil, err } @@ -108,3 +110,35 @@ return response, nil } + +func handleRequestRedirects(retrier *forkedHttpRetrier, request *http.Request) (*http.Response, error) { + const maxRedirects = 10 + // Handle temporary redirects. + redirects := -1 + for { + redirects++ + if redirects >= maxRedirects { + return nil, fmt.Errorf("stopped after %d redirects", redirects) + } + response, err := retrier.RetryRequest(request) + // For GET and HEAD, we cause the request execution + // to return httpRedirectErr if a temporary redirect + // is returned, and then deal with it here. + if err, ok := err.(*url.Error); ok && err.Err == httpRedirectErr { + request.URL, err.Err = url.Parse(err.URL) + if err.Err != nil { + return nil, err + } + continue + } + // For other methods, we must check the response code. + if err == nil && response.StatusCode == http.StatusTemporaryRedirect { + request.URL, err = response.Location() + if err != nil { + return nil, err + } + continue + } + return response, err + } +} diff -Nru juju-core-1.17.2/src/launchpad.net/gwacl/x509dispatcher_test.go juju-core-1.17.3/src/launchpad.net/gwacl/x509dispatcher_test.go --- juju-core-1.17.2/src/launchpad.net/gwacl/x509dispatcher_test.go 2014-01-31 06:11:04.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/gwacl/x509dispatcher_test.go 2014-02-20 16:10:59.000000000 +0000 @@ -22,6 +22,7 @@ // channel, and finally returns the given status code. If body is not nil, it // will be returned as the request body. func makeRecordingHTTPServer(requests chan *Request, status int, body []byte, headers http.Header) *httptest.Server { + var server *httptest.Server returnRequest := func(w http.ResponseWriter, r *http.Request) { // Capture all the request body content for later inspection. requestBody, err := ioutil.ReadAll(r.Body) @@ -29,7 +30,8 @@ panic(err) } requests <- &Request{r, requestBody} - + // Set a default Location so we can test redirect loops easily. + w.Header().Set("Location", server.URL+r.URL.Path) for header, values := range headers { for _, value := range values { w.Header().Set(header, value) @@ -42,7 +44,8 @@ } serveMux := http.NewServeMux() serveMux.HandleFunc("/", returnRequest) - return httptest.NewServer(serveMux) + server = httptest.NewServer(serveMux) + return server } func (*x509DispatcherSuite) TestGetRequestDoesHTTPGET(c *C) { @@ -184,3 +187,54 @@ c.Check(response.Header[customHeader], DeepEquals, customValue) } + +func (*x509DispatcherSuite) TestRequestsFollowRedirects(c *C) { + httpRequests := make(chan *Request, 2) + serverConflict := makeRecordingHTTPServer(httpRequests, http.StatusConflict, nil, nil) + defer serverConflict.Close() + redirPath := "/else/where" + responseHeaders := make(http.Header) + responseHeaders.Set("Location", serverConflict.URL+redirPath) + serverRedir := makeRecordingHTTPServer(httpRequests, http.StatusTemporaryRedirect, nil, responseHeaders) + defer serverRedir.Close() + session, err := newX509Session("subscriptionid", "", "West US", NoRetryPolicy) + c.Assert(err, IsNil) + path := "/foo/bar" + version := "test-version" + + // Test both GET and DELETE: DELETE does not normally + // automatically follow redirects, however Azure requires + // us to. + requests := []*X509Request{ + newX509RequestGET(serverRedir.URL+path, version), + newX509RequestDELETE(serverRedir.URL+path, version), + } + for _, request := range requests { + response, err := performX509Request(session, request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusConflict) + c.Assert(httpRequests, HasLen, 2) + c.Assert((<-httpRequests).URL.String(), Equals, path) + c.Assert((<-httpRequests).URL.String(), Equals, redirPath) + } +} + +func (*x509DispatcherSuite) TestRequestsLimitRedirects(c *C) { + httpRequests := make(chan *Request, 10) + serverRedir := makeRecordingHTTPServer(httpRequests, http.StatusTemporaryRedirect, nil, nil) + defer serverRedir.Close() + session, err := newX509Session("subscriptionid", "", "West US", NoRetryPolicy) + c.Assert(err, IsNil) + path := "/foo/bar" + version := "test-version" + request := newX509RequestGET(serverRedir.URL+path, version) + + response, err := performX509Request(session, request) + c.Assert(err, ErrorMatches, "stopped after 10 redirects") + c.Assert(response, IsNil) + c.Assert(httpRequests, HasLen, 10) + close(httpRequests) + for req := range httpRequests { + c.Assert(req.URL.String(), Equals, path) + } +} diff -Nru juju-core-1.17.2/src/launchpad.net/gwacl/x509session.go juju-core-1.17.3/src/launchpad.net/gwacl/x509session.go --- juju-core-1.17.2/src/launchpad.net/gwacl/x509session.go 2014-01-31 06:11:04.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/gwacl/x509session.go 2014-02-20 16:10:59.000000000 +0000 @@ -4,6 +4,7 @@ package gwacl import ( + "errors" "fmt" "launchpad.net/gwacl/fork/http" "launchpad.net/gwacl/fork/tls" @@ -19,6 +20,11 @@ retryPolicy RetryPolicy } +// httpRedirectErr is a unique error used to prevent +// net/http from automatically following redirects. +// See commentary on CheckRedirect in newX509Session. +var httpRedirectErr = errors.New("redirect") + // newX509Session creates and returns a new x509Session based on credentials // and X509 certificate files. // For testing purposes, certFile can be passed as the empty string and it @@ -43,6 +49,13 @@ // hit the above Go bug. DisableKeepAlives: true, }, + // See https://code.google.com/p/go/issues/detail?id=4800 + // We need to follow temporary redirects (307s), while + // retaining headers. We also need to follow redirects + // for POST and DELETE automatically. + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return httpRedirectErr + }, } endpointURL := GetEndpoint(location).ManagementAPI() diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/agent/agent.go juju-core-1.17.3/src/launchpad.net/juju-core/agent/agent.go --- juju-core-1.17.2/src/launchpad.net/juju-core/agent/agent.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/agent/agent.go 2014-02-20 16:10:15.000000000 +0000 @@ -9,6 +9,7 @@ "regexp" "sync" + "github.com/errgo/errgo" "github.com/loggo/loggo" "launchpad.net/juju-core/errors" @@ -21,17 +22,16 @@ var logger = loggo.GetLogger("juju.agent") const ( - LxcBridge = "LXC_BRIDGE" - ProviderType = "PROVIDER_TYPE" - ContainerType = "CONTAINER_TYPE" - Namespace = "NAMESPACE" - StorageDir = "STORAGE_DIR" - StorageAddr = "STORAGE_ADDR" - SharedStorageDir = "SHARED_STORAGE_DIR" - SharedStorageAddr = "SHARED_STORAGE_ADDR" - AgentServiceName = "AGENT_SERVICE_NAME" - MongoServiceName = "MONGO_SERVICE_NAME" - RsyslogConfPath = "RSYSLOG_CONF_PATH" + LxcBridge = "LXC_BRIDGE" + ProviderType = "PROVIDER_TYPE" + ContainerType = "CONTAINER_TYPE" + Namespace = "NAMESPACE" + StorageDir = "STORAGE_DIR" + StorageAddr = "STORAGE_ADDR" + AgentServiceName = "AGENT_SERVICE_NAME" + MongoServiceName = "MONGO_SERVICE_NAME" + RsyslogConfPath = "RSYSLOG_CONF_PATH" + BootstrapJobs = "BOOTSTRAP_JOBS" ) // The Config interface is the sole way that the agent gets access to the @@ -75,7 +75,7 @@ // OpenState tries to open a direct connection to the state database using // the given Conf. - OpenState() (*state.State, error) + OpenState(policy state.Policy) (*state.State, error) // Write writes the agent configuration. Write() error @@ -150,16 +150,16 @@ // machine or unit agent. func NewAgentConfig(params AgentConfigParams) (Config, error) { if params.DataDir == "" { - return nil, requiredError("data directory") + return nil, errgo.Trace(requiredError("data directory")) } if params.Tag == "" { - return nil, requiredError("entity tag") + return nil, errgo.Trace(requiredError("entity tag")) } if params.Password == "" { - return nil, requiredError("password") + return nil, errgo.Trace(requiredError("password")) } if params.CACert == nil { - return nil, requiredError("CA certificate") + return nil, errgo.Trace(requiredError("CA certificate")) } // Note that the password parts of the state and api information are // blank. This is by design. @@ -202,10 +202,10 @@ // a machine running the state server. func NewStateMachineConfig(params StateMachineConfigParams) (Config, error) { if params.StateServerCert == nil { - return nil, requiredError("state server cert") + return nil, errgo.Trace(requiredError("state server cert")) } if params.StateServerKey == nil { - return nil, requiredError("state server key") + return nil, errgo.Trace(requiredError("state server key")) } config0, err := NewAgentConfig(params.AgentConfigParams) if err != nil { @@ -308,7 +308,7 @@ func (c *configInternal) APIAddresses() ([]string, error) { if c.apiDetails == nil { - return []string{}, fmt.Errorf("No apidetails in config") + return []string{}, errgo.New("No apidetails in config") } return append([]string{}, c.apiDetails.addresses...), nil } @@ -323,7 +323,7 @@ func (c *configInternal) check() error { if c.stateDetails == nil && c.apiDetails == nil { - return requiredError("state or API addresses") + return errgo.Trace(requiredError("state or API addresses")) } if c.stateDetails != nil { if err := checkAddrs(c.stateDetails.addresses, "state server address"); err != nil { @@ -342,11 +342,11 @@ func checkAddrs(addrs []string, what string) error { if len(addrs) == 0 { - return requiredError(what) + return errgo.Trace(requiredError(what)) } for _, a := range addrs { if !validAddr.MatchString(a) { - return fmt.Errorf("invalid %s %q", what, a) + return errgo.New("invalid %s %q", what, a) } } return nil @@ -429,7 +429,7 @@ return st, password, nil } -func (c *configInternal) OpenState() (*state.State, error) { +func (c *configInternal) OpenState(policy state.Policy) (*state.State, error) { info := state.Info{ Addrs: c.stateDetails.addresses, Password: c.stateDetails.password, @@ -437,7 +437,7 @@ Tag: c.tag, } if info.Password != "" { - st, err := state.Open(&info, state.DefaultDialOpts()) + st, err := state.Open(&info, state.DefaultDialOpts(), policy) if err == nil { return st, nil } @@ -448,5 +448,5 @@ } } info.Password = c.oldPassword - return state.Open(&info, state.DefaultDialOpts()) + return state.Open(&info, state.DefaultDialOpts(), policy) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/agent/bootstrap.go juju-core-1.17.3/src/launchpad.net/juju-core/agent/bootstrap.go --- juju-core-1.17.2/src/launchpad.net/juju-core/agent/bootstrap.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/agent/bootstrap.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,6 +4,7 @@ package agent import ( + "encoding/json" "fmt" "launchpad.net/juju-core/constraints" @@ -28,7 +29,22 @@ // InitializeState returns the newly initialized state and bootstrap // machine. If it fails, the state may well be irredeemably compromised. type StateInitializer interface { - InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts) (*state.State, *state.Machine, error) + InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts, policy state.Policy) (*state.State, *state.Machine, error) +} + +// MarshalBootstrapJobs may be used to marshal a set of +// machine jobs for the bootstrap agent, to be added +// into the agent configuration. +func MarshalBootstrapJobs(jobs ...state.MachineJob) (string, error) { + b, err := json.Marshal(jobs) + return string(b), err +} + +// UnmarshalBootstrapJobs unmarshals a set of machine +// jobs marshalled with MarshalBootstrapJobs. +func UnmarshalBootstrapJobs(s string) (jobs []state.MachineJob, err error) { + err = json.Unmarshal([]byte(s), &jobs) + return jobs, err } // BootstrapMachineConfig holds configuration information @@ -51,7 +67,7 @@ const bootstrapMachineId = "0" -func (c *configInternal) InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts) (*state.State, *state.Machine, error) { +func (c *configInternal) InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts, policy state.Policy) (*state.State, *state.Machine, error) { if c.Tag() != names.MachineTag(bootstrapMachineId) { return nil, nil, fmt.Errorf("InitializeState not called with bootstrap machine's configuration") } @@ -60,7 +76,7 @@ CACert: c.caCert, } logger.Debugf("initializing address %v", info.Addrs) - st, err := state.Initialize(&info, envCfg, timeout) + st, err := state.Initialize(&info, envCfg, timeout, policy) if err != nil { return nil, nil, fmt.Errorf("failed to initialize state: %v", err) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/agent/bootstrap_test.go juju-core-1.17.3/src/launchpad.net/juju-core/agent/bootstrap_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/agent/bootstrap_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/agent/bootstrap_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -8,6 +8,7 @@ "launchpad.net/juju-core/agent" "launchpad.net/juju-core/constraints" + "launchpad.net/juju-core/environs" "launchpad.net/juju-core/environs/config" "launchpad.net/juju-core/instance" "launchpad.net/juju-core/state" @@ -71,7 +72,7 @@ envCfg, err := config.New(config.NoDefaults, envAttrs) c.Assert(err, gc.IsNil) - st, m, err := cfg.InitializeState(envCfg, mcfg, state.DialOpts{}) + st, m, err := cfg.InitializeState(envCfg, mcfg, state.DialOpts{}, environs.NewStatePolicy()) c.Assert(err, gc.IsNil) defer st.Close() @@ -106,7 +107,7 @@ c.Assert(newCfg.Tag(), gc.Equals, "machine-0") c.Assert(agent.Password(newCfg), gc.Not(gc.Equals), pwHash) c.Assert(agent.Password(newCfg), gc.Not(gc.Equals), testing.DefaultMongoPassword) - st1, err := cfg.OpenState() + st1, err := cfg.OpenState(environs.NewStatePolicy()) c.Assert(err, gc.IsNil) defer st1.Close() } @@ -136,13 +137,13 @@ envCfg, err := config.New(config.NoDefaults, envAttrs) c.Assert(err, gc.IsNil) - st, _, err := cfg.InitializeState(envCfg, mcfg, state.DialOpts{}) + st, _, err := cfg.InitializeState(envCfg, mcfg, state.DialOpts{}, environs.NewStatePolicy()) c.Assert(err, gc.IsNil) err = st.SetAdminMongoPassword("") c.Check(err, gc.IsNil) st.Close() - st, _, err = cfg.InitializeState(envCfg, mcfg, state.DialOpts{}) + st, _, err = cfg.InitializeState(envCfg, mcfg, state.DialOpts{}, environs.NewStatePolicy()) if err == nil { st.Close() } @@ -156,9 +157,25 @@ Tag: "", Password: password, } - st, err := state.Open(info, state.DialOpts{}) + st, err := state.Open(info, state.DialOpts{}, environs.NewStatePolicy()) c.Assert(err, gc.IsNil) defer st.Close() _, err = st.Machine("0") c.Assert(err, gc.IsNil) } + +func (s *bootstrapSuite) TestMarshalUnmarshalBootstrapJobs(c *gc.C) { + jobs := [][]state.MachineJob{ + {}, + {state.JobHostUnits}, + {state.JobManageEnviron}, + {state.JobHostUnits, state.JobManageEnviron}, + } + for _, jobs := range jobs { + marshalled, err := agent.MarshalBootstrapJobs(jobs...) + c.Assert(err, gc.IsNil) + unmarshalled, err := agent.UnmarshalBootstrapJobs(marshalled) + c.Assert(err, gc.IsNil) + c.Assert(unmarshalled, gc.DeepEquals, jobs) + } +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/agent/format-1.16.go juju-core-1.17.3/src/launchpad.net/juju-core/agent/format-1.16.go --- juju-core-1.17.2/src/launchpad.net/juju-core/agent/format-1.16.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/agent/format-1.16.go 2014-02-20 16:10:15.000000000 +0000 @@ -17,12 +17,10 @@ const ( format_1_16 = "format 1.16" // Old environment variables that are now stored in agent config. - JujuLxcBridge = "JUJU_LXC_BRIDGE" - JujuProviderType = "JUJU_PROVIDER_TYPE" - JujuStorageDir = "JUJU_STORAGE_DIR" - JujuStorageAddr = "JUJU_STORAGE_ADDR" - JujuSharedStorageDir = "JUJU_SHARED_STORAGE_DIR" - JujuSharedStorageAddr = "JUJU_SHARED_STORAGE_ADDR" + jujuLxcBridge = "JUJU_LXC_BRIDGE" + jujuProviderType = "JUJU_PROVIDER_TYPE" + jujuStorageDir = "JUJU_STORAGE_DIR" + jujuStorageAddr = "JUJU_STORAGE_ADDR" ) // formatter_1_16 is the formatter for the 1.16 format. @@ -178,26 +176,20 @@ environment string config string }{{ - JujuProviderType, + jujuProviderType, ProviderType, }, { osenv.JujuContainerTypeEnvKey, ContainerType, }, { - JujuLxcBridge, + jujuLxcBridge, LxcBridge, }, { - JujuStorageDir, + jujuStorageDir, StorageDir, }, { - JujuStorageAddr, + jujuStorageAddr, StorageAddr, - }, { - JujuSharedStorageDir, - SharedStorageDir, - }, { - JujuSharedStorageAddr, - SharedStorageAddr, }} { value := os.Getenv(name.environment) if value != "" { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/agent/format-1.16_whitebox_test.go juju-core-1.17.3/src/launchpad.net/juju-core/agent/format-1.16_whitebox_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/agent/format-1.16_whitebox_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/agent/format-1.16_whitebox_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -97,32 +97,28 @@ } func (s *format_1_16Suite) TestMigrate(c *gc.C) { - s.PatchEnvironment(JujuLxcBridge, "lxc bridge") - s.PatchEnvironment(JujuProviderType, "provider type") + s.PatchEnvironment(jujuLxcBridge, "lxc bridge") + s.PatchEnvironment(jujuProviderType, "provider type") s.PatchEnvironment(osenv.JujuContainerTypeEnvKey, "container type") - s.PatchEnvironment(JujuStorageDir, "storage dir") - s.PatchEnvironment(JujuStorageAddr, "storage addr") - s.PatchEnvironment(JujuSharedStorageDir, "shared storage dir") - s.PatchEnvironment(JujuSharedStorageAddr, "shared storage addr") + s.PatchEnvironment(jujuStorageDir, "storage dir") + s.PatchEnvironment(jujuStorageAddr, "storage addr") config := newTestConfig(c) s.formatter.migrate(config) expected := map[string]string{ - LxcBridge: "lxc bridge", - ProviderType: "provider type", - ContainerType: "container type", - StorageDir: "storage dir", - StorageAddr: "storage addr", - SharedStorageDir: "shared storage dir", - SharedStorageAddr: "shared storage addr", + LxcBridge: "lxc bridge", + ProviderType: "provider type", + ContainerType: "container type", + StorageDir: "storage dir", + StorageAddr: "storage addr", } c.Assert(config.values, gc.DeepEquals, expected) } func (s *format_1_16Suite) TestMigrateOnlySetsExisting(c *gc.C) { - s.PatchEnvironment(JujuProviderType, "provider type") + s.PatchEnvironment(jujuProviderType, "provider type") config := newTestConfig(c) s.formatter.migrate(config) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/agent/tools/toolsdir.go juju-core-1.17.3/src/launchpad.net/juju-core/agent/tools/toolsdir.go --- juju-core-1.17.2/src/launchpad.net/juju-core/agent/tools/toolsdir.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/agent/tools/toolsdir.go 2014-02-20 16:10:15.000000000 +0000 @@ -15,6 +15,8 @@ "path" "strings" + "github.com/errgo/errgo" + coretools "launchpad.net/juju-core/tools" "launchpad.net/juju-core/version" ) @@ -99,7 +101,7 @@ } name := path.Join(dir, hdr.Name) if err := writeFile(name, os.FileMode(hdr.Mode&0777), tr); err != nil { - return fmt.Errorf("tar extract %q failed: %v", name, err) + return errgo.Annotatef(err, "tar extract %q failed", name) } } toolsMetadataData, err := json.Marshal(tools) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cert/cert.go juju-core-1.17.3/src/launchpad.net/juju-core/cert/cert.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cert/cert.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cert/cert.go 2014-02-20 16:10:15.000000000 +0000 @@ -16,6 +16,8 @@ "math/big" "net" "time" + + "github.com/errgo/errgo" ) var KeyBits = 1024 @@ -61,11 +63,11 @@ func Verify(srvCertPEM, caCertPEM []byte, when time.Time) error { caCert, err := ParseCert(caCertPEM) if err != nil { - return fmt.Errorf("cannot parse CA certificate: %v", err) + return errgo.Annotate(err, "cannot parse CA certificate") } srvCert, err := ParseCert(srvCertPEM) if err != nil { - return fmt.Errorf("cannot parse server certificate: %v", err) + return errgo.Annotate(err, "cannot parse server certificate") } pool := x509.NewCertPool() pool.AddCert(caCert) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cloudinit/cloudinit.go juju-core-1.17.3/src/launchpad.net/juju-core/cloudinit/cloudinit.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cloudinit/cloudinit.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cloudinit/cloudinit.go 2014-02-20 16:10:15.000000000 +0000 @@ -7,6 +7,9 @@ package cloudinit import ( + "bytes" + "text/template" + yaml "launchpad.net/goyaml" ) @@ -38,10 +41,41 @@ } // AptSource is an apt(8) source, comprising a source location, -// with an optional Key. +// with an optional Key, and optional apt_preferences(5). type AptSource struct { - Source string `yaml:"source"` - Key string `yaml:"key,omitempty"` + Source string `yaml:"source"` + Key string `yaml:"key,omitempty"` + Prefs *AptPreferences `yaml:"-"` +} + +// AptPreferences is a set of apt_preferences(5) compatible +// preferences for an apt source. It can be used to override the +// default priority for the source. Path where the file will be +// created (usually in /etc/apt/preferences.d/). +type AptPreferences struct { + Path string + Explanation string + Package string + Pin string + PinPriority int +} + +// FileContents generates an apt_preferences(5) file from the fields +// in prefs. +func (prefs *AptPreferences) FileContents() string { + const prefTemplate = ` +Explanation: {{.Explanation}} +Package: {{.Package}} +Pin: {{.Pin}} +Pin-Priority: {{.PinPriority}} +` + var buf bytes.Buffer + t := template.Must(template.New("").Parse(prefTemplate[1:])) + err := t.Execute(&buf, prefs) + if err != nil { + panic(err) + } + return buf.String() } // command represents a shell command. diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cloudinit/cloudinit_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cloudinit/cloudinit_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cloudinit/cloudinit_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cloudinit/cloudinit_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -177,7 +177,35 @@ "AptSources", "apt_sources:\n- source: keyName\n key: someKey\n", func(cfg *cloudinit.Config) { - cfg.AddAptSource("keyName", "someKey") + cfg.AddAptSource("keyName", "someKey", nil) + }, + }, + { + "AptSources with preferences", + `apt_sources: +- source: keyName + key: someKey +runcmd: +- install -D -m 644 /dev/null '/some/path' +- 'printf ''%s\n'' ''Explanation: test + + Package: * + + Pin: release n=series + + Pin-Priority: 123 + + '' > ''/some/path''' +`, + func(cfg *cloudinit.Config) { + prefs := &cloudinit.AptPreferences{ + Path: "/some/path", + Explanation: "test", + Package: "*", + Pin: "release n=series", + PinPriority: 123, + } + cfg.AddAptSource("keyName", "someKey", prefs) }, }, { @@ -189,6 +217,13 @@ }, }, { + "Packages with --target-release", + "packages:\n- --target-release 'precise-updates/cloud-tools' 'mongodb-server'\n", + func(cfg *cloudinit.Config) { + cfg.AddPackageFromTargetRelease("mongodb-server", "precise-updates/cloud-tools") + }, + }, + { "BootCmd", "bootcmd:\n- ls > /dev\n- - ls\n - '>with space'\n", func(cfg *cloudinit.Config) { @@ -276,7 +311,11 @@ c.Assert(cfg.Packages(), gc.HasLen, 0) cfg.AddPackage("a b c") cfg.AddPackage("d!") - c.Assert(cfg.Packages(), gc.DeepEquals, []string{"a b c", "d!"}) + expectedPackages := []string{"a b c", "d!"} + c.Assert(cfg.Packages(), gc.DeepEquals, expectedPackages) + cfg.AddPackageFromTargetRelease("package", "series") + expectedPackages = append(expectedPackages, "--target-release 'series' 'package'") + c.Assert(cfg.Packages(), gc.DeepEquals, expectedPackages) } func (S) TestSetOutput(c *gc.C) { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cloudinit/options.go juju-core-1.17.3/src/launchpad.net/juju-core/cloudinit/options.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cloudinit/options.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cloudinit/options.go 2014-02-20 16:10:15.000000000 +0000 @@ -10,6 +10,10 @@ "launchpad.net/juju-core/utils/ssh" ) +// CloudToolsPrefsPath defines the default location of +// apt_preferences(5) file for the cloud-tools pocket. +const CloudToolsPrefsPath = "/etc/apt/preferences.d/50-cloud-tools" + // SetAttr sets an arbitrary attribute in the cloudinit config. // If value is nil the attribute will be deleted; otherwise // the value will be marshalled according to the rules @@ -73,13 +77,19 @@ // AddAptSource adds an apt source. The key holds the // public key of the source, in the form expected by apt-key(8). -func (cfg *Config) AddAptSource(name, key string) { +func (cfg *Config) AddAptSource(name, key string, prefs *AptPreferences) { src, _ := cfg.attrs["apt_sources"].([]*AptSource) cfg.attrs["apt_sources"] = append(src, &AptSource{ Source: name, Key: key, - }) + Prefs: prefs, + }, + ) + if prefs != nil { + // Create the apt preferences file. + cfg.AddFile(prefs.Path, prefs.FileContents(), 0644) + } } // AptSources returns the apt sources added with AddAptSource. @@ -102,6 +112,13 @@ cfg.attrs["packages"] = append(cfg.Packages(), name) } +// AddPackageFromTargetRelease adds a package to be installed using +// the given release, passed to apt-get with the --target-release +// argument. +func (cfg *Config) AddPackageFromTargetRelease(packageName, targetRelease string) { + cfg.AddPackage(fmt.Sprintf("--target-release '%s' '%s'", targetRelease, packageName)) +} + // Packages returns a list of packages that will be // installed on first boot. func (cfg *Config) Packages() []string { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cloudinit/sshinit/configure.go juju-core-1.17.3/src/launchpad.net/juju-core/cloudinit/sshinit/configure.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cloudinit/sshinit/configure.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cloudinit/sshinit/configure.go 2014-02-20 16:10:15.000000000 +0000 @@ -141,6 +141,12 @@ } cmds = append(cmds, cloudinit.LogProgressCmd("Adding apt repository: %s", src.Source)) cmds = append(cmds, "add-apt-repository -y "+utils.ShQuote(src.Source)) + if src.Prefs != nil { + path := utils.ShQuote(src.Prefs.Path) + contents := utils.ShQuote(src.Prefs.FileContents()) + cmds = append(cmds, "install -D -m 644 /dev/null "+path) + cmds = append(cmds, `printf '%s\n' `+contents+` > `+path) + } } if len(cfg.AptSources()) > 0 || cfg.AptUpdate() { cmds = append(cmds, cloudinit.LogProgressCmd("Running apt-get update")) @@ -152,7 +158,12 @@ } for _, pkg := range cfg.Packages() { cmds = append(cmds, cloudinit.LogProgressCmd("Installing package: %s", pkg)) - cmd := fmt.Sprintf(aptget+"install %s", utils.ShQuote(pkg)) + if !strings.Contains(pkg, "--target-release") { + // We only need to shquote the package name if it does not + // contain additional arguments. + pkg = utils.ShQuote(pkg) + } + cmd := fmt.Sprintf(aptget+"install %s", pkg) cmds = append(cmds, cmd) } if len(cmds) > 0 { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cloudinit/sshinit/configure_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cloudinit/sshinit/configure_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cloudinit/sshinit/configure_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cloudinit/sshinit/configure_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -103,6 +103,16 @@ checkIff(gc.Matches, needsCloudTools), "(.|\n)*add-apt-repository.*cloud-tools(.|\n)*", ) + c.Assert( + script, + checkIff(gc.Matches, needsCloudTools), + "(.|\n)*Pin: release n=precise-updates/cloud-tools\nPin-Priority: 400(.|\n)*", + ) + c.Assert( + script, + checkIff(gc.Matches, needsCloudTools), + "(.|\n)*install -D -m 644 /dev/null '/etc/apt/preferences.d/50-cloud-tools'(.|\n)*", + ) // Only Quantal requires the PPA (for mongo). needsJujuPPA := series == "quantal" @@ -143,7 +153,7 @@ cfg.SetAptUpdate(true) assertScriptMatches(c, cfg, aptGetUpdatePattern, true) cfg.SetAptUpdate(false) - cfg.AddAptSource("source", "key") + cfg.AddAptSource("source", "key", nil) assertScriptMatches(c, cfg, aptGetUpdatePattern, true) } @@ -152,7 +162,7 @@ aptGetUpgradePattern := aptgetRegexp + "upgrade(.|\n)*" cfg := cloudinit.New() cfg.SetAptUpdate(true) - cfg.AddAptSource("source", "key") + cfg.AddAptSource("source", "key", nil) assertScriptMatches(c, cfg, aptGetUpgradePattern, false) cfg.SetAptUpgrade(true) assertScriptMatches(c, cfg, aptGetUpgradePattern, true) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/cmd.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/cmd.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/cmd.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/cmd.go 2014-02-20 16:10:15.000000000 +0000 @@ -112,12 +112,27 @@ return filepath.Join(ctx.Dir, path) } -// InterruptNotify partially satisfies environs/bootstrap.BootstrapContext +// GetStdin satisfies environs.BootstrapContext +func (ctx *Context) GetStdin() io.Reader { + return ctx.Stdin +} + +// GetStdout satisfies environs.BootstrapContext +func (ctx *Context) GetStdout() io.Writer { + return ctx.Stdout +} + +// GetStderr satisfies environs.BootstrapContext +func (ctx *Context) GetStderr() io.Writer { + return ctx.Stderr +} + +// InterruptNotify satisfies environs.BootstrapContext func (ctx *Context) InterruptNotify(c chan<- os.Signal) { signal.Notify(c, os.Interrupt) } -// StopInterruptNotify partially satisfies environs/bootstrap.BootstrapContext +// StopInterruptNotify satisfies environs.BootstrapContext func (ctx *Context) StopInterruptNotify(c chan<- os.Signal) { signal.Stop(c) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/bootstrap.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/bootstrap.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/bootstrap.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/bootstrap.go 2014-02-20 16:10:15.000000000 +0000 @@ -5,7 +5,6 @@ import ( "fmt" - "io" "os" "strings" @@ -21,6 +20,7 @@ "launchpad.net/juju-core/environs/imagemetadata" "launchpad.net/juju-core/environs/sync" "launchpad.net/juju-core/environs/tools" + "launchpad.net/juju-core/errors" "launchpad.net/juju-core/provider" "launchpad.net/juju-core/utils/set" "launchpad.net/juju-core/version" @@ -95,41 +95,33 @@ return cmd.CheckEmpty(args) } -type bootstrapContext struct { - *cmd.Context -} - -func (c bootstrapContext) Stdin() io.Reader { - return c.Context.Stdin -} - -func (c bootstrapContext) Stdout() io.Writer { - return c.Context.Stdout -} - -func (c bootstrapContext) Stderr() io.Writer { - return c.Context.Stderr +func destroyPreparedEnviron(env environs.Environ, store configstore.Storage, err *error, action string) { + if *err == nil { + return + } + if err := environs.Destroy(env, store); err != nil { + logger.Errorf("%s failed, and the environment could not be destroyed: %v", action, err) + } } // Run connects to the environment specified on the command line and bootstraps // a juju in that environment if none already exists. If there is as yet no environments.yaml file, // the user is informed how to create one. -func (c *BootstrapCommand) Run(ctx *cmd.Context) error { +func (c *BootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { store, err := configstore.Default() if err != nil { return err } - environ, err := environs.PrepareFromName(c.EnvName, store) + var existing bool + if _, err := store.ReadInfo(c.EnvName); !errors.IsNotFoundError(err) { + existing = true + } + environ, err := environs.PrepareFromName(c.EnvName, ctx, store) if err != nil { return err } - bootstrapContext := bootstrapContext{ctx} - // If the environment has a special bootstrap Storage, use it wherever - // we'd otherwise use environ.Storage. - if bs, ok := environ.(environs.BootstrapStorager); ok { - if err := bs.EnableBootstrapStorage(bootstrapContext); err != nil { - return fmt.Errorf("failed to enable bootstrap storage: %v", err) - } + if !existing { + defer destroyPreparedEnviron(environ, store, &resultErr, "Bootstrap") } if err := bootstrap.EnsureNotBootstrapped(environ); err != nil { return err @@ -161,7 +153,7 @@ return err } } - return bootstrap.Bootstrap(bootstrapContext, environ, c.Constraints) + return bootstrap.Bootstrap(ctx, environ, c.Constraints) } func (c *BootstrapCommand) uploadTools(environ environs.Environ) error { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/bootstrap_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/bootstrap_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/bootstrap_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/bootstrap_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -476,6 +476,28 @@ c.Assert(errText, gc.Matches, expectedErrText) } +func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) { + _, fake := makeEmptyFakeHome(c) + defer fake.Restore() + opc, errc := runCommand(nullContext(), new(BootstrapCommand), "-e", "brokenenv") + err := <-errc + c.Assert(err, gc.ErrorMatches, "dummy.Bootstrap is broken") + var opDestroy *dummy.OpDestroy + for opDestroy == nil { + select { + case op := <-opc: + switch op := op.(type) { + case dummy.OpDestroy: + opDestroy = &op + } + default: + c.Error("expected call to env.Destroy") + return + } + } + c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken") +} + // createToolsSource writes the mock tools and metadata into a temporary // directory and returns it. func createToolsSource(c *gc.C, versions []version.Binary) string { @@ -494,7 +516,7 @@ dummy.Reset() store, err := configstore.Default() c.Assert(err, gc.IsNil) - env, err := environs.PrepareFromName("peckham", store) + env, err := environs.PrepareFromName("peckham", nullContext(), store) c.Assert(err, gc.IsNil) envtesting.RemoveAllTools(c, env) return env, fake diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/deploy.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/deploy.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/deploy.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/deploy.go 2014-02-20 16:10:15.000000000 +0000 @@ -9,6 +9,7 @@ "os" "launchpad.net/gnuflag" + "launchpad.net/juju-core/charm" "launchpad.net/juju-core/cmd" "launchpad.net/juju-core/constraints" diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/destroyenvironment.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/destroyenvironment.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/destroyenvironment.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/destroyenvironment.go 2014-02-20 16:10:15.000000000 +0000 @@ -47,7 +47,7 @@ f.StringVar(&c.envName, "environment", "", "juju environment to operate in") } -func (c *DestroyEnvironmentCommand) Run(ctx *cmd.Context) error { +func (c *DestroyEnvironmentCommand) Run(ctx *cmd.Context) (result error) { store, err := configstore.Default() if err != nil { return fmt.Errorf("cannot open environment info storage: %v", err) @@ -74,6 +74,22 @@ // This is necessary to destroy broken environments, where the // API server is inaccessible or faulty. if !c.force { + defer func() { + if result == nil { + return + } + logger.Errorf(`failed to destroy environment %q + +If the environment is unusable, then you may run + + juju destroy-environment --force + +to forcefully destroy the environment. Upon doing so, review +your environment provider console for any resources that need +to be cleaned up. + +`, c.envName) + }() conn, err := juju.NewAPIConn(environ, api.DefaultDialOpts()) if err != nil { return err diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/destroyenvironment_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/destroyenvironment_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/destroyenvironment_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/destroyenvironment_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -27,7 +27,7 @@ func (s *destroyEnvSuite) TestDestroyEnvironmentCommand(c *gc.C) { // Prepare the environment so we can destroy it. - _, err := environs.PrepareFromName("dummyenv", s.ConfigStore) + _, err := environs.PrepareFromName("dummyenv", nullContext(), s.ConfigStore) c.Assert(err, gc.IsNil) // check environment is mandatory @@ -46,7 +46,7 @@ func (s *destroyEnvSuite) TestDestroyEnvironmentCommandEFlag(c *gc.C) { // Prepare the environment so we can destroy it. - _, err := environs.PrepareFromName("dummyenv", s.ConfigStore) + _, err := environs.PrepareFromName("dummyenv", nullContext(), s.ConfigStore) c.Assert(err, gc.IsNil) // check that either environment or the flag is mandatory @@ -89,13 +89,15 @@ c.Assert(err, gc.IsNil) // Prepare the environment so we can destroy it. - _, err = environs.PrepareFromName("dummyenv", s.ConfigStore) + _, err = environs.PrepareFromName("dummyenv", nullContext(), s.ConfigStore) c.Assert(err, gc.IsNil) // destroy with broken environment opc, errc := runCommand(nullContext(), new(DestroyEnvironmentCommand), "dummyenv", "--yes") - c.Check(<-opc, gc.IsNil) - c.Check(<-errc, gc.ErrorMatches, "dummy.Destroy is broken") + op, ok := (<-opc).(dummy.OpDestroy) + c.Assert(ok, jc.IsTrue) + c.Assert(op.Error, gc.ErrorMatches, "dummy.Destroy is broken") + c.Check(<-errc, gc.Equals, op.Error) c.Check(<-opc, gc.IsNil) } @@ -120,7 +122,7 @@ ctx.Stdin = &stdin // Prepare the environment so we can destroy it. - env, err := environs.PrepareFromName("dummyenv", s.ConfigStore) + env, err := environs.PrepareFromName("dummyenv", nullContext(), s.ConfigStore) c.Assert(err, gc.IsNil) assertEnvironNotDestroyed(c, env, s.ConfigStore) @@ -154,7 +156,7 @@ for _, answer := range []string{"y", "Y", "yes", "YES"} { // Prepare the environment so we can destroy it. s.Reset(c) - env, err := environs.PrepareFromName("dummyenv", s.ConfigStore) + env, err := environs.PrepareFromName("dummyenv", nullContext(), s.ConfigStore) c.Assert(err, gc.IsNil) stdin.Reset() diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/plugin.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/plugin.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/plugin.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/plugin.go 2014-02-20 16:10:15.000000000 +0000 @@ -14,6 +14,7 @@ "strings" "launchpad.net/gnuflag" + "launchpad.net/juju-core/cmd" "launchpad.net/juju-core/juju/osenv" "launchpad.net/juju-core/log" diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/ssh_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/ssh_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/ssh_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/ssh_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -41,7 +41,7 @@ s.JujuConnSuite.SetUpTest(c) s.bin = c.MkDir() - s.PatchEnvironment("PATH", s.bin+":"+os.Getenv("PATH")) + s.PatchEnvPathPrepend(s.bin) for _, name := range []string{"ssh", "scp"} { f, err := os.OpenFile(filepath.Join(s.bin, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777) c.Assert(err, gc.IsNil) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/synctools.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/synctools.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/synctools.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/synctools.go 2014-02-20 16:10:15.000000000 +0000 @@ -12,6 +12,7 @@ "launchpad.net/juju-core/environs/configstore" "launchpad.net/juju-core/environs/filestorage" "launchpad.net/juju-core/environs/sync" + "launchpad.net/juju-core/errors" "launchpad.net/juju-core/version" ) @@ -30,6 +31,7 @@ public bool source string localDir string + destination string } var _ cmd.Command = (*SyncToolsCommand)(nil) @@ -60,9 +62,15 @@ f.BoolVar(&c.public, "public", false, "tools are for a public cloud, so generate mirrors information") f.StringVar(&c.source, "source", "", "local source directory") f.StringVar(&c.localDir, "local-dir", "", "local destination directory") + f.StringVar(&c.destination, "destination", "", "local destination directory") } func (c *SyncToolsCommand) Init(args []string) error { + if c.destination != "" { + // Override localDir with destination as localDir now replaces destination + c.localDir = c.destination + logger.Warningf("Use of the --destination flag is deprecated in 1.18. Please use --local-dir instead.") + } if c.versionStr != "" { var err error if c.majorVersion, c.minorVersion, err = version.ParseMajorMinor(c.versionStr); err != nil { @@ -72,7 +80,7 @@ return cmd.CheckEmpty(args) } -func (c *SyncToolsCommand) Run(ctx *cmd.Context) error { +func (c *SyncToolsCommand) Run(ctx *cmd.Context) (resultErr error) { // Register writer for output on screen. loggo.RegisterWriter("synctools", cmd.NewCommandLogWriter("juju.environs.sync", ctx.Stdout, ctx.Stderr), loggo.INFO) defer loggo.RemoveWriter("synctools") @@ -80,10 +88,17 @@ if err != nil { return err } - environ, err := environs.PrepareFromName(c.EnvName, store) + var existing bool + if _, err := store.ReadInfo(c.EnvName); !errors.IsNotFoundError(err) { + existing = true + } + environ, err := environs.PrepareFromName(c.EnvName, ctx, store) if err != nil { return err } + if !existing { + defer destroyPreparedEnviron(environ, store, &resultErr, "Sync-tools") + } target := environ.Storage() if c.localDir != "" { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/synctools_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/synctools_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/synctools_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/synctools_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -7,6 +7,7 @@ "errors" "time" + "github.com/loggo/loggo" gc "launchpad.net/gocheck" "launchpad.net/juju-core/cmd" @@ -124,7 +125,7 @@ func (s *syncToolsSuite) TestSyncToolsCommand(c *gc.C) { for i, test := range syncToolsCommandTests { c.Logf("test %d: %s", i, test.description) - targetEnv, err := environs.PrepareFromName("test-target", s.configStore) + targetEnv, err := environs.PrepareFromName("test-target", nullContext(), s.configStore) c.Assert(err, gc.IsNil) called := false syncTools = func(sctx *sync.SyncContext) error { @@ -167,3 +168,35 @@ c.Assert(called, jc.IsTrue) s.Reset(c) } + +func (s *syncToolsSuite) TestSyncToolsCommandDeprecatedDestination(c *gc.C) { + called := false + dir := c.MkDir() + syncTools = func(sctx *sync.SyncContext) error { + c.Assert(sctx.AllVersions, gc.Equals, false) + c.Assert(sctx.DryRun, gc.Equals, false) + c.Assert(sctx.Dev, gc.Equals, false) + c.Assert(sctx.Source, gc.Equals, "") + url, err := sctx.Target.URL("") + c.Assert(err, gc.IsNil) + c.Assert(url, gc.Equals, "file://"+dir) + called = true + return nil + } + // Register writer. + tw := &loggo.TestWriter{} + c.Assert(loggo.RegisterWriter("deprecated-tester", tw, loggo.DEBUG), gc.IsNil) + defer loggo.RemoveWriter("deprecated-tester") + // Add deprecated message to be checked. + messages := []jc.SimpleMessage{ + {loggo.WARNING, "Use of the --destination flag is deprecated in 1.18. Please use --local-dir instead."}, + } + // Run sync-tools command with --destination flag. + ctx, err := runSyncToolsCommand(c, "-e", "test-target", "--destination", dir) + c.Assert(err, gc.IsNil) + c.Assert(ctx, gc.NotNil) + c.Assert(called, jc.IsTrue) + // Check deprecated message was logged. + c.Check(tw.Log, jc.LogMatches, messages) + s.Reset(c) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/upgradecharm_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/upgradecharm_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/juju/upgradecharm_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/juju/upgradecharm_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -12,6 +12,7 @@ gc "launchpad.net/gocheck" "launchpad.net/juju-core/charm" + charmtesting "launchpad.net/juju-core/charm/testing" jujutesting "launchpad.net/juju-core/juju/testing" "launchpad.net/juju-core/state" "launchpad.net/juju-core/testing" @@ -21,6 +22,15 @@ jujutesting.RepoSuite } +func (s *UpgradeCharmErrorsSuite) SetUpTest(c *gc.C) { + s.RepoSuite.SetUpTest(c) + mockstore := charmtesting.NewMockStore(c, map[string]int{}) + s.AddCleanup(func(*gc.C) { mockstore.Close() }) + s.PatchValue(&charm.Store, &charm.CharmStore{ + BaseURL: mockstore.Address(), + }) +} + var _ = gc.Suite(&UpgradeCharmErrorsSuite{}) func runUpgradeCharm(c *gc.C, args ...string) error { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/agent.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/agent.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/agent.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/agent.go 2014-02-20 16:10:15.000000000 +0000 @@ -12,6 +12,7 @@ "launchpad.net/juju-core/agent" "launchpad.net/juju-core/cmd" + "launchpad.net/juju-core/environs" "launchpad.net/juju-core/errors" "launchpad.net/juju-core/state" "launchpad.net/juju-core/state/api" @@ -146,7 +147,7 @@ } func openState(agentConfig agent.Config, a Agent) (*state.State, AgentState, error) { - st, err := agentConfig.OpenState() + st, err := agentConfig.OpenState(environs.NewStatePolicy()) if err != nil { return nil, nil, err } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/agent_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/agent_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/agent_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/agent_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -13,6 +13,7 @@ "launchpad.net/juju-core/agent" agenttools "launchpad.net/juju-core/agent/tools" "launchpad.net/juju-core/cmd" + "launchpad.net/juju-core/environs" envtesting "launchpad.net/juju-core/environs/testing" envtools "launchpad.net/juju-core/environs/tools" "launchpad.net/juju-core/juju/testing" @@ -178,7 +179,10 @@ func (s *agentSuite) SetUpSuite(c *gc.C) { s.oldRestartDelay = worker.RestartDelay - worker.RestartDelay = coretesting.ShortWait + // We could use testing.ShortWait, but this thrashes quite + // a bit when some tests are restarting every 50ms for 10 seconds, + // so use a slightly more friendly delay. + worker.RestartDelay = 250 * time.Millisecond s.JujuConnSuite.SetUpSuite(c) } @@ -262,17 +266,6 @@ c.Assert(err, gc.IsNil) } -func (s *agentSuite) proposeVersion(c *gc.C, vers version.Number) { - oldcfg, err := s.State.EnvironConfig() - c.Assert(err, gc.IsNil) - cfg, err := oldcfg.Apply(map[string]interface{}{ - "agent-version": vers.String(), - }) - c.Assert(err, gc.IsNil) - err = s.State.SetEnvironConfig(cfg, oldcfg) - c.Assert(err, gc.IsNil) -} - func (s *agentSuite) testOpenAPIState(c *gc.C, ent state.AgentEntity, agentCmd Agent, initialPassword string) { conf, err := agent.ReadConf(s.DataDir(), ent.Tag()) c.Assert(err, gc.IsNil) @@ -331,7 +324,7 @@ func (s *agentSuite) assertCanOpenState(c *gc.C, tag, dataDir string) { config, err := agent.ReadConf(dataDir, tag) c.Assert(err, gc.IsNil) - st, err := config.OpenState() + st, err := config.OpenState(environs.NewStatePolicy()) c.Assert(err, gc.IsNil) st.Close() } @@ -339,25 +332,11 @@ func (s *agentSuite) assertCannotOpenState(c *gc.C, tag, dataDir string) { config, err := agent.ReadConf(dataDir, tag) c.Assert(err, gc.IsNil) - _, err = config.OpenState() + _, err = config.OpenState(environs.NewStatePolicy()) expectErr := fmt.Sprintf("cannot log in to juju database as %q: unauthorized mongo access: auth fails", tag) c.Assert(err, gc.ErrorMatches, expectErr) } -func (s *agentSuite) testUpgrade(c *gc.C, agent runner, tag string, currentTools *coretools.Tools) { - newVers := version.Current - newVers.Patch++ - newTools := envtesting.AssertUploadFakeToolsVersions(c, s.Conn.Environ.Storage(), newVers)[0] - s.proposeVersion(c, newVers.Number) - err := runWithTimeout(agent) - envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{ - AgentName: tag, - OldTools: currentTools, - NewTools: newTools, - DataDir: s.DataDir(), - }) -} - func refreshConfig(c *gc.C, config agent.Config) agent.Config { config, err := agent.ReadConf(config.DataDir(), config.Tag()) c.Assert(err, gc.IsNil) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/bootstrap.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/bootstrap.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/bootstrap.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/bootstrap.go 2014-02-20 16:10:15.000000000 +0000 @@ -15,6 +15,7 @@ "launchpad.net/juju-core/agent" "launchpad.net/juju-core/cmd" "launchpad.net/juju-core/constraints" + "launchpad.net/juju-core/environs" "launchpad.net/juju-core/environs/bootstrap" "launchpad.net/juju-core/environs/cloudinit" "launchpad.net/juju-core/environs/config" @@ -74,23 +75,19 @@ if err != nil { return err } - // TODO(fwereade): we need to be able to customize machine jobs, - // not just hardcode these values; in particular, JobHostUnits - // on a machine, like this one, that is running JobManageEnviron - // (not to mention the actual state server itself...) will allow - // a malicious or compromised unit to trivially access to the - // user's environment credentials. However, given that this point - // is currently moot (see Upgrader in this package), the pseudo- - // local provider mode (in which everything is deployed with - // `--to 0`) offers enough value to enough people that - // JobHostUnits is currently always enabled. This will one day - // have to change, but it's strictly less important than fixing - // Upgrader, and it's a capability we'll always want to have - // available for the aforementioned use case. + // agent.BootstrapJobs is an optional field in the agent + // config, and was introduced after 1.17.2. We default to + // allowing units on machine-0 if missing. jobs := []state.MachineJob{ state.JobManageEnviron, state.JobHostUnits, } + if bootstrapJobs := c.Conf.config.Value(agent.BootstrapJobs); bootstrapJobs != "" { + jobs, err = agent.UnmarshalBootstrapJobs(bootstrapJobs) + if err != nil { + return err + } + } var characteristics instance.HardwareCharacteristics if len(bsState.Characteristics) > 0 { characteristics = bsState.Characteristics[0] @@ -100,7 +97,7 @@ Jobs: jobs, InstanceId: bsState.StateInstances[0], Characteristics: characteristics, - }, state.DefaultDialOpts()) + }, state.DefaultDialOpts(), environs.NewStatePolicy()) if err != nil { return err } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/bootstrap_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/bootstrap_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/bootstrap_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/bootstrap_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -13,6 +13,7 @@ "launchpad.net/juju-core/agent" "launchpad.net/juju-core/constraints" + "launchpad.net/juju-core/environs" "launchpad.net/juju-core/environs/bootstrap" "launchpad.net/juju-core/environs/jujutest" "launchpad.net/juju-core/errors" @@ -118,7 +119,7 @@ Addrs: []string{testing.MgoServer.Addr()}, CACert: []byte(testing.CACert), Password: testPasswordHash(), - }, state.DefaultDialOpts()) + }, state.DefaultDialOpts(), environs.NewStatePolicy()) c.Assert(err, gc.IsNil) defer st.Close() machines, err := st.AllMachines() @@ -145,7 +146,7 @@ Addrs: []string{testing.MgoServer.Addr()}, CACert: []byte(testing.CACert), Password: testPasswordHash(), - }, state.DefaultDialOpts()) + }, state.DefaultDialOpts(), environs.NewStatePolicy()) c.Assert(err, gc.IsNil) defer st.Close() cons, err := st.EnvironConstraints() @@ -164,7 +165,7 @@ return &v } -func (s *BootstrapSuite) TestMachinerWorkers(c *gc.C) { +func (s *BootstrapSuite) TestDefaultMachineJobs(c *gc.C) { _, cmd, err := s.initBootstrapCommand(c, "--env-config", testConfig) c.Assert(err, gc.IsNil) err = cmd.Run(nil) @@ -174,7 +175,7 @@ Addrs: []string{testing.MgoServer.Addr()}, CACert: []byte(testing.CACert), Password: testPasswordHash(), - }, state.DefaultDialOpts()) + }, state.DefaultDialOpts(), environs.NewStatePolicy()) c.Assert(err, gc.IsNil) defer st.Close() m, err := st.Machine("0") @@ -184,8 +185,31 @@ }) } +func (s *BootstrapSuite) TestConfiguredMachineJobs(c *gc.C) { + agentConf, cmd, err := s.initBootstrapCommand(c, "--env-config", testConfig) + c.Assert(err, gc.IsNil) + bootstrapJobs, err := agent.MarshalBootstrapJobs(state.JobManageEnviron) + c.Assert(err, gc.IsNil) + agentConf.SetValue(agent.BootstrapJobs, bootstrapJobs) + err = agentConf.Write() + c.Assert(err, gc.IsNil) + err = cmd.Run(nil) + c.Assert(err, gc.IsNil) + + st, err := state.Open(&state.Info{ + Addrs: []string{testing.MgoServer.Addr()}, + CACert: []byte(testing.CACert), + Password: testPasswordHash(), + }, state.DefaultDialOpts(), environs.NewStatePolicy()) + c.Assert(err, gc.IsNil) + defer st.Close() + m, err := st.Machine("0") + c.Assert(err, gc.IsNil) + c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageEnviron}) +} + func testOpenState(c *gc.C, info *state.Info, expectErrType error) { - st, err := state.Open(info, state.DefaultDialOpts()) + st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy()) if st != nil { st.Close() } @@ -213,7 +237,7 @@ // Check we can log in to mongo as admin. info.Tag, info.Password = "", testPasswordHash() - st, err := state.Open(info, state.DefaultDialOpts()) + st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy()) c.Assert(err, gc.IsNil) // Reset password so the tests can continue to use the same server. defer st.Close() @@ -231,7 +255,7 @@ machineConf1, err := agent.ReadConf(machineConf.DataDir(), "machine-0") c.Assert(err, gc.IsNil) - st, err = machineConf1.OpenState() + st, err = machineConf1.OpenState(environs.NewStatePolicy()) c.Assert(err, gc.IsNil) defer st.Close() } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/machine.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/machine.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/machine.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/machine.go 2014-02-20 16:10:15.000000000 +0000 @@ -221,7 +221,7 @@ runner.StartWorker("charm-revision-updater", func() (worker.Worker, error) { return charmrevisionworker.NewRevisionUpdateWorker(st.CharmRevisionUpdater()), nil }) - case params.JobManageState: + case params.JobManageStateDeprecated: // Legacy environments may set this, but we ignore it. default: // TODO(dimitern): Once all workers moved over to using @@ -337,7 +337,7 @@ runner.StartWorker("minunitsworker", func() (worker.Worker, error) { return minunitsworker.NewMinUnitsWorker(st), nil }) - case state.JobManageState: + case state.JobManageStateDeprecated: // Legacy environments may set this, but we ignore it. default: logger.Warningf("ignoring unknown job %q", job) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/machine_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/machine_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/machine_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/machine_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -44,6 +44,7 @@ "launchpad.net/juju-core/worker/deployer" "launchpad.net/juju-core/worker/instancepoller" "launchpad.net/juju-core/worker/machineenvironmentworker" + "launchpad.net/juju-core/worker/upgrader" ) type MachineSuite struct { @@ -181,7 +182,6 @@ } func (s *MachineSuite) TestDyingMachine(c *gc.C) { - c.Skip("Disabled as breaks test isolation somehow, see lp:1206195") m, _, _ := s.primeAgent(c, state.JobHostUnits) a := s.newAgent(c, m) done := make(chan error) @@ -404,8 +404,25 @@ panic("watcher died") } +func (s *agentSuite) testUpgrade(c *gc.C, agent runner, tag string, currentTools *tools.Tools) { + newVers := version.Current + newVers.Patch++ + envtesting.AssertUploadFakeToolsVersions(c, s.Conn.Environ.Storage(), newVers) + err := s.State.SetEnvironAgentVersion(newVers.Number) + c.Assert(err, gc.IsNil) + err = runWithTimeout(agent) + envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{ + AgentName: tag, + OldTools: currentTools.Version, + NewTools: newVers, + DataDir: s.DataDir(), + }) +} + func (s *MachineSuite) TestUpgrade(c *gc.C) { m, _, currentTools := s.primeAgent(c, state.JobManageEnviron, state.JobHostUnits) + err := m.SetAgentVersion(currentTools.Version) + c.Assert(err, gc.IsNil) a := s.newAgent(c, m) s.testUpgrade(c, a, m.Tag(), currentTools) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/main_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/main_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/main_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/main_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -20,11 +20,21 @@ "launchpad.net/juju-core/cmd" "launchpad.net/juju-core/environs" "launchpad.net/juju-core/testing" + "launchpad.net/juju-core/testing/testbase" + "launchpad.net/juju-core/worker/deployer" "launchpad.net/juju-core/worker/uniter/jujuc" ) var caCertFile string +func mkdtemp(prefix string) string { + d, err := ioutil.TempDir("", prefix) + if err != nil { + panic(err) + } + return d +} + func mktemp(prefix string, content string) string { f, err := ioutil.TempFile("", prefix) if err != nil { @@ -39,6 +49,12 @@ } func TestPackage(t *stdtesting.T) { + // Change the default init dir in worker/deployer, + // so the deployer doesn't try to remove upstart + // jobs from tests. + restore := testbase.PatchValue(&deployer.InitDir, mkdtemp("juju-worker-deployer")) + defer restore() + // Change the path to "juju-run", so that the // tests don't try to write to /usr/local/bin. jujuRun = mktemp("juju-run", "") diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/run.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/run.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/run.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/run.go 2014-02-20 16:10:15.000000000 +0000 @@ -141,11 +141,16 @@ if err != nil { return nil, err } - lock.Lock("juju-run") + err = lock.Lock("juju-run") + if err != nil { + return nil, err + } defer lock.Unlock() + runCmd := `[ -f "/home/ubuntu/.juju-proxy" ] && . "/home/ubuntu/.juju-proxy"` + "\n" + c.commands + return exec.RunCommands( exec.RunParams{ - Commands: c.commands, + Commands: runCmd, }) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/unit_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/unit_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/jujud/unit_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/jujud/unit_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,19 +4,27 @@ package main import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" "time" gc "launchpad.net/gocheck" "launchpad.net/juju-core/agent" "launchpad.net/juju-core/cmd" + "launchpad.net/juju-core/environs" + envtesting "launchpad.net/juju-core/environs/testing" jujutesting "launchpad.net/juju-core/juju/testing" "launchpad.net/juju-core/names" "launchpad.net/juju-core/state" "launchpad.net/juju-core/state/api/params" coretesting "launchpad.net/juju-core/testing" "launchpad.net/juju-core/tools" + "launchpad.net/juju-core/version" "launchpad.net/juju-core/worker" + "launchpad.net/juju-core/worker/upgrader" ) type UnitSuite struct { @@ -39,16 +47,23 @@ const initialUnitPassword = "unit-password-1234567890" // primeAgent creates a unit, and sets up the unit agent's directory. -// It returns the new unit and the agent's configuration. -func (s *UnitSuite) primeAgent(c *gc.C) (*state.Unit, agent.Config, *tools.Tools) { +// It returns the assigned machine, new unit and the agent's configuration. +func (s *UnitSuite) primeAgent(c *gc.C) (*state.Machine, *state.Unit, agent.Config, *tools.Tools) { jujutesting.AddStateServerMachine(c, s.State) svc := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) unit, err := svc.AddUnit() c.Assert(err, gc.IsNil) err = unit.SetPassword(initialUnitPassword) c.Assert(err, gc.IsNil) + // Assign the unit to a machine. + err = unit.AssignToNewMachine() + c.Assert(err, gc.IsNil) + id, err := unit.AssignedMachineId() + c.Assert(err, gc.IsNil) + machine, err := s.State.Machine(id) + c.Assert(err, gc.IsNil) conf, tools := s.agentSuite.primeAgent(c, unit.Tag(), initialUnitPassword) - return unit, conf, tools + return machine, unit, conf, tools } func (s *UnitSuite) newAgent(c *gc.C, unit *state.Unit) *UnitAgent { @@ -121,7 +136,7 @@ } func (s *UnitSuite) TestRunStop(c *gc.C) { - unit, _, _ := s.primeAgent(c) + _, unit, _, _ := s.primeAgent(c) a := s.newAgent(c, unit) go func() { c.Check(a.Run(nil), gc.IsNil) }() defer func() { c.Check(a.Stop(), gc.IsNil) }() @@ -129,13 +144,54 @@ } func (s *UnitSuite) TestUpgrade(c *gc.C) { - unit, _, currentTools := s.primeAgent(c) - a := s.newAgent(c, unit) - s.testUpgrade(c, a, unit.Tag(), currentTools) + machine, unit, _, currentTools := s.primeAgent(c) + agent := s.newAgent(c, unit) + newVers := version.Current + newVers.Patch++ + envtesting.PrimeTools(c, s.Conn.Environ.Storage(), s.DataDir(), newVers) + + // Set up fake downloaded tools for the assigned machine. + libDir := c.MkDir() + s.PatchValue(&environs.DataDir, libDir) + fakeToolsPath := filepath.Join(libDir, "tools", newVers.String()) + err := os.MkdirAll(fakeToolsPath, 0700) + c.Assert(err, gc.IsNil) + fakeTools := tools.Tools{ + Version: newVers, + URL: "fake-url", + Size: 1234, + SHA256: "checksum", + } + toolsMetadataData, err := json.Marshal(fakeTools) + c.Assert(err, gc.IsNil) + err = ioutil.WriteFile(filepath.Join(fakeToolsPath, "downloaded-tools.txt"), []byte(toolsMetadataData), 0644) + c.Assert(err, gc.IsNil) + + // Set the machine agent version to trigger an upgrade. + err = machine.SetAgentVersion(newVers) + c.Assert(err, gc.IsNil) + err = runWithTimeout(agent) + envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{ + AgentName: unit.Tag(), + OldTools: currentTools.Version, + NewTools: newVers, + DataDir: s.DataDir(), + }) +} + +func (s *UnitSuite) TestUpgradeFailsWithoutTools(c *gc.C) { + machine, unit, _, _ := s.primeAgent(c) + agent := s.newAgent(c, unit) + newVers := version.Current + newVers.Patch++ + err := machine.SetAgentVersion(newVers) + c.Assert(err, gc.IsNil) + err = runWithTimeout(agent) + c.Assert(err, gc.ErrorMatches, "timed out waiting for agent to finish.*") } func (s *UnitSuite) TestWithDeadUnit(c *gc.C) { - unit, _, _ := s.primeAgent(c) + _, unit, _, _ := s.primeAgent(c) err := unit.EnsureDead() c.Assert(err, gc.IsNil) a := s.newAgent(c, unit) @@ -151,7 +207,7 @@ } func (s *UnitSuite) TestOpenAPIState(c *gc.C) { - unit, _, _ := s.primeAgent(c) + _, unit, _, _ := s.primeAgent(c) s.testOpenAPIState(c, unit, s.newAgent(c, unit), initialUnitPassword) } @@ -174,7 +230,7 @@ } func (s *UnitSuite) TestOpenAPIStateWithDeadEntityTerminates(c *gc.C) { - unit, conf, _ := s.primeAgent(c) + _, unit, conf, _ := s.primeAgent(c) err := unit.EnsureDead() c.Assert(err, gc.IsNil) _, _, err = openAPIState(conf, &fakeUnitAgent{"wordpress/0"}) @@ -184,7 +240,7 @@ func (s *UnitSuite) TestOpenStateFails(c *gc.C) { // Start a unit agent and make sure it doesn't set a mongo password // we can use to connect to state with. - unit, conf, _ := s.primeAgent(c) + _, unit, conf, _ := s.primeAgent(c) a := s.newAgent(c, unit) go func() { c.Check(a.Run(nil), gc.IsNil) }() defer func() { c.Check(a.Stop(), gc.IsNil) }() diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/imagemetadata.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/imagemetadata.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/imagemetadata.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/imagemetadata.go 2014-02-20 16:10:15.000000000 +0000 @@ -61,10 +61,16 @@ } func (c *ImageMetadataCommand) Init(args []string) error { + return cmd.CheckEmpty(args) +} + +// setParams sets parameters based on the environment configuration +// for those which have not been explicitly specified. +func (c *ImageMetadataCommand) setParams(context *cmd.Context) error { c.privateStorage = "" var environ environs.Environ if store, err := configstore.Default(); err == nil { - if environ, err = environs.PrepareFromName(c.EnvName, store); err == nil { + if environ, err = environs.PrepareFromName(c.EnvName, context, store); err == nil { logger.Infof("creating image metadata for environment %q", environ.Name()) // If the user has not specified region and endpoint, try and get it from the environment. if c.Region == "" || c.Endpoint == "" { @@ -120,8 +126,7 @@ return err } } - - return cmd.CheckEmpty(args) + return nil } var helpDoc = ` @@ -143,8 +148,10 @@ ` func (c *ImageMetadataCommand) Run(context *cmd.Context) error { + if err := c.setParams(context); err != nil { + return err + } out := context.Stdout - im := &imagemetadata.ImageMetadata{ Id: c.ImageId, Arch: c.Arch, diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/imagemetadata_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/imagemetadata_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/imagemetadata_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/imagemetadata_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -228,6 +228,6 @@ c.Logf("test: %d", i) ctx := testing.Context(c) code := cmd.Main(&ImageMetadataCommand{}, ctx, t.args) - c.Check(code, gc.Equals, 2) + c.Check(code, gc.Equals, 1) } } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/signmetadata_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/signmetadata_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/signmetadata_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/signmetadata_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -8,6 +8,7 @@ "io/ioutil" "os" "path/filepath" + "strings" "github.com/loggo/loggo" gc "launchpad.net/gocheck" @@ -16,7 +17,6 @@ "launchpad.net/juju-core/environs/simplestreams" sstesting "launchpad.net/juju-core/environs/simplestreams/testing" coretesting "launchpad.net/juju-core/testing" - "strings" ) type SignMetadataSuite struct{} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/toolsmetadata_test.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/toolsmetadata_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/toolsmetadata_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/toolsmetadata_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -43,7 +43,7 @@ dummy.Reset() loggo.ResetLoggers() }) - env, err := environs.PrepareFromName("erewhemos", configstore.NewMem()) + env, err := environs.PrepareFromName("erewhemos", coretesting.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) s.env = env envtesting.RemoveAllTools(c, s.env) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/validateimagemetadata.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/validateimagemetadata.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/validateimagemetadata.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/validateimagemetadata.go 2014-02-20 16:10:15.000000000 +0000 @@ -134,7 +134,7 @@ if err != nil { return err } - environ, err := environs.PrepareFromName(c.EnvName, store) + environ, err := environs.PrepareFromName(c.EnvName, context, store) if err != nil { return err } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/validatetoolsmetadata.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/validatetoolsmetadata.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/validatetoolsmetadata.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-metadata/validatetoolsmetadata.go 2014-02-20 16:10:15.000000000 +0000 @@ -141,7 +141,7 @@ if err != nil { return err } - environ, err := environs.PrepareFromName(c.EnvName, store) + environ, err := environs.PrepareFromName(c.EnvName, context, store) if err == nil { mdLookup, ok := environ.(simplestreams.MetadataValidator) if !ok { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-restore/restore.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-restore/restore.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/plugins/juju-restore/restore.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/plugins/juju-restore/restore.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,16 +4,20 @@ package main import ( + "archive/tar" "bytes" + "compress/gzip" "fmt" "io" + "io/ioutil" "os" "os/exec" - "os/signal" + "path" "text/template" "github.com/loggo/loggo" "launchpad.net/gnuflag" + "launchpad.net/goyaml" "launchpad.net/juju-core/cmd" "launchpad.net/juju-core/constraints" @@ -105,7 +109,7 @@ initctl start juju-db mongoEval() { - mongo --ssl -u admin -p {{.AdminSecret | shquote}} localhost:37017/admin --eval "$1" + mongo --ssl -u {{.Creds.Tag}} -p {{.Creds.Password | shquote}} localhost:37017/juju --eval "$1" } # wait for mongo to come up after starting the juju-db upstart service. for i in $(seq 1 60) @@ -121,11 +125,11 @@ initctl start jujud-machine-0 `) -func updateBootstrapMachineScript(instanceId instance.Id, adminSecret string) string { +func updateBootstrapMachineScript(instanceId instance.Id, creds credentials) string { return execTemplate(updateBootstrapMachineTemplate, struct { NewInstanceId instance.Id - AdminSecret string - }{instanceId, adminSecret}) + Creds credentials + }{instanceId, creds}) } func (c *restoreCommand) Run(ctx *cmd.Context) error { @@ -136,6 +140,11 @@ if err := c.Log.Start(ctx); err != nil { return err } + creds, err := extractCreds(c.backupFile) + if err != nil { + return fmt.Errorf("cannot extract credentials from backup file: %v", err) + } + progress("extracted credentials from backup file") store, err := configstore.Default() if err != nil { return err @@ -144,21 +153,21 @@ if err != nil { return err } - env, err := rebootstrap(cfg, c.Constraints) + env, err := rebootstrap(cfg, ctx, c.Constraints) if err != nil { return fmt.Errorf("cannot re-bootstrap environment: %v", err) } - logger.Infof("connecting to newly bootstrapped instance") + progress("connecting to newly bootstrapped instance") conn, err := juju.NewAPIConn(env, api.DefaultDialOpts()) if err != nil { return fmt.Errorf("cannot connect to bootstrap instance: %v", err) } - logger.Infof("restoring bootstrap machine") - newInstId, machine0Addr, err := restoreBootstrapMachine(conn, c.backupFile) + progress("restoring bootstrap machine") + newInstId, machine0Addr, err := restoreBootstrapMachine(conn, c.backupFile, creds) if err != nil { return fmt.Errorf("cannot restore bootstrap machine: %v", err) } - logger.Infof("restored bootstrap machine") + progress("restored bootstrap machine") // Update the environ state to point to the new instance. if err := bootstrap.SaveState(env.Storage(), &bootstrap.BootstrapState{ StateInstances: []instance.Id{newInstId}, @@ -172,25 +181,29 @@ if !ok { return fmt.Errorf("configuration has no CA certificate") } - logger.Infof("opening state") + progress("opening state") st, err := state.Open(&state.Info{ Addrs: []string{fmt.Sprintf("%s:%d", machine0Addr, cfg.StatePort())}, CACert: caCert, - Tag: "", - Password: env.Config().AdminSecret(), - }, state.DefaultDialOpts()) + Tag: creds.Tag, + Password: creds.Password, + }, state.DefaultDialOpts(), environs.NewStatePolicy()) if err != nil { return fmt.Errorf("cannot open state: %v", err) } - logger.Infof("updating all machines") + progress("updating all machines") if err := updateAllMachines(st, machine0Addr); err != nil { return fmt.Errorf("cannot update machines: %v", err) } return nil } -func rebootstrap(cfg *config.Config, cons constraints.Value) (environs.Environ, error) { - logger.Infof("re-bootstrapping environment") +func progress(f string, a ...interface{}) { + fmt.Printf("%s\n", fmt.Sprintf(f, a...)) +} + +func rebootstrap(cfg *config.Config, ctx *cmd.Context, cons constraints.Value) (environs.Environ, error) { + progress("re-bootstrapping environment") // Turn on safe mode so that the newly bootstrapped instance // will not destroy all the instances it does not know about. cfg, err := cfg.Apply(map[string]interface{}{ @@ -231,13 +244,13 @@ // error-prone) or we could provide a --no-check flag to make // it go ahead anyway without the check. - if err := bootstrap.Bootstrap(bootstrapContext{}, env, cons); err != nil { + if err := bootstrap.Bootstrap(ctx, env, cons); err != nil { return nil, fmt.Errorf("cannot bootstrap new instance: %v", err) } return env, nil } -func restoreBootstrapMachine(conn *juju.APIConn, backupFile string) (newInstId instance.Id, addr string, err error) { +func restoreBootstrapMachine(conn *juju.APIConn, backupFile string, creds credentials) (newInstId instance.Id, addr string, err error) { addr, err = conn.State.Client().PublicAddress("0") if err != nil { return "", "", fmt.Errorf("cannot get public address of bootstrap machine: %v", err) @@ -252,17 +265,76 @@ } newInstId = instance.Id(info.InstanceId) + progress("copying backup file to bootstrap host") if err := scp(backupFile, addr, "~/juju-backup.tgz"); err != nil { return "", "", fmt.Errorf("cannot copy backup file to bootstrap instance: %v", err) } - - adminSecret := conn.Environ.Config().AdminSecret() - if err := ssh(addr, updateBootstrapMachineScript(newInstId, adminSecret)); err != nil { + progress("updating bootstrap machine") + if err := ssh(addr, updateBootstrapMachineScript(newInstId, creds)); err != nil { return "", "", fmt.Errorf("update script failed: %v", err) } return newInstId, addr, nil } +type credentials struct { + Tag string + Password string +} + +func extractCreds(backupFile string) (credentials, error) { + f, err := os.Open(backupFile) + if err != nil { + return credentials{}, err + } + defer f.Close() + gzr, err := gzip.NewReader(f) + if err != nil { + return credentials{}, fmt.Errorf("cannot unzip %q: %v", backupFile, err) + } + defer gzr.Close() + outerTar, err := findFileInTar(gzr, "juju-backup/root.tar") + if err != nil { + return credentials{}, err + } + agentConf, err := findFileInTar(outerTar, "var/lib/juju/agents/machine-0/agent.conf") + if err != nil { + return credentials{}, err + } + data, err := ioutil.ReadAll(agentConf) + if err != nil { + return credentials{}, fmt.Errorf("failed to read agent config file: %v", err) + } + var conf interface{} + if err := goyaml.Unmarshal(data, &conf); err != nil { + return credentials{}, fmt.Errorf("cannot unmarshal agent config file: %v", err) + } + m, ok := conf.(map[interface{}]interface{}) + if !ok { + return credentials{}, fmt.Errorf("config file unmarshalled to %T not %T", conf, m) + } + password, ok := m["statepassword"].(string) + if !ok || password == "" { + return credentials{}, fmt.Errorf("agent password not found in configuration") + } + return credentials{ + Tag: "machine-0", + Password: password, + }, nil +} + +func findFileInTar(r io.Reader, name string) (io.Reader, error) { + tarr := tar.NewReader(r) + for { + hdr, err := tarr.Next() + if err != nil { + return nil, fmt.Errorf("%q not found: %v", name, err) + } + if path.Clean(hdr.Name) == name { + return tarr, nil + } + } +} + var agentAddressTemplate = mustParseTemplate(` set -exu cd /var/lib/juju/agents @@ -311,7 +383,7 @@ if err != nil { logger.Errorf("failed to update machine %s: %v", machine, err) } else { - logger.Infof("updated machine %s", machine) + progress("updated machine %s", machine) } done <- err }() @@ -327,7 +399,7 @@ // runMachineUpdate connects via ssh to the machine and runs the update script func runMachineUpdate(m *state.Machine, sshArg string) error { - logger.Infof("updating machine: %v\n", m) + progress("updating machine: %v\n", m) addr := instance.SelectPublicAddress(m.Addresses()) if addr == "" { return fmt.Errorf("no appropriate public address found") @@ -350,7 +422,7 @@ if err != nil { return fmt.Errorf("ssh command failed: %v (%q)", err, data) } - logger.Infof("ssh command succeeded: %q", data) + progress("ssh command succeeded: %q", data) return nil } @@ -363,7 +435,6 @@ "-o", "PasswordAuthentication no", file, "ubuntu@"+host+":"+destFile) - logger.Infof("copying backup file to bootstrap host") logger.Debugf("scp command: %s %q", cmd.Path, cmd.Args) out, err := cmd.CombinedOutput() if err == nil { @@ -390,25 +461,3 @@ } return buf.String() } - -type bootstrapContext struct{} - -func (bootstrapContext) Stdin() io.Reader { - return os.Stdin -} - -func (bootstrapContext) Stdout() io.Writer { - return os.Stdout -} - -func (bootstrapContext) Stderr() io.Writer { - return os.Stderr -} - -func (bootstrapContext) InterruptNotify(c chan<- os.Signal) { - signal.Notify(c, os.Interrupt) -} - -func (bootstrapContext) StopInterruptNotify(c chan<- os.Signal) { - signal.Stop(c) -} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/cmd/supercommand.go juju-core-1.17.3/src/launchpad.net/juju-core/cmd/supercommand.go --- juju-core-1.17.2/src/launchpad.net/juju-core/cmd/supercommand.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/cmd/supercommand.go 2014-02-20 16:10:15.000000000 +0000 @@ -11,7 +11,6 @@ "strings" "github.com/loggo/loggo" - "launchpad.net/gnuflag" ) @@ -293,7 +292,9 @@ if err != nil && err != ErrSilent { logger.Errorf("%v", err) // Now that this has been logged, don't log again in cmd.Main. - err = ErrSilent + if !IsRcPassthroughError(err) { + err = ErrSilent + } } else { logger.Infof("command finished") } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/constraints/constraints.go juju-core-1.17.3/src/launchpad.net/juju-core/constraints/constraints.go --- juju-core-1.17.2/src/launchpad.net/juju-core/constraints/constraints.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/constraints/constraints.go 2014-02-20 16:10:15.000000000 +0000 @@ -9,6 +9,8 @@ "strconv" "strings" + "github.com/errgo/errgo" + "launchpad.net/juju-core/instance" ) @@ -207,7 +209,7 @@ return fmt.Errorf("unknown constraint %q", name) } if err != nil { - return fmt.Errorf("bad %q constraint: %v", name, err) + return errgo.Annotatef(err, "bad %q constraint", name) } return nil } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/container/lxc/lxc_test.go juju-core-1.17.3/src/launchpad.net/juju-core/container/lxc/lxc_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/container/lxc/lxc_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/container/lxc/lxc_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -35,34 +35,11 @@ var _ = gc.Suite(&LxcSuite{}) -func (s *LxcSuite) SetUpSuite(c *gc.C) { - s.TestSuite.SetUpSuite(c) - tmpDir := c.MkDir() - restore := testbase.PatchEnvironment("PATH", tmpDir) - s.AddSuiteCleanup(func(*gc.C) { restore() }) - err := ioutil.WriteFile( - filepath.Join(tmpDir, "apt-config"), - []byte(aptConfigScript), - 0755) - c.Assert(err, gc.IsNil) -} - func (s *LxcSuite) SetUpTest(c *gc.C) { s.TestSuite.SetUpTest(c) loggo.GetLogger("juju.container.lxc").SetLogLevel(loggo.TRACE) } -const ( - aptHTTPProxy = "http://1.2.3.4:3142" - configProxyExtra = `Acquire::https::Proxy "false"; -Acquire::ftp::Proxy "false";` -) - -var ( - configHttpProxy = fmt.Sprintf(`Acquire::http::Proxy "%s";`, aptHTTPProxy) - aptConfigScript = fmt.Sprintf("#!/bin/sh\n echo '%s\n%s'", configHttpProxy, configProxyExtra) -) - func (s *LxcSuite) TestStartContainer(c *gc.C) { manager := lxc.NewContainerManager(container.ManagerConfig{}) instance := containertesting.StartContainer(c, manager, "1/lxc/0") @@ -80,17 +57,13 @@ err = goyaml.Unmarshal(data, &x) c.Assert(err, gc.IsNil) - c.Assert(x["apt_proxy"], gc.Equals, aptHTTPProxy) - var scripts []string for _, s := range x["runcmd"].([]interface{}) { scripts = append(scripts, s.(string)) } - c.Assert(scripts[len(scripts)-4:], gc.DeepEquals, []string{ + c.Assert(scripts[len(scripts)-2:], gc.DeepEquals, []string{ "start jujud-machine-1-lxc-0", - "install -D -m 644 /dev/null '/etc/apt/apt.conf.d/99proxy-extra'", - fmt.Sprintf(`printf '%%s\n' '%s' > '/etc/apt/apt.conf.d/99proxy-extra'`, configProxyExtra), "ifconfig", }) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/container/userdata.go juju-core-1.17.3/src/launchpad.net/juju-core/container/userdata.go --- juju-core-1.17.2/src/launchpad.net/juju-core/container/userdata.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/container/userdata.go 2014-02-20 16:10:15.000000000 +0000 @@ -6,19 +6,15 @@ import ( "io/ioutil" "path/filepath" - "regexp" - "strings" "github.com/loggo/loggo" coreCloudinit "launchpad.net/juju-core/cloudinit" "launchpad.net/juju-core/environs/cloudinit" - "launchpad.net/juju-core/utils" ) var ( - logger = loggo.GetLogger("juju.container") - aptHTTPProxyRE = regexp.MustCompile(`(?i)^Acquire::HTTP::Proxy\s+"([^"]+)";$`) + logger = loggo.GetLogger("juju.container") ) func WriteUserData(machineConfig *cloudinit.MachineConfig, directory string) (string, error) { @@ -44,33 +40,6 @@ return nil, err } - // Run apt-config to fetch proxy settings from host. If no proxy - // settings are configured, then we don't set up any proxy information - // on the container. - proxyConfig, err := utils.AptConfigProxy() - if err != nil { - return nil, err - } - if proxyConfig != "" { - var proxyLines []string - for _, line := range strings.Split(proxyConfig, "\n") { - line = strings.TrimSpace(line) - if len(line) > 0 { - if m := aptHTTPProxyRE.FindStringSubmatch(line); m != nil { - cloudConfig.SetAptProxy(m[1]) - } else { - proxyLines = append(proxyLines, line) - } - } - } - if len(proxyLines) > 0 { - cloudConfig.AddFile( - "/etc/apt/apt.conf.d/99proxy-extra", - strings.Join(proxyLines, "\n"), - 0644) - } - } - // Run ifconfig to get the addresses of the internal container at least // logged in the host. cloudConfig.AddRunCmd("ifconfig") diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/dependencies_test.go juju-core-1.17.3/src/launchpad.net/juju-core/dependencies_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/dependencies_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/dependencies_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,42 @@ +// Copyright 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package juju_test + +import ( + "go/build" + "io/ioutil" + "path/filepath" + "strings" + "testing" + + gc "launchpad.net/gocheck" +) + +func Test(t *testing.T) { + gc.TestingT(t) +} + +type dependenciesTest struct{} + +var _ = gc.Suite(&dependenciesTest{}) + +func projectRoot(c *gc.C) string { + p, err := build.Import("launchpad.net/juju-core", "", build.FindOnly) + c.Assert(err, gc.IsNil) + return p.Dir +} + +func (*dependenciesTest) TestDependenciesTsvFormat(c *gc.C) { + filename := filepath.Join(projectRoot(c), "dependencies.tsv") + content, err := ioutil.ReadFile(filename) + c.Assert(err, gc.IsNil) + + for _, line := range strings.Split(string(content), "\n") { + if line == "" { + continue + } + segments := strings.Split(line, "\t") + c.Assert(segments, gc.HasLen, 4) + } +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/dependencies.tsv juju-core-1.17.3/src/launchpad.net/juju-core/dependencies.tsv --- juju-core-1.17.2/src/launchpad.net/juju-core/dependencies.tsv 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/dependencies.tsv 2014-02-20 16:10:15.000000000 +0000 @@ -1,6 +1,7 @@ code.google.com/p/go.crypto hg 6478cc9340cbbe6c04511280c5007722269108e9 184 code.google.com/p/go.net hg 3591c18acabc99439c783463ef00e6dc277eee39 77 labix.org/v2/mgo bzr gustavo@niemeyer.net-20131118213720-aralgr4ienh0gdyq 248 +github.com/errgo/errgo git 93d72bf813883d1054cae1c001d3a46603f7f559 github.com/loggo/loggo git 89458b4dc99692bc24efe9c2252d7587f8dc247b launchpad.net/gnuflag bzr roger.peppe@canonical.com-20121003093437-zcyyw0lpvj2nifpk 12 launchpad.net/goamz bzr roger.peppe@canonical.com-20131218155244-hbnkvlkkzy3vmlh9 44 @@ -9,6 +10,6 @@ launchpad.net/gomaasapi bzr ian.booth@canonical.com-20131017011445-m1hmr0ap14osd7li 47 launchpad.net/goose bzr tarmac-20140124165235-h9rloooc531udms5 116 launchpad.net/goyaml bzr gustavo@niemeyer.net-20131114120802-abe042syx64z2m7s 50 -launchpad.net/gwacl bzr tarmac-20131031081035-b33m6fyrrdiuf408 229 +launchpad.net/gwacl bzr tarmac-20140205045433-81h182mhz24fzp5e 231 launchpad.net/lpad bzr gustavo@niemeyer.net-20120626194701-536yx0g9jdq2ik3h 64 launchpad.net/tomb bzr gustavo@niemeyer.net-20130531003818-70ikdgklbxopn8x4 17 diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/doc/charms-in-action.txt juju-core-1.17.3/src/launchpad.net/juju-core/doc/charms-in-action.txt --- juju-core-1.17.2/src/launchpad.net/juju-core/doc/charms-in-action.txt 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/doc/charms-in-action.txt 2014-02-20 16:10:15.000000000 +0000 @@ -104,6 +104,7 @@ * $JUJU_CONTEXT_ID and $JUJU_AGENT_SOCKET are set (but should not be messed with: the command line tools won't work without them). * $JUJU_API_ADDRESSES holds a space separated list of juju API addresses. + * $JUJU_ENV_NAME holds the human friendly name of the current environment. Hook tools ---------- diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/boilerplate_config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/boilerplate_config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/boilerplate_config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/boilerplate_config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,11 +4,14 @@ package environs_test import ( + "strings" + gc "launchpad.net/gocheck" "launchpad.net/juju-core/environs" "launchpad.net/juju-core/juju/osenv" _ "launchpad.net/juju-core/provider/ec2" + _ "launchpad.net/juju-core/provider/manual" _ "launchpad.net/juju-core/provider/openstack" ) @@ -23,3 +26,15 @@ _, err := environs.ReadEnvironsBytes([]byte(boilerplate_text)) c.Assert(err, gc.IsNil) } + +func (*BoilerplateConfigSuite) TestBoilerPlateAliases(c *gc.C) { + defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir())) + boilerplate_text := environs.BoilerplateConfig() + // There should be only one occurrence of "manual", despite + // there being an alias ("null"). There should be nothing for + // aliases. + n := strings.Count(boilerplate_text, "type: manual") + c.Assert(n, gc.Equals, 1) + n = strings.Count(boilerplate_text, "type: null") + c.Assert(n, gc.Equals, 0) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/bootstrap/bootstrap_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/bootstrap/bootstrap_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/bootstrap/bootstrap_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/bootstrap/bootstrap_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -53,11 +53,6 @@ s.LoggingSuite.TearDownTest(c) } -func bootstrapContext(c *gc.C) environs.BootstrapContext { - ctx := coretesting.Context(c) - return envtesting.NewBootstrapContext(ctx) -} - func (s *bootstrapSuite) TestBootstrapNeedsSettings(c *gc.C) { env := newEnviron("bar", noKeysDefined) s.setDummyStorage(c, env) @@ -69,20 +64,20 @@ env.cfg = cfg } - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.ErrorMatches, "environment configuration has no admin-secret") fixEnv("admin-secret", "whatever") - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.ErrorMatches, "environment configuration has no ca-cert") fixEnv("ca-cert", coretesting.CACert) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.ErrorMatches, "environment configuration has no ca-private-key") fixEnv("ca-private-key", coretesting.CAKey) uploadTools(c, env) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) } @@ -95,7 +90,7 @@ func (s *bootstrapSuite) TestBootstrapEmptyConstraints(c *gc.C) { env := newEnviron("foo", useDefaultKeys) s.setDummyStorage(c, env) - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) c.Assert(env.bootstrapCount, gc.Equals, 1) c.Assert(env.constraints, gc.DeepEquals, constraints.Value{}) @@ -105,7 +100,7 @@ env := newEnviron("foo", useDefaultKeys) s.setDummyStorage(c, env) cons := constraints.MustParse("cpu-cores=2 mem=4G") - err := bootstrap.Bootstrap(bootstrapContext(c), env, cons) + err := bootstrap.Bootstrap(coretesting.Context(c), env, cons) c.Assert(err, gc.IsNil) c.Assert(env.bootstrapCount, gc.Equals, 1) c.Assert(env.constraints, gc.DeepEquals, cons) @@ -157,7 +152,7 @@ } cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + env, err := environs.Prepare(cfg, coretesting.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) envtesting.RemoveAllTools(c, env) @@ -170,7 +165,7 @@ if test.Arch != "" { cons = constraints.MustParse("arch=" + test.Arch) } - err = bootstrap.Bootstrap(bootstrapContext(c), env, cons) + err = bootstrap.Bootstrap(coretesting.Context(c), env, cons) if test.Err != nil { c.Check(err, gc.ErrorMatches, ".*"+test.Err.Error()) continue @@ -193,7 +188,7 @@ env := newEnviron("foo", useDefaultKeys) s.setDummyStorage(c, env) envtesting.RemoveFakeTools(c, env.Storage()) - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) // bootstrap.Bootstrap leaves it to the provider to // locate bootstrap tools. c.Assert(err, gc.IsNil) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/cloudinit/cloudinit.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/cloudinit/cloudinit.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/cloudinit/cloudinit.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/cloudinit/cloudinit.go 2014-02-20 16:10:15.000000000 +0000 @@ -10,6 +10,7 @@ "path" "strings" + "github.com/errgo/errgo" "launchpad.net/goyaml" "launchpad.net/juju-core/agent" @@ -208,11 +209,8 @@ // Note: this must be the last runcmd we do in ConfigureBasic, as // the presence of the nonce file is used to gate the remainder // of synchronous bootstrap. - noncefile := shquote(path.Join(cfg.DataDir, NonceFile)) - c.AddScripts( - fmt.Sprintf("install -D -m %o /dev/null %s", 0644, noncefile), - fmt.Sprintf(`printf '%%s\n' %s > %s`, shquote(cfg.MachineNonce), noncefile), - ) + noncefile := path.Join(cfg.DataDir, NonceFile) + c.AddFile(noncefile, cfg.MachineNonce, 0644) return nil } @@ -247,6 +245,7 @@ // juju requires git for managing charm directories. c.AddPackage("git") c.AddPackage("cpu-checker") + c.AddPackage("bridge-utils") // Write out the apt proxy settings if (cfg.AptProxySettings != osenv.ProxySettings{}) { @@ -276,8 +275,16 @@ shquote(cfg.ProxySettings.AsScriptEnvironment()))) } + // Make the lock dir and change the ownership of the lock dir itself to + // ubuntu:ubuntu from root:root so the juju-run command run as the ubuntu + // user is able to get access to the hook execution lock (like the uniter + // itself does.) + lockDir := path.Join(cfg.DataDir, "locks") c.AddScripts( - fmt.Sprintf("mkdir -p %s", cfg.DataDir), + fmt.Sprintf("mkdir -p %s", lockDir), + // We only try to change ownership if there is an ubuntu user + // defined, and we determine this by the existance of the home dir. + fmt.Sprintf("[ -e /home/ubuntu ] && chown ubuntu:ubuntu %s", lockDir), fmt.Sprintf("mkdir -p %s", cfg.LogDir), ) @@ -287,11 +294,11 @@ if strings.HasPrefix(cfg.Tools.URL, fileSchemePrefix) { copyCmd = fmt.Sprintf("cp %s $bin/tools.tar.gz", shquote(cfg.Tools.URL[len(fileSchemePrefix):])) } else { - wgetCommand := "wget" + curlCommand := "curl" if cfg.DisableSSLHostnameVerification { - wgetCommand = "wget --no-check-certificate" + curlCommand = "curl --insecure" } - copyCmd = fmt.Sprintf("%s --no-verbose -O $bin/tools.tar.gz %s", wgetCommand, shquote(cfg.Tools.URL)) + copyCmd = fmt.Sprintf("%s -o $bin/tools.tar.gz %s", curlCommand, shquote(cfg.Tools.URL)) c.AddRunCmd(cloudinit.LogProgressCmd("Fetching tools: %s", copyCmd)) } toolsJson, err := json.Marshal(cfg.Tools) @@ -346,9 +353,17 @@ if cfg.NeedMongoPPA() { const key = "" // key is loaded from PPA - c.AddAptSource("ppa:juju/stable", key) + c.AddAptSource("ppa:juju/stable", key, nil) + } + if cfg.Tools.Version.Series == "precise" { + // In precise we add the cloud-tools pocket and + // pin it with a lower priority, so we need to + // explicitly specify the target release when + // installing mongodb-server from there. + c.AddPackageFromTargetRelease("mongodb-server", "precise-updates/cloud-tools") + } else { + c.AddPackage("mongodb-server") } - c.AddPackage("mongodb-server") } certKey := string(cfg.StateServerCert) + string(cfg.StateServerKey) c.AddFile(cfg.dataFile("server.pem"), certKey, 0600) @@ -456,7 +471,7 @@ } cmds, err := acfg.WriteCommands() if err != nil { - return nil, err + return nil, errgo.Annotate(err, "failed to write commands") } c.AddScripts(cmds...) return acfg, nil @@ -474,7 +489,7 @@ conf := upstart.MachineAgentUpstartService(name, toolsDir, cfg.DataDir, cfg.LogDir, tag, machineId, nil) cmds, err := conf.InstallCommands() if err != nil { - return fmt.Errorf("cannot make cloud-init upstart script for the %s agent: %v", tag, err) + return errgo.Annotatef(err, "cannot make cloud-init upstart script for the %s agent", tag) } c.AddRunCmd(cloudinit.LogProgressCmd("Starting Juju machine agent (%s)", name)) c.AddScripts(cmds...) @@ -496,7 +511,7 @@ conf := upstart.MongoUpstartService(name, cfg.DataDir, dbDir, cfg.StatePort) cmds, err := conf.InstallCommands() if err != nil { - return fmt.Errorf("cannot make cloud-init upstart script for the state database: %v", err) + return errgo.Annotate(err, "cannot make cloud-init upstart script for the state database") } c.AddRunCmd(cloudinit.LogProgressCmd("Starting MongoDB server (%s)", name)) c.AddScripts(cmds...) @@ -599,7 +614,14 @@ } const url = "http://ubuntu-cloud.archive.canonical.com/ubuntu" name := fmt.Sprintf("deb %s %s-updates/cloud-tools main", url, series) - c.AddAptSource(name, CanonicalCloudArchiveSigningKey) + prefs := &cloudinit.AptPreferences{ + Path: cloudinit.CloudToolsPrefsPath, + Explanation: "Pin with lower priority, not to interfere with charms", + Package: "*", + Pin: fmt.Sprintf("release n=%s-updates/cloud-tools", series), + PinPriority: 400, + } + c.AddAptSource(name, CanonicalCloudArchiveSigningKey, prefs) } func (cfg *MachineConfig) NeedMongoPPA() bool { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/cloudinit/cloudinit_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/cloudinit/cloudinit_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/cloudinit/cloudinit_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/cloudinit/cloudinit_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -100,12 +100,13 @@ printf '%s\\n' 'FAKE_NONCE' > '/var/lib/juju/nonce.txt' test -e /proc/self/fd/9 \|\| exec 9>&2 \(\[ ! -e /home/ubuntu/.profile \] \|\| grep -q '.juju-proxy' /home/ubuntu/.profile\) \|\| printf .* >> /home/ubuntu/.profile -mkdir -p /var/lib/juju +mkdir -p /var/lib/juju/locks +\[ -e /home/ubuntu \] && chown ubuntu:ubuntu /var/lib/juju/locks mkdir -p /var/log/juju echo 'Fetching tools.* bin='/var/lib/juju/tools/1\.2\.3-precise-amd64' mkdir -p \$bin -wget --no-verbose -O \$bin/tools\.tar\.gz 'http://foo\.com/tools/releases/juju1\.2\.3-precise-amd64\.tgz' +curl -o \$bin/tools\.tar\.gz 'http://foo\.com/tools/releases/juju1\.2\.3-precise-amd64\.tgz' sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-precise-amd64\.sha256 grep '1234' \$bin/juju1\.2\.3-precise-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\) tar zxf \$bin/tools.tar.gz -C \$bin @@ -119,6 +120,8 @@ printf '%s\\n' '.*' > '/var/lib/juju/agents/machine-0/format' install -m 600 /dev/null '/var/lib/juju/agents/machine-0/agent\.conf' printf '%s\\n' '.*' > '/var/lib/juju/agents/machine-0/agent\.conf' +install -D -m 644 /dev/null '/etc/apt/preferences\.d/50-cloud-tools' +printf '%s\\n' '.*' > '/etc/apt/preferences\.d/50-cloud-tools' install -D -m 600 /dev/null '/var/lib/juju/system-identity' printf '%s\\n' '.*' > '/var/lib/juju/system-identity' install -D -m 600 /dev/null '/var/lib/juju/server\.pem' @@ -182,7 +185,7 @@ inexactMatch: true, expectScripts: ` bin='/var/lib/juju/tools/1\.2\.3-raring-amd64' -wget --no-verbose -O \$bin/tools\.tar\.gz 'http://foo\.com/tools/releases/juju1\.2\.3-raring-amd64\.tgz' +curl -o \$bin/tools\.tar\.gz 'http://foo\.com/tools/releases/juju1\.2\.3-raring-amd64\.tgz' sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-raring-amd64\.sha256 grep '1234' \$bin/juju1\.2\.3-raring-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\) rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-raring-amd64\.sha256 @@ -225,12 +228,13 @@ printf '%s\\n' 'FAKE_NONCE' > '/var/lib/juju/nonce.txt' test -e /proc/self/fd/9 \|\| exec 9>&2 \(\[ ! -e /home/ubuntu/\.profile \] \|\| grep -q '.juju-proxy' /home/ubuntu/.profile\) \|\| printf .* >> /home/ubuntu/.profile -mkdir -p /var/lib/juju +mkdir -p /var/lib/juju/locks +\[ -e /home/ubuntu \] && chown ubuntu:ubuntu /var/lib/juju/locks mkdir -p /var/log/juju echo 'Fetching tools.* bin='/var/lib/juju/tools/1\.2\.3-linux-amd64' mkdir -p \$bin -wget --no-verbose -O \$bin/tools\.tar\.gz 'http://foo\.com/tools/releases/juju1\.2\.3-linux-amd64\.tgz' +curl -o \$bin/tools\.tar\.gz 'http://foo\.com/tools/releases/juju1\.2\.3-linux-amd64\.tgz' sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-linux-amd64\.sha256 grep '1234' \$bin/juju1\.2\.3-linux-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\) tar zxf \$bin/tools.tar.gz -C \$bin @@ -322,7 +326,7 @@ }, inexactMatch: true, expectScripts: ` -wget --no-check-certificate --no-verbose -O \$bin/tools\.tar\.gz 'http://foo\.com/tools/releases/juju1\.2\.3-linux-amd64\.tgz' +curl --insecure -o \$bin/tools\.tar\.gz 'http://foo\.com/tools/releases/juju1\.2\.3-linux-amd64\.tgz' `, }, { // empty contraints. @@ -559,7 +563,7 @@ } } -// CheckPackage checks that the cloudinit will or won't install the given +// checkPackage checks that the cloudinit will or won't install the given // package, depending on the value of match. func checkPackage(c *gc.C, x map[interface{}]interface{}, pkg string, match bool) { pkgs0 := x["packages"] @@ -575,7 +579,9 @@ found := false for _, p0 := range pkgs { p := p0.(string) - if p == pkg { + hasTargetRelease := strings.Contains(p, "--target-release") + hasQuotedPkg := strings.Contains(p, "'"+pkg+"'") + if p == pkg || (hasTargetRelease && hasQuotedPkg) { found = true } } @@ -587,7 +593,7 @@ } } -// CheckAptSources checks that the cloudinit will or won't install the given +// checkAptSources checks that the cloudinit will or won't install the given // source, depending on the value of match. func checkAptSource(c *gc.C, x map[interface{}]interface{}, source, key string, match bool) { sources0 := x["apt_sources"] diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/cloudinit.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/cloudinit.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/cloudinit.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/cloudinit.go 2014-02-20 16:10:15.000000000 +0000 @@ -6,11 +6,14 @@ import ( "fmt" + "github.com/errgo/errgo" + "launchpad.net/juju-core/agent" coreCloudinit "launchpad.net/juju-core/cloudinit" "launchpad.net/juju-core/constraints" "launchpad.net/juju-core/environs/cloudinit" "launchpad.net/juju-core/environs/config" + "launchpad.net/juju-core/juju/osenv" "launchpad.net/juju-core/names" "launchpad.net/juju-core/state" "launchpad.net/juju-core/state/api" @@ -28,8 +31,11 @@ // CloudInitOutputLog is the default cloud-init-output.log file path. const CloudInitOutputLog = "/var/log/cloud-init-output.log" -// RsyslogConfPath is the default rsyslogd conf file path. -const RsyslogConfPath = "/etc/rsyslog.d/25-juju.conf" +// DefaultRsyslogConfPath is the default rsyslogd conf file path. +const DefaultRsyslogConfPath = "/etc/rsyslog.d/25-juju.conf" + +// Override for testing. +var RsyslogConfPath = DefaultRsyslogConfPath // MongoServiceName is the default Upstart service name for Mongo. const MongoServiceName = "juju-db" @@ -70,10 +76,17 @@ return mcfg } +// PopulateMachineConfig is called both from the FinishMachineConfig below, +// which does have access to the environment config, and from the container +// provisioners, which don't have access to the environment config. Everything +// that is needed to provision a container needs to be returned to the +// provisioner in the ContainerConfig structure. Those values are then used to +// call this function. func PopulateMachineConfig(mcfg *cloudinit.MachineConfig, providerType, authorizedKeys string, sslHostnameVerification bool, syslogPort int, + proxy, aptProxy osenv.ProxySettings, ) error { if authorizedKeys == "" { return fmt.Errorf("environment configuration has no authorized-keys") @@ -86,6 +99,8 @@ mcfg.AgentEnvironment[agent.ContainerType] = string(mcfg.MachineContainerType) mcfg.DisableSSLHostnameVerification = !sslHostnameVerification mcfg.SyslogPort = syslogPort + mcfg.ProxySettings = proxy + mcfg.AptProxySettings = aptProxy return nil } @@ -102,11 +117,17 @@ func FinishMachineConfig(mcfg *cloudinit.MachineConfig, cfg *config.Config, cons constraints.Value) (err error) { defer utils.ErrorContextf(&err, "cannot complete machine configuration") - if err := PopulateMachineConfig(mcfg, cfg.Type(), cfg.AuthorizedKeys(), cfg.SSLHostnameVerification(), cfg.SyslogPort()); err != nil { + if err := PopulateMachineConfig( + mcfg, + cfg.Type(), + cfg.AuthorizedKeys(), + cfg.SSLHostnameVerification(), + cfg.SyslogPort(), + cfg.ProxySettings(), + cfg.AptProxySettings(), + ); err != nil { return err } - mcfg.ProxySettings = cfg.ProxySettings() - mcfg.AptProxySettings = cfg.AptProxySettings() // The following settings are only appropriate at bootstrap time. At the // moment, the only state server is the bootstrap node, but this @@ -138,7 +159,7 @@ // These really are directly relevant to running a state server. cert, key, err := cfg.GenerateStateServerCertAndKey() if err != nil { - return fmt.Errorf("cannot generate state server certificate: %v", err) + return errgo.Annotate(err, "cannot generate state server certificate") } mcfg.StateServerCert = cert mcfg.StateServerKey = key diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/config/config.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/config/config.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/config/config.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/config/config.go 2014-02-20 16:10:15.000000000 +0000 @@ -12,6 +12,7 @@ "strings" "time" + "github.com/errgo/errgo" "github.com/loggo/loggo" "launchpad.net/juju-core/cert" @@ -62,10 +63,10 @@ // Config holds an immutable environment configuration. type Config struct { - // m holds the attributes that are defined for Config. - // t holds the other attributes that are passed in (aka UnknownAttrs). + // defined holds the attributes that are defined for Config. + // unknown holds the other attributes that are passed in (aka UnknownAttrs). // the union of these two are AllAttrs - m, t map[string]interface{} + defined, unknown map[string]interface{} } // Defaulting is a value that specifies whether a configuration @@ -105,13 +106,13 @@ if withDefaults { checker = withDefaultsChecker } - m, err := checker.Coerce(attrs, nil) + defined, err := checker.Coerce(attrs, nil) if err != nil { return nil, err } c := &Config{ - m: m.(map[string]interface{}), - t: make(map[string]interface{}), + defined: defined.(map[string]interface{}), + unknown: make(map[string]interface{}), } if withDefaults { if err := c.fillInDefaults(); err != nil { @@ -128,7 +129,7 @@ // Copy unknown attributes onto the type-specific map. for k, v := range attrs { if _, ok := fields[k]; !ok { - c.t[k] = v + c.unknown[k] = v } } return c, nil @@ -153,7 +154,7 @@ if _, ok := levels["unit"]; !ok { loggingConfig = loggingConfig + ";unit=DEBUG" } - c.m["logging-config"] = loggingConfig + c.defined["logging-config"] = loggingConfig return nil } @@ -169,12 +170,12 @@ keys := c.asString("authorized-keys") if path != "" || keys == "" { var err error - c.m["authorized-keys"], err = ReadAuthorizedKeys(path) + c.defined["authorized-keys"], err = ReadAuthorizedKeys(path) if err != nil { return err } } - delete(c.m, "authorized-keys-path") + delete(c.defined, "authorized-keys-path") // Don't use c.Name() because the name hasn't // been verified yet. @@ -182,11 +183,11 @@ if name == "" { return fmt.Errorf("empty name in environment configuration") } - err := maybeReadAttrFromFile(c.m, "ca-cert", name+"-cert.pem") + err := maybeReadAttrFromFile(c.defined, "ca-cert", name+"-cert.pem") if err != nil { return err } - err = maybeReadAttrFromFile(c.m, "ca-private-key", name+"-private-key.pem") + err = maybeReadAttrFromFile(c.defined, "ca-private-key", name+"-private-key.pem") if err != nil { return err } @@ -195,7 +196,7 @@ func (c *Config) fillInStringDefault(attr string) { if c.asString(attr) == "" { - c.m[attr] = defaults[attr] + c.defined[attr] = defaults[attr] } } @@ -205,19 +206,19 @@ func (cfg *Config) processDeprecatedAttributes() { // The tools url has changed so ensure that both old and new values are in the config so that // upgrades work. "tools-url" is the old attribute name. - if oldToolsURL := cfg.m["tools-url"]; oldToolsURL != nil && oldToolsURL.(string) != "" { + if oldToolsURL := cfg.defined["tools-url"]; oldToolsURL != nil && oldToolsURL.(string) != "" { _, newToolsSpecified := cfg.ToolsURL() // Ensure the new attribute name "tools-metadata-url" is set. if !newToolsSpecified { - cfg.m["tools-metadata-url"] = oldToolsURL + cfg.defined["tools-metadata-url"] = oldToolsURL } } // Even if the user has edited their environment yaml to remove the deprecated tools-url value, // we still want it in the config for upgrades. - cfg.m["tools-url"], _ = cfg.ToolsURL() + cfg.defined["tools-url"], _ = cfg.ToolsURL() // Update the provider type from null to manual. if cfg.Type() == "null" { - cfg.m["type"] = "manual" + cfg.defined["type"] = "manual" } } @@ -227,20 +228,20 @@ func Validate(cfg, old *Config) error { // Check that we don't have any disallowed fields. for _, attr := range allowedWithDefaultsOnly { - if _, ok := cfg.m[attr]; ok { + if _, ok := cfg.defined[attr]; ok { return fmt.Errorf("attribute %q is not allowed in configuration", attr) } } // Check that mandatory fields are specified. for _, attr := range mandatoryWithoutDefaults { - if _, ok := cfg.m[attr]; !ok { + if _, ok := cfg.defined[attr]; !ok { return fmt.Errorf("%s missing from environment configuration", attr) } } // Check that all other fields that have been specified are non-empty, // unless they're allowed to be empty for backward compatibility, - for attr, val := range cfg.m { + for attr, val := range cfg.defined { if !isEmpty(val) { continue } @@ -255,14 +256,14 @@ // Check that the agent version parses ok if set explicitly; otherwise leave // it alone. - if v, ok := cfg.m["agent-version"].(string); ok { + if v, ok := cfg.defined["agent-version"].(string); ok { if _, err := version.Parse(v); err != nil { return fmt.Errorf("invalid agent version in environment configuration: %q", v) } } // If the logging config is set, make sure it is valid. - if v, ok := cfg.m["logging-config"].(string); ok { + if v, ok := cfg.defined["logging-config"].(string); ok { if _, err := loggo.ParseConfigurationString(v); err != nil { return err } @@ -277,7 +278,7 @@ caKey, caKeyOK := cfg.CAPrivateKey() if caCertOK || caKeyOK { if err := verifyKeyPair(caCert, caKey); err != nil { - return fmt.Errorf("bad CA certificate/key in configuration: %v", err) + return errgo.Annotate(err, "bad CA certificate/key in configuration") } } @@ -292,7 +293,7 @@ // Check the immutable config values. These can't change if old != nil { for _, attr := range immutableAttributes { - if newv, oldv := cfg.m[attr], old.m[attr]; newv != oldv { + if newv, oldv := cfg.defined[attr], old.defined[attr]; newv != oldv { return fmt.Errorf("cannot change %s from %#v to %#v", attr, oldv, newv) } } @@ -324,22 +325,22 @@ panic(fmt.Errorf("unexpected type %T in configuration", val)) } -// maybeReadAttrFromFile sets m[attr] to: +// maybeReadAttrFromFile sets defined[attr] to: // -// 1) The content of the file m[attr+"-path"], if that's set -// 2) The value of m[attr] if it is already set. -// 3) The content of defaultPath if it exists and m[attr] is unset -// 4) Preserves the content of m[attr], otherwise +// 1) The content of the file defined[attr+"-path"], if that's set +// 2) The value of defined[attr] if it is already set. +// 3) The content of defaultPath if it exists and defined[attr] is unset +// 4) Preserves the content of defined[attr], otherwise // -// The m[attr+"-path"] key is always deleted. -func maybeReadAttrFromFile(m map[string]interface{}, attr, defaultPath string) error { +// The defined[attr+"-path"] key is always deleted. +func maybeReadAttrFromFile(defined map[string]interface{}, attr, defaultPath string) error { pathAttr := attr + "-path" - path, _ := m[pathAttr].(string) - delete(m, pathAttr) + path, _ := defined[pathAttr].(string) + delete(defined, pathAttr) hasPath := path != "" if !hasPath { // No path and attribute is already set; leave it be. - if s, _ := m[attr].(string); s != "" { + if s, _ := defined[attr].(string); s != "" { return nil } path = defaultPath @@ -363,7 +364,7 @@ if len(data) == 0 { return fmt.Errorf("file %q is empty", path) } - m[attr] = string(data) + defined[attr] = string(data) return nil } @@ -371,16 +372,16 @@ // in once place. It returns the given named attribute as a string, // returning "" if it isn't found. func (c *Config) asString(name string) string { - value, _ := c.m[name].(string) + value, _ := c.defined[name].(string) return value } // mustString returns the named attribute as an string, panicking if // it is not found or is empty. func (c *Config) mustString(name string) string { - value, _ := c.m[name].(string) + value, _ := c.defined[name].(string) if value == "" { - panic(fmt.Errorf("empty value for %q found in configuration (type %T, val %v)", name, c.m[name], c.m[name])) + panic(fmt.Errorf("empty value for %q found in configuration (type %T, val %v)", name, c.defined[name], c.defined[name])) } return value } @@ -389,7 +390,7 @@ // it is not found or is zero. Zero values should have been // diagnosed at Validate time. func (c *Config) mustInt(name string) int { - value, _ := c.m[name].(int) + value, _ := c.defined[name].(int) if value == 0 { panic(fmt.Errorf("empty value for %q found in configuration", name)) } @@ -498,13 +499,13 @@ RetryDelay: time.Duration(DefaultBootstrapSSHRetryDelay) * time.Second, AddressesDelay: time.Duration(DefaultBootstrapSSHAddressesDelay) * time.Second, } - if v, ok := c.m["bootstrap-timeout"].(int); ok && v != 0 { + if v, ok := c.defined["bootstrap-timeout"].(int); ok && v != 0 { opts.Timeout = time.Duration(v) * time.Second } - if v, ok := c.m["bootstrap-retry-delay"].(int); ok && v != 0 { + if v, ok := c.defined["bootstrap-retry-delay"].(int); ok && v != 0 { opts.RetryDelay = time.Duration(v) * time.Second } - if v, ok := c.m["bootstrap-addresses-delay"].(int); ok && v != 0 { + if v, ok := c.defined["bootstrap-addresses-delay"].(int); ok && v != 0 { opts.AddressesDelay = time.Duration(v) * time.Second } return opts @@ -513,7 +514,7 @@ // CACert returns the certificate of the CA that signed the state server // certificate, in PEM format, and whether the setting is available. func (c *Config) CACert() ([]byte, bool) { - if s, ok := c.m["ca-cert"]; ok { + if s, ok := c.defined["ca-cert"]; ok { return []byte(s.(string)), true } return nil, false @@ -522,7 +523,7 @@ // CAPrivateKey returns the private key of the CA that signed the state // server certificate, in PEM format, and whether the setting is available. func (c *Config) CAPrivateKey() (key []byte, ok bool) { - if s, ok := c.m["ca-private-key"]; ok && s != "" { + if s, ok := c.defined["ca-private-key"]; ok && s != "" { return []byte(s.(string)), true } return nil, false @@ -531,7 +532,7 @@ // AdminSecret returns the administrator password. // It's empty if the password has not been set. func (c *Config) AdminSecret() string { - if s, ok := c.m["admin-secret"]; ok && s != "" { + if s, ok := c.defined["admin-secret"]; ok && s != "" { return s.(string) } return "" @@ -548,7 +549,7 @@ // and whether it has been set. Once an environment is bootstrapped, this // must always be valid. func (c *Config) AgentVersion() (version.Number, bool) { - if v, ok := c.m["agent-version"].(string); ok { + if v, ok := c.defined["agent-version"].(string); ok { n, err := version.Parse(v) if err != nil { panic(err) // We should have checked it earlier. @@ -561,7 +562,7 @@ // ToolsURL returns the URL that locates the tools tarballs and metadata, // and whether it has been set. func (c *Config) ToolsURL() (string, bool) { - if url, ok := c.m["tools-metadata-url"]; ok && url != "" { + if url, ok := c.defined["tools-metadata-url"]; ok && url != "" { return url.(string), true } return "", false @@ -570,7 +571,7 @@ // ImageMetadataURL returns the URL at which the metadata used to locate image ids is located, // and wether it has been set. func (c *Config) ImageMetadataURL() (string, bool) { - if url, ok := c.m["image-metadata-url"]; ok && url != "" { + if url, ok := c.defined["image-metadata-url"]; ok && url != "" { return url.(string), true } return "", false @@ -578,13 +579,13 @@ // Development returns whether the environment is in development mode. func (c *Config) Development() bool { - return c.m["development"].(bool) + return c.defined["development"].(bool) } // SSLHostnameVerification returns weather the environment has requested // SSL hostname verification to be enabled. func (c *Config) SSLHostnameVerification() bool { - return c.m["ssl-hostname-verification"].(bool) + return c.defined["ssl-hostname-verification"].(bool) } // LoggingConfig returns the configuration string for the loggers. @@ -601,7 +602,7 @@ // ProvisionerSafeMode reports whether the provisioner should not // destroy machines it does not know about. func (c *Config) ProvisionerSafeMode() bool { - v, _ := c.m["provisioner-safe-mode"].(bool) + v, _ := c.defined["provisioner-safe-mode"].(bool) return v } @@ -609,8 +610,8 @@ // used to identify which image ids to search // when starting an instance. func (c *Config) ImageStream() string { - v, ok := c.m["image-stream"].(string) - if ok { + v, _ := c.defined["image-stream"].(string) + if v != "" { return v } return "released" @@ -621,29 +622,29 @@ // also be wrong attributes, though. Only the specific environment // implementation can tell. func (c *Config) UnknownAttrs() map[string]interface{} { - t := make(map[string]interface{}) - for k, v := range c.t { - t[k] = v + newAttrs := make(map[string]interface{}) + for k, v := range c.unknown { + newAttrs[k] = v } - return t + return newAttrs } // AllAttrs returns a copy of the raw configuration attributes. func (c *Config) AllAttrs() map[string]interface{} { - m := c.UnknownAttrs() - for k, v := range c.m { - m[k] = v + allAttrs := c.UnknownAttrs() + for k, v := range c.defined { + allAttrs[k] = v } - return m + return allAttrs } // Apply returns a new configuration that has the attributes of c plus attrs. func (c *Config) Apply(attrs map[string]interface{}) (*Config, error) { - m := c.AllAttrs() + defined := c.AllAttrs() for k, v := range attrs { - m[k] = v + defined[k] = v } - return New(NoDefaults, m) + return New(NoDefaults, defined) } var fields = schema.Fields{ @@ -707,7 +708,6 @@ "apt-http-proxy": schema.Omit, "apt-https-proxy": schema.Omit, "apt-ftp-proxy": schema.Omit, - "image-stream": schema.Omit, "bootstrap-timeout": schema.Omit, "bootstrap-retry-delay": schema.Omit, "bootstrap-addresses-delay": schema.Omit, @@ -732,6 +732,8 @@ "syslog-port": DefaultSyslogPort, // Authentication string sent with requests to the charm store "charm-store-auth": "", + // Previously image-stream could be set to an empty value + "image-stream": "", } func allowEmpty(attr string) bool { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/config/config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/config/config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/config/config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/config/config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -1013,6 +1013,7 @@ attrs["image-metadata-url"] = "" attrs["tools-metadata-url"] = "" attrs["tools-url"] = "" + attrs["image-stream"] = "" // Default firewall mode is instance attrs["firewall-mode"] = string(config.FwInstance) c.Assert(cfg.AllAttrs(), jc.DeepEquals, attrs) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/config.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/config.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/config.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/config.go 2014-02-20 16:10:15.000000000 +0000 @@ -45,7 +45,7 @@ if kind == "" { return fmt.Errorf("environment %q has no type", rawEnviron["name"]) } - p := providers[kind] + p, _ := Provider(kind) if p == nil { return fmt.Errorf("environment %q has an unknown provider type %q", rawEnviron["name"], kind) } @@ -107,22 +107,38 @@ // providers maps from provider type to EnvironProvider for // each registered provider type. +// +// providers should not typically be used directly; the +// Provider function will handle provider type aliases, +// and should be used instead. var providers = make(map[string]EnvironProvider) +// providerAliases is a map of provider type aliases. +var providerAliases = make(map[string]string) + // RegisterProvider registers a new environment provider. Name gives the name // of the provider, and p the interface to that provider. // -// RegisterProvider will panic if the same provider name is registered more than -// once. -func RegisterProvider(name string, p EnvironProvider) { - if providers[name] != nil { +// RegisterProvider will panic if the provider name or any of the aliases +// are registered more than once. +func RegisterProvider(name string, p EnvironProvider, alias ...string) { + if providers[name] != nil || providerAliases[name] != "" { panic(fmt.Errorf("juju: duplicate provider name %q", name)) } providers[name] = p + for _, alias := range alias { + if providers[alias] != nil || providerAliases[alias] != "" { + panic(fmt.Errorf("juju: duplicate provider alias %q", alias)) + } + providerAliases[alias] = name + } } // Provider returns the previously registered provider with the given type. func Provider(typ string) (EnvironProvider, error) { + if alias, ok := providerAliases[typ]; ok { + typ = alias + } p, ok := providers[typ] if !ok { return nil, fmt.Errorf("no registered provider for %q", typ) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/configstore/disk.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/configstore/disk.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/configstore/disk.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/configstore/disk.go 2014-02-20 16:10:15.000000000 +0000 @@ -9,6 +9,7 @@ "os" "path/filepath" + "github.com/errgo/errgo" "github.com/loggo/loggo" "launchpad.net/goyaml" @@ -162,14 +163,14 @@ func (info *environInfo) Write() error { data, err := goyaml.Marshal(info) if err != nil { - return fmt.Errorf("cannot marshal environment info: %v", err) + return errgo.Annotate(err, "cannot marshal environment info") } // Create a temporary file and rename it, so that the data // changes atomically. parent, _ := filepath.Split(info.path) tmpFile, err := ioutil.TempFile(parent, "") if err != nil { - return fmt.Errorf("cannot create temporary file: %v", err) + return errgo.Annotate(err, "cannot create temporary file") } _, err = tmpFile.Write(data) // N.B. We need to close the file before renaming it @@ -177,11 +178,11 @@ // error. tmpFile.Close() if err != nil { - return fmt.Errorf("cannot write temporary file: %v", err) + return errgo.Annotate(err, "cannot write temporary file") } if err := utils.ReplaceFile(tmpFile.Name(), info.path); err != nil { os.Remove(tmpFile.Name()) - return fmt.Errorf("cannot rename new environment info file: %v", err) + return errgo.Annotate(err, "cannot rename new environment info file") } info.initialized = true return nil diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -304,6 +304,93 @@ c.Assert(cfg1.AllAttrs(), gc.DeepEquals, expect) } +type dummyProvider struct { + environs.EnvironProvider +} + +func (s *suite) TestRegisterProvider(c *gc.C) { + s.PatchValue(environs.Providers, make(map[string]environs.EnvironProvider)) + s.PatchValue(environs.ProviderAliases, make(map[string]string)) + type step struct { + name string + aliases []string + err string + } + type test []step + + tests := []test{ + []step{{ + name: "providerName", + }}, + []step{{ + name: "providerName", + aliases: []string{"providerName"}, + err: "juju: duplicate provider alias \"providerName\"", + }}, + []step{{ + name: "providerName", + aliases: []string{"providerAlias", "providerAlias"}, + err: "juju: duplicate provider alias \"providerAlias\"", + }}, + []step{{ + name: "providerName", + aliases: []string{"providerAlias1", "providerAlias2"}, + }}, + []step{{ + name: "providerName", + }, { + name: "providerName", + err: "juju: duplicate provider name \"providerName\"", + }}, + []step{{ + name: "providerName1", + }, { + name: "providerName2", + aliases: []string{"providerName"}, + }}, + []step{{ + name: "providerName1", + }, { + name: "providerName2", + aliases: []string{"providerName1"}, + err: "juju: duplicate provider alias \"providerName1\"", + }}, + } + + registerProvider := func(name string, aliases []string) (err error) { + defer func() { err, _ = recover().(error) }() + registered := &dummyProvider{} + environs.RegisterProvider(name, registered, aliases...) + p, err := environs.Provider(name) + c.Assert(err, gc.IsNil) + c.Assert(p, gc.Equals, registered) + for _, alias := range aliases { + p, err := environs.Provider(alias) + c.Assert(err, gc.IsNil) + c.Assert(p, gc.Equals, registered) + c.Assert(p, gc.Equals, registered) + } + return nil + } + for i, test := range tests { + c.Logf("test %d: %v", i, test) + for k := range *environs.Providers { + delete(*environs.Providers, k) + } + for k := range *environs.ProviderAliases { + delete(*environs.ProviderAliases, k) + } + for _, step := range test { + err := registerProvider(step.name, step.aliases) + if step.err == "" { + c.Assert(err, gc.IsNil) + } else { + c.Assert(err, gc.ErrorMatches, step.err) + } + } + } +} + type ConfigDeprecationSuite struct { suite writer *loggo.TestWriter diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/emptystorage_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/emptystorage_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/emptystorage_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/emptystorage_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -63,7 +63,8 @@ func (s *verifyStorageSuite) TestVerifyStorage(c *gc.C) { defer testing.MakeFakeHome(c, existingEnv, "existing").Restore() - environ, err := environs.PrepareFromName("test", configstore.NewMem()) + ctx := testing.Context(c) + environ, err := environs.PrepareFromName("test", ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) stor := environ.Storage() err = environs.VerifyStorage(stor) @@ -80,7 +81,8 @@ func (s *verifyStorageSuite) TestVerifyStorageFails(c *gc.C) { defer testing.MakeFakeHome(c, existingEnv, "existing").Restore() - environ, err := environs.PrepareFromName("test", configstore.NewMem()) + ctx := testing.Context(c) + environ, err := environs.PrepareFromName("test", ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) stor := environ.Storage() someError := errors.Unauthorizedf("you shall not pass") diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/errors.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/errors.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/errors.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/errors.go 2014-02-20 16:10:15.000000000 +0000 @@ -12,27 +12,3 @@ ErrNoInstances = errors.New("no instances found") ErrPartialInstances = errors.New("only some instances were found") ) - -// containersUnsupportedError indicates that the environment does not support -// creation of containers. -type containersUnsupportedError struct { - msg string -} - -func (e *containersUnsupportedError) Error() string { - return e.msg -} - -// IsContainersUnsupportedError reports whether the error -// was created by NewContainersUnsupportedError. -func IsContainersUnsupportedError(err error) bool { - _, ok := err.(*containersUnsupportedError) - return ok -} - -// NewContainersUnsupportedError returns a new error -// which satisfies IsContainersUnsupported and reports -// the given message. -func NewContainersUnsupported(msg string) error { - return &containersUnsupportedError{msg: msg} -} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/export_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/export_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/export_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/export_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -3,9 +3,10 @@ package environs -func Providers() map[string]EnvironProvider { - return providers -} +var ( + Providers = &providers + ProviderAliases = &providerAliases +) func UpdateEnvironAttrs(envs *Environs, name string, newAttrs map[string]interface{}) { for k, v := range newAttrs { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/imagemetadata/urls_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/imagemetadata/urls_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/imagemetadata/urls_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/imagemetadata/urls_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -46,7 +46,7 @@ } cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + env, err := environs.Prepare(cfg, testing.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) // Put a file in images since the dummy storage provider requires a // file to exist before the URL can be found. This is to ensure it behaves diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/instances/instancetype.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/instances/instancetype.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/instances/instancetype.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/instances/instancetype.go 2014-02-20 16:10:15.000000000 +0000 @@ -74,52 +74,44 @@ // minMemoryHeuristic is the assumed minimum amount of memory (in MB) we prefer in order to run a server (1GB) const minMemoryHeuristic = 1024 -// getMatchingInstanceTypes returns all instance types matching ic.Constraints and available -// in ic.Region, sorted by increasing region-specific cost (if known). -func getMatchingInstanceTypes(ic *InstanceConstraint, allInstanceTypes []InstanceType) ([]InstanceType, error) { - cons := ic.Constraints - region := ic.Region - var itypes []InstanceType - - // Iterate over allInstanceTypes, finding matching ones. - for _, itype := range allInstanceTypes { +// matchingTypesForConstraint returns instance types from allTypes which match cons. +func matchingTypesForConstraint(allTypes []InstanceType, cons constraints.Value) []InstanceType { + var matchingTypes []InstanceType + for _, itype := range allTypes { itype, ok := itype.match(cons) if !ok { continue } - itypes = append(itypes, itype) + matchingTypes = append(matchingTypes, itype) } + return matchingTypes +} - if len(itypes) == 0 { - // No matching instance types were found, so the fallback is to: - // 1. Sort by memory and find the smallest matching both the required architecture - // and our own heuristic: minimum amount of memory required to run a realistic server, or - // 2. Sort by memory in reverse order and return the largest one, which will hopefully work, - // albeit not the best match - archCons := constraints.Value{Arch: ic.Constraints.Arch} - for _, itype := range allInstanceTypes { - itype, ok := itype.match(archCons) - if !ok { - continue - } - itypes = append(itypes, itype) - } - sort.Sort(byMemory(itypes)) - var fallbackType *InstanceType - // 1. check for smallest instance type that can realistically run a server - for _, itype := range itypes { - if itype.Mem >= minMemoryHeuristic { - itcopy := itype - fallbackType = &itcopy - break - } - } - if fallbackType == nil && len(itypes) > 0 { - // 2. just get the one with the largest memory - fallbackType = &itypes[len(itypes)-1] - } - if fallbackType != nil { - itypes = []InstanceType{*fallbackType} +// getMatchingInstanceTypes returns all instance types matching ic.Constraints and available +// in ic.Region, sorted by increasing region-specific cost (if known). +func getMatchingInstanceTypes(ic *InstanceConstraint, allInstanceTypes []InstanceType) ([]InstanceType, error) { + region := ic.Region + var itypes []InstanceType + + // Rules used to select instance types: + // - non memory constraints like cpu-cores etc are always honoured + // - if no mem constraint specified, try opinionated default with enough mem to run a server. + // - if no matches and no mem constraint specified, try again and return any matching instance + // with the largest memory + cons := ic.Constraints + if ic.Constraints.Mem == nil { + minMem := uint64(minMemoryHeuristic) + cons.Mem = &minMem + } + itypes = matchingTypesForConstraint(allInstanceTypes, cons) + + // No matches using opinionated default, so if no mem constraint specified, + // look for matching instance with largest memory. + if len(itypes) == 0 && ic.Constraints.Mem == nil { + itypes = matchingTypesForConstraint(allInstanceTypes, ic.Constraints) + if len(itypes) > 0 { + sort.Sort(byMemory(itypes)) + itypes = []InstanceType{itypes[len(itypes)-1]} } } // If we have matching instance types, we can return those, sorted by cost. @@ -129,7 +121,7 @@ } // No luck, so report the error. - return nil, fmt.Errorf("no instance types in %s matching constraints %q", region, cons) + return nil, fmt.Errorf("no instance types in %s matching constraints %q", region, ic.Constraints) } // tagsMatch returns if the tags in wanted all exist in have. @@ -185,5 +177,11 @@ func (s byMemory) Len() int { return len(s) } func (s byMemory) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s byMemory) Less(i, j int) bool { - return s[i].Mem < s[j].Mem + inst0, inst1 := &s[i], &s[j] + if inst0.Mem != inst1.Mem { + return s[i].Mem < s[j].Mem + } + // Memory is equal, so use cost as a tie breaker. + // Result is in descending order of cost so instance with lowest cost is used. + return inst0.Cost > inst1.Cost } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/instances/instancetype_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/instances/instancetype_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/instances/instancetype_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/instances/instancetype_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -140,24 +140,59 @@ arches: []string{"arm"}, }, { - about: "fallback instance type, enough memory for mongodb", - cons: "mem=8G", + about: "enough memory for mongodb if mem not specified", + cons: "cpu-cores=4", itypesToUse: []InstanceType{ + {Id: "5", Name: "it-5", Arches: []string{"amd64"}, Mem: 1024, CpuCores: 2}, + {Id: "4", Name: "it-4", Arches: []string{"amd64"}, Mem: 2048, CpuCores: 4}, + {Id: "3", Name: "it-3", Arches: []string{"amd64"}, Mem: 1024, CpuCores: 4}, + {Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 256, CpuCores: 4}, + {Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512, CpuCores: 4}, + }, + expectedItypes: []string{"it-3", "it-4"}, + }, + { + about: "small mem specified, use that even though less than needed for mongodb", + cons: "mem=300M", + itypesToUse: []InstanceType{ + {Id: "3", Name: "it-3", Arches: []string{"amd64"}, Mem: 2048}, + {Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 256}, + {Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512}, + }, + expectedItypes: []string{"it-1", "it-3"}, + }, + { + about: "mem specified and match found", + cons: "mem=4G arch=amd64", + itypesToUse: []InstanceType{ + {Id: "4", Name: "it-4", Arches: []string{"arm"}, Mem: 8096}, {Id: "3", Name: "it-3", Arches: []string{"amd64"}, Mem: 4096}, {Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 2048}, {Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512}, }, - expectedItypes: []string{"it-2"}, + expectedItypes: []string{"it-3"}, }, { - about: "fallback instance type, not enough memory for mongodb", - cons: "mem=4G", + about: "largest mem available matching other constraints if mem not specified", + cons: "cpu-cores=4", itypesToUse: []InstanceType{ - {Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 256}, - {Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512}, + {Id: "3", Name: "it-3", Arches: []string{"amd64"}, Mem: 1024, CpuCores: 2}, + {Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 256, CpuCores: 4}, + {Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512, CpuCores: 4}, }, expectedItypes: []string{"it-1"}, }, + { + about: "largest mem available matching other constraints if mem not specified, cost is tie breaker", + cons: "cpu-cores=4", + itypesToUse: []InstanceType{ + {Id: "4", Name: "it-4", Arches: []string{"amd64"}, Mem: 1024, CpuCores: 2}, + {Id: "3", Name: "it-3", Arches: []string{"amd64"}, Mem: 256, CpuCores: 4}, + {Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 512, CpuCores: 4, Cost: 50}, + {Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512, CpuCores: 4, Cost: 100}, + }, + expectedItypes: []string{"it-2"}, + }, } func constraint(region, cons string) *InstanceConstraint { @@ -195,6 +230,12 @@ _, err = getMatchingInstanceTypes(constraint("test", "arch=i386 mem=8G"), instanceTypes) c.Check(err, gc.ErrorMatches, `no instance types in test matching constraints "arch=i386 mem=8192M"`) + + _, err = getMatchingInstanceTypes(constraint("test", "cpu-cores=9000"), instanceTypes) + c.Check(err, gc.ErrorMatches, `no instance types in test matching constraints "cpu-cores=9000"`) + + _, err = getMatchingInstanceTypes(constraint("test", "mem=90000M"), instanceTypes) + c.Check(err, gc.ErrorMatches, `no instance types in test matching constraints "mem=90000M"`) } var instanceTypeMatchTests = []struct { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/interface.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/interface.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/interface.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/interface.go 2014-02-20 16:10:15.000000000 +0000 @@ -21,7 +21,7 @@ // configuration attributes in the returned environment should // be saved to be used later. If the environment is already // prepared, this call is equivalent to Open. - Prepare(cfg *config.Config) (Environ, error) + Prepare(ctx BootstrapContext, cfg *config.Config) (Environ, error) // Open opens the environment and returns it. // The configuration must have come from a previously @@ -63,20 +63,6 @@ Storage() storage.Storage } -// BootstrapStorager is an interface through which an Environ may be -// instructed to use a special "bootstrap storage". Bootstrap storage -// is one that may be used before the bootstrap machine agent has been -// provisioned. -// -// This is useful for environments where the storage is managed by the -// machine agent once bootstrapped. -type BootstrapStorager interface { - // EnableBootstrapStorage enables bootstrap storage, returning an - // error if enablement failed. If nil is returned, then calling - // this again will have no effect and will return nil. - EnableBootstrapStorage(BootstrapContext) error -} - // ConfigGetter implements access to an environments configuration. type ConfigGetter interface { // Config returns the configuration data with which the Environ was created. @@ -85,28 +71,6 @@ Config() *config.Config } -// Prechecker is an optional interface that an Environ may implement, -// in order to support pre-flight checking of instance/container creation. -// -// Prechecker's methods are best effort, and not guaranteed to eliminate -// all invalid parameters. If a precheck method returns nil, it is not -// guaranteed that the constraints are valid; if a non-nil error is -// returned, then the constraints are definitely invalid. -type Prechecker interface { - // PrecheckInstance performs a preflight check on the specified - // series and constraints, ensuring that they are possibly valid for - // creating an instance in this environment. - PrecheckInstance(series string, cons constraints.Value) error - - // PrecheckContainer performs a preflight check on the container type, - // ensuring that the environment is possibly capable of creating a - // container of the specified type and series. - // - // The container type must be a valid ContainerType as specified - // in the instance package, and != instance.NONE. - PrecheckContainer(series string, kind instance.ContainerType) error -} - // An Environ represents a juju environment as specified // in the environments.yaml file. // @@ -194,6 +158,10 @@ // Provider returns the EnvironProvider that created this Environ. Provider() EnvironProvider + + // TODO(axw) 2014-02-11 #pending-review + // Embed state.Prechecker, and introduce an EnvironBase + // that embeds a no-op prechecker implementation. } // BootstrapContext is an interface that is passed to @@ -201,9 +169,9 @@ // information about and manipulating the context in which // it is being invoked. type BootstrapContext interface { - Stdin() io.Reader - Stdout() io.Writer - Stderr() io.Writer + GetStdin() io.Reader + GetStdout() io.Writer + GetStderr() io.Writer // InterruptNotify starts watching for interrupt signals // on behalf of the caller, sending them to the supplied diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/jujutest/livetests.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/jujutest/livetests.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/jujutest/livetests.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/jujutest/livetests.go 2014-02-20 16:10:15.000000000 +0000 @@ -118,7 +118,7 @@ } cfg, err := config.New(config.NoDefaults, t.TestConfig) c.Assert(err, gc.IsNil) - e, err := environs.Prepare(cfg, t.ConfigStore) + e, err := environs.Prepare(cfg, coretesting.Context(c), t.ConfigStore) c.Assert(err, gc.IsNil, gc.Commentf("preparing environ %#v", t.TestConfig)) c.Assert(e, gc.NotNil) t.Env = e @@ -140,7 +140,7 @@ envtesting.UploadFakeTools(c, t.Env.Storage()) err := common.EnsureNotBootstrapped(t.Env) c.Assert(err, gc.IsNil) - err = bootstrap.Bootstrap(bootstrapContext(c), t.Env, cons) + err = bootstrap.Bootstrap(coretesting.Context(c), t.Env, cons) c.Assert(err, gc.IsNil) t.bootstrapped = true } @@ -159,22 +159,12 @@ func (t *LiveTests) TestPrechecker(c *gc.C) { // Providers may implement Prechecker. If they do, then they should // return nil for empty constraints (excluding the null provider). - prechecker, ok := t.Env.(environs.Prechecker) + prechecker, ok := t.Env.(state.Prechecker) if !ok { return } - - const series = "precise" - var cons constraints.Value - c.Check(prechecker.PrecheckInstance(series, cons), gc.IsNil) - - err := prechecker.PrecheckContainer(series, instance.LXC) - // If err is nil, that is fine, some providers support containers. - if err != nil { - // But for ones that don't, they should have a standard error format. - c.Check(err, gc.ErrorMatches, ".*provider does not support .*containers") - c.Check(err, jc.Satisfies, environs.IsContainersUnsupportedError) - } + err := prechecker.PrecheckInstance("precise", constraints.Value{}) + c.Assert(err, gc.IsNil) } // TestStartStop is similar to Tests.TestStartStop except @@ -895,7 +885,7 @@ "state-server": false, "name": "dummy storage", })) - dummyenv, err := environs.Prepare(dummyCfg, configstore.NewMem()) + dummyenv, err := environs.Prepare(dummyCfg, coretesting.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) defer dummyenv.Destroy() @@ -904,7 +894,7 @@ attrs := t.TestConfig.Merge(coretesting.Attrs{"default-series": other.Series}) cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, t.ConfigStore) + env, err := environs.Prepare(cfg, coretesting.Context(c), t.ConfigStore) c.Assert(err, gc.IsNil) defer environs.Destroy(env, t.ConfigStore) @@ -924,7 +914,7 @@ err = storageCopy(dummyStorage, currentName, envStorage, otherName) c.Assert(err, gc.IsNil) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) conn, err := juju.NewConn(env) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/jujutest/tests.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/jujutest/tests.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/jujutest/tests.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/jujutest/tests.go 2014-02-20 16:10:15.000000000 +0000 @@ -61,7 +61,7 @@ func (t *Tests) Prepare(c *gc.C) environs.Environ { cfg, err := config.New(config.NoDefaults, t.TestConfig) c.Assert(err, gc.IsNil) - e, err := environs.Prepare(cfg, t.ConfigStore) + e, err := environs.Prepare(cfg, coretesting.Context(c), t.ConfigStore) c.Assert(err, gc.IsNil, gc.Commentf("preparing environ %#v", t.TestConfig)) c.Assert(e, gc.NotNil) return e @@ -78,10 +78,6 @@ t.LoggingSuite.TearDownTest(c) } -func bootstrapContext(c *gc.C) environs.BootstrapContext { - return envtesting.NewBootstrapContext(coretesting.Context(c)) -} - func (t *Tests) TestStartStop(c *gc.C) { e := t.Prepare(c) envtesting.UploadFakeTools(c, e.Storage()) @@ -138,7 +134,7 @@ envtesting.UploadFakeTools(c, e.Storage()) err := common.EnsureNotBootstrapped(e) c.Assert(err, gc.IsNil) - err = bootstrap.Bootstrap(bootstrapContext(c), e, constraints.Value{}) + err = bootstrap.Bootstrap(coretesting.Context(c), e, constraints.Value{}) c.Assert(err, gc.IsNil) info, apiInfo, err := e.StateInfo() @@ -166,7 +162,7 @@ err = common.EnsureNotBootstrapped(e3) c.Assert(err, gc.IsNil) - err = bootstrap.Bootstrap(bootstrapContext(c), e3, constraints.Value{}) + err = bootstrap.Bootstrap(coretesting.Context(c), e3, constraints.Value{}) c.Assert(err, gc.IsNil) err = common.EnsureNotBootstrapped(e3) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/manual/bootstrap.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/manual/bootstrap.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/manual/bootstrap.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/manual/bootstrap.go 2014-02-20 16:10:15.000000000 +0000 @@ -104,12 +104,13 @@ } }() - // Get a file:// scheme tools URL for the tools, which will have been - // copied to the remote machine's storage directory. + // If the tools are on the machine already, get a file:// scheme tools URL. tools := *possibleTools[0] storageDir := args.Environ.StorageDir() toolsStorageName := envtools.StorageName(tools.Version) - tools.URL = fmt.Sprintf("file://%s/%s", storageDir, toolsStorageName) + if url, _ := bootstrapStorage.URL(toolsStorageName); url == tools.URL { + tools.URL = fmt.Sprintf("file://%s/%s", storageDir, toolsStorageName) + } // Add the local storage configuration. agentEnv, err := localstorage.StoreConfig(args.Environ) @@ -136,5 +137,5 @@ for k, v := range agentEnv { mcfg.AgentEnvironment[k] = v } - return provisionMachineAgent(args.Host, mcfg, args.Context.Stderr()) + return provisionMachineAgent(args.Host, mcfg, args.Context.GetStderr()) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/manual/bootstrap_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/manual/bootstrap_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/manual/bootstrap_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/manual/bootstrap_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,19 +4,22 @@ package manual import ( + "fmt" + "io" "os" gc "launchpad.net/gocheck" "launchpad.net/juju-core/environs" "launchpad.net/juju-core/environs/bootstrap" + "launchpad.net/juju-core/environs/cloudinit" "launchpad.net/juju-core/environs/filestorage" "launchpad.net/juju-core/environs/storage" - envtesting "launchpad.net/juju-core/environs/testing" "launchpad.net/juju-core/environs/tools" "launchpad.net/juju-core/instance" "launchpad.net/juju-core/juju/testing" coretesting "launchpad.net/juju-core/testing" + "launchpad.net/juju-core/version" ) type bootstrapSuite struct { @@ -28,11 +31,9 @@ type localStorageEnviron struct { environs.Environ - storage storage.Storage - storageAddr string - storageDir string - sharedStorageAddr string - sharedStorageDir string + storage storage.Storage + storageAddr string + storageDir string } func (e *localStorageEnviron) Storage() storage.Storage { @@ -47,14 +48,6 @@ return e.storageDir } -func (e *localStorageEnviron) SharedStorageAddr() string { - return e.sharedStorageAddr -} - -func (e *localStorageEnviron) SharedStorageDir() string { - return e.sharedStorageDir -} - func (s *bootstrapSuite) SetUpTest(c *gc.C) { s.JujuConnSuite.SetUpTest(c) s.env = &localStorageEnviron{ @@ -81,7 +74,7 @@ HardwareCharacteristics: &instance.HardwareCharacteristics{ Arch: &arch, }, - Context: envtesting.NewBootstrapContext(coretesting.Context(c)), + Context: coretesting.Context(c), } } @@ -162,3 +155,28 @@ defer fakeSSH{SkipDetection: true, SkipProvisionAgent: true}.install(c).Restore() c.Assert(Bootstrap(args), gc.ErrorMatches, "no matching tools available") } + +func (s *bootstrapSuite) TestBootstrapToolsFileURL(c *gc.C) { + storageName := tools.StorageName(version.Current) + sftpURL, err := s.env.Storage().URL(storageName) + c.Assert(err, gc.IsNil) + fileURL := fmt.Sprintf("file://%s/%s", s.env.storageDir, storageName) + s.testBootstrapToolsURL(c, sftpURL, fileURL) +} + +func (s *bootstrapSuite) TestBootstrapToolsExternalURL(c *gc.C) { + const externalURL = "http://test.invalid/tools.tgz" + s.testBootstrapToolsURL(c, externalURL, externalURL) +} + +func (s *bootstrapSuite) testBootstrapToolsURL(c *gc.C, toolsURL, expectedURL string) { + s.PatchValue(&provisionMachineAgent, func(host string, mcfg *cloudinit.MachineConfig, w io.Writer) error { + c.Assert(mcfg.Tools.URL, gc.Equals, expectedURL) + return nil + }) + args := s.getArgs(c) + args.PossibleTools[0].URL = toolsURL + defer fakeSSH{SkipDetection: true}.install(c).Restore() + err := Bootstrap(args) + c.Assert(err, gc.IsNil) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/manual/init_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/manual/init_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/manual/init_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/manual/init_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -43,7 +43,7 @@ // will return an error. stderr will be included in the error message. defer sshtesting.InstallFakeSSH(c, detectionScript, []string{scriptResponse, "oh noes"}, 33)() hc, _, err := DetectSeriesAndHardwareCharacteristics("hostname") - c.Assert(err, gc.ErrorMatches, "exit status 33 \\(oh noes\\)") + c.Assert(err, gc.ErrorMatches, "rc: 33 \\(oh noes\\)") // if the script doesn't fail, stderr is simply ignored. defer sshtesting.InstallFakeSSH(c, detectionScript, []string{scriptResponse, "non-empty-stderr"}, 0)() hc, _, err = DetectSeriesAndHardwareCharacteristics("hostname") @@ -134,7 +134,7 @@ // will return an error. stderr will be included in the error message. defer sshtesting.InstallFakeSSH(c, checkProvisionedScript, []string{"non-empty-stdout", "non-empty-stderr"}, 255)() _, err = checkProvisioned("example.com") - c.Assert(err, gc.ErrorMatches, "exit status 255 \\(non-empty-stderr\\)") + c.Assert(err, gc.ErrorMatches, "rc: 255 \\(non-empty-stderr\\)") } func (s *initialisationSuite) TestInitUbuntuUserNonExisting(c *gc.C) { @@ -153,5 +153,5 @@ defer sshtesting.InstallFakeSSH(c, "", []string{"", "failed to create ubuntu user"}, 123)() defer sshtesting.InstallFakeSSH(c, "", "", 1)() // simulate failure of ubuntu@ login err := InitUbuntuUser("testhost", "testuser", "", nil, nil) - c.Assert(err, gc.ErrorMatches, "exit status 123 \\(failed to create ubuntu user\\)") + c.Assert(err, gc.ErrorMatches, "rc: 123 \\(failed to create ubuntu user\\)") } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/manual/provisioner.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/manual/provisioner.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/manual/provisioner.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/manual/provisioner.go 2014-02-20 16:10:15.000000000 +0000 @@ -293,7 +293,7 @@ return machineParams, nil } -func provisionMachineAgent(host string, mcfg *cloudinit.MachineConfig, progressWriter io.Writer) error { +var provisionMachineAgent = func(host string, mcfg *cloudinit.MachineConfig, progressWriter io.Writer) error { script, err := generateProvisioningScript(mcfg) if err != nil { return err diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/manual/provisioner_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/manual/provisioner_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/manual/provisioner_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/manual/provisioner_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -70,7 +70,7 @@ }.install(c).Restore() machineId, err = ProvisionMachine(args) if errorCode != 0 { - c.Assert(err, gc.ErrorMatches, fmt.Sprintf("exit status %d", errorCode)) + c.Assert(err, gc.ErrorMatches, fmt.Sprintf("rc: %d", errorCode)) c.Assert(machineId, gc.Equals, "") } else { c.Assert(err, gc.IsNil) @@ -104,7 +104,7 @@ SkipProvisionAgent: true, }.install(c).Restore() _, err = ProvisionMachine(args) - c.Assert(err, gc.ErrorMatches, "error checking if provisioned: exit status 255") + c.Assert(err, gc.ErrorMatches, "error checking if provisioned: rc: 255") } func (s *provisionerSuite) TestFinishMachineConfig(c *gc.C) { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/open.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/open.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/open.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/open.go 2014-02-20 16:10:15.000000000 +0000 @@ -9,6 +9,8 @@ "strings" "time" + "github.com/errgo/errgo" + "launchpad.net/juju-core/cert" "launchpad.net/juju-core/environs/config" "launchpad.net/juju-core/environs/configstore" @@ -116,12 +118,12 @@ // and environment information is created using the // given store. If the environment is already prepared, // it behaves like NewFromName. -func PrepareFromName(name string, store configstore.Storage) (Environ, error) { +func PrepareFromName(name string, ctx BootstrapContext, store configstore.Storage) (Environ, error) { cfg, _, err := ConfigForName(name, store) if err != nil { return nil, err } - return Prepare(cfg, store) + return Prepare(cfg, ctx, store) } // NewFromAttrs returns a new environment based on the provided configuration @@ -146,7 +148,7 @@ // Prepare prepares a new environment based on the provided configuration. // If the environment is already prepared, it behaves like New. -func Prepare(cfg *config.Config, store configstore.Storage) (Environ, error) { +func Prepare(cfg *config.Config, ctx BootstrapContext, store configstore.Storage) (Environ, error) { p, err := Provider(cfg.Type()) if err != nil { return nil, err @@ -173,15 +175,7 @@ if err != nil { return nil, fmt.Errorf("cannot create new info for environment %q: %v", cfg.Name(), err) } - cfg, err = ensureAdminSecret(cfg) - if err != nil { - return nil, fmt.Errorf("cannot generate admin-secret: %v", err) - } - cfg, err = ensureCertificate(cfg) - if err != nil { - return nil, fmt.Errorf("cannot ensure CA certificate: %v", err) - } - env, err := p.Prepare(cfg) + env, err := prepare(ctx, cfg, info, p) if err != nil { if err := info.Destroy(); err != nil { logger.Warningf("cannot destroy newly created environment info: %v", err) @@ -195,6 +189,18 @@ return env, nil } +func prepare(ctx BootstrapContext, cfg *config.Config, info configstore.EnvironInfo, p EnvironProvider) (Environ, error) { + cfg, err := ensureAdminSecret(cfg) + if err != nil { + return nil, fmt.Errorf("cannot generate admin-secret: %v", err) + } + cfg, err = ensureCertificate(cfg) + if err != nil { + return nil, fmt.Errorf("cannot ensure CA certificate: %v", err) + } + return p.Prepare(ctx, cfg) +} + // ensureAdminSecret returns a config with a non-empty admin-secret. func ensureAdminSecret(cfg *config.Config) (*config.Config, error) { if cfg.AdminSecret() != "" { @@ -235,7 +241,13 @@ if err := env.Destroy(); err != nil { return err } - info, err := store.ReadInfo(name) + return DestroyInfo(name, store) +} + +// DestroyInfo destroys the configuration data for the named +// environment from the given store. +func DestroyInfo(envName string, store configstore.Storage) error { + info, err := store.ReadInfo(envName) if err != nil { if errors.IsNotFoundError(err) { return nil @@ -243,7 +255,7 @@ return err } if err := info.Destroy(); err != nil { - return fmt.Errorf("cannot destroy environment configuration information: %v", err) + return errgo.Annotate(err, "cannot destroy environment configuration information") } return nil } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/open_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/open_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/open_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/open_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -37,10 +37,10 @@ // matches *Settings.Map() cfg, err := config.New(config.NoDefaults, dummySampleConfig()) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + ctx := testing.Context(c) + env, err := environs.Prepare(cfg, ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) envtesting.UploadFakeTools(c, env.Storage()) - ctx := envtesting.NewBootstrapContext(testing.Context(c)) err = bootstrap.Bootstrap(ctx, env, constraints.Value{}) c.Assert(err, gc.IsNil) } @@ -57,7 +57,8 @@ func (*OpenSuite) TestNewFromName(c *gc.C) { defer testing.MakeFakeHome(c, testing.MultipleEnvConfigNoDefault, testing.SampleCertName).Restore() store := configstore.NewMem() - e, err := environs.PrepareFromName("erewhemos", store) + ctx := testing.Context(c) + e, err := environs.PrepareFromName("erewhemos", ctx, store) c.Assert(err, gc.IsNil) e, err = environs.NewFromName("erewhemos", store) @@ -96,7 +97,8 @@ func (*OpenSuite) TestPrepareFromName(c *gc.C) { defer testing.MakeFakeHome(c, testing.MultipleEnvConfigNoDefault, testing.SampleCertName).Restore() - e, err := environs.PrepareFromName("erewhemos", configstore.NewMem()) + ctx := testing.Context(c) + e, err := environs.PrepareFromName("erewhemos", ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) c.Assert(e.Name(), gc.Equals, "erewhemos") // Check we can access storage ok, which implies the environment has been prepared. @@ -175,7 +177,8 @@ cfg, err := config.New(config.NoDefaults, baselineAttrs) c.Assert(err, gc.IsNil) store := configstore.NewMem() - env, err := environs.Prepare(cfg, store) + ctx := testing.Context(c) + env, err := environs.Prepare(cfg, ctx, store) c.Assert(err, gc.IsNil) // Check we can access storage ok, which implies the environment has been prepared. c.Assert(env.Storage(), gc.NotNil) @@ -204,7 +207,7 @@ c.Assert(caCert.Subject.CommonName, gc.Equals, `juju-generated CA for environment "`+testing.SampleEnvName+`"`) // Check we can call Prepare again. - env, err = environs.Prepare(cfg, store) + env, err = environs.Prepare(cfg, ctx, store) c.Assert(err, gc.IsNil) c.Assert(env.Name(), gc.Equals, "erewhemos") c.Assert(env.Storage(), gc.NotNil) @@ -221,13 +224,14 @@ cfg, err := config.New(config.NoDefaults, baselineAttrs) c.Assert(err, gc.IsNil) - env0, err := environs.Prepare(cfg, configstore.NewMem()) + ctx := testing.Context(c) + env0, err := environs.Prepare(cfg, ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) adminSecret0 := env0.Config().AdminSecret() c.Assert(adminSecret0, gc.HasLen, 32) c.Assert(adminSecret0, gc.Matches, "^[0-9a-f]*$") - env1, err := environs.Prepare(cfg, configstore.NewMem()) + env1, err := environs.Prepare(cfg, ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) adminSecret1 := env1.Config().AdminSecret() c.Assert(adminSecret1, gc.HasLen, 32) @@ -245,9 +249,13 @@ }, )) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + store := configstore.NewMem() + env, err := environs.Prepare(cfg, testing.Context(c), store) c.Assert(err, gc.ErrorMatches, "cannot ensure CA certificate: environment configuration with a certificate but no CA private key") c.Assert(env, gc.IsNil) + // Ensure that the config storage info is cleaned up. + _, err = store.ReadInfo(cfg.Name()) + c.Assert(err, jc.Satisfies, errors.IsNotFoundError) } func (*OpenSuite) TestPrepareWithExistingKeyPair(c *gc.C) { @@ -260,7 +268,8 @@ }, )) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + ctx := testing.Context(c) + env, err := environs.Prepare(cfg, ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) cfgCertPEM, cfgCertOK := env.Config().CACert() cfgKeyPEM, cfgKeyOK := env.Config().CAPrivateKey() @@ -282,7 +291,8 @@ store := configstore.NewMem() // Prepare the environment and sanity-check that // the config storage info has been made. - e, err := environs.Prepare(cfg, store) + ctx := testing.Context(c) + e, err := environs.Prepare(cfg, ctx, store) c.Assert(err, gc.IsNil) _, err = store.ReadInfo(e.Name()) c.Assert(err, gc.IsNil) @@ -328,7 +338,8 @@ func (s *checkEnvironmentSuite) TestCheckEnvironment(c *gc.C) { defer testing.MakeFakeHome(c, checkEnv, "existing").Restore() - environ, err := environs.PrepareFromName("test", configstore.NewMem()) + ctx := testing.Context(c) + environ, err := environs.PrepareFromName("test", ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) // VerifyStorage is sufficient for our tests and much simpler @@ -343,7 +354,8 @@ func (s *checkEnvironmentSuite) TestCheckEnvironmentFileNotFound(c *gc.C) { defer testing.MakeFakeHome(c, checkEnv, "existing").Restore() - environ, err := environs.PrepareFromName("test", configstore.NewMem()) + ctx := testing.Context(c) + environ, err := environs.PrepareFromName("test", ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) // VerifyStorage is sufficient for our tests and much simpler @@ -364,7 +376,8 @@ func (s *checkEnvironmentSuite) TestCheckEnvironmentGetFails(c *gc.C) { defer testing.MakeFakeHome(c, checkEnv, "existing").Restore() - environ, err := environs.PrepareFromName("test", configstore.NewMem()) + ctx := testing.Context(c) + environ, err := environs.PrepareFromName("test", ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) // VerifyStorage is sufficient for our tests and much simpler @@ -384,7 +397,8 @@ func (s *checkEnvironmentSuite) TestCheckEnvironmentBadContent(c *gc.C) { defer testing.MakeFakeHome(c, checkEnv, "existing").Restore() - environ, err := environs.PrepareFromName("test", configstore.NewMem()) + ctx := testing.Context(c) + environ, err := environs.PrepareFromName("test", ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) // We mock a bad (eg. from a Python-juju environment) bootstrap-verify. diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/simplestreams/simplestreams.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/simplestreams/simplestreams.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/simplestreams/simplestreams.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/simplestreams/simplestreams.go 2014-02-20 16:10:15.000000000 +0000 @@ -127,8 +127,8 @@ seriesVersionsMutex.Lock() defer seriesVersionsMutex.Unlock() updateSeriesVersions() - series := []string{} - for s, _ := range seriesVersions { + var series []string + for s := range seriesVersions { series = append(series, s) } return series diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/simplestreams/simplestreams_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/simplestreams/simplestreams_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/simplestreams/simplestreams_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/simplestreams/simplestreams_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -13,6 +13,7 @@ "launchpad.net/juju-core/environs/simplestreams" sstesting "launchpad.net/juju-core/environs/simplestreams/testing" + coretesting "launchpad.net/juju-core/testing" jc "launchpad.net/juju-core/testing/checkers" ) @@ -411,8 +412,7 @@ defer cleanup() series := simplestreams.SupportedSeries() sort.Strings(series) - series = series[0:4] - c.Assert(series, gc.DeepEquals, []string{"precise", "quantal", "raring", "saucy"}) + c.Assert(series, gc.DeepEquals, coretesting.SupportedSeries) } var getMirrorTests = []struct { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/sshstorage/storage_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/sshstorage/storage_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/sshstorage/storage_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/sshstorage/storage_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -62,8 +62,7 @@ c.Assert(err, gc.IsNil) s.bin = c.MkDir() - restoreEnv := testbase.PatchEnvironment("PATH", s.bin+":"+os.Getenv("PATH")) - s.AddSuiteCleanup(func(*gc.C) { restoreEnv() }) + s.PatchEnvPathPrepend(s.bin) // Create a "sudo" command which shifts away the "-n", sets // SUDO_UID/SUDO_GID, and executes the remaining args. diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/statepolicy.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/statepolicy.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/statepolicy.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/statepolicy.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,34 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package environs + +import ( + "launchpad.net/juju-core/environs/config" + "launchpad.net/juju-core/errors" + "launchpad.net/juju-core/state" +) + +// environStatePolicy implements state.Policy in +// terms of environs.Environ and related types. +type environStatePolicy struct{} + +var _ state.Policy = environStatePolicy{} + +// NewStatePolicy returns a state.Policy that is +// implemented in terms of Environ and related +// types. +func NewStatePolicy() state.Policy { + return environStatePolicy{} +} + +func (environStatePolicy) Prechecker(cfg *config.Config) (state.Prechecker, error) { + env, err := New(cfg) + if err != nil { + return nil, err + } + if p, ok := env.(state.Prechecker); ok { + return p, nil + } + return nil, errors.NewNotImplementedError("Prechecker") +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/storage/interfaces.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/storage/interfaces.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/storage/interfaces.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/storage/interfaces.go 2014-02-20 16:10:15.000000000 +0000 @@ -5,6 +5,7 @@ import ( "io" + "launchpad.net/juju-core/utils" ) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/storage/storage_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/storage/storage_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/storage/storage_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/storage/storage_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -42,7 +42,7 @@ func (s *datasourceSuite) SetUpTest(c *gc.C) { s.home = testing.MakeFakeHome(c, existingEnv, "existing") - environ, err := environs.PrepareFromName("test", configstore.NewMem()) + environ, err := environs.PrepareFromName("test", testing.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) s.stor = environ.Storage() s.baseURL, err = s.stor.URL("") diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/sync/sync_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/sync/sync_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/sync/sync_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/sync/sync_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -68,7 +68,7 @@ `) s.AddCleanup(func(*gc.C) { fakeHome.Restore() }) var err error - s.targetEnv, err = environs.PrepareFromName("test-target", configstore.NewMem()) + s.targetEnv, err = environs.PrepareFromName("test-target", coretesting.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) envtesting.RemoveAllTools(c, s.targetEnv) @@ -264,7 +264,7 @@ // We only want to use simplestreams to find any synced tools. cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) c.Assert(err, gc.IsNil) - s.env, err = environs.Prepare(cfg, configstore.NewMem()) + s.env, err = environs.Prepare(cfg, coretesting.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/testing/bootstrap.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/testing/bootstrap.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/testing/bootstrap.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/testing/bootstrap.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,11 +4,8 @@ package testing import ( - "io" - "github.com/loggo/loggo" - "launchpad.net/juju-core/cmd" "launchpad.net/juju-core/environs" "launchpad.net/juju-core/environs/cloudinit" "launchpad.net/juju-core/instance" @@ -29,23 +26,3 @@ } return testbase.PatchValue(&common.FinishBootstrap, f) } - -type bootstrapContext struct { - *cmd.Context -} - -func (c bootstrapContext) Stdin() io.Reader { - return c.Context.Stdin -} - -func (c bootstrapContext) Stdout() io.Writer { - return c.Context.Stdout -} - -func (c bootstrapContext) Stderr() io.Writer { - return c.Context.Stderr -} - -func NewBootstrapContext(ctx *cmd.Context) environs.BootstrapContext { - return bootstrapContext{ctx} -} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/testing/tools.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/testing/tools.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/testing/tools.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/testing/tools.go 2014-02-20 16:10:15.000000000 +0000 @@ -67,15 +67,14 @@ } } -// CheckUpgraderReadyError ensures the obtained and expected errors are equal, allowing for the fact that -// the error's tools attributes may not have size and checksum set. +// CheckUpgraderReadyError ensures the obtained and expected errors are equal. func CheckUpgraderReadyError(c *gc.C, obtained error, expected *upgrader.UpgradeReadyError) { c.Assert(obtained, gc.FitsTypeOf, &upgrader.UpgradeReadyError{}) err := obtained.(*upgrader.UpgradeReadyError) c.Assert(err.AgentName, gc.Equals, expected.AgentName) c.Assert(err.DataDir, gc.Equals, expected.DataDir) - CheckTools(c, err.OldTools, expected.OldTools) - CheckTools(c, err.NewTools, expected.NewTools) + c.Assert(err.OldTools, gc.Equals, expected.OldTools) + c.Assert(err.NewTools, gc.Equals, expected.NewTools) } // PrimeTools sets up the current version of the tools to vers and @@ -83,7 +82,6 @@ func PrimeTools(c *gc.C, stor storage.Storage, dataDir string, vers version.Binary) *coretools.Tools { err := os.RemoveAll(filepath.Join(dataDir, "tools")) c.Assert(err, gc.IsNil) - version.Current = vers agentTools, err := uploadFakeToolsVersion(stor, vers) c.Assert(err, gc.IsNil) resp, err := http.Get(agentTools.URL) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/tools/storage_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/tools/storage_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/tools/storage_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/tools/storage_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -12,6 +12,7 @@ envtesting "launchpad.net/juju-core/environs/testing" envtools "launchpad.net/juju-core/environs/tools" "launchpad.net/juju-core/provider/dummy" + "launchpad.net/juju-core/testing" "launchpad.net/juju-core/testing/testbase" coretools "launchpad.net/juju-core/tools" "launchpad.net/juju-core/version" @@ -29,7 +30,7 @@ s.LoggingSuite.SetUpTest(c) cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) c.Assert(err, gc.IsNil) - s.env, err = environs.Prepare(cfg, configstore.NewMem()) + s.env, err = environs.Prepare(cfg, testing.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) s.dataDir = c.MkDir() } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/tools/tools_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/tools/tools_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/tools/tools_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/tools/tools_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -25,6 +25,7 @@ envtools "launchpad.net/juju-core/environs/tools" "launchpad.net/juju-core/errors" "launchpad.net/juju-core/provider/dummy" + "launchpad.net/juju-core/testing" jc "launchpad.net/juju-core/testing/checkers" "launchpad.net/juju-core/testing/testbase" coretools "launchpad.net/juju-core/tools" @@ -171,7 +172,7 @@ dummy.Reset() cfg, err := config.New(config.NoDefaults, dummy.SampleConfig().Merge(attrs)) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + env, err := environs.Prepare(cfg, testing.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) s.env = env s.removeTools(c) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/environs/tools/urls_test.go juju-core-1.17.3/src/launchpad.net/juju-core/environs/tools/urls_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/environs/tools/urls_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/environs/tools/urls_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -43,7 +43,7 @@ } cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + env, err := environs.Prepare(cfg, testing.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) return env } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/juju/apiconn_test.go juju-core-1.17.3/src/launchpad.net/juju-core/juju/apiconn_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/juju/apiconn_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/juju/apiconn_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -46,11 +46,12 @@ func (*NewAPIConnSuite) TestNewConn(c *gc.C) { cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + ctx := coretesting.Context(c) + env, err := environs.Prepare(cfg, ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) envtesting.UploadFakeTools(c, env.Storage()) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(ctx, env, constraints.Value{}) c.Assert(err, gc.IsNil) cfg = env.Config() diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/juju/conn.go juju-core-1.17.3/src/launchpad.net/juju-core/juju/conn.go --- juju-core-1.17.2/src/launchpad.net/juju-core/juju/conn.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/juju/conn.go 2014-02-20 16:10:15.000000000 +0000 @@ -54,7 +54,7 @@ info.Password = password opts := state.DefaultDialOpts() - st, err := state.Open(info, opts) + st, err := state.Open(info, opts, environs.NewStatePolicy()) if errors.IsUnauthorizedError(err) { log.Noticef("juju: authorization error while connecting to state server; retrying") // We can't connect with the administrator password,; @@ -66,7 +66,7 @@ // connecting to mongo before the state has been // initialized and the initial password set. for a := redialStrategy.Start(); a.Next(); { - st, err = state.Open(info, opts) + st, err = state.Open(info, opts, environs.NewStatePolicy()) if !errors.IsUnauthorizedError(err) { break } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/juju/conn_test.go juju-core-1.17.3/src/launchpad.net/juju-core/juju/conn_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/juju/conn_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/juju/conn_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -62,17 +62,14 @@ c.Assert(err, gc.IsNil) } -func bootstrapContext(c *gc.C) environs.BootstrapContext { - return envtesting.NewBootstrapContext(coretesting.Context(c)) -} - func (*NewConnSuite) TestNewConnWithoutAdminSecret(c *gc.C) { cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + ctx := coretesting.Context(c) + env, err := environs.Prepare(cfg, ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) envtesting.UploadFakeTools(c, env.Storage()) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(ctx, env, constraints.Value{}) c.Assert(err, gc.IsNil) attrs := env.Config().AllAttrs() @@ -88,10 +85,11 @@ if store == nil { store = configstore.NewMem() } - env, err := environs.PrepareFromName(envName, store) + ctx := coretesting.Context(c) + env, err := environs.PrepareFromName(envName, ctx, store) c.Assert(err, gc.IsNil) envtesting.UploadFakeTools(c, env.Storage()) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(ctx, env, constraints.Value{}) c.Assert(err, gc.IsNil) } @@ -133,15 +131,16 @@ }) cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + ctx := coretesting.Context(c) + env, err := environs.Prepare(cfg, ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) envtesting.UploadFakeTools(c, env.Storage()) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(ctx, env, constraints.Value{}) c.Assert(err, gc.IsNil) info, _, err := env.StateInfo() c.Assert(err, gc.IsNil) info.Password = utils.UserPasswordHash("side-effect secret", utils.CompatSalt) - st, err := state.Open(info, state.DefaultDialOpts()) + st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy()) c.Assert(err, gc.IsNil) defer assertClose(c, st) @@ -182,10 +181,11 @@ }) cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + ctx := coretesting.Context(c) + env, err := environs.Prepare(cfg, ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) envtesting.UploadFakeTools(c, env.Storage()) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(ctx, env, constraints.Value{}) c.Assert(err, gc.IsNil) // Make a new Conn, which will push the secrets. @@ -218,10 +218,11 @@ }) cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + ctx := coretesting.Context(c) + env, err := environs.Prepare(cfg, ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) envtesting.UploadFakeTools(c, env.Storage()) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(ctx, env, constraints.Value{}) c.Assert(err, gc.IsNil) // Check that Bootstrap has correctly used a hash @@ -229,7 +230,7 @@ info, _, err := env.StateInfo() c.Assert(err, gc.IsNil) info.Password = utils.UserPasswordHash("nutkin", utils.CompatSalt) - st, err := state.Open(info, state.DefaultDialOpts()) + st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy()) c.Assert(err, gc.IsNil) assertClose(c, st) @@ -241,7 +242,7 @@ // Check that the password has now been changed to the original // admin password. info.Password = "nutkin" - st1, err := state.Open(info, state.DefaultDialOpts()) + st1, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy()) c.Assert(err, gc.IsNil) assertClose(c, st1) @@ -272,10 +273,11 @@ s.ToolsFixture.SetUpTest(c) cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) c.Assert(err, gc.IsNil) - environ, err := environs.Prepare(cfg, configstore.NewMem()) + ctx := coretesting.Context(c) + environ, err := environs.Prepare(cfg, ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) envtesting.UploadFakeTools(c, environ.Storage()) - err = bootstrap.Bootstrap(bootstrapContext(c), environ, constraints.Value{}) + err = bootstrap.Bootstrap(ctx, environ, constraints.Value{}) c.Assert(err, gc.IsNil) s.conn, err = juju.NewConn(environ) c.Assert(err, gc.IsNil) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/juju/testing/conn.go juju-core-1.17.3/src/launchpad.net/juju-core/juju/testing/conn.go --- juju-core-1.17.2/src/launchpad.net/juju-core/juju/testing/conn.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/juju/testing/conn.go 2014-02-20 16:10:15.000000000 +0000 @@ -177,9 +177,9 @@ err = os.Mkdir(osenv.JujuHome(), 0777) c.Assert(err, gc.IsNil) - dataDir := filepath.Join(s.RootDir, "/var/lib/juju") - err = os.MkdirAll(dataDir, 0777) + err = os.MkdirAll(s.DataDir(), 0777) c.Assert(err, gc.IsNil) + s.PatchEnvironment(osenv.JujuEnvEnvKey, "") // TODO(rog) remove these files and add them only when // the tests specifically need them (in cmd/juju for example) @@ -195,13 +195,14 @@ c.Assert(err, gc.IsNil) s.ConfigStore = store - environ, err := environs.PrepareFromName("dummyenv", s.ConfigStore) + ctx := testing.Context(c) + environ, err := environs.PrepareFromName("dummyenv", ctx, s.ConfigStore) c.Assert(err, gc.IsNil) // sanity check we've got the correct environment. c.Assert(environ.Name(), gc.Equals, "dummyenv") + s.PatchValue(&dummy.DataDir, s.DataDir()) envtesting.MustUploadFakeTools(environ.Storage()) - ctx := envtesting.NewBootstrapContext(testing.Context(c)) c.Assert(bootstrap.Bootstrap(ctx, environ, constraints.Value{}), gc.IsNil) s.BackingState = environ.(GetStater).GetStateInAPIServer() diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/log/syslog/config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/log/syslog/config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/log/syslog/config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/log/syslog/config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -6,14 +6,15 @@ import ( "io/ioutil" "path/filepath" - "testing" + stdtesting "testing" gc "launchpad.net/gocheck" "launchpad.net/juju-core/log/syslog" + syslogtesting "launchpad.net/juju-core/log/syslog/testing" ) -func Test(t *testing.T) { +func Test(t *stdtesting.T) { gc.TestingT(t) } @@ -40,32 +41,10 @@ c.Assert(string(data), gc.Equals, expectedConf) } -var expectedAccumulateSyslogConf = ` -$ModLoad imfile - -$InputFilePersistStateInterval 50 -$InputFilePollInterval 5 -$InputFileName /var/log/juju/some-machine.log -$InputFileTag juju-some-machine: -$InputFileStateFile some-machine -$InputRunFileMonitor - -$ModLoad imudp -$UDPServerRun 8888 - -# Messages received from remote rsyslog machines have messages prefixed with a space, -# so add one in for local messages too if needed. -$template JujuLogFormat,"%syslogtag:6:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" - -$FileCreateMode 0644 -:syslogtag, startswith, "juju-" /var/log/juju/all-machines.log;JujuLogFormat -& ~ -$FileCreateMode 0640 -` - func (s *SyslogConfigSuite) TestAccumulateConfigRender(c *gc.C) { syslogConfigRenderer := syslog.NewAccumulateConfig("some-machine", 8888, "") - s.assertRsyslogConfigContents(c, syslogConfigRenderer, expectedAccumulateSyslogConf) + s.assertRsyslogConfigContents( + c, syslogConfigRenderer, syslogtesting.ExpectedAccumulateSyslogConf(c, "some-machine", "", 8888)) } func (s *SyslogConfigSuite) TestAccumulateConfigWrite(c *gc.C) { @@ -77,56 +56,25 @@ c.Assert(err, gc.IsNil) syslogConfData, err := ioutil.ReadFile(syslogConfigRenderer.ConfigFilePath()) c.Assert(err, gc.IsNil) - c.Assert(string(syslogConfData), gc.Equals, expectedAccumulateSyslogConf) + c.Assert(string(syslogConfData), gc.Equals, syslogtesting.ExpectedAccumulateSyslogConf(c, "some-machine", "", 8888)) } -var expectedAccumulateNamespaceSyslogConf = ` -$ModLoad imfile - -$InputFilePersistStateInterval 50 -$InputFilePollInterval 5 -$InputFileName /var/log/juju/some-machine.log -$InputFileTag juju-namespace-some-machine: -$InputFileStateFile some-machine-namespace -$InputRunFileMonitor - -$ModLoad imudp -$UDPServerRun 8888 - -# Messages received from remote rsyslog machines have messages prefixed with a space, -# so add one in for local messages too if needed. -$template JujuLogFormat-namespace,"%syslogtag:16:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" - -$FileCreateMode 0644 -:syslogtag, startswith, "juju-namespace-" /var/log/juju-namespace/all-machines.log;JujuLogFormat-namespace -& ~ -$FileCreateMode 0640 -` - func (s *SyslogConfigSuite) TestAccumulateConfigRenderWithNamespace(c *gc.C) { syslogConfigRenderer := syslog.NewAccumulateConfig("some-machine", 8888, "namespace") - s.assertRsyslogConfigContents(c, syslogConfigRenderer, expectedAccumulateNamespaceSyslogConf) + s.assertRsyslogConfigContents( + c, syslogConfigRenderer, syslogtesting.ExpectedAccumulateSyslogConf(c, "some-machine", "namespace", 8888)) } -var expectedForwardSyslogConf = ` -$ModLoad imfile - -$InputFilePersistStateInterval 50 -$InputFilePollInterval 5 -$InputFileName /var/log/juju/some-machine.log -$InputFileTag juju-some-machine: -$InputFileStateFile some-machine -$InputRunFileMonitor - -$template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%" - -:syslogtag, startswith, "juju-" @server:999;LongTagForwardFormat -& ~ -` - func (s *SyslogConfigSuite) TestForwardConfigRender(c *gc.C) { syslogConfigRenderer := syslog.NewForwardConfig("some-machine", 999, "", []string{"server"}) - s.assertRsyslogConfigContents(c, syslogConfigRenderer, expectedForwardSyslogConf) + s.assertRsyslogConfigContents( + c, syslogConfigRenderer, syslogtesting.ExpectedForwardSyslogConf(c, "some-machine", "", 999)) +} + +func (s *SyslogConfigSuite) TestForwardConfigRenderWithNamespace(c *gc.C) { + syslogConfigRenderer := syslog.NewForwardConfig("some-machine", 999, "namespace", []string{"server"}) + s.assertRsyslogConfigContents( + c, syslogConfigRenderer, syslogtesting.ExpectedForwardSyslogConf(c, "some-machine", "namespace", 999)) } func (s *SyslogConfigSuite) TestForwardConfigWrite(c *gc.C) { @@ -138,5 +86,5 @@ c.Assert(err, gc.IsNil) syslogConfData, err := ioutil.ReadFile(syslogConfigRenderer.ConfigFilePath()) c.Assert(err, gc.IsNil) - c.Assert(string(syslogConfData), gc.Equals, expectedForwardSyslogConf) + c.Assert(string(syslogConfData), gc.Equals, syslogtesting.ExpectedForwardSyslogConf(c, "some-machine", "", 999)) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/log/syslog/testing/syslogconf.go juju-core-1.17.3/src/launchpad.net/juju-core/log/syslog/testing/syslogconf.go --- juju-core-1.17.2/src/launchpad.net/juju-core/log/syslog/testing/syslogconf.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/log/syslog/testing/syslogconf.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,87 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package testing + +import ( + "bytes" + "text/template" + + gc "launchpad.net/gocheck" +) + +var expectedAccumulateSyslogConfTemplate = ` +$ModLoad imfile + +$InputFilePersistStateInterval 50 +$InputFilePollInterval 5 +$InputFileName /var/log/juju/{{machine}}.log +$InputFileTag juju{{namespace}}-{{machine}}: +$InputFileStateFile {{machine}}{{namespace}} +$InputRunFileMonitor + +$ModLoad imudp +$UDPServerRun {{port}} + +# Messages received from remote rsyslog machines have messages prefixed with a space, +# so add one in for local messages too if needed. +$template JujuLogFormat{{namespace}},"%syslogtag:{{offset}}:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n" + +$FileCreateMode 0644 +:syslogtag, startswith, "juju{{namespace}}-" /var/log/juju{{namespace}}/all-machines.log;JujuLogFormat{{namespace}} +& ~ +$FileCreateMode 0640 +` + +// ExpectedAccumulateSyslogConf returns the expected content for a rsyslog file on a state server. +func ExpectedAccumulateSyslogConf(c *gc.C, machineTag, namespace string, port int) string { + if namespace != "" { + namespace = "-" + namespace + } + t := template.New("") + t.Funcs(template.FuncMap{ + "machine": func() string { return machineTag }, + "namespace": func() string { return namespace }, + "port": func() int { return port }, + "offset": func() int { return 6 + len(namespace) }, + }) + t = template.Must(t.Parse(expectedAccumulateSyslogConfTemplate)) + var conf bytes.Buffer + err := t.Execute(&conf, nil) + c.Assert(err, gc.IsNil) + return conf.String() +} + +var expectedForwardSyslogConfTemplate = ` +$ModLoad imfile + +$InputFilePersistStateInterval 50 +$InputFilePollInterval 5 +$InputFileName /var/log/juju/{{machine}}.log +$InputFileTag juju{{namespace}}-{{machine}}: +$InputFileStateFile {{machine}}{{namespace}} +$InputRunFileMonitor + +$template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%" + +:syslogtag, startswith, "juju{{namespace}}-" @server:{{port}};LongTagForwardFormat +& ~ +` + +// ExpectedForwardSyslogConf returns the expected content for a rsyslog file on a host machine. +func ExpectedForwardSyslogConf(c *gc.C, machineTag, namespace string, port int) string { + if namespace != "" { + namespace = "-" + namespace + } + t := template.New("") + t.Funcs(template.FuncMap{ + "machine": func() string { return machineTag }, + "namespace": func() string { return namespace }, + "port": func() int { return port }, + }) + t = template.Must(t.Parse(expectedForwardSyslogConfTemplate)) + var conf bytes.Buffer + err := t.Execute(&conf, nil) + c.Assert(err, gc.IsNil) + return conf.String() +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/all/all.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/all/all.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/all/all.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/all/all.go 2014-02-20 16:10:15.000000000 +0000 @@ -7,9 +7,9 @@ import ( _ "launchpad.net/juju-core/provider/azure" _ "launchpad.net/juju-core/provider/ec2" + //_ "launchpad.net/juju-core/provider/joyent" _ "launchpad.net/juju-core/provider/local" _ "launchpad.net/juju-core/provider/maas" _ "launchpad.net/juju-core/provider/manual" _ "launchpad.net/juju-core/provider/openstack" - //_ "launchpad.net/juju-core/provider/joyent" ) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/azure/config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/azure/config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/azure/config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/azure/config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -192,3 +192,13 @@ } c.Check(secretAttrs, gc.DeepEquals, expectedAttrs) } + +func (*configSuite) TestEmptyImageStream1dot16Compat(c *gc.C) { + attrs := makeAzureConfigMap(c) + attrs["image-stream"] = "" + provider := azureEnvironProvider{} + cfg, err := config.New(config.UseDefaults, attrs) + c.Assert(err, gc.IsNil) + _, err = provider.Validate(cfg, nil) + c.Assert(err, gc.IsNil) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/azure/environ.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/azure/environ.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/azure/environ.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/azure/environ.go 2014-02-20 16:10:15.000000000 +0000 @@ -123,18 +123,6 @@ return key, nil } -// PrecheckInstance is specified in the environs.Prechecker interface. -func (*azureEnviron) PrecheckInstance(series string, cons constraints.Value) error { - return nil -} - -// PrecheckContainer is specified in the environs.Prechecker interface. -func (*azureEnviron) PrecheckContainer(series string, kind instance.ContainerType) error { - // This check can either go away or be relaxed when the azure - // provider manages container addressibility. - return environs.NewContainersUnsupported("azure provider does not support containers") -} - // Name is specified in the Environ interface. func (env *azureEnviron) Name() string { return env.name diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/azure/environprovider.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/azure/environprovider.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/azure/environprovider.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/azure/environprovider.go 2014-02-20 16:10:15.000000000 +0000 @@ -42,7 +42,7 @@ } // Prepare is specified in the EnvironProvider interface. -func (prov azureEnvironProvider) Prepare(cfg *config.Config) (environs.Environ, error) { +func (prov azureEnvironProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { // TODO prepare environment as necessary return prov.Open(cfg) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/azure/environ_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/azure/environ_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/azure/environ_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/azure/environ_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -93,15 +93,6 @@ c.Check(env.Name(), gc.Equals, env.name) } -func (*environSuite) TestPrecheck(c *gc.C) { - env := azureEnviron{name: "foo"} - var cons constraints.Value - err := env.PrecheckInstance("saucy", cons) - c.Check(err, gc.IsNil) - err = env.PrecheckContainer("saucy", instance.LXC) - c.Check(err, gc.ErrorMatches, "azure provider does not support containers") -} - func (*environSuite) TestConfigReturnsConfig(c *gc.C) { cfg := new(config.Config) ecfg := azureEnvironConfig{Config: cfg} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/common/bootstrap.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/common/bootstrap.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/common/bootstrap.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/common/bootstrap.go 2014-02-20 16:10:15.000000000 +0000 @@ -68,12 +68,12 @@ return err } - fmt.Fprintln(ctx.Stderr(), "Launching instance") + fmt.Fprintln(ctx.GetStderr(), "Launching instance") inst, hw, err := env.StartInstance(cons, selectedTools, machineConfig) if err != nil { return fmt.Errorf("cannot start bootstrap instance: %v", err) } - fmt.Fprintf(ctx.Stderr(), " - %s\n", inst.Id()) + fmt.Fprintf(ctx.GetStderr(), " - %s\n", inst.Id()) var characteristics []instance.HardwareCharacteristics if hw != nil { @@ -144,12 +144,12 @@ defer close(ch) go func() { for _ = range ch { - fmt.Fprintln(ctx.Stderr(), "Cleaning up failed bootstrap") + fmt.Fprintln(ctx.GetStderr(), "Cleaning up failed bootstrap") } }() if inst != nil { - fmt.Fprintln(ctx.Stderr(), "Stopping instance...") + fmt.Fprintln(ctx.GetStderr(), "Stopping instance...") if stoperr := env.StopInstances([]instance.Instance{inst}); stoperr != nil { logger.Errorf("cannot stop failed bootstrap instance %q: %v", inst.Id(), stoperr) } else { @@ -217,7 +217,7 @@ Host: "ubuntu@" + addr, Client: client, Config: cloudcfg, - ProgressWriter: ctx.Stderr(), + ProgressWriter: ctx.GetStderr(), }) } @@ -365,14 +365,14 @@ checker := parallelHostChecker{ Try: parallel.NewTry(0, nil), client: client, - stderr: ctx.Stderr(), + stderr: ctx.GetStderr(), active: make(map[instance.Address]chan struct{}), checkDelay: timeout.RetryDelay, checkHostScript: checkHostScript, } defer checker.Kill() - fmt.Fprintln(ctx.Stderr(), "Waiting for address") + fmt.Fprintln(ctx.GetStderr(), "Waiting for address") for { select { case <-pollAddresses.C: diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/common/bootstrap_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/common/bootstrap_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/common/bootstrap_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/common/bootstrap_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,7 +4,6 @@ package common_test import ( - "bytes" "fmt" "os" "time" @@ -76,22 +75,13 @@ return func() *config.Config { return cfg } } -// bootstrapContext creates a BootstrapContext which -// writes stderr to the bytes.Buffer returned. -func bootstrapContext(c *gc.C) (ctx environs.BootstrapContext, stderr *bytes.Buffer) { - cmdContext := coretesting.Context(c) - stderr = &bytes.Buffer{} - cmdContext.Stderr = stderr - return envtesting.NewBootstrapContext(cmdContext), stderr -} - func (s *BootstrapSuite) TestCannotWriteStateFile(c *gc.C) { brokenStorage := &mockStorage{ Storage: newStorage(s, c), putErr: fmt.Errorf("noes!"), } env := &mockEnviron{storage: brokenStorage} - ctx, _ := bootstrapContext(c) + ctx := coretesting.Context(c) err := common.Bootstrap(ctx, env, constraints.Value{}) c.Assert(err, gc.ErrorMatches, "cannot create initial state file: noes!") } @@ -118,7 +108,7 @@ config: configGetter(c), } - ctx, _ := bootstrapContext(c) + ctx := coretesting.Context(c) err = common.Bootstrap(ctx, env, checkCons) c.Assert(err, gc.ErrorMatches, "cannot start bootstrap instance: meh, not started") } @@ -149,7 +139,7 @@ config: configGetter(c), } - ctx, _ := bootstrapContext(c) + ctx := coretesting.Context(c) err := common.Bootstrap(ctx, env, constraints.Value{}) c.Assert(err, gc.ErrorMatches, "cannot save state: suddenly a wild blah") c.Assert(stopped, gc.HasLen, 1) @@ -186,7 +176,7 @@ config: configGetter(c), } - ctx, _ := bootstrapContext(c) + ctx := coretesting.Context(c) err := common.Bootstrap(ctx, env, constraints.Value{}) c.Assert(err, gc.ErrorMatches, "cannot save state: suddenly a wild blah") c.Assert(stopped, gc.HasLen, 1) @@ -231,7 +221,7 @@ setConfig: setConfig, } originalAuthKeys := env.Config().AuthorizedKeys() - ctx, _ := bootstrapContext(c) + ctx := coretesting.Context(c) err := common.Bootstrap(ctx, env, constraints.Value{}) c.Assert(err, gc.IsNil) @@ -268,14 +258,14 @@ } func (s *BootstrapSuite) TestWaitSSHTimesOutWaitingForAddresses(c *gc.C) { - ctx, stderr := bootstrapContext(c) + ctx := coretesting.Context(c) _, err := common.WaitSSH(ctx, nil, ssh.DefaultClient, "/bin/true", neverAddresses{}, testSSHTimeout) c.Check(err, gc.ErrorMatches, `waited for `+testSSHTimeout.Timeout.String()+` without getting any addresses`) - c.Check(stderr.String(), gc.Matches, "Waiting for address\n") + c.Check(coretesting.Stderr(ctx), gc.Matches, "Waiting for address\n") } func (s *BootstrapSuite) TestWaitSSHKilledWaitingForAddresses(c *gc.C) { - ctx, stderr := bootstrapContext(c) + ctx := coretesting.Context(c) interrupted := make(chan os.Signal, 1) go func() { <-time.After(2 * time.Millisecond) @@ -283,7 +273,7 @@ }() _, err := common.WaitSSH(ctx, interrupted, ssh.DefaultClient, "/bin/true", neverAddresses{}, testSSHTimeout) c.Check(err, gc.ErrorMatches, "interrupted") - c.Check(stderr.String(), gc.Matches, "Waiting for address\n") + c.Check(coretesting.Stderr(ctx), gc.Matches, "Waiting for address\n") } type brokenAddresses struct { @@ -295,10 +285,10 @@ } func (s *BootstrapSuite) TestWaitSSHStopsOnBadError(c *gc.C) { - ctx, stderr := bootstrapContext(c) + ctx := coretesting.Context(c) _, err := common.WaitSSH(ctx, nil, ssh.DefaultClient, "/bin/true", brokenAddresses{}, testSSHTimeout) c.Check(err, gc.ErrorMatches, "getting addresses: Addresses will never work") - c.Check(stderr.String(), gc.Equals, "Waiting for address\n") + c.Check(coretesting.Stderr(ctx), gc.Equals, "Waiting for address\n") } type neverOpensPort struct { @@ -311,12 +301,12 @@ } func (s *BootstrapSuite) TestWaitSSHTimesOutWaitingForDial(c *gc.C) { - ctx, stderr := bootstrapContext(c) + ctx := coretesting.Context(c) // 0.x.y.z addresses are always invalid _, err := common.WaitSSH(ctx, nil, ssh.DefaultClient, "/bin/true", &neverOpensPort{addr: "0.1.2.3"}, testSSHTimeout) c.Check(err, gc.ErrorMatches, `waited for `+testSSHTimeout.Timeout.String()+` without being able to connect: mock connection failure to 0.1.2.3`) - c.Check(stderr.String(), gc.Matches, + c.Check(coretesting.Stderr(ctx), gc.Matches, "Waiting for address\n"+ "(Attempting to connect to 0.1.2.3:22\n)+") } @@ -339,14 +329,14 @@ } func (s *BootstrapSuite) TestWaitSSHKilledWaitingForDial(c *gc.C) { - ctx, stderr := bootstrapContext(c) + ctx := coretesting.Context(c) timeout := testSSHTimeout timeout.Timeout = 1 * time.Minute interrupted := make(chan os.Signal, 1) _, err := common.WaitSSH(ctx, interrupted, ssh.DefaultClient, "", &interruptOnDial{name: "0.1.2.3", interrupted: interrupted}, timeout) c.Check(err, gc.ErrorMatches, "interrupted") // Exact timing is imprecise but it should have tried a few times before being killed - c.Check(stderr.String(), gc.Matches, + c.Check(coretesting.Stderr(ctx), gc.Matches, "Waiting for address\n"+ "(Attempting to connect to 0.1.2.3:22\n)+") } @@ -371,7 +361,7 @@ } func (s *BootstrapSuite) TestWaitSSHRefreshAddresses(c *gc.C) { - ctx, stderr := bootstrapContext(c) + ctx := coretesting.Context(c) _, err := common.WaitSSH(ctx, nil, ssh.DefaultClient, "", &addressesChange{addrs: [][]string{ nil, nil, @@ -383,10 +373,11 @@ // Not necessarily the last one in the list, due to scheduling. c.Check(err, gc.ErrorMatches, `waited for `+testSSHTimeout.Timeout.String()+` without being able to connect: mock connection failure to 0.1.2.[34]`) - c.Check(stderr.String(), gc.Matches, + stderr := coretesting.Stderr(ctx) + c.Check(stderr, gc.Matches, "Waiting for address\n"+ "(.|\n)*(Attempting to connect to 0.1.2.3:22\n)+(.|\n)*") - c.Check(stderr.String(), gc.Matches, + c.Check(stderr, gc.Matches, "Waiting for address\n"+ "(.|\n)*(Attempting to connect to 0.1.2.4:22\n)+(.|\n)*") } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/dummy/config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/dummy/config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/dummy/config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/dummy/config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -29,7 +29,8 @@ attrs := dummy.SampleConfig().Delete("secret") cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + ctx := testing.Context(c) + env, err := environs.Prepare(cfg, ctx, configstore.NewMem()) c.Assert(err, gc.IsNil) defer env.Destroy() expected := map[string]string{ @@ -81,7 +82,8 @@ c.Assert(err, gc.ErrorMatches, test.errorMsg) continue } - env, err := environs.Prepare(cfg, configstore.NewMem()) + ctx := testing.Context(c) + env, err := environs.Prepare(cfg, ctx, configstore.NewMem()) if test.errorMsg != "" { c.Assert(err, gc.ErrorMatches, test.errorMsg) continue diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/dummy/environs.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/dummy/environs.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/dummy/environs.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/dummy/environs.go 2014-02-20 16:10:15.000000000 +0000 @@ -97,17 +97,16 @@ // Operation represents an action on the dummy provider. type Operation interface{} -type GenericOperation struct { - Env string -} - type OpBootstrap struct { Context environs.BootstrapContext Env string Constraints constraints.Value } -type OpDestroy GenericOperation +type OpDestroy struct { + Env string + Error error +} type OpStartInstance struct { Env string @@ -147,8 +146,9 @@ // environProvider represents the dummy provider. There is only ever one // instance of this type (providerInstance) type environProvider struct { - mu sync.Mutex - ops chan<- Operation + mu sync.Mutex + ops chan<- Operation + statePolicy state.Policy // We have one state for each environment name state map[int]*environState maxStateId int @@ -165,6 +165,7 @@ id int name string ops chan<- Operation + statePolicy state.Policy mu sync.Mutex maxId int // maximum instance id allocated so far. insts map[instance.Id]*dummyInstance @@ -226,6 +227,7 @@ if testing.MgoServer.Addr() != "" { testing.MgoServer.Reset() } + providerInstance.statePolicy = environs.NewStatePolicy() } func (state *environState) destroy() { @@ -263,10 +265,11 @@ // newState creates the state for a new environment with the // given name and starts an http server listening for // storage requests. -func newState(name string, ops chan<- Operation) *environState { +func newState(name string, ops chan<- Operation, policy state.Policy) *environState { s := &environState{ name: name, ops: ops, + statePolicy: policy, insts: make(map[instance.Id]*dummyInstance), globalPorts: make(map[instance.Port]bool), } @@ -288,6 +291,15 @@ go http.Serve(l, mux) } +// SetStatePolicy sets the state.Policy to use when a +// state server is initialised by dummy. +func SetStatePolicy(policy state.Policy) { + p := &providerInstance + p.mu.Lock() + defer p.mu.Unlock() + p.statePolicy = policy +} + // Listen closes the previously registered listener (if any). // Subsequent operations on any dummy environment can be received on c // (if not nil). @@ -423,7 +435,7 @@ return env, nil } -func (p *environProvider) Prepare(cfg *config.Config) (environs.Environ, error) { +func (p *environProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { cfg, err := p.prepare(cfg) if err != nil { return nil, err @@ -451,7 +463,7 @@ panic(fmt.Errorf("cannot share a state between two dummy environs; old %q; new %q", old.name, name)) } } - state := newState(name, p.ops) + state := newState(name, p.ops, p.statePolicy) p.maxStateId++ state.id = p.maxStateId p.state[state.id] = state @@ -491,6 +503,9 @@ var errBroken = errors.New("broken environment") +// Override for testing - the data directory with which the state api server is initialised. +var DataDir = "" + func (e *environ) ecfg() *environConfig { e.ecfgMutex.Lock() ecfg := e.ecfgUnlocked @@ -572,7 +587,7 @@ // so that we can call it here. info := stateInfo() - st, err := state.Initialize(info, cfg, state.DefaultDialOpts()) + st, err := state.Initialize(info, cfg, state.DefaultDialOpts(), estate.statePolicy) if err != nil { panic(err) } @@ -586,7 +601,7 @@ if err != nil { panic(err) } - estate.apiServer, err = apiserver.NewServer(st, "localhost:0", []byte(testing.ServerCert), []byte(testing.ServerKey), "") + estate.apiServer, err = apiserver.NewServer(st, "localhost:0", []byte(testing.ServerCert), []byte(testing.ServerKey), DataDir) if err != nil { panic(err) } @@ -637,11 +652,8 @@ return nil } -func (e *environ) Destroy() error { +func (e *environ) Destroy() (res error) { defer delay() - if err := e.checkBroken("Destroy"); err != nil { - return err - } estate, err := e.state() if err != nil { if err == provider.ErrDestroyed { @@ -649,6 +661,10 @@ } return err } + defer func() { estate.ops <- OpDestroy{Env: estate.name, Error: res} }() + if err := e.checkBroken("Destroy"); err != nil { + return err + } p := &providerInstance p.mu.Lock() delete(p.state, estate.id) @@ -656,7 +672,6 @@ estate.mu.Lock() defer estate.mu.Unlock() - estate.ops <- OpDestroy{Env: estate.name} estate.destroy() return nil } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/ec2/config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/ec2/config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/ec2/config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/ec2/config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -294,12 +294,13 @@ cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env0, err := providerInstance.Prepare(cfg) + ctx := testing.Context(c) + env0, err := providerInstance.Prepare(ctx, cfg) c.Assert(err, gc.IsNil) bucket0 := env0.(*environ).ecfg().controlBucket() c.Assert(bucket0, gc.Matches, "[a-f0-9]{32}") - env1, err := providerInstance.Prepare(cfg) + env1, err := providerInstance.Prepare(ctx, cfg) c.Assert(err, gc.IsNil) bucket1 := env1.(*environ).ecfg().controlBucket() c.Assert(bucket1, gc.Matches, "[a-f0-9]{32}") @@ -315,7 +316,7 @@ cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env, err := providerInstance.Prepare(cfg) + env, err := providerInstance.Prepare(testing.Context(c), cfg) c.Assert(err, gc.IsNil) bucket := env.(*environ).ecfg().controlBucket() c.Assert(bucket, gc.Equals, "burblefoo") diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/ec2/ec2.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/ec2/ec2.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/ec2/ec2.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/ec2/ec2.go 2014-02-20 16:10:15.000000000 +0000 @@ -207,7 +207,7 @@ return e, nil } -func (p environProvider) Prepare(cfg *config.Config) (environs.Environ, error) { +func (p environProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { attrs := cfg.UnknownAttrs() if _, ok := attrs["control-bucket"]; !ok { uuid, err := utils.NewUUID() @@ -306,18 +306,6 @@ return s3 } -// PrecheckInstance is specified in the environs.Prechecker interface. -func (e *environ) PrecheckInstance(series string, cons constraints.Value) error { - return nil -} - -// PrecheckContainer is specified in the environs.Prechecker interface. -func (e *environ) PrecheckContainer(series string, kind instance.ContainerType) error { - // This check can either go away or be relaxed when the ec2 - // provider manages container addressibility. - return environs.NewContainersUnsupported("ec2 provider does not support containers") -} - func (e *environ) Name() string { return e.name } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/ec2/image_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/ec2/image_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/ec2/image_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/ec2/image_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -94,7 +94,7 @@ series: "precise", arches: both, cons: "cpu-power=", - itype: "t1.micro", + itype: "m1.small", image: "ami-00000033", }, { series: "precise", diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/ec2/local_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/ec2/local_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/ec2/local_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/ec2/local_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -74,7 +74,7 @@ } cfg, err := config.New(config.NoDefaults, envAttrs) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + env, err := environs.Prepare(cfg, coretesting.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) c.Assert(env, gc.NotNil) @@ -229,25 +229,10 @@ t.srv.stopServer(c) } -func bootstrapContext(c *gc.C) environs.BootstrapContext { - return envtesting.NewBootstrapContext(coretesting.Context(c)) -} - -func (t *localServerSuite) TestPrecheck(c *gc.C) { - env := t.Prepare(c) - prechecker, ok := env.(environs.Prechecker) - c.Assert(ok, jc.IsTrue) - var cons constraints.Value - err := prechecker.PrecheckInstance("precise", cons) - c.Check(err, gc.IsNil) - err = prechecker.PrecheckContainer("precise", instance.LXC) - c.Check(err, gc.ErrorMatches, "ec2 provider does not support containers") -} - func (t *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) { env := t.Prepare(c) envtesting.UploadFakeTools(c, env.Storage()) - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) // check that the state holds the id of the bootstrap machine. @@ -334,7 +319,7 @@ func (t *localServerSuite) TestInstanceStatus(c *gc.C) { env := t.Prepare(c) envtesting.UploadFakeTools(c, env.Storage()) - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) t.srv.ec2srv.SetInitialInstanceState(ec2test.Terminated) inst, _ := testing.AssertStartInstance(c, env, "1") @@ -345,7 +330,7 @@ func (t *localServerSuite) TestStartInstanceHardwareCharacteristics(c *gc.C) { env := t.Prepare(c) envtesting.UploadFakeTools(c, env.Storage()) - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) _, hc := testing.AssertStartInstance(c, env, "1") c.Check(*hc.Arch, gc.Equals, "amd64") @@ -357,7 +342,7 @@ func (t *localServerSuite) TestAddresses(c *gc.C) { env := t.Prepare(c) envtesting.UploadFakeTools(c, env.Storage()) - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) inst, _ := testing.AssertStartInstance(c, env, "1") c.Assert(err, gc.IsNil) @@ -442,7 +427,7 @@ cfg, err := config.New(config.NoDefaults, localConfigAttrs) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + env, err := environs.Prepare(cfg, coretesting.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) t.env = env } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/joyent/config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/joyent/config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/joyent/config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/joyent/config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -286,7 +286,7 @@ c.Logf("test %d: %s", i, test.info) attrs := validAttrs().Merge(test.insert).Delete(test.remove...) testConfig := newConfig(c, attrs) - preparedConfig, err := joyent.Provider.Prepare(testConfig) + preparedConfig, err := joyent.Provider.Prepare(testing.Context(c), testConfig) if test.err == "" { c.Assert(err, gc.IsNil) attrs := preparedConfig.Config().AllAttrs() diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/joyent/provider.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/joyent/provider.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/joyent/provider.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/joyent/provider.go 2014-02-20 16:10:15.000000000 +0000 @@ -30,7 +30,7 @@ var errNotImplemented = errors.New("not implemented in Joyent provider") -func (environProvider) Prepare(cfg *config.Config) (environs.Environ, error) { +func (environProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { // This method may be called with an incomplete cfg. It should make every // reasonable effort to create a valid configuration based on the supplied, // and open the resulting environment. diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/config.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/config.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/config.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/config.go 2014-02-20 16:10:15.000000000 +0000 @@ -19,26 +19,24 @@ var ( configFields = schema.Fields{ - "root-dir": schema.String(), - "bootstrap-ip": schema.String(), - "network-bridge": schema.String(), - "container": schema.String(), - "storage-port": schema.ForceInt(), - "shared-storage-port": schema.ForceInt(), - "namespace": schema.String(), + "root-dir": schema.String(), + "bootstrap-ip": schema.String(), + "network-bridge": schema.String(), + "container": schema.String(), + "storage-port": schema.ForceInt(), + "namespace": schema.String(), } // The port defaults below are not entirely arbitrary. Local user web // frameworks often use 8000 or 8080, so I didn't want to use either of // these, but did want the familiarity of using something in the 8000 // range. configDefaults = schema.Defaults{ - "root-dir": "", - "network-bridge": "lxcbr0", - "container": string(instance.LXC), - "bootstrap-ip": schema.Omit, - "storage-port": 8040, - "shared-storage-port": 8041, - "namespace": "", + "root-dir": "", + "network-bridge": "lxcbr0", + "container": string(instance.LXC), + "bootstrap-ip": schema.Omit, + "storage-port": 8040, + "namespace": "", } ) @@ -73,10 +71,6 @@ return c.attrs["network-bridge"].(string) } -func (c *environConfig) sharedStorageDir() string { - return filepath.Join(c.rootDir(), "shared-storage") -} - func (c *environConfig) storageDir() string { return filepath.Join(c.rootDir(), "storage") } @@ -89,43 +83,28 @@ return filepath.Join(c.rootDir(), "log") } -// A config is bootstrapped if the bootstrap-ip address has been set. -func (c *environConfig) bootstrapped() bool { - _, found := c.attrs["bootstrap-ip"] - return found -} - +// bootstrapIPAddress returns the IP address of the bootstrap machine. +// As of 1.18 this is only set inside the environment, and not in the +// .jenv file. func (c *environConfig) bootstrapIPAddress() string { - addr, found := c.attrs["bootstrap-ip"] - if found { - return addr.(string) - } - return "" + addr, _ := c.attrs["bootstrap-ip"].(string) + return addr } func (c *environConfig) storagePort() int { return c.attrs["storage-port"].(int) } -func (c *environConfig) sharedStoragePort() int { - return c.attrs["shared-storage-port"].(int) -} - func (c *environConfig) storageAddr() string { return fmt.Sprintf("%s:%d", c.bootstrapIPAddress(), c.storagePort()) } -func (c *environConfig) sharedStorageAddr() string { - return fmt.Sprintf("%s:%d", c.bootstrapIPAddress(), c.sharedStoragePort()) -} - func (c *environConfig) configFile(filename string) string { return filepath.Join(c.rootDir(), filename) } func (c *environConfig) createDirs() error { for _, dirname := range []string{ - c.sharedStorageDir(), c.storageDir(), c.mongoDir(), c.logDir(), diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,13 +4,11 @@ package local_test import ( - "os" "path/filepath" - "runtime" - "syscall" gc "launchpad.net/gocheck" + "launchpad.net/juju-core/constraints" "launchpad.net/juju-core/environs/config" "launchpad.net/juju-core/juju/osenv" "launchpad.net/juju-core/provider" @@ -24,10 +22,6 @@ var _ = gc.Suite(&configSuite{}) -func (s *configSuite) SetUpTest(c *gc.C) { - s.baseProviderSuite.SetUpTest(c) -} - func minimalConfigValues() map[string]interface{} { return testing.FakeConfig().Merge(testing.Attrs{ "name": "test", @@ -114,64 +108,8 @@ func (s *configSuite) TestBootstrapAsRoot(c *gc.C) { restore := local.SetRootCheckFunction(func() bool { return true }) defer restore() - _, err := local.Provider.Prepare(minimalConfig(c)) - c.Assert(err, gc.ErrorMatches, "bootstrapping a local environment must not be done as root") -} - -type configRootSuite struct { - baseProviderSuite -} - -var _ = gc.Suite(&configRootSuite{}) - -func (s *configRootSuite) SetUpTest(c *gc.C) { - s.baseProviderSuite.SetUpTest(c) - // Skip if not linux - if runtime.GOOS != "linux" { - c.Skip("not running linux") - } - // Skip if not running as root. - if os.Getuid() != 0 { - c.Skip("not running as root") - } -} - -func (s *configRootSuite) TestCreateDirsNoUserJustRoot(c *gc.C) { - defer os.Setenv("SUDO_UID", os.Getenv("SUDO_UID")) - defer os.Setenv("SUDO_GID", os.Getenv("SUDO_GID")) - - os.Setenv("SUDO_UID", "") - os.Setenv("SUDO_GID", "") - - testConfig := minimalConfig(c) - err := local.CreateDirs(c, testConfig) - c.Assert(err, gc.IsNil) - // Check that the dirs are owned by root. - for _, dir := range local.CheckDirs(c, testConfig) { - info, err := os.Stat(dir) - c.Assert(err, gc.IsNil) - // This call is linux specific, but then so is sudo - c.Assert(info.Sys().(*syscall.Stat_t).Uid, gc.Equals, uint32(0)) - c.Assert(info.Sys().(*syscall.Stat_t).Gid, gc.Equals, uint32(0)) - } -} - -func (s *configRootSuite) TestCreateDirsAsUser(c *gc.C) { - defer os.Setenv("SUDO_UID", os.Getenv("SUDO_UID")) - defer os.Setenv("SUDO_GID", os.Getenv("SUDO_GID")) - - os.Setenv("SUDO_UID", "1000") - os.Setenv("SUDO_GID", "1000") - - testConfig := minimalConfig(c) - err := local.CreateDirs(c, testConfig) + env, err := local.Provider.Prepare(testing.Context(c), minimalConfig(c)) c.Assert(err, gc.IsNil) - // Check that the dirs are owned by the UID/GID set above.. - for _, dir := range local.CheckDirs(c, testConfig) { - info, err := os.Stat(dir) - c.Assert(err, gc.IsNil) - // This call is linux specific, but then so is sudo - c.Assert(info.Sys().(*syscall.Stat_t).Uid, gc.Equals, uint32(1000)) - c.Assert(info.Sys().(*syscall.Stat_t).Gid, gc.Equals, uint32(1000)) - } + err = env.Bootstrap(testing.Context(c), constraints.Value{}) + c.Assert(err, gc.ErrorMatches, "bootstrapping a local environment must not be done as root") } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/environ.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/environ.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/environ.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/environ.go 2014-02-20 16:10:15.000000000 +0000 @@ -9,6 +9,7 @@ "os" "os/exec" "path/filepath" + "regexp" "strings" "sync" @@ -48,12 +49,13 @@ var _ envtools.SupportsCustomSources = (*localEnviron)(nil) type localEnviron struct { - localMutex sync.Mutex - config *environConfig - name string - sharedStorageListener net.Listener - storageListener net.Listener - containerManager container.Manager + localMutex sync.Mutex + config *environConfig + name string + bridgeAddress string + localStorage storage.Storage + storageListener net.Listener + containerManager container.Manager } // GetToolsSources returns a list of sources which are used to search for simplestreams tools metadata. @@ -80,18 +82,6 @@ return fmt.Sprintf("/etc/rsyslog.d/25-juju-%s.conf", env.config.namespace()) } -// PrecheckInstance is specified in the environs.Prechecker interface. -func (*localEnviron) PrecheckInstance(series string, cons constraints.Value) error { - return nil -} - -// PrecheckContainer is specified in the environs.Prechecker interface. -func (*localEnviron) PrecheckContainer(series string, kind instance.ContainerType) error { - // This check can either go away or be relaxed when the local - // provider can do nested containers. - return environs.NewContainersUnsupported("local provider does not support nested containers") -} - func ensureNotRoot() error { if checkIfRoot() { return fmt.Errorf("bootstrapping a local environment must not be done as root") @@ -128,6 +118,23 @@ return err } + // Record the bootstrap IP, so the containers know where to go for storage. + cfg, err := env.Config().Apply(map[string]interface{}{ + "bootstrap-ip": env.bridgeAddress, + }) + if err == nil { + err = env.SetConfig(cfg) + } + if err != nil { + logger.Errorf("failed to apply bootstrap-ip to config: %v", err) + return err + } + + bootstrapJobs, err := agent.MarshalBootstrapJobs(state.JobManageEnviron) + if err != nil { + return err + } + mcfg := environs.NewBootstrapMachineConfig(stateFileURL, privateKey) mcfg.Tools = selectedTools[0] mcfg.DataDir = env.config.rootDir() @@ -138,13 +145,12 @@ mcfg.MachineAgentServiceName = env.machineAgentServiceName() mcfg.MongoServiceName = env.mongoServiceName() mcfg.AgentEnvironment = map[string]string{ - agent.Namespace: env.config.namespace(), - agent.StorageDir: env.config.storageDir(), - agent.StorageAddr: env.config.storageAddr(), - agent.SharedStorageDir: env.config.sharedStorageDir(), - agent.SharedStorageAddr: env.config.sharedStorageAddr(), + agent.Namespace: env.config.namespace(), + agent.StorageDir: env.config.storageDir(), + agent.StorageAddr: env.config.storageAddr(), + agent.BootstrapJobs: bootstrapJobs, } - if err := environs.FinishMachineConfig(mcfg, env.Config(), cons); err != nil { + if err := environs.FinishMachineConfig(mcfg, cfg, cons); err != nil { return err } // don't write proxy settings for local machine @@ -177,8 +183,8 @@ } cmd := exec.Command("sudo", "/bin/bash", "-s") cmd.Stdin = strings.NewReader(script) - cmd.Stdout = ctx.Stdout() - cmd.Stderr = ctx.Stderr() + cmd.Stdout = ctx.GetStdout() + cmd.Stderr = ctx.GetStderr() return cmd.Run() } @@ -194,22 +200,6 @@ return env.config.Config } -func createLocalStorageListener(dir, address string) (net.Listener, error) { - info, err := os.Stat(dir) - if os.IsNotExist(err) { - return nil, fmt.Errorf("storage directory %q does not exist, bootstrap first", dir) - } else if err != nil { - return nil, err - } else if !info.Mode().IsDir() { - return nil, fmt.Errorf("%q exists but is not a directory (and it needs to be)", dir) - } - storage, err := filestorage.NewFileStorageWriter(dir, filestorage.UseDefaultTmpDir) - if err != nil { - return nil, err - } - return httpstorage.Serve(address, storage) -} - // SetConfig is specified in the Environ interface. func (env *localEnviron) SetConfig(cfg *config.Config) error { ecfg, err := providerInstance.newConfig(cfg) @@ -232,19 +222,19 @@ return err } - // Here is the end of normal config setting. - if ecfg.bootstrapped() { + // When the localEnviron value is created on the client + // side, the bootstrap-ip attribute will not exist, + // because it is only set *within* the running + // environment, not in the configuration created by + // Prepare. + // + // When bootstrapIPAddress returns a non-empty string, + // we know we are running server-side and thus must use + // httpstorage. + if addr := ecfg.bootstrapIPAddress(); addr != "" { + env.bridgeAddress = addr return nil } - if err := ensureNotRoot(); err != nil { - return err - } - return env.bootstrapAddressAndStorage(cfg) -} - -// bootstrapAddressAndStorage finishes up the setup of the environment in -// situations where there is no machine agent running yet. -func (env *localEnviron) bootstrapAddressAndStorage(cfg *config.Config) error { // If we get to here, it is because we haven't yet bootstrapped an // environment, and saved the config in it, or we are running a command // from the command line, so it is ok to work on the assumption that we @@ -252,7 +242,16 @@ if err := env.config.createDirs(); err != nil { return err } + // Record the network bridge address and create a filestorage. + if err := env.resolveBridgeAddress(cfg); err != nil { + return err + } + return env.setLocalStorage() +} +// resolveBridgeAddress finishes up the setup of the environment in +// situations where there is no machine agent running yet. +func (env *localEnviron) resolveBridgeAddress(cfg *config.Config) error { // We need the provider config to get the network bridge. config, err := providerInstance.newConfig(cfg) if err != nil { @@ -263,52 +262,22 @@ bridgeAddress, err := getAddressForInterface(networkBridge) if err != nil { logger.Infof("configure a different bridge using 'network-bridge' in the config file") - return fmt.Errorf("cannot find address of network-bridge: %q", networkBridge) + return fmt.Errorf("cannot find address of network-bridge: %q: %v", networkBridge, err) } logger.Debugf("found %q as address for %q", bridgeAddress, networkBridge) - cfg, err = cfg.Apply(map[string]interface{}{ - "bootstrap-ip": bridgeAddress, - }) - if err != nil { - logger.Errorf("failed to apply new addresses to config: %v", err) - return err - } - // Now recreate the config based on the settings with the bootstrap id. - config, err = providerInstance.newConfig(cfg) - if err != nil { - logger.Errorf("failed to create new environ config: %v", err) - return err - } - env.config = config - - return env.setupLocalStorage() + env.bridgeAddress = bridgeAddress + return nil } -// setupLocalStorage looks to see if there is someone listening on the storage -// address port. If there is we assume that it is ours and all is good. If -// there is no one listening on that port, create listeners for both storage -// and the shared storage for the duration of the commands execution. -func (env *localEnviron) setupLocalStorage() error { - // Try to listen to the storageAddress. - logger.Debugf("checking %s to see if machine agent running storage listener", env.config.storageAddr()) - connection, err := net.Dial("tcp", env.config.storageAddr()) - if err != nil { - logger.Debugf("nope, start some") - // These listeners are part of the environment structure so as to remain - // referenced for the duration of the open environment. This is only for - // environs that have been created due to a user command. - env.storageListener, err = createLocalStorageListener(env.config.storageDir(), env.config.storageAddr()) - if err != nil { - return err - } - env.sharedStorageListener, err = createLocalStorageListener(env.config.sharedStorageDir(), env.config.sharedStorageAddr()) - if err != nil { - return err - } - } else { - logger.Debugf("yes, don't start local storage listeners") - connection.Close() +// setLocalStorage creates a filestorage so tools can +// be synced and so forth without having a machine agent +// running. +func (env *localEnviron) setLocalStorage() error { + storage, err := filestorage.NewFileStorageWriter(env.config.storageDir(), filestorage.UseDefaultTmpDir) + if err != nil { + return err } + env.localStorage = storage return nil } @@ -397,14 +366,13 @@ // Storage is specified in the Environ interface. func (env *localEnviron) Storage() storage.Storage { + // localStorage is non-nil if we're running from the CLI + if env.localStorage != nil { + return env.localStorage + } return httpstorage.Client(env.config.storageAddr()) } -// Implements environs.BootstrapStorager. -func (env *localEnviron) EnableBootstrapStorage() error { - return env.setupLocalStorage() -} - // Destroy is specified in the Environ interface. func (env *localEnviron) Destroy() error { // Kill all running instances. This must be done as @@ -414,7 +382,9 @@ if err != nil { return err } - args := append([]string{juju}, os.Args[1:]...) + args := []string{osenv.JujuHomeEnvKey + "=" + osenv.JujuHome()} + args = append(args, juju) + args = append(args, os.Args[1:]...) args = append(args, "-y") cmd := exec.Command("sudo", args...) cmd.Stdout = os.Stdout @@ -433,7 +403,7 @@ cmd := exec.Command( "pkill", fmt.Sprintf("-%d", terminationworker.TerminationSignal), - "jujud", + "-f", filepath.Join(regexp.QuoteMeta(env.config.rootDir()), ".*", "jujud"), ) return cmd.Run() } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/environprovider.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/environprovider.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/environprovider.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/environprovider.go 2014-02-20 16:10:15.000000000 +0000 @@ -7,6 +7,7 @@ "fmt" "net" "os" + "os/user" "syscall" "github.com/loggo/loggo" @@ -32,6 +33,8 @@ environs.RegisterProvider(provider.Local, providerInstance) } +var userCurrent = user.Current + // Open implements environs.EnvironProvider.Open. func (environProvider) Open(cfg *config.Config) (environs.Environ, error) { logger.Infof("opening environment %q", cfg.Name()) @@ -48,8 +51,16 @@ // for backwards compatibility: older versions did not store the namespace // in config. if namespace, _ := cfg.UnknownAttrs()["namespace"].(string); namespace == "" { + username := os.Getenv("USER") + if username == "" { + u, err := userCurrent() + if err != nil { + return nil, fmt.Errorf("failed to determine username for namespace: %v", err) + } + username = u.Username + } var err error - namespace = fmt.Sprintf("%s-%s", os.Getenv("USER"), cfg.Name()) + namespace = fmt.Sprintf("%s-%s", username, cfg.Name()) cfg, err = cfg.Apply(map[string]interface{}{"namespace": namespace}) if err != nil { return nil, fmt.Errorf("failed to create namespace: %v", err) @@ -72,8 +83,16 @@ return environ, nil } +var detectAptProxies = utils.DetectAptProxies + // Prepare implements environs.EnvironProvider.Prepare. -func (p environProvider) Prepare(cfg *config.Config) (environs.Environ, error) { +func (p environProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { + // The user must not set bootstrap-ip; this is determined by the provider, + // and its presence used to determine whether the environment has yet been + // bootstrapped. + if _, ok := cfg.UnknownAttrs()["bootstrap-ip"]; ok { + return nil, fmt.Errorf("bootstrap-ip must not be specified") + } err := checkLocalPort(cfg.StatePort(), "state port") if err != nil { return nil, err @@ -103,7 +122,7 @@ if cfg.AptHttpProxy() == "" && cfg.AptHttpsProxy() == "" && cfg.AptFtpProxy() == "" { - proxy, err := utils.DetectAptProxies() + proxy, err := detectAptProxies() if err != nil { return nil, err } @@ -122,7 +141,7 @@ } // checkLocalPort checks that the passed port is not used so far. -func checkLocalPort(port int, description string) error { +var checkLocalPort = func(port int, description string) error { logger.Infof("checking %s", description) // Try to connect the port on localhost. address := fmt.Sprintf("localhost:%d", port) @@ -184,11 +203,6 @@ oldLocalConfig.storagePort(), localConfig.storagePort()) } - if localConfig.sharedStoragePort() != oldLocalConfig.sharedStoragePort() { - return nil, fmt.Errorf("cannot change shared-storage-port from %v to %v", - oldLocalConfig.sharedStoragePort(), - localConfig.sharedStoragePort()) - } } // Currently only supported containers are "lxc" and "kvm". if localConfig.container() != instance.LXC && localConfig.container() != instance.KVM { @@ -224,10 +238,6 @@ # default port is used by another program. # storage-port: 8040 - # Override the shared storage port if you have multiple local providers, or if the - # default port is used by another program. - # shared-storage-port: 8041 - # Override the network bridge if you have changed the default lxc bridge # network-bridge: lxcbr0 diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/environprovider_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/environprovider_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/environprovider_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/environprovider_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,6 +4,9 @@ package local_test import ( + "errors" + "os/user" + "github.com/loggo/loggo" gc "launchpad.net/gocheck" @@ -53,10 +56,14 @@ s.PatchEnvironment("ftp_proxy", "") s.PatchEnvironment("FTP_PROXY", "") s.HookCommandOutput(&utils.AptCommandOutput, nil, nil) + s.PatchValue(local.CheckLocalPort, func(port int, desc string) error { + return nil + }) + restore := local.MockAddressForInterface() + s.AddCleanup(func(*gc.C) { restore() }) } func (s *prepareSuite) TestPrepareCapturesEnvironment(c *gc.C) { - c.Skip("fails if local provider running already") baseConfig, err := config.New(config.UseDefaults, map[string]interface{}{ "type": provider.Local, "name": "test", @@ -64,7 +71,6 @@ c.Assert(err, gc.IsNil) provider, err := environs.Provider(provider.Local) c.Assert(err, gc.IsNil) - defer local.MockAddressForInterface()() for i, test := range []struct { message string @@ -209,7 +215,7 @@ testConfig, err = baseConfig.Apply(test.extraConfig) c.Assert(err, gc.IsNil) } - env, err := provider.Prepare(testConfig) + env, err := provider.Prepare(testing.Context(c), testConfig) c.Assert(err, gc.IsNil) envConfig := env.Config() @@ -226,3 +232,50 @@ } } } + +func (s *prepareSuite) TestPrepareNamespace(c *gc.C) { + s.PatchValue(local.DetectAptProxies, func() (osenv.ProxySettings, error) { + return osenv.ProxySettings{}, nil + }) + basecfg, err := config.New(config.UseDefaults, map[string]interface{}{ + "type": "local", + "name": "test", + }) + provider, err := environs.Provider("local") + c.Assert(err, gc.IsNil) + + type test struct { + userEnv string + userOS string + userOSErr error + namespace string + err string + } + tests := []test{{ + userEnv: "someone", + userOS: "other", + namespace: "someone-test", + }, { + userOS: "other", + namespace: "other-test", + }, { + userOSErr: errors.New("oh noes"), + err: "failed to determine username for namespace: oh noes", + }} + + for i, test := range tests { + c.Logf("test %d: %v", i, test) + s.PatchEnvironment("USER", test.userEnv) + s.PatchValue(local.UserCurrent, func() (*user.User, error) { + return &user.User{Username: test.userOS}, test.userOSErr + }) + env, err := provider.Prepare(testing.Context(c), basecfg) + if test.err == "" { + c.Assert(err, gc.IsNil) + cfg := env.Config() + c.Assert(cfg.UnknownAttrs()["namespace"], gc.Equals, test.namespace) + } else { + c.Assert(err, gc.ErrorMatches, test.err) + } + } +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/environ_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/environ_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/environ_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/environ_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -11,6 +11,7 @@ gc "launchpad.net/gocheck" + "launchpad.net/juju-core/agent" coreCloudinit "launchpad.net/juju-core/cloudinit" "launchpad.net/juju-core/constraints" "launchpad.net/juju-core/environs" @@ -19,8 +20,8 @@ "launchpad.net/juju-core/environs/jujutest" envtesting "launchpad.net/juju-core/environs/testing" "launchpad.net/juju-core/environs/tools" - "launchpad.net/juju-core/instance" "launchpad.net/juju-core/provider/local" + "launchpad.net/juju-core/state" coretesting "launchpad.net/juju-core/testing" jc "launchpad.net/juju-core/testing/checkers" ) @@ -37,6 +38,11 @@ s.ToolsFixture.SetUpTest(c) } +func (s *environSuite) TearDownTest(c *gc.C) { + s.ToolsFixture.TearDownTest(c) + s.baseProviderSuite.TearDownTest(c) +} + func (*environSuite) TestOpenFailsWithProtectedDirectories(c *gc.C) { testConfig := minimalConfig(c) testConfig, err := testConfig.Apply(map[string]interface{}{ @@ -69,21 +75,6 @@ c.Assert(strings.Contains(url, "/tools"), jc.IsTrue) } -func (s *environSuite) TestPrecheck(c *gc.C) { - testConfig := minimalConfig(c) - environ, err := local.Provider.Open(testConfig) - c.Assert(err, gc.IsNil) - var cons constraints.Value - prechecker, ok := environ.(environs.Prechecker) - c.Assert(ok, jc.IsTrue) - - err = prechecker.PrecheckInstance("precise", cons) - c.Check(err, gc.IsNil) - - err = prechecker.PrecheckContainer("precise", instance.LXC) - c.Check(err, gc.ErrorMatches, "local provider does not support nested containers") -} - type localJujuTestSuite struct { baseProviderSuite jujutest.Tests @@ -101,7 +92,7 @@ c.Assert(err, gc.IsNil) s.oldPath = os.Getenv("PATH") s.testPath = c.MkDir() - os.Setenv("PATH", s.testPath+":"+s.oldPath) + s.PatchEnvPathPrepend(s.testPath) // Add in an admin secret s.Tests.TestConfig["admin-secret"] = "sekrit" @@ -115,7 +106,6 @@ func (s *localJujuTestSuite) TearDownTest(c *gc.C) { s.Tests.TearDownTest(c) - os.Setenv("PATH", s.oldPath) s.restoreRootCheck() s.baseProviderSuite.TearDownTest(c) } @@ -146,14 +136,19 @@ c.Assert(cloudcfg.AptUpdate(), jc.IsFalse) c.Assert(cloudcfg.AptUpgrade(), jc.IsFalse) c.Assert(cloudcfg.Packages(), gc.HasLen, 0) + c.Assert(mcfg.AgentEnvironment, gc.Not(gc.IsNil)) + // local does not allow machine-0 to host units + bootstrapJobs, err := agent.UnmarshalBootstrapJobs(mcfg.AgentEnvironment[agent.BootstrapJobs]) + c.Assert(err, gc.IsNil) + c.Assert(bootstrapJobs, gc.DeepEquals, []state.MachineJob{state.JobManageEnviron}) return nil }) testConfig := minimalConfig(c) - environ, err := local.Provider.Prepare(testConfig) + ctx := coretesting.Context(c) + environ, err := local.Provider.Prepare(ctx, testConfig) c.Assert(err, gc.IsNil) envtesting.UploadFakeTools(c, environ.Storage()) defer environ.Storage().RemoveAll() - ctx := envtesting.NewBootstrapContext(coretesting.Context(c)) err = environ.Bootstrap(ctx, constraints.Value{}) c.Assert(err, gc.IsNil) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/export_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/export_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/export_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/export_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -6,12 +6,15 @@ gc "launchpad.net/gocheck" "launchpad.net/juju-core/environs/config" - "launchpad.net/juju-core/utils" + "launchpad.net/juju-core/testing/testbase" ) var ( - Provider = providerInstance - FinishBootstrap = &finishBootstrap + Provider = providerInstance + FinishBootstrap = &finishBootstrap + CheckLocalPort = &checkLocalPort + DetectAptProxies = &detectAptProxies + UserCurrent = &userCurrent ) // SetRootCheckFunction allows tests to override the check for a root user. @@ -42,7 +45,6 @@ c.Assert(err, gc.IsNil) return []string{ localConfig.rootDir(), - localConfig.sharedStorageDir(), localConfig.storageDir(), localConfig.mongoDir(), } @@ -51,13 +53,8 @@ // MockAddressForInterface replaces the getAddressForInterface with a function // that returns a constant localhost ip address. func MockAddressForInterface() func() { - getAddressForInterface = func(name string) (string, error) { + return testbase.PatchValue(&getAddressForInterface, func(name string) (string, error) { logger.Debugf("getAddressForInterface called for %s", name) return "127.0.0.1", nil - } - return func() { - getAddressForInterface = utils.GetAddressForInterface - } + }) } - -var CheckLocalPort = checkLocalPort diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/instance.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/instance.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/instance.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/instance.go 2014-02-20 16:10:15.000000000 +0000 @@ -41,7 +41,7 @@ }, { NetworkScope: instance.NetworkCloudLocal, Type: instance.Ipv4Address, - Value: inst.env.config.bootstrapIPAddress(), + Value: inst.env.bridgeAddress, }} return addrs, nil } @@ -51,7 +51,7 @@ // DNSName implements instance.Instance.DNSName. func (inst *localInstance) DNSName() (string, error) { if inst.id == bootstrapInstanceId { - return inst.env.config.bootstrapIPAddress(), nil + return inst.env.bridgeAddress, nil } // Get the IPv4 address from eth0 return getAddressForInterface("eth0") diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/local_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/local_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/local/local_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/local/local_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -6,7 +6,6 @@ import ( "fmt" "net" - stdtesting "testing" gc "launchpad.net/gocheck" @@ -34,15 +33,17 @@ } func (*localSuite) TestCheckLocalPort(c *gc.C) { - // Block a ports - addr := fmt.Sprintf(":%d", 65501) - ln, err := net.Listen("tcp", addr) + // Listen on a random port. + ln, err := net.Listen("tcp", ":0") c.Assert(err, gc.IsNil) defer ln.Close() + port := ln.Addr().(*net.TCPAddr).Port - err = local.CheckLocalPort(65501, "test port") - c.Assert(err, gc.ErrorMatches, "cannot use 65501 as test port, already in use") + checkLocalPort := *local.CheckLocalPort + err = checkLocalPort(port, "test port") + c.Assert(err, gc.ErrorMatches, fmt.Sprintf("cannot use %d as test port, already in use", port)) - err = local.CheckLocalPort(65502, "another test port") + ln.Close() + err = checkLocalPort(port, "test port, no longer in use") c.Assert(err, gc.IsNil) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/maas/environprovider.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/maas/environprovider.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/maas/environprovider.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/maas/environprovider.go 2014-02-20 16:10:15.000000000 +0000 @@ -41,7 +41,7 @@ var errAgentNameAlreadySet = errors.New( "maas-agent-name is already set; this should not be set by hand") -func (p maasEnvironProvider) Prepare(cfg *config.Config) (environs.Environ, error) { +func (p maasEnvironProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { attrs := cfg.UnknownAttrs() oldName, found := attrs["maas-agent-name"] if found && oldName != "" { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/maas/environprovider_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/maas/environprovider_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/maas/environprovider_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/maas/environprovider_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -52,7 +52,8 @@ config, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - environ, err := suite.makeEnviron().Provider().Prepare(config) + ctx := testing.Context(c) + environ, err := suite.makeEnviron().Provider().Prepare(ctx, config) c.Assert(err, gc.IsNil) preparedConfig := environ.Config() @@ -76,7 +77,8 @@ config, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - _, err = suite.makeEnviron().Provider().Prepare(config) + ctx := testing.Context(c) + _, err = suite.makeEnviron().Provider().Prepare(ctx, config) c.Assert(err, gc.Equals, errAgentNameAlreadySet) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/maas/environ_whitebox_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/maas/environ_whitebox_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/maas/environ_whitebox_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/maas/environ_whitebox_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -149,16 +149,12 @@ return utils.Gunzip(data) } -func bootstrapContext(c *gc.C) environs.BootstrapContext { - return envtesting.NewBootstrapContext(coretesting.Context(c)) -} - func (suite *environSuite) TestStartInstanceStartsInstance(c *gc.C) { suite.setupFakeTools(c) env := suite.makeEnviron() // Create node 0: it will be used as the bootstrap node. suite.testMAASObject.TestServer.NewNode(`{"system_id": "node0", "hostname": "host0"}`) - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) // The bootstrap node has been acquired and started. operations := suite.testMAASObject.TestServer.NodeOperations() @@ -372,7 +368,7 @@ suite.setupFakeTools(c) env := suite.makeEnviron() suite.testMAASObject.TestServer.NewNode(`{"system_id": "thenode", "hostname": "host"}`) - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) } @@ -388,14 +384,14 @@ c.Assert(err, gc.IsNil) err = env.SetConfig(cfg) c.Assert(err, gc.IsNil) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Check(err, gc.ErrorMatches, "cannot find bootstrap tools.*") } func (suite *environSuite) TestBootstrapFailsIfNoNodes(c *gc.C) { suite.setupFakeTools(c) env := suite.makeEnviron() - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) // Since there are no nodes, the attempt to allocate one returns a // 409: Conflict. c.Check(err, gc.ErrorMatches, ".*409.*") @@ -407,7 +403,7 @@ suite.testMAASObject.TestServer.NewNode(`{"system_id": "bootstrapnode", "hostname": "host"}`) // bootstrap.Bootstrap calls Environ.Bootstrap. This works. - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/config.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/config.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/config.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/config.go 2014-02-20 16:10:15.000000000 +0000 @@ -19,11 +19,13 @@ "storage-listen-ip": schema.String(), "storage-port": schema.Int(), "storage-auth-key": schema.String(), + "use-sshstorage": schema.Bool(), } configDefaults = schema.Defaults{ "bootstrap-user": "", "storage-listen-ip": "", "storage-port": defaultStoragePort, + "use-sshstorage": true, } ) @@ -36,6 +38,14 @@ return &environConfig{Config: config, attrs: attrs} } +func (c *environConfig) useSSHStorage() bool { + // Prior to 1.17.3, the use-sshstorage attribute + // did not exist. We take non-existence to be + // equivalent to false. + useSSHStorage, _ := c.attrs["use-sshstorage"].(bool) + return useSSHStorage +} + func (c *environConfig) bootstrapHost() string { return c.attrs["bootstrap-host"].(string) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -11,6 +11,7 @@ "launchpad.net/juju-core/environs/config" coretesting "launchpad.net/juju-core/testing" + jc "launchpad.net/juju-core/testing/checkers" "launchpad.net/juju-core/testing/testbase" ) @@ -26,6 +27,9 @@ "type": "manual", "bootstrap-host": "hostname", "storage-auth-key": "whatever", + // Not strictly necessary, but simplifies testing by disabling + // ssh storage by default. + "use-sshstorage": false, // While the ca-cert bits aren't entirely minimal, they avoid the need // to set up a fake home. "ca-cert": coretesting.CACert, @@ -120,3 +124,16 @@ c.Assert(testConfig.storageAddr(), gc.Equals, "hostname:1234") c.Assert(testConfig.storageListenAddr(), gc.Equals, "10.0.0.123:1234") } + +func (s *configSuite) TestStorageCompat(c *gc.C) { + // Older environment configurations will not have the + // use-sshstorage attribute. We treat them as if they + // have use-sshstorage=false. + values := MinimalConfigValues() + delete(values, "use-sshstorage") + cfg, err := config.New(config.UseDefaults, values) + c.Assert(err, gc.IsNil) + envConfig := newEnvironConfig(cfg, values) + c.Assert(err, gc.IsNil) + c.Assert(envConfig.useSSHStorage(), jc.IsFalse) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/environ.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/environ.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/environ.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/environ.go 2014-02-20 16:10:15.000000000 +0000 @@ -51,15 +51,13 @@ var logger = loggo.GetLogger("juju.provider.manual") type manualEnviron struct { - cfg *environConfig - cfgmutex sync.Mutex - bootstrapStorage storage.Storage - bootstrapStorageMutex sync.Mutex - ubuntuUserInited bool - ubuntuUserInitMutex sync.Mutex + cfg *environConfig + cfgmutex sync.Mutex + storage storage.Storage + ubuntuUserInited bool + ubuntuUserInitMutex sync.Mutex } -var _ environs.BootstrapStorager = (*manualEnviron)(nil) var _ envtools.SupportsCustomSources = (*manualEnviron)(nil) var errNoStartInstance = errors.New("manual provider cannot start instances") @@ -92,27 +90,13 @@ return e.envConfig().Name() } -var initUbuntuUser = manual.InitUbuntuUser - -func (e *manualEnviron) ensureBootstrapUbuntuUser(ctx environs.BootstrapContext) error { - e.ubuntuUserInitMutex.Lock() - defer e.ubuntuUserInitMutex.Unlock() - if e.ubuntuUserInited { - return nil - } - cfg := e.envConfig() - err := initUbuntuUser(cfg.bootstrapHost(), cfg.bootstrapUser(), cfg.AuthorizedKeys(), ctx.Stdin(), ctx.Stdout()) +func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) error { + // Set "use-sshstorage" to false, so agents know not to use sshstorage. + cfg, err := e.Config().Apply(map[string]interface{}{"use-sshstorage": false}) if err != nil { - logger.Errorf("initializing ubuntu user: %v", err) return err } - logger.Infof("initialized ubuntu user") - e.ubuntuUserInited = true - return nil -} - -func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) error { - if err := e.ensureBootstrapUbuntuUser(ctx); err != nil { + if err := e.SetConfig(cfg); err != nil { return err } envConfig := e.envConfig() @@ -147,6 +131,35 @@ if err != nil { return err } + // Set storage. If "use-sshstorage" is true then use the SSH storage. + // Otherwise, use HTTP storage. + // + // We don't change storage once it's been set. Storage parameters + // are fixed at bootstrap time, and it is not possible to change + // them. + if e.storage == nil { + var stor storage.Storage + if envConfig.useSSHStorage() { + storageDir := e.StorageDir() + storageTmpdir := path.Join(dataDir, storageTmpSubdir) + stor, err = newSSHStorage("ubuntu@"+e.cfg.bootstrapHost(), storageDir, storageTmpdir) + if err != nil { + return fmt.Errorf("initialising SSH storage failed: %v", err) + } + } else { + caCertPEM, ok := envConfig.CACert() + if !ok { + // should not be possible to validate base config + return fmt.Errorf("ca-cert not set") + } + authkey := envConfig.storageAuthKey() + stor, err = httpstorage.ClientTLS(envConfig.storageAddr(), caCertPEM, authkey) + if err != nil { + return fmt.Errorf("initialising HTTPS storage failed: %v", err) + } + } + e.storage = stor + } e.cfg = envConfig return nil } @@ -182,27 +195,6 @@ }) } -// Implements environs.BootstrapStorager. -func (e *manualEnviron) EnableBootstrapStorage(ctx environs.BootstrapContext) error { - e.bootstrapStorageMutex.Lock() - defer e.bootstrapStorageMutex.Unlock() - if e.bootstrapStorage != nil { - return nil - } - if err := e.ensureBootstrapUbuntuUser(ctx); err != nil { - return err - } - cfg := e.envConfig() - storageDir := e.StorageDir() - storageTmpdir := path.Join(dataDir, storageTmpSubdir) - bootstrapStorage, err := newSSHStorage("ubuntu@"+cfg.bootstrapHost(), storageDir, storageTmpdir) - if err != nil { - return err - } - e.bootstrapStorage = bootstrapStorage - return nil -} - // GetToolsSources returns a list of sources which are // used to search for simplestreams tools metadata. func (e *manualEnviron) GetToolsSources() ([]simplestreams.DataSource, error) { @@ -213,24 +205,9 @@ } func (e *manualEnviron) Storage() storage.Storage { - e.bootstrapStorageMutex.Lock() - defer e.bootstrapStorageMutex.Unlock() - if e.bootstrapStorage != nil { - return e.bootstrapStorage - } - caCertPEM, authkey := e.StorageCACert(), e.StorageAuthKey() - if caCertPEM != nil && authkey != "" { - storage, err := httpstorage.ClientTLS(e.envConfig().storageAddr(), caCertPEM, authkey) - if err != nil { - // Should be impossible, since ca-cert will always be validated. - logger.Errorf("initialising HTTPS storage failed: %v", err) - } else { - return storage - } - } else { - logger.Errorf("missing CA cert or auth-key") - } - return nil + e.cfgmutex.Lock() + defer e.cfgmutex.Unlock() + return e.storage } var runSSHCommand = func(host string, command []string) (stderr string, err error) { @@ -254,6 +231,10 @@ return err } +func (*manualEnviron) PrecheckInstance(series string, cons constraints.Value) error { + return errors.New(`use "juju add-machine ssh:[user@]" to provision machines`) +} + func (e *manualEnviron) OpenPorts(ports []instance.Port) error { return nil } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/environ_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/environ_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/environ_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/environ_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -5,7 +5,6 @@ import ( "errors" - "io" "strings" gc "launchpad.net/gocheck" @@ -13,10 +12,8 @@ "launchpad.net/juju-core/environs" "launchpad.net/juju-core/environs/manual" "launchpad.net/juju-core/environs/storage" - envtesting "launchpad.net/juju-core/environs/testing" "launchpad.net/juju-core/environs/tools" "launchpad.net/juju-core/instance" - coretesting "launchpad.net/juju-core/testing" jc "launchpad.net/juju-core/testing/checkers" "launchpad.net/juju-core/testing/testbase" ) @@ -26,11 +23,16 @@ env *manualEnviron } +type dummyStorage struct { + storage.Storage +} + var _ = gc.Suite(&environSuite{}) func (s *environSuite) SetUpTest(c *gc.C) { - envConfig := getEnvironConfig(c, MinimalConfigValues()) - s.env = &manualEnviron{cfg: envConfig} + env, err := manualProvider{}.Open(MinimalConfig(c)) + c.Assert(err, gc.IsNil) + s.env = env.(*manualEnviron) } func (s *environSuite) TestSetConfig(c *gc.C) { @@ -127,40 +129,3 @@ c.Assert(err, gc.IsNil) c.Assert(strings.Contains(url, "/tools"), jc.IsTrue) } - -type dummyStorage struct { - storage.Storage -} - -func (s *environSuite) TestEnvironBootstrapStorager(c *gc.C) { - var newSSHStorageResult = struct { - stor storage.Storage - err error - }{dummyStorage{}, errors.New("failed to get SSH storage")} - s.PatchValue(&newSSHStorage, func(sshHost, storageDir, storageTmpdir string) (storage.Storage, error) { - return newSSHStorageResult.stor, newSSHStorageResult.err - }) - - var initUbuntuResult error - s.PatchValue(&initUbuntuUser, func(host, user, authorizedKeys string, stdin io.Reader, stdout io.Writer) error { - return initUbuntuResult - }) - - ctx := envtesting.NewBootstrapContext(coretesting.Context(c)) - initUbuntuResult = errors.New("failed to initialise ubuntu user") - c.Assert(s.env.EnableBootstrapStorage(ctx), gc.Equals, initUbuntuResult) - initUbuntuResult = nil - c.Assert(s.env.EnableBootstrapStorage(ctx), gc.Equals, newSSHStorageResult.err) - // after the user is initialised once successfully, - // another attempt will not be made. - initUbuntuResult = errors.New("failed to initialise ubuntu user") - c.Assert(s.env.EnableBootstrapStorage(ctx), gc.Equals, newSSHStorageResult.err) - - // after the bootstrap storage is initialised once successfully, - // another attempt will not be made. - backup := newSSHStorageResult.err - newSSHStorageResult.err = nil - c.Assert(s.env.EnableBootstrapStorage(ctx), gc.IsNil) - newSSHStorageResult.err = backup - c.Assert(s.env.EnableBootstrapStorage(ctx), gc.IsNil) -} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/export_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/export_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/export_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/export_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -5,4 +5,6 @@ var ( ProviderInstance = manualProvider{} + NewSSHStorage = &newSSHStorage + InitUbuntuUser = &initUbuntuUser ) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/provider.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/provider.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/provider.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/provider.go 2014-02-20 16:10:15.000000000 +0000 @@ -9,6 +9,7 @@ "launchpad.net/juju-core/environs" "launchpad.net/juju-core/environs/config" + "launchpad.net/juju-core/environs/manual" "launchpad.net/juju-core/utils" ) @@ -16,14 +17,25 @@ func init() { p := manualProvider{} - environs.RegisterProvider("null", p) - environs.RegisterProvider("manual", p) + environs.RegisterProvider("manual", p, "null") } var errNoBootstrapHost = errors.New("bootstrap-host must be specified") -func (p manualProvider) Prepare(cfg *config.Config) (environs.Environ, error) { - if _, ok := cfg.UnknownAttrs()["storage-auth-key"].(string); !ok { +var initUbuntuUser = manual.InitUbuntuUser + +func ensureBootstrapUbuntuUser(ctx environs.BootstrapContext, cfg *environConfig) error { + err := initUbuntuUser(cfg.bootstrapHost(), cfg.bootstrapUser(), cfg.AuthorizedKeys(), ctx.GetStdin(), ctx.GetStdout()) + if err != nil { + logger.Errorf("initializing ubuntu user: %v", err) + return err + } + logger.Infof("initialized ubuntu user") + return nil +} + +func (p manualProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { + if _, ok := cfg.UnknownAttrs()["storage-auth-key"]; !ok { uuid, err := utils.NewUUID() if err != nil { return nil, err @@ -35,7 +47,17 @@ return nil, err } } - return p.Open(cfg) + if use, ok := cfg.UnknownAttrs()["use-sshstorage"].(bool); ok && !use { + return nil, fmt.Errorf("use-sshstorage must not be specified") + } + envConfig, err := p.validate(cfg, nil) + if err != nil { + return nil, err + } + if err := ensureBootstrapUbuntuUser(ctx, envConfig); err != nil { + return nil, err + } + return p.open(envConfig) } func (p manualProvider) Open(cfg *config.Config) (environs.Environ, error) { @@ -47,7 +69,12 @@ } func (p manualProvider) open(cfg *environConfig) (environs.Environ, error) { - return &manualEnviron{cfg: cfg}, nil + env := &manualEnviron{cfg: cfg} + // Need to call SetConfig to initialise storage. + if err := env.SetConfig(cfg.Config); err != nil { + return nil, err + } + return env, nil } func checkImmutableString(cfg, old *environConfig, key string) error { @@ -89,6 +116,10 @@ if oldPort != newPort { return nil, fmt.Errorf("cannot change storage-port from %q to %q", oldPort, newPort) } + oldUseSSHStorage, newUseSSHStorage := oldEnvConfig.useSSHStorage(), envConfig.useSSHStorage() + if oldUseSSHStorage != newUseSSHStorage && newUseSSHStorage == true { + return nil, fmt.Errorf("cannot change use-sshstorage from %v to %v", oldUseSSHStorage, newUseSSHStorage) + } } return envConfig, nil } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/provider_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/provider_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/provider_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/provider_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,10 +4,16 @@ package manual_test import ( + "fmt" + "io" + gc "launchpad.net/gocheck" + "launchpad.net/juju-core/environs" "launchpad.net/juju-core/environs/config" + "launchpad.net/juju-core/environs/storage" "launchpad.net/juju-core/provider/manual" + coretesting "launchpad.net/juju-core/testing" jc "launchpad.net/juju-core/testing/checkers" "launchpad.net/juju-core/testing/testbase" "launchpad.net/juju-core/utils" @@ -19,14 +25,49 @@ var _ = gc.Suite(&providerSuite{}) +func (s *providerSuite) SetUpTest(c *gc.C) { + s.LoggingSuite.SetUpTest(c) + s.PatchValue(manual.InitUbuntuUser, func(host, user, keys string, stdin io.Reader, stdout io.Writer) error { + return nil + }) +} + func (s *providerSuite) TestPrepare(c *gc.C) { minimal := manual.MinimalConfigValues() + minimal["use-sshstorage"] = true delete(minimal, "storage-auth-key") testConfig, err := config.New(config.UseDefaults, minimal) c.Assert(err, gc.IsNil) - env, err := manual.ProviderInstance.Prepare(testConfig) + env, err := manual.ProviderInstance.Prepare(coretesting.Context(c), testConfig) c.Assert(err, gc.IsNil) cfg := env.Config() key, _ := cfg.UnknownAttrs()["storage-auth-key"].(string) c.Assert(key, jc.Satisfies, utils.IsValidUUIDString) } + +func (s *providerSuite) TestPrepareUseSSHStorage(c *gc.C) { + minimal := manual.MinimalConfigValues() + minimal["use-sshstorage"] = false + testConfig, err := config.New(config.UseDefaults, minimal) + c.Assert(err, gc.IsNil) + _, err = manual.ProviderInstance.Prepare(coretesting.Context(c), testConfig) + c.Assert(err, gc.ErrorMatches, "use-sshstorage must not be specified") + + s.PatchValue(manual.NewSSHStorage, func(sshHost, storageDir, storageTmpdir string) (storage.Storage, error) { + return nil, fmt.Errorf("newSSHStorage failed") + }) + minimal["use-sshstorage"] = true + testConfig, err = config.New(config.UseDefaults, minimal) + c.Assert(err, gc.IsNil) + _, err = manual.ProviderInstance.Prepare(coretesting.Context(c), testConfig) + c.Assert(err, gc.ErrorMatches, "initialising SSH storage failed: newSSHStorage failed") +} + +func (s *providerSuite) TestNullAlias(c *gc.C) { + p, err := environs.Provider("manual") + c.Assert(p, gc.NotNil) + c.Assert(err, gc.IsNil) + p, err = environs.Provider("null") + c.Assert(p, gc.NotNil) + c.Assert(err, gc.IsNil) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/suite_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/suite_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/manual/suite_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/manual/suite_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -7,8 +7,15 @@ "testing" gc "launchpad.net/gocheck" + + "launchpad.net/juju-core/environs/storage" + "launchpad.net/juju-core/provider/manual" ) func Test(t *testing.T) { + // Prevent any use of ssh for storage. + *manual.NewSSHStorage = func(sshHost, storageDir, storageTmpdir string) (storage.Storage, error) { + return nil, nil + } gc.TestingT(t) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/openstack/config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/openstack/config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/openstack/config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/openstack/config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -460,12 +460,13 @@ cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env0, err := providerInstance.Prepare(cfg) + ctx := testing.Context(c) + env0, err := providerInstance.Prepare(ctx, cfg) c.Assert(err, gc.IsNil) bucket0 := env0.(*environ).ecfg().controlBucket() c.Assert(bucket0, gc.Matches, "[a-f0-9]{32}") - env1, err := providerInstance.Prepare(cfg) + env1, err := providerInstance.Prepare(ctx, cfg) c.Assert(err, gc.IsNil) bucket1 := env1.(*environ).ecfg().controlBucket() c.Assert(bucket1, gc.Matches, "[a-f0-9]{32}") @@ -482,7 +483,7 @@ cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - env, err := providerInstance.Prepare(cfg) + env, err := providerInstance.Prepare(testing.Context(c), cfg) c.Assert(err, gc.IsNil) bucket := env.(*environ).ecfg().controlBucket() c.Assert(bucket, gc.Equals, "burblefoo") diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/openstack/live_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/openstack/live_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/openstack/live_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/openstack/live_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -10,7 +10,6 @@ "sort" gc "launchpad.net/gocheck" - "launchpad.net/goose/client" "launchpad.net/goose/identity" "launchpad.net/goose/nova" @@ -231,7 +230,7 @@ c.Assert(env, gc.NotNil) defer env.Destroy() // Bootstrap and start an instance. - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) inst, _ := jujutesting.AssertStartInstance(c, env, "100") // Check whether the instance has the default security group assigned. diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/openstack/local_test.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/openstack/local_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/openstack/local_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/openstack/local_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -252,21 +252,6 @@ s.LoggingSuite.TearDownTest(c) } -func bootstrapContext(c *gc.C) environs.BootstrapContext { - return envtesting.NewBootstrapContext(coretesting.Context(c)) -} - -func (s *localServerSuite) TestPrecheck(c *gc.C) { - var cons constraints.Value - env := s.Prepare(c) - prechecker, ok := env.(environs.Prechecker) - c.Assert(ok, jc.IsTrue) - err := prechecker.PrecheckInstance("precise", cons) - c.Check(err, gc.IsNil) - err = prechecker.PrecheckContainer("precise", instance.LXC) - c.Check(err, gc.ErrorMatches, "openstack provider does not support containers") -} - // If the bootstrap node is configured to require a public IP address, // bootstrapping fails if an address cannot be allocated. func (s *localServerSuite) TestBootstrapFailsWhenPublicIPError(c *gc.C) { @@ -285,7 +270,7 @@ c.Assert(err, gc.IsNil) env, err := environs.New(cfg) c.Assert(err, gc.IsNil) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.ErrorMatches, "(.|\n)*cannot allocate a public IP as needed(.|\n)*") } @@ -312,9 +297,9 @@ "use-floating-ip": false, })) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, s.ConfigStore) + env, err := environs.Prepare(cfg, coretesting.Context(c), s.ConfigStore) c.Assert(err, gc.IsNil) - err = bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err = bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) inst, _ := testing.AssertStartInstance(c, env, "100") err = env.StopInstances([]instance.Instance{inst}) @@ -323,7 +308,7 @@ func (s *localServerSuite) TestStartInstanceHardwareCharacteristics(c *gc.C) { env := s.Prepare(c) - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) _, hc := testing.AssertStartInstanceWithConstraints(c, env, "100", constraints.MustParse("mem=1024")) c.Check(*hc.Arch, gc.Equals, "amd64") @@ -522,7 +507,7 @@ // It should be moved to environs.jujutests.Tests. func (s *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) { env := s.Prepare(c) - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) // check that the state holds the id of the bootstrap machine. @@ -530,7 +515,7 @@ c.Assert(err, gc.IsNil) c.Assert(stateData.StateInstances, gc.HasLen, 1) - expectedHardware := instance.MustParseHardware("arch=amd64 cpu-cores=1 mem=512M") + expectedHardware := instance.MustParseHardware("arch=amd64 cpu-cores=1 mem=2G") insts, err := env.AllInstances() c.Assert(err, gc.IsNil) c.Assert(insts, gc.HasLen, 1) @@ -609,7 +594,7 @@ env := s.Open(c) // An error occurs if no suitable image is found. - _, err := openstack.FindInstanceSpec(env, "saucy", "amd64", "mem=8G") + _, err := openstack.FindInstanceSpec(env, "saucy", "amd64", "mem=1G") c.Assert(err, gc.ErrorMatches, `no "saucy" images in some-region with arches \[amd64\]`) } @@ -760,7 +745,7 @@ c.Assert(attrs["auth-url"].(string)[:8], gc.Equals, "https://") cfg, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - s.env, err = environs.Prepare(cfg, configstore.NewMem()) + s.env, err = environs.Prepare(cfg, coretesting.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) s.attrs = s.env.Config().AllAttrs() } @@ -817,7 +802,7 @@ openstack.UseTestImageData(metadataStorage, s.cred) defer openstack.RemoveTestImageData(metadataStorage) - err = bootstrap.Bootstrap(bootstrapContext(c), s.env, constraints.Value{}) + err = bootstrap.Bootstrap(coretesting.Context(c), s.env, constraints.Value{}) c.Assert(err, gc.IsNil) } @@ -960,7 +945,7 @@ func (s *localServerSuite) TestAllInstancesIgnoresOtherMachines(c *gc.C) { env := s.Prepare(c) - err := bootstrap.Bootstrap(bootstrapContext(c), env, constraints.Value{}) + err := bootstrap.Bootstrap(coretesting.Context(c), env, constraints.Value{}) c.Assert(err, gc.IsNil) // Check that we see 1 instance in the environment diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/provider/openstack/provider.go juju-core-1.17.3/src/launchpad.net/juju-core/provider/openstack/provider.go --- juju-core-1.17.2/src/launchpad.net/juju-core/provider/openstack/provider.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/provider/openstack/provider.go 2014-02-20 16:10:15.000000000 +0000 @@ -180,7 +180,7 @@ return e, nil } -func (p environProvider) Prepare(cfg *config.Config) (environs.Environ, error) { +func (p environProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { attrs := cfg.UnknownAttrs() if _, ok := attrs["control-bucket"]; !ok { uuid, err := utils.NewUUID() @@ -483,18 +483,6 @@ return nova } -// PrecheckInstance is specified in the environs.Prechecker interface. -func (*environ) PrecheckInstance(series string, cons constraints.Value) error { - return nil -} - -// PrecheckContainer is specified in the environs.Prechecker interface. -func (*environ) PrecheckContainer(series string, kind instance.ContainerType) error { - // This check can either go away or be relaxed when the openstack - // provider manages container addressibility. - return environs.NewContainersUnsupported("openstack provider does not support containers") -} - func (e *environ) Name() string { return e.name } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/replicaset/replicaset_test.go juju-core-1.17.3/src/launchpad.net/juju-core/replicaset/replicaset_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/replicaset/replicaset_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/replicaset/replicaset_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -5,9 +5,8 @@ "testing" "time" - gc "launchpad.net/gocheck" - "labix.org/v2/mgo" + gc "launchpad.net/gocheck" coretesting "launchpad.net/juju-core/testing" "launchpad.net/juju-core/utils" @@ -348,10 +347,14 @@ for attempt.Next() { var err error res, err = CurrentStatus(session) - - if err != nil && !attempt.HasNext() { - c.Errorf("Couldn't get status before timeout, got err: %v", err) - return + if err != nil { + if !attempt.HasNext() { + c.Errorf("Couldn't get status before timeout, got err: %v", err) + return + } else { + // try again + continue + } } if res.Members[0].State == PrimaryState && diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/scripts/win-installer/setup.iss juju-core-1.17.3/src/launchpad.net/juju-core/scripts/win-installer/setup.iss --- juju-core-1.17.2/src/launchpad.net/juju-core/scripts/win-installer/setup.iss 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/scripts/win-installer/setup.iss 2014-02-20 16:10:15.000000000 +0000 @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "Juju" -#define MyAppVersion "1.17.2" +#define MyAppVersion "1.17.3" #define MyAppPublisher "Canonical, Ltd" #define MyAppURL "http://juju.ubuntu.com/" #define MyAppExeName "juju.exe" diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/addmachine.go juju-core-1.17.3/src/launchpad.net/juju-core/state/addmachine.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/addmachine.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/addmachine.go 2014-02-20 16:10:15.000000000 +0000 @@ -218,6 +218,11 @@ if err != nil { return nil, nil, err } + if template.InstanceId == "" { + if err := st.precheckInstance(template.Series, template.Constraints); err != nil { + return nil, nil, err + } + } seq, err := st.sequence("machine") if err != nil { return nil, nil, err @@ -331,6 +336,15 @@ if err != nil { return nil, nil, err } + if containerType == "" { + return nil, nil, fmt.Errorf("no container type specified") + } + if parentTemplate.InstanceId == "" { + if err := st.precheckInstance(parentTemplate.Series, parentTemplate.Constraints); err != nil { + return nil, nil, err + } + } + parentDoc := machineDocForTemplate(parentTemplate, strconv.Itoa(seq)) newId, err := st.newContainerId(parentDoc.Id, containerType) if err != nil { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/api/agent/machine_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/api/agent/machine_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/api/agent/machine_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/api/agent/machine_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -9,6 +9,7 @@ gc "launchpad.net/gocheck" + "launchpad.net/juju-core/environs" "launchpad.net/juju-core/errors" "launchpad.net/juju-core/juju/testing" "launchpad.net/juju-core/state" @@ -83,7 +84,7 @@ } func tryOpenState(info *state.Info) error { - st, err := state.Open(info, state.DialOpts{}) + st, err := state.Open(info, state.DialOpts{}, environs.NewStatePolicy()) if err == nil { st.Close() } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/api/deployer/deployer_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/api/deployer/deployer_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/api/deployer/deployer_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/api/deployer/deployer_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -43,7 +43,7 @@ func (s *deployerSuite) SetUpTest(c *gc.C) { s.JujuConnSuite.SetUpTest(c) - s.stateAPI, s.machine = s.OpenAPIAsNewMachine(c, state.JobManageState, state.JobManageEnviron, state.JobHostUnits) + s.stateAPI, s.machine = s.OpenAPIAsNewMachine(c, state.JobManageEnviron, state.JobHostUnits) var err error // Create the needed services and relate them. diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/api/params/constants.go juju-core-1.17.3/src/launchpad.net/juju-core/state/api/params/constants.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/api/params/constants.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/api/params/constants.go 2014-02-20 16:10:15.000000000 +0000 @@ -21,7 +21,7 @@ JobHostUnits MachineJob = "JobHostUnits" JobManageEnviron MachineJob = "JobManageEnviron" // Deprecated in 1.18 - JobManageState MachineJob = "JobManageState" + JobManageStateDeprecated MachineJob = "JobManageState" ) // NeedsState returns true if the job requires a state connection. diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/api/params/internal.go juju-core-1.17.3/src/launchpad.net/juju-core/state/api/params/internal.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/api/params/internal.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/api/params/internal.go 2014-02-20 16:10:15.000000000 +0000 @@ -96,7 +96,7 @@ Results []StringResult } -// CharmArchiveURLResult holds a charm archive (bunle) URL, a +// CharmArchiveURLResult holds a charm archive (bundle) URL, a // DisableSSLHostnameVerification flag or an error. type CharmArchiveURLResult struct { Error *Error @@ -111,6 +111,14 @@ Results []CharmArchiveURLResult } +// EnvironmentResult holds the result of an API call returning a name and UUID +// for an environment. +type EnvironmentResult struct { + Error *Error + Name string + UUID string +} + // ResolvedModeResult holds a resolved mode or an error. type ResolvedModeResult struct { Error *Error diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/api/params/params.go juju-core-1.17.3/src/launchpad.net/juju-core/state/api/params/params.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/api/params/params.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/api/params/params.go 2014-02-20 16:10:15.000000000 +0000 @@ -11,6 +11,7 @@ "launchpad.net/juju-core/charm" "launchpad.net/juju-core/constraints" "launchpad.net/juju-core/instance" + "launchpad.net/juju-core/juju/osenv" "launchpad.net/juju-core/utils/ssh" "launchpad.net/juju-core/version" ) @@ -513,6 +514,8 @@ AuthorizedKeys string SSLHostnameVerification bool SyslogPort int + Proxy osenv.ProxySettings + AptProxy osenv.ProxySettings } // ProvisioningScriptParams contains the parameters for the diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/api/uniter/environ.go juju-core-1.17.3/src/launchpad.net/juju-core/state/api/uniter/environ.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/api/uniter/environ.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/api/uniter/environ.go 2014-02-20 16:10:15.000000000 +0000 @@ -3,30 +3,21 @@ package uniter -import ( - "launchpad.net/juju-core/state/api/params" -) - // This module implements a subset of the interface provided by // state.Environment, as needed by the uniter API. // Environment represents the state of an environment. type Environment struct { - st *State + name string + uuid string } // UUID returns the universally unique identifier of the environment. -// -// NOTE: This differs from state.Environment.UUID() by returning an -// error as well, because it needs to make an API call -// -// TODO(dimitern): 2013-09-06 bug 1221834 -// Cache this after getting it once - it's immutable. -func (e Environment) UUID() (string, error) { - var result params.StringResult - err := e.st.caller.Call("Uniter", "", "CurrentEnvironUUID", nil, &result) - if err != nil { - return "", err - } - return result.Result, nil +func (e Environment) UUID() string { + return e.uuid +} + +// Name returns the human friendly name of the environment. +func (e Environment) Name() string { + return e.name } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/api/uniter/environ_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/api/uniter/environ_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/api/uniter/environ_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/api/uniter/environ_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -5,16 +5,26 @@ import ( gc "launchpad.net/gocheck" + + "launchpad.net/juju-core/state" + "launchpad.net/juju-core/state/api/uniter" ) type environSuite struct { uniterSuite + apiEnviron *uniter.Environment + stateEnviron *state.Environment } var _ = gc.Suite(&environSuite{}) func (s *environSuite) SetUpTest(c *gc.C) { s.uniterSuite.SetUpTest(c) + var err error + s.apiEnviron, err = s.uniter.Environment() + c.Assert(err, gc.IsNil) + s.stateEnviron, err = s.State.Environment() + c.Assert(err, gc.IsNil) } func (s *environSuite) TearDownTest(c *gc.C) { @@ -22,11 +32,9 @@ } func (s *environSuite) TestUUID(c *gc.C) { - apiEnviron, err := s.uniter.Environment() - c.Assert(err, gc.IsNil) - stateEnviron, err := s.State.Environment() - c.Assert(err, gc.IsNil) - uuid, err := apiEnviron.UUID() - c.Assert(err, gc.IsNil) - c.Assert(uuid, gc.Equals, stateEnviron.UUID()) + c.Assert(s.apiEnviron.UUID(), gc.Equals, s.stateEnviron.UUID()) +} + +func (s *environSuite) TestName(c *gc.C) { + c.Assert(s.apiEnviron.Name(), gc.Equals, s.stateEnviron.Name()) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/api/uniter/uniter.go juju-core-1.17.3/src/launchpad.net/juju-core/state/api/uniter/uniter.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/api/uniter/uniter.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/api/uniter/uniter.go 2014-02-20 16:10:15.000000000 +0000 @@ -155,7 +155,38 @@ // Environment returns the environment entity. func (st *State) Environment() (*Environment, error) { - return &Environment{st}, nil + var result params.EnvironmentResult + err := st.caller.Call("Uniter", "", "CurrentEnvironment", nil, &result) + if params.IsCodeNotImplemented(err) { + // Fall back to using the 1.16 API. + return st.environment1dot16() + } + if err != nil { + return nil, err + } + if err := result.Error; err != nil { + return nil, err + } + return &Environment{ + name: result.Name, + uuid: result.UUID, + }, nil +} + +// environment1dot16 requests just the UUID of the current environment, when +// using an older API server that does not support CurrentEnvironment API call. +func (st *State) environment1dot16() (*Environment, error) { + var result params.StringResult + err := st.caller.Call("Uniter", "", "CurrentEnvironUUID", nil, &result) + if err != nil { + return nil, err + } + if err := result.Error; err != nil { + return nil, err + } + return &Environment{ + uuid: result.Result, + }, nil } // APIAddresses returns the list of addresses used to connect to the API. diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/api/upgrader/unitupgrader_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/api/upgrader/unitupgrader_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/api/upgrader/unitupgrader_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/api/upgrader/unitupgrader_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,192 @@ +// Copyright 2012, 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrader_test + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + + gc "launchpad.net/gocheck" + + "launchpad.net/juju-core/errors" + jujutesting "launchpad.net/juju-core/juju/testing" + "launchpad.net/juju-core/state" + "launchpad.net/juju-core/state/api" + "launchpad.net/juju-core/state/api/params" + "launchpad.net/juju-core/state/api/upgrader" + statetesting "launchpad.net/juju-core/state/testing" + jc "launchpad.net/juju-core/testing/checkers" + "launchpad.net/juju-core/tools" + "launchpad.net/juju-core/utils" + "launchpad.net/juju-core/version" +) + +type unitUpgraderSuite struct { + jujutesting.JujuConnSuite + + stateAPI *api.State + + // These are raw State objects. Use them for setup and assertions, but + // should never be touched by the API calls themselves + rawMachine *state.Machine + rawUnit *state.Unit + + // Tools for the assigned machine. + fakeTools *tools.Tools + + st *upgrader.State +} + +var _ = gc.Suite(&unitUpgraderSuite{}) + +func (s *unitUpgraderSuite) SetUpTest(c *gc.C) { + s.JujuConnSuite.SetUpTest(c) + + s.rawMachine, _, _, s.rawUnit = s.addMachineServiceCharmAndUnit(c, "wordpress") + password, err := utils.RandomPassword() + c.Assert(err, gc.IsNil) + err = s.rawUnit.SetPassword(password) + c.Assert(err, gc.IsNil) + s.stateAPI = s.OpenAPIAs(c, s.rawUnit.Tag(), password) + + // Set up fake downloaded tools for the assigned machine. + fakeToolsPath := filepath.Join(s.DataDir(), "tools", version.Current.String()) + err = os.MkdirAll(fakeToolsPath, 0700) + c.Assert(err, gc.IsNil) + s.fakeTools = &tools.Tools{ + Version: version.Current, + URL: "fake-url", + Size: 1234, + SHA256: "checksum", + } + toolsMetadataData, err := json.Marshal(s.fakeTools) + c.Assert(err, gc.IsNil) + err = ioutil.WriteFile(filepath.Join(fakeToolsPath, "downloaded-tools.txt"), []byte(toolsMetadataData), 0644) + c.Assert(err, gc.IsNil) + + // Create the upgrader facade. + s.st = s.stateAPI.Upgrader() + c.Assert(s.st, gc.NotNil) +} + +func (s *unitUpgraderSuite) addMachineServiceCharmAndUnit(c *gc.C, serviceName string) (*state.Machine, *state.Service, *state.Charm, *state.Unit) { + machine, err := s.State.AddMachine("quantal", state.JobHostUnits) + c.Assert(err, gc.IsNil) + charm := s.AddTestingCharm(c, serviceName) + service := s.AddTestingService(c, serviceName, charm) + unit, err := service.AddUnit() + c.Assert(err, gc.IsNil) + err = unit.AssignToMachine(machine) + c.Assert(err, gc.IsNil) + return machine, service, charm, unit +} + +func (s *unitUpgraderSuite) TestSetVersionWrongUnit(c *gc.C) { + err := s.st.SetVersion("unit-wordpress-42", version.Current) + c.Assert(err, gc.ErrorMatches, "permission denied") + c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) +} + +func (s *unitUpgraderSuite) TestSetVersionNotUnit(c *gc.C) { + err := s.st.SetVersion("foo-42", version.Current) + c.Assert(err, gc.ErrorMatches, "permission denied") + c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) +} + +func (s *unitUpgraderSuite) TestSetVersion(c *gc.C) { + cur := version.Current + agentTools, err := s.rawUnit.AgentTools() + c.Assert(err, jc.Satisfies, errors.IsNotFoundError) + c.Assert(agentTools, gc.IsNil) + err = s.st.SetVersion(s.rawUnit.Tag(), cur) + c.Assert(err, gc.IsNil) + s.rawUnit.Refresh() + agentTools, err = s.rawUnit.AgentTools() + c.Assert(err, gc.IsNil) + c.Check(agentTools.Version, gc.Equals, cur) +} + +func (s *unitUpgraderSuite) TestToolsWrongUnit(c *gc.C) { + tools, _, err := s.st.Tools("unit-wordpress-42") + c.Assert(err, gc.ErrorMatches, "permission denied") + c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) + c.Assert(tools, gc.IsNil) +} + +func (s *unitUpgraderSuite) TestToolsNotUnit(c *gc.C) { + tools, _, err := s.st.Tools("foo-42") + c.Assert(err, gc.ErrorMatches, "permission denied") + c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) + c.Assert(tools, gc.IsNil) +} + +func (s *unitUpgraderSuite) TestTools(c *gc.C) { + cur := version.Current + curTools := &tools.Tools{Version: cur, URL: ""} + curTools.Version.Minor++ + s.rawMachine.SetAgentVersion(cur) + // UnitUpgrader.Tools returns the *desired* set of tools, not the currently + // running set. We want to be upgraded to cur.Version + stateTools, _, err := s.st.Tools(s.rawUnit.Tag()) + c.Assert(err, gc.IsNil) + c.Assert(stateTools, gc.DeepEquals, s.fakeTools) +} + +func (s *unitUpgraderSuite) TestWatchAPIVersion(c *gc.C) { + w, err := s.st.WatchAPIVersion(s.rawUnit.Tag()) + c.Assert(err, gc.IsNil) + defer statetesting.AssertStop(c, w) + wc := statetesting.NewNotifyWatcherC(c, s.BackingState, w) + // Initial event + wc.AssertOneChange() + vers := version.MustParseBinary("10.20.34-quantal-amd64") + err = s.rawMachine.SetAgentVersion(vers) + c.Assert(err, gc.IsNil) + // One change noticing the new version + wc.AssertOneChange() + vers = version.MustParseBinary("10.20.35-quantal-amd64") + err = s.rawMachine.SetAgentVersion(vers) + c.Assert(err, gc.IsNil) + wc.AssertOneChange() + statetesting.AssertStop(c, w) + wc.AssertClosed() +} + +func (s *unitUpgraderSuite) TestWatchAPIVersionWrongUnit(c *gc.C) { + _, err := s.st.WatchAPIVersion("unit-wordpress-42") + c.Assert(err, gc.ErrorMatches, "permission denied") + c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) +} + +func (s *unitUpgraderSuite) TestWatchAPIVersionNotUnit(c *gc.C) { + _, err := s.st.WatchAPIVersion("foo-42") + c.Assert(err, gc.ErrorMatches, "permission denied") + c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) +} + +func (s *unitUpgraderSuite) TestDesiredVersion(c *gc.C) { + cur := version.Current + curTools := &tools.Tools{Version: cur, URL: ""} + curTools.Version.Minor++ + s.rawMachine.SetAgentVersion(cur) + // UnitUpgrader.DesiredVersion returns the *desired* set of tools, not the + // currently running set. We want to be upgraded to cur.Version + stateVersion, err := s.st.DesiredVersion(s.rawUnit.Tag()) + c.Assert(err, gc.IsNil) + c.Assert(stateVersion, gc.Equals, cur.Number) +} + +func (s *unitUpgraderSuite) TestDesiredVersionWrongUnit(c *gc.C) { + _, err := s.st.DesiredVersion("unit-wordpress-42") + c.Assert(err, gc.ErrorMatches, "permission denied") + c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) +} + +func (s *unitUpgraderSuite) TestDesiredVersionNotUnit(c *gc.C) { + _, err := s.st.DesiredVersion("foo-42") + c.Assert(err, gc.ErrorMatches, "permission denied") + c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/api/upgrader/upgrader_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/api/upgrader/upgrader_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/api/upgrader/upgrader_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/api/upgrader/upgrader_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -26,7 +26,7 @@ coretesting.MgoTestPackage(t) } -type upgraderSuite struct { +type machineUpgraderSuite struct { testing.JujuConnSuite stateAPI *api.State @@ -34,16 +34,13 @@ // These are raw State objects. Use them for setup and assertions, but // should never be touched by the API calls themselves rawMachine *state.Machine - rawCharm *state.Charm - rawService *state.Service - rawUnit *state.Unit st *upgrader.State } -var _ = gc.Suite(&upgraderSuite{}) +var _ = gc.Suite(&machineUpgraderSuite{}) -func (s *upgraderSuite) SetUpTest(c *gc.C) { +func (s *machineUpgraderSuite) SetUpTest(c *gc.C) { s.JujuConnSuite.SetUpTest(c) s.stateAPI, s.rawMachine = s.OpenAPIAsNewMachine(c) // Create the upgrader facade. @@ -53,18 +50,24 @@ // Note: This is really meant as a unit-test, this isn't a test that should // need all of the setup we have for this test suite -func (s *upgraderSuite) TestNew(c *gc.C) { +func (s *machineUpgraderSuite) TestNew(c *gc.C) { upgrader := upgrader.NewState(s.stateAPI) c.Assert(upgrader, gc.NotNil) } -func (s *upgraderSuite) TestSetVersionWrongMachine(c *gc.C) { - err := s.st.SetVersion("42", version.Current) +func (s *machineUpgraderSuite) TestSetVersionWrongMachine(c *gc.C) { + err := s.st.SetVersion("machine-42", version.Current) c.Assert(err, gc.ErrorMatches, "permission denied") c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) } -func (s *upgraderSuite) TestSetVersion(c *gc.C) { +func (s *machineUpgraderSuite) TestSetVersionNotMachine(c *gc.C) { + err := s.st.SetVersion("foo-42", version.Current) + c.Assert(err, gc.ErrorMatches, "permission denied") + c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) +} + +func (s *machineUpgraderSuite) TestSetVersion(c *gc.C) { cur := version.Current agentTools, err := s.rawMachine.AgentTools() c.Assert(err, jc.Satisfies, errors.IsNotFoundError) @@ -77,14 +80,21 @@ c.Check(agentTools.Version, gc.Equals, cur) } -func (s *upgraderSuite) TestToolsWrongMachine(c *gc.C) { - tools, _, err := s.st.Tools("42") +func (s *machineUpgraderSuite) TestToolsWrongMachine(c *gc.C) { + tools, _, err := s.st.Tools("machine-42") + c.Assert(err, gc.ErrorMatches, "permission denied") + c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) + c.Assert(tools, gc.IsNil) +} + +func (s *machineUpgraderSuite) TestToolsNotMachine(c *gc.C) { + tools, _, err := s.st.Tools("foo-42") c.Assert(err, gc.ErrorMatches, "permission denied") c.Assert(err, jc.Satisfies, params.IsCodeUnauthorized) c.Assert(tools, gc.IsNil) } -func (s *upgraderSuite) TestTools(c *gc.C) { +func (s *machineUpgraderSuite) TestTools(c *gc.C) { cur := version.Current curTools := &tools.Tools{Version: cur, URL: ""} curTools.Version.Minor++ @@ -106,7 +116,7 @@ c.Assert(disableSSLHostnameVerification, jc.IsTrue) } -func (s *upgraderSuite) TestWatchAPIVersion(c *gc.C) { +func (s *machineUpgraderSuite) TestWatchAPIVersion(c *gc.C) { w, err := s.st.WatchAPIVersion(s.rawMachine.Tag()) c.Assert(err, gc.IsNil) defer statetesting.AssertStop(c, w) @@ -130,7 +140,7 @@ wc.AssertClosed() } -func (s *upgraderSuite) TestDesiredVersion(c *gc.C) { +func (s *machineUpgraderSuite) TestDesiredVersion(c *gc.C) { cur := version.Current curTools := &tools.Tools{Version: cur, URL: ""} curTools.Version.Minor++ diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/apiserver.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/apiserver.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/apiserver.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/apiserver.go 2014-02-20 16:10:15.000000000 +0000 @@ -132,7 +132,7 @@ } func (n *requestNotifier) leave() { - logger.Infof("[%X] API connection terminated after %v", n.id, time.Since(n.start)) + logger.Infof("[%X] %s API connection terminated after %v", n.id, n.tag(), time.Since(n.start)) } func (n requestNotifier) ClientRequest(hdr *rpc.Header, body interface{}) { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/charms.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/charms.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/charms.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/charms.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,6 +4,7 @@ package apiserver import ( + "archive/zip" "crypto/sha256" "encoding/base64" "encoding/hex" @@ -17,6 +18,8 @@ "path/filepath" "strings" + "github.com/errgo/errgo" + "launchpad.net/juju-core/charm" envtesting "launchpad.net/juju-core/environs/testing" "launchpad.net/juju-core/names" @@ -126,6 +129,10 @@ if _, err := io.Copy(tempFile, r.Body); err != nil { return nil, fmt.Errorf("error processing file upload: %v", err) } + err = h.processUploadedArchive(tempFile.Name()) + if err != nil { + return nil, err + } archive, err := charm.ReadBundle(tempFile.Name()) if err != nil { return nil, fmt.Errorf("invalid charm archive: %v", err) @@ -151,6 +158,196 @@ return preparedURL, nil } +// processUploadedArchive opens the given charm archive from path, +// inspects it to see if it has all files at the root of the archive +// or it has subdirs. It repackages the archive so it has all the +// files at the root dir, if necessary, replacing the original archive +// at path. +func (h *charmsHandler) processUploadedArchive(path string) error { + // Open the archive as a zip. + f, err := os.OpenFile(path, os.O_RDWR, 0644) + if err != nil { + return err + } + defer f.Close() + fi, err := f.Stat() + if err != nil { + return err + } + zipr, err := zip.NewReader(f, fi.Size()) + if err != nil { + return errgo.Annotate(err, "cannot open charm archive") + } + + // Find out the root dir prefix from the archive. + rootDir, err := h.findArchiveRootDir(zipr) + if err != nil { + return errgo.Annotate(err, "cannot read charm archive") + } + if rootDir == "" { + // Normal charm, just use charm.ReadBundle(). + return nil + } + // There is one or more subdirs, so we need extract it to a temp + // dir and then read is as a charm dir. + tempDir, err := ioutil.TempDir("", "charm-extract") + if err != nil { + return errgo.Annotate(err, "cannot create temp directory") + } + defer os.RemoveAll(tempDir) + err = h.extractArchiveTo(zipr, rootDir, tempDir) + if err != nil { + return errgo.Annotate(err, "cannot extract charm archive") + } + dir, err := charm.ReadDir(tempDir) + if err != nil { + return errgo.Annotate(err, "cannot read extracted archive") + } + // Now repackage the dir as a bundle at the original path. + if err := f.Truncate(0); err != nil { + return err + } + if err := dir.BundleTo(f); err != nil { + return err + } + return nil +} + +// fixPath converts all forward and backslashes in path to the OS path +// separator and calls filepath.Clean before returning it. +func (h *charmsHandler) fixPath(path string) string { + sep := string(filepath.Separator) + p := strings.Replace(path, "\\", sep, -1) + return filepath.Clean(strings.Replace(p, "/", sep, -1)) +} + +// findArchiveRootDir scans a zip archive and returns the rootDir of +// the archive, the one containing metadata.yaml, config.yaml and +// revision files, or an error if the archive appears invalid. +func (h *charmsHandler) findArchiveRootDir(zipr *zip.Reader) (string, error) { + numFound := 0 + metadataFound := false // metadata.yaml is the only required file. + rootPath := "" + lookFor := []string{"metadata.yaml", "config.yaml", "revision"} + for _, fh := range zipr.File { + for _, fname := range lookFor { + dir, file := filepath.Split(h.fixPath(fh.Name)) + if file == fname { + if file == "metadata.yaml" { + metadataFound = true + } + numFound++ + if rootPath == "" { + rootPath = dir + } else if rootPath != dir { + return "", fmt.Errorf("invalid charm archive: expected all %v files in the same directory", lookFor) + } + if numFound == len(lookFor) { + return rootPath, nil + } + } + } + } + if !metadataFound { + return "", fmt.Errorf("invalid charm archive: missing metadata.yaml") + } + return rootPath, nil +} + +// extractArchiveTo extracts an archive to the given destDir, removing +// the rootDir from each file, effectively reducing any nested subdirs +// to the root level. +func (h *charmsHandler) extractArchiveTo(zipr *zip.Reader, rootDir, destDir string) error { + for _, fh := range zipr.File { + err := h.extractSingleFile(fh, rootDir, destDir) + if err != nil { + return err + } + } + return nil +} + +// extractSingleFile extracts the given zip file header, removing +// rootDir from the filename, to the destDir. +func (h *charmsHandler) extractSingleFile(fh *zip.File, rootDir, destDir string) error { + cleanName := h.fixPath(fh.Name) + relName, err := filepath.Rel(rootDir, cleanName) + if err != nil { + // Skip paths not relative to roo + return nil + } + if strings.Contains(relName, "..") || relName == "." { + // Skip current dir and paths outside rootDir. + return nil + } + dirName := filepath.Dir(relName) + f, err := fh.Open() + if err != nil { + return err + } + defer f.Close() + + mode := fh.Mode() + destPath := filepath.Join(destDir, relName) + if dirName != "" && mode&os.ModeDir != 0 { + err = os.MkdirAll(destPath, mode&0777) + if err != nil { + return err + } + return nil + } + + if mode&os.ModeSymlink != 0 { + data, err := ioutil.ReadAll(f) + if err != nil { + return err + } + target := string(data) + if filepath.IsAbs(target) { + return fmt.Errorf("symlink %q is absolute: %q", cleanName, target) + } + p := filepath.Join(dirName, target) + if strings.Contains(p, "..") { + return fmt.Errorf("symlink %q links out of charm: %s", cleanName, target) + } + err = os.Symlink(target, destPath) + if err != nil { + return err + } + } + if dirName == "hooks" { + if mode&os.ModeType == 0 { + // Set all hooks executable (by owner) + mode = mode | 0100 + } + } + + // Check file type. + e := "file has an unknown type: %q" + switch mode & os.ModeType { + case os.ModeDir, os.ModeSymlink, 0: + // That's expected, it's ok. + e = "" + case os.ModeNamedPipe: + e = "file is a named pipe: %q" + case os.ModeSocket: + e = "file is a socket: %q" + case os.ModeDevice: + e = "file is a device: %q" + } + if e != "" { + return fmt.Errorf(e, destPath) + } + + out, err := os.OpenFile(destPath, os.O_CREATE|os.O_WRONLY, mode&0777) + if err != nil { + return fmt.Errorf("creating %q failed: %v", destPath, err) + } + defer out.Close() + _, err = io.Copy(out, f) + return err +} + // repackageAndUploadCharm expands the given charm archive to a // temporary directoy, repackages it with the given curl's revision, // then uploads it to providr storage, and finally updates the state. @@ -159,66 +356,66 @@ // dir and the repackaged archive. tempDir, err := ioutil.TempDir("", "charm-download") if err != nil { - return fmt.Errorf("cannot create temp directory: %v", err) + return errgo.Annotate(err, "cannot create temp directory") } defer os.RemoveAll(tempDir) extractPath := filepath.Join(tempDir, "extracted") repackagedPath := filepath.Join(tempDir, "repackaged.zip") repackagedArchive, err := os.Create(repackagedPath) if err != nil { - return fmt.Errorf("cannot repackage uploaded charm: %v", err) + return errgo.Annotate(err, "cannot repackage uploaded charm") } defer repackagedArchive.Close() // Expand and repack it with the revision specified by curl. archive.SetRevision(curl.Revision) if err := archive.ExpandTo(extractPath); err != nil { - return fmt.Errorf("cannot extract uploaded charm: %v", err) + return errgo.Annotate(err, "cannot extract uploaded charm") } charmDir, err := charm.ReadDir(extractPath) if err != nil { - return fmt.Errorf("cannot read extracted charm: %v", err) + return errgo.Annotate(err, "cannot read extracted charm") } // Bundle the charm and calculate its sha256 hash at the // same time. hash := sha256.New() err = charmDir.BundleTo(io.MultiWriter(hash, repackagedArchive)) if err != nil { - return fmt.Errorf("cannot repackage uploaded charm: %v", err) + return errgo.Annotate(err, "cannot repackage uploaded charm") } bundleSHA256 := hex.EncodeToString(hash.Sum(nil)) size, err := repackagedArchive.Seek(0, 2) if err != nil { - return fmt.Errorf("cannot get charm file size: %v", err) + return errgo.Annotate(err, "cannot get charm file size") } // Seek to the beginning so the subsequent Put will read // the whole file again. if _, err := repackagedArchive.Seek(0, 0); err != nil { - return fmt.Errorf("cannot rewind the charm file reader: %v", err) + return errgo.Annotate(err, "cannot rewind the charm file reader") } // Now upload to provider storage. storage, err := envtesting.GetEnvironStorage(h.state) if err != nil { - return fmt.Errorf("cannot access provider storage: %v", err) + return errgo.Annotate(err, "cannot access provider storage") } name := charm.Quote(curl.String()) if err := storage.Put(name, repackagedArchive, size); err != nil { - return fmt.Errorf("cannot upload charm to provider storage: %v", err) + return errgo.Annotate(err, "cannot upload charm to provider storage") } storageURL, err := storage.URL(name) if err != nil { - return fmt.Errorf("cannot get storage URL for charm: %v", err) + return errgo.Annotate(err, "cannot get storage URL for charm") } bundleURL, err := url.Parse(storageURL) if err != nil { - return fmt.Errorf("cannot parse storage URL: %v", err) + return errgo.Annotate(err, "cannot parse storage URL") } // And finally, update state. _, err = h.state.UpdateUploadedCharm(archive, curl, bundleURL, bundleSHA256) if err != nil { - return fmt.Errorf("cannot update uploaded charm in state: %v", err) + return errgo.Annotate(err, "cannot update uploaded charm in state") } return nil } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/charms_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/charms_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/charms_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/charms_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -11,6 +11,8 @@ "net/http" "net/url" "os" + "path/filepath" + "strings" gc "launchpad.net/gocheck" @@ -98,7 +100,7 @@ // check the error at extraction time later. resp, err := s.uploadRequest(c, s.charmsURI(c, "?series=quantal"), true, tempFile.Name()) c.Assert(err, gc.IsNil) - s.assertResponse(c, resp, http.StatusBadRequest, "invalid charm archive: zip: not a valid zip file", "") + s.assertResponse(c, resp, http.StatusBadRequest, "cannot open charm archive: zip: not a valid zip file", "") // Now try with the default Content-Type. resp, err = s.uploadRequest(c, s.charmsURI(c, "?series=quantal"), false, tempFile.Name()) @@ -181,6 +183,63 @@ c.Assert(downloadedSHA256, gc.Equals, expectedSHA256) } +func (s *charmsSuite) TestUploadRepackagesNestedArchives(c *gc.C) { + // Make a clone of the dummy charm in a nested directory. + rootDir := c.MkDir() + dirPath := filepath.Join(rootDir, "subdir1", "subdir2") + err := os.MkdirAll(dirPath, 0755) + c.Assert(err, gc.IsNil) + dir := coretesting.Charms.ClonedDir(dirPath, "dummy") + // Now tweak the path the dir thinks it is in and bundle it. + dir.Path = rootDir + tempFile, err := ioutil.TempFile(c.MkDir(), "charm") + c.Assert(err, gc.IsNil) + defer tempFile.Close() + defer os.Remove(tempFile.Name()) + err = dir.BundleTo(tempFile) + c.Assert(err, gc.IsNil) + + // Try reading it as a bundle - should fail due to nested dirs. + _, err = charm.ReadBundle(tempFile.Name()) + c.Assert(err, gc.ErrorMatches, "bundle file not found: metadata.yaml") + + // Now try uploading it - should succeeed and be repackaged. + resp, err := s.uploadRequest(c, s.charmsURI(c, "?series=quantal"), true, tempFile.Name()) + c.Assert(err, gc.IsNil) + expectedURL := charm.MustParseURL("local:quantal/dummy-1") + s.assertResponse(c, resp, http.StatusOK, "", expectedURL.String()) + sch, err := s.State.Charm(expectedURL) + c.Assert(err, gc.IsNil) + c.Assert(sch.URL(), gc.DeepEquals, expectedURL) + c.Assert(sch.Revision(), gc.Equals, 1) + c.Assert(sch.IsUploaded(), jc.IsTrue) + + // Get it from the storage and try to read it as a bundle - it + // should succeed, because it was repackaged during upload to + // strip nested dirs. + archiveName := strings.TrimPrefix(sch.BundleURL().RequestURI(), "/dummyenv/private/") + storage, err := envtesting.GetEnvironStorage(s.State) + c.Assert(err, gc.IsNil) + reader, err := storage.Get(archiveName) + c.Assert(err, gc.IsNil) + defer reader.Close() + + data, err := ioutil.ReadAll(reader) + c.Assert(err, gc.IsNil) + downloadedFile, err := ioutil.TempFile(c.MkDir(), "downloaded") + c.Assert(err, gc.IsNil) + defer downloadedFile.Close() + defer os.Remove(downloadedFile.Name()) + err = ioutil.WriteFile(downloadedFile.Name(), data, 0644) + c.Assert(err, gc.IsNil) + + bundle, err := charm.ReadBundle(downloadedFile.Name()) + c.Assert(err, gc.IsNil) + c.Assert(bundle.Revision(), jc.DeepEquals, sch.Revision()) + c.Assert(bundle.Meta(), jc.DeepEquals, sch.Meta()) + c.Assert(bundle.Config(), jc.DeepEquals, sch.Config()) +} + func (s *charmsSuite) charmsURI(c *gc.C, query string) string { _, info, err := s.APIConn.Environ.StateInfo() c.Assert(err, gc.IsNil) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/client/api_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/client/api_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/client/api_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/client/api_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -11,6 +11,7 @@ gc "launchpad.net/gocheck" "launchpad.net/juju-core/constraints" + "launchpad.net/juju-core/environs" "launchpad.net/juju-core/environs/config" "launchpad.net/juju-core/errors" "launchpad.net/juju-core/instance" @@ -111,7 +112,7 @@ stateInfo.Password = password st, err := state.Open(stateInfo, state.DialOpts{ Timeout: 25 * time.Millisecond, - }) + }, environs.NewStatePolicy()) if err == nil { st.Close() } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/client/client.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/client/client.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/client/client.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/client/client.go 2014-02-20 16:10:15.000000000 +0000 @@ -9,6 +9,7 @@ "os" "strings" + "github.com/errgo/errgo" "github.com/loggo/loggo" "launchpad.net/juju-core/charm" @@ -851,47 +852,47 @@ store := config.AuthorizeCharmRepo(CharmStore, envConfig) downloadedCharm, err := store.Get(charmURL) if err != nil { - return fmt.Errorf("cannot download charm %q: %v", charmURL.String(), err) + return errgo.Annotatef(err, "cannot download charm %q", charmURL.String()) } // Open it and calculate the SHA256 hash. downloadedBundle, ok := downloadedCharm.(*charm.Bundle) if !ok { - return fmt.Errorf("expected a charm archive, got %T", downloadedCharm) + return errgo.New("expected a charm archive, got %T", downloadedCharm) } archive, err := os.Open(downloadedBundle.Path) if err != nil { - return fmt.Errorf("cannot read downloaded charm: %v", err) + return errgo.Annotate(err, "cannot read downloaded charm") } defer archive.Close() bundleSHA256, size, err := utils.ReadSHA256(archive) if err != nil { - return fmt.Errorf("cannot calculate SHA256 hash of charm: %v", err) + return errgo.Annotate(err, "cannot calculate SHA256 hash of charm") } if _, err := archive.Seek(0, 0); err != nil { - return fmt.Errorf("cannot rewind charm archive: %v", err) + return errgo.Annotate(err, "cannot rewind charm archive") } // Get the environment storage and upload the charm. env, err := environs.New(envConfig) if err != nil { - return fmt.Errorf("cannot access environment: %v", err) + return errgo.Annotate(err, "cannot access environment") } storage := env.Storage() archiveName, err := CharmArchiveName(charmURL.Name, charmURL.Revision) if err != nil { - return fmt.Errorf("cannot generate charm archive name: %v", err) + return errgo.Annotate(err, "cannot generate charm archive name") } if err := storage.Put(archiveName, archive, size); err != nil { - return fmt.Errorf("cannot upload charm to provider storage: %v", err) + return errgo.Annotate(err, "cannot upload charm to provider storage") } storageURL, err := storage.URL(archiveName) if err != nil { - return fmt.Errorf("cannot get storage URL for charm: %v", err) + return errgo.Annotate(err, "cannot get storage URL for charm") } bundleURL, err := url.Parse(storageURL) if err != nil { - return fmt.Errorf("cannot parse storage URL: %v", err) + return errgo.Annotate(err, "cannot parse storage URL") } // Finally, update the charm data in state and mark it as no longer pending. @@ -903,7 +904,7 @@ // us. This means we have to delete what we just uploaded // to storage. if err := storage.Remove(archiveName); err != nil { - logger.Errorf("cannot remove duplicated charm from storage: %v", err) + errgo.Annotate(err, "cannot remove duplicated charm from storage") } return nil } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/client/run.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/client/run.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/client/run.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/client/run.go 2014-02-20 16:10:15.000000000 +0000 @@ -5,8 +5,6 @@ import ( "fmt" - "launchpad.net/juju-core/state" - "launchpad.net/juju-core/utils" "path/filepath" "sort" "sync" @@ -14,7 +12,9 @@ "launchpad.net/juju-core/environs/cloudinit" "launchpad.net/juju-core/instance" + "launchpad.net/juju-core/state" "launchpad.net/juju-core/state/api/params" + "launchpad.net/juju-core/utils" "launchpad.net/juju-core/utils/set" "launchpad.net/juju-core/utils/ssh" ) @@ -106,8 +106,7 @@ if err != nil { return results, err } - command := `[ -f "$HOME/.juju-proxy" ] && . "$HOME/.juju-proxy"` - command += fmt.Sprintf("\njuju-run --no-context %s", quotedCommands) + command := fmt.Sprintf("juju-run --no-context %s", quotedCommands) execParam := remoteParamsForMachine(machine, command, run.Timeout) params = append(params, execParam) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/client/run_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/client/run_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/client/run_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/client/run_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -6,7 +6,6 @@ import ( "fmt" "io/ioutil" - "os" "path/filepath" "time" @@ -144,8 +143,7 @@ func (s *runSuite) mockSSH(c *gc.C, cmd string) { testbin := c.MkDir() fakessh := filepath.Join(testbin, "ssh") - newPath := testbin + ":" + os.Getenv("PATH") - s.PatchEnvironment("PATH", newPath) + s.PatchEnvPathPrepend(testbin) err := ioutil.WriteFile(fakessh, []byte(cmd), 0755) c.Assert(err, gc.IsNil) } @@ -264,7 +262,7 @@ c.Assert(results, gc.HasLen, 3) expectedResults := []params.RunResult{ params.RunResult{ - ExecResponse: exec.ExecResponse{Stdout: []byte("[ -f \"$HOME/.juju-proxy\" ] && . \"$HOME/.juju-proxy\"\njuju-run --no-context 'hostname'\n")}, + ExecResponse: exec.ExecResponse{Stdout: []byte("juju-run --no-context 'hostname'\n")}, MachineId: "0", }, params.RunResult{ diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/common/environwatcher_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/common/environwatcher_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/common/environwatcher_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/common/environwatcher_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -16,6 +16,7 @@ "launchpad.net/juju-core/state/api/params" "launchpad.net/juju-core/state/apiserver/common" apiservertesting "launchpad.net/juju-core/state/apiserver/testing" + "launchpad.net/juju-core/testing" jc "launchpad.net/juju-core/testing/checkers" "launchpad.net/juju-core/testing/testbase" ) @@ -187,7 +188,7 @@ func testingEnvConfig(c *gc.C) *config.Config { cfg, err := config.New(config.NoDefaults, dummy.SampleConfig()) c.Assert(err, gc.IsNil) - env, err := environs.Prepare(cfg, configstore.NewMem()) + env, err := environs.Prepare(cfg, testing.Context(c), configstore.NewMem()) c.Assert(err, gc.IsNil) return env.Config() } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/common/tools.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/common/tools.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/common/tools.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/common/tools.go 2014-02-20 16:10:15.000000000 +0000 @@ -103,3 +103,50 @@ // in state, or even in the API servers return envtools.FindExactTools(env, agentVersion, existingTools.Version.Series, existingTools.Version.Arch) } + +// ToolsSetter implements a common Tools method for use by various +// facades. +type ToolsSetter struct { + st state.EntityFinder + getCanWrite GetAuthFunc +} + +// NewToolsGetter returns a new ToolsGetter. The GetAuthFunc will be +// used on each invocation of Tools to determine current permissions. +func NewToolsSetter(st state.EntityFinder, getCanWrite GetAuthFunc) *ToolsSetter { + return &ToolsSetter{ + st: st, + getCanWrite: getCanWrite, + } +} + +// SetTools updates the recorded tools version for the agents. +func (t *ToolsSetter) SetTools(args params.EntitiesVersion) (params.ErrorResults, error) { + results := params.ErrorResults{ + Results: make([]params.ErrorResult, len(args.AgentTools)), + } + canWrite, err := t.getCanWrite() + if err != nil { + return results, err + } + for i, agentTools := range args.AgentTools { + err := t.setOneAgentVersion(agentTools.Tag, agentTools.Tools.Version, canWrite) + results.Results[i].Error = ServerError(err) + } + return results, nil +} + +func (t *ToolsSetter) setOneAgentVersion(tag string, vers version.Binary, canWrite AuthFunc) error { + if !canWrite(tag) { + return ErrPerm + } + entity0, err := t.st.FindEntity(tag) + if err != nil { + return err + } + entity, ok := entity0.(state.AgentTooler) + if !ok { + return NotSupportedError(tag, "agent tools") + } + return entity.SetAgentVersion(vers) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/common/tools_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/common/tools_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/common/tools_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/common/tools_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -71,3 +71,62 @@ c.Assert(err, gc.ErrorMatches, "splat") c.Assert(result.Results, gc.HasLen, 1) } + +func (s *toolsSuite) TestSetTools(c *gc.C) { + getCanWrite := func() (common.AuthFunc, error) { + return func(tag string) bool { + return tag == "machine-0" || tag == "machine-42" + }, nil + } + ts := common.NewToolsSetter(s.State, getCanWrite) + c.Assert(ts, gc.NotNil) + + err := s.machine0.SetAgentVersion(version.Current) + c.Assert(err, gc.IsNil) + + args := params.EntitiesVersion{ + AgentTools: []params.EntityVersion{{ + Tag: "machine-0", + Tools: ¶ms.Version{ + Version: version.Current, + }, + }, { + Tag: "machine-1", + Tools: ¶ms.Version{ + Version: version.Current, + }, + }, { + Tag: "machine-42", + Tools: ¶ms.Version{ + Version: version.Current, + }, + }}, + } + result, err := ts.SetTools(args) + c.Assert(err, gc.IsNil) + c.Assert(result.Results, gc.HasLen, 3) + c.Assert(result.Results[0].Error, gc.IsNil) + agentTools, err := s.machine0.AgentTools() + c.Assert(err, gc.IsNil) + c.Assert(agentTools.Version, gc.DeepEquals, version.Current) + c.Assert(result.Results[1].Error, gc.DeepEquals, apiservertesting.ErrUnauthorized) + c.Assert(result.Results[2].Error, gc.DeepEquals, apiservertesting.NotFoundError("machine 42")) +} + +func (s *toolsSuite) TestToolsSetError(c *gc.C) { + getCanWrite := func() (common.AuthFunc, error) { + return nil, fmt.Errorf("splat") + } + ts := common.NewToolsSetter(s.State, getCanWrite) + args := params.EntitiesVersion{ + AgentTools: []params.EntityVersion{{ + Tag: "machine-42", + Tools: ¶ms.Version{ + Version: version.Current, + }, + }}, + } + result, err := ts.SetTools(args) + c.Assert(err, gc.ErrorMatches, "splat") + c.Assert(result.Results, gc.HasLen, 1) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/environment/environment_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/environment/environment_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/environment/environment_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/environment/environment_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -31,7 +31,7 @@ s.JujuConnSuite.SetUpTest(c) var err error - s.machine0, err = s.State.AddMachine("quantal", state.JobHostUnits, state.JobManageState, state.JobManageEnviron) + s.machine0, err = s.State.AddMachine("quantal", state.JobHostUnits, state.JobManageEnviron) c.Assert(err, gc.IsNil) s.authorizer = apiservertesting.FakeAuthorizer{ diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/keymanager/keymanager_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/keymanager/keymanager_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/keymanager/keymanager_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/keymanager/keymanager_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,11 +4,11 @@ package keymanager_test import ( + "fmt" "strings" gc "launchpad.net/gocheck" - "fmt" jujutesting "launchpad.net/juju-core/juju/testing" "launchpad.net/juju-core/state/api/params" "launchpad.net/juju-core/state/apiserver/common" diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/keymanager/testing/fakesshimport.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/keymanager/testing/fakesshimport.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/keymanager/testing/fakesshimport.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/keymanager/testing/fakesshimport.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,8 +4,9 @@ package testing import ( - sshtesting "launchpad.net/juju-core/utils/ssh/testing" "strings" + + sshtesting "launchpad.net/juju-core/utils/ssh/testing" ) var importResponses = map[string]string{ diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/provisioner/provisioner.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/provisioner/provisioner.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/provisioner/provisioner.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/provisioner/provisioner.go 2014-02-20 16:10:15.000000000 +0000 @@ -197,6 +197,8 @@ result.AuthorizedKeys = config.AuthorizedKeys() result.SSLHostnameVerification = config.SSLHostnameVerification() result.SyslogPort = config.SyslogPort() + result.Proxy = config.ProxySettings() + result.AptProxy = config.AptProxySettings() return result, nil } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/provisioner/provisioner_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/provisioner/provisioner_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/provisioner/provisioner_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/provisioner/provisioner_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -12,6 +12,7 @@ "launchpad.net/juju-core/constraints" "launchpad.net/juju-core/errors" "launchpad.net/juju-core/instance" + "launchpad.net/juju-core/juju/osenv" "launchpad.net/juju-core/juju/testing" "launchpad.net/juju-core/state" "launchpad.net/juju-core/state/api/params" @@ -682,12 +683,26 @@ } func (s *withoutStateServerSuite) TestContainerConfig(c *gc.C) { + cfg, err := s.State.EnvironConfig() + c.Assert(err, gc.IsNil) + newCfg, err := cfg.Apply(map[string]interface{}{ + "http-proxy": "http://proxy.example.com:9000", + }) + c.Assert(err, gc.IsNil) + err = s.State.SetEnvironConfig(newCfg, cfg) + c.Assert(err, gc.IsNil) + expectedProxy := osenv.ProxySettings{ + Http: "http://proxy.example.com:9000", + } + results, err := s.provisioner.ContainerConfig() c.Check(err, gc.IsNil) c.Check(results.ProviderType, gc.Equals, "dummy") c.Check(results.AuthorizedKeys, gc.Equals, coretesting.FakeAuthKeys) c.Check(results.SSLHostnameVerification, jc.IsTrue) c.Check(results.SyslogPort, gc.Equals, 2345) + c.Check(results.Proxy, gc.DeepEquals, expectedProxy) + c.Check(results.AptProxy, gc.DeepEquals, expectedProxy) } func (s *withoutStateServerSuite) TestToolsRefusesWrongAgent(c *gc.C) { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/root.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/root.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/root.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/root.go 2014-02-20 16:10:15.000000000 +0000 @@ -9,6 +9,7 @@ "launchpad.net/tomb" + "launchpad.net/juju-core/names" "launchpad.net/juju-core/rpc" "launchpad.net/juju-core/state" "launchpad.net/juju-core/state/apiserver/agent" @@ -193,12 +194,27 @@ // Upgrader returns an object that provides access to the Upgrader API facade. // The id argument is reserved for future use and must be empty. -func (r *srvRoot) Upgrader(id string) (*upgrader.UpgraderAPI, error) { +func (r *srvRoot) Upgrader(id string) (upgrader.Upgrader, error) { if id != "" { // TODO: There is no direct test for this return nil, common.ErrBadId } - return upgrader.NewUpgraderAPI(r.srv.state, r.resources, r) + // The type of upgrader we return depends on who is asking. + // Machines get an UpgraderAPI, units get a UnitUpgraderAPI. + // This is tested in the state/api/upgrader package since there + // are currently no direct srvRoot tests. + tagKind, _, err := names.ParseTag(r.GetAuthTag(), "") + if err != nil { + return nil, common.ErrPerm + } + switch tagKind { + case names.MachineTagKind: + return upgrader.NewUpgraderAPI(r.srv.state, r.resources, r) + case names.UnitTagKind: + return upgrader.NewUnitUpgraderAPI(r.srv.state, r.resources, r, r.srv.dataDir) + } + // Not a machine or unit. + return nil, common.ErrPerm } // KeyUpdater returns an object that provides access to the KeyUpdater API facade. diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/uniter/uniter.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/uniter/uniter.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/uniter/uniter.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/uniter/uniter.go 2014-02-20 16:10:15.000000000 +0000 @@ -72,19 +72,19 @@ } func (u *UniterAPI) getUnit(tag string) (*state.Unit, error) { - entity, err := u.st.FindEntity(tag) + _, name, err := names.ParseTag(tag, names.UnitTagKind) if err != nil { return nil, err } - return entity.(*state.Unit), nil + return u.st.Unit(name) } func (u *UniterAPI) getService(tag string) (*state.Service, error) { - entity, err := u.st.FindEntity(tag) + _, name, err := names.ParseTag(tag, names.ServiceTagKind) if err != nil { return nil, err } - return entity.(*state.Service), nil + return u.st.Service(name) } // PublicAddress returns the public address for each given unit, if set. @@ -749,6 +749,17 @@ return result, err } +// CurrentEnvironment returns the name and UUID for the current juju environment. +func (u *UniterAPI) CurrentEnvironment() (params.EnvironmentResult, error) { + result := params.EnvironmentResult{} + env, err := u.st.Environment() + if err == nil { + result.Name = env.Name() + result.UUID = env.UUID() + } + return result, err +} + // ProviderType returns the provider type used by the current juju // environment. // @@ -854,17 +865,21 @@ if remoteUnitTag == u.auth.GetAuthTag() { return "", common.ErrPerm } - remoteUnit, err := u.getUnit(remoteUnitTag) + // Check remoteUnit is indeed related. Note that we don't want to actually get + // the *Unit, because it might have been removed; but its relation settings will + // persist until the relation itself has been removed (and must remain accessible + // because the local unit's view of reality may be time-shifted). + _, remoteUnitName, err := names.ParseTag(remoteUnitTag, names.UnitTagKind) if err != nil { - return "", common.ErrPerm + return "", err } - // Check remoteUnit is indeed related. + remoteServiceName := names.UnitService(remoteUnitName) rel := relUnit.Relation() - _, err = rel.RelatedEndpoints(remoteUnit.ServiceName()) + _, err = rel.RelatedEndpoints(remoteServiceName) if err != nil { return "", common.ErrPerm } - return remoteUnit.Name(), nil + return remoteUnitName, nil } // ReadRemoteSettings returns the remote settings of each given set of diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/uniter/uniter_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/uniter/uniter_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/uniter/uniter_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/uniter/uniter_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -54,7 +54,7 @@ s.wpCharm = s.AddTestingCharm(c, "wordpress") // Create two machines, two services and add a unit to each service. var err error - s.machine0, err = s.State.AddMachine("quantal", state.JobHostUnits, state.JobManageState, state.JobManageEnviron) + s.machine0, err = s.State.AddMachine("quantal", state.JobHostUnits, state.JobManageEnviron) c.Assert(err, gc.IsNil) s.machine1, err = s.State.AddMachine("quantal", state.JobHostUnits) c.Assert(err, gc.IsNil) @@ -900,6 +900,19 @@ c.Assert(result, gc.DeepEquals, params.StringResult{Result: env.UUID()}) } +func (s *uniterSuite) TestCurrentEnvironment(c *gc.C) { + env, err := s.State.Environment() + c.Assert(err, gc.IsNil) + + result, err := s.uniter.CurrentEnvironment() + c.Assert(err, gc.IsNil) + expected := params.EnvironmentResult{ + Name: env.Name(), + UUID: env.UUID(), + } + c.Assert(result, gc.DeepEquals, expected) +} + func (s *uniterSuite) addRelation(c *gc.C, first, second string) *state.Relation { eps, err := s.State.InferEndpoints([]string{first, second}) c.Assert(err, gc.IsNil) @@ -1192,6 +1205,7 @@ {Relation: rel.Tag(), LocalUnit: "user-admin", RemoteUnit: "unit-wordpress-0"}, }} result, err := s.uniter.ReadRemoteSettings(args) + // We don't set the remote unit settings on purpose to test the error. expectErr := `cannot read settings for unit "mysql/0" in relation "wordpress:db mysql:server": settings not found` c.Assert(err, gc.IsNil) @@ -1210,6 +1224,7 @@ {Error: apiservertesting.ErrUnauthorized}, }, }) + // Now leave the mysqlUnit and re-enter with new settings. relUnit, err = rel.Unit(s.mysqlUnit) c.Assert(err, gc.IsNil) @@ -1229,15 +1244,27 @@ LocalUnit: "unit-wordpress-0", RemoteUnit: "unit-mysql-0", }}} - result, err = s.uniter.ReadRemoteSettings(args) - c.Assert(err, gc.IsNil) - c.Assert(result, gc.DeepEquals, params.RelationSettingsResults{ + expect := params.RelationSettingsResults{ Results: []params.RelationSettingsResult{ {Settings: params.RelationSettings{ "other": "things", }}, }, - }) + } + result, err = s.uniter.ReadRemoteSettings(args) + c.Assert(err, gc.IsNil) + c.Assert(result, gc.DeepEquals, expect) + + // Now destroy the remote unit, and check its settings can still be read. + err = s.mysqlUnit.Destroy() + c.Assert(err, gc.IsNil) + err = s.mysqlUnit.EnsureDead() + c.Assert(err, gc.IsNil) + err = s.mysqlUnit.Remove() + c.Assert(err, gc.IsNil) + result, err = s.uniter.ReadRemoteSettings(args) + c.Assert(err, gc.IsNil) + c.Assert(result, gc.DeepEquals, expect) } func (s *uniterSuite) TestReadRemoteSettingsWithNonStringValuesFails(c *gc.C) { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/upgrader/suite_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/upgrader/suite_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/upgrader/suite_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/upgrader/suite_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,8 +4,9 @@ package upgrader_test import ( - coretesting "launchpad.net/juju-core/testing" stdtesting "testing" + + coretesting "launchpad.net/juju-core/testing" ) func TestAll(t *stdtesting.T) { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/upgrader/unitupgrader.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/upgrader/unitupgrader.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/upgrader/unitupgrader.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/upgrader/unitupgrader.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,162 @@ +// Copyright 2012, 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrader + +import ( + agenttools "launchpad.net/juju-core/agent/tools" + "launchpad.net/juju-core/names" + "launchpad.net/juju-core/state" + "launchpad.net/juju-core/state/api/params" + "launchpad.net/juju-core/state/apiserver/common" + "launchpad.net/juju-core/state/watcher" + "launchpad.net/juju-core/tools" + "launchpad.net/juju-core/version" +) + +// UnitUpgraderAPI provides access to the UnitUpgrader API facade. +type UnitUpgraderAPI struct { + *common.ToolsSetter + + st *state.State + resources *common.Resources + authorizer common.Authorizer + dataDir string +} + +// NewUnitUpgraderAPI creates a new server-side UnitUpgraderAPI facade. +func NewUnitUpgraderAPI( + st *state.State, + resources *common.Resources, + authorizer common.Authorizer, + dataDir string, +) (*UnitUpgraderAPI, error) { + if !authorizer.AuthUnitAgent() { + return nil, common.ErrPerm + } + + getCanWrite := func() (common.AuthFunc, error) { + return authorizer.AuthOwner, nil + } + return &UnitUpgraderAPI{ + ToolsSetter: common.NewToolsSetter(st, getCanWrite), + st: st, + resources: resources, + authorizer: authorizer, + dataDir: dataDir, + }, nil +} + +func (u *UnitUpgraderAPI) watchAssignedMachine(unitTag string) (string, error) { + machine, err := u.getAssignedMachine(unitTag) + if err != nil { + return "", err + } + watch := machine.Watch() + // Consume the initial event. Technically, API + // calls to Watch 'transmit' the initial event + // in the Watch response. But NotifyWatchers + // have no state to transmit. + if _, ok := <-watch.Changes(); ok { + return u.resources.Register(watch), nil + } + return "", watcher.MustErr(watch) +} + +// WatchAPIVersion starts a watcher to track if there is a new version +// of the API that we want to upgrade to. The watcher tracks changes to +// the unit's assigned machine since that's where the required agent version is stored. +func (u *UnitUpgraderAPI) WatchAPIVersion(args params.Entities) (params.NotifyWatchResults, error) { + result := params.NotifyWatchResults{ + Results: make([]params.NotifyWatchResult, len(args.Entities)), + } + for i, agent := range args.Entities { + err := common.ErrPerm + if u.authorizer.AuthOwner(agent.Tag) { + var watcherId string + watcherId, err = u.watchAssignedMachine(agent.Tag) + if err == nil { + result.Results[i].NotifyWatcherId = watcherId + } + } + result.Results[i].Error = common.ServerError(err) + } + return result, nil +} + +// DesiredVersion reports the Agent Version that we want that unit to be running. +// The desired version is what the unit's assigned machine is running. +func (u *UnitUpgraderAPI) DesiredVersion(args params.Entities) (params.VersionResults, error) { + result := make([]params.VersionResult, len(args.Entities)) + if len(args.Entities) == 0 { + return params.VersionResults{}, nil + } + for i, entity := range args.Entities { + err := common.ErrPerm + if u.authorizer.AuthOwner(entity.Tag) { + result[i].Version, err = u.getMachineToolsVersion(entity.Tag) + } + result[i].Error = common.ServerError(err) + } + return params.VersionResults{result}, nil +} + +// Tools finds the tools necessary for the given agents. +func (u *UnitUpgraderAPI) Tools(args params.Entities) (params.ToolsResults, error) { + result := params.ToolsResults{ + Results: make([]params.ToolsResult, len(args.Entities)), + } + for i, entity := range args.Entities { + err := common.ErrPerm + if u.authorizer.AuthOwner(entity.Tag) { + result.Results[i].Tools, err = u.getMachineTools(entity.Tag) + } + result.Results[i].Error = common.ServerError(err) + } + return result, nil +} + +func (u *UnitUpgraderAPI) getAssignedMachine(tag string) (*state.Machine, error) { + // Check that we really have a unit tag. + _, unitName, err := names.ParseTag(tag, names.UnitTagKind) + if err != nil { + return nil, common.ErrPerm + } + unit, err := u.st.Unit(unitName) + if err != nil { + return nil, common.ErrPerm + } + id, err := unit.AssignedMachineId() + if err != nil { + return nil, err + } + return u.st.Machine(id) +} + +func (u *UnitUpgraderAPI) getMachineTools(tag string) (*tools.Tools, error) { + machine, err := u.getAssignedMachine(tag) + if err != nil { + return nil, err + } + machineTools, err := machine.AgentTools() + if err != nil { + return nil, err + } + // For older 1.16 upgrader workers, we need to supply a tools URL since the worker will attempt to + // download the tools even though they already have been fetched by the machine agent. Newer upgrader + // workers do not have this problem. So to be compatible across all versions, we return the full tools + // metadata as recorded in the downloaded tools directory. + downloadedTools, err := agenttools.ReadTools(u.dataDir, machineTools.Version) + if err != nil { + return nil, err + } + return downloadedTools, nil +} + +func (u *UnitUpgraderAPI) getMachineToolsVersion(tag string) (*version.Number, error) { + agentTools, err := u.getMachineTools(tag) + if err != nil { + return nil, err + } + return &agentTools.Version.Number, nil +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/upgrader/unitupgrader_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/upgrader/unitupgrader_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/upgrader/unitupgrader_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/upgrader/unitupgrader_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,303 @@ +// Copyright 2012, 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrader_test + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + + gc "launchpad.net/gocheck" + + "launchpad.net/juju-core/errors" + jujutesting "launchpad.net/juju-core/juju/testing" + "launchpad.net/juju-core/state" + "launchpad.net/juju-core/state/api/params" + "launchpad.net/juju-core/state/apiserver/common" + apiservertesting "launchpad.net/juju-core/state/apiserver/testing" + "launchpad.net/juju-core/state/apiserver/upgrader" + statetesting "launchpad.net/juju-core/state/testing" + jc "launchpad.net/juju-core/testing/checkers" + "launchpad.net/juju-core/tools" + "launchpad.net/juju-core/version" +) + +type unitUpgraderSuite struct { + jujutesting.JujuConnSuite + + // These are raw State objects. Use them for setup and assertions, but + // should never be touched by the API calls themselves + rawMachine *state.Machine + rawUnit *state.Unit + upgrader *upgrader.UnitUpgraderAPI + resources *common.Resources + authorizer apiservertesting.FakeAuthorizer + + // Tools for the assigned machine. + fakeTools *tools.Tools +} + +var _ = gc.Suite(&unitUpgraderSuite{}) + +func (s *unitUpgraderSuite) SetUpTest(c *gc.C) { + s.JujuConnSuite.SetUpTest(c) + s.resources = common.NewResources() + + // Create a machine and unit to work with + var err error + _, err = s.State.AddMachine("quantal", state.JobHostUnits) + c.Assert(err, gc.IsNil) + svc := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) + s.rawUnit, err = svc.AddUnit() + c.Assert(err, gc.IsNil) + // Assign the unit to the machine. + s.rawMachine, err = s.rawUnit.AssignToCleanMachine() + c.Assert(err, gc.IsNil) + + // The default auth is as the unit agent + s.authorizer = apiservertesting.FakeAuthorizer{ + Tag: s.rawUnit.Tag(), + LoggedIn: true, + UnitAgent: true, + } + // Set up fake downloaded tools for the assigned machine. + fakeToolsPath := filepath.Join(s.DataDir(), "tools", version.Current.String()) + err = os.MkdirAll(fakeToolsPath, 0700) + c.Assert(err, gc.IsNil) + s.fakeTools = &tools.Tools{ + Version: version.Current, + URL: "fake-url", + Size: 1234, + SHA256: "checksum", + } + toolsMetadataData, err := json.Marshal(s.fakeTools) + c.Assert(err, gc.IsNil) + err = ioutil.WriteFile(filepath.Join(fakeToolsPath, "downloaded-tools.txt"), []byte(toolsMetadataData), 0644) + c.Assert(err, gc.IsNil) + + s.upgrader, err = upgrader.NewUnitUpgraderAPI(s.State, s.resources, s.authorizer, s.DataDir()) + c.Assert(err, gc.IsNil) +} + +func (s *unitUpgraderSuite) TearDownTest(c *gc.C) { + if s.resources != nil { + s.resources.StopAll() + } + s.JujuConnSuite.TearDownTest(c) +} + +func (s *unitUpgraderSuite) TestWatchAPIVersionNothing(c *gc.C) { + // Not an error to watch nothing + results, err := s.upgrader.WatchAPIVersion(params.Entities{}) + c.Assert(err, gc.IsNil) + c.Check(results.Results, gc.HasLen, 0) +} + +func (s *unitUpgraderSuite) TestWatchAPIVersion(c *gc.C) { + args := params.Entities{ + Entities: []params.Entity{{Tag: s.rawUnit.Tag()}}, + } + results, err := s.upgrader.WatchAPIVersion(args) + c.Assert(err, gc.IsNil) + c.Check(results.Results, gc.HasLen, 1) + c.Check(results.Results[0].NotifyWatcherId, gc.Not(gc.Equals), "") + c.Check(results.Results[0].Error, gc.IsNil) + resource := s.resources.Get(results.Results[0].NotifyWatcherId) + c.Check(resource, gc.NotNil) + + w := resource.(state.NotifyWatcher) + wc := statetesting.NewNotifyWatcherC(c, s.State, w) + wc.AssertNoChange() + + err = s.rawMachine.SetAgentVersion(version.MustParseBinary("3.4.567.8-quantal-amd64")) + c.Assert(err, gc.IsNil) + wc.AssertOneChange() + statetesting.AssertStop(c, w) + wc.AssertClosed() +} + +func (s *unitUpgraderSuite) TestUpgraderAPIRefusesNonUnitAgent(c *gc.C) { + anAuthorizer := s.authorizer + anAuthorizer.MachineAgent = true + anAuthorizer.UnitAgent = false + anUpgrader, err := upgrader.NewUnitUpgraderAPI(s.State, s.resources, anAuthorizer, "") + c.Check(err, gc.NotNil) + c.Check(anUpgrader, gc.IsNil) + c.Assert(err, gc.ErrorMatches, "permission denied") +} + +func (s *unitUpgraderSuite) TestWatchAPIVersionRefusesWrongAgent(c *gc.C) { + // We are a unit agent, but not the one we are trying to track + anAuthorizer := s.authorizer + anAuthorizer.Tag = "unit-wordpress-12354" + anUpgrader, err := upgrader.NewUnitUpgraderAPI(s.State, s.resources, anAuthorizer, "") + c.Check(err, gc.IsNil) + args := params.Entities{ + Entities: []params.Entity{{Tag: s.rawUnit.Tag()}}, + } + results, err := anUpgrader.WatchAPIVersion(args) + // It is not an error to make the request, but the specific item is rejected + c.Assert(err, gc.IsNil) + c.Check(results.Results, gc.HasLen, 1) + c.Check(results.Results[0].NotifyWatcherId, gc.Equals, "") + c.Assert(results.Results[0].Error, gc.DeepEquals, apiservertesting.ErrUnauthorized) +} + +func (s *unitUpgraderSuite) TestToolsNothing(c *gc.C) { + // Not an error to watch nothing + results, err := s.upgrader.Tools(params.Entities{}) + c.Assert(err, gc.IsNil) + c.Check(results.Results, gc.HasLen, 0) +} + +func (s *unitUpgraderSuite) TestToolsRefusesWrongAgent(c *gc.C) { + anAuthorizer := s.authorizer + anAuthorizer.Tag = "unit-wordpress-12354" + anUpgrader, err := upgrader.NewUnitUpgraderAPI(s.State, s.resources, anAuthorizer, "") + c.Check(err, gc.IsNil) + args := params.Entities{ + Entities: []params.Entity{{Tag: s.rawUnit.Tag()}}, + } + results, err := anUpgrader.Tools(args) + // It is not an error to make the request, but the specific item is rejected + c.Assert(err, gc.IsNil) + c.Check(results.Results, gc.HasLen, 1) + toolResult := results.Results[0] + c.Assert(toolResult.Error, gc.DeepEquals, apiservertesting.ErrUnauthorized) +} + +func (s *unitUpgraderSuite) TestToolsForAgent(c *gc.C) { + agent := params.Entity{Tag: s.rawUnit.Tag()} + + // The machine must have its existing tools set before we query for the + // next tools. This is so that we can grab Arch and Series without + // having to pass it in again + err := s.rawMachine.SetAgentVersion(version.Current) + c.Assert(err, gc.IsNil) + + args := params.Entities{Entities: []params.Entity{agent}} + results, err := s.upgrader.Tools(args) + c.Assert(err, gc.IsNil) + assertTools := func() { + c.Check(results.Results, gc.HasLen, 1) + c.Assert(results.Results[0].Error, gc.IsNil) + agentTools := results.Results[0].Tools + c.Check(agentTools, gc.DeepEquals, s.fakeTools) + } + assertTools() +} + +func (s *unitUpgraderSuite) TestSetToolsNothing(c *gc.C) { + // Not an error to watch nothing + results, err := s.upgrader.SetTools(params.EntitiesVersion{}) + c.Assert(err, gc.IsNil) + c.Check(results.Results, gc.HasLen, 0) +} + +func (s *unitUpgraderSuite) TestSetToolsRefusesWrongAgent(c *gc.C) { + anAuthorizer := s.authorizer + anAuthorizer.Tag = "unit-wordpress-12354" + anUpgrader, err := upgrader.NewUnitUpgraderAPI(s.State, s.resources, anAuthorizer, "") + c.Check(err, gc.IsNil) + args := params.EntitiesVersion{ + AgentTools: []params.EntityVersion{{ + Tag: s.rawUnit.Tag(), + Tools: ¶ms.Version{ + Version: version.Current, + }, + }}, + } + + results, err := anUpgrader.SetTools(args) + c.Assert(results.Results, gc.HasLen, 1) + c.Assert(results.Results[0].Error, gc.DeepEquals, apiservertesting.ErrUnauthorized) +} + +func (s *unitUpgraderSuite) TestSetTools(c *gc.C) { + cur := version.Current + _, err := s.rawUnit.AgentTools() + c.Assert(err, jc.Satisfies, errors.IsNotFoundError) + args := params.EntitiesVersion{ + AgentTools: []params.EntityVersion{{ + Tag: s.rawUnit.Tag(), + Tools: ¶ms.Version{ + Version: cur, + }}, + }, + } + results, err := s.upgrader.SetTools(args) + c.Assert(err, gc.IsNil) + c.Assert(results.Results, gc.HasLen, 1) + c.Assert(results.Results[0].Error, gc.IsNil) + // Check that the new value actually got set, we must Refresh because + // it was set on a different Machine object + err = s.rawUnit.Refresh() + c.Assert(err, gc.IsNil) + realTools, err := s.rawUnit.AgentTools() + c.Assert(err, gc.IsNil) + c.Check(realTools.Version.Arch, gc.Equals, cur.Arch) + c.Check(realTools.Version.Series, gc.Equals, cur.Series) + c.Check(realTools.Version.Major, gc.Equals, cur.Major) + c.Check(realTools.Version.Minor, gc.Equals, cur.Minor) + c.Check(realTools.Version.Patch, gc.Equals, cur.Patch) + c.Check(realTools.Version.Build, gc.Equals, cur.Build) + c.Check(realTools.URL, gc.Equals, "") +} + +func (s *unitUpgraderSuite) TestDesiredVersionNothing(c *gc.C) { + // Not an error to watch nothing + results, err := s.upgrader.DesiredVersion(params.Entities{}) + c.Assert(err, gc.IsNil) + c.Check(results.Results, gc.HasLen, 0) +} + +func (s *unitUpgraderSuite) TestDesiredVersionRefusesWrongAgent(c *gc.C) { + anAuthorizer := s.authorizer + anAuthorizer.Tag = "unit-wordpress-12354" + anUpgrader, err := upgrader.NewUnitUpgraderAPI(s.State, s.resources, anAuthorizer, "") + c.Check(err, gc.IsNil) + args := params.Entities{ + Entities: []params.Entity{{Tag: s.rawUnit.Tag()}}, + } + results, err := anUpgrader.DesiredVersion(args) + // It is not an error to make the request, but the specific item is rejected + c.Assert(err, gc.IsNil) + c.Check(results.Results, gc.HasLen, 1) + toolResult := results.Results[0] + c.Assert(toolResult.Error, gc.DeepEquals, apiservertesting.ErrUnauthorized) +} + +func (s *unitUpgraderSuite) TestDesiredVersionNoticesMixedAgents(c *gc.C) { + err := s.rawMachine.SetAgentVersion(version.Current) + c.Assert(err, gc.IsNil) + args := params.Entities{Entities: []params.Entity{ + {Tag: s.rawUnit.Tag()}, + {Tag: "unit-wordpress-12345"}, + }} + results, err := s.upgrader.DesiredVersion(args) + c.Assert(err, gc.IsNil) + c.Check(results.Results, gc.HasLen, 2) + c.Assert(results.Results[0].Error, gc.IsNil) + agentVersion := results.Results[0].Version + c.Assert(agentVersion, gc.NotNil) + c.Check(*agentVersion, gc.DeepEquals, version.Current.Number) + + c.Assert(results.Results[1].Error, gc.DeepEquals, apiservertesting.ErrUnauthorized) + c.Assert(results.Results[1].Version, gc.IsNil) + +} + +func (s *unitUpgraderSuite) TestDesiredVersionForAgent(c *gc.C) { + err := s.rawMachine.SetAgentVersion(version.Current) + c.Assert(err, gc.IsNil) + args := params.Entities{Entities: []params.Entity{{Tag: s.rawUnit.Tag()}}} + results, err := s.upgrader.DesiredVersion(args) + c.Assert(err, gc.IsNil) + c.Check(results.Results, gc.HasLen, 1) + c.Assert(results.Results[0].Error, gc.IsNil) + agentVersion := results.Results[0].Version + c.Assert(agentVersion, gc.NotNil) + c.Check(*agentVersion, gc.DeepEquals, version.Current.Number) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/upgrader/upgrader.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/upgrader/upgrader.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/upgrader/upgrader.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/upgrader/upgrader.go 2014-02-20 16:10:15.000000000 +0000 @@ -14,9 +14,17 @@ "launchpad.net/juju-core/version" ) +type Upgrader interface { + WatchAPIVersion(args params.Entities) (params.NotifyWatchResults, error) + DesiredVersion(args params.Entities) (params.VersionResults, error) + Tools(args params.Entities) (params.ToolsResults, error) + SetTools(args params.EntitiesVersion) (params.ErrorResults, error) +} + // UpgraderAPI provides access to the Upgrader API facade. type UpgraderAPI struct { *common.ToolsGetter + *common.ToolsSetter st *state.State resources *common.Resources @@ -29,14 +37,15 @@ resources *common.Resources, authorizer common.Authorizer, ) (*UpgraderAPI, error) { - if !authorizer.AuthMachineAgent() && !authorizer.AuthUnitAgent() { + if !authorizer.AuthMachineAgent() { return nil, common.ErrPerm } - getCanRead := func() (common.AuthFunc, error) { + getCanReadWrite := func() (common.AuthFunc, error) { return authorizer.AuthOwner, nil } return &UpgraderAPI{ - ToolsGetter: common.NewToolsGetter(st, getCanRead), + ToolsGetter: common.NewToolsGetter(st, getCanReadWrite), + ToolsSetter: common.NewToolsSetter(st, getCanReadWrite), st: st, resources: resources, authorizer: authorizer, @@ -102,38 +111,3 @@ } return params.VersionResults{results}, nil } - -// SetTools updates the recorded tools version for the agents. -func (u *UpgraderAPI) SetTools(args params.EntitiesVersion) (params.ErrorResults, error) { - results := params.ErrorResults{ - Results: make([]params.ErrorResult, len(args.AgentTools)), - } - for i, agentTools := range args.AgentTools { - err := u.setOneAgentVersion(agentTools.Tag, agentTools.Tools.Version) - results.Results[i].Error = common.ServerError(err) - } - return results, nil -} - -func (u *UpgraderAPI) setOneAgentVersion(tag string, vers version.Binary) error { - if !u.authorizer.AuthOwner(tag) { - return common.ErrPerm - } - entity, err := u.findEntity(tag) - if err != nil { - return err - } - return entity.SetAgentVersion(vers) -} - -func (u *UpgraderAPI) findEntity(tag string) (state.AgentTooler, error) { - entity0, err := u.st.FindEntity(tag) - if err != nil { - return nil, err - } - entity, ok := entity0.(state.AgentTooler) - if !ok { - return nil, common.NotSupportedError(tag, "agent tools") - } - return entity, nil -} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/upgrader/upgrader_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/upgrader/upgrader_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/apiserver/upgrader/upgrader_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/apiserver/upgrader/upgrader_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -88,10 +88,9 @@ wc.AssertClosed() } -func (s *upgraderSuite) TestUpgraderAPIRefusesNonAgent(c *gc.C) { - // We aren't even a machine agent +func (s *upgraderSuite) TestUpgraderAPIRefusesNonMachineAgent(c *gc.C) { anAuthorizer := s.authorizer - anAuthorizer.UnitAgent = false + anAuthorizer.UnitAgent = true anAuthorizer.MachineAgent = false anUpgrader, err := upgrader.NewUpgraderAPI(s.State, s.resources, anAuthorizer) c.Check(err, gc.NotNil) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/compat_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/compat_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/compat_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/compat_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -36,7 +36,7 @@ func (s *compatSuite) SetUpTest(c *gc.C) { s.LoggingSuite.SetUpTest(c) s.MgoSuite.SetUpTest(c) - s.state = TestingInitialize(c, nil) + s.state = TestingInitialize(c, nil, Policy(nil)) env, err := s.state.Environment() c.Assert(err, gc.IsNil) s.env = env diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/conn_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/conn_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/conn_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/conn_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -9,6 +9,8 @@ "labix.org/v2/mgo" gc "launchpad.net/gocheck" + "launchpad.net/juju-core/environs/config" + "launchpad.net/juju-core/errors" "launchpad.net/juju-core/state" "launchpad.net/juju-core/testing" "launchpad.net/juju-core/testing/testbase" @@ -32,6 +34,7 @@ units *mgo.Collection stateServers *mgo.Collection State *state.State + policy mockPolicy } func (cs *ConnSuite) SetUpSuite(c *gc.C) { @@ -47,7 +50,8 @@ func (cs *ConnSuite) SetUpTest(c *gc.C) { cs.LoggingSuite.SetUpTest(c) cs.MgoSuite.SetUpTest(c) - cs.State = state.TestingInitialize(c, nil) + cs.policy = mockPolicy{} + cs.State = state.TestingInitialize(c, nil, &cs.policy) cs.annotations = cs.MgoSuite.Session.DB("juju").C("annotations") cs.charms = cs.MgoSuite.Session.DB("juju").C("charms") cs.machines = cs.MgoSuite.Session.DB("juju").C("machines") @@ -88,3 +92,14 @@ func (s *ConnSuite) AddMetaCharm(c *gc.C, name, metaYaml string, revsion int) *state.Charm { return state.AddCustomCharm(c, s.State, name, "metadata.yaml", metaYaml, "quantal", revsion) } + +type mockPolicy struct { + getPrechecker func(*config.Config) (state.Prechecker, error) +} + +func (p *mockPolicy) Prechecker(cfg *config.Config) (state.Prechecker, error) { + if p.getPrechecker != nil { + return p.getPrechecker(cfg) + } + return nil, errors.NewNotImplementedError("Prechecker") +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/environ.go juju-core-1.17.3/src/launchpad.net/juju-core/state/environ.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/environ.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/environ.go 2014-02-20 16:10:15.000000000 +0000 @@ -55,6 +55,11 @@ return e.doc.UUID } +// Name returns the human friendly name of the environment. +func (e *Environment) Name() string { + return e.doc.Name +} + // Life returns whether the environment is Alive, Dying or Dead. func (e *Environment) Life() Life { return e.doc.Life diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/environ_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/environ_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/environ_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/environ_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -28,6 +28,10 @@ c.Assert(s.env.Tag(), gc.Equals, expected) } +func (s *EnvironSuite) TestName(c *gc.C) { + c.Assert(s.env.Name(), gc.Equals, "testenv") +} + func (s *EnvironSuite) TestUUID(c *gc.C) { uuidA := s.env.UUID() c.Assert(uuidA, gc.HasLen, 36) @@ -36,7 +40,7 @@ s.State.Close() s.MgoSuite.TearDownTest(c) s.MgoSuite.SetUpTest(c) - s.State = state.TestingInitialize(c, nil) + s.State = state.TestingInitialize(c, nil, state.Policy(nil)) env, err := s.State.Environment() c.Assert(err, gc.IsNil) uuidB := env.UUID() diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/export_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/export_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/export_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/export_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -10,9 +10,9 @@ "path/filepath" "labix.org/v2/mgo" + "labix.org/v2/mgo/txn" gc "launchpad.net/gocheck" - "labix.org/v2/mgo/txn" "launchpad.net/juju-core/charm" "launchpad.net/juju-core/environs/config" "launchpad.net/juju-core/instance" @@ -94,14 +94,22 @@ }) } +// SetPolicy updates the State's policy field to the +// given Policy, and returns the old value. +func SetPolicy(st *State, p Policy) Policy { + old := st.policy + st.policy = p + return old +} + // TestingInitialize initializes the state and returns it. If state was not // already initialized, and cfg is nil, the minimal default environment // configuration will be used. -func TestingInitialize(c *gc.C, cfg *config.Config) *State { +func TestingInitialize(c *gc.C, cfg *config.Config, policy Policy) *State { if cfg == nil { cfg = testing.EnvironConfig(c) } - st, err := Initialize(TestingStateInfo(), cfg, TestingDialOpts()) + st, err := Initialize(TestingStateInfo(), cfg, TestingDialOpts(), policy) c.Assert(err, gc.IsNil) return st } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/initialize_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/initialize_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/initialize_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/initialize_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -37,7 +37,7 @@ s.LoggingSuite.SetUpTest(c) s.MgoSuite.SetUpTest(c) var err error - s.State, err = state.Open(state.TestingStateInfo(), state.TestingDialOpts()) + s.State, err = state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) } @@ -59,7 +59,7 @@ cfg := testing.EnvironConfig(c) initial := cfg.AllAttrs() - st, err := state.Initialize(state.TestingStateInfo(), cfg, state.TestingDialOpts()) + st, err := state.Initialize(state.TestingStateInfo(), cfg, state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) c.Assert(st, gc.NotNil) err = st.Close() @@ -85,7 +85,7 @@ func (s *InitializeSuite) TestDoubleInitializeConfig(c *gc.C) { cfg := testing.EnvironConfig(c) initial := cfg.AllAttrs() - st := state.TestingInitialize(c, cfg) + st := state.TestingInitialize(c, cfg, state.Policy(nil)) st.Close() // A second initialize returns an open *State, but ignores its params. @@ -93,7 +93,7 @@ // for originally... cfg, err := cfg.Apply(map[string]interface{}{"authorized-keys": "something-else"}) c.Assert(err, gc.IsNil) - st, err = state.Initialize(state.TestingStateInfo(), cfg, state.TestingDialOpts()) + st, err = state.Initialize(state.TestingStateInfo(), cfg, state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) c.Assert(st, gc.NotNil) st.Close() @@ -108,11 +108,11 @@ good := testing.EnvironConfig(c) bad, err := good.Apply(map[string]interface{}{"admin-secret": "foo"}) - _, err = state.Initialize(state.TestingStateInfo(), bad, state.TestingDialOpts()) + _, err = state.Initialize(state.TestingStateInfo(), bad, state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.ErrorMatches, "admin-secret should never be written to the state") // admin-secret blocks SetEnvironConfig. - st := state.TestingInitialize(c, good) + st := state.TestingInitialize(c, good, state.Policy(nil)) st.Close() err = s.State.SetEnvironConfig(bad, good) c.Assert(err, gc.ErrorMatches, "admin-secret should never be written to the state") @@ -131,11 +131,11 @@ bad, err := config.New(config.NoDefaults, attrs) c.Assert(err, gc.IsNil) - _, err = state.Initialize(state.TestingStateInfo(), bad, state.TestingDialOpts()) + _, err = state.Initialize(state.TestingStateInfo(), bad, state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.ErrorMatches, "agent-version must always be set in state") // Bad agent-version blocks SetEnvironConfig. - st := state.TestingInitialize(c, good) + st := state.TestingInitialize(c, good, state.Policy(nil)) st.Close() err = s.State.SetEnvironConfig(bad, good) c.Assert(err, gc.ErrorMatches, "agent-version must always be set in state") diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/machine.go juju-core-1.17.3/src/launchpad.net/juju-core/state/machine.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/machine.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/machine.go 2014-02-20 16:10:15.000000000 +0000 @@ -39,7 +39,7 @@ JobManageEnviron // Deprecated in 1.18. - JobManageState + JobManageStateDeprecated ) var jobNames = map[MachineJob]params.MachineJob{ @@ -47,7 +47,7 @@ JobManageEnviron: params.JobManageEnviron, // Deprecated in 1.18. - JobManageState: params.JobManageState, + JobManageStateDeprecated: params.JobManageStateDeprecated, } // AllJobs returns all supported machine jobs. @@ -89,6 +89,7 @@ Tools *tools.Tools `bson:",omitempty"` Jobs []MachineJob NoVote bool + HasVote bool PasswordHash string Clean bool // We store 2 different sets of addresses for the machine, obtained @@ -210,6 +211,29 @@ return hasJob(m.doc.Jobs, JobManageEnviron) && !m.doc.NoVote } +// HasVote reports whether that machine is currently a voting +// member of the replica set. +func (m *Machine) HasVote() bool { + return m.doc.HasVote +} + +// SetHasVote sets whether the machine is currently a voting +// member of the replica set. It should only be called +// from the worker that maintains the replica set. +func (m *Machine) SetHasVote(hasVote bool) error { + ops := []txn.Op{{ + C: m.st.machines.Name, + Id: m.doc.Id, + Assert: notDeadDoc, + Update: D{{"$set", D{{"hasvote", hasVote}}}}, + }} + if err := m.st.runTransaction(ops); err != nil { + return fmt.Errorf("cannot set HasVote of machine %v: %v", m, onAbort(err, errDead)) + } + m.doc.HasVote = hasVote + return nil +} + // IsManager returns true if the machine has JobManageEnviron. func (m *Machine) IsManager() bool { return hasJob(m.doc.Jobs, JobManageEnviron) @@ -461,6 +485,7 @@ {{"principals", D{{"$size", 0}}}}, {{"principals", D{{"$exists", false}}}}, }}, + {"hasvote", D{{"$ne", true}}}, } // 3 attempts: one with original data, one with refreshed data, and a final // one intended to determine the cause of failure of the preceding attempt. @@ -502,6 +527,9 @@ // machine is not voting) return fmt.Errorf("machine %s is required by the environment", m.doc.Id) } + if m.doc.HasVote { + return fmt.Errorf("machine %s is a voting replica set member", m.doc.Id) + } if len(m.doc.Principals) != 0 { return &HasAssignedUnitsError{ MachineId: m.doc.Id, diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/machine_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/machine_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/machine_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/machine_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -227,6 +227,49 @@ c.Assert(err, gc.IsNil) } +func (s *MachineSuite) TestHasVote(c *gc.C) { + c.Assert(s.machine.HasVote(), jc.IsFalse) + + // Make another machine value so that + // it won't have the cached HasVote value. + m, err := s.State.Machine(s.machine.Id()) + c.Assert(err, gc.IsNil) + + err = s.machine.SetHasVote(true) + c.Assert(err, gc.IsNil) + c.Assert(s.machine.HasVote(), jc.IsTrue) + c.Assert(m.HasVote(), jc.IsFalse) + + err = m.Refresh() + c.Assert(err, gc.IsNil) + c.Assert(m.HasVote(), jc.IsTrue) + + err = m.SetHasVote(false) + c.Assert(err, gc.IsNil) + c.Assert(m.HasVote(), jc.IsFalse) + + c.Assert(s.machine.HasVote(), jc.IsTrue) + err = s.machine.Refresh() + c.Assert(err, gc.IsNil) + c.Assert(s.machine.HasVote(), jc.IsFalse) +} + +func (s *MachineSuite) TestCannotDestroyMachineWithVote(c *gc.C) { + err := s.machine.SetHasVote(true) + c.Assert(err, gc.IsNil) + + // Make another machine value so that + // it won't have the cached HasVote value. + m, err := s.State.Machine(s.machine.Id()) + c.Assert(err, gc.IsNil) + + err = s.machine.Destroy() + c.Assert(err, gc.ErrorMatches, "machine "+s.machine.Id()+" is a voting replica set member") + + err = m.Destroy() + c.Assert(err, gc.ErrorMatches, "machine "+s.machine.Id()+" is a voting replica set member") +} + func (s *MachineSuite) TestRemoveAbort(c *gc.C) { err := s.machine.EnsureDead() c.Assert(err, gc.IsNil) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/megawatcher_internal_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/megawatcher_internal_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/megawatcher_internal_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/megawatcher_internal_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -47,7 +47,7 @@ func (s *storeManagerStateSuite) SetUpTest(c *gc.C) { s.LoggingSuite.SetUpTest(c) s.MgoSuite.SetUpTest(c) - s.State = TestingInitialize(c, nil) + s.State = TestingInitialize(c, nil, Policy(nil)) s.State.AddUser("admin", "pass") } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/open.go juju-core-1.17.3/src/launchpad.net/juju-core/state/open.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/open.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/open.go 2014-02-20 16:10:15.000000000 +0000 @@ -23,6 +23,13 @@ "launchpad.net/juju-core/utils" ) +// mongoSocketTimeout should be long enough that +// even a slow mongo server will respond in that +// length of time. Since mongo servers ping themselves +// every 10 seconds, that seems like a reasonable +// default. +const mongoSocketTimeout = 10 * time.Second + // Info encapsulates information about cluster of // servers holding juju state and can be used to make a // connection to that cluster. @@ -62,8 +69,13 @@ // Open connects to the server described by the given // info, waits for it to be initialized, and returns a new State // representing the environment connected to. -// It returns unauthorizedError if access is unauthorized. -func Open(info *Info, opts DialOpts) (*State, error) { +// +// A policy may be provided, which will be used to validate and +// modify behaviour of certain operations in state. A nil policy +// may be provided. +// +// Open returns unauthorizedError if access is unauthorized. +func Open(info *Info, opts DialOpts, policy Policy) (*State, error) { logger.Infof("opening state; mongo addresses: %q; entity %q", info.Addrs, info.Tag) if len(info.Addrs) == 0 { return nil, stderrors.New("no mongo addresses") @@ -103,19 +115,20 @@ return nil, err } logger.Infof("connection established") - st, err := newState(session, info) + st, err := newState(session, info, policy) if err != nil { session.Close() return nil, err } + session.SetSocketTimeout(mongoSocketTimeout) return st, nil } // Initialize sets up an initial empty state and returns it. // This needs to be performed only once for a given environment. // It returns unauthorizedError if access is unauthorized. -func Initialize(info *Info, cfg *config.Config, opts DialOpts) (rst *State, err error) { - st, err := Open(info, opts) +func Initialize(info *Info, cfg *config.Config, opts DialOpts, policy Policy) (rst *State, err error) { + st, err := Open(info, opts, policy) if err != nil { return nil, err } @@ -209,7 +222,7 @@ return false } -func newState(session *mgo.Session, info *Info) (*State, error) { +func newState(session *mgo.Session, info *Info, policy Policy) (*State, error) { db := session.DB("juju") pdb := session.DB("presence") if info.Tag != "" { @@ -227,6 +240,7 @@ } st := &State{ info: info, + policy: policy, db: db, environments: db.C("environments"), charms: db.C("charms"), diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/policy.go juju-core-1.17.3/src/launchpad.net/juju-core/state/policy.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/policy.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/policy.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,63 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package state + +import ( + "fmt" + + "launchpad.net/juju-core/constraints" + "launchpad.net/juju-core/environs/config" + "launchpad.net/juju-core/errors" +) + +// Policy is an interface provided to State that may +// be consulted by State to validate or modify the +// behaviour of certain operations. +// +// If a Policy implementation does not implement one +// of the methods, it must return an error that +// satisfies errors.IsNotImplemented, and will thus +// be ignored. Any other error will cause an error +// in the use of the policy. +type Policy interface { + // Prechecker takes a *config.Config and returns + // a (possibly nil) Prechecker or an error. + Prechecker(*config.Config) (Prechecker, error) +} + +// Prechecker is a policy interface that is provided to State +// to perform pre-flight checking of instance creation. +type Prechecker interface { + // PrecheckInstance performs a preflight check on the specified + // series and constraints, ensuring that they are possibly valid for + // creating an instance in this environment. + // + // PrecheckInstance is best effort, and not guaranteed to eliminate + // all invalid parameters. If PrecheckInstance returns nil, it is not + // guaranteed that the constraints are valid; if a non-nil error is + // returned, then the constraints are definitely invalid. + PrecheckInstance(series string, cons constraints.Value) error +} + +// precheckInstance calls the state's assigned policy, if non-nil, to obtain +// a Prechecker, and calls PrecheckInstance if a non-nil Prechecker is returned. +func (st *State) precheckInstance(series string, cons constraints.Value) error { + if st.policy == nil { + return nil + } + cfg, err := st.EnvironConfig() + if err != nil { + return err + } + prechecker, err := st.policy.Prechecker(cfg) + if errors.IsNotImplementedError(err) { + return nil + } else if err != nil { + return err + } + if prechecker == nil { + return fmt.Errorf("policy returned nil prechecker without an error") + } + return prechecker.PrecheckInstance(series, cons) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/prechecker_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/prechecker_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/prechecker_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/prechecker_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,132 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package state_test + +import ( + "fmt" + + gc "launchpad.net/gocheck" + + "launchpad.net/juju-core/constraints" + "launchpad.net/juju-core/environs/config" + "launchpad.net/juju-core/errors" + "launchpad.net/juju-core/instance" + "launchpad.net/juju-core/state" +) + +type PrecheckerSuite struct { + ConnSuite + prechecker mockPrechecker +} + +var _ = gc.Suite(&PrecheckerSuite{}) + +type mockPrechecker struct { + precheckInstanceError error + precheckInstanceSeries string + precheckInstanceConstraints constraints.Value +} + +func (p *mockPrechecker) PrecheckInstance(series string, cons constraints.Value) error { + p.precheckInstanceSeries = series + p.precheckInstanceConstraints = cons + return p.precheckInstanceError +} + +func (s *PrecheckerSuite) SetUpTest(c *gc.C) { + s.ConnSuite.SetUpTest(c) + s.prechecker = mockPrechecker{} + s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) { + return &s.prechecker, nil + } +} + +func (s *PrecheckerSuite) TestPrecheckInstance(c *gc.C) { + // PrecheckInstance should be called with the specified + // series, and the specified constraints merged with the + // environment constraints, when attempting to create an + // instance. + envCons := constraints.MustParse("mem=4G") + template, err := s.addOneMachine(c, envCons) + c.Assert(err, gc.IsNil) + c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series) + cons := template.Constraints.WithFallbacks(envCons) + c.Assert(s.prechecker.precheckInstanceConstraints, gc.DeepEquals, cons) +} + +func (s *PrecheckerSuite) TestPrecheckErrors(c *gc.C) { + // Ensure that AddOneMachine fails when PrecheckInstance returns an error. + s.prechecker.precheckInstanceError = fmt.Errorf("no instance for you") + _, err := s.addOneMachine(c, constraints.Value{}) + c.Assert(err, gc.ErrorMatches, ".*no instance for you") + + // If the policy's Prechecker method fails, that will be returned first. + s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) { + return nil, fmt.Errorf("no prechecker for you") + } + _, err = s.addOneMachine(c, constraints.Value{}) + c.Assert(err, gc.ErrorMatches, ".*no prechecker for you") +} + +func (s *PrecheckerSuite) TestPrecheckPrecheckerUnimplemented(c *gc.C) { + var precheckerErr error + s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) { + return nil, precheckerErr + } + _, err := s.addOneMachine(c, constraints.Value{}) + c.Assert(err, gc.ErrorMatches, "cannot add a new machine: policy returned nil prechecker without an error") + precheckerErr = errors.NewNotImplementedError("Prechecker") + _, err = s.addOneMachine(c, constraints.Value{}) + c.Assert(err, gc.IsNil) +} + +func (s *PrecheckerSuite) TestPrecheckNoPolicy(c *gc.C) { + s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) { + c.Errorf("should not have been invoked") + return nil, nil + } + state.SetPolicy(s.State, nil) + _, err := s.addOneMachine(c, constraints.Value{}) + c.Assert(err, gc.IsNil) +} + +func (s *PrecheckerSuite) addOneMachine(c *gc.C, envCons constraints.Value) (state.MachineTemplate, error) { + err := s.State.SetEnvironConstraints(envCons) + c.Assert(err, gc.IsNil) + oneJob := []state.MachineJob{state.JobHostUnits} + extraCons := constraints.MustParse("cpu-cores=4") + template := state.MachineTemplate{ + Series: "precise", + Constraints: extraCons, + Jobs: oneJob, + } + _, err = s.State.AddOneMachine(template) + return template, err +} + +func (s *PrecheckerSuite) TestPrecheckInstanceInjectMachine(c *gc.C) { + template := state.MachineTemplate{ + InstanceId: instance.Id("bootstrap"), + Series: "precise", + Nonce: state.BootstrapNonce, + Jobs: []state.MachineJob{state.JobManageEnviron}, + } + _, err := s.State.AddOneMachine(template) + c.Assert(err, gc.IsNil) + // PrecheckInstance should not have been called, as we've + // injected a machine with an existing instance. + c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, "") +} + +func (s *PrecheckerSuite) TestPrecheckContainerNewMachine(c *gc.C) { + // Attempting to add a container to a new machine should cause + // PrecheckInstance to be called. + template := state.MachineTemplate{ + Series: "precise", + Jobs: []state.MachineJob{state.JobHostUnits}, + } + _, err := s.State.AddMachineInsideNewMachine(template, template, instance.LXC) + c.Assert(err, gc.IsNil) + c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/settings_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/settings_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/settings_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/settings_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,8 +4,6 @@ package state import ( - "time" - "labix.org/v2/mgo/txn" gc "launchpad.net/gocheck" @@ -37,7 +35,7 @@ // connecting to the testing state server. func TestingDialOpts() DialOpts { return DialOpts{ - Timeout: 100 * time.Millisecond, + Timeout: testing.LongWait, } } @@ -55,7 +53,7 @@ s.LoggingSuite.SetUpTest(c) s.MgoSuite.SetUpTest(c) // TODO(dfc) this logic is duplicated with the metawatcher_test. - state, err := Open(TestingStateInfo(), TestingDialOpts()) + state, err := Open(TestingStateInfo(), TestingDialOpts(), Policy(nil)) c.Assert(err, gc.IsNil) s.state = state diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/state.go juju-core-1.17.3/src/launchpad.net/juju-core/state/state.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/state.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/state.go 2014-02-20 16:10:15.000000000 +0000 @@ -44,6 +44,7 @@ // managed by juju. type State struct { info *Info + policy Policy db *mgo.Database environments *mgo.Collection charms *mgo.Collection @@ -116,6 +117,14 @@ return st.db.Session.Ping() } +// MongoSession returns the underlying mongodb session +// used by the state. It is exposed so that external code +// can maintain the mongo replica set and should not +// otherwise be used. +func (st *State) MongoSession() *mgo.Session { + return st.db.Session +} + func (st *State) Watch() *multiwatcher.Watcher { st.mu.Lock() if st.allManager == nil { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/state_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/state_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/state_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/state_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -54,12 +54,17 @@ func (s *StateSuite) TestDialAgain(c *gc.C) { // Ensure idempotent operations on Dial are working fine. for i := 0; i < 2; i++ { - st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts()) + st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) c.Assert(st.Close(), gc.IsNil) } } +func (s *StateSuite) TestMongoSession(c *gc.C) { + session := s.State.MongoSession() + c.Assert(session.Ping(), gc.IsNil) +} + func (s *StateSuite) TestAddresses(c *gc.C) { var err error machines := make([]*state.Machine, 4) @@ -465,7 +470,7 @@ }{ {state.JobHostUnits, "JobHostUnits"}, {state.JobManageEnviron, "JobManageEnviron"}, - {state.JobManageState, "JobManageState"}, + {state.JobManageStateDeprecated, "JobManageState"}, {0, ""}, {5, ""}, } @@ -1645,6 +1650,37 @@ wc.AssertNoChange() } +func (s *StateSuite) TestWatchStateServerInfo(c *gc.C) { + _, err := s.State.AddMachine("quantal", state.JobManageEnviron) + c.Assert(err, gc.IsNil) + + w := s.State.WatchStateServerInfo() + defer statetesting.AssertStop(c, w) + + // Initial event. + wc := statetesting.NewNotifyWatcherC(c, s.State, w) + wc.AssertOneChange() + + info, err := s.State.StateServerInfo() + c.Assert(err, gc.IsNil) + c.Assert(info, jc.DeepEquals, &state.StateServerInfo{ + MachineIds: []string{"0"}, + VotingMachineIds: []string{"0"}, + }) + + err = s.State.EnsureAvailability(3, constraints.Value{}, "quantal") + c.Assert(err, gc.IsNil) + + wc.AssertOneChange() + + info, err = s.State.StateServerInfo() + c.Assert(err, gc.IsNil) + c.Assert(info, jc.DeepEquals, &state.StateServerInfo{ + MachineIds: []string{"0", "1", "2"}, + VotingMachineIds: []string{"0", "1", "2"}, + }) +} + type attrs map[string]interface{} func (s *StateSuite) TestWatchEnvironConfig(c *gc.C) { @@ -1818,7 +1854,7 @@ } func tryOpenState(info *state.Info) error { - st, err := state.Open(info, state.TestingDialOpts()) + st, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil)) if err == nil { st.Close() } @@ -1845,7 +1881,7 @@ info.Addrs = []string{"0.1.2.3:1234"} st, err := state.Open(info, state.DialOpts{ Timeout: 1 * time.Millisecond, - }) + }, state.Policy(nil)) if err == nil { st.Close() } @@ -1861,7 +1897,7 @@ t0 := time.Now() st, err := state.Open(info, state.DialOpts{ Timeout: 1 * time.Millisecond, - }) + }, state.Policy(nil)) if err == nil { st.Close() } @@ -1957,7 +1993,7 @@ func testSetMongoPassword(c *gc.C, getEntity func(st *state.State) (entity, error)) { info := state.TestingStateInfo() - st, err := state.Open(info, state.TestingDialOpts()) + st, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) defer st.Close() // Turn on fully-authenticated mode. @@ -1978,7 +2014,7 @@ // Check that we can log in with the correct password. info.Password = "foo" - st1, err := state.Open(info, state.TestingDialOpts()) + st1, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) defer st1.Close() @@ -2598,7 +2634,7 @@ // interact with the closed state, causing it to return an // unexpected error (often "Closed explictly"). func testWatcherDiesWhenStateCloses(c *gc.C, startWatcher func(c *gc.C, st *state.State) waiter) { - st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts()) + st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) watcher := startWatcher(c, st) err = st.Close() @@ -2640,7 +2676,7 @@ c.Assert(err, gc.NotNil) c.Assert(info, gc.IsNil) - st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts()) + st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) defer st.Close() @@ -2659,7 +2695,7 @@ c.Assert(err, gc.IsNil) c.Assert(info, jc.DeepEquals, &state.StateServerInfo{}) - st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts()) + st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) defer st.Close() @@ -2690,7 +2726,7 @@ c.Assert(info.MachineIds, gc.HasLen, 0) c.Assert(info.VotingMachineIds, gc.HasLen, 0) - st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts()) + st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) defer st.Close() diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/unit_test.go juju-core-1.17.3/src/launchpad.net/juju-core/state/unit_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/unit_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/unit_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -633,7 +633,7 @@ c.Assert(err, gc.IsNil) info := state.TestingStateInfo() - st, err := state.Open(info, state.TestingDialOpts()) + st, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) defer st.Close() // Turn on fully-authenticated mode. @@ -663,7 +663,7 @@ // Connect as the machine entity. info.Tag = m.Tag() info.Password = "foo" - st1, err := state.Open(info, state.TestingDialOpts()) + st1, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) defer st1.Close() @@ -678,7 +678,7 @@ // that entity, change the password for a new unit. info.Tag = unit.Tag() info.Password = "bar" - st2, err := state.Open(info, state.TestingDialOpts()) + st2, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil)) c.Assert(err, gc.IsNil) defer st2.Close() diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/state/watcher.go juju-core-1.17.3/src/launchpad.net/juju-core/state/watcher.go --- juju-core-1.17.2/src/launchpad.net/juju-core/state/watcher.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/state/watcher.go 2014-02-20 16:10:15.000000000 +0000 @@ -1059,6 +1059,10 @@ return newEntityWatcher(m.st, m.st.instanceData, m.doc.Id) } +func (st *State) WatchStateServerInfo() NotifyWatcher { + return newEntityWatcher(st, st.stateServers, environGlobalKey) +} + // Watch returns a watcher for observing changes to a machine. func (m *Machine) Watch() NotifyWatcher { return newEntityWatcher(m.st, m.st.machines, m.doc.Id) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/testing/checkers/deepequal_test.go juju-core-1.17.3/src/launchpad.net/juju-core/testing/checkers/deepequal_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/testing/checkers/deepequal_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/testing/checkers/deepequal_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -11,9 +11,10 @@ package checkers_test import ( - "launchpad.net/juju-core/testing/checkers" "regexp" "testing" + + "launchpad.net/juju-core/testing/checkers" ) func deepEqual(a1, a2 interface{}) bool { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/testing/constants.go juju-core-1.17.3/src/launchpad.net/juju-core/testing/constants.go --- juju-core-1.17.2/src/launchpad.net/juju-core/testing/constants.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/testing/constants.go 2014-02-20 16:10:15.000000000 +0000 @@ -25,3 +25,6 @@ Total: LongWait, Delay: ShortWait, } + +// SupportedSeries lists the series known to Juju. +var SupportedSeries = []string{"precise", "quantal", "raring", "saucy", "trusty"} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/testing/environ.go juju-core-1.17.3/src/launchpad.net/juju-core/testing/environ.go --- juju-core-1.17.2/src/launchpad.net/juju-core/testing/environ.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/testing/environ.go 2014-02-20 16:10:15.000000000 +0000 @@ -13,12 +13,20 @@ "launchpad.net/juju-core/environs/config" "launchpad.net/juju-core/juju/osenv" "launchpad.net/juju-core/testing/testbase" + "launchpad.net/juju-core/utils/ssh" ) // FakeAuthKeys holds the authorized key used for testing // purposes in FakeConfig. It is valid for parsing with the utils/ssh // authorized-key utilities. -const FakeAuthKeys = `ssh-rsa BBBB jo@bloggs.com` +const FakeAuthKeys = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQDP8fPSAMFm2PQGoVUks/FENVUMww1QTK6m++Y2qX9NGHm43kwEzxfoWR77wo6fhBhgFHsQ6ogE/cYLx77hOvjTchMEP74EVxSce0qtDjI7SwYbOpAButRId3g/Ef4STz8= joe@0.1.2.4` + +func init() { + _, err := ssh.ParseAuthorisedKey(FakeAuthKeys) + if err != nil { + panic("FakeAuthKeys does not hold a valid authorized key: " + err.Error()) + } +} // FakeConfig() returns an environment configuration for a // fake provider with all required attributes set. diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/testing/testbase/cleanup.go juju-core-1.17.3/src/launchpad.net/juju-core/testing/testbase/cleanup.go --- juju-core-1.17.2/src/launchpad.net/juju-core/testing/testbase/cleanup.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/testing/testbase/cleanup.go 2014-02-20 16:10:15.000000000 +0000 @@ -62,6 +62,13 @@ s.AddCleanup(func(*gc.C) { restore() }) } +// PatchEnvPathPrepend prepends the given path to the environment $PATH and restores the +// original path on test teardown. +func (s *CleanupSuite) PatchEnvPathPrepend(dir string) { + restore := PatchEnvPathPrepend(dir) + s.AddCleanup(func(*gc.C) { restore() }) +} + // PatchValue sets the 'dest' variable the the value passed in. The old value // is saved and returned to the original value at test tear down time using a // cleanup function. The value must be assignable to the element type of the diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/testing/testbase/patch.go juju-core-1.17.3/src/launchpad.net/juju-core/testing/testbase/patch.go --- juju-core-1.17.2/src/launchpad.net/juju-core/testing/testbase/patch.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/testing/testbase/patch.go 2014-02-20 16:10:15.000000000 +0000 @@ -6,6 +6,7 @@ import ( "os" "reflect" + "strings" ) // Restorer holds a function that can be used @@ -58,3 +59,14 @@ os.Setenv(name, oldValue) } } + +// PatchEnvPathPrepend provides a simple way to prepend path to the start of the +// PATH environment variable. Returns a function that restores the environment +// to what it was before. +func PatchEnvPathPrepend(dir string) Restorer { + return PatchEnvironment("PATH", joinPathLists(dir, os.Getenv("PATH"))) +} + +func joinPathLists(paths ...string) string { + return strings.Join(paths, string(os.PathListSeparator)) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/testing/testbase/patch_test.go juju-core-1.17.3/src/launchpad.net/juju-core/testing/testbase/patch_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/testing/testbase/patch_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/testing/testbase/patch_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -83,3 +83,18 @@ restore() c.Assert(order, gc.DeepEquals, []string{"second", "first"}) } + +func (*PatchEnvironmentSuite) TestPatchEnvPathPrepend(c *gc.C) { + oldPath := os.Getenv("PATH") + dir := "/bin/bar" + + // just in case something goes wrong + defer os.Setenv("PATH", oldPath) + + restore := testbase.PatchEnvPathPrepend(dir) + + expect := dir + string(os.PathListSeparator) + oldPath + c.Check(os.Getenv("PATH"), gc.Equals, expect) + restore() + c.Check(os.Getenv("PATH"), gc.Equals, oldPath) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/tools/list.go juju-core-1.17.3/src/launchpad.net/juju-core/tools/list.go --- juju-core-1.17.2/src/launchpad.net/juju-core/tools/list.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/tools/list.go 2014-02-20 16:10:15.000000000 +0000 @@ -74,7 +74,7 @@ var result List var best version.Number for _, tools := range src { - if best.Less(tools.Version.Number) { + if best.Compare(tools.Version.Number) < 0 { // Found new best number; reset result list. best = tools.Version.Number result = append(result[:0], tools) @@ -95,7 +95,7 @@ toolVersion := tool.Version.Number if newest == toolVersion { found = true - } else if newest.Less(toolVersion) && + } else if newest.Compare(toolVersion) < 0 && toolVersion.Major == newest.Major && toolVersion.Minor == newest.Minor { newest = toolVersion diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/doc.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/doc.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/doc.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/doc.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,14 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +// The upgrades package provides infrastructure to upgrade previous Juju +// deployments to the current Juju version. The upgrade is performed on +// a per node basis, across all of the running Juju machines. +// +// Important exported APIs include: +// PerformUpgrade, which is invoked on each node by the machine agent with: +// fromVersion - the Juju version from which the upgrade is occurring +// target - the type of Juju node being upgraded +// context - provides API access to Juju state servers +// +package upgrades diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/export_test.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/export_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/export_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/export_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,15 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades + +var ( + UpgradeOperations = &upgradeOperations + UbuntuHome = &ubuntuHome + + // 118 upgrade functions + StepsFor118 = stepsFor118 + EnsureLockDirExistsAndUbuntuWritable = ensureLockDirExistsAndUbuntuWritable + UpgradeStateServerRsyslogConfig = upgradeStateServerRsyslogConfig + UpgradeHostMachineRsyslogConfig = upgradeHostMachineRsyslogConfig +) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/file.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/file.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/file.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/file.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,30 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades + +import ( + "io/ioutil" + "os" + "path/filepath" +) + +// WriteReplacementFile create a file called filename with the specified +// contents, allowing for the fact that filename may already exist. +// It first writes data to a temp file and then renames so that if the +// writing fails, any original content is preserved. +func WriteReplacementFile(filename string, data []byte, perm os.FileMode) error { + // Write the data to a temp file + confDir := filepath.Dir(filename) + tempDir, err := ioutil.TempDir(confDir, "") + if err != nil { + return err + } + tempFile := filepath.Join(tempDir, "newfile") + defer os.RemoveAll(tempDir) + err = ioutil.WriteFile(tempFile, []byte(data), perm) + if err != nil { + return err + } + return os.Rename(tempFile, filename) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/file_test.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/file_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/file_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/file_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,48 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades_test + +import ( + "io/ioutil" + "os" + "path/filepath" + + gc "launchpad.net/gocheck" + + "launchpad.net/juju-core/testing/testbase" + "launchpad.net/juju-core/upgrades" +) + +type fileSuite struct { + testbase.LoggingSuite +} + +var _ = gc.Suite(&fileSuite{}) + +func checkFile(c *gc.C, filename string) { + context, err := ioutil.ReadFile(filename) + c.Assert(string(context), gc.Equals, "world") + fi, err := os.Stat(filename) + c.Assert(err, gc.IsNil) + mode := os.FileMode(0644) + c.Assert(fi.Mode()&(os.ModeType|mode), gc.Equals, mode) +} + +func (s *fileSuite) TestWriteReplacementFileWhenNotExists(c *gc.C) { + dir := c.MkDir() + filename := filepath.Join(dir, "foo.conf") + err := upgrades.WriteReplacementFile(filename, []byte("world"), 0644) + c.Assert(err, gc.IsNil) + checkFile(c, filename) +} + +func (s *fileSuite) TestWriteReplacementFileWhenExists(c *gc.C) { + dir := c.MkDir() + filename := filepath.Join(dir, "foo.conf") + err := ioutil.WriteFile(filename, []byte("hello"), 0755) + c.Assert(err, gc.IsNil) + err = upgrades.WriteReplacementFile(filename, []byte("world"), 0644) + c.Assert(err, gc.IsNil) + checkFile(c, filename) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/lockdirectory.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/lockdirectory.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/lockdirectory.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/lockdirectory.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,41 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades + +import ( + "fmt" + "path" + + "launchpad.net/juju-core/utils/exec" +) + +var ubuntuHome = "/home/ubuntu" + +// Previously the lock directory was created when the uniter started. This +// allows serialization of all of the hook execution across units running on a +// single machine. This lock directory is now also used but the juju-run +// command on the host machine. juju-run also gets a lock on the hook +// execution fslock prior to execution. However, the lock directory was owned +// by root, and the juju-run process was being executed by the ubuntu user, so +// we need to change the ownership of the lock directory to ubuntu:ubuntu. +// Also we need to make sure that this directory exists on machines with no +// units. +func ensureLockDirExistsAndUbuntuWritable(context Context) error { + lockDir := path.Join(context.AgentConfig().DataDir(), "locks") + // We only try to change ownership if there is an ubuntu user + // defined, and we determine this by the existance of the home dir. + command := fmt.Sprintf(""+ + "mkdir -p %s\n"+ + "[ -e %s ] && chown ubuntu:ubuntu %s\n", + lockDir, ubuntuHome, lockDir) + logger.Tracef("command: %s", command) + result, err := exec.RunCommands(exec.RunParams{ + Commands: command, + }) + if err != nil { + return err + } + logger.Tracef("stdout: %s", result.Stdout) + return nil +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/lockdirectory_test.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/lockdirectory_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/lockdirectory_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/lockdirectory_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,93 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades_test + +import ( + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/loggo/loggo" + gc "launchpad.net/gocheck" + + "launchpad.net/juju-core/testing" + jc "launchpad.net/juju-core/testing/checkers" + "launchpad.net/juju-core/upgrades" +) + +type ensureLockDirSuite struct { + testing.FakeHomeSuite + bin string + home string + datadir string + lockdir string + ctx upgrades.Context +} + +var _ = gc.Suite(&ensureLockDirSuite{}) + +// fakecommand outputs its arguments to stdout for verification +var fakecommand = `#!/bin/bash + +echo $@ | tee $0.args +` + +func (s *ensureLockDirSuite) SetUpTest(c *gc.C) { + s.FakeHomeSuite.SetUpTest(c) + + s.bin = c.MkDir() + s.PatchEnvPathPrepend(s.bin) + + err := ioutil.WriteFile( + filepath.Join(s.bin, "chown"), + []byte(fakecommand), 0777) + c.Assert(err, gc.IsNil) + + loggo.GetLogger("juju.upgrade").SetLogLevel(loggo.TRACE) + + s.home = c.MkDir() + s.PatchValue(upgrades.UbuntuHome, s.home) + + s.datadir = c.MkDir() + s.lockdir = filepath.Join(s.datadir, "locks") + s.ctx = &mockContext{agentConfig: &mockAgentConfig{dataDir: s.datadir}} +} + +func (s *ensureLockDirSuite) assertChownCalled(c *gc.C) { + bytes, err := ioutil.ReadFile(filepath.Join(s.bin, "chown.args")) + c.Assert(err, gc.IsNil) + c.Assert(string(bytes), gc.Equals, fmt.Sprintf("ubuntu:ubuntu %s\n", s.lockdir)) +} + +func (s *ensureLockDirSuite) assertNoChownCalled(c *gc.C) { + c.Assert(filepath.Join(s.bin, "chown.args"), jc.DoesNotExist) +} + +func (s *ensureLockDirSuite) TestLockDirCreated(c *gc.C) { + err := upgrades.EnsureLockDirExistsAndUbuntuWritable(s.ctx) + c.Assert(err, gc.IsNil) + + c.Assert(s.lockdir, jc.IsDirectory) + s.assertChownCalled(c) +} + +func (s *ensureLockDirSuite) TestIdempotent(c *gc.C) { + err := upgrades.EnsureLockDirExistsAndUbuntuWritable(s.ctx) + c.Assert(err, gc.IsNil) + + err = upgrades.EnsureLockDirExistsAndUbuntuWritable(s.ctx) + c.Assert(err, gc.IsNil) + + c.Assert(s.lockdir, jc.IsDirectory) + s.assertChownCalled(c) +} + +func (s *ensureLockDirSuite) TestNoChownIfNoHome(c *gc.C) { + s.PatchValue(upgrades.UbuntuHome, filepath.Join(s.home, "not-exist")) + err := upgrades.EnsureLockDirExistsAndUbuntuWritable(s.ctx) + c.Assert(err, gc.IsNil) + + c.Assert(s.lockdir, jc.IsDirectory) + s.assertNoChownCalled(c) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/operations.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/operations.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/operations.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/operations.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,20 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades + +import "launchpad.net/juju-core/version" + +// upgradeOperations returns an ordered slice of sets of operations needed +// to upgrade Juju to particular version. The slice is ordered by target +// version, so that the sets of operations are executed in order from oldest +// version to most recent. +var upgradeOperations = func() []Operation { + steps := []Operation{ + upgradeToVersion{ + version.MustParse("1.18.0"), + stepsFor118(), + }, + } + return steps +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/rsyslogconf.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/rsyslogconf.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/rsyslogconf.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/rsyslogconf.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,50 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades + +import ( + "launchpad.net/juju-core/agent" + "launchpad.net/juju-core/environs" + "launchpad.net/juju-core/log/syslog" +) + +func confParams(context Context) (machineTag, namespace string, port int, err error) { + namespace = context.AgentConfig().Value(agent.Namespace) + machineTag = context.AgentConfig().Tag() + + environment := context.APIState().Environment() + config, err := environment.EnvironConfig() + if err != nil { + return "", "", 0, err + } + port = config.SyslogPort() + return machineTag, namespace, port, err +} + +// upgradeStateServerRsyslogConfig upgrades a rsuslog config file on a state server. +func upgradeStateServerRsyslogConfig(context Context) (err error) { + machineTag, namespace, syslogPort, err := confParams(context) + configRenderer := syslog.NewAccumulateConfig(machineTag, syslogPort, namespace) + data, err := configRenderer.Render() + if err != nil { + return nil + } + return WriteReplacementFile(environs.RsyslogConfPath, []byte(data), 0644) +} + +// upgradeHostMachineRsyslogConfig upgrades a rsuslog config file on a host machine. +func upgradeHostMachineRsyslogConfig(context Context) (err error) { + machineTag, namespace, syslogPort, err := confParams(context) + addr, err := context.AgentConfig().APIAddresses() + if err != nil { + return err + } + + configRenderer := syslog.NewForwardConfig(machineTag, syslogPort, namespace, addr) + data, err := configRenderer.Render() + if err != nil { + return nil + } + return WriteReplacementFile(environs.RsyslogConfPath, []byte(data), 0644) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/rsyslogconf_test.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/rsyslogconf_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/rsyslogconf_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/rsyslogconf_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,72 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades_test + +import ( + "io/ioutil" + "path/filepath" + + gc "launchpad.net/gocheck" + + "launchpad.net/juju-core/environs" + jujutesting "launchpad.net/juju-core/juju/testing" + syslogtesting "launchpad.net/juju-core/log/syslog/testing" + "launchpad.net/juju-core/state" + "launchpad.net/juju-core/upgrades" +) + +type rsyslogSuite struct { + jujutesting.JujuConnSuite + + syslogPath string + ctx upgrades.Context +} + +var _ = gc.Suite(&rsyslogSuite{}) + +func (s *rsyslogSuite) SetUpTest(c *gc.C) { + s.JujuConnSuite.SetUpTest(c) + + dir := c.MkDir() + s.syslogPath = filepath.Join(dir, "fakesyslog.conf") + s.PatchValue(&environs.RsyslogConfPath, s.syslogPath) + + apiState, _ := s.OpenAPIAsNewMachine(c, state.JobManageEnviron) + s.ctx = &mockContext{ + agentConfig: &mockAgentConfig{ + tag: "machine-tag", + namespace: "namespace", + apiAddresses: []string{"server:1234"}, + }, + apiState: apiState, + } +} + +func (s *rsyslogSuite) TestStateServerUpgrade(c *gc.C) { + err := upgrades.UpgradeStateServerRsyslogConfig(s.ctx) + c.Assert(err, gc.IsNil) + + data, err := ioutil.ReadFile(s.syslogPath) + c.Assert(err, gc.IsNil) + c.Assert(string(data), gc.Equals, syslogtesting.ExpectedAccumulateSyslogConf(c, "machine-tag", "namespace", 2345)) +} + +func (s *rsyslogSuite) TestStateServerUpgradeIdempotent(c *gc.C) { + s.TestStateServerUpgrade(c) + s.TestStateServerUpgrade(c) +} + +func (s *rsyslogSuite) TestHostMachineUpgrade(c *gc.C) { + err := upgrades.UpgradeHostMachineRsyslogConfig(s.ctx) + c.Assert(err, gc.IsNil) + + data, err := ioutil.ReadFile(s.syslogPath) + c.Assert(err, gc.IsNil) + c.Assert(string(data), gc.Equals, syslogtesting.ExpectedForwardSyslogConf(c, "machine-tag", "namespace", 2345)) +} + +func (s *rsyslogSuite) TestHostServerUpgradeIdempotent(c *gc.C) { + s.TestHostMachineUpgrade(c) + s.TestHostMachineUpgrade(c) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/steps118.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/steps118.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/steps118.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/steps118.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,25 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades + +// stepsFor118 returns upgrade steps to upgrade to a Juju 1.18 deployment. +func stepsFor118() []Step { + return []Step{ + &upgradeStep{ + description: "make $DATADIR/locks owned by ubuntu:ubuntu", + targets: []Target{HostMachine}, + run: ensureLockDirExistsAndUbuntuWritable, + }, + &upgradeStep{ + description: "upgrade rsyslog config file on state server", + targets: []Target{StateServer}, + run: upgradeStateServerRsyslogConfig, + }, + &upgradeStep{ + description: "upgrade rsyslog config file on host machine", + targets: []Target{HostMachine}, + run: upgradeHostMachineRsyslogConfig, + }, + } +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/steps118_test.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/steps118_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/steps118_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/steps118_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,29 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades_test + +import ( + gc "launchpad.net/gocheck" + + "launchpad.net/juju-core/testing/testbase" + "launchpad.net/juju-core/upgrades" +) + +type steps118Suite struct { + testbase.LoggingSuite +} + +var _ = gc.Suite(&steps118Suite{}) + +var expectedSteps = []string{ + "make $DATADIR/locks owned by ubuntu:ubuntu", + "upgrade rsyslog config file on state server", + "upgrade rsyslog config file on host machine", +} + +func (s *steps118Suite) TestUpgradeOperationsContent(c *gc.C) { + upgradeSteps := upgrades.StepsFor118() + c.Assert(upgradeSteps, gc.HasLen, 3) + assertExpectedSteps(c, upgradeSteps, expectedSteps) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/upgrade.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/upgrade.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/upgrade.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/upgrade.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,182 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades + +import ( + "fmt" + + "github.com/loggo/loggo" + + "launchpad.net/juju-core/agent" + "launchpad.net/juju-core/state/api" + "launchpad.net/juju-core/version" +) + +var logger = loggo.GetLogger("juju.upgrade") + +// Step defines an idempotent operation that is run to perform +// a specific upgrade step. +type Step interface { + // Description is a human readable description of what the upgrade step does. + Description() string + + // Targets returns the target machine types for which the upgrade step is applicable. + Targets() []Target + + // Run executes the upgrade business logic. + Run(context Context) error +} + +// Operation defines what steps to perform to upgrade to a target version. +type Operation interface { + // The Juju version for which this operation is applicable. + // Upgrade operations designed for versions of Juju earlier + // than we are upgrading from are not run since such steps would + // already have been used to get to the version we are running now. + TargetVersion() version.Number + + // Steps to perform during an upgrade. + Steps() []Step +} + +// Target defines the type of machine for which a particular upgrade +// step can be run. +type Target string + +const ( + // AllMachines applies to any machine. + AllMachines = Target("allMachines") + + // HostMachine is a machine on which units are deployed. + HostMachine = Target("hostMachine") + + // StateServer is a machine participating in a Juju state server cluster. + StateServer = Target("stateServer") +) + +// upgradeToVersion encapsulates the steps which need to be run to +// upgrade any prior version of Juju to targetVersion. +type upgradeToVersion struct { + targetVersion version.Number + steps []Step +} + +// Steps is defined on the Operation interface. +func (u upgradeToVersion) Steps() []Step { + return u.steps +} + +// TargetVersion is defined on the Operation interface. +func (u upgradeToVersion) TargetVersion() version.Number { + return u.targetVersion +} + +// Context is used give the upgrade steps attributes needed +// to do their job. +type Context interface { + // APIState returns an API connection to state. + APIState() *api.State + + // AgentConfig returns the agent config for the machine that is being upgraded. + AgentConfig() agent.Config +} + +// UpgradeContext is a default Context implementation. +type UpgradeContext struct { + // Work in progress........ + // Exactly what a context needs is to be determined as the + // implementation evolves. + st *api.State + agentConfig agent.Config +} + +// APIState is defined on the Context interface. +func (c *UpgradeContext) APIState() *api.State { + return c.st +} + +// AgentConfig is defined on the Context interface. +func (c *UpgradeContext) AgentConfig() agent.Config { + return c.agentConfig +} + +// upgradeError records a description of the step being performed and the error. +type upgradeError struct { + description string + err error +} + +func (e *upgradeError) Error() string { + return fmt.Sprintf("%s: %v", e.description, e.err) +} + +// PerformUpgrade runs the business logic needed to upgrade the current "from" version to this +// version of Juju on the "target" type of machine. +func PerformUpgrade(from version.Number, target Target, context Context) *upgradeError { + // If from is not known, it is 1.16. + if from == version.Zero { + from = version.MustParse("1.16.0") + } + for _, upgradeOps := range upgradeOperations() { + // Do not run steps for versions of Juju earlier or same as we are upgrading from. + if upgradeOps.TargetVersion().Compare(from) < 1 { + continue + } + if err := runUpgradeSteps(context, target, upgradeOps); err != nil { + return err + } + } + return nil +} + +// validTarget returns true if target is in step.Targets(). +func validTarget(target Target, step Step) bool { + for _, opTarget := range step.Targets() { + if target == AllMachines || target == opTarget { + return true + } + } + return len(step.Targets()) == 0 +} + +// runUpgradeSteps runs all the upgrade steps relevant to target. +// As soon as any error is encountered, the operation is aborted since +// subsequent steps may required successful completion of earlier ones. +// The steps must be idempotent so that the entire upgrade operation can +// be retried. +func runUpgradeSteps(context Context, target Target, upgradeOp Operation) *upgradeError { + for _, step := range upgradeOp.Steps() { + if !validTarget(target, step) { + continue + } + if err := step.Run(context); err != nil { + return &upgradeError{ + description: step.Description(), + err: err, + } + } + } + return nil +} + +type upgradeStep struct { + description string + targets []Target + run func(Context) error +} + +// Description is defined on the Step interface. +func (step *upgradeStep) Description() string { + return step.description +} + +// Targets is defined on the Step interface. +func (step *upgradeStep) Targets() []Target { + return step.targets +} + +// Run is defined on the Step interface. +func (step *upgradeStep) Run(context Context) error { + return step.run(context) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/upgrade_test.go juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/upgrade_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upgrades/upgrade_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upgrades/upgrade_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,257 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrades_test + +import ( + "errors" + "strings" + stdtesting "testing" + + gc "launchpad.net/gocheck" + + "launchpad.net/juju-core/agent" + "launchpad.net/juju-core/state/api" + coretesting "launchpad.net/juju-core/testing" + jc "launchpad.net/juju-core/testing/checkers" + "launchpad.net/juju-core/testing/testbase" + "launchpad.net/juju-core/upgrades" + "launchpad.net/juju-core/version" +) + +func TestPackage(t *stdtesting.T) { + coretesting.MgoTestPackage(t) +} + +// assertExpectedSteps is a helper function used to check that the upgrade steps match +// what is expected for a version. +func assertExpectedSteps(c *gc.C, steps []upgrades.Step, expectedSteps []string) { + var stepNames = make([]string, len(steps)) + for i, step := range steps { + stepNames[i] = step.Description() + } + c.Assert(stepNames, gc.DeepEquals, expectedSteps) +} + +type upgradeSuite struct { + testbase.LoggingSuite +} + +var _ = gc.Suite(&upgradeSuite{}) + +type mockUpgradeOperation struct { + targetVersion version.Number + steps []upgrades.Step +} + +func (m *mockUpgradeOperation) TargetVersion() version.Number { + return m.targetVersion +} + +func (m *mockUpgradeOperation) Steps() []upgrades.Step { + return m.steps +} + +type mockUpgradeStep struct { + msg string + targets []upgrades.Target +} + +func (u *mockUpgradeStep) Description() string { + return u.msg +} + +func (u *mockUpgradeStep) Targets() []upgrades.Target { + return u.targets +} + +func (u *mockUpgradeStep) Run(context upgrades.Context) error { + if strings.HasSuffix(u.msg, "error") { + return errors.New("upgrade error occurred") + } + ctx := context.(*mockContext) + ctx.messages = append(ctx.messages, u.msg) + return nil +} + +type mockContext struct { + messages []string + agentConfig *mockAgentConfig + apiState *api.State +} + +func (c *mockContext) APIState() *api.State { + return c.apiState +} + +func (c *mockContext) AgentConfig() agent.Config { + return c.agentConfig +} + +type mockAgentConfig struct { + agent.Config + dataDir string + tag string + namespace string + apiAddresses []string +} + +func (mock *mockAgentConfig) Tag() string { + return mock.tag +} + +func (mock *mockAgentConfig) DataDir() string { + return mock.dataDir +} + +func (mock *mockAgentConfig) APIAddresses() ([]string, error) { + return mock.apiAddresses, nil +} + +func (mock *mockAgentConfig) Value(name string) string { + if name == agent.Namespace { + return mock.namespace + } + return "" +} + +func targets(targets ...upgrades.Target) (upgradeTargets []upgrades.Target) { + for _, t := range targets { + upgradeTargets = append(upgradeTargets, t) + } + return upgradeTargets +} + +func upgradeOperations() []upgrades.Operation { + steps := []upgrades.Operation{ + &mockUpgradeOperation{ + targetVersion: version.MustParse("1.12.0"), + steps: []upgrades.Step{ + &mockUpgradeStep{"step 1 - 1.12.0", nil}, + &mockUpgradeStep{"step 2 error", targets(upgrades.HostMachine)}, + &mockUpgradeStep{"step 3", targets(upgrades.HostMachine)}, + }, + }, + &mockUpgradeOperation{ + targetVersion: version.MustParse("1.16.0"), + steps: []upgrades.Step{ + &mockUpgradeStep{"step 1 - 1.16.0", targets(upgrades.HostMachine)}, + &mockUpgradeStep{"step 2 - 1.16.0", targets(upgrades.HostMachine)}, + &mockUpgradeStep{"step 3 - 1.16.0", targets(upgrades.StateServer)}, + }, + }, + &mockUpgradeOperation{ + targetVersion: version.MustParse("1.17.0"), + steps: []upgrades.Step{ + &mockUpgradeStep{"step 1 - 1.17.0", targets(upgrades.HostMachine)}, + }, + }, + &mockUpgradeOperation{ + targetVersion: version.MustParse("1.17.1"), + steps: []upgrades.Step{ + &mockUpgradeStep{"step 1 - 1.17.1", targets(upgrades.HostMachine)}, + &mockUpgradeStep{"step 2 - 1.17.1", targets(upgrades.StateServer)}, + }, + }, + &mockUpgradeOperation{ + targetVersion: version.MustParse("1.18.0"), + steps: []upgrades.Step{ + &mockUpgradeStep{"step 1 - 1.18.0", targets(upgrades.HostMachine)}, + &mockUpgradeStep{"step 2 - 1.18.0", targets(upgrades.StateServer)}, + }, + }, + } + return steps +} + +type upgradeTest struct { + about string + fromVersion string + target upgrades.Target + expectedSteps []string + err string +} + +var upgradeTests = []upgradeTest{ + { + about: "from version excludes steps for same version", + fromVersion: "1.18.0", + target: upgrades.HostMachine, + expectedSteps: []string{}, + }, + { + about: "from version excludes older steps", + fromVersion: "1.17.0", + target: upgrades.HostMachine, + expectedSteps: []string{"step 1 - 1.17.1", "step 1 - 1.18.0"}, + }, + { + about: "incompatible targets excluded", + fromVersion: "1.17.1", + target: upgrades.StateServer, + expectedSteps: []string{"step 2 - 1.18.0"}, + }, + { + about: "allMachines matches everything", + fromVersion: "1.17.1", + target: upgrades.AllMachines, + expectedSteps: []string{"step 1 - 1.18.0", "step 2 - 1.18.0"}, + }, + { + about: "error aborts, subsequent steps not run", + fromVersion: "1.10.0", + target: upgrades.HostMachine, + expectedSteps: []string{"step 1 - 1.12.0"}, + err: "step 2 error: upgrade error occurred", + }, + { + about: "default from version is 1.16", + fromVersion: "", + target: upgrades.StateServer, + expectedSteps: []string{"step 2 - 1.17.1", "step 2 - 1.18.0"}, + }, +} + +func (s *upgradeSuite) TestPerformUpgrade(c *gc.C) { + s.PatchValue(upgrades.UpgradeOperations, upgradeOperations) + for i, test := range upgradeTests { + c.Logf("%d: %s", i, test.about) + var messages []string + ctx := &mockContext{ + messages: messages, + } + fromVersion := version.Zero + if test.fromVersion != "" { + fromVersion = version.MustParse(test.fromVersion) + } + err := upgrades.PerformUpgrade(fromVersion, test.target, ctx) + if test.err == "" { + c.Check(err, gc.IsNil) + } else { + c.Check(err, gc.ErrorMatches, test.err) + } + c.Check(ctx.messages, jc.DeepEquals, test.expectedSteps) + } +} + +func (s *upgradeSuite) TestUpgradeOperationsOrdered(c *gc.C) { + var previous version.Number + for i, utv := range (*upgrades.UpgradeOperations)() { + vers := utv.TargetVersion() + if i > 0 { + c.Check(previous.Compare(vers), gc.Equals, -1) + } + previous = vers + } +} + +var expectedVersions = []string{"1.18.0"} + +func (s *upgradeSuite) TestUpgradeOperationsVersions(c *gc.C) { + var versions []string + for _, utv := range (*upgrades.UpgradeOperations)() { + versions = append(versions, utv.TargetVersion().String()) + + } + c.Assert(versions, gc.DeepEquals, expectedVersions) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/upstart/upstart_test.go juju-core-1.17.3/src/launchpad.net/juju-core/upstart/upstart_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/upstart/upstart_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/upstart/upstart_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -29,9 +29,8 @@ var _ = gc.Suite(&UpstartSuite{}) func (s *UpstartSuite) SetUpTest(c *gc.C) { - origPath := os.Getenv("PATH") s.testPath = c.MkDir() - s.PatchEnvironment("PATH", s.testPath+":"+origPath) + s.PatchEnvPathPrepend(s.testPath) s.PatchValue(&upstart.InstallStartRetryAttempts, utils.AttemptStrategy{}) s.service = &upstart.Service{Name: "some-service", InitDir: c.MkDir()} _, err := os.Create(filepath.Join(s.service.InitDir, "some-service.conf")) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/export_test.go juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/export_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/export_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/export_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -7,4 +7,6 @@ ReadAuthorisedKeys = readAuthorisedKeys WriteAuthorisedKeys = writeAuthorisedKeys InitDefaultClient = initDefaultClient + DefaultIdentities = &defaultIdentities + SSHDial = &sshDial ) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/run_test.go juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/run_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/run_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/run_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -5,7 +5,6 @@ import ( "io/ioutil" - "os" "path/filepath" gc "launchpad.net/gocheck" @@ -28,8 +27,7 @@ s.LoggingSuite.SetUpTest(c) s.testbin = c.MkDir() s.fakessh = filepath.Join(s.testbin, "ssh") - newPath := s.testbin + ":" + os.Getenv("PATH") - s.PatchEnvironment("PATH", newPath) + s.PatchEnvPathPrepend(s.testbin) } func (s *ExecuteSSHCommandSuite) fakeSSH(c *gc.C, cmd string) { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/ssh.go juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/ssh.go --- juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/ssh.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/ssh.go 2014-02-20 16:10:15.000000000 +0000 @@ -12,6 +12,10 @@ "bytes" "errors" "io" + "os/exec" + "syscall" + + "launchpad.net/juju-core/cmd" ) // Options is a client-implementation independent SSH options set. @@ -115,7 +119,14 @@ if err := c.Start(); err != nil { return err } - return c.Wait() + err := c.Wait() + if exitError, ok := err.(*exec.ExitError); ok && exitError != nil { + status := exitError.ProcessState.Sys().(syscall.WaitStatus) + if status.Exited() { + return cmd.NewRcPassthroughError(status.ExitStatus()) + } + } + return err } // Start starts the command running, but does not wait for diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/ssh_gocrypto.go juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/ssh_gocrypto.go --- juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/ssh_gocrypto.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/ssh_gocrypto.go 2014-02-20 16:10:15.000000000 +0000 @@ -77,6 +77,8 @@ sess *ssh.Session } +var sshDial = ssh.Dial + func (c *goCryptoCommand) ensureSession() (*ssh.Session, error) { if c.sess != nil { return c.sess, nil @@ -97,7 +99,7 @@ ssh.ClientAuthKeyring(keyring{c.signers}), }, } - conn, err := ssh.Dial("tcp", c.addr, config) + conn, err := sshDial("tcp", c.addr, config) if err != nil { return nil, err } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/ssh_gocrypto_test.go juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/ssh_gocrypto_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/ssh_gocrypto_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/ssh_gocrypto_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -5,6 +5,7 @@ import ( "encoding/binary" + "errors" "net" "sync" @@ -107,10 +108,14 @@ defer ssh.ClearClientKeys() err = ssh.LoadClientKeys(c.MkDir()) c.Assert(err, gc.IsNil) + + s.PatchValue(ssh.SSHDial, func(network, address string, cfg *cryptossh.ClientConfig) (*cryptossh.ClientConn, error) { + return nil, errors.New("ssh.Dial failed") + }) cmd = client.Command("0.1.2.3", []string{"echo", "123"}, nil) _, err = cmd.Output() // error message differs based on whether using cgo or not - c.Assert(err, gc.ErrorMatches, `(dial tcp )?0\.1\.2\.3:22: invalid argument`) + c.Assert(err, gc.ErrorMatches, "ssh.Dial failed") } func (s *SSHGoCryptoCommandSuite) TestCommand(c *gc.C) { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/ssh_openssh.go juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/ssh_openssh.go --- juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/ssh_openssh.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/ssh_openssh.go 2014-02-20 16:10:15.000000000 +0000 @@ -10,10 +10,22 @@ "os" "os/exec" "strings" + + "launchpad.net/juju-core/utils" ) var opensshCommonOptions = []string{"-o", "StrictHostKeyChecking no"} +// default identities will not be attempted if +// -i is specified and they are not explcitly +// included. +var defaultIdentities = []string{ + "~/.ssh/identity", + "~/.ssh/id_rsa", + "~/.ssh/id_dsa", + "~/.ssh/id_ecdsa", +} + type opensshCommandKind int const ( @@ -62,7 +74,26 @@ if options.allocatePTY { args = append(args, "-t") } - for _, identity := range options.identities { + identities := append([]string{}, options.identities...) + if pk := PrivateKeyFiles(); len(pk) > 0 { + // Add client keys as implicit identities + identities = append(identities, pk...) + } + // If any identities are specified, the + // default ones must be explicitly specified. + if len(identities) > 0 { + for _, identity := range defaultIdentities { + path, err := utils.NormalizePath(identity) + if err != nil { + logger.Warningf("failed to normalize path %q: %v", identity, err) + continue + } + if _, err := os.Stat(path); err == nil { + identities = append(identities, path) + } + } + } + for _, identity := range identities { args = append(args, "-i", identity) } if options.port != 0 { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/ssh_test.go juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/ssh_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/ssh_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/ssh_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -11,6 +11,7 @@ gc "launchpad.net/gocheck" + "launchpad.net/juju-core/cmd" "launchpad.net/juju-core/testing/testbase" "launchpad.net/juju-core/utils/ssh" ) @@ -36,9 +37,10 @@ c.Assert(err, gc.IsNil) err = ioutil.WriteFile(s.fakescp, []byte(echoCommandScript), 0755) c.Assert(err, gc.IsNil) - s.PatchEnvironment("PATH", s.testbin+":"+os.Getenv("PATH")) + s.PatchEnvPathPrepend(s.testbin) s.client, err = ssh.NewOpenSSHClient() c.Assert(err, gc.IsNil) + s.PatchValue(ssh.DefaultIdentities, nil) } func (s *SSHCommandSuite) command(args ...string) *ssh.Cmd { @@ -134,3 +136,45 @@ // EnablePTY has no effect for Copy c.Assert(string(out), gc.Equals, s.fakescp+" -o StrictHostKeyChecking no -i x -i y -P 2022 /tmp/blah foo@bar.com:baz\n") } + +func (s *SSHCommandSuite) TestCommandClientKeys(c *gc.C) { + clientKeysDir := c.MkDir() + defer ssh.ClearClientKeys() + err := ssh.LoadClientKeys(clientKeysDir) + c.Assert(err, gc.IsNil) + ck := filepath.Join(clientKeysDir, "juju_id_rsa") + var opts ssh.Options + opts.SetIdentities("x", "y") + s.assertCommandArgs(c, s.commandOptions([]string{"echo", "123"}, &opts), + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -i x -i y -i "+ck+" localhost -- echo 123", + ) +} + +func (s *SSHCommandSuite) TestCommandError(c *gc.C) { + var opts ssh.Options + err := ioutil.WriteFile(s.fakessh, []byte("#!/bin/sh\nexit 42"), 0755) + c.Assert(err, gc.IsNil) + command := s.client.Command("ignored", []string{"echo", "foo"}, &opts) + err = command.Run() + c.Assert(cmd.IsRcPassthroughError(err), gc.Equals, true) +} + +func (s *SSHCommandSuite) TestCommandDefaultIdentities(c *gc.C) { + var opts ssh.Options + tempdir := c.MkDir() + def1 := filepath.Join(tempdir, "def1") + def2 := filepath.Join(tempdir, "def2") + s.PatchValue(ssh.DefaultIdentities, []string{def1, def2}) + // If no identities are specified, then the defaults aren't added. + s.assertCommandArgs(c, s.commandOptions([]string{"echo", "123"}, &opts), + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no localhost -- echo 123", + ) + // If identities are specified, then the defaults are must added. + // Only the defaults that exist on disk will be added. + err := ioutil.WriteFile(def2, nil, 0644) + c.Assert(err, gc.IsNil) + opts.SetIdentities("x", "y") + s.assertCommandArgs(c, s.commandOptions([]string{"echo", "123"}, &opts), + s.fakessh+" -o StrictHostKeyChecking no -o PasswordAuthentication no -i x -i y -i "+def2+" localhost -- echo 123", + ) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/testing/fakessh.go juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/testing/fakessh.go --- juju-core-1.17.2/src/launchpad.net/juju-core/utils/ssh/testing/fakessh.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/utils/ssh/testing/fakessh.go 2014-02-20 16:10:15.000000000 +0000 @@ -6,7 +6,6 @@ import ( "fmt" "io/ioutil" - "os" "path/filepath" gc "launchpad.net/gocheck" @@ -77,5 +76,5 @@ script := fmt.Sprintf(sshscript, stdout, stderr, rc) err := ioutil.WriteFile(ssh, []byte(script), 0777) c.Assert(err, gc.IsNil) - return testbase.PatchEnvironment("PATH", fakebin+":"+os.Getenv("PATH")) + return testbase.PatchEnvPathPrepend(fakebin) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/utils/voyeur/value.go juju-core-1.17.3/src/launchpad.net/juju-core/utils/voyeur/value.go --- juju-core-1.17.2/src/launchpad.net/juju-core/utils/voyeur/value.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/utils/voyeur/value.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,144 @@ +// Copyright 2012, 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +// Package voyeur implements a concurrency-safe value that can be watched for +// changes. +package voyeur + +import ( + "sync" +) + +// Value represents a shared value that can be watched for changes. Methods on +// a Value may be called concurrently. The zero Value is +// ok to use, and is equivalent to a NewValue result +// with a nil initial value. +type Value struct { + val interface{} + version int + mu sync.RWMutex + wait sync.Cond + closed bool +} + +// NewValue creates a new Value holding the given initial value. If initial is +// nil, any watchers will wait until a value is set. +func NewValue(initial interface{}) *Value { + v := new(Value) + v.init() + if initial != nil { + v.val = initial + v.version++ + } + return v +} + +func (v *Value) needsInit() bool { + return v.wait.L == nil +} + +func (v *Value) init() { + if v.needsInit() { + v.wait.L = v.mu.RLocker() + } +} + +// Set sets the shared value to val. +func (v *Value) Set(val interface{}) { + v.mu.Lock() + v.init() + v.val = val + v.version++ + v.mu.Unlock() + v.wait.Broadcast() +} + +// Close closes the Value, unblocking any outstanding watchers. Close always +// returns nil. +func (v *Value) Close() error { + v.mu.Lock() + v.init() + v.closed = true + v.mu.Unlock() + v.wait.Broadcast() + return nil +} + +// Get returns the current value. If the Value has been closed, ok will be +// false. +func (v *Value) Get() (val interface{}, ok bool) { + v.mu.RLock() + defer v.mu.RUnlock() + if v.closed { + return v.val, false + } + return v.val, true +} + +// Watch returns a Watcher that can be used to watch for changes to the value. +func (v *Value) Watch() *Watcher { + return &Watcher{value: v} +} + +// Watcher represents a single watcher of a shared value. +type Watcher struct { + value *Value + version int + current interface{} + closed bool +} + +// Next blocks until there is a new value to be retrieved from the value that is +// being watched. It also unblocks when the value or the Watcher itself is +// closed. Next returns false if the value or the Watcher itself have been +// closed. +func (w *Watcher) Next() bool { + val := w.value + val.mu.RLock() + defer val.mu.RUnlock() + if val.needsInit() { + val.mu.RUnlock() + val.mu.Lock() + val.init() + val.mu.Unlock() + val.mu.RLock() + } + + // We can go around this loop a maximum of two times, + // because the only thing that can cause a Wait to + // return is for the condition to be triggered, + // which can only happen if the value is set (causing + // the version to increment) or it is closed + // causing the closed flag to be set. + // Both these cases will cause Next to return. + for { + if w.version != val.version { + w.version = val.version + w.current = val.val + return true + } + if val.closed || w.closed { + return false + } + + // Wait releases the lock until triggered and then reacquires the lock, + // thus avoiding a deadlock. + val.wait.Wait() + } +} + +// Close closes the Watcher without closing the underlying +// value. It may be called concurrently with Next. +func (w *Watcher) Close() { + w.value.mu.Lock() + w.value.init() + w.closed = true + w.value.mu.Unlock() + w.value.wait.Broadcast() +} + +// Value returns the last value that was retrieved from the watched Value by +// Next. +func (w *Watcher) Value() interface{} { + return w.current +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/utils/voyeur/value_test.go juju-core-1.17.3/src/launchpad.net/juju-core/utils/voyeur/value_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/utils/voyeur/value_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/utils/voyeur/value_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,227 @@ +// Copyright 2012, 2013 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package voyeur + +import ( + "fmt" + "testing" + + gc "launchpad.net/gocheck" + + jc "launchpad.net/juju-core/testing/checkers" + "launchpad.net/juju-core/testing/testbase" +) + +type suite struct { + testbase.LoggingSuite +} + +var _ = gc.Suite(&suite{}) + +func Test(t *testing.T) { gc.TestingT(t) } + +func ExampleWatcher_Next() { + v := NewValue(nil) + + // The channel is not necessary for normal use of the watcher. + // It just makes the test output predictable. + ch := make(chan bool) + + go func() { + for x := 0; x < 3; x++ { + v.Set(fmt.Sprintf("value%d", x)) + ch <- true + } + v.Close() + }() + w := v.Watch() + for w.Next() { + fmt.Println(w.Value()) + <-ch + } + + // output: + // value0 + // value1 + // value2 +} + +func (s *suite) TestValueGetSet(c *gc.C) { + v := NewValue(nil) + expected := "12345" + v.Set(expected) + got, ok := v.Get() + c.Assert(ok, jc.IsTrue) + c.Assert(got, gc.Equals, expected) +} + +func (s *suite) TestValueInitial(c *gc.C) { + expected := "12345" + v := NewValue(expected) + got, ok := v.Get() + c.Assert(ok, jc.IsTrue) + c.Assert(got, gc.Equals, expected) +} + +func (s *suite) TestValueClose(c *gc.C) { + expected := "12345" + v := NewValue(expected) + c.Assert(v.Close(), gc.IsNil) + got, ok := v.Get() + c.Assert(ok, jc.IsFalse) + c.Assert(got, gc.Equals, expected) + + // test that we can close multiple times without a problem + c.Assert(v.Close(), gc.IsNil) +} + +func (s *suite) TestWatcher(c *gc.C) { + vals := []string{"one", "two", "three"} + + // blocking on the channel forces the scheduler to let the other goroutine + // run for a bit, so we get predictable results. This is not necessary for + // normal use of the watcher. + ch := make(chan bool) + + v := NewValue(nil) + + go func() { + for _, s := range vals { + v.Set(s) + ch <- true + } + v.Close() + }() + + w := v.Watch() + c.Assert(w.Next(), jc.IsTrue) + c.Assert(w.Value(), gc.Equals, vals[0]) + + // test that we can get the same value multiple times + c.Assert(w.Value(), gc.Equals, vals[0]) + <-ch + + // now try skipping a value by calling next without getting the value + c.Assert(w.Next(), jc.IsTrue) + <-ch + + c.Assert(w.Next(), jc.IsTrue) + c.Assert(w.Value(), gc.Equals, vals[2]) + <-ch + + c.Assert(w.Next(), jc.IsFalse) +} + +func (s *suite) TestDoubleSet(c *gc.C) { + vals := []string{"one", "two", "three"} + + // blocking on the channel forces the scheduler to let the other goroutine + // run for a bit, so we get predictable results. This is not necessary for + // normal use of the watcher. + ch := make(chan bool) + + v := NewValue(nil) + + go func() { + v.Set(vals[0]) + ch <- true + v.Set(vals[1]) + v.Set(vals[2]) + ch <- true + v.Close() + ch <- true + }() + + w := v.Watch() + c.Assert(w.Next(), jc.IsTrue) + c.Assert(w.Value(), gc.Equals, vals[0]) + <-ch + + // since we did two sets before sending on the channel, + // we should just get vals[2] here and not get vals[1] + c.Assert(w.Next(), jc.IsTrue) + c.Assert(w.Value(), gc.Equals, vals[2]) +} + +func (s *suite) TestTwoReceivers(c *gc.C) { + vals := []string{"one", "two", "three"} + + // blocking on the channel forces the scheduler to let the other goroutine + // run for a bit, so we get predictable results. This is not necessary for + // normal use of the watcher. + ch := make(chan bool) + + v := NewValue(nil) + + watcher := func() { + w := v.Watch() + x := 0 + for w.Next() { + c.Assert(w.Value(), gc.Equals, vals[x]) + x++ + <-ch + } + c.Assert(x, gc.Equals, len(vals)) + <-ch + } + + go watcher() + go watcher() + + for _, val := range vals { + v.Set(val) + ch <- true + ch <- true + } + + v.Close() + ch <- true + ch <- true +} + +func (s *suite) TestCloseWatcher(c *gc.C) { + vals := []string{"one", "two", "three"} + + // blocking on the channel forces the scheduler to let the other goroutine + // run for a bit, so we get predictable results. This is not necessary for + // normal use of the watcher. + ch := make(chan bool) + + v := NewValue(nil) + + w := v.Watch() + go func() { + x := 0 + for w.Next() { + c.Assert(w.Value(), gc.Equals, vals[x]) + x++ + <-ch + } + // the value will only get set once before the watcher is closed + c.Assert(x, gc.Equals, 1) + <-ch + }() + + v.Set(vals[0]) + ch <- true + w.Close() + ch <- true + + // prove the value is not closed, even though the watcher is + _, ok := v.Get() + c.Assert(ok, jc.IsTrue) +} + +func (s *suite) TestWatchZeroValue(c *gc.C) { + var v Value + ch := make(chan bool) + go func() { + w := v.Watch() + ch <- true + ch <- w.Next() + }() + <-ch + v.Set(struct{}{}) + c.Assert(<-ch, jc.IsTrue) +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/version/version.go juju-core-1.17.3/src/launchpad.net/juju-core/version/version.go --- juju-core-1.17.2/src/launchpad.net/juju-core/version/version.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/version/version.go 2014-02-20 16:10:15.000000000 +0000 @@ -22,7 +22,7 @@ // The presence and format of this constant is very important. // The debian/rules build recipe uses this value for the version // number of the release package. -const version = "1.17.2" +const version = "1.17.3" // Current gives the current version of the system. If the file // "FORCE-VERSION" is present in the same directory as the running @@ -193,20 +193,27 @@ return s } -// Less returns whether v is semantically earlier in the -// version sequence than w. -func (v Number) Less(w Number) bool { +// Compare returns -1, 0 or 1 depending on whether +// v is less than, equal to or greater than w. +func (v Number) Compare(w Number) int { + if v == w { + return 0 + } + less := false switch { case v.Major != w.Major: - return v.Major < w.Major + less = v.Major < w.Major case v.Minor != w.Minor: - return v.Minor < w.Minor + less = v.Minor < w.Minor case v.Patch != w.Patch: - return v.Patch < w.Patch + less = v.Patch < w.Patch case v.Build != w.Build: - return v.Build < w.Build + less = v.Build < w.Build + } + if less { + return -1 } - return false + return 1 } // GetBSON turns v into a bson.Getter so it can be saved directly diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/version/version_test.go juju-core-1.17.3/src/launchpad.net/juju-core/version/version_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/version/version_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/version/version_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -5,11 +5,12 @@ import ( "encoding/json" - "labix.org/v2/mgo/bson" "strings" "testing" + "labix.org/v2/mgo/bson" gc "launchpad.net/gocheck" + "launchpad.net/juju-core/version" ) @@ -24,40 +25,33 @@ // N.B. The FORCE-VERSION logic is tested in the environs package. var cmpTests = []struct { - v1, v2 string - less bool - eq bool + v1, v2 string + compare int }{ - {"1.0.0", "1.0.0", false, true}, - {"01.0.0", "1.0.0", false, true}, - {"10.0.0", "9.0.0", false, false}, - {"1.0.0", "1.0.1", true, false}, - {"1.0.1", "1.0.0", false, false}, - {"1.0.0", "1.1.0", true, false}, - {"1.1.0", "1.0.0", false, false}, - {"1.0.0", "2.0.0", true, false}, - {"2.0.0", "1.0.0", false, false}, - {"2.0.0.0", "2.0.0", false, true}, - {"2.0.0.0", "2.0.0.0", false, true}, - {"2.0.0.1", "2.0.0.0", false, false}, - {"2.0.1.10", "2.0.0.0", false, false}, + {"1.0.0", "1.0.0", 0}, + {"01.0.0", "1.0.0", 0}, + {"10.0.0", "9.0.0", 1}, + {"1.0.0", "1.0.1", -1}, + {"1.0.1", "1.0.0", 1}, + {"1.0.0", "1.1.0", -1}, + {"1.1.0", "1.0.0", 1}, + {"1.0.0", "2.0.0", -1}, + {"2.0.0", "1.0.0", 1}, + {"2.0.0.0", "2.0.0", 0}, + {"2.0.0.0", "2.0.0.0", 0}, + {"2.0.0.1", "2.0.0.0", 1}, + {"2.0.1.10", "2.0.0.0", 1}, } -func (suite) TestComparison(c *gc.C) { +func (suite) TestCompare(c *gc.C) { for i, test := range cmpTests { c.Logf("test %d", i) v1, err := version.Parse(test.v1) c.Assert(err, gc.IsNil) v2, err := version.Parse(test.v2) c.Assert(err, gc.IsNil) - less := v1.Less(v2) - gt := v2.Less(v1) - c.Check(less, gc.Equals, test.less) - if test.eq { - c.Check(gt, gc.Equals, false) - } else { - c.Check(gt, gc.Equals, !test.less) - } + compare := v1.Compare(v2) + c.Check(compare, gc.Equals, test.compare) } } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/deployer/simple.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/deployer/simple.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/deployer/simple.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/deployer/simple.go 2014-02-20 16:10:15.000000000 +0000 @@ -21,6 +21,10 @@ "launchpad.net/juju-core/version" ) +// InitDir is the default upstart init directory. +// This is a var so it can be overridden by tests. +var InitDir = "/etc/init" + // APICalls defines the interface to the API that the simple context needs. type APICalls interface { ConnectionInfo() (params.DeployerConnectionValues, error) @@ -65,7 +69,7 @@ return &SimpleContext{ api: api, agentConfig: agentConfig, - initDir: "/etc/init", + initDir: InitDir, logDir: "/var/log/juju", } } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/firewaller/firewaller.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/firewaller/firewaller.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/firewaller/firewaller.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/firewaller/firewaller.go 2014-02-20 16:10:15.000000000 +0000 @@ -4,14 +4,13 @@ package firewaller import ( - "fmt" - + "github.com/errgo/errgo" + "github.com/loggo/loggo" "launchpad.net/tomb" "launchpad.net/juju-core/environs" "launchpad.net/juju-core/environs/config" "launchpad.net/juju-core/instance" - "launchpad.net/juju-core/log" "launchpad.net/juju-core/names" apifirewaller "launchpad.net/juju-core/state/api/firewaller" "launchpad.net/juju-core/state/api/params" @@ -20,6 +19,8 @@ "launchpad.net/juju-core/worker" ) +var logger = loggo.GetLogger("juju.worker.firewaller") + // Firewaller watches the state for ports opened or closed // and reflects those changes onto the backing environment. type Firewaller struct { @@ -93,7 +94,7 @@ return err } if err := fw.environ.SetConfig(config); err != nil { - log.Errorf("worker/firewaller: loaded invalid environment configuration: %v", err) + logger.Errorf("loaded invalid environment configuration: %v", err) } case change, ok := <-fw.machinesWatcher.Changes(): if !ok { @@ -121,7 +122,7 @@ case change := <-fw.portsChange: change.unitd.ports = change.ports if err := fw.flushUnits([]*unitData{change.unitd}); err != nil { - return fmt.Errorf("cannot change firewall ports: %v", err) + return errgo.Annotate(err, "cannot change firewall ports") } case change := <-fw.exposedChange: change.serviced.exposed = change.exposed @@ -130,7 +131,7 @@ unitds = append(unitds, unitd) } if err := fw.flushUnits(unitds); err != nil { - return fmt.Errorf("cannot change firewall ports: %v", err) + return errgo.Annotate(err, "cannot change firewall ports") } } } @@ -139,7 +140,7 @@ // stop a watcher with logging of a possible error. func stop(what string, stopper watcher.Stopper) { if err := stopper.Stop(); err != nil { - log.Errorf("worker/firewaller: error stopping %s: %v", what, err) + logger.Errorf("error stopping %s: %v", what, err) } } @@ -156,7 +157,7 @@ if params.IsCodeNotFound(err) { return nil } else if err != nil { - return fmt.Errorf("worker/firewaller: cannot watch machine units: %v", err) + return errgo.Annotate(err, "cannot watch machine units") } unitw, err := m.WatchUnits() if err != nil { @@ -176,7 +177,7 @@ if err != nil { stop("units watcher", unitw) delete(fw.machineds, tag) - return fmt.Errorf("worker/firewaller: cannot respond to units changes for %q: %v", tag, err) + return errgo.Annotatef(err, "cannot respond to units changes for %q", tag) } } go machined.watchLoop(unitw) @@ -266,14 +267,14 @@ toOpen := Diff(wantedPorts, initialPorts) toClose := Diff(initialPorts, wantedPorts) if len(toOpen) > 0 { - log.Infof("worker/firewaller: opening global ports %v", toOpen) + logger.Infof("opening global ports %v", toOpen) if err := fw.environ.OpenPorts(toOpen); err != nil { return err } instance.SortPorts(toOpen) } if len(toClose) > 0 { - log.Infof("worker/firewaller: closing global ports %v", toClose) + logger.Infof("closing global ports %v", toClose) if err := fw.environ.ClosePorts(toClose); err != nil { return err } @@ -318,7 +319,7 @@ toOpen := Diff(machined.ports, initialPorts) toClose := Diff(initialPorts, machined.ports) if len(toOpen) > 0 { - log.Infof("worker/firewaller: opening instance ports %v for %q", + logger.Infof("opening instance ports %v for %q", toOpen, machined.tag) if err := instances[0].OpenPorts(machineId, toOpen); err != nil { // TODO(mue) Add local retry logic. @@ -327,7 +328,7 @@ instance.SortPorts(toOpen) } if len(toClose) > 0 { - log.Infof("worker/firewaller: closing instance ports %v for %q", + logger.Infof("closing instance ports %v for %q", toClose, machined.tag) if err := instances[0].ClosePorts(machineId, toClose); err != nil { // TODO(mue) Add local retry logic. @@ -361,7 +362,7 @@ if unit == nil || unit.Life() == params.Dead || machineTag != knownMachineTag { fw.forgetUnit(unitd) changed = append(changed, unitd) - log.Debugf("worker/firewaller: stopped watching unit %s", name) + logger.Debugf("stopped watching unit %s", name) } } else if unit != nil && unit.Life() != params.Dead && fw.machineds[machineTag] != nil { err = fw.startUnit(unit, machineTag) @@ -369,11 +370,11 @@ return err } changed = append(changed, fw.unitds[name]) - log.Debugf("worker/firewaller: started watching unit %s", name) + logger.Debugf("started watching unit %s", name) } } if err := fw.flushUnits(changed); err != nil { - return fmt.Errorf("cannot change firewall ports: %v", err) + return errgo.Annotate(err, "cannot change firewall ports") } return nil } @@ -442,7 +443,7 @@ return err } instance.SortPorts(toOpen) - log.Infof("worker/firewaller: opened ports %v in environment", toOpen) + logger.Infof("opened ports %v in environment", toOpen) } if len(toClose) > 0 { if err := fw.environ.ClosePorts(toClose); err != nil { @@ -450,7 +451,7 @@ return err } instance.SortPorts(toClose) - log.Infof("worker/firewaller: closed ports %v in environment", toClose) + logger.Infof("closed ports %v in environment", toClose) } return nil } @@ -490,7 +491,7 @@ return err } instance.SortPorts(toOpen) - log.Infof("worker/firewaller: opened ports %v on %q", toOpen, machined.tag) + logger.Infof("opened ports %v on %q", toOpen, machined.tag) } if len(toClose) > 0 { if err := instances[0].ClosePorts(machineId, toClose); err != nil { @@ -498,7 +499,7 @@ return err } instance.SortPorts(toClose) - log.Infof("worker/firewaller: closed ports %v on %q", toClose, machined.tag) + logger.Infof("closed ports %v on %q", toClose, machined.tag) } return nil } @@ -522,7 +523,7 @@ if err != nil { return err } - log.Debugf("worker/firewaller: started watching %q", tag) + logger.Debugf("started watching %q", tag) } return nil } @@ -539,7 +540,7 @@ if err := machined.Stop(); err != nil { return err } - log.Debugf("worker/firewaller: stopped watching %q", machined.tag) + logger.Debugf("stopped watching %q", machined.tag) return nil } @@ -549,7 +550,7 @@ serviced := unitd.serviced machined := unitd.machined if err := unitd.Stop(); err != nil { - log.Errorf("worker/firewaller: unit watcher %q returned error when stopping: %v", name, err) + logger.Errorf("unit watcher %q returned error when stopping: %v", name, err) } // Clean up after stopping. delete(fw.unitds, name) @@ -558,7 +559,7 @@ if len(serviced.unitds) == 0 { // Stop service data after all units are removed. if err := serviced.Stop(); err != nil { - log.Errorf("worker/firewaller: service watcher %q returned error when stopping: %v", serviced.service.Name(), err) + logger.Errorf("service watcher %q returned error when stopping: %v", serviced.service.Name(), err) } delete(fw.serviceds, serviced.service.Name()) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/localstorage/config.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/localstorage/config.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/localstorage/config.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/localstorage/config.go 2014-02-20 16:10:15.000000000 +0000 @@ -14,14 +14,12 @@ // Move these variables out of agent when we can do upgrades in // the right place. In this case, the local provider should do // the envvar-to-agent.conf migration. - StorageDir = agent.StorageDir - StorageAddr = agent.StorageAddr - SharedStorageDir = agent.SharedStorageDir - SharedStorageAddr = agent.SharedStorageAddr - StorageCACert = "StorageCACert" - StorageCAKey = "StorageCAKey" - StorageHostnames = "StorageHostnames" - StorageAuthKey = "StorageAuthKey" + StorageDir = agent.StorageDir + StorageAddr = agent.StorageAddr + StorageCACert = "StorageCACert" + StorageCAKey = "StorageCAKey" + StorageHostnames = "StorageHostnames" + StorageAuthKey = "StorageAuthKey" ) // LocalStorageConfig is an interface that, if implemented, may be used @@ -30,8 +28,6 @@ type LocalStorageConfig interface { StorageDir() string StorageAddr() string - SharedStorageDir() string - SharedStorageAddr() string } // LocalTLSStorageConfig is an interface that extends LocalStorageConfig @@ -55,14 +51,12 @@ } type config struct { - storageDir string - storageAddr string - sharedStorageDir string - sharedStorageAddr string - caCertPEM []byte - caKeyPEM []byte - hostnames []string - authkey string + storageDir string + storageAddr string + caCertPEM []byte + caKeyPEM []byte + hostnames []string + authkey string } // StoreConfig takes a LocalStorageConfig (or derivative interface), @@ -72,8 +66,6 @@ kv := make(map[string]string) kv[StorageDir] = storageConfig.StorageDir() kv[StorageAddr] = storageConfig.StorageAddr() - kv[SharedStorageDir] = storageConfig.SharedStorageDir() - kv[SharedStorageAddr] = storageConfig.SharedStorageAddr() if tlsConfig, ok := storageConfig.(LocalTLSStorageConfig); ok { if authkey := tlsConfig.StorageAuthKey(); authkey != "" { kv[StorageAuthKey] = authkey @@ -97,11 +89,9 @@ func loadConfig(agentConfig agent.Config) (*config, error) { config := &config{ - storageDir: agentConfig.Value(StorageDir), - storageAddr: agentConfig.Value(StorageAddr), - sharedStorageDir: agentConfig.Value(SharedStorageDir), - sharedStorageAddr: agentConfig.Value(SharedStorageAddr), - authkey: agentConfig.Value(StorageAuthKey), + storageDir: agentConfig.Value(StorageDir), + storageAddr: agentConfig.Value(StorageAddr), + authkey: agentConfig.Value(StorageAuthKey), } caCertPEM := agentConfig.Value(StorageCACert) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/localstorage/config_test.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/localstorage/config_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/localstorage/config_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/localstorage/config_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -21,10 +21,8 @@ } type localStorageConfig struct { - storageDir string - storageAddr string - sharedStorageDir string - sharedStorageAddr string + storageDir string + storageAddr string } func (c *localStorageConfig) StorageDir() string { @@ -35,14 +33,6 @@ return c.storageAddr } -func (c *localStorageConfig) SharedStorageDir() string { - return c.sharedStorageDir -} - -func (c *localStorageConfig) SharedStorageAddr() string { - return c.sharedStorageAddr -} - type localTLSStorageConfig struct { localStorageConfig caCertPEM []byte @@ -72,23 +62,17 @@ m, err := localstorage.StoreConfig(&config) c.Assert(err, gc.IsNil) c.Assert(m, gc.DeepEquals, map[string]string{ - localstorage.StorageDir: "", - localstorage.StorageAddr: "", - localstorage.SharedStorageDir: "", - localstorage.SharedStorageAddr: "", + localstorage.StorageDir: "", + localstorage.StorageAddr: "", }) config.storageDir = "a" config.storageAddr = "b" - config.sharedStorageDir = "c" - config.sharedStorageAddr = "d" m, err = localstorage.StoreConfig(&config) c.Assert(err, gc.IsNil) c.Assert(m, gc.DeepEquals, map[string]string{ - localstorage.StorageDir: config.storageDir, - localstorage.StorageAddr: config.storageAddr, - localstorage.SharedStorageDir: config.sharedStorageDir, - localstorage.SharedStorageAddr: config.sharedStorageAddr, + localstorage.StorageDir: config.storageDir, + localstorage.StorageAddr: config.storageAddr, }) } @@ -97,16 +81,12 @@ m, err := localstorage.StoreConfig(&config) c.Assert(err, gc.IsNil) c.Assert(m, gc.DeepEquals, map[string]string{ - localstorage.StorageDir: "", - localstorage.StorageAddr: "", - localstorage.SharedStorageDir: "", - localstorage.SharedStorageAddr: "", + localstorage.StorageDir: "", + localstorage.StorageAddr: "", }) config.storageDir = "a" config.storageAddr = "b" - config.sharedStorageDir = "c" - config.sharedStorageAddr = "d" config.caCertPEM = []byte("heyhey") config.caKeyPEM = []byte("hoho") config.hostnames = []string{"easy", "as", "1.2.3"} @@ -114,14 +94,12 @@ m, err = localstorage.StoreConfig(&config) c.Assert(err, gc.IsNil) c.Assert(m, gc.DeepEquals, map[string]string{ - localstorage.StorageDir: config.storageDir, - localstorage.StorageAddr: config.storageAddr, - localstorage.SharedStorageDir: config.sharedStorageDir, - localstorage.SharedStorageAddr: config.sharedStorageAddr, - localstorage.StorageCACert: string(config.caCertPEM), - localstorage.StorageCAKey: string(config.caKeyPEM), - localstorage.StorageHostnames: mustMarshalYAML(c, config.hostnames), - localstorage.StorageAuthKey: config.authkey, + localstorage.StorageDir: config.storageDir, + localstorage.StorageAddr: config.storageAddr, + localstorage.StorageCACert: string(config.caCertPEM), + localstorage.StorageCAKey: string(config.caKeyPEM), + localstorage.StorageHostnames: mustMarshalYAML(c, config.hostnames), + localstorage.StorageAuthKey: config.authkey, }) } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/localstorage/worker.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/localstorage/worker.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/localstorage/worker.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/localstorage/worker.go 2014-02-20 16:10:15.000000000 +0000 @@ -79,17 +79,6 @@ } defer storageListener.Close() - if config.sharedStorageAddr != "" && config.sharedStorageDir != "" { - sharedStorageListener, err := s.serveStorage(config.sharedStorageAddr, config.sharedStorageDir, config) - if err != nil { - logger.Errorf("error with local storage: %v", err) - return err - } - defer sharedStorageListener.Close() - } else { - logger.Infof("no shared storage: dir=%q addr=%q", config.sharedStorageDir, config.sharedStorageAddr) - } - logger.Infof("storage routines started, awaiting death") <-s.tomb.Dying() diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/machineenvironmentworker/machineenvironmentworker.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/machineenvironmentworker/machineenvironmentworker.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/machineenvironmentworker/machineenvironmentworker.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/machineenvironmentworker/machineenvironmentworker.go 2014-02-20 16:10:15.000000000 +0000 @@ -65,8 +65,9 @@ func NewMachineEnvironmentWorker(api *environment.Facade, agentConfig agent.Config) worker.Worker { // We don't write out system files for the local provider on machine zero // as that is the host machine. - writeSystemFiles := !(agentConfig.Tag() == names.MachineTag("0") && - agentConfig.Value(agent.JujuProviderType) == provider.Local) + writeSystemFiles := (agentConfig.Tag() != names.MachineTag("0") || + agentConfig.Value(agent.ProviderType) != provider.Local) + logger.Debugf("write system files: %v", writeSystemFiles) envWorker := &MachineEnvironmentWorker{ api: api, writeSystemFiles: writeSystemFiles, diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/machineenvironmentworker/machineenvironmentworker_test.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/machineenvironmentworker/machineenvironmentworker_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/machineenvironmentworker/machineenvironmentworker_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/machineenvironmentworker/machineenvironmentworker_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -211,7 +211,7 @@ } func (mock *mockConfig) Value(key string) string { - if key == agent.JujuProviderType { + if key == agent.ProviderType { return mock.provider } return "" diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/peergrouper/desired.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/peergrouper/desired.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/peergrouper/desired.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/peergrouper/desired.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,291 @@ +package peergrouper + +import ( + "fmt" + "sort" + + "launchpad.net/loggo" + + "launchpad.net/juju-core/replicaset" +) + +var logger = loggo.GetLogger("juju.worker.peergrouper") + +// peerGroupInfo holds information that may contribute to +// a peer group. +type peerGroupInfo struct { + machines []*machine // possibly map[id] *machine + statuses []replicaset.MemberStatus + members []replicaset.Member +} + +// machine represents a machine in State. +type machine struct { + id string + wantsVote bool + hostPort string +} + +// desiredPeerGroup returns the mongo peer group according to the given +// servers and a map with an element for each machine in info.machines +// specifying whether that machine has been configured as voting. It may +// return (nil, nil, nil) if the current group is already correct. +func desiredPeerGroup(info *peerGroupInfo) ([]replicaset.Member, map[*machine]bool, error) { + changed := false + members, extra, maxId := info.membersMap() + + // We may find extra peer group members if the machines + // have been removed or their state server status removed. + // This should only happen if they had been set to non-voting + // before removal, in which case we want to remove it + // from the members list. If we find a member that's still configured + // to vote, it's an error. + // TODO There are some other possibilities + // for what to do in that case. + // 1) leave them untouched, but deal + // with others as usual "i didn't see that bit" + // 2) leave them untouched, deal with others, + // but make sure the extras aren't eligible to + // be primary. + // 3) remove them "get rid of bad rubbish" + // 4) bomb out "run in circles, scream and shout" + // 5) do nothing "nothing to see here" + for _, member := range extra { + if member.Votes == nil || *member.Votes > 0 { + return nil, nil, fmt.Errorf("voting non-machine member found in peer group") + } + changed = true + } + + toRemoveVote, toAddVote, toKeep := possiblePeerGroupChanges(info, members) + + // Set up initial record of machine votes. Any changes after + // this will trigger a peer group election. + machineVoting := make(map[*machine]bool) + for _, m := range info.machines { + if member := members[m]; member != nil && isVotingMember(member) { + machineVoting[m] = true + } + } + setVoting := func(m *machine, voting bool) { + setMemberVoting(members[m], voting) + machineVoting[m] = voting + changed = true + } + adjustVotes(toRemoveVote, toAddVote, setVoting) + + addNewMembers(members, toKeep, maxId, setVoting) + if updateAddresses(members, info.machines) { + changed = true + } + if !changed { + return nil, nil, nil + } + var memberSet []replicaset.Member + for _, member := range members { + memberSet = append(memberSet, *member) + } + return memberSet, machineVoting, nil +} + +func isVotingMember(member *replicaset.Member) bool { + return member.Votes == nil || *member.Votes > 0 +} + +// possiblePeerGroupChanges returns a set of slices +// classifying all the existing machines according to +// how their vote might move. +// toRemoveVote holds machines whose vote should +// be removed; toAddVote holds machines which are +// ready to vote; toKeep holds machines with no desired +// change to their voting status (this includes machines +// that are not yet represented in the peer group). +func possiblePeerGroupChanges( + info *peerGroupInfo, + members map[*machine]*replicaset.Member, +) (toRemoveVote, toAddVote, toKeep []*machine) { + statuses := info.statusesMap(members) + + for _, m := range info.machines { + member := members[m] + isVoting := member != nil && isVotingMember(member) + switch { + case m.wantsVote && isVoting: + toKeep = append(toKeep, m) + case m.wantsVote && !isVoting: + if status, ok := statuses[m]; ok && isReady(status) { + toAddVote = append(toAddVote, m) + } else { + toKeep = append(toKeep, m) + } + case !m.wantsVote && isVoting: + toRemoveVote = append(toRemoveVote, m) + case !m.wantsVote && !isVoting: + toKeep = append(toKeep, m) + } + } + // sort machines to be added and removed so that we + // get deterministic behaviour when testing. Earlier + // entries will be dealt with preferentially, so we could + // potentially sort by some other metric in each case. + sort.Sort(byId(toRemoveVote)) + sort.Sort(byId(toAddVote)) + sort.Sort(byId(toKeep)) + return toRemoveVote, toAddVote, toKeep +} + +// updateAddresses updates the members' addresses from the machines' addresses. +// It reports whether any changes have been made. +func updateAddresses(members map[*machine]*replicaset.Member, machines []*machine) bool { + changed := false + // Make sure all members' machine addresses are up to date. + for _, m := range machines { + if m.hostPort == "" { + continue + } + // TODO ensure that replicaset works correctly with IPv6 [host]:port addresses. + if m.hostPort != members[m].Address { + members[m].Address = m.hostPort + changed = true + } + } + return changed +} + +// adjustVotes adjusts the votes of the given machines, taking +// care not to let the total number of votes become even at +// any time. It calls setVoting to change the voting status +// of a machine. +func adjustVotes(toRemoveVote, toAddVote []*machine, setVoting func(*machine, bool)) { + // Remove voting members if they can be replaced by + // candidates that are ready. This does not affect + // the total number of votes. + nreplace := min(len(toRemoveVote), len(toAddVote)) + for i := 0; i < nreplace; i++ { + from := toRemoveVote[i] + to := toAddVote[i] + setVoting(from, false) + setVoting(to, true) + } + toAddVote = toAddVote[nreplace:] + toRemoveVote = toRemoveVote[nreplace:] + + // At this point, one or both of toAdd or toRemove is empty, so + // we can adjust the voting-member count by an even delta, + // maintaining the invariant that the total vote count is odd. + if len(toAddVote) > 0 { + toAddVote = toAddVote[0 : len(toAddVote)-len(toAddVote)%2] + for _, m := range toAddVote { + setVoting(m, true) + } + } else { + toRemoveVote = toRemoveVote[0 : len(toRemoveVote)-len(toRemoveVote)%2] + for _, m := range toRemoveVote { + setVoting(m, false) + } + } +} + +// addNewMembers adds new members from toKeep +// to the given set of members, allocating ids from +// maxId upwards. It calls setVoting to set the voting +// status of each new member. +func addNewMembers( + members map[*machine]*replicaset.Member, + toKeep []*machine, + maxId int, + setVoting func(*machine, bool), +) { + for _, m := range toKeep { + if members[m] == nil && m.hostPort != "" { + // This machine was not previously in the members list, + // so add it (as non-voting). We maintain the + // id manually to make it easier for tests. + maxId++ + member := &replicaset.Member{ + Tags: map[string]string{ + "juju-machine-id": m.id, + }, + Id: maxId, + } + members[m] = member + setVoting(m, false) + } + } +} + +func isReady(status replicaset.MemberStatus) bool { + return status.Healthy && (status.State == replicaset.PrimaryState || + status.State == replicaset.SecondaryState) +} + +func setMemberVoting(member *replicaset.Member, voting bool) { + if voting { + member.Votes = nil + member.Priority = nil + } else { + votes := 0 + member.Votes = &votes + priority := 0.0 + member.Priority = &priority + } +} + +type byId []*machine + +func (l byId) Len() int { return len(l) } +func (l byId) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l byId) Less(i, j int) bool { return l[i].id < l[j].id } + +// membersMap returns the replica-set members inside info keyed +// by machine. Any members that do not have a corresponding +// machine are returned in extra. +// The maximum replica-set id is returned in maxId. +func (info *peerGroupInfo) membersMap() (members map[*machine]*replicaset.Member, extra []replicaset.Member, maxId int) { + maxId = -1 + members = make(map[*machine]*replicaset.Member) + for _, member := range info.members { + member := member + var found *machine + if mid, ok := member.Tags["juju-machine-id"]; ok { + for _, m := range info.machines { + if m.id == mid { + found = m + break + } + } + } + if found != nil { + members[found] = &member + } else { + extra = append(extra, member) + } + if member.Id > maxId { + maxId = member.Id + } + } + return members, extra, maxId +} + +// statusesMap returns the statuses inside info keyed by machine. +// The provided members map holds the members keyed by machine, +// as returned by membersMap. +func (info *peerGroupInfo) statusesMap(members map[*machine]*replicaset.Member) map[*machine]replicaset.MemberStatus { + statuses := make(map[*machine]replicaset.MemberStatus) + for _, status := range info.statuses { + for m, member := range members { + if member.Id == status.Id { + statuses[m] = status + break + } + } + } + return statuses +} + +func min(i, j int) int { + if i < j { + return i + } + return j +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/peergrouper/desired_test.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/peergrouper/desired_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/peergrouper/desired_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/peergrouper/desired_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,359 @@ +package peergrouper + +import ( + "fmt" + "sort" + "strconv" + "strings" + stdtesting "testing" + + gc "launchpad.net/gocheck" + + "launchpad.net/juju-core/replicaset" + jc "launchpad.net/juju-core/testing/checkers" + "launchpad.net/juju-core/testing/testbase" +) + +func TestPackage(t *stdtesting.T) { + gc.TestingT(t) +} + +type desiredPeerGroupSuite struct { + testbase.LoggingSuite +} + +var _ = gc.Suite(&desiredPeerGroupSuite{}) + +const mongoPort = 1234 + +var desiredPeerGroupTests = []struct { + about string + machines []*machine + statuses []replicaset.MemberStatus + members []replicaset.Member + + expectMembers []replicaset.Member + expectVoting []bool + expectErr string +}{{ + about: "single machine, no change", + machines: mkMachines("11v"), + members: mkMembers("1v"), + statuses: mkStatuses("1p"), + expectVoting: []bool{true}, + expectMembers: nil, +}, { + about: "extra member with nil Vote", + machines: mkMachines("11v"), + members: mkMembers("1v 2vT"), + statuses: mkStatuses("1p 2s"), + expectVoting: []bool{true}, + expectErr: "voting non-machine member found in peer group", +}, { + about: "extra member with >1 votes", + machines: mkMachines("11v"), + members: append(mkMembers("1v"), replicaset.Member{ + Id: 2, + Votes: newInt(2), + Address: "0.1.2.12:1234", + }), + statuses: mkStatuses("1p 2s"), + expectVoting: []bool{true}, + expectErr: "voting non-machine member found in peer group", +}, { + about: "new machine with no associated member", + machines: mkMachines("11v 12v"), + members: mkMembers("1v"), + statuses: mkStatuses("1p"), + expectVoting: []bool{true, false}, + expectMembers: mkMembers("1v 2"), +}, { + about: "one machine has become ready to vote (-> no change)", + machines: mkMachines("11v 12v"), + members: mkMembers("1v 2"), + statuses: mkStatuses("1p 2s"), + expectVoting: []bool{true, false}, + expectMembers: nil, +}, { + about: "two machines have become ready to vote (-> added)", + machines: mkMachines("11v 12v 13v"), + members: mkMembers("1v 2 3"), + statuses: mkStatuses("1p 2s 3s"), + expectVoting: []bool{true, true, true}, + expectMembers: mkMembers("1v 2v 3v"), +}, { + about: "two machines have become ready to vote but one is not healthy (-> no change)", + machines: mkMachines("11v 12v 13v"), + members: mkMembers("1v 2 3"), + statuses: mkStatuses("1p 2s 3sH"), + expectVoting: []bool{true, false, false}, + expectMembers: nil, +}, { + about: "three machines have become ready to vote (-> 2 added)", + machines: mkMachines("11v 12v 13v 14v"), + members: mkMembers("1v 2 3 4"), + statuses: mkStatuses("1p 2s 3s 4s"), + expectVoting: []bool{true, true, true, false}, + expectMembers: mkMembers("1v 2v 3v 4"), +}, { + about: "one machine ready to lose vote with no others -> no change", + machines: mkMachines("11"), + members: mkMembers("1v"), + statuses: mkStatuses("1p"), + expectVoting: []bool{true}, + expectMembers: nil, +}, { + about: "two machines ready to lose vote -> votes removed", + machines: mkMachines("11 12v 13"), + members: mkMembers("1v 2v 3v"), + statuses: mkStatuses("1p 2p 3p"), + expectVoting: []bool{false, true, false}, + expectMembers: mkMembers("1 2v 3"), +}, { + about: "machines removed as state server -> removed from members", + machines: mkMachines("11v"), + members: mkMembers("1v 2 3"), + statuses: mkStatuses("1p 2s 3s"), + expectVoting: []bool{true}, + expectMembers: mkMembers("1v"), +}, { + about: "a candidate can take the vote of a non-candidate when they're ready", + machines: mkMachines("11v 12v 13 14v"), + members: mkMembers("1v 2v 3v 4"), + statuses: mkStatuses("1p 2s 3s 4s"), + expectVoting: []bool{true, true, false, true}, + expectMembers: mkMembers("1v 2v 3 4v"), +}, { + about: "several candidates can take non-candidates' votes", + machines: mkMachines("11v 12v 13 14 15 16v 17v 18v"), + members: mkMembers("1v 2v 3v 4v 5v 6 7 8"), + statuses: mkStatuses("1p 2s 3s 4s 5s 6s 7s 8s"), + expectVoting: []bool{true, true, false, false, false, true, true, true}, + expectMembers: mkMembers("1v 2v 3 4 5 6v 7v 8v"), +}, { + about: "a changed machine address should propagate to the members", + machines: append(mkMachines("11v 12v"), &machine{ + id: "13", + wantsVote: true, + hostPort: "0.1.99.13:1234", + }), + statuses: mkStatuses("1s 2p 3p"), + members: mkMembers("1v 2v 3v"), + expectVoting: []bool{true, true, true}, + expectMembers: append(mkMembers("1v 2v"), replicaset.Member{ + Id: 3, + Address: "0.1.99.13:1234", + Tags: memberTag("13"), + }), +}} + +func (*desiredPeerGroupSuite) TestDesiredPeerGroup(c *gc.C) { + for i, test := range desiredPeerGroupTests { + c.Logf("\ntest %d: %s", i, test.about) + info := &peerGroupInfo{ + machines: test.machines, + statuses: test.statuses, + members: test.members, + } + members, voting, err := desiredPeerGroup(info) + if test.expectErr != "" { + c.Assert(err, gc.ErrorMatches, test.expectErr) + c.Assert(members, gc.IsNil) + continue + } + sort.Sort(membersById(members)) + c.Assert(members, jc.DeepEquals, test.expectMembers) + if len(members) == 0 { + continue + } + for i, m := range info.machines { + c.Assert(voting[m], gc.Equals, test.expectVoting[i], gc.Commentf("machine %s", m.id)) + } + // Assure ourselves that the total number of desired votes is odd in + // all circumstances. + c.Assert(countVotes(members)%2, gc.Equals, 1) + + // Make sure that when the members are set as + // required, that there's no further change + // if desiredPeerGroup is called again. + info.members = members + members, voting, err = desiredPeerGroup(info) + c.Assert(members, gc.IsNil) + c.Assert(voting, gc.IsNil) + c.Assert(err, gc.IsNil) + } +} + +func countVotes(members []replicaset.Member) int { + tot := 0 + for _, m := range members { + v := 1 + if m.Votes != nil { + v = *m.Votes + } + tot += v + } + return tot +} + +func newInt(i int) *int { + return &i +} + +func newFloat64(f float64) *float64 { + return &f +} + +// mkMachines returns a slice of *machine based on +// the given description. +// Each machine in the description is white-space separated +// and holds the decimal machine id followed by an optional +// "v" if the machine wants a vote. +func mkMachines(description string) []*machine { + descrs := parseDescr(description) + ms := make([]*machine, len(descrs)) + for i, d := range descrs { + ms[i] = &machine{ + id: fmt.Sprint(d.id), + hostPort: fmt.Sprintf("0.1.2.%d:%d", d.id, mongoPort), + wantsVote: strings.Contains(d.flags, "v"), + } + } + return ms +} + +func memberTag(id string) map[string]string { + return map[string]string{"juju-machine-id": id} +} + +// mkMembers returns a slice of *replicaset.Member +// based on the given description. +// Each member in the description is white-space separated +// and holds the decimal replica-set id optionally followed by the characters: +// - 'v' if the member is voting. +// - 'T' if the member has no associated machine tags. +// Unless the T flag is specified, the machine tag +// will be the replica-set id + 10. +func mkMembers(description string) []replicaset.Member { + descrs := parseDescr(description) + ms := make([]replicaset.Member, len(descrs)) + for i, d := range descrs { + machineId := d.id + 10 + m := replicaset.Member{ + Id: d.id, + Address: fmt.Sprintf("0.1.2.%d:%d", machineId, mongoPort), + Tags: memberTag(fmt.Sprint(machineId)), + } + if !strings.Contains(d.flags, "v") { + m.Priority = newFloat64(0) + m.Votes = newInt(0) + } + if strings.Contains(d.flags, "T") { + m.Tags = nil + } + ms[i] = m + } + return ms +} + +var stateFlags = map[rune]replicaset.MemberState{ + 'p': replicaset.PrimaryState, + 's': replicaset.SecondaryState, +} + +// mkStatuses returns a slice of *replicaset.Member +// based on the given description. +// Each member in the description is white-space separated +// and holds the decimal replica-set id optionally followed by the +// characters: +// - 'H' if the instance is not healthy. +// - 'p' if the instance is in PrimaryState +// - 's' if the instance is in SecondaryState +func mkStatuses(description string) []replicaset.MemberStatus { + descrs := parseDescr(description) + ss := make([]replicaset.MemberStatus, len(descrs)) + for i, d := range descrs { + machineId := d.id + 10 + s := replicaset.MemberStatus{ + Id: d.id, + Address: fmt.Sprintf("0.1.2.%d:%d", machineId, mongoPort), + Healthy: !strings.Contains(d.flags, "H"), + State: replicaset.UnknownState, + } + for _, r := range d.flags { + if state, ok := stateFlags[r]; ok { + s.State = state + } + } + ss[i] = s + } + return ss +} + +type descr struct { + id int + flags string +} + +func isNotDigit(r rune) bool { + return r < '0' || r > '9' +} + +var parseDescrTests = []struct { + descr string + expect []descr +}{{ + descr: "", + expect: []descr{}, +}, { + descr: "0", + expect: []descr{{id: 0}}, +}, { + descr: "1foo", + expect: []descr{{id: 1, flags: "foo"}}, +}, { + descr: "10c 5 6443arble ", + expect: []descr{{ + id: 10, + flags: "c", + }, { + id: 5, + }, { + id: 6443, + flags: "arble", + }}, +}} + +func (*desiredPeerGroupSuite) TestParseDescr(c *gc.C) { + for i, test := range parseDescrTests { + c.Logf("test %d. %q", i, test.descr) + c.Assert(parseDescr(test.descr), jc.DeepEquals, test.expect) + } +} + +// parseDescr parses white-space separated fields of the form +// into descr structures. +func parseDescr(s string) []descr { + fields := strings.Fields(s) + descrs := make([]descr, len(fields)) + for i, field := range fields { + d := &descrs[i] + i := strings.IndexFunc(field, isNotDigit) + if i == -1 { + i = len(field) + } + id, err := strconv.Atoi(field[0:i]) + if err != nil { + panic(fmt.Errorf("bad field %q", field)) + } + d.id = id + d.flags = field[i:] + } + return descrs +} + +type membersById []replicaset.Member + +func (l membersById) Len() int { return len(l) } +func (l membersById) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l membersById) Less(i, j int) bool { return l[i].Id < l[j].Id } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/provisioner/kvm-broker.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/provisioner/kvm-broker.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/provisioner/kvm-broker.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/provisioner/kvm-broker.go 2014-02-20 16:10:15.000000000 +0000 @@ -85,6 +85,8 @@ config.AuthorizedKeys, config.SSLHostnameVerification, config.SyslogPort, + config.Proxy, + config.AptProxy, ); err != nil { kvmLogger.Errorf("failed to populate machine config: %v", err) return nil, nil, err diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/provisioner/lxc-broker.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/provisioner/lxc-broker.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/provisioner/lxc-broker.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/provisioner/lxc-broker.go 2014-02-20 16:10:15.000000000 +0000 @@ -75,6 +75,8 @@ config.AuthorizedKeys, config.SSLHostnameVerification, config.SyslogPort, + config.Proxy, + config.AptProxy, ); err != nil { lxcLogger.Errorf("failed to populate machine config: %v", err) return nil, nil, err diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/provisioner/lxc-broker_test.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/provisioner/lxc-broker_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/provisioner/lxc-broker_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/provisioner/lxc-broker_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -282,5 +282,9 @@ type fakeAPI struct{} func (*fakeAPI) ContainerConfig() (params.ContainerConfig, error) { - return params.ContainerConfig{"fake", coretesting.FakeAuthKeys, true, 2345}, nil + return params.ContainerConfig{ + ProviderType: "fake", + AuthorizedKeys: coretesting.FakeAuthKeys, + SSLHostnameVerification: true, + SyslogPort: 2345}, nil } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/provisioner/provisioner_test.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/provisioner/provisioner_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/provisioner/provisioner_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/provisioner/provisioner_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -57,6 +57,12 @@ } func (s *CommonProvisionerSuite) SetUpTest(c *gc.C) { + // Disable the default state policy, because the + // provisioner needs to be able to test pathological + // scenarios where a machine exists in state with + // invalid environment config. + dummy.SetStatePolicy(nil) + s.JujuConnSuite.SetUpTest(c) // Create the operations channel with more than enough space // for those tests that don't listen on it. diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/context.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/context.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/context.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/context.go 2014-02-20 16:10:15.000000000 +0000 @@ -60,6 +60,9 @@ // uuid is the universally unique identifier of the environment. uuid string + // envName is the human friendly name of the environment. + envName string + // relationId identifies the relation for which a relation hook is // executing. If it is -1, the context is not running a relation hook; // otherwise, its value must be a valid key into the relations map. @@ -84,13 +87,14 @@ proxySettings osenv.ProxySettings } -func NewHookContext(unit *uniter.Unit, id, uuid string, relationId int, - remoteUnitName string, relations map[int]*ContextRelation, +func NewHookContext(unit *uniter.Unit, id, uuid, envName string, + relationId int, remoteUnitName string, relations map[int]*ContextRelation, apiAddrs []string, serviceOwner string, proxySettings osenv.ProxySettings) (*HookContext, error) { ctx := &HookContext{ unit: unit, id: id, uuid: uuid, + envName: envName, relationId: relationId, remoteUnitName: remoteUnitName, relations: relations, @@ -184,6 +188,7 @@ "JUJU_AGENT_SOCKET=" + socketPath, "JUJU_UNIT_NAME=" + ctx.unit.Name(), "JUJU_ENV_UUID=" + ctx.uuid, + "JUJU_ENV_NAME=" + ctx.envName, "JUJU_API_ADDRESSES=" + strings.Join(ctx.apiAddrs, " "), } if r, found := ctx.HookRelation(); found { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/context_test.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/context_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/context_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/context_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -181,6 +181,7 @@ env: map[string]string{ "JUJU_UNIT_NAME": "u/0", "JUJU_API_ADDRESSES": expectedApiAddrs, + "JUJU_ENV_NAME": "test-env-name", "http_proxy": "http", "HTTP_PROXY": "http", "https_proxy": "https", @@ -195,6 +196,7 @@ env: map[string]string{ "JUJU_UNIT_NAME": "u/0", "JUJU_API_ADDRESSES": expectedApiAddrs, + "JUJU_ENV_NAME": "test-env-name", "JUJU_RELATION": "db", "JUJU_RELATION_ID": "db:1", "JUJU_REMOTE_UNIT": "", @@ -207,6 +209,7 @@ env: map[string]string{ "JUJU_UNIT_NAME": "u/0", "JUJU_API_ADDRESSES": expectedApiAddrs, + "JUJU_ENV_NAME": "test-env-name", "JUJU_RELATION": "db", "JUJU_RELATION_ID": "db:1", "JUJU_REMOTE_UNIT": "r/1", @@ -428,7 +431,7 @@ // ...and that its settings are no longer cached. _, err := ctx.ReadSettings("u/2") - c.Assert(err, gc.ErrorMatches, "permission denied") + c.Assert(err, gc.ErrorMatches, "cannot read settings for unit \"u/2\" in relation \"u:ring\": settings not found") } func (s *ContextRelationSuite) TestMemberCaching(c *gc.C) { @@ -710,8 +713,9 @@ _, found := s.relctxs[relid] c.Assert(found, gc.Equals, true) } - context, err := uniter.NewHookContext(s.apiUnit, "TestCtx", uuid, relid, remote, - s.relctxs, apiAddrs, "test-owner", proxies) + context, err := uniter.NewHookContext(s.apiUnit, "TestCtx", uuid, + "test-env-name", relid, remote, s.relctxs, apiAddrs, "test-owner", + proxies) c.Assert(err, gc.IsNil) return context } @@ -764,6 +768,7 @@ "JUJU_CONTEXT_ID": "TestCtx", "JUJU_AGENT_SOCKET": "/path/to/socket", "JUJU_UNIT_NAME": "u/0", + "JUJU_ENV_NAME": "test-env-name", } for key, value := range expected { c.Check(executionEnvironment[key], gc.Equals, value) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/debug/server_test.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/debug/server_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/debug/server_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/debug/server_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -39,7 +39,7 @@ func (s *DebugHooksServerSuite) SetUpTest(c *gc.C) { s.fakebin = c.MkDir() s.tmpdir = c.MkDir() - s.PatchEnvironment("PATH", s.fakebin+":"+os.Getenv("PATH")) + s.PatchEnvPathPrepend(s.fakebin) s.PatchEnvironment("TMPDIR", s.tmpdir) s.PatchEnvironment("TEST_RESULT", "") for _, name := range fakecommands { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/jujuc/config-get.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/jujuc/config-get.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/jujuc/config-get.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/jujuc/config-get.go 2014-02-20 16:10:15.000000000 +0000 @@ -7,6 +7,7 @@ "fmt" "launchpad.net/gnuflag" + "launchpad.net/juju-core/cmd" ) diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/uniter.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/uniter.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/uniter.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/uniter.go 2014-02-20 16:10:15.000000000 +0000 @@ -66,6 +66,7 @@ relationers map[int]*Relationer relationHooks chan hook.Info uuid string + envName string dataDir string baseDir string @@ -189,10 +190,8 @@ if err != nil { return err } - u.uuid, err = env.UUID() - if err != nil { - return err - } + u.uuid = env.UUID() + u.envName = env.Name() runListenerSocketPath := filepath.Join(u.baseDir, RunListenerFile) logger.Debugf("starting juju-run listener on %s:%s", RunListenerNetType, runListenerSocketPath) @@ -341,8 +340,8 @@ // Make a copy of the proxy settings. proxySettings := u.proxy - return NewHookContext(u.unit, hctxId, u.uuid, relationId, remoteUnitName, - ctxRelations, apiAddrs, ownerTag, proxySettings) + return NewHookContext(u.unit, hctxId, u.uuid, u.envName, relationId, + remoteUnitName, ctxRelations, apiAddrs, ownerTag, proxySettings) } func (u *Uniter) acquireHookLock(message string) (err error) { diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/uniter_test.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/uniter_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/uniter/uniter_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/uniter/uniter_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -848,7 +848,9 @@ return filepath.Join(testDir, name) } echoUnitNameToFile := func(name string) string { - return fmt.Sprintf("echo juju run ${JUJU_UNIT_NAME} > %s", filepath.Join(testDir, name)) + path := filepath.Join(testDir, name) + template := "echo juju run ${JUJU_UNIT_NAME} > %s.tmp; mv %s.tmp %s" + return fmt.Sprintf(template, path, path, path) } tests := []uniterTest{ ut( diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/upgrader/error.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/upgrader/error.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/upgrader/error.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/upgrader/error.go 2014-02-20 16:10:15.000000000 +0000 @@ -0,0 +1,34 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package upgrader + +import ( + agenttools "launchpad.net/juju-core/agent/tools" + "launchpad.net/juju-core/version" +) + +// UpgradeReadyError is returned by an Upgrader to report that +// an upgrade is ready to be performed and a restart is due. +type UpgradeReadyError struct { + AgentName string + OldTools version.Binary + NewTools version.Binary + DataDir string +} + +func (e *UpgradeReadyError) Error() string { + return "must restart: an agent upgrade is available" +} + +// ChangeAgentTools does the actual agent upgrade. +// It should be called just before an agent exits, so that +// it will restart running the new tools. +func (e *UpgradeReadyError) ChangeAgentTools() error { + tools, err := agenttools.ChangeAgentTools(e.DataDir, e.AgentName, e.NewTools) + if err != nil { + return err + } + logger.Infof("upgraded from %v to %v (%q)", e.OldTools, tools.Version, tools.URL) + return nil +} diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/upgrader/upgrader.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/upgrader/upgrader.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/upgrader/upgrader.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/upgrader/upgrader.go 2014-02-20 16:10:15.000000000 +0000 @@ -26,31 +26,6 @@ return time.After(5 * time.Second) } -// UpgradeReadyError is returned by an Upgrader to report that -// an upgrade is ready to be performed and a restart is due. -type UpgradeReadyError struct { - AgentName string - OldTools *coretools.Tools - NewTools *coretools.Tools - DataDir string -} - -func (e *UpgradeReadyError) Error() string { - return "must restart: an agent upgrade is available" -} - -// ChangeAgentTools does the actual agent upgrade. -// It should be called just before an agent exits, so that -// it will restart running the new tools. -func (e *UpgradeReadyError) ChangeAgentTools() error { - tools, err := agenttools.ChangeAgentTools(e.DataDir, e.AgentName, e.NewTools.Version) - if err != nil { - return err - } - logger.Infof("upgraded from %v to %v (%q)", e.OldTools.Version, tools.Version, tools.URL) - return nil -} - var logger = loggo.GetLogger("juju.worker.upgrader") // Upgrader represents a worker that watches the state for upgrade @@ -155,8 +130,8 @@ err := u.ensureTools(wantTools, disableSSLHostnameVerification) if err == nil { return &UpgradeReadyError{ - OldTools: currentTools, - NewTools: wantTools, + OldTools: version.Current, + NewTools: wantTools.Version, AgentName: u.tag, DataDir: u.dataDir, } diff -Nru juju-core-1.17.2/src/launchpad.net/juju-core/worker/upgrader/upgrader_test.go juju-core-1.17.3/src/launchpad.net/juju-core/worker/upgrader/upgrader_test.go --- juju-core-1.17.2/src/launchpad.net/juju-core/worker/upgrader/upgrader_test.go 2014-01-31 06:10:21.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/juju-core/worker/upgrader/upgrader_test.go 2014-02-20 16:10:15.000000000 +0000 @@ -83,6 +83,7 @@ c.Assert(err, gc.IsNil) stor := s.Conn.Environ.Storage() agentTools := envtesting.PrimeTools(c, stor, s.DataDir(), vers) + s.PatchValue(&version.Current, agentTools.Version) err = envtools.MergeAndWriteMetadata(stor, coretools.List{agentTools}, envtools.DoNotWriteMirrors) _, err = s.machine.AgentTools() c.Assert(err, jc.Satisfies, errors.IsNotFoundError) @@ -97,7 +98,8 @@ func (s *UpgraderSuite) TestUpgraderSetVersion(c *gc.C) { vers := version.MustParseBinary("5.4.3-precise-amd64") - envtesting.PrimeTools(c, s.Conn.Environ.Storage(), s.DataDir(), vers) + agentTools := envtesting.PrimeTools(c, s.Conn.Environ.Storage(), s.DataDir(), vers) + s.PatchValue(&version.Current, agentTools.Version) err := os.RemoveAll(filepath.Join(s.DataDir(), "tools")) c.Assert(err, gc.IsNil) @@ -117,6 +119,7 @@ func (s *UpgraderSuite) TestUpgraderUpgradesImmediately(c *gc.C) { stor := s.Conn.Environ.Storage() oldTools := envtesting.PrimeTools(c, stor, s.DataDir(), version.MustParseBinary("5.4.3-precise-amd64")) + s.PatchValue(&version.Current, oldTools.Version) newTools := envtesting.AssertUploadFakeToolsVersions( c, stor, version.MustParseBinary("5.4.5-precise-amd64"))[0] err := envtools.MergeAndWriteMetadata(stor, coretools.List{oldTools, newTools}, envtools.DoNotWriteMirrors) @@ -133,8 +136,8 @@ err = u.Stop() envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{ AgentName: s.machine.Tag(), - OldTools: oldTools, - NewTools: newTools, + OldTools: oldTools.Version, + NewTools: newTools.Version, DataDir: s.DataDir(), }) foundTools, err := agenttools.ReadTools(s.DataDir(), newTools.Version) @@ -145,6 +148,7 @@ func (s *UpgraderSuite) TestUpgraderRetryAndChanged(c *gc.C) { stor := s.Conn.Environ.Storage() oldTools := envtesting.PrimeTools(c, stor, s.DataDir(), version.MustParseBinary("5.4.3-precise-amd64")) + s.PatchValue(&version.Current, oldTools.Version) newTools := envtesting.AssertUploadFakeToolsVersions( c, stor, version.MustParseBinary("5.4.5-precise-amd64"))[0] err := envtools.MergeAndWriteMetadata(stor, coretools.List{oldTools, newTools}, envtools.DoNotWriteMirrors) @@ -187,8 +191,8 @@ case err := <-done: envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{ AgentName: s.machine.Tag(), - OldTools: oldTools, - NewTools: newerTools, + OldTools: oldTools.Version, + NewTools: newerTools.Version, DataDir: s.DataDir(), }) case <-time.After(coretesting.LongWait): @@ -202,12 +206,13 @@ } stor := s.Conn.Environ.Storage() newTools := envtesting.PrimeTools(c, stor, s.DataDir(), version.MustParseBinary("5.4.3-precise-amd64")) + s.PatchValue(&version.Current, newTools.Version) err := envtools.MergeAndWriteMetadata(stor, coretools.List{newTools}, envtools.DoNotWriteMirrors) c.Assert(err, gc.IsNil) ugErr := &upgrader.UpgradeReadyError{ AgentName: "anAgent", - OldTools: oldTools, - NewTools: newTools, + OldTools: oldTools.Version, + NewTools: newTools.Version, DataDir: s.DataDir(), } err = ugErr.ChangeAgentTools() @@ -220,6 +225,7 @@ func (s *UpgraderSuite) TestEnsureToolsChecksBeforeDownloading(c *gc.C) { stor := s.Conn.Environ.Storage() newTools := envtesting.PrimeTools(c, stor, s.DataDir(), version.MustParseBinary("5.4.3-precise-amd64")) + s.PatchValue(&version.Current, newTools.Version) // We've already downloaded the tools, so change the URL to be // something invalid and ensure we don't actually get an error, because // it doesn't actually do an HTTP request diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/COPYING juju-core-1.17.3/src/launchpad.net/loggo/COPYING --- juju-core-1.17.2/src/launchpad.net/loggo/COPYING 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/COPYING 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/COPYING.LESSER juju-core-1.17.3/src/launchpad.net/loggo/COPYING.LESSER --- juju-core-1.17.2/src/launchpad.net/loggo/COPYING.LESSER 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/COPYING.LESSER 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/example/first.go juju-core-1.17.3/src/launchpad.net/loggo/example/first.go --- juju-core-1.17.2/src/launchpad.net/loggo/example/first.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/example/first.go 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,27 @@ +package main + +import ( + "launchpad.net/loggo" +) + +var first = loggo.GetLogger("first") + +func FirstCritical(message string) { + first.Criticalf(message) +} + +func FirstError(message string) { + first.Errorf(message) +} + +func FirstWarning(message string) { + first.Warningf(message) +} + +func FirstInfo(message string) { + first.Infof(message) +} + +func FirstTrace(message string) { + first.Tracef(message) +} diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/example/main.go juju-core-1.17.3/src/launchpad.net/loggo/example/main.go --- juju-core-1.17.2/src/launchpad.net/loggo/example/main.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/example/main.go 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "os" + + "launchpad.net/loggo" +) + +var logger = loggo.GetLogger("main") +var rootLogger = loggo.GetLogger("") + +func main() { + args := os.Args + if len(args) > 1 { + loggo.ConfigureLoggers(args[1]) + } else { + fmt.Println("Add a parameter to configure the logging:") + fmt.Println("E.g. \"=INFO;first=TRACE\"") + } + fmt.Println("\nCurrent logging levels:") + fmt.Println(loggo.LoggerInfo()) + fmt.Println("") + + rootLogger.Infof("Start of test.") + + FirstCritical("first critical") + FirstError("first error") + FirstWarning("first warning") + FirstInfo("first info") + FirstTrace("first trace") + + SecondCritical("first critical") + SecondError("first error") + SecondWarning("first warning") + SecondInfo("first info") + SecondTrace("first trace") + +} diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/example/second.go juju-core-1.17.3/src/launchpad.net/loggo/example/second.go --- juju-core-1.17.2/src/launchpad.net/loggo/example/second.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/example/second.go 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,27 @@ +package main + +import ( + "launchpad.net/loggo" +) + +var second = loggo.GetLogger("second") + +func SecondCritical(message string) { + second.Criticalf(message) +} + +func SecondError(message string) { + second.Errorf(message) +} + +func SecondWarning(message string) { + second.Warningf(message) +} + +func SecondInfo(message string) { + second.Infof(message) +} + +func SecondTrace(message string) { + second.Tracef(message) +} diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/formatter.go juju-core-1.17.3/src/launchpad.net/loggo/formatter.go --- juju-core-1.17.2/src/launchpad.net/loggo/formatter.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/formatter.go 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,26 @@ +package loggo + +import ( + "fmt" + "path/filepath" + "time" +) + +// Formatter defines the single method Format, which takes the logging +// information, and converts it to a string. +type Formatter interface { + Format(level Level, module, filename string, line int, timestamp time.Time, message string) string +} + +// DefaultFormatter provides a simple concatenation of all the components. +type DefaultFormatter struct{} + +// Format returns the parameters separated by spaces except for filename and +// line which are separated by a colon. The timestamp is shown to second +// resolution in UTC. +func (*DefaultFormatter) Format(level Level, module, filename string, line int, timestamp time.Time, message string) string { + ts := timestamp.In(time.UTC).Format("2006-01-02 15:04:05") + // Just get the basename from the filename + filename = filepath.Base(filename) + return fmt.Sprintf("%s %s %s %s:%d %s", ts, level, module, filename, line, message) +} diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/formatter_test.go juju-core-1.17.3/src/launchpad.net/loggo/formatter_test.go --- juju-core-1.17.2/src/launchpad.net/loggo/formatter_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/formatter_test.go 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,21 @@ +package loggo_test + +import ( + "time" + + . "launchpad.net/gocheck" + "launchpad.net/loggo" +) + +type formatterSuite struct{} + +var _ = Suite(&formatterSuite{}) + +func (*formatterSuite) TestDefaultFormat(c *C) { + location, err := time.LoadLocation("UTC") + c.Assert(err, IsNil) + testTime := time.Date(2013, 5, 3, 10, 53, 24, 123456, location) + formatter := &loggo.DefaultFormatter{} + formatted := formatter.Format(loggo.WARNING, "test.module", "some/deep/filename", 42, testTime, "hello world!") + c.Assert(formatted, Equals, "2013-05-03 10:53:24 WARNING test.module filename:42 hello world!") +} diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/LICENSE juju-core-1.17.3/src/launchpad.net/loggo/LICENSE --- juju-core-1.17.2/src/launchpad.net/loggo/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/LICENSE 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,15 @@ +loggo - A logging library for Go + +Copyright 2013, Canonical Ltd. + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) any +later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + +See both COPYING and COPYING.LESSER for the full terms of the GNU Lesser +General Public License. diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/logger.go juju-core-1.17.3/src/launchpad.net/loggo/logger.go --- juju-core-1.17.2/src/launchpad.net/loggo/logger.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/logger.go 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,421 @@ +// Module level logging for Go +// +// This package provides an alternative to the standard library log package. +// +// The actual logging functions never return errors. If you are logging +// something, you really don't want to be worried about the logging +// having trouble. +// +// Modules have names that are defined by dotted strings. +// e.g. "first.second.third" +// +// There is a root module that has the name "". Each module +// (except the root module) has a parent, identified by the part of +// the name without the last dotted value. +// e.g. the parent of "first.second.third" is "first.second" +// the parent of "first.second" is "first" +// the parent of "first" is "" (the root module) +// +// Each module can specify its own severity level. Logging calls that are of +// a lower severity than the module's effective severity level are not written +// out. +// +// Loggers are created using the GetLogger function. +// e.g. logger := loggo.GetLogger("foo.bar") +// +// By default there is one writer registered, which will write to Stderr, +// and the root module, which will only emit warnings and above. +// If you want to continue using the default +// logger, but have it emit all logging levels you need to do the following. +// +// writer, _, err := loggo.RemoveWriter("default") +// // err is non-nil if and only if the name isn't found. +// loggo.RegisterWriter("default", writer, loggo.TRACE) +package loggo + +import ( + "fmt" + "runtime" + "sort" + "strings" + "sync" + "sync/atomic" + "time" +) + +// Level holds a severity level. +type Level uint32 + +// The severity levels. Higher values are more considered more +// important. +const ( + UNSPECIFIED Level = iota + TRACE + DEBUG + INFO + WARNING + ERROR + CRITICAL +) + +// A Logger represents a logging module. It has an associated logging +// level which can be changed; messages of lesser severity will +// be dropped. Loggers have a hierarchical relationship - see +// the package documentation. +// +// The zero Logger value is usable - any messages logged +// to it will be sent to the root Logger. +type Logger struct { + impl *module +} + +type module struct { + name string + level Level + parent *module +} + +// Initially the modules map only contains the root module. +var ( + root = &module{level: WARNING} + modulesMutex sync.Mutex + modules = map[string]*module{ + "": root, + } +) + +func (level Level) String() string { + switch level { + case UNSPECIFIED: + return "UNSPECIFIED" + case TRACE: + return "TRACE" + case DEBUG: + return "DEBUG" + case INFO: + return "INFO" + case WARNING: + return "WARNING" + case ERROR: + return "ERROR" + case CRITICAL: + return "CRITICAL" + } + return "" +} + +// get atomically gets the value of the given level. +func (level *Level) get() Level { + return Level(atomic.LoadUint32((*uint32)(level))) +} + +// set atomically sets the value of the receiver +// to the given level. +func (level *Level) set(newLevel Level) { + atomic.StoreUint32((*uint32)(level), uint32(newLevel)) +} + +// getLoggerInternal assumes that the modulesMutex is locked. +func getLoggerInternal(name string) Logger { + impl, found := modules[name] + if found { + return Logger{impl} + } + parentName := "" + if i := strings.LastIndex(name, "."); i >= 0 { + parentName = name[0:i] + } + parent := getLoggerInternal(parentName).impl + impl = &module{name, UNSPECIFIED, parent} + modules[name] = impl + return Logger{impl} +} + +// GetLogger returns a Logger for the given module name, +// creating it and its parents if necessary. +func GetLogger(name string) Logger { + // Lowercase the module name, and look for it in the modules map. + name = strings.ToLower(name) + modulesMutex.Lock() + defer modulesMutex.Unlock() + return getLoggerInternal(name) +} + +// LoggerInfo returns information about the configured loggers and their logging +// levels. The information is returned in the format expected by +// ConfigureModules. Loggers with UNSPECIFIED level will not +// be included. +func LoggerInfo() string { + output := []string{} + // output in alphabetical order. + keys := []string{} + modulesMutex.Lock() + defer modulesMutex.Unlock() + for key := range modules { + keys = append(keys, key) + } + sort.Strings(keys) + for _, name := range keys { + mod := modules[name] + severity := mod.level + if severity == UNSPECIFIED { + continue + } + output = append(output, fmt.Sprintf("%s=%s", mod.Name(), severity)) + } + return strings.Join(output, ";") +} + +// ParseConfigurationString parses a logger configuration string into a map of +// logger names and their associated log level. This method is provided to +// allow other programs to pre-validate a configuration string rather than +// just calling ConfigureLoggers. +// +// Loggers are colon- or semicolon-separated; each module is specified as +// =. White space outside of module names and levels is +// ignored. The root module is specified with the name "". +// +// An example specification: +// `=ERROR; foo.bar=WARNING` +func ParseConfigurationString(specification string) (map[string]Level, error) { + values := strings.FieldsFunc(specification, func(r rune) bool { return r == ';' || r == ':' }) + levels := make(map[string]Level) + for _, value := range values { + s := strings.SplitN(value, "=", 2) + if len(s) < 2 { + return nil, fmt.Errorf("logger specification expected '=', found %q", value) + } + name := strings.TrimSpace(s[0]) + levelStr := strings.TrimSpace(s[1]) + if name == "" || levelStr == "" { + return nil, fmt.Errorf("logger specification %q has blank name or level", value) + } + if name == "" { + name = "" + } + level, ok := ParseLevel(levelStr) + if !ok { + return nil, fmt.Errorf("unknown severity level %q", levelStr) + } + levels[name] = level + } + return levels, nil +} + +// ConfigureLoggers configures loggers according to the given string +// specification, which specifies a set of modules and their associated +// logging levels. Loggers are colon- or semicolon-separated; each +// module is specified as =. White space outside of +// module names and levels is ignored. The root module is specified +// with the name "". +// +// An example specification: +// `=ERROR; foo.bar=WARNING` +func ConfigureLoggers(specification string) error { + if specification == "" { + return nil + } + levels, err := ParseConfigurationString(specification) + if err != nil { + return err + } + for name, level := range levels { + GetLogger(name).SetLogLevel(level) + } + return nil +} + +// ResetLogging iterates through the known modules and sets the levels of all +// to UNSPECIFIED, except for which is set to WARNING. +func ResetLoggers() { + modulesMutex.Lock() + defer modulesMutex.Unlock() + for name, module := range modules { + if name == "" { + module.level.set(WARNING) + } else { + module.level.set(UNSPECIFIED) + } + } +} + +// ParseLevel converts a string representation of a logging level to a +// Level. It returns the level and whether it was valid or not. +func ParseLevel(level string) (Level, bool) { + level = strings.ToUpper(level) + switch level { + case "UNSPECIFIED": + return UNSPECIFIED, true + case "TRACE": + return TRACE, true + case "DEBUG": + return DEBUG, true + case "INFO": + return INFO, true + case "WARN", "WARNING": + return WARNING, true + case "ERROR": + return ERROR, true + case "CRITICAL": + return CRITICAL, true + } + return UNSPECIFIED, false +} + +func (logger Logger) getModule() *module { + if logger.impl == nil { + return root + } + return logger.impl +} + +// Name returns the logger's module name. +func (logger Logger) Name() string { + return logger.getModule().Name() +} + +// LogLevel returns the configured log level of the logger. +func (logger Logger) LogLevel() Level { + return logger.getModule().level.get() +} + +func (module *module) getEffectiveLogLevel() Level { + // Note: the root module is guaranteed to have a + // specified logging level, so acts as a suitable sentinel + // for this loop. + for { + if level := module.level.get(); level != UNSPECIFIED { + return level + } + module = module.parent + } + panic("unreachable") +} + +func (module *module) Name() string { + if module.name == "" { + return "" + } + return module.name +} + +// EffectiveLogLevel returns the effective log level of +// the receiver - that is, messages with a lesser severity +// level will be discarded. +// +// If the log level of the receiver is unspecified, +// it will be taken from the effective log level of its +// parent. +func (logger Logger) EffectiveLogLevel() Level { + return logger.getModule().getEffectiveLogLevel() +} + +// SetLogLevel sets the severity level of the given logger. +// The root logger cannot be set to UNSPECIFIED level. +// See EffectiveLogLevel for how this affects the +// actual messages logged. +func (logger Logger) SetLogLevel(level Level) { + module := logger.getModule() + // The root module can't be unspecified. + if module.name == "" && level == UNSPECIFIED { + level = WARNING + } + module.level.set(level) +} + +// Logf logs a printf-formatted message at the given level. +// A message will be discarded if level is less than the +// the effective log level of the logger. +// Note that the writers may also filter out messages that +// are less than their registered minimum severity level. +func (logger Logger) Logf(level Level, message string, args ...interface{}) { + if logger.getModule().getEffectiveLogLevel() > level || + !WillWrite(level) || + level < TRACE || + level > CRITICAL { + return + } + // Gather time, and filename, line number. + now := time.Now() // get this early. + // Param to Caller is the call depth. Since this method is called from + // the Logger methods, we want the place that those were called from. + _, file, line, ok := runtime.Caller(2) + if !ok { + file = "???" + line = 0 + } + // Trim newline off format string, following usual + // Go logging conventions. + if len(message) > 0 && message[len(message)-1] == '\n' { + message = message[0 : len(message)-1] + } + + formattedMessage := fmt.Sprintf(message, args...) + writeToWriters(level, logger.impl.name, file, line, now, formattedMessage) +} + +// Criticalf logs the printf-formatted message at critical level. +func (logger Logger) Criticalf(message string, args ...interface{}) { + logger.Logf(CRITICAL, message, args...) +} + +// Errorf logs the printf-formatted message at error level. +func (logger Logger) Errorf(message string, args ...interface{}) { + logger.Logf(ERROR, message, args...) +} + +// Warningf logs the printf-formatted message at warning level. +func (logger Logger) Warningf(message string, args ...interface{}) { + logger.Logf(WARNING, message, args...) +} + +// Infof logs the printf-formatted message at info level. +func (logger Logger) Infof(message string, args ...interface{}) { + logger.Logf(INFO, message, args...) +} + +// Debugf logs the printf-formatted message at debug level. +func (logger Logger) Debugf(message string, args ...interface{}) { + logger.Logf(DEBUG, message, args...) +} + +// Tracef logs the printf-formatted message at trace level. +func (logger Logger) Tracef(message string, args ...interface{}) { + logger.Logf(TRACE, message, args...) +} + +// IsLevelEnabled returns whether debugging is enabled +// for the given log level. +func (logger Logger) IsLevelEnabled(level Level) bool { + return logger.getModule().getEffectiveLogLevel() <= level +} + +// IsErrorEnabled returns whether debugging is enabled +// at error level. +func (logger Logger) IsErrorEnabled() bool { + return logger.IsLevelEnabled(ERROR) +} + +// IsWarningEnabled returns whether debugging is enabled +// at warning level. +func (logger Logger) IsWarningEnabled() bool { + return logger.IsLevelEnabled(WARNING) +} + +// IsInfoEnabled returns whether debugging is enabled +// at info level. +func (logger Logger) IsInfoEnabled() bool { + return logger.IsLevelEnabled(INFO) +} + +// IsDebugEnabled returns whether debugging is enabled +// at debug level. +func (logger Logger) IsDebugEnabled() bool { + return logger.IsLevelEnabled(DEBUG) +} + +// IsTraceEnabled returns whether debugging is enabled +// at trace level. +func (logger Logger) IsTraceEnabled() bool { + return logger.IsLevelEnabled(TRACE) +} diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/logger_test.go juju-core-1.17.3/src/launchpad.net/loggo/logger_test.go --- juju-core-1.17.2/src/launchpad.net/loggo/logger_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/logger_test.go 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,435 @@ +package loggo_test + +import ( + "io/ioutil" + "os" + "testing" + + . "launchpad.net/gocheck" + "launchpad.net/loggo" +) + +func Test(t *testing.T) { + TestingT(t) +} + +type loggerSuite struct{} + +var _ = Suite(&loggerSuite{}) + +func (*loggerSuite) SetUpTest(c *C) { + loggo.ResetLoggers() +} + +func (*loggerSuite) TestRootLogger(c *C) { + root := loggo.Logger{} + c.Assert(root.Name(), Equals, "") + c.Assert(root.IsErrorEnabled(), Equals, true) + c.Assert(root.IsWarningEnabled(), Equals, true) + c.Assert(root.IsInfoEnabled(), Equals, false) + c.Assert(root.IsDebugEnabled(), Equals, false) + c.Assert(root.IsTraceEnabled(), Equals, false) +} + +func (*loggerSuite) TestModuleName(c *C) { + logger := loggo.GetLogger("loggo.testing") + c.Assert(logger.Name(), Equals, "loggo.testing") +} + +func (*loggerSuite) TestSetLevel(c *C) { + logger := loggo.GetLogger("testing") + + c.Assert(logger.LogLevel(), Equals, loggo.UNSPECIFIED) + c.Assert(logger.EffectiveLogLevel(), Equals, loggo.WARNING) + c.Assert(logger.IsErrorEnabled(), Equals, true) + c.Assert(logger.IsWarningEnabled(), Equals, true) + c.Assert(logger.IsInfoEnabled(), Equals, false) + c.Assert(logger.IsDebugEnabled(), Equals, false) + c.Assert(logger.IsTraceEnabled(), Equals, false) + logger.SetLogLevel(loggo.TRACE) + c.Assert(logger.LogLevel(), Equals, loggo.TRACE) + c.Assert(logger.EffectiveLogLevel(), Equals, loggo.TRACE) + c.Assert(logger.IsErrorEnabled(), Equals, true) + c.Assert(logger.IsWarningEnabled(), Equals, true) + c.Assert(logger.IsInfoEnabled(), Equals, true) + c.Assert(logger.IsDebugEnabled(), Equals, true) + c.Assert(logger.IsTraceEnabled(), Equals, true) + logger.SetLogLevel(loggo.DEBUG) + c.Assert(logger.LogLevel(), Equals, loggo.DEBUG) + c.Assert(logger.EffectiveLogLevel(), Equals, loggo.DEBUG) + c.Assert(logger.IsErrorEnabled(), Equals, true) + c.Assert(logger.IsWarningEnabled(), Equals, true) + c.Assert(logger.IsInfoEnabled(), Equals, true) + c.Assert(logger.IsDebugEnabled(), Equals, true) + c.Assert(logger.IsTraceEnabled(), Equals, false) + logger.SetLogLevel(loggo.INFO) + c.Assert(logger.LogLevel(), Equals, loggo.INFO) + c.Assert(logger.EffectiveLogLevel(), Equals, loggo.INFO) + c.Assert(logger.IsErrorEnabled(), Equals, true) + c.Assert(logger.IsWarningEnabled(), Equals, true) + c.Assert(logger.IsInfoEnabled(), Equals, true) + c.Assert(logger.IsDebugEnabled(), Equals, false) + c.Assert(logger.IsTraceEnabled(), Equals, false) + logger.SetLogLevel(loggo.WARNING) + c.Assert(logger.LogLevel(), Equals, loggo.WARNING) + c.Assert(logger.EffectiveLogLevel(), Equals, loggo.WARNING) + c.Assert(logger.IsErrorEnabled(), Equals, true) + c.Assert(logger.IsWarningEnabled(), Equals, true) + c.Assert(logger.IsInfoEnabled(), Equals, false) + c.Assert(logger.IsDebugEnabled(), Equals, false) + c.Assert(logger.IsTraceEnabled(), Equals, false) + logger.SetLogLevel(loggo.ERROR) + c.Assert(logger.LogLevel(), Equals, loggo.ERROR) + c.Assert(logger.EffectiveLogLevel(), Equals, loggo.ERROR) + c.Assert(logger.IsErrorEnabled(), Equals, true) + c.Assert(logger.IsWarningEnabled(), Equals, false) + c.Assert(logger.IsInfoEnabled(), Equals, false) + c.Assert(logger.IsDebugEnabled(), Equals, false) + c.Assert(logger.IsTraceEnabled(), Equals, false) + // This is added for completeness, but not really expected to be used. + logger.SetLogLevel(loggo.CRITICAL) + c.Assert(logger.LogLevel(), Equals, loggo.CRITICAL) + c.Assert(logger.EffectiveLogLevel(), Equals, loggo.CRITICAL) + c.Assert(logger.IsErrorEnabled(), Equals, false) + c.Assert(logger.IsWarningEnabled(), Equals, false) + c.Assert(logger.IsInfoEnabled(), Equals, false) + c.Assert(logger.IsDebugEnabled(), Equals, false) + c.Assert(logger.IsTraceEnabled(), Equals, false) + logger.SetLogLevel(loggo.UNSPECIFIED) + c.Assert(logger.LogLevel(), Equals, loggo.UNSPECIFIED) + c.Assert(logger.EffectiveLogLevel(), Equals, loggo.WARNING) +} + +func (*loggerSuite) TestLevelsSharedForSameModule(c *C) { + logger1 := loggo.GetLogger("testing.module") + logger2 := loggo.GetLogger("testing.module") + + logger1.SetLogLevel(loggo.INFO) + c.Assert(logger1.IsInfoEnabled(), Equals, true) + c.Assert(logger2.IsInfoEnabled(), Equals, true) +} + +func (*loggerSuite) TestModuleLowered(c *C) { + logger1 := loggo.GetLogger("TESTING.MODULE") + logger2 := loggo.GetLogger("Testing") + + c.Assert(logger1.Name(), Equals, "testing.module") + c.Assert(logger2.Name(), Equals, "testing") +} + +func (*loggerSuite) TestLevelsInherited(c *C) { + root := loggo.GetLogger("") + first := loggo.GetLogger("first") + second := loggo.GetLogger("first.second") + + root.SetLogLevel(loggo.ERROR) + c.Assert(root.LogLevel(), Equals, loggo.ERROR) + c.Assert(root.EffectiveLogLevel(), Equals, loggo.ERROR) + c.Assert(first.LogLevel(), Equals, loggo.UNSPECIFIED) + c.Assert(first.EffectiveLogLevel(), Equals, loggo.ERROR) + c.Assert(second.LogLevel(), Equals, loggo.UNSPECIFIED) + c.Assert(second.EffectiveLogLevel(), Equals, loggo.ERROR) + + first.SetLogLevel(loggo.DEBUG) + c.Assert(root.LogLevel(), Equals, loggo.ERROR) + c.Assert(root.EffectiveLogLevel(), Equals, loggo.ERROR) + c.Assert(first.LogLevel(), Equals, loggo.DEBUG) + c.Assert(first.EffectiveLogLevel(), Equals, loggo.DEBUG) + c.Assert(second.LogLevel(), Equals, loggo.UNSPECIFIED) + c.Assert(second.EffectiveLogLevel(), Equals, loggo.DEBUG) + + second.SetLogLevel(loggo.INFO) + c.Assert(root.LogLevel(), Equals, loggo.ERROR) + c.Assert(root.EffectiveLogLevel(), Equals, loggo.ERROR) + c.Assert(first.LogLevel(), Equals, loggo.DEBUG) + c.Assert(first.EffectiveLogLevel(), Equals, loggo.DEBUG) + c.Assert(second.LogLevel(), Equals, loggo.INFO) + c.Assert(second.EffectiveLogLevel(), Equals, loggo.INFO) + + first.SetLogLevel(loggo.UNSPECIFIED) + c.Assert(root.LogLevel(), Equals, loggo.ERROR) + c.Assert(root.EffectiveLogLevel(), Equals, loggo.ERROR) + c.Assert(first.LogLevel(), Equals, loggo.UNSPECIFIED) + c.Assert(first.EffectiveLogLevel(), Equals, loggo.ERROR) + c.Assert(second.LogLevel(), Equals, loggo.INFO) + c.Assert(second.EffectiveLogLevel(), Equals, loggo.INFO) +} + +var parseLevelTests = []struct { + str string + level loggo.Level + fail bool +}{{ + str: "trace", + level: loggo.TRACE, +}, { + str: "TrAce", + level: loggo.TRACE, +}, { + str: "TRACE", + level: loggo.TRACE, +}, { + str: "debug", + level: loggo.DEBUG, +}, { + str: "DEBUG", + level: loggo.DEBUG, +}, { + str: "info", + level: loggo.INFO, +}, { + str: "INFO", + level: loggo.INFO, +}, { + str: "warn", + level: loggo.WARNING, +}, { + str: "WARN", + level: loggo.WARNING, +}, { + str: "warning", + level: loggo.WARNING, +}, { + str: "WARNING", + level: loggo.WARNING, +}, { + str: "error", + level: loggo.ERROR, +}, { + str: "ERROR", + level: loggo.ERROR, +}, { + str: "critical", + level: loggo.CRITICAL, +}, { + str: "not_specified", + fail: true, +}, { + str: "other", + fail: true, +}, { + str: "", + fail: true, +}} + +func (*loggerSuite) TestParseLevel(c *C) { + for _, test := range parseLevelTests { + level, ok := loggo.ParseLevel(test.str) + c.Assert(level, Equals, test.level) + c.Assert(ok, Equals, !test.fail) + } +} + +var levelStringValueTests = map[loggo.Level]string{ + loggo.UNSPECIFIED: "UNSPECIFIED", + loggo.DEBUG: "DEBUG", + loggo.TRACE: "TRACE", + loggo.INFO: "INFO", + loggo.WARNING: "WARNING", + loggo.ERROR: "ERROR", + loggo.CRITICAL: "CRITICAL", + loggo.Level(42): "", // other values are unknown +} + +func (*loggerSuite) TestLevelStringValue(c *C) { + for level, str := range levelStringValueTests { + c.Assert(level.String(), Equals, str) + } +} + +var configureLoggersTests = []struct { + spec string + info string + err string +}{{ + spec: "", + info: "=WARNING", +}, { + spec: "=UNSPECIFIED", + info: "=WARNING", +}, { + spec: "=DEBUG", + info: "=DEBUG", +}, { + spec: "test.module=debug", + info: "=WARNING;test.module=DEBUG", +}, { + spec: "module=info; sub.module=debug; other.module=warning", + info: "=WARNING;module=INFO;other.module=WARNING;sub.module=DEBUG", +}, { + spec: " foo.bar \t\r\n= \t\r\nCRITICAL \t\r\n; \t\r\nfoo \r\t\n = DEBUG", + info: "=WARNING;foo=DEBUG;foo.bar=CRITICAL", +}, { + spec: "foo;bar", + info: "=WARNING", + err: `logger specification expected '=', found "foo"`, +}, { + spec: "=foo", + info: "=WARNING", + err: `logger specification "=foo" has blank name or level`, +}, { + spec: "foo=", + info: "=WARNING", + err: `logger specification "foo=" has blank name or level`, +}, { + spec: "=", + info: "=WARNING", + err: `logger specification "=" has blank name or level`, +}, { + spec: "foo=unknown", + info: "=WARNING", + err: `unknown severity level "unknown"`, +}, { + // Test that nothing is changed even when the + // first part of the specification parses ok. + spec: "module=info; foo=unknown", + info: "=WARNING", + err: `unknown severity level "unknown"`, +}} + +func (*loggerSuite) TestConfigureLoggers(c *C) { + for i, test := range configureLoggersTests { + c.Logf("test %d: %q", i, test.spec) + loggo.ResetLoggers() + err := loggo.ConfigureLoggers(test.spec) + c.Check(loggo.LoggerInfo(), Equals, test.info) + if test.err != "" { + c.Assert(err, ErrorMatches, test.err) + continue + } + c.Assert(err, IsNil) + + // Test that it's idempotent. + err = loggo.ConfigureLoggers(test.spec) + c.Assert(err, IsNil) + c.Assert(loggo.LoggerInfo(), Equals, test.info) + + // Test that calling ConfigureLoggers with the + // output of LoggerInfo works too. + err = loggo.ConfigureLoggers(test.info) + c.Assert(err, IsNil) + c.Assert(loggo.LoggerInfo(), Equals, test.info) + } +} + +type logwriterSuite struct { + logger loggo.Logger + writer *loggo.TestWriter +} + +var _ = Suite(&logwriterSuite{}) + +func (s *logwriterSuite) SetUpTest(c *C) { + loggo.ResetLoggers() + loggo.RemoveWriter("default") + s.writer = &loggo.TestWriter{} + err := loggo.RegisterWriter("test", s.writer, loggo.TRACE) + c.Assert(err, IsNil) + s.logger = loggo.GetLogger("test.writer") + // Make it so the logger itself writes all messages. + s.logger.SetLogLevel(loggo.TRACE) +} + +func (s *logwriterSuite) TearDownTest(c *C) { + loggo.ResetWriters() +} + +func (s *logwriterSuite) TestLogDoesntLogWeirdLevels(c *C) { + s.logger.Logf(loggo.UNSPECIFIED, "message") + c.Assert(s.writer.Log, HasLen, 0) + + s.logger.Logf(loggo.Level(42), "message") + c.Assert(s.writer.Log, HasLen, 0) + + s.logger.Logf(loggo.CRITICAL+loggo.Level(1), "message") + c.Assert(s.writer.Log, HasLen, 0) +} + +func (s *logwriterSuite) TestMessageFormatting(c *C) { + s.logger.Logf(loggo.INFO, "some %s included", "formatting") + c.Assert(s.writer.Log, HasLen, 1) + c.Assert(s.writer.Log[0].Message, Equals, "some formatting included") + c.Assert(s.writer.Log[0].Level, Equals, loggo.INFO) +} + +func (s *logwriterSuite) BenchmarkLoggingNoWriters(c *C) { + // No writers + loggo.RemoveWriter("test") + for i := 0; i < c.N; i++ { + s.logger.Warningf("just a simple warning for %d", i) + } +} + +func (s *logwriterSuite) BenchmarkLoggingNoWritersNoFormat(c *C) { + // No writers + loggo.RemoveWriter("test") + for i := 0; i < c.N; i++ { + s.logger.Warningf("just a simple warning") + } +} + +func (s *logwriterSuite) BenchmarkLoggingTestWriters(c *C) { + for i := 0; i < c.N; i++ { + s.logger.Warningf("just a simple warning for %d", i) + } + c.Assert(s.writer.Log, HasLen, c.N) +} + +func setupTempFileWriter(c *C) (logFile *os.File, cleanup func()) { + loggo.RemoveWriter("test") + logFile, err := ioutil.TempFile("", "loggo-test") + c.Assert(err, IsNil) + cleanup = func() { + logFile.Close() + os.Remove(logFile.Name()) + } + writer := loggo.NewSimpleWriter(logFile, &loggo.DefaultFormatter{}) + err = loggo.RegisterWriter("testfile", writer, loggo.TRACE) + c.Assert(err, IsNil) + return +} + +func (s *logwriterSuite) BenchmarkLoggingDiskWriter(c *C) { + logFile, cleanup := setupTempFileWriter(c) + defer cleanup() + msg := "just a simple warning for %d" + for i := 0; i < c.N; i++ { + s.logger.Warningf(msg, i) + } + offset, err := logFile.Seek(0, os.SEEK_CUR) + c.Assert(err, IsNil) + c.Assert((offset > int64(len(msg))*int64(c.N)), Equals, true, + Commentf("Not enough data was written to the log file.")) +} + +func (s *logwriterSuite) BenchmarkLoggingDiskWriterNoMessages(c *C) { + logFile, cleanup := setupTempFileWriter(c) + defer cleanup() + // Change the log level + writer, _, err := loggo.RemoveWriter("testfile") + c.Assert(err, IsNil) + loggo.RegisterWriter("testfile", writer, loggo.WARNING) + msg := "just a simple warning for %d" + for i := 0; i < c.N; i++ { + s.logger.Debugf(msg, i) + } + offset, err := logFile.Seek(0, os.SEEK_CUR) + c.Assert(err, IsNil) + c.Assert(offset, Equals, int64(0), + Commentf("Data was written to the log file.")) +} + +func (s *logwriterSuite) BenchmarkLoggingDiskWriterNoMessagesLogLevel(c *C) { + logFile, cleanup := setupTempFileWriter(c) + defer cleanup() + // Change the log level + s.logger.SetLogLevel(loggo.WARNING) + msg := "just a simple warning for %d" + for i := 0; i < c.N; i++ { + s.logger.Debugf(msg, i) + } + offset, err := logFile.Seek(0, os.SEEK_CUR) + c.Assert(err, IsNil) + c.Assert(offset, Equals, int64(0), + Commentf("Data was written to the log file.")) +} diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/testwriter.go juju-core-1.17.3/src/launchpad.net/loggo/testwriter.go --- juju-core-1.17.2/src/launchpad.net/loggo/testwriter.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/testwriter.go 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,36 @@ +package loggo + +import ( + "path" + "time" +) + +// TestLogValues represents a single logging call. +type TestLogValues struct { + Level Level + Module string + Filename string + Line int + Timestamp time.Time + Message string +} + +// TestWriter is a useful Writer for testing purposes. Each component of the +// logging message is stored in the Log array. +type TestWriter struct { + Log []TestLogValues +} + +// Write saves the params as members in the TestLogValues struct appended to the Log array. +func (writer *TestWriter) Write(level Level, module, filename string, line int, timestamp time.Time, message string) { + if writer.Log == nil { + writer.Log = []TestLogValues{} + } + writer.Log = append(writer.Log, + TestLogValues{level, module, path.Base(filename), line, timestamp, message}) +} + +// Clear removes any saved log messages. +func (writer *TestWriter) Clear() { + writer.Log = []TestLogValues{} +} diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/writer.go juju-core-1.17.3/src/launchpad.net/loggo/writer.go --- juju-core-1.17.2/src/launchpad.net/loggo/writer.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/writer.go 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,149 @@ +package loggo + +import ( + "fmt" + "io" + "os" + "sync" + "time" +) + +// Writer is implemented by any recipient of log messages. +type Writer interface { + // Write writes a message to the Writer with the given + // level and module name. The filename and line hold + // the file name and line number of the code that is + // generating the log message; the time stamp holds + // the time the log message was generated, and + // message holds the log message itself. + Write(level Level, name, filename string, line int, timestamp time.Time, message string) +} + +type registeredWriter struct { + writer Writer + level Level +} + +// defaultName is the name of a writer that is registered +// by default that writes to stderr. +const defaultName = "default" + +var ( + writerMutex sync.Mutex + writers = map[string]*registeredWriter{ + defaultName: ®isteredWriter{ + writer: NewSimpleWriter(os.Stderr, &DefaultFormatter{}), + level: TRACE, + }, + } + globalMinLevel = TRACE +) + +// ResetWriters puts the list of writers back into the initial state. +func ResetWriters() { + writerMutex.Lock() + defer writerMutex.Unlock() + writers = map[string]*registeredWriter{ + "default": ®isteredWriter{ + writer: NewSimpleWriter(os.Stderr, &DefaultFormatter{}), + level: TRACE, + }, + } + findMinLevel() +} + +// ReplaceDefaultWriter is a convenience method that does the equivalent of +// RemoveWriter and then RegisterWriter with the name "default". The previous +// default writer, if any is returned. +func ReplaceDefaultWriter(writer Writer) (Writer, error) { + if writer == nil { + return nil, fmt.Errorf("Writer cannot be nil") + } + writerMutex.Lock() + defer writerMutex.Unlock() + reg, found := writers[defaultName] + if !found { + return nil, fmt.Errorf("there is no %q writer", defaultName) + } + oldWriter := reg.writer + reg.writer = writer + return oldWriter, nil + +} + +// RegisterWriter adds the writer to the list of writers that get notified +// when logging. When registering, the caller specifies the minimum logging +// level that will be written, and a name for the writer. If there is already +// a registered writer with that name, an error is returned. +func RegisterWriter(name string, writer Writer, minLevel Level) error { + if writer == nil { + return fmt.Errorf("Writer cannot be nil") + } + writerMutex.Lock() + defer writerMutex.Unlock() + if _, found := writers[name]; found { + return fmt.Errorf("there is already a Writer registered with the name %q", name) + } + writers[name] = ®isteredWriter{writer: writer, level: minLevel} + findMinLevel() + return nil +} + +// RemoveWriter removes the Writer identified by 'name' and returns it. +// If the Writer is not found, an error is returned. +func RemoveWriter(name string) (Writer, Level, error) { + writerMutex.Lock() + defer writerMutex.Unlock() + registered, found := writers[name] + if !found { + return nil, UNSPECIFIED, fmt.Errorf("Writer %q is not registered", name) + } + delete(writers, name) + findMinLevel() + return registered.writer, registered.level, nil +} + +func findMinLevel() { + // We assume the lock is already held + minLevel := CRITICAL + for _, registered := range writers { + if registered.level < minLevel { + minLevel = registered.level + } + } + globalMinLevel.set(minLevel) +} + +// WillWrite returns whether there are any writers registered +// at or above the given severity level. If it returns +// false, a log message at the given level will be discarded. +func WillWrite(level Level) bool { + return level >= globalMinLevel.get() +} + +func writeToWriters(level Level, module, filename string, line int, timestamp time.Time, message string) { + writerMutex.Lock() + defer writerMutex.Unlock() + for _, registered := range writers { + if level >= registered.level { + registered.writer.Write(level, module, filename, line, timestamp, message) + } + } +} + +type simpleWriter struct { + writer io.Writer + formatter Formatter +} + +// NewSimpleWriter returns a new writer that writes +// log messages to the given io.Writer formatting the +// messages with the given formatter. +func NewSimpleWriter(writer io.Writer, formatter Formatter) Writer { + return &simpleWriter{writer, formatter} +} + +func (simple *simpleWriter) Write(level Level, module, filename string, line int, timestamp time.Time, message string) { + logLine := simple.formatter.Format(level, module, filename, line, timestamp, message) + fmt.Fprintln(simple.writer, logLine) +} diff -Nru juju-core-1.17.2/src/launchpad.net/loggo/writer_test.go juju-core-1.17.3/src/launchpad.net/loggo/writer_test.go --- juju-core-1.17.2/src/launchpad.net/loggo/writer_test.go 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1.17.3/src/launchpad.net/loggo/writer_test.go 2014-02-20 16:11:02.000000000 +0000 @@ -0,0 +1,241 @@ +package loggo_test + +import ( + "fmt" + "time" + + . "launchpad.net/gocheck" + "launchpad.net/loggo" +) + +type writerBasicsSuite struct{} + +var _ = Suite(&writerBasicsSuite{}) + +func (s *writerBasicsSuite) TearDownTest(c *C) { + loggo.ResetWriters() +} + +func (*writerBasicsSuite) TestRemoveDefaultWriter(c *C) { + defaultWriter, level, err := loggo.RemoveWriter("default") + c.Assert(err, IsNil) + c.Assert(level, Equals, loggo.TRACE) + c.Assert(defaultWriter, Not(IsNil)) + + // Trying again fails. + defaultWriter, level, err = loggo.RemoveWriter("default") + c.Assert(err, ErrorMatches, `Writer "default" is not registered`) + c.Assert(level, Equals, loggo.UNSPECIFIED) + c.Assert(defaultWriter, IsNil) +} + +func (*writerBasicsSuite) TestRegisterWriterExistingName(c *C) { + err := loggo.RegisterWriter("default", &loggo.TestWriter{}, loggo.INFO) + c.Assert(err, ErrorMatches, `there is already a Writer registered with the name "default"`) +} + +func (*writerBasicsSuite) TestRegisterNilWriter(c *C) { + err := loggo.RegisterWriter("nil", nil, loggo.INFO) + c.Assert(err, ErrorMatches, `Writer cannot be nil`) +} + +func (*writerBasicsSuite) TestRegisterWriterTypedNil(c *C) { + // If the interface is a typed nil, we have to trust the user. + var writer *loggo.TestWriter + err := loggo.RegisterWriter("nil", writer, loggo.INFO) + c.Assert(err, IsNil) +} + +func (*writerBasicsSuite) TestReplaceDefaultWriter(c *C) { + oldWriter, err := loggo.ReplaceDefaultWriter(&loggo.TestWriter{}) + c.Assert(oldWriter, NotNil) + c.Assert(err, IsNil) +} + +func (*writerBasicsSuite) TestReplaceDefaultWriterWithNil(c *C) { + oldWriter, err := loggo.ReplaceDefaultWriter(nil) + c.Assert(oldWriter, IsNil) + c.Assert(err, ErrorMatches, "Writer cannot be nil") +} + +func (*writerBasicsSuite) TestReplaceDefaultWriterNoDefault(c *C) { + loggo.RemoveWriter("default") + oldWriter, err := loggo.ReplaceDefaultWriter(&loggo.TestWriter{}) + c.Assert(oldWriter, IsNil) + c.Assert(err, ErrorMatches, `there is no "default" writer`) +} + +func (s *writerBasicsSuite) TestWillWrite(c *C) { + // By default, the root logger watches TRACE messages + c.Assert(loggo.WillWrite(loggo.TRACE), Equals, true) + // Note: ReplaceDefaultWriter doesn't let us change the default log + // level :( + writer, _, err := loggo.RemoveWriter("default") + c.Assert(err, IsNil) + c.Assert(writer, NotNil) + err = loggo.RegisterWriter("default", writer, loggo.CRITICAL) + c.Assert(err, IsNil) + c.Assert(loggo.WillWrite(loggo.TRACE), Equals, false) + c.Assert(loggo.WillWrite(loggo.DEBUG), Equals, false) + c.Assert(loggo.WillWrite(loggo.INFO), Equals, false) + c.Assert(loggo.WillWrite(loggo.WARNING), Equals, false) + c.Assert(loggo.WillWrite(loggo.CRITICAL), Equals, true) +} + +type writerSuite struct { + logger loggo.Logger +} + +var _ = Suite(&writerSuite{}) + +func (s *writerSuite) SetUpTest(c *C) { + loggo.ResetLoggers() + loggo.RemoveWriter("default") + s.logger = loggo.GetLogger("test.writer") + // Make it so the logger itself writes all messages. + s.logger.SetLogLevel(loggo.TRACE) +} + +func (s *writerSuite) TearDownTest(c *C) { + loggo.ResetWriters() +} + +func (s *writerSuite) TearDownSuite(c *C) { + loggo.ResetLoggers() +} + +func (s *writerSuite) TestWritingCapturesFileAndLineAndModule(c *C) { + writer := &loggo.TestWriter{} + err := loggo.RegisterWriter("test", writer, loggo.INFO) + c.Assert(err, IsNil) + + s.logger.Infof("Info message") + + // WARNING: test checks the line number of the above logger lines, this + // will mean that if the above line moves, the test will fail unless + // updated. + c.Assert(writer.Log, HasLen, 1) + c.Assert(writer.Log[0].Filename, Equals, "writer_test.go") + c.Assert(writer.Log[0].Line, Equals, 112) + c.Assert(writer.Log[0].Module, Equals, "test.writer") +} + +func (s *writerSuite) TestWritingLimitWarning(c *C) { + writer := &loggo.TestWriter{} + err := loggo.RegisterWriter("test", writer, loggo.WARNING) + c.Assert(err, IsNil) + + start := time.Now() + s.logger.Criticalf("Something critical.") + s.logger.Errorf("An error.") + s.logger.Warningf("A warning message") + s.logger.Infof("Info message") + s.logger.Tracef("Trace the function") + end := time.Now() + + c.Assert(writer.Log, HasLen, 3) + c.Assert(writer.Log[0].Level, Equals, loggo.CRITICAL) + c.Assert(writer.Log[0].Message, Equals, "Something critical.") + c.Assert(writer.Log[0].Timestamp, Between(start, end)) + + c.Assert(writer.Log[1].Level, Equals, loggo.ERROR) + c.Assert(writer.Log[1].Message, Equals, "An error.") + c.Assert(writer.Log[1].Timestamp, Between(start, end)) + + c.Assert(writer.Log[2].Level, Equals, loggo.WARNING) + c.Assert(writer.Log[2].Message, Equals, "A warning message") + c.Assert(writer.Log[2].Timestamp, Between(start, end)) +} + +func (s *writerSuite) TestWritingLimitTrace(c *C) { + writer := &loggo.TestWriter{} + err := loggo.RegisterWriter("test", writer, loggo.TRACE) + c.Assert(err, IsNil) + + start := time.Now() + s.logger.Criticalf("Something critical.") + s.logger.Errorf("An error.") + s.logger.Warningf("A warning message") + s.logger.Infof("Info message") + s.logger.Tracef("Trace the function") + end := time.Now() + + c.Assert(writer.Log, HasLen, 5) + c.Assert(writer.Log[0].Level, Equals, loggo.CRITICAL) + c.Assert(writer.Log[0].Message, Equals, "Something critical.") + c.Assert(writer.Log[0].Timestamp, Between(start, end)) + + c.Assert(writer.Log[1].Level, Equals, loggo.ERROR) + c.Assert(writer.Log[1].Message, Equals, "An error.") + c.Assert(writer.Log[1].Timestamp, Between(start, end)) + + c.Assert(writer.Log[2].Level, Equals, loggo.WARNING) + c.Assert(writer.Log[2].Message, Equals, "A warning message") + c.Assert(writer.Log[2].Timestamp, Between(start, end)) + + c.Assert(writer.Log[3].Level, Equals, loggo.INFO) + c.Assert(writer.Log[3].Message, Equals, "Info message") + c.Assert(writer.Log[3].Timestamp, Between(start, end)) + + c.Assert(writer.Log[4].Level, Equals, loggo.TRACE) + c.Assert(writer.Log[4].Message, Equals, "Trace the function") + c.Assert(writer.Log[4].Timestamp, Between(start, end)) +} + +func (s *writerSuite) TestMultipleWriters(c *C) { + errorWriter := &loggo.TestWriter{} + err := loggo.RegisterWriter("error", errorWriter, loggo.ERROR) + c.Assert(err, IsNil) + warningWriter := &loggo.TestWriter{} + err = loggo.RegisterWriter("warning", warningWriter, loggo.WARNING) + c.Assert(err, IsNil) + infoWriter := &loggo.TestWriter{} + err = loggo.RegisterWriter("info", infoWriter, loggo.INFO) + c.Assert(err, IsNil) + traceWriter := &loggo.TestWriter{} + err = loggo.RegisterWriter("trace", traceWriter, loggo.TRACE) + c.Assert(err, IsNil) + + s.logger.Errorf("An error.") + s.logger.Warningf("A warning message") + s.logger.Infof("Info message") + s.logger.Tracef("Trace the function") + + c.Assert(errorWriter.Log, HasLen, 1) + c.Assert(warningWriter.Log, HasLen, 2) + c.Assert(infoWriter.Log, HasLen, 3) + c.Assert(traceWriter.Log, HasLen, 4) +} + +func Between(start, end time.Time) Checker { + if end.Before(start) { + return &betweenChecker{end, start} + } + return &betweenChecker{start, end} +} + +type betweenChecker struct { + start, end time.Time +} + +func (checker *betweenChecker) Info() *CheckerInfo { + info := CheckerInfo{ + Name: "Between", + Params: []string{"obtained"}, + } + return &info +} + +func (checker *betweenChecker) Check(params []interface{}, names []string) (result bool, error string) { + when, ok := params[0].(time.Time) + if !ok { + return false, "obtained value type must be time.Time" + } + if when.Before(checker.start) { + return false, fmt.Sprintf("obtained value %#v type must before start value of %#v", when, checker.start) + } + if when.After(checker.end) { + return false, fmt.Sprintf("obtained value %#v type must after end value of %#v", when, checker.end) + } + return true, "" +}