github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/modelsummaries_test.go (about) 1 // Copyright 2017 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 "time" 10 11 "github.com/juju/mgo/v3/bson" 12 "github.com/juju/names/v5" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils/v3" 15 gc "gopkg.in/check.v1" 16 17 "github.com/juju/juju/cloud" 18 "github.com/juju/juju/core/arch" 19 "github.com/juju/juju/core/instance" 20 "github.com/juju/juju/core/permission" 21 "github.com/juju/juju/core/status" 22 "github.com/juju/juju/state" 23 "github.com/juju/juju/storage" 24 "github.com/juju/juju/testing" 25 "github.com/juju/juju/testing/factory" 26 ) 27 28 type ModelSummariesSuite struct { 29 ConnSuite 30 } 31 32 var _ = gc.Suite(&ModelSummariesSuite{}) 33 34 func (s *ModelSummariesSuite) Setup4Models(c *gc.C) map[string]string { 35 modelUUIDs := make(map[string]string) 36 user1 := s.Factory.MakeUser(c, &factory.UserParams{ 37 Name: "user1write", 38 NoModelUser: true, 39 }) 40 st1 := s.Factory.MakeModel(c, &factory.ModelParams{ 41 Name: "user1model", 42 Owner: user1.Tag(), 43 }) 44 modelUUIDs["user1model"] = st1.ModelUUID() 45 st1.Close() 46 user2 := s.Factory.MakeUser(c, &factory.UserParams{ 47 Name: "user2read", 48 NoModelUser: true, 49 }) 50 st2 := s.Factory.MakeModel(c, &factory.ModelParams{ 51 Name: "user2model", 52 Owner: user2.Tag(), 53 Type: state.ModelTypeCAAS, 54 }) 55 modelUUIDs["user2model"] = st2.ModelUUID() 56 f2 := factory.NewFactory(st2, s.StatePool) 57 f2.MakeUnit(c, nil) 58 st2.Close() 59 user3 := s.Factory.MakeUser(c, &factory.UserParams{ 60 Name: "user3admin", 61 NoModelUser: true, 62 }) 63 st3 := s.Factory.MakeModel(c, &factory.ModelParams{ 64 Name: "user3model", 65 Owner: user3.Tag(), 66 }) 67 modelUUIDs["user3model"] = st3.ModelUUID() 68 st3.Close() 69 owner := s.Model.Owner() 70 err := s.State.AddCloud(cloud.Cloud{ 71 Name: "stratus", 72 Type: "low", 73 AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType, cloud.UserPassAuthType}, 74 Regions: []cloud.Region{ 75 { 76 Name: "dummy-region", 77 Endpoint: "dummy-endpoint", 78 IdentityEndpoint: "dummy-identity-endpoint", 79 StorageEndpoint: "dummy-storage-endpoint", 80 }, 81 }, 82 }, s.Owner.Name()) 83 c.Assert(err, jc.ErrorIsNil) 84 85 cred := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ 86 "foo": "foo val", 87 "bar": "bar val", 88 }) 89 tag := names.NewCloudCredentialTag(fmt.Sprintf("stratus/%v/foobar", owner.Name())) 90 err = s.State.UpdateCloudCredential(tag, cred) 91 c.Assert(err, jc.ErrorIsNil) 92 93 sharedSt := s.Factory.MakeModel(c, &factory.ModelParams{ 94 Name: "shared", 95 // Owned by test-admin 96 Owner: owner, 97 CloudName: "stratus", 98 CloudCredential: tag, 99 }) 100 modelUUIDs["shared"] = sharedSt.ModelUUID() 101 defer sharedSt.Close() 102 sharedModel, err := sharedSt.Model() 103 c.Assert(err, jc.ErrorIsNil) 104 _, err = sharedModel.AddUser(state.UserAccessSpec{ 105 User: user1.UserTag(), 106 CreatedBy: owner, 107 Access: "write", 108 }) 109 c.Assert(err, jc.ErrorIsNil) 110 // User 2 has read access to the shared model 111 _, err = sharedModel.AddUser(state.UserAccessSpec{ 112 User: user2.UserTag(), 113 CreatedBy: owner, 114 Access: "read", 115 }) 116 c.Assert(err, jc.ErrorIsNil) 117 _, err = sharedModel.AddUser(state.UserAccessSpec{ 118 User: user3.UserTag(), 119 CreatedBy: owner, 120 Access: "admin", 121 }) 122 c.Assert(err, jc.ErrorIsNil) 123 return modelUUIDs 124 } 125 126 func (s *ModelSummariesSuite) modelNamesForUser(c *gc.C, user string, isSuperuser bool) []string { 127 tag := names.NewUserTag(user) 128 modelQuery, closer, err := s.State.ModelQueryForUser(tag, isSuperuser) 129 defer closer() 130 c.Assert(err, jc.ErrorIsNil) 131 var docs []struct { 132 Name string `bson:"name"` 133 } 134 modelQuery.Select(bson.M{"name": 1}) 135 err = modelQuery.All(&docs) 136 c.Assert(err, jc.ErrorIsNil) 137 names := make([]string, 0) 138 for _, doc := range docs { 139 names = append(names, doc.Name) 140 } 141 sort.Strings(names) 142 return names 143 } 144 145 func (s *ModelSummariesSuite) TestModelsForUserAdmin(c *gc.C) { 146 s.Setup4Models(c) 147 names := s.modelNamesForUser(c, s.Model.Owner().Name(), true) 148 // Admin always gets to see all models 149 c.Check(names, gc.DeepEquals, []string{"shared", "testmodel", "user1model", "user2model", "user3model"}) 150 } 151 152 func (s *ModelSummariesSuite) TestModelsForSuperuserWithoutAll(c *gc.C) { 153 s.Setup4Models(c) 154 summaries, err := s.State.ModelSummariesForUser(s.Model.Owner(), false) 155 c.Assert(err, jc.ErrorIsNil) 156 names := make([]string, len(summaries)) 157 for i, summary := range summaries { 158 names[i] = summary.Name 159 } 160 sort.Strings(names) 161 c.Check(names, gc.DeepEquals, []string{"shared", "testmodel"}) 162 } 163 164 func (s *ModelSummariesSuite) TestModelsForSuperuserWithAll(c *gc.C) { 165 s.Setup4Models(c) 166 summaries, err := s.State.ModelSummariesForUser(s.Model.Owner(), true) 167 c.Assert(err, jc.ErrorIsNil) 168 names := make([]string, len(summaries)) 169 access := make(map[string]string) 170 isController := make(map[string]bool) 171 for i, summary := range summaries { 172 names[i] = summary.Name 173 access[summary.Name] = string(summary.Access) 174 isController[summary.Name] = summary.IsController 175 } 176 sort.Strings(names) 177 c.Check(names, gc.DeepEquals, []string{"shared", "testmodel", "user1model", "user2model", "user3model"}) 178 c.Check(access, gc.DeepEquals, map[string]string{ 179 "shared": "admin", 180 "testmodel": "admin", 181 "user1model": "", 182 "user2model": "", 183 "user3model": "", 184 }) 185 c.Check(isController, gc.DeepEquals, map[string]bool{ 186 "shared": false, 187 "testmodel": true, 188 "user1model": false, 189 "user2model": false, 190 "user3model": false, 191 }) 192 } 193 194 func (s *ModelSummariesSuite) TestModelsForUser1(c *gc.C) { 195 // User1 is only added to the model they own and the shared model as write 196 s.Setup4Models(c) 197 names := s.modelNamesForUser(c, "user1write", false) 198 c.Check(names, gc.DeepEquals, []string{"shared", "user1model"}) 199 } 200 201 func (s *ModelSummariesSuite) TestModelsForUser2(c *gc.C) { 202 // User2 is only added to the model they own and the shared model as read 203 s.Setup4Models(c) 204 names := s.modelNamesForUser(c, "user2read", false) 205 c.Check(names, gc.DeepEquals, []string{"shared", "user2model"}) 206 } 207 208 func (s *ModelSummariesSuite) TestModelsForUser3(c *gc.C) { 209 // User2 is only added to the model they own and the shared model as admin 210 s.Setup4Models(c) 211 names := s.modelNamesForUser(c, "user3admin", false) 212 c.Check(names, gc.DeepEquals, []string{"shared", "user3model"}) 213 } 214 215 // NOTE: (jam 2017-12-11) We probably only ever stripped Importing models because there details might not be complete. 216 // We probably actually want to include importing models, and just handle when they don't have complete data. 217 func (s *ModelSummariesSuite) TestModelsForIgnoresImportingModels(c *gc.C) { 218 s.Setup4Models(c) 219 cfg := testing.CustomModelConfig(c, testing.Attrs{ 220 "name": "importing", 221 "uuid": utils.MustNewUUID().String(), 222 "type": state.ModelTypeIAAS, 223 }) 224 _, stImporting, err := s.Controller.NewModel(state.ModelArgs{ 225 Type: state.ModelTypeIAAS, 226 CloudName: "dummy", 227 CloudRegion: "dummy-region", 228 Config: cfg, 229 Owner: names.NewUserTag("user1write"), 230 MigrationMode: state.MigrationModeImporting, 231 EnvironVersion: s.Model.EnvironVersion(), 232 StorageProviderRegistry: storage.StaticProviderRegistry{}, 233 }) 234 defer stImporting.Close() 235 c.Assert(err, jc.ErrorIsNil) 236 237 // Since the new model is importing, when we do the list we shouldn't see it. 238 names := s.modelNamesForUser(c, "user3admin", false) 239 c.Check(names, gc.DeepEquals, []string{"shared", "user3model"}) 240 // Superuser doesn't see importing models, either 241 names = s.modelNamesForUser(c, s.Model.Owner().Name(), true) 242 c.Check(names, gc.DeepEquals, []string{"shared", "testmodel", "user1model", "user2model", "user3model"}) 243 } 244 245 func (s *ModelSummariesSuite) TestContainsConfigInformation(c *gc.C) { 246 s.Setup4Models(c) 247 summaries, err := s.State.ModelSummariesForUser(names.NewUserTag("user1write"), false) 248 c.Assert(err, jc.ErrorIsNil) 249 c.Check(summaries, gc.HasLen, 2) 250 // We don't guarantee the order of the summaries, but the data for each model should match the same 251 // information you would get if you instantiate the model directly 252 summaryA := summaries[0] 253 model, ph, err := s.StatePool.GetModel(summaryA.UUID) 254 defer ph.Release() 255 c.Assert(err, jc.ErrorIsNil) 256 conf, err := model.Config() 257 c.Assert(err, jc.ErrorIsNil) 258 c.Check(summaryA.ProviderType, gc.Equals, conf.Type()) 259 version, ok := conf.AgentVersion() 260 c.Assert(ok, jc.IsTrue) 261 c.Check(summaryA.AgentVersion, gc.NotNil) 262 c.Check(*summaryA.AgentVersion, gc.Equals, version) 263 } 264 265 func (s *ModelSummariesSuite) TestContainsProviderType(c *gc.C) { 266 s.Setup4Models(c) 267 summaries, err := s.State.ModelSummariesForUser(names.NewUserTag("user1write"), false) 268 c.Assert(err, jc.ErrorIsNil) 269 c.Check(summaries, gc.HasLen, 2) 270 // We don't guarantee the order of the summaries, but both should have the same ProviderType 271 summaryA := summaries[0] 272 model, ph, err := s.StatePool.GetModel(summaryA.UUID) 273 defer ph.Release() 274 c.Assert(err, jc.ErrorIsNil) 275 conf, err := model.Config() 276 c.Assert(err, jc.ErrorIsNil) 277 c.Check(summaryA.ProviderType, gc.Equals, conf.Type()) 278 } 279 280 func (s *ModelSummariesSuite) TestContainsModelStatus(c *gc.C) { 281 modelNameToUUID := s.Setup4Models(c) 282 expectedStatus := map[string]status.StatusInfo{ 283 "shared": { 284 Status: status.Available, 285 Message: "human message", 286 }, 287 "user1model": { 288 Status: status.Busy, 289 Message: "human message", 290 }, 291 } 292 shared, ph, err := s.StatePool.GetModel(modelNameToUUID["shared"]) 293 defer ph.Release() 294 c.Assert(err, jc.ErrorIsNil) 295 err = shared.SetStatus(expectedStatus["shared"]) 296 c.Assert(err, jc.ErrorIsNil) 297 user1, ph, err := s.StatePool.GetModel(modelNameToUUID["user1model"]) 298 defer ph.Release() 299 c.Assert(err, jc.ErrorIsNil) 300 err = user1.SetStatus(expectedStatus["user1model"]) 301 c.Assert(err, jc.ErrorIsNil) 302 summaries, err := s.State.ModelSummariesForUser(names.NewUserTag("user1write"), false) 303 c.Assert(err, jc.ErrorIsNil) 304 c.Check(summaries, gc.HasLen, 2) 305 statuses := make(map[string]status.StatusInfo) 306 for _, summary := range summaries { 307 // We nil the time, because we don't want to compare it, we nil the Data map to avoid comparing an 308 // empty map to a nil map 309 st := summary.Status 310 st.Since = nil 311 st.Data = nil 312 statuses[summary.Name] = st 313 } 314 c.Check(statuses, jc.DeepEquals, expectedStatus) 315 } 316 317 func (s *ModelSummariesSuite) TestContainsModelStatusSuspended(c *gc.C) { 318 modelNameToUUID := s.Setup4Models(c) 319 expectedStatus := map[string]status.StatusInfo{ 320 "shared": { 321 Status: status.Suspended, 322 Message: "suspended since cloud credential is not valid", 323 Data: map[string]interface{}{"reason": "test"}, 324 }, 325 "user1model": { 326 Status: status.Busy, 327 Message: "human message", 328 Data: map[string]interface{}{}, 329 }, 330 } 331 shared, err := s.StatePool.Get(modelNameToUUID["shared"]) 332 defer shared.Release() 333 c.Assert(err, jc.ErrorIsNil) 334 c.Assert(shared.InvalidateModelCredential("test"), jc.ErrorIsNil) 335 336 user1, ph, err := s.StatePool.GetModel(modelNameToUUID["user1model"]) 337 defer ph.Release() 338 c.Assert(err, jc.ErrorIsNil) 339 err = user1.SetStatus(expectedStatus["user1model"]) 340 c.Assert(err, jc.ErrorIsNil) 341 summaries, err := s.State.ModelSummariesForUser(names.NewUserTag("user1write"), false) 342 c.Assert(err, jc.ErrorIsNil) 343 c.Check(summaries, gc.HasLen, 2) 344 statuses := make(map[string]status.StatusInfo) 345 for _, summary := range summaries { 346 // We nil the time, because we don't want to compare it, we nil the Data map to avoid comparing an 347 // empty map to a nil map 348 st := summary.Status 349 st.Since = nil 350 statuses[summary.Name] = st 351 } 352 c.Check(statuses, jc.DeepEquals, expectedStatus) 353 } 354 355 func (s *ModelSummariesSuite) TestContainsAccessInformation(c *gc.C) { 356 modelNameToUUID := s.Setup4Models(c) 357 shared, ph, err := s.StatePool.GetModel(modelNameToUUID["shared"]) 358 defer ph.Release() 359 c.Assert(err, jc.ErrorIsNil) 360 err = shared.UpdateLastModelConnection(names.NewUserTag("auser")) 361 s.Clock.Advance(time.Hour) 362 c.Assert(err, jc.ErrorIsNil) 363 timeShared := s.Clock.Now().Round(time.Second).UTC() 364 err = shared.UpdateLastModelConnection(names.NewUserTag("user1write")) 365 c.Assert(err, jc.ErrorIsNil) 366 s.Clock.Advance(time.Hour) // give a different time for user2 accessing the shared model 367 err = shared.UpdateLastModelConnection(names.NewUserTag("user2read")) 368 c.Assert(err, jc.ErrorIsNil) 369 user1, ph, err := s.StatePool.GetModel(modelNameToUUID["user1model"]) 370 defer ph.Release() 371 c.Assert(err, jc.ErrorIsNil) 372 s.Clock.Advance(time.Hour) 373 timeUser1 := s.Clock.Now().Round(time.Second).UTC() 374 err = user1.UpdateLastModelConnection(names.NewUserTag("user1write")) 375 c.Assert(err, jc.ErrorIsNil) 376 377 summaries, err := s.State.ModelSummariesForUser(names.NewUserTag("user1write"), false) 378 c.Assert(err, jc.ErrorIsNil) 379 c.Check(summaries, gc.HasLen, 2) 380 times := make(map[string]time.Time) 381 access := make(map[string]permission.Access) 382 for _, summary := range summaries { 383 c.Assert(summary.UserLastConnection, gc.NotNil, gc.Commentf("nil time for %v", summary.Name)) 384 times[summary.Name] = summary.UserLastConnection.UTC() 385 access[summary.Name] = summary.Access 386 } 387 c.Check(times, gc.DeepEquals, map[string]time.Time{ 388 "shared": timeShared, 389 "user1model": timeUser1, 390 }) 391 c.Check(access, gc.DeepEquals, map[string]permission.Access{ 392 "shared": permission.WriteAccess, 393 "user1model": permission.AdminAccess, 394 }) 395 } 396 397 func (s *ModelSummariesSuite) TestContainsMachineInformation(c *gc.C) { 398 modelNameToUUID := s.Setup4Models(c) 399 shared, err := s.StatePool.Get(modelNameToUUID["shared"]) 400 defer shared.Release() 401 c.Assert(err, jc.ErrorIsNil) 402 onecore := uint64(1) 403 twocores := uint64(2) 404 threecores := uint64(3) 405 m0, err := shared.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 406 c.Assert(err, jc.ErrorIsNil) 407 c.Assert(m0.Life(), gc.Equals, state.Alive) 408 err = m0.SetInstanceInfo("i-12345", "", "nonce", &instance.HardwareCharacteristics{ 409 CpuCores: &onecore, 410 }, nil, nil, nil, nil, nil) 411 c.Assert(err, jc.ErrorIsNil) 412 m1, err := shared.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 413 c.Assert(err, jc.ErrorIsNil) 414 err = m1.SetInstanceInfo("i-45678", "", "nonce", &instance.HardwareCharacteristics{ 415 CpuCores: &twocores, 416 }, nil, nil, nil, nil, nil) 417 c.Assert(err, jc.ErrorIsNil) 418 m2, err := shared.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 419 c.Assert(err, jc.ErrorIsNil) 420 err = m2.SetInstanceInfo("i-78901", "", "nonce", &instance.HardwareCharacteristics{ 421 CpuCores: &threecores, 422 }, nil, nil, nil, nil, nil) 423 c.Assert(err, jc.ErrorIsNil) 424 // No instance 425 _, err = shared.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 426 c.Assert(err, jc.ErrorIsNil) 427 // Dying instance, should not count to Cores or Machine count 428 mDying, err := shared.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 429 c.Assert(err, jc.ErrorIsNil) 430 err = mDying.SetInstanceInfo("i-78901", "", "nonce", &instance.HardwareCharacteristics{ 431 CpuCores: &threecores, 432 }, nil, nil, nil, nil, nil) 433 c.Assert(err, jc.ErrorIsNil) 434 err = mDying.Destroy() 435 c.Assert(err, jc.ErrorIsNil) 436 // Instance data, but no core count 437 m4, err := shared.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 438 c.Assert(err, jc.ErrorIsNil) 439 arch := arch.DefaultArchitecture 440 err = m4.SetInstanceInfo("i-78901", "", "nonce", &instance.HardwareCharacteristics{ 441 Arch: &arch, 442 }, nil, nil, nil, nil, nil) 443 c.Assert(err, jc.ErrorIsNil) 444 445 summaries, err := s.State.ModelSummariesForUser(names.NewUserTag("user1write"), false) 446 c.Assert(err, jc.ErrorIsNil) 447 c.Check(summaries, gc.HasLen, 2) 448 summaryMap := make(map[string]*state.ModelSummary) 449 for i := range summaries { 450 summaryMap[summaries[i].Name] = &summaries[i] 451 } 452 sharedSummary := summaryMap["shared"] 453 c.Assert(sharedSummary, gc.NotNil) 454 c.Check(sharedSummary.MachineCount, gc.Equals, int64(5)) 455 c.Check(sharedSummary.CoreCount, gc.Equals, int64(1+2+3)) 456 userSummary := summaryMap["user1model"] 457 c.Assert(userSummary, gc.NotNil) 458 c.Check(userSummary.MachineCount, gc.Equals, int64(0)) 459 c.Check(userSummary.CoreCount, gc.Equals, int64(0)) 460 } 461 462 func (s *ModelSummariesSuite) TestContainsMigrationInformation(c *gc.C) { 463 //modelNameToUUID := s.Setup4Models(c) 464 // TODO: Figure out how to create a multiple-attempt migration information, and assert that we expose the right info 465 } 466 467 func (s *ModelSummariesSuite) namedSummariesForUser(c *gc.C, user string) map[string]*state.ModelSummary { 468 summaries, err := s.State.ModelSummariesForUser(names.NewUserTag(user), false) 469 c.Assert(err, jc.ErrorIsNil) 470 summaryMap := make(map[string]*state.ModelSummary, len(summaries)) 471 for i := range summaries { 472 summaryMap[summaries[i].Name] = &summaries[i] 473 } 474 return summaryMap 475 } 476 477 func (s *ModelSummariesSuite) TestModelsWithNoSettings(c *gc.C) { 478 modelNameToUUID := s.Setup4Models(c) 479 m2uuid := modelNameToUUID["user2model"] 480 // Mark the model as dying, and move to start tearing it down 481 model, ph, err := s.StatePool.GetModel(m2uuid) 482 c.Assert(err, jc.ErrorIsNil) 483 defer ph.Release() 484 err = model.SetStatus(status.StatusInfo{ 485 Status: status.Available, 486 Message: "running", 487 }) 488 c.Assert(err, jc.ErrorIsNil) 489 490 summaryMap := s.namedSummariesForUser(c, "user2read") 491 // Even though user2model is dying/dead, it should still be in the output. 492 c.Check(summaryMap, gc.HasLen, 2) 493 userSummary := summaryMap["user2model"] 494 c.Assert(userSummary, gc.NotNil) 495 c.Check(userSummary.Status.Message, gc.Equals, "running") 496 497 err = model.Destroy(state.DestroyModelParams{}) 498 c.Assert(err, jc.ErrorIsNil) 499 err = model.SetStatus(status.StatusInfo{ 500 Status: status.Destroying, 501 Message: "stopping", 502 }) 503 c.Assert(err, jc.ErrorIsNil) 504 505 summaryMap = s.namedSummariesForUser(c, "user2read") 506 // Even though user2model is dying/dead, it should still be in the output. 507 c.Check(summaryMap, gc.HasLen, 2) 508 userSummary = summaryMap["user2model"] 509 c.Assert(userSummary, gc.NotNil) 510 c.Check(userSummary.Status.Message, gc.Equals, "stopping") 511 512 // Now we start tearing down some of the collections for this model, and see that it still shows up. 513 settings := s.Session.DB("juju").C("settings") 514 // The settings document for this model 515 err = settings.Remove(bson.M{"_id": m2uuid + ":e"}) 516 c.Assert(err, jc.ErrorIsNil) 517 summaryMap = s.namedSummariesForUser(c, "user2read") 518 c.Assert(err, jc.ErrorIsNil) 519 // Even though user2model is dying/dead, it should still be in the output. 520 c.Check(summaryMap, gc.HasLen, 2) 521 userSummary = summaryMap["user2model"] 522 c.Assert(userSummary, gc.NotNil) 523 c.Check(userSummary.Status.Message, gc.Equals, "stopping") 524 } 525 526 func (s *ModelSummariesSuite) TestCAASModel(c *gc.C) { 527 s.Setup4Models(c) 528 529 summaryMap := s.namedSummariesForUser(c, "user2read") 530 c.Check(summaryMap, gc.HasLen, 2) 531 userSummary := summaryMap["user2model"] 532 c.Assert(userSummary, gc.NotNil) 533 c.Assert(userSummary.MachineCount, gc.Equals, int64(0)) 534 c.Assert(userSummary.CoreCount, gc.Equals, int64(0)) 535 c.Assert(userSummary.UnitCount, gc.Equals, int64(1)) 536 }