storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/endpoint-ellipses_test.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "fmt" 21 "reflect" 22 "testing" 23 24 "storj.io/minio/pkg/ellipses" 25 ) 26 27 // Tests create endpoints with ellipses and without. 28 func TestCreateServerEndpoints(t *testing.T) { 29 testCases := []struct { 30 serverAddr string 31 args []string 32 success bool 33 }{ 34 // Invalid input. 35 {"", []string{}, false}, 36 // Range cannot be negative. 37 {":9000", []string{"/export1{-1...1}"}, false}, 38 // Range cannot start bigger than end. 39 {":9000", []string{"/export1{64...1}"}, false}, 40 // Range can only be numeric. 41 {":9000", []string{"/export1{a...z}"}, false}, 42 // Duplicate disks not allowed. 43 {":9000", []string{"/export1{1...32}", "/export1{1...32}"}, false}, 44 // Same host cannot export same disk on two ports - special case localhost. 45 {":9001", []string{"http://localhost:900{1...2}/export{1...64}"}, false}, 46 // Valid inputs. 47 {":9000", []string{"/export1"}, true}, 48 {":9000", []string{"/export1", "/export2", "/export3", "/export4"}, true}, 49 {":9000", []string{"/export1{1...64}"}, true}, 50 {":9000", []string{"/export1{01...64}"}, true}, 51 {":9000", []string{"/export1{1...32}", "/export1{33...64}"}, true}, 52 {":9001", []string{"http://localhost:9001/export{1...64}"}, true}, 53 {":9001", []string{"http://localhost:9001/export{01...64}"}, true}, 54 } 55 56 for _, testCase := range testCases { 57 testCase := testCase 58 t.Run("", func(t *testing.T) { 59 _, _, err := createServerEndpoints(testCase.serverAddr, testCase.args...) 60 if err != nil && testCase.success { 61 t.Errorf("Expected success but failed instead %s", err) 62 } 63 if err == nil && !testCase.success { 64 t.Errorf("Expected failure but passed instead") 65 } 66 }) 67 } 68 } 69 70 func TestGetDivisibleSize(t *testing.T) { 71 testCases := []struct { 72 totalSizes []uint64 73 result uint64 74 }{{[]uint64{24, 32, 16}, 8}, 75 {[]uint64{32, 8, 4}, 4}, 76 {[]uint64{8, 8, 8}, 8}, 77 {[]uint64{24}, 24}, 78 } 79 80 for _, testCase := range testCases { 81 testCase := testCase 82 t.Run("", func(t *testing.T) { 83 gotGCD := getDivisibleSize(testCase.totalSizes) 84 if testCase.result != gotGCD { 85 t.Errorf("Expected %v, got %v", testCase.result, gotGCD) 86 } 87 }) 88 } 89 } 90 91 // Test tests calculating set indexes with ENV override for drive count. 92 func TestGetSetIndexesEnvOverride(t *testing.T) { 93 testCases := []struct { 94 args []string 95 totalSizes []uint64 96 indexes [][]uint64 97 envOverride uint64 98 success bool 99 }{ 100 { 101 []string{"data{1...64}"}, 102 []uint64{64}, 103 [][]uint64{{8, 8, 8, 8, 8, 8, 8, 8}}, 104 8, 105 true, 106 }, 107 { 108 []string{"http://host{1...2}/data{1...180}"}, 109 []uint64{360}, 110 [][]uint64{{15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15}}, 111 15, 112 true, 113 }, 114 { 115 []string{"http://host{1...12}/data{1...12}"}, 116 []uint64{144}, 117 [][]uint64{{12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12}}, 118 12, 119 true, 120 }, 121 { 122 []string{"http://host{0...5}/data{1...28}"}, 123 []uint64{168}, 124 [][]uint64{{12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12}}, 125 12, 126 true, 127 }, 128 // Incorrect custom set drive count. 129 { 130 []string{"http://host{0...5}/data{1...28}"}, 131 []uint64{168}, 132 nil, 133 10, 134 false, 135 }, 136 // Failure not divisible number of disks. 137 { 138 []string{"http://host{1...11}/data{1...11}"}, 139 []uint64{121}, 140 [][]uint64{{11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11}}, 141 11, 142 true, 143 }, 144 { 145 []string{"data{1...60}"}, 146 nil, 147 nil, 148 8, 149 false, 150 }, 151 { 152 []string{"data{1...64}"}, 153 nil, 154 nil, 155 64, 156 false, 157 }, 158 { 159 []string{"data{1...64}"}, 160 nil, 161 nil, 162 2, 163 false, 164 }, 165 } 166 167 for _, testCase := range testCases { 168 testCase := testCase 169 t.Run("", func(t *testing.T) { 170 var argPatterns = make([]ellipses.ArgPattern, len(testCase.args)) 171 for i, arg := range testCase.args { 172 patterns, err := ellipses.FindEllipsesPatterns(arg) 173 if err != nil { 174 t.Fatalf("Unexpected failure %s", err) 175 } 176 argPatterns[i] = patterns 177 } 178 179 gotIndexes, err := getSetIndexes(testCase.args, testCase.totalSizes, testCase.envOverride, argPatterns) 180 if err != nil && testCase.success { 181 t.Errorf("Expected success but failed instead %s", err) 182 } 183 if err == nil && !testCase.success { 184 t.Errorf("Expected failure but passed instead") 185 } 186 if !reflect.DeepEqual(testCase.indexes, gotIndexes) { 187 t.Errorf("Expected %v, got %v", testCase.indexes, gotIndexes) 188 } 189 }) 190 } 191 } 192 193 // Test tests calculating set indexes. 194 func TestGetSetIndexes(t *testing.T) { 195 testCases := []struct { 196 args []string 197 totalSizes []uint64 198 indexes [][]uint64 199 success bool 200 }{ 201 // Invalid inputs. 202 { 203 []string{"data{1...3}"}, 204 []uint64{3}, 205 nil, 206 false, 207 }, 208 { 209 []string{"data/controller1/export{1...2}, data/controller2/export{1...4}, data/controller3/export{1...8}"}, 210 []uint64{2, 4, 8}, 211 nil, 212 false, 213 }, 214 { 215 []string{"data{1...17}/export{1...52}"}, 216 []uint64{14144}, 217 nil, 218 false, 219 }, 220 // Valid inputs. 221 { 222 []string{"data{1...27}"}, 223 []uint64{27}, 224 [][]uint64{{9, 9, 9}}, 225 true, 226 }, 227 { 228 []string{"http://host{1...3}/data{1...180}"}, 229 []uint64{540}, 230 [][]uint64{{15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15}}, 231 true, 232 }, 233 { 234 []string{"http://host{1...2}.rack{1...4}/data{1...180}"}, 235 []uint64{1440}, 236 [][]uint64{{16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}}, 237 true, 238 }, 239 { 240 []string{"http://host{1...2}/data{1...180}"}, 241 []uint64{360}, 242 [][]uint64{{12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12}}, 243 true, 244 }, 245 { 246 []string{"data/controller1/export{1...4}, data/controller2/export{1...8}, data/controller3/export{1...12}"}, 247 []uint64{4, 8, 12}, 248 [][]uint64{{4}, {4, 4}, {4, 4, 4}}, 249 true, 250 }, 251 { 252 []string{"data{1...64}"}, 253 []uint64{64}, 254 [][]uint64{{16, 16, 16, 16}}, 255 true, 256 }, 257 { 258 []string{"data{1...24}"}, 259 []uint64{24}, 260 [][]uint64{{12, 12}}, 261 true, 262 }, 263 { 264 []string{"data/controller{1...11}/export{1...8}"}, 265 []uint64{88}, 266 [][]uint64{{11, 11, 11, 11, 11, 11, 11, 11}}, 267 true, 268 }, 269 { 270 []string{"data{1...4}"}, 271 []uint64{4}, 272 [][]uint64{{4}}, 273 true, 274 }, 275 { 276 []string{"data/controller1/export{1...10}, data/controller2/export{1...10}, data/controller3/export{1...10}"}, 277 []uint64{10, 10, 10}, 278 [][]uint64{{10}, {10}, {10}}, 279 true, 280 }, 281 { 282 []string{"data{1...16}/export{1...52}"}, 283 []uint64{832}, 284 [][]uint64{{16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}}, 285 true, 286 }, 287 } 288 289 for _, testCase := range testCases { 290 testCase := testCase 291 t.Run("", func(t *testing.T) { 292 var argPatterns = make([]ellipses.ArgPattern, len(testCase.args)) 293 for i, arg := range testCase.args { 294 patterns, err := ellipses.FindEllipsesPatterns(arg) 295 if err != nil { 296 t.Fatalf("Unexpected failure %s", err) 297 } 298 argPatterns[i] = patterns 299 } 300 gotIndexes, err := getSetIndexes(testCase.args, testCase.totalSizes, 0, argPatterns) 301 if err != nil && testCase.success { 302 t.Errorf("Expected success but failed instead %s", err) 303 } 304 if err == nil && !testCase.success { 305 t.Errorf("Expected failure but passed instead") 306 } 307 if !reflect.DeepEqual(testCase.indexes, gotIndexes) { 308 t.Errorf("Expected %v, got %v", testCase.indexes, gotIndexes) 309 } 310 }) 311 } 312 } 313 314 func getHexSequences(start int, number int, paddinglen int) (seq []string) { 315 for i := start; i <= number; i++ { 316 if paddinglen == 0 { 317 seq = append(seq, fmt.Sprintf("%x", i)) 318 } else { 319 seq = append(seq, fmt.Sprintf(fmt.Sprintf("%%0%dx", paddinglen), i)) 320 } 321 } 322 return seq 323 } 324 325 func getSequences(start int, number int, paddinglen int) (seq []string) { 326 for i := start; i <= number; i++ { 327 if paddinglen == 0 { 328 seq = append(seq, fmt.Sprintf("%d", i)) 329 } else { 330 seq = append(seq, fmt.Sprintf(fmt.Sprintf("%%0%dd", paddinglen), i)) 331 } 332 } 333 return seq 334 } 335 336 // Test tests parses endpoint ellipses input pattern. 337 func TestParseEndpointSet(t *testing.T) { 338 testCases := []struct { 339 arg string 340 es endpointSet 341 success bool 342 }{ 343 // Tests invalid inputs. 344 { 345 "...", 346 endpointSet{}, 347 false, 348 }, 349 // No range specified. 350 { 351 "{...}", 352 endpointSet{}, 353 false, 354 }, 355 // Invalid range. 356 { 357 "http://minio{2...3}/export/set{1...0}", 358 endpointSet{}, 359 false, 360 }, 361 // Range cannot be smaller than 4 minimum. 362 { 363 "/export{1..2}", 364 endpointSet{}, 365 false, 366 }, 367 // Unsupported characters. 368 { 369 "/export/test{1...2O}", 370 endpointSet{}, 371 false, 372 }, 373 // Tests valid inputs. 374 { 375 "{1...27}", 376 endpointSet{ 377 []ellipses.ArgPattern{ 378 []ellipses.Pattern{ 379 { 380 Prefix: "", 381 Suffix: "", 382 Seq: getSequences(1, 27, 0), 383 }, 384 }, 385 }, 386 nil, 387 [][]uint64{{9, 9, 9}}, 388 }, 389 true, 390 }, 391 { 392 "/export/set{1...64}", 393 endpointSet{ 394 []ellipses.ArgPattern{ 395 []ellipses.Pattern{ 396 { 397 Prefix: "/export/set", 398 Suffix: "", 399 Seq: getSequences(1, 64, 0), 400 }, 401 }, 402 }, 403 nil, 404 [][]uint64{{16, 16, 16, 16}}, 405 }, 406 true, 407 }, 408 // Valid input for distributed setup. 409 { 410 "http://minio{2...3}/export/set{1...64}", 411 endpointSet{ 412 []ellipses.ArgPattern{ 413 []ellipses.Pattern{ 414 { 415 Prefix: "", 416 Suffix: "", 417 Seq: getSequences(1, 64, 0), 418 }, 419 { 420 Prefix: "http://minio", 421 Suffix: "/export/set", 422 Seq: getSequences(2, 3, 0), 423 }, 424 }, 425 }, 426 nil, 427 [][]uint64{{16, 16, 16, 16, 16, 16, 16, 16}}, 428 }, 429 true, 430 }, 431 // Supporting some advanced cases. 432 { 433 "http://minio{1...64}.mydomain.net/data", 434 endpointSet{ 435 []ellipses.ArgPattern{ 436 []ellipses.Pattern{ 437 { 438 Prefix: "http://minio", 439 Suffix: ".mydomain.net/data", 440 Seq: getSequences(1, 64, 0), 441 }, 442 }, 443 }, 444 nil, 445 [][]uint64{{16, 16, 16, 16}}, 446 }, 447 true, 448 }, 449 { 450 "http://rack{1...4}.mydomain.minio{1...16}/data", 451 endpointSet{ 452 []ellipses.ArgPattern{ 453 []ellipses.Pattern{ 454 { 455 Prefix: "", 456 Suffix: "/data", 457 Seq: getSequences(1, 16, 0), 458 }, 459 { 460 Prefix: "http://rack", 461 Suffix: ".mydomain.minio", 462 Seq: getSequences(1, 4, 0), 463 }, 464 }, 465 }, 466 nil, 467 [][]uint64{{16, 16, 16, 16}}, 468 }, 469 true, 470 }, 471 // Supporting kubernetes cases. 472 { 473 "http://minio{0...15}.mydomain.net/data{0...1}", 474 endpointSet{ 475 []ellipses.ArgPattern{ 476 []ellipses.Pattern{ 477 { 478 Prefix: "", 479 Suffix: "", 480 Seq: getSequences(0, 1, 0), 481 }, 482 { 483 Prefix: "http://minio", 484 Suffix: ".mydomain.net/data", 485 Seq: getSequences(0, 15, 0), 486 }, 487 }, 488 }, 489 nil, 490 [][]uint64{{16, 16}}, 491 }, 492 true, 493 }, 494 // No host regex, just disks. 495 { 496 "http://server1/data{1...32}", 497 endpointSet{ 498 []ellipses.ArgPattern{ 499 []ellipses.Pattern{ 500 { 501 Prefix: "http://server1/data", 502 Suffix: "", 503 Seq: getSequences(1, 32, 0), 504 }, 505 }, 506 }, 507 nil, 508 [][]uint64{{16, 16}}, 509 }, 510 true, 511 }, 512 // No host regex, just disks with two position numerics. 513 { 514 "http://server1/data{01...32}", 515 endpointSet{ 516 []ellipses.ArgPattern{ 517 []ellipses.Pattern{ 518 { 519 Prefix: "http://server1/data", 520 Suffix: "", 521 Seq: getSequences(1, 32, 2), 522 }, 523 }, 524 }, 525 nil, 526 [][]uint64{{16, 16}}, 527 }, 528 true, 529 }, 530 // More than 2 ellipses are supported as well. 531 { 532 "http://minio{2...3}/export/set{1...64}/test{1...2}", 533 endpointSet{ 534 []ellipses.ArgPattern{ 535 []ellipses.Pattern{ 536 { 537 Prefix: "", 538 Suffix: "", 539 Seq: getSequences(1, 2, 0), 540 }, 541 { 542 Prefix: "", 543 Suffix: "/test", 544 Seq: getSequences(1, 64, 0), 545 }, 546 { 547 Prefix: "http://minio", 548 Suffix: "/export/set", 549 Seq: getSequences(2, 3, 0), 550 }, 551 }, 552 }, 553 nil, 554 [][]uint64{{16, 16, 16, 16, 16, 16, 16, 16, 555 16, 16, 16, 16, 16, 16, 16, 16}}, 556 }, 557 true, 558 }, 559 // More than 1 ellipses per argument for standalone setup. 560 { 561 "/export{1...10}/disk{1...10}", 562 endpointSet{ 563 []ellipses.ArgPattern{ 564 []ellipses.Pattern{ 565 { 566 Prefix: "", 567 Suffix: "", 568 Seq: getSequences(1, 10, 0), 569 }, 570 { 571 Prefix: "/export", 572 Suffix: "/disk", 573 Seq: getSequences(1, 10, 0), 574 }, 575 }, 576 }, 577 nil, 578 [][]uint64{{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}}, 579 }, 580 true, 581 }, 582 // IPv6 ellipses with hexadecimal expansion 583 { 584 "http://[2001:3984:3989::{1...a}]/disk{1...10}", 585 endpointSet{ 586 []ellipses.ArgPattern{ 587 []ellipses.Pattern{ 588 { 589 Prefix: "", 590 Suffix: "", 591 Seq: getSequences(1, 10, 0), 592 }, 593 { 594 Prefix: "http://[2001:3984:3989::", 595 Suffix: "]/disk", 596 Seq: getHexSequences(1, 10, 0), 597 }, 598 }, 599 }, 600 nil, 601 [][]uint64{{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}}, 602 }, 603 true, 604 }, 605 // IPv6 ellipses with hexadecimal expansion with 3 position numerics. 606 { 607 "http://[2001:3984:3989::{001...00a}]/disk{1...10}", 608 endpointSet{ 609 []ellipses.ArgPattern{ 610 []ellipses.Pattern{ 611 { 612 Prefix: "", 613 Suffix: "", 614 Seq: getSequences(1, 10, 0), 615 }, 616 { 617 Prefix: "http://[2001:3984:3989::", 618 Suffix: "]/disk", 619 Seq: getHexSequences(1, 10, 3), 620 }, 621 }, 622 }, 623 nil, 624 [][]uint64{{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}}, 625 }, 626 true, 627 }, 628 } 629 630 for _, testCase := range testCases { 631 testCase := testCase 632 t.Run("", func(t *testing.T) { 633 gotEs, err := parseEndpointSet(0, testCase.arg) 634 if err != nil && testCase.success { 635 t.Errorf("Expected success but failed instead %s", err) 636 } 637 if err == nil && !testCase.success { 638 t.Errorf("Expected failure but passed instead") 639 } 640 if !reflect.DeepEqual(testCase.es, gotEs) { 641 t.Errorf("Expected %v, got %v", testCase.es, gotEs) 642 } 643 }) 644 } 645 }