Make upgrade-gui a controller command. #6181

Merged
merged 1 commit into from Sep 7, 2016
Jump to file or symbol
Failed to load files and symbols.
+251 −183
Split
@@ -1,7 +1,7 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
-package api
+package controller
import (
"bytes"
@@ -24,7 +24,7 @@ const (
// GUIArchives retrieves information about Juju GUI archives currently present
// in the Juju controller.
func (c *Client) GUIArchives() ([]params.GUIArchiveVersion, error) {
- httpClient, err := c.st.RootHTTPClient()
+ httpClient, err := c.facade.RawAPICaller().HTTPClient()
if err != nil {
return nil, errors.Annotate(err, "cannot retrieve HTTP client")
}
@@ -50,7 +50,7 @@ func (c *Client) UploadGUIArchive(r io.ReadSeeker, hash string, size int64, vers
req.ContentLength = size
// Retrieve a client and send the request.
- httpClient, err := c.st.RootHTTPClient()
+ httpClient, err := c.facade.RawAPICaller().HTTPClient()
if err != nil {
return false, errors.Annotate(err, "cannot retrieve HTTP client")
}
@@ -78,7 +78,7 @@ func (c *Client) SelectGUIVersion(vers version.Number) error {
}
// Retrieve a client and send the request.
- httpClient, err := c.st.RootHTTPClient()
+ httpClient, err := c.facade.RawAPICaller().HTTPClient()
if err != nil {
return errors.Annotate(err, "cannot retrieve HTTP client")
}
@@ -0,0 +1,148 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the AGPLv3, see LICENCE file for details.
+
+package controller_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+
+ "github.com/juju/testing"
+ jc "github.com/juju/testing/checkers"
+ "github.com/juju/version"
+ gc "gopkg.in/check.v1"
+
+ "github.com/juju/juju/api/base"
+ "github.com/juju/juju/api/controller"
+ "github.com/juju/juju/apiserver/params"
+)
+
+// sendJSONResponse encodes the given content as JSON and writes it to the
+// given response writer.
+func sendJSONResponse(c *gc.C, w http.ResponseWriter, content interface{}) {
+ w.Header().Set("Content-Type", params.ContentTypeJSON)
+ encoder := json.NewEncoder(w)
+ err := encoder.Encode(content)
+ c.Assert(err, jc.ErrorIsNil)
+}
+
+// withHTTPClient sets up a fixture with the given address and handle, then
+// runs the given test and checks that the HTTP handler has been called with
+// the given method.
+func withHTTPClient(c *gc.C, address, expectMethod string, handle func(http.ResponseWriter, *http.Request), test func(*controller.Client)) {
+ fix := newHTTPFixture(address, handle)
+ stub := fix.run(c, func(ac base.APICallCloser) {
+ client := controller.NewClient(ac)
+ test(client)
+ })
+ stub.CheckCalls(c, []testing.StubCall{{expectMethod, nil}})
+}
+
+func (s *Suite) TestGUIArchives(c *gc.C) {
+ response := params.GUIArchiveResponse{
+ Versions: []params.GUIArchiveVersion{{
+ Version: version.MustParse("1.0.0"),
+ SHA256: "hash1",
+ Current: false,
+ }, {
+ Version: version.MustParse("2.0.0"),
+ SHA256: "hash2",
+ Current: true,
+ }},
+ }
+ withHTTPClient(c, "/gui-archive", "GET", func(w http.ResponseWriter, req *http.Request) {
+ defer req.Body.Close()
+ sendJSONResponse(c, w, response)
+ }, func(client *controller.Client) {
+ // Retrieve the GUI archive versions.
+ versions, err := client.GUIArchives()
+ c.Assert(err, jc.ErrorIsNil)
+ c.Assert(versions, jc.DeepEquals, response.Versions)
+ })
+}
+
+func (s *Suite) TestGUIArchivesError(c *gc.C) {
+ withHTTPClient(c, "/gui-archive", "GET", func(w http.ResponseWriter, req *http.Request) {
+ defer req.Body.Close()
+ w.WriteHeader(http.StatusBadRequest)
+ }, func(client *controller.Client) {
+ // Call to get GUI archive versions.
+ versions, err := client.GUIArchives()
+ c.Assert(err, gc.ErrorMatches, "cannot retrieve GUI archives info: .*")
+ c.Assert(versions, gc.IsNil)
+ })
+}
+
+func (s *Suite) TestUploadGUIArchive(c *gc.C) {
+ archive := []byte("archive content")
+ hash, size, vers := "archive-hash", int64(len(archive)), version.MustParse("2.1.0")
+ withHTTPClient(c, "/gui-archive", "POST", func(w http.ResponseWriter, req *http.Request) {
+ defer req.Body.Close()
+ err := req.ParseForm()
+ c.Assert(err, jc.ErrorIsNil)
+ // Check version and content length.
+ c.Assert(req.Form.Get("version"), gc.Equals, vers.String())
+ c.Assert(req.ContentLength, gc.Equals, size)
+ // Check request body.
+ obtainedArchive, err := ioutil.ReadAll(req.Body)
+ c.Assert(err, jc.ErrorIsNil)
+ c.Assert(obtainedArchive, gc.DeepEquals, archive)
+ // Check hash.
+ h := req.Form.Get("hash")
+ c.Assert(h, gc.Equals, hash)
+ // Send the response.
+ sendJSONResponse(c, w, params.GUIArchiveVersion{
+ Current: true,
+ })
+ }, func(client *controller.Client) {
+ // Upload a new Juju GUI archive.
+ current, err := client.UploadGUIArchive(bytes.NewReader(archive), hash, size, vers)
+ c.Assert(err, jc.ErrorIsNil)
+ c.Assert(current, jc.IsTrue)
+ })
+}
+
+func (s *Suite) TestUploadGUIArchiveError(c *gc.C) {
+ archive := []byte("archive content")
+ hash, size, vers := "archive-hash", int64(len(archive)), version.MustParse("2.1.0")
+ withHTTPClient(c, "/gui-archive", "POST", func(w http.ResponseWriter, req *http.Request) {
+ defer req.Body.Close()
+ w.WriteHeader(http.StatusBadRequest)
+ }, func(client *controller.Client) {
+ // Call to upload a new Juju GUI archive.
+ current, err := client.UploadGUIArchive(bytes.NewReader(archive), hash, size, vers)
+ c.Assert(err, gc.ErrorMatches, "cannot upload the GUI archive: .*")
+ c.Assert(current, jc.IsFalse)
+ })
+}
+
+func (s *Suite) TestSelectGUIVersion(c *gc.C) {
+ vers := version.MustParse("2.0.42")
+ withHTTPClient(c, "/gui-version", "PUT", func(w http.ResponseWriter, req *http.Request) {
+ defer req.Body.Close()
+ // Check request body.
+ var request params.GUIVersionRequest
+ decoder := json.NewDecoder(req.Body)
+ err := decoder.Decode(&request)
+ c.Assert(err, jc.ErrorIsNil)
+ c.Assert(request.Version, gc.Equals, vers)
+ }, func(client *controller.Client) {
+ // Switch to a specific Juju GUI version.
+ err := client.SelectGUIVersion(vers)
+ c.Assert(err, jc.ErrorIsNil)
+ })
+}
+
+func (s *Suite) TestSelectGUIVersionError(c *gc.C) {
+ vers := version.MustParse("2.0.42")
+ withHTTPClient(c, "/gui-version", "PUT", func(w http.ResponseWriter, req *http.Request) {
+ defer req.Body.Close()
+ w.WriteHeader(http.StatusBadRequest)
+ }, func(client *controller.Client) {
+ // Call to select a Juju GUI version.
+ err := client.SelectGUIVersion(vers)
+ c.Assert(err, gc.ErrorMatches, "cannot select GUI version: .*")
+ })
+}
@@ -0,0 +1,86 @@
+// Copyright 2016 Canonical Ltd.
+// Licensed under the AGPLv3, see LICENCE file for details.
+
+package controller_test
+
+import (
+ "net"
+ "net/http"
+ "net/url"
+
+ "github.com/juju/httprequest"
+ "github.com/juju/testing"
+ jc "github.com/juju/testing/checkers"
+ gc "gopkg.in/check.v1"
+
+ "github.com/juju/juju/api/base"
+)
+
+// newHTTPFixture creates and returns an HTTP fixture to be used in order to
+// mock controller HTTP requests to the given controller address.
+// Use it like in the following example:
+// fix := newHTTPFixture("/my/controller/path", func(w http.ResponseWriter, req *http.Request) {
+// // Simulate what's returned by the server.
+// })
+// stub := fix.run(c, func(ac base.APICallCloser) {
+// // Do something with the API caller.
+// })
+// At this point the stub, if the handler has been called, includes one call
+// with the HTTP method requested while running the test function.
+func newHTTPFixture(address string, handle func(http.ResponseWriter, *http.Request)) *httpFixture {
+ return &httpFixture{
+ address: address,
+ handle: handle,
+ }
+}
+
+// httpFixture is used to mock controller HTTP API calls.
+type httpFixture struct {
+ address string
+ handle func(http.ResponseWriter, *http.Request)
+}
+
+// run sets up the fixture and run the given test. See newHTTPFixture for an
+// example of how to use this.
+func (f *httpFixture) run(c *gc.C, test func(base.APICallCloser)) *testing.Stub {
+ stub := &testing.Stub{}
+ lis, err := net.Listen("tcp", "127.0.0.1:0")
+ c.Assert(err, jc.ErrorIsNil)
+ defer lis.Close()
+ mux := http.NewServeMux()
+ mux.HandleFunc(f.address, func(w http.ResponseWriter, r *http.Request) {
+ stub.AddCall(r.Method)
+ f.handle(w, r)
+ })
+ go func() {
+ http.Serve(lis, mux)
+ }()
+ test(&httpAPICallCloser{
+ url: &url.URL{
+ Scheme: "http",
+ Host: lis.Addr().String(),
+ },
+ })
+ return stub
+}
+
+var _ base.APICallCloser = (*httpAPICallCloser)(nil)
+
+// httpAPICallCloser implements base.APICallCloser.
+type httpAPICallCloser struct {
+ base.APICallCloser
+ url *url.URL
+}
+
+// BestFacadeVersion implements base.APICallCloser.
+func (*httpAPICallCloser) BestFacadeVersion(facade string) int {
+ return 42
+}
+
+// HTTPClient implements base.APICallCloser. The returned HTTP client can be
+// used to send requests to the testing server set up in httpFixture.run().
+func (ac *httpAPICallCloser) HTTPClient() (*httprequest.Client, error) {
+ return &httprequest.Client{
+ BaseURL: ac.url.String(),
+ }, nil
+}
Oops, something went wrong.