github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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 "path" 17 "strings" 18 19 "github.com/juju/errors" 20 "github.com/juju/loggo" 21 "github.com/juju/names" 22 jc "github.com/juju/testing/checkers" 23 "golang.org/x/net/websocket" 24 gc "gopkg.in/check.v1" 25 "gopkg.in/juju/charm.v5" 26 27 "github.com/juju/juju/api" 28 "github.com/juju/juju/apiserver/params" 29 jujunames "github.com/juju/juju/juju/names" 30 jujutesting "github.com/juju/juju/juju/testing" 31 "github.com/juju/juju/state" 32 "github.com/juju/juju/testcharms" 33 coretesting "github.com/juju/juju/testing" 34 "github.com/juju/juju/testing/factory" 35 "github.com/juju/juju/version" 36 ) 37 38 type clientSuite struct { 39 jujutesting.JujuConnSuite 40 } 41 42 var _ = gc.Suite(&clientSuite{}) 43 44 // TODO(jam) 2013-08-27 http://pad.lv/1217282 45 // Right now most of the direct tests for api.Client behavior are in 46 // apiserver/client/*_test.go 47 48 func (s *clientSuite) TestCloseMultipleOk(c *gc.C) { 49 client := s.APIState.Client() 50 c.Assert(client.Close(), gc.IsNil) 51 c.Assert(client.Close(), gc.IsNil) 52 c.Assert(client.Close(), gc.IsNil) 53 } 54 55 func (s *clientSuite) TestUploadToolsOtherEnvironment(c *gc.C) { 56 otherSt, otherAPISt := s.otherEnviron(c) 57 defer otherSt.Close() 58 defer otherAPISt.Close() 59 client := otherAPISt.Client() 60 newVersion := version.MustParseBinary("5.4.3-quantal-amd64") 61 var called bool 62 63 // build fake tools 64 expectedTools, _ := coretesting.TarGz( 65 coretesting.NewTarFile(jujunames.Jujud, 0777, "jujud contents "+newVersion.String())) 66 67 // UploadTools does not use the facades, so instead of patching the 68 // facade call, we set up a fake endpoint to test. 69 defer fakeAPIEndpoint(c, client, envEndpoint(c, otherAPISt, "tools"), "POST", 70 func(w http.ResponseWriter, r *http.Request) { 71 called = true 72 73 c.Assert(r.URL.Query(), gc.DeepEquals, url.Values{ 74 "binaryVersion": []string{"5.4.3-quantal-amd64"}, 75 "series": []string{""}, 76 }) 77 defer r.Body.Close() 78 obtainedTools, err := ioutil.ReadAll(r.Body) 79 c.Assert(err, jc.ErrorIsNil) 80 c.Assert(obtainedTools, gc.DeepEquals, expectedTools) 81 }, 82 ).Close() 83 84 // We don't test the error or tools results as we only wish to assert that 85 // the API client POSTs the tools archive to the correct endpoint. 86 client.UploadTools(bytes.NewReader(expectedTools), newVersion) 87 c.Assert(called, jc.IsTrue) 88 } 89 90 func (s *clientSuite) TestAddLocalCharm(c *gc.C) { 91 charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy") 92 curl := charm.MustParseURL( 93 fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()), 94 ) 95 client := s.APIState.Client() 96 97 // Test the sanity checks first. 98 _, err := client.AddLocalCharm(charm.MustParseURL("cs:quantal/wordpress-1"), nil) 99 c.Assert(err, gc.ErrorMatches, `expected charm URL with local: schema, got "cs:quantal/wordpress-1"`) 100 101 // Upload an archive with its original revision. 102 savedURL, err := client.AddLocalCharm(curl, charmArchive) 103 c.Assert(err, jc.ErrorIsNil) 104 c.Assert(savedURL.String(), gc.Equals, curl.String()) 105 106 // Upload a charm directory with changed revision. 107 charmDir := testcharms.Repo.ClonedDir(c.MkDir(), "dummy") 108 charmDir.SetDiskRevision(42) 109 savedURL, err = client.AddLocalCharm(curl, charmDir) 110 c.Assert(err, jc.ErrorIsNil) 111 c.Assert(savedURL.Revision, gc.Equals, 42) 112 113 // Upload a charm directory again, revision should be bumped. 114 savedURL, err = client.AddLocalCharm(curl, charmDir) 115 c.Assert(err, jc.ErrorIsNil) 116 c.Assert(savedURL.String(), gc.Equals, curl.WithRevision(43).String()) 117 } 118 119 func (s *clientSuite) TestAddLocalCharmOtherEnvironment(c *gc.C) { 120 charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy") 121 curl := charm.MustParseURL( 122 fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()), 123 ) 124 125 otherSt, otherAPISt := s.otherEnviron(c) 126 defer otherSt.Close() 127 defer otherAPISt.Close() 128 client := otherAPISt.Client() 129 130 // Upload an archive 131 savedURL, err := client.AddLocalCharm(curl, charmArchive) 132 c.Assert(err, jc.ErrorIsNil) 133 c.Assert(savedURL.String(), gc.Equals, curl.String()) 134 135 charm, err := otherSt.Charm(curl) 136 c.Assert(err, jc.ErrorIsNil) 137 c.Assert(charm.String(), gc.Equals, curl.String()) 138 } 139 140 func (s *clientSuite) otherEnviron(c *gc.C) (*state.State, api.Connection) { 141 otherSt := s.Factory.MakeEnvironment(c, nil) 142 info := s.APIInfo(c) 143 info.EnvironTag = otherSt.EnvironTag() 144 apiState, err := api.Open(info, api.DefaultDialOpts()) 145 c.Assert(err, jc.ErrorIsNil) 146 return otherSt, apiState 147 } 148 149 func (s *clientSuite) TestAddLocalCharmError(c *gc.C) { 150 client := s.APIState.Client() 151 152 // AddLocalCharm does not use the facades, so instead of patching the 153 // facade call, we set up a fake endpoint to test. 154 defer fakeAPIEndpoint(c, client, envEndpoint(c, s.APIState, "charms"), "POST", 155 func(w http.ResponseWriter, r *http.Request) { 156 http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) 157 }, 158 ).Close() 159 160 charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy") 161 curl := charm.MustParseURL( 162 fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()), 163 ) 164 165 _, err := client.AddLocalCharm(curl, charmArchive) 166 c.Assert(err, gc.ErrorMatches, "charm upload failed: 405 \\(Method Not Allowed\\)") 167 } 168 169 func fakeAPIEndpoint(c *gc.C, client *api.Client, address, method string, handle func(http.ResponseWriter, *http.Request)) net.Listener { 170 lis, err := net.Listen("tcp", "127.0.0.1:0") 171 c.Assert(err, jc.ErrorIsNil) 172 173 http.HandleFunc(address, func(w http.ResponseWriter, r *http.Request) { 174 if r.Method == method { 175 handle(w, r) 176 } 177 }) 178 go func() { 179 http.Serve(lis, nil) 180 }() 181 api.SetServerAddress(client, "http", lis.Addr().String()) 182 return lis 183 } 184 185 // envEndpoint returns "/environment/<env-uuid>/<destination>" 186 func envEndpoint(c *gc.C, apiState api.Connection, destination string) string { 187 envTag, err := apiState.EnvironTag() 188 c.Assert(err, jc.ErrorIsNil) 189 return path.Join("/environment", envTag.Id(), destination) 190 } 191 192 func (s *clientSuite) TestClientEnvironmentUUID(c *gc.C) { 193 environ, err := s.State.Environment() 194 c.Assert(err, jc.ErrorIsNil) 195 196 client := s.APIState.Client() 197 c.Assert(client.EnvironmentUUID(), gc.Equals, environ.Tag().Id()) 198 } 199 200 func (s *clientSuite) TestClientEnvironmentUsers(c *gc.C) { 201 client := s.APIState.Client() 202 cleanup := api.PatchClientFacadeCall(client, 203 func(request string, paramsIn interface{}, response interface{}) error { 204 c.Assert(paramsIn, gc.IsNil) 205 if response, ok := response.(*params.EnvUserInfoResults); ok { 206 response.Results = []params.EnvUserInfoResult{ 207 {Result: ¶ms.EnvUserInfo{UserName: "one"}}, 208 {Result: ¶ms.EnvUserInfo{UserName: "two"}}, 209 {Result: ¶ms.EnvUserInfo{UserName: "three"}}, 210 } 211 } else { 212 c.Log("wrong output structure") 213 c.Fail() 214 } 215 return nil 216 }, 217 ) 218 defer cleanup() 219 220 obtained, err := client.EnvironmentUserInfo() 221 c.Assert(err, jc.ErrorIsNil) 222 223 c.Assert(obtained, jc.DeepEquals, []params.EnvUserInfo{ 224 {UserName: "one"}, 225 {UserName: "two"}, 226 {UserName: "three"}, 227 }) 228 } 229 230 func (s *clientSuite) TestShareEnvironmentExistingUser(c *gc.C) { 231 client := s.APIState.Client() 232 user := s.Factory.MakeEnvUser(c, nil) 233 cleanup := api.PatchClientFacadeCall(client, 234 func(request string, paramsIn interface{}, response interface{}) error { 235 if users, ok := paramsIn.(params.ModifyEnvironUsers); ok { 236 c.Assert(users.Changes, gc.HasLen, 1) 237 c.Logf(string(users.Changes[0].Action), gc.Equals, string(params.AddEnvUser)) 238 c.Logf(users.Changes[0].UserTag, gc.Equals, user.UserTag().String()) 239 } else { 240 c.Fatalf("wrong input structure") 241 } 242 if result, ok := response.(*params.ErrorResults); ok { 243 err := ¶ms.Error{ 244 Message: "error message", 245 Code: params.CodeAlreadyExists, 246 } 247 *result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}}} 248 } else { 249 c.Fatalf("wrong input structure") 250 } 251 return nil 252 }, 253 ) 254 defer cleanup() 255 256 err := client.ShareEnvironment(user.UserTag()) 257 c.Assert(err, jc.ErrorIsNil) 258 logMsg := fmt.Sprintf("WARNING juju.api environment is already shared with %s", user.UserName()) 259 c.Assert(c.GetTestLog(), jc.Contains, logMsg) 260 } 261 262 func (s *clientSuite) TestDestroyEnvironment(c *gc.C) { 263 client := s.APIState.Client() 264 var called bool 265 cleanup := api.PatchClientFacadeCall(client, 266 func(req string, args interface{}, resp interface{}) error { 267 c.Assert(req, gc.Equals, "DestroyEnvironment") 268 called = true 269 return nil 270 }) 271 defer cleanup() 272 273 err := client.DestroyEnvironment() 274 c.Assert(err, jc.ErrorIsNil) 275 c.Assert(called, jc.IsTrue) 276 } 277 278 func (s *clientSuite) TestShareEnvironmentThreeUsers(c *gc.C) { 279 client := s.APIState.Client() 280 existingUser := s.Factory.MakeEnvUser(c, nil) 281 localUser := s.Factory.MakeUser(c, nil) 282 newUserTag := names.NewUserTag("foo@bar") 283 cleanup := api.PatchClientFacadeCall(client, 284 func(request string, paramsIn interface{}, response interface{}) error { 285 if users, ok := paramsIn.(params.ModifyEnvironUsers); ok { 286 c.Assert(users.Changes, gc.HasLen, 3) 287 c.Assert(string(users.Changes[0].Action), gc.Equals, string(params.AddEnvUser)) 288 c.Assert(users.Changes[0].UserTag, gc.Equals, existingUser.UserTag().String()) 289 c.Assert(string(users.Changes[1].Action), gc.Equals, string(params.AddEnvUser)) 290 c.Assert(users.Changes[1].UserTag, gc.Equals, localUser.UserTag().String()) 291 c.Assert(string(users.Changes[2].Action), gc.Equals, string(params.AddEnvUser)) 292 c.Assert(users.Changes[2].UserTag, gc.Equals, newUserTag.String()) 293 } else { 294 c.Log("wrong input structure") 295 c.Fail() 296 } 297 if result, ok := response.(*params.ErrorResults); ok { 298 err := ¶ms.Error{Message: "existing user"} 299 *result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}, {Error: nil}, {Error: nil}}} 300 } else { 301 c.Log("wrong output structure") 302 c.Fail() 303 } 304 return nil 305 }, 306 ) 307 defer cleanup() 308 309 err := client.ShareEnvironment(existingUser.UserTag(), localUser.UserTag(), newUserTag) 310 c.Assert(err, gc.ErrorMatches, `existing user`) 311 } 312 313 func (s *clientSuite) TestUnshareEnvironmentThreeUsers(c *gc.C) { 314 client := s.APIState.Client() 315 missingUser := s.Factory.MakeEnvUser(c, nil) 316 localUser := s.Factory.MakeUser(c, nil) 317 newUserTag := names.NewUserTag("foo@bar") 318 cleanup := api.PatchClientFacadeCall(client, 319 func(request string, paramsIn interface{}, response interface{}) error { 320 if users, ok := paramsIn.(params.ModifyEnvironUsers); ok { 321 c.Assert(users.Changes, gc.HasLen, 3) 322 c.Assert(string(users.Changes[0].Action), gc.Equals, string(params.RemoveEnvUser)) 323 c.Assert(users.Changes[0].UserTag, gc.Equals, missingUser.UserTag().String()) 324 c.Assert(string(users.Changes[1].Action), gc.Equals, string(params.RemoveEnvUser)) 325 c.Assert(users.Changes[1].UserTag, gc.Equals, localUser.UserTag().String()) 326 c.Assert(string(users.Changes[2].Action), gc.Equals, string(params.RemoveEnvUser)) 327 c.Assert(users.Changes[2].UserTag, gc.Equals, newUserTag.String()) 328 } else { 329 c.Log("wrong input structure") 330 c.Fail() 331 } 332 if result, ok := response.(*params.ErrorResults); ok { 333 err := ¶ms.Error{Message: "error unsharing user"} 334 *result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}, {Error: nil}, {Error: nil}}} 335 } else { 336 c.Log("wrong output structure") 337 c.Fail() 338 } 339 return nil 340 }, 341 ) 342 defer cleanup() 343 344 err := client.UnshareEnvironment(missingUser.UserTag(), localUser.UserTag(), newUserTag) 345 c.Assert(err, gc.ErrorMatches, "error unsharing user") 346 } 347 348 func (s *clientSuite) TestUnshareEnvironmentMissingUser(c *gc.C) { 349 client := s.APIState.Client() 350 user := names.NewUserTag("bob@local") 351 cleanup := api.PatchClientFacadeCall(client, 352 func(request string, paramsIn interface{}, response interface{}) error { 353 if users, ok := paramsIn.(params.ModifyEnvironUsers); ok { 354 c.Assert(users.Changes, gc.HasLen, 1) 355 c.Logf(string(users.Changes[0].Action), gc.Equals, string(params.RemoveEnvUser)) 356 c.Logf(users.Changes[0].UserTag, gc.Equals, user.String()) 357 } else { 358 c.Fatalf("wrong input structure") 359 } 360 if result, ok := response.(*params.ErrorResults); ok { 361 err := ¶ms.Error{ 362 Message: "error message", 363 Code: params.CodeNotFound, 364 } 365 *result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}}} 366 } else { 367 c.Fatalf("wrong input structure") 368 } 369 return nil 370 }, 371 ) 372 defer cleanup() 373 374 err := client.UnshareEnvironment(user) 375 c.Assert(err, jc.ErrorIsNil) 376 logMsg := fmt.Sprintf("WARNING juju.api environment was not previously shared with user %s", user.Username()) 377 c.Assert(c.GetTestLog(), jc.Contains, logMsg) 378 } 379 380 func (s *clientSuite) TestWatchDebugLogConnected(c *gc.C) { 381 // Shows both the unmarshalling of a real error, and 382 // that the api server is connected. 383 client := s.APIState.Client() 384 reader, err := client.WatchDebugLog(api.DebugLogParams{}) 385 c.Assert(err, gc.ErrorMatches, "cannot open log file: .*") 386 c.Assert(reader, gc.IsNil) 387 } 388 389 func (s *clientSuite) TestConnectionErrorBadConnection(c *gc.C) { 390 s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) { 391 return nil, fmt.Errorf("bad connection") 392 }) 393 client := s.APIState.Client() 394 reader, err := client.WatchDebugLog(api.DebugLogParams{}) 395 c.Assert(err, gc.ErrorMatches, "bad connection") 396 c.Assert(reader, gc.IsNil) 397 } 398 399 func (s *clientSuite) TestConnectionErrorNoData(c *gc.C) { 400 s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) { 401 return ioutil.NopCloser(&bytes.Buffer{}), nil 402 }) 403 client := s.APIState.Client() 404 reader, err := client.WatchDebugLog(api.DebugLogParams{}) 405 c.Assert(err, gc.ErrorMatches, "unable to read initial response: EOF") 406 c.Assert(reader, gc.IsNil) 407 } 408 409 func (s *clientSuite) TestConnectionErrorBadData(c *gc.C) { 410 s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) { 411 junk := strings.NewReader("junk\n") 412 return ioutil.NopCloser(junk), nil 413 }) 414 client := s.APIState.Client() 415 reader, err := client.WatchDebugLog(api.DebugLogParams{}) 416 c.Assert(err, gc.ErrorMatches, "unable to unmarshal initial response: .*") 417 c.Assert(reader, gc.IsNil) 418 } 419 420 func (s *clientSuite) TestConnectionErrorReadError(c *gc.C) { 421 s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) { 422 err := fmt.Errorf("bad read") 423 return ioutil.NopCloser(&badReader{err}), nil 424 }) 425 client := s.APIState.Client() 426 reader, err := client.WatchDebugLog(api.DebugLogParams{}) 427 c.Assert(err, gc.ErrorMatches, "unable to read initial response: bad read") 428 c.Assert(reader, gc.IsNil) 429 } 430 431 func (s *clientSuite) TestParamsEncoded(c *gc.C) { 432 s.PatchValue(api.WebsocketDialConfig, echoURL(c)) 433 434 params := api.DebugLogParams{ 435 IncludeEntity: []string{"a", "b"}, 436 IncludeModule: []string{"c", "d"}, 437 ExcludeEntity: []string{"e", "f"}, 438 ExcludeModule: []string{"g", "h"}, 439 Limit: 100, 440 Backlog: 200, 441 Level: loggo.ERROR, 442 Replay: true, 443 } 444 445 client := s.APIState.Client() 446 reader, err := client.WatchDebugLog(params) 447 c.Assert(err, jc.ErrorIsNil) 448 449 connectURL := connectURLFromReader(c, reader) 450 values := connectURL.Query() 451 c.Assert(values, jc.DeepEquals, url.Values{ 452 "includeEntity": params.IncludeEntity, 453 "includeModule": params.IncludeModule, 454 "excludeEntity": params.ExcludeEntity, 455 "excludeModule": params.ExcludeModule, 456 "maxLines": {"100"}, 457 "backlog": {"200"}, 458 "level": {"ERROR"}, 459 "replay": {"true"}, 460 }) 461 } 462 463 func (s *clientSuite) TestDebugLogRootPath(c *gc.C) { 464 s.PatchValue(api.WebsocketDialConfig, echoURL(c)) 465 466 // If the server is old, we log at "/log" 467 info := s.APIInfo(c) 468 info.EnvironTag = names.NewEnvironTag("") 469 apistate, err := api.OpenWithVersion(info, api.DialOpts{}, 1) 470 c.Assert(err, jc.ErrorIsNil) 471 defer apistate.Close() 472 reader, err := apistate.Client().WatchDebugLog(api.DebugLogParams{}) 473 c.Assert(err, jc.ErrorIsNil) 474 connectURL := connectURLFromReader(c, reader) 475 c.Assert(connectURL.Path, gc.Matches, "/log") 476 } 477 478 func (s *clientSuite) TestDebugLogAtUUIDLogPath(c *gc.C) { 479 s.PatchValue(api.WebsocketDialConfig, echoURL(c)) 480 // If the server supports it, we should log at "/environment/UUID/log" 481 environ, err := s.State.Environment() 482 c.Assert(err, jc.ErrorIsNil) 483 info := s.APIInfo(c) 484 info.EnvironTag = environ.EnvironTag() 485 apistate, err := api.Open(info, api.DialOpts{}) 486 c.Assert(err, jc.ErrorIsNil) 487 defer apistate.Close() 488 reader, err := apistate.Client().WatchDebugLog(api.DebugLogParams{}) 489 c.Assert(err, jc.ErrorIsNil) 490 connectURL := connectURLFromReader(c, reader) 491 c.Assert(connectURL.Path, gc.Matches, fmt.Sprintf("/environment/%s/log", environ.UUID())) 492 } 493 494 func (s *clientSuite) TestOpenUsesEnvironUUIDPaths(c *gc.C) { 495 info := s.APIInfo(c) 496 // Backwards compatibility, passing EnvironTag = "" should just work 497 info.EnvironTag = names.NewEnvironTag("") 498 apistate, err := api.Open(info, api.DialOpts{}) 499 c.Assert(err, jc.ErrorIsNil) 500 apistate.Close() 501 502 // Passing in the correct environment UUID should also work 503 environ, err := s.State.Environment() 504 c.Assert(err, jc.ErrorIsNil) 505 info.EnvironTag = environ.EnvironTag() 506 apistate, err = api.Open(info, api.DialOpts{}) 507 c.Assert(err, jc.ErrorIsNil) 508 apistate.Close() 509 510 // Passing in a bad environment UUID should fail with a known error 511 info.EnvironTag = names.NewEnvironTag("dead-beef-123456") 512 apistate, err = api.Open(info, api.DialOpts{}) 513 c.Check(err, gc.ErrorMatches, `unknown environment: "dead-beef-123456"`) 514 c.Check(err, jc.Satisfies, params.IsCodeNotFound) 515 c.Assert(apistate, gc.IsNil) 516 } 517 518 func (s *clientSuite) TestSetEnvironAgentVersionDuringUpgrade(c *gc.C) { 519 // This is an integration test which ensure that a test with the 520 // correct error code is seen by the client from the 521 // SetEnvironAgentVersion call when an upgrade is in progress. 522 envConfig, err := s.State.EnvironConfig() 523 c.Assert(err, jc.ErrorIsNil) 524 agentVersion, ok := envConfig.AgentVersion() 525 c.Assert(ok, jc.IsTrue) 526 machine := s.Factory.MakeMachine(c, &factory.MachineParams{ 527 Jobs: []state.MachineJob{state.JobManageEnviron}, 528 }) 529 err = machine.SetAgentVersion(version.MustParseBinary(agentVersion.String() + "-quantal-amd64")) 530 c.Assert(err, jc.ErrorIsNil) 531 nextVersion := version.MustParse("9.8.7") 532 _, err = s.State.EnsureUpgradeInfo(machine.Id(), agentVersion, nextVersion) 533 c.Assert(err, jc.ErrorIsNil) 534 535 err = s.APIState.Client().SetEnvironAgentVersion(nextVersion) 536 537 // Expect an error with a error code that indicates this specific 538 // situation. The client needs to be able to reliably identify 539 // this error and handle it differently to other errors. 540 c.Assert(params.IsCodeUpgradeInProgress(err), jc.IsTrue) 541 } 542 543 func (s *clientSuite) TestAbortCurrentUpgrade(c *gc.C) { 544 client := s.APIState.Client() 545 someErr := errors.New("random") 546 cleanup := api.PatchClientFacadeCall(client, 547 func(request string, args interface{}, response interface{}) error { 548 c.Assert(request, gc.Equals, "AbortCurrentUpgrade") 549 c.Assert(args, gc.IsNil) 550 c.Assert(response, gc.IsNil) 551 return someErr 552 }, 553 ) 554 defer cleanup() 555 556 err := client.AbortCurrentUpgrade() 557 c.Assert(err, gc.Equals, someErr) // Confirms that the correct facade was called 558 } 559 560 func (s *clientSuite) TestEnvironmentGet(c *gc.C) { 561 client := s.APIState.Client() 562 env, err := client.EnvironmentGet() 563 c.Assert(err, jc.ErrorIsNil) 564 // Check a known value, just checking that there is something there. 565 c.Assert(env["type"], gc.Equals, "dummy") 566 } 567 568 func (s *clientSuite) TestEnvironmentSet(c *gc.C) { 569 client := s.APIState.Client() 570 err := client.EnvironmentSet(map[string]interface{}{ 571 "some-name": "value", 572 "other-name": true, 573 }) 574 c.Assert(err, jc.ErrorIsNil) 575 // Check them using EnvironmentGet. 576 env, err := client.EnvironmentGet() 577 c.Assert(err, jc.ErrorIsNil) 578 c.Assert(env["some-name"], gc.Equals, "value") 579 c.Assert(env["other-name"], gc.Equals, true) 580 } 581 582 func (s *clientSuite) TestEnvironmentUnset(c *gc.C) { 583 client := s.APIState.Client() 584 err := client.EnvironmentSet(map[string]interface{}{ 585 "some-name": "value", 586 }) 587 c.Assert(err, jc.ErrorIsNil) 588 589 // Now unset it and make sure it isn't there. 590 err = client.EnvironmentUnset("some-name") 591 c.Assert(err, jc.ErrorIsNil) 592 593 env, err := client.EnvironmentGet() 594 c.Assert(err, jc.ErrorIsNil) 595 _, found := env["some-name"] 596 c.Assert(found, jc.IsFalse) 597 } 598 599 // badReader raises err when Read is called. 600 type badReader struct { 601 err error 602 } 603 604 func (r *badReader) Read(p []byte) (n int, err error) { 605 return 0, r.err 606 } 607 608 func echoURL(c *gc.C) func(*websocket.Config) (io.ReadCloser, error) { 609 response := ¶ms.ErrorResult{} 610 message, err := json.Marshal(response) 611 c.Assert(err, jc.ErrorIsNil) 612 return func(config *websocket.Config) (io.ReadCloser, error) { 613 pr, pw := io.Pipe() 614 go func() { 615 fmt.Fprintf(pw, "%s\n", message) 616 fmt.Fprintf(pw, "%s\n", config.Location) 617 }() 618 return pr, nil 619 } 620 } 621 622 func connectURLFromReader(c *gc.C, rc io.ReadCloser) *url.URL { 623 bufReader := bufio.NewReader(rc) 624 location, err := bufReader.ReadString('\n') 625 c.Assert(err, jc.ErrorIsNil) 626 connectURL, err := url.Parse(strings.TrimSpace(location)) 627 c.Assert(err, jc.ErrorIsNil) 628 rc.Close() 629 return connectURL 630 }