github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/tests/openapi/client/openapi_task_check (about) 1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 import sys 4 import requests 5 6 SHARD_TASK_NAME = "test-shard" 7 ILLEGAL_CHAR_TASK_NAME = "t-Ë!s`t" 8 SOURCE1_NAME = "mysql-01" 9 SOURCE2_NAME = "mysql-02" 10 11 12 API_ENDPOINT = "http://127.0.0.1:8361/api/v1/tasks" 13 14 15 def create_task_failed(): 16 task = { 17 "name": "test", 18 "task_mode": "all", 19 "shard_mode": "pessimistic_xxd", # pessimistic_xxd is not a valid shard mode 20 "meta_schema": "dm-meta", 21 "enhance_online_schema_change": True, 22 "on_duplicate": "error", 23 "target_config": { 24 "host": "127.0.0.1", 25 "port": 4000, 26 "user": "root", 27 "password": "", 28 }, 29 "table_migrate_rule": [ 30 { 31 "source": { 32 "source_name": SOURCE1_NAME, 33 "schema": "openapi", 34 "table": "*", 35 }, 36 "target": {"schema": "openapi", "table": "t"}, 37 }, 38 { 39 "source": { 40 "source_name": SOURCE2_NAME, 41 "schema": "openapi", 42 "table": "*", 43 }, 44 "target": {"schema": "openapi", "table": "t"}, 45 }, 46 ], 47 "source_config": { 48 "source_conf": [ 49 {"source_name": SOURCE1_NAME}, 50 {"source_name": SOURCE2_NAME}, 51 ], 52 }, 53 } 54 resp = requests.post(url=API_ENDPOINT, json={"task": task}) 55 print("create_task_failed resp=", resp.json()) 56 assert resp.status_code == 400 57 58 def create_task_with_precheck(task_name, ignore_check, is_success, check_result): 59 task = { 60 "name": task_name, 61 "task_mode": "all", 62 "meta_schema": "dm_meta", 63 "shard_mode": "pessimistic", 64 "enhance_online_schema_change": True, 65 "on_duplicate": "error", 66 "ignore_checking_items" : ["version", ignore_check], 67 "target_config": { 68 "host": "127.0.0.1", 69 "port": 4000, 70 "user": "root", 71 "password": "", 72 }, 73 "table_migrate_rule": [ 74 { 75 "source": { 76 "source_name": SOURCE1_NAME, 77 "schema": "openapi", 78 "table": "t*", 79 }, 80 "target": {"schema": "openapi", "table": "t"}, 81 }, 82 { 83 "source": { 84 "source_name": SOURCE2_NAME, 85 "schema": "openapi", 86 "table": "t*", 87 }, 88 "target": {"schema": "openapi", "table": "t"}, 89 }, 90 ], 91 "source_config": { 92 "source_conf": [ 93 {"source_name": SOURCE1_NAME}, 94 {"source_name": SOURCE2_NAME}, 95 ], 96 }, 97 } 98 99 resp = requests.post(url=API_ENDPOINT, json={"task": task}) 100 print("create_task_with_precheck resp=", resp.json()) 101 if is_success == "success": 102 assert resp.status_code == 201 103 assert check_result in resp.json()["check_result"] 104 else: 105 assert resp.status_code == 400 106 assert check_result in resp.json()["error_msg"] 107 108 def create_noshard_task_success(task_name, tartget_table_name=""): 109 task = { 110 "name": task_name, 111 "task_mode": "all", 112 "meta_schema": "dm-meta", 113 "enhance_online_schema_change": True, 114 "on_duplicate": "error", 115 "target_config": { 116 "host": "127.0.0.1", 117 "port": 4000, 118 "user": "root", 119 "password": "", 120 }, 121 "table_migrate_rule": [ 122 { 123 "source": { 124 "source_name": SOURCE1_NAME, 125 "schema": "openapi", 126 "table": "*", 127 }, 128 "target": {"schema": "openapi", "table": tartget_table_name}, 129 }, 130 { 131 "source": { 132 "source_name": SOURCE2_NAME, 133 "schema": "openapi", 134 "table": "*", 135 }, 136 "target": {"schema": "openapi", "table": tartget_table_name}, 137 }, 138 ], 139 "source_config": { 140 "source_conf": [ 141 {"source_name": SOURCE1_NAME}, 142 {"source_name": SOURCE2_NAME}, 143 ], 144 }, 145 } 146 resp = requests.post(url=API_ENDPOINT, json={"task": task}) 147 print("create_noshard_task_success resp=", resp.json()) 148 assert resp.status_code == 201 149 150 def create_incremental_task_with_gtid_success(task_name,binlog_name1,binlog_pos1,binlog_gtid1,binlog_name2,binlog_pos2,binlog_gtid2): 151 task = { 152 "name": task_name, 153 "task_mode": "incremental", 154 "meta_schema": "dm_meta", 155 "enhance_online_schema_change": True, 156 "on_duplicate": "error", 157 "target_config": { 158 "host": "127.0.0.1", 159 "port": 4000, 160 "user": "root", 161 "password": "", 162 }, 163 "table_migrate_rule": [ 164 { 165 "source": { 166 "source_name": SOURCE1_NAME, 167 "schema": "openapi", 168 "table": "*", 169 }, 170 "target": {"schema": "openapi", "table": ""}, 171 }, 172 { 173 "source": { 174 "source_name": SOURCE2_NAME, 175 "schema": "openapi", 176 "table": "*", 177 }, 178 "target": {"schema": "openapi", "table": ""}, 179 }, 180 ], 181 "source_config": { 182 "source_conf": [ 183 {"source_name": SOURCE1_NAME}, 184 {"source_name": SOURCE2_NAME}, 185 ], 186 }, 187 } 188 189 if binlog_pos1 != "": 190 task["source_config"] = { 191 "source_conf": [ 192 { 193 "source_name": SOURCE1_NAME, 194 "binlog_name": binlog_name1, 195 "binlog_pos": int(binlog_pos1), 196 "binlog_gtid": binlog_gtid1, 197 }, 198 { 199 "source_name": SOURCE2_NAME, 200 "binlog_name": binlog_name2, 201 "binlog_pos": int(binlog_pos2), 202 "binlog_gtid": binlog_gtid2, 203 }, 204 ], 205 } 206 207 resp = requests.post(url=API_ENDPOINT, json={"task": task}) 208 print("create_incremental_task_with_gtid_success resp=", resp.json()) 209 assert resp.status_code == 201 210 211 def create_shard_task_success(): 212 task = { 213 "name": SHARD_TASK_NAME, 214 "task_mode": "all", 215 "shard_mode": "pessimistic", 216 "meta_schema": "dm-meta", 217 "enhance_online_schema_change": True, 218 "on_duplicate": "error", 219 "target_config": { 220 "host": "127.0.0.1", 221 "port": 4000, 222 "user": "root", 223 "password": "", 224 }, 225 "table_migrate_rule": [ 226 { 227 "source": { 228 "source_name": SOURCE1_NAME, 229 "schema": "openapi", 230 "table": "*", 231 }, 232 "target": {"schema": "openapi", "table": "t"}, 233 "binlog_filter_rule": ["rule-1"], 234 }, 235 { 236 "source": { 237 "source_name": SOURCE2_NAME, 238 "schema": "openapi", 239 "table": "*", 240 }, 241 "target": {"schema": "openapi", "table": "t"}, 242 "binlog_filter_rule": ["rule-2"], 243 }, 244 ], 245 "source_config": { 246 "full_migrate_conf": { 247 "export_threads": 4, 248 "import_threads": 16, 249 "data_dir": "./exported_data", 250 "consistency": "auto", 251 }, 252 "incr_migrate_conf": {"repl_threads": 16, "repl_batch": 100}, 253 "source_conf": [ 254 {"source_name": SOURCE1_NAME}, 255 {"source_name": SOURCE2_NAME}, 256 ], 257 }, 258 "binlog_filter_rule": { 259 "rule-1": { 260 "ignore_event": ["delete"], 261 }, 262 "rule-2": { 263 "ignore_sql": ["alter table openapi.t add column aaa int;"], 264 }, 265 }, 266 } 267 resp = requests.post(url=API_ENDPOINT, json={"task": task}) 268 print("create_shard_task_success resp=", resp.json()) 269 assert resp.status_code == 201 270 271 272 def start_task_success(task_name, source_name): 273 url = API_ENDPOINT + "/" + task_name + "/start" 274 req = {} 275 if source_name != "": 276 req = {"source_name_list": [source_name], "remove_meta": True} 277 resp = requests.post(url=url, json=req) 278 if resp.status_code != 200: 279 print("start_task_failed resp=", resp.json()) 280 assert resp.status_code == 200 281 282 def start_task_failed(task_name, source_name, check_result): 283 url = API_ENDPOINT + "/" + task_name + "/start" 284 req = {} 285 if source_name != "": 286 req = {"source_name_list": [source_name], "remove_meta": True} 287 resp = requests.post(url=url, json=req) 288 if resp.status_code == 200: 289 print("start_task_failed resp should not be 200") 290 assert resp.status_code == 400 291 print("start_task_failed resp=", resp.json()) 292 assert check_result in resp.json()["error_msg"] 293 294 def start_task_with_condition(task_name, start_time, duration, is_success, check_result): 295 url = API_ENDPOINT + "/" + task_name + "/start" 296 req = {"start_time": start_time, "safe_mode_time_duration": duration} 297 resp = requests.post(url=url, json=req) 298 if is_success == "success": 299 if resp.status_code != 200: 300 print("start_task_with_condition_failed resp=", resp.json()) 301 assert resp.status_code == 200 302 print("start_task_with_condition success") 303 else: 304 if resp.status_code == 200: 305 print("start_task_with_condition_failed resp should not be 200") 306 assert resp.status_code == 400 307 print("start_task_with_condition resp=", resp.json()) 308 assert check_result in resp.json()["error_msg"] 309 310 def stop_task_with_condition(task_name, source_name, timeout_duration): 311 url = API_ENDPOINT + "/" + task_name + "/stop" 312 req = {"timeout_duration": timeout_duration} 313 if source_name != "": 314 req = {"source_name_list": [source_name], "timeout_duration": timeout_duration} 315 resp = requests.post(url=url, json=req) 316 if resp.status_code != 200: 317 print("stop_task_failed resp=", resp.json()) 318 assert resp.status_code == 200 319 320 def stop_task_success(task_name, source_name): 321 url = API_ENDPOINT + "/" + task_name + "/stop" 322 req = {} 323 if source_name != "": 324 req = {"source_name_list": [source_name]} 325 resp = requests.post(url=url, json=req) 326 if resp.status_code != 200: 327 print("stop_task_failed resp=", resp.json()) 328 assert resp.status_code == 200 329 330 def delete_task_success(task_name): 331 resp = requests.delete(url=API_ENDPOINT + "/" + task_name) 332 assert resp.status_code == 204 333 print("delete_task_success") 334 335 336 def delete_task_failed(task_name): 337 resp = requests.delete(url=API_ENDPOINT + "/" + task_name) 338 print("delete_task_failed resp=", resp.json()) 339 assert resp.status_code == 400 340 341 342 def delete_task_with_force_success(task_name): 343 resp = requests.delete(url=API_ENDPOINT + "/" + task_name + "?force=true") 344 assert resp.status_code == 204 345 print("delete_task_success") 346 347 348 def get_task_status_failed(task_name): 349 url = API_ENDPOINT + "/" + task_name + "/status" 350 resp = requests.get(url=url) 351 print("get_task_status_failed resp=", resp.json()) 352 assert resp.status_code == 400 353 354 355 def get_illegal_char_task_status_failed(): 356 # task name contains illegal char but api server can handle it. 357 # return 400 is because of the task is not started. 358 url = API_ENDPOINT + "/" + ILLEGAL_CHAR_TASK_NAME + "/status" 359 resp = requests.get(url=url) 360 print("get_illegal_char_task_status_failed resp=", resp.json()) 361 assert resp.status_code == 400 362 if sys.version_info.major == 2: 363 # need decode in python2 364 assert ILLEGAL_CHAR_TASK_NAME.decode("utf-8") in resp.json()["error_msg"] 365 else: 366 assert ILLEGAL_CHAR_TASK_NAME in resp.json()["error_msg"] 367 368 369 def get_task_status_success(task_name, total): 370 url = API_ENDPOINT + "/" + task_name + "/status" 371 resp = requests.get(url=url) 372 data = resp.json() 373 assert resp.status_code == 200 374 print("get_task_status_success resp=", data) 375 assert data["total"] == int(total) 376 377 378 def get_task_status_success_but_worker_meet_error(task_name, total): 379 url = API_ENDPOINT + "/" + task_name + "/status" 380 resp = requests.get(url=url) 381 data = resp.json() 382 assert resp.status_code == 200 383 print("get_task_status_success_but_worker_meet_error resp=", data) 384 assert data["total"] == int(total) 385 for status in data["data"]: 386 assert status["name"] == task_name 387 assert status["error_msg"] is not None 388 389 390 def get_task_list(task_count): 391 url = API_ENDPOINT 392 resp = requests.get(url=url) 393 data = resp.json() 394 assert resp.status_code == 200 395 print("get_task_list resp=", data) 396 assert data["total"] == int(task_count) 397 398 399 def get_task_list_with_status(task_count, task_name, status_count): 400 url = API_ENDPOINT + "?with_status=true" 401 resp = requests.get(url=url) 402 data = resp.json() 403 assert resp.status_code == 200 404 print("get_task_list_with_status resp=", data) 405 406 assert data["total"] == int(task_count) 407 find_task = False 408 for task in data["data"]: 409 if task["name"] == task_name: 410 find_task = True 411 assert len(task["status_list"]) == int(status_count) 412 assert find_task 413 414 415 def operate_schema_and_table_success(task_name, source_name, schema_name, table_name): 416 schema_url = API_ENDPOINT + "/" + task_name + "/sources/" + source_name + "/schemas" 417 schema_resp = requests.get(url=schema_url) 418 assert schema_resp.status_code == 200 419 print("get_task_schema_success schema resp=", schema_resp.json()) 420 assert len(schema_resp.json()) > 0 421 422 schema_list = schema_resp.json() 423 assert schema_name in schema_list 424 table_url = schema_url + "/" + schema_name 425 table_resp = requests.get(url=table_url) 426 assert table_resp.status_code == 200 427 print("get_task_schema_success table resp=", table_resp.json()) 428 table_list = table_resp.json() 429 assert table_name in table_list 430 431 single_table_url = table_url + "/" + table_name 432 create_table_resp = requests.get(url=single_table_url) 433 assert create_table_resp.status_code == 200 434 create_table = create_table_resp.json() 435 print("get_task_schema_success create table resp=", create_table) 436 assert create_table["table_name"] == table_name 437 assert create_table["schema_name"] == schema_name 438 assert table_name in create_table["schema_create_sql"] 439 440 # overwrite table 441 set_table_data = { 442 "sql_content": "CREATE TABLE openapi.t1(i TINYINT, j INT UNIQUE KEY);", 443 "flush": True, 444 "sync": True, 445 } 446 resp = requests.put(url=single_table_url, json=set_table_data) 447 assert resp.status_code == 200 448 table_list = requests.get(url=table_url).json() 449 print("get_task_schema_success table resp=", table_list) 450 assert len(table_list) == 1 451 452 453 def create_task_template_success(task_name, target_table_name=""): 454 url = API_ENDPOINT + "/templates" 455 task = { 456 "name": task_name, 457 "task_mode": "all", 458 "shard_mode": "pessimistic", 459 "meta_schema": "dm-meta", 460 "enhance_online_schema_change": True, 461 "on_duplicate": "error", 462 "target_config": { 463 "host": "127.0.0.1", 464 "port": 4000, 465 "user": "root", 466 "password": "", 467 }, 468 "table_migrate_rule": [ 469 { 470 "source": { 471 "source_name": SOURCE1_NAME, 472 "schema": "openapi", 473 "table": "*", 474 }, 475 "target": {"schema": "openapi", "table": target_table_name}, 476 }, 477 { 478 "source": { 479 "source_name": SOURCE2_NAME, 480 "schema": "openapi", 481 "table": "*", 482 }, 483 "target": {"schema": "openapi", "table": target_table_name}, 484 }, 485 ], 486 "source_config": { 487 "source_conf": [ 488 {"source_name": SOURCE1_NAME}, 489 {"source_name": SOURCE2_NAME}, 490 ], 491 }, 492 } 493 resp = requests.post(url=url, json=task) 494 print("create_task_template_success resp=", resp.json()) 495 assert resp.status_code == 201 496 497 498 def create_task_template_failed(): 499 url = API_ENDPOINT + "/templates" 500 task = { 501 "name": "test", 502 "task_mode": "all", 503 "shard_mode": "pessimistic_xxd", # pessimistic_xxd is not a valid shard mode 504 "meta_schema": "dm-meta", 505 "enhance_online_schema_change": True, 506 "on_duplicate": "error", 507 "target_config": { 508 "host": "127.0.0.1", 509 "port": 4000, 510 "user": "root", 511 "password": "", 512 }, 513 "table_migrate_rule": [ 514 { 515 "source": { 516 "source_name": SOURCE1_NAME, 517 "schema": "openapi", 518 "table": "*", 519 }, 520 "target": {"schema": "openapi", "table": "t"}, 521 }, 522 { 523 "source": { 524 "source_name": SOURCE2_NAME, 525 "schema": "openapi", 526 "table": "*", 527 }, 528 "target": {"schema": "openapi", "table": "t"}, 529 }, 530 ], 531 "source_config": { 532 "source_conf": [ 533 {"source_name": SOURCE1_NAME}, 534 {"source_name": SOURCE2_NAME}, 535 ], 536 }, 537 } 538 resp = requests.post(url=url, json=task) 539 print("create_task_template_failed resp=", resp.json()) 540 assert resp.status_code == 400 541 542 543 def list_task_template(count): 544 url = API_ENDPOINT + "/templates" 545 resp = requests.get(url=url) 546 data = resp.json() 547 assert resp.status_code == 200 548 print("list_task_template resp=", data) 549 assert data["total"] == int(count) 550 551 552 def import_task_template(success_count, failed_count): 553 url = API_ENDPOINT + "/templates/import" 554 resp = requests.post(url=url) 555 data = resp.json() 556 print("import_task_template resp=", data) 557 assert resp.status_code == 202 558 assert len(data["success_task_list"]) == int(success_count) 559 assert len(data["failed_task_list"]) == int(failed_count) 560 561 562 def get_task_template(name): 563 url = API_ENDPOINT + "/templates/" + name 564 resp = requests.get(url=url) 565 data = resp.json() 566 assert resp.status_code == 200 567 print("get_task_template resp=", data) 568 assert data["name"] == name 569 570 571 def update_task_template_success(name, task_mode): 572 url = API_ENDPOINT + "/templates/" + name 573 574 # get task template first 575 task = requests.get(url=url).json() 576 task["task_mode"] = task_mode 577 resp = requests.put(url=url, json=task) 578 print("update_task_template_success resp=", resp.json()) 579 assert resp.status_code == 200 580 581 # update task template success 582 assert requests.get(url=url).json()["task_mode"] == task_mode 583 584 585 def delete_task_template(name): 586 url = API_ENDPOINT + "/templates/" + name 587 resp = requests.delete(url=url) 588 assert resp.status_code == 204 589 print("delete_task_template") 590 591 592 def check_noshard_task_dump_status_success(task_name, total): 593 url = API_ENDPOINT + "/" + task_name + "/status" 594 resp = requests.get(url=url) 595 data = resp.json() 596 assert resp.status_code == 200 597 print("check_dump_status_success resp=", data) 598 assert data["data"][0]["dump_status"]["finished_bytes"] == int(total) 599 600 601 def do_complex_operations(task_name): 602 source1_url = "http://127.0.0.1:8361/api/v1/sources/" + SOURCE1_NAME 603 task_url = "http://127.0.0.1:8361/api/v1/tasks/" + task_name 604 enable_source_url = source1_url + "/enable" 605 disable_source_url = source1_url + "/disable" 606 607 stop_url = task_url + "/stop" 608 start_url = task_url + "/start" 609 status_url = task_url + "/status" 610 migrate_targets_url = task_url + "/sources/" + SOURCE1_NAME + "/migrate_targets" 611 612 # get source 613 source = requests.get(source1_url).json() 614 # update source failed 615 update_source_req = {"source": source} 616 resp = requests.put(source1_url, json=update_source_req) 617 assert resp.status_code == 400 618 619 # get task 620 task = requests.get(task_url).json() 621 622 # update task failed 623 update_task_req = {"task": task} 624 resp = requests.put(task_url, json=update_task_req) 625 assert resp.status_code == 400 626 627 # stop task 628 resp = requests.post(stop_url) 629 status = requests.get(status_url).json() 630 for s in status["data"]: 631 assert s["stage"] == "Paused" 632 633 # update task success 634 task["source_config"]["incr_migrate_conf"]["repl_threads"] = 1 635 update_task_req = {"task": task} 636 resp = requests.put(task_url, json=update_task_req) 637 if resp.status_code != 200: 638 print("update task failed", resp.json()) 639 assert resp.status_code == 200 640 task_after_updated = requests.get(task_url).json() 641 assert task_after_updated["source_config"]["incr_migrate_conf"]["repl_threads"] == 1 642 643 # start again 644 resp = requests.post(start_url) 645 if resp.status_code != 200: 646 print("start task failed", resp.json()) 647 assert resp.status_code == 200 648 status = requests.get(status_url).json() 649 for s in status["data"]: 650 assert s["stage"] == "Running" 651 652 # disable source1, subtask on source is paused 653 resp = requests.post(disable_source_url) 654 if resp.status_code != 200: 655 print("disable source failed", resp.json()) 656 assert resp.status_code == 200 657 status = requests.get(status_url).json() 658 for s in status["data"]: 659 if s["source_name"] == SOURCE1_NAME: 660 assert s["stage"] == "Paused" 661 else: 662 assert s["stage"] == "Running" 663 664 # update source1 success 665 source["enable_gtid"] = True 666 source["password"] = "123456" 667 resp = requests.put(source1_url, json=update_source_req) 668 if resp.status_code != 200: 669 print("update source failed", resp.json()) 670 assert resp.status_code == 200 671 672 # enable source task will start again 673 resp = requests.post(enable_source_url) 674 if resp.status_code != 200: 675 print("enable source failed", resp.json()) 676 assert resp.status_code == 200 677 status = requests.get(status_url).json() 678 for s in status["data"]: 679 assert s["stage"] == "Running" 680 681 # list migrate targets 682 source1_migrate_rules = [] 683 for rule in task["table_migrate_rule"]: 684 if rule["source"]["source_name"] == SOURCE1_NAME: 685 source1_migrate_rules.append(rule) 686 687 resp = requests.get(migrate_targets_url) 688 if resp.status_code != 200: 689 print("list migrate targets failed", resp.json()) 690 assert resp.status_code == 200 691 data = resp.json() 692 assert data["total"] == len(source1_migrate_rules) 693 assert ( 694 data["data"][0]["source_schema"] == source1_migrate_rules[0]["source"]["schema"] 695 ) 696 assert ( 697 data["data"][0]["target_schema"] == source1_migrate_rules[0]["target"]["schema"] 698 ) 699 700 701 if __name__ == "__main__": 702 FUNC_MAP = { 703 "create_task_failed": create_task_failed, 704 "create_noshard_task_success": create_noshard_task_success, 705 "create_shard_task_success": create_shard_task_success, 706 "create_incremental_task_with_gtid_success": create_incremental_task_with_gtid_success, 707 "delete_task_failed": delete_task_failed, 708 "delete_task_success": delete_task_success, 709 "delete_task_with_force_success": delete_task_with_force_success, 710 "start_task_success": start_task_success, 711 "start_task_failed": start_task_failed, 712 "start_task_with_condition": start_task_with_condition, 713 "stop_task_with_condition": stop_task_with_condition, 714 "stop_task_success": stop_task_success, 715 "get_task_list": get_task_list, 716 "get_task_list_with_status": get_task_list_with_status, 717 "get_task_status_failed": get_task_status_failed, 718 "get_illegal_char_task_status_failed": get_illegal_char_task_status_failed, 719 "get_task_status_success": get_task_status_success, 720 "get_task_status_success_but_worker_meet_error": get_task_status_success_but_worker_meet_error, 721 "operate_schema_and_table_success": operate_schema_and_table_success, 722 "create_task_template_success": create_task_template_success, 723 "create_task_template_failed": create_task_template_failed, 724 "list_task_template": list_task_template, 725 "import_task_template": import_task_template, 726 "get_task_template": get_task_template, 727 "update_task_template_success": update_task_template_success, 728 "delete_task_template": delete_task_template, 729 "check_noshard_task_dump_status_success": check_noshard_task_dump_status_success, 730 "do_complex_operations": do_complex_operations, 731 "create_task_with_precheck": create_task_with_precheck, 732 } 733 734 func = FUNC_MAP[sys.argv[1]] 735 if len(sys.argv) >= 2: 736 func(*sys.argv[2:]) 737 else: 738 func()