github.com/drycc/workflow-cli@v1.5.3-0.20240322092846-d4ee25983af9/cmd/limits_test.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "fmt" 6 "net/http" 7 "testing" 8 9 drycc "github.com/drycc/controller-sdk-go" 10 "github.com/drycc/controller-sdk-go/api" 11 "github.com/drycc/workflow-cli/pkg/testutil" 12 "github.com/stretchr/testify/assert" 13 ) 14 15 const getPlanFixture string = ` 16 { 17 "id": "std1.large.c1m1", 18 "spec": { 19 "id": "std1", 20 "cpu": { 21 "name": "Unknown CPU", 22 "cores": 32, 23 "clock": "3100MHZ", 24 "boost": "3700MHZ", 25 "threads": 64 26 }, 27 "memory": { 28 "size": "64GB", 29 "type": "DDR4-ECC" 30 }, 31 "features": { 32 "gpu": { 33 "name": "Unknown Integrated GPU", 34 "tmus": 1, 35 "rops": 1, 36 "cores": 128, 37 "memory": { 38 "size": "shared", 39 "type": "shared" 40 } 41 }, 42 "network": "10G" 43 }, 44 "keywords": [ 45 "amd", 46 "intel", 47 "unknown" 48 ], 49 "disabled": false 50 }, 51 "cpu": 1, 52 "memory": 1, 53 "features": { 54 "gpu": 1, 55 "network": 1 56 }, 57 "disabled": false 58 } 59 ` 60 const specsFixture string = ` 61 { 62 "results": [{ 63 "id": "std1", 64 "cpu": { 65 "name": "Unknown CPU", 66 "cores": 32, 67 "clock": "3100MHZ", 68 "boost": "3700MHZ", 69 "threads": 64 70 }, 71 "memory": { 72 "size": "64GB", 73 "type": "DDR4-ECC" 74 }, 75 "features": { 76 "gpu": { 77 "name": "Unknown Integrated GPU", 78 "tmus": 1, 79 "rops": 1, 80 "cores": 128, 81 "memory": { 82 "size": "shared", 83 "type": "shared" 84 } 85 }, 86 "network": "10G" 87 }, 88 "keywords": [ 89 "amd", 90 "intel", 91 "unknown" 92 ], 93 "disabled": false 94 }], 95 "count": 1 96 } 97 ` 98 99 const plansFixture string = ` 100 { 101 "results": [{ 102 "id": "std1.large.c1m1", 103 "spec": { 104 "id": "std1", 105 "cpu": { 106 "name": "Unknown CPU", 107 "cores": 32, 108 "clock": "3100MHZ", 109 "boost": "3700MHZ", 110 "threads": 64 111 }, 112 "memory": { 113 "size": "64GB", 114 "type": "DDR4-ECC" 115 }, 116 "features": { 117 "gpu": { 118 "name": "Unknown Integrated GPU", 119 "tmus": 1, 120 "rops": 1, 121 "cores": 128, 122 "memory": { 123 "size": "shared", 124 "type": "shared" 125 } 126 }, 127 "network": "10G" 128 }, 129 "keywords": [ 130 "amd", 131 "intel", 132 "unknown" 133 ], 134 "disabled": false 135 }, 136 "cpu": 1, 137 "memory": 1, 138 "disabled": false 139 }, 140 { 141 "id": "std1.large.c1m2", 142 "spec": { 143 "id": "std1", 144 "cpu": { 145 "name": "Unknown CPU", 146 "cores": 32, 147 "clock": "3100MHZ", 148 "boost": "3700MHZ", 149 "threads": 64 150 }, 151 "memory": { 152 "size": "64GB", 153 "type": "DDR4-ECC" 154 }, 155 "features": { 156 "gpu": { 157 "name": "Unknown Integrated GPU", 158 "tmus": 1, 159 "rops": 1, 160 "cores": 128, 161 "memory": { 162 "size": "shared", 163 "type": "shared" 164 } 165 }, 166 "network": "10G" 167 }, 168 "keywords": [ 169 "amd", 170 "intel", 171 "unknown" 172 ], 173 "disabled": false 174 }, 175 "cpu": 1, 176 "memory": 2, 177 "disabled": false 178 } 179 ], 180 "count": 2 181 } 182 ` 183 184 type parseLimitCase struct { 185 Input string 186 Key string 187 Value string 188 ExpectedError bool 189 ExpectedMsg string 190 } 191 192 func newTestServer(t *testing.T) (string, *testutil.TestServer) { 193 cf, server, err := testutil.NewTestServerAndClient() 194 if err != nil { 195 t.Fatal(err) 196 } 197 server.Mux.HandleFunc("/v2/limits/plans/std1.large.c1m1/", func(w http.ResponseWriter, _ *http.Request) { 198 w.Header().Add("DRYCC_API_VERSION", drycc.APIVersion) 199 testutil.SetHeaders(w) 200 fmt.Fprint(w, getPlanFixture) 201 }) 202 server.Mux.HandleFunc("/v2/limits/specs/", func(w http.ResponseWriter, _ *http.Request) { 203 w.Header().Add("DRYCC_API_VERSION", drycc.APIVersion) 204 testutil.SetHeaders(w) 205 fmt.Fprint(w, specsFixture) 206 }) 207 208 server.Mux.HandleFunc("/v2/limits/plans/", func(w http.ResponseWriter, _ *http.Request) { 209 w.Header().Add("DRYCC_API_VERSION", drycc.APIVersion) 210 testutil.SetHeaders(w) 211 fmt.Fprint(w, plansFixture) 212 }) 213 214 return cf, server 215 } 216 217 func TestParseLimit(t *testing.T) { 218 t.Parallel() 219 220 var errorHint = ` doesn't fit format type=#unit or type=# 221 Examples: web=std1.large.c1m1` 222 223 cases := []parseLimitCase{ 224 {"web=std1.large.c1m1", "web", "std1.large.c1m1", false, ""}, 225 {"web=std1.large.c2m2", "web", "std1.large.c2m2", false, ""}, 226 {"task=std1.large.c2m2", "task", "std1.large.c2m2", false, ""}, 227 {"task=std1.large.c2m4", "task", "std1.large.c2m4", false, ""}, 228 {"task-big=std1.large.c2m4", "task-big", "std1.large.c2m4", false, ""}, 229 {"task=[]std1.large.c2m4", "", "", true, "task=[]std1.large.c2m4" + errorHint}, 230 {"task[]=&std1.large.c2m4", "", "", true, "task[]=&std1.large.c2m4" + errorHint}, 231 {"task~!=&std1.large.c2m4", "", "", true, "task~!=&std1.large.c2m4" + errorHint}, 232 } 233 234 for _, check := range cases { 235 key, value, err := parseLimit(check.Input) 236 if check.ExpectedError { 237 assert.Equal(t, err.Error(), check.ExpectedMsg, "error") 238 } else { 239 assert.NoError(t, err) 240 assert.Equal(t, key, check.Key, "key") 241 assert.Equal(t, value, check.Value, "value") 242 } 243 } 244 } 245 246 type parseLimitsCase struct { 247 Input []string 248 ExpectedMap map[string]interface{} 249 ExpectedError bool 250 ExpectedMsg string 251 } 252 253 func TestLimitTags(t *testing.T) { 254 t.Parallel() 255 256 cases := []parseLimitsCase{ 257 {[]string{"web=std1.large.c1m1", "worker=std1.large.c1m2"}, map[string]interface{}{"web": "std1.large.c1m1", "worker": "std1.large.c1m2"}, false, ""}, 258 {[]string{"foo=", "web=std1.large.c1m1"}, nil, true, `foo= doesn't fit format type=#unit or type=# 259 Examples: web=std1.large.c1m1`}, 260 } 261 262 for _, check := range cases { 263 actual, err := parseLimits(check.Input) 264 if check.ExpectedError { 265 assert.Equal(t, err.Error(), check.ExpectedMsg, "error") 266 } else { 267 assert.NoError(t, err) 268 assert.Equal(t, actual, check.ExpectedMap, "map") 269 } 270 } 271 } 272 273 func TestLimitsList(t *testing.T) { 274 t.Parallel() 275 cf, server := newTestServer(t) 276 defer server.Close() 277 278 server.Mux.HandleFunc("/v2/apps/enterprise/config/", func(w http.ResponseWriter, _ *http.Request) { 279 testutil.SetHeaders(w) 280 fmt.Fprintf(w, `{ 281 "owner": "jkirk", 282 "app": "enterprise", 283 "values": {}, 284 "limits": { 285 "web": "std1.large.c1m1", 286 "worker": "std1.large.c1m1", 287 "db": "std1.large.c1m1" 288 }, 289 "tags": {}, 290 "registry": {}, 291 "created": "2014-01-01T00:00:00UTC", 292 "updated": "2014-01-01T00:00:00UTC", 293 "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" 294 }`) 295 }) 296 297 var b bytes.Buffer 298 cmdr := DryccCmd{WOut: &b, ConfigFile: cf} 299 300 err := cmdr.LimitsList("enterprise") 301 assert.NoError(t, err) 302 assert.Equal(t, b.String(), `PTYPE PLAN VCPUS MEMORY FEATURES 303 db std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 304 web std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 305 worker std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 306 `, "output") 307 308 server.Mux.HandleFunc("/v2/apps/franklin/config/", func(w http.ResponseWriter, _ *http.Request) { 309 testutil.SetHeaders(w) 310 fmt.Fprintf(w, `{ 311 "owner": "bedison", 312 "app": "franklin", 313 "limits": {}, 314 "cpu": {}, 315 "tags": {}, 316 "registry": {}, 317 "created": "2014-01-01T00:00:00UTC", 318 "updated": "2014-01-01T00:00:00UTC", 319 "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" 320 }`) 321 }) 322 b.Reset() 323 324 err = cmdr.LimitsList("franklin") 325 assert.NoError(t, err) 326 assert.Equal(t, b.String(), `No limits found in franklin app. 327 `, "output") 328 } 329 330 func TestLimitsSet(t *testing.T) { 331 t.Parallel() 332 cf, server := newTestServer(t) 333 defer server.Close() 334 335 server.Mux.HandleFunc("/v2/apps/foo/config/", func(w http.ResponseWriter, r *http.Request) { 336 testutil.SetHeaders(w) 337 if r.Method == "POST" { 338 testutil.AssertBody(t, api.Config{ 339 Limits: map[string]interface{}{ 340 "web": "std1.large.c1m1", 341 }, 342 }, r) 343 } 344 345 fmt.Fprintf(w, `{ 346 "owner": "jkirk", 347 "app": "foo", 348 "values": {}, 349 "limits": {"web": "std1.large.c1m1"}, 350 "tags": {}, 351 "registry": {}, 352 "created": "2014-01-01T00:00:00UTC", 353 "updated": "2014-01-01T00:00:00UTC", 354 "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" 355 }`) 356 }) 357 358 var b bytes.Buffer 359 cmdr := DryccCmd{WOut: &b, ConfigFile: cf} 360 361 err := cmdr.LimitsSet("foo", []string{"web=std1.large.c1m1"}) 362 assert.NoError(t, err) 363 364 assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done 365 366 PTYPE PLAN VCPUS MEMORY FEATURES 367 web std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 368 `, "output") 369 370 server.Mux.HandleFunc("/v2/apps/franklin/config/", func(w http.ResponseWriter, r *http.Request) { 371 testutil.SetHeaders(w) 372 if r.Method == "POST" { 373 testutil.AssertBody(t, api.Config{ 374 Limits: map[string]interface{}{ 375 "web": "std1.large.c1m1", 376 }, 377 }, r) 378 } 379 380 fmt.Fprintf(w, `{ 381 "owner": "bedison", 382 "app": "franklin", 383 "values": {}, 384 "limits": { 385 "web": "std1.large.c1m1" 386 }, 387 "tags": {}, 388 "registry": {}, 389 "created": "2014-01-01T00:00:00UTC", 390 "updated": "2014-01-01T00:00:00UTC", 391 "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" 392 }`) 393 }) 394 b.Reset() 395 396 err = cmdr.LimitsSet("franklin", []string{"web=std1.large.c1m1"}) 397 assert.NoError(t, err) 398 399 assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done 400 401 PTYPE PLAN VCPUS MEMORY FEATURES 402 web std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 403 `, "output") 404 405 // with requests/limit parameter 406 server.Mux.HandleFunc("/v2/apps/jim/config/", func(w http.ResponseWriter, r *http.Request) { 407 testutil.SetHeaders(w) 408 if r.Method == "POST" { 409 testutil.AssertBody(t, api.Config{ 410 Limits: map[string]interface{}{ 411 "web": "std1.large.c1m1", 412 "worker": "std1.large.c1m1", 413 "db": "std1.large.c1m1", 414 }, 415 }, r) 416 } 417 418 fmt.Fprintf(w, `{ 419 "owner": "foo", 420 "app": "jim", 421 "values": {}, 422 "limits": { 423 "web": "std1.large.c1m1", 424 "worker": "std1.large.c1m1", 425 "db": "std1.large.c1m1" 426 }, 427 "tags": {}, 428 "registry": {}, 429 "created": "2014-01-01T00:00:00UTC", 430 "updated": "2014-01-01T00:00:00UTC", 431 "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" 432 }`) 433 }) 434 b.Reset() 435 436 err = cmdr.LimitsSet("jim", []string{"web=std1.large.c1m1", "worker=std1.large.c1m1", "db=std1.large.c1m1"}) 437 assert.NoError(t, err) 438 439 assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done 440 441 PTYPE PLAN VCPUS MEMORY FEATURES 442 db std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 443 web std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 444 worker std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 445 `, "output") 446 447 // with requests/limit parameter 448 server.Mux.HandleFunc("/v2/apps/phew/config/", func(w http.ResponseWriter, r *http.Request) { 449 testutil.SetHeaders(w) 450 if r.Method == "POST" { 451 testutil.AssertBody(t, api.Config{ 452 Limits: map[string]interface{}{ 453 "web": "std1.large.c1m1", 454 "worker": "std1.large.c1m1", 455 "db": "std1.large.c1m1", 456 }, 457 }, r) 458 } 459 460 fmt.Fprintf(w, `{ 461 "owner": "foo", 462 "app": "jim", 463 "values": {}, 464 "limits": { 465 "web": "std1.large.c1m1", 466 "worker": "std1.large.c1m1", 467 "db": "std1.large.c1m1" 468 }, 469 "tags": {}, 470 "registry": {}, 471 "created": "2014-01-01T00:00:00UTC", 472 "updated": "2014-01-01T00:00:00UTC", 473 "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" 474 }`) 475 }) 476 b.Reset() 477 478 err = cmdr.LimitsSet("phew", []string{"web=std1.large.c1m1", "worker=std1.large.c1m1", "db=std1.large.c1m1"}) 479 assert.NoError(t, err) 480 481 assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done 482 483 PTYPE PLAN VCPUS MEMORY FEATURES 484 db std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 485 web std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 486 worker std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 487 `, "output") 488 } 489 490 func TestLimitsUnset(t *testing.T) { 491 t.Parallel() 492 cf, server := newTestServer(t) 493 defer server.Close() 494 495 server.Mux.HandleFunc("/v2/apps/foo/config/", func(w http.ResponseWriter, r *http.Request) { 496 testutil.SetHeaders(w) 497 if r.Method == "POST" { 498 testutil.AssertBody(t, api.Config{ 499 Limits: map[string]interface{}{ 500 "web": nil, 501 }, 502 }, r) 503 } 504 505 fmt.Fprintf(w, `{ 506 "owner": "jkirk", 507 "app": "foo", 508 "values": {}, 509 "limits": { 510 "web": "std1.large.c1m1" 511 }, 512 "tags": {}, 513 "registry": {}, 514 "created": "2014-01-01T00:00:00UTC", 515 "updated": "2014-01-01T00:00:00UTC", 516 "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" 517 }`) 518 }) 519 520 var b bytes.Buffer 521 cmdr := DryccCmd{WOut: &b, ConfigFile: cf} 522 523 err := cmdr.LimitsUnset("foo", []string{"web"}) 524 assert.NoError(t, err) 525 526 assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done 527 528 PTYPE PLAN VCPUS MEMORY FEATURES 529 web std1.large.c1m1 1 1 GiB Unknown Integrated GPU shared * 1 530 `, "output") 531 532 server.Mux.HandleFunc("/v2/apps/franklin/config/", func(w http.ResponseWriter, r *http.Request) { 533 testutil.SetHeaders(w) 534 if r.Method == "POST" { 535 testutil.AssertBody(t, api.Config{ 536 Limits: map[string]interface{}{ 537 "web": nil, 538 }, 539 }, r) 540 } 541 542 fmt.Fprintf(w, `{ 543 "owner": "bedison", 544 "app": "franklin", 545 "values": {}, 546 "limits": {}, 547 "tags": {}, 548 "registry": {}, 549 "created": "2014-01-01T00:00:00UTC", 550 "updated": "2014-01-01T00:00:00UTC", 551 "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" 552 }`) 553 }) 554 b.Reset() 555 556 err = cmdr.LimitsUnset("franklin", []string{"web"}) 557 assert.NoError(t, err) 558 559 assert.Equal(t, testutil.StripProgress(b.String()), `Applying limits... done 560 561 No limits found in franklin app. 562 `, "output") 563 } 564 565 func TestLimitsSpecs(t *testing.T) { 566 t.Parallel() 567 cf, server := newTestServer(t) 568 defer server.Close() 569 570 var b bytes.Buffer 571 cmdr := DryccCmd{WOut: &b, ConfigFile: cf} 572 573 err := cmdr.LimitsSpecs("", 10) 574 assert.NoError(t, err) 575 assert.Equal(t, b.String(), `ID CPU CLOCK BOOST CORES THREADS NETWORK FEATURES 576 std1 Unknown CPU 3100MHZ 3700MHZ 32 64 10G Unknown Integrated GPU shared 577 `, "output") 578 } 579 580 func TestLimitsPlans(t *testing.T) { 581 t.Parallel() 582 cf, server := newTestServer(t) 583 defer server.Close() 584 585 var b bytes.Buffer 586 cmdr := DryccCmd{WOut: &b, ConfigFile: cf} 587 588 err := cmdr.LimitsPlans("", 0, 0, 100) 589 assert.NoError(t, err) 590 assert.Equal(t, b.String(), `ID SPEC CPU VCPUS MEMORY FEATURES 591 std1.large.c1m1 std1 Unknown CPU 1 1 GiB Unknown Integrated GPU shared 592 std1.large.c1m2 std1 Unknown CPU 1 2 GiB Unknown Integrated GPU shared 593 `, "output") 594 }