golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/makemac/main_test.go (about) 1 // Copyright 2024 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "context" 9 "fmt" 10 "testing" 11 "time" 12 13 "github.com/google/go-cmp/cmp" 14 spb "go.chromium.org/luci/swarming/proto/api_v2" 15 "golang.org/x/build/internal/macservice" 16 "golang.org/x/build/internal/secret" 17 ) 18 19 func init() { 20 secret.InitFlagSupport(context.Background()) 21 } 22 23 // recordMacServiceClient is a macserviceClient that records mutating requests. 24 type recordMacServiceClient struct { 25 lease []macservice.LeaseRequest 26 renew []macservice.RenewRequest 27 vacate []macservice.VacateRequest 28 } 29 30 func (r *recordMacServiceClient) Lease(req macservice.LeaseRequest) (macservice.LeaseResponse, error) { 31 r.lease = append(r.lease, req) 32 return macservice.LeaseResponse{}, nil // Perhaps fake LeaseResponse.PendingLease.LeaseID? 33 } 34 35 func (r *recordMacServiceClient) Renew(req macservice.RenewRequest) (macservice.RenewResponse, error) { 36 r.renew = append(r.renew, req) 37 return macservice.RenewResponse{}, nil // Perhaps fake RenewResponse.Expires? 38 } 39 40 func (r *recordMacServiceClient) Vacate(req macservice.VacateRequest) error { 41 r.vacate = append(r.vacate, req) 42 return nil 43 } 44 45 func (r *recordMacServiceClient) Find(req macservice.FindRequest) (macservice.FindResponse, error) { 46 return macservice.FindResponse{}, fmt.Errorf("unimplemented") 47 } 48 49 func TestHandleMissingBots(t *testing.T) { 50 const project = managedProjectPrefix + "/swarming.example.com" 51 52 // Test leases: 53 // * "healthy" connected to LUCI, and is healthy. 54 // * "dead" connected to LUCI, but later died. 55 // * "newBooting" never connected to LUCI, but was just created 5min ago. 56 // * "neverBooted" never connected to LUCI, and was created 5hr ago. 57 // * "neverBootedUnmanaged" never connected to LUCI, and was created 5hr ago, but is not managed by makemac. 58 // 59 // handleMissingBots should vacate neverBooted and none of the others. 60 bots := map[string]*spb.BotInfo{ 61 "healthy": {BotId: "healthy"}, 62 "dead": {BotId: "dead", IsDead: true}, 63 } 64 leases := map[string]macservice.Instance{ 65 "healthy": { 66 Lease: macservice.Lease{ 67 LeaseID: "healthy", 68 VMResourceNamespace: macservice.Namespace{ProjectName: project}, 69 Expires: time.Now().Add(createExpirationDuration - 2*time.Hour), 70 }, 71 }, 72 "newBooting": { 73 Lease: macservice.Lease{ 74 LeaseID: "newBooting", 75 VMResourceNamespace: macservice.Namespace{ProjectName: project}, 76 Expires: time.Now().Add(createExpirationDuration - 5*time.Minute), 77 }, 78 }, 79 "neverBooted": { 80 Lease: macservice.Lease{ 81 LeaseID: "neverBooted", 82 VMResourceNamespace: macservice.Namespace{ProjectName: project}, 83 Expires: time.Now().Add(createExpirationDuration - 2*time.Hour), 84 }, 85 }, 86 "neverBootedUnmanaged": { 87 Lease: macservice.Lease{ 88 LeaseID: "neverBootedUnmanaged", 89 VMResourceNamespace: macservice.Namespace{ProjectName: "other"}, 90 Expires: time.Now().Add(createExpirationDuration - 2*time.Hour), 91 }, 92 }, 93 } 94 95 var mc recordMacServiceClient 96 handleMissingBots(&mc, bots, leases) 97 98 got := mc.vacate 99 want := []macservice.VacateRequest{{LeaseID: "neverBooted"}} 100 if diff := cmp.Diff(want, got); diff != "" { 101 t.Errorf("Vacated leases mismatch (-want +got):\n%s", diff) 102 } 103 104 if _, ok := leases["neverBooted"]; ok { 105 t.Errorf("neverBooted present in leases, want deleted") 106 } 107 } 108 109 func TestHandleDeadBots(t *testing.T) { 110 const project = managedProjectPrefix + "/swarming.example.com" 111 112 // Test leases: 113 // * "healthy" connected to LUCI, and is healthy. 114 // * "dead" connected to LUCI, but later died, and the lease is gone from MacService. 115 // * "deadLeasePresent" connected to LUCI, but later died, and the lease is still present on MacService. 116 // * "deadLeasePresentUnmanaged" connected to LUCI, but later died, and the lease is still present on MacService, but is not managed by makemac. 117 // * "neverBooted" never connected to LUCI, and was created 5hr ago. 118 // 119 // handleDeadBots should vacate deadLeasePresent and none of the others. 120 bots := map[string]*spb.BotInfo{ 121 "healthy": {BotId: "healthy"}, 122 "dead": {BotId: "dead", IsDead: true}, 123 "deadLeasePresent": {BotId: "deadLeasePresent", IsDead: true}, 124 } 125 leases := map[string]macservice.Instance{ 126 "healthy": { 127 Lease: macservice.Lease{ 128 LeaseID: "healthy", 129 VMResourceNamespace: macservice.Namespace{ProjectName: project}, 130 Expires: time.Now().Add(createExpirationDuration - 2*time.Hour), 131 }, 132 }, 133 "deadLeasePresent": { 134 Lease: macservice.Lease{ 135 LeaseID: "deadLeasePresent", 136 VMResourceNamespace: macservice.Namespace{ProjectName: project}, 137 // Lease created 5 minutes ago. Doesn't matter; 138 // new lease checked don't apply here. See 139 // comment in handleDeadBots. 140 Expires: time.Now().Add(createExpirationDuration - 5*time.Minute), 141 }, 142 }, 143 "deadLeasePresentUnmanaged": { 144 Lease: macservice.Lease{ 145 LeaseID: "deadLeasePresentUnmanaged", 146 VMResourceNamespace: macservice.Namespace{ProjectName: "other"}, 147 Expires: time.Now().Add(createExpirationDuration - 5*time.Minute), 148 }, 149 }, 150 "neverBooted": { 151 Lease: macservice.Lease{ 152 LeaseID: "neverBooted", 153 VMResourceNamespace: macservice.Namespace{ProjectName: project}, 154 Expires: time.Now().Add(createExpirationDuration - 2*time.Hour), 155 }, 156 }, 157 } 158 159 var mc recordMacServiceClient 160 handleDeadBots(&mc, bots, leases) 161 162 got := mc.vacate 163 want := []macservice.VacateRequest{{LeaseID: "deadLeasePresent"}} 164 if diff := cmp.Diff(want, got); diff != "" { 165 t.Errorf("Vacated leases mismatch (-want +got):\n%s", diff) 166 } 167 168 if _, ok := leases["deadLeasePresent"]; ok { 169 t.Errorf("deadLeasePresent present in leases, want deleted") 170 } 171 } 172 173 func TestRenewLeases(t *testing.T) { 174 const project = managedProjectPrefix + "/swarming.example.com" 175 176 // Test leases: 177 // * "new" was created <1hr ago. 178 // * "standard" was created >1hr ago. 179 // * "unmanaged" was created >1hr ago, but is not managed by makemac. 180 // 181 // renewLeases should renew "standard" and none of the others. 182 leases := map[string]macservice.Instance{ 183 "new": { 184 Lease: macservice.Lease{ 185 LeaseID: "new", 186 VMResourceNamespace: macservice.Namespace{ProjectName: project}, 187 Expires: time.Now().Add(createExpirationDuration - 5*time.Minute), 188 }, 189 }, 190 "standard": { 191 Lease: macservice.Lease{ 192 LeaseID: "standard", 193 VMResourceNamespace: macservice.Namespace{ProjectName: project}, 194 Expires: time.Now().Add(renewExpirationDuration - 5*time.Minute), 195 }, 196 }, 197 "unmanaged": { 198 Lease: macservice.Lease{ 199 LeaseID: "unmanaged", 200 VMResourceNamespace: macservice.Namespace{ProjectName: "other"}, 201 Expires: time.Now().Add(renewExpirationDuration - 5*time.Minute), 202 }, 203 }, 204 } 205 206 var mc recordMacServiceClient 207 renewLeases(&mc, leases) 208 209 got := mc.renew 210 want := []macservice.RenewRequest{{LeaseID: "standard", Duration: renewExpirationDurationString}} 211 if diff := cmp.Diff(want, got); diff != "" { 212 t.Errorf("Renewed leases mismatch (-want +got):\n%s", diff) 213 } 214 } 215 216 func TestHandleObsoleteLeases(t *testing.T) { 217 swarming1 := &swarmingConfig{ 218 Host: "swarming1.example.com", 219 Pool: "example.pool", 220 } 221 project1 := managedProjectPrefix + "/" + swarming1.Host 222 swarming2 := &swarmingConfig{ 223 Host: "swarming2.example.com", 224 Pool: "example.pool", 225 } 226 project2 := managedProjectPrefix + "/" + swarming2.Host 227 228 // Test leases: 229 // * "active" uses image "active-image" 230 // * "obsolete" uses image "obsolete-image" 231 // * "obsolete-on-swarming2" uses image "obsolete-image" on "swarming2" (as configured) 232 // * "unmanaged" uses image "obsolete-image", but is not managed by makemac. 233 // 234 // handleObsoleteLeases should vacate "obsolete" and none of the others. 235 config := map[*swarmingConfig][]imageConfig{ 236 swarming1: { 237 { 238 Hostname: "active", 239 Cert: "dummy-cert", 240 Key: "dummy-key", 241 Image: "active-image", 242 MinCount: 1, 243 }, 244 }, 245 swarming2: { 246 { 247 Hostname: "obsolete-on-swarming2", 248 Cert: "dummy-cert", 249 Key: "dummy-key", 250 Image: "obsolete-image", 251 MinCount: 1, 252 }, 253 }, 254 } 255 leases := map[string]macservice.Instance{ 256 "active": { 257 Lease: macservice.Lease{ 258 LeaseID: "active", 259 VMResourceNamespace: macservice.Namespace{ProjectName: project1}, 260 }, 261 InstanceSpecification: macservice.InstanceSpecification{ 262 DiskSelection: macservice.DiskSelection{ 263 ImageHashes: macservice.ImageHashes{ 264 BootSHA256: "active-image", 265 }, 266 }, 267 }, 268 }, 269 "obsolete": { 270 Lease: macservice.Lease{ 271 LeaseID: "obsolete", 272 VMResourceNamespace: macservice.Namespace{ProjectName: project1}, 273 }, 274 InstanceSpecification: macservice.InstanceSpecification{ 275 DiskSelection: macservice.DiskSelection{ 276 ImageHashes: macservice.ImageHashes{ 277 BootSHA256: "obsolete-image", 278 }, 279 }, 280 }, 281 }, 282 "obsolete-on-swarming2": { 283 Lease: macservice.Lease{ 284 LeaseID: "obsolete-on-swarming2", 285 VMResourceNamespace: macservice.Namespace{ProjectName: project2}, 286 }, 287 InstanceSpecification: macservice.InstanceSpecification{ 288 DiskSelection: macservice.DiskSelection{ 289 ImageHashes: macservice.ImageHashes{ 290 BootSHA256: "obsolete-image", 291 }, 292 }, 293 }, 294 }, 295 "unmanaged": { 296 Lease: macservice.Lease{ 297 LeaseID: "obsolete", 298 VMResourceNamespace: macservice.Namespace{ProjectName: "other"}, 299 }, 300 InstanceSpecification: macservice.InstanceSpecification{ 301 DiskSelection: macservice.DiskSelection{ 302 ImageHashes: macservice.ImageHashes{ 303 BootSHA256: "obsolete-image", 304 }, 305 }, 306 }, 307 }, 308 } 309 310 var mc recordMacServiceClient 311 handleObsoleteLeases(&mc, config, leases) 312 313 got := mc.vacate 314 want := []macservice.VacateRequest{{LeaseID: "obsolete"}} 315 if diff := cmp.Diff(want, got); diff != "" { 316 t.Errorf("Vacated leases mismatch (-want +got):\n%s", diff) 317 } 318 } 319 320 func TestAddNewLeases(t *testing.T) { 321 swarming1 := &swarmingConfig{ 322 Host: "swarming1.example.com", 323 Pool: "example.pool", 324 } 325 project1 := managedProjectPrefix + "/" + swarming1.Host 326 swarming2 := &swarmingConfig{ 327 Host: "swarming2.example.com", 328 Pool: "example.pool", 329 } 330 331 // Test leases: 332 // * "image-a-1" uses image "image-a" on "swarming1" 333 // * "unmanaged" uses image "image-a", but is not managed by makemac. 334 // 335 // Test images: 336 // * On "swarming1": 337 // * "image-a" wants 2 instances. 338 // * "image-b" wants 2 instances. 339 // * On "swarming2": 340 // * "image-a" wants 1 instances. 341 // 342 // addNewLeases should create: 343 // * 1 "image-a" instance on "swarming1" 344 // * 1 "image-a" instance on "swarming2" 345 // * 2 "image-b" instances on "swarming1" 346 config := map[*swarmingConfig][]imageConfig{ 347 swarming1: { 348 { 349 Hostname: "a", 350 Cert: "dummy-cert", 351 Key: "dummy-key", 352 Image: "image-a", 353 MinCount: 2, 354 }, 355 { 356 Hostname: "b", 357 Cert: "dummy-cert", 358 Key: "dummy-key", 359 Image: "image-b", 360 MinCount: 2, 361 }, 362 }, 363 swarming2: { 364 { 365 Hostname: "a", 366 Cert: "dummy-cert", 367 Key: "dummy-key", 368 Image: "image-a", 369 MinCount: 1, 370 }, 371 }, 372 } 373 leases := map[string]macservice.Instance{ 374 "image-a-1": { 375 Lease: macservice.Lease{ 376 LeaseID: "image-a-1", 377 VMResourceNamespace: macservice.Namespace{ProjectName: project1}, 378 }, 379 InstanceSpecification: macservice.InstanceSpecification{ 380 DiskSelection: macservice.DiskSelection{ 381 ImageHashes: macservice.ImageHashes{ 382 BootSHA256: "image-a", 383 }, 384 }, 385 }, 386 }, 387 "unmanaged": { 388 Lease: macservice.Lease{ 389 LeaseID: "unmanaged", 390 VMResourceNamespace: macservice.Namespace{ProjectName: "other"}, 391 }, 392 InstanceSpecification: macservice.InstanceSpecification{ 393 DiskSelection: macservice.DiskSelection{ 394 ImageHashes: macservice.ImageHashes{ 395 BootSHA256: "image-a", 396 }, 397 }, 398 }, 399 }, 400 } 401 402 var mc recordMacServiceClient 403 addNewLeases(&mc, config, leases) 404 405 leaseASwarm1, err := makeLeaseRequest(swarming1, &config[swarming1][0]) 406 if err != nil { 407 t.Fatalf("makeLeaseRequest(a, swarm1) got err %v want nil", err) 408 } 409 leaseBSwarm1, err := makeLeaseRequest(swarming1, &config[swarming1][1]) 410 if err != nil { 411 t.Fatalf("makeLeaseRequest(b, swarm1) got err %v want nil", err) 412 } 413 leaseASwarm2, err := makeLeaseRequest(swarming2, &config[swarming2][0]) 414 if err != nil { 415 t.Fatalf("makeLeaseRequest(a, swarm2) got err %v want nil", err) 416 } 417 418 got := mc.lease 419 want := []macservice.LeaseRequest{leaseASwarm1, leaseBSwarm1, leaseBSwarm1, leaseASwarm2} 420 if diff := cmp.Diff(want, got); diff != "" { 421 t.Errorf("Lease request mismatch (-want +got):\n%s", diff) 422 } 423 }