github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/api/client_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package api_test 5 6 import ( 7 "bufio" 8 "bytes" 9 "encoding/json" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net" 14 "net/http" 15 "net/url" 16 "strings" 17 18 "code.google.com/p/go.net/websocket" 19 "github.com/juju/errors" 20 "github.com/juju/loggo" 21 "github.com/juju/names" 22 jc "github.com/juju/testing/checkers" 23 gc "gopkg.in/check.v1" 24 "gopkg.in/juju/charm.v4" 25 26 "github.com/juju/juju/api" 27 "github.com/juju/juju/apiserver/params" 28 jujutesting "github.com/juju/juju/juju/testing" 29 "github.com/juju/juju/state" 30 "github.com/juju/juju/testcharms" 31 "github.com/juju/juju/testing/factory" 32 "github.com/juju/juju/version" 33 ) 34 35 type clientSuite struct { 36 jujutesting.JujuConnSuite 37 } 38 39 var _ = gc.Suite(&clientSuite{}) 40 41 // TODO(jam) 2013-08-27 http://pad.lv/1217282 42 // Right now most of the direct tests for api.Client behavior are in 43 // apiserver/client/*_test.go 44 45 func (s *clientSuite) TestCloseMultipleOk(c *gc.C) { 46 client := s.APIState.Client() 47 c.Assert(client.Close(), gc.IsNil) 48 c.Assert(client.Close(), gc.IsNil) 49 c.Assert(client.Close(), gc.IsNil) 50 } 51 52 func (s *clientSuite) TestAddLocalCharm(c *gc.C) { 53 charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy") 54 curl := charm.MustParseURL( 55 fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()), 56 ) 57 client := s.APIState.Client() 58 59 // Test the sanity checks first. 60 _, err := client.AddLocalCharm(charm.MustParseURL("cs:quantal/wordpress-1"), nil) 61 c.Assert(err, gc.ErrorMatches, `expected charm URL with local: schema, got "cs:quantal/wordpress-1"`) 62 63 // Upload an archive with its original revision. 64 savedURL, err := client.AddLocalCharm(curl, charmArchive) 65 c.Assert(err, jc.ErrorIsNil) 66 c.Assert(savedURL.String(), gc.Equals, curl.String()) 67 68 // Upload a charm directory with changed revision. 69 charmDir := testcharms.Repo.ClonedDir(c.MkDir(), "dummy") 70 charmDir.SetDiskRevision(42) 71 savedURL, err = client.AddLocalCharm(curl, charmDir) 72 c.Assert(err, jc.ErrorIsNil) 73 c.Assert(savedURL.Revision, gc.Equals, 42) 74 75 // Upload a charm directory again, revision should be bumped. 76 savedURL, err = client.AddLocalCharm(curl, charmDir) 77 c.Assert(err, jc.ErrorIsNil) 78 c.Assert(savedURL.String(), gc.Equals, curl.WithRevision(43).String()) 79 } 80 81 func (s *clientSuite) TestAddLocalCharmError(c *gc.C) { 82 lis, err := net.Listen("tcp", "127.0.0.1:0") 83 c.Assert(err, jc.ErrorIsNil) 84 defer lis.Close() 85 url := fmt.Sprintf("http://%v", lis.Addr()) 86 http.HandleFunc("/charms", func(w http.ResponseWriter, r *http.Request) { 87 if r.Method == "POST" { 88 http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) 89 } 90 }) 91 go func() { 92 http.Serve(lis, nil) 93 }() 94 95 client := s.APIState.Client() 96 api.SetServerRoot(client, url) 97 98 charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy") 99 curl := charm.MustParseURL( 100 fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()), 101 ) 102 _, err = client.AddLocalCharm(curl, charmArchive) 103 c.Assert(err, gc.ErrorMatches, "charm upload failed: 405 \\(Method Not Allowed\\)") 104 } 105 106 func (s *clientSuite) TestClientEnvironmentUUID(c *gc.C) { 107 environ, err := s.State.Environment() 108 c.Assert(err, jc.ErrorIsNil) 109 110 client := s.APIState.Client() 111 c.Assert(client.EnvironmentUUID(), gc.Equals, environ.Tag().Id()) 112 } 113 114 func (s *clientSuite) TestShareEnvironmentExistingUser(c *gc.C) { 115 client := s.APIState.Client() 116 user := s.Factory.MakeEnvUser(c, nil) 117 cleanup := api.PatchClientFacadeCall(client, 118 func(request string, paramsIn interface{}, response interface{}) error { 119 if users, ok := paramsIn.(params.ModifyEnvironUsers); ok { 120 c.Assert(users.Changes, gc.HasLen, 1) 121 c.Logf(string(users.Changes[0].Action), gc.Equals, string(params.AddEnvUser)) 122 c.Logf(users.Changes[0].UserTag, gc.Equals, user.UserTag().String()) 123 } else { 124 c.Fatalf("wrong input structure") 125 } 126 if result, ok := response.(*params.ErrorResults); ok { 127 err := ¶ms.Error{Message: "failed to create environment user: env user already exists"} 128 *result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}}} 129 } else { 130 c.Fatalf("wrong input structure") 131 } 132 return nil 133 }, 134 ) 135 defer cleanup() 136 137 err := client.ShareEnvironment([]names.UserTag{user.UserTag()}) 138 c.Assert(err, gc.ErrorMatches, "failed to create environment user: env user already exists") 139 } 140 141 func (s *clientSuite) TestShareEnvironmentThreeUsers(c *gc.C) { 142 client := s.APIState.Client() 143 existingUser := s.Factory.MakeEnvUser(c, nil) 144 localUser := s.Factory.MakeUser(c, nil) 145 newUserTag := names.NewUserTag("foo@bar") 146 cleanup := api.PatchClientFacadeCall(client, 147 func(request string, paramsIn interface{}, response interface{}) error { 148 if users, ok := paramsIn.(params.ModifyEnvironUsers); ok { 149 c.Assert(users.Changes, gc.HasLen, 3) 150 c.Logf(string(users.Changes[0].Action), gc.Equals, string(params.AddEnvUser)) 151 c.Logf(users.Changes[0].UserTag, gc.Equals, existingUser.UserTag().String()) 152 c.Logf(string(users.Changes[1].Action), gc.Equals, string(params.AddEnvUser)) 153 c.Logf(users.Changes[1].UserTag, gc.Equals, localUser.UserTag().String()) 154 c.Logf(string(users.Changes[1].Action), gc.Equals, string(params.AddEnvUser)) 155 c.Logf(users.Changes[1].UserTag, gc.Equals, newUserTag.String()) 156 } else { 157 c.Log("wrong input structure") 158 c.Fail() 159 } 160 if result, ok := response.(*params.ErrorResults); ok { 161 err := ¶ms.Error{Message: "existing user"} 162 *result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}, {Error: nil}, {Error: nil}}} 163 } else { 164 c.Log("wrong output structure") 165 c.Fail() 166 } 167 return nil 168 }, 169 ) 170 defer cleanup() 171 172 err := client.ShareEnvironment([]names.UserTag{existingUser.UserTag(), localUser.UserTag(), newUserTag}) 173 c.Assert(err, gc.ErrorMatches, `existing user`) 174 } 175 176 func (s *clientSuite) TestShareEnvironmentRealAPIServer(c *gc.C) { 177 client := s.APIState.Client() 178 user := names.NewUserTag("foo@ubuntuone") 179 err := client.ShareEnvironment([]names.UserTag{user}) 180 c.Assert(err, jc.ErrorIsNil) 181 182 envUser, err := s.State.EnvironmentUser(user) 183 c.Assert(err, jc.ErrorIsNil) 184 c.Assert(envUser.UserName(), gc.Equals, user.Username()) 185 c.Assert(envUser.CreatedBy(), gc.Equals, s.AdminUserTag(c).Username()) 186 c.Assert(envUser.LastConnection(), gc.IsNil) 187 } 188 189 func (s *clientSuite) TestUnshareEnvironmentRealAPIServer(c *gc.C) { 190 client := s.APIState.Client() 191 user := names.NewUserTag("foo@ubuntuone") 192 err := client.ShareEnvironment([]names.UserTag{user}) 193 c.Assert(err, jc.ErrorIsNil) 194 195 envUser, err := s.State.EnvironmentUser(user) 196 c.Assert(err, jc.ErrorIsNil) 197 c.Assert(envUser.UserName(), gc.Equals, user.Username()) 198 199 err = client.UnshareEnvironment([]names.UserTag{user}) 200 c.Assert(err, jc.ErrorIsNil) 201 202 _, err = s.State.EnvironmentUser(user) 203 c.Assert(errors.IsNotFound(err), jc.IsTrue) 204 } 205 206 func (s *clientSuite) TestWatchDebugLogConnected(c *gc.C) { 207 // Shows both the unmarshalling of a real error, and 208 // that the api server is connected. 209 client := s.APIState.Client() 210 reader, err := client.WatchDebugLog(api.DebugLogParams{}) 211 c.Assert(err, gc.ErrorMatches, "cannot open log file: .*") 212 c.Assert(reader, gc.IsNil) 213 } 214 215 func (s *clientSuite) TestConnectionErrorBadConnection(c *gc.C) { 216 s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) { 217 return nil, fmt.Errorf("bad connection") 218 }) 219 client := s.APIState.Client() 220 reader, err := client.WatchDebugLog(api.DebugLogParams{}) 221 c.Assert(err, gc.ErrorMatches, "bad connection") 222 c.Assert(reader, gc.IsNil) 223 } 224 225 func (s *clientSuite) TestConnectionErrorNoData(c *gc.C) { 226 s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) { 227 return ioutil.NopCloser(&bytes.Buffer{}), nil 228 }) 229 client := s.APIState.Client() 230 reader, err := client.WatchDebugLog(api.DebugLogParams{}) 231 c.Assert(err, gc.ErrorMatches, "unable to read initial response: EOF") 232 c.Assert(reader, gc.IsNil) 233 } 234 235 func (s *clientSuite) TestConnectionErrorBadData(c *gc.C) { 236 s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) { 237 junk := strings.NewReader("junk\n") 238 return ioutil.NopCloser(junk), nil 239 }) 240 client := s.APIState.Client() 241 reader, err := client.WatchDebugLog(api.DebugLogParams{}) 242 c.Assert(err, gc.ErrorMatches, "unable to unmarshal initial response: .*") 243 c.Assert(reader, gc.IsNil) 244 } 245 246 func (s *clientSuite) TestConnectionErrorReadError(c *gc.C) { 247 s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) { 248 err := fmt.Errorf("bad read") 249 return ioutil.NopCloser(&badReader{err}), nil 250 }) 251 client := s.APIState.Client() 252 reader, err := client.WatchDebugLog(api.DebugLogParams{}) 253 c.Assert(err, gc.ErrorMatches, "unable to read initial response: bad read") 254 c.Assert(reader, gc.IsNil) 255 } 256 257 func (s *clientSuite) TestParamsEncoded(c *gc.C) { 258 s.PatchValue(api.WebsocketDialConfig, echoURL(c)) 259 260 params := api.DebugLogParams{ 261 IncludeEntity: []string{"a", "b"}, 262 IncludeModule: []string{"c", "d"}, 263 ExcludeEntity: []string{"e", "f"}, 264 ExcludeModule: []string{"g", "h"}, 265 Limit: 100, 266 Backlog: 200, 267 Level: loggo.ERROR, 268 Replay: true, 269 } 270 271 client := s.APIState.Client() 272 reader, err := client.WatchDebugLog(params) 273 c.Assert(err, jc.ErrorIsNil) 274 275 connectURL := connectURLFromReader(c, reader) 276 277 c.Assert(connectURL.Path, gc.Matches, "/log") 278 values := connectURL.Query() 279 c.Assert(values, jc.DeepEquals, url.Values{ 280 "includeEntity": params.IncludeEntity, 281 "includeModule": params.IncludeModule, 282 "excludeEntity": params.ExcludeEntity, 283 "excludeModule": params.ExcludeModule, 284 "maxLines": {"100"}, 285 "backlog": {"200"}, 286 "level": {"ERROR"}, 287 "replay": {"true"}, 288 }) 289 } 290 291 func (s *clientSuite) TestDebugLogRootPath(c *gc.C) { 292 s.PatchValue(api.WebsocketDialConfig, echoURL(c)) 293 294 // If the server is old, we log at "/log" 295 info := s.APIInfo(c) 296 info.EnvironTag = names.NewEnvironTag("") 297 apistate, err := api.Open(info, api.DialOpts{}) 298 c.Assert(err, jc.ErrorIsNil) 299 defer apistate.Close() 300 reader, err := apistate.Client().WatchDebugLog(api.DebugLogParams{}) 301 c.Assert(err, jc.ErrorIsNil) 302 connectURL := connectURLFromReader(c, reader) 303 c.Assert(connectURL.Path, gc.Matches, "/log") 304 } 305 306 func (s *clientSuite) TestDebugLogAtUUIDLogPath(c *gc.C) { 307 s.PatchValue(api.WebsocketDialConfig, echoURL(c)) 308 // If the server supports it, we should log at "/environment/UUID/log" 309 environ, err := s.State.Environment() 310 c.Assert(err, jc.ErrorIsNil) 311 info := s.APIInfo(c) 312 info.EnvironTag = environ.EnvironTag() 313 apistate, err := api.Open(info, api.DialOpts{}) 314 c.Assert(err, jc.ErrorIsNil) 315 defer apistate.Close() 316 reader, err := apistate.Client().WatchDebugLog(api.DebugLogParams{}) 317 c.Assert(err, jc.ErrorIsNil) 318 connectURL := connectURLFromReader(c, reader) 319 c.ExpectFailure("debug log always goes to /log for compatibility http://pad.lv/1326799") 320 c.Assert(connectURL.Path, gc.Matches, fmt.Sprintf("/%s/log", environ.UUID())) 321 } 322 323 func (s *clientSuite) TestOpenUsesEnvironUUIDPaths(c *gc.C) { 324 info := s.APIInfo(c) 325 // Backwards compatibility, passing EnvironTag = "" should just work 326 info.EnvironTag = names.NewEnvironTag("") 327 apistate, err := api.Open(info, api.DialOpts{}) 328 c.Assert(err, jc.ErrorIsNil) 329 apistate.Close() 330 331 // Passing in the correct environment UUID should also work 332 environ, err := s.State.Environment() 333 c.Assert(err, jc.ErrorIsNil) 334 info.EnvironTag = environ.EnvironTag() 335 apistate, err = api.Open(info, api.DialOpts{}) 336 c.Assert(err, jc.ErrorIsNil) 337 apistate.Close() 338 339 // Passing in a bad environment UUID should fail with a known error 340 info.EnvironTag = names.NewEnvironTag("dead-beef-123456") 341 apistate, err = api.Open(info, api.DialOpts{}) 342 c.Check(err, gc.ErrorMatches, `unknown environment: "dead-beef-123456"`) 343 c.Check(err, jc.Satisfies, params.IsCodeNotFound) 344 c.Assert(apistate, gc.IsNil) 345 } 346 347 func (s *clientSuite) TestSetEnvironAgentVersionDuringUpgrade(c *gc.C) { 348 // This is an integration test which ensure that a test with the 349 // correct error code is seen by the client from the 350 // SetEnvironAgentVersion call when an upgrade is in progress. 351 envConfig, err := s.State.EnvironConfig() 352 c.Assert(err, jc.ErrorIsNil) 353 agentVersion, ok := envConfig.AgentVersion() 354 c.Assert(ok, jc.IsTrue) 355 machine := s.Factory.MakeMachine(c, &factory.MachineParams{ 356 Jobs: []state.MachineJob{state.JobManageEnviron}, 357 }) 358 err = machine.SetAgentVersion(version.MustParseBinary(agentVersion.String() + "-quantal-amd64")) 359 c.Assert(err, jc.ErrorIsNil) 360 nextVersion := version.MustParse("9.8.7") 361 _, err = s.State.EnsureUpgradeInfo(machine.Id(), agentVersion, nextVersion) 362 c.Assert(err, jc.ErrorIsNil) 363 364 err = s.APIState.Client().SetEnvironAgentVersion(nextVersion) 365 366 // Expect an error with a error code that indicates this specific 367 // situation. The client needs to be able to reliably identify 368 // this error and handle it differently to other errors. 369 c.Assert(params.IsCodeUpgradeInProgress(err), jc.IsTrue) 370 } 371 372 func (s *clientSuite) TestAbortCurrentUpgrade(c *gc.C) { 373 client := s.APIState.Client() 374 someErr := errors.New("random") 375 cleanup := api.PatchClientFacadeCall(client, 376 func(request string, args interface{}, response interface{}) error { 377 c.Assert(request, gc.Equals, "AbortCurrentUpgrade") 378 c.Assert(args, gc.IsNil) 379 c.Assert(response, gc.IsNil) 380 return someErr 381 }, 382 ) 383 defer cleanup() 384 385 err := client.AbortCurrentUpgrade() 386 c.Assert(err, gc.Equals, someErr) // Confirms that the correct facade was called 387 } 388 389 func (s *clientSuite) TestEnvironmentGet(c *gc.C) { 390 client := s.APIState.Client() 391 env, err := client.EnvironmentGet() 392 c.Assert(err, jc.ErrorIsNil) 393 // Check a known value, just checking that there is something there. 394 c.Assert(env["type"], gc.Equals, "dummy") 395 } 396 397 func (s *clientSuite) TestEnvironmentSet(c *gc.C) { 398 client := s.APIState.Client() 399 err := client.EnvironmentSet(map[string]interface{}{ 400 "some-name": "value", 401 "other-name": true, 402 }) 403 c.Assert(err, jc.ErrorIsNil) 404 // Check them using EnvironmentGet. 405 env, err := client.EnvironmentGet() 406 c.Assert(err, jc.ErrorIsNil) 407 c.Assert(env["some-name"], gc.Equals, "value") 408 c.Assert(env["other-name"], gc.Equals, true) 409 } 410 411 func (s *clientSuite) TestEnvironmentUnset(c *gc.C) { 412 client := s.APIState.Client() 413 err := client.EnvironmentSet(map[string]interface{}{ 414 "some-name": "value", 415 }) 416 c.Assert(err, jc.ErrorIsNil) 417 418 // Now unset it and make sure it isn't there. 419 err = client.EnvironmentUnset("some-name") 420 c.Assert(err, jc.ErrorIsNil) 421 422 env, err := client.EnvironmentGet() 423 c.Assert(err, jc.ErrorIsNil) 424 _, found := env["some-name"] 425 c.Assert(found, jc.IsFalse) 426 } 427 428 // badReader raises err when Read is called. 429 type badReader struct { 430 err error 431 } 432 433 func (r *badReader) Read(p []byte) (n int, err error) { 434 return 0, r.err 435 } 436 437 func echoURL(c *gc.C) func(*websocket.Config) (io.ReadCloser, error) { 438 response := ¶ms.ErrorResult{} 439 message, err := json.Marshal(response) 440 c.Assert(err, jc.ErrorIsNil) 441 return func(config *websocket.Config) (io.ReadCloser, error) { 442 pr, pw := io.Pipe() 443 go func() { 444 fmt.Fprintf(pw, "%s\n", message) 445 fmt.Fprintf(pw, "%s\n", config.Location) 446 }() 447 return pr, nil 448 } 449 } 450 451 func connectURLFromReader(c *gc.C, rc io.ReadCloser) *url.URL { 452 bufReader := bufio.NewReader(rc) 453 location, err := bufReader.ReadString('\n') 454 c.Assert(err, jc.ErrorIsNil) 455 connectURL, err := url.Parse(strings.TrimSpace(location)) 456 c.Assert(err, jc.ErrorIsNil) 457 rc.Close() 458 return connectURL 459 }