github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/modeluser_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state_test 5 6 import ( 7 "fmt" 8 "sort" 9 10 "github.com/juju/errors" 11 jc "github.com/juju/testing/checkers" 12 "github.com/juju/utils" 13 gc "gopkg.in/check.v1" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/permission" 17 "github.com/juju/juju/state" 18 "github.com/juju/juju/storage" 19 "github.com/juju/juju/testing" 20 "github.com/juju/juju/testing/factory" 21 ) 22 23 type ModelUserSuite struct { 24 ConnSuite 25 } 26 27 var _ = gc.Suite(&ModelUserSuite{}) 28 29 func (s *ModelUserSuite) TestAddModelUser(c *gc.C) { 30 now := s.State.NowToTheSecond() 31 user := s.Factory.MakeUser(c, 32 &factory.UserParams{ 33 Name: "validusername", 34 NoModelUser: true, 35 }) 36 createdBy := s.Factory.MakeUser(c, &factory.UserParams{Name: "createdby"}) 37 modelUser, err := s.State.AddModelUser( 38 s.State.ModelUUID(), 39 state.UserAccessSpec{ 40 User: user.UserTag(), 41 CreatedBy: createdBy.UserTag(), 42 Access: permission.WriteAccess, 43 }) 44 c.Assert(err, jc.ErrorIsNil) 45 46 c.Assert(modelUser.UserID, gc.Equals, fmt.Sprintf("%s:validusername@local", s.modelTag.Id())) 47 c.Assert(modelUser.Object, gc.Equals, s.modelTag) 48 c.Assert(modelUser.UserName, gc.Equals, "validusername@local") 49 c.Assert(modelUser.DisplayName, gc.Equals, user.DisplayName()) 50 c.Assert(modelUser.Access, gc.Equals, permission.WriteAccess) 51 c.Assert(modelUser.CreatedBy.Id(), gc.Equals, "createdby@local") 52 c.Assert(modelUser.DateCreated.Equal(now) || modelUser.DateCreated.After(now), jc.IsTrue) 53 when, err := s.State.LastModelConnection(modelUser.UserTag) 54 c.Assert(err, jc.Satisfies, state.IsNeverConnectedError) 55 c.Assert(when.IsZero(), jc.IsTrue) 56 57 modelUser, err = s.State.UserAccess(user.UserTag(), s.State.ModelTag()) 58 c.Assert(err, jc.ErrorIsNil) 59 c.Assert(modelUser.UserID, gc.Equals, fmt.Sprintf("%s:validusername@local", s.modelTag.Id())) 60 c.Assert(modelUser.Object, gc.Equals, s.modelTag) 61 c.Assert(modelUser.UserName, gc.Equals, "validusername@local") 62 c.Assert(modelUser.DisplayName, gc.Equals, user.DisplayName()) 63 c.Assert(modelUser.Access, gc.Equals, permission.WriteAccess) 64 c.Assert(modelUser.CreatedBy.Id(), gc.Equals, "createdby@local") 65 c.Assert(modelUser.DateCreated.Equal(now) || modelUser.DateCreated.After(now), jc.IsTrue) 66 when, err = s.State.LastModelConnection(modelUser.UserTag) 67 c.Assert(err, jc.Satisfies, state.IsNeverConnectedError) 68 c.Assert(when.IsZero(), jc.IsTrue) 69 } 70 71 func (s *ModelUserSuite) TestAddReadOnlyModelUser(c *gc.C) { 72 user := s.Factory.MakeUser(c, 73 &factory.UserParams{ 74 Name: "validusername", 75 NoModelUser: true, 76 }) 77 createdBy := s.Factory.MakeUser(c, &factory.UserParams{Name: "createdby"}) 78 modelUser, err := s.State.AddModelUser( 79 s.State.ModelUUID(), 80 state.UserAccessSpec{ 81 User: user.UserTag(), 82 CreatedBy: createdBy.UserTag(), 83 Access: permission.ReadAccess, 84 }) 85 c.Assert(err, jc.ErrorIsNil) 86 87 c.Assert(modelUser.UserName, gc.Equals, "validusername@local") 88 c.Assert(modelUser.DisplayName, gc.Equals, user.DisplayName()) 89 c.Assert(modelUser.Access, gc.Equals, permission.ReadAccess) 90 91 // Make sure that it is set when we read the user out. 92 modelUser, err = s.State.UserAccess(user.UserTag(), s.State.ModelTag()) 93 c.Assert(err, jc.ErrorIsNil) 94 c.Assert(modelUser.UserName, gc.Equals, "validusername@local") 95 c.Assert(modelUser.Access, gc.Equals, permission.ReadAccess) 96 } 97 98 func (s *ModelUserSuite) TestAddReadWriteModelUser(c *gc.C) { 99 user := s.Factory.MakeUser(c, 100 &factory.UserParams{ 101 Name: "validusername", 102 NoModelUser: true, 103 }) 104 createdBy := s.Factory.MakeUser(c, &factory.UserParams{Name: "createdby"}) 105 modelUser, err := s.State.AddModelUser( 106 s.State.ModelUUID(), 107 state.UserAccessSpec{ 108 User: user.UserTag(), 109 CreatedBy: createdBy.UserTag(), 110 Access: permission.WriteAccess, 111 }) 112 c.Assert(err, jc.ErrorIsNil) 113 114 c.Assert(modelUser.UserName, gc.Equals, "validusername@local") 115 c.Assert(modelUser.DisplayName, gc.Equals, user.DisplayName()) 116 c.Assert(modelUser.Access, gc.Equals, permission.WriteAccess) 117 118 // Make sure that it is set when we read the user out. 119 modelUser, err = s.State.UserAccess(user.UserTag(), s.State.ModelTag()) 120 c.Assert(err, jc.ErrorIsNil) 121 c.Assert(modelUser.UserName, gc.Equals, "validusername@local") 122 c.Assert(modelUser.Access, gc.Equals, permission.WriteAccess) 123 } 124 125 func (s *ModelUserSuite) TestAddAdminModelUser(c *gc.C) { 126 user := s.Factory.MakeUser(c, 127 &factory.UserParams{ 128 Name: "validusername", 129 NoModelUser: true, 130 }) 131 createdBy := s.Factory.MakeUser(c, &factory.UserParams{Name: "createdby"}) 132 modelUser, err := s.State.AddModelUser( 133 s.State.ModelUUID(), 134 state.UserAccessSpec{ 135 User: user.UserTag(), 136 CreatedBy: createdBy.UserTag(), 137 Access: permission.AdminAccess, 138 }) 139 c.Assert(err, jc.ErrorIsNil) 140 141 c.Assert(modelUser.UserName, gc.Equals, "validusername@local") 142 c.Assert(modelUser.DisplayName, gc.Equals, user.DisplayName()) 143 c.Assert(modelUser.Access, gc.Equals, permission.AdminAccess) 144 145 // Make sure that it is set when we read the user out. 146 modelUser, err = s.State.UserAccess(user.UserTag(), s.State.ModelTag()) 147 c.Assert(err, jc.ErrorIsNil) 148 c.Assert(modelUser.UserName, gc.Equals, "validusername@local") 149 c.Assert(modelUser.Access, gc.Equals, permission.AdminAccess) 150 } 151 152 func (s *ModelUserSuite) TestDefaultAccessModelUser(c *gc.C) { 153 user := s.Factory.MakeUser(c, 154 &factory.UserParams{ 155 Name: "validusername", 156 NoModelUser: true, 157 }) 158 createdBy := s.Factory.MakeUser(c, &factory.UserParams{Name: "createdby"}) 159 modelUser, err := s.State.AddModelUser( 160 s.State.ModelUUID(), 161 state.UserAccessSpec{ 162 User: user.UserTag(), 163 CreatedBy: createdBy.UserTag(), 164 Access: permission.ReadAccess, 165 }) 166 c.Assert(err, jc.ErrorIsNil) 167 c.Assert(modelUser.Access, gc.Equals, permission.ReadAccess) 168 } 169 170 func (s *ModelUserSuite) TestSetAccessModelUser(c *gc.C) { 171 user := s.Factory.MakeUser(c, 172 &factory.UserParams{ 173 Name: "validusername", 174 NoModelUser: true, 175 }) 176 createdBy := s.Factory.MakeUser(c, &factory.UserParams{Name: "createdby"}) 177 modelUser, err := s.State.AddModelUser( 178 s.State.ModelUUID(), 179 state.UserAccessSpec{ 180 User: user.UserTag(), 181 CreatedBy: createdBy.UserTag(), 182 Access: permission.AdminAccess, 183 }) 184 c.Assert(err, jc.ErrorIsNil) 185 c.Assert(modelUser.Access, gc.Equals, permission.AdminAccess) 186 187 s.State.SetUserAccess(modelUser.UserTag, s.State.ModelTag(), permission.ReadAccess) 188 189 modelUser, err = s.State.UserAccess(user.UserTag(), s.State.ModelTag()) 190 c.Assert(modelUser.Access, gc.Equals, permission.ReadAccess) 191 } 192 193 func (s *ModelUserSuite) TestCaseUserNameVsId(c *gc.C) { 194 model, err := s.State.Model() 195 c.Assert(err, jc.ErrorIsNil) 196 197 user, err := s.State.AddModelUser( 198 s.State.ModelUUID(), 199 state.UserAccessSpec{ 200 User: names.NewUserTag("Bob@RandomProvider"), 201 CreatedBy: model.Owner(), 202 Access: permission.ReadAccess, 203 }) 204 c.Assert(err, gc.IsNil) 205 c.Assert(user.UserName, gc.Equals, "Bob@RandomProvider") 206 c.Assert(user.UserID, gc.Equals, state.DocID(s.State, "bob@randomprovider")) 207 } 208 209 func (s *ModelUserSuite) TestCaseSensitiveModelUserErrors(c *gc.C) { 210 model, err := s.State.Model() 211 c.Assert(err, jc.ErrorIsNil) 212 s.Factory.MakeModelUser(c, &factory.ModelUserParams{User: "Bob@ubuntuone"}) 213 214 _, err = s.State.AddModelUser( 215 s.State.ModelUUID(), 216 state.UserAccessSpec{ 217 User: names.NewUserTag("boB@ubuntuone"), 218 CreatedBy: model.Owner(), 219 Access: permission.ReadAccess, 220 }) 221 c.Assert(err, gc.ErrorMatches, `user access "boB@ubuntuone" already exists`) 222 c.Assert(errors.IsAlreadyExists(err), jc.IsTrue) 223 } 224 225 func (s *ModelUserSuite) TestCaseInsensitiveLookupInMultiEnvirons(c *gc.C) { 226 assertIsolated := func(st1, st2 *state.State, usernames ...string) { 227 f := factory.NewFactory(st1) 228 expectedUser := f.MakeModelUser(c, &factory.ModelUserParams{User: usernames[0]}) 229 230 // assert case insensitive lookup for each username 231 for _, username := range usernames { 232 userTag := names.NewUserTag(username) 233 obtainedUser, err := st1.UserAccess(userTag, st1.ModelTag()) 234 c.Assert(err, jc.ErrorIsNil) 235 c.Assert(obtainedUser, gc.DeepEquals, expectedUser) 236 237 _, err = st2.UserAccess(userTag, st2.ModelTag()) 238 c.Assert(errors.IsNotFound(err), jc.IsTrue) 239 } 240 } 241 242 otherSt := s.Factory.MakeModel(c, nil) 243 defer otherSt.Close() 244 assertIsolated(s.State, otherSt, 245 "Bob@UbuntuOne", 246 "bob@ubuntuone", 247 "BOB@UBUNTUONE", 248 ) 249 assertIsolated(otherSt, s.State, 250 "Sam@UbuntuOne", 251 "sam@ubuntuone", 252 "SAM@UBUNTUONE", 253 ) 254 } 255 256 func (s *ModelUserSuite) TestAddModelDisplayName(c *gc.C) { 257 modelUserDefault := s.Factory.MakeModelUser(c, nil) 258 c.Assert(modelUserDefault.DisplayName, gc.Matches, "display name-[0-9]*") 259 260 modelUser := s.Factory.MakeModelUser(c, &factory.ModelUserParams{DisplayName: "Override user display name"}) 261 c.Assert(modelUser.DisplayName, gc.Equals, "Override user display name") 262 } 263 264 func (s *ModelUserSuite) TestAddModelNoUserFails(c *gc.C) { 265 createdBy := s.Factory.MakeUser(c, &factory.UserParams{Name: "createdby"}) 266 _, err := s.State.AddModelUser( 267 s.State.ModelUUID(), 268 state.UserAccessSpec{ 269 User: names.NewLocalUserTag("validusername"), 270 CreatedBy: createdBy.UserTag(), 271 Access: permission.ReadAccess, 272 }) 273 c.Assert(err, gc.ErrorMatches, `user "validusername" does not exist locally: user "validusername" not found`) 274 } 275 276 func (s *ModelUserSuite) TestAddModelNoCreatedByUserFails(c *gc.C) { 277 user := s.Factory.MakeUser(c, &factory.UserParams{Name: "validusername"}) 278 _, err := s.State.AddModelUser( 279 s.State.ModelUUID(), 280 state.UserAccessSpec{ 281 User: user.UserTag(), 282 CreatedBy: names.NewLocalUserTag("createdby"), 283 Access: permission.ReadAccess, 284 }) 285 c.Assert(err, gc.ErrorMatches, `createdBy user "createdby" does not exist locally: user "createdby" not found`) 286 } 287 288 func (s *ModelUserSuite) TestRemoveModelUser(c *gc.C) { 289 user := s.Factory.MakeUser(c, &factory.UserParams{Name: "validUsername"}) 290 _, err := s.State.UserAccess(user.UserTag(), s.State.ModelTag()) 291 c.Assert(err, jc.ErrorIsNil) 292 293 err = s.State.RemoveUserAccess(user.UserTag(), s.State.ModelTag()) 294 c.Assert(err, jc.ErrorIsNil) 295 296 _, err = s.State.UserAccess(user.UserTag(), s.State.ModelTag()) 297 c.Assert(err, jc.Satisfies, errors.IsNotFound) 298 } 299 300 func (s *ModelUserSuite) TestRemoveModelUserFails(c *gc.C) { 301 user := s.Factory.MakeUser(c, &factory.UserParams{NoModelUser: true}) 302 err := s.State.RemoveUserAccess(user.UserTag(), s.State.ModelTag()) 303 c.Assert(err, jc.Satisfies, errors.IsNotFound) 304 } 305 306 func (s *ModelUserSuite) TestUpdateLastConnection(c *gc.C) { 307 now := s.State.NowToTheSecond() 308 createdBy := s.Factory.MakeUser(c, &factory.UserParams{Name: "createdby"}) 309 user := s.Factory.MakeUser(c, &factory.UserParams{Name: "validusername", Creator: createdBy.Tag()}) 310 modelUser, err := s.State.UserAccess(user.UserTag(), s.State.ModelTag()) 311 c.Assert(err, jc.ErrorIsNil) 312 err = s.State.UpdateLastModelConnection(user.UserTag()) 313 c.Assert(err, jc.ErrorIsNil) 314 when, err := s.State.LastModelConnection(modelUser.UserTag) 315 c.Assert(err, jc.ErrorIsNil) 316 // It is possible that the update is done over a second boundary, so we need 317 // to check for after now as well as equal. 318 c.Assert(when.After(now) || when.Equal(now), jc.IsTrue) 319 } 320 321 func (s *ModelUserSuite) TestUpdateLastConnectionTwoModelUsers(c *gc.C) { 322 now := s.State.NowToTheSecond() 323 324 // Create a user and add them to the inital model. 325 createdBy := s.Factory.MakeUser(c, &factory.UserParams{Name: "createdby"}) 326 user := s.Factory.MakeUser(c, &factory.UserParams{Name: "validusername", Creator: createdBy.Tag()}) 327 modelUser, err := s.State.UserAccess(user.UserTag(), s.State.ModelTag()) 328 c.Assert(err, jc.ErrorIsNil) 329 330 // Create a second model and add the same user to this. 331 st2 := s.Factory.MakeModel(c, nil) 332 defer st2.Close() 333 modelUser2, err := st2.AddModelUser( 334 st2.ModelUUID(), 335 state.UserAccessSpec{ 336 User: user.UserTag(), 337 CreatedBy: createdBy.UserTag(), 338 Access: permission.ReadAccess, 339 }) 340 c.Assert(err, jc.ErrorIsNil) 341 342 // Now we have two model users with the same username. Ensure we get 343 // separate last connections. 344 345 // Connect modelUser and get last connection. 346 err = s.State.UpdateLastModelConnection(user.UserTag()) 347 c.Assert(err, jc.ErrorIsNil) 348 when, err := s.State.LastModelConnection(modelUser.UserTag) 349 c.Assert(err, jc.ErrorIsNil) 350 c.Assert(when.After(now) || when.Equal(now), jc.IsTrue) 351 352 // Try to get last connection for modelUser2. As they have never connected, 353 // we expect to get an error. 354 _, err = st2.LastModelConnection(modelUser2.UserTag) 355 c.Assert(err, gc.ErrorMatches, `never connected: "validusername@local"`) 356 357 // Connect modelUser2 and get last connection. 358 err = s.State.UpdateLastModelConnection(modelUser2.UserTag) 359 c.Assert(err, jc.ErrorIsNil) 360 when, err = s.State.LastModelConnection(modelUser2.UserTag) 361 c.Assert(err, jc.ErrorIsNil) 362 c.Assert(when.After(now) || when.Equal(now), jc.IsTrue) 363 } 364 365 func (s *ModelUserSuite) TestModelsForUserNone(c *gc.C) { 366 tag := names.NewUserTag("non-existent@remote") 367 models, err := s.State.ModelsForUser(tag) 368 c.Assert(err, jc.ErrorIsNil) 369 c.Assert(models, gc.HasLen, 0) 370 } 371 372 func (s *ModelUserSuite) TestModelsForUserNewLocalUser(c *gc.C) { 373 user := s.Factory.MakeUser(c, &factory.UserParams{NoModelUser: true}) 374 models, err := s.State.ModelsForUser(user.UserTag()) 375 c.Assert(err, jc.ErrorIsNil) 376 c.Assert(models, gc.HasLen, 0) 377 } 378 379 func (s *ModelUserSuite) TestModelsForUser(c *gc.C) { 380 user := s.Factory.MakeUser(c, nil) 381 models, err := s.State.ModelsForUser(user.UserTag()) 382 c.Assert(err, jc.ErrorIsNil) 383 c.Assert(models, gc.HasLen, 1) 384 c.Assert(models[0].UUID(), gc.Equals, s.State.ModelUUID()) 385 st, err := s.State.ForModel(models[0].ModelTag()) 386 c.Assert(err, jc.ErrorIsNil) 387 modelUser, err := s.State.UserAccess(user.UserTag(), models[0].ModelTag()) 388 when, err := st.LastModelConnection(modelUser.UserTag) 389 c.Assert(err, jc.Satisfies, state.IsNeverConnectedError) 390 c.Assert(when.IsZero(), jc.IsTrue) 391 c.Assert(st.Close(), jc.ErrorIsNil) 392 } 393 394 func (s *ModelUserSuite) newEnvWithOwner(c *gc.C, name string, owner names.UserTag) *state.Model { 395 // Don't use the factory to call MakeModel because it may at some 396 // time in the future be modified to do additional things. Instead call 397 // the state method directly to create an model to make sure that 398 // the owner is able to access the model. 399 uuid, err := utils.NewUUID() 400 c.Assert(err, jc.ErrorIsNil) 401 cfg := testing.CustomModelConfig(c, testing.Attrs{ 402 "name": name, 403 "uuid": uuid.String(), 404 }) 405 model, st, err := s.State.NewModel(state.ModelArgs{ 406 CloudName: "dummy", CloudRegion: "dummy-region", Config: cfg, Owner: owner, 407 StorageProviderRegistry: storage.StaticProviderRegistry{}, 408 }) 409 c.Assert(err, jc.ErrorIsNil) 410 defer st.Close() 411 return model 412 } 413 414 func (s *ModelUserSuite) TestModelsForUserEnvOwner(c *gc.C) { 415 owner := names.NewUserTag("external@remote") 416 model := s.newEnvWithOwner(c, "test-model", owner) 417 418 models, err := s.State.ModelsForUser(owner) 419 c.Assert(err, jc.ErrorIsNil) 420 c.Assert(models, gc.HasLen, 1) 421 s.checkSameModel(c, models[0].Model, model) 422 } 423 424 func (s *ModelUserSuite) checkSameModel(c *gc.C, env1, env2 *state.Model) { 425 c.Check(env1.Name(), gc.Equals, env2.Name()) 426 c.Check(env1.UUID(), gc.Equals, env2.UUID()) 427 } 428 429 func (s *ModelUserSuite) newEnvWithUser(c *gc.C, name string, user names.UserTag) *state.Model { 430 envState := s.Factory.MakeModel(c, &factory.ModelParams{Name: name}) 431 defer envState.Close() 432 newEnv, err := envState.Model() 433 c.Assert(err, jc.ErrorIsNil) 434 435 _, err = envState.AddModelUser( 436 envState.ModelUUID(), 437 state.UserAccessSpec{ 438 User: user, CreatedBy: newEnv.Owner(), 439 Access: permission.ReadAccess, 440 }) 441 c.Assert(err, jc.ErrorIsNil) 442 return newEnv 443 } 444 445 func (s *ModelUserSuite) TestModelsForUserOfNewEnv(c *gc.C) { 446 userTag := names.NewUserTag("external@remote") 447 model := s.newEnvWithUser(c, "test-model", userTag) 448 449 models, err := s.State.ModelsForUser(userTag) 450 c.Assert(err, jc.ErrorIsNil) 451 c.Assert(models, gc.HasLen, 1) 452 s.checkSameModel(c, models[0].Model, model) 453 } 454 455 func (s *ModelUserSuite) TestModelsForUserMultiple(c *gc.C) { 456 userTag := names.NewUserTag("external@remote") 457 expected := []*state.Model{ 458 s.newEnvWithUser(c, "user1", userTag), 459 s.newEnvWithUser(c, "user2", userTag), 460 s.newEnvWithUser(c, "user3", userTag), 461 s.newEnvWithOwner(c, "owner1", userTag), 462 s.newEnvWithOwner(c, "owner2", userTag), 463 } 464 sort.Sort(UUIDOrder(expected)) 465 466 models, err := s.State.ModelsForUser(userTag) 467 c.Assert(err, jc.ErrorIsNil) 468 c.Assert(models, gc.HasLen, len(expected)) 469 sort.Sort(userUUIDOrder(models)) 470 for i := range expected { 471 s.checkSameModel(c, models[i].Model, expected[i]) 472 } 473 } 474 475 func (s *ModelUserSuite) TestIsControllerAdmin(c *gc.C) { 476 isAdmin, err := s.State.IsControllerAdmin(s.Owner) 477 c.Assert(err, jc.ErrorIsNil) 478 c.Assert(isAdmin, jc.IsTrue) 479 480 user := s.Factory.MakeUser(c, &factory.UserParams{NoModelUser: true}) 481 isAdmin, err = s.State.IsControllerAdmin(user.UserTag()) 482 c.Assert(err, jc.ErrorIsNil) 483 c.Assert(isAdmin, jc.IsFalse) 484 485 s.State.SetUserAccess(user.UserTag(), s.State.ControllerTag(), permission.SuperuserAccess) 486 isAdmin, err = s.State.IsControllerAdmin(user.UserTag()) 487 c.Assert(err, jc.ErrorIsNil) 488 c.Assert(isAdmin, jc.IsTrue) 489 490 readonly := s.Factory.MakeModelUser(c, &factory.ModelUserParams{Access: permission.ReadAccess}) 491 isAdmin, err = s.State.IsControllerAdmin(readonly.UserTag) 492 c.Assert(err, jc.ErrorIsNil) 493 c.Assert(isAdmin, jc.IsFalse) 494 } 495 496 func (s *ModelUserSuite) TestIsControllerAdminFromOtherState(c *gc.C) { 497 user := s.Factory.MakeUser(c, &factory.UserParams{NoModelUser: true}) 498 499 otherState := s.Factory.MakeModel(c, &factory.ModelParams{Owner: user.UserTag()}) 500 defer otherState.Close() 501 502 isAdmin, err := otherState.IsControllerAdmin(user.UserTag()) 503 c.Assert(err, jc.ErrorIsNil) 504 c.Assert(isAdmin, jc.IsFalse) 505 506 isAdmin, err = otherState.IsControllerAdmin(s.Owner) 507 c.Assert(err, jc.ErrorIsNil) 508 c.Assert(isAdmin, jc.IsTrue) 509 } 510 511 // UUIDOrder is used to sort the models into a stable order 512 type UUIDOrder []*state.Model 513 514 func (a UUIDOrder) Len() int { return len(a) } 515 func (a UUIDOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 516 func (a UUIDOrder) Less(i, j int) bool { return a[i].UUID() < a[j].UUID() } 517 518 // userUUIDOrder is used to sort the UserModels into a stable order 519 type userUUIDOrder []*state.UserModel 520 521 func (a userUUIDOrder) Len() int { return len(a) } 522 func (a userUUIDOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 523 func (a userUUIDOrder) Less(i, j int) bool { return a[i].UUID() < a[j].UUID() }