github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/cloud/cloud_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cloud_test 5 6 import ( 7 gitjujutesting "github.com/juju/testing" 8 jc "github.com/juju/testing/checkers" 9 gc "gopkg.in/check.v1" 10 "gopkg.in/juju/names.v2" 11 12 cloudfacade "github.com/juju/juju/apiserver/cloud" 13 "github.com/juju/juju/apiserver/params" 14 apiservertesting "github.com/juju/juju/apiserver/testing" 15 "github.com/juju/juju/cloud" 16 _ "github.com/juju/juju/provider/dummy" 17 ) 18 19 type cloudSuite struct { 20 gitjujutesting.IsolationSuite 21 backend mockBackend 22 authorizer apiservertesting.FakeAuthorizer 23 api *cloudfacade.CloudAPI 24 } 25 26 var _ = gc.Suite(&cloudSuite{}) 27 28 func (s *cloudSuite) SetUpTest(c *gc.C) { 29 s.IsolationSuite.SetUpTest(c) 30 s.authorizer = apiservertesting.FakeAuthorizer{ 31 Tag: names.NewUserTag("admin@local"), 32 } 33 s.backend = mockBackend{ 34 cloud: cloud.Cloud{ 35 Type: "dummy", 36 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 37 Regions: []cloud.Region{{Name: "nether", Endpoint: "endpoint"}}, 38 }, 39 creds: map[string]cloud.Credential{ 40 names.NewCloudCredentialTag("meep/bruce@local/one").Canonical(): cloud.NewEmptyCredential(), 41 names.NewCloudCredentialTag("meep/bruce@local/two").Canonical(): cloud.NewCredential(cloud.UserPassAuthType, map[string]string{ 42 "username": "admin", 43 "password": "adm1n", 44 }), 45 }, 46 } 47 var err error 48 s.api, err = cloudfacade.NewCloudAPI(&s.backend, &s.authorizer) 49 c.Assert(err, jc.ErrorIsNil) 50 } 51 52 func (s *cloudSuite) TestCloud(c *gc.C) { 53 results, err := s.api.Cloud(params.Entities{ 54 Entities: []params.Entity{{Tag: "cloud-my-cloud"}, {Tag: "machine-0"}}, 55 }) 56 c.Assert(err, jc.ErrorIsNil) 57 s.backend.CheckCalls(c, []gitjujutesting.StubCall{ 58 {"Cloud", []interface{}{"my-cloud"}}, 59 }) 60 c.Assert(results.Results, gc.HasLen, 2) 61 c.Assert(results.Results[0].Error, gc.IsNil) 62 c.Assert(results.Results[0].Cloud, jc.DeepEquals, ¶ms.Cloud{ 63 Type: "dummy", 64 AuthTypes: []string{"empty", "userpass"}, 65 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}}, 66 }) 67 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 68 Message: `"machine-0" is not a valid cloud tag`, 69 }) 70 } 71 72 func (s *cloudSuite) TestClouds(c *gc.C) { 73 result, err := s.api.Clouds() 74 c.Assert(err, jc.ErrorIsNil) 75 s.backend.CheckCallNames(c, "Clouds") 76 c.Assert(result.Clouds, jc.DeepEquals, map[string]params.Cloud{ 77 "cloud-my-cloud": { 78 Type: "dummy", 79 AuthTypes: []string{"empty", "userpass"}, 80 Regions: []params.CloudRegion{{Name: "nether", Endpoint: "endpoint"}}, 81 }, 82 }) 83 } 84 85 func (s *cloudSuite) TestDefaultCloud(c *gc.C) { 86 result, err := s.api.DefaultCloud() 87 c.Assert(err, jc.ErrorIsNil) 88 s.backend.CheckCallNames(c, "ControllerModel") 89 c.Assert(result, jc.DeepEquals, params.StringResult{ 90 Result: "cloud-some-cloud", 91 }) 92 } 93 94 func (s *cloudSuite) TestUserCredentials(c *gc.C) { 95 s.authorizer.Tag = names.NewUserTag("bruce@local") 96 results, err := s.api.UserCredentials(params.UserClouds{UserClouds: []params.UserCloud{{ 97 UserTag: "machine-0", 98 CloudTag: "cloud-meep", 99 }, { 100 UserTag: "user-admin", 101 CloudTag: "cloud-meep", 102 }, { 103 UserTag: "user-bruce", 104 CloudTag: "cloud-meep", 105 }}}) 106 c.Assert(err, jc.ErrorIsNil) 107 s.backend.CheckCallNames(c, "ControllerTag", "CloudCredentials") 108 s.backend.CheckCall(c, 1, "CloudCredentials", names.NewUserTag("bruce"), "meep") 109 110 c.Assert(results.Results, gc.HasLen, 3) 111 c.Assert(results.Results[0].Error, jc.DeepEquals, ¶ms.Error{ 112 Message: `"machine-0" is not a valid user tag`, 113 }) 114 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 115 Message: "permission denied", Code: params.CodeUnauthorized, 116 }) 117 c.Assert(results.Results[2].Error, gc.IsNil) 118 c.Assert(results.Results[2].Result, jc.SameContents, []string{ 119 "cloudcred-meep_bruce@local_one", 120 "cloudcred-meep_bruce@local_two", 121 }) 122 } 123 124 func (s *cloudSuite) TestUserCredentialsAdminAccess(c *gc.C) { 125 s.authorizer.Tag = names.NewUserTag("admin@local") 126 results, err := s.api.UserCredentials(params.UserClouds{UserClouds: []params.UserCloud{{ 127 UserTag: "user-julia", 128 CloudTag: "cloud-meep", 129 }}}) 130 c.Assert(err, jc.ErrorIsNil) 131 s.backend.CheckCallNames(c, "ControllerTag", "CloudCredentials") 132 c.Assert(results.Results, gc.HasLen, 1) 133 // admin can access others' credentials 134 c.Assert(results.Results[0].Error, gc.IsNil) 135 } 136 137 func (s *cloudSuite) TestUpdateCredentials(c *gc.C) { 138 s.authorizer.Tag = names.NewUserTag("bruce@local") 139 results, err := s.api.UpdateCredentials(params.UpdateCloudCredentials{Credentials: []params.UpdateCloudCredential{{ 140 Tag: "machine-0", 141 }, { 142 Tag: "cloudcred-meep_admin_whatever", 143 }, { 144 Tag: "cloudcred-meep_bruce_three", 145 Credential: params.CloudCredential{ 146 AuthType: "oauth1", 147 Attributes: map[string]string{"token": "foo:bar:baz"}, 148 }, 149 }}}) 150 c.Assert(err, jc.ErrorIsNil) 151 s.backend.CheckCallNames(c, "ControllerTag", "UpdateCloudCredential") 152 c.Assert(results.Results, gc.HasLen, 3) 153 c.Assert(results.Results[0].Error, jc.DeepEquals, ¶ms.Error{ 154 Message: `"machine-0" is not a valid cloudcred tag`, 155 }) 156 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 157 Message: "permission denied", Code: params.CodeUnauthorized, 158 }) 159 c.Assert(results.Results[2].Error, gc.IsNil) 160 161 s.backend.CheckCall( 162 c, 1, "UpdateCloudCredential", 163 names.NewCloudCredentialTag("meep/bruce/three"), 164 cloud.NewCredential( 165 cloud.OAuth1AuthType, 166 map[string]string{"token": "foo:bar:baz"}, 167 ), 168 ) 169 } 170 171 func (s *cloudSuite) TestUpdateCredentialsAdminAccess(c *gc.C) { 172 s.authorizer.Tag = names.NewUserTag("admin@local") 173 results, err := s.api.UpdateCredentials(params.UpdateCloudCredentials{Credentials: []params.UpdateCloudCredential{{ 174 Tag: "cloudcred-meep_julia_three", 175 Credential: params.CloudCredential{ 176 AuthType: "oauth1", 177 Attributes: map[string]string{"token": "foo:bar:baz"}, 178 }, 179 }}}) 180 c.Assert(err, jc.ErrorIsNil) 181 s.backend.CheckCallNames(c, "ControllerTag", "UpdateCloudCredential") 182 c.Assert(results.Results, gc.HasLen, 1) 183 // admin can update others' credentials 184 c.Assert(results.Results[0].Error, gc.IsNil) 185 } 186 187 func (s *cloudSuite) TestRevokeCredentials(c *gc.C) { 188 s.authorizer.Tag = names.NewUserTag("bruce@local") 189 results, err := s.api.RevokeCredentials(params.Entities{Entities: []params.Entity{{ 190 Tag: "machine-0", 191 }, { 192 Tag: "cloudcred-meep_admin_whatever", 193 }, { 194 Tag: "cloudcred-meep_bruce_three", 195 }}}) 196 c.Assert(err, jc.ErrorIsNil) 197 s.backend.CheckCallNames(c, "ControllerTag", "RemoveCloudCredential") 198 c.Assert(results.Results, gc.HasLen, 3) 199 c.Assert(results.Results[0].Error, jc.DeepEquals, ¶ms.Error{ 200 Message: `"machine-0" is not a valid cloudcred tag`, 201 }) 202 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 203 Message: "permission denied", Code: params.CodeUnauthorized, 204 }) 205 c.Assert(results.Results[2].Error, gc.IsNil) 206 207 s.backend.CheckCall( 208 c, 1, "RemoveCloudCredential", 209 names.NewCloudCredentialTag("meep/bruce/three"), 210 ) 211 } 212 213 func (s *cloudSuite) TestRevokeCredentialsAdminAccess(c *gc.C) { 214 s.authorizer.Tag = names.NewUserTag("admin@local") 215 results, err := s.api.RevokeCredentials(params.Entities{Entities: []params.Entity{{ 216 Tag: "cloudcred-meep_julia_three", 217 }}}) 218 c.Assert(err, jc.ErrorIsNil) 219 s.backend.CheckCallNames(c, "ControllerTag", "RemoveCloudCredential") 220 c.Assert(results.Results, gc.HasLen, 1) 221 // admin can revoke others' credentials 222 c.Assert(results.Results[0].Error, gc.IsNil) 223 } 224 225 func (s *cloudSuite) TestCredential(c *gc.C) { 226 s.authorizer.Tag = names.NewUserTag("bruce@local") 227 results, err := s.api.Credential(params.Entities{Entities: []params.Entity{{ 228 Tag: "machine-0", 229 }, { 230 Tag: "cloudcred-meep_admin_foo", 231 }, { 232 Tag: "cloudcred-meep_bruce@local_two", 233 }}}) 234 c.Assert(err, jc.ErrorIsNil) 235 s.backend.CheckCallNames(c, "ControllerTag", "CloudCredentials", "Cloud") 236 s.backend.CheckCall(c, 1, "CloudCredentials", names.NewUserTag("bruce@local"), "meep") 237 238 c.Assert(results.Results, gc.HasLen, 3) 239 c.Assert(results.Results[0].Error, jc.DeepEquals, ¶ms.Error{ 240 Message: `"machine-0" is not a valid cloudcred tag`, 241 }) 242 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 243 Message: "permission denied", Code: params.CodeUnauthorized, 244 }) 245 c.Assert(results.Results[2].Error, gc.IsNil) 246 c.Assert(results.Results[2].Result, jc.DeepEquals, ¶ms.CloudCredential{ 247 AuthType: "userpass", 248 Attributes: map[string]string{"username": "admin"}, 249 Redacted: []string{"password"}, 250 }) 251 } 252 253 func (s *cloudSuite) TestCredentialAdminAccess(c *gc.C) { 254 s.authorizer.Tag = names.NewUserTag("admin@local") 255 results, err := s.api.Credential(params.Entities{Entities: []params.Entity{{ 256 Tag: "cloudcred-meep_bruce@local_two", 257 }}}) 258 c.Assert(err, jc.ErrorIsNil) 259 s.backend.CheckCallNames(c, "ControllerTag", "CloudCredentials", "Cloud") 260 c.Assert(results.Results, gc.HasLen, 1) 261 // admin can access others' credentials 262 c.Assert(results.Results[0].Error, gc.IsNil) 263 } 264 265 type mockBackend struct { 266 gitjujutesting.Stub 267 cloud cloud.Cloud 268 creds map[string]cloud.Credential 269 } 270 271 func (st *mockBackend) IsControllerAdmin(user names.UserTag) (bool, error) { 272 st.MethodCall(st, "IsControllerAdmin", user) 273 return user.Canonical() == "admin@local", st.NextErr() 274 } 275 276 func (st *mockBackend) ControllerModel() (cloudfacade.Model, error) { 277 st.MethodCall(st, "ControllerModel") 278 credentialTag := names.NewCloudCredentialTag("some-cloud/admin@local/some-credential") 279 return &mockModel{"some-cloud", "some-region", credentialTag}, st.NextErr() 280 } 281 282 func (st *mockBackend) ControllerTag() names.ControllerTag { 283 st.MethodCall(st, "ControllerTag") 284 return names.NewControllerTag("deadbeef-1bad-500d-9000-4b1d0d06f00d") 285 } 286 287 func (st *mockBackend) ModelTag() names.ModelTag { 288 st.MethodCall(st, "ModelTag") 289 return names.NewModelTag("deadbeef-0bad-400d-8000-4b1d0d06f00d") 290 } 291 292 func (st *mockBackend) Cloud(name string) (cloud.Cloud, error) { 293 st.MethodCall(st, "Cloud", name) 294 return st.cloud, st.NextErr() 295 } 296 297 func (st *mockBackend) Clouds() (map[names.CloudTag]cloud.Cloud, error) { 298 st.MethodCall(st, "Clouds") 299 return map[names.CloudTag]cloud.Cloud{ 300 names.NewCloudTag("my-cloud"): st.cloud, 301 }, st.NextErr() 302 } 303 304 func (st *mockBackend) CloudCredentials(user names.UserTag, cloudName string) (map[string]cloud.Credential, error) { 305 st.MethodCall(st, "CloudCredentials", user, cloudName) 306 return st.creds, st.NextErr() 307 } 308 309 func (st *mockBackend) UpdateCloudCredential(tag names.CloudCredentialTag, cred cloud.Credential) error { 310 st.MethodCall(st, "UpdateCloudCredential", tag, cred) 311 return st.NextErr() 312 } 313 314 func (st *mockBackend) RemoveCloudCredential(tag names.CloudCredentialTag) error { 315 st.MethodCall(st, "RemoveCloudCredential", tag) 316 return st.NextErr() 317 } 318 319 func (st *mockBackend) Close() error { 320 st.MethodCall(st, "Close") 321 return st.NextErr() 322 } 323 324 type mockModel struct { 325 cloud string 326 cloudRegion string 327 cloudCredentialTag names.CloudCredentialTag 328 } 329 330 func (m *mockModel) Cloud() string { 331 return m.cloud 332 } 333 334 func (m *mockModel) CloudRegion() string { 335 return m.cloudRegion 336 } 337 338 func (m *mockModel) CloudCredential() (names.CloudCredentialTag, bool) { 339 return m.cloudCredentialTag, true 340 }