github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/apiserver/tools_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserver_test
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/url"
    12  	"path"
    13  
    14  	"github.com/juju/utils"
    15  	gc "launchpad.net/gocheck"
    16  
    17  	"github.com/juju/juju/environs/tools"
    18  	toolstesting "github.com/juju/juju/environs/tools/testing"
    19  	"github.com/juju/juju/state"
    20  	"github.com/juju/juju/state/api/params"
    21  	coretools "github.com/juju/juju/tools"
    22  	"github.com/juju/juju/version"
    23  )
    24  
    25  type toolsSuite struct {
    26  	authHttpSuite
    27  }
    28  
    29  var _ = gc.Suite(&toolsSuite{})
    30  
    31  func (s *toolsSuite) SetUpSuite(c *gc.C) {
    32  	s.JujuConnSuite.SetUpSuite(c)
    33  	s.archiveContentType = "application/x-tar-gz"
    34  }
    35  
    36  func (s *toolsSuite) TestToolsUploadedSecurely(c *gc.C) {
    37  	_, info, err := s.APIConn.Environ.StateInfo()
    38  	c.Assert(err, gc.IsNil)
    39  	uri := "http://" + info.Addrs[0] + "/tools"
    40  	_, err = s.sendRequest(c, "", "", "PUT", uri, "", nil)
    41  	c.Assert(err, gc.ErrorMatches, `.*malformed HTTP response.*`)
    42  }
    43  
    44  func (s *toolsSuite) TestRequiresAuth(c *gc.C) {
    45  	resp, err := s.sendRequest(c, "", "", "GET", s.toolsURI(c, ""), "", nil)
    46  	c.Assert(err, gc.IsNil)
    47  	s.assertErrorResponse(c, resp, http.StatusUnauthorized, "unauthorized")
    48  }
    49  
    50  func (s *toolsSuite) TestRequiresPOST(c *gc.C) {
    51  	resp, err := s.authRequest(c, "PUT", s.toolsURI(c, ""), "", nil)
    52  	c.Assert(err, gc.IsNil)
    53  	s.assertErrorResponse(c, resp, http.StatusMethodNotAllowed, `unsupported method: "PUT"`)
    54  }
    55  
    56  func (s *toolsSuite) TestAuthRequiresUser(c *gc.C) {
    57  	// Add a machine and try to login.
    58  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
    59  	c.Assert(err, gc.IsNil)
    60  	err = machine.SetProvisioned("foo", "fake_nonce", nil)
    61  	c.Assert(err, gc.IsNil)
    62  	password, err := utils.RandomPassword()
    63  	c.Assert(err, gc.IsNil)
    64  	err = machine.SetPassword(password)
    65  	c.Assert(err, gc.IsNil)
    66  
    67  	resp, err := s.sendRequest(c, machine.Tag(), password, "POST", s.toolsURI(c, ""), "", nil)
    68  	c.Assert(err, gc.IsNil)
    69  	s.assertErrorResponse(c, resp, http.StatusUnauthorized, "unauthorized")
    70  
    71  	// Now try a user login.
    72  	resp, err = s.authRequest(c, "POST", s.toolsURI(c, ""), "", nil)
    73  	c.Assert(err, gc.IsNil)
    74  	s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected binaryVersion argument")
    75  }
    76  
    77  func (s *toolsSuite) TestUploadRequiresVersion(c *gc.C) {
    78  	resp, err := s.authRequest(c, "POST", s.toolsURI(c, ""), "", nil)
    79  	c.Assert(err, gc.IsNil)
    80  	s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected binaryVersion argument")
    81  }
    82  
    83  func (s *toolsSuite) TestUploadFailsWithNoTools(c *gc.C) {
    84  	// Create an empty file.
    85  	tempFile, err := ioutil.TempFile(c.MkDir(), "tools")
    86  	c.Assert(err, gc.IsNil)
    87  
    88  	resp, err := s.uploadRequest(c, s.toolsURI(c, "?binaryVersion=1.18.0-quantal-amd64"), true, tempFile.Name())
    89  	c.Assert(err, gc.IsNil)
    90  	s.assertErrorResponse(c, resp, http.StatusBadRequest, "no tools uploaded")
    91  }
    92  
    93  func (s *toolsSuite) TestUploadFailsWithInvalidContentType(c *gc.C) {
    94  	// Create an empty file.
    95  	tempFile, err := ioutil.TempFile(c.MkDir(), "tools")
    96  	c.Assert(err, gc.IsNil)
    97  
    98  	// Now try with the default Content-Type.
    99  	resp, err := s.uploadRequest(c, s.toolsURI(c, "?binaryVersion=1.18.0-quantal-amd64"), false, tempFile.Name())
   100  	c.Assert(err, gc.IsNil)
   101  	s.assertErrorResponse(
   102  		c, resp, http.StatusBadRequest, "expected Content-Type: application/x-tar-gz, got: application/octet-stream")
   103  }
   104  
   105  func (s *toolsSuite) setupToolsForUpload(c *gc.C) (coretools.List, version.Binary, string) {
   106  	localStorage := c.MkDir()
   107  	vers := version.MustParseBinary("1.9.0-quantal-amd64")
   108  	versionStrings := []string{vers.String()}
   109  	expectedTools := toolstesting.MakeToolsWithCheckSum(c, localStorage, "releases", versionStrings)
   110  	toolsFile := tools.StorageName(vers)
   111  	return expectedTools, vers, path.Join(localStorage, toolsFile)
   112  }
   113  
   114  func (s *toolsSuite) TestUpload(c *gc.C) {
   115  	// Make some fake tools.
   116  	expectedTools, vers, toolPath := s.setupToolsForUpload(c)
   117  	// Now try uploading them.
   118  	resp, err := s.uploadRequest(
   119  		c, s.toolsURI(c, "?binaryVersion="+vers.String()), true, toolPath)
   120  	c.Assert(err, gc.IsNil)
   121  
   122  	// Check the response.
   123  	stor := s.Conn.Environ.Storage()
   124  	toolsURL, err := stor.URL(tools.StorageName(vers))
   125  	c.Assert(err, gc.IsNil)
   126  	expectedTools[0].URL = toolsURL
   127  	s.assertUploadResponse(c, resp, expectedTools[0])
   128  
   129  	// Check the contents.
   130  	r, err := stor.Get(tools.StorageName(vers))
   131  	c.Assert(err, gc.IsNil)
   132  	uploadedData, err := ioutil.ReadAll(r)
   133  	c.Assert(err, gc.IsNil)
   134  	expectedData, err := ioutil.ReadFile(toolPath)
   135  	c.Assert(err, gc.IsNil)
   136  	c.Assert(uploadedData, gc.DeepEquals, expectedData)
   137  }
   138  
   139  func (s *toolsSuite) TestUploadAllowsTopLevelPath(c *gc.C) {
   140  	// Backwards compatibility check, that we can upload tools to
   141  	// https://host:port/tools
   142  	expectedTools, vers, toolPath := s.setupToolsForUpload(c)
   143  	url := s.toolsURL(c, "binaryVersion="+vers.String())
   144  	url.Path = "/tools"
   145  	resp, err := s.uploadRequest(c, url.String(), true, toolPath)
   146  	c.Assert(err, gc.IsNil)
   147  	// Check the response.
   148  	stor := s.Conn.Environ.Storage()
   149  	toolsURL, err := stor.URL(tools.StorageName(vers))
   150  	c.Assert(err, gc.IsNil)
   151  	expectedTools[0].URL = toolsURL
   152  	s.assertUploadResponse(c, resp, expectedTools[0])
   153  }
   154  
   155  func (s *toolsSuite) TestUploadAllowsEnvUUIDPath(c *gc.C) {
   156  	// Check that we can upload tools to https://host:port/ENVUUID/tools
   157  	environ, err := s.State.Environment()
   158  	c.Assert(err, gc.IsNil)
   159  	expectedTools, vers, toolPath := s.setupToolsForUpload(c)
   160  	url := s.toolsURL(c, "binaryVersion="+vers.String())
   161  	url.Path = fmt.Sprintf("/environment/%s/tools", environ.UUID())
   162  	resp, err := s.uploadRequest(c, url.String(), true, toolPath)
   163  	c.Assert(err, gc.IsNil)
   164  	// Check the response.
   165  	stor := s.Conn.Environ.Storage()
   166  	toolsURL, err := stor.URL(tools.StorageName(vers))
   167  	c.Assert(err, gc.IsNil)
   168  	expectedTools[0].URL = toolsURL
   169  	s.assertUploadResponse(c, resp, expectedTools[0])
   170  }
   171  
   172  func (s *toolsSuite) TestUploadRejectsWrongEnvUUIDPath(c *gc.C) {
   173  	// Check that we cannot access the tools at https://host:port/BADENVUUID/tools
   174  	url := s.toolsURL(c, "")
   175  	url.Path = "/environment/dead-beef-123456/tools"
   176  	resp, err := s.authRequest(c, "POST", url.String(), "", nil)
   177  	c.Assert(err, gc.IsNil)
   178  	s.assertErrorResponse(c, resp, http.StatusNotFound, `unknown environment: "dead-beef-123456"`)
   179  }
   180  
   181  func (s *toolsSuite) TestUploadFakeSeries(c *gc.C) {
   182  	// Make some fake tools.
   183  	expectedTools, vers, toolPath := s.setupToolsForUpload(c)
   184  	// Now try uploading them.
   185  	params := "?binaryVersion=" + vers.String() + "&series=precise,trusty"
   186  	resp, err := s.uploadRequest(c, s.toolsURI(c, params), true, toolPath)
   187  	c.Assert(err, gc.IsNil)
   188  
   189  	// Check the response.
   190  	stor := s.Conn.Environ.Storage()
   191  	toolsURL, err := stor.URL(tools.StorageName(vers))
   192  	c.Assert(err, gc.IsNil)
   193  	expectedTools[0].URL = toolsURL
   194  	s.assertUploadResponse(c, resp, expectedTools[0])
   195  
   196  	// Check the contents.
   197  	for _, series := range []string{"precise", "quantal", "trusty"} {
   198  		toolsVersion := vers
   199  		toolsVersion.Series = series
   200  		r, err := stor.Get(tools.StorageName(toolsVersion))
   201  		c.Assert(err, gc.IsNil)
   202  		uploadedData, err := ioutil.ReadAll(r)
   203  		c.Assert(err, gc.IsNil)
   204  		expectedData, err := ioutil.ReadFile(toolPath)
   205  		c.Assert(err, gc.IsNil)
   206  		c.Assert(uploadedData, gc.DeepEquals, expectedData)
   207  	}
   208  }
   209  
   210  func (s *toolsSuite) toolsURL(c *gc.C, query string) *url.URL {
   211  	uri := s.baseURL(c)
   212  	uri.Path += "/tools"
   213  	uri.RawQuery = query
   214  	return uri
   215  }
   216  
   217  func (s *toolsSuite) toolsURI(c *gc.C, query string) string {
   218  	if query != "" && query[0] == '?' {
   219  		query = query[1:]
   220  	}
   221  	return s.toolsURL(c, query).String()
   222  }
   223  
   224  func (s *toolsSuite) assertUploadResponse(c *gc.C, resp *http.Response, agentTools *coretools.Tools) {
   225  	body := assertResponse(c, resp, http.StatusOK, "application/json")
   226  	toolsResult := jsonToolsResponse(c, body)
   227  	c.Check(toolsResult.Error, gc.IsNil)
   228  	c.Check(toolsResult.Tools, gc.DeepEquals, agentTools)
   229  }
   230  
   231  func (s *toolsSuite) assertGetFileResponse(c *gc.C, resp *http.Response, expBody, expContentType string) {
   232  	body := assertResponse(c, resp, http.StatusOK, expContentType)
   233  	c.Check(string(body), gc.Equals, expBody)
   234  }
   235  
   236  func (s *toolsSuite) assertErrorResponse(c *gc.C, resp *http.Response, expCode int, expError string) {
   237  	body := assertResponse(c, resp, expCode, "application/json")
   238  	err := jsonToolsResponse(c, body).Error
   239  	c.Assert(err, gc.NotNil)
   240  	c.Check(err, gc.ErrorMatches, expError)
   241  }
   242  
   243  func jsonToolsResponse(c *gc.C, body []byte) (jsonResponse params.ToolsResult) {
   244  	err := json.Unmarshal(body, &jsonResponse)
   245  	c.Assert(err, gc.IsNil)
   246  	return
   247  }