From b39e0c82ec073d62333265ac8e28e7ab45e4fb01 Mon Sep 17 00:00:00 2001 From: doubletao318 Date: Mon, 12 Apr 2021 23:25:31 -0700 Subject: [PATCH] DME Storage 1.0.0 CP3 --- config/global.yml | 14 +- .../storage/oceanstor/get_lgs_by_host.yml | 9 + playbook/storage/oceanstor/get_luns_by_pg.yml | 9 + .../storage/oceanstor/get_snapshots_by_cg.yml | 9 + rundeck/jobs/project001/01_create_host.yaml | 2 +- .../jobs/project001/03_modify_host_info.yaml | 2 +- .../jobs/project001/05_create_cluster.yaml | 2 +- .../project001/07_modify_cluster_info.yaml | 6 +- .../project001/09_create_luns_for_host.yaml | 4 +- .../jobs/project001/10_map_luns_to_host.yaml | 2 + .../project001/11_unmap_luns_from_host.yaml | 3 + .../13_create_luns_for_cluster.yaml | 4 +- .../project001/14_map_luns_to_cluster.yaml | 2 + .../15_unmap_luns_from_cluster.yaml | 3 + rundeck/jobs/project001/16_delete_lun.yaml | 3 + rundeck/jobs/project001/17_change_class.yaml | 3 + rundeck/jobs/project001/18_remove_class.yaml | 3 + rundeck/jobs/project001/21_create_luns.yaml | 6 +- .../project001/23_resize_luns_for_host.yaml | 3 + .../24_resize_luns_for_cluster.yaml | 3 + .../25_create_snapshots_for_host.yaml | 4 +- .../26_change_snapshots_for_host.yaml | 2 + .../27_create_snapshots_for_cluster.yaml | 4 +- .../28_change_snapshots_for_cluster.yaml | 2 + .../jobs/project001/29_dr_test_for_host.yaml | 5 +- .../project001/30_dr_test_clean_for_host.yaml | 5 +- .../project001/31_dr_test_for_cluster.yaml | 5 +- .../32_dr_test_clean_for_cluster.yaml | 5 +- .../project001/33_add_lun_group_to_host.yaml | 2 +- .../35_add_lun_group_to_cluster.yaml | 2 +- .../jobs/project001/37_add_replica_host.yaml | 2 + .../jobs/project001/38_add_drtest_host.yaml | 2 + .../jobs/project001/39_add_metro_host.yaml | 2 + .../project001/40_remove_replica_host.yaml | 2 + .../project001/41_add_replica_cluster.yaml | 2 + .../project001/42_add_drtest_cluster.yaml | 2 + .../jobs/project001/43_add_metro_cluster.yaml | 2 + .../project001/44_remove_replica_cluster.yaml | 2 + rundeck/jobs/project001/45_migrate_host.yaml | 2 + .../jobs/project001/46_migrate_cluster.yaml | 2 + rundeck/jobs/project001/49_create_pg.yaml | 157 + .../jobs/project001/50_add_luns_to_pg.yaml | 347 ++ .../project001/51_remove_luns_from_pg.yaml | 236 ++ rundeck/jobs/project001/52_delete_pg.yaml | 158 + .../jobs/project001/53_dr_test_for_pg.yaml | 150 + .../project001/54_dr_test_clean_for_pg.yaml | 150 + .../project001/55_dr_test_for_multi_host.yaml | 121 + .../56_dr_test_clean_for_multi_host.yaml | 119 + .../57_dr_test_for_multi_cluster.yaml | 124 + .../58_dr_test_clean_for_multi_cluster.yaml | 120 + rundeck/projects/project001/readme.md | 71 + .../webapp/project001/images/49_create_pg.png | Bin 0 -> 14747 bytes .../project001/images/50_add_luns_to_pg.png | Bin 0 -> 15280 bytes .../images/51_remove_luns_from_pg.png | Bin 0 -> 14534 bytes .../webapp/project001/images/52_delete_pg.png | Bin 0 -> 15125 bytes .../project001/images/53_dr_test_for_pg.png | Bin 0 -> 14730 bytes .../images/54_dr_test_clean_for_pg.png | Bin 0 -> 15322 bytes .../images/55_dr_test_for_multi_host.png | Bin 0 -> 24377 bytes .../56_dr_test_clean_for_multi_host.png | Bin 0 -> 24956 bytes .../images/57_dr_test_for_multi_cluster.png | Bin 0 -> 25827 bytes .../58_dr_test_clean_for_multi_cluster.png | Bin 0 -> 26309 bytes .../workflow/project001/01_create_host.yml | 2 +- .../project001/03_modify_host_info.yml | 236 +- .../workflow/project001/05_create_cluster.yml | 2 +- .../project001/07_modify_cluster_info.yml | 247 +- .../project001/09_create_luns_for_host.yml | 78 +- .../project001/10_map_luns_to_host.yml | 124 +- .../project001/11_unmap_luns_from_host.yml | 10 - .../project001/13_create_luns_for_cluster.yml | 80 +- .../project001/14_map_luns_to_cluster.yml | 126 +- .../project001/15_unmap_luns_from_cluster.yml | 10 - .../workflow/project001/21_create_luns.yml | 6 +- .../25_create_snapshots_for_host.yml | 12 +- .../27_create_snapshots_for_cluster.yml | 14 +- .../project001/29_dr_test_for_host.yml | 5 - .../project001/30_dr_test_clean_for_host.yml | 24 +- .../project001/31_dr_test_for_cluster.yml | 5 - .../32_dr_test_clean_for_cluster.yml | 24 +- .../project001/33_add_lun_group_to_host.yml | 2 +- .../35_add_lun_group_to_cluster.yml | 2 +- .../project001/37_add_replica_host.yml | 140 +- .../project001/38_add_drtest_host.yml | 176 +- .../workflow/project001/39_add_metro_host.yml | 168 +- .../project001/40_remove_replica_host.yml | 208 +- .../project001/41_add_replica_cluster.yml | 140 +- .../project001/42_add_drtest_cluster.yml | 176 +- .../project001/43_add_metro_cluster.yml | 168 +- .../project001/44_remove_replica_cluster.yml | 208 +- .../workflow/project001/45_migrate_host.yml | 8 +- .../project001/46_migrate_cluster.yml | 8 +- rundeck/workflow/project001/49_create_pg.yml | 814 ++++ .../workflow/project001/50_add_luns_to_pg.yml | 3381 +++++++++++++++++ .../project001/51_remove_luns_from_pg.yml | 2997 +++++++++++++++ rundeck/workflow/project001/52_delete_pg.yml | 915 +++++ .../workflow/project001/53_dr_test_for_pg.yml | 456 +++ .../project001/54_dr_test_clean_for_pg.yml | 737 ++++ .../project001/55_dr_test_for_multi_host.yml | 544 +++ .../56_dr_test_clean_for_multi_host.yml | 844 ++++ .../57_dr_test_for_multi_cluster.yml | 614 +++ .../58_dr_test_clean_for_multi_cluster.yml | 921 +++++ .../50_exsit_dr_test_luns_loop_helper.yml | 34 + .../56_target_luns_loop_helper.yml | 40 + .../project001/update_lun_kpi_table.yml | 15 +- service/dj_data_service.py | 397 +- service/dm_data_service.py | 383 +- service/log.py | 46 + task/cmdb/list_instances.yml | 57 +- task/storage/oceanstor/activate_snapshots.yml | 2 +- .../oceanstor/add_luns_to_clone_cg.yml | 2 +- task/storage/oceanstor/add_luns_to_lg.yml | 2 +- task/storage/oceanstor/add_luns_to_pg.yml | 7 +- task/storage/oceanstor/add_ports_to_host.yml | 2 +- task/storage/oceanstor/check_host_lun_id.yml | 2 +- .../check_host_lun_id_loop_helper.yml | 14 + task/storage/oceanstor/check_host_wwns.yml | 66 + task/storage/oceanstor/check_hosts.yml | 2 +- task/storage/oceanstor/check_luns.yml | 2 +- task/storage/oceanstor/create_clone.yml | 2 +- task/storage/oceanstor/create_drstar.yml | 6 +- task/storage/oceanstor/create_lg.yml | 2 +- task/storage/oceanstor/create_pg.yml | 39 +- task/storage/oceanstor/create_snapshot_cg.yml | 21 +- task/storage/oceanstor/create_snapshots.yml | 2 +- .../oceanstor/deactivate_snapshots.yml | 2 +- task/storage/oceanstor/delete_host.yml | 2 +- task/storage/oceanstor/delete_lg.yml | 4 +- task/storage/oceanstor/delete_luns.yml | 4 +- task/storage/oceanstor/delete_snapshots.yml | 2 +- task/storage/oceanstor/enable_drstar.yml | 6 +- task/storage/oceanstor/get_lgs_by_host.yml | 52 + task/storage/oceanstor/get_luns_by_pg.yml | 52 + .../oceanstor/get_replication_cgs_by_pg.yml | 54 + .../get_replication_cgs_by_pg_loop_helper.yml | 14 + .../oceanstor/get_snapshot_cgs_by_pg.yml | 58 + .../get_snapshot_cgs_by_pg_loop_helper.yml | 16 + .../storage/oceanstor/get_snapshots_by_cg.yml | 50 + task/storage/oceanstor/map_luns_to_host.yml | 4 +- .../oceanstor/map_luns_to_hostgroup.yml | 4 +- task/storage/oceanstor/modify_host.yml | 2 +- task/storage/oceanstor/modify_lun.yml | 2 +- .../oceanstor/remove_luns_from_clone_cg.yml | 2 +- .../storage/oceanstor/remove_luns_from_lg.yml | 2 +- .../storage/oceanstor/remove_luns_from_pg.yml | 3 +- .../oceanstor/remove_ports_from_host.yml | 2 +- .../oceanstor/unmap_luns_from_host.yml | 4 +- task/task/wait_task_complete.yml | 12 +- task/util/csv2xml.py | 50 + task/util/csv2xml.yml | 12 + task/volume/remove_volumes_from_tier.yml | 1 + 149 files changed, 15753 insertions(+), 2341 deletions(-) create mode 100644 playbook/storage/oceanstor/get_lgs_by_host.yml create mode 100644 playbook/storage/oceanstor/get_luns_by_pg.yml create mode 100644 playbook/storage/oceanstor/get_snapshots_by_cg.yml create mode 100644 rundeck/jobs/project001/49_create_pg.yaml create mode 100644 rundeck/jobs/project001/50_add_luns_to_pg.yaml create mode 100644 rundeck/jobs/project001/51_remove_luns_from_pg.yaml create mode 100644 rundeck/jobs/project001/52_delete_pg.yaml create mode 100644 rundeck/jobs/project001/53_dr_test_for_pg.yaml create mode 100644 rundeck/jobs/project001/54_dr_test_clean_for_pg.yaml create mode 100644 rundeck/jobs/project001/55_dr_test_for_multi_host.yaml create mode 100644 rundeck/jobs/project001/56_dr_test_clean_for_multi_host.yaml create mode 100644 rundeck/jobs/project001/57_dr_test_for_multi_cluster.yaml create mode 100644 rundeck/jobs/project001/58_dr_test_clean_for_multi_cluster.yaml create mode 100644 rundeck/webapp/project001/images/49_create_pg.png create mode 100644 rundeck/webapp/project001/images/50_add_luns_to_pg.png create mode 100644 rundeck/webapp/project001/images/51_remove_luns_from_pg.png create mode 100644 rundeck/webapp/project001/images/52_delete_pg.png create mode 100644 rundeck/webapp/project001/images/53_dr_test_for_pg.png create mode 100644 rundeck/webapp/project001/images/54_dr_test_clean_for_pg.png create mode 100644 rundeck/webapp/project001/images/55_dr_test_for_multi_host.png create mode 100644 rundeck/webapp/project001/images/56_dr_test_clean_for_multi_host.png create mode 100644 rundeck/webapp/project001/images/57_dr_test_for_multi_cluster.png create mode 100644 rundeck/webapp/project001/images/58_dr_test_clean_for_multi_cluster.png create mode 100644 rundeck/workflow/project001/49_create_pg.yml create mode 100644 rundeck/workflow/project001/50_add_luns_to_pg.yml create mode 100644 rundeck/workflow/project001/51_remove_luns_from_pg.yml create mode 100644 rundeck/workflow/project001/52_delete_pg.yml create mode 100644 rundeck/workflow/project001/53_dr_test_for_pg.yml create mode 100644 rundeck/workflow/project001/54_dr_test_clean_for_pg.yml create mode 100644 rundeck/workflow/project001/55_dr_test_for_multi_host.yml create mode 100644 rundeck/workflow/project001/56_dr_test_clean_for_multi_host.yml create mode 100644 rundeck/workflow/project001/57_dr_test_for_multi_cluster.yml create mode 100644 rundeck/workflow/project001/58_dr_test_clean_for_multi_cluster.yml create mode 100644 rundeck/workflow/project001/loop_helper/50_exsit_dr_test_luns_loop_helper.yml create mode 100644 rundeck/workflow/project001/loop_helper/56_target_luns_loop_helper.yml create mode 100644 service/log.py create mode 100644 task/storage/oceanstor/check_host_lun_id_loop_helper.yml create mode 100644 task/storage/oceanstor/check_host_wwns.yml create mode 100644 task/storage/oceanstor/get_lgs_by_host.yml create mode 100644 task/storage/oceanstor/get_luns_by_pg.yml create mode 100644 task/storage/oceanstor/get_replication_cgs_by_pg.yml create mode 100644 task/storage/oceanstor/get_replication_cgs_by_pg_loop_helper.yml create mode 100644 task/storage/oceanstor/get_snapshot_cgs_by_pg.yml create mode 100644 task/storage/oceanstor/get_snapshot_cgs_by_pg_loop_helper.yml create mode 100644 task/storage/oceanstor/get_snapshots_by_cg.yml create mode 100644 task/util/csv2xml.py create mode 100644 task/util/csv2xml.yml diff --git a/config/global.yml b/config/global.yml index 9bf7bb4..28c584e 100644 --- a/config/global.yml +++ b/config/global.yml @@ -27,7 +27,7 @@ GLOBAL: recoveryPolicy: 1 # 1/automatic, 2/manual syncSpeed: 2 # 1/low, 2/medium, 3/high, 4/highest syncType: 3 # 1/manual, 2/wait after last sync begin, 3/wait after last sync ends - interval: 60 # synchronize interval in seconds (10 ~ 86400) + interval: 30 # synchronize interval in seconds (10 ~ 86400) compress: False # enable compress for async replication: true, false timeout: 10 # synchronize remote I/O timeout threshold in seconds, default: 10, options: 10~30, or set to 255 to disable timeout syncRetries: 3600 # 5 hours @@ -83,19 +83,13 @@ DMDATASERVICE: # DeviceManager Data service configuratio DEFAULT: noneName: "NONE" noneValue: "__NONE__" - suffixDigits: 3 + suffixDigits: 4 LOGGING: - format: '[%(asctime)s] %(levelname)-5s - %(message)s' + format: '%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s' datefmt: '%Y-%m-%d %H:%M:%S' level: 'DEBUG' - levelno: - CRITICAL: 50 - ERROR: 40 - WARNING: 30 - INFO: 20 - DEBUG: 10 - NOTSET: 0 + path: "/var/lib/rundeck/logs/data_service" DJSERVICE: API: diff --git a/playbook/storage/oceanstor/get_lgs_by_host.yml b/playbook/storage/oceanstor/get_lgs_by_host.yml new file mode 100644 index 0000000..b848de1 --- /dev/null +++ b/playbook/storage/oceanstor/get_lgs_by_host.yml @@ -0,0 +1,9 @@ +- name: Delete LUN Group + hosts: localhost + vars_files: + - ../../../config/global.yml + gather_facts: no + become: no + tasks: + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_lgs_by_host.yml" diff --git a/playbook/storage/oceanstor/get_luns_by_pg.yml b/playbook/storage/oceanstor/get_luns_by_pg.yml new file mode 100644 index 0000000..5bdbbe8 --- /dev/null +++ b/playbook/storage/oceanstor/get_luns_by_pg.yml @@ -0,0 +1,9 @@ +- name: Delete LUN Group + hosts: localhost + vars_files: + - ../../../config/global.yml + gather_facts: no + become: no + tasks: + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_luns_by_pg.yml" \ No newline at end of file diff --git a/playbook/storage/oceanstor/get_snapshots_by_cg.yml b/playbook/storage/oceanstor/get_snapshots_by_cg.yml new file mode 100644 index 0000000..b9ae486 --- /dev/null +++ b/playbook/storage/oceanstor/get_snapshots_by_cg.yml @@ -0,0 +1,9 @@ +- name: Delete LUN Group + hosts: localhost + vars_files: + - ../../../config/global.yml + gather_facts: no + become: no + tasks: + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_snapshots_by_cg.yml" \ No newline at end of file diff --git a/rundeck/jobs/project001/01_create_host.yaml b/rundeck/jobs/project001/01_create_host.yaml index 95a9dd2..5cb1900 100644 --- a/rundeck/jobs/project001/01_create_host.yaml +++ b/rundeck/jobs/project001/01_create_host.yaml @@ -69,7 +69,7 @@ valuesUrl: http://localhost:26336/rest/data/v1/echo?host=1.%20Create%20host:%20"${option.Country.value}_${option.OS_Type.value}_${option.Host_Name.value}_1"%20on%20storage:%20"${option.Storage.value}"&wwn=2.%20Add%20WWNs%20"${option.Host_WWN.value}"%20to%20host:%20"${option.Country.value}_${option.OS_Type.value}_${option.Host_Name.value}_1"&cluster=3.%20Add%20host%20"${option.Country.value}_${option.OS_Type.value}_${option.Host_Name.value}_1"%20to%20cluster:%20"${option.Cluster_Name.value}" description: Select to confirm primary host result

Protection Settings

- name: Session_Name - description: Input Session Name used for CGs (optional, max 16 chars, '_' not permitted, default is Primary Host Name) + description: Input Session Name used for CGs (optional, max 13 chars, '_' not permitted, default is Primary Host Name) - enforced: true name: Enable_HyperMetro value: 'N' diff --git a/rundeck/jobs/project001/03_modify_host_info.yaml b/rundeck/jobs/project001/03_modify_host_info.yaml index d1465b5..71eb3b8 100644 --- a/rundeck/jobs/project001/03_modify_host_info.yaml +++ b/rundeck/jobs/project001/03_modify_host_info.yaml @@ -67,7 +67,7 @@ name: Session_Name valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=DESCRIPTION:1&nameSplit=_&valueAttr=DESCRIPTION:1&valueSplit=_&filter=NAME::${option.LUN_Group.value} - name: Modify_Session_Name - description: Input New Session Name (start with {Country}0, max 19 chars, '_' not permitted)

Performance Class

+ description: Input New Session Name (start with {Country}0, max 16 chars, '_' not permitted)

Performance Class

- enforced: true name: Class_1 valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=DESCRIPTION:0:2:3&valueAttr=DESCRIPTION:0:2:3&filter=NAME::${option.LUN_Group.value} diff --git a/rundeck/jobs/project001/05_create_cluster.yaml b/rundeck/jobs/project001/05_create_cluster.yaml index c142ca9..1438712 100644 --- a/rundeck/jobs/project001/05_create_cluster.yaml +++ b/rundeck/jobs/project001/05_create_cluster.yaml @@ -62,7 +62,7 @@ valuesUrl: http://localhost:26336/rest/data/v1/echo?cluster=1.%20Create%20cluster:%20"${option.Country.value}_${option.OS_Type.value}_${option.Cluster_Name.value}_1"%20on%20storage:%20"${option.Storage.value}"&hosts=2.%20Add%20hosts%20"${option.Hosts.value}"%20to%20cluster:%20"${option.Country.value}_${option.OS_Type.value}_${option.Cluster_Name.value}_1" description: Select to confirm primary cluster result

Protection Settings

- name: Session_Name - description: Input Session Name used for grouping (optional, max 16 chars, '_' not permitted, default is Primary Cluster Name) + description: Input Session Name used for grouping (optional, max 13 chars, '_' not permitted, default is Primary Cluster Name) - enforced: true name: Enable_HyperMetro value: 'N' diff --git a/rundeck/jobs/project001/07_modify_cluster_info.yaml b/rundeck/jobs/project001/07_modify_cluster_info.yaml index 20156b6..4f17229 100644 --- a/rundeck/jobs/project001/07_modify_cluster_info.yaml +++ b/rundeck/jobs/project001/07_modify_cluster_info.yaml @@ -70,7 +70,7 @@ name: Session_Name valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=DESCRIPTION:1&nameSplit=_&valueAttr=DESCRIPTION:1&valueSplit=_&filter=NAME::${option.LUN_Group.value} - name: Modify_Session_Name - description: Input New Session Name (start with {Country}0, max 19 chars, '_' not permitted)

Performance Class

+ description: Input New Session Name (start with {Country}0, max 16 chars, '_' not permitted)

Performance Class

- enforced: true name: Class_1 valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=DESCRIPTION:0:2:3&valueAttr=DESCRIPTION:0:2:3&filter=NAME::${option.LUN_Group.value} @@ -225,12 +225,12 @@ Modify_DR_Cluster_Description: ${option.Modify_DR_Cluster_Description} Class_2: ${option.Class_2} Modify_Class_2: ${option.Modify_Class_2} - Check_Result_3: ${option.Check_Result_2} + Check_Result_3: ${option.Check_Result_3} DR_Test_Cluster: ${option.DR_Test_Cluster} Class_3: ${option.Class_3} Modify_Class_3: ${option.Modify_Class_3} - Check_Result_4: ${option.Check_Result_3} + Check_Result_4: ${option.Check_Result_4} Protection_Group: ${option.Protection_Group} Metro_CG: ${option.Metro_CG} diff --git a/rundeck/jobs/project001/09_create_luns_for_host.yaml b/rundeck/jobs/project001/09_create_luns_for_host.yaml index 7384826..4d40732 100644 --- a/rundeck/jobs/project001/09_create_luns_for_host.yaml +++ b/rundeck/jobs/project001/09_create_luns_for_host.yaml @@ -55,7 +55,7 @@ - delimiter: '|' multivalued: true name: LUN_Description - description: Add LUN Description (optional, max 200 chars for all items, '|' and '_' not permitted) + description: Add LUN Description (optional, max 255 chars for all items, '|' and '_' not permitted) - enforced: true name: Pool valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/storagepool?nameAttr=NAME&valueAttr=ID @@ -221,6 +221,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + Host: ${option.Host} Storage: ${option.Storage} Storage_Room: ${option.Storage_Room} diff --git a/rundeck/jobs/project001/10_map_luns_to_host.yaml b/rundeck/jobs/project001/10_map_luns_to_host.yaml index 111d3a6..c233a2a 100644 --- a/rundeck/jobs/project001/10_map_luns_to_host.yaml +++ b/rundeck/jobs/project001/10_map_luns_to_host.yaml @@ -220,6 +220,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + Host: ${option.Host} Storage: ${option.Storage} Storage_Room: ${option.Storage_Room} diff --git a/rundeck/jobs/project001/11_unmap_luns_from_host.yaml b/rundeck/jobs/project001/11_unmap_luns_from_host.yaml index e9327f5..8e70352 100644 --- a/rundeck/jobs/project001/11_unmap_luns_from_host.yaml +++ b/rundeck/jobs/project001/11_unmap_luns_from_host.yaml @@ -182,6 +182,9 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + Host: ${option.Host} Storage: ${option.Storage} Storage_Room: ${option.Storage_Room} diff --git a/rundeck/jobs/project001/13_create_luns_for_cluster.yaml b/rundeck/jobs/project001/13_create_luns_for_cluster.yaml index 9d3487e..a371505 100644 --- a/rundeck/jobs/project001/13_create_luns_for_cluster.yaml +++ b/rundeck/jobs/project001/13_create_luns_for_cluster.yaml @@ -58,7 +58,7 @@ - delimiter: '|' multivalued: true name: LUN_Description - description: Add LUN Description (optional, max 200 chars for all items, '|' and '_' not permitted) + description: Add LUN Description (optional, max 255 chars for all items, '|' and '_' not permitted) - enforced: true name: Pool valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/storagepool?nameAttr=NAME&valueAttr=ID @@ -224,6 +224,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + Cluster: ${option.Cluster} Storage: ${option.Storage} Storage_Room: ${option.Storage_Room} diff --git a/rundeck/jobs/project001/14_map_luns_to_cluster.yaml b/rundeck/jobs/project001/14_map_luns_to_cluster.yaml index a029385..5b97054 100644 --- a/rundeck/jobs/project001/14_map_luns_to_cluster.yaml +++ b/rundeck/jobs/project001/14_map_luns_to_cluster.yaml @@ -223,6 +223,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + Cluster: ${option.Cluster} Storage: ${option.Storage} Storage_Room: ${option.Storage_Room} diff --git a/rundeck/jobs/project001/15_unmap_luns_from_cluster.yaml b/rundeck/jobs/project001/15_unmap_luns_from_cluster.yaml index 9a0ce92..e3f50b7 100644 --- a/rundeck/jobs/project001/15_unmap_luns_from_cluster.yaml +++ b/rundeck/jobs/project001/15_unmap_luns_from_cluster.yaml @@ -185,6 +185,9 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + Cluster: ${option.Cluster} Storage: ${option.Storage} Storage_Room: ${option.Storage_Room} diff --git a/rundeck/jobs/project001/16_delete_lun.yaml b/rundeck/jobs/project001/16_delete_lun.yaml index cf7a80a..bd2d640 100644 --- a/rundeck/jobs/project001/16_delete_lun.yaml +++ b/rundeck/jobs/project001/16_delete_lun.yaml @@ -62,6 +62,9 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + Select_LUN: ${option.Select_LUN} Upload_LUN: ${file.Upload_LUN} Delimiter: "${option.Delimiter}" diff --git a/rundeck/jobs/project001/17_change_class.yaml b/rundeck/jobs/project001/17_change_class.yaml index 464c087..8e595c3 100644 --- a/rundeck/jobs/project001/17_change_class.yaml +++ b/rundeck/jobs/project001/17_change_class.yaml @@ -55,6 +55,9 @@ ansible-extra-vars: |- WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + Select_LUN: ${option.Select_LUN} Upload_LUN: ${file.Upload_LUN} Delimiter: "${option.Delimiter}" diff --git a/rundeck/jobs/project001/18_remove_class.yaml b/rundeck/jobs/project001/18_remove_class.yaml index 0c06519..78138ab 100644 --- a/rundeck/jobs/project001/18_remove_class.yaml +++ b/rundeck/jobs/project001/18_remove_class.yaml @@ -57,6 +57,9 @@ ansible-extra-vars: |- WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + Select_LUN: ${option.Select_LUN} Upload_LUN: ${file.Upload_LUN} Delimiter: "${option.Delimiter}" diff --git a/rundeck/jobs/project001/21_create_luns.yaml b/rundeck/jobs/project001/21_create_luns.yaml index 214f6d4..239f822 100644 --- a/rundeck/jobs/project001/21_create_luns.yaml +++ b/rundeck/jobs/project001/21_create_luns.yaml @@ -49,7 +49,7 @@ type: file - name: Delimiter value: ',' - - description: Maximum 28 characters, LUN naming convension is {COUNTRY:2} _ {OS:3} _ {HOST:16} _ {CHAIN:1} _ {PROTECT:1}{TYPE:1}{Suffix:3} + - description: Maximum 28 characters, LUN naming convension is {COUNTRY:2} _ {OS:3} _ {HOST:16} _ {CHAIN:1} _ {PROTECT:1}{TYPE:1}{Suffix:4} name: LUN_Prefix - name: Start_Suffix value: 0 @@ -60,7 +60,7 @@ - delimiter: '|' multivalued: true name: LUN_Description - description: Add LUN Description (optional, max 200 chars for all items, '|' and '_' not permitted) + description: Add LUN Description (optional, max 255 chars for all items, '|' and '_' not permitted) scheduleEnabled: true sequence: commands: @@ -73,6 +73,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Job_User: ${job.user.name} + Site: ${option.Site} Room: ${option.Room} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/23_resize_luns_for_host.yaml b/rundeck/jobs/project001/23_resize_luns_for_host.yaml index e14b274..5095809 100644 --- a/rundeck/jobs/project001/23_resize_luns_for_host.yaml +++ b/rundeck/jobs/project001/23_resize_luns_for_host.yaml @@ -173,6 +173,9 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + Host: ${option.Host} Storage: ${option.Storage} Storage_Room: ${option.Storage_Room} diff --git a/rundeck/jobs/project001/24_resize_luns_for_cluster.yaml b/rundeck/jobs/project001/24_resize_luns_for_cluster.yaml index 4f562e1..0d0435b 100644 --- a/rundeck/jobs/project001/24_resize_luns_for_cluster.yaml +++ b/rundeck/jobs/project001/24_resize_luns_for_cluster.yaml @@ -176,6 +176,9 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + Cluster: ${option.Cluster} Storage: ${option.Storage} Storage_Room: ${option.Storage_Room} diff --git a/rundeck/jobs/project001/25_create_snapshots_for_host.yaml b/rundeck/jobs/project001/25_create_snapshots_for_host.yaml index 8cf3224..842f11f 100644 --- a/rundeck/jobs/project001/25_create_snapshots_for_host.yaml +++ b/rundeck/jobs/project001/25_create_snapshots_for_host.yaml @@ -70,7 +70,7 @@ - delimiter: '|' multivalued: true name: Snapshot_Description - description: Add Snapshot Description (optional, max 200 chars for all items, '|' and '_' not permitted) + description: Add Snapshot Description (optional, max 255 chars for all items, '|' and '_' not permitted) - enforced: true name: Class_1 valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=DESCRIPTION:0:2:3&valueAttr=DESCRIPTION:0:2:3&filter=NAME::${option.Target_LUN_Group.value} @@ -106,6 +106,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + Target_Host: ${option.Target_Host} Target_LUN_Group: ${option.Target_LUN_Group} Target_HyperMetro: ${option.Target_HyperMetro} diff --git a/rundeck/jobs/project001/26_change_snapshots_for_host.yaml b/rundeck/jobs/project001/26_change_snapshots_for_host.yaml index 43cea67..9761ac0 100644 --- a/rundeck/jobs/project001/26_change_snapshots_for_host.yaml +++ b/rundeck/jobs/project001/26_change_snapshots_for_host.yaml @@ -107,6 +107,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + Target_Host: ${option.Target_Host} Target_LUN_Group: ${option.Target_LUN_Group} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/27_create_snapshots_for_cluster.yaml b/rundeck/jobs/project001/27_create_snapshots_for_cluster.yaml index 1a40c4f..34e6f0a 100644 --- a/rundeck/jobs/project001/27_create_snapshots_for_cluster.yaml +++ b/rundeck/jobs/project001/27_create_snapshots_for_cluster.yaml @@ -76,7 +76,7 @@ - delimiter: '|' multivalued: true name: Snapshot_Description - description: Add Snapshot Description (optional, max 200 chars for all items, '|' and '_' not permitted) + description: Add Snapshot Description (optional, max 255 chars for all items, '|' and '_' not permitted) - enforced: true name: Class_1 valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=DESCRIPTION:0:2:3&valueAttr=DESCRIPTION:0:2:3&filter=NAME::${option.Target_LUN_Group.value} @@ -112,6 +112,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + Target_Cluster: ${option.Target_Cluster} Target_LUN_Group: ${option.Target_LUN_Group} Target_HyperMetro: ${option.Target_HyperMetro} diff --git a/rundeck/jobs/project001/28_change_snapshots_for_cluster.yaml b/rundeck/jobs/project001/28_change_snapshots_for_cluster.yaml index c2e094c..7d5e8a2 100644 --- a/rundeck/jobs/project001/28_change_snapshots_for_cluster.yaml +++ b/rundeck/jobs/project001/28_change_snapshots_for_cluster.yaml @@ -110,6 +110,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + Target_Cluster: ${option.Target_Cluster} Target_LUN_Group: ${option.Target_LUN_Group} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/29_dr_test_for_host.yaml b/rundeck/jobs/project001/29_dr_test_for_host.yaml index 9fa8795..a9c8494 100644 --- a/rundeck/jobs/project001/29_dr_test_for_host.yaml +++ b/rundeck/jobs/project001/29_dr_test_for_host.yaml @@ -85,7 +85,7 @@ valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/protectgroup?nameAttr=protectGroupName&valueAttr=protectGroupName&range=[0-100]&filter=lunGroupId::${option.DR_LUN_Group_ID.value} - enforced: true name: DR_CG - valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.DR_Protection_Group.value}%20and%20RUNNINGSTATUS::1 + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.DR_Protection_Group.value} - enforced: true name: DR_Test_CG_ID valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=ID&valueAttr=ID&filter=NAME::${option.DR_Test_CG.value} @@ -104,6 +104,9 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + DR_Host: ${option.DR_Host} DR_Test_Host: ${option.DR_Test_Host} DR_Storage: ${option.DR_Storage} diff --git a/rundeck/jobs/project001/30_dr_test_clean_for_host.yaml b/rundeck/jobs/project001/30_dr_test_clean_for_host.yaml index 6fd4131..6a243b6 100644 --- a/rundeck/jobs/project001/30_dr_test_clean_for_host.yaml +++ b/rundeck/jobs/project001/30_dr_test_clean_for_host.yaml @@ -80,7 +80,7 @@ valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/protectgroup?nameAttr=protectGroupName&valueAttr=protectGroupName&range=[0-100]&filter=lunGroupId::${option.DR_LUN_Group_ID.value} - enforced: true name: DR_CG - valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.DR_Protection_Group.value}%20and%20RUNNINGSTATUS::1 + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.DR_Protection_Group.value} - enforced: true name: DR_Test_CG_ID valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=ID&valueAttr=ID&filter=NAME::${option.DR_Test_CG.value} @@ -102,6 +102,9 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + DR_Host: ${option.DR_Host} DR_Test_Host: ${option.DR_Test_Host} DR_Storage: ${option.DR_Storage} diff --git a/rundeck/jobs/project001/31_dr_test_for_cluster.yaml b/rundeck/jobs/project001/31_dr_test_for_cluster.yaml index 7ef9bfd..c81b366 100644 --- a/rundeck/jobs/project001/31_dr_test_for_cluster.yaml +++ b/rundeck/jobs/project001/31_dr_test_for_cluster.yaml @@ -88,7 +88,7 @@ valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/protectgroup?nameAttr=protectGroupName&valueAttr=protectGroupName&range=[0-100]&filter=lunGroupId::${option.DR_LUN_Group_ID.value} - enforced: true name: DR_CG - valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.DR_Protection_Group.value}%20and%20RUNNINGSTATUS::1 + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.DR_Protection_Group.value} - enforced: true name: DR_Test_CG_ID valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=ID&valueAttr=ID&filter=NAME::${option.DR_Test_CG.value} @@ -107,6 +107,9 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + DR_Cluster: ${option.DR_Cluster} DR_Test_Cluster: ${option.DR_Test_Cluster} DR_Storage: ${option.DR_Storage} diff --git a/rundeck/jobs/project001/32_dr_test_clean_for_cluster.yaml b/rundeck/jobs/project001/32_dr_test_clean_for_cluster.yaml index 07979ae..be8e312 100644 --- a/rundeck/jobs/project001/32_dr_test_clean_for_cluster.yaml +++ b/rundeck/jobs/project001/32_dr_test_clean_for_cluster.yaml @@ -83,7 +83,7 @@ valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/protectgroup?nameAttr=protectGroupName&valueAttr=protectGroupName&range=[0-100]&filter=lunGroupId::${option.DR_LUN_Group_ID.value} - enforced: true name: DR_CG - valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.DR_Protection_Group.value}%20and%20RUNNINGSTATUS::1 + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.DR_Protection_Group.value} - enforced: true name: DR_Test_CG_ID valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=ID&valueAttr=ID&filter=NAME::${option.DR_Test_CG.value} @@ -105,6 +105,9 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + DR_Cluster: ${option.DR_Cluster} DR_Test_Cluster: ${option.DR_Test_Cluster} DR_Storage: ${option.DR_Storage} diff --git a/rundeck/jobs/project001/33_add_lun_group_to_host.yaml b/rundeck/jobs/project001/33_add_lun_group_to_host.yaml index c5559fe..60b10c1 100644 --- a/rundeck/jobs/project001/33_add_lun_group_to_host.yaml +++ b/rundeck/jobs/project001/33_add_lun_group_to_host.yaml @@ -42,7 +42,7 @@ valuesUrl: http://localhost:26336/rest/data/v1/search/tier?valueAttr=name - name: Session_Name required: true - description: Input Session Name used for CGs (max 16 chars, '_' not permitted) + description: Input Session Name used for CGs (max 13 chars, '_' not permitted) - enforced: true name: Enable_HyperMetro value: 'N' diff --git a/rundeck/jobs/project001/35_add_lun_group_to_cluster.yaml b/rundeck/jobs/project001/35_add_lun_group_to_cluster.yaml index b8e6da4..9c67dcd 100644 --- a/rundeck/jobs/project001/35_add_lun_group_to_cluster.yaml +++ b/rundeck/jobs/project001/35_add_lun_group_to_cluster.yaml @@ -43,7 +43,7 @@ name: Class_1 required: true valuesUrl: http://localhost:26336/rest/data/v1/search/tier?valueAttr=name - - description: Input Session Name used for CGs (max 16 chars, '_' not permitted) + - description: Input Session Name used for CGs (max 13 chars, '_' not permitted) name: Session_Name required: true - enforced: true diff --git a/rundeck/jobs/project001/37_add_replica_host.yaml b/rundeck/jobs/project001/37_add_replica_host.yaml index f5c99f4..c07b11c 100644 --- a/rundeck/jobs/project001/37_add_replica_host.yaml +++ b/rundeck/jobs/project001/37_add_replica_host.yaml @@ -175,6 +175,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + OS_Type: ${option.OS_Type} Host: ${option.Host} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/38_add_drtest_host.yaml b/rundeck/jobs/project001/38_add_drtest_host.yaml index 5b2f2ec..0048bba 100644 --- a/rundeck/jobs/project001/38_add_drtest_host.yaml +++ b/rundeck/jobs/project001/38_add_drtest_host.yaml @@ -151,6 +151,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + OS_Type: ${option.OS_Type} Host: ${option.Host} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/39_add_metro_host.yaml b/rundeck/jobs/project001/39_add_metro_host.yaml index 25b4f57..31df750 100644 --- a/rundeck/jobs/project001/39_add_metro_host.yaml +++ b/rundeck/jobs/project001/39_add_metro_host.yaml @@ -151,6 +151,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + OS_Type: ${option.OS_Type} Host: ${option.Host} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/40_remove_replica_host.yaml b/rundeck/jobs/project001/40_remove_replica_host.yaml index 056f8f6..68fcb87 100644 --- a/rundeck/jobs/project001/40_remove_replica_host.yaml +++ b/rundeck/jobs/project001/40_remove_replica_host.yaml @@ -190,6 +190,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + OS_Type: ${option.OS_Type} Host: ${option.Host} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/41_add_replica_cluster.yaml b/rundeck/jobs/project001/41_add_replica_cluster.yaml index b19d8b3..b70516c 100644 --- a/rundeck/jobs/project001/41_add_replica_cluster.yaml +++ b/rundeck/jobs/project001/41_add_replica_cluster.yaml @@ -177,6 +177,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + OS_Type: ${option.OS_Type} Cluster: ${option.Cluster} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/42_add_drtest_cluster.yaml b/rundeck/jobs/project001/42_add_drtest_cluster.yaml index 56b9aa1..fc869af 100644 --- a/rundeck/jobs/project001/42_add_drtest_cluster.yaml +++ b/rundeck/jobs/project001/42_add_drtest_cluster.yaml @@ -156,6 +156,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + OS_Type: ${option.OS_Type} Cluster: ${option.Cluster} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/43_add_metro_cluster.yaml b/rundeck/jobs/project001/43_add_metro_cluster.yaml index d92baa4..a095adc 100644 --- a/rundeck/jobs/project001/43_add_metro_cluster.yaml +++ b/rundeck/jobs/project001/43_add_metro_cluster.yaml @@ -156,6 +156,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + OS_Type: ${option.OS_Type} Cluster: ${option.Cluster} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/44_remove_replica_cluster.yaml b/rundeck/jobs/project001/44_remove_replica_cluster.yaml index 42902ff..e15174e 100644 --- a/rundeck/jobs/project001/44_remove_replica_cluster.yaml +++ b/rundeck/jobs/project001/44_remove_replica_cluster.yaml @@ -193,6 +193,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + OS_Type: ${option.OS_Type} Cluster: ${option.Cluster} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/45_migrate_host.yaml b/rundeck/jobs/project001/45_migrate_host.yaml index 2cd47ff..d189312 100644 --- a/rundeck/jobs/project001/45_migrate_host.yaml +++ b/rundeck/jobs/project001/45_migrate_host.yaml @@ -83,6 +83,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + OS_Type: ${option.OS_Type} Host: ${option.Host} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/46_migrate_cluster.yaml b/rundeck/jobs/project001/46_migrate_cluster.yaml index 63bff61..9dc7a0f 100644 --- a/rundeck/jobs/project001/46_migrate_cluster.yaml +++ b/rundeck/jobs/project001/46_migrate_cluster.yaml @@ -88,6 +88,8 @@ WBE_CODE: ${option.WBE_CODE} TICKET_NUMBER: ${option.TICKET_NUMBER} Country: ${option.Country} + Job_User: ${job.user.name} + OS_Type: ${option.OS_Type} Cluster: ${option.Cluster} Storage: ${option.Storage} diff --git a/rundeck/jobs/project001/49_create_pg.yaml b/rundeck/jobs/project001/49_create_pg.yaml new file mode 100644 index 0000000..b79bf12 --- /dev/null +++ b/rundeck/jobs/project001/49_create_pg.yaml @@ -0,0 +1,157 @@ +- defaultTab: monitor + description: Create Protection Group with Replication CG, and HyperMetro CG + executionEnabled: true + id: 34533a0b-9a0b-4229-852e-4c5f57971024 + loglevel: INFO + name: 49_create_pg + nodeFilterEditable: false + options: + - name: Job_Description + value: Create Protection Group with Replication CG, and HyperMetro CG + description: Create Protection Group + - name: Username + valuesUrl: http://localhost:26336/rest/data/v1/echo?${job.user.name}=${job.user.name} + description: (Optional) Alternative username/password to login all storage + - name: Password + secure: true + valueExposed: true + description:

Country, OS Type and Location

+ - enforced: true + name: Country + required: true + valuesUrl: http://localhost:26336/rest/data/v1/search/project?pageNo=1&pageSize=100&valueAttr=name + - enforced: true + name: OS_Type + required: true + valuesUrl: http://localhost:26336/rest/data/v1/enum/OSTYPE?nameAttr=desc&valueAttr=key&filter={"enable":1} + - enforced: true + name: Site + required: true + valuesUrl: http://localhost:26336/rest/data/v1/enum/DC?nameAttr=desc&valueAttr=key + description:

Protection Settings

+ - name: Session_Name + description: Input Session Name used for CGs (optional, max 13 chars, '_' not permitted) + required: true + - enforced: true + name: Enable_HyperMetro + value: 'N' + required: true + values: + - 'N' + - 'Y' + - enforced: true + name: Protection_Level + value: '1' + required: true + values: + - '1' + - '2' + - '3' + - enforced: true + name: DR_Sync_Mode + required: true + valuesUrl: http://localhost:26336/rest/data/v1/enum/REPMODE?nameAttr=desc&valueAttr=enum + description:

Primary Protection Group

+ - enforced: true + name: Primary_Storage_Room + required: true + valuesUrl: http://localhost:26336/rest/data/v1/enum/AZ?nameAttr=desc&valueAttr=key&filter={"dc":"${option.Site.value}"} + - enforced: true + name: Primary_Storage + required: true + valuesUrl: http://localhost:26336/rest/data/v1/join/storage?pageNo=1&pageSize=100&nameAttr=deviceName&valueAttr=sn&condition={"constraint":[{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]}&relations=[{"obj":"az","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.Primary_Storage_Room.value}"}}]}}] + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_1 + required: true + valuesUrl: http://localhost:26336/rest/data/v1/echo?pg=1.%20Create%20primary%20protect%20group:%20"${option.Country.value}0${option.Session_Name.value}_1"%20on%20storage:%20"${option.Primary_Storage.value}" + description: Select to confirm primary Protection group result

Metro Protection Group

+ - name: Metro_Storage + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Primary_Storage.value}/data/v1/search/HyperMetroDomain?range=[0-100]&nameAttr=REMOTEDEVICES:11&nameSplit=%22&valueAttr=REMOTEDEVICES:7&valueSplit=%22 + - enforced: true + name: Metro_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&relations=[{"obj":"storage","condition":{"constraint":[{"logOp":"and","simple":{"name":"sn","operator":"equal","value":"${option.Metro_Storage.value}"}}]}}] + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_2 + valuesUrl: http://localhost:26336/rest/data/v1/echo?pg=1.%20Create%20Metro%20protect%20group:%20"${option.Country.value}0${option.Session_Name.value}_1"%20on%20storage:%20"${option.Metro_Storage.value}" + description: Select to confirm metro Protection group result

DR Protection Group

+ - name: DR_Storage + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Primary_Storage.value}/data/v1/search/remote_device?nameAttr=NAME&valueAttr=SN&filter=ARRAYTYPE!!2 + - enforced: true + name: DR_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&relations=[{"obj":"storage","condition":{"constraint":[{"logOp":"and","simple":{"name":"sn","operator":"equal","value":"${option.DR_Storage.value}"}}]}}] + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_3 + valuesUrl: http://localhost:26336/rest/data/v1/echo?pg=1.%20Create%20DR%20protect%20group:%20"${option.Country.value}0${option.Session_Name.value}_2"%20on%20storage:%20"${option.DR_Storage.value}" + description: Select to confirm the DR Protection group result + scheduleEnabled: true + sequence: + commands: + - configuration: + ansible-become: 'false' + ansible-disable-limit: 'false' + ansible-extra-vars: |- + username: ${option.Username} + password: ${option.Password} + + Country: ${option.Country} + OS_Type: ${option.OS_Type} + Site: ${option.Site} + + + Session_Name: ${option.Session_Name} + Enable_HyperMetro: ${option.Enable_HyperMetro} + Protection_Level: ${option.Protection_Level} + DR_Sync_Mode: ${option.DR_Sync_Mode} + + Primary_Storage_Room: ${option.Primary_Storage_Room} + Primary_Storage: ${option.Primary_Storage} + + Metro_Storage: ${option.Metro_Storage} + Metro_Storage_Room: ${option.Metro_Storage_Room} + + DR_Storage: ${option.DR_Storage} + DR_Storage_Room: ${option.DR_Storage_Room} + Check_Result_1: ${option.Check_Result_1} + Check_Result_2: ${option.Check_Result_2} + Check_Result_3: ${option.Check_Result_3} + ansible-playbook: /var/lib/rundeck/ansible/rundeck/workflow/project001/49_create_pg.yml + description: Create Host + nodeStep: false + type: com.batix.rundeck.plugins.AnsiblePlaybookWorkflowStep + keepgoing: false + pluginConfig: + LogFilter: + - config: + bgcolor: green + regex: TASK.*Step_.* + type: highlight-output + - config: + bgcolor: red + regex: TASK.*Rollback_.* + type: highlight-output + - config: + bgcolor: blue + regex: TASK.*Precheck_.* + type: highlight-output + - config: + bgcolor: cyan + regex: TASK.*Result_.* + type: highlight-output + - config: + fgcolor: green + mode: bold + regex: ok=[1-9][0-9]* + type: highlight-output + - config: + fgcolor: red + mode: bold + regex: failed=[1-9][0-9]* + type: highlight-output + strategy: node-first + uuid: 34533a0b-9a0b-4229-852e-4c5f57971024 diff --git a/rundeck/jobs/project001/50_add_luns_to_pg.yaml b/rundeck/jobs/project001/50_add_luns_to_pg.yaml new file mode 100644 index 0000000..9d47d2c --- /dev/null +++ b/rundeck/jobs/project001/50_add_luns_to_pg.yaml @@ -0,0 +1,347 @@ +- defaultTab: monitor + description: Create LUNs or select LUNs and add them to Protection Groups + executionEnabled: true + id: 22916134-7113-4969-8d11-786574075a11 + loglevel: INFO + name: 50_add_luns_to_pg + nodeFilterEditable: false + options: + - name: Job_Description + value: Create LUNs or select LUNs and add them to Protection Groups + - name: Username + valuesUrl: http://localhost:26336/rest/data/v1/echo?${job.user.name}=${job.user.name} + description: (Optional) Alternative username/password to login all storage + - name: Password + secure: true + valueExposed: true + description:

Ticket

+ - required: true + name: WBE_CODE + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + - required: true + name: TICKET_NUMBER + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + description:

Select Primary Host

+ - enforced: true + name: Country + required: true + valuesUrl: http://localhost:26336/rest/data/v1/search/project?pageNo=1&pageSize=100&valueAttr=name + - name: OS_Type + valuesUrl: http://localhost:26336/rest/data/v1/enum/OSTYPE?nameAttr=desc&valueAttr=key&filter={"enable":1} + - label: Search Host Name + name: Host_Search + - enforced: true + name: Host + required: true + valuesUrl: http://localhost:26336/rest/data/v1/search/storagehost?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&valueUnique=true&condition={"constraint":[{"simple":{"name":"name","operator":"begin%20with","value":"${option.Country.value}_${option.OS_Type.value}"}},{"logOp":"and","simple":{"name":"name","operator":"contain","value":"${option.Host_Search.value}"}},{"logOp":"and","simple":{"name":"name","operator":"end%20with","value":"_1"}},{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]} + - enforced: true + name: Storage + valuesUrl: http://localhost:26336/rest/data/v1/join/storage?pageNo=1&pageSize=100&nameAttr=deviceName&valueAttr=sn&condition={"constraint":[{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]}&relations=[{"obj":"storagehost","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.Host.value}"}}]}},{"obj":"az","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.Storage_Room.value}"}}]}}] + - enforced: true + name: Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&relations=[{"obj":"host","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.Host.value}"}}]}}] + description:

Create New LUNs

+ - label: LUN Size (GB) + name: LUN_Size + - label: Number of LUNs + name: LUN_Num + - delimiter: '|' + multivalued: true + name: LUN_Description + description: Add LUN Description (optional, max 255 chars for all items, '|' and '_' not permitted) + - enforced: true + name: Pool + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/storagepool?nameAttr=NAME&valueAttr=ID + - enforced: true + name: Workload + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/workload_type?nameAttr=NAME&valueAttr=ID + description:

Select Un-mapped LUNs

+ - label: Search LUN on Storage + name: Search_LUN + - delimiter: ',' + enforced: true + multivalued: true + name: Select_LUN + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lun?range=[0-100]&nameAttr=NAME&valueAttr=NAME&filter=NAME:${option.Search_LUN.value}%20and%20mapped::false + description:

Set LUN Info

+ - enforced: true + name: LUN_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/associate/mapping?nameAttr=lunGroupName&valueAttr=lunGroupName&matchAttr=mappingType&match=1&range=[0-100]&obj=host&filter=NAME::${option.Host.value} + description: Select LUN Group to find protection chain + - name: Start_SCSI_ID + - enforced: true + name: Class_1 + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=DESCRIPTION:0:2:3&valueAttr=DESCRIPTION:0:2:3&filter=NAME::${option.LUN_Group.value} + - name: Designate_Class_1 + valuesUrl: http://localhost:26336/rest/data/v1/search/tier?valueAttr=name + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_1 + required: true + valuesUrl: http://localhost:26336/rest/data/v1/echo?lun=1.%20Create%20${option.LUN_Num.value}%20x%20${option.LUN_Size.value}GB%20LUNs%20for%20host:%20"${option.Host.value}&select_lun=2.%20Select%20LUNs%20"${option.Select_LUN.value}"%20for%20host:%20"${option.Host.value}" + description: Select to confirm the primary host result

Protection Info

+ - label: Search Protection Group + name: Protection_Group_Search + - enforced: true + name: Protection_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/protectgroup?nameAttr=protectGroupName&valueAttr=protectGroupName&range=[0-100]&filter=NAME:${option.Country.value}0${option.Protection_Group_Search.value}%20and%20description::${option.Enable_HyperMetro.value}${option.Protection_Level.value} + - enforced: true + name: Enable_HyperMetro + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=DESCRIPTION:0:0:1&valueAttr=DESCRIPTION:0:0:1&filter=NAME::${option.LUN_Group.value} + - enforced: true + name: Protection_Level + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=DESCRIPTION:0:1:2&valueAttr=DESCRIPTION:0:1:2&filter=NAME::${option.LUN_Group.value} + - enforced: true + name: Session_Name + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/protectgroup?range=[0-100]&nameAttr=protectGroupName:0&nameSplit=_&valueAttr=protectGroupName:0&valueSplit=_&filter=NAME::${option.Protection_Group.value} + description:

Metro Host

+ - enforced: true + name: Metro_Host + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Metro_Storage.value}/data/v1/search/host?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=NAME::${option.Host.value} + - enforced: true + name: Metro_Storage + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/detail/HyperMetroDomain?ID=${option.Metro_Domain.value}&nameAttr=REMOTEDEVICES:11&nameSplit="&valueAttr=REMOTEDEVICES:7&valueSplit=" + - enforced: true + name: Metro_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&relations=[{"obj":"storage","condition":{"constraint":[{"logOp":"and","simple":{"name":"sn","operator":"equal","value":"${option.Metro_Storage.value}"}}]}}] + - enforced: true + name: Metro_Pool + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Metro_Storage.value}/data/v1/search/storagepool?nameAttr=NAME&valueAttr=ID + - enforced: true + name: Metro_Workload + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Metro_Storage.value}/data/v1/search/workload_type?nameAttr=NAME&valueAttr=ID + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_2 + valuesUrl: http://localhost:26336/rest/data/v1/echo?lun=Create%20metro%20LUNs%20for%20host%20"${option.Metro_Host.value}" + description: Select to confirm the metro host result

DR Host

+ - enforced: true + name: DR_Host + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/associate/mapping?nameAttr=hostName&valueAttr=hostName&range=[0-100]&obj=lungroup&filter=NAME::${option.DR_LUN_Group.value} + - enforced: true + name: DR_Storage + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/remote_device?nameAttr=NAME&valueAttr=SN&range=[0-100]&filter=ID::${option.DR_Storage_ID.value} + - enforced: true + name: DR_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&relations=[{"obj":"storage","condition":{"constraint":[{"logOp":"and","simple":{"name":"sn","operator":"equal","value":"${option.DR_Storage.value}"}}]}}] + - enforced: true + name: DR_Pool + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/storagepool?nameAttr=NAME&valueAttr=ID + - enforced: true + name: DR_Workload + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/workload_type?nameAttr=NAME&valueAttr=ID + - enforced: true + name: Class_2 + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=DESCRIPTION:0:3:4&valueAttr=DESCRIPTION:0:3:4&filter=NAME::${option.LUN_Group.value} + - name: Designate_Class_2 + valuesUrl: http://localhost:26336/rest/data/v1/search/tier?valueAttr=name + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_3 + valuesUrl: http://localhost:26336/rest/data/v1/echo?lun=Create%20DR%20LUNs%20for%20host%20"${option.DR_Host.value}" + description: Select to confirm the DR host result

DR Test Host

+ - enforced: true + name: DR_Test_Host + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/host?range=[0-100]&nameAttr=NAME&valueAttr=NAME&filter=NAME:${option.DR_Host_Name.value}_3 + - enforced: true + name: Class_3 + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=DESCRIPTION:0:4:5&valueAttr=DESCRIPTION:0:4:5&filter=NAME::${option.LUN_Group.value} + - name: Designate_Class_3 + valuesUrl: http://localhost:26336/rest/data/v1/search/tier?valueAttr=name + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_4 + valuesUrl: http://localhost:26336/rest/data/v1/echo?lun=Create%20DR%20Test%20LUNs%20for%20host%20"${option.DR_Test_Host.value}" + description: Select to confirm the DR test host result

Additional Info

+ - enforced: true + name: LUN_Group_ID + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?nameAttr=ID&valueAttr=ID&range=[0-100]&filter=NAME::${option.LUN_Group.value} + - enforced: true + name: LUN_Group_NO + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/lungroup?nameAttr=NAME:4:2&nameSplit=_&valueAttr=NAME:4:2&valueSplit=_&range=[0-100]&filter=NAME::${option.LUN_Group.value} + - enforced: true + name: LG_Protection_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/protectgroup?nameAttr=protectGroupName&valueAttr=protectGroupName&range=[0-100]&filter=lunGroupId::${option.LUN_Group_ID.value} + - enforced: true + name: Metro_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/hypermetro_consistentgroup?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - enforced: true + name: Metro_CG_ID + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/hypermetro_consistentgroup?nameAttr=ID&valueAttr=ID&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - enforced: true + name: Metro_Domain + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/hypermetro_consistentgroup?nameAttr=DOMAINNAME&valueAttr=DOMAINID&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - enforced: true + name: DR_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - enforced: true + name: DR_LG_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.LG_Protection_Group.value} + - enforced: true + name: DR_Mode_Enum + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=REPLICATIONMODEL&valueAttr=REPLICATIONMODEL&range=[0-100]&filter=NAME::${option.DR_CG.value} + - enforced: true + name: DR_Mode + valuesUrl: http://localhost:26336/rest/data/v1/enum/REPMODE?nameAttr=desc&valueAttr=key&filter={"enum":${option.DR_Mode_Enum.value}} + - enforced: true + name: DR_Storage_ID + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=remoteArrayID&valueAttr=remoteArrayID&range=[0-100]&filter=NAME::${option.DR_CG.value} + - enforced: true + name: DR_Protection_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=rmtPgName&valueAttr=rmtPgName&range=[0-100]&filter=NAME::${option.DR_CG.value} + - enforced: true + name: DR_LG_Protection_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=rmtPgName&valueAttr=rmtPgName&range=[0-100]&filter=NAME::${option.DR_LG_CG.value} + - enforced: true + name: DR_LUN_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/protectgroup?nameAttr=lunGroupName&valueAttr=lunGroupName&range=[0-100]&filter=protectGroupName::${option.DR_LG_Protection_Group.value} + - enforced: true + name: DR_Host_Name + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/host?range=[0-100]&nameAttr=NAME:2&nameSplit=_&valueAttr=NAME:2&valueSplit=_&filter=NAME::${option.DR_Host.value} + - enforced: true + name: DR_Test_LUN_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/lungroup?range=[0-100]&nameAttr=NAME&valueAttr=NAME&filter=NAME::${option.DR_Test_Host.value}_LG${option.LUN_Group_NO.value} + - enforced: true + name: DR_Test_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/associate/snapshot_consistency_group?nameAttr=NAME&valueAttr=NAME&matchAttr=NAME&match=${option.Session_Name.value}&range=[0-4096]&obj=protectgroup&objIdAttr=protectGroupId&filter=protectGroupName::${option.DR_Protection_Group.value} + - enforced: true + name: DR_Test_CG_ID + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=ID&valueAttr=ID&filter=NAME::${option.DR_Test_CG.value} + - enforced: true + name: DR_Test_CG_Status + valuesUrl: http://localhost:26336/rest/data/v1/enum/SNAPCG?nameAttr=desc&valueAttr=enum&filter={"enum":${option.DR_Test_CG_Status_Enum.value}} + - enforced: true + name: DR_Test_CG_Status_Enum + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=RUNNINGSTATUS&valueAttr=RUNNINGSTATUS&filter=NAME::${option.DR_Test_CG.value} + - enforced: true + name: DR_Star + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/dr_star?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=memberType::2%20and%20localResource::${option.Protection_Group.value} + - enforced: true + name: Standby_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Metro_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - enforced: true + name: Standby_CG_ID + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Metro_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=ID&valueAttr=ID&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + scheduleEnabled: true + sequence: + commands: + - configuration: + ansible-become: 'false' + ansible-disable-limit: 'false' + ansible-extra-vars: |- + username: ${option.Username} + password: ${option.Password} + + WBE_CODE: ${option.WBE_CODE} + TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + + Host: ${option.Host} + Storage: ${option.Storage} + Storage_Room: ${option.Storage_Room} + LUN_Size: ${option.LUN_Size} + LUN_Num: ${option.LUN_Num} + Start_SCSI_ID: ${option.Start_SCSI_ID} + LUN_Description: ${option.LUN_Description} + Pool: ${option.Pool} + Workload: ${option.Workload} + Select_LUN: ${option.Select_LUN} + Check_Result_1: ${option.Check_Result_1} + + Metro_Host: ${option.Metro_Host} + Metro_Storage: ${option.Metro_Storage} + Metro_Storage_Room: ${option.Metro_Storage_Room} + Metro_Pool: ${option.Metro_Pool} + Metro_Workload: ${option.Metro_Workload} + Check_Result_2: ${option.Check_Result_2} + + DR_Host: ${option.DR_Host} + DR_Storage: ${option.DR_Storage} + DR_Storage_Room: ${option.DR_Storage_Room} + DR_Pool: ${option.DR_Pool} + DR_Workload: ${option.DR_Workload} + Check_Result_3: ${option.Check_Result_3} + + DR_Test_Host: ${option.DR_Test_Host} + Check_Result_4: ${option.Check_Result_4} + + Enable_HyperMetro: ${option.Enable_HyperMetro} + Metro_CG: ${option.Metro_CG} + Metro_CG_ID: ${option.Metro_CG_ID} + Session_Name: ${option.Session_Name} + Protection_Level: ${option.Protection_Level} + Class_1: ${option.Class_1} + Class_2: ${option.Class_2} + Class_3: ${option.Class_3} + Designate_Class_1: ${option.Designate_Class_1} + Designate_Class_2: ${option.Designate_Class_2} + Designate_Class_3: ${option.Designate_Class_3} + LUN_Group: ${option.LUN_Group} + Protection_Group: ${option.Protection_Group} + DR_CG: ${option.DR_CG} + DR_Mode: ${option.DR_Mode} + DR_Mode_Enum: ${option.DR_Mode_Enum} + DR_Storage_ID: ${option.DR_Storage_ID} + DR_Protection_Group: ${option.DR_Protection_Group} + DR_LUN_Group: ${option.DR_LUN_Group} + DR_Test_LUN_Group: ${option.DR_Test_LUN_Group} + DR_Test_CG: ${option.DR_Test_CG} + DR_Test_CG_ID: ${option.DR_Test_CG_ID} + DR_Test_CG_Status: ${option.DR_Test_CG_Status} + DR_Star: ${option.DR_Star} + Standby_CG: ${option.Standby_CG} + Standby_CG_ID: ${option.Standby_CG_ID} + ansible-playbook: /var/lib/rundeck/ansible/rundeck/workflow/project001/50_add_luns_to_pg.yml + nodeStep: false + type: com.batix.rundeck.plugins.AnsiblePlaybookWorkflowStep + - configuration: + command: /bin/psql automation -c "\copy (select * from activity where WBE_CODE = '${option.WBE_CODE}' and TICKET_NUMBER = '${option.TICKET_NUMBER}') TO '/var/lib/rundeck/exp/webapp/project001/export/${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv' CSV HEADER" + description: Export Changes + nodeStep: true + type: localexec + - configuration: + command: echo "${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv" + description: Download Link + nodeStep: true + plugins: + LogFilter: + - config: + datatype: text/html + type: render-datatype + type: localexec + keepgoing: false + pluginConfig: + LogFilter: + - config: + bgcolor: green + regex: TASK.*Step_.* + type: highlight-output + - config: + bgcolor: red + regex: TASK.*Rollback_.* + type: highlight-output + - config: + bgcolor: blue + regex: TASK.*Precheck_.* + type: highlight-output + - config: + bgcolor: cyan + regex: TASK.*Result_.* + type: highlight-output + - config: + fgcolor: green + mode: bold + regex: ok=[1-9][0-9]* + type: highlight-output + - config: + fgcolor: red + mode: bold + regex: failed=[1-9][0-9]* + type: highlight-output + strategy: node-first + uuid: 22916134-7113-4969-8d11-786574075a11 diff --git a/rundeck/jobs/project001/51_remove_luns_from_pg.yaml b/rundeck/jobs/project001/51_remove_luns_from_pg.yaml new file mode 100644 index 0000000..ca66267 --- /dev/null +++ b/rundeck/jobs/project001/51_remove_luns_from_pg.yaml @@ -0,0 +1,236 @@ +- defaultTab: monitor + description: Remove LUNs from LUN Group, Protect Group, Replication CGs, HyperMetro CG of a Protection Group, and delete replicas + executionEnabled: true + id: 85eef634-f4c3-4848-b621-5d92a348ee7e + loglevel: INFO + name: 51_remove_luns_from_pg + nodeFilterEditable: false + options: + - name: Job_Description + value: Remove LUNs from LUN Group, Protect Group, Replication CGs, HyperMetro CG, and delete replicas + - name: Username + valuesUrl: http://localhost:26336/rest/data/v1/echo?${job.user.name}=${job.user.name} + description: (Optional) Alternative username/password to login all storage + - name: Password + secure: true + valueExposed: true + description:

Ticket

+ - required: true + name: WBE_CODE + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + - required: true + name: TICKET_NUMBER + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + description:

Country, OS Type and Location

+ - enforced: true + name: Country + required: true + valuesUrl: http://localhost:26336/rest/data/v1/search/project?pageNo=1&pageSize=100&valueAttr=name + - name: OS_Type + valuesUrl: http://localhost:26336/rest/data/v1/enum/OSTYPE?nameAttr=desc&valueAttr=key&filter={"enable":1} + - enforced: true + name: Site + required: true + valuesUrl: http://localhost:26336/rest/data/v1/enum/DC?nameAttr=desc&valueAttr=key + - enforced: true + name: Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/enum/AZ?nameAttr=desc&valueAttr=key&filter={"dc":"${option.Site.value}"} + - enforced: true + name: Storage + valuesUrl: http://localhost:26336/rest/data/v1/join/storage?pageNo=1&pageSize=100&nameAttr=deviceName&valueAttr=sn&condition={"constraint":[{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]}&relations=[{"obj":"az","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.Storage_Room.value}"}}]}}] + description:

Select Primary Protection Group & LUNs

+ - label: Search Protection Group Name + name: Protection_Group_Search + - enforced: true + name: Protection_Group + required: true + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/protectgroup?nameAttr=protectGroupName&valueAttr=protectGroupName&range=[0-100]&filter=NAME:${option.Country.value}0${option.Protection_Group_Search.value} + description:

Select Primary LUNs

+ - delimiter: ',' + enforced: true + multivalued: true + required: true + name: Remove_LUN + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/associate/lun?nameAttr=NAME&valueAttr=NAME&range=[0-4096]&obj=protectgroup&objIdAttr=protectGroupId&filter=protectGroupName::${option.Protection_Group.value} + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_1 + required: true + valuesUrl: http://localhost:26336/rest/data/v1/echo?lun=Remove%20luns%20"${option.Remove_LUN.value}"%20from%20protection%20group:%20"${option.Protection_Group.value}" + description: Select to confirm the primary host result

Protection Info

+ - enforced: true + name: Enable_HyperMetro + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/protectgroup?range=[0-100]&nameAttr=description:0:0:1&valueAttr=description:0:0:1&filter=NAME::${option.Protection_Group.value} + - enforced: true + name: Protection_Level + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/protectgroup?range=[0-100]&nameAttr=description:0:1:2&valueAttr=description:0:1:2&filter=NAME::${option.Protection_Group.value} + - enforced: true + name: Session_Name + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/protectgroup?range=[0-100]&nameAttr=protectGroupName:0&nameSplit=_&valueAttr=protectGroupName:0&valueSplit=_&filter=NAME::${option.Protection_Group.value} + description:

Metro Protection Group

+ - enforced: true + name: Metro_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&relations=[{"obj":"storage","condition":{"constraint":[{"logOp":"and","simple":{"name":"sn","operator":"equal","value":"${option.Metro_Storage.value}"}}]}}] + - enforced: true + name: Metro_Storage + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/detail/HyperMetroDomain?ID=${option.Metro_Domain.value}&nameAttr=REMOTEDEVICES:11&nameSplit="&valueAttr=REMOTEDEVICES:7&valueSplit=" + - enforced: true + name: Metro_Protection_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Metro_Storage.value}/data/v1/search/protectgroup?range=[0-100]&nameAttr=protectGroupName&valueAttr=protectGroupName&filter=NAME::${option.Protection_Group.value} + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_2 + valuesUrl: http://localhost:26336/rest/data/v1/echo?lun=Remove%20and%20delete%20metro%20LUNs%20from%20Protection%20Group:%20"${option.Metro_Protection_Group.value}" + description: Select to confirm the metro protection group result

DR Protection Group

+ - enforced: true + name: DR_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&relations=[{"obj":"storage","condition":{"constraint":[{"logOp":"and","simple":{"name":"sn","operator":"equal","value":"${option.DR_Storage.value}"}}]}}] + - enforced: true + name: DR_Storage_ID + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=remoteArrayID&valueAttr=remoteArrayID&range=[0-100]&filter=NAME::${option.DR_CG.value} + - enforced: true + name: DR_Storage + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/remote_device?nameAttr=NAME&valueAttr=SN&range=[0-100]&filter=ID::${option.DR_Storage_ID.value} + - enforced: true + name: DR_Protection_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=rmtPgName&valueAttr=rmtPgName&range=[0-100]&filter=NAME::${option.DR_CG.value} + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_3 + valuesUrl: http://localhost:26336/rest/data/v1/echo?lun=Remove%20and%20delete%20DR%20LUNs%20from%20protection%20group:%20"${option.DR_Protection_Group.value}" + description: Select to confirm the DR host result

Additional Info

+ - enforced: true + name: Metro_Domain + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/hypermetro_consistentgroup?nameAttr=DOMAINNAME&valueAttr=DOMAINID&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - enforced: true + name: Metro_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/hypermetro_consistentgroup?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - enforced: true + name: DR_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - enforced: true + name: DR_Mode_Enum + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=REPLICATIONMODEL&valueAttr=REPLICATIONMODEL&range=[0-100]&filter=NAME::${option.DR_CG.value} + - enforced: true + name: DR_Mode + valuesUrl: http://localhost:26336/rest/data/v1/enum/REPMODE?nameAttr=desc&valueAttr=key&filter={"enum":${option.DR_Mode_Enum.value}} + - enforced: true + name: DR_Test_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/associate/snapshot_consistency_group?nameAttr=NAME&valueAttr=NAME&matchAttr=NAME&match=${option.Session_Name.value}&range=[0-4096]&obj=protectgroup&objIdAttr=protectGroupId&filter=protectGroupName::${option.DR_Protection_Group.value} + - enforced: true + name: DR_Test_CG_ID + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=ID&valueAttr=ID&filter=NAME::${option.DR_Test_CG.value} + - enforced: true + name: DR_Test_CG_Status + valuesUrl: http://localhost:26336/rest/data/v1/enum/SNAPCG?nameAttr=desc&valueAttr=enum&filter={"enum":${option.DR_Test_CG_Status_Enum.value}} + - enforced: true + name: DR_Test_CG_Status_Enum + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=RUNNINGSTATUS&valueAttr=RUNNINGSTATUS&filter=NAME::${option.DR_Test_CG.value} + - enforced: true + name: DR_Star + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/dr_star?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=memberType::2%20and%20localResource::${option.Protection_Group.value} + - enforced: true + name: Standby_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Metro_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - enforced: true + name: Standby_CG_ID + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Metro_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=ID&valueAttr=ID&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + scheduleEnabled: true + sequence: + commands: + - configuration: + ansible-become: 'false' + ansible-disable-limit: 'false' + ansible-extra-vars: |- + username: ${option.Username} + password: ${option.Password} + + WBE_CODE: ${option.WBE_CODE} + TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + + Protection_Group: ${option.Protection_Group} + Storage: ${option.Storage} + Storage_Room: ${option.Storage_Room} + Remove_LUN: ${option.Remove_LUN} + Check_Result_1: ${option.Check_Result_1} + + Metro_Protection_Group: ${option.Metro_Protection_Group} + Metro_Storage: ${option.Metro_Storage} + Metro_Storage_Room: ${option.Metro_Storage_Room} + Check_Result_2: ${option.Check_Result_2} + + DR_Protection_Group: ${option.DR_Protection_Group} + DR_Storage: ${option.DR_Storage} + DR_Storage_Room: ${option.DR_Storage_Room} + Check_Result_3: ${option.Check_Result_3} + + Session_Name: ${option.Session_Name} + Enable_HyperMetro: ${option.Enable_HyperMetro} + Metro_CG: ${option.Metro_CG} + Metro_CG_ID: ${option.Metro_CG_ID} + Protection_Level: ${option.Protection_Level} + Protection_Group: ${option.Protection_Group} + DR_CG: ${option.DR_CG} + DR_Mode: ${option.DR_Mode} + DR_Mode_Enum: ${option.DR_Mode_Enum} + DR_Storage_ID: ${option.DR_Storage_ID} + DR_Test_CG: ${option.DR_Test_CG} + DR_Test_CG_ID: ${option.DR_Test_CG_ID} + DR_Test_CG_Status: ${option.DR_Test_CG_Status} + DR_Star: ${option.DR_Star} + Standby_CG: ${option.Standby_CG} + Standby_CG_ID: ${option.Standby_CG_ID} + ansible-playbook: /var/lib/rundeck/ansible/rundeck/workflow/project001/51_remove_luns_from_pg.yml + nodeStep: false + type: com.batix.rundeck.plugins.AnsiblePlaybookWorkflowStep + - configuration: + command: /bin/psql automation -c "\copy (select * from activity where WBE_CODE = '${option.WBE_CODE}' and TICKET_NUMBER = '${option.TICKET_NUMBER}') TO '/var/lib/rundeck/exp/webapp/project001/export/${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv' CSV HEADER" + description: Export Changes + nodeStep: true + type: localexec + - configuration: + command: echo "${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv" + description: Download Link + nodeStep: true + plugins: + LogFilter: + - config: + datatype: text/html + type: render-datatype + type: localexec + keepgoing: false + pluginConfig: + LogFilter: + - config: + bgcolor: green + regex: TASK.*Step_.* + type: highlight-output + - config: + bgcolor: red + regex: TASK.*Rollback_.* + type: highlight-output + - config: + bgcolor: blue + regex: TASK.*Precheck_.* + type: highlight-output + - config: + bgcolor: cyan + regex: TASK.*Result_.* + type: highlight-output + - config: + fgcolor: green + mode: bold + regex: ok=[1-9][0-9]* + type: highlight-output + - config: + fgcolor: red + mode: bold + regex: failed=[1-9][0-9]* + type: highlight-output + strategy: node-first + uuid: 85eef634-f4c3-4848-b621-5d92a348ee7e diff --git a/rundeck/jobs/project001/52_delete_pg.yaml b/rundeck/jobs/project001/52_delete_pg.yaml new file mode 100644 index 0000000..c21dbfd --- /dev/null +++ b/rundeck/jobs/project001/52_delete_pg.yaml @@ -0,0 +1,158 @@ +- defaultTab: monitor + description: 'Delete Protection Group, Replication CG, HyperMetro CG' + executionEnabled: true + id: 5d57bbca-2442-4868-90e1-1372bc24bed0 + loglevel: INFO + name: 52_delete_pg + nodeFilterEditable: false + options: + - name: Job_Description + value: Delete Protection Group, Replication CG, HyperMetro CG + description: Requires no LUNs in Protection Group + - name: Username + valuesUrl: http://localhost:26336/rest/data/v1/echo?${job.user.name}=${job.user.name} + description: (Optional) Alternative username/password to login all storage + - name: Password + secure: true + valueExposed: true + description:

Select Primary Protection Group

+ - enforced: true + name: Country + required: true + valuesUrl: http://localhost:26336/rest/data/v1/search/project?pageNo=1&pageSize=100&valueAttr=name + - enforced: true + name: Site + required: true + valuesUrl: http://localhost:26336/rest/data/v1/enum/DC?nameAttr=desc&valueAttr=key + description: Select to confirm the primary host result

Protection Info

+ - enforced: true + name: Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/enum/AZ?nameAttr=desc&valueAttr=key&filter={"dc":"${option.Site.value}"} + - enforced: true + name: Storage + valuesUrl: http://localhost:26336/rest/data/v1/join/storage?pageNo=1&pageSize=100&nameAttr=deviceName&valueAttr=sn&condition={"constraint":[{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]}&relations=[{"obj":"az","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.Storage_Room.value}"}}]}}] + - label: Search Protection Group Name + name: Protection_Group_Search + - enforced: true + name: Protection_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/protectgroup?nameAttr=protectGroupName&valueAttr=protectGroupName&range=[0-100]&filter=NAME:${option.Country.value}0${option.Protection_Group_Search.value} + - enforced: true + name: Enable_HyperMetro + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/protectgroup?range=[0-100]&nameAttr=description:0:0:1&valueAttr=description:0:0:1&filter=NAME::${option.Protection_Group.value} + - enforced: true + name: Protection_Level + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/protectgroup?range=[0-100]&nameAttr=description:0:1:2&valueAttr=description:0:1:2&filter=NAME::${option.Protection_Group.value} + - enforced: true + name: Session_Name + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/protectgroup?range=[0-100]&nameAttr=protectGroupName:0&nameSplit=_&valueAttr=protectGroupName:0&valueSplit=_&filter=NAME::${option.Protection_Group.value} + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_1 + required: true + valuesUrl: http://localhost:26336/rest/data/v1/echo?pg=Delete%20protection%20group:%20"${option.Protection_Group.value}"%20and%20correlated%20CGs + description:

Metro Protection Group

+ + - enforced: true + name: Metro_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&relations=[{"obj":"storage","condition":{"constraint":[{"logOp":"and","simple":{"name":"sn","operator":"equal","value":"${option.Metro_Storage.value}"}}]}}] + - enforced: true + name: Metro_Storage + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/detail/HyperMetroDomain?ID=${option.Metro_Domain.value}&nameAttr=REMOTEDEVICES:11&nameSplit="&valueAttr=REMOTEDEVICES:7&valueSplit=" + - enforced: true + name: Metro_Protection_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Metro_Storage.value}/data/v1/search/protectgroup?range=[0-100]&nameAttr=protectGroupName&valueAttr=protectGroupName&filter=NAME::${option.Protection_Group.value} + - enforced: true + name: Metro_Domain + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/hypermetro_consistentgroup?nameAttr=DOMAINNAME&valueAttr=DOMAINID&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - enforced: true + name: Metro_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/hypermetro_consistentgroup?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_2 + valuesUrl: http://localhost:26336/rest/data/v1/echo?pg=Delete%20protection%20group:%20"${option.Metro_Protection_Group.value}"%20and%20correlated%20CGs + description: Select to confirm the metro protection group result

DR Protection Group

+ - enforced: true + name: DR_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&relations=[{"obj":"storage","condition":{"constraint":[{"logOp":"and","simple":{"name":"sn","operator":"equal","value":"${option.DR_Storage.value}"}}]}}] + - enforced: true + name: DR_Storage_ID + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=remoteArrayID&valueAttr=remoteArrayID&range=[0-100]&filter=NAME::${option.DR_CG.value} + - enforced: true + name: DR_Storage + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/remote_device?nameAttr=NAME&valueAttr=SN&range=[0-100]&filter=ID::${option.DR_Storage_ID.value} + - enforced: true + name: DR_Protection_Group + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=rmtPgName&valueAttr=rmtPgName&range=[0-100]&filter=NAME::${option.DR_CG.value} + - enforced: true + name: DR_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.Protection_Group.value} + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_3 + valuesUrl: http://localhost:26336/rest/data/v1/echo?pg=Delete%20DR%20protection%20group:%20"${option.DR_Protection_Group.value}"%20and%20correlated%20CGs + scheduleEnabled: true + sequence: + commands: + - configuration: + ansible-become: 'false' + ansible-disable-limit: 'false' + ansible-extra-vars: |- + username: ${option.Username} + password: ${option.Password} + + Storage: ${option.Storage} + Protection_Group: ${option.Protection_Group} + Enable_HyperMetro: ${option.Enable_HyperMetro} + Protection_Level: ${option.Protection_Level} + Session_Name: ${option.Session_Name} + Check_Result_1: ${option.Check_Result_1} + + Metro_Storage_Room: ${option.Metro_Storage_Room} + Metro_Storage: ${option.Metro_Storage} + Metro_Protection_Group: ${option.Metro_Protection_Group} + Metro_CG: ${option.Metro_CG} + Check_Result_2: ${option.Check_Result_2} + + DR_Storage: ${option.DR_Storage} + DR_Protection_Group: ${option.DR_Protection_Group} + DR_CG: ${option.DR_CG} + Check_Result_3: ${option.Check_Result_3} + + ansible-playbook: /var/lib/rundeck/ansible/rundeck/workflow/project001/52_delete_pg.yml + nodeStep: false + type: com.batix.rundeck.plugins.AnsiblePlaybookWorkflowStep + keepgoing: false + pluginConfig: + LogFilter: + - config: + bgcolor: green + regex: TASK.*Step_.* + type: highlight-output + - config: + bgcolor: red + regex: TASK.*Rollback_.* + type: highlight-output + - config: + bgcolor: blue + regex: TASK.*Precheck_.* + type: highlight-output + - config: + bgcolor: cyan + regex: TASK.*Result_.* + type: highlight-output + - config: + fgcolor: green + mode: bold + regex: ok=[1-9][0-9]* + type: highlight-output + - config: + fgcolor: red + mode: bold + regex: failed=[1-9][0-9]* + type: highlight-output + strategy: node-first + uuid: 5d57bbca-2442-4868-90e1-1372bc24bed0 diff --git a/rundeck/jobs/project001/53_dr_test_for_pg.yaml b/rundeck/jobs/project001/53_dr_test_for_pg.yaml new file mode 100644 index 0000000..67584ee --- /dev/null +++ b/rundeck/jobs/project001/53_dr_test_for_pg.yaml @@ -0,0 +1,150 @@ +- defaultTab: monitor + description: Move WWNs from DR Host to DR Test Host, Activate Snapshot CG + executionEnabled: true + id: bf3196d6-5602-49eb-8158-39e69a875d01 + loglevel: INFO + name: 53_dr_test_for_pg + nodeFilterEditable: false + options: + - name: Job_Description + value: Move WWNs from DR Host to DR Test Host, Activate Snapshot CG + - name: Username + valuesUrl: http://localhost:26336/rest/data/v1/echo?${job.user.name}=${job.user.name} + description: (Optional) Alternative username/password to login all storage + - name: Password + secure: true + valueExposed: true + description:

Ticket

+ - required: true + name: WBE_CODE + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + - required: true + name: TICKET_NUMBER + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + description:

Country, OS Type and Location

+ - enforced: true + name: Country + required: true + valuesUrl: http://localhost:26336/rest/data/v1/search/project?pageNo=1&pageSize=100&valueAttr=name + - name: OS_Type + valuesUrl: http://localhost:26336/rest/data/v1/enum/OSTYPE?nameAttr=desc&valueAttr=key&filter={"enable":1} + - enforced: true + name: Site + required: true + valuesUrl: http://localhost:26336/rest/data/v1/enum/DC?nameAttr=desc&valueAttr=key + - enforced: true + name: DR_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/enum/AZ?nameAttr=desc&valueAttr=key&filter={"dc":"${option.Site.value}"} + - enforced: true + name: DR_Storage + valuesUrl: http://localhost:26336/rest/data/v1/join/storage?pageNo=1&pageSize=100&nameAttr=deviceName&valueAttr=sn&condition={"constraint":[{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]}&relations=[{"obj":"az","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.DR_Storage_Room.value}"}}]}}] + description:

Select DR Protection Group

+ - label: Search DR Protection Group Name + name: DR_Protection_Group_Search + - enforced: true + name: DR_Protection_Group + required: true + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/protectgroup?nameAttr=protectGroupName&valueAttr=protectGroupName&range=[0-100]&filter=NAME:${option.Country.value}0${option.DR_Protection_Group_Search.value} + description:

DR Test Snapshot Consistency Group

+ - enforced: true + name: DR_Test_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/associate/snapshot_consistency_group?nameAttr=NAME&valueAttr=NAME&matchAttr=NAME&match=${option.Session_Name.value}&range=[0-4096]&obj=protectgroup&objIdAttr=protectGroupId&filter=protectGroupName::${option.DR_Protection_Group.value} + - enforced: true + name: DR_Test_CG_Status + valuesUrl: http://localhost:26336/rest/data/v1/enum/SNAPCG?nameAttr=desc&valueAttr=enum&filter={"enum":${option.DR_Test_CG_Status_Enum.value}} + - enforced: true + name: Designate_Class_3 + valuesUrl: http://localhost:26336/rest/data/v1/search/tier?valueAttr=name + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_1 + required: true + valuesUrl: http://localhost:26336/rest/data/v1/echo?snap=Activate%20DR%20Test%20Snapshot%20CG%20"${option.DR_Test_CG.value}" + description: Select to confirm the result

Additional Info

+ - enforced: true + name: Session_Name + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/protectgroup?range=[0-100]&nameAttr=protectGroupName:0&nameSplit=_&valueAttr=protectGroupName:0&valueSplit=_&filter=NAME::${option.DR_Protection_Group.value} + - enforced: true + name: DR_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.DR_Protection_Group.value}%20and%20RUNNINGSTATUS::1 + - enforced: true + name: DR_Test_CG_ID + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=ID&valueAttr=ID&filter=NAME::${option.DR_Test_CG.value} + - enforced: true + name: DR_Test_CG_Status_Enum + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=RUNNINGSTATUS&valueAttr=RUNNINGSTATUS&filter=NAME::${option.DR_Test_CG.value} + scheduleEnabled: true + sequence: + commands: + - configuration: + ansible-become: 'false' + ansible-disable-limit: 'false' + ansible-extra-vars: |- + username: ${option.Username} + password: ${option.Password} + + WBE_CODE: ${option.WBE_CODE} + TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + + OS_Type: ${option.OS_Type} + DR_Storage: ${option.DR_Storage} + DR_Storage_Room: ${option.DR_Storage_Room} + DR_Protection_Group: ${option.DR_Protection_Group} + DR_CG: ${option.DR_CG} + DR_Test_CG: ${option.DR_Test_CG} + DR_Test_CG_ID: ${option.DR_Test_CG_ID} + DR_Test_CG_Status: ${option.DR_Test_CG_Status} + Designate_Class_3: ${option.Designate_Class_3} + Check_Result_1: ${option.Check_Result_1} + ansible-playbook: /var/lib/rundeck/ansible/rundeck/workflow/project001/53_dr_test_for_pg.yml + nodeStep: false + type: com.batix.rundeck.plugins.AnsiblePlaybookWorkflowStep + - configuration: + command: /bin/psql automation -c "\copy (select * from activity where WBE_CODE = '${option.WBE_CODE}' and TICKET_NUMBER = '${option.TICKET_NUMBER}') TO '/var/lib/rundeck/exp/webapp/project001/export/${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv' CSV HEADER" + description: Export Changes + nodeStep: true + type: localexec + - configuration: + command: echo "${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv" + description: Download Link + nodeStep: true + plugins: + LogFilter: + - config: + datatype: text/html + type: render-datatype + type: localexec + keepgoing: false + pluginConfig: + LogFilter: + - config: + bgcolor: green + regex: TASK.*Step_.* + type: highlight-output + - config: + bgcolor: red + regex: TASK.*Rollback_.* + type: highlight-output + - config: + bgcolor: blue + regex: TASK.*Precheck_.* + type: highlight-output + - config: + bgcolor: cyan + regex: TASK.*Result_.* + type: highlight-output + - config: + fgcolor: green + mode: bold + regex: ok=[1-9][0-9]* + type: highlight-output + - config: + fgcolor: red + mode: bold + regex: failed=[1-9][0-9]* + type: highlight-output + strategy: node-first + uuid: bf3196d6-5602-49eb-8158-39e69a875d01 diff --git a/rundeck/jobs/project001/54_dr_test_clean_for_pg.yaml b/rundeck/jobs/project001/54_dr_test_clean_for_pg.yaml new file mode 100644 index 0000000..1dd8455 --- /dev/null +++ b/rundeck/jobs/project001/54_dr_test_clean_for_pg.yaml @@ -0,0 +1,150 @@ +- defaultTab: monitor + description: Move WWNs from DR Test Host to DR Host, Re-Create Snapshot CG + executionEnabled: true + id: bbd5a12a-87e9-4ad1-9e45-49e5a7d10148 + loglevel: INFO + name: 54_dr_test_clean_for_pg + nodeFilterEditable: false + options: + - name: Job_Description + value: Move WWNs from DR Test Host to DR Host, Re-Create Snapshot CG + - name: Username + valuesUrl: http://localhost:26336/rest/data/v1/echo?${job.user.name}=${job.user.name} + description: (Optional) Alternative username/password to login all storage + - name: Password + secure: true + valueExposed: true + description:

Ticket

+ - required: true + name: WBE_CODE + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + - required: true + name: TICKET_NUMBER + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + description:

Select DR Host

+ - enforced: true + name: Country + required: true + valuesUrl: http://localhost:26336/rest/data/v1/search/project?pageNo=1&pageSize=100&valueAttr=name + - name: OS_Type + valuesUrl: http://localhost:26336/rest/data/v1/enum/OSTYPE?nameAttr=desc&valueAttr=key&filter={"enable":1} + - enforced: true + name: Site + required: true + valuesUrl: http://localhost:26336/rest/data/v1/enum/DC?nameAttr=desc&valueAttr=key + - enforced: true + name: DR_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/enum/AZ?nameAttr=desc&valueAttr=key&filter={"dc":"${option.Site.value}"} + - enforced: true + name: DR_Storage + valuesUrl: http://localhost:26336/rest/data/v1/join/storage?pageNo=1&pageSize=100&nameAttr=deviceName&valueAttr=sn&condition={"constraint":[{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]}&relations=[{"obj":"az","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.DR_Storage_Room.value}"}}]}}] + description:

Select Primary Protection Group & LUNs

+ - label: Search DR Protection Group Name + name: DR_Protection_Group_Search + - enforced: true + name: DR_Protection_Group + required: true + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/protectgroup?nameAttr=protectGroupName&valueAttr=protectGroupName&range=[0-100]&filter=NAME:${option.Country.value}0${option.DR_Protection_Group_Search.value} + description:

DR Test Snapshot Consistency Group

+ - enforced: true + name: DR_Test_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/associate/snapshot_consistency_group?nameAttr=NAME&valueAttr=NAME&matchAttr=NAME&match=${option.Session_Name.value}&range=[0-4096]&obj=protectgroup&objIdAttr=protectGroupId&filter=protectGroupName::${option.DR_Protection_Group.value} + - enforced: true + name: DR_Test_CG_Status + valuesUrl: http://localhost:26336/rest/data/v1/enum/SNAPCG?nameAttr=desc&valueAttr=enum&filter={"enum":${option.DR_Test_CG_Status_Enum.value}} + - enforced: true + name: Designate_Class_3 + valuesUrl: http://localhost:26336/rest/data/v1/search/tier?valueAttr=name + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_1 + required: true + valuesUrl: http://localhost:26336/rest/data/v1/echo?snap=Activate%20DR%20Test%20Snapshot%20CG%20"${option.DR_Test_CG.value}" + description: Select to confirm the result

Additional Info

+ - enforced: true + name: Session_Name + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/protectgroup?range=[0-100]&nameAttr=protectGroupName:0&nameSplit=_&valueAttr=protectGroupName:0&valueSplit=_&filter=NAME::${option.DR_Protection_Group.value} + - enforced: true + name: DR_CG + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/CONSISTENTGROUP?nameAttr=NAME&valueAttr=NAME&range=[0-100]&filter=localPgName::${option.DR_Protection_Group.value}%20and%20RUNNINGSTATUS::1 + - enforced: true + name: DR_Test_CG_ID + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=ID&valueAttr=ID&filter=NAME::${option.DR_Test_CG.value} + - enforced: true + name: DR_Test_CG_Status_Enum + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/snapshot_consistency_group?nameAttr=RUNNINGSTATUS&valueAttr=RUNNINGSTATUS&filter=NAME::${option.DR_Test_CG.value} + scheduleEnabled: true + sequence: + commands: + - configuration: + ansible-become: 'false' + ansible-disable-limit: 'false' + ansible-extra-vars: |- + username: ${option.Username} + password: ${option.Password} + + WBE_CODE: ${option.WBE_CODE} + TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + + OS_Type: ${option.OS_Type} + DR_Storage: ${option.DR_Storage} + DR_Storage_Room: ${option.DR_Storage_Room} + DR_Protection_Group: ${option.DR_Protection_Group} + DR_CG: ${option.DR_CG} + DR_Test_CG: ${option.DR_Test_CG} + DR_Test_CG_ID: ${option.DR_Test_CG_ID} + DR_Test_CG_Status: ${option.DR_Test_CG_Status} + Designate_Class_3: ${option.Designate_Class_3} + Check_Result_1: ${option.Check_Result_1} + ansible-playbook: /var/lib/rundeck/ansible/rundeck/workflow/project001/54_dr_test_clean_for_pg.yml + nodeStep: false + type: com.batix.rundeck.plugins.AnsiblePlaybookWorkflowStep + - configuration: + command: /bin/psql automation -c "\copy (select * from activity where WBE_CODE = '${option.WBE_CODE}' and TICKET_NUMBER = '${option.TICKET_NUMBER}') TO '/var/lib/rundeck/exp/webapp/project001/export/${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv' CSV HEADER" + description: Export Changes + nodeStep: true + type: localexec + - configuration: + command: echo "${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv" + description: Download Link + nodeStep: true + plugins: + LogFilter: + - config: + datatype: text/html + type: render-datatype + type: localexec + keepgoing: false + pluginConfig: + LogFilter: + - config: + bgcolor: green + regex: TASK.*Step_.* + type: highlight-output + - config: + bgcolor: red + regex: TASK.*Rollback_.* + type: highlight-output + - config: + bgcolor: blue + regex: TASK.*Precheck_.* + type: highlight-output + - config: + bgcolor: cyan + regex: TASK.*Result_.* + type: highlight-output + - config: + fgcolor: green + mode: bold + regex: ok=[1-9][0-9]* + type: highlight-output + - config: + fgcolor: red + mode: bold + regex: failed=[1-9][0-9]* + type: highlight-output + strategy: node-first + uuid: bbd5a12a-87e9-4ad1-9e45-49e5a7d10148 diff --git a/rundeck/jobs/project001/55_dr_test_for_multi_host.yaml b/rundeck/jobs/project001/55_dr_test_for_multi_host.yaml new file mode 100644 index 0000000..fbcb255 --- /dev/null +++ b/rundeck/jobs/project001/55_dr_test_for_multi_host.yaml @@ -0,0 +1,121 @@ +- defaultTab: monitor + description: Move WWNs from DR Hosts to DR Test Hosts, Activate Snapshot CG + executionEnabled: true + id: 30173e49-6fc7-4079-80c0-39a5dd4820d9 + loglevel: INFO + name: 55_dr_test_for_multi_host + nodeFilterEditable: false + options: + - name: Job_Description + value: Move WWNs from DR Hosts to DR Test Hosts, Activate Snapshot CG + - name: Username + valuesUrl: http://localhost:26336/rest/data/v1/echo?${job.user.name}=${job.user.name} + description: (Optional) Alternative username/password to login all storage + - name: Password + secure: true + valueExposed: true + description:

Ticket

+ - required: true + name: WBE_CODE + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + - required: true + name: TICKET_NUMBER + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + description:

Select DR Host

+ - enforced: true + name: Country + required: true + valuesUrl: http://localhost:26336/rest/data/v1/search/project?pageNo=1&pageSize=100&valueAttr=name + - name: OS_Type + valuesUrl: http://localhost:26336/rest/data/v1/enum/OSTYPE?nameAttr=desc&valueAttr=key&filter={"enable":1} + - enforced: true + name: DR_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name + - enforced: true + name: DR_Storage + valuesUrl: http://localhost:26336/rest/data/v1/join/storage?pageNo=1&pageSize=100&nameAttr=deviceName&valueAttr=sn&condition={"constraint":[{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]}&relations=[{"obj":"az","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.DR_Storage_Room.value}"}}]}}] + - label: Search Host Name + name: Host_Search + - delimiter: ',' + enforced: true + label: DR Hosts + multivalued: true + name: Select_DR_Host + valuesUrl: http://localhost:26336/rest/data/v1/search/storagehost?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&valueUnique=true&condition={"constraint":[{"simple":{"name":"name","operator":"begin%20with","value":"${option.Country.value}_${option.OS_Type.value}"}},{"logOp":"and","simple":{"name":"name","operator":"contain","value":"${option.Host_Search.value}"}},{"logOp":"and","simple":{"name":"name","operator":"end%20with","value":"_2"}},{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]} + - name: Designate_Class_3 + valuesUrl: http://localhost:26336/rest/data/v1/search/tier?valueAttr=name + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_1 + required: true + valuesUrl: http://localhost:26336/rest/data/v1/echo?host=Move%20WWNs%20from%20DR%20Hosts%20"${option.Select_DR_Host.value}"%20to%20DR%20Test%20Hosts%20"${option.Select_DR_Host.value}" + scheduleEnabled: true + sequence: + commands: + - configuration: + ansible-become: 'false' + ansible-disable-limit: 'false' + ansible-extra-vars: |- + username: ${option.Username} + password: ${option.Password} + + WBE_CODE: ${option.WBE_CODE} + TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + + OS_Type: ${option.OS_Type} + DR_Storage: ${option.DR_Storage} + DR_Storage_Room: ${option.DR_Storage_Room} + Select_DR_Host: ${option.Select_DR_Host} + Check_Result_1: ${option.Check_Result_1} + ansible-playbook: /var/lib/rundeck/ansible/rundeck/workflow/project001/55_dr_test_for_multi_host.yml + nodeStep: false + type: com.batix.rundeck.plugins.AnsiblePlaybookWorkflowStep + - configuration: + command: /bin/psql automation -c "\copy (select * from activity where WBE_CODE = '${option.WBE_CODE}' and TICKET_NUMBER = '${option.TICKET_NUMBER}') TO '/var/lib/rundeck/exp/webapp/project001/export/${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv' CSV HEADER" + description: Export Changes + nodeStep: true + type: localexec + - configuration: + command: echo "${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv" + description: Download Link + nodeStep: true + plugins: + LogFilter: + - config: + datatype: text/html + type: render-datatype + type: localexec + keepgoing: false + pluginConfig: + LogFilter: + - config: + bgcolor: green + regex: TASK.*Step_.* + type: highlight-output + - config: + bgcolor: red + regex: TASK.*Rollback_.* + type: highlight-output + - config: + bgcolor: blue + regex: TASK.*Precheck_.* + type: highlight-output + - config: + bgcolor: cyan + regex: TASK.*Result_.* + type: highlight-output + - config: + fgcolor: green + mode: bold + regex: ok=[1-9][0-9]* + type: highlight-output + - config: + fgcolor: red + mode: bold + regex: failed=[1-9][0-9]* + type: highlight-output + strategy: node-first + uuid: 30173e49-6fc7-4079-80c0-39a5dd4820d9 diff --git a/rundeck/jobs/project001/56_dr_test_clean_for_multi_host.yaml b/rundeck/jobs/project001/56_dr_test_clean_for_multi_host.yaml new file mode 100644 index 0000000..5eb6a06 --- /dev/null +++ b/rundeck/jobs/project001/56_dr_test_clean_for_multi_host.yaml @@ -0,0 +1,119 @@ +- defaultTab: monitor + description: Move WWNs from DR Test Hosts to DR Hosts, Re-Create Snapshot CG + executionEnabled: true + id: 3d843ddc-6c18-4f73-ad9a-e67cda198aab + loglevel: INFO + name: 56_dr_test_clean_for_multi_host + nodeFilterEditable: false + options: + - name: Job_Description + value: Move WWNs from DR Test Hosts to DR Hosts, Re-Create Snapshot CG + - name: Username + valuesUrl: http://localhost:26336/rest/data/v1/echo?${job.user.name}=${job.user.name} + description: (Optional) Alternative username/password to login all storage + - name: Password + secure: true + valueExposed: true + description:

Ticket

+ - required: true + name: WBE_CODE + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + - required: true + name: TICKET_NUMBER + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + description:

Select DR Host

+ - enforced: true + name: Country + required: true + valuesUrl: http://localhost:26336/rest/data/v1/search/project?pageNo=1&pageSize=100&valueAttr=name + - name: OS_Type + valuesUrl: http://localhost:26336/rest/data/v1/enum/OSTYPE?nameAttr=desc&valueAttr=key&filter={"enable":1} + - enforced: true + name: DR_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name + - enforced: true + name: DR_Storage + valuesUrl: http://localhost:26336/rest/data/v1/join/storage?pageNo=1&pageSize=100&nameAttr=deviceName&valueAttr=sn&condition={"constraint":[{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]}&relations=[{"obj":"az","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.DR_Storage_Room.value}"}}]}}] + - label: Search Host Name + name: Host_Search + - delimiter: ',' + enforced: true + label: DR Hosts + multivalued: true + name: Select_DR_Host + valuesUrl: http://localhost:26336/rest/data/v1/search/storagehost?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name&valueUnique=true&condition={"constraint":[{"simple":{"name":"name","operator":"begin%20with","value":"${option.Country.value}_${option.OS_Type.value}"}},{"logOp":"and","simple":{"name":"name","operator":"contain","value":"${option.Host_Search.value}"}},{"logOp":"and","simple":{"name":"name","operator":"end%20with","value":"_2"}},{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]} + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_1 + required: true + valuesUrl: http://localhost:26336/rest/data/v1/echo?host=Move%20WWNs%20from%20DR%20Hosts%20"${option.Select_DR_Host.value}"%20to%20DR%20Test%20Hosts%20"${option.Select_DR_Host.value}" + scheduleEnabled: true + sequence: + commands: + - configuration: + ansible-become: 'false' + ansible-disable-limit: 'false' + ansible-extra-vars: |- + username: ${option.Username} + password: ${option.Password} + + WBE_CODE: ${option.WBE_CODE} + TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + + OS_Type: ${option.OS_Type} + DR_Storage: ${option.DR_Storage} + DR_Storage_Room: ${option.DR_Storage_Room} + Select_DR_Host: ${option.Select_DR_Host} + Check_Result_1: ${option.Check_Result_1} + ansible-playbook: /var/lib/rundeck/ansible/rundeck/workflow/project001/56_dr_test_clean_for_multi_host.yml + nodeStep: false + type: com.batix.rundeck.plugins.AnsiblePlaybookWorkflowStep + - configuration: + command: /bin/psql automation -c "\copy (select * from activity where WBE_CODE = '${option.WBE_CODE}' and TICKET_NUMBER = '${option.TICKET_NUMBER}') TO '/var/lib/rundeck/exp/webapp/project001/export/${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv' CSV HEADER" + description: Export Changes + nodeStep: true + type: localexec + - configuration: + command: echo "${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv" + description: Download Link + nodeStep: true + plugins: + LogFilter: + - config: + datatype: text/html + type: render-datatype + type: localexec + keepgoing: false + pluginConfig: + LogFilter: + - config: + bgcolor: green + regex: TASK.*Step_.* + type: highlight-output + - config: + bgcolor: red + regex: TASK.*Rollback_.* + type: highlight-output + - config: + bgcolor: blue + regex: TASK.*Precheck_.* + type: highlight-output + - config: + bgcolor: cyan + regex: TASK.*Result_.* + type: highlight-output + - config: + fgcolor: green + mode: bold + regex: ok=[1-9][0-9]* + type: highlight-output + - config: + fgcolor: red + mode: bold + regex: failed=[1-9][0-9]* + type: highlight-output + strategy: node-first + uuid: 3d843ddc-6c18-4f73-ad9a-e67cda198aab diff --git a/rundeck/jobs/project001/57_dr_test_for_multi_cluster.yaml b/rundeck/jobs/project001/57_dr_test_for_multi_cluster.yaml new file mode 100644 index 0000000..6802d5e --- /dev/null +++ b/rundeck/jobs/project001/57_dr_test_for_multi_cluster.yaml @@ -0,0 +1,124 @@ +- defaultTab: monitor + description: Move WWNs from DR Hosts to DR Test Hosts, Activate Snapshot CG + executionEnabled: true + id: bbd23f67-65f8-432b-8d5c-2c31d40fd383 + loglevel: INFO + name: 57_dr_test_for_multi_cluster + nodeFilterEditable: false + options: + - name: Job_Description + value: Move WWNs from DR Hosts to DR Test Hosts, Activate Snapshot CG + - name: Username + valuesUrl: http://localhost:26336/rest/data/v1/echo?${job.user.name}=${job.user.name} + description: (Optional) Alternative username/password to login all storage + - name: Password + secure: true + valueExposed: true + description:

Ticket

+ - required: true + name: WBE_CODE + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + - required: true + name: TICKET_NUMBER + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + description:

Select DR Cluster

+ - enforced: true + name: Country + required: true + valuesUrl: http://localhost:26336/rest/data/v1/search/project?pageNo=1&pageSize=100&valueAttr=name + - name: OS_Type + valuesUrl: http://localhost:26336/rest/data/v1/enum/OSTYPE?nameAttr=desc&valueAttr=key&filter={"enable":1} + - enforced: true + name: DR_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name + - enforced: true + name: DR_Storage + valuesUrl: http://localhost:26336/rest/data/v1/join/storage?pageNo=1&pageSize=100&nameAttr=deviceName&valueAttr=sn&condition={"constraint":[{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]}&relations=[{"obj":"az","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.DR_Storage_Room.value}"}}]}}] + - label: Search Cluster Name + name: Cluster_Search + - delimiter: ',' + enforced: true + label: DR Hosts + multivalued: true + name: Select_DR_Cluster + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/hostgroup?range=[0-100]&nameAttr=NAME&valueAttr=NAME&filter=NAME:${option.Country.value}_${option.OS_Type.value}_${option.Cluster_Search.value} + - name: Designate_Class_3 + valuesUrl: http://localhost:26336/rest/data/v1/search/tier?valueAttr=name + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_1 + required: true + valuesUrl: http://localhost:26336/rest/data/v1/echo?cluster=Move%20WWNs%20from%20DR%20Hosts%20in%20Cluster%20"${option.Select_DR_Cluster.value}"%20to%20DR%20Test%20Hosts%20in%20Cluster%20" + description: Select to confirm the result + scheduleEnabled: true + sequence: + commands: + - configuration: + ansible-become: 'false' + ansible-disable-limit: 'false' + ansible-extra-vars: |- + username: ${option.Username} + password: ${option.Password} + + WBE_CODE: ${option.WBE_CODE} + TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + + OS_Type: ${option.OS_Type} + DR_Storage_Room: ${option.DR_Storage_Room} + DR_Storage: ${option.DR_Storage} + Select_DR_Cluster: ${option.Select_DR_Cluster} + Class_3: ${option.Class_3} + Designate_Class_3: ${option.Designate_Class_3} + Check_Result_1: ${option.Check_Result_1} + ansible-playbook: /var/lib/rundeck/ansible/rundeck/workflow/project001/57_dr_test_for_multi_cluster.yml + nodeStep: false + type: com.batix.rundeck.plugins.AnsiblePlaybookWorkflowStep + - configuration: + command: /bin/psql automation -c "\copy (select * from activity where WBE_CODE = '${option.WBE_CODE}' and TICKET_NUMBER = '${option.TICKET_NUMBER}') TO '/var/lib/rundeck/exp/webapp/project001/export/${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv' CSV HEADER" + description: Export Changes + nodeStep: true + type: localexec + - configuration: + command: echo "${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv" + description: Download Link + nodeStep: true + plugins: + LogFilter: + - config: + datatype: text/html + type: render-datatype + type: localexec + keepgoing: false + pluginConfig: + LogFilter: + - config: + bgcolor: green + regex: TASK.*Step_.* + type: highlight-output + - config: + bgcolor: red + regex: TASK.*Rollback_.* + type: highlight-output + - config: + bgcolor: blue + regex: TASK.*Precheck_.* + type: highlight-output + - config: + bgcolor: cyan + regex: TASK.*Result_.* + type: highlight-output + - config: + fgcolor: green + mode: bold + regex: ok=[1-9][0-9]* + type: highlight-output + - config: + fgcolor: red + mode: bold + regex: failed=[1-9][0-9]* + type: highlight-output + strategy: node-first + uuid: bbd23f67-65f8-432b-8d5c-2c31d40fd383 diff --git a/rundeck/jobs/project001/58_dr_test_clean_for_multi_cluster.yaml b/rundeck/jobs/project001/58_dr_test_clean_for_multi_cluster.yaml new file mode 100644 index 0000000..db60997 --- /dev/null +++ b/rundeck/jobs/project001/58_dr_test_clean_for_multi_cluster.yaml @@ -0,0 +1,120 @@ +- defaultTab: monitor + description: Move WWNs from DR Test Hosts to DR Hosts, Re-Create Snapshot CG + executionEnabled: true + id: 9654a965-0129-497e-b386-408c9cc2fdcf + loglevel: INFO + name: 58_dr_test_clean_for_multi_cluster + nodeFilterEditable: false + options: + - name: Job_Description + value: Move WWNs from DR Test Hosts to DR Hosts, Re-Create Snapshot CG + - name: Username + valuesUrl: http://localhost:26336/rest/data/v1/echo?${job.user.name}=${job.user.name} + description: (Optional) Alternative username/password to login all storage + - name: Password + secure: true + valueExposed: true + description:

Ticket

+ - required: true + name: WBE_CODE + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + - required: true + name: TICKET_NUMBER + valuesUrl: http://localhost:26336/rest/data/v1/echo?${DATE:YYYYMMddHHmmss}=timestamp + description:

Select DR Cluster

+ - enforced: true + name: Country + required: true + valuesUrl: http://localhost:26336/rest/data/v1/search/project?pageNo=1&pageSize=100&valueAttr=name + - name: OS_Type + valuesUrl: http://localhost:26336/rest/data/v1/enum/OSTYPE?nameAttr=desc&valueAttr=key&filter={"enable":1} + - enforced: true + name: DR_Storage_Room + valuesUrl: http://localhost:26336/rest/data/v1/join/az?pageNo=1&pageSize=100&nameAttr=name&valueAttr=name + - enforced: true + name: DR_Storage + valuesUrl: http://localhost:26336/rest/data/v1/join/storage?pageNo=1&pageSize=100&nameAttr=deviceName&valueAttr=sn&condition={"constraint":[{"logOp":"and","simple":{"name":"dataStatus","operator":"equal","value":"normal"}}]}&relations=[{"obj":"az","condition":{"constraint":[{"logOp":"and","simple":{"name":"name","operator":"equal","value":"${option.DR_Storage_Room.value}"}}]}}] + - label: Search Cluster Name + name: Cluster_Search + - delimiter: ',' + enforced: true + label: DR Hosts + multivalued: true + name: Select_DR_Test_Cluster + valuesUrl: http://localhost:26337/deviceManager/rest/${option.DR_Storage.value}/data/v1/search/hostgroup?range=[0-100]&nameAttr=NAME&valueAttr=NAME&filter=NAME:${option.Country.value}_${option.OS_Type.value}_${option.Cluster_Search.value} + - delimiter: ',' + enforced: true + multivalued: true + name: Check_Result_1 + required: true + valuesUrl: http://localhost:26336/rest/data/v1/echo?cluster=Move%20WWNs%20from%20DR%20Hosts%20in%20Cluster%20"${option.Select_DR_Cluster.value}"%20to%20DR%20Test%20Hosts%20in%20Cluster%20" + description: Select to confirm the result + scheduleEnabled: true + sequence: + commands: + - configuration: + ansible-become: 'false' + ansible-disable-limit: 'false' + ansible-extra-vars: |- + username: ${option.Username} + password: ${option.Password} + + WBE_CODE: ${option.WBE_CODE} + TICKET_NUMBER: ${option.TICKET_NUMBER} + Country: ${option.Country} + Job_User: ${job.user.name} + + OS_Type: ${option.OS_Type} + DR_Storage_Room: ${option.DR_Storage_Room} + DR_Storage: ${option.DR_Storage} + Select_DR_Test_Cluster: ${option.Select_DR_Test_Cluster} + Check_Result_1: ${option.Check_Result_1} + ansible-playbook: /var/lib/rundeck/ansible/rundeck/workflow/project001/58_dr_test_clean_for_multi_cluster.yml + nodeStep: false + type: com.batix.rundeck.plugins.AnsiblePlaybookWorkflowStep + - configuration: + command: /bin/psql automation -c "\copy (select * from activity where WBE_CODE = '${option.WBE_CODE}' and TICKET_NUMBER = '${option.TICKET_NUMBER}') TO '/var/lib/rundeck/exp/webapp/project001/export/${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv' CSV HEADER" + description: Export Changes + nodeStep: true + type: localexec + - configuration: + command: echo "${job.name}-${job.execid}-${job.user.name}-${option.TICKET_NUMBER}.csv" + description: Download Link + nodeStep: true + plugins: + LogFilter: + - config: + datatype: text/html + type: render-datatype + type: localexec + keepgoing: false + pluginConfig: + LogFilter: + - config: + bgcolor: green + regex: TASK.*Step_.* + type: highlight-output + - config: + bgcolor: red + regex: TASK.*Rollback_.* + type: highlight-output + - config: + bgcolor: blue + regex: TASK.*Precheck_.* + type: highlight-output + - config: + bgcolor: cyan + regex: TASK.*Result_.* + type: highlight-output + - config: + fgcolor: green + mode: bold + regex: ok=[1-9][0-9]* + type: highlight-output + - config: + fgcolor: red + mode: bold + regex: failed=[1-9][0-9]* + type: highlight-output + strategy: node-first + uuid: 9654a965-0129-497e-b386-408c9cc2fdcf diff --git a/rundeck/projects/project001/readme.md b/rundeck/projects/project001/readme.md index 616e613..2ecb715 100644 --- a/rundeck/projects/project001/readme.md +++ b/rundeck/projects/project001/readme.md @@ -315,4 +315,75 @@ + + +### Protection Group + + + + + + + + + + + + + + + + +
49 - Create PG50 - Add LUNs to PG51 - Remove LUNs from PG52 - Delete PG
+ + + + + + + +
+ + + + + + + + + + + + +
53 - DR Test for PG54 - DR Test Clean for PG
+ + + +
+ +### Multi Host DR Test + + + + + + + + + + + + + + + +
55 - Multi Host DR Test56 - Multi Host DR Test Clean57 - Multi Cluster DR Test58 - Multi Cluster DR Test Clean
+ + + + + + + +
\ No newline at end of file diff --git a/rundeck/webapp/project001/images/49_create_pg.png b/rundeck/webapp/project001/images/49_create_pg.png new file mode 100644 index 0000000000000000000000000000000000000000..6a91d6db9d97d06bad14109dd351ccace2c6921c GIT binary patch literal 14747 zcmZX5XH-*d)Fo9wdhdiLO^S;25^2(;BT|*3ASHAm6hp5{?_E$pdheZ}fC3RI0s%~< zgdz}#U;>d$-kDk7{Fom}?pn#Z&%Gz-?q{EU_DL}})u+G7bCZOGgx=6V*OG*Ul%04y zO-)98kEOc%nD{~CZ(tWhLc%cd--|TwHUlpSi6Dugu9h`;?WoM*qmM)Q4@Oa@JBT+3 z9kMWt)~OKUjc#lzDIk)GLc!ux53NNfm1=aB&1u2(M^Y3)Pr*AFy&oL<9aO6ms%ls} zU{i_%nul=i*;=jcO#Xrsl-rn6Ud>pj=qRD>1t3}}k;LV!%kIjg(uHBQ!pMMFm zTSIv&yM>r9i&2E8)x3sOGqJZO3U4a#7W2+Fp2sb z1#}y|$qUd+tSB%_kEY6$@X%7Bzo{lY-uZ~Mgr(yhUzyn_QeIm}lx@F)TO^+3pV-92Pt@(22 zIW|m)D$=00aj>_O^A?s*PImm(cn-VXQ?fg^PUB>geX23<`79ER$omE5n8}=6DO8~_ zjOnXrs%WZvrFQ0z{*BXQm9j-L^cI&6()X6NGj}(QSy|}{lImHAQK)86^bfL)c&8&# zc4Mry5{!@Vt_M2?3=}?|eKTL!Ow1=#GJe&*Z0~fZoKGO*$5lC}7-kuVS@`m0AyHv` zB!Z^5fz7}5?lOKG{Aep0md#NYO@*7L016`|r`HD^s~i z8KSEt+|Thy*#jFkX=-PK%$^F!;!}#X7SC7^eoChDmPy{K-D0I?BF@Xi-@BK;ebBMz z5l=6F4fUC@gQ6^#J~4($rZ$bjXcD1BFZq#H31`{@z0w^-$S6v*ZYmN{kZeAN&!rTy z&OO9Sj6K*<7sj|Wh^izcGIdGEODfG`yd&RJZx@CFJU(AOfNghkb%^JE{j)ig6)Ovfd#b-Tfut^wkM01W037dJli2& zdNuGqTAgd|Rw3uA(5@a@k@zs75Fu#N^Bk}ATl{F1BKGa4xjNmfLU#($=4&|}Jz70+ zfGmJmQlpXlZc<)c;<%Wu;#^p&7*H>+!dhNb|Ey!%KmES1k~ZzfZ79@S}K8c4;975UADVQ&!OD z!~QtWswphV`gglVpf^1{I578C@h5H`tL@LOV#0HePBM_Fqf9GO6=m-`=R)u?NUREl z549PGQ3aIk(;o|T&VEfegYIt5o4Gj4PNz4r<=HV7+k3?hn>E}%<2gQD7*?w`1q5=C z%SuGHPgw6WH#ekQ7&dkqLnaOvgzdw*S7zj*OEZIG^-5*3rM1Dy71e0HyCNS>-)k%9 z5$u9ceg1AwumbPl`1+7jRSvP{ycAw}#p+g@0e3(_8h_|A&laarW{ zZiiqfbSBA1{2@4+yGFvbOOU;|L}0{o#Q+AKHq)9m8(XNkbx76MuANK~kpR-GMOmQt zsM|>85s_pn5_4&^brpVK~;W5o}S zmQreb_;j@cQ|T}h>92+p9=l;qd{_;8=W-PC$d4DMbHsr3@tx(~S;$sesRp{k)?0RY zl92if)%_4u$8HEux%d)R$RU&RUTKH+AA{b`iMPEx5C#pjuBwYd>zfEsaVo-=o*onD z#G^Jr7?eYdQqT9*TwFUTTEwd+p|e!MyXm);SqX=*VnaN$ai(u|SRQ+s$qhBixaPtp zWTdFdxN^QoYWxFEc%%nonKLC}vT6Ne@r_%`rKDZRcjFxFnkuCv{0yci5@3mV z=*2e)cIr>r6}Oc6<_|tARFrya=Hl-FEu*$xHd$)%jJ?O3-9k>+Db+%cq#38fg2Anw zM%%5F&xd{RZ8~>%cxt)Kcs$3@3xP&P%R==e~bx*H<(wMN&px^ur6(A0BD?E=36bXi8L(;Fv<{PJgb;2P6> zpScX_VN#x+zr?oFb+##ME=t{&MBmQ;iBVkLC)zyodF2NQwlAb(&`XDUAyK^9`z4c* z!^O2=5g)5-)M&hi!%BWLNO@- z3OaXiCfw#{z>pWgVsilZ7`C;A1Qx$&eOL?WMfUqfG-O-f=bLJ6LbRGF?vlv7Sljk6 z#BaZNe>*s*;`vEuEoFIQ8K8npv4TbDMdMtX$awWFW07w|)(~(BRT^)uTg%?+PT6C+ z1Vtw-b3LVih-j(V7`02ch--K3CnX=S@l7Xn!BZ2uBBRxJxiOAv+Tn9?iL|V@TQ4@h z{rhn6S|QE`mP6fu%aRT_DWwFs1skC=cHJA$##`dGT4gZ~>#KS&DKQA?wlZ6*%gjSO zHb96)fP2KS%Vdx0pi8RcCINbvR}7fo#r{~suYL_G6}wnh9dKrKPw3+ahz7oloK~9( z(_L9c11)pi^e5U%O&X%KgA6BVkxBaNH4qj5F9H-kvJ^g)ls?pNIVB+!^3nr!cyUJ# zBX#O6Rg4zoUKYVWC!LA0cTwcg6Iv-1#Yxi|SI!Sk%9> ze>1=-RCs`@>OL!7trJmS@5`#2K_-rFNZP!*G!2uN6LjXi>Psk7PT=IgS|oz#GJ1q~ zKSrAfb~b9U1ZmpmbS?K;iF~l%Xzw@c{LbEOZMT4v27KTqxx-8k>naHUt})*23kgmY zlpG&Sb{DpN7twaS$9+Z)Gy)^s-yCd8`Zcj=n&Q7R7^_K@-U&7Lx;@0zh}cddSIkgb zXEBXPbsx(eG?zzZfz-~yAx`quQ)#=A_5GN%XN_YE!3s?(X0@n!q@kJ`onnvxVYIa= zQ6Hnt@p_y23DydoH~1*#c*x_SsqWv{w}aW8`z+*&0HrMwd8UpSmo-+TfYa`z8^kpa zW1h|WH9kp;jB{d1pjEfq9^=bj$NCPEN%Jon$qcv!)Sg=o;rbBhsYMHfm(ZQ%mwLz_ z(e!E;hit3!mQR}Sq*0re5K&K}M2@=B)`uP9nMMnX2ju@4?zcsGzPq+6?STCxn zFWM-qq)dbcRIX1i_Ir_z!vwX}b-Lx{7UERyQ3gof;dqH2uhz6qCxYzB_tPpbM81;E zEAJ<-drD;m?RE5@C-s7hO+wYOs}h}M7Rp_PD0+4~wJl%L|JaL+hrIjo=*4O<4WMn@ z%9W7;rP%i|b6ExU5S^c=e*}V~)yFPMJw{qh#FJ*fKlP_F@12kpg~HSXA5ne1`LZvx zUX4}*Hr3!xI%?H_cf*2mTX#GY;IGv<_(_FFXrjpMok9v3(92${m%qtf3OgO2Dx;pk zv*Zqmr*)OHo~}2|%WtbNJ&ud?`}WlRcf>ce0@wwUb7FO*I|B0Ul%EL9@Q`-V_DqQYO6W(nqr@ zcxlo$9li5?wV)XPal2NfR)(47TA!L`ZB8jB)2qw0&b=OC_`!N(&TRNS)Hx#SzQJLu zNwq3O-sjK~&co5NYl4DqKSH|c%8`+I3jJR7V~3pZzfwcz42L#+<^JrW!|7ytUi!W$o$DMScgr+Azu%ssT{qqFuVQjg{^a}Fx_S{{g*dWl5J3Cld59V z=nAv@aFvwTFIRR$X>XK^arvNX6@09S2_(yNBRyF`^{KrjH)O6-;NW3H+mmxa=O7!6 z9-hl39}7EXTTdaCYC3qlJK~E^QX~33i%fGK`Fzv+S9{c8*XANuyx4`EN7dt8M3zpV zBpDRqUc`A1SBaKx$f>B#WwK=Mdpw@%wL0%Hx`KGaM&C9a8dknss&BXy5HY^xOTXEV zC;}A#cxBcqDGG+{IYgRc6wA%?e5zDk8kh z=Dw7e*<~ihI?!rYnR!SuX|FywTs36V{#8)5RLoSBaZ_l*J?>8ZTiwJckxvNY7}@%aTb z_0-wIqzifq82pa1F_If!Ub z>R8g(fx;x`LL(Z?Ca268nQkI#sZ|)Cexb#a$a6gO&AVTOk8kV~Qja&82Kg0YL@7Nq zrxn61xw#?*aTn+Ia*8Q<2ag=7L*6#$ie(>C5b_nI7_fjCLxX;B5Ox@2x9xX8V*(JsdYy9C|APnTm%3WdJdvpW;mYI z8(ORKO=l<`#mP!A=K{u3-&~+^@4W=#J(&fx5;1oh` znkaBt;q86CN&(c~C9f!zrUQzf16!g2w4V+m?r=8#cF9rbK6*Q7sSFs)_(nGvKG>OA z{ayPf6N{*;+2f``PwF}vtM*G@>HLlzl<}G9EBr(0$Hf905@&Z5D^1gXB{8kt{hl+u zY2MTZ@~EpduHUWy$nT3$&rknWmB9Cjal1ORY@D!#*eZM)hvK%7roz9i>n9R)ejsbG z<|tie(1~--UNlpVl*RV4?DQ?Vn{$utp2N}zpytGdJo?Be69OQl^bsi>K6x$%C z0d~O0*}+Av)xQg*n&w1`=_Cx`f>i!S^DC4Z>OKx3Y=(a`>;E=gbk>2aj3T%S8IvS1 z{^R7^ZxU3jzy6z!9 zI}KJ)74YRE-q(;y-4x(7Q&QyH+fX%9G?$^S!a}L*c6&POo_SMCvI{X^zLKHy2eCcV zS$SA4F4mk3$Rf0&!cLUbH~Klu6H30)eRskTA;Ceo)wAM&h8OMg1YX0|N-F;ZvosWk z|Fi!1C;N29aaf||xFQbbi5s(~LX!4uRRxlu1%bXor5PnZ?LI9vl61P$E;A}(vpmhp zFrrnk2hUjA>qcA%rZv?XMX3wa0(CaE_F*1&jb_4~iJ%gEd_)WBsyu4-w$DN$b9WMG zUH)eLgL=&L%X1_Nd3RerN+OU=oe07h42~j_HcX)BKy8BYctfv8{*TeF2V(p0Ac>}g z^q?>ElUw1`k5WD>SZxqVMBSNQfIy1o;h-Ew4#S>z6;D3O=Ym;k5e=g)P?!jJS~+T#1IPYkXfIHREr^Nw7H1 z-0>-)p#DDEFzrbXC&~)rW_B2nY=GmXX67{P+iVs9faD$k120x)j~m}EQ7TC-8e!dM zt*39?|4^oQ4=|zgl&)0TLOXeBJATE0uyzl;zOAHQtK)MY?RZ97;)CmeoGCWuaZ6i(tlC_sTp~WqXAiMwAymp z?jlOO%G@B{S-~pazFsz=%mI2`SbCwA1SQa}vRdQ5y!arKl;>{;@OAFMhHbs6E+eTi z7l}|9y+dbMWYy=yb;vSqFY&h_}Vt? zjZ*&GFOPrR;-&Qb&Y7~;{K2Ly@8FxA*DOWK@NJK#J4SMfxYZ%0V0oW6f4v$vt8A2t z>85aR4Mls4TYRM-fz6vPbftSTPU+br%38GeU(R^gpgz=B2SPpU= zBL@pm!yjsC6t2;7ov0N$<+w*%2e3%DHrDbA zte5}5$3N8|NRZSR=2E)O|tz-bpE1#GcAsIUsQJa^cT-Q3G;DdtCb}5sZ}DlZ`8)f$L`P7;{3h7Nj2gjk{r0G=MGQ zFJR4Z1~0I=ii8&mI?Y(4G?5{PEz(&Ln_}Ljs2d>p%4gk!QLE*;;+i|1Q)-K{wv}c6 zmE4?7Eo59uFv$0U?18#ml3s~1S*GuuK!BUs7uckN@ZU$g4GMw1nCPn-V8v861Iv@J4yHbpT){R-|8$~til1&fp8?J4?wh+Rk#A`j zv3yIS;f`R0-wihcu}fe&opCF?Sq02H)m|5<=~je$)cQNeWmjh!whkM``Jct1rSA>w zic>c43mnj&+WXArBMK*FTKyyX>TG&$=ZR% zJK@3j_wFiSB*@8LzLqiBXiDu(sj3_mi*rowfAk8@!=viH0)V9b-C?AmqEb zolAt?khCb zRnNSjNUGIwK?`Y z2-5sQ03U3{c){oD;w3>O3GpZ|Sw$wFXB!uzo84mCe>YYC+cPW79_~=?0^3pHCMuaP z79Q^8VgS<_Y>cg#Xn8#FE)Lx%@_h$a@@E2OeJ+L`LgnB})?mMNe!nx!Uh6D6 z2!aQH`aG&#dG3f(P`6eDbsrfBMF_pcPKyF1F>Xl3uudX>D^3=}s&(ubIt zoWQ{&=M`&)=V#CbP45>AZ{Z9E-E%bMk2oy7MIe@CfZ?^a-K4iCdlzoGvZJ&yv}7dF z9NA4u%Uv??hx(7@wHzgb_O~w52Gov|-bmU*XD=IK-gvK_yi7qx9+H(&jmobx3h)bI zje5nW&_VW))bcV$>(x$QwU1wGUcqA|N@~Jab4xSc?o43}{yi9W23PrzmusKKaefes zlzE~R8vev>geUwL&tC=bvY4TRmZdcAN(QvR+@8#5voZ>IoYBivJktZ|<4RYntUx8f zQ2FrSmCtCzC+^RL((DSCS+kAq1xv-HyhHa@{WB5G_pv?hyHGs3M){f3ows=OvR-|} z=fOs{Lv08h+FGfXg#G;fI@R~LtBX4}O-~#>MKqQwi(^FOL@5o-T}oVe8U7Zg6q_wh z!#m(&nO1MPM9a;kd3$mr!c$_!R`zdi zJDPjvip3-1X+W3qgZj<+4+}d{d{CImSx!rV$&Xki*^*RieU2!ma-ZYdDkY7(;xB^q zXfuB4ug~+No5WKFl_j#MtJfby8%Tf5K`eisq-_a!qA9-ryHYGrQC55-4qy-{iwagS z5d5Wt&^q#mqaY2ZZK}cc8W{`GNphOR49Tx3#c|_4g|9Bcl&B!f*fvq16roM$u|*Ry zK##g$;7za>?tR@7ApPVD3Lm&SEP*#1^0t2c+Mu-7H`w1lJWM*!H#Bf`E_d>|V93+& z_e|-X(Y24gFLd+tH9&>dOAr4p(t(X7`~$_47zzPuYoQwvwa)0)i*M63FvjyN$0YJ@hak?-L%{Saawe9k(HrJQX^ad5XU5=|waLx_@wk_+Lw&uGn7yEY zfB<0e;j|fQQ^yk7Kh-uK=p%DHc>#(F3D4oD+-~S41g4AAi^S|BbJPu=BSo_+K z-BQAmf*TWfDC)1mb!jDEXmS2AABJFduPUD5mnVNb!=iV8Mz#F?`g5NU=a(^heSMO4 z{aH!;3!(rY1|}b*-=vG$9;kRmr|PXwLc*=}pBG>_pZeTsP)5$ce#*rfLHSfOg}O_6 zga6Ud4N=BhIA7(TBRU@6*w!x@ztgd)JBQw1AW~aNV7GZQCD3>HE?NXfb5LaUhF0qy zm^@Svo8|Q3{?r5Q(vO5~zx42C)W?7;uV4MkbQI!?Pe%)SmNh4u@o6 zDk=`VYln~O!j~GWUrB|?OEb*^rI856|ge@Hy9Mxg7|>zh~o_}o+=D7FzsiE(7OA|jn>mhII|(7YV)AE zRDU$C$Cn3UIt%-B|3uAEQAV}4D=xCWYlz0@Ia;yhO|2ut7w>=X1g^)PT0+(?9Ot_~ zVNcpV+K_AUfGgE_=>YG$K6`~|Hj7fRE1Iq2=AxzngzT$@XC;KLpL&Ia`4h&NCE4sT zL?}`NZ3+hBC9xg&0zM1Sz{!P5KimcEOVo3Yf!}a;KSSQd_DHG$NEuJ#O7yb;>mycB zkuP*)Ju66^Hi`MrT2}E!(zUa4q~g z7i2EFf)CfGf|1mPr!N#PyMqe>l>jW;AZ1`w-u7g6dyK9G=cU#}9Gx~03j?hG!vOj4 z<2|3FaJ&n^zl2>Nj-6M#y_t#)Lc2-g7pFv+^-D)GULRbAmcII^D(QdklNz+a&ivGW zY|@4%BiG^3hf85B=;C^i&(e^}R#g=^nNZt9r9PDb!m5C-<)OP4G#Na$Od3>H`;oo0 zE@E*?3ZDc^Ed_N!Vv{#5`>11XDUX>&DQg%FlcMQ92T^9|yHPLRb=tt~JR?B&IHNL0 zF&;wN?CLFk?p30fv_IcOT}K4KT72pNBx!_g#v4r8|9=0d&2%Jf0G{z4-GQQz#8?9{ z=iGyU>s(}$FxiA-r!NB4B4?!na8gXHwv+AIS0wS~(v z>e&GBUdCgB3naLUWPM&q?33ARnP`uHfRPHs^O6k64T0Go@{zJ*a-=}?K-6U@$Io(( zzwcKqhO^;gzhh#xlz>-Y0V1ZNbDo)E#DKaC>}HfeGlp8NN0Iq zQK_B5Enro=>S_KABMnU{?)gl{H^yKHn6sN>P{jn2PK*$E`2JMXuZFQW%4py@e7OKP z3=y67Tg9v!fQj@?vm))yVc6YZ<-psy;0H6$OKIo5^I5TTD)IU@Pbh(y1it6g+Yf!D zDvj7nuy%ZQBR9aq3Nn!2N`R}Le^Z=8BY)HuCKsbje}xfE52I$ySnh#?a*)(D*3uX) z55np{-IRz`s;{~UK66(qbrc^lKl-4*o7A;WfWwH;gv%hP0Wf)d{rCU0p_zbBo};IU zPLAhzbG%}(-YT1pvKfb23}7j$2`YlMy9wb?XU=U1@iCUG{W3H>0}D6=GWfw#Y${fF zDhSKr*Y7Xl4jO8g&nhZ#m%n@QzgKLJ_(lF>JnX<9@|%4*Y3sS60@SG_0u|(a!M`l8 zw@%OBb`gpwUta2F-V*Qc(z@|`FTh++;>Kj=TRqG<_?Y@JiJC5I{-6SqyN7>7&X>pw z4kStUzYLp(WkBZRoPT4KI0;+O62hOcjh3OPzt5uluSTN4m@c#CsJ{!hTQi>h_|7-D zYc_r|9&!DZ(HHp-T|10M(nsC(1NsaQ`#?|}->;sc#hKAxI?)F4pTGfr)mk2@_z)T@2q`Vj(tlet4L=9J5H8RMb5nC+$WJ#3eK5b1 zq)AWmk;96c0%IA(lGhkA#Y}Tr{HPly$k;013jDH=cs%==q1Rt@XZ|yzlrr$a2Q>Q{`IV)CC;sDjFKccX~VQ15v=K zy^XZnLS@gOgBbAsu$Rr)N_+EEOs#rs{duU_KN1mtl}(sbKo!vsrT&RFcMl{=1O6 z>?q~~-p4VQM3>l~l|><6Bzw~ar%w^7O&oNg{A=GGC@clnSNwuJzbOHeWgq*L_DpyC zp}lqul^@?ooz{)bH-cE2z^{`TB#hVhq|+S;Z9Pu|6#kqtZ#M;CKE7}|XFctpEWh~d z6`>Ibuj-cUA<7TDkeb!ey04nwagES8eD6Kxs;2@PIEy&C_1IawZ8o57S4sL(zljh$ z2A$`@0tPp~eL9t|0oGS-$=mKMI46~Kuq z4?i%6ZcB-Muuoe>(BaEWQaJN-A%juYMH3ob<{N>^BhgnnOajc~l-laiFf|Jo%CD#E z0|1kVfAgT0s37=2|M04!zJCCzU+?hISV=_OU1#~?D~|Yt4bP17BMBPcAgA+xiv!@` zy9ehbU*T;%K_|#EjBQsqJuehigw7EH+9(EFwPsOof6vm-SN@O<#}$7pGrAa z4Sb(2z&*&0h$IoXj~h=j4wz5259WH@JowA&Uw*^!t0ku&m&YCVgZXmf!oW)HewR6i zo9Kg@q!QKvM*Q)P3FBtiYhWM$TIj;3AqU@jki#x=Ik3FM7u;-LMl?gMWBbb8b=2I5 z8~EzioYZUb^j{yIxk6Ut_VFK=hJX7@%isdEr_CgYq_GgNd2;+FZD}e)zeKlZJ!Obd z-9{qc`jqVOpOQ^Ru{T}fmC5$CSc*=|{@rSPW>d`e!|D=2AY|}ti(FP5G?IUx?e^wl*vDo-<3?cD zQGQmsw_(qp#E=cYt`X-}H6L%0|1BG2684QD56itoOGfe1 zFoj5e%1F$l-mYSqdE+2!zJjs;xg2d$xQz-=XNjGIIoLlL3SH^$fRd=}KP1+Bt`K5d z!V!{&&%^}eyXlXMT!tS^nWVq-GB#YQ7K6kAz9e&%yT$<$Yh57%Brpz$GLlq@YIt**TYt57p#J@WjgF|i2XDUZIOJZAdUy}ayNsun z(K>y9dyO}aj)b^dAyZUC)bbHj$e0WX-p-B2Uh;Bq=v+tG2z$q=1US9xFFLc-qX{~S z4jM+CD-RU85wvDAa_Y7EEoK=#SMBBaS}vAqqVKJawT4}EnNKFprh7C)?5>eu{ zIB$3%S>24sM3?n1kHu~M-(4)muPK3nsSHE(;X-eBlnK)t)MSd(OPd1fe66J9p?|RB ztWkH=&GIq7eH+UG?nJ!|4crT}-jxr|R{%OP1a<~`4QcD1XM3S3bQx^Tm4!GAKh64I zmYq!x|HuwGmH%0M@hC-DkisBZTwtpQG1@u{v|I_3Q=m^UMLYd#A%L{!7Y`q;uq>jozxGtr|OKXbdLyQDhE_Kjnf$~ zHO+r%l`$-#x7Ul!VS!gHLc&S9ifl9o-me)G#39n^R`moy^5&PwupZ>yYm^VJuPt!b z!@1TdQm1FD9mR~Env$tVtxoOZd7hbK?k;z;IaP5=<-X71ZmqnJH%9#d*=N%afKrPU zcYg8z_(2*$=pP92f!t#nZ_YM}XVTuw${#0Si@e0k!o3P26)~-A&~0?tnV+Q3p+t5p zA%?09U;>_4CHU;qzn+p2ap2ffrZRQqg={WeN}euo;+a%Z0qvEaVYZ+Nn+QD#pN`md z`UJR(nv8lWn&bwakDR==pYOQtKXJUgq9kmyr{jjYr3#mk33(K)ev?%Q=5K;f4+S-N09O-7~gt_FmQo0BUM_hFEjORY-pJW9v4A@QpDlS4eingfq2PH{5YZ@Kgd0jCYG$ zz}FtKSeJQl{T?9Mwp9z>v|n+@s<1*Vcasi**?{9HV-HC4R7{394J>X+k?1S}(E@Xh zFS@hl%o^5q0fu*!pU2E8`Up_v5iQBsw&{6p}JgQMr#DpvaEVjtRe4F~D@t#Abz+Sq+9HS0jQuG57 zI{3?FiMYuqo6+;c&%NP?Rpffo*s`}F;6pcmciS2oqM>u%kCr_NXHN2mY?Y%3mh%f!+XwqJu zXGYG)mJm(9&JaH7%N9%MKqky(yBq_vzy(TG4sIhn^5S(@N!LXTom*0c*;nzoK46} ztjZ_5U1~b@e`s51+_4x^=+= zp14IR(B63!53sT!b}E8X1KB+qNS^dEX>*M}}}g}yS+XrPF{8i|=*Q~A%Z6aIhw$Rtu15_1Z!(Tqu# zXSsYTs4!c%J(q#aYUk1Tb%39M?_VdH$;2e`Zg1|^vA?9lJxp?=X7%Ce=s}{O8RINC z$ISaVTBN@=+T%JHjWxUX=rSM4Oj?rm?0dA$gIIXXJ08nr4~|xi+mKwjSU?^tjnkOg zOQ%$C}U5%46 zqSOl!jhx3p<91toOP3D0F97SdSDU{F1`ND_VRKMYc$!QkLu3%bp&--1+@_(xe>1|A zaJO*|m*O0^lmK#pTR~ZWcacATa0zdaH1hw!1I$R6glL16o#Ud?d7svjLf~Vl(Z7US zy=AEB45L)rCm)%^V~3I=##nz3{AJ4Q&E|JKNnQKg({=W55-nqL=5d>4P#QwCG^I=2 zMn@&RrMkUQn^)fhgKk6!4~5lNSL(;2v_*(6Ou5g|^utIwrgGwWRil}n6Gs;A`(8DT zv8q4cIxb7=-VK?f68J4D7>F?D$%e-^_bY1ADw7$f8L2}{q(IvYh=uu=f>b@67gWp3smZlgn%Lg%_TgQS3UWqJxp$@)wZs-t% zMNRR+UZd@vXcu~wes&KhbkL>W?&zvdb^vBW>jxJrAS`f3T}x0@2j zOzDFjIxA@CyfZimh9GjQ_sTl9F70uv@a+15wV>m<2lY0-wfftmQPPnfqeuSaC8w&1 z6{?b749lRd-tjt4Oii<9R2ivXe2=WggY-0$PRx;Z{K?=nBD?o7PK~{F(`8Y!S_)j&K8UOa=ooAd~0}!61;-g zkJ8ZDqCU9TM_Pzfg`Ry^OLil_xe~Y)LI-42^OR+3pJp~;ESlIPg)KLGA>vniS0kLx zMfAg)pSSSil>$wE7lFj?;7bk4-n-t`C2)P6_GVN3@feVc`K^-OirR$EDhNJV9uuj0 z&Dn!Qn`Zc@)TPs`&;cidbctU2PFvr}vK7XJTGYGSy`}RxCYSJ#u_G7sA}dv(?qV8x zt}p4`5D^^D!dj{h*ri z-))Fnp?X)0kn0jFsw|<19e<^{nDWUr?_bg6irCm8E?TwEC?W{*%~j{JozmB`8YOk- zbeznla;%p3I38K96!JADS$4aLF%X3vwz+vRUj~%5bSRU$DiF<69&!BB;uKDfuz)W8 z>r0nesd9d(4$;s5{>rayMj6^IMOXZwu#?t0!h+&+sa=?RXsduWC zHx!}7aVY+GFN7}3>#telMg z^<^fQg$q*NAR@JALp}8f%)rNI0b}nf>k{5CSv^>|+dX=E^EQ8Zcx}>P?w91 zG-f8FUpxbHa?GrBS$6$GN#QRhuM*$b&xMAtV2WmbG=_M6G zs@JAo4|VB*yL^FyrduU~3g2vm0P{!+GM@fqT@ PC5fS)scy5jbL{^DE>Zg# literal 0 HcmV?d00001 diff --git a/rundeck/webapp/project001/images/50_add_luns_to_pg.png b/rundeck/webapp/project001/images/50_add_luns_to_pg.png new file mode 100644 index 0000000000000000000000000000000000000000..f0bb8242cc85a0df90ca238974d0971c2f9e3916 GIT binary patch literal 15280 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 literal 0 HcmV?d00001 diff --git a/rundeck/webapp/project001/images/51_remove_luns_from_pg.png b/rundeck/webapp/project001/images/51_remove_luns_from_pg.png new file mode 100644 index 0000000000000000000000000000000000000000..2978869ce67b96d83588b0913fcb86fee11372d6 GIT binary patch literal 14534 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

IGq_m}yj&iv&;@Be7_`f@Y3E}t=-A;ik&uwG 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 literal 0 HcmV?d00001 diff --git a/rundeck/webapp/project001/images/53_dr_test_for_pg.png b/rundeck/webapp/project001/images/53_dr_test_for_pg.png new file mode 100644 index 0000000000000000000000000000000000000000..e8ac59575ca2a7ea6aaa1c07f3c7b236eaade243 GIT binary patch literal 14730 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 Yg{)C-;SC^MaV6d}Wes>a%qsl<0RS2QA^-pY literal 0 HcmV?d00001 diff --git a/rundeck/webapp/project001/images/54_dr_test_clean_for_pg.png b/rundeck/webapp/project001/images/54_dr_test_clean_for_pg.png new file mode 100644 index 0000000000000000000000000000000000000000..e79cfa318b1d62c116bb962e1cb0c8e3bee263d4 GIT binary patch literal 15322 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 S3*ee{@ibI)lq(U>BL6>r?)dru literal 0 HcmV?d00001 diff --git a/rundeck/webapp/project001/images/55_dr_test_for_multi_host.png b/rundeck/webapp/project001/images/55_dr_test_for_multi_host.png new file mode 100644 index 0000000000000000000000000000000000000000..4a57123a3e910341e9d14ab3d7f599e805842107 GIT binary patch literal 24377 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 +*2{H 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 0 - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" @@ -272,12 +265,12 @@ metroLgId: "{{ lgIds[0] }}" metroLunIds: "{{ checkedLuns[primaryLgName] | json_query('[*].ID') }}" metroLunNames: "{{ checkedLuns[primaryLgName] | json_query('[*].NAME') }}" - metroLunDesc: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - metroLunNamesNew: "{{ metroLunNamesNew + [ primaryHostNameNew + metroLunNames[item.0][-6:] ] }}" - metroLunDescNew: "{{ metroLunDescNew + [ class1New + metroLunDesc[item.0][1:] | replace(sessionName, sessionNameNew)] }}" - with_indexed_items: "{{ metroLunIds }}" + metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(primaryHostNameNew, old[4], class1New, old[6], old[7], old[8], sessionNameNew, old[10]) ] }}" + vars: + old: "{{ item.split('_') }}" + with_items: "{{ metroLunNames }}" when: metroLunIds|length > 0 - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" @@ -322,12 +315,12 @@ drLgId: "{{ lgIds[0] }}" drLunIds: "{{ checkedLuns[drLgName] | json_query('[*].ID') }}" drLunNames: "{{ checkedLuns[drLgName] | json_query('[*].NAME') }}" - drLunDesc: "{{ checkedLuns[drLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - drLunNamesNew: "{{ drLunNamesNew + [ drHostNameNew + drLunNames[item.0][-6:] ] }}" - drLunDescNew: "{{ drLunDescNew + [ class1New + drLunDesc[item.0][1:] | replace(sessionName, sessionNameNew)] }}" - with_indexed_items: "{{ drLunIds }}" + drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drHostNameNew, old[4], class2New, old[6], old[7], old[8], sessionNameNew, old[10]) ] }}" + vars: + old: "{{ item.split('_') }}" + with_items: "{{ drLunNames }}" when: drLunIds|length > 0 - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" @@ -359,12 +352,12 @@ drTestLgId: "{{ lgIds[0] }}" drTestLunIds: "{{ checkedLuns[drTestLgName] | json_query('[*].ID') }}" drTestLunNames: "{{ checkedLuns[drTestLgName] | json_query('[*].NAME') }}" - drTestLunDesc: "{{ checkedLuns[drTestLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - drTestLunNamesNew: "{{ drTestLunNamesNew + [ drTestHostNameNew + drTestLunNames[item.0][-6:] ] }}" - drTestLunDescNew: "{{ drTestLunDescNew + [ class1New + drTestLunDesc[item.0][1:] | replace(sessionName, sessionNameNew)] }}" - with_indexed_items: "{{ drTestLunIds }}" + drTestLunNamesNew: "{{ drTestLunNamesNew + [ lunNameTemplate | format(drTestHostNameNew, old[4], class3New, old[6], old[7], old[8], sessionNameNew, old[10]) ] }}" + vars: + old: "{{ item.split('_') }}" + with_items: "{{ drTestLunNames }}" when: drTestLunIds|length > 0 - name: Query Snapshot CG by Name @@ -422,7 +415,7 @@ Step_1_1_Rollbacked: False # Modify Primary LUNs - Step_1_2_Execute: "{{ (primaryLunIds|length > 0) and ((primaryLunNames|sort != primaryLunNamesNew|sort) or (primaryLunDesc|sort != primaryLunDescNew|sort) ) }}" + Step_1_2_Execute: "{{ (primaryLunIds|length > 0) and (primaryLunNames|sort != primaryLunNamesNew|sort) }}" Step_1_2_Completed: False Step_1_2_Rollbacked: False @@ -457,7 +450,7 @@ Step_2_1_Rollbacked: False # Modify Metro LUNs - Step_2_2_Execute: "{{ (metroEnable == 'Y') and (metroLunIds|length > 0) and ((metroLunNames|sort != metroLunNamesNew|sort) or (metroLunDesc|sort != metroLunDescNew|sort) ) and (metroEnable == 'Y') }}" + Step_2_2_Execute: "{{ (metroEnable == 'Y') and (metroLunIds|length > 0) and (metroLunNames|sort != metroLunNamesNew|sort) and (metroEnable == 'Y') }}" Step_2_2_Completed: False Step_2_2_Rollbacked: False @@ -482,7 +475,7 @@ Step_3_1_Rollbacked: False # Modify DR LUNs - Step_3_2_Execute: "{{ (protectLevel|int >= 2) and (drLunIds|length > 0) and ((drLunNames|sort != drLunNamesNew|sort) or (drLunDesc|sort != drLunDescNew|sort) ) }}" + Step_3_2_Execute: "{{ (protectLevel|int >= 2) and (drLunIds|length > 0) and (drLunNames|sort != drLunNamesNew|sort) }}" Step_3_2_Completed: False Step_3_2_Rollbacked: False @@ -502,7 +495,7 @@ Step_4_1_Rollbacked: False # Modify DR Test LUNs - Step_4_2_Execute: "{{ (protectLevel|int == 3) and (drTestLunIds|length > 0) and ((drTestLunNames|sort != drTestLunNamesNew|sort) or (drTestLunDesc|sort != drTestLunDescNew|sort) ) }}" + Step_4_2_Execute: "{{ (protectLevel|int == 3) and (drTestLunIds|length > 0) and (drTestLunNames|sort != drTestLunNamesNew|sort) }}" Step_4_2_Completed: False Step_4_2_Rollbacked: False @@ -597,7 +590,11 @@ new: "{{ class1New }}" old: "{{ class1 }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ primaryLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ primaryLunNames }}" tierName: "{{ class1New }}" @@ -617,7 +614,11 @@ new: "{{ class1New }}" old: "{{ class1 }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ metroLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ metroLunNames }}" tierName: "{{ class1New }}" @@ -637,7 +638,11 @@ new: "{{ class2New }}" old: "{{ class2 }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ drLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ drLunNames }}" tierName: "{{ class2New }}" @@ -688,9 +693,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDesc }}" - new: "{{ primaryLunDescNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -698,21 +700,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_1_2_Completed: True @@ -907,9 +894,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDesc }}" - new: "{{ metroLunDescNew }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -917,21 +901,6 @@ volumeNames: "{{ metroLunNames }}" newVolumeNames: "{{ metroLunNamesNew }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDescNew[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_2_2_Completed: True @@ -1070,9 +1039,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDesc }}" - new: "{{ drLunDescNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1080,21 +1046,6 @@ volumeNames: "{{ drLunNames }}" newVolumeNames: "{{ drLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescNew[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_2_Completed: True @@ -1206,9 +1157,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDesc }}" - new: "{{ drTestLunDescNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1216,21 +1164,6 @@ volumeNames: "{{ drTestLunNames }}" newVolumeNames: "{{ drTestLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - desc: "{{ drTestLunDescNew[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_2_Completed: True @@ -1369,9 +1302,6 @@ name: old: "{{ drTestLunNamesNew }}" new: "{{ drTestLunNames }}" - desc: - old: "{{ drTestLunDescNew }}" - new: "{{ drTestLunDesc }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1379,21 +1309,6 @@ volumeNames: "{{ drTestLunNamesNew }}" newVolumeNames: "{{ drTestLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - desc: "{{ drTestLunDesc[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_2_Rollbacked: True @@ -1504,9 +1419,6 @@ name: old: "{{ drLunNamesNew }}" new: "{{ drLunNames }}" - desc: - old: "{{ drLunDescNew }}" - new: "{{ drLunDesc }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1514,21 +1426,6 @@ volumeNames: "{{ drLunNamesNew }}" newVolumeNames: "{{ drLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDesc[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_2_Rollbacked: True @@ -1667,9 +1564,6 @@ name: old: "{{ metroLunNamesNew }}" new: "{{ metroLunNames }}" - desc: - old: "{{ metroLunDescNew }}" - new: "{{ metroLunDesc }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1684,14 +1578,6 @@ deviceToken: "{{ metroDeviceToken }}" deviceSession: "{{ metroDeviceSession }}" - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDesc[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_2_2_Rollbacked: True @@ -1886,9 +1772,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescNew }}" - new: "{{ primaryLunDesc }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1896,21 +1779,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDesc[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_1_2_Rollbacked: True @@ -1958,7 +1826,11 @@ new: "{{ class2 }}" old: "{{ class2New }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ drLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ drLunNames }}" tierName: "{{ class2 }}" @@ -1978,7 +1850,11 @@ new: "{{ class1 }}" old: "{{ class1New }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ metroLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ metroLunNames }}" tierName: "{{ class1 }}" @@ -1998,7 +1874,11 @@ new: "{{ class1 }}" old: "{{ class1New }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ primaryLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ primaryLunNames }}" tierName: "{{ class1 }}" @@ -2228,9 +2108,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDesc }}" - new: "{{ primaryLunDescNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_1_2_Completed }}" @@ -2345,9 +2222,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDesc }}" - new: "{{ metroLunDescNew }}" device: "{{ metroDeviceName }}" result: succeeded: "{{ Step_2_2_Completed }}" @@ -2432,9 +2306,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDesc }}" - new: "{{ drLunDescNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_3_2_Completed }}" @@ -2504,9 +2375,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDesc }}" - new: "{{ drTestLunDescNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_4_2_Completed }}" diff --git a/rundeck/workflow/project001/05_create_cluster.yml b/rundeck/workflow/project001/05_create_cluster.yml index 7bf07bf..6816343 100644 --- a/rundeck/workflow/project001/05_create_cluster.yml +++ b/rundeck/workflow/project001/05_create_cluster.yml @@ -29,7 +29,7 @@ Cluster_Description: "{{ (Cluster_Description is none) or (Cluster_Description|length <= 64) }}" Hosts: "{{ (checked_cluster_hosts|unique == [True]) }}" Class_1: "{{ Class_1 in ['A','B','C','D'] }}" - Session_Name: "{{ (Session_Name is none) or (Session_Name|length <= 16 and '_' not in Session_Name) }}" + Session_Name: "{{ (Session_Name is none) or (Session_Name|length <= 13 and '_' not in Session_Name) }}" Enable_HyperMetro: "{{ Enable_HyperMetro in ['Y','N'] }}" Protection_Level: "{{ Protection_Level|int in [1,2,3] }}" Check_Result_1: "{{ ('cluster' in Check_Result_1) and (Hosts is none or 'hosts' in Check_Result_1) }}" diff --git a/rundeck/workflow/project001/07_modify_cluster_info.yml b/rundeck/workflow/project001/07_modify_cluster_info.yml index 7adcc69..d6543d2 100644 --- a/rundeck/workflow/project001/07_modify_cluster_info.yml +++ b/rundeck/workflow/project001/07_modify_cluster_info.yml @@ -15,7 +15,7 @@ Modify_Cluster_Name: "{{ (Modify_Cluster_Name is none) or (Modify_Cluster_Name|length <= 16 and '_' not in Modify_Cluster_Name) }}" Modify_Cluster_Description: "{{ (Modify_Cluster_Description is none) or (Modify_Cluster_Description|length <= 64) }}" Modify_Class_1: "{{ (Modify_Class_1 is none) or (Modify_Class_1 in ['A','B','C','D']) }}" - Modify_Session_Name: "{{ (Modify_Session_Name is none) or (Modify_Session_Name[0:3] == Country + '0' and Modify_Session_Name|length <= 19 and '_' not in Modify_Session_Name) }}" + Modify_Session_Name: "{{ (Modify_Session_Name is none) or (Modify_Session_Name[0:3] == Country + '0' and Modify_Session_Name|length <= 16 and '_' not in Modify_Session_Name) }}" Check_Result_1: "{{ ('cluster' in Check_Result_1) }}" - name: Precheck_0_1 - Check Params @@ -142,24 +142,17 @@ - set_fact: primaryLunIds: [] primaryLunNames: [] - primaryLunDesc: [] primaryLunNamesNew: [] - primaryLunDescNew: [] metroLunIds: [] metroLunNames: [] - metroLunDesc: [] metroLunNamesNew: [] - metroLunDescNew: [] drLunIds: [] drLunNames: [] - drLunDesc: [] drLunNamesNew: [] - drLunDescNew: [] drTestLunIds: [] drTestLunNames: [] - drTestLunDesc: [] drTestLunNamesNew: [] - drTestLunDescNew: [] + lunNameTemplate: "%s_%s_%s_%s_%s_%s_%s_%s" # Example: IT_AIX_HOST1MV_1, TS0000, A, 00, H3, MV, IT0DJGSS01, T1 - set_fact: Precheck_1_Execute: True @@ -201,12 +194,12 @@ primaryLgId: "{{ lgIds[0] }}" primaryLunIds: "{{ checkedLuns[primaryLgName] | json_query('[*].ID') }}" primaryLunNames: "{{ checkedLuns[primaryLgName] | json_query('[*].NAME') }}" - primaryLunDesc: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - primaryLunNamesNew: "{{ primaryLunNamesNew + [ primaryClusterNameNew + primaryLunNames[item.0][-6:] ] }}" - primaryLunDescNew: "{{ primaryLunDescNew + [ class1New + primaryLunDesc[item.0][1:] | replace(sessionName, sessionNameNew) ] }}" - with_indexed_items: "{{ primaryLunIds }}" + primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryClusterNameNew, old[4], class1New, old[6], old[7], old[8], sessionNameNew, old[10]) ] }}" + vars: + old: "{{ item.split('_') }}" + with_items: "{{ primaryLunNames }}" when: primaryLunIds|length > 0 - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" @@ -250,12 +243,12 @@ metroLgId: "{{ lgIds[0] }}" metroLunIds: "{{ checkedLuns[primaryLgName] | json_query('[*].ID') }}" metroLunNames: "{{ checkedLuns[primaryLgName] | json_query('[*].NAME') }}" - metroLunDesc: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - metroLunNamesNew: "{{ metroLunNamesNew + [ primaryClusterNameNew + metroLunNames[item.0][-6:] ] }}" - metroLunDescNew: "{{ metroLunDescNew + [ class1New + metroLunDesc[item.0][1:] | replace(sessionName, sessionNameNew)] }}" - with_indexed_items: "{{ metroLunIds }}" + metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(primaryClusterNameNew, old[4], class1New, old[6], old[7], old[8], sessionNameNew, old[10]) ] }}" + vars: + old: "{{ item.split('_') }}" + with_items: "{{ metroLunNames }}" when: metroLunIds|length > 0 - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" @@ -300,12 +293,12 @@ drLgId: "{{ lgIds[0] }}" drLunIds: "{{ checkedLuns[drLgName] | json_query('[*].ID') }}" drLunNames: "{{ checkedLuns[drLgName] | json_query('[*].NAME') }}" - drLunDesc: "{{ checkedLuns[drLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - drLunNamesNew: "{{ drLunNamesNew + [ drClusterNameNew + drLunNames[item.0][-6:] ] }}" - drLunDescNew: "{{ drLunDescNew + [ class1New + drLunDesc[item.0][1:] | replace(sessionName, sessionNameNew)] }}" - with_indexed_items: "{{ drLunIds }}" + drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drClusterNameNew, old[4], class2New, old[6], old[7], old[8], sessionNameNew, old[10]) ] }}" + vars: + old: "{{ item.split('_') }}" + with_items: "{{ drLunNames }}" when: drLunIds|length > 0 - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" @@ -337,12 +330,12 @@ drTestLgId: "{{ lgIds[0] }}" drTestLunIds: "{{ checkedLuns[drTestLgName] | json_query('[*].ID') }}" drTestLunNames: "{{ checkedLuns[drTestLgName] | json_query('[*].NAME') }}" - drTestLunDesc: "{{ checkedLuns[drTestLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - drTestLunNamesNew: "{{ drTestLunNamesNew + [ drTestClusterNameNew + drTestLunNames[item.0][-6:] ] }}" - drTestLunDescNew: "{{ drTestLunDescNew + [ class1New + drTestLunDesc[item.0][1:] | replace(sessionName, sessionNameNew)] }}" - with_indexed_items: "{{ drTestLunIds }}" + drTestLunNamesNew: "{{ drTestLunNamesNew + [ lunNameTemplate | format(drTestClusterNameNew, old[4], class3New, old[6], old[7], old[8], sessionNameNew, old[10]) ] }}" + vars: + old: "{{ item.split('_') }}" + with_items: "{{ drTestLunNames }}" when: drTestLunIds|length > 0 - name: Query Snapshot CG by Name @@ -391,7 +384,7 @@ Step_1_1_Rollbacked: False # Modify Primary LUNs - Step_1_2_Execute: "{{ (primaryLunIds|length > 0) and ((primaryLunNames|sort != primaryLunNamesNew|sort) or (primaryLunDesc|sort != primaryLunDescNew|sort) ) }}" + Step_1_2_Execute: "{{ (primaryLunIds|length > 0) and (primaryLunNames|sort != primaryLunNamesNew|sort) }}" Step_1_2_Completed: False Step_1_2_Rollbacked: False @@ -426,7 +419,7 @@ Step_2_1_Rollbacked: False # Modify Metro LUNs - Step_2_2_Execute: "{{ (metroEnable == 'Y') and (metroLunIds|length > 0) and ((metroLunNames|sort != metroLunNamesNew|sort) or (metroLunDesc|sort != metroLunDescNew|sort) ) }}" + Step_2_2_Execute: "{{ (metroEnable == 'Y') and (metroLunIds|length > 0) and (metroLunNames|sort != metroLunNamesNew|sort) }}" Step_2_2_Completed: False Step_2_2_Rollbacked: False @@ -451,7 +444,7 @@ Step_3_1_Rollbacked: False # Modify DR LUNs - Step_3_2_Execute: "{{ (protectLevel|int >= 2) and (drLunIds|length > 0) and ((drLunNames|sort != drLunNamesNew|sort) or (drLunDesc|sort != drLunDescNew|sort) ) }}" + Step_3_2_Execute: "{{ (protectLevel|int >= 2) and (drLunIds|length > 0) and (drLunNames|sort != drLunNamesNew|sort) }}" Step_3_2_Completed: False Step_3_2_Rollbacked: False @@ -471,7 +464,7 @@ Step_4_1_Rollbacked: False # Modify DR Test LUNs - Step_4_2_Execute: "{{ (protectLevel|int == 3) and (drTestLunIds|length > 0) and ((drTestLunNames|sort != drTestLunNamesNew|sort) or (drTestLunDesc|sort != drTestLunDescNew|sort) ) }}" + Step_4_2_Execute: "{{ (protectLevel|int == 3) and (drTestLunIds|length > 0) and (drTestLunNames|sort != drTestLunNamesNew|sort) }}" Step_4_2_Completed: False Step_4_2_Rollbacked: False @@ -523,7 +516,11 @@ new: "{{ class1New }}" old: "{{ class1 }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ primaryLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ primaryLunNames }}" tierName: "{{ class1New }}" @@ -543,7 +540,11 @@ new: "{{ class1New }}" old: "{{ class1 }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ metroLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ metroLunNames }}" tierName: "{{ class1New }}" @@ -563,7 +564,11 @@ new: "{{ class2New }}" old: "{{ class2 }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ drLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ drLunNames }}" tierName: "{{ class2New }}" @@ -614,9 +619,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDesc }}" - new: "{{ primaryLunDescNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -624,21 +626,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_1_2_Completed: True @@ -833,9 +820,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDesc }}" - new: "{{ metroLunDescNew }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -843,21 +827,6 @@ volumeNames: "{{ metroLunNames }}" newVolumeNames: "{{ metroLunNamesNew }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDescNew[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_2_2_Completed: True @@ -996,9 +965,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDesc }}" - new: "{{ drLunDescNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1006,21 +972,6 @@ volumeNames: "{{ drLunNames }}" newVolumeNames: "{{ drLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescNew[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_2_Completed: True @@ -1132,9 +1083,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDesc }}" - new: "{{ drTestLunDescNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1142,21 +1090,6 @@ volumeNames: "{{ drTestLunNames }}" newVolumeNames: "{{ drTestLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - desc: "{{ drTestLunDescNew[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_2_Completed: True @@ -1224,7 +1157,7 @@ # End Steps rescue: - # Begin Rollback + # Begin Rollback - block: - name: Rollback_4_4 - Modify DR Test CG Name @@ -1295,9 +1228,6 @@ name: old: "{{ drTestLunNamesNew }}" new: "{{ drTestLunNames }}" - desc: - old: "{{ drTestLunDescNew }}" - new: "{{ drTestLunDesc }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1305,21 +1235,6 @@ volumeNames: "{{ drTestLunNamesNew }}" newVolumeNames: "{{ drTestLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - desc: "{{ drTestLunDesc[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_2_Rollbacked: True @@ -1430,9 +1345,6 @@ name: old: "{{ drLunNamesNew }}" new: "{{ drLunNames }}" - desc: - old: "{{ drLunDescNew }}" - new: "{{ drLunDesc }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1440,21 +1352,6 @@ volumeNames: "{{ drLunNamesNew }}" newVolumeNames: "{{ drLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDesc[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_2_Rollbacked: True @@ -1593,9 +1490,6 @@ name: old: "{{ metroLunNamesNew }}" new: "{{ metroLunNames }}" - desc: - old: "{{ metroLunDescNew }}" - new: "{{ metroLunDesc }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1603,21 +1497,6 @@ volumeNames: "{{ metroLunNamesNew }}" newVolumeNames: "{{ metroLunNames }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDesc[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_2_2_Rollbacked: True @@ -1812,9 +1691,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescNew }}" - new: "{{ primaryLunDesc }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1822,21 +1698,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDesc[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_1_2_Rollbacked: True @@ -1884,7 +1745,11 @@ new: "{{ class2 }}" old: "{{ class2New }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ drLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ drLunNames }}" tierName: "{{ class2 }}" @@ -1904,7 +1769,11 @@ new: "{{ class1 }}" old: "{{ class1New }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ metroLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ metroLunNames }}" tierName: "{{ class1 }}" @@ -1924,7 +1793,11 @@ new: "{{ class1 }}" old: "{{ class1New }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ primaryLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: volumeNames: "{{ primaryLunNames }}" tierName: "{{ class1 }}" @@ -1936,7 +1809,7 @@ # End Rollbacks always: - # Begin Final steps + # Begin Final steps - name: Final_Step_1 - Sync Devices set_fact: @@ -2085,9 +1958,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDesc }}" - new: "{{ primaryLunDescNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_1_2_Completed }}" @@ -2202,9 +2072,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDesc }}" - new: "{{ metroLunDescNew }}" device: "{{ metroDeviceName }}" result: succeeded: "{{ Step_2_2_Completed }}" @@ -2289,9 +2156,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDesc }}" - new: "{{ drLunDescNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_3_2_Completed }}" @@ -2361,9 +2225,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDesc }}" - new: "{{ drTestLunDescNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_4_2_Completed }}" diff --git a/rundeck/workflow/project001/09_create_luns_for_host.yml b/rundeck/workflow/project001/09_create_luns_for_host.yml index e4f795d..79c333c 100644 --- a/rundeck/workflow/project001/09_create_luns_for_host.yml +++ b/rundeck/workflow/project001/09_create_luns_for_host.yml @@ -14,7 +14,7 @@ Storage: "{{ (Storage is not none and Storage != DEFAULT.noneValue) and (Storage|string|length == 20) }}" LUN_Size: "{{ (LUN_Size is not none) and (LUN_Size|int >= 1) }}" LUN_Num: "{{ (LUN_Num is not none) and (LUN_Num|int >= 1) }}" - LUN_Description: "{{ (LUN_Description is none) or (LUN_Description|string|length <= 200) }}" + LUN_Description: "{{ (LUN_Description is none) or (LUN_Description|string|length <= 255) }}" Class_1: "{{ Class_1 in ['A','B','C','D'] }}" Designate_Class_1: "{{ (Designate_Class_1 is none) or (Designate_Class_1 in ['A','B','C','D']) }}" Check_Result_1: "{{ ('lun' in Check_Result_1) }}" @@ -125,14 +125,14 @@ replicaType: "{{ REPTYPE[metroEnable+protectLevel|string]['type'] }}" # See ../../config/project001.yml - set_fact: - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" primaryLunPrefix: "{{primaryHostName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDesc: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1|{{lunRemarks}}" + primaryLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" metroLunPrefix: "{{metroHostName}}_{{protectType}}M" drLunPrefix: "{{drHostName}}_{{protectType}}N" - drLunDesc: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2|{{lunRemarks}}" + drLunSuffix: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" drTestLunPrefix: "{{drTestHostName}}_{{protectType}}N" - drTestLunDesc: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3|{{lunRemarks}}" + drTestLunSuffix: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" drTestCgNameDefault: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}3" drStarCgNameDefault: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}0" @@ -214,7 +214,7 @@ primaryLunNames: [] - set_fact: - primaryLunNames: "{{ primaryLunNames + [ lunNameTemplate | format(primaryLunPrefix, nextScsiId|int + item|int) ] }}" + primaryLunNames: "{{ primaryLunNames + [ lunNameTemplate | format(primaryLunPrefix, (nextScsiId|int + item|int), primaryLunSuffix) ] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -264,7 +264,7 @@ metroLunNames: [] - set_fact: - metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, nextScsiId|int + item|int) ] }}" + metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, (nextScsiId|int + item|int), primaryLunSuffix) ] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -317,7 +317,7 @@ drLunNames: [] - set_fact: - drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, nextScsiId|int + item|int) ] }}" + drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, (nextScsiId|int + item|int), drLunSuffix) ] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -358,8 +358,8 @@ drTestLunDescs: [] - set_fact: - drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, nextScsiId|int + item|int) ] }}" - drTestLunDescs: "{{ drTestLunDescs + [drTestLunDesc] }}" + drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, (nextScsiId|int + item|int), drTestLunSuffix) ] }}" + drTestLunDescs: "{{ drTestLunDescs + [lunRemarks] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -557,12 +557,11 @@ msg: params: luns: - lunNamePrefix: "{{ primaryLunPrefix }}" - startSuffix: "{{ nextScsiId }}" + newLunNames: "{{ primaryLunNames }}" startScsiId: "{{ nextScsiId }}" poolId: "{{ primaryPoolId }}" workload: "{{ primaryWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" addLgName: "{{ primaryLgName }}" device: "{{ primaryDeviceName }}" @@ -575,17 +574,15 @@ - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_luns.yml" vars: - lunNamePrefix: "{{ primaryLunPrefix }}" - startSuffix: "{{ nextScsiId }}" + newLunNames: "{{ primaryLunNames }}" startScsiId: "{{ nextScsiId }}" poolId: "{{ primaryPoolId }}" workload: "{{ primaryWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" addLgName: "{{ primaryLgName }}" - set_fact: primaryLunIds: "{{ newLunIds }}" - primaryLunNames: "{{ newLunNames }}" Step_1_1_Completed: True # End Step_1_1 @@ -626,11 +623,10 @@ msg: params: luns: - lunNamePrefix: "{{ metroLunPrefix }}" - startSuffix: "{{ nextScsiId }}" + newLunNames: "{{ metroLunNames }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" device: "{{ metroDeviceName }}" - set_fact: @@ -642,15 +638,13 @@ - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_luns.yml" vars: - lunNamePrefix: "{{ metroLunPrefix }}" - startSuffix: "{{ nextScsiId }}" + newLunNames: "{{ metroLunNames }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" - set_fact: metroLunIds: "{{ newLunIds }}" - metroLunNames: "{{ newLunNames }}" Step_2_1_Completed: True # End Step_2_1 @@ -761,11 +755,10 @@ msg: params: luns: - lunNamePrefix: "{{ drLunPrefix }}" - startSuffix: "{{ nextScsiId }}" + newLunNames: "{{ drLunNames }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" - desc: "{{ drLunDesc }}" + desc: "{{ lunRemarks }}" device: "{{ drDeviceName }}" - set_fact: @@ -777,15 +770,13 @@ - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_luns.yml" vars: - lunNamePrefix: "{{ drLunPrefix }}" - startSuffix: "{{ nextScsiId }}" + newLunNames: "{{ drLunNames }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" - desc: "{{ drLunDesc }}" + desc: "{{ lunRemarks }}" - set_fact: drLunIds: "{{ newLunIds }}" - drLunNames: "{{ newLunNames }}" Step_3_1_Completed: True # End Step_3_1 @@ -1101,11 +1092,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" @@ -2113,11 +2099,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" @@ -2191,12 +2172,11 @@ msg: params: luns: - lunNamePrefix: "{{ primaryLunPrefix }}" - startSuffix: "{{ nextScsiId }}" + newLunNames: "{{ primaryLunNames }}" startScsiId: "{{ nextScsiId }}" poolId: "{{ primaryPoolId }}" workload: "{{ primaryWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" addLgName: "{{ primaryLgName }}" device: "{{ primaryDeviceName }}" result: @@ -2222,11 +2202,10 @@ msg: params: luns: - lunNamePrefix: "{{ metroLunPrefix }}" - startSuffix: "{{ nextScsiId }}" + newLunNames: "{{ metroLunNames }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" device: "{{ metroDeviceName }}" result: succeeded: "{{ Step_2_1_Completed }}" @@ -2283,11 +2262,10 @@ msg: params: luns: - lunNamePrefix: "{{ drLunPrefix }}" - startSuffix: "{{ nextScsiId }}" + newLunNames: "{{ drLunNames }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" - desc: "{{ drLunDesc }}" + desc: "{{ lunRemarks }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_3_1_Completed }}" diff --git a/rundeck/workflow/project001/10_map_luns_to_host.yml b/rundeck/workflow/project001/10_map_luns_to_host.yml index 243e2c5..adbc6f2 100644 --- a/rundeck/workflow/project001/10_map_luns_to_host.yml +++ b/rundeck/workflow/project001/10_map_luns_to_host.yml @@ -119,15 +119,15 @@ replicaType: "{{ REPTYPE[metroEnable+protectLevel|string]['type'] }}" # See ../../config/project001.yml - set_fact: - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" primaryLunPrefix: "{{primaryHostName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + primaryLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" metroLunPrefix: "{{metroHostName}}_{{protectType}}M" - metroLunDesc1: "{{ class1 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}1" + metroLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}1" drLunPrefix: "{{drHostName}}_{{protectType}}N" - drLunDesc1: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" + drLunSuffix: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" drTestLunPrefix: "{{drTestHostName}}_{{protectType}}N" - drTestLunDesc1: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" + drTestLunSuffix: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" drTestCgNameDefault: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}3" drStarCgNameDefault: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}0" @@ -214,13 +214,9 @@ primaryLunDescs: "{{ checkedLuns | json_query('[*].DESCRIPTION') }}" primaryLunsSector: "{{ checkedLuns | json_query('[*].CAPACITY') }}" primaryLunNamesNew: [] - primaryLunDescsNew: [] - set_fact: - primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefix, nextScsiId|int + item.0) ] }}" - primaryLunDescsNew: "{{ primaryLunDescsNew + [ primaryLunDescs[item.0] | replace(primaryLunDesc1, primaryLunDesc1New) ] }}" - vars: - primaryLunDesc1: "{{ primaryLunDescs[item.0].split('|')[0] }}" + primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefix, (nextScsiId|int + item.0), primaryLunSuffix) ] }}" with_indexed_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" @@ -236,9 +232,11 @@ - set_fact: primaryLunsInTier: "{{ checkedVolumes | json_query(queryVolumesInTier) }}" + primaryLunsInTierClass: "{{ checkedVolumes | json_query(queryVolumesInTierClass) }}" primaryLunsNotInTier: "{{ checkedVolumes | json_query(queryVolumesNotInTier) }}" vars: - queryVolumesInTier: "[? service_level_name != '' && service_level_name != '{{class1}}' && service_level_name != null ].name" + queryVolumesInTier: "[? service_level_name != '' && service_level_name != null ].name" + queryVolumesInTierClass: "[? service_level_name != '' && service_level_name != null ].service_level_name" queryVolumesNotInTier: "[? service_level_name == '' || service_level_name == null ].name" # End Precheck_1 @@ -281,13 +279,9 @@ - set_fact: metroLunNames: [] - metroLunDescs: [] - set_fact: - metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, nextScsiId|int + item.0) ] }}" - metroLunDescs: "{{ metroLunDescs + [ primaryLunDescs[item.0] | replace(primaryLunDesc1, metroLunDesc1) ] }}" - vars: - primaryLunDesc1: "{{ primaryLunDescs[item.0].split('|')[0] }}" + metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, (nextScsiId|int + item.0), metroLunSuffix) ] }}" with_indexed_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -339,13 +333,9 @@ - set_fact: drLunNames: [] - drLunDescs: [] - set_fact: - drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, nextScsiId|int + item.0) ] }}" - drLunDescs: "{{ drLunDescs + [ primaryLunDescs[item.0] | replace(primaryLunDesc1, drLunDesc1) ] }}" - vars: - primaryLunDesc1: "{{ primaryLunDescs[item.0].split('|')[0] }}" + drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, (nextScsiId|int + item.0), drLunSuffix) ] }}" with_indexed_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -383,13 +373,9 @@ existDrTestLuns: "{{ checkedLuns[drTestLgName] | json_query('[*].lunName') }}" existDrTestLunsDesc: [] drTestLunNames: [] - drTestLunDescs: [] - set_fact: - drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, nextScsiId|int + item.0) ] }}" - drTestLunDescs: "{{ drTestLunDescs + [ primaryLunDescs[item.0] | replace(primaryLunDesc1, drTestLunDesc1) ] }}" - vars: - primaryLunDesc1: "{{ primaryLunDescs[item.0].split('|')[0] }}" + drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, (nextScsiId|int + item.0), drTestLunSuffix) ] }}" with_indexed_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -591,7 +577,7 @@ params: luns: lunNames: "{{ metroLunNames }}" - lunDescs: "{{ metroLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -609,7 +595,7 @@ lunSector: "{{ primaryLunsSector[i] }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ metroLunDescs[i] }}" + desc: "{{ primaryLunDescs[i] }}" loop: "{{ range(0, metroLunNames|length) | list }}" loop_control: loop_var: i @@ -634,7 +620,7 @@ params: luns: lunNames: "{{ drLunNames }}" - lunDescs: "{{ drLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" device: "{{ drDeviceName }}" @@ -652,7 +638,7 @@ lunSector: "{{ primaryLunsSector[i] }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" - desc: "{{ drLunDescs[i] }}" + desc: "{{ primaryLunDescs[i] }}" loop: "{{ range(0, drLunNames|length) | list }}" loop_control: loop_var: i @@ -1043,7 +1029,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ existDrTestLuns + drTestLunNames }}" activate: False - snapDescs: "{{ existDrTestLunsDesc + drTestLunDescs }}" + snapDescs: "{{ existDrTestLunsDesc + primaryLunDescs }}" device: "{{ drDeviceName }}" - set_fact: @@ -1053,18 +1039,13 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" cgName: "{{ drTestCgName }}" snapNames: "{{ existDrTestLuns + drTestLunNames }}" activate: False - snapDescs: "{{ existDrTestLunsDesc + drTestLunDescs }}" + snapDescs: "{{ existDrTestLunsDesc + primaryLunDescs }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" vars: @@ -1156,14 +1137,14 @@ volumeNames: "{{ primaryLunNames }}" tierName: "{{ class1 }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" vars: - volumeNames: "{{ primaryLunsNotInTier }}" - tierName: "{{ class1 }}" + volumeNames: "{{ primaryLunsInTier }}" + when: primaryLunsInTier|length > 0 - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: - volumeNames: "{{ primaryLunsInTier }}" + volumeNames: "{{ primaryLunNames }}" tierName: "{{ class1 }}" - set_fact: @@ -1180,9 +1161,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1190,21 +1168,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_7_Completed: True @@ -1562,9 +1525,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1572,21 +1532,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_7_Rollbacked: True @@ -1969,11 +1914,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" @@ -2105,6 +2045,15 @@ vars: volumeNames: "{{ primaryLunNames }}" + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: ["{{ item.0 }}"] + tierName: "{{ item.1 }}" + with_together: + - "{{ primaryLunsInTier }}" + - "{{ primaryLunsInTierClass }}" + when: primaryLunsInTier|length > 0 + - set_fact: Step_3_6_Rollbacked: True @@ -2124,7 +2073,7 @@ params: luns: lunNames: "{{ metroLunNames }}" - lunDescs: "{{ metroLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -2140,7 +2089,7 @@ params: luns: lunNames: "{{ drLunNames }}" - lunDescs: "{{ drLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" device: "{{ drDeviceName }}" @@ -2311,7 +2260,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ existDrTestLuns + drTestLunNames }}" activate: False - snapDescs: "{{ existDrTestLunsDesc + drTestLunDescs }}" + snapDescs: "{{ existDrTestLunsDesc + primaryLunDescs }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_3_3_Completed }}" @@ -2367,9 +2316,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_3_7_Completed }}" diff --git a/rundeck/workflow/project001/11_unmap_luns_from_host.yml b/rundeck/workflow/project001/11_unmap_luns_from_host.yml index 0cf631f..da77a64 100644 --- a/rundeck/workflow/project001/11_unmap_luns_from_host.yml +++ b/rundeck/workflow/project001/11_unmap_luns_from_host.yml @@ -1068,11 +1068,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" @@ -1932,11 +1927,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" diff --git a/rundeck/workflow/project001/13_create_luns_for_cluster.yml b/rundeck/workflow/project001/13_create_luns_for_cluster.yml index 4b3a12b..467a8b3 100644 --- a/rundeck/workflow/project001/13_create_luns_for_cluster.yml +++ b/rundeck/workflow/project001/13_create_luns_for_cluster.yml @@ -14,7 +14,7 @@ Storage: "{{ (Storage is not none and Storage != DEFAULT.noneValue) and (Storage|string|length == 20) }}" LUN_Size: "{{ (LUN_Size is not none) and (LUN_Size|int >= 1) }}" LUN_Num: "{{ (LUN_Num is not none) and (LUN_Num|int >= 1) }}" - LUN_Description: "{{ (LUN_Description is none) or (LUN_Description|string|length <= 200) }}" + LUN_Description: "{{ (LUN_Description is none) or (LUN_Description|string|length <= 255) }}" Class_1: "{{ Class_1 in ['A','B','C','D'] }}" Designate_Class_1: "{{ (Designate_Class_1 is none) or (Designate_Class_1 in ['A','B','C','D']) }}" Check_Result_1: "{{ ('lun' in Check_Result_1) }}" @@ -125,14 +125,14 @@ replicaType: "{{ REPTYPE[metroEnable+protectLevel|string]['type'] }}" # See ../../config/project001.yml - set_fact: - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" primaryLunPrefix: "{{primaryClusterName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDesc: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1|{{lunRemarks}}" + primaryLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" metroLunPrefix: "{{metroClusterName}}_{{protectType}}M" drLunPrefix: "{{drClusterName}}_{{protectType}}N" - drLunDesc: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2|{{lunRemarks}}" + drLunSuffix: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" drTestLunPrefix: "{{drTestClusterName}}_{{protectType}}N" - drTestLunDesc: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3|{{lunRemarks}}" + drTestLunSuffix: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" drTestCgNameDefault: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}3" drStarCgNameDefault: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}0" @@ -208,12 +208,12 @@ when: Start_SCSI_ID|default(none) is not none - set_fact: - nextSuffix: "{{ (maxScsiId|int - nextScsiId|int + 1) }}" + nextLunNo: "{{ (maxScsiId|int - nextScsiId|int + 1) }}" primaryLunNames: [] lunScsiIds: [] - set_fact: - primaryLunNames: "{{ primaryLunNames + [ lunNameTemplate | format(primaryLunPrefix, ( nextSuffix|int + item|int ) ) ] }}" + primaryLunNames: "{{ primaryLunNames + [ lunNameTemplate | format(primaryLunPrefix, (nextLunNo|int + item|int), primaryLunSuffix ) ] }}" lunScsiIds: "{{ lunScsiIds + [nextScsiId|int - item|int] }}" with_sequence: start=0 count="{{lunNum}}" @@ -264,7 +264,7 @@ metroLunNames: [] - set_fact: - metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, ( nextSuffix|int + item|int ) ) ] }}" + metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, (nextLunNo|int + item|int), primaryLunSuffix ) ] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -316,7 +316,7 @@ drLunNames: [] - set_fact: - drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, ( nextSuffix|int + item|int ) ) ] }}" + drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, (nextLunNo|int + item|int), drLunSuffix ) ] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -357,8 +357,8 @@ drTestLunDescs: [] - set_fact: - drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, ( nextSuffix|int + item|int ) ) ] }}" - drTestLunDescs: "{{ drTestLunDescs + [drTestLunDesc] }}" + drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, (nextLunNo|int + item|int), drTestLunSuffix ) ] }}" + drTestLunDescs: "{{ drTestLunDescs + [lunRemarks] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -556,12 +556,11 @@ msg: params: luns: - lunNamePrefix: "{{ primaryLunPrefix }}" - startSuffix: "{{ nextSuffix }}" + newLunNames: "{{ primaryLunNames }}" addLunScsiIds: "{{ lunScsiIds }}" poolId: "{{ primaryPoolId }}" workload: "{{ primaryWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" addLgName: "{{ primaryLgName }}" device: "{{ primaryDeviceName }}" @@ -574,17 +573,15 @@ - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_luns.yml" vars: - lunNamePrefix: "{{ primaryLunPrefix }}" - startSuffix: "{{ nextSuffix }}" + newLunNames: "{{ primaryLunNames }}" addLunScsiIds: "{{ lunScsiIds }}" poolId: "{{ primaryPoolId }}" workload: "{{ primaryWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" addLgName: "{{ primaryLgName }}" - set_fact: primaryLunIds: "{{ newLunIds }}" - primaryLunNames: "{{ newLunNames }}" Step_1_1_Completed: True # End Step_1_1 @@ -625,11 +622,10 @@ msg: params: luns: - lunNamePrefix: "{{ metroLunPrefix }}" - startSuffix: "{{ nextSuffix }}" + newLunNames: "{{ metroLunNames }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" device: "{{ metroDeviceName }}" - set_fact: @@ -641,15 +637,13 @@ - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_luns.yml" vars: - lunNamePrefix: "{{ metroLunPrefix }}" - startSuffix: "{{ nextSuffix }}" + newLunNames: "{{ metroLunNames }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" - set_fact: metroLunIds: "{{ newLunIds }}" - metroLunNames: "{{ newLunNames }}" Step_2_1_Completed: True # End Step_2_1 @@ -759,11 +753,10 @@ msg: params: luns: - lunNamePrefix: "{{ drLunPrefix }}" - startSuffix: "{{ nextSuffix }}" + newLunNames: "{{ drLunNames }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" - desc: "{{ drLunDesc }}" + desc: "{{ lunRemarks }}" device: "{{ drDeviceName }}" - set_fact: @@ -775,15 +768,13 @@ - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_luns.yml" vars: - lunNamePrefix: "{{ drLunPrefix }}" - startSuffix: "{{ nextSuffix }}" + newLunNames: "{{ drLunNames }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" - desc: "{{ drLunDesc }}" + desc: "{{ lunRemarks }}" - set_fact: drLunIds: "{{ newLunIds }}" - drLunNames: "{{ newLunNames }}" Step_3_1_Completed: True # End Step_3_1 @@ -1098,11 +1089,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" @@ -2111,11 +2097,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" @@ -2189,12 +2170,11 @@ msg: params: luns: - lunNamePrefix: "{{ primaryLunPrefix }}" - startSuffix: "{{ nextSuffix }}" + newLunNames: "{{ primaryLunNames }}" addLunScsiIds: "{{ lunScsiIds }}" poolId: "{{ primaryPoolId }}" workload: "{{ primaryWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" addLgName: "{{ primaryLgName }}" device: "{{ primaryDeviceName }}" result: @@ -2220,11 +2200,10 @@ msg: params: luns: - lunNamePrefix: "{{ metroLunPrefix }}" - startSuffix: "{{ nextSuffix }}" + newLunNames: "{{ metroLunNames }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ primaryLunDesc }}" + desc: "{{ lunRemarks }}" device: "{{ metroDeviceName }}" result: succeeded: "{{ Step_2_1_Completed }}" @@ -2281,11 +2260,10 @@ msg: params: luns: - lunNamePrefix: "{{ drLunPrefix }}" - startSuffix: "{{ nextSuffix }}" + newLunNames: "{{ drLunNames }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" - desc: "{{ drLunDesc }}" + desc: "{{ lunRemarks }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_3_1_Completed }}" diff --git a/rundeck/workflow/project001/14_map_luns_to_cluster.yml b/rundeck/workflow/project001/14_map_luns_to_cluster.yml index 721a285..e7b6f7c 100644 --- a/rundeck/workflow/project001/14_map_luns_to_cluster.yml +++ b/rundeck/workflow/project001/14_map_luns_to_cluster.yml @@ -120,15 +120,15 @@ replicaType: "{{ REPTYPE[metroEnable+protectLevel|string]['type'] }}" # See ../../config/project001.yml - set_fact: - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" primaryLunPrefix: "{{primaryClusterName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + primaryLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" metroLunPrefix: "{{metroClusterName}}_{{protectType}}M" - metroLunDesc1: "{{ class1 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}1" + metroLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}1" drLunPrefix: "{{drClusterName}}_{{protectType}}N" - drLunDesc1: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" + drLunSuffix: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" drTestLunPrefix: "{{drTestClusterName}}_{{protectType}}N" - drTestLunDesc1: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" + drTestLunSuffix: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" drTestCgNameDefault: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}3" drStarCgNameDefault: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}0" @@ -203,7 +203,7 @@ when: Start_SCSI_ID|default(none) is not none - set_fact: - nextSuffix: "{{ (maxScsiId|int - nextScsiId|int + 1) }}" + nextLunNo: "{{ (maxScsiId|int - nextScsiId|int + 1) }}" lunScsiIds: [] - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -215,14 +215,10 @@ primaryLunDescs: "{{ checkedLuns | json_query('[*].DESCRIPTION') }}" primaryLunsSector: "{{ checkedLuns | json_query('[*].CAPACITY') }}" primaryLunNamesNew: [] - primaryLunDescsNew: [] - set_fact: - primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefix, ( nextSuffix|int + item|int ) ) ] }}" + primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefix, (nextLunNo|int + item|int), primaryLunSuffix ) ] }}" lunScsiIds: "{{ lunScsiIds + [nextScsiId|int - item|int] }}" - primaryLunDescsNew: "{{ primaryLunDescsNew + [ primaryLunDescs[item|int] | replace(primaryLunDesc1, primaryLunDesc1New) ] }}" - vars: - primaryLunDesc1: "{{ primaryLunDescs[item|int].split('|')[0] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" @@ -238,9 +234,11 @@ - set_fact: primaryLunsInTier: "{{ checkedVolumes | json_query(queryVolumesInTier) }}" + primaryLunsInTierClass: "{{ checkedVolumes | json_query(queryVolumesInTierClass) }}" primaryLunsNotInTier: "{{ checkedVolumes | json_query(queryVolumesNotInTier) }}" vars: - queryVolumesInTier: "[? service_level_name != '' && service_level_name != '{{class1}}' && service_level_name != null ].name" + queryVolumesInTier: "[? service_level_name != '' && service_level_name != null ].name" + queryVolumesInTierClass: "[? service_level_name != '' && service_level_name != null ].service_level_name" queryVolumesNotInTier: "[? service_level_name == '' || service_level_name == null ].name" # End Precheck_1 @@ -283,13 +281,9 @@ - set_fact: metroLunNames: [] - metroLunDescs: [] - set_fact: - metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, ( nextSuffix|int + item|int ) ) ] }}" - metroLunDescs: "{{ metroLunDescs + [ primaryLunDescs[item|int] | replace(primaryLunDesc1, metroLunDesc1) ] }}" - vars: - primaryLunDesc1: "{{ primaryLunDescs[item|int].split('|')[0] }}" + metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, (nextLunNo|int + item|int), metroLunSuffix ) ] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -337,13 +331,9 @@ - set_fact: drLunNames: [] - drLunDescs: [] - set_fact: - drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, ( nextSuffix|int + item|int ) ) ] }}" - drLunDescs: "{{ drLunDescs + [ primaryLunDescs[item|int] | replace(primaryLunDesc1, drLunDesc1) ] }}" - vars: - primaryLunDesc1: "{{ primaryLunDescs[item|int].split('|')[0] }}" + drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, (nextLunNo|int + item|int), drLunSuffix ) ] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -381,13 +371,9 @@ existDrTestLuns: "{{ checkedLuns[drTestLgName] | json_query('[*].lunName') }}" existDrTestLunsDesc: [] drTestLunNames: [] - drTestLunDescs: [] - set_fact: - drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, ( nextSuffix|int + item|int ) ) ] }}" - drTestLunDescs: "{{ drTestLunDescs + [ primaryLunDescs[item|int] | replace(primaryLunDesc1, drTestLunDesc1) ] }}" - vars: - primaryLunDesc1: "{{ primaryLunDescs[item|int].split('|')[0] }}" + drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, (nextLunNo|int + item|int), drTestLunSuffix ) ] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -589,7 +575,7 @@ params: luns: lunNames: "{{ metroLunNames }}" - lunDescs: "{{ metroLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -607,7 +593,7 @@ lunSector: "{{ primaryLunsSector[i] }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ metroLunDescs[i] }}" + desc: "{{ primaryLunDescs[i] }}" loop: "{{ range(0, metroLunNames|length) | list }}" loop_control: loop_var: i @@ -632,7 +618,7 @@ params: luns: lunNames: "{{ drLunNames }}" - lunDescs: "{{ drLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" device: "{{ drDeviceName }}" @@ -650,7 +636,7 @@ lunSector: "{{ primaryLunsSector[i] }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" - desc: "{{ drLunDescs[i] }}" + desc: "{{ primaryLunDescs[i] }}" loop: "{{ range(0, drLunNames|length) | list }}" loop_control: loop_var: i @@ -1045,7 +1031,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ existDrTestLuns + drTestLunNames }}" activate: False - snapDescs: "{{ existDrTestLunsDesc + drTestLunDescs }}" + snapDescs: "{{ existDrTestLunsDesc + primaryLunDescs }}" device: "{{ drDeviceName }}" - set_fact: @@ -1055,18 +1041,13 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" cgName: "{{ drTestCgName }}" snapNames: "{{ existDrTestLuns + drTestLunNames }}" activate: False - snapDescs: "{{ existDrTestLunsDesc + drTestLunDescs }}" + snapDescs: "{{ existDrTestLunsDesc + primaryLunDescs }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" vars: @@ -1158,14 +1139,14 @@ volumeNames: "{{ primaryLunNames }}" tierName: "{{ class1 }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" vars: - volumeNames: "{{ primaryLunsNotInTier }}" - tierName: "{{ class1 }}" + volumeNames: "{{ primaryLunsInTier }}" + when: primaryLunsInTier|length > 0 - - import_tasks: "{{GLOBAL.baseDir}}/task/volume/change_volumes_to_tier.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" vars: - volumeNames: "{{ primaryLunsInTier }}" + volumeNames: "{{ primaryLunNames }}" tierName: "{{ class1 }}" - set_fact: @@ -1182,9 +1163,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1192,21 +1170,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_7_Completed: True @@ -1564,9 +1527,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1574,21 +1534,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_7_Rollbacked: True @@ -1971,11 +1916,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" @@ -2107,6 +2047,15 @@ vars: volumeNames: "{{ primaryLunNames }}" + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: ["{{ item.0 }}"] + tierName: "{{ item.1 }}" + with_together: + - "{{ primaryLunsInTier }}" + - "{{ primaryLunsInTierClass }}" + when: primaryLunsInTier|length > 0 + - set_fact: Step_3_6_Rollbacked: True @@ -2126,7 +2075,7 @@ params: luns: lunNames: "{{ metroLunNames }}" - lunDescs: "{{ metroLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -2142,7 +2091,7 @@ params: luns: lunNames: "{{ drLunNames }}" - lunDescs: "{{ drLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" device: "{{ drDeviceName }}" @@ -2313,7 +2262,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ existDrTestLuns + drTestLunNames }}" activate: False - snapDescs: "{{ existDrTestLunsDesc + drTestLunDescs }}" + snapDescs: "{{ existDrTestLunsDesc + primaryLunDescs }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_3_3_Completed }}" @@ -2369,9 +2318,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_3_7_Completed }}" diff --git a/rundeck/workflow/project001/15_unmap_luns_from_cluster.yml b/rundeck/workflow/project001/15_unmap_luns_from_cluster.yml index 101385e..ace0c9d 100644 --- a/rundeck/workflow/project001/15_unmap_luns_from_cluster.yml +++ b/rundeck/workflow/project001/15_unmap_luns_from_cluster.yml @@ -1059,11 +1059,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" @@ -1923,11 +1918,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" diff --git a/rundeck/workflow/project001/21_create_luns.yml b/rundeck/workflow/project001/21_create_luns.yml index 3c850d1..1327ebe 100644 --- a/rundeck/workflow/project001/21_create_luns.yml +++ b/rundeck/workflow/project001/21_create_luns.yml @@ -33,7 +33,7 @@ checked_params: LUN_Size: "{{ (LUN_Size is not none) and (LUN_Size|int >= 1) }}" LUN_Num: "{{ (LUN_Num is not none) and (LUN_Num|int >= 1) }}" - LUN_Description: "{{ (LUN_Description is none) or (LUN_Description|string|length <= 200) }}" + LUN_Description: "{{ (LUN_Description is none) or (LUN_Description|string|length <= 255) }}" LUN_Prefix: "{{ (LUN_Prefix is not none) and (LUN_Prefix|string|length <= 28) }}" Start_Suffix: "{{ (Start_Suffix is none) or (Start_Suffix|int >= 0 and (Start_Suffix|int + LUN_Num|int) < 1000 ) }}" @@ -43,9 +43,9 @@ - set_fact: lunNum: "{{ LUN_Num|int if (LUN_Num is not none) else 0 }}" - lunDesc: "{{Class}}_00_00_00_0000000000000000_01|{{ LUN_Description|string if (LUN_Description is not none) else '' }}" + lunDesc: "{{ LUN_Description|string if (LUN_Description is not none) else '' }}" startSuffix: "{{ Start_Suffix if (Start_Suffix is not none) else 0 }}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s_NN%0{{DEFAULT.suffixDigits}}d_{{Class}}_00_00_00_0000000000000000_01" lunNames: [] lunDescs: [] lunSizes: [] diff --git a/rundeck/workflow/project001/25_create_snapshots_for_host.yml b/rundeck/workflow/project001/25_create_snapshots_for_host.yml index f1a1d23..4edc807 100644 --- a/rundeck/workflow/project001/25_create_snapshots_for_host.yml +++ b/rundeck/workflow/project001/25_create_snapshots_for_host.yml @@ -56,8 +56,8 @@ - set_fact: sourceCgName: "{{targetLgName}}_SNAP_{{timestamp}}" targetLunPrefix: "{{targetHostName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - targetLunDesc: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else site}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1|{{snapRemarks}}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + targetLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else site}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" - set_fact: Precheck_1_Execute: True @@ -131,7 +131,7 @@ targetLunNames: [] - set_fact: - targetLunNames: "{{ targetLunNames + [ lunNameTemplate | format(targetLunPrefix, nextScsiId|int + item|int) ] }}" + targetLunNames: "{{ targetLunNames + [ lunNameTemplate | format(targetLunPrefix, (nextScsiId|int + item|int), targetLunSuffix) ] }}" with_sequence: start=0 count="{{lunNum}}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" @@ -188,7 +188,7 @@ cgName: "{{ sourceCgName }}" snapNames: "{{ targetLunNames }}" activate: "{{ snapActivate }}" - snapDesc: "{{ targetLunDesc }}" + snapDesc: "{{ snapRemarks }}" device: "{{ deviceName }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" @@ -197,7 +197,7 @@ cgName: "{{ sourceCgName }}" snapNames: "{{ targetLunNames }}" activate: "{{ snapActivate }}" - snapDesc: "{{ targetLunDesc }}" + snapDesc: "{{ snapRemarks }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" @@ -396,7 +396,7 @@ cgName: "{{ sourceCgName }}" snapNames: "{{ targetLunNames }}" activate: True - snapDesc: "{{ targetLunDesc }}" + snapDesc: "{{ snapRemarks }}" result: succeeded: "{{ Step_1_1_Completed }}" rollbacked: "{{ Step_1_1_Rollbacked }}" diff --git a/rundeck/workflow/project001/27_create_snapshots_for_cluster.yml b/rundeck/workflow/project001/27_create_snapshots_for_cluster.yml index f41b3fe..0c4ec81 100644 --- a/rundeck/workflow/project001/27_create_snapshots_for_cluster.yml +++ b/rundeck/workflow/project001/27_create_snapshots_for_cluster.yml @@ -56,8 +56,8 @@ - set_fact: sourceCgName: "{{targetLgName}}_SNAP_{{timestamp}}" targetLunPrefix: "{{targetClusterName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - targetLunDesc: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else site}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1|{{snapRemarks}}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + targetLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else site}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" - set_fact: Precheck_1_Execute: True @@ -129,12 +129,12 @@ when: Start_SCSI_ID|default(none) is not none - set_fact: - nextSuffix: "{{ (maxScsiId|int - nextScsiId|int + 1) }}" + nextLunNo: "{{ (maxScsiId|int - nextScsiId|int + 1) }}" targetLunNames: [] lunScsiIds: [] - set_fact: - targetLunNames: "{{ targetLunNames + [ lunNameTemplate | format(targetLunPrefix, ( nextSuffix|int + item|int ) ) ] }}" + targetLunNames: "{{ targetLunNames + [ lunNameTemplate | format(targetLunPrefix, (nextLunNo|int + item|int), targetLunSuffix ) ] }}" lunScsiIds: "{{ lunScsiIds + [nextScsiId|int - item|int] }}" with_sequence: start=0 count="{{lunNum}}" @@ -192,7 +192,7 @@ cgName: "{{ sourceCgName }}" snapNames: "{{ targetLunNames }}" activate: "{{ snapActivate }}" - snapDesc: "{{ targetLunDesc }}" + snapDesc: "{{ snapRemarks }}" device: "{{ deviceName }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" @@ -201,7 +201,7 @@ cgName: "{{ sourceCgName }}" snapNames: "{{ targetLunNames }}" activate: "{{ snapActivate }}" - snapDesc: "{{ targetLunDesc }}" + snapDesc: "{{ snapRemarks }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" @@ -400,7 +400,7 @@ cgName: "{{ sourceCgName }}" snapNames: "{{ targetLunNames }}" activate: True - snapDesc: "{{ targetLunDesc }}" + snapDesc: "{{ snapRemarks }}" result: succeeded: "{{ Step_1_1_Completed }}" rollbacked: "{{ Step_1_1_Rollbacked }}" diff --git a/rundeck/workflow/project001/29_dr_test_for_host.yml b/rundeck/workflow/project001/29_dr_test_for_host.yml index 7702d51..60ec2f3 100644 --- a/rundeck/workflow/project001/29_dr_test_for_host.yml +++ b/rundeck/workflow/project001/29_dr_test_for_host.yml @@ -138,11 +138,6 @@ cgName: "{{ drTestCgName }}" device: "{{ deviceName }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/activate_snapshot_cg.yml" vars: cgName: "{{ drTestCgName }}" diff --git a/rundeck/workflow/project001/30_dr_test_clean_for_host.yml b/rundeck/workflow/project001/30_dr_test_clean_for_host.yml index 1aa0097..cbefbd4 100644 --- a/rundeck/workflow/project001/30_dr_test_clean_for_host.yml +++ b/rundeck/workflow/project001/30_dr_test_clean_for_host.yml @@ -42,14 +42,7 @@ drTestCgName: "{{ DR_Test_CG }}" drTestCgId: "{{ DR_Test_CG_ID }}" class3: "{{ Class_3 }}" - - - set_fact: - protectType: "{{ REPTYPE['N3']['enum'] }}" # See ../../config/project001.yml - replicaType: "{{ REPTYPE['N3']['type'] }}" # See ../../config/project001.yml - - - set_fact: - targetLunPrefix: "{{drTestHostName}}_{{protectType}}N" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s_%s_%s_%s_%s_%s_%s_%s" # Example: IT_AIX_HOST1MV_3, TN0000, A, 00, H3, MV, IT0DJGSS01, T1 - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" @@ -118,14 +111,14 @@ - set_fact: existDrLunNames: "{{ checkedLuns[drLgName] | json_query('[*].NAME') }}" - existDrLunDescs: "{{ checkedLuns[drLgName] | json_query('[*].DESCRIPTION') }}" + targetLunDescs: "{{ checkedLuns[drLgName] | json_query('[*].DESCRIPTION') }}" targetLunNames: [] - targetLunDescs: [] - set_fact: - targetLunNames: "{{ targetLunNames + [ lunNameTemplate | format(targetLunPrefix, item.1[-3:]|int) ] }}" - targetLunDescs: "{{ targetLunDescs + [ class3 + existDrLunDescs[item.0][1:] ] }}" - with_indexed_items: "{{ existDrLunNames }}" + targetLunNames: "{{ targetLunNames + [ lunNameTemplate | format(drTestHostName, dr[4], class3, dr[6], dr[7], dr[8], dr[9], dr[10] ) ] }}" + vars: + dr: "{{ item.split('_') }}" + with_items: "{{ existDrLunNames }}" - name: Query Snapshots in CG uri: @@ -356,11 +349,6 @@ cgName: "{{ drTestCgName }}" device: "{{ deviceName }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" diff --git a/rundeck/workflow/project001/31_dr_test_for_cluster.yml b/rundeck/workflow/project001/31_dr_test_for_cluster.yml index 17c1441..0cc9e0d 100644 --- a/rundeck/workflow/project001/31_dr_test_for_cluster.yml +++ b/rundeck/workflow/project001/31_dr_test_for_cluster.yml @@ -179,11 +179,6 @@ cgName: "{{ drTestCgName }}" device: "{{ deviceName }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/activate_snapshot_cg.yml" vars: cgName: "{{ drTestCgName }}" diff --git a/rundeck/workflow/project001/32_dr_test_clean_for_cluster.yml b/rundeck/workflow/project001/32_dr_test_clean_for_cluster.yml index 37bd246..7ea3c23 100644 --- a/rundeck/workflow/project001/32_dr_test_clean_for_cluster.yml +++ b/rundeck/workflow/project001/32_dr_test_clean_for_cluster.yml @@ -42,14 +42,7 @@ drTestCgName: "{{ DR_Test_CG }}" drTestCgId: "{{ DR_Test_CG_ID }}" class3: "{{ Class_3 }}" - - - set_fact: - protectType: "{{ REPTYPE['N3']['enum'] }}" # See ../../config/project001.yml - replicaType: "{{ REPTYPE['N3']['type'] }}" # See ../../config/project001.yml - - - set_fact: - targetLunPrefix: "{{drTestClusterName}}_{{protectType}}N" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s_%s_%s_%s_%s_%s_%s_%s" # Example: IT_AIX_HOST1MV_3, TN0000, A, 00, H3, MV, IT0DJGSS01, T1 - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" @@ -163,14 +156,14 @@ - set_fact: existDrLunNames: "{{ checkedLuns[drLgName] | json_query('[*].NAME') }}" - existDrLunDescs: "{{ checkedLuns[drLgName] | json_query('[*].DESCRIPTION') }}" + targetLunDescs: "{{ checkedLuns[drLgName] | json_query('[*].DESCRIPTION') }}" targetLunNames: [] - targetLunDescs: [] - set_fact: - targetLunNames: "{{ targetLunNames + [ lunNameTemplate | format(targetLunPrefix, item.1[-3:]|int) ] }}" - targetLunDescs: "{{ targetLunDescs + [ class3 + existDrLunDescs[item.0][1:] ] }}" - with_indexed_items: "{{ existDrLunNames }}" + targetLunNames: "{{ targetLunNames + [ lunNameTemplate | format(drTestClusterName, dr[4], class3, dr[6], dr[7], dr[8], dr[9], dr[10] ) ] }}" + vars: + dr: "{{ item.split('_') }}" + with_items: "{{ existDrLunNames }}" - name: Query Snapshots in CG uri: @@ -403,11 +396,6 @@ cgName: "{{ drTestCgName }}" device: "{{ deviceName }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" diff --git a/rundeck/workflow/project001/33_add_lun_group_to_host.yml b/rundeck/workflow/project001/33_add_lun_group_to_host.yml index a02024e..1297b8a 100644 --- a/rundeck/workflow/project001/33_add_lun_group_to_host.yml +++ b/rundeck/workflow/project001/33_add_lun_group_to_host.yml @@ -17,7 +17,7 @@ Storage_Room: "{{ (Storage_Room is not none and Storage_Room != DEFAULT.noneValue) and Storage_Room in AZ }}" Host: "{{ Host is not none and Host != DEFAULT.noneValue }}" Class_1: "{{ Class_1 in ['A','B','C','D'] }}" - Session_Name: "{{ (Session_Name is not none) and (Session_Name|length <= 16 and '_' not in Session_Name) }}" + Session_Name: "{{ (Session_Name is not none) and (Session_Name|length <= 13 and '_' not in Session_Name) }}" Enable_HyperMetro: "{{ Enable_HyperMetro in ['Y','N'] }}" Protection_Level: "{{ Protection_Level|int in [1,2,3] }}" Check_Result_1: "{{ ('lungroup' in Check_Result_1) }}" diff --git a/rundeck/workflow/project001/35_add_lun_group_to_cluster.yml b/rundeck/workflow/project001/35_add_lun_group_to_cluster.yml index a50861b..18ce081 100644 --- a/rundeck/workflow/project001/35_add_lun_group_to_cluster.yml +++ b/rundeck/workflow/project001/35_add_lun_group_to_cluster.yml @@ -16,7 +16,7 @@ Storage_Room: "{{ (Storage_Room is not none and Storage_Room != DEFAULT.noneValue) and Storage_Room in AZ }}" Cluster: "{{ Cluster is not none and Cluster != DEFAULT.noneValue }}" Class_1: "{{ Class_1 in ['A','B','C','D'] }}" - Session_Name: "{{ (Session_Name is not none) and (Session_Name|length <= 16 and '_' not in Session_Name) }}" + Session_Name: "{{ (Session_Name is not none) and (Session_Name|length <= 13 and '_' not in Session_Name) }}" Enable_HyperMetro: "{{ Enable_HyperMetro in ['Y','N'] }}" Protection_Level: "{{ Protection_Level|int in [1,2,3] }}" Check_Result_1: "{{ ('lungroup' in Check_Result_1) }}" diff --git a/rundeck/workflow/project001/37_add_replica_host.yml b/rundeck/workflow/project001/37_add_replica_host.yml index 14bfa50..ee9db06 100644 --- a/rundeck/workflow/project001/37_add_replica_host.yml +++ b/rundeck/workflow/project001/37_add_replica_host.yml @@ -107,12 +107,12 @@ - set_fact: lgSuffix: "{{ LUN_Group[-2:] }}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" primaryLgName: "{{ LUN_Group }}" primaryLgDescNew: "{{ metroEnable }}{{ protectLevel }}{{class1}}{{class2}}{{class3}}_{{ sessionName }}" primaryPgName: "{{ Protection_Group }}" primaryLunPrefixNew: "{{primaryHostName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + primaryLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" - set_fact: metroDeviceSn: "{{ Metro_Storage|string }}" @@ -145,7 +145,7 @@ drLgDesc: "N{{ protectLevel }}{{ class1 }}{{class2}}{{class3}}_{{ sessionName }}" drPgName: "{{ drHostName }}_PG{{ lgSuffix }}" drLunPrefix: "{{drHostName}}_{{protectType}}N" - drLunDesc1: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" + drLunSuffix: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" when: protectLevel|int >= 2 - set_fact: @@ -157,7 +157,7 @@ - set_fact: drTestLgName: "{{ drTestHostName }}_LG{{ lgSuffix }}" drTestLunPrefix: "{{drTestHostName}}_{{protectType}}N" - drTestLunDesc1: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" + drTestLunSuffix: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" when: protectLevel|int == 3 - set_fact: @@ -242,15 +242,12 @@ primaryLunSectors: "{{ checkedLuns[primaryLgName] | json_query('[*].CAPACITY') }}" primaryLunDescs: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" primaryLunNamesNew: [] - primaryLunDescsNew: [] - set_fact: - primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, suffix|int) ] }}" - primaryLunDescsNew: "{{ primaryLunDescsNew + [ primaryLunDesc1New + '|' + remark ] }}" + primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, lunNo|int, primaryLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -303,9 +300,9 @@ metroLunNamesNew: [] - set_fact: - metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, suffix|int) ] }}" + metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, lunNo|int, primaryLunSuffixNew) ] }}" vars: - suffix: "{{ item.split('_')[4][2:] }}" + lunNo: "{{ item.split('_')[4][2:] }}" with_items: "{{ primaryLunNames }}" - block: @@ -445,15 +442,12 @@ - set_fact: drLunNames: [] - drLunDescs: [] - set_fact: - drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, suffix|int) ] }}" - drLunDescs: "{{ drLunDescs + [ drLunDesc1 + '|' + remark ] }}" + drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, lunNo|int, drLunSuffix) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" vars: @@ -499,15 +493,12 @@ - set_fact: drTestLunNames: [] - drTestLunDescs: [] - set_fact: - drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, suffix|int) ] }}" - drTestLunDescs: "{{ drTestLunDescs + [ drTestLunDesc1 + '|' + remark ] }}" + drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, lunNo|int, drTestLunSuffix) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" vars: @@ -1180,7 +1171,7 @@ params: luns: lunNames: "{{ metroLunNamesNew }}" - lunDescs: "{{ primaryLunDescsNew }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -1198,7 +1189,7 @@ lunSector: "{{ primaryLunSectors[i] }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ primaryLunDescsNew[i] }}" + desc: "{{ primaryLunDescs[i] }}" loop: "{{ range(0, metroLunNamesNew|length) | list }}" loop_control: loop_var: i @@ -1320,7 +1311,7 @@ params: luns: lunNames: "{{ drLunNames }}" - lunDescs: "{{ drLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" device: "{{ drDeviceName }}" @@ -1338,7 +1329,7 @@ lunSector: "{{ primaryLunSectors[i] }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" - desc: "{{ drLunDescs[i] }}" + desc: "{{ primaryLunDescs[i] }}" loop: "{{ range(0, drLunNames|length) | list }}" loop_control: loop_var: i @@ -1572,7 +1563,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ primaryLunDescs }}" device: "{{ drDeviceName }}" - set_fact: @@ -1582,18 +1573,13 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ primaryLunDescs }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" vars: @@ -1708,9 +1694,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1718,21 +1701,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_7_3_Completed: True @@ -1833,9 +1801,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1843,21 +1808,6 @@ volumeNames: "{{ metroLunNames }}" newVolumeNames: "{{ metroLunNamesNew }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_8_4_Completed: True @@ -2121,9 +2071,6 @@ name: old: "{{ metroLunNamesNew }}" new: "{{ metroLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -2131,21 +2078,6 @@ volumeNames: "{{ metroLunNamesNew }}" newVolumeNames: "{{ metroLunNames }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_8_4_Rollbacked: True @@ -2246,9 +2178,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -2256,21 +2185,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_7_3_Rollbacked: True @@ -3178,7 +3092,7 @@ params: luns: lunNames: "{{ metroLunNamesNew }}" - lunDescs: "{{ primaryLunDescsNew }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -3238,7 +3152,7 @@ params: luns: lunNames: "{{ drLunNames }}" - lunDescs: "{{ drLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" device: "{{ drDeviceName }}" @@ -3352,7 +3266,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ primaryLunDescs }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_6_1_Completed }}" @@ -3414,9 +3328,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_7_3_Completed }}" @@ -3479,9 +3390,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ metroDeviceName }}" result: succeeded: "{{ Step_8_4_Completed }}" diff --git a/rundeck/workflow/project001/38_add_drtest_host.yml b/rundeck/workflow/project001/38_add_drtest_host.yml index bd65aa0..d3d07d0 100644 --- a/rundeck/workflow/project001/38_add_drtest_host.yml +++ b/rundeck/workflow/project001/38_add_drtest_host.yml @@ -105,7 +105,7 @@ class3: "{{ Class_3 if (protectLevel|int == 3) else '0' }}" enableAlua: "{{ 1 if (OS_Type == 'SOL') else 0 }}" # Enable ALUA for Solaris enableMetroAlua: "{{ 1 if (OS_Type == 'SOL') else 0 }}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" - set_fact: drTestHostName: "{{ drHostName[0:-1] }}3" @@ -119,27 +119,23 @@ primaryLunNames: [] primaryLunNamesNew: [] primaryLunPrefixNew: "{{primaryHostName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDescsNew: [] - primaryLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + primaryLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" metroCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}1" metroLunIds: [] metroLunNames: [] metroLunNamesNew: [] metroLunPrefixNew: "{{primaryHostName}}_{{protectType}}M" - metroLunDescsNew: [] - metroLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + metroLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" drCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}2" drLgDescNew: "N{{ protectLevel }}{{ class1 }}{{class2}}{{class3}}_{{ sessionName }}" drLunIds: [] drLunNames: [] drLunNamesNew: [] drLunPrefixNew: "{{drHostName}}_{{protectType}}N" - drLunDescsNew: [] - drLunDesc1New: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" + drLunSuffixNew: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" drTestLunNames: [] drTestLunPrefix: "{{drTestHostName}}_{{protectType}}N" - drTestLunDescs: [] - drTestLunDesc1: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" + drTestLunSuffix: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" drStarCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}0" standbyCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}4" @@ -188,15 +184,12 @@ primaryLgDesc: "{{ checkedLgs[0].DESCRIPTION }}" primaryLunNames: "{{ checkedLuns[primaryLgName] | json_query('[*].NAME') }}" primaryLunIds: "{{ checkedLuns[primaryLgName] | json_query('[*].ID') }}" - primaryLunDescs: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, suffix|int) ] }}" - primaryLunDescsNew: "{{ primaryLunDescsNew + [ primaryLunDesc1New + '|' + remark ] }}" + primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, lunNo|int, primaryLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -243,15 +236,12 @@ metroLgDesc: "{{ checkedLgs[0].DESCRIPTION }}" metroLunNames: "{{ checkedLuns[primaryLgName] | json_query('[*].NAME') }}" metroLunIds: "{{ checkedLuns[primaryLgName] | json_query('[*].ID') }}" - metroLunDescs: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, suffix|int) ] }}" - metroLunDescsNew: "{{ metroLunDescsNew + [ metroLunDesc1New + '|' + remark ] }}" + metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, lunNo|int, metroLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ metroLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ metroLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ metroLunNames }}" # End Precheck_2 when: Precheck_2_Execute @@ -321,12 +311,10 @@ drLunSectors: "{{ checkedLuns[drLgName] | json_query('[*].CAPACITY') }}" - set_fact: - drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, suffix|int) ] }}" - drLunDescsNew: "{{ drLunDescsNew + [ drLunDesc1New + '|' + remark ] }}" + drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, lunNo|int, drLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drLunNames }}" # End Precheck_3 when: Precheck_3_Execute @@ -350,12 +338,10 @@ checkExist: False - set_fact: - drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, suffix|int) ] }}" - drTestLunDescs: "{{ drTestLunDescs + [ drTestLunDesc1 + '|' + remark ] }}" + drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, lunNo|int, drTestLunSuffix) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drLunNames }}" # End Precheck_4 when: Precheck_4_Execute @@ -579,7 +565,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ drLunDescs }}" device: "{{ drDeviceName }}" - set_fact: @@ -589,18 +575,13 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ drLunDescs }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" vars: @@ -771,9 +752,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -781,21 +759,6 @@ volumeNames: "{{ drLunNames }}" newVolumeNames: "{{ drLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescsNew[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_5_Completed: True @@ -896,9 +859,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDescs }}" - new: "{{ metroLunDescsNew }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -906,21 +866,6 @@ volumeNames: "{{ metroLunNames }}" newVolumeNames: "{{ metroLunNamesNew }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDescsNew[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_4_Completed: True @@ -993,9 +938,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1003,21 +945,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_3_Completed: True @@ -1091,9 +1018,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1101,21 +1025,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_3_Rollbacked: True @@ -1188,9 +1097,6 @@ name: old: "{{ metroLunNamesNew }}" new: "{{ metroLunNames }}" - desc: - old: "{{ metroLunDescsNew }}" - new: "{{ metroLunDescs }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1198,21 +1104,6 @@ volumeNames: "{{ metroLunNamesNew }}" newVolumeNames: "{{ metroLunNames }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDescs[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_4_Rollbacked: True @@ -1313,9 +1204,6 @@ name: old: "{{ drLunNamesNew }}" new: "{{ drLunNames }}" - desc: - old: "{{ drLunDescsNew }}" - new: "{{ drLunDescs }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1323,21 +1211,6 @@ volumeNames: "{{ drLunNamesNew }}" newVolumeNames: "{{ drLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescs[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_5_Rollbacked: True @@ -1663,7 +1536,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ drLunDescs }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_2_1_Completed }}" @@ -1755,9 +1628,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_3_5_Completed }}" @@ -1820,9 +1690,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDescs }}" - new: "{{ metroLunDescsNew }}" device: "{{ metroDeviceName }}" result: succeeded: "{{ Step_4_4_Completed }}" @@ -1870,9 +1737,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_5_3_Completed }}" diff --git a/rundeck/workflow/project001/39_add_metro_host.yml b/rundeck/workflow/project001/39_add_metro_host.yml index bb03617..ec6bf0a 100644 --- a/rundeck/workflow/project001/39_add_metro_host.yml +++ b/rundeck/workflow/project001/39_add_metro_host.yml @@ -85,7 +85,7 @@ class3: "{{ Class_3 if (protectLevel|int == 3) else '0' }}" enableAlua: "{{ 1 if (OS_Type == 'SOL') else 0 }}" # Enable ALUA for Solaris enableMetroAlua: "{{ 1 if (OS_Type == 'SOL') else 0 }}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" - set_fact: metroClusterName: "{{ Metro_Cluster_Name }}" @@ -123,27 +123,23 @@ primaryLunNames: [] primaryLunNamesNew: [] primaryLunPrefixNew: "{{primaryHostName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDescsNew: [] - primaryLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + primaryLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" metroLunNames: [] metroLunPrefix: "{{primaryHostName}}_{{protectType}}M" - metroLunDescs: [] - metroLunDesc1: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + metroLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" drCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}2" drLgDescNew: "N{{ protectLevel }}{{ class1 }}{{class2}}{{class3}}_{{ sessionName }}" drLunIds: [] drLunNames: [] drLunNamesNew: [] drLunPrefixNew: "{{DR_Host}}_{{protectType}}N" - drLunDescsNew: [] - drLunDesc1New: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" + drLunSuffixNew: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" drTestCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}3" drTestLunIds: [] drTestLunNames: [] drTestLunNamesNew: [] drTestLunPrefixNew: "{{DR_Test_Host}}_{{protectType}}N" - drTestLunDescsNew: [] - drTestLunDesc1New: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" + drTestLunSuffixNew: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" - set_fact: Precheck_1_Execute: True @@ -233,12 +229,10 @@ primaryLunDescs: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, suffix|int) ] }}" - primaryLunDescsNew: "{{ primaryLunDescsNew + [ primaryLunDesc1New + '|' + remark ] }}" + primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, lunNo|int, primaryLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -280,9 +274,9 @@ metroDeviceSession: "{{ deviceSession }}" - set_fact: - metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, suffix|int) ] }}" + metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, lunNo|int, metroLunSuffix) ] }}" vars: - suffix: "{{ item.split('_')[4][2:] }}" + lunNo: "{{ item.split('_')[4][2:] }}" with_items: "{{ primaryLunNames }}" - set_fact: @@ -367,15 +361,12 @@ drLgDesc: "{{ checkedLgs[0].DESCRIPTION }}" drLunIds: "{{ checkedLuns[drLgName] | json_query('[*].ID') }}" drLunNames: "{{ checkedLuns[drLgName] | json_query('[*].NAME') }}" - drLunDescs: "{{ checkedLuns[drLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, suffix|int) ] }}" - drLunDescsNew: "{{ drLunDescsNew + [ drLunDesc1New + '|' + remark ] }}" + drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, lunNo|int, drLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -408,15 +399,12 @@ drTestLgDesc: "{{ checkedLgs[0].DESCRIPTION }}" drTestLunNames: "{{ checkedLuns[drTestLgName] | json_query('[*].NAME') }}" drTestLunIds: "{{ checkedLuns[drTestLgName] | json_query('[*].ID') }}" - drTestLunDescs: "{{ checkedLuns[drTestLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - drTestLunNamesNew: "{{ drTestLunNamesNew + [ lunNameTemplate | format(drTestLunPrefixNew, suffix|int) ] }}" - drTestLunDescsNew: "{{ drTestLunDescsNew + [ drTestLunDesc1New + '|' + remark ] }}" + drTestLunNamesNew: "{{ drTestLunNamesNew + [ lunNameTemplate | format(drTestLunPrefixNew, lunNo|int, drTestLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drTestLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drTestLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drTestLunNames }}" # End Precheck_4 when: Precheck_4_Execute @@ -751,7 +739,7 @@ params: luns: lunNames: "{{ metroLunNames }}" - lunDescs: "{{ primaryLunDescsNew }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -769,7 +757,7 @@ lunSector: "{{ primaryLunSectors[i] }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ primaryLunDescsNew[i] }}" + desc: "{{ primaryLunDescs[i] }}" loop: "{{ range(0, metroLunNames|length) | list }}" loop_control: loop_var: i @@ -1109,9 +1097,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1119,21 +1104,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_3_Completed: True @@ -1234,9 +1204,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1244,21 +1211,6 @@ volumeNames: "{{ drLunNames }}" newVolumeNames: "{{ drLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescsNew[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_4_Completed: True @@ -1303,9 +1255,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDescs }}" - new: "{{ drTestLunDescsNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1313,21 +1262,6 @@ volumeNames: "{{ drTestLunNames }}" newVolumeNames: "{{ drTestLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - desc: "{{ drTestLunDescsNew[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_6_2_Completed: True @@ -1506,9 +1440,6 @@ name: old: "{{ drTestLunNamesNew }}" new: "{{ drTestLunNames }}" - desc: - old: "{{ drTestLunDescsNew }}" - new: "{{ drTestLunDescs }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1516,22 +1447,6 @@ volumeNames: "{{ drTestLunNamesNew }}" newVolumeNames: "{{ drTestLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - newLunName: "{{ drTestLunNames[i] }}" - desc: "{{ drTestLunDescs[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_6_2_Rollbacked: True @@ -1573,9 +1488,6 @@ name: old: "{{ drLunNamesNew }}" new: "{{ drLunNames }}" - desc: - old: "{{ drLunDescsNew }}" - new: "{{ drLunDescs }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1583,21 +1495,6 @@ volumeNames: "{{ drLunNamesNew }}" newVolumeNames: "{{ drLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescs[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_4_Rollbacked: True @@ -1698,9 +1595,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1708,21 +1602,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_3_Rollbacked: True @@ -2175,7 +2054,7 @@ params: luns: lunNames: "{{ metroLunNames }}" - lunDescs: "{{ primaryLunDescsNew }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -2319,9 +2198,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_4_3_Completed }}" @@ -2384,9 +2260,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_5_4_Completed }}" @@ -2416,9 +2289,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDescs }}" - new: "{{ drTestLunDescsNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_6_2_Completed }}" diff --git a/rundeck/workflow/project001/40_remove_replica_host.yml b/rundeck/workflow/project001/40_remove_replica_host.yml index e1c95fe..30bd997 100644 --- a/rundeck/workflow/project001/40_remove_replica_host.yml +++ b/rundeck/workflow/project001/40_remove_replica_host.yml @@ -118,7 +118,7 @@ class3: "{{ Class_3 if (protectLevel|int == 3) else '0' }}" enableAlua: "{{ 1 if (OS_Type == 'SOL') else 0 }}" # Enable ALUA for Solaris enableMetroAlua: "{{ 1 if (OS_Type == 'SOL') else 0 }}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" - set_fact: primaryLgDescNew: "{{ metroEnable }}{{ protectLevel }}{{class1}}{{class2}}{{class3}}_{{ sessionName }}" @@ -126,16 +126,14 @@ primaryLunNames: [] primaryLunNamesNew: [] primaryLunPrefixNew: "{{primaryHostName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDescsNew: [] - primaryLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + primaryLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" metroCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}1" metroLunIds: [] metroLunNames: [] metroLunsInTier: [] metroLunNamesNew: [] metroLunPrefixNew: "{{primaryHostName}}_{{protectType}}M" - metroLunDescsNew: [] - metroLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + metroLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" drCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}2" drLgDescNew: "N{{ protectLevel }}{{ class1 }}{{class2}}{{class3}}_{{ sessionName }}" drLunIds: [] @@ -143,15 +141,13 @@ drLunsInTier: [] drLunNamesNew: [] drLunPrefixNew: "{{drHostName}}_{{protectType}}N" - drLunDescsNew: [] - drLunDesc1New: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" + drLunSuffixNew: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" drTestCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}3" drTestLunIds: [] drTestLunNames: [] drTestLunNamesNew: [] drTestLunPrefixNew: "{{drTestHostName}}_{{protectType}}N" - drTestLunDescsNew: [] - drTestLunDesc1New: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" + drTestLunSuffixNew: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" drStarCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}0" standbyCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}4" @@ -204,12 +200,10 @@ primaryLunDescs: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, suffix|int) ] }}" - primaryLunDescsNew: "{{ primaryLunDescsNew + [ primaryLunDesc1New + '|' + remark ] }}" + primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, lunNo|int, primaryLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -295,12 +289,10 @@ metroLunsWorkload: "{{ checkedLuns[primaryLgName] | json_query('[*].WORKLOADTYPEID') }}" - set_fact: - metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, suffix|int) ] }}" - metroLunDescsNew: "{{ metroLunDescsNew + [ metroLunDesc1New + '|' + remark ] }}" + metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, lunNo|int, metroLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ metroLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ metroLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ metroLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -406,12 +398,10 @@ drLunsWorkload: "{{ checkedLuns[drLgName] | json_query('[*].WORKLOADTYPEID') }}" - set_fact: - drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, suffix|int) ] }}" - drLunDescsNew: "{{ drLunDescsNew + [ drLunDesc1New + '|' + remark ] }}" + drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, lunNo|int, drLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -485,12 +475,10 @@ drTestLunsWorkload: "{{ checkedLuns[drTestLgName] | json_query('[*].WORKLOADTYPEID') }}" - set_fact: - drTestLunNamesNew: "{{ drTestLunNamesNew + [ lunNameTemplate | format(drTestLunPrefixNew, suffix|int) ] }}" - drTestLunDescsNew: "{{ drTestLunDescsNew + [ drTestLunDesc1New + '|' + remark ] }}" + drTestLunNamesNew: "{{ drTestLunNamesNew + [ lunNameTemplate | format(drTestLunPrefixNew, lunNo|int, drTestLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drTestLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drTestLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drTestLunNames }}" # End Precheck_4 when: Precheck_4_Execute @@ -1328,9 +1316,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDescs }}" - new: "{{ drTestLunDescsNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1338,21 +1323,6 @@ volumeNames: "{{ drTestLunNames }}" newVolumeNames: "{{ drTestLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - desc: "{{ drTestLunDescsNew[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_2_Completed: True @@ -1510,9 +1480,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1520,21 +1487,6 @@ volumeNames: "{{ drLunNames }}" newVolumeNames: "{{ drLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescsNew[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_6_5_Completed: True @@ -1635,9 +1587,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDescs }}" - new: "{{ metroLunDescsNew }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1645,21 +1594,6 @@ volumeNames: "{{ metroLunNames }}" newVolumeNames: "{{ metroLunNamesNew }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDescsNew[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_7_4_Completed: True @@ -1732,9 +1666,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1742,21 +1673,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_8_3_Completed: True @@ -1940,9 +1856,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1950,21 +1863,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_8_3_Rollbacked: True @@ -2037,9 +1935,6 @@ name: old: "{{ metroLunNamesNew }}" new: "{{ metroLunNames }}" - desc: - old: "{{ metroLunDescsNew }}" - new: "{{ metroLunDescs }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -2047,21 +1942,6 @@ volumeNames: "{{ metroLunNamesNew }}" newVolumeNames: "{{ metroLunNames }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDescs[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_7_4_Rollbacked: True @@ -2162,9 +2042,6 @@ name: old: "{{ drLunNamesNew }}" new: "{{ drLunNames }}" - desc: - old: "{{ drLunDescsNew }}" - new: "{{ drLunDescs }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -2172,21 +2049,6 @@ volumeNames: "{{ drLunNamesNew }}" newVolumeNames: "{{ drLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescs[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_6_5_Rollbacked: True @@ -2344,9 +2206,6 @@ name: old: "{{ drTestLunNamesNew }}" new: "{{ drTestLunNames }}" - desc: - old: "{{ drTestLunDescsNew }}" - new: "{{ drTestLunDescs }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -2354,22 +2213,6 @@ volumeNames: "{{ drTestLunNamesNew }}" newVolumeNames: "{{ drTestLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - newLunName: "{{ drTestLunNames[i] }}" - desc: "{{ drTestLunDescs[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_2_Rollbacked: True @@ -3073,11 +2916,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" @@ -3478,9 +3316,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDescs }}" - new: "{{ drTestLunDescsNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_5_2_Completed }}" @@ -3574,9 +3409,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_6_5_Completed }}" @@ -3639,9 +3471,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDescs }}" - new: "{{ metroLunDescsNew }}" device: "{{ metroDeviceName }}" result: succeeded: "{{ Step_7_4_Completed }}" @@ -3689,9 +3518,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_8_3_Completed }}" diff --git a/rundeck/workflow/project001/41_add_replica_cluster.yml b/rundeck/workflow/project001/41_add_replica_cluster.yml index 9b6e3f6..b2f7ccf 100644 --- a/rundeck/workflow/project001/41_add_replica_cluster.yml +++ b/rundeck/workflow/project001/41_add_replica_cluster.yml @@ -129,12 +129,12 @@ - set_fact: lgSuffix: "{{ LUN_Group[-2:] }}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" primaryLgName: "{{ LUN_Group }}" primaryLgDescNew: "{{ metroEnable }}{{ protectLevel }}{{class1}}{{class2}}{{class3}}_{{ sessionName }}" primaryPgName: "{{ Protection_Group }}" primaryLunPrefixNew: "{{primaryClusterName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + primaryLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" - set_fact: metroDeviceSn: "{{ Metro_Storage|string }}" @@ -165,7 +165,7 @@ drLgDesc: "N{{ protectLevel }}{{ class1 }}{{class2}}{{class3}}_{{ sessionName }}" drPgName: "{{ drClusterName }}_PG{{ lgSuffix }}" drLunPrefix: "{{drClusterName}}_{{protectType}}N" - drLunDesc1: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" + drLunSuffix: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" when: protectLevel|int >= 2 - set_fact: @@ -177,7 +177,7 @@ - set_fact: drTestLgName: "{{ drTestClusterName }}_LG{{ lgSuffix }}" drTestLunPrefix: "{{drTestClusterName}}_{{protectType}}N" - drTestLunDesc1: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" + drTestLunSuffix: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" when: protectLevel|int == 3 - set_fact: @@ -240,15 +240,12 @@ primaryLunSectors: "{{ checkedLuns[primaryLgName] | json_query('[*].CAPACITY') }}" primaryLunDescs: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" primaryLunNamesNew: [] - primaryLunDescsNew: [] - set_fact: - primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, suffix|int) ] }}" - primaryLunDescsNew: "{{ primaryLunDescsNew + [ primaryLunDesc1New + '|' + remark ] }}" + primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, lunNo|int, primaryLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -301,9 +298,9 @@ metroLunNamesNew: [] - set_fact: - metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, suffix|int) ] }}" + metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, lunNo|int, primaryLunSuffixNew) ] }}" vars: - suffix: "{{ item.split('_')[4][2:] }}" + lunNo: "{{ item.split('_')[4][2:] }}" with_items: "{{ primaryLunNames }}" - block: @@ -428,15 +425,12 @@ - set_fact: drLunNames: [] - drLunDescs: [] - set_fact: - drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, suffix|int) ] }}" - drLunDescs: "{{ drLunDescs + [ drLunDesc1 + '|' + remark ] }}" + drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, lunNo|int, drLunSuffix) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" vars: @@ -474,15 +468,12 @@ - set_fact: drTestLunNames: [] - drTestLunDescs: [] - set_fact: - drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, suffix|int) ] }}" - drTestLunDescs: "{{ drTestLunDescs + [ drTestLunDesc1 + '|' + remark ] }}" + drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, lunNo|int, drTestLunSuffix) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" vars: @@ -1125,7 +1116,7 @@ params: luns: lunNames: "{{ metroLunNamesNew }}" - lunDescs: "{{ primaryLunDescsNew }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -1143,7 +1134,7 @@ lunSector: "{{ primaryLunSectors[i] }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ primaryLunDescsNew[i] }}" + desc: "{{ primaryLunDescs[i] }}" loop: "{{ range(0, metroLunNamesNew|length) | list }}" loop_control: loop_var: i @@ -1265,7 +1256,7 @@ params: luns: lunNames: "{{ drLunNames }}" - lunDescs: "{{ drLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" device: "{{ drDeviceName }}" @@ -1283,7 +1274,7 @@ lunSector: "{{ primaryLunSectors[i] }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" - desc: "{{ drLunDescs[i] }}" + desc: "{{ primaryLunDescs[i] }}" loop: "{{ range(0, drLunNames|length) | list }}" loop_control: loop_var: i @@ -1517,7 +1508,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ primaryLunDescs }}" device: "{{ drDeviceName }}" - set_fact: @@ -1527,18 +1518,13 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ primaryLunDescs }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" vars: @@ -1653,9 +1639,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1663,21 +1646,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_7_3_Completed: True @@ -1778,9 +1746,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1788,21 +1753,6 @@ volumeNames: "{{ metroLunNames }}" newVolumeNames: "{{ metroLunNamesNew }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_8_4_Completed: True @@ -2066,9 +2016,6 @@ name: old: "{{ metroLunNamesNew }}" new: "{{ metroLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -2076,21 +2023,6 @@ volumeNames: "{{ metroLunNamesNew }}" newVolumeNames: "{{ metroLunNames }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_8_4_Rollbacked: True @@ -2191,9 +2123,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -2201,21 +2130,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_7_3_Rollbacked: True @@ -3110,7 +3024,7 @@ params: luns: lunNames: "{{ metroLunNamesNew }}" - lunDescs: "{{ primaryLunDescsNew }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -3170,7 +3084,7 @@ params: luns: lunNames: "{{ drLunNames }}" - lunDescs: "{{ drLunDescs }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ drPoolId }}" workload: "{{ drWorkload }}" device: "{{ drDeviceName }}" @@ -3284,7 +3198,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ primaryLunDescs }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_6_1_Completed }}" @@ -3346,9 +3260,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_7_3_Completed }}" @@ -3411,9 +3322,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ metroDeviceName }}" result: succeeded: "{{ Step_8_4_Completed }}" diff --git a/rundeck/workflow/project001/42_add_drtest_cluster.yml b/rundeck/workflow/project001/42_add_drtest_cluster.yml index a37cc4f..b675a5d 100644 --- a/rundeck/workflow/project001/42_add_drtest_cluster.yml +++ b/rundeck/workflow/project001/42_add_drtest_cluster.yml @@ -114,7 +114,7 @@ class1: "{{ Class_1 }}" class2: "{{ Class_2 if (protectLevel|int >= 2) else '0' }}" class3: "{{ Class_3 if (protectLevel|int == 3) else '0' }}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" - set_fact: drTestClusterName: "{{ drClusterName[0:-1] }}3" @@ -128,27 +128,23 @@ primaryLunNames: [] primaryLunNamesNew: [] primaryLunPrefixNew: "{{primaryClusterName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDescsNew: [] - primaryLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + primaryLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" metroCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}1" metroLunIds: [] metroLunNames: [] metroLunNamesNew: [] metroLunPrefixNew: "{{primaryClusterName}}_{{protectType}}M" - metroLunDescsNew: [] - metroLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + metroLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" drCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}2" drLgDescNew: "N{{ protectLevel }}{{ class1 }}{{class2}}{{class3}}_{{ sessionName }}" drLunIds: [] drLunNames: [] drLunNamesNew: [] drLunPrefixNew: "{{drClusterName}}_{{protectType}}N" - drLunDescsNew: [] - drLunDesc1New: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" + drLunSuffixNew: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" drTestLunNames: [] drTestLunPrefix: "{{drTestClusterName}}_{{protectType}}N" - drTestLunDescs: [] - drTestLunDesc1: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" + drTestLunSuffix: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" drStarCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}0" standbyCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}4" @@ -193,15 +189,12 @@ primaryLgDesc: "{{ checkedLgs[0].DESCRIPTION }}" primaryLunNames: "{{ checkedLuns[primaryLgName] | json_query('[*].NAME') }}" primaryLunIds: "{{ checkedLuns[primaryLgName] | json_query('[*].ID') }}" - primaryLunDescs: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, suffix|int) ] }}" - primaryLunDescsNew: "{{ primaryLunDescsNew + [ primaryLunDesc1New + '|' + remark ] }}" + primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, lunNo|int, primaryLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -248,15 +241,12 @@ metroLgDesc: "{{ checkedLgs[0].DESCRIPTION }}" metroLunNames: "{{ checkedLuns[primaryLgName] | json_query('[*].NAME') }}" metroLunIds: "{{ checkedLuns[primaryLgName] | json_query('[*].ID') }}" - metroLunDescs: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, suffix|int) ] }}" - metroLunDescsNew: "{{ metroLunDescsNew + [ metroLunDesc1New + '|' + remark ] }}" + metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, lunNo|int, metroLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ metroLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ metroLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ metroLunNames }}" # End Precheck_2 when: Precheck_2_Execute @@ -309,12 +299,10 @@ drLunSectors: "{{ checkedLuns[drLgName] | json_query('[*].CAPACITY') }}" - set_fact: - drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, suffix|int) ] }}" - drLunDescsNew: "{{ drLunDescsNew + [ drLunDesc1New + '|' + remark ] }}" + drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, lunNo|int, drLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drLunNames }}" # End Precheck_3 when: Precheck_3_Execute @@ -338,12 +326,10 @@ checkExist: False - set_fact: - drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, suffix|int) ] }}" - drTestLunDescs: "{{ drTestLunDescs + [ drTestLunDesc1 + '|' + remark ] }}" + drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, lunNo|int, drTestLunSuffix) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drLunNames }}" # End Precheck_4 when: Precheck_4_Execute @@ -559,7 +545,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ drLunDescs }}" device: "{{ drDeviceName }}" - set_fact: @@ -569,18 +555,13 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ drLunDescs }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" vars: @@ -751,9 +732,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -761,21 +739,6 @@ volumeNames: "{{ drLunNames }}" newVolumeNames: "{{ drLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescsNew[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_5_Completed: True @@ -876,9 +839,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDescs }}" - new: "{{ metroLunDescsNew }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -886,21 +846,6 @@ volumeNames: "{{ metroLunNames }}" newVolumeNames: "{{ metroLunNamesNew }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDescsNew[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_4_Completed: True @@ -973,9 +918,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -983,21 +925,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_3_Completed: True @@ -1071,9 +998,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1081,21 +1005,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_3_Rollbacked: True @@ -1168,9 +1077,6 @@ name: old: "{{ metroLunNamesNew }}" new: "{{ metroLunNames }}" - desc: - old: "{{ metroLunDescsNew }}" - new: "{{ metroLunDescs }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1178,21 +1084,6 @@ volumeNames: "{{ metroLunNamesNew }}" newVolumeNames: "{{ metroLunNames }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDescs[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_4_Rollbacked: True @@ -1293,9 +1184,6 @@ name: old: "{{ drLunNamesNew }}" new: "{{ drLunNames }}" - desc: - old: "{{ drLunDescsNew }}" - new: "{{ drLunDescs }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1303,21 +1191,6 @@ volumeNames: "{{ drLunNamesNew }}" newVolumeNames: "{{ drLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescs[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_3_5_Rollbacked: True @@ -1639,7 +1512,7 @@ cgName: "{{ drTestCgName }}" snapNames: "{{ drTestLunNames }}" activate: False - snapDescs: "{{ drTestLunDescs }}" + snapDescs: "{{ drLunDescs }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_2_1_Completed }}" @@ -1731,9 +1604,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_3_5_Completed }}" @@ -1796,9 +1666,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDescs }}" - new: "{{ metroLunDescsNew }}" device: "{{ metroDeviceName }}" result: succeeded: "{{ Step_4_4_Completed }}" @@ -1846,9 +1713,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_5_3_Completed }}" diff --git a/rundeck/workflow/project001/43_add_metro_cluster.yml b/rundeck/workflow/project001/43_add_metro_cluster.yml index 8f901ce..d4c4ebe 100644 --- a/rundeck/workflow/project001/43_add_metro_cluster.yml +++ b/rundeck/workflow/project001/43_add_metro_cluster.yml @@ -94,7 +94,7 @@ class1: "{{ Class_1 }}" class2: "{{ Class_2 if (protectLevel|int >= 2) else '0' }}" class3: "{{ Class_3 if (protectLevel|int == 3) else '0' }}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" - set_fact: metroHosts: "{{ Metro_Hosts.split(',') }}" @@ -132,27 +132,23 @@ primaryLunNames: [] primaryLunNamesNew: [] primaryLunPrefixNew: "{{primaryClusterName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDescsNew: [] - primaryLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + primaryLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" metroLunNames: [] metroLunPrefix: "{{primaryClusterName}}_{{protectType}}M" - metroLunDescs: [] - metroLunDesc1: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + metroLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" drCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}2" drLgDescNew: "N{{ protectLevel }}{{ class1 }}{{class2}}{{class3}}_{{ sessionName }}" drLunIds: [] drLunNames: [] drLunNamesNew: [] drLunPrefixNew: "{{DR_Cluster}}_{{protectType}}N" - drLunDescsNew: [] - drLunDesc1New: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" + drLunSuffixNew: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" drTestCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}3" drTestLunIds: [] drTestLunNames: [] drTestLunNamesNew: [] drTestLunPrefixNew: "{{DR_Test_Cluster}}_{{protectType}}N" - drTestLunDescsNew: [] - drTestLunDesc1New: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" + drTestLunSuffixNew: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" - set_fact: Precheck_1_Execute: True @@ -209,12 +205,10 @@ primaryLunDescs: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, suffix|int) ] }}" - primaryLunDescsNew: "{{ primaryLunDescsNew + [ primaryLunDesc1New + '|' + remark ] }}" + primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, lunNo|int, primaryLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -255,9 +249,9 @@ metroDeviceSession: "{{ deviceSession }}" - set_fact: - metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, suffix|int) ] }}" + metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, lunNo|int, metroLunSuffix) ] }}" vars: - suffix: "{{ item.split('_')[4][2:] }}" + lunNo: "{{ item.split('_')[4][2:] }}" with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hostgroups.yml" @@ -327,15 +321,12 @@ drLgDesc: "{{ checkedLgs[0].DESCRIPTION }}" drLunIds: "{{ checkedLuns[drLgName] | json_query('[*].ID') }}" drLunNames: "{{ checkedLuns[drLgName] | json_query('[*].NAME') }}" - drLunDescs: "{{ checkedLuns[drLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, suffix|int) ] }}" - drLunDescsNew: "{{ drLunDescsNew + [ drLunDesc1New + '|' + remark ] }}" + drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, lunNo|int, drLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -368,15 +359,12 @@ drTestLgDesc: "{{ checkedLgs[0].DESCRIPTION }}" drTestLunNames: "{{ checkedLuns[drTestLgName] | json_query('[*].NAME') }}" drTestLunIds: "{{ checkedLuns[drTestLgName] | json_query('[*].ID') }}" - drTestLunDescs: "{{ checkedLuns[drTestLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - drTestLunNamesNew: "{{ drTestLunNamesNew + [ lunNameTemplate | format(drTestLunPrefixNew, suffix|int) ] }}" - drTestLunDescsNew: "{{ drTestLunDescsNew + [ drTestLunDesc1New + '|' + remark ] }}" + drTestLunNamesNew: "{{ drTestLunNamesNew + [ lunNameTemplate | format(drTestLunPrefixNew, lunNo|int, drTestLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drTestLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drTestLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drTestLunNames }}" # End Precheck_4 when: Precheck_4_Execute @@ -701,7 +689,7 @@ params: luns: lunNames: "{{ metroLunNames }}" - lunDescs: "{{ primaryLunDescsNew }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -719,7 +707,7 @@ lunSector: "{{ primaryLunSectors[i] }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" - desc: "{{ primaryLunDescsNew[i] }}" + desc: "{{ primaryLunDescs[i] }}" loop: "{{ range(0, metroLunNames|length) | list }}" loop_control: loop_var: i @@ -1059,9 +1047,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1069,21 +1054,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_3_Completed: True @@ -1184,9 +1154,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1194,21 +1161,6 @@ volumeNames: "{{ drLunNames }}" newVolumeNames: "{{ drLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescsNew[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_4_Completed: True @@ -1253,9 +1205,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDescs }}" - new: "{{ drTestLunDescsNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1263,21 +1212,6 @@ volumeNames: "{{ drTestLunNames }}" newVolumeNames: "{{ drTestLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - desc: "{{ drTestLunDescsNew[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_6_2_Completed: True @@ -1456,9 +1390,6 @@ name: old: "{{ drTestLunNamesNew }}" new: "{{ drTestLunNames }}" - desc: - old: "{{ drTestLunDescsNew }}" - new: "{{ drTestLunDescs }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1466,22 +1397,6 @@ volumeNames: "{{ drTestLunNamesNew }}" newVolumeNames: "{{ drTestLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - newLunName: "{{ drTestLunNames[i] }}" - desc: "{{ drTestLunDescs[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_6_2_Rollbacked: True @@ -1523,9 +1438,6 @@ name: old: "{{ drLunNamesNew }}" new: "{{ drLunNames }}" - desc: - old: "{{ drLunDescsNew }}" - new: "{{ drLunDescs }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1533,21 +1445,6 @@ volumeNames: "{{ drLunNamesNew }}" newVolumeNames: "{{ drLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescs[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_4_Rollbacked: True @@ -1648,9 +1545,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1658,21 +1552,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_4_3_Rollbacked: True @@ -2120,7 +1999,7 @@ params: luns: lunNames: "{{ metroLunNames }}" - lunDescs: "{{ primaryLunDescsNew }}" + lunDescs: "{{ primaryLunDescs }}" poolId: "{{ metroPoolId }}" workload: "{{ metroWorkload }}" device: "{{ metroDeviceName }}" @@ -2264,9 +2143,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_4_3_Completed }}" @@ -2329,9 +2205,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_5_4_Completed }}" @@ -2361,9 +2234,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDescs }}" - new: "{{ drTestLunDescsNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_6_2_Completed }}" diff --git a/rundeck/workflow/project001/44_remove_replica_cluster.yml b/rundeck/workflow/project001/44_remove_replica_cluster.yml index 706242a..4e88b0a 100644 --- a/rundeck/workflow/project001/44_remove_replica_cluster.yml +++ b/rundeck/workflow/project001/44_remove_replica_cluster.yml @@ -116,7 +116,7 @@ class1: "{{ Class_1 }}" class2: "{{ Class_2 if (protectLevel|int >= 2) else '0' }}" class3: "{{ Class_3 if (protectLevel|int == 3) else '0' }}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" - set_fact: primaryLgDescNew: "{{ metroEnable }}{{ protectLevel }}{{class1}}{{class2}}{{class3}}_{{ sessionName }}" @@ -124,16 +124,14 @@ primaryLunNames: [] primaryLunNamesNew: [] primaryLunPrefixNew: "{{primaryClusterName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" - primaryLunDescsNew: [] - primaryLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + primaryLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" metroCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}1" metroLunIds: [] metroLunNames: [] metroLunsInTier: [] metroLunNamesNew: [] metroLunPrefixNew: "{{primaryClusterName}}_{{protectType}}M" - metroLunDescsNew: [] - metroLunDesc1New: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + metroLunSuffixNew: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" drCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}2" drLgDescNew: "N{{ protectLevel }}{{ class1 }}{{class2}}{{class3}}_{{ sessionName }}" drLunIds: [] @@ -141,15 +139,13 @@ drLunsInTier: [] drLunNamesNew: [] drLunPrefixNew: "{{drClusterName}}_{{protectType}}N" - drLunDescsNew: [] - drLunDesc1New: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" + drLunSuffixNew: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" drTestCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}3" drTestLunIds: [] drTestLunNames: [] drTestLunNamesNew: [] drTestLunPrefixNew: "{{drTestClusterName}}_{{protectType}}N" - drTestLunDescsNew: [] - drTestLunDesc1New: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" + drTestLunSuffixNew: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" drStarCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}0" standbyCgNameNew: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}4" @@ -198,12 +194,10 @@ primaryLunDescs: "{{ checkedLuns[primaryLgName] | json_query('[*].DESCRIPTION') }}" - set_fact: - primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, suffix|int) ] }}" - primaryLunDescsNew: "{{ primaryLunDescsNew + [ primaryLunDesc1New + '|' + remark ] }}" + primaryLunNamesNew: "{{ primaryLunNamesNew + [ lunNameTemplate | format(primaryLunPrefixNew, lunNo|int, primaryLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ primaryLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ primaryLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -267,12 +261,10 @@ metroLunsWorkload: "{{ checkedLuns[primaryLgName] | json_query('[*].WORKLOADTYPEID') }}" - set_fact: - metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, suffix|int) ] }}" - metroLunDescsNew: "{{ metroLunDescsNew + [ metroLunDesc1New + '|' + remark ] }}" + metroLunNamesNew: "{{ metroLunNamesNew + [ lunNameTemplate | format(metroLunPrefixNew, lunNo|int, metroLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ metroLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ metroLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ metroLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -352,12 +344,10 @@ drLunsWorkload: "{{ checkedLuns[drLgName] | json_query('[*].WORKLOADTYPEID') }}" - set_fact: - drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, suffix|int) ] }}" - drLunDescsNew: "{{ drLunDescsNew + [ drLunDesc1New + '|' + remark ] }}" + drLunNamesNew: "{{ drLunNamesNew + [ lunNameTemplate | format(drLunPrefixNew, lunNo|int, drLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drLunNames }}" - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" vars: @@ -424,12 +414,10 @@ drTestLunsWorkload: "{{ checkedLuns[drTestLgName] | json_query('[*].WORKLOADTYPEID') }}" - set_fact: - drTestLunNamesNew: "{{ drTestLunNamesNew + [ lunNameTemplate | format(drTestLunPrefixNew, suffix|int) ] }}" - drTestLunDescsNew: "{{ drTestLunDescsNew + [ drTestLunDesc1New + '|' + remark ] }}" + drTestLunNamesNew: "{{ drTestLunNamesNew + [ lunNameTemplate | format(drTestLunPrefixNew, lunNo|int, drTestLunSuffixNew) ] }}" vars: - suffix: "{{ item.1.split('_')[4][2:] }}" - remark: "{{ drTestLunDescs[item.0].split('|')[1] | default('') }}" - with_indexed_items: "{{ drTestLunNames }}" + lunNo: "{{ item.split('_')[4][2:] }}" + with_items: "{{ drTestLunNames }}" # End Precheck_4 when: Precheck_4_Execute @@ -1176,9 +1164,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDescs }}" - new: "{{ drTestLunDescsNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1186,21 +1171,6 @@ volumeNames: "{{ drTestLunNames }}" newVolumeNames: "{{ drTestLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - desc: "{{ drTestLunDescsNew[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_2_Completed: True @@ -1358,9 +1328,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1368,21 +1335,6 @@ volumeNames: "{{ drLunNames }}" newVolumeNames: "{{ drLunNamesNew }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescsNew[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_6_5_Completed: True @@ -1483,9 +1435,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDescs }}" - new: "{{ metroLunDescsNew }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1493,21 +1442,6 @@ volumeNames: "{{ metroLunNames }}" newVolumeNames: "{{ metroLunNamesNew }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDescsNew[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_7_4_Completed: True @@ -1580,9 +1514,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1590,21 +1521,6 @@ volumeNames: "{{ primaryLunNames }}" newVolumeNames: "{{ primaryLunNamesNew }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescsNew[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_8_3_Completed: True @@ -1788,9 +1704,6 @@ name: old: "{{ primaryLunNamesNew }}" new: "{{ primaryLunNames }}" - desc: - old: "{{ primaryLunDescsNew }}" - new: "{{ primaryLunDescs }}" device: "{{ primaryDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1798,21 +1711,6 @@ volumeNames: "{{ primaryLunNamesNew }}" newVolumeNames: "{{ primaryLunNames }}" - - set_fact: - deviceHost: "{{ primaryDeviceHost }}" - devicePort: "{{ primaryDevicePort }}" - deviceSn: "{{ primaryDeviceSn }}" - deviceToken: "{{ primaryDeviceToken }}" - deviceSession: "{{ primaryDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ primaryLunIds[i] }}" - desc: "{{ primaryLunDescs[i] }}" - loop: "{{ range(0, primaryLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_8_3_Rollbacked: True @@ -1885,9 +1783,6 @@ name: old: "{{ metroLunNamesNew }}" new: "{{ metroLunNames }}" - desc: - old: "{{ metroLunDescsNew }}" - new: "{{ metroLunDescs }}" device: "{{ metroDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -1895,21 +1790,6 @@ volumeNames: "{{ metroLunNamesNew }}" newVolumeNames: "{{ metroLunNames }}" - - set_fact: - deviceHost: "{{ metroDeviceHost }}" - devicePort: "{{ metroDevicePort }}" - deviceSn: "{{ metroDeviceSn }}" - deviceToken: "{{ metroDeviceToken }}" - deviceSession: "{{ metroDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ metroLunIds[i] }}" - desc: "{{ metroLunDescs[i] }}" - loop: "{{ range(0, metroLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_7_4_Rollbacked: True @@ -2010,9 +1890,6 @@ name: old: "{{ drLunNamesNew }}" new: "{{ drLunNames }}" - desc: - old: "{{ drLunDescsNew }}" - new: "{{ drLunDescs }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -2020,21 +1897,6 @@ volumeNames: "{{ drLunNamesNew }}" newVolumeNames: "{{ drLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drLunIds[i] }}" - desc: "{{ drLunDescs[i] }}" - loop: "{{ range(0, drLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_6_5_Rollbacked: True @@ -2192,9 +2054,6 @@ name: old: "{{ drTestLunNamesNew }}" new: "{{ drTestLunNames }}" - desc: - old: "{{ drTestLunDescsNew }}" - new: "{{ drTestLunDescs }}" device: "{{ drDeviceName }}" - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" @@ -2202,22 +2061,6 @@ volumeNames: "{{ drTestLunNamesNew }}" newVolumeNames: "{{ drTestLunNames }}" - - set_fact: - deviceHost: "{{ drDeviceHost }}" - devicePort: "{{ drDevicePort }}" - deviceSn: "{{ drDeviceSn }}" - deviceToken: "{{ drDeviceToken }}" - deviceSession: "{{ drDeviceSession }}" - - - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/modify_lun.yml" - vars: - modifyLunId: "{{ drTestLunIds[i] }}" - newLunName: "{{ drTestLunNames[i] }}" - desc: "{{ drTestLunDescs[i] }}" - loop: "{{ range(0, drTestLunIds|length) | list }}" - loop_control: - loop_var: i - - set_fact: Step_5_2_Rollbacked: True @@ -2814,11 +2657,6 @@ deviceToken: "{{ drDeviceToken }}" deviceSession: "{{ drDeviceSession }}" - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/sync_replication_cg.yml" - vars: - cgName: "{{ drCgName }}" - waitSync: True - - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" vars: pgName: "{{ drPgName }}" @@ -3180,9 +3018,6 @@ name: old: "{{ drTestLunNames }}" new: "{{ drTestLunNamesNew }}" - desc: - old: "{{ drTestLunDescs }}" - new: "{{ drTestLunDescsNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_5_2_Completed }}" @@ -3276,9 +3111,6 @@ name: old: "{{ drLunNames }}" new: "{{ drLunNamesNew }}" - desc: - old: "{{ drLunDescs }}" - new: "{{ drLunDescsNew }}" device: "{{ drDeviceName }}" result: succeeded: "{{ Step_6_5_Completed }}" @@ -3341,9 +3173,6 @@ name: old: "{{ metroLunNames }}" new: "{{ metroLunNamesNew }}" - desc: - old: "{{ metroLunDescs }}" - new: "{{ metroLunDescsNew }}" device: "{{ metroDeviceName }}" result: succeeded: "{{ Step_7_4_Completed }}" @@ -3391,9 +3220,6 @@ name: old: "{{ primaryLunNames }}" new: "{{ primaryLunNamesNew }}" - desc: - old: "{{ primaryLunDescs }}" - new: "{{ primaryLunDescsNew }}" device: "{{ primaryDeviceName }}" result: succeeded: "{{ Step_8_3_Completed }}" diff --git a/rundeck/workflow/project001/45_migrate_host.yml b/rundeck/workflow/project001/45_migrate_host.yml index 92b4116..df44c62 100644 --- a/rundeck/workflow/project001/45_migrate_host.yml +++ b/rundeck/workflow/project001/45_migrate_host.yml @@ -41,7 +41,7 @@ replicaType: "{{ REPTYPE['N1']['type'] }}" # See ../../config/project001.yml enableAlua: "{{ 1 if (OS_Type == 'SOL') else 0 }}" # Enable ALUA for Solaris enableMetroAlua: "{{ 1 if (OS_Type == 'SOL') else 0 }}" - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s_%s_%s_%s" - set_fact: primaryLunNames: [] @@ -145,11 +145,11 @@ targetLuns: "{{ targetLuns + [targetLun] }}" vars: targetLun: - name: "{{ lunNameTemplate | format(targetLunPrefix, nextScsiId|int + item.0) }}" - desc: "{{ primaryLunDescs[item.0][0:1] }}_00_{{replicaType}}_00_0000000000000000_01|{{primaryLunDescs[item.0].split('|')[1] | default('') }}" + name: "{{ lunNameTemplate | format(targetLunPrefix, (nextScsiId|int + item.0), item.1.split('_')[5], '00', replicaType, '00_0000000000000000_01' ) }}" + desc: "{{ primaryLunDescs[item.0] }}" sector: "{{ primaryLunSectors[item.0] }}" lg: "{{ primaryLunLgs[item.0] }}" - class: "{{ primaryLunDescs[item.0][0:1] }}" + class: "{{ item.1.split('_')[5] }}" scsiId: "{{ nextScsiId|int + item.0 }}" with_indexed_items: "{{ primaryLunNames }}" diff --git a/rundeck/workflow/project001/46_migrate_cluster.yml b/rundeck/workflow/project001/46_migrate_cluster.yml index 1c6ec6a..e68c19c 100644 --- a/rundeck/workflow/project001/46_migrate_cluster.yml +++ b/rundeck/workflow/project001/46_migrate_cluster.yml @@ -51,7 +51,7 @@ protectType: "{{ REPTYPE['N1']['enum'] }}" # See ../../config/project001.yml replicaType: "{{ REPTYPE['N1']['type'] }}" # See ../../config/project001.yml maxScsiId: "{{ OSTYPE[OS_Type]['max_scsi_id'] }}" # See ../../config/project001.yml - lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d" + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s_%s_%s_%s" - set_fact: primaryLunNames: [] @@ -132,11 +132,11 @@ targetLuns: "{{ targetLuns + [targetLun] }}" vars: targetLun: - name: "{{ lunNameTemplate | format( targetLunPrefix, (maxScsiId|int - (nextScsiId|int - item.0) + 1) ) }}" - desc: "{{ primaryLunDescs[item.0][0:1] }}_00_{{replicaType}}_00_0000000000000000_01|{{ primaryLunDescs[item.0].split('|')[1] | default('') }}" + name: "{{ lunNameTemplate | format(targetLunPrefix, (maxScsiId|int - (nextScsiId|int - item.0) + 1), item.1.split('_')[5], '00', replicaType, '00_0000000000000000_01' ) }}" + desc: "{{ primaryLunDescs[item.0] }}" sector: "{{ primaryLunSectors[item.0] }}" lg: "{{ primaryLunLgs[item.0] }}" - class: "{{ primaryLunDescs[item.0][0:1] }}" + class: "{{ item.1.split('_')[5] }}" scsiId: "{{ nextScsiId|int - item.0 }}" with_indexed_items: "{{ primaryLunNames }}" diff --git a/rundeck/workflow/project001/49_create_pg.yml b/rundeck/workflow/project001/49_create_pg.yml new file mode 100644 index 0000000..eda379b --- /dev/null +++ b/rundeck/workflow/project001/49_create_pg.yml @@ -0,0 +1,814 @@ +- name: Create Protection Group + hosts: localhost + vars_files: + - ../../../config/global.yml + - ../../../config/project001.yml + gather_facts: no + become: no + tasks: + + # Check Protection Group Params + - block: + - set_fact: + checked_pg_params: + Country: "{{ (Country is not none and Country != DEFAULT.noneValue) and Country|length == 2 }}" + OS_Type: "{{ (OS_Type is not none and OS_Type != DEFAULT.noneValue) and OS_Type in OSTYPE }}" + Site: "{{ (Site is not none and Site != DEFAULT.noneValue) and Site in DC }}" + Primary_Storage_Room: "{{ (Primary_Storage_Room is not none and Primary_Storage_Room != DEFAULT.noneValue) and Primary_Storage_Room in AZ }}" + Primary_Storage: "{{ (Primary_Storage is not none and Primary_Storage != DEFAULT.noneValue) and (Primary_Storage|string|length == 20) }}" + Session_Name: "{{ Session_Name|length <= 13 and '_' not in Session_Name }}" + Enable_HyperMetro: "{{ Enable_HyperMetro in ['Y','N'] }}" + Protection_Level: "{{ Protection_Level|int in [1,2,3] }}" + Check_Result_1: "{{ ('pg' in Check_Result_1) }}" + + - name: Precheck_0_1 - Check Protection Group Params + debug: + msg: "{{checked_pg_params}}" + failed_when: checked_pg_params.values()|unique != [True] + + # Check Metro Protection Group Params + - block: + - set_fact: + checked_metro_pg_params: + Metro_Storage: "{{ (Metro_Storage is not none and Metro_Storage != DEFAULT.noneValue) and (Metro_Storage|string|length == 20) }}" + Metro_Storage_Room: "{{ (Metro_Storage_Room is not none and Metro_Storage_Room != DEFAULT.noneValue) and Metro_Storage_Room in AZ }}" + Check_Result_2: "{{ ('pg' in Check_Result_1) }}" + + - name: Precheck_0_2 - Check Metro Protection Group Params + debug: + msg: "{{checked_metro_pg_params}}" + failed_when: checked_metro_pg_params.values()|unique != [True] + + when: Enable_HyperMetro == 'Y' + + + # Check DR Protection Group Params + - block: + - set_fact: + checked_dr_pg_params: + DR_Storage: "{{ (DR_Storage is not none and DR_Storage != DEFAULT.noneValue) and (DR_Storage|string|length == 20) }}" + DR_Storage_Room: "{{ (DR_Storage_Room is not none and DR_Storage_Room != DEFAULT.noneValue) and DR_Storage_Room in AZ }}" + Check_Result_3: "{{ ('pg' in Check_Result_1) }}" + + - name: Precheck_0_3 - Check DR Protection Group Params + debug: + msg: "{{checked_dr_pg_params}}" + failed_when: checked_dr_pg_params.values()|unique != [True] + + when: Protection_Level|int >= 2 + + # Set Params + + - set_fact: + sessionName: "{{Country}}0{{ Session_Name}}" + protectType: "{{ REPTYPE[Enable_HyperMetro+Protection_Level|string]['enum'] }}" # See ../../config/project001.yml + replicaType: "{{ REPTYPE[Enable_HyperMetro+Protection_Level|string]['type'] }}" # See ../../config/project001.yml + + - set_fact: + primaryDeviceSn: "{{ Primary_Storage|string }}" + primaryPgName: "{{ sessionName }}_1" + primaryPgDesc: "{{ Enable_HyperMetro }}{{ Protection_Level }}" + + - set_fact: + metroDeviceSn: "{{ Metro_Storage|string if (Metro_Storage is not none) else none }}" + metroCgName: "{{ replicaType }}_{{ Site }}_{{ sessionName }}_{{ protectType }}1" + when: + - Enable_HyperMetro == 'Y' + + - set_fact: + drDeviceSn: "{{ DR_Storage|string if (DR_Storage is not none) else none }}" + drCgName: "{{ replicaType }}_{{ Site }}_{{ sessionName }}_{{ protectType }}2" + drSyncMode: "{{ DR_Sync_Mode }}" + when: + - Protection_Level|int >= 2 + + - set_fact: + drPgName: "{{ sessionName }}_2" + when: + - Protection_Level|int >= 2 + + - set_fact: + standbyCgName: "{{ replicaType }}_{{ Site }}_{{ sessionName }}_{{ protectType }}4" + when: + - Enable_HyperMetro == 'Y' + - Protection_Level|int >= 2 + + - set_fact: + drTestCgName: "{{ replicaType }}_{{ Site }}_{{ sessionName }}_{{ protectType }}3" + when: + - Protection_Level|int == 3 + + - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" + + - block: + - name: Precheck_1 - Check Primary Protection Group + debug: + msg: + protectGroup: "{{ primaryPgName }}" + device: "{{ primaryDeviceSn }}" + + - name: Login to Primary Device + set_fact: + deviceSn: "{{ primaryDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + primaryDeviceName: "{{ deviceName }}" + primaryDeviceHost: "{{ deviceHost }}" + primaryDevicePort: "{{ devicePort }}" + primaryDeviceToken: "{{ deviceToken }}" + primaryDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: ["{{ primaryPgName }}"] + checkExist: False + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hypermetro_cgs.yml" + vars: + cgNames: ["{{ metroCgName }}"] + checkExist: False + when: + - Enable_HyperMetro == 'Y' + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_replication_cgs.yml" + vars: + cgNames: [ "{{ drCgName }}" ] + checkExist: False + when: + - Protection_Level|int>= 2 + + # End Precheck_1 + # End Block + + - block: + - name: Precheck_2 - Check Metro Protection Group + debug: + msg: + protectGroup: "{{ primaryPgName }}" + device: "{{ metroDeviceSn }}" + + - name: Login to Metro Device + set_fact: + deviceSn: "{{ metroDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + metroDeviceName: "{{ deviceName }}" + metroDeviceHost: "{{ deviceHost }}" + metroDevicePort: "{{ devicePort }}" + metroDeviceToken: "{{ deviceToken }}" + metroDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: ["{{ primaryPgName }}"] + checkExist: False + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hypermetro_cgs.yml" + vars: + cgNames: ["{{ metroCgName }}"] + checkExist: False + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_replication_cgs.yml" + vars: + cgNames: [ "{{ standbyCgName }}" ] + checkExist: False + when: + - Protection_Level|int>= 2 + + # End Precheck_2 + + # End Block + when: + - Enable_HyperMetro == 'Y' + + - block: + - name: Precheck_3 - Check DR Protection Group + debug: + msg: + drProtectGroup: "{{ drPgName }}" + device: "{{ drDeviceSn }}" + + - name: Login to DR Device + set_fact: + deviceSn: "{{ drDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + drDeviceName: "{{ deviceName }}" + drDeviceHost: "{{ deviceHost }}" + drDevicePort: "{{ devicePort }}" + drDeviceToken: "{{ deviceToken }}" + drDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: ["{{ drPgName }}"] + checkExist: False + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_replication_cgs.yml" + vars: + cgNames: [ "{{ drCgName }}" ] + checkExist: False + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_replication_cgs.yml" + vars: + cgNames: [ "{{ standbyCgName }}" ] + checkExist: False + when: + - Enable_HyperMetro == 'Y' + + # End Precheck_3 + + # End Block + when: + - Protection_Level|int >= 2 + + - block: + - name: Precheck_4 - Check DR Test Cgs + debug: + msg: + drProtectGroup: "{{ drPgName }}" + device: "{{ drDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_snapshot_cgs.yml" + vars: + cgNames: ["{{ drTestCgName }}"] + checkExist: False + + # End Precheck_4 + + # End Block + when: + - Protection_Level|int == 3 + + + - block: + + # Begin Workflow Steps + + - set_fact: + # Create Primary Protection Group + Step_1_3_Execute: "{{ True }}" + Step_1_3_Completed: False + Step_1_3_Rollbacked: False + + # Create Metro Protection Group + Step_2_3_Execute: "{{ Enable_HyperMetro == 'Y' }}" + Step_2_3_Completed: False + Step_2_3_Rollbacked: False + + # Create HyperMetro CG + Step_2_4_Execute: "{{ Enable_HyperMetro == 'Y' }}" + Step_2_4_Completed: False + Step_2_4_Rollbacked: False + + # Create DR Protection Group + Step_3_3_Execute: "{{ Protection_Level|int >= 2 }}" + Step_3_3_Completed: False + Step_3_3_Rollbacked: False + + # Create Replication CG + Step_3_4_Execute: "{{ Protection_Level|int >= 2 }}" + Step_3_4_Completed: False + Step_3_4_Rollbacked: False + + # Create Standby Replication CG + Step_3_5_Execute: "{{ Protection_Level|int >= 2 and Enable_HyperMetro == 'Y' }}" + Step_3_5_Completed: False + Step_3_5_Rollbacked: False + + - name: Workflow - Create Protection Group + debug: + msg: + Step_1_3: "[{{Step_1_3_Execute}}] Create Primary Protection Group" + Step_2_3: "[{{Step_2_3_Execute}}] Create Metro Protection Group" + Step_2_4: "[{{Step_2_4_Execute}}] Create Metro CG" + Step_3_3: "[{{Step_3_3_Execute}}] Create DR Protection Group" + Step_3_4: "[{{Step_3_4_Execute}}] Create Replication CG" + Step_3_5: "[{{Step_3_5_Execute}}] Create Standby Replication CG" + + - block: + - name: Step_1_3 - Create Primary Protection Group + debug: + msg: + params: + pg: "{{ primaryPgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + desc: "{{ primaryPgDesc }}" + + - set_fact: + primaryPgId: "{{ newPgId }}" + Step_1_3_Completed: True + when: Step_1_3_Execute + + - block: + - name: Step_2_3 - Create Metro Protection Group + debug: + msg: + params: + pg: "{{ primaryPgName }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + desc: "{{ primaryPgDesc }}" + + - set_fact: + metroPgId: "{{ newPgId }}" + Step_2_3_Completed: True + when: Step_2_3_Execute + + - block: + - name: Step_2_4 - Create Metro CG + debug: + msg: + params: + device: "{{ primaryDeviceName }}" + remote: "{{ metroDeviceName }}" + cg: "{{ metroCgName }}" + pg: + local: + name: "{{ primaryPgName }}" + id: "{{ primaryPgId }}" + remote: + name: "{{ primaryPgName }}" + id: "{{ metroPgId }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_hypermetro_cg.yml" + vars: + cgName: "{{ metroCgName }}" + remoteSn: "{{ metroDeviceSn }}" + localPgId: "{{ primaryPgId }}" + remotePgId: "{{ metroPgId }}" + + - set_fact: + Step_2_4_Completed: True + when: Step_2_4_Execute + + - block: + - name: Step_3_3 - Create DR Protection Group + debug: + msg: + params: + pg: "{{ drPgName }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_pg.yml" + vars: + pgName: "{{ drPgName }}" + desc: "{{ primaryPgDesc }}" + + - set_fact: + drPgId: "{{ newPgId }}" + Step_3_3_Completed: True + when: Step_3_3_Execute + + - block: + - name: Step_3_4 - Create Replication CG + debug: + msg: + params: + device: "{{ primaryDeviceName }}" + remote: "{{ drDeviceName }}" + cg: "{{ drCgName }}" + pg: + local: + name: "{{ primaryPgName }}" + id: "{{ primaryPgId }}" + remote: + name: "{{ drPgName }}" + id: "{{ drPgId }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_replication_cg.yml" + vars: + cgName: "{{ drCgName }}" + remoteSn: "{{ drDeviceSn }}" + mode: "{{ drSyncMode }}" + localPgId: "{{ primaryPgId }}" + remotePgId: "{{ drPgId }}" + + - set_fact: + Step_3_4_Completed: True + when: Step_3_4_Execute + + - block: + - name: Step_3_5 - Create Standby Replication CG + debug: + msg: + params: + device: "{{ metroDeviceName }}" + remote: "{{ drDeviceName }}" + cg: "{{ standbyCgName }}" + pg: + local: + name: "{{ primaryPgName }}" + id: "{{ metroPgId }}" + remote: + name: "{{ drPgName }}" + id: "{{ drPgId }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_replication_cg.yml" + vars: + cgName: "{{ standbyCgName }}" + remoteSn: "{{ drDeviceSn }}" + mode: "{{ GLOBAL.replication.syncMode.async }}" + localPgId: "{{ metroPgId }}" + remotePgId: "{{ drPgId }}" + + - set_fact: + Step_3_5_Completed: True + when: Step_3_5_Execute + + # End Workflow Steps + + # End Block + + rescue: + + # Begin Workflow Rollbacks + + - block: + - name: Rollback_3_5 - Delete Standby Replication CG + debug: + msg: + params: + device: "{{ metroDeviceName }}" + cg: "{{ standbyCgName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_replication_cg.yml" + vars: + cgName: "{{ standbyCgName }}" + + - set_fact: + Step_3_5_Rollbacked: True + when: Step_3_5_Completed + + - block: + - name: Rollback_3_4 - Delete Replication CG + debug: + msg: + params: + device: "{{ primaryDeviceName }}" + cg: "{{ drCgName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_replication_cg.yml" + vars: + cgName: "{{ drCgName }}" + + - set_fact: + Step_3_4_Rollbacked: True + when: Step_3_4_Completed + + - block: + - name: Rollback_3_3 - Delete DR Protection Group + debug: + msg: + params: + pg: "{{ drPgName }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_pg.yml" + vars: + pgName: "{{ drPgName }}" + + - set_fact: + Step_3_3_Rollbacked: True + when: Step_3_3_Completed + + - block: + - name: Rollback_2_4 - Delete HyperMetro CG + debug: + msg: + params: + device: "{{ primaryDeviceName }}" + cg: "{{ metroCgName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_hypermetro_cg.yml" + vars: + cgName: "{{ metroCgName }}" + + - set_fact: + Step_2_4_Rollbacked: True + when: Step_2_4_Completed + + - block: + - name: Rollback_2_3 - Delete for Metro Protection Group + debug: + msg: + params: + pgName: "{{ primaryPgName }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + + - set_fact: + Step_2_3_Rollbacked: True + when: Step_2_3_Completed + + - block: + - name: Rollback_1_3 - Delete Primary Protection Group + debug: + msg: + params: + pgName: "{{ primaryPgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + + - set_fact: + Step_1_3_Rollbacked: True + when: Step_1_3_Completed + + # End Workflow Rollbacks + + # End Rescure + always: + + # Begin Final Steps + + - name: Final_Step_1 - Sync Devices + set_fact: + deviceSynced: [] + primaryDeviceNeedSync: "{{ (Step_1_3_Completed|bool == True and Step_1_3_Rollbacked|bool == False) }}" + metroDeviceNeedSync: "{{ (Step_2_3_Completed|bool == True and Step_2_3_Rollbacked|bool == False) }}" + remoteDeviceNeedSync: "{{ (Step_3_3_Completed|bool == True and Step_3_3_Rollbacked|bool == False) }}" + + - name: Final_Step_1_1 - Sync Primary Device + debug: + msg: + device: "{{ primaryDeviceName }}" + when: primaryDeviceNeedSync + + - set_fact: + deviceName: "{{ primaryDeviceName }}" + when: primaryDeviceNeedSync + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + when: primaryDeviceNeedSync + + - set_fact: + deviceSynced: "{{ deviceSynced + [primaryDeviceName] }}" + when: primaryDeviceNeedSync + + - name: Final_Step_1_2 - Sync Metro Device + debug: + msg: + device: "{{ metroDeviceName }}" + when: + - metroDeviceNeedSync + + - set_fact: + deviceName: "{{ metroDeviceName }}" + when: + - metroDeviceNeedSync + - metroDeviceName not in deviceSynced + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + when: + - metroDeviceNeedSync + - metroDeviceName not in deviceSynced + + - set_fact: + deviceSynced: "{{ deviceSynced + [metroDeviceName] }}" + when: + - metroDeviceNeedSync + - metroDeviceName not in deviceSynced + + - name: Final_Step_1_3 - Sync DR Device + debug: + msg: + device: "{{ drDeviceName }}" + when: + - remoteDeviceNeedSync + + - set_fact: + deviceName: "{{ drDeviceName }}" + when: + - remoteDeviceNeedSync + - drDeviceName not in deviceSynced + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + when: + - remoteDeviceNeedSync + - drDeviceName not in deviceSynced + + - set_fact: + deviceSynced: "{{ deviceSynced + [drDeviceName] }}" + when: + - remoteDeviceNeedSync + - drDeviceName not in deviceSynced + + # End Final Steps + + # End Always Block + + # End Workflow + + - block: + + # Begin Validate Results + + - name: Result_1_3 - Create Primary Protection Group + debug: + msg: + params: + pg: "{{ primaryPgName }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_1_3_Completed }}" + rollbacked: "{{ Step_1_3_Rollbacked }}" + failed_when: Step_1_3_Completed|bool == False + when: Step_1_3_Execute + + - name: Result_2_3 - Create Metro Protection Group + debug: + msg: + params: + pg: "{{ primaryPgName }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_2_3_Completed }}" + rollbacked: "{{ Step_2_3_Rollbacked }}" + failed_when: Step_2_3_Completed|bool == False + when: Step_2_3_Execute + + - name: Result_2_4 - Create HyperMetro CG + debug: + msg: + params: + device: "{{ primaryDeviceName }}" + remote: "{{ metroDeviceName }}" + cg: "{{ metroCgName }}" + pg: + local: + name: "{{ primaryPgName }}" + id: "{{ primaryPgId }}" + remote: + name: "{{ primaryPgName }}" + id: "{{ metroPgId }}" + result: + succeeded: "{{ Step_2_4_Completed }}" + rollbacked: "{{ Step_2_4_Rollbacked }}" + failed_when: Step_2_4_Completed|bool == False + when: Step_2_4_Execute + + - name: Result_3_3 - Create DR Protection Group + debug: + msg: + params: + pg: "{{ drPgName }}" + result: + succeeded: "{{ Step_3_3_Completed }}" + rollbacked: "{{ Step_3_3_Rollbacked }}" + failed_when: Step_3_3_Completed|bool == False + when: Step_3_3_Execute + + - name: Result_3_4 - Create Replication CG + debug: + msg: + params: + device: "{{ primaryDeviceName }}" + remote: "{{ drDeviceName }}" + cg: "{{ drCgName }}" + pg: + local: + name: "{{ primaryPgName }}" + id: "{{ primaryPgId }}" + remote: + name: "{{ drPgName }}" + id: "{{ drPgId }}" + result: + succeeded: "{{ Step_3_4_Completed }}" + rollbacked: "{{ Step_3_4_Rollbacked }}" + failed_when: Step_3_4_Completed|bool == False + when: Step_3_4_Execute + + - name: Result_3_5 - Create Standby Replication CG + debug: + msg: + params: + device: "{{ metroDeviceName }}" + remote: "{{ drDeviceName }}" + cg: "{{ standbyCgName }}" + pg: + local: + name: "{{ primaryPgName }}" + id: "{{ metroPgId }}" + remote: + name: "{{ drPgName }}" + id: "{{ drPgId }}" + result: + succeeded: "{{ Step_3_5_Completed }}" + rollbacked: "{{ Step_3_5_Rollbacked }}" + failed_when: Step_3_5_Completed|bool == False + when: Step_3_5_Execute + + - name: Synced Device + debug: + msg: + synced: "{{ deviceSynced }}" + # End Validate Results + + # End Block + + # End Tasks + +# End Playbook \ No newline at end of file diff --git a/rundeck/workflow/project001/50_add_luns_to_pg.yml b/rundeck/workflow/project001/50_add_luns_to_pg.yml new file mode 100644 index 0000000..be86dfd --- /dev/null +++ b/rundeck/workflow/project001/50_add_luns_to_pg.yml @@ -0,0 +1,3381 @@ +- name: Add LUNs For Protection Group + hosts: localhost + vars_files: + - ../../../config/global.yml + - ../../../config/project001.yml + gather_facts: no + become: no + tasks: + # Check Params + - block: + - set_fact: + create_lun_params_exists: "{{ (LUN_Size is not none) and (LUN_Num is not none) }}" + select_lun_params_exists: "{{ Select_LUN is not none }}" + + - set_fact: + checked_params: + Host: "{{ Host is not none and Host != DEFAULT.noneValue }}" + Storage: "{{ (Storage is not none and Storage != DEFAULT.noneValue) and (Storage|string|length == 20) }}" + Select_LUN: "{{ create_lun_params_exists or (select_lun_params_exists and (Select_LUN|string).split(',')|length >= 1) }}" + LUN_Size: "{{ select_lun_params_exists or ((LUN_Size is not none) and (LUN_Size|int >= 1)) }}" + LUN_Num: "{{ select_lun_params_exists or ((LUN_Num is not none) and (LUN_Num|int >= 1)) }}" + LUN_Description: "{{ select_lun_params_exists or ((LUN_Description is none) or (LUN_Description|string|length <= 255)) }}" + Class_1: "{{ Class_1 in ['A','B','C','D'] }}" + Designate_Class_1: "{{ (Designate_Class_1 is none) or (Designate_Class_1 in ['A','B','C','D']) }}" + Check_Result_1: "{{ ('lun' in Check_Result_1) or ('select_lun' in Check_Result_1)}}" + + - name: Precheck_0_1 - Check Params + debug: + msg: "{{checked_params}}" + failed_when: checked_params.values()|unique != [True] + + # Check Metro Params + - block: + - set_fact: + checked_metro_params: + Metro_Host: "{{ Metro_Host is not none and Metro_Host != DEFAULT.noneValue }}" + Metro_Storage: "{{ (Metro_Storage is not none and Metro_Storage != DEFAULT.noneValue) and (Metro_Storage|string|length == 20) }}" + Check_Result_2: "{{ ('lun' in Check_Result_2) }}" + + - name: Precheck_0_2 - Check Metro Params + debug: + msg: "{{checked_metro_params}}" + failed_when: checked_metro_params.values()|unique != [True] + + when: Enable_HyperMetro == 'Y' + + # Check DR Params + - block: + - set_fact: + checked_dr_params: + DR_Host: "{{ DR_Host is not none and DR_Host != DEFAULT.noneValue }}" + DR_Storage: "{{ (DR_Storage is not none and DR_Storage != DEFAULT.noneValue) and (DR_Storage|string|length == 20) }}" + Class_2: "{{ Class_2 in ['A','B','C','D'] }}" + Designate_Class_2: "{{ (Designate_Class_2 is none) or (Designate_Class_2 in ['A','B','C','D']) }}" + Check_Result_3: "{{ ('lun' in Check_Result_3) }}" + + - name: Precheck_0_3 - Check DR Params + debug: + msg: "{{checked_dr_params}}" + failed_when: checked_dr_params.values()|unique != [True] + + when: Protection_Level|int >= 2 + + # Check DR Test Params + - block: + - set_fact: + checked_drtest_params: + DR_Test_Host: "{{ DR_Test_Host is not none and DR_Test_Host != DEFAULT.noneValue }}" + Class_3: "{{ Class_3 in ['A','B','C','D'] }}" + Designate_Class_3: "{{ (Designate_Class_3 is none) or (Designate_Class_3 in ['A','B','C','D']) }}" + Check_Result_4: "{{ ('lun' in Check_Result_4) }}" + + - name: Precheck_0_4 - Check DR Test Params + debug: + msg: "{{checked_drtest_params}}" + failed_when: checked_drtest_params.values()|unique != [True] + + when: Protection_Level|int == 3 + + - set_fact: + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + osType: "{{ Host.split('_')[1] }}" + createLunSize: "{{ LUN_Size }}" + createLunNum: "{{ LUN_Num|default(0)|int }}" + createLunRemarks: "{{ LUN_Description|string if (LUN_Description is not none) else '' }}" + createPoolId: "{{ Pool }}" + createWorkload: "{{ Workload }}" + selectedLunNames: "{{ (Select_LUN|string).split(',') | sort if Select_LUN is not none else [] }}" + selectedLunNum: "{{ (Select_LUN|string).split(',')|length if Select_LUN is not none else 0 }}" + primaryDeviceSn: "{{ Storage|string }}" + primaryRoom: "{{ Storage_Room }}" + primarySite: "{{ AZ[Storage_Room]['dc'] }}" + primaryHostName: "{{ Host }}" + primaryLgName: "{{ LUN_Group }}" + primaryLg00Name: "{{ Host }}_LG00" + primaryLg00Desc: "{{ Enable_HyperMetro }}{{ Protection_Level }}{{ Class_1 }}{{ Class_2 if Protection_Level|int >= 2 else '0' }}{{Class_3 if Protection_Level|int >= 3 else '0'}}_{{ Session_Name }}" + primaryPgName: "{{ Protection_Group }}" + + - set_fact: + metroEnable: "{{ Enable_HyperMetro }}" + metroHostName: "{{ Metro_Host }}" + metroDeviceSn: "{{ Metro_Storage|string }}" + metroRoom: "{{ Metro_Storage_Room }}" + metroSite: "{{ AZ[Metro_Storage_Room]['dc'] }}" + metroPoolId: "{{ Metro_Pool }}" + metroWorkload: "{{ Metro_Workload }}" + metroCgName: "{{ Metro_CG }}" + metroCgId: "{{ Metro_CG_ID }}" + sessionName: "{{ Session_Name }}" + protectLevel: "{{ Protection_Level }}" + class1: "{{ Designate_Class_1 if (Designate_Class_1 is not none) else Class_1 }}" + class2: "{{ Designate_Class_2 if (Designate_Class_2 is not none) else Class_2 }}" + class3: "{{ Designate_Class_3 if (Designate_Class_3 is not none) else Class_3 }}" + drCgName: "{{ DR_CG }}" + drCgMode: "{{ DR_Mode }}" + drCgModeEnum: "{{ DR_Mode_Enum }}" + drDevId: "{{ DR_Storage_ID }}" + drDeviceSn: "{{ DR_Storage|string }}" + drRoom: "{{ DR_Storage_Room }}" + drSite: "{{ AZ[DR_Storage_Room]['dc'] }}" + drHostName: "{{ DR_Host }}" + drPoolId: "{{ DR_Pool }}" + drWorkload: "{{ DR_Workload }}" + drLgName: "{{ DR_LUN_Group }}" + drLg00Name: "{{ DR_Host }}_LG00" + drLg00Desc: "N{{ Protection_Level }}{{ Class_1 }}{{Class_2}}{{Class_3 if Protection_Level|int >= 3 else '0'}}_{{ Session_Name }}" + drPgName: "{{ DR_Protection_Group }}" + drTestHostName: "{{ DR_Test_Host }}" + drTestLgName: "{{ DR_Test_LUN_Group }}" + drTestLg00Name: "{{ DR_Test_Host }}_LG00" + drStarCgName: "{{ DR_Star if (DR_Star is not none and DR_Star != DEFAULT.noneValue) else '' }}" + standbyCgName: "{{ Standby_CG }}" + standbyCgId: "{{ Standby_CG_ID }}" + + - set_fact: + minScsiId: "{{ OSTYPE[osType]['min_scsi_id'] }}" # See ../../config/project001.yml + protectType: "{{ REPTYPE[metroEnable+protectLevel|string]['enum'] }}" # See ../../config/project001.yml + replicaType: "{{ REPTYPE[metroEnable+protectLevel|string]['type'] }}" # See ../../config/project001.yml + + - set_fact: + lunNameTemplate: "%s%0{{DEFAULT.suffixDigits}}d_%s" + primaryLunPrefix: "{{primaryHostName}}_{{protectType}}{{'S' if metroEnable == 'Y' else 'N'}}" + primaryLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{'00' if replicaType == '00' else primaryRoom}}_{{'0000000000000000' if replicaType == '00' else sessionName}}_{{'0' if replicaType == '00' else protectType}}1" + metroLunPrefix: "{{metroHostName}}_{{protectType}}M" + metroLunSuffix: "{{ class1 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}1" + drLunPrefix: "{{drHostName}}_{{protectType}}N" + drLunSuffix: "{{ class2 }}_00_{{replicaType}}_{{primaryRoom}}_{{sessionName}}_{{protectType}}2" + drTestLunPrefix: "{{drTestHostName}}_{{protectType}}N" + drTestLunSuffix: "{{ class3 }}_00_00_{{primaryRoom}}_{{sessionName}}_{{protectType}}3" + drTestCgNameDefault: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}3" + drStarCgNameDefault: "{{ replicaType }}_{{ primaryRoom }}_{{ sessionName }}_{{ protectType }}0" + + - set_fact: + drTestCgName: "{{ DR_Test_CG if (DR_Test_CG is not none and DR_Test_CG != DEFAULT.noneValue) else drTestCgNameDefault }}" + drTestCgId: "{{ DR_Test_CG_ID }}" + drTestCgActivated: "{{ (DR_Test_CG_Status == SNAPCG.activated.enum) if (DR_Test_CG_Status is not none and DR_Test_CG_Status != DEFAULT.noneValue) else False }}" + drTestCgExists: "{{ True if (DR_Test_CG is not none and DR_Test_CG != DEFAULT.noneValue) else False }}" + + - set_fact: + Precheck_1_Execute: True + Precheck_2_Execute: "{{ (metroEnable == 'Y') }}" + Precheck_3_Execute: "{{ (protectLevel|int >= 2) }}" + Precheck_4_Execute: "{{ (protectLevel|int == 3) }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" + + - block: + - name: Precheck_1 - Check Primary Host + debug: + msg: + host: "{{ primaryHostName }}" + device: "{{ primaryDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/host/check_hosts.yml" + vars: + hostNames: ["{{ primaryHostName }}"] + + - name: Login Device + set_fact: + deviceSn: "{{ primaryDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + primaryDeviceName: "{{ deviceName }}" + primaryDeviceHost: "{{ deviceHost }}" + primaryDevicePort: "{{ devicePort }}" + primaryDeviceToken: "{{ deviceToken }}" + primaryDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hosts.yml" + vars: + hostNames: ["{{ primaryHostName }}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id.yml" + vars: + hostName: "{{ primaryHostName }}" + + - block: + - set_fact: + currentMaxScsiId: "{{ minScsiId|int - 1 }}" + placeholderScsiIds: "{{ OSTYPE[osType]['placeholder_scsi_id'] }}" + + - set_fact: + currentMaxScsiId: "{{ item|int }}" + when: + - currentMaxScsiId|int < item|int + - item|int not in placeholderScsiIds + with_items: "{{ checkedHostLunIds }}" + + - set_fact: + nextScsiId: "{{ currentMaxScsiId|int + 1 }}" + lastScsiId: "{{ currentMaxScsiId|int + createLunNum|int + selectedLunNum|int }}" + when: Start_SCSI_ID|default(none) is none + + - block: + - set_fact: + nextScsiId: "{{ Start_SCSI_ID|int }}" + lastScsiId: "{{ Start_SCSI_ID|int + createLunNum|int + selectedLunNum|int - 1 }}" + + - name: Check Host SCSI IDs Conflicts + fail: + msg: "SCSI ID {{ item|int }} is occupied" + when: item|int >= nextScsiId|int and item|int <= lastScsiId|int + with_items: "{{ checkedHostLunIds }}" + + when: Start_SCSI_ID|default(none) is not none + + - set_fact: + createLunNames: [] + createLunIds: [] + createLunDescs: [] + createLunsSector: [] + selectedLuns: [] + selectedLunIds: [] + selectedLunsClass: [] + selectedLunNamesNew: [] + selectedLunDescs: [] + selectedLunsSector: [] + primaryLunNames: [] + primaryLunIds: [] + primaryLunDescs: [] + primaryLunsSector: [] + primaryLunsInTier: [] + primaryLunsInTierClass: [] + primaryLunsNotInTier: [] + createNextScsiId: "{{ nextScsiId|int }}" + createLastScsiId: "{{ nextScsiId|int + createLunNum|int - 1 }}" + selectedNextScsiId: "{{ nextScsiId|int + createLunNum|int }}" + selectedLastScsiId: "{{ lastScsiId|int }}" + + - block: + - set_fact: + createLunNames: "{{ createLunNames + [ lunNameTemplate | format(primaryLunPrefix, (createNextScsiId|int + item|int), primaryLunSuffix) ] }}" + createLunDescs: "{{ createLunDescs + [ createLunRemarks ] }}" + createLunsSector: "{{ createLunsSector + [ createLunSize|int * 1024 * 1024 * 2 ] }}" + with_sequence: start=0 count="{{createLunNum}}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ createLunNames }}" + checkExist: False + + when: create_lun_params_exists + + - block: + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ selectedLunNames }}" + + - set_fact: + selectedLuns: "{{ checkedLuns }}" + selectedLunIds: "{{ lunIds }}" + selectedLunDescs: "{{ checkedLuns | json_query('[*].DESCRIPTION') }}" + selectedLunsSector: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + + - set_fact: + selectedLunNamesNew: "{{ selectedLunNamesNew + [ lunNameTemplate | format(primaryLunPrefix, (selectedNextScsiId|int + item.0), primaryLunSuffix) ] }}" + with_indexed_items: "{{ selectedLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ selectedLunNamesNew }}" + checkExist: False + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ selectedLunNames }}" + + - set_fact: + selectedLunsClass: "{{ selectedLunsClass + [ checkedVolumes[item.0].service_level_name ] }}" + with_indexed_items: "{{ selectedLunNames }}" + + - set_fact: + primaryLunsInTier: "{{ checkedVolumes | json_query(queryVolumesInTier) }}" + primaryLunsInTierClass: "{{ checkedVolumes | json_query(queryVolumesInTierClass) }}" + primaryLunsNotInTier: "{{ checkedVolumes | json_query(queryVolumesNotInTier) }}" + vars: + queryVolumesInTier: "[? service_level_name != '' && service_level_name != null ].name" + queryVolumesInTierClass: "[? service_level_name != '' && service_level_name != null ].service_level_name" + queryVolumesNotInTier: "[? service_level_name == '' || service_level_name == null ].name" + + when: select_lun_params_exists + + - set_fact: + primaryLunIds: "{{ createLunIds + selectedLunIds }}" + primaryLunNames: "{{ createLunNames + selectedLunNamesNew }}" + primaryLunDescs: "{{ createLunDescs + selectedLunDescs }}" + primaryLunsSector: "{{ createLunsSector + selectedLunsSector }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: ["{{ primaryLgName }}", "{{ primaryLg00Name }}"] + + # End Precheck_1 + when: Precheck_1_Execute + + + - block: + - name: Precheck_2 - Check Metro Host + debug: + msg: + host: "{{ metroHostName }}" + device: "{{ metroDeviceSn }}" + + - name: Login Metro Device + set_fact: + deviceSn: "{{ metroDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + metroDeviceName: "{{ deviceName }}" + metroDeviceHost: "{{ deviceHost }}" + metroDevicePort: "{{ devicePort }}" + metroDeviceToken: "{{ deviceToken }}" + metroDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hosts.yml" + vars: + hostNames: ["{{ metroHostName }}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id.yml" + vars: + hostName: "{{ metroHostName }}" + + - name: Check Metro Host SCSI IDs Conflicts + fail: + msg: "SCSI ID {{ item|int }} is occupied" + when: item|int >= nextScsiId|int and item|int <= lastScsiId|int + with_items: "{{ checkedHostLunIds }}" + + - set_fact: + metroLunNames: [] + + - set_fact: + metroLunNames: "{{ metroLunNames + [ lunNameTemplate | format(metroLunPrefix, (nextScsiId|int + item.0), metroLunSuffix) ] }}" + with_indexed_items: "{{ primaryLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ metroLunNames }}" + checkExist: False + + - block: + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_lgs_by_host.yml" + vars: + hostName: "{{ primaryHostName }}" + + - set_fact: + metroLg00Exists: False + + - set_fact: + metroLg00Exists: True + when: item.lunGroupName|string == primaryLg00Name|string + with_items: "{{ checkedLgs }}" + + # End Precheck_2 + when: Precheck_2_Execute + + - block: + - name: Precheck_3 - Check DR Host + debug: + msg: + host: "{{ drHostName }}" + device: "{{ drDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/host/check_hosts.yml" + vars: + hostNames: ["{{ drHostName }}"] + + - name: Login DR Device + set_fact: + deviceSn: "{{ drDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + drDeviceName: "{{ deviceName }}" + drDeviceHost: "{{ deviceHost }}" + drDevicePort: "{{ devicePort }}" + drDeviceToken: "{{ deviceToken }}" + drDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hosts.yml" + vars: + hostNames: ["{{ drHostName }}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id.yml" + vars: + hostName: "{{ drHostName }}" + + - name: Check DR Host SCSI IDs Conflicts + fail: + msg: "SCSI ID {{ item|int }} is occupied" + when: item|int >= nextScsiId|int and item|int <= lastScsiId|int + with_items: "{{ checkedHostLunIds }}" + + - set_fact: + drLunNames: [] + + - set_fact: + drLunNames: "{{ drLunNames + [ lunNameTemplate | format(drLunPrefix, (nextScsiId|int + item.0), drLunSuffix) ] }}" + with_indexed_items: "{{ primaryLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ drLunNames }}" + checkExist: False + + - block: + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_lgs_by_host.yml" + vars: + hostName: "{{ drHostName }}" + + - set_fact: + drLg00Exists: False + + - set_fact: + drLg00Exists: True + when: item.lunGroupName|string == drLg00Name|string + with_items: "{{ checkedLgs }}" + + # End Precheck_3 + when: Precheck_3_Execute + + + - block: + - name: Precheck_4 - Check DR Test Host + debug: + msg: + host: "{{ drTestHostName }}" + device: "{{ drDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hosts.yml" + vars: + hostNames: ["{{ drTestHostName }}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id.yml" + vars: + hostName: "{{ drTestHostName }}" + + - name: Check DR Test Host SCSI IDs Conflicts + fail: + msg: "SCSI ID {{ item|int }} is occupied" + when: item|int >= nextScsiId|int and item|int <= lastScsiId|int + with_items: "{{ checkedHostLunIds }}" + + - set_fact: + drTestLunNames: [] + drTestLg00Exists: "{{ True if (drTestLg00Name in checkedLuns) else False }}" + + - set_fact: + drTestLunNames: "{{ drTestLunNames + [ lunNameTemplate | format(drTestLunPrefix, (nextScsiId|int + item.0), drTestLunSuffix) ] }}" + with_indexed_items: "{{ primaryLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ drTestLunNames }}" + checkExist: False + + # Check other snapshots in SCGs + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_snapshots_by_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + + - set_fact: + existDrTestLunNames: "{{ checkedSnapshots|json_query('[*].NAME') }}" + existDrTestLunsDescs: "{{ checkedSnapshots|json_query('[*].DESCRIPTION') }}" + existDrTestLunsId: "{{ checkedSnapshots|json_query('[*].ID') }}" + drTestHostNamesAll: [] + + - set_fact: + drTestHostNamesAll: "{{ drTestHostNamesAll + [ hostName ] }}" + vars: + hostName: "{% set field = item.split('_') %}{% set output = field[:4] %}{{'_'.join(output)}}" + with_items: + - "{{ existDrTestLunNames }}" + + - set_fact: + drTestHostNamesAll: "{{ drTestHostNamesAll | unique }}" + checkedHostLuns: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id_loop_helper.yml" + vars: + hostName: "{{ name }}" + loop: "{{ drTestHostNamesAll }}" + loop_control: + loop_var: name + + - set_fact: + existDrTestLuns: [] + + - include_tasks: "{{GLOBAL.baseDir}}/rundeck/workflow/project001/loop_helper/50_exsit_dr_test_luns_loop_helper.yml" + vars: + drTestLun: "{{ checkedSnapshot }}" + loop: "{{ checkedSnapshots }}" + loop_control: + loop_var: checkedSnapshot + + # End Precheck_4 + when: Precheck_4_Execute + + + - block: + + # Begin Workflow Steps + + - set_fact: + + # Create Primary LUNs + Step_1_1_Execute: "{{ create_lun_params_exists }}" + Step_1_1_Completed: False + Step_1_1_Rollbacked: False + + # Modify Selected LUNs + Step_1_1_1_Execute: "{{ select_lun_params_exists }}" + Step_1_1_1_Completed: False + Step_1_1_1_Rollbacked: False + + # Add Primary LUNs to Protection Group + Step_1_1_2_Execute: "{{ True }}" + Step_1_1_2_Completed: False + Step_1_1_2_Rollbacked: False + + # Add Primary LUNs to LUN Group + Step_1_1_3_Execute: "{{ True }}" + Step_1_1_3_Completed: False + Step_1_1_3_Rollbacked: False + + # Disable DR Star + Step_1_2_Execute: "{{ (protectLevel|int >= 2) and (metroEnable == 'Y') and (drStarCgName != '') }}" + Step_1_2_Completed: False + Step_1_2_Rollbacked: False + + # Create Metro LUNs + Step_2_1_Execute: "{{ (metroEnable == 'Y') }}" + Step_2_1_Completed: False + Step_2_1_Rollbacked: False + + # Add Metro LUNs to Protection Group + Step_2_1_1_Execute: "{{ metroEnable == 'Y' }}" + Step_2_1_1_Completed: False + Step_2_1_1_Rollbacked: False + + # Create Metro Pairs + Step_2_2_Execute: "{{ (metroEnable == 'Y') }}" + Step_2_2_Completed: False + Step_2_2_Rollbacked: False + + # Create LUN Group for Metro Host + Step_2_3_0_Execute: "{{ (metroEnable == 'Y') and (metroLg00Exists|bool == False) }}" + Step_2_3_0_Completed: False + Step_2_3_0_Rollbacked: False + + # Add Metro LUNs to LUN Group + Step_2_3_Execute: "{{ (metroEnable == 'Y') }}" + Step_2_3_Completed: False + Step_2_3_Rollbacked: False + + # Add Metro Pairs to HyperMetro CG + Step_2_4_Execute: "{{ (metroEnable == 'Y') }}" + Step_2_4_Completed: False + Step_2_4_Rollbacked: False + + # Create DR LUNs + Step_3_1_Execute: "{{ (protectLevel|int >= 2) }}" + Step_3_1_Completed: False + Step_3_1_Rollbacked: False + + # Add DR LUNs to Protection Group + Step_3_1_1_Execute: "{{ (protectLevel|int >= 2) }}" + Step_3_1_1_Completed: False + Step_3_1_1_Rollbacked: False + + # Create DR Pairs + Step_3_2_Execute: "{{ (protectLevel|int >= 2) }}" + Step_3_2_Completed: False + Step_3_2_Rollbacked: False + + # Create Standby DR Pairs + Step_3_3_Execute: "{{ (protectLevel|int >= 2) and (metroEnable == 'Y') }}" + Step_3_3_Completed: False + Step_3_3_Rollbacked: False + + # Create LUN Group for DR Host + Step_3_3_1_Execute: "{{ (protectLevel|int >= 2) and (drLg00Exists|bool == False) }}" + Step_3_3_1_Completed: False + Step_3_3_1_Rollbacked: False + + # Add DR LUNs to LUN Group + Step_3_4_Execute: "{{ (protectLevel|int >= 2) }}" + Step_3_4_Completed: False + Step_3_4_Rollbacked: False + + # Add DR Pair to Replication CG + Step_3_5_Execute: "{{ (protectLevel|int >= 2) }}" + Step_3_5_Completed: False + Step_3_5_Rollbacked: False + + # Add Standby DR Pair to Standby Replication CG + Step_3_6_Execute: "{{ (protectLevel|int >= 2) and (metroEnable == 'Y') }}" + Step_3_6_Completed: False + Step_3_6_Rollbacked: False + + # Create DR Star + Step_3_7_Execute: "{{ (protectLevel|int >= 2) and (metroEnable == 'Y') and (drStarCgName == '') }}" + Step_3_7_Completed: False + Step_3_7_Rollbacked: False + + # Enable DR Star + Step_3_8_Execute: "{{ (protectLevel|int >= 2) and (metroEnable == 'Y') and (drStarCgName != '') }}" + Step_3_8_Completed: False + Step_3_8_Rollbacked: False + + # Create LUN Group for DR Test Host + Step_4_0_Execute: "{{ (protectLevel|int == 3) and (drTestLg00Exists|bool == False) }}" + Step_4_0_Completed: False + Step_4_0_Rollbacked: False + + # Remove Existing DR Test LUNs from LUN Group + Step_4_1_Execute: "{{ (protectLevel|int == 3) and (existDrTestLuns|length > 0) and (drTestCgActivated|bool == False) }}" + Step_4_1_Completed: False + Step_4_1_Rollbacked: False + + # Delete Existing DR Test Snapshot CG + Step_4_2_Execute: "{{ (protectLevel|int == 3) and (drTestCgExists|bool == True) and (drTestCgActivated|bool == False) }}" + Step_4_2_Completed: False + Step_4_2_Rollbacked: False + + # Create DR Test Snapshot CG + Step_4_3_Execute: "{{ (protectLevel|int == 3) and (drTestCgActivated|bool == False) }}" + Step_4_3_Completed: False + Step_4_3_Rollbacked: False + + # Add Existing DR Test LUNs to LUN Group + Step_4_4_Execute: "{{ (protectLevel|int == 3) and (existDrTestLuns|length > 0) and (drTestCgActivated|bool == False) }}" + Step_4_4_Completed: False + Step_4_4_Rollbacked: False + + # Add DR Test LUNs to LUN Group + Step_4_5_Execute: "{{ (protectLevel|int == 3) and (drTestCgActivated|bool == False) }}" + Step_4_5_Completed: False + Step_4_5_Rollbacked: False + + deviceSynced: [] + + # Set Class for Primary LUNs + Step_5_1_Execute: True + Step_5_1_Completed: False + Step_5_1_Rollbacked: False + + # Set Class for Metro LUNs + Step_5_2_Execute: "{{ (metroEnable == 'Y') }}" + Step_5_2_Completed: False + Step_5_2_Rollbacked: False + + # Set Class for DR LUNs + Step_5_3_Execute: "{{ (protectLevel|int >= 2) }}" + Step_5_3_Completed: False + Step_5_3_Rollbacked: False + + # Insert Primary LUNs to KPI table + Step_6_1_Execute: "{{ True }}" + Step_6_1_Completed: False + + # Insert Metro LUNs to KPI table + Step_6_2_Execute: "{{ (metroEnable == 'Y') }}" + Step_6_2_Completed: False + + # Insert DR LUNs to KPI table + Step_6_3_Execute: "{{ (protectLevel|int >= 2) }}" + Step_6_3_Completed: False + + # Insert DR Test LUNs to KPI table + Step_6_4_Execute: "{{ (protectLevel|int == 3) and (drTestCgActivated|bool == False) }}" + Step_6_4_Completed: False + + # Update Existing DR Test LUNs to KPI table + Step_6_5_Execute: "{{ (protectLevel|int == 3) and (existDrTestLuns|length > 0) and (drTestCgActivated|bool == False) }}" + Step_6_5_Completed: False + + - name: Workflow - Create LUNs for Host + debug: + msg: + Step_1_1: "[{{Step_1_1_Execute}}] Create Primary LUNs" + Step_1_1_1: "[{{Step_1_1_1_Execute}}] Modify Selected LUNs" + Step_1_1_2: "[{{Step_1_1_2_Execute}}] Add Primary LUNs to Protection Group" + Step_1_1_3: "[{{Step_1_1_3_Execute}}] Add Primary LUNs to LUN Group" + Step_1_2: "[{{Step_1_2_Execute}}] Disable DR Star" + Step_2_1: "[{{Step_2_1_Execute}}] Create Metro LUNs" + Step_2_1_1: "[{{Step_2_1_1_Execute}}] Add Metro LUNs to Protection Group" + Step_2_2: "[{{Step_2_2_Execute}}] Create Metro Pairs" + Step_2_3_0: "[{{Step_2_3_0_Execute}}] Create LUN Group for Metro Host" + Step_2_3: "[{{Step_2_3_Execute}}] Add Metro LUNs to LUN Group" + Step_2_4: "[{{Step_2_4_Execute}}] Add Metro Pairs to HyperMetro CG" + Step_3_1: "[{{Step_3_1_Execute}}] Create DR LUNs" + Step_3_1_1: "[{{Step_3_1_1_Execute}}] Add DR LUNs to Protection Group" + Step_3_2: "[{{Step_3_2_Execute}}] Create DR Pairs" + Step_3_3: "[{{Step_3_3_Execute}}] Create Standby DR Pairs" + Step_3_3_1: "[{{Step_3_3_1_Execute}}] Create LUN Group for DR Host" + Step_3_4: "[{{Step_3_4_Execute}}] Add DR LUNs to LUN Group" + Step_3_5: "[{{Step_3_5_Execute}}] Add DR Pair to Replication CG" + Step_3_6: "[{{Step_3_6_Execute}}] Add Standby DR Pair to Standby Replication CG" + Step_3_7: "[{{Step_3_7_Execute}}] Create DR Star" + Step_3_8: "[{{Step_3_8_Execute}}] Enable DR Star" + Step_4_0: "[{{Step_4_0_Execute}}] Create LUN Group for DR Test Host" + Step_4_1: "[{{Step_4_1_Execute}}] Remove Existing DR Test LUNs from LUN Group" + Step_4_2: "[{{Step_4_2_Execute}}] Delete Existing DR Test Snapshot CG" + Step_4_3: "[{{Step_4_3_Execute}}] Create DR Test Snapshot CG" + Step_4_4: "[{{Step_4_4_Execute}}] Add Existing DR Test LUNs to LUN Group" + Step_4_5: "[{{Step_4_5_Execute}}] Add DR Test LUNs to LUN Group" + Step_5_1: "[{{Step_5_1_Execute}}] Set Class for Primary LUNs" + Step_5_2: "[{{Step_5_2_Execute}}] Set Class for Metro LUNs" + Step_5_3: "[{{Step_5_3_Execute}}] Set Class for DR LUNs" + Step_6_1: "[{{Step_6_1_Execute}}] Insert Primary LUNs to KPI table" + Step_6_2: "[{{Step_6_2_Execute}}] Insert Metro LUNs to KPI table" + Step_6_3: "[{{Step_6_3_Execute}}] Insert DR LUNs to KPI table" + Step_6_4: "[{{Step_6_4_Execute}}] Insert DR Test LUNs to KPI table" + Step_6_5: "[{{Step_6_5_Execute}}] Update Existing DR Test LUNs to KPI table" + + - block: + - name: Step_1_1 - Create Primary LUNs + debug: + msg: + params: + luns: + lunSize: "{{ createLunSize }}" + newLunNames: "{{ createLunNames }}" + startScsiId: "{{ createNextScsiId }}" + poolId: "{{ createPoolId }}" + workload: "{{ createWorkload }}" + desc: "{{ createLunRemarks }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_luns.yml" + vars: + lunSize: "{{ createLunSize }}" + newLunNames: "{{ createLunNames }}" + startScsiId: "{{ createNextScsiId }}" + poolId: "{{ createPoolId }}" + workload: "{{ createWorkload }}" + desc: "{{ createLunRemarks }}" + + - set_fact: + createLunIds: "{{ newLunIds }}" + primaryLunIds: "{{ newLunIds + selectedLunIds }}" + Step_1_1_Completed: True + + # End Step_1_1 + + # End block + when: Step_1_1_Execute + + - block: + - name: Step_1_1_1 - Modify Selected LUNs + debug: + msg: + params: + luns: + name: + old: "{{ selectedLunNames }}" + new: "{{ selectedLunNamesNew }}" + device: "{{ primaryDeviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" + vars: + volumeNames: "{{ selectedLunNames }}" + newVolumeNames: "{{ selectedLunNamesNew }}" + deviceSn: "{{ primaryDeviceSn }}" + + - set_fact: + Step_1_1_1_Completed: True + + # End Step_1_1_1 + + # End block + when: Step_1_1_1_Execute + + - block: + - name: Step_1_1_2 - Add Primary LUNs to Protection Group + debug: + msg: + params: + lun: + lunNames: "{{ primaryLunNames }}" + pgName: "{{ primaryPgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_pg.yml" + vars: + lunNames: "{{ primaryLunNames }}" + pgName: "{{ primaryPgName }}" + + - set_fact: + Step_1_1_2_Completed: True + + # End Step_1_1_2 + + # End block + when: Step_1_1_2_Execute + + - block: + - name: Step_1_1_3 - Add Primary LUNs to LUN Group + debug: + msg: + params: + lun: + lunNames: "{{ primaryLunNames }}" + lgName: "{{ primaryLg00Name }}" + startScsiId: "{{ nextScsiId }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lunNames: "{{ primaryLunNames }}" + lgName: "{{ primaryLg00Name }}" + startScsiId: "{{ nextScsiId }}" + + - set_fact: + Step_1_1_3_Completed: True + + # End Step_1_1_3 + + # End block + when: Step_1_1_3_Execute + + - block: + - name: Step_1_2 - Disable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/disable_drstar.yml" + vars: + drStarName: "{{ drStarCgName }}" + + - set_fact: + Step_1_2_Completed: True + + # End Step_1_2 + + # End block + when: Step_1_2_Execute + + - block: + - name: Step_2_1 - Create Metro LUNs + debug: + msg: + params: + luns: + lunNames: "{{ metroLunNames }}" + lunDescs: "{{ primaryLunDescs }}" + poolId: "{{ metroPoolId }}" + workload: "{{ metroWorkload }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_lun.yml" + vars: + lunName: "{{ metroLunNames[i] }}" + lunSector: "{{ primaryLunsSector[i] }}" + poolId: "{{ metroPoolId }}" + workload: "{{ metroWorkload }}" + desc: "{{ primaryLunDescs[i] }}" + loop: "{{ range(0, metroLunNames|length) | list }}" + loop_control: + loop_var: i + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ metroLunNames }}" + + - set_fact: + metroLunIds: "{{ lunIds }}" + Step_2_1_Completed: True + + # End Step_2_1 + + # End block + when: Step_2_1_Execute + + - block: + - name: Step_2_1_1 - Add Metro LUNs to Protection Group + debug: + msg: + params: + lun: + lunNames: "{{ metroLunNames }}" + pgName: "{{ primaryPgName }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + lunNames: "{{ metroLunNames }}" + + - set_fact: + Step_2_1_1_Completed: True + + # End Step_2_1_1 + + # End block + when: Step_2_1_1_Execute + + - block: + - name: Step_2_2 - Create Metro Pairs + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + remoteSn: "{{ metroDeviceSn }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_hypermetro_pairs.yml" + vars: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + remoteSn: "{{ metroDeviceSn }}" + + - set_fact: + Step_2_2_Completed: True + + # End Step_2_2 + + # End block + when: Step_2_2_Execute + + - block: + - name: Step_2_3_0 - Create LUN Group for Metro Host + debug: + msg: + params: + host: "{{ primaryHostName }}" + lg: "{{ primaryLg00Name }}" + desc: "{{ primaryLg00Desc }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + # Set addLunIds to empty list for variable name duplicated issue. + - set_fact: + addLunIds: [] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_lg.yml" + vars: + lgName: "{{ primaryLg00Name }}" + mapHostNames: ["{{ primaryHostName }}"] + desc: "{{ primaryLg00Desc }}" + + - set_fact: + metroLgId: "{{ newLgId }}" + Step_2_3_0_Completed: True + + # End Step_2_3_0 + + # End block + when: Step_2_3_0_Execute + + - block: + - name: Step_2_3 - Add Metro LUNs to LUN Group + debug: + msg: + params: + lunNames: "{{ metroLunNames }}" + lgName: "{{ primaryLg00Name }}" + startScsiId: "{{ nextScsiId }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lgName: "{{ primaryLg00Name }}" + lunNames: "{{ metroLunNames }}" + startScsiId: "{{ nextScsiId }}" + + - set_fact: + Step_2_3_Completed: True + + # End Step_2_3 + + # End block + when: Step_2_3_Execute + + - block: + - name: Step_2_4 - Add Metro Pairs to HyperMetro CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + cgName: "{{ metroCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_hypermetro_cg.yml" + vars: + cgName: "{{ metroCgName }}" + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + waitSync: True + + - set_fact: + Step_2_4_Completed: True + + # End Step_2_4 + + # End block + when: Step_2_4_Execute + + - block: + - name: Step_3_1 - Create DR LUNs + debug: + msg: + params: + luns: + lunNames: "{{ drLunNames }}" + lunDescs: "{{ primaryLunDescs }}" + poolId: "{{ drPoolId }}" + workload: "{{ drWorkload }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_lun.yml" + vars: + lunName: "{{ drLunNames[i] }}" + lunSector: "{{ primaryLunsSector[i] }}" + poolId: "{{ drPoolId }}" + workload: "{{ drWorkload }}" + desc: "{{ primaryLunDescs[i] }}" + loop: "{{ range(0, drLunNames|length) | list }}" + loop_control: + loop_var: i + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ drLunNames }}" + + - set_fact: + drLunIds: "{{ lunIds }}" + Step_3_1_Completed: True + + # End Step_3_1 + + # End block + when: Step_3_1_Execute + + - block: + - name: Step_3_1_1 - Add DR LUNs to Protection Group + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + lunIds: "{{ drLunIds }}" + pgName: "{{ drPgName }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_pg.yml" + vars: + pgName: "{{ drPgName }}" + lunNames: "{{ drLunNames }}" + + - set_fact: + Step_3_1_1_Completed: True + + # End Step_3_1_1 + + # End block + when: Step_3_1_1_Execute + + - block: + - name: Step_3_2 - Create DR Pairs + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + mode: "{{ drCgModeEnum }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_replication_pairs.yml" + vars: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + mode: "{{ drCgModeEnum }}" + + - set_fact: + Step_3_2_Completed: True + + # End Step_3_2 + + # End block + when: Step_3_2_Execute + + - block: + - name: Step_3_3 - Create Standby DR Pairs + debug: + msg: + params: + pairs: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + mode: "{{ GLOBAL.replication.syncMode.async }}" + standby: True + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_replication_pairs.yml" + vars: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + mode: "{{ GLOBAL.replication.syncMode.async }}" + standby: True + + - set_fact: + Step_3_3_Completed: True + + # End Step_3_3 + + # End block + when: Step_3_3_Execute + + - block: + - name: Step_3_3_1 - Create LUN Group for DR Host + debug: + msg: + params: + host: "{{ drHostName }}" + lg: "{{ drLg00Name }}" + desc: "{{ drLg00Desc }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + # Set addLunIds to empty list for variable name duplicated issue. + - set_fact: + addLunIds: [] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_lg.yml" + vars: + lgName: "{{ drLg00Name }}" + mapHostNames: ["{{ drHostName }}"] + desc: "{{ drLg00Desc }}" + + - set_fact: + drLgId: "{{ newLgId }}" + Step_3_3_1_Completed: True + + # End Step_3_3_1 + + # End block + when: Step_3_3_1_Execute + + - block: + - name: Step_3_4 - Add DR LUNs to LUN Group + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + lgName: "{{ drLg00Name }}" + startScsiId: "{{ nextScsiId }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lgName: "{{ drLg00Name }}" + lunNames: "{{ drLunNames }}" + startScsiId: "{{ nextScsiId }}" + + - set_fact: + Step_3_4_Completed: True + + # End Step_3_4 + + # End block + when: Step_3_4_Execute + + - block: + - name: Step_3_5 - Add DR Pairs to Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + cgName: "{{ drCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_replication_cg.yml" + vars: + cgName: "{{ drCgName }}" + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + waitSync: True + + - set_fact: + Step_3_5_Completed: True + + # End Step_3_5 + + # End block + when: Step_3_5_Execute + + - block: + - name: Step_3_6 - Add Standby DR Pairs to Standby Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + standby: True + cgName: "{{ standbyCgName }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_replication_cg.yml" + vars: + cgName: "{{ standbyCgName }}" + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + standby: True + + - set_fact: + Step_3_6_Completed: True + + # End Step_3_6 + + # End block + when: Step_3_6_Execute + + - block: + - name: Step_3_7 - Create DR Star + debug: + msg: + params: + drstar: + drStarName: "{{ drStarCgNameDefault }}" + mode: 1 # HyperMetro + Async Replication + memberType: 2 # CG + metroId: "{{ metroCgId }}" + asyncId: "{{ standbyCgId }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_drstar.yml" + vars: + drStarName: "{{ drStarCgNameDefault }}" + mode: 1 # HyperMetro + Async Replication + memberType: 2 # CG + metroId: "{{ metroCgId }}" + asyncId: "{{ standbyCgId }}" + + - set_fact: + Step_3_7_Completed: True + + # End Step_3_7 + + # End block + when: Step_3_7_Execute + + - block: + - name: Step_3_8 - Enable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/enable_drstar.yml" + vars: + drStarName: "{{ drStarCgName }}" + + - set_fact: + Step_3_8_Completed: True + + # End Step_3_8 + + # End block + when: Step_3_8_Execute + + - block: + - name: Step_4_0 - Create LUN Group for DR Test Host + debug: + msg: + params: + host: "{{ drTestHostName }}" + lg: "{{ drTestLg00Name }}" + desc: "{{ drLg00Desc }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + # Set addLunIds to empty list for variable name duplicated issue. + - set_fact: + addLunIds: [] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_lg.yml" + vars: + lgName: "{{ drTestLg00Name }}" + mapHostNames: ["{{ drTestHostName }}"] + desc: "{{ drLg00Desc }}" + + - set_fact: + drTestLgId: "{{ newLgId }}" + Step_4_0_Completed: True + + # End Step_4_0 + + # End block + when: Step_4_0_Execute + + - block: + - name: Step_4_1 - Remove Existing DR Test LUNs from LUN Group + debug: + msg: + params: + luns: "{{ existDrTestLuns }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ existDrTestLun.lgName }}" + lunNames: ["{{ existDrTestLun.lunName }}"] + loop: "{{ existDrTestLuns }}" + loop_control: + loop_var: existDrTestLun + + - set_fact: + Step_4_1_Completed: True + + # End Step_4_1 + + # End block + when: Step_4_1_Execute + + - block: + - name: Step_4_2 - Delete Existing DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + + - set_fact: + Step_4_2_Completed: True + + # End Step_4_2 + + # End block + when: Step_4_2_Execute + + - block: + - name: Step_4_3 - Create DR Test Snapshot CG + debug: + msg: + params: + snapCg: + pgName: "{{ drPgName }}" + cgName: "{{ drTestCgName }}" + snapNames: "{{ existDrTestLunNames + drTestLunNames }}" + activate: False + snapDescs: "{{ existDrTestLunsDescs + primaryLunDescs }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" + vars: + pgName: "{{ drPgName }}" + cgName: "{{ drTestCgName }}" + snapNames: "{{ existDrTestLunNames + drTestLunNames }}" + activate: False + snapDescs: "{{ existDrTestLunsDescs + primaryLunDescs }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ existDrTestLunNames }}" + checkExist: True + + - set_fact: + existDrTestLunsIdNew: "{{ lunIds }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ drTestLunNames }}" + checkExist: True + + - set_fact: + drTestLunIds: "{{ lunIds }}" + Step_4_3_Completed: True + + # End Step_4_3 + + # End block + when: Step_4_3_Execute + + - block: + - name: Step_4_4 - Add Existing DR Test LUNs to LUN Group + debug: + msg: + params: + luns: "{{ existDrTestLuns }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lgName: "{{ existDrTestLun.lgName }}" + lunNames: ["{{ existDrTestLun.lunName }}"] + addLunScsiIds: ["{{ existDrTestLun.lunScsiId }}"] + loop: "{{ existDrTestLuns }}" + loop_control: + loop_var: existDrTestLun + + - set_fact: + Step_4_4_Completed: True + + # End Step_4_4 + + # End block + when: Step_4_4_Execute + + - block: + - name: Step_4_5 - Add DR Test LUNs to LUN Group + debug: + msg: + params: + lunNames: "{{ drTestLunNames }}" + lgName: "{{ drTestLg00Name }}" + startScsiId: "{{ nextScsiId }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lgName: "{{ drTestLg00Name }}" + lunNames: "{{ drTestLunNames }}" + startScsiId: "{{ nextScsiId }}" + + - set_fact: + Step_4_5_Completed: True + + # End Step_4_5 + + # End block + when: Step_4_5_Execute + + # End Device Steps + + # Begin DJ Steps + + - block: + - name: Step_5_1 - Set Class for Primary LUNs + debug: + msg: + params: + volumeNames: "{{ primaryLunNames }}" + tierName: "{{ class1 }}" + #projectName: "{{ Country }}" + + - set_fact: + deviceName: "{{ primaryDeviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + + - set_fact: + deviceSynced: "{{ deviceSynced + [primaryDeviceName] }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ primaryLunNames }}" + waitExist: True + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ primaryLunsInTier }}" + when: primaryLunsInTier|length > 0 + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: "{{ primaryLunNames }}" + tierName: "{{ class1 }}" + + - set_fact: + Step_5_1_Completed: True + + # - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_project.yml" + # vars: + # volumeNames: "{{ primaryLunNames }}" + # projectName: "{{ Country }}" + + # End Step_5_1 + + # End block + when: Step_5_1_Execute + + - block: + - name: Step_5_2 - Set Class for Metro LUNs + debug: + msg: + params: + volumeNames: "{{ metroLunNames }}" + tierName: "{{ class1 }}" + #projectName: "{{ Country }}" + + - set_fact: + deviceName: "{{ metroDeviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + when: + - metroDeviceName not in deviceSynced + + - set_fact: + deviceSynced: "{{ deviceSynced + [metroDeviceName] }}" + when: + - metroDeviceName not in deviceSynced + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ metroLunNames }}" + waitExist: True + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: "{{ metroLunNames }}" + tierName: "{{ class1 }}" + + - set_fact: + Step_5_2_Completed: True + + # - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_project.yml" + # vars: + # volumeNames: "{{ metroLunNames }}" + # projectName: "{{ Country }}" + + # End Step_5_2 + + # End block + when: Step_5_2_Execute + + - block: + - name: Step_5_3 - Set Class for DR LUNs + debug: + msg: + params: + volumeNames: "{{ drLunNames }}" + tierName: "{{ class2 }}" + #projectName: "{{ Country }}" + + - set_fact: + deviceName: "{{ drDeviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + when: + - drDeviceName not in deviceSynced + + - set_fact: + deviceSynced: "{{ deviceSynced + [drDeviceName] }}" + when: + - drDeviceName not in deviceSynced + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ drLunNames }}" + waitExist: True + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: "{{ drLunNames }}" + tierName: "{{ class2 }}" + + - set_fact: + Step_5_3_Completed: True + + # - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_project.yml" + # vars: + # volumeNames: "{{ drLunNames }}" + # projectName: "{{ Country }}" + + # End Step_5_3 + + # End block + when: Step_5_3_Execute + + - block: + - name: Step_6_1 - Insert Primary LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ primaryLunIds }}" + device: "{{ primaryDeviceName }}" + + # Minus orphan capacity + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "" + SITE: "{{ primarySite }}" + ENVIRONMENT: "" + STORAGE_CLASS: "{{ selectedLunsClass[item.0] }}" + CAPACITY_GB: "-{{ (selectedLunsSector[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ primaryDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ selectedLunIds }}" + + # Add capacity to primary host + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ primaryHostName }}" + SITE: "{{ primarySite }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "{{ class1 }}" + CAPACITY_GB: "{{ (selectedLunsSector[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ primaryDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ selectedLunsSector }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "create" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ primaryHostName }}" + SITE: "{{ primarySite }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "{{ class1 }}" + CAPACITY_GB: "{{ createLunSize }}" + STORAGE: "{{ primaryDeviceName }}" + VDISK_UID: "{{ item }}" + with_items: "{{ createLunIds }}" + + - set_fact: + Step_6_1_Completed: True + + # End Step_6_1 + + # End block + when: Step_6_1_Execute + + - block: + - name: Step_6_2 - Insert Metro LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ metroLunIds }}" + device: "{{ metroDeviceName }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "create" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ metroHostName }}" + SITE: "{{ metroSite }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "{{ class1 }}" + CAPACITY_GB: "{{ (primaryLunsSector[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ metroDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ metroLunIds }}" + + - set_fact: + Step_6_2_Completed: True + + # End Step_6_2 + + # End block + when: Step_6_2_Execute + + - block: + - name: Step_6_3 - Insert DR LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drLunIds }}" + device: "{{ drDeviceName }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "create" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ drHostName }}" + SITE: "{{ drSite }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "{{ class2 }}" + CAPACITY_GB: "{{ (primaryLunsSector[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ drDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drLunIds }}" + + - set_fact: + Step_6_3_Completed: True + + # End Step_6_3 + + # End block + when: Step_6_3_Execute + + - block: + - name: Step_6_4 - Insert DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + device: "{{ drDeviceName }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "create" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ drTestHostName }}" + SITE: "{{ drSite }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "{{ (primaryLunsSector[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ drDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drTestLunIds }}" + + - set_fact: + Step_6_4_Completed: True + + # End Step_6_4 + + # End block + when: Step_6_4_Execute + + - block: + - name: Step_6_5 - Update Existing DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: + old: "{{ existDrTestLunsId }}" + new: "{{ existDrTestLunsIdNew }}" + device: "{{ drDeviceName }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "delete" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ drTestHostName }}" + SITE: "{{ drSite }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "-{{ (existDrTestLuns[item.0].lunSector|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ drDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ existDrTestLunsId }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "create" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ drTestHostName }}" + SITE: "{{ drSite }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "{{ (existDrTestLuns[item.0].lunSector|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ drDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ existDrTestLunsIdNew }}" + + - set_fact: + Step_6_5_Completed: True + + # End Step_6_5 + + # End block + when: Step_6_5_Execute + + # End Block + rescue: + # Begin Rollback + + - block: + - name: Rollback_5_3 - Remove Class for DR LUNs + debug: + msg: + params: + volumeNames: "{{ drLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ drLunNames }}" + + - set_fact: + Step_5_3_Rollbacked: True + + # End Rollback_5_3 + + # End block + when: Step_5_3_Completed + + - block: + - name: Rollback_5_2 - Remove Class for Metro LUNs + debug: + msg: + params: + volumeNames: "{{ metroLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ metroLunNames }}" + + - set_fact: + Step_5_2_Rollbacked: True + + # End Rollback_5_2 + + # End block + when: Step_5_2_Completed + + - block: + - name: Rollback_5_1 - Remove Class for Primary LUNs + debug: + msg: + params: + volumeNames: "{{ primaryLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ primaryLunNames }}" + waitExist: True + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ primaryLunNames }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: ["{{ item.0 }}"] + tierName: "{{ item.1 }}" + with_together: + - "{{ primaryLunsInTier }}" + - "{{ primaryLunsInTierClass }}" + when: primaryLunsInTier|length > 0 + + - set_fact: + Step_5_1_Rollbacked: True + + # End Rollback_5_1 + + # End block + when: Step_5_1_Completed + + - block: + - name: Rollback_4_5 - Remove DR Test LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ drTestLunNames }}" + lgName: "{{ drTestLg00Name }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ drTestLg00Name }}" + lunNames: "{{ drTestLunNames }}" + + - set_fact: + Step_4_5_Rollbacked: True + + # End Rollback_4_5 + + # End block + when: Step_4_5_Completed + + - block: + - name: Rollback_4_4 - Remove Existing DR Test LUNs from LUN Group + debug: + msg: + params: + luns: "{{ existDrTestLuns }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ existDrTestLun.lgName }}" + lunNames: ["{{ existDrTestLun.lunName }}"] + loop: "{{ existDrTestLuns }}" + loop_control: + loop_var: existDrTestLun + + - set_fact: + Step_4_4_Rollbacked: True + + # End Rollback_4_4 + + # End block + when: Step_4_4_Completed + + - block: + - name: Rollback_4_3 - Delete DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + + - set_fact: + Step_4_3_Rollbacked: True + + # End Rollback_4_3 + + # End block + when: Step_4_3_Completed + + - block: + - name: Rollback_4_0 - Delete LUN Group for DR Test Host + debug: + msg: + params: + lg: "{{ drTestLg00Name }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_lg.yml" + vars: + lgName: "{{ drTestLg00Name }}" + unmap: True + + - set_fact: + Step_4_0_Rollbacked: True + + # End Rollback_4_0 + + # End block + when: Step_4_0_Completed + + - block: + - name: Rollback_3_8 - Disable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/disable_drstar.yml" + vars: + drStarName: "{{ drStarCgName }}" + + - set_fact: + Step_3_8_Rollbacked: True + + # End Rollback_3_8 + + # End block + when: Step_3_8_Completed + + - block: + - name: Rollback_3_7 - Delete DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgNameDefault }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_drstar.yml" + vars: + drStarName: "{{ drStarCgNameDefault }}" + + - set_fact: + Step_3_7_Rollbacked: True + + # End Rollback_3_7 + + # End block + when: Step_3_7_Completed + + + - block: + - name: Rollback_3_4 - Remove DR LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + lgName: "{{ drLg00Name }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ drLg00Name }}" + lunNames: "{{ drLunNames }}" + + - set_fact: + Step_3_4_Rollbacked: True + + # End Rollback_3_4 + + # End block + when: Step_3_4_Completed + + - block: + - name: Rollback_3_3_1 - Delete LUN Group for DR Host + debug: + msg: + params: + lg: "{{ drLg00Name }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_lg.yml" + vars: + lgName: "{{ drLg00Name }}" + unmap: True + + - set_fact: + Step_3_3_1_Rollbacked: True + + # End Rollback_3_4 + + # End block + when: Step_3_3_1_Completed + + - block: + - name: Rollback_3_1_1 - Remove DR LUNs from Protection Group + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + pgName: "{{ drPgName }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_pg.yml" + vars: + pgName: "{{ drPgName }}" + lunNames: "{{ drLunNames }}" + + - set_fact: + Step_3_1_1_Rollbacked: True + + # End Rollback_3_1_1 + + # End block + when: Step_3_1_1_Completed + + - block: + - name: Rollback_2_3 - Remove Metro LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ metroLunNames }}" + lgName: "{{ primaryLg00Name }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ primaryLg00Name }}" + lunNames: "{{ metroLunNames }}" + + - set_fact: + Step_2_3_Rollbacked: True + + # End Rollback_2_3 + + # End block + when: Step_2_3_Completed + + - block: + - name: Rollback_2_3_0 - Delete LUN Group for Metro Host + debug: + msg: + params: + lg: "{{ primaryLg00Name }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_lg.yml" + vars: + lgName: "{{ primaryLg00Name }}" + unmap: True + + - set_fact: + Step_2_3_Rollbacked: True + + # End Rollback_2_3_0 + + # End block + when: Step_2_3_0_Completed + + + - block: + - name: Rollback_2_1_1 - Remove Metro LUNs from Protection Group + debug: + msg: + params: + lun: + lunNames: "{{ metroLunNames }}" + pgName: "{{ primaryPgName }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + lunNames: "{{ metroLunNames }}" + + - set_fact: + Step_2_1_1_Rollbacked: True + + # End Rollback_2_1_1 + + # End block + when: Step_2_1_1_Completed + + - block: + - name: Rollback_1_1_3 - Remove Primary LUNs from LUN Group + debug: + msg: + params: + luns: + lunNames: "{{ primaryLunNames }}" + lgName: "{{ primaryLg00Name }}" + startScsiId: "{{ nextScsiId }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ primaryLg00Name }}" + lunNames: "{{ primaryLunNames }}" + + # End Rollback_1_1_3 + + # End block + when: Step_1_1_3_Completed + + - block: + - name: Rollback_1_1_2 - Remove Primary LUNs from Protection Group + debug: + msg: + params: + lun: + lunNames: "{{ primaryLunNames }}" + pgName: "{{ primaryPgName }}" + device: "{{ primaryDeviceName }}" + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + lunNames: "{{ primaryLunNames }}" + + - set_fact: + Step_1_1_2_Rollbacked: True + + # End Rollback_1_1_2 + + # End block + when: Step_1_1_2_Completed + + - block: + - name: Rollback_1_1_1 - Modify Selected LUNs + debug: + msg: + params: + luns: + name: + new: "{{ selectedLunNames }}" + old: "{{ selectedLunNamesNew }}" + device: "{{ primaryDeviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/rename_volumes.yml" + vars: + volumeNames: "{{ selectedLunNamesNew }}" + newVolumeNames: "{{ selectedLunNames }}" + + - set_fact: + Step_1_1_1_Rollbacked: True + + # End Rollback_1_1_1 + + # End block + when: Step_1_1_1_Completed + + - block: + - name: Rollback_3_6 - Remove Standby DR Pairs from Standby Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + cgName: "{{ standbyCgName }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_replication_cg.yml" + vars: + cgName: "{{ standbyCgName }}" + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + + - set_fact: + Step_3_6_Rollbacked: True + Step_3_3_Rollbacked: True + + # End Rollback_3_6 + + # End block + when: Step_3_6_Completed + + - block: + - name: Rollback_3_5 - Remove DR Pairs from Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + cgName: "{{ drCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_replication_cg.yml" + vars: + cgName: "{{ drCgName }}" + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + + - set_fact: + Step_3_5_Rollbacked: True + Step_3_2_Rollbacked: True + + + # End Rollback_3_5 + + # End block + when: Step_3_5_Completed + + - block: + - name: Rollback_3_3 - Delete Standby DR Pairs + debug: + msg: + params: + pairs: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_replication_pairs.yml" + vars: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + + - set_fact: + Step_3_3_Rollbacked: True + + # End Rollback_3_3 + + # End block + when: + - Step_3_3_Completed + - Step_3_3_Rollbacked|bool == False + + - block: + - name: Rollback_3_2 - Delete DR Pairs + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_replication_pairs.yml" + vars: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + + - set_fact: + Step_3_2_Rollbacked: True + + # End Rollback_3_2 + + # End block + when: + - Step_3_2_Completed + - Step_3_2_Rollbacked|bool == False + + - block: + - name: Rollback_3_1 - Delete DR LUNs + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_luns.yml" + vars: + lunNames: "{{ drLunNames }}" + + - set_fact: + Step_3_1_Rollbacked: True + + # End Rollback_3_1 + + # End block + when: Step_3_1_Completed + + - block: + - name: Rollback_2_4 - Remove Metro Pairs from HyperMetro CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + cgName: "{{ metroCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_hypermetro_cg.yml" + vars: + cgName: "{{ metroCgName }}" + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + + - set_fact: + Step_2_4_Rollbacked: True + Step_2_2_Rollbacked: True + + # End Rollback_2_4 + + # End block + when: Step_2_4_Completed + + - block: + - name: Rollback_2_2 - Delete Metro Pairs + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + remoteSn: "{{ metroDeviceSn }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_hypermetro_pairs.yml" + vars: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + remoteSn: "{{ metroDeviceSn }}" + + - set_fact: + Step_2_2_Rollbacked: True + + # End Rollback_2_2 + + # End block + when: + - Step_2_2_Completed + - Step_2_2_Rollbacked|bool == False + + - block: + - name: Rollback_2_1 - Delete Metro LUNs + debug: + msg: + params: + lunNames: "{{ metroLunNames }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_luns.yml" + vars: + lunNames: "{{ metroLunNames }}" + + - set_fact: + Step_2_1_Rollbacked: True + + # End Rollback_2_1 + + # End block + when: Step_2_1_Completed + + - block: + - name: Rollback_1_2 - Enable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/enable_drstar.yml" + vars: + drStarName: "{{ drStarCgName }}" + + - set_fact: + Step_1_2_Rollbacked: True + + # End Rollback_1_2 + + # End block + when: Step_1_2_Completed + + - block: + - name: Rollback_1_1 - Delete Primary LUNs + debug: + msg: + params: + lunNames: "{{ primaryLunNames }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_luns.yml" + vars: + lunNames: "{{ primaryLunNames }}" + + - set_fact: + Step_1_1_Rollbacked: True + + # End Rollback_1_1 + + # End block + when: Step_1_1_Completed + + - block: + - name: Rollback_4_2 - Create DR Test Snapshot CG + debug: + msg: + params: + snapCg: + pgName: "{{ drPgName }}" + cgName: "{{ drTestCgName }}" + snapNames: "{{ existDrTestLunNames }}" + activate: False + snapDescs: "{{ existDrTestLunsDescs }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" + vars: + pgName: "{{ drPgName }}" + cgName: "{{ drTestCgName }}" + snapNames: "{{ existDrTestLunNames }}" + activate: False + snapDescs: "{{ existDrTestLunsDescs }}" + + - set_fact: + Step_4_2_Rollbacked: True + + # End Rollback_4_2 + + # End block + when: Step_4_2_Completed + + - block: + - name: Rollback_4_1 - Add Existing DR Test LUNs to LUN Group + debug: + msg: + params: + luns: "{{ existDrTestLuns }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lgName: "{{ existDrTestLun.lgName }}" + lunNames: ["{{ existDrTestLun.lunName }}"] + addLunScsiIds: ["{{ existDrTestLun.lunScsiId }}"] + loop: "{{ existDrTestLuns }}" + loop_control: + loop_var: existDrTestLun + + - set_fact: + Step_4_1_Rollbacked: True + + # End Rollback_4_1 + + # End block + when: Step_4_1_Completed + + - block: + - name: Re-Sync Storage Devices + debug: + msg: + params: + devices: "{{ deviceSynced }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + loop: "{{ deviceSynced }}" + loop_control: + loop_var: deviceName + + when: deviceSynced|length > 0 + + # End Rollbacks + + # End Workflow + + # Begin Validate Results + + - block: + - name: Result_1_1 - Create Primary LUNs + debug: + msg: + params: + luns: + lunSize: "{{ createLunSize }}" + newLunNames: "{{ createLunNames }}" + startScsiId: "{{ createNextScsiId }}" + poolId: "{{ createPoolId }}" + workload: "{{ createWorkload }}" + desc: "{{ createLunRemarks }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_1_1_Completed }}" + rollbacked: "{{ Step_1_1_Rollbacked }}" + failed_when: Step_1_1_Completed|bool == False + when: Step_1_1_Execute + + - name: Result_1_1_1 - Modify Selected LUNs + debug: + msg: + params: + luns: + name: + new: "{{ selectedLunNames }}" + old: "{{ selectedLunNamesNew }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_1_1_1_Completed }}" + rollbacked: "{{ Step_1_1_1_Rollbacked }}" + failed_when: Step_1_1_1_Completed|bool == False + when: Step_1_1_1_Execute + + - name: Result_1_1_2 - Add Primary LUNs to Protection Group + debug: + msg: + params: + lun: + lunNames: "{{ primaryLunNames }}" + pgName: "{{ primaryPgName }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_1_1_2_Completed }}" + rollbacked: "{{ Step_1_1_2_Rollbacked }}" + failed_when: Step_1_1_2_Completed|bool == False + when: Step_1_1_2_Execute + + - name: Result_1_1_3 - Add Primary LUNs to LUN Group + debug: + msg: + params: + lun: + primaryLunNames: "{{ primaryLunNames }}" + selectedLunNames: "{{ selectedLunNames }}" + primaryLunIds: "{{ primaryLunIds }}" + selectedLunIds: "{{ selectedLunIds }}" + lgName: "{{ primaryLg00Name }}" + startScsiId: "{{ nextScsiId }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_1_1_3_Completed }}" + rollbacked: "{{ Step_1_1_3_Rollbacked }}" + failed_when: Step_1_1_3_Completed|bool == False + when: Step_1_1_3_Execute + + - name: Result_1_3 - Disable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_1_2_Completed }}" + rollbacked: "{{ Step_1_2_Rollbacked }}" + failed_when: Step_1_2_Completed|bool == False + when: Step_1_2_Execute + + - name: Result_2_1 - Create Metro LUNs + debug: + msg: + params: + luns: + lunNames: "{{ metroLunNames }}" + lunDescs: "{{ primaryLunDescs }}" + poolId: "{{ metroPoolId }}" + workload: "{{ metroWorkload }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_2_1_Completed }}" + rollbacked: "{{ Step_2_1_Rollbacked }}" + failed_when: Step_2_1_Completed|bool == False + when: Step_2_1_Execute + + - name: Result_2_1_1 - Add Metro LUNs to Protection Group + debug: + msg: + params: + lun: + lunNames: "{{ metroLunNames }}" + lunIds: "{{ metroLunIds }}" + pgName: "{{ primaryPgName }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_2_1_1_Completed }}" + rollbacked: "{{ Step_2_1_1_Rollbacked }}" + failed_when: Step_2_1_1_Completed|bool == False + when: Step_2_1_1_Execute + + - name: Result_2_2 - Create Metro Pairs + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + remoteSn: "{{ metroDeviceSn }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_2_2_Completed }}" + rollbacked: "{{ Step_2_2_Rollbacked }}" + failed_when: Step_2_2_Completed|bool == False + when: Step_2_2_Execute + + - name: Result_2_3_0 - Create LUN Group for Metro Host + debug: + msg: + params: + host: "{{ primaryHostName }}" + lg: "{{ primaryLg00Name }}" + desc: "{{ primaryLg00Desc }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_2_3_0_Completed }}" + rollbacked: "{{ Step_2_3_0_Rollbacked }}" + failed_when: Step_2_3_0_Completed|bool == False + when: Step_2_3_0_Execute + + - name: Result_2_3 - Add Metro LUNs to LUN Group + debug: + msg: + params: + lunNames: "{{ metroLunNames }}" + lgName: "{{ primaryLg00Name }}" + startScsiId: "{{ nextScsiId }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_2_3_Completed }}" + rollbacked: "{{ Step_2_3_Rollbacked }}" + failed_when: Step_2_3_Completed|bool == False + when: Step_2_3_Execute + + - name: Result_2_4 - Add Metro Pairs to HyperMetro CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + cgName: "{{ metroCgName }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_2_4_Completed }}" + rollbacked: "{{ Step_2_4_Rollbacked }}" + failed_when: Step_2_4_Completed|bool == False + when: Step_2_4_Execute + + - name: Result_3_1 - Create DR LUNs + debug: + msg: + params: + luns: + lunNames: "{{ drLunNames }}" + lunDescs: "{{ primaryLunDescs }}" + poolId: "{{ drPoolId }}" + workload: "{{ drWorkload }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_3_1_Completed }}" + rollbacked: "{{ Step_3_1_Rollbacked }}" + failed_when: Step_3_1_Completed|bool == False + when: Step_3_1_Execute + + - name: Result_3_1_1 - Add DR LUNs to Protection Group + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + lunIds: "{{ drLunIds }}" + pgName: "{{ drPgName }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_3_1_1_Completed }}" + rollbacked: "{{ Step_3_1_1_Rollbacked }}" + failed_when: Step_3_1_1_Completed|bool == False + when: Step_3_1_1_Execute + + - name: Result_3_2 - Create DR Pairs + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + mode: "{{ drCgModeEnum }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_3_2_Completed }}" + rollbacked: "{{ Step_3_2_Rollbacked }}" + failed_when: Step_3_2_Completed|bool == False + when: Step_3_2_Execute + + - name: Result_3_3 - Create Standby DR Pairs + debug: + msg: + params: + pairs: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + mode: "{{ GLOBAL.replication.syncMode.async }}" + standby: True + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_3_3_Completed }}" + rollbacked: "{{ Step_3_3_Rollbacked }}" + failed_when: Step_3_3_Completed|bool == False + when: Step_3_3_Execute + + + - name: Result_3_3_1 - Create LUN Group for DR Host + debug: + msg: + params: + host: "{{ drHostName }}" + lg: "{{ drLg00Name }}" + desc: "{{ drLg00Desc }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_3_3_1_Completed }}" + rollbacked: "{{ Step_3_3_1_Rollbacked }}" + failed_when: Step_3_3_1_Completed|bool == False + when: Step_3_3_1_Execute + + - name: Result_3_4 - Add DR LUNs to LUN Group + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + lgName: "{{ drLg00Name }}" + startScsiId: "{{ nextScsiId }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_3_4_Completed }}" + rollbacked: "{{ Step_3_4_Rollbacked }}" + failed_when: Step_3_4_Completed|bool == False + when: Step_3_4_Execute + + - name: Result_3_5 - Add DR Pairs to Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + cgName: "{{ drCgName }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_3_5_Completed }}" + rollbacked: "{{ Step_3_5_Rollbacked }}" + failed_when: Step_3_5_Completed|bool == False + when: Step_3_5_Execute + + - name: Result_3_6 - Add Standby DR Pairs to Standby Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + standby: True + cgName: "{{ standbyCgName }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_3_6_Completed }}" + rollbacked: "{{ Step_3_6_Rollbacked }}" + failed_when: Step_3_6_Completed|bool == False + when: Step_3_6_Execute + + + - name: Result_3_7 - Create DR Star + debug: + msg: + params: + drstar: + drStarName: "{{ drStarCgNameDefault }}" + mode: 1 # HyperMetro + Async Replication + memberType: 2 # CG + metroId: "{{ metroCgId }}" + asyncId: "{{ standbyCgId }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_3_7_Completed }}" + rollbacked: "{{ Step_3_7_Rollbacked }}" + failed_when: Step_3_7_Completed|bool == False + when: Step_3_7_Execute + + - name: Result_3_8 - Enable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_3_8_Completed }}" + rollbacked: "{{ Step_3_8_Rollbacked }}" + failed_when: Step_3_8_Completed|bool == False + when: Step_3_8_Execute + + - name: Result_4_0 - Create LUN Group for DR Test Host + debug: + msg: + params: + host: "{{ drTestHostName }}" + lg: "{{ drTestLg00Name }}" + desc: "{{ drLg00Desc }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_4_0_Completed }}" + rollbacked: "{{ Step_4_0_Rollbacked }}" + failed_when: Step_4_0_Completed|bool == False + when: Step_4_0_Execute + + - name: Result_4_1 - Remove Existing DR Test LUNs from LUN Group + debug: + msg: + params: + luns: "{{ existDrTestLuns }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_4_1_Completed }}" + rollbacked: "{{ Step_4_1_Rollbacked }}" + failed_when: Step_4_1_Completed|bool == False + when: Step_4_1_Execute + + - name: Result_4_2 - Delete Existing DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_4_2_Completed }}" + rollbacked: "{{ Step_4_2_Rollbacked }}" + failed_when: Step_4_2_Completed|bool == False + when: Step_4_2_Execute + + - name: Result_4_3 - Create DR Test Snapshot CG + debug: + msg: + params: + snapCg: + pgName: "{{ drPgName }}" + cgName: "{{ drTestCgName }}" + snapNames: "{{ existDrTestLunNames + drTestLunNames }}" + activate: False + snapDescs: "{{ existDrTestLunsDescs + primaryLunDescs }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_4_3_Completed }}" + rollbacked: "{{ Step_4_3_Rollbacked }}" + failed_when: Step_4_3_Completed|bool == False + when: Step_4_3_Execute + + - name: Result_4_4 - Add Existing DR Test LUNs to LUN Group + debug: + msg: + params: + luns: "{{ existDrTestLuns }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_4_4_Completed }}" + rollbacked: "{{ Step_4_4_Rollbacked }}" + failed_when: Step_4_4_Completed|bool == False + when: Step_4_4_Execute + + - name: Result_4_5 - Add DR Test LUNs to LUN Group + debug: + msg: + params: + lunNames: "{{ drTestLunNames }}" + lgName: "{{ drTestLg00Name }}" + startScsiId: "{{ nextScsiId }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_4_5_Completed }}" + rollbacked: "{{ Step_4_5_Rollbacked }}" + failed_when: Step_4_5_Completed|bool == False + when: Step_4_5_Execute + + - name: Result_5_1 - Set Class for Primary LUNs + debug: + msg: + params: + volumeNames: "{{ primaryLunNames }}" + tierName: "{{ class1 }}" + #projectName: "{{ Country }}" + result: + succeeded: "{{ Step_5_1_Completed }}" + rollbacked: "{{ Step_5_1_Rollbacked }}" + failed_when: Step_5_1_Completed|bool == False + when: Step_5_1_Execute + + - name: Result_5_2 - Set Class for Metro LUNs + debug: + msg: + params: + volumeNames: "{{ metroLunNames }}" + tierName: "{{ class1 }}" + #projectName: "{{ Country }}" + result: + succeeded: "{{ Step_5_2_Completed }}" + rollbacked: "{{ Step_5_2_Rollbacked }}" + failed_when: Step_5_2_Completed|bool == False + when: Step_5_2_Execute + + - name: Result_5_3 - Set Class for DR LUNs + debug: + msg: + params: + volumeNames: "{{ drLunNames }}" + tierName: "{{ class2 }}" + #projectName: "{{ Country }}" + result: + succeeded: "{{ Step_5_3_Completed }}" + rollbacked: "{{ Step_5_3_Rollbacked }}" + failed_when: Step_5_3_Completed|bool == False + when: Step_5_3_Execute + + - name: Result_6_1 - Insert Primary LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ primaryLunIds }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_6_1_Completed }}" + failed_when: Step_6_1_Completed|bool == False + when: Step_6_1_Execute + + - name: Result_6_2 - Insert Metro LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ metroLunIds }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_6_2_Completed }}" + failed_when: Step_6_2_Completed|bool == False + when: Step_6_2_Execute + + - name: Result_6_3 - Insert DR LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drLunIds }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_6_3_Completed }}" + failed_when: Step_6_3_Completed|bool == False + when: Step_6_3_Execute + + - name: Result_6_4 - Insert DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_6_4_Completed }}" + failed_when: Step_6_4_Completed|bool == False + when: Step_6_4_Execute + + - name: Result_6_5 - Update Existing DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: + old: "{{ existDrTestLunsId }}" + new: "{{ existDrTestLunsIdNew }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_6_5_Completed }}" + failed_when: Step_6_5_Completed|bool == False + when: Step_6_5_Execute + + # End Validate Results + + # End Tasks + +# End Playbook \ No newline at end of file diff --git a/rundeck/workflow/project001/51_remove_luns_from_pg.yml b/rundeck/workflow/project001/51_remove_luns_from_pg.yml new file mode 100644 index 0000000..8aacfa1 --- /dev/null +++ b/rundeck/workflow/project001/51_remove_luns_from_pg.yml @@ -0,0 +1,2997 @@ +- name: Remove LUNs From Protection Group + hosts: localhost + vars_files: + - ../../../config/global.yml + - ../../../config/project001.yml + gather_facts: no + become: no + tasks: + + # Check Params + - block: + - set_fact: + checked_params: + Protection_Group: "{{ Protection_Group is not none and Protection_Group != DEFAULT.noneValue }}" + Storage: "{{ (Storage is not none and Storage != DEFAULT.noneValue) and (Storage|string|length == 20) }}" + Remove_LUN: "{{ Remove_LUN is not none and Remove_LUN != DEFAULT.noneValue }}" + Check_Result_1: "{{ ('lun' in Check_Result_1) }}" + + - name: Precheck_0_1 - Check Params + debug: + msg: "{{checked_params}}" + failed_when: checked_params.values()|unique != [True] + + # Check Metro Params + - block: + - set_fact: + checked_metro_params: + Metro_Protection_Group: "{{ Metro_Protection_Group is not none and Metro_Protection_Group != DEFAULT.noneValue }}" + Metro_Storage: "{{ (Metro_Storage is not none and Metro_Storage != DEFAULT.noneValue) and (Metro_Storage|string|length == 20) }}" + Check_Result_2: "{{ ('lun' in Check_Result_2) }}" + + - name: Precheck_0_2 - Check Metro Params + debug: + msg: "{{checked_metro_params}}" + failed_when: checked_metro_params.values()|unique != [True] + + when: Enable_HyperMetro == 'Y' + + # Check DR Params + - block: + - set_fact: + checked_dr_params: + DR_Protection_Group: "{{ DR_Protection_Group is not none and DR_Protection_Group != DEFAULT.noneValue }}" + DR_Storage: "{{ (DR_Storage is not none and DR_Storage != DEFAULT.noneValue) and (DR_Storage|string|length == 20) }}" + Check_Result_3: "{{ ('lun' in Check_Result_3) }}" + + - name: Precheck_0_3 - Check DR Params + debug: + msg: "{{checked_dr_params}}" + failed_when: checked_dr_params.values()|unique != [True] + when: Protection_Level|int >= 2 + + - set_fact: + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + primaryLunNames: "{{ Remove_LUN.split(',')|sort }}" + primaryDeviceSn: "{{ Storage|string }}" + primaryRoom: "{{ Storage_Room }}" + primarySite: "{{ AZ[Storage_Room]['dc'] }}" + primaryPgName: "{{ Protection_Group }}" + sessionName: "{{ Session_Name }}" + metroEnable: "{{ Enable_HyperMetro }}" + metroDeviceSn: "{{ Metro_Storage|string }}" + metroRoom: "{{ Metro_Storage_Room }}" + metroSite: "{{ AZ[Metro_Storage_Room]['dc'] }}" + metroCgName: "{{ Metro_CG }}" + metroCgId: "{{ Metro_CG_ID }}" + protectLevel: "{{ Protection_Level }}" + drCgName: "{{ DR_CG }}" + drCgMode: "{{ DR_Mode }}" + drCgModeEnum: "{{ DR_Mode_Enum }}" + drDevId: "{{ DR_Storage_ID }}" + drDeviceSn: "{{ DR_Storage|string }}" + drRoom: "{{ DR_Storage_Room }}" + drSite: "{{ AZ[DR_Storage_Room]['dc'] }}" + drPgName: "{{ DR_Protection_Group }}" + drTestCgName: "{{ DR_Test_CG }}" + drTestCgId: "{{ DR_Test_CG_ID }}" + drTestCgActivated: "{{ (DR_Test_CG_Status == SNAPCG.activated.enum) if (DR_Test_CG_Status is not none and DR_Test_CG_Status != DEFAULT.noneValue) else False }}" + drStarCgName: "{{ DR_Star if (DR_Star is not none and DR_Star != DEFAULT.noneValue) else '' }}" + standbyCgName: "{{ Standby_CG }}" + standbyCgId: "{{ Standby_CG_ID }}" + hostNameTemplate: "%s_%s_%s_%s" + + - set_fact: + protectType: "{{ REPTYPE[metroEnable+protectLevel|string]['enum'] }}" # See ../../config/project001.yml + replicaType: "{{ REPTYPE[metroEnable+protectLevel|string]['type'] }}" # See ../../config/project001.yml + + - set_fact: + primaryLunIds: [] + primaryLunsInTier: [] + primaryLg00Names: [] + primaryHostNames: [] + metroLunIds: [] + metroLunNames: [] + metroLunsInTier: [] + metroLg00Names: [] + metroHostNames: [] + drLunIds: [] + drLg00Names: [] + drHostNames: [] + drLunNames: [] + drLunsInTier: [] + drTestLunIds: [] + drTestLg00Names: [] + drTestHostNames: [] + drTestLunNames: [] + existDrTestLuns: [] + + - set_fact: + Precheck_1_Execute: True + Precheck_2_Execute: "{{ (metroEnable == 'Y') }}" + Precheck_3_Execute: "{{ (protectLevel|int >= 2) }}" + Precheck_4_Execute: "{{ (protectLevel|int == 3) }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" + + - block: + - name: Precheck_1 - Check Primary Protection Group + debug: + msg: + pg: "{{ primaryPgName }}" + device: "{{ primaryDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ primaryLunNames }}" + + - set_fact: + primaryLunsInTier: "{{ checkedVolumes | json_query(queryLunsName) }}" + primaryLunsInTierClass: "{{ checkedVolumes | json_query(queryLunsInTierClass) }}" + primaryLunsSize: "{{ checkedVolumes | json_query('[*].capacity') }}" + primaryLunsClass: [] + vars: + queryLunsName: "[? service_level_name != '' && service_level_name != null ].name" + queryLunsInTierClass: "[? service_level_name != '' && service_level_name != null ].service_level_name" + + - set_fact: + primaryLunsClass: "{{ primaryLunsClass + [ checkedVolumes[item.0].service_level_name ] }}" + with_indexed_items: "{{ primaryLunNames }}" + + - set_fact: + primaryLg00Names: "{{ primaryLg00Names + [hostName + '_LG00'] }}" + primaryHostNames: "{{ primaryHostNames + [hostName] }}" + vars: + field: "{{ item.split('_') }}" + hostName: "{{ hostNameTemplate | format(field[0],field[1],field[2],field[3]) }}" + with_items: "{{ primaryLunNames }}" + + - set_fact: + primaryLg00UniqueNames: "{{ primaryLg00Names | unique }}" + primaryHostUniqueNames: "{{ primaryHostNames | unique }}" + + - name: Login Device + set_fact: + deviceSn: "{{ primaryDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + primaryDeviceName: "{{ deviceName }}" + primaryDeviceHost: "{{ deviceHost }}" + primaryDevicePort: "{{ devicePort }}" + primaryDeviceToken: "{{ deviceToken }}" + primaryDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: ["{{ primaryPgName }}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ primaryLg00UniqueNames }}" + + - set_fact: + checkedHostLuns: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id_loop_helper.yml" + vars: + hostName: "{{ item }}" + with_items: "{{ primaryHostUniqueNames }}" + + - set_fact: + primaryHostLunsAll: [] + primaryHostLunIds: [] + + - set_fact: + primaryHostLunsAll: "{{ primaryHostLunsAll + checkedHostLuns[item.0][item.1] }}" + with_indexed_items: "{{ primaryLg00UniqueNames }}" + + - set_fact: + primaryHostLunIds: "{{ primaryHostLunIds + primaryHostLunsAll | json_query(queryScsiId) }}" + primaryLunIds: "{{ primaryLunIds + primaryHostLunsAll | json_query(queryLunId) }}" + vars: + queryScsiId: "[? lunName=='{{item}}'].hostLunId" + queryLunId: "[? lunName=='{{item}}'].lunId" + with_items: "{{ primaryLunNames }}" + + - name: Check Primary LUNs are Mapped to Host + debug: + msg: + primaryHostLunIds: "{{ primaryHostLunIds }}" + primaryLunNames: "{{ primaryLunNames }}" + failed_when: primaryLunNames|length != primaryHostLunIds|length + + - name: Get HyperMetro Pairs + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/HyperMetroPair?filter=LOCALOBJNAME%3A%3A{{item}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: METRO_PAIRS + with_items: "{{ primaryLunNames }}" + when: metroEnable == 'Y' + + - name: Get Metro LUNs + vars: + queryMetroLunIds: "[*].REMOTEOBJID" + queryMetroLunNames: "[*].REMOTEOBJNAME" + set_fact: + metroLunIds: "{{ metroLunIds + METRO_PAIRS.results[item.0].json.data | default([]) | json_query(queryMetroLunIds) }}" + metroLunNames: "{{ metroLunNames + METRO_PAIRS.results[item.0].json.data | default([]) | json_query(queryMetroLunNames) }}" + with_indexed_items: "{{ primaryLunNames }}" + when: metroEnable == 'Y' + + - name: Check Exist Replication Pairs + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/REPLICATIONPAIR?filter=LOCALRESNAME%3A%3A{{item}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: DR_PAIRS + with_items: "{{ primaryLunNames }}" + when: protectLevel|int >= 2 + + - name: Get DR LUNs + vars: + queryDrLunIds: "[? REMOTEDEVICEID=='{{drDevId}}'].REMOTERESID" + queryDrLunNames: "[? REMOTEDEVICEID=='{{drDevId}}'].REMOTERESNAME" + set_fact: + drLunIds: "{{ drLunIds + DR_PAIRS.results[item.0].json.data | default([]) | json_query(queryDrLunIds) }}" + drLunNames: "{{ drLunNames + DR_PAIRS.results[item.0].json.data | default([]) | json_query(queryDrLunNames) }}" + with_indexed_items: "{{ primaryLunNames }}" + when: protectLevel|int >= 2 + + # End Precheck_1 + when: Precheck_1_Execute + + + - block: + - name: Precheck_2 - Check Metro Protection Group + debug: + msg: + pg: "{{ primaryPgName }}" + device: "{{ metroDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ metroLunNames }}" + + - set_fact: + metroLunsInTier: "{{ checkedVolumes | json_query(queryLunsInTier) }}" + metroLunsInTierClass: "{{ checkedVolumes | json_query(queryLunsInTierClass) }}" + metroLunsSize: "{{ checkedVolumes | json_query('[*].capacity') }}" + metroLunsClass: [] + vars: + queryLunsInTier: "[? service_level_name != '' && service_level_name != null ].name" + queryLunsInTierClass: "[? service_level_name != '' && service_level_name != null ].service_level_name" + + - set_fact: + metroLunsClass: "{{ metroLunsClass + [ checkedVolumes[item.0].service_level_name ] }}" + with_indexed_items: "{{ metroLunNames }}" + + - set_fact: + metroLg00Names: "{{ metroLg00Names + [hostName + '_LG00'] }}" + metroHostNames: "{{ metroHostNames + [hostName] }}" + vars: + field: "{{ item.split('_') }}" + hostName: "{{ hostNameTemplate | format(field[0],field[1],field[2],field[3]) }}" + with_items: "{{ metroLunNames }}" + + - set_fact: + metroLg00UniqueNames: "{{ metroLg00Names | unique }}" + metroHostUniqueNames: "{{ metroHostNames | unique }}" + + - name: Login Metro Device + set_fact: + deviceSn: "{{ metroDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + metroDeviceName: "{{ deviceName }}" + metroDeviceHost: "{{ deviceHost }}" + metroDevicePort: "{{ devicePort }}" + metroDeviceToken: "{{ deviceToken }}" + metroDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: ["{{ primaryPgName }}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ metroLg00UniqueNames }}" + + - set_fact: + checkedHostLuns: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id_loop_helper.yml" + vars: + hostName: "{{ item }}" + with_items: "{{ metroHostNames }}" + + - set_fact: + metroHostLunsAll: [] + metroHostLunIds: [] + + - set_fact: + metroHostLunsAll: "{{ metroHostLunsAll + checkedHostLuns[item.0][item.1] }}" + with_indexed_items: "{{ metroLg00UniqueNames }}" + + - set_fact: + metroHostLunIds: "{{ metroHostLunIds + metroHostLunsAll | json_query(queryScsiId) }}" + vars: + queryScsiId: "[? lunName=='{{item}}'].hostLunId" + with_items: "{{ metroLunNames }}" + + - name: Check Metro LUNs are Mapped to Host + debug: + msg: + metroHostLunIds: "{{ metroHostLunIds }}" + metroLunNames: "{{ metroLunNames }}" + failed_when: (metroLunNames|length != metroHostLunIds|length) + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ metroLunNames }}" + + - set_fact: + metroLunsDesc: "{{ checkedLuns | json_query('[*].DESCRIPTION') }}" + metroLunsSector: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + metroLunsPool: "{{ checkedLuns | json_query('[*].PARENTID') }}" + metroLunsWorkload: "{{ checkedLuns | json_query('[*].WORKLOADTYPEID') }}" + + # Need to Delete DR Star When remove the last LUNs + - set_fact: + deleteDrStar: "{{ True if (metroHostLunsAll|length == metroLunNames|length) else False}}" + when: protectLevel|int >= 2 + + # Need to Disable DR Star when remove LUNs from CG + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_drstars.yml" + vars: + drStarNames: [ "{{ drStarCgName }}" ] + checkExist: True + when: protectLevel|int >= 2 + + # End Precheck_2 + when: + - Precheck_2_Execute + - metroLunNames|length > 0 + + + - block: + - name: Precheck_3 - Check DR Protection Group + debug: + msg: + pg: "{{ drPgName }}" + device: "{{ drDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ drLunNames }}" + + - set_fact: + drLunsInTier: "{{ checkedVolumes | json_query(queryLunsInTier) }}" + drLunsInTierClass: "{{ checkedVolumes | json_query(queryLunsInTierClass) }}" + drLunsSize: "{{ checkedVolumes | json_query('[*].capacity') }}" + drLunsClass: [] + vars: + queryLunsInTier: "[? service_level_name != '' && service_level_name != null ].name" + queryLunsInTierClass: "[? service_level_name != '' && service_level_name != null ].service_level_name" + + - set_fact: + drLunsClass: "{{ drLunsClass + [ checkedVolumes[item.0].service_level_name ] }}" + with_indexed_items: "{{ drLunNames }}" + + - set_fact: + drLg00Names: "{{ drLg00Names + [hostName + '_LG00'] }}" + drHostNames: "{{ drHostNames + [hostName] }}" + vars: + field: "{{ item.split('_') }}" + hostName: "{{ hostNameTemplate | format(field[0],field[1],field[2],field[3]) }}" + with_items: "{{ drLunNames }}" + + - set_fact: + drLg00UniqueNames: "{{ drLg00Names | unique }}" + drHostUniqueNames: "{{ drHostNames | unique }}" + + - name: Login DR Device + set_fact: + deviceSn: "{{ drDeviceSn }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + drDeviceName: "{{ deviceName }}" + drDeviceHost: "{{ deviceHost }}" + drDevicePort: "{{ devicePort }}" + drDeviceToken: "{{ deviceToken }}" + drDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: ["{{ drPgName }}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ drLg00UniqueNames }}" + + - set_fact: + checkedHostLuns: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id_loop_helper.yml" + vars: + hostName: "{{ item }}" + with_items: "{{ drHostUniqueNames }}" + + - set_fact: + drHostLunsAll: [] + drHostLunIds: [] + + - set_fact: + drHostLunsAll: "{{ drHostLunsAll + checkedHostLuns[item.0][item.1] }}" + with_indexed_items: "{{ drLg00UniqueNames }}" + + - set_fact: + drHostLunIds: "{{ drHostLunIds + drHostLunsAll | json_query(queryScsiId) }}" + vars: + queryScsiId: "[? lunName=='{{item}}'].hostLunId" + with_items: "{{ drLunNames }}" + + - name: Check DR LUNs are Mapped to Host + debug: + msg: + drHostLunIds: "{{ drHostLunIds }}" + drLunNames: "{{ drLunNames }}" + failed_when: (drLunNames|length != drHostLunIds|length) + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ drLunNames }}" + + - set_fact: + drLunsDesc: "{{ checkedLuns | json_query('[*].DESCRIPTION') }}" + drLunsSector: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + drLunsPool: "{{ checkedLuns | json_query('[*].PARENTID') }}" + drLunsWorkload: "{{ checkedLuns | json_query('[*].WORKLOADTYPEID') }}" + + - name: Query Snapshots in CG + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/snapshot/associate?ASSOCIATEOBJTYPE=57646&ASSOCIATEOBJID={{drTestCgId}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: SNAPSHOTS + when: protectLevel|int == 3 + + - set_fact: + drTestLunNames: "{{ drTestLunNames + SNAPSHOTS.json.data | default([]) | json_query(querySnapName) }}" + vars: + querySnapName: "[? SOURCELUNNAME == '{{item}}'].NAME" + with_items: "{{ drLunNames }}" + when: protectLevel|int == 3 + + - set_fact: + drTestLunNamesAll: "{{ SNAPSHOTS.json.data | default([]) | json_query('[*].NAME') }}" + + # End Precheck_3 + when: + - Precheck_3_Execute + - drLunNames|length > 0 + + + - block: + - name: Precheck_4 - Check DR Test Protection Group + debug: + msg: + pg: "{{ drPgName }}" + device: "{{ drDeviceSn }}" + + - set_fact: + drTestLg00Names: "{{ drTestLg00Names + [hostName + '_LG00'] }}" + drTestHostNames: "{{ drTestHostNames + [hostName] }}" + vars: + field: "{{ item.split('_') }}" + hostName: "{{ hostNameTemplate | format(field[0],field[1],field[2],field[3]) }}" + with_items: "{{ drTestLunNamesAll }}" + + - set_fact: + drTestLg00UniqueNames: "{{ drTestLg00Names | unique }}" + drTestHostUniqueNames: "{{ drTestHostNames | unique }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ drTestLg00UniqueNames }}" + + - set_fact: + checkedHostLuns: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id_loop_helper.yml" + vars: + hostName: "{{ item }}" + with_items: "{{ drTestHostUniqueNames }}" + + - set_fact: + drTestHostLunsAll: [] + + - set_fact: + drTestHostLunsAll: "{{ drTestHostLunsAll + checkedHostLuns[item.0][item.1] }}" + with_indexed_items: "{{ drTestLg00UniqueNames }}" + + - set_fact: + drTestLunsAll: "{{ drTestHostLunsAll | json_query('[*].lunName') }}" + drTestLunsAllId: "{{ drTestHostLunsAll | json_query('[*].lunId') }}" + drTestLunsAllScsiId: "{{ drTestHostLunsAll | json_query('[*].hostLunId') }}" + drTestLunScsiIds: [] + + - set_fact: + drTestLunIds: "{{ drTestLunIds + drTestHostLunsAll | json_query(queryLunId) }}" + drTestLunScsiIds: "{{ drTestLunScsiIds + drTestHostLunsAll | json_query(queryScsiId) }}" + vars: + queryScsiId: "[? lunName=='{{item}}'].hostLunId" + queryLunId: "[? lunName=='{{item}}'].lunId" + with_items: "{{ drTestLunNames }}" + + - name: Check DR Test LUNs are Mapped to Host + debug: + msg: + drTestLunScsiIds: "{{ drTestLunScsiIds }}" + drTestLunNames: "{{ drTestLunNames }}" + failed_when: (drTestLunNames|length != drTestLunScsiIds|length) + + - set_fact: + existDrTestLuns: "{{ drTestLunsAll | difference(drTestLunNames) }}" + existDrTestLunsId: "{{ drTestLunsAllId | difference(drTestLunIds) }}" + existDrTestLunsScsiId: "{{ drTestLunsAllScsiId | difference(drTestLunScsiIds) }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ drTestLunsAll }}" + + - set_fact: + drTestLunsAllSector: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + drTestLunsAllDesc: "{{ checkedLuns | json_query('[*].DESCRIPTION') }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ existDrTestLuns }}" + + - set_fact: + existDrTestLunsSector: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + existDrTestLunsDesc: "{{ checkedLuns | json_query('[*].DESCRIPTION') }}" + + # End Precheck_4 + when: + - Precheck_4_Execute + - drTestLunNames|length > 0 + + - block: + + # Begin Workflow Steps + + - set_fact: + + # Remove DR LUNs Class on DJ + Step_0_1_Execute: "{{ (protectLevel|int >= 2 and drLunsInTier|length > 0) }}" + Step_0_1_Completed: False + Step_0_1_Rollbacked: False + + # Remove Metro LUNs Class on DJ + Step_0_2_Execute: "{{ (metroEnable == 'Y' and metroLunsInTier|length > 0) }}" + Step_0_2_Completed: False + Step_0_2_Rollbacked: False + + # Remove Primary LUNs Class on DJ + Step_0_3_Execute: "{{ primaryLunsInTier|length > 0 }}" + Step_0_3_Completed: False + Step_0_3_Rollbacked: False + + # Remove DR Test LUNs from LUN Group + Step_1_1_Execute: "{{ (protectLevel|int == 3 and drTestLunNames|length > 0) and (drTestCgActivated|bool == False) }}" + Step_1_1_Completed: False + Step_1_1_Rollbacked: False + + # Delete DR Test Snapshot CG + Step_1_2_Execute: "{{ (protectLevel|int == 3 and drTestLunNames|length > 0) and (drTestCgActivated|bool == False) }}" + Step_1_2_Completed: False + Step_1_2_Rollbacked: False + + # Remove DR LUNs from LUN Group + Step_1_3_Execute: "{{ (protectLevel|int >= 2 and drLunNames|length > 0) }}" + Step_1_3_Completed: False + Step_1_3_Rollbacked: False + + # Remove Metro LUNs from LUN Group + Step_1_4_Execute: "{{ (metroEnable == 'Y' and metroLunNames|length > 0) }}" + Step_1_4_Completed: False + Step_1_4_Rollbacked: False + + # Remove Primary LUNs from LUN Group + Step_1_5_Execute: True + Step_1_5_Completed: False + Step_1_5_Rollbacked: False + + # Delete DR Star + Step_2_0_Execute: "{{ (protectLevel|int >= 2 and drLunNames|length > 0) and (metroEnable == 'Y' and metroLunNames|length > 0) and (deleteDrStar|bool == True) }}" + Step_2_0_Completed: False + Step_2_0_Rollbacked: False + + # Disable DR Star + Step_2_1_Execute: "{{ (protectLevel|int >= 2 and drLunNames|length > 0) and (metroEnable == 'Y' and metroLunNames|length > 0) and (deleteDrStar|bool == False) }}" + Step_2_1_Completed: False + Step_2_1_Rollbacked: False + + # Remove DR LUNs from Protection Group + Step_3_0_1_Execute: "{{ (protectLevel|int >= 2 and drLunNames|length > 0) }}" + Step_3_0_1_Completed: False + Step_3_0_1_Rollbacked: False + + # Remove Metro LUNs from Protection Group + Step_3_0_2_Execute: "{{ (metroEnable == 'Y' and metroLunNames|length > 0) }}" + Step_3_0_2_Completed: False + Step_3_0_2_Rollbacked: False + + # Remove Primary LUNs from Protection Group + Step_3_0_3_Execute: True + Step_3_0_3_Completed: False + Step_3_0_3_Rollbacked: False + + # Remove Standby DR Pairs from Standby Replication CG + Step_2_2_Execute: "{{ (protectLevel|int >= 2 and drLunNames|length > 0) and (metroEnable == 'Y' and metroLunNames|length > 0) }}" + Step_2_2_Completed: False + Step_2_2_Rollbacked: False + + # Remove DR Pairs from Replication CG + Step_2_3_Execute: "{{ (protectLevel|int >= 2 and drLunNames|length > 0) }}" + Step_2_3_Completed: False + Step_2_3_Rollbacked: False + + # Remove Metro Pairs from HyperMetro CG + Step_2_4_Execute: "{{ (metroEnable == 'Y' and metroLunNames|length > 0) }}" + Step_2_4_Completed: False + Step_2_4_Rollbacked: False + + # Enable DR Star + Step_2_5_Execute: "{{ (protectLevel|int >= 2 and drLunNames|length > 0) and (metroEnable == 'Y' and metroLunNames|length > 0) and (deleteDrStar|bool == False) }}" + Step_2_5_Completed: False + Step_2_5_Rollbacked: False + + # Re-create DR Test Snapshot CG + Step_3_1_Execute: "{{ (protectLevel|int == 3 and existDrTestLuns|length > 0) and (drTestCgActivated|bool == False) }}" + Step_3_1_Completed: False + Step_3_1_Rollbacked: False + + # Add Existing DR Test LUNs to LUN Group + Step_3_2_Execute: "{{ (protectLevel|int == 3 and existDrTestLuns|length > 0) and (drTestCgActivated|bool == False) }}" + Step_3_2_Completed: False + Step_3_2_Rollbacked: False + + # Delete DR LUNs + Step_3_3_Execute: "{{ (protectLevel|int >= 2 and drLunNames|length > 0) and (drTestCgActivated|bool == False) }}" + Step_3_3_Completed: False + Step_3_3_Rollbacked: False + + # Delete Metro LUNs + Step_3_4_Execute: "{{ (metroEnable == 'Y' and metroLunNames|length > 0) }}" + Step_3_4_Completed: False + Step_3_4_Rollbacked: False + + # Delete Empty DR Test LUN Group + Step_3_5_Execute: "{{ (protectLevel|int == 3 and drTestLunNames|length > 0) and (drTestCgActivated|bool == False) }}" + Step_3_5_Completed: False + Step_3_5_Rollbacked: False + + # Delete Empty DR LUN Group + Step_3_6_Execute: "{{ (protectLevel|int >= 2 and drLunNames|length > 0) }}" + Step_3_6_Completed: False + Step_3_6_Rollbacked: False + + # Delete Empty Metro LUN Group + Step_3_7_Execute: "{{ (metroEnable == 'Y' and metroLunNames|length > 0) }}" + Step_3_7_Completed: False + Step_3_7_Rollbacked: False + + deviceSynced: [] + + # Sync Primary Device + Step_4_1_Execute: True + Step_4_1_Completed: False + + # Sync Metro Device + Step_4_2_Execute: "{{ (metroEnable == 'Y' and metroLunNames|length > 0) }}" + Step_4_2_Completed: False + + # Sync DR Device + Step_4_3_Execute: "{{ (protectLevel|int >= 2 and drLunNames|length > 0) }}" + Step_4_3_Completed: False + + # Insert Primary LUNs to KPI table + Step_5_1_Execute: True + Step_5_1_Completed: False + + # Insert Metro LUNs to KPI table + Step_5_2_Execute: "{{ (metroEnable == 'Y' and metroLunNames|length > 0) }}" + Step_5_2_Completed: False + + # Insert DR LUNs to KPI table + Step_5_3_Execute: "{{ (protectLevel|int >= 2 and drLunNames|length > 0) }}" + Step_5_3_Completed: False + + # Insert DR Test LUNs to KPI table + Step_5_4_Execute: "{{ (protectLevel|int == 3 and drTestLunNames|length > 0) and (drTestCgActivated|bool == False) }}" + Step_5_4_Completed: False + + # Update Existing DR Test LUNs to KPI table + Step_5_5_Execute: "{{ (protectLevel|int == 3 and existDrTestLuns|length > 0) and (drTestCgActivated|bool == False) }}" + Step_5_5_Completed: False + + - name: Workflow - Remove LUNs from Protection Group + debug: + msg: + Step_0_1: "[{{Step_0_1_Execute}}] Remove DR LUNs Class on DJ" + Step_0_2: "[{{Step_0_2_Execute}}] Remove Metro LUNs Class on DJ" + Step_0_3: "[{{Step_0_3_Execute}}] Remove Primary LUNs Class on DJ" + Step_1_1: "[{{Step_1_1_Execute}}] Remove DR Test LUNs from LUN Group" + Step_1_2: "[{{Step_1_2_Execute}}] Delete DR Test Snapshot CG" + Step_1_3: "[{{Step_1_3_Execute}}] Remove DR LUNs from LUN Group" + Step_1_4: "[{{Step_1_4_Execute}}] Remove Metro LUNs from LUN Group" + Step_1_5: "[{{Step_1_5_Execute}}] Remove Primary LUNs from LUN Group" + Step_2_0: "[{{Step_2_0_Execute}}] Delete DR Star" + Step_2_1: "[{{Step_2_1_Execute}}] Disable DR Star" + Step_3_0_1: "[{{Step_3_0_1_Execute}}] Remove DR LUNs from Protection Group" + Step_3_0_2: "[{{Step_3_0_2_Execute}}] Remove Metro LUNs from Protection Group" + Step_3_0_3: "[{{Step_3_0_3_Execute}}] Remove Primary LUNs from Protection Group" + Step_2_2: "[{{Step_2_2_Execute}}] Remove Standby DR Pairs from Standby Replication CG" + Step_2_3: "[{{Step_2_3_Execute}}] Remove DR Pairs from Replication CG" + Step_2_4: "[{{Step_2_4_Execute}}] Remove Metro Pairs from HyperMetro CG" + Step_2_5: "[{{Step_2_5_Execute}}] Enable DR Star" + Step_3_1: "[{{Step_3_1_Execute}}] Re-create DR Test Snapshot CG" + Step_3_2: "[{{Step_3_2_Execute}}] Add exist DR Test LUNs to LUN Group" + Step_3_3: "[{{Step_3_3_Execute}}] Delete DR LUNs" + Step_3_4: "[{{Step_3_4_Execute}}] Delete Metro LUNs" + Step_3_5: "[{{Step_3_5_Execute}}] Delete Empty DR Test LUN Group" + Step_3_6: "[{{Step_3_6_Execute}}] Delete Empty DR LUN Group" + Step_3_7: "[{{Step_3_7_Execute}}] Delete Empty Metro LUN Group" + Step_4_1: "[{{Step_4_1_Execute}}] Sync Primary Device" + Step_4_2: "[{{Step_4_2_Execute}}] Sync Metro Device" + Step_4_3: "[{{Step_4_3_Execute}}] Sync DR Device" + Step_5_1: "[{{Step_5_1_Execute}}] Insert Primary LUNs to KPI table" + Step_5_2: "[{{Step_5_2_Execute}}] Insert Metro LUNs to KPI table" + Step_5_3: "[{{Step_5_3_Execute}}] Insert DR LUNs to KPI table" + Step_5_4: "[{{Step_5_4_Execute}}] Insert DR Test LUNs to KPI table" + Step_5_5: "[{{Step_5_5_Execute}}] Update Existing DR Test LUNs to KPI table" + + + - block: + - name: Step_0_1 - Remove DR LUNs Class on DJ + debug: + msg: + params: + volumeNames: "{{ drLunsInTier }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ drLunsInTier }}" + when: drLunsInTier|length > 0 + + - set_fact: + Step_0_1_Completed: True + + # End Step_0_1 + + # End block + when: Step_0_1_Execute + + - block: + - name: Step_0_2 - Remove Metro LUNs Class on DJ + debug: + msg: + params: + volumeNames: "{{ metroLunsInTier }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ metroLunsInTier }}" + when: metroLunsInTier|length > 0 + + - set_fact: + Step_0_2_Completed: True + + # End Step_0_2 + + # End block + when: Step_0_2_Execute + + - block: + - name: Step_0_3 - Remove Primary LUNs Class on DJ + debug: + msg: + params: + volumeNames: "{{ primaryLunsInTier }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ primaryLunsInTier }}" + when: primaryLunsInTier|length > 0 + + - set_fact: + Step_0_3_Completed: True + + # End Step_0_3 + + # End block + when: Step_0_3_Execute + + - block: + - name: Step_1_1 - Remove DR Test LUNs from LUN Group + debug: + msg: + params: + lg: "{{ drTestLg00Names }}" + lunNames: "{{ drTestLunsAll }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + field: "{{ item.split('_') }}" + hostName: "{{ hostNameTemplate | format(field[0],field[1],field[2],field[3]) }}" + lgName: "{{ hostName + '_LG00' }}" + lunNames: [ "{{ item }}" ] + with_items: "{{ drTestLunsAll }}" + + - set_fact: + Step_1_1_Completed: True + + # End Step_1_1 + + # End block + when: Step_1_1_Execute + + - block: + - name: Step_1_2 - Delete DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + + - set_fact: + Step_1_2_Completed: True + + # End Step_1_2 + + # End block + when: Step_1_2_Execute + + - block: + - name: Step_1_3 - Remove DR LUNs from LUN Group + debug: + msg: + params: + lg: "{{ drLg00Names }}" + lunNames: "{{ drLunNames }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ item.0 }}" + lunNames: [ "{{ item.1 }}" ] + with_together: + - "{{ drLg00Names }}" + - "{{ drLunNames }}" + + - set_fact: + Step_1_3_Completed: True + + # End Step_1_3 + + # End block + when: Step_1_3_Execute + + - block: + - name: Step_1_4 - Remove Metro LUNs from LUN Group + debug: + msg: + params: + lg: "{{ metroLg00Names }}" + lunNames: "{{ metroLunNames }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ item.0 }}" + lunNames: [ "{{ item.1 }}" ] + with_together: + - "{{ metroLg00Names }}" + - "{{ metroLunNames }}" + + - set_fact: + Step_1_4_Completed: True + + # End Step_1_4 + + # End block + when: Step_1_4_Execute + + - block: + - name: Step_1_5 - Remove Primary LUNs from LUN Group + debug: + msg: + params: + lg: "{{ primaryLg00Names }}" + lunNames: "{{ primaryLunNames }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ item.0 }}" + lunNames: [ "{{ item.1 }}" ] + with_together: + - "{{ primaryLg00Names }}" + - "{{ primaryLunNames }}" + + - set_fact: + Step_1_5_Completed: True + + # End Step_1_5 + + # End block + when: Step_1_5_Execute + + - block: + - name: Step_2_0 - Delete DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_drstar.yml" + vars: + drStarName: "{{ drStarCgName }}" + + - set_fact: + Step_2_0_Completed: True + + # End Step_2_0 + + # End block + when: Step_2_0_Execute + + - block: + - name: Step_2_1 - Disable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/disable_drstar.yml" + vars: + drStarName: "{{ drStarCgName }}" + + - set_fact: + Step_2_1_Completed: True + + # End Step_2_1 + + # End block + when: Step_2_1_Execute + + - block: + - name: Step_3_0_1 - Remove DR LUNs from Protection Group + debug: + msg: + params: + pgName: "{{ drPgName }}" + lunNames: "{{ drLunNames }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_pg.yml" + vars: + pgName: "{{ drPgName }}" + lunNames: "{{ drLunNames }}" + + - set_fact: + Step_3_0_1_Completed: True + + # End Step_3_0_1 + + # End block + when: Step_3_0_1_Execute + + - block: + - name: Step_3_0_2 - Remove Metro LUNs from Protection Group + debug: + msg: + params: + pgName: "{{ primaryPgName }}" + lunNames: "{{ metroLunNames }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + lunNames: "{{ metroLunNames }}" + + - set_fact: + Step_3_0_2_Completed: True + + # End Step_3_0_2 + + # End block + when: Step_3_0_2_Execute + + - block: + - name: Step_3_0_3 - Remove Primary LUNs from Protection Group + debug: + msg: + params: + pgName: "{{ primaryPgName }}" + lunNames: "{{ primaryLunNames }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + lunNames: "{{ primaryLunNames }}" + + - set_fact: + Step_3_0_3_Completed: True + + # End Step_3_0_3 + + # End block + when: Step_3_0_3_Execute + + - block: + - name: Step_2_2 - Remove Standby DR Pairs from Standby Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + cgName: "{{ standbyCgName }}" + deletePairs: True + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_replication_cg.yml" + vars: + cgName: "{{ standbyCgName }}" + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + deletePairs: True + + - set_fact: + Step_2_2_Completed: True + + # End Step_2_2 + + # End block + when: Step_2_2_Execute + + - block: + - name: Step_2_3 - Remove DR Pairs from Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + cgName: "{{ drCgName }}" + deletePairs: True + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_replication_cg.yml" + vars: + cgName: "{{ drCgName }}" + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + deletePairs: True + + - set_fact: + Step_2_3_Completed: True + + # End Step_2_3 + + # End block + when: Step_2_3_Execute + + - block: + - name: Step_2_4 - Remove Metro Pairs from HyperMetro CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + cgName: "{{ metroCgName }}" + deletePairs: True + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_hypermetro_cg.yml" + vars: + cgName: "{{ metroCgName }}" + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + deletePairs: True + + - set_fact: + Step_2_4_Completed: True + + # End Step_2_4 + + # End block + when: Step_2_4_Execute + + - block: + - name: Step_2_5 - Enable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/enable_drstar.yml" + vars: + drStarName: "{{ drStarCgName }}" + + - set_fact: + Step_2_5_Completed: True + + # End Step_2_5 + + # End block + when: Step_2_5_Execute + + - block: + - name: Step_3_1 - Re-create DR Test Snapshot CG + debug: + msg: + params: + snapCg: + pgName: "{{ drPgName }}" + cgName: "{{ drTestCgName }}" + snapNames: "{{ existDrTestLuns }}" + activate: False + snapDescs: "{{ existDrTestLunsDesc }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" + vars: + pgName: "{{ drPgName }}" + cgName: "{{ drTestCgName }}" + snapNames: "{{ existDrTestLuns }}" + activate: False + snapDescs: "{{ existDrTestLunsDesc }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ existDrTestLuns }}" + checkExist: True + + - set_fact: + existDrTestLunsIdNew: "{{ lunIds }}" + Step_3_1_Completed: True + + # End Step_3_1 + + # End block + when: Step_3_1_Execute + + - block: + - name: Step_3_2 - Add Existing DR Test LUNs to LUN Group + debug: + msg: + params: + lunNames: "{{ existDrTestLuns }}" + addLunScsiIds: "{{ existDrTestLunsScsiId }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + field: "{{ item.0.split('_') }}" + hostName: "{{ hostNameTemplate | format(field[0],field[1],field[2],field[3]) }}" + lgName: "{{ hostName + '_LG00' }}" + lunNames: [ "{{ item.0 }}" ] + addLunScsiIds: [ "{{ item.1 }}" ] + with_together: + - "{{ existDrTestLuns }}" + - "{{ existDrTestLunsScsiId }}" + + - set_fact: + Step_3_2_Completed: True + + # End Step_3_2 + + # End block + when: Step_3_2_Execute + + - block: + - name: Step_3_3 - Delete DR LUNs + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + device: "{{ drDeviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/disable_volumes_qos.yml" + vars: + volumeNames: "{{ drLunNames }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_luns.yml" + vars: + lunNames: "{{ drLunNames }}" + + - set_fact: + Step_3_3_Completed: True + + # End Step_3_3 + + # End block + when: Step_3_3_Execute + + - block: + - name: Step_3_4 - Delete Metro LUNs + debug: + msg: + params: + lunNames: "{{ metroLunNames }}" + device: "{{ metroDeviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/disable_volumes_qos.yml" + vars: + volumeNames: "{{ metroLunNames }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_luns.yml" + vars: + lunNames: "{{ metroLunNames }}" + + - set_fact: + Step_3_4_Completed: True + + # End Step_3_4 + + # End block + when: Step_3_4_Execute + + - block: + - name: Step_3_5 - Delete Empty DR Test LUN Group + debug: + msg: + params: + lg: "{{ drTestLg00Names }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ drTestLg00UniqueNames }}" + + - set_fact: + drTestEmptyLg00Names: "{{ checkedLgs | json_query(queryName)}}" + drTestEmptyLg00Descs: "{{ checkedLgs | json_query(queryDesc)}}" + drTestEmptyLg00Hosts: [] + vars: + queryName: "[? lunNumber=='0'].NAME" + queryDesc: "[? lunNumber=='0'].NAME" + + - set_fact: + drTestEmptyLg00Hosts: "{{ drTestEmptyLg00Hosts + [ item[:-5] ]}}" + with_items: "{{ drTestEmptyLg00Names }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_lg.yml" + vars: + lgName: "{{ item }}" + unmap: True + with_items: "{{ drTestEmptyLg00Names }}" + + - set_fact: + Step_3_5_Completed: True + + # End Step_3_5 + + # End block + when: Step_3_5_Execute + + - block: + - name: Step_3_6 - Delete Empty DR LUN Group + debug: + msg: + params: + lg: "{{ drLg00Names }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ drLg00UniqueNames }}" + + - set_fact: + drEmptyLg00Names: "{{ checkedLgs | json_query(queryName)}}" + drEmptyLg00Descs: "{{ checkedLgs | json_query(queryDesc)}}" + drEmptyLg00Hosts: [] + vars: + queryName: "[? lunNumber=='0'].NAME" + queryDesc: "[? lunNumber=='0'].NAME" + + - set_fact: + drEmptyLg00Hosts: "{{ drEmptyLg00Hosts + [ item[:-5] ]}}" + with_items: "{{ drEmptyLg00Names }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_lg.yml" + vars: + lgName: "{{ item }}" + unmap: True + with_items: "{{ drEmptyLg00Names }}" + + - set_fact: + Step_3_6_Completed: True + + # End Step_3_6 + + # End block + when: Step_3_6_Execute + + - block: + - name: Step_3_7 - Delete Empty Metro LUN Group + debug: + msg: + params: + lg: "{{ metroLg00Names }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ metroLg00UniqueNames }}" + + - set_fact: + metroEmptyLg00Names: "{{ checkedLgs | json_query(queryName)}}" + metroEmptyLg00Descs: "{{ checkedLgs | json_query(queryDesc)}}" + metroEmptyLg00Hosts: [] + vars: + queryName: "[? lunNumber=='0'].NAME" + queryDesc: "[? lunNumber=='0'].NAME" + + - set_fact: + metroEmptyLg00Hosts: "{{ metroEmptyLg00Hosts + [ item[:-5] ]}}" + with_items: "{{ metroEmptyLg00Names }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_lg.yml" + vars: + lgName: "{{ item }}" + unmap: True + with_items: "{{ metroEmptyLg00Names }}" + + - set_fact: + Step_3_7_Completed: True + + # End Step_3_7 + + # End block + when: Step_3_7_Execute + + # End Device Steps + + # Begin DJ Steps + + - block: + - name: Step_4_1 - Sync Primary Device + debug: + msg: + params: + deviceName: "{{ primaryDeviceName }}" + + - set_fact: + deviceName: "{{ primaryDeviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + + - set_fact: + deviceSynced: "{{ deviceSynced + [primaryDeviceName] }}" + + - set_fact: + Step_4_1_Completed: True + + # End Step_4_1 + + # End block + when: Step_4_1_Execute + + - block: + - name: Step_4_2 - Sync Metro Device + debug: + msg: + params: + deviceName: "{{ metroDeviceName }}" + + - set_fact: + deviceName: "{{ metroDeviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + when: + - metroDeviceName not in deviceSynced + + - set_fact: + deviceSynced: "{{ deviceSynced + [metroDeviceName] }}" + when: + - metroDeviceName not in deviceSynced + + - set_fact: + Step_4_2_Completed: True + + # End Step_4_2 + + # End block + when: Step_4_2_Execute + + - block: + - name: Step_4_3 - Sync DR Device + debug: + msg: + params: + deviceName: "{{ drDeviceName }}" + + - set_fact: + deviceName: "{{ drDeviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + when: + - drDeviceName not in deviceSynced + + - set_fact: + deviceSynced: "{{ deviceSynced + [drDeviceName] }}" + when: + - drDeviceName not in deviceSynced + + - set_fact: + Step_4_3_Completed: True + + # End Step_4_3 + + # End block + when: Step_4_3_Execute + + # End DJ Steps + + - block: + - name: Step_5_1 - Insert Primary LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ primaryLunIds }}" + device: "{{ primaryDeviceName }}" + + # Minus capacity from primary host + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "" + SITE: "{{ primarySite }}" + ENVIRONMENT: "" + STORAGE_CLASS: "{{ primaryLunsClass[item.0] }}" + CAPACITY_GB: "-{{ primaryLunsSize[item.0] }}" + STORAGE: "{{ primaryDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ primaryLunIds }}" + + # Add orphan capacity + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "" + SITE: "{{ primarySite }}" + ENVIRONMENT: "" + STORAGE_CLASS: "" + CAPACITY_GB: "{{ primaryLunsSize[item.0] }}" + STORAGE: "{{ primaryDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ primaryLunIds }}" + + - set_fact: + Step_5_1_Completed: True + + # End Step_5_1 + + # End block + when: Step_5_1_Execute + + - block: + - name: Step_5_2 - Insert Metro LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ metroLunIds }}" + device: "{{ metroDeviceName }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "delete" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "" + SITE: "{{ metroSite }}" + ENVIRONMENT: "" + STORAGE_CLASS: "{{ metroLunsClass[item.0] }}" + CAPACITY_GB: "-{{ metroLunsSize[item.0] }}" + STORAGE: "{{ metroDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ metroLunIds }}" + + - set_fact: + Step_5_2_Completed: True + + # End Step_5_2 + + # End block + when: Step_5_2_Execute + + - block: + - name: Step_5_3 - Insert DR LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drLunIds }}" + device: "{{ drDeviceName }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "delete" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "" + SITE: "{{ drSite }}" + ENVIRONMENT: "" + STORAGE_CLASS: "{{ drLunsClass[item.0] }}" + CAPACITY_GB: "-{{ drLunsSize[item.0] }}" + STORAGE: "{{ drDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drLunIds }}" + when: drTestCgActivated|bool == False + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "" + SITE: "{{ drSite }}" + ENVIRONMENT: "" + STORAGE_CLASS: "{{ drLunsClass[item.0] }}" + CAPACITY_GB: "-{{ drLunsSize[item.0] }}" + STORAGE: "{{ drDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drLunIds }}" + when: drTestCgActivated|bool == True + + # Update orphan DR LUNs + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "" + SITE: "{{ drSite }}" + ENVIRONMENT: "" + STORAGE_CLASS: "" + CAPACITY_GB: "{{ drLunsSize[item.0] }}" + STORAGE: "{{ drDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drLunIds }}" + when: drTestCgActivated|bool == True + + - set_fact: + Step_5_3_Completed: True + + # End Step_5_3 + + # End block + when: Step_5_3_Execute + + - block: + - name: Step_5_4 - Insert DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunsAllId }}" + device: "{{ drDeviceName }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "delete" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "" + SITE: "{{ drSite }}" + ENVIRONMENT: "" + STORAGE_CLASS: "" + CAPACITY_GB: "-{{ (drTestLunsAllSector[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ drDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drTestLunsAllId }}" + + - set_fact: + Step_5_4_Completed: True + + # End Step_5_4 + + # End block + when: Step_5_4_Execute + + - block: + - name: Step_5_5 - Update Existing DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: + old: "{{ existDrTestLunsId }}" + new: "{{ existDrTestLunsIdNew }}" + device: "{{ drDeviceName }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "create" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "" + SITE: "{{ drSite }}" + ENVIRONMENT: "" + STORAGE_CLASS: "" + CAPACITY_GB: "{{ (existDrTestLunsSector[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ drDeviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ existDrTestLunsIdNew }}" + + - set_fact: + Step_5_5_Completed: True + + # End Step_5_5 + + # End block + when: Step_5_5_Execute + + # End Block + rescue: + # Begin Rollback + + - block: + - name: Rollback_3_7 - Create Metro LUN Group + debug: + msg: + params: + lg: "{{ metroEmptyLg00Names }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_lg.yml" + vars: + lgName: "{{ item.0 }}" + mapHostNames: ["{{ item.1 }}"] + desc: "{{ item.2 }}" + with_together: + - "{{ metroEmptyLg00Names }}" + - "{{ metroEmptyLg00Hosts }}" + - "{{ metroEmptyLg00Descs }}" + + - set_fact: + Step_3_7_Rollbacked: True + + # End Rollback_3_7 + + # End block + when: Step_3_7_Completed + + - block: + - name: Rollback_3_6 - Create DR LUN Group + debug: + msg: + params: + lg: "{{ drEmptyLg00Names }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_lg.yml" + vars: + lgName: "{{ item.0 }}" + mapHostNames: ["{{ item.1 }}"] + desc: "{{ item.2 }}" + with_together: + - "{{ drEmptyLg00Names }}" + - "{{ drEmptyLg00Hosts }}" + - "{{ drEmptyLg00Descs }}" + + - set_fact: + Step_3_6_Rollbacked: True + + # End Rollback_3_6 + + # End block + when: Step_3_6_Completed + + - block: + - name: Rollback_3_5 - Create DR Test LUN Group + debug: + msg: + params: + lg: "{{ drTestEmptyLg00Names }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_lg.yml" + vars: + lgName: "{{ item.0 }}" + mapHostNames: ["{{ item.1 }}"] + desc: "{{ item.2 }}" + with_together: + - "{{ drTestEmptyLg00Names }}" + - "{{ drTestEmptyLg00Hosts }}" + - "{{ drTestEmptyLg00Descs }}" + + - set_fact: + Step_3_5_Rollbacked: True + + # End Rollback_3_5 + + # End block + when: Step_3_5_Completed + + - block: + - name: Rollback_3_4 - Create Metro LUNs + debug: + msg: + params: + lunNames: "{{ metroLunNames }}" + lunDescs: "{{ metroLunsDesc }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_lun.yml" + vars: + lunName: "{{ metroLunNames[i] }}" + lunSector: "{{ metroLunsSector[i] }}" + poolId: "{{ metroLunsPool[i] }}" + workload: "{{ metroLunsWorkload[i] }}" + desc: "{{ metroLunsDesc[i] }}" + loop: "{{ range(0, metroLunNames|length) | list }}" + loop_control: + loop_var: i + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ metroLunNames }}" + + - set_fact: + metroLunIds: "{{ lunIds }}" + Step_3_4_Rollbacked: True + + # End block + when: Step_3_4_Completed + + - block: + - name: Rollback_3_3 - Create DR LUNs + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + lunDescs: "{{ drLunsDesc }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_lun.yml" + vars: + lunName: "{{ drLunNames[i] }}" + lunSector: "{{ drLunsSector[i] }}" + poolId: "{{ drLunsPool[i] }}" + workload: "{{ drLunsWorkload[i] }}" + desc: "{{ drLunsDesc[i] }}" + loop: "{{ range(0, drLunNames|length) | list }}" + loop_control: + loop_var: i + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ drLunNames }}" + + - set_fact: + drLunIds: "{{ lunIds }}" + Step_3_3_Rollbacked: True + + # End block + when: Step_3_3_Completed + + - block: + - name: Rollback_3_2 - Remove Existing DR Test LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ existDrTestLuns }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + field: "{{ item.split('_') }}" + hostName: "{{ hostNameTemplate | format(field[0],field[1],field[2],field[3]) }}" + lgName: "{{ hostName + '_LG00' }}" + lunNames: [ "{{ item }}" ] + with_items: "{{ existDrTestLuns }}" + + - set_fact: + Step_3_2_Rollbacked: True + + # End block + when: Step_3_2_Completed + + - block: + - name: Rollback_3_1 - Delete DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + + - set_fact: + Step_3_1_Rollbacked: True + + # End block + when: Step_3_1_Completed + + - block: + - name: Rollback_1_5 - Add Primary LUNs to LUN Group + debug: + msg: + params: + lg: "{{ primaryLg00Names }}" + lunNames: "{{ primaryLunNames }}" + addLunScsiIds: "{{ primaryHostLunIds }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lgName: "{{ item.0 }}" + lunNames: [ "{{ item.1 }}" ] + addLunScsiIds: [ "{{ item.2 }}" ] + with_together: + - "{{ primaryLg00Names }}" + - "{{ primaryLunNames }}" + - "{{ primaryHostLunIds }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_hypermetro_pairs.yml" + vars: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + remoteSn: "{{ metroDeviceSn }}" + when: Step_2_4_Completed + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_replication_pairs.yml" + vars: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + mode: "{{ drCgModeEnum }}" + when: Step_2_3_Completed + + - set_fact: + Step_1_5_Rollbacked: True + + # End Step_1_5 + + # End block + when: Step_1_5_Completed + + - block: + - name: Rollback_1_4 - Add Metro LUNs to LUN Group + debug: + msg: + params: + lg: "{{ metroLg00Names }}" + lunNames: "{{ metroLunNames }}" + addLunScsiIds: "{{ metroHostLunIds }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lgName: "{{ item.0 }}" + lunNames: [ "{{ item.1 }}" ] + addLunScsiIds: [ "{{ item.2 }}" ] + with_together: + - "{{ metroLg00Names }}" + - "{{ metroLunNames }}" + - "{{ metroHostLunIds }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_replication_pairs.yml" + vars: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + remoteSn: "{{ drDeviceSn }}" + mode: "{{ GLOBAL.replication.syncMode.async }}" + standby: True + when: Step_2_2_Completed + + - set_fact: + Step_1_4_Rollbacked: True + + # End Rollback_1_4 + + # End block + when: Step_1_4_Completed + + - block: + - name: Rollback_1_3 - Add DR LUNs to LUN Group + debug: + msg: + params: + lg: "{{ drLg00Names }}" + lunNames: "{{ drLunNames }}" + addLunScsiIds: "{{ drHostLunIds }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lgName: "{{ item.0 }}" + lunNames: [ "{{ item.1 }}" ] + addLunScsiIds: [ "{{ item.2 }}" ] + with_together: + - "{{ drLg00Names }}" + - "{{ drLunNames }}" + - "{{ drHostLunIds }}" + + - set_fact: + Step_1_3_Rollbacked: True + + # End Rollback_1_3 + + # End block + when: Step_1_3_Completed + + - block: + - name: Rollback_2_5 - Disable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/disable_drstar.yml" + vars: + drStarName: "{{ drStarCgName }}" + + - set_fact: + Step_2_5_Rollbacked: True + + # End Rollback_2_5 + + # End block + when: Step_2_5_Completed + + + - block: + - name: Rollback_2_4 - Add Metro Pairs to HyperMetro CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + cgName: "{{ metroCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_hypermetro_cg.yml" + vars: + cgName: "{{ metroCgName }}" + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + waitSync: True + + - set_fact: + Step_2_4_Rollbacked: True + + # End Rollback_2_4 + + # End block + when: Step_2_4_Completed + + + - block: + - name: Rollback_2_3 - Add DR Pairs to Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + cgName: "{{ drCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_replication_cg.yml" + vars: + cgName: "{{ drCgName }}" + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + waitSync: True + + - set_fact: + Step_2_3_Rollbacked: True + + # End Rollback_2_3 + + # End block + when: Step_2_3_Completed + + - block: + - name: Rollback_2_2 - Add Standby DR Pairs to Standby Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + cgName: "{{ standbyCgName }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_replication_cg.yml" + vars: + cgName: "{{ standbyCgName }}" + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + standby: True + + - set_fact: + Step_2_2_Rollbacked: True + + # End Rollback_2_2 + + # End block + when: Step_2_2_Completed + + - block: + - name: Rollback_3_0_3 - Add Primary LUNs to Protection Group + debug: + msg: + params: + lunNames: "{{ primaryLunNames }}" + pgName: "{{ primaryPgName }}" + addLunScsiIds: "{{ primaryHostLunIds }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + lunNames: "{{ primaryLunNames }}" + addLunScsiIds: "{{ primaryHostLunIds }}" + + - set_fact: + Step_3_0_3_Rollbacked: True + + # End Step_3_0_3 + + # End block + when: Step_3_0_3_Completed + + - block: + - name: Rollback_3_0_2 - Add Metro LUNs to Protection Group + debug: + msg: + params: + lunNames: "{{ metroLunNames }}" + pgName: "{{ primaryPgName }}" + addLunScsiIds: "{{ metroHostLunIds }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + lunNames: "{{ metroLunNames }}" + addLunScsiIds: "{{ metroHostLunIds }}" + + - set_fact: + Step_3_0_2_Rollbacked: True + + # End Rollback_3_0_2 + + # End block + when: Step_3_0_2_Completed + + - block: + - name: Rollback_3_0_1 - Add DR LUNs to Protection Group + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + pgName: "{{ drPgName }}" + addLunScsiIds: "{{ drHostLunIds }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_pg.yml" + vars: + pgName: "{{ drPgName }}" + lunNames: "{{ drLunNames }}" + addLunScsiIds: "{{ drHostLunIds }}" + + - set_fact: + Step_3_0_1_Rollbacked: True + + # End Rollback_3_0_1 + + # End block + when: Step_3_0_1_Completed + + - block: + - name: Rollback_2_1 - Enable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/enable_drstar.yml" + vars: + drStarName: "{{ drStarCgName }}" + + + - set_fact: + Step_2_1_Rollbacked: True + + # End Rollback_2_1 + + # End block + when: Step_2_1_Completed + + - block: + - name: Rollback_2_0 - Create DR Star + debug: + msg: + params: + drstar: + drStarName: "{{ drStarCgName }}" + mode: 1 # HyperMetro + Async Replication + memberType: 2 # CG + metroId: "{{ metroCgId }}" + asyncId: "{{ standbyCgId }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_drstar.yml" + vars: + drStarName: "{{ drStarCgName }}" + mode: 1 # HyperMetro + Async Replication + memberType: 2 # CG + metroId: "{{ metroCgId }}" + asyncId: "{{ standbyCgId }}" + + - set_fact: + Step_2_0_Rollbacked: True + + # End Rollback_2_0 + + # End block + when: Step_2_0_Completed + + - block: + - name: Rollback_1_2 - Re-create DR Test Snapshot CG + debug: + msg: + params: + snapCg: + pgName: "{{ drPgName }}" + cgName: "{{ drTestCgName }}" + snapNames: "{{ drTestLunsAll }}" + activate: False + snapDescs: "{{ drTestLunsAllDesc }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" + vars: + pgName: "{{ drPgName }}" + cgName: "{{ drTestCgName }}" + snapNames: "{{ drTestLunsAll }}" + activate: False + snapDescs: "{{ drTestLunsAllDesc }}" + + - set_fact: + Step_1_2_Rollbacked: True + + # End block + when: Step_1_2_Completed + + - block: + - name: Rollback_1_1 - Add DR Test LUNs to LUN Group + debug: + msg: + params: + lg: "{{ drTestLg00Names }}" + lunNames: "{{ drTestLunsAll }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + field: "{{ item.split('_') }}" + hostName: "{{ hostNameTemplate | format(field[0],field[1],field[2],field[3]) }}" + lunNames: [ "{{ hostName + '_LG00' }}" ] + addLunScsiIds: [ "{{ item.2 }}" ] + with_together: + - "{{ drTestLunsAll }}" + - "{{ drTestLunsAllScsiId }}" + + - set_fact: + Step_1_1_Rollbacked: True + + # End Rollback_1_1 + + # End block + when: Step_1_1_Completed + + - block: + - name: Re-Sync Storage Devices + debug: + msg: + params: + devices: "{{ deviceSynced }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + loop: "{{ deviceSynced }}" + loop_control: + loop_var: deviceName + + when: deviceSynced|length > 0 + + - block: + - name: Rollback_0_3 - Set Primary LUN Class on DJ + debug: + msg: + params: + volumeNames: "{{ primaryLunsInTier }}" + tierName: "{{ primaryLunsInTierClass }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ primaryLunNames }}" + waitExist: True + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: ["{{ primaryLunsInTier[i] }}"] + tierName: "{{ primaryLunsInTierClass[i] }}" + loop: "{{ range(0, primaryLunsInTier|length) | list }}" + loop_control: + loop_var: i + + - set_fact: + Step_0_3_Rollbacked: True + + # End block + when: Step_0_3_Completed + + - block: + - name: Rollback_0_2 - Set Metro LUN Class on DJ + debug: + msg: + params: + volumeNames: "{{ metroLunsInTier }}" + tierName: "{{ metroLunsInTierClass }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ metroLunNames }}" + waitExist: True + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: ["{{ metroLunsInTier[i] }}"] + tierName: "{{ metroLunsInTierClass[i] }}" + loop: "{{ range(0, metroLunsInTier|length) | list }}" + loop_control: + loop_var: i + + - set_fact: + Step_0_2_Rollbacked: True + + # End block + when: Step_0_2_Completed + + - block: + - name: Rollback_0_1 - Set DR LUN Class on DJ + debug: + msg: + params: + volumeNames: "{{ drLunsInTier }}" + tierName: "{{ drLunsInTierClass }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ drLunNames }}" + waitExist: True + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: ["{{ drLunsInTier[i] }}"] + tierName: "{{ drLunsInTierClass[i] }}" + loop: "{{ range(0, drLunsInTier|length) | list }}" + loop_control: + loop_var: i + + - set_fact: + Step_0_1_Rollbacked: True + + # End block + when: Step_0_1_Completed + + # End Rollbacks + + # End Workflow + + # Begin Validate Results + + - block: + + - name: Result_0_1 - Remove DR LUNs Class on DJ + debug: + msg: + params: + volumeNames: "{{ drLunsInTier }}" + result: + succeeded: "{{ Step_0_1_Completed }}" + rollbacked: "{{ Step_0_1_Rollbacked }}" + failed_when: Step_0_1_Completed|bool == False + when: Step_0_1_Execute + + - name: Result_0_2 - Remove Metro LUNs Class on DJ + debug: + msg: + params: + volumeNames: "{{ metroLunsInTier }}" + result: + succeeded: "{{ Step_0_2_Completed }}" + rollbacked: "{{ Step_0_2_Rollbacked }}" + failed_when: Step_0_2_Completed|bool == False + when: Step_0_2_Execute + + - name: Result_0_3 - Remove Primary LUNs Class on DJ + debug: + msg: + params: + volumeNames: "{{ primaryLunsInTier }}" + result: + succeeded: "{{ Step_0_3_Completed }}" + rollbacked: "{{ Step_0_3_Rollbacked }}" + failed_when: Step_0_3_Completed|bool == False + when: Step_0_3_Execute + + - name: Result_1_1 - Remove DR Test LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ drTestLunsAll }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_1_1_Completed }}" + rollbacked: "{{ Step_1_1_Rollbacked }}" + failed_when: Step_1_1_Completed|bool == False + when: Step_1_1_Execute + + - name: Result_1_2 - Delete DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_1_2_Completed }}" + rollbacked: "{{ Step_1_2_Rollbacked }}" + failed_when: Step_1_2_Completed|bool == False + when: Step_1_2_Execute + + - name: Result_1_3 - Remove DR LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_1_3_Completed }}" + rollbacked: "{{ Step_1_3_Rollbacked }}" + failed_when: Step_1_3_Completed|bool == False + when: Step_1_3_Execute + + - name: Result_1_4 - Remove Metro LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ metroLunNames }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_1_4_Completed }}" + rollbacked: "{{ Step_1_4_Rollbacked }}" + failed_when: Step_1_4_Completed|bool == False + when: Step_1_4_Execute + + + - name: Result_1_5 - Remove Primary LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ primaryLunNames }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_1_5_Completed }}" + rollbacked: "{{ Step_1_5_Rollbacked }}" + failed_when: Step_1_5_Completed|bool == False + when: Step_1_5_Execute + + - name: Result_2_0 - Delete DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_2_0_Completed }}" + rollbacked: "{{ Step_2_0_Rollbacked }}" + failed_when: Step_2_0_Completed|bool == False + when: Step_2_0_Execute + + - name: Result_2_1 - Disable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_2_1_Completed }}" + rollbacked: "{{ Step_2_1_Rollbacked }}" + failed_when: Step_2_1_Completed|bool == False + when: Step_2_1_Execute + + - name: Result_2_2 - Remove Standby DR Pairs from Standby Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ metroLunIds }}" + remoteLunIds: "{{ drLunIds }}" + cgName: "{{ standbyCgName }}" + deletePairs: True + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_2_2_Completed }}" + rollbacked: "{{ Step_2_2_Rollbacked }}" + failed_when: Step_2_2_Completed|bool == False + when: Step_2_2_Execute + + - name: Result_2_3 - Remove DR Pairs from Replication CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ drLunIds }}" + cgName: "{{ drCgName }}" + deletePairs: True + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_2_3_Completed }}" + rollbacked: "{{ Step_2_3_Rollbacked }}" + failed_when: Step_2_3_Completed|bool == False + when: Step_2_3_Execute + + - name: Result_2_4 - Remove Metro Pairs from HyperMetro CG + debug: + msg: + params: + pairs: + localLunIds: "{{ primaryLunIds }}" + remoteLunIds: "{{ metroLunIds }}" + cgName: "{{ metroCgName }}" + deletePairs: True + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_2_4_Completed }}" + rollbacked: "{{ Step_2_4_Rollbacked }}" + failed_when: Step_2_4_Completed|bool == False + when: Step_2_4_Execute + + - name: Result_2_5 - Enable DR Star + debug: + msg: + params: + drStarName: "{{ drStarCgName }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_2_5_Completed }}" + rollbacked: "{{ Step_2_5_Rollbacked }}" + failed_when: Step_2_5_Completed|bool == False + when: Step_2_5_Execute + + - name: Result_3_1 - Re-create DR Test Snapshot CG + debug: + msg: + params: + snapCg: + pgName: "{{ drPgName }}" + cgName: "{{ drTestCgName }}" + snapNames: "{{ existDrTestLuns }}" + activate: False + snapDescs: "{{ existDrTestLunsDesc }}" + result: + succeeded: "{{ Step_3_1_Completed }}" + rollbacked: "{{ Step_3_1_Rollbacked }}" + failed_when: Step_3_1_Completed|bool == False + when: Step_3_1_Execute + + - name: Result_3_2 - Add Existing DR Test LUNs to LUN Group + debug: + msg: + params: + lunNames: "{{ existDrTestLuns }}" + addLunScsiIds: "{{ existDrTestLunsScsiId }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_3_2_Completed }}" + rollbacked: "{{ Step_3_2_Rollbacked }}" + failed_when: Step_3_2_Completed|bool == False + when: Step_3_2_Execute + + - name: Result_3_3 - Delete DR LUNs + debug: + msg: + params: + lunNames: "{{ drLunNames }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_3_3_Completed }}" + rollbacked: "{{ Step_3_3_Rollbacked }}" + failed_when: Step_3_3_Completed|bool == False + when: Step_3_3_Execute + + - name: Result_3_4 - Delete Metro LUNs + debug: + msg: + params: + lunNames: "{{ metroLunNames }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_3_4_Completed }}" + rollbacked: "{{ Step_3_4_Rollbacked }}" + failed_when: Step_3_4_Completed|bool == False + when: Step_3_4_Execute + + - name: Result_3_5 - Delete Empty DR Test Lun Group + debug: + msg: + params: + lg: "{{ drTestEmptyLg00Names }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_3_5_Completed }}" + rollbacked: "{{ Step_3_5_Rollbacked }}" + failed_when: Step_3_5_Completed|bool == False + when: Step_3_5_Execute + + - name: Result_3_6 - Delete Empty DR Lun Group + debug: + msg: + params: + lg: "{{ drEmptyLg00Names }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_3_6_Completed }}" + rollbacked: "{{ Step_3_6_Rollbacked }}" + failed_when: Step_3_6_Completed|bool == False + when: Step_3_6_Execute + + - name: Result_3_7 - Delete Empty Metro Lun Group + debug: + msg: + params: + lg: "{{ metroEmptyLg00Names }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_3_7_Completed }}" + rollbacked: "{{ Step_3_7_Rollbacked }}" + failed_when: Step_3_7_Completed|bool == False + when: Step_3_7_Execute + + - name: Result_4_1 - Sync Primary Device + debug: + msg: + params: + deviceName: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_4_1_Completed }}" + failed_when: Step_4_1_Completed|bool == False + when: Step_4_1_Execute + + - name: Result_4_2 - Sync Metro Device + debug: + msg: + params: + deviceName: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_4_2_Completed }}" + failed_when: Step_4_2_Completed|bool == False + when: Step_4_2_Execute + + - name: Result_4_3 - Sync DR Device + debug: + msg: + params: + deviceName: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_4_3_Completed }}" + failed_when: Step_4_3_Completed|bool == False + when: Step_4_3_Execute + + - name: Result_5_1 - Insert Primary LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ primaryLunIds }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_5_1_Completed }}" + failed_when: Step_5_1_Completed|bool == False + when: Step_5_1_Execute + + - name: Result_5_2 - Insert Metro LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ metroLunIds }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_5_2_Completed }}" + failed_when: Step_5_2_Completed|bool == False + when: Step_5_2_Execute + + - name: Result_5_3 - Insert DR LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drLunIds }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_5_3_Completed }}" + failed_when: Step_5_3_Completed|bool == False + when: Step_5_3_Execute + + - name: Result_5_4 - Insert DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunsAllId }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_5_4_Completed }}" + failed_when: Step_5_4_Completed|bool == False + when: Step_5_4_Execute + + - name: Result_5_5 - Update Existing DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: + old: "{{ existDrTestLunsId }}" + new: "{{ existDrTestLunsIdNew }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_5_5_Completed }}" + failed_when: Step_5_5_Completed|bool == False + when: Step_5_5_Execute + + # End Validate Results + + # End Tasks + +# End Playbook \ No newline at end of file diff --git a/rundeck/workflow/project001/52_delete_pg.yml b/rundeck/workflow/project001/52_delete_pg.yml new file mode 100644 index 0000000..ce9e1a4 --- /dev/null +++ b/rundeck/workflow/project001/52_delete_pg.yml @@ -0,0 +1,915 @@ +- name: Delete Protection Group + hosts: localhost + vars_files: + - ../../../config/global.yml + - ../../../config/project001.yml + gather_facts: no + become: no + tasks: + # Check Protection Group Params + - block: + - set_fact: + checked_pg_params: + Storage: "{{ (Storage is not none and Storage != DEFAULT.noneValue) and (Storage|string|length == 20) }}" + Protection_Group: "{{ Protection_Group is not none and Protection_Group != DEFAULT.noneValue }}" + Enable_HyperMetro: "{{ Enable_HyperMetro in ['Y','N'] }}" + Protection_Level: "{{ Protection_Level|int in [1,2,3] }}" + Check_Result_1: "{{ ('pg' in Check_Result_1) }}" + + - name: Precheck_0_1 - Check Protection Group Params + debug: + msg: "{{checked_pg_params}}" + failed_when: checked_pg_params.values()|unique != [True] + + # Check Metro Protection Group Params + - block: + - set_fact: + checked_metro_pg_params: + Metro_Storage: "{{ (Metro_Storage is not none and Metro_Storage != DEFAULT.noneValue) and (Metro_Storage|string|length == 20) }}" + Metro_Protection_Group: "{{ Metro_Protection_Group is not none and Metro_Protection_Group != DEFAULT.noneValue }}" + Metro_CG: "{{ Metro_CG is not none and Metro_CG != DEFAULT.noneValue }}" + Check_Result_2: "{{ ('pg' in Check_Result_2) }}" + + - name: Precheck_0_2 - Check Metro Protection Group Params + debug: + msg: "{{checked_metro_pg_params}}" + failed_when: checked_metro_pg_params.values()|unique != [True] + when: Enable_HyperMetro == 'Y' + + # Check DR Protection Group Params + - block: + - set_fact: + checked_dr_pg_params: + DR_Storage: "{{ (DR_Storage is not none and DR_Storage != DEFAULT.noneValue) and (DR_Storage|string|length == 20) }}" + DR_Protection_Group: "{{ DR_Protection_Group is not none and DR_Protection_Group != DEFAULT.noneValue }}" + DR_CG: "{{ DR_CG is not none and DR_CG != DEFAULT.noneValue }}" + Check_Result_3: "{{ ('pg' in Check_Result_3) }}" + + - name: Precheck_0_3 - Check DR Protection Group Params + debug: + msg: "{{checked_dr_pg_params}}" + failed_when: checked_dr_pg_params.values()|unique != [True] + when: Protection_Level|int >= 2 + + - name: Set variables + set_fact: + primaryDeviceSn: "{{ Storage|string if (Storage is not none) else none }}" + primaryPgName: "{{ Protection_Group }}" + sessionName: "{{ Session_Name }}" + metroEnable: "{{ Enable_HyperMetro }}" + protectLevel: "{{ Protection_Level }}" + metroDeviceSn: "{{ Metro_Storage|string }}" + drDeviceSn: "{{ DR_Storage|string }}" + drPgName: "{{ DR_Protection_Group }}" + + + - set_fact: + Precheck_0_Execute: True + Precheck_1_Execute: True + Precheck_2_Execute: "{{ (metroEnable == 'Y') }}" + Precheck_3_Execute: "{{ (protectLevel|int >= 2) }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" + + - block: + - name: Precheck_1 - Check Primary Protection Group + debug: + msg: + pg: "{{ primaryPgName }}" + device: "{{ primaryDeviceSn }}" + + - name: Login Device + set_fact: + deviceSn: "{{ primaryDeviceSn }}" + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + primaryDeviceName: "{{ deviceName }}" + primaryDeviceHost: "{{ deviceHost }}" + primaryDevicePort: "{{ devicePort }}" + primaryDeviceToken: "{{ deviceToken }}" + primaryDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: ["{{ primaryPgName }}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + + - set_fact: + primaryPg: "{{ checkedPg }}" + primaryRepCgNum: "{{ checkedPg['replicationGroupNum']|int }}" + primaryMetroCgNum: "{{ checkedPg['hyperMetroGroupNum']|int }}" + + - name: Check No Snapshot CG + vars: + snapCgNum: "{{ checkedPg['snapshotGroupNum']|int }}" + fail: + msg: "Snapshot CG exists: {{ checkedPg['snapshotGroupName'] }}" + when: snapCgNum|int > 0 + + - name: Check No Clone CG + vars: + cloneCgNum: "{{ checkedPg['cloneGroupNum']|int }}" + fail: + msg: "Clone CG exists: {{ checkedPg['cloneGroupName'] }}" + when: cloneCgNum|int > 0 + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_replication_cgs.yml" + vars: + cgNames: "{{ checkedPg['replicationGroupName'].split(',') }}" + when: primaryRepCgNum|int > 0 + + - set_fact: + primaryRepCgs: "{{ checkedRepCgs }}" + when: primaryRepCgNum|int > 0 + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hypermetro_cgs.yml" + vars: + cgNames: ["{{ checkedPg['hyperMetroName'] }}"] + when: primaryMetroCgNum|int == 1 + + - set_fact: + primaryMetroCg: "{{ checkedMetroCgs[0] }}" + when: primaryMetroCgNum|int == 1 + + # End Precheck_1 + + when: Precheck_1_Execute + + - block: + - name: Precheck_2 - Check Metro Protection Group + debug: + msg: + pg: "{{ primaryPgName }}" + device: "{{ metroDeviceSn }}" + + - name: Login Metro Device + set_fact: + deviceSn: "{{ metroDeviceSn }}" + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + metroDeviceName: "{{ deviceName }}" + metroDeviceHost: "{{ deviceHost }}" + metroDevicePort: "{{ devicePort }}" + metroDeviceToken: "{{ deviceToken }}" + metroDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: ["{{ primaryPgName }}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + + - set_fact: + metroPg: "{{ checkedPg }}" + metroRepCgNum: "{{ checkedPg['replicationGroupNum']|int }}" + + - name: Check No Snapshot CG + vars: + snapCgNum: "{{ checkedPg['snapshotGroupNum']|int }}" + fail: + msg: "Snapshot CG exists: {{ checkedPg['snapshotGroupName'] }}" + when: snapCgNum|int > 0 + + - name: Check No Clone CG + vars: + cloneCgNum: "{{ checkedPg['cloneGroupNum']|int }}" + fail: + msg: "Clone CG exists: {{ checkedPg['cloneGroupName'] }}" + when: cloneCgNum|int > 0 + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_replication_cgs.yml" + vars: + cgNames: "{{ checkedPg['replicationGroupName'].split(',') }}" + when: metroRepCgNum|int > 0 + + - set_fact: + standbyRepCgs: "{{ checkedRepCgs }}" + when: metroRepCgNum|int > 0 + + # End Precheck_2 + when: Precheck_2_Execute + + - block: + - name: Precheck_3 - Check DR Protection Group + debug: + msg: + pg: "{{ drPgName }}" + device: "{{ drDeviceSn }}" + + - name: Login Device + set_fact: + deviceSn: "{{ drDeviceSn }}" + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - set_fact: + drDeviceName: "{{ deviceName }}" + drDeviceHost: "{{ deviceHost }}" + drDevicePort: "{{ devicePort }}" + drDeviceToken: "{{ deviceToken }}" + drDeviceSession: "{{ deviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: ["{{ drPgName }}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_pg.yml" + vars: + pgName: "{{ drPgName }}" + + - set_fact: + drPg: "{{ checkedPg }}" + drRepCgNum: "{{ checkedPg['replicationGroupNum']|int }}" + + - name: Check No Snapshot CG + vars: + snapCgNum: "{{ checkedPg['snapshotGroupNum']|int }}" + fail: + msg: "Snapshot CG exists: {{ checkedPg['snapshotGroupName'] }}" + when: snapCgNum|int > 0 + + - name: Check No Clone CG + vars: + cloneCgNum: "{{ checkedPg['cloneGroupNum']|int }}" + fail: + msg: "Clone CG exists: {{ checkedPg['cloneGroupName'] }}" + when: cloneCgNum|int > 0 + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_replication_cgs.yml" + vars: + cgNames: "{{ checkedPg['replicationGroupName'].split(',') }}" + when: drRepCgNum|int > 0 + + - set_fact: + drRepCgs: "{{ checkedRepCgs }}" + when: drRepCgNum|int > 0 + + # End Precheck_3 + when: Precheck_3_Execute + + - block: + - set_fact: + + # To bypass the Replication CGs on the other side storage + deletedRepCgNames: [] + rollbackedRepCgNames: [] + + # Delete HyperMetro CG + Step_1_1_Execute: "{{ primaryMetroCgNum|int == 1 }}" + Step_1_1_Completed: False + Step_1_1_Rollbacked: False + + # Delete Replication CG + Step_1_2_Execute: "{{ primaryRepCgNum|int > 0 }}" + Step_1_2_Completed: False + Step_1_2_Rollbacked: False + + # Delete LUN Group + Step_1_3_Execute: True + Step_1_3_Completed: False + Step_1_3_Rollbacked: False + + # Delete Standby Replication CG + Step_2_1_Execute: "{{ (metroEnable == 'Y') and (metroRepCgNum|int > 0) }}" + Step_2_1_Completed: False + Step_2_1_Rollbacked: False + + # Delete Metro LUN Group + Step_2_2_Execute: "{{ (metroEnable == 'Y') }}" + Step_2_2_Completed: False + Step_2_2_Rollbacked: False + + # Delete DR Replication CG + Step_3_1_Execute: "{{ (protectLevel|int >= 2) and (drRepCgNum|int > 0) }}" + Step_3_1_Completed: False + Step_3_1_Rollbacked: False + + # Delete DR LUN Group + Step_3_2_Execute: "{{ (protectLevel|int >= 2) }}" + Step_3_2_Completed: False + Step_3_2_Rollbacked: False + + - name: Workflow - Delete Protection Group + debug: + msg: + Step_1_1: "[{{Step_1_1_Execute}}] Delete HyperMetro CG" + Step_1_2: "[{{Step_1_2_Execute}}] Delete Replication CG" + Step_1_3: "[{{Step_1_3_Execute}}] Delete Protection Group" + + Step_2_1: "[{{Step_2_1_Execute}}] Delete Standby Replication CG" + Step_2_2: "[{{Step_2_2_Execute}}] Delete Metro Protection Group" + + Step_3_1: "[{{Step_3_1_Execute}}] Delete DR Replication CG" + Step_3_2: "[{{Step_3_2_Execute}}] Delete DR Protection Group" + + - block: + - name: Step_1_1 - Delete HyperMetro CG + debug: + msg: + params: + pg: "{{ primaryPgName }}" + cgName: "{{ primaryMetroCg.NAME }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_hypermetro_cg.yml" + vars: + cgName: "{{ primaryMetroCg.NAME }}" + deletePairs: False + + - set_fact: + primaryRemovedMPairIds: "{{ removedPairIds }}" + Step_1_1_Completed: True + when: Step_1_1_Execute + + - block: + - name: Step_1_2 - Delete Replication CGs + debug: + msg: + params: + pg: "{{ primaryPgName }}" + cgNames: "{{ primaryRepCgs | json_query('[*].NAME') }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + rcgNames: "{{ primaryRepCgs | json_query('[*].NAME') | difference(deletedRepCgNames) }}" + outPairIds: {} + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_replication_cg.yml" + vars: + deletePairs: False + loop: "{{ rcgNames }}" + loop_control: + loop_var: cgName + when: + - rcgNames|length > 0 + + - set_fact: + deletedRepCgNames: "{{ deletedRepCgNames + rcgNames }}" + primaryRemovedRPairIds: "{{ outPairIds }}" + Step_1_2_Completed: True + when: Step_1_2_Execute + + - block: + - name: Step_1_3 - Delete Protection Group + debug: + msg: + params: + pgName: "{{ primaryPgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + unmap: True + + - set_fact: + primaryRemovedLunIds: "{{ removedLunIds }}" + primaryDeletedPg: "{{ deletedPg }}" + Step_1_3_Completed: True + when: Step_1_3_Execute + + + - block: + - name: Step_2_1 - Delete Standby Replication CGs + debug: + msg: + params: + pg: "{{ primaryPgName }}" + cgNames: "{{ standbyRepCgs | json_query('[*].NAME') }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + rcgNames: "{{ standbyRepCgs | json_query('[*].NAME') | difference(deletedRepCgNames) }}" + outPairIds: {} + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_replication_cg.yml" + vars: + deletePairs: False + loop: "{{ rcgNames }}" + loop_control: + loop_var: cgName + when: + - rcgNames|length > 0 + + - set_fact: + deletedRepCgNames: "{{ deletedRepCgNames + rcgNames }}" + metroRemovedRPairIds: "{{ outPairIds }}" + Step_2_1_Completed: True + when: Step_2_1_Execute + + - block: + - name: Step_2_2 - Delete Metro Protection Group + debug: + msg: + params: + pgName: "{{ primaryPgName }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_pg.yml" + vars: + pgName: "{{ primaryPgName }}" + + - set_fact: + metroRemovedLunIds: "{{ removedLunIds }}" + metroDeletedPg: "{{ deletedPg }}" + Step_2_2_Completed: True + when: Step_2_2_Execute + + - block: + - name: Step_3_1 - Delete DR Replication CGs + debug: + msg: + params: + pg: "{{ drPgName }}" + cgNames: "{{ drRepCgs | json_query('[*].NAME') }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + rcgNames: "{{ drRepCgs | json_query('[*].NAME') | difference(deletedRepCgNames) }}" + outPairIds: {} + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_replication_cg.yml" + vars: + deletePairs: False + loop: "{{ rcgNames }}" + loop_control: + loop_var: cgName + when: + - rcgNames|length > 0 + + - set_fact: + deletedRepCgNames: "{{ deletedRepCgNames + rcgNames }}" + drRemovedRPairIds: "{{ outPairIds }}" + Step_3_1_Completed: True + when: Step_3_1_Execute + + - block: + - name: Step_3_2 - Delete DR Protection Group + debug: + msg: + params: + pgName: "{{ drPgName }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_pg.yml" + vars: + pgName: "{{ drPgName }}" + + - set_fact: + drRemovedLunIds: "{{ removedLunIds }}" + drDeletedPg: "{{ deletedPg }}" + Step_3_2_Completed: True + when: Step_3_2_Execute + + # End Steps + rescue: + # Begin Rollback + + - block: + - name: Rollback_3_2 - Create DR Protection Group + debug: + msg: + params: + addLunIds: "{{ drRemovedLunIds }}" + pgName: "{{ drDeletedPg.protectGroupName }}" + desc: "{{ drDeletedPg.description }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_pg.yml" + vars: + addLunIds: "{{ drRemovedLunIds }}" + pgName: "{{ drDeletedPg.protectGroupName }}" + desc: "{{ drDeletedPg.description }}" + + - set_fact: + Step_3_2_Rollbacked: True + when: Step_3_2_Completed + + - block: + - name: Rollback_2_2 - Create Metro Protection Group + debug: + msg: + params: + addLunIds: "{{ metroRemovedLunIds }}" + pgName: "{{ metroDeletedPg.protectGroupName }}" + desc: "{{ metroDeletedPg.description }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_pg.yml" + vars: + addLunIds: "{{ metroRemovedLunIds }}" + pgName: "{{ metroDeletedPg.protectGroupName }}" + desc: "{{ metroDeletedPg.description }}" + + - set_fact: + Step_2_2_Rollbacked: True + when: Step_2_2_Completed + + - block: + - name: Rollback_1_3 - Create Protection Group + debug: + msg: + params: + addLunIds: "{{ primaryRemovedLunIds }}" + pgName: "{{ primaryDeletedPg.protectGroupName }}" + desc: "{{ primaryDeletedPg.description }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_pg.yml" + vars: + addLunIds: "{{ primaryRemovedLunIds }}" + pgName: "{{ primaryDeletedPg.protectGroupName }}" + desc: "{{ primaryDeletedPg.description }}" + + - set_fact: + Step_1_3_Rollbacked: True + when: Step_1_3_Completed + + - block: + - name: Rollback_1_2 - Create Replication CGs + debug: + msg: + params: + pg: "{{ primaryPgName }}" + cgNames: "{{ primaryRepCgs | json_query('[*].NAME') }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + rcgNames: "{{ primaryRepCgs | json_query('[*].NAME') | difference(rollbackedRepCgNames) }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_replication_cg.yml" + vars: + query: "[? NAME=='{{cgName}}']" + cg: "{{ primaryRepCgs | json_query(query) | first }}" + remoteDevId: "{{ cg.remoteArrayID }}" + mode: "{{ cg.REPLICATIONMODEL }}" + localPgId: "{{ cg.localpgId }}" + remotePgId: "{{ cg.rmtpgId }}" + addPairIds: "{{ primaryRemovedRPairIds[cgName] }}" + loop: "{{ rcgNames }}" + loop_control: + loop_var: cgName + when: + - rcgNames|length > 0 + + - set_fact: + rollbackedRepCgNames: "{{ rollbackedRepCgNames + rcgNames }}" + Step_1_2_Rollbacked: True + when: Step_1_2_Completed + + - block: + - name: Rollback_1_1 - Create HyperMetro CG + debug: + msg: + params: + pg: "{{ primaryPgName }}" + cgName: "{{ primaryMetroCg.NAME }}" + localPgName: "{{ primaryMetroCg.localPgName }}" + rmtPgName: "{{ primaryMetroCg.rmtPgName }}" + device: "{{ primaryDeviceName }}" + + - set_fact: + deviceHost: "{{ primaryDeviceHost }}" + devicePort: "{{ primaryDevicePort }}" + deviceSn: "{{ primaryDeviceSn }}" + deviceToken: "{{ primaryDeviceToken }}" + deviceSession: "{{ primaryDeviceSession }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_hypermetro_cg.yml" + vars: + cgName: "{{ primaryMetroCg.NAME }}" + remoteSn: "{{ primaryMetroDeviceSn }}" + localPgId: "{{ primaryMetroCg.localPgId }}" + remotePgId: "{{ primaryMetroCg.remotePgId }}" + + - set_fact: + Step_1_1_Rollbacked: True + when: Step_1_1_Completed + + - block: + - name: Rollback_2_1 - Create Standby Replication CGs + debug: + msg: + params: + pg: "{{ primaryPgName }}" + cgNames: "{{ standbyRepCgs | json_query('[*].NAME') }}" + device: "{{ metroDeviceName }}" + + - set_fact: + deviceHost: "{{ metroDeviceHost }}" + devicePort: "{{ metroDevicePort }}" + deviceSn: "{{ metroDeviceSn }}" + deviceToken: "{{ metroDeviceToken }}" + deviceSession: "{{ metroDeviceSession }}" + rcgNames: "{{ standbyRepCgs | json_query('[*].NAME') | difference(rollbackedRepCgNames) }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_replication_cg.yml" + vars: + query: "[? NAME=='{{cgName}}']" + cg: "{{ standbyRepCgs | json_query(query) | first }}" + remoteDevId: "{{ cg.remoteArrayID }}" + mode: "{{ cg.REPLICATIONMODEL }}" + localPgId: "{{ cg.localpgId }}" + remotePgId: "{{ cg.rmtpgId }}" + addPairIds: "{{ metroRemovedRPairIds[cgName] }}" + loop: "{{ rcgNames }}" + loop_control: + loop_var: cgName + when: + - rcgNames|length > 0 + + - set_fact: + rollbackedRepCgNames: "{{ rollbackedRepCgNames + rcgNames }}" + Step_2_1_Rollbacked: True + when: Step_2_1_Completed + + - block: + - name: Rollback_3_1 - Create DR Replication CGs + debug: + msg: + params: + pg: "{{ drPgName }}" + cgNames: "{{ drRepCgs | json_query('[*].NAME') }}" + device: "{{ drDeviceName }}" + + - set_fact: + deviceHost: "{{ drDeviceHost }}" + devicePort: "{{ drDevicePort }}" + deviceSn: "{{ drDeviceSn }}" + deviceToken: "{{ drDeviceToken }}" + deviceSession: "{{ drDeviceSession }}" + rcgNames: "{{ drRepCgs | json_query('[*].NAME') | difference(rollbackedRepCgNames) }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_replication_cg.yml" + vars: + query: "[? NAME=='{{cgName}}']" + cg: "{{ drRepCgs | json_query(query) | first }}" + remoteDevId: "{{ cg.remoteArrayID }}" + mode: "{{ cg.REPLICATIONMODEL }}" + localPgId: "{{ cg.localpgId }}" + remotePgId: "{{ cg.rmtpgId }}" + addPairIds: "{{ drRemovedRPairIds[cgName] }}" + loop: "{{ rcgNames }}" + loop_control: + loop_var: cgName + when: + - rcgNames|length > 0 + + - set_fact: + rollbackedRepCgNames: "{{ rollbackedRepCgNames + rcgNames }}" + Step_3_1_Rollbacked: True + when: Step_3_1_Completed + + # End Rollbacks + always: + + - name: Final_Step_1 - Sync Devices + set_fact: + deviceSynced: [] + primaryDeviceNeedSync: "{{ (Step_1_3_Completed|bool == True and Step_1_3_Rollbacked|bool == False) }}" + metroDeviceNeedSync: "{{ (Step_2_2_Completed|bool == True and Step_2_2_Rollbacked|bool == False) }}" + drDeviceNeedSync: "{{ (Step_3_2_Completed|bool == True and Step_3_2_Rollbacked|bool == False) }}" + + - name: Final_Step_1_1 - Sync Primary Device + debug: + msg: + device: "{{ primaryDeviceName }}" + when: primaryDeviceNeedSync + + - set_fact: + deviceName: "{{ primaryDeviceName }}" + when: primaryDeviceNeedSync + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + when: primaryDeviceNeedSync + + - set_fact: + deviceSynced: "{{ deviceSynced + [primaryDeviceName] }}" + when: primaryDeviceNeedSync + + - name: Final_Step_1_2 - Sync Metro Device + debug: + msg: + device: "{{ metroDeviceName }}" + when: + - metroDeviceNeedSync + + - set_fact: + deviceName: "{{ metroDeviceName }}" + when: + - metroDeviceNeedSync + - metroDeviceName not in deviceSynced + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + when: + - metroDeviceNeedSync + - metroDeviceName not in deviceSynced + + - set_fact: + deviceSynced: "{{ deviceSynced + [metroDeviceName] }}" + when: + - metroDeviceNeedSync + - metroDeviceName not in deviceSynced + + - name: Final_Step_1_3 - Sync DR Device + debug: + msg: + device: "{{ drDeviceName }}" + when: + - drDeviceNeedSync + + - set_fact: + deviceName: "{{ drDeviceName }}" + when: + - drDeviceNeedSync + - drDeviceName not in deviceSynced + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + when: + - drDeviceNeedSync + - drDeviceName not in deviceSynced + + - set_fact: + deviceSynced: "{{ deviceSynced + [drDeviceName] }}" + when: + - drDeviceNeedSync + - drDeviceName not in deviceSynced + + # End Final Steps + + # End Workflow + + # Begin Validate Results + + - block: + + - name: Result_1_1 - Delete HyperMetro CG + debug: + msg: + params: + pg: "{{ primaryPgName }}" + cgName: "{{ primaryMetroCg.NAME }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_1_1_Completed }}" + rollbacked: "{{ Step_1_1_Rollbacked }}" + failed_when: Step_1_1_Completed|bool == False + when: Step_1_1_Execute + + - name: Result_1_2 - Delete Replication CGs + debug: + msg: + params: + pg: "{{ primaryPgName }}" + cgNames: "{{ primaryRepCgs | json_query('[*].NAME') }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_1_2_Completed }}" + rollbacked: "{{ Step_1_2_Rollbacked }}" + failed_when: Step_1_2_Completed|bool == False + when: Step_1_2_Execute + + - name: Result_1_3 - Delete Protection Group + debug: + msg: + params: + pg: "{{ primaryPgName }}" + device: "{{ primaryDeviceName }}" + result: + succeeded: "{{ Step_1_3_Completed }}" + rollbacked: "{{ Step_1_3_Rollbacked }}" + failed_when: Step_1_3_Completed|bool == False + when: Step_1_3_Execute + + - name: Result_2_1 - Delete Standby Replication CGs + debug: + msg: + params: + pg: "{{ primaryPgName }}" + cgNames: "{{ standbyRepCgs | json_query('[*].NAME') }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_2_1_Completed }}" + rollbacked: "{{ Step_2_1_Rollbacked }}" + failed_when: Step_2_1_Completed|bool == False + when: Step_2_1_Execute + + - name: Result_2_2 - Delete Metro Protection Group + debug: + msg: + params: + pg: "{{ primaryPgName }}" + device: "{{ metroDeviceName }}" + result: + succeeded: "{{ Step_2_2_Completed }}" + rollbacked: "{{ Step_2_2_Rollbacked }}" + failed_when: Step_2_2_Completed|bool == False + when: Step_2_2_Execute + + - name: Result_3_1 - Delete DR Replication CGs + debug: + msg: + params: + pg: "{{ drPgName }}" + cgNames: "{{ drRepCgs | json_query('[*].NAME') }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_3_1_Completed }}" + rollbacked: "{{ Step_3_1_Rollbacked }}" + failed_when: Step_3_1_Completed|bool == False + when: Step_3_1_Execute + + - name: Result_3_2 - Delete DR Protection Group + debug: + msg: + params: + pg: "{{ drPgName }}" + device: "{{ drDeviceName }}" + result: + succeeded: "{{ Step_3_2_Completed }}" + rollbacked: "{{ Step_3_2_Rollbacked }}" + failed_when: Step_3_2_Completed|bool == False + when: Step_3_2_Execute + + - name: Synced Device + debug: + msg: + synced: "{{ deviceSynced }}" + + # End Validates + + # End Tasks + +# End Playbook \ No newline at end of file diff --git a/rundeck/workflow/project001/53_dr_test_for_pg.yml b/rundeck/workflow/project001/53_dr_test_for_pg.yml new file mode 100644 index 0000000..0ec8fa4 --- /dev/null +++ b/rundeck/workflow/project001/53_dr_test_for_pg.yml @@ -0,0 +1,456 @@ +- name: DR Test for Protection Group + hosts: localhost + vars_files: + - ../../../config/global.yml + - ../../../config/project001.yml + gather_facts: no + become: no + tasks: + + # Check Params + - block: + - set_fact: + checked_params: + DR_Storage: "{{ (DR_Storage is not none and DR_Storage != DEFAULT.noneValue) and (DR_Storage|string|length == 20) }}" + DR_Protection_Group: "{{ DR_Protection_Group is not none and DR_Protection_Group != DEFAULT.noneValue }}" + DR_CG: "{{ DR_CG is not none and DR_CG != DEFAULT.noneValue }}" + DR_Test_CG: "{{ DR_Test_CG is not none and DR_Test_CG != DEFAULT.noneValue }}" + DR_Test_CG_Status: "{{ DR_Test_CG_Status is not none and DR_Test_CG_Status != DEFAULT.noneValue and DR_Test_CG_Status != SNAPCG.activated.enum }}" + Check_Result_1: "{{ 'snap' in Check_Result_1 }}" + + - name: Precheck_0_1 - Check Params + debug: + msg: "{{checked_params}}" + failed_when: checked_params.values()|unique != [True] + + - set_fact: + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + osType: "{{ OS_Type }}" + deviceSn: "{{ DR_Storage|string }}" + room: "{{ DR_Storage_Room }}" + site: "{{ AZ[DR_Storage_Room]['dc'] }}" + drPgName: "{{ DR_Protection_Group }}" + drCgName: "{{ DR_CG }}" + drTestCgId: "{{ DR_Test_CG_ID }}" + drTestCgName: "{{ DR_Test_CG }}" + class3: "{{ Designate_Class_3 }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - name: Precheck_1 - Check Protection Group, Hosts, WWNs + debug: + msg: + pgName: "{{ drPgName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: ["{{drPgName}}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_luns_by_pg.yml" + vars: + pgName: "{{drPgName}}" + + - set_fact: + drLunNames: "{{ checkedLuns | json_query('[*].NAME') }}" + drHostNames: [] + drTestHostNames: [] + + - set_fact: + drHostNames: "{{ drHostNames + [item[:-6]] }}" + drTestHostNames: "{{ drTestHostNames + [item[:-7] + '3'] }}" + with_items: "{{ drLunNames }}" + + - set_fact: + drHostNames: "{{ drHostNames | unique }}" + drTestHostNames: "{{ drTestHostNames | unique}}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_wwns.yml" + vars: + hostNames: "{{ drTestHostNames }}" + checkExist: False + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_wwns.yml" + vars: + hostNames: "{{ drHostNames }}" + + - set_fact: + drHostWwns: "{{ checkedWwns }}" + drTestHostWwns: {} + + - set_fact: + drTestHostWwns: "{{ drTestHostWwns | combine( { item.0: drHostWwns[item.1] } ) }}" + with_together: + - "{{ drTestHostNames }}" + - "{{ drHostNames }}" + + - name: Query Snapshots in CG + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/snapshot/associate?ASSOCIATEOBJTYPE=57646&ASSOCIATEOBJID={{drTestCgId}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: SNAPSHOTS + + - set_fact: + drTestLunNames: "{{ SNAPSHOTS.json.data | default([]) | json_query('[*].NAME') }}" + drTestLunIds: "{{ SNAPSHOTS.json.data | default([]) | json_query('[*].ID') }}" + drTestLunSectors: "{{ SNAPSHOTS.json.data | default([]) | json_query('[*].USERCAPACITY') }}" + + - debug: + msg: + drHostWwns: "{{ drHostWwns }}" + drCgName: "{{ drCgName }}" + drTestLunNames: "{{ drTestLunNames }}" + drTestCgName: "{{ drTestCgName }}" + failed_when: drHostWwns|length == 0 or drTestLunNames|length == 0 + + - block: + + # Begin Workflow Steps + + - set_fact: + + # Activate DR Test Snapshot CG + Step_1_1_Execute: True + Step_1_1_Completed: False + Step_1_1_Rollbacked: False + + # Set Class for DR Test LUNs + Step_1_2_Execute: True + Step_1_2_Completed: False + Step_1_2_Rollbacked: False + + # Remove WWNs from DR Hosts + Step_2_1_Execute: True + Step_2_1_Completed: False + Step_2_1_Rollbacked: False + + # Add WWNs to DR Test Hosts + Step_2_2_Execute: True + Step_2_2_Completed: False + Step_2_2_Rollbacked: False + + # Update DR Test LUNs to KPI table + Step_3_1_Execute: True + Step_3_1_Completed: False + + - name: Workflow - DR Test for Host + debug: + msg: + Step_1_1: "[{{Step_1_1_Execute}}] Activate DR Test Snapshot CG" + Step_1_2: "[{{Step_1_2_Execute}}] Set Class for DR Test LUNs" + Step_2_1: "[{{Step_2_1_Execute}}] Remove WWNs from DR Hosts" + Step_2_2: "[{{Step_2_2_Execute}}] Add WWNs to DR Test Hosts" + Step_3_1: "[{{Step_3_1_Execute}}] Update DR Test LUNs to KPI table" + + - block: + - name: Step_1_1 - Activate DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/activate_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + + - set_fact: + Step_1_1_Completed: True + + # End Step_1_1 + + # End block + when: Step_1_1_Execute + + - block: + - name: Step_1_2 - Set Class for DR Test LUNs + debug: + msg: + params: + volumeNames: "{{ drTestLunNames }}" + tierName: "{{ class3 }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: "{{ drTestLunNames }}" + tierName: "{{ class3 }}" + + - set_fact: + Step_1_2_Completed: True + + # End Step_1_2 + + # End block + when: Step_1_2_Execute + + - block: + - name: Step_2_1 - Remove WWNs from DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ host2wwn.value }}" + hostName: "{{ host2wwn.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: host2wwn + + + - set_fact: + Step_2_1_Completed: True + + # End Step_2_1 + + # End block + when: Step_2_1_Execute + + - block: + - name: Step_2_2 - Add WWNs to DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ host2wwn.value }}" + hostName: "{{ host2wwn.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: host2wwn + + - set_fact: + Step_2_2_Completed: True + + # End Step_2_2 + + # End block + when: Step_2_2_Execute + + - block: + - name: Step_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + class: "{{ class3 }}" + + # Minus Non-Class capacity + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ drPgName }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "-{{ (drTestLunSectors[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drTestLunIds }}" + + # Add capacity to Class + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ drPgName }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "{{ class3 }}" + CAPACITY_GB: "{{ (drTestLunSectors[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drTestLunIds }}" + + - set_fact: + Step_3_1_Completed: True + + # End Step_3_1 + + # End block + when: Step_3_1_Execute + + # End Steps + rescue: + + - block: + - name: Rollback_2_2 - Remove WWNs from DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ host2wwn.value }}" + hostName: "{{ host2wwn.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: host2wwn + + - set_fact: + Step_2_2_Rollbacked: True + + # End Rollback_2_2 + + # End block + when: Step_2_2_Completed + + - block: + - name: Rollback_2_1 - Add WWNs to DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ host2wwn.value }}" + hostName: "{{ host2wwn.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: host2wwn + + - set_fact: + Step_2_1_Rollbacked: True + + # End Rollback_2_1 + + # End block + when: Step_2_1_Completed + + - block: + - name: Rollback_1_2 - Remove Class for DR Test LUNs + debug: + msg: + params: + volumeNames: "{{ drTestLunNames }}" + tierName: "{{ class3 }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ drTestLunNames }}" + tierName: "{{ class3 }}" + + - set_fact: + Step_1_2_Rollbacked: True + + # End Rollback_1_2 + + # End block + when: Step_1_2_Completed + + - block: + - name: Rollback_1_1 - Deactivate DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/deactivate_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + + - set_fact: + Step_1_1_Rollbacked: True + + # End Rollback_1_1 + + # End block + when: Step_1_1_Completed + + # End Rollbacks + always: + + - name: Final_Step_1 - Sync DR Device + debug: + msg: + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + + # End Final Steps + + # End Workflow + + + - block: + + # Begin Validate Results + + - name: Result_1_1 - Activate DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_1_1_Completed }}" + rollbacked: "{{ Step_1_1_Rollbacked }}" + failed_when: Step_1_1_Completed|bool == False + when: Step_1_1_Execute + + - name: Result_1_2 - Set Class for DR Test LUNs + debug: + msg: + params: + volumeNames: "{{ drTestLunNames }}" + tierName: "{{ class3 }}" + result: + succeeded: "{{ Step_1_2_Completed }}" + rollbacked: "{{ Step_1_2_Rollbacked }}" + failed_when: Step_1_2_Completed|bool == False + when: Step_1_2_Execute + + - name: Result_2_1 - Remove WWNs from DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_1_Completed }}" + rollbacked: "{{ Step_2_1_Rollbacked }}" + failed_when: Step_2_1_Completed|bool == False + when: Step_2_1_Execute + + - name: Result_2_2 - Add WWNs to DR Test Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_2_Completed }}" + rollbacked: "{{ Step_2_2_Rollbacked }}" + failed_when: Step_2_2_Completed|bool == False + when: Step_2_2_Execute + + - name: Result_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + class: "{{ class3 }}" + result: + succeeded: "{{ Step_3_1_Completed }}" + failed_when: Step_3_1_Completed|bool == False + when: Step_3_1_Execute \ No newline at end of file diff --git a/rundeck/workflow/project001/54_dr_test_clean_for_pg.yml b/rundeck/workflow/project001/54_dr_test_clean_for_pg.yml new file mode 100644 index 0000000..913c60d --- /dev/null +++ b/rundeck/workflow/project001/54_dr_test_clean_for_pg.yml @@ -0,0 +1,737 @@ +- name: DR Test Clean for Hosts + hosts: localhost + vars_files: + - ../../../config/global.yml + - ../../../config/project001.yml + gather_facts: no + become: no + tasks: + + # Check Params + - block: + - set_fact: + checked_params: + DR_Storage: "{{ (DR_Storage is not none and DR_Storage != DEFAULT.noneValue) and (DR_Storage|string|length == 20) }}" + DR_Protection_Group: "{{ DR_Protection_Group is not none and DR_Protection_Group != DEFAULT.noneValue }}" + DR_CG: "{{ DR_CG is not none and DR_CG != DEFAULT.noneValue }}" + DR_Test_CG: "{{ DR_Test_CG is not none and DR_Test_CG != DEFAULT.noneValue }}" + DR_Test_CG_Status: "{{ DR_Test_CG_Status is not none and DR_Test_CG_Status != DEFAULT.noneValue and DR_Test_CG_Status == SNAPCG.activated.enum }}" + Check_Result_1: "{{ 'snap' in Check_Result_1 }}" + + - name: Precheck_0_1 - Check Params + debug: + msg: "{{checked_params}}" + failed_when: checked_params.values()|unique != [True] + + - set_fact: + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + osType: "{{ OS_Type }}" + deviceSn: "{{ DR_Storage|string }}" + room: "{{ DR_Storage_Room }}" + site: "{{ AZ[DR_Storage_Room]['dc'] }}" + drPgName: "{{ DR_Protection_Group }}" + drCgName: "{{ DR_CG }}" + drTestCgName: "{{ DR_Test_CG }}" + drTestCgId: "{{ DR_Test_CG_ID }}" + class3: "{{ Designate_Class_3 }}" + + - set_fact: + protectType: "{{ REPTYPE['N3']['enum'] }}" # See ../../config/project001.yml + replicaType: "{{ REPTYPE['N3']['type'] }}" # See ../../config/project001.yml + + - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - name: Precheck_1 - Check Protection Group,Hosts, WWNs + debug: + msg: + pgName: "{{ drPgName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: ["{{drPgName}}"] + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_luns_by_pg.yml" + vars: + pgName: "{{drPgName}}" + + - set_fact: + drLunNames: "{{ checkedLuns | json_query('[*].NAME') }}" + drHostNames: [] + drTestHostNames: [] + drTestLg00Names: [] + + - set_fact: + drHostNames: "{{ drHostNames + [item[:-6]] }}" + drTestHostNames: "{{ drTestHostNames + [item[:-7] + '3'] }}" + drTestLg00Names: "{{ drTestLg00Names + [item[:-7] + '3_LG00'] }}" + with_items: "{{ drLunNames }}" + + - set_fact: + drHostNames: "{{ drHostNames | unique }}" + drTestHostNames: "{{ drTestHostNames | unique}}" + drTestLg00Names: "{{ drTestLg00Names | unique}}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_wwns.yml" + vars: + hostNames: "{{ drHostNames }}" + checkExist: False + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_wwns.yml" + vars: + hostNames: "{{ drTestHostNames }}" + + - set_fact: + drTestHostWwns: "{{ checkedWwns }}" + drHostWwns: {} + + - set_fact: + drHostWwns: "{{ drHostWwns | combine( { item.0: drTestHostWwns[item.1] } ) }}" + with_together: + - "{{ drHostNames }}" + - "{{ drTestHostNames }}" + + - name: Query Snapshots in CG + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/snapshot/associate?ASSOCIATEOBJTYPE=57646&ASSOCIATEOBJID={{drTestCgId}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: SNAPSHOTS + + - set_fact: + drTestLunNames: "{{ SNAPSHOTS.json.data | default([]) | json_query('[*].NAME') }}" + drTestLunIds: "{{ SNAPSHOTS.json.data | default([]) | json_query('[*].ID') }}" + drTestLunSectors: "{{ SNAPSHOTS.json.data | default([]) | json_query('[*].USERCAPACITY') }}" + sourceDrLunNames: "{{ SNAPSHOTS.json.data | default([]) | json_query('[*].SOURCELUNNAME') }}" + + - debug: + msg: + drTestHostWwns: "{{ drTestHostWwns }}" + drCgName: "{{ drCgName }}" + drTestLunNames: "{{ drTestLunNames }}" + drTestCgName: "{{ drTestCgName }}" + failed_when: drTestHostWwns|length == 0 or drTestLunNames|length == 0 + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ drTestLunNames }}" + + - set_fact: + drTestLunClass: [] + drTestLunsInTier: {} + tierNames: "{{ checkedVolumes | json_query('[*].service_level_name') | unique }}" + + - set_fact: + drTestLunClass: "{{ drTestLunClass + [ checkedVolumes[item.0].service_level_name ] }}" + with_indexed_items: "{{ drTestLunNames }}" + + - set_fact: + drTestLunsInTier: "{{ drTestLunsInTier | combine( { item: checkedVolumes | json_query(queryVolumesInTier) } ) }}" + vars: + queryVolumesInTier: "[? service_level_name == '{{ item }}' ].id" + with_items: "{{ tierNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ drLunNames }}" + + - set_fact: + existDrLunNames: "{{ checkedLuns | json_query('[*].NAME') }}" + existDrLunDescs: "{{ checkedLuns | json_query('[*].DESCRIPTION') }}" + targetLunNames: [] + targetLunDescs: [] + + - set_fact: + targetLunNames: "{{ targetLunNames + [ item.0[:-7] + '3' + item.0[-6:] ] }}" + targetLunDescs: "{{ targetLunDescs + [ class3 + item.1[1:] ] }}" + with_together: + - "{{ existDrLunNames }}" + - "{{ existDrLunDescs }}" + + - set_fact: + orphanDrLunNames: "{{ sourceDrLunNames | difference(existDrLunNames) }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ orphanDrLunNames }}" + when: orphanDrLunNames|length > 0 + + - set_fact: + orphanDrLunIds: "{{ checkedLuns | json_query('[*].ID') }}" + orphanDrLunSectors: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + when: orphanDrLunNames|length > 0 + + - set_fact: + checkedHostLuns: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id_loop_helper.yml" + vars: + hostName: "{{ name }}" + loop: "{{ drTestHostNames | flatten(levels=1) }}" + loop_control: + loop_var: name + + - set_fact: + drTestHostLunsAll: [] + targetLunLunScsiIds: [] + + - set_fact: + drTestHostLunsAll: "{{ drTestHostLunsAll + checkedHostLuns[item.0][item.1] }}" + with_indexed_items: "{{ drTestLg00Names }}" + + - set_fact: + targetLunLunScsiIds: "{{ targetLunLunScsiIds + drTestHostLunsAll | json_query(queryScsiId) }}" + vars: + queryScsiId: "[? lunName=='{{item}}'].hostLunId" + with_items: "{{ targetLunNames }}" + + - block: + + # Begin Workflow Steps + + - set_fact: + + # Remove WWNs from DR Test Hosts + Step_1_1_Execute: True + Step_1_1_Completed: False + Step_1_1_Rollbacked: False + + # Add WWNs to DR Hosts + Step_1_2_Execute: True + Step_1_2_Completed: False + Step_1_2_Rollbacked: False + + # Remove DR Test LUNs from Class + Step_2_1_Execute: True + Step_2_1_Completed: False + Step_2_1_Rollbacked: False + + # Remove DR Test LUNs from LUN Group + Step_2_2_Execute: True + Step_2_2_Completed: False + Step_2_2_Rollbacked: False + + # Delete DR Test Snapshot CG + Step_2_3_Execute: True + Step_2_3_Completed: False + Step_2_3_Rollbacked: False + + # Re-Create DR Test Snapshot CG + Step_2_4_Execute: "{{ targetLunNames|length > 0 }}" + Step_2_4_Completed: False + Step_2_4_Rollbacked: False + + # Add DR Test LUNs to LUN Group + Step_2_5_Execute: "{{ targetLunNames|length > 0 }}" + Step_2_5_Completed: False + Step_2_5_Rollbacked: False + + # Delete Orphan DR LUNs + Step_2_6_Execute: "{{ orphanDrLunNames|length > 0 }}" + Step_2_6_Completed: False + Step_2_6_Rollbacked: False + + # Update DR Test LUNs to KPI table + Step_3_1_Execute: True + Step_3_1_Completed: False + Step_3_1_Rollbacked: False + + # Update Orphan DR LUNs to KPI table + Step_3_2_Execute: "{{ orphanDrLunNames|length > 0 }}" + Step_3_2_Completed: False + Step_3_2_Rollbacked: False + + - name: Workflow - DR Test Clean for Hosts + debug: + msg: + Step_1_1: "[{{Step_1_1_Execute}}] Remove WWNs from DR Test Hosts" + Step_1_2: "[{{Step_1_2_Execute}}] Add WWNs to DR Hosts" + Step_2_1: "[{{Step_2_1_Execute}}] Remove DR Test LUNs from Class" + Step_2_2: "[{{Step_2_2_Execute}}] Remove DR Test LUNs from LUN Group" + Step_2_3: "[{{Step_2_3_Execute}}] Delete DR Test Snapshot CG" + Step_2_4: "[{{Step_2_4_Execute}}] Re-Create DR Test Snapshot CG" + Step_2_5: "[{{Step_2_5_Execute}}] Add DR Test LUNs to LUN Group" + Step_2_6: "[{{Step_2_6_Execute}}] Delete Orphan DR LUNs" + Step_3_1: "[{{Step_3_1_Execute}}] Update DR Test LUNs to KPI table" + Step_3_2: "[{{Step_3_2_Execute}}] Update Orphan DR LUNs to KPI table" + + - block: + - name: Step_1_1 - Remove WWNs from DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ host2wwn.value }}" + hostName: "{{ host2wwn.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: host2wwn + + - set_fact: + Step_1_1_Completed: True + + # End Step_1_1 + + # End block + when: Step_1_1_Execute + + - block: + - name: Step_1_2 - Add WWNs to DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ host2wwn.value }}" + hostName: "{{ host2wwn.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: host2wwn + + - set_fact: + Step_1_2_Completed: True + + # End Step_1_2 + + # End block + when: Step_1_2_Execute + + - block: + - name: Step_2_1 - Remove DR Test LUNs from Class + debug: + msg: + params: + drTestLunsInTier: "{{ drTestLunsInTier }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeIds: "{{ drTestLunsInTier[tierName] }}" + loop: "{{ tierNames }}" + loop_control: + loop_var: tierName + + - set_fact: + Step_2_1_Completed: True + + # End Step_2_1 + + # End block + when: Step_2_1_Execute + + - block: + - name: Step_2_2 - Remove DR Test LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ drTestLunNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ name[:-5] + 'LG00' }}" + lunNames: [ "{{ name }}" ] + loop: "{{ drTestLunNames | flatten(levels=1) }}" + loop_control: + loop_var: name + + - set_fact: + Step_2_2_Completed: True + + # End Step_2_2 + + # End block + when: Step_2_2_Execute + + - block: + - name: Step_2_3 - Delete DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + + - set_fact: + Step_2_3_Completed: True + + # End Step_2_3 + + # End block + when: Step_2_3_Execute + + - block: + - name: Step_2_4 - Re-Create DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" + vars: + pgName: "{{ drPgName }}" + cgName: "{{ drTestCgName }}" + snapNames: "{{ targetLunNames }}" + activate: False + snapDescs: "{{ targetLunDescs }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ targetLunNames }}" + + - set_fact: + targetLunIds: "{{ checkedLuns | json_query('[*].ID') }}" + targetLunSectors: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + + - set_fact: + Step_2_4_Completed: True + + # End Step_2_4 + + # End block + when: Step_2_4_Execute + + - block: + - name: Step_2_5 - Add DR Test LUNs to LUN Group + debug: + msg: + params: + lunNames: "{{ targetLunNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lgName: "{{ param.0[:-5] + 'LG00' }}" + lunNames: [ "{{ param.0 }}" ] + addLunScsiIds: [ "{{ param.1 }}" ] + loop: "{{ targetLunNames | zip(targetLunLunScsiIds) | list }}" + loop_control: + loop_var: param + + - set_fact: + Step_2_5_Completed: True + + # End Step_2_5 + + # End block + when: Step_2_5_Execute + + - block: + - name: Step_2_6 - Delete Orphan DR LUNs + debug: + msg: + params: + lunNames: "{{ orphanDrLunNames }}" + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_luns.yml" + vars: + lunNames: "{{ orphanDrLunNames }}" + + - set_fact: + Step_2_6_Completed: True + + # End Step_2_6 + + # End block + when: Step_2_6_Execute + + - block: + - name: Step_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + class: "{{ drTestLunClass }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "delete" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ item.1[:-6] }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "{{ drTestLunClass[item.0] }}" + CAPACITY_GB: "-{{ (drTestLunSectors[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drTestLunIds }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "create" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ item.1[:-6] }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "{{ (targetLunSectors[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ targetLunIds }}" + when: targetLunIds|default([])|length > 0 + + - set_fact: + Step_3_1_Completed: True + + # End Step_3_1 + + # End block + when: Step_3_1_Execute + + - block: + - name: Step_3_2 - Update Orphan DR LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ orphanDrLunIds }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "delete" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ drHostName }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "-{{ (orphanDrLunSectors[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ orphanDrLunIds }}" + + - set_fact: + Step_3_2_Completed: True + + # End Step_3_2 + + # End block + when: Step_3_2_Execute + + # End Steps + rescue: + + # Unable to rollback DR Test CGs, need to manually rollback + + - block: + - name: Rollback_2_4 - Reactivated DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/activate_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + + - set_fact: + Step_2_3_Rollbacked: True + Step_2_4_Rollbacked: True + + # End Rollback_2_4 + + # End block + when: Step_2_4_Completed + + - block: + - name: Rollback_1_2 - Remove WWNs from DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ host2wwn.value }}" + hostName: "{{ host2wwn.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: host2wwn + + - set_fact: + Step_1_2_Rollbacked: True + + # End Rollback_1_2 + + # End block + when: Step_1_2_Completed + + - block: + - name: Rollback_1_1 - Add WWNs to DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ host2wwn.value }}" + hostName: "{{ host2wwn.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: host2wwn + + - set_fact: + Step_1_1_Rollbacked: True + + # End Rollback_1_1 + + # End block + when: Step_1_1_Completed + + # End Rollbacks + always: + + - name: Final_Step_1 - Sync DR Device + debug: + msg: + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + + # End Final Steps + + # End Workflow + + + - block: + + # Begin Validate Results + + - name: Result_1_1 - Remove WWNs from DR Test Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_1_1_Completed }}" + rollbacked: "{{ Step_1_1_Rollbacked }}" + failed_when: Step_1_1_Completed|bool == False + when: Step_1_1_Execute + + - name: Result_1_2 - Add WWNs to DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_1_2_Completed }}" + rollbacked: "{{ Step_1_2_Rollbacked }}" + failed_when: Step_1_2_Completed|bool == False + when: Step_1_2_Execute + + - name: Result_2_1 - Remove DR Test LUNs from Class + debug: + msg: + params: + drTestLunsInTier: "{{ drTestLunsInTier }}" + result: + succeeded: "{{ Step_2_1_Completed }}" + rollbacked: "{{ Step_2_1_Rollbacked }}" + failed_when: Step_2_1_Completed|bool == False + when: Step_2_1_Execute + + - name: Result_2_2 - Remove DR Test LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ drTestLunNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_2_Completed }}" + rollbacked: "{{ Step_2_2_Rollbacked }}" + failed_when: Step_2_2_Completed|bool == False + when: Step_2_2_Execute + + - name: Result_2_3 - Delete DR Test Snapshot CG + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_3_Completed }}" + rollbacked: "{{ Step_2_3_Rollbacked }}" + failed_when: Step_2_3_Completed|bool == False + when: Step_2_3_Execute + + - name: Result_2_4 - Re-Create DR Test Snapshots + debug: + msg: + params: + cgName: "{{ drTestCgName }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_4_Completed }}" + rollbacked: "{{ Step_2_4_Rollbacked }}" + failed_when: Step_2_4_Completed|bool == False + when: Step_2_4_Execute + + - name: Result_2_5 - Add DR Test LUNs to LUN Group + debug: + msg: + params: + lunNames: "{{ targetLunNames }}" + addLunScsiIds: "{{ targetLunLunScsiIds }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_5_Completed }}" + rollbacked: "{{ Step_2_5_Rollbacked }}" + failed_when: Step_2_5_Completed|bool == False + when: Step_2_5_Execute + + - name: Result_2_6 - Delete Orphan DR LUNs + debug: + msg: + params: + lunNames: "{{ orphanDrLunNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_6_Completed }}" + rollbacked: "{{ Step_2_6_Rollbacked }}" + failed_when: Step_2_6_Completed|bool == False + when: Step_2_6_Execute + + - name: Result_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + result: + succeeded: "{{ Step_3_1_Completed }}" + failed_when: Step_3_1_Completed|bool == False + when: Step_3_1_Execute + + - name: Result_3_2 - Update Orphan DR LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ orphanDrLunIds }}" + result: + succeeded: "{{ Step_3_2_Completed }}" + failed_when: Step_3_2_Completed|bool == False + when: Step_3_2_Execute + diff --git a/rundeck/workflow/project001/55_dr_test_for_multi_host.yml b/rundeck/workflow/project001/55_dr_test_for_multi_host.yml new file mode 100644 index 0000000..d1446fd --- /dev/null +++ b/rundeck/workflow/project001/55_dr_test_for_multi_host.yml @@ -0,0 +1,544 @@ +- name: DR Test for Hosts + hosts: localhost + vars_files: + - ../../../config/global.yml + - ../../../config/project001.yml + gather_facts: no + become: no + tasks: + + # Check Params + - block: + - set_fact: + checked_params: + Select_DR_Host: "{{ Select_DR_Host is not none and (Select_DR_Host|string).split(',')|length >= 1 }}" + DR_Storage: "{{ (DR_Storage is not none and DR_Storage != DEFAULT.noneValue) and (DR_Storage|string|length == 20) }}" + Check_Result_1: "{{ 'host' in Check_Result_1 }}" + + - name: Precheck_0_1 - Check Params + debug: + msg: "{{checked_params}}" + failed_when: checked_params.values()|unique != [True] + + - set_fact: + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + osType: "{{ OS_Type }}" + deviceSn: "{{ DR_Storage|string }}" + room: "{{ DR_Storage_Room }}" + site: "{{ AZ[DR_Storage_Room]['dc'] }}" + drHostNames: "{{ (Select_DR_Host|string).split(',') }}" + designateClass3: "{{ Designate_Class_3|default(none) }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - name: Precheck_1 - Check Hosts, WWNs + debug: + msg: + drHostNames: "{{ drHostNames }}" + + - set_fact: + drTestHostNames: [] + drLgNames: [] + drPgNames: [] + drTestCgNames: [] + drCgNames: [] + drHostLunsAll: [] + drTestLunClasses: [] + + # check wwns + - set_fact: + drTestHostNames: "{{ drTestHostNames + [ item[:-1] + '3' ] }}" + with_items: "{{ drHostNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_wwns.yml" + vars: + hostNames: "{{ drTestHostNames }}" + checkExist: False + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_wwns.yml" + vars: + hostNames: "{{ drHostNames }}" + + - set_fact: + drHostWwns: "{{ checkedWwns }}" + drTestHostWwns: {} + + - set_fact: + drTestHostWwns: "{{ drTestHostWwns | combine( { item.0: drHostWwns[item.1] } ) }}" + with_together: + - "{{ drTestHostNames }}" + - "{{ drHostNames }}" + + # check host lun + - set_fact: + checkedHostLuns: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id_loop_helper.yml" + vars: + hostName: "{{ drTestHostName }}" + loop: "{{ drTestHostNames }}" + loop_control: + loop_var: drTestHostName + + - set_fact: + combinedHostLuns: {} + - set_fact: + combinedHostLuns: "{{ combinedHostLuns | combine(item) }}" + loop: "{{ checkedHostLuns }}" + + - name: Check Host Luns Number in LGs + debug: + msg: + item: "{{ item }}" + failed_when: item.value|length == 0 + with_dict: "{{ combinedHostLuns }}" + + - set_fact: + drTestLunNames: "{{ combinedHostLuns.values() | flatten(levels=2) | json_query('[*].lunName') }}" + drTestLunIds: "{{ combinedHostLuns.values() | flatten(levels=2) | json_query('[*].lunId') }}" + drTestLgNames: "{{ combinedHostLuns.keys() }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ drTestLunNames }}" + + - set_fact: + drTestLunSectors: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + + - set_fact: + drTestLunClasses: "{{ drTestLunClasses + [ designateClass3 if (designateClass3 != '' ) else item.split('_')[5] ]}}" + with_items: "{{ drTestLunNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ drTestLunNames }}" + + - name: Check Volume in Tier Class + debug: + msg: + lunsInTierClass: "{{ checkedVolumes|json_query(queryLunsInTierClass) }}" + failed_when: checkedVolumes|json_query(queryLunsInTierClass)|length > 0 + vars: + queryLunsInTierClass: "[? service_level_name != '' && service_level_name != null ].name" + + - set_fact: + drPgNames: "{{ drPgNames + [ item[:-6] + '2_PG' + item[-2:] ] }}" + with_items: "{{ drTestLgNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: "{{ drPgNames }}" + + - set_fact: + drPgIds: "{{ pgIds }}" + checkedPgSnapCgs: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_snapshot_cgs_by_pg_loop_helper.yml" + vars: + pgId: "{{ drPgId }}" + loop: "{{ drPgIds }}" + loop_control: + loop_var: drPgId + + - name: Check Snapshot CGs Number and Status + debug: + msg: + SnapshotCgs: "{{ item }}" + status: "{{ item[0].RUNNINGSTATUS }}" + failed_when: (item|length|int != 1) or (item|length|int == 1 and item[0].RUNNINGSTATUS|int != SNAPCG.unactivated.enum) + loop: "{{ checkedPgSnapCgs }}" + + - set_fact: + drTestCgNames: "{{ drTestCgNames + [ item[0].NAME ] }}" + loop: "{{ checkedPgSnapCgs }}" + + - set_fact: + checkedPgRepCgs: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_replication_cgs_by_pg_loop_helper.yml" + vars: + pgId: "{{ drPgId }}" + loop: "{{ drPgIds }}" + loop_control: + loop_var: drPgId + + - debug: + msg: + checkedPgRepCgs: "{{ checkedPgRepCgs }}" + drHostNames: "{{ drPgNames }}" + failed_when: item|length|int != 1 + loop: "{{ checkedPgRepCgs }}" + + - set_fact: + drCgNames: "{{ drCgNames + [ item[0].NAME ] }}" + loop: "{{ checkedPgRepCgs }}" + + - debug: + msg: + drHostWwns: "{{ drHostWwns }}" + drCgNames: "{{ drCgNames }}" + drTestCgNames: "{{ drTestCgNames }}" + drTestHostNames: "{{ drTestHostNames }}" + drTestLunNames: "{{ drTestLunNames }}" + drTestLunClasses: "{{ drTestLunClasses }}" + failed_when: (drHostWwns|length == 0) or (drTestLunNames|length == 0) + + - block: + + # Begin Workflow Steps + + - set_fact: + + # Activate DR Test Snapshot CGs + Step_1_1_Execute: True + Step_1_1_Completed: False + Step_1_1_Rollbacked: False + + # Set Class for DR Test LUNs + Step_1_2_Execute: True + Step_1_2_Completed: False + Step_1_2_Rollbacked: False + + # Remove WWNs from DR Hosts + Step_2_1_Execute: True + Step_2_1_Completed: False + Step_2_1_Rollbacked: False + + # Add WWNs to DR Test Hosts + Step_2_2_Execute: True + Step_2_2_Completed: False + Step_2_2_Rollbacked: False + + # Update DR Test LUNs to KPI table + Step_3_1_Execute: True + Step_3_1_Completed: False + + - name: Workflow - DR Test for Hosts + debug: + msg: + Step_1_1: "[{{Step_1_1_Execute}}] Activate DR Test Snapshot CGs" + Step_1_2: "[{{Step_1_2_Execute}}] Set Class for DR Test LUNs" + Step_2_1: "[{{Step_2_1_Execute}}] Remove WWNs from DR Hosts" + Step_2_2: "[{{Step_2_2_Execute}}] Add WWNs to DR Test Hosts" + Step_3_1: "[{{Step_3_1_Execute}}] Update DR Test LUNs to KPI table" + + - block: + - name: Step_1_1 - Activate DR Test Snapshot CGs + debug: + msg: + params: + cgNames: "{{ drCgNames }}" + cgTestNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/activate_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + loop: "{{ drTestCgNames }}" + loop_control: + loop_var: drTestCgName + + - set_fact: + Step_1_1_Completed: True + + # End Step_1_1 + + # End block + when: Step_1_1_Execute + + - block: + - name: Step_1_2 - Set Class for DR Test LUNs + debug: + msg: + params: + volumeNames: "{{ drTestLunNames }}" + tierNames: "{{ drTestLunClasses }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: ["{{ drTestLun.0 }}"] + tierName: "{{ drTestLun.1 }}" + loop: "{{ drTestLunNames|zip(drTestLunClasses)|list }}" + loop_control: + loop_var: drTestLun + + - set_fact: + Step_1_2_Completed: True + + # End Step_1_2 + + # End block + when: Step_1_2_Execute + + - block: + - name: Step_2_1 - Remove WWNs from DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ drHostWwnsItem.value }}" + hostName: "{{ drHostWwnsItem.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: drHostWwnsItem + + - set_fact: + Step_2_1_Completed: True + + # End Step_2_1 + + # End block + when: Step_2_1_Execute + + - block: + - name: Step_2_2 - Add WWNs to DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ drHostTestWwnsItem.value }}" + hostName: "{{ drHostTestWwnsItem.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: drHostTestWwnsItem + + - set_fact: + Step_2_2_Completed: True + + # End Step_2_2 + + # End block + when: Step_2_2_Execute + + - block: + - name: Step_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + classes: "{{ drTestLunClasses }}" + + # Minus Non-Class capacity + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ '_'.join(drTestLunNames[item.0].split('_')[:4]) }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "-{{ (drTestLunSectors[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drTestLunIds }}" + + # Add capacity to Class + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ '_'.join(drTestLunNames[item.0].split('_')[:4]) }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "{{ drTestLunClasses[item.0] }}" + CAPACITY_GB: "{{ (drTestLunSectors[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drTestLunIds }}" + + - set_fact: + Step_3_1_Completed: True + + # End Step_3_1 + + # End block + when: Step_3_1_Execute + + # End Steps + rescue: + + - block: + - name: Rollback_2_2 - Remove WWNs from DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ drTestHostWwnsItem.value }}" + hostName: "{{ drTestHostWwnsItem.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: drTestHostWwnsItem + + - set_fact: + Step_2_2_Rollbacked: True + + # End Rollback_2_2 + + # End block + when: Step_2_2_Completed + + - block: + - name: Rollback_2_1 - Add WWNs to DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ drHostWwnsItem.value }}" + hostName: "{{ drHostWwnsItem.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: drHostWwnsItem + + - set_fact: + Step_2_1_Rollbacked: True + + # End Rollback_2_1 + + # End block + when: Step_2_1_Completed + + - block: + - name: Rollback_1_2 - Remove Class for DR Test LUNs + debug: + msg: + params: + volumeNames: "{{ drTestLunNames }}" + tierNames: "{{ drTestLunClasses }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ drTestLun.0 }}" + tierName: "{{ drTestLun.1 }}" + loop: "{{ drTestLunNames|zip(drTestLunClasses)|list }}" + loop_control: + loop_var: drTestLun + + - set_fact: + Step_1_2_Rollbacked: True + + # End Rollback_1_2 + + # End block + when: Step_1_2_Completed + + - block: + - name: Rollback_1_1 - Deactivate DR Test Snapshot CGs + debug: + msg: + params: + cgTestNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/deactivate_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + loop: "{{ drTestCgNames }}" + loop_control: + loop_var: drTestCgName + + - set_fact: + Step_1_1_Rollbacked: True + + # End Rollback_1_1 + + # End block + when: Step_1_1_Completed + + # End Rollbacks + always: + + - name: Final_Step_1 - Sync DR Device + debug: + msg: + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + + # End Final Steps + + # End Workflow + + + - block: + + # Begin Validate Results + + - name: Result_1_1 - Activate DR Test Snapshot CGs + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + cgTestNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_1_1_Completed }}" + rollbacked: "{{ Step_1_1_Rollbacked }}" + failed_when: Step_1_1_Completed|bool == False + when: Step_1_1_Execute + + - name: Result_1_2 - Set Class for DR Test LUNs + debug: + msg: + params: + volumeNames: "{{ drTestLunNames }}" + tierName: "{{ drTestLunClasses }}" + result: + succeeded: "{{ Step_1_2_Completed }}" + rollbacked: "{{ Step_1_2_Rollbacked }}" + failed_when: Step_1_2_Completed|bool == False + when: Step_1_2_Execute + + - name: Result_2_1 - Remove WWNs from DR Host + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_1_Completed }}" + rollbacked: "{{ Step_2_1_Rollbacked }}" + failed_when: Step_2_1_Completed|bool == False + when: Step_2_1_Execute + + - name: Result_2_2 - Add WWNs to DR Test Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_2_Completed }}" + rollbacked: "{{ Step_2_2_Rollbacked }}" + failed_when: Step_2_2_Completed|bool == False + when: Step_2_2_Execute + + - name: Result_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + classes: "{{ drTestLunClasses }}" + result: + succeeded: "{{ Step_3_1_Completed }}" + failed_when: Step_3_1_Completed|bool == False + when: Step_3_1_Execute \ No newline at end of file diff --git a/rundeck/workflow/project001/56_dr_test_clean_for_multi_host.yml b/rundeck/workflow/project001/56_dr_test_clean_for_multi_host.yml new file mode 100644 index 0000000..9ffa055 --- /dev/null +++ b/rundeck/workflow/project001/56_dr_test_clean_for_multi_host.yml @@ -0,0 +1,844 @@ +- name: DR Test Clean for Hosts + hosts: localhost + vars_files: + - ../../../config/global.yml + - ../../../config/project001.yml + gather_facts: no + become: no + tasks: + + # Check Params + - block: + - set_fact: + checked_params: + Select_DR_Host: "{{ (Select_DR_Host|string).split(',')|length >= 1 }}" + DR_Storage: "{{ (DR_Storage is not none and DR_Storage != DEFAULT.noneValue) and (DR_Storage|string|length == 20) }}" + Check_Result_1: "{{ 'host' in Check_Result_1 }}" + + - name: Precheck_0_1 - Check Params + debug: + msg: "{{checked_params}}" + failed_when: checked_params.values()|unique != [True] + + - set_fact: + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + osType: "{{ OS_Type }}" + deviceSn: "{{ DR_Storage|string }}" + room: "{{ DR_Storage_Room }}" + site: "{{ AZ[DR_Storage_Room]['dc'] }}" + drHostNames: "{{ (Select_DR_Host|string).split(',') }}" + + - set_fact: + protectType: "{{ REPTYPE['N3']['enum'] }}" # N3 is same as Y3 enum, use one of them to stand all cases, See ../../config/project001.yml + + - set_fact: + lunNameTemplate: "%s_{{protectType}}N%0{{DEFAULT.suffixDigits}}d" + + - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - name: Precheck_1 - Check Hosts, WWNs + debug: + msg: + drHostNames: "{{ drHostNames }}" + + # check wwns + - set_fact: + drTestHostNames: [] + + - set_fact: + drTestHostNames: "{{ drTestHostNames + [ item[:-1] + '3' ] }}" + with_items: "{{ drHostNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_wwns.yml" + vars: + hostNames: "{{ drHostNames }}" + checkExist: False + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_wwns.yml" + vars: + hostNames: "{{ drTestHostNames }}" + + - set_fact: + drTestHostWwns: "{{ checkedWwns }}" + drHostWwns: {} + + - set_fact: + drHostWwns: "{{ drHostWwns | combine( { item.0: drTestHostWwns[item.1] } ) }}" + with_together: + - "{{ drHostNames }}" + - "{{ drTestHostNames }}" + + # check host lun + - set_fact: + checkedHostLuns: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id_loop_helper.yml" + vars: + hostName: "{{ drHostName }}" + loop: "{{ drHostNames }}" + loop_control: + loop_var: drHostName + + - set_fact: + checkedDrHostLuns: {} + + - set_fact: + checkedDrHostLuns: "{{ checkedDrHostLuns | combine(item) }}" + loop: "{{ checkedHostLuns }}" + + - name: Check Host Luns Number in LGs + debug: + msg: + item: "{{ item }}" + failed_when: item.value|length == 0 + with_dict: "{{ checkedDrHostLuns }}" + + - set_fact: + drLgNames: "{{ checkedDrHostLuns.keys() }}" + drTestLgNames: [] + drPgNames: [] + + - set_fact: + drPgNames: "{{ drPgNames + [ item[:-6] + '2_PG' + item[-2:] ] }}" + drTestLgNames: "{{ drTestLgNames + [ item[:-6] + '3_LG' + item[-2:] ] }}" + with_items: "{{ drLgNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: "{{ drPgNames }}" + + - set_fact: + drPgIds: "{{ pgIds }}" + checkedPgSnapCgs: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_snapshot_cgs_by_pg_loop_helper.yml" + vars: + pgId: "{{ drPgId }}" + loop: "{{ drPgIds }}" + loop_control: + loop_var: drPgId + + - name: Check Snapshot CGs Number and Status + debug: + msg: + SnapshotCgs: "{{ item }}" + status: "{{ item[0].RUNNINGSTATUS }}" + failed_when: (item|length|int != 1) or (item|length|int == 1 and item[0].RUNNINGSTATUS|int != SNAPCG.activated.enum) + loop: "{{ checkedPgSnapCgs }}" + + - set_fact: + drTestCgNames: [] + drTestCgIds: [] + + - set_fact: + drTestCgNames: "{{ drTestCgNames + [ item[0].NAME ] }}" + drTestCgIds: "{{ drTestCgIds + [ item[0].ID ] }}" + loop: "{{ checkedPgSnapCgs }}" + + - set_fact: + checkedPgRepCgs: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_replication_cgs_by_pg_loop_helper.yml" + vars: + pgId: "{{ drPgId }}" + loop: "{{ drPgIds }}" + loop_control: + loop_var: drPgId + + - debug: + msg: + checkedPgRepCgs: "{{ checkedPgRepCgs }}" + drHostNames: "{{ drPgNames }}" + failed_when: item|length|int != 1 + loop: "{{ checkedPgRepCgs }}" + + - set_fact: + drCgNames: [] + + - set_fact: + drCgNames: "{{ drCgNames + [ item[0].NAME ] }}" + loop: "{{ checkedPgRepCgs }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ drLgNames }}" + + - set_fact: + targetLunsAll: [] + targetLunNamesAll: [] + + - include_tasks: "{{GLOBAL.baseDir}}/rundeck/workflow/project001/loop_helper/56_target_luns_loop_helper.yml" + vars: + luns: "{{ checkedLuns[drLgName] }}" + hostLuns: "{{ checkedDrHostLuns[drLgName] }}" + pgName: "{{ drPgNames[index] }}" + cgName: "{{ drTestCgNames[index] }}" + lgName: "{{ drTestLgNames[index] }}" + hostName: "{{ drTestHostNames[index] }}" + className: "{{ checkedLgs[index].DESCRIPTION[4:5] }}" + loop: "{{ drLgNames }}" + loop_control: + index_var: index + loop_var: drLgName + + - set_fact: + targetLunNamesAll: "{{ targetLunNamesAll + item.lunNames }}" + loop: "{{ targetLunsAll }}" + + - set_fact: + existDrLunNamesAll: [] + + - set_fact: + existDrLunNamesAll: "{{ existDrLunNames + [checkedLuns[item] | json_query('[*].NAME')] }}" + loop: "{{ drLgNames }}" + + + - name: Query Snapshots in CG + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/snapshot/associate?ASSOCIATEOBJTYPE=57646&ASSOCIATEOBJID={{item}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: SNAPSHOTS + with_items: "{{ drTestCgIds }}" + + - set_fact: + sourceDrLunNames: [] + + - name: Get AZ IDs + set_fact: + sourceDrLunNames: "{{ sourceDrLunNames + SNAPSHOTS.json.data | default([]) | json_query('[*].SOURCELUNNAME') }}" + with_indexed_items: "{{ drTestCgIds }}" + + - set_fact: + orphanDrLunNames: "{{ sourceDrLunNames | difference(existDrLunNamesAll) }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ orphanDrLunNames }}" + when: orphanDrLunNames|length > 0 + + - set_fact: + orphanDrLunIds: "{{ checkedLuns | json_query('[*].ID') }}" + orphanDrLunSectors: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + when: orphanDrLunNames|length > 0 + + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ drTestLgNames }}" + + - set_fact: + checkedDrLgLuns: "{{ checkedLuns }}" + drTestLunNames: [] + drTestLunIds: [] + drTestLunSectors: [] + + - set_fact: + drTestLunNames: "{{ drTestLunNames + item.value | json_query('[*].NAME') }}" + drTestLunIds: "{{ drTestLunIds + item.value | json_query('[*].ID') }}" + drTestLunSectors: "{{ drTestLunSectors + item.value | json_query('[*].CAPACITY') }}" + with_dict: "{{ checkedLuns }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ drTestLunNames }}" + + - set_fact: + drTestLunClass: [] + drTestLunsInTier: {} + tierNames: "{{ checkedVolumes | json_query('[*].service_level_name') | unique }}" + + - set_fact: + drTestLunClass: "{{ drTestLunClass + [ checkedVolumes[item.0].service_level_name ] }}" + with_indexed_items: "{{ drTestLunNames }}" + + - set_fact: + drTestLunsInTier: "{{ drTestLunsInTier | combine( { item: checkedVolumes | json_query(queryVolumesInTier) } ) }}" + vars: + queryVolumesInTier: "[? service_level_name == '{{ item }}' ].id" + with_items: "{{ tierNames }}" + + - debug: + msg: + drHostWwns: "{{ drHostWwns }}" + drCgNames: "{{ drCgNames }}" + drTestLunNames: "{{ drTestLunNames }}" + drTestCgNames: "{{ drTestCgNames }}" + targetLunsAll: "{{ targetLunsAll }}" + failed_when: drHostWwns|length == 0 or drTestLunNames|length == 0 + + - block: + + # Begin Workflow Steps + + - set_fact: + + # Remove WWNs from DR Test Hosts + Step_1_1_Execute: True + Step_1_1_Completed: False + Step_1_1_Rollbacked: False + + # Add WWNs to DR Hosts + Step_1_2_Execute: True + Step_1_2_Completed: False + Step_1_2_Rollbacked: False + + # Remove DR Test LUNs from Class + Step_2_1_Execute: True + Step_2_1_Completed: False + Step_2_1_Rollbacked: False + + # Remove DR Test LUNs from LUN Group + Step_2_2_Execute: True + Step_2_2_Completed: False + Step_2_2_Rollbacked: False + + # Delete DR Test Snapshot CGs + Step_2_3_Execute: True + Step_2_3_Completed: False + Step_2_3_Rollbacked: False + + # Re-Create DR Test Snapshot CGs + Step_2_4_Execute: "{{ targetLunNames|length > 0 }}" + Step_2_4_Completed: False + Step_2_4_Rollbacked: False + + # Add DR Test LUNs to LUN Group + Step_2_5_Execute: "{{ targetLunNames|length > 0 }}" + Step_2_5_Completed: False + Step_2_5_Rollbacked: False + + # Delete Orphan DR LUNs + Step_2_6_Execute: "{{ orphanDrLunNames|length > 0 }}" + Step_2_6_Completed: False + Step_2_6_Rollbacked: False + + # Update DR Test LUNs to KPI table + Step_3_1_Execute: True + Step_3_1_Completed: False + Step_3_1_Rollbacked: False + + # Update Orphan DR LUNs to KPI table + Step_3_2_Execute: "{{ orphanDrLunNames|length > 0 }}" + Step_3_2_Completed: False + Step_3_2_Rollbacked: False + + - name: Workflow - DR Test Clean for Multi-Host + debug: + msg: + Step_1_1: "[{{Step_1_1_Execute}}] Remove WWNs from DR Test Hosts" + Step_1_2: "[{{Step_1_2_Execute}}] Add WWNs to DR Hosts" + Step_2_1: "[{{Step_2_1_Execute}}] Remove DR Test LUNs from Class" + Step_2_2: "[{{Step_2_2_Execute}}] Remove DR Test LUNs from LUN Group" + Step_2_3: "[{{Step_2_3_Execute}}] Delete DR Test Snapshot CGs" + Step_2_4: "[{{Step_2_4_Execute}}] Re-Create DR Test Snapshot CGs" + Step_2_5: "[{{Step_2_5_Execute}}] Add DR Test LUNs to LUN Group" + Step_2_6: "[{{Step_2_6_Execute}}] Delete Orphan DR LUNs" + Step_3_1: "[{{Step_3_1_Execute}}] Update DR Test LUNs to KPI table" + Step_3_2: "[{{Step_3_2_Execute}}] Update Orphan DR LUNs to KPI table" + + - block: + - name: Step_1_1 - Remove WWNs from DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ drTestHostWwnsItem.value }}" + hostName: "{{ drTestHostWwnsItem.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: drTestHostWwnsItem + + - set_fact: + Step_1_1_Completed: True + + # End Step_1_1 + + # End block + when: Step_1_1_Execute + + - block: + - name: Step_1_2 - Add WWNs to DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ drHostWwnsItem.value }}" + hostName: "{{ drHostWwnsItem.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: drHostWwnsItem + + - set_fact: + Step_1_2_Completed: True + + # End Step_1_2 + + # End block + when: Step_1_2_Execute + + - block: + - name: Step_2_1 - Remove DR Test LUNs from Class + debug: + msg: + params: + drTestLunsInTier: "{{ drTestLunsInTier }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeIds: "{{ drTestLunsInTierItem.value }}" + loop: "{{ lookup('dict', drTestLunsInTier, wantlist=True) }}" + loop_control: + loop_var: drTestLunsInTierItem + + - set_fact: + Step_2_1_Completed: True + + # End Step_2_1 + + # End block + when: Step_2_1_Execute + + - block: + - name: Step_2_2 - Remove DR Test LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ drTestLunNames }}" + lgName: "{{ drTestLgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ checkedDrLgLunsItem.key }}" + lunNames: "{{ checkedDrLgLunsItem.value | json_query('[*].NAME') }}" + loop: "{{ lookup('dict', checkedDrLgLuns, wantlist=True) }}" + loop_control: + loop_var: checkedDrLgLunsItem + + - set_fact: + Step_2_2_Completed: True + + # End Step_2_2 + + # End block + when: Step_2_2_Execute + + - block: + - name: Step_2_3 - Delete DR Test Snapshot CGs + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + loop: "{{ drTestCgNames }}" + loop_control: + loop_var: drTestCgName + + - set_fact: + Step_2_3_Completed: True + + # End Step_2_3 + + # End block + when: Step_2_3_Execute + + - block: + - name: Step_2_4 - Re-Create DR Test Snapshot CGs + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" + vars: + pgName: "{{ targetLuns.pgName }}" + cgName: "{{ targetLuns.cgName }}" + snapNames: "{{ targetLuns.lunNames }}" + activate: False + snapDescs: "{{ targetLuns.lunDescs }}" + loop: "{{ targetLunsAll }}" + loop_control: + loop_var: targetLuns + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ targetLunNamesAll }}" + + - set_fact: + targetLunIdsAll: "{{ checkedLuns | json_query('[*].ID') }}" + targetLunSectorsAll: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + + - set_fact: + Step_2_4_Completed: True + + # End Step_2_4 + + # End block + when: Step_2_4_Execute + + - block: + - name: Step_2_5 - Add DR Test LUNs to LUN Group + debug: + msg: + params: + luns: "{{ targetLunsAll }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lunNames: "{{ targetLuns.lunNames }}" + lgName: "{{ targetLuns.lgName }}" + addLunScsiIds: "{{ targetLuns.lunScsiIds }}" + loop: "{{ targetLunsAll }}" + loop_control: + loop_var: targetLuns + + - set_fact: + Step_2_5_Completed: True + + # End Step_2_5 + + # End block + when: Step_2_5_Execute + + - block: + - name: Step_2_6 - Delete Orphan DR LUNs + debug: + msg: + params: + lunNames: "{{ orphanDrLunNames }}" + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_luns.yml" + vars: + lunNames: "{{ orphanDrLunNames }}" + + - set_fact: + Step_2_6_Completed: True + + # End Step_2_6 + + # End block + when: Step_2_6_Execute + + - block: + - name: Step_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + lunNames: "{{ drTestLunNames }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "delete" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ '_'.join(drTestLunNames[index].split('_')[:4]) }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "{{ drTestLunClass[index] }}" + CAPACITY_GB: "-{{ (drTestLunSectors[index]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ lunId }}" + loop: "{{ drTestLunIds }}" + loop_control: + index_var: index + loop_var: lunId + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "create" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ '_'.join(targetLunNamesAll[index].split('_')[:4]) }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "{{ (targetLunSectorsAll[index]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ lunId }}" + loop: "{{ targetLunIdsAll }}" + loop_control: + index_var: index + loop_var: lunId + when: targetLunIdsAll|default([])|length > 0 + + - set_fact: + Step_3_1_Completed: True + + # End Step_3_1 + + # End block + when: Step_3_1_Execute + + - block: + - name: Step_3_2 - Update Orphan DR LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ orphanDrLunIds }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "delete" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ orphanDrLunNames[index][:-6] }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "-{{ (orphanDrLunSectors[index]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ lunId }}" + with_indexed_items: "{{ orphanDrLunIds }}" + loop: "{{ orphanDrLunIds }}" + loop_control: + index_var: index + loop_var: lunId + + - set_fact: + Step_3_2_Completed: True + + # End Step_3_2 + + # End block + when: Step_3_2_Execute + + # End Steps + rescue: + + # Unable to rollback DR Test CGs, need to manually rollback + + - block: + - name: Rollback_2_4 - Reactivated DR Test Snapshot CG + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/activate_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + loop: "{{ drTestCgNames }}" + loop_control: + loop_var: drTestCgName + + - set_fact: + Step_2_3_Rollbacked: True + Step_2_4_Rollbacked: True + + # End Rollback_2_4 + + # End block + when: Step_2_4_Completed + + - block: + - name: Rollback_1_2 - Remove WWNs from DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + hostNames: "{{ drHostNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ drHostWwnsItem.value }}" + hostName: "{{ drHostWwnsItem.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: drHostWwnsItem + + - set_fact: + Step_1_2_Rollbacked: True + + # End Rollback_1_2 + + # End block + when: Step_1_2_Completed + + - block: + - name: Rollback_1_1 - Add WWNs to DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + hostNames: "{{ drTestHostNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ drTestHostWwnsItem.value }}" + hostName: "{{ drTestHostWwnsItem.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: drTestHostWwnsItem + + - set_fact: + Step_1_1_Rollbacked: True + + # End Rollback_1_1 + + # End block + when: Step_1_1_Completed + + # End Rollbacks + always: + + - name: Final_Step_1 - Sync DR Device + debug: + msg: + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + + # End Final Steps + + # End Workflow + + + - block: + + # Begin Validate Results + + - name: Result_1_1 - Remove WWNs from DR Test Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + hostNames: "{{ drTestHostNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_1_1_Completed }}" + rollbacked: "{{ Step_1_1_Rollbacked }}" + failed_when: Step_1_1_Completed|bool == False + when: Step_1_1_Execute + + - name: Result_1_2 - Add WWNs to DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + hostNames: "{{ drHostNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_1_2_Completed }}" + rollbacked: "{{ Step_1_2_Rollbacked }}" + failed_when: Step_1_2_Completed|bool == False + when: Step_1_2_Execute + + - name: Result_2_1 - Remove DR Test LUNs from Class + debug: + msg: + params: + drTestLunsInTier: "{{ drTestLunsInTier }}" + result: + succeeded: "{{ Step_2_1_Completed }}" + rollbacked: "{{ Step_2_1_Rollbacked }}" + failed_when: Step_2_1_Completed|bool == False + when: Step_2_1_Execute + + - name: Result_2_2 - Remove DR Test LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ drTestLunNames }}" + lgNames: "{{ drTestLgNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_2_Completed }}" + rollbacked: "{{ Step_2_2_Rollbacked }}" + failed_when: Step_2_2_Completed|bool == False + when: Step_2_2_Execute + + - name: Result_2_3 - Delete DR Test Snapshot CG + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_3_Completed }}" + rollbacked: "{{ Step_2_3_Rollbacked }}" + failed_when: Step_2_3_Completed|bool == False + when: Step_2_3_Execute + + - name: Result_2_4 - Re-Create DR Test Snapshots + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_4_Completed }}" + rollbacked: "{{ Step_2_4_Rollbacked }}" + failed_when: Step_2_4_Completed|bool == False + when: Step_2_4_Execute + + - name: Result_2_5 - Add DR Test LUNs to LUN Group + debug: + msg: + params: + luns: "{{ targetLunsAll }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_5_Completed }}" + rollbacked: "{{ Step_2_5_Rollbacked }}" + failed_when: Step_2_5_Completed|bool == False + when: Step_2_5_Execute + + - name: Result_2_6 - Delete Orphan DR LUNs + debug: + msg: + params: + lunNames: "{{ orphanDrLunNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_6_Completed }}" + rollbacked: "{{ Step_2_6_Rollbacked }}" + failed_when: Step_2_6_Completed|bool == False + when: Step_2_6_Execute + + - name: Result_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + result: + succeeded: "{{ Step_3_1_Completed }}" + failed_when: Step_3_1_Completed|bool == False + when: Step_3_1_Execute + + - name: Result_3_2 - Update Orphan DR LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ orphanDrLunIds }}" + result: + succeeded: "{{ Step_3_2_Completed }}" + failed_when: Step_3_2_Completed|bool == False + when: Step_3_2_Execute + diff --git a/rundeck/workflow/project001/57_dr_test_for_multi_cluster.yml b/rundeck/workflow/project001/57_dr_test_for_multi_cluster.yml new file mode 100644 index 0000000..11479b3 --- /dev/null +++ b/rundeck/workflow/project001/57_dr_test_for_multi_cluster.yml @@ -0,0 +1,614 @@ +- name: DR Test for Clusters + hosts: localhost + vars_files: + - ../../../config/global.yml + - ../../../config/project001.yml + gather_facts: no + become: no + tasks: + # Check Params + - block: + - set_fact: + checked_params: + Select_DR_Cluster: "{% set clusters = [] %}{% set Select_DR_Cluster = Select_DR_Cluster|string %}{% for cluster in Select_DR_Cluster.split(',') if cluster.endswith('_2') %}{{ clusters.append(cluster) }}{% endfor %}{{ (clusters | length >= 1) and (clusters | length == Select_DR_Cluster.split(',') | length) }}" + DR_Storage: "{{ (DR_Storage is not none and DR_Storage != DEFAULT.noneValue) and (DR_Storage|string|length == 20) }}" + Check_Result_1: "{{ ('cluster' in Check_Result_1) }}" + + - name: Precheck_0_1 - Check Params + debug: + msg: "{{checked_params}}" + failed_when: checked_params.values()|unique != [True] + + - set_fact: + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + osType: "{{ OS_Type }}" + deviceSn: "{{ DR_Storage|string }}" + room: "{{ DR_Storage_Room }}" + site: "{{ AZ[DR_Storage_Room]['dc'] }}" + drClusterNames: "{{ (Select_DR_Cluster|string).split(',') }}" + designateClass3: "{{ Designate_Class_3|default(none) }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - name: Precheck_1 - Check Clusters, Hosts, WWNs + debug: + msg: + hostGroupNames: "{{ drClusterNames }}" + + # check wwns + - set_fact: + drTestClusterNames: [] + - set_fact: + drTestClusterNames: "{{ drTestClusterNames + [ item[:-1] + '3' ] }}" + with_items: "{{ drClusterNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hostgroups.yml" + vars: + hostGroupNames: "{{ drClusterNames }}" + - set_fact: + drClusterIds: "{{ hostGroupIds }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hostgroups.yml" + vars: + hostGroupNames: "{{ drTestClusterNames }}" + - set_fact: + drTestClusterIds: "{{ hostGroupIds }}" + + - name: Get DR Hosts + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host/associate?ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID={{item}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: DR_HOSTS + failed_when: (DR_HOSTS.json.error.code|int != 0) or ('data' not in DR_HOSTS.json) or (DR_HOSTS.json.data|length == 0) + with_items: "{{ drClusterIds }}" + + - set_fact: + drHostNames: "{{ DR_HOSTS.results | json_query('[*].json.data[*].NAME') | flatten(levels=1) }}" + drHostIds: "{{ DR_HOSTS.results | json_query('[*].json.data[*].ID') | flatten(levels=1) }}" + drHostWwns: {} + + - name: Get DR Test Hosts + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host/associate?ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID={{item}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: DR_TEST_HOSTS + failed_when: (DR_TEST_HOSTS.json.error.code|int != 0) or ('data' not in DR_TEST_HOSTS.json) or (DR_TEST_HOSTS.json.data|length == 0) + with_items: "{{ drTestClusterIds }}" + + - set_fact: + drTestHostNames: "{{ DR_TEST_HOSTS.results | json_query('[*].json.data[*].NAME') | flatten(levels=1) }}" + drTestHostIds: "{{ DR_TEST_HOSTS.results | json_query('[*].json.data[*].ID') | flatten(levels=1) }}" + drTestHostWwns: {} + + - debug: + msg: + drHostNames: "{{ drHostNames }}" + drHostIds: "{{ drHostIds }}" + drTestHostNames: "{{ drTestHostNames }}" + drTestHostIds: "{{ drTestHostIds }}" + failed_when: drHostNames|length != drTestHostNames|length or drHostNames == 0 + + - name: Get WWNs + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/fc_initiator?filter=PARENTID%3A%3A{{drHostIds[item.0]}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: WWNs + failed_when: (WWNs.json.error.code|int != 0) or ('data' not in WWNs.json) or (WWNs.json.data|length == 0) + with_indexed_items: "{{ drHostNames }}" + + - set_fact: + drHostWwns: "{{ drHostWwns | combine( { item.1: WWNs.results[item.0].json.data | json_query('[*].ID') } ) }}" + drTestHostWwns: "{{ drTestHostWwns | combine( { drTestHostNames[item.0]: WWNs.results[item.0].json.data | json_query('[*].ID') } ) }}" + with_indexed_items: "{{ drHostNames }}" + + - name: Query Host Mapping + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/mapping/associate?ASSOCIATEOBJTYPE=21&ASSOCIATEOBJID={{item}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: HOST_MAPPINGs + with_items: "{{ drTestHostIds }}" + + - set_fact: + checkedLgNames: "{{ HOST_MAPPINGs.results | json_query(queryHostLgName) | flatten(levels=1) | unique }}" + vars: + queryHostLgName: "[*].json.data[?lunGroupName!=''].lunGroupName" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ checkedLgNames }}" + + - set_fact: + drTestLunNames: "{{ checkedLuns.values() | flatten(levels=2) | json_query('[*].NAME') }}" + drTestLunIds: "{{ checkedLuns.values() | flatten(levels=2) | json_query('[*].ID') }}" + drTestLgNames: "{{ checkedLuns.keys() }}" + + - name: Check DR Host Luns Number in LGs + debug: + msg: + drTestLunNames: "{{ drTestLunNames }}" + failed_when: drTestLunNames|length == 0 + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ drTestLunNames }}" + + - set_fact: + drTestLunSectors: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + drTestLunDesc: "{{ checkedLuns | json_query('[*].DESCRIPTION') }}" + drTestLunClasses: [] + + - set_fact: + drTestLunClasses: "{{ drTestLunClasses + [ designateClass3 if (designateClass3 != '' ) else item[:1] ]}}" + with_items: "{{ drTestLunDesc }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ drTestLunNames }}" + + - name: Check Volume in Tier Class + debug: + msg: + lunsInTierClass: "{{ checkedVolumes|json_query(queryLunsInTierClass) }}" + failed_when: checkedVolumes|json_query(queryLunsInTierClass)|length > 0 + vars: + queryLunsInTierClass: "[? service_level_name != '' && service_level_name != null ].name" + + - set_fact: + drPgNames: [] + + - set_fact: + drPgNames: "{{ drPgNames + [ item[:-6] + '2_PG' + item[-2:] ] }}" + with_items: "{{ drTestLgNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: "{{ drPgNames }}" + + - set_fact: + drPgIds: "{{ pgIds }}" + checkedPgSnapCgs: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_snapshot_cgs_by_pg_loop_helper.yml" + vars: + pgId: "{{ drPgId }}" + ignoreEmpty: true + loop: "{{ drPgIds }}" + loop_control: + loop_var: drPgId + + - name: Check Snapshot CGs Number and Status + debug: + msg: + SnapshotCgs: "{{ item }}" + status: "{{ item[0].RUNNINGSTATUS }}" + failed_when: (item|length|int != 1) or (item|length|int == 1 and item[0].RUNNINGSTATUS|int != SNAPCG.unactivated.enum) + loop: "{{ checkedPgSnapCgs }}" + + - set_fact: + drTestCgNames: [] + + - set_fact: + drTestCgNames: "{{ drTestCgNames + [ item[0].NAME ] }}" + loop: "{{ checkedPgSnapCgs }}" + + - set_fact: + checkedPgRepCgs: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_replication_cgs_by_pg_loop_helper.yml" + vars: + pgId: "{{ drPgId }}" + loop: "{{ drPgIds }}" + loop_control: + loop_var: drPgId + + - debug: + msg: + checkedPgRepCgs: "{{ checkedPgRepCgs }}" + drHostNames: "{{ drPgNames }}" + failed_when: item|length|int != 1 + loop: "{{ checkedPgRepCgs }}" + + - set_fact: + drCgNames: [] + + - set_fact: + drCgNames: "{{ drCgNames + [ item[0].NAME ] }}" + loop: "{{ checkedPgRepCgs }}" + + - debug: + msg: + drHostWwns: "{{ drHostWwns }}" + drCgNames: "{{ drCgNames }}" + drTestCgNames: "{{ drTestCgNames }}" + drTestHostNames: "{{ drTestHostNames }}" + drTestLunNames: "{{ drTestLunNames }}" + drTestLunClasses: "{{ drTestLunClasses }}" + failed_when: (drHostWwns|length == 0) or (drTestLunNames|length == 0) + + - block: + + # Begin Workflow Steps + + - set_fact: + # Activate DR Test Snapshot CGs + Step_1_1_Execute: True + Step_1_1_Completed: False + Step_1_1_Rollbacked: False + + # Set Class for DR Test LUNs + Step_1_2_Execute: True + Step_1_2_Completed: False + Step_1_2_Rollbacked: False + + # Remove WWNs from DR Hosts + Step_2_1_Execute: True + Step_2_1_Completed: False + Step_2_1_Rollbacked: False + + # Add WWNs to DR Test Hosts + Step_2_2_Execute: True + Step_2_2_Completed: False + Step_2_2_Rollbacked: False + + # Update DR Test LUNs to KPI table + Step_3_1_Execute: True + Step_3_1_Completed: False + + - name: Workflow - DR Test for Hosts + debug: + msg: + Step_1_1: "[{{Step_1_1_Execute}}] Activate DR Test Snapshot CGs" + Step_1_2: "[{{Step_1_2_Execute}}] Set Class for DR Test LUNs" + Step_2_1: "[{{Step_2_1_Execute}}] Remove WWNs from DR Hosts" + Step_2_2: "[{{Step_2_2_Execute}}] Add WWNs to DR Test Hosts" + Step_3_1: "[{{Step_3_1_Execute}}] Update DR Test LUNs to KPI table" + + - block: + - name: Step_1_1 - Activate DR Test Snapshot CGs + debug: + msg: + params: + cgNames: "{{ drCgNames }}" + cgTestNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/activate_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + loop: "{{ drTestCgNames }}" + loop_control: + loop_var: drTestCgName + + - set_fact: + Step_1_1_Completed: True + + # End Step_1_1 + + # End block + when: Step_1_1_Execute + + - block: + - name: Step_1_2 - Set Class for DR Test LUNs + debug: + msg: + params: + volumeNames: "{{ drTestLunNames }}" + tierNames: "{{ drTestLunClasses }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/add_volumes_to_tier.yml" + vars: + volumeNames: ["{{ drTestLun.0 }}"] + tierName: "{{ drTestLun.1 }}" + loop: "{{ drTestLunNames|zip(drTestLunClasses)|list }}" + loop_control: + loop_var: drTestLun + + - set_fact: + Step_1_2_Completed: True + + # End Step_1_2 + + # End block + when: Step_1_2_Execute + + - block: + - name: Step_2_1 - Remove WWNs from DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ drHostWwnsItem.value }}" + hostName: "{{ drHostWwnsItem.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: drHostWwnsItem + + - set_fact: + Step_2_1_Completed: True + + # End Step_2_1 + + # End block + when: Step_2_1_Execute + + - block: + - name: Step_2_2 - Add WWNs to DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + hostNames: "{{ drTestHostNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ drHostTestWwnsItem.value }}" + hostName: "{{ drHostTestWwnsItem.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: drHostTestWwnsItem + + - set_fact: + Step_2_2_Completed: True + + # End Step_2_2 + + # End block + when: Step_2_2_Execute + + - block: + - name: Step_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + classes: "{{ drTestLunClasses }}" + + # Minus Non-Class capacity + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ '_'.join(drTestLunNames[item.0].split('_')[:4]) }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "-{{ (drTestLunSectors[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drTestLunIds }}" + + # Add capacity to Class + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "modify" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ '_'.join(drTestLunNames[item.0].split('_')[:4]) }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "{{ drTestLunClasses[item.0] }}" + CAPACITY_GB: "{{ (drTestLunSectors[item.0]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ item.1 }}" + with_indexed_items: "{{ drTestLunIds }}" + + - set_fact: + Step_3_1_Completed: True + + # End Step_3_1 + + # End block + when: Step_3_1_Execute + + # End Steps + rescue: + + - block: + - name: Rollback_2_2 - Remove WWNs from DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + hostNames: "{{ drTestHostNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ drTestHostWwnsItem.value }}" + hostName: "{{ drTestHostWwnsItem.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: drTestHostWwnsItem + + - set_fact: + Step_2_2_Rollbacked: True + + # End Rollback_2_2 + + # End block + when: Step_2_2_Completed + + - block: + - name: Rollback_2_1 - Add WWNs to DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + hostNames: "{{ drHostNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ drHostWwnsItem.value }}" + hostName: "{{ drHostWwnsItem.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: drHostWwnsItem + + - set_fact: + Step_2_1_Rollbacked: True + + # End Rollback_2_1 + + # End block + when: Step_2_1_Completed + + - block: + - name: Rollback_1_2 - Remove Class for DR Test LUNs + debug: + msg: + params: + volumeNames: "{{ drTestLunNames }}" + tierNames: "{{ drTestLunClasses }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeNames: "{{ drTestLun.0 }}" + tierName: "{{ drTestLun.1 }}" + loop: "{{ drTestLunNames|zip(drTestLunClasses)|list }}" + loop_control: + loop_var: drTestLun + + - set_fact: + Step_1_2_Rollbacked: True + + # End Rollback_1_2 + + # End block + when: Step_1_2_Completed + + - block: + - name: Rollback_1_1 - Deactivate DR Test Snapshot CGs + debug: + msg: + params: + cgTestNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/deactivate_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + loop: "{{ drTestCgNames }}" + loop_control: + loop_var: drTestCgName + + - set_fact: + Step_1_1_Rollbacked: True + + # End Rollback_1_1 + + # End block + when: Step_1_1_Completed + + # End Rollbacks + always: + + - name: Final_Step_1 - Sync DR Device + debug: + msg: + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + + # End Final Steps + + # End Workflow + + + - block: + + # Begin Validate Results + + - name: Result_1_1 - Activate DR Test Snapshot CGs + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + cgTestNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_1_1_Completed }}" + rollbacked: "{{ Step_1_1_Rollbacked }}" + failed_when: Step_1_1_Completed|bool == False + when: Step_1_1_Execute + + - name: Result_1_2 - Set Class for DR Test LUNs + debug: + msg: + params: + volumeNames: "{{ drTestLunNames }}" + tierName: "{{ drTestLunClasses }}" + result: + succeeded: "{{ Step_1_2_Completed }}" + rollbacked: "{{ Step_1_2_Rollbacked }}" + failed_when: Step_1_2_Completed|bool == False + when: Step_1_2_Execute + + - name: Result_2_1 - Remove WWNs from DR Host + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + hostNames: "{{ drHostNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_1_Completed }}" + rollbacked: "{{ Step_2_1_Rollbacked }}" + failed_when: Step_2_1_Completed|bool == False + when: Step_2_1_Execute + + - name: Result_2_2 - Add WWNs to DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + hostNames: "{{ drTestHostNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_2_Completed }}" + rollbacked: "{{ Step_2_2_Rollbacked }}" + failed_when: Step_2_2_Completed|bool == False + when: Step_2_2_Execute + + - name: Result_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + classes: "{{ drTestLunClasses }}" + result: + succeeded: "{{ Step_3_1_Completed }}" + failed_when: Step_3_1_Completed|bool == False + when: Step_3_1_Execute \ No newline at end of file diff --git a/rundeck/workflow/project001/58_dr_test_clean_for_multi_cluster.yml b/rundeck/workflow/project001/58_dr_test_clean_for_multi_cluster.yml new file mode 100644 index 0000000..336fa05 --- /dev/null +++ b/rundeck/workflow/project001/58_dr_test_clean_for_multi_cluster.yml @@ -0,0 +1,921 @@ +- name: DR Test Clean for Hosts + hosts: localhost + vars_files: + - ../../../config/global.yml + - ../../../config/project001.yml + gather_facts: no + become: no + tasks: + + # Check Params + - block: + - set_fact: + checked_params: + Select_DR_Test_Cluster: "{% set clusters = [] %}{% set Select_DR_Test_Cluster = Select_DR_Test_Cluster|string %}{% for cluster in Select_DR_Test_Cluster.split(',') if cluster.endswith('_3') %}{{ clusters.append(cluster) }}{% endfor %}{{ (clusters | length >= 1) and (clusters | length == Select_DR_Test_Cluster.split(',') | length) }}" + DR_Storage: "{{ (DR_Storage is not none and DR_Storage != DEFAULT.noneValue) and (DR_Storage|string|length == 20) }}" + Check_Result_1: "{{ ('cluster' in Check_Result_1) }}" + + - name: Precheck_0_1 - Check Params + debug: + msg: "{{checked_params}}" + failed_when: checked_params.values()|unique != [True] + + - set_fact: + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + osType: "{{ OS_Type }}" + deviceSn: "{{ DR_Storage|string }}" + room: "{{ DR_Storage_Room }}" + site: "{{ AZ[DR_Storage_Room]['dc'] }}" + drTestClusterNames: "{{ (Select_DR_Test_Cluster|string).split(',') }}" + drClusterNames: [] + + - set_fact: + drClusterNames: "{{ drClusterNames + [ item[:-1] + '2' ] }}" + with_items: "{{ drTestClusterNames }}" + + - set_fact: + protectType: "{{ REPTYPE['N3']['enum'] }}" # N3 is same as Y3 enum, use one of them to stand all cases, See ../../config/project001.yml + + - set_fact: + lunNameTemplate: "%s_{{protectType}}N%0{{DEFAULT.suffixDigits}}d" + + - import_tasks: "{{GLOBAL.baseDir}}/task/user/login.yml" + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/login_storage.yml" + + - name: Precheck_1 - Check Clusters, WWNs, Replication CG & Snapshots + debug: + msg: + drClusterNames: "{{drClusterNames}}" + drTestClusterNames: "{{drTestClusterNames}}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hostgroups.yml" + vars: + hostGroupNames: "{{ drClusterNames }}" + - set_fact: + drClusterIds: "{{ hostGroupIds }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_hostgroups.yml" + vars: + hostGroupNames: "{{ drTestClusterNames }}" + - set_fact: + drTestClusterIds: "{{ hostGroupIds }}" + + - name: Get DR Hosts + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host/associate?ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID={{item}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: DR_HOSTS + failed_when: (DR_HOSTS.json.error.code|int != 0) or ('data' not in DR_HOSTS.json) or (DR_HOSTS.json.data|length == 0) + with_items: "{{ drClusterIds }}" + + - set_fact: + drHostNames: "{{ DR_HOSTS.results | json_query('[*].json.data[*].NAME') | flatten(levels=1) }}" + drHostIds: "{{ DR_HOSTS.results | json_query('[*].json.data[*].ID') | flatten(levels=1) }}" + drHostWwns: {} + + - name: Get DR Test Hosts + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host/associate?ASSOCIATEOBJTYPE=14&ASSOCIATEOBJID={{item}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: DR_TEST_HOSTS + failed_when: (DR_TEST_HOSTS.json.error.code|int != 0) or ('data' not in DR_TEST_HOSTS.json) or (DR_TEST_HOSTS.json.data|length == 0) + with_items: "{{ drTestClusterIds }}" + + - set_fact: + drTestHostNames: "{{ DR_TEST_HOSTS.results | json_query('[*].json.data[*].NAME') | flatten(levels=1) }}" + drTestHostIds: "{{ DR_TEST_HOSTS.results | json_query('[*].json.data[*].ID') | flatten(levels=1) }}" + drTestHostWwns: {} + + - debug: + msg: + drHostNames: "{{ drHostNames }}" + drHostIds: "{{ drHostIds }}" + drTestHostNames: "{{ drTestHostNames }}" + drTestHostIds: "{{ drTestHostIds }}" + failed_when: drHostNames|length != drTestHostNames|length or drHostNames == 0 + + # check wwns + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_wwns.yml" + vars: + hostNames: "{{ drHostNames }}" + checkExist: False + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_wwns.yml" + vars: + hostNames: "{{ drTestHostNames }}" + + - set_fact: + drTestHostWwns: "{{ checkedWwns }}" + drHostWwns: {} + + - set_fact: + drHostWwns: "{{ drHostWwns | combine( { item.0: drTestHostWwns[item.1] } ) }}" + with_together: + - "{{ drHostNames }}" + - "{{ drTestHostNames }}" + + # check host lun + - set_fact: + checkedHostLuns: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id_loop_helper.yml" + vars: + hostName: "{{ drHostName }}" + loop: "{{ drHostNames }}" + loop_control: + loop_var: drHostName + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id_loop_helper.yml" + vars: + hostGroupName: "{{ drClusterName }}" + loop: "{{ drClusterNames }}" + loop_control: + loop_var: drClusterName + + - set_fact: + checkedDrHostLuns: {} + + - set_fact: + checkedDrHostLuns: "{{ checkedDrHostLuns | combine({item.key:item.value}) }}" + with_dict: "{{ checkedHostLuns }}" + when: + - item.value|length > 0 + + - name: Check Host Luns Number in LGs + debug: + msg: + drTestLun: "{{ checkedDrHostLuns }}" + failed_when: checkedDrHostLuns|length == 0 + + - set_fact: + drLgNames: "{{ checkedDrHostLuns.keys() }}" + drTestLgNames: [] + drPgNames: [] + + - set_fact: + drPgNames: "{{ drPgNames + [ item[:-6] + '2_PG' + item[-2:] ] }}" + drTestLgNames: "{{ drTestLgNames + [ item[:-6] + '3_LG' + item[-2:] ] }}" + with_items: "{{ drLgNames }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_pgs.yml" + vars: + pgNames: "{{ drPgNames }}" + + - set_fact: + drPgIds: "{{ pgIds }}" + checkedPgSnapCgs: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_snapshot_cgs_by_pg_loop_helper.yml" + vars: + pgId: "{{ drPgId }}" + ignoreEmpty: true + loop: "{{ drPgIds }}" + loop_control: + loop_var: drPgId + + - name: Check Snapshot CGs Number and Status + debug: + msg: + SnapshotCgs: "{{ item }}" + status: "{{ item[0].RUNNINGSTATUS }}" + failed_when: (item|length|int != 1) or (item|length|int == 1 and item[0].RUNNINGSTATUS|int != SNAPCG.activated.enum) + loop: "{{ checkedPgSnapCgs }}" + + - set_fact: + drTestCgNames: [] + drTestCgIds: [] + + - set_fact: + drTestCgNames: "{{ drTestCgNames + [ item[0].NAME ] }}" + drTestCgIds: "{{ drTestCgIds + [ item[0].ID ] }}" + loop: "{{ checkedPgSnapCgs }}" + + - set_fact: + checkedPgRepCgs: [] + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_replication_cgs_by_pg_loop_helper.yml" + vars: + pgId: "{{ drPgId }}" + loop: "{{ drPgIds }}" + loop_control: + loop_var: drPgId + + - debug: + msg: + checkedPgRepCgs: "{{ checkedPgRepCgs }}" + drHostNames: "{{ drPgNames }}" + failed_when: item|length|int != 1 + loop: "{{ checkedPgRepCgs }}" + + - set_fact: + drCgNames: [] + + - set_fact: + drCgNames: "{{ drCgNames + [ item[0].NAME ] }}" + loop: "{{ checkedPgRepCgs }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ drLgNames }}" + + - set_fact: + targetLunsAll: [] + targetLunNamesAll: [] + + - debug: + msg: + drLgNames: "{{ drLgNames }}" + drPgNames: "{{ drPgNames }}" + drTestCgNames: "{{ drTestCgNames }}" + drTestLgNames: "{{ drTestLgNames }}" + drTestHostNames: "{{ drTestHostNames }}" + + - include_tasks: "{{GLOBAL.baseDir}}/rundeck/workflow/project001/loop_helper/56_target_luns_loop_helper.yml" + vars: + luns: "{{ checkedLuns[drLgName] }}" + hostLuns: "{{ checkedDrHostLuns[drLgName] }}" + pgName: "{{ drPgNames[index] }}" + cgName: "{{ drTestCgNames[index] }}" + lgName: "{{ drTestLgNames[index] }}" + hostName: "{{ drTestHostNames[index] }}" + className: "{{ checkedLgs[index].DESCRIPTION[4:5] }}" + loop: "{{ drLgNames }}" + loop_control: + index_var: index + loop_var: drLgName + + - debug: + msg: + targetLunsAll: "{{ targetLunsAll }}" + + - set_fact: + targetLunNamesAll: "{{ targetLunNamesAll + item.lunNames }}" + loop: "{{ targetLunsAll }}" + + - set_fact: + existDrLunNamesAll: [] + + - set_fact: + existDrLunNamesAll: "{{ existDrLunNames + [checkedLuns[item] | json_query('[*].NAME')] }}" + loop: "{{ drLgNames }}" + + - name: Query Snapshots in CG + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/snapshot/associate?ASSOCIATEOBJTYPE=57646&ASSOCIATEOBJID={{item}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: SNAPSHOTS + with_items: "{{ drTestCgIds }}" + + - set_fact: + sourceDrLunNames: [] + + - name: Get AZ IDs + set_fact: + sourceDrLunNames: "{{ sourceDrLunNames + SNAPSHOTS.json.data | default([]) | json_query('[*].SOURCELUNNAME') }}" + with_indexed_items: "{{ drTestCgIds }}" + + - set_fact: + orphanDrLunNames: "{{ sourceDrLunNames | difference(existDrLunNamesAll) }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ orphanDrLunNames }}" + when: orphanDrLunNames|length > 0 + + - set_fact: + orphanDrLunIds: "{{ checkedLuns | json_query('[*].ID') }}" + orphanDrLunSectors: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + when: orphanDrLunNames|length > 0 + + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_lgs.yml" + vars: + lgNames: "{{ drTestLgNames }}" + + - set_fact: + checkedDrLgLuns: "{{ checkedLuns }}" + drTestLunNames: [] + drTestLunIds: [] + drTestLunSectors: [] + + - set_fact: + drTestLunNames: "{{ drTestLunNames + item.value | json_query('[*].NAME') }}" + drTestLunIds: "{{ drTestLunIds + item.value | json_query('[*].ID') }}" + drTestLunSectors: "{{ drTestLunSectors + item.value | json_query('[*].CAPACITY') }}" + with_dict: "{{ checkedLuns }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/volume/check_volumes.yml" + vars: + volumeNames: "{{ drTestLunNames }}" + + - set_fact: + drTestLunClass: [] + drTestLunsInTier: {} + tierNames: "{{ checkedVolumes | json_query('[*].service_level_name') | unique }}" + + - set_fact: + drTestLunClass: "{{ drTestLunClass + [ checkedVolumes[item.0].service_level_name ] }}" + with_indexed_items: "{{ drTestLunNames }}" + + - set_fact: + drTestLunsInTier: "{{ drTestLunsInTier | combine( { item: checkedVolumes | json_query(queryVolumesInTier) } ) }}" + vars: + queryVolumesInTier: "[? service_level_name == '{{ item }}' ].id" + with_items: "{{ tierNames }}" + + - debug: + msg: + drHostWwns: "{{ drHostWwns }}" + drCgNames: "{{ drCgNames }}" + drTestLunNames: "{{ drTestLunNames }}" + drTestCgNames: "{{ drTestCgNames }}" + targetLunsAll: "{{ targetLunsAll }}" + failed_when: drHostWwns|length == 0 or drTestLunNames|length == 0 + + - block: + + # Begin Workflow Steps + + - set_fact: + + # Remove WWNs from DR Test Hosts + Step_1_1_Execute: True + Step_1_1_Completed: False + Step_1_1_Rollbacked: False + + # Add WWNs to DR Hosts + Step_1_2_Execute: True + Step_1_2_Completed: False + Step_1_2_Rollbacked: False + + # Remove DR Test LUNs from Class + Step_2_1_Execute: True + Step_2_1_Completed: False + Step_2_1_Rollbacked: False + + # Remove DR Test LUNs from LUN Group + Step_2_2_Execute: True + Step_2_2_Completed: False + Step_2_2_Rollbacked: False + + # Delete DR Test Snapshot CGs + Step_2_3_Execute: True + Step_2_3_Completed: False + Step_2_3_Rollbacked: False + + # Re-Create DR Test Snapshot CGs + Step_2_4_Execute: "{{ targetLunNames|length > 0 }}" + Step_2_4_Completed: False + Step_2_4_Rollbacked: False + + # Add DR Test LUNs to LUN Group + Step_2_5_Execute: "{{ targetLunNames|length > 0 }}" + Step_2_5_Completed: False + Step_2_5_Rollbacked: False + + # Delete Orphan DR LUNs + Step_2_6_Execute: "{{ orphanDrLunNames|length > 0 }}" + Step_2_6_Completed: False + Step_2_6_Rollbacked: False + + # Update DR Test LUNs to KPI table + Step_3_1_Execute: True + Step_3_1_Completed: False + Step_3_1_Rollbacked: False + + # Update Orphan DR LUNs to KPI table + Step_3_2_Execute: "{{ orphanDrLunNames|length > 0 }}" + Step_3_2_Completed: False + Step_3_2_Rollbacked: False + + - name: Workflow - DR Test Clean for Multi-Host + debug: + msg: + Step_1_1: "[{{Step_1_1_Execute}}] Remove WWNs from DR Test Hosts" + Step_1_2: "[{{Step_1_2_Execute}}] Add WWNs to DR Hosts" + Step_2_1: "[{{Step_2_1_Execute}}] Remove DR Test LUNs from Class" + Step_2_2: "[{{Step_2_2_Execute}}] Remove DR Test LUNs from LUN Group" + Step_2_3: "[{{Step_2_3_Execute}}] Delete DR Test Snapshot CGs" + Step_2_4: "[{{Step_2_4_Execute}}] Re-Create DR Test Snapshot CGs" + Step_2_5: "[{{Step_2_5_Execute}}] Add DR Test LUNs to LUN Group" + Step_2_6: "[{{Step_2_6_Execute}}] Delete Orphan DR LUNs" + Step_3_1: "[{{Step_3_1_Execute}}] Update DR Test LUNs to KPI table" + Step_3_2: "[{{Step_3_2_Execute}}] Update Orphan DR LUNs to KPI table" + + - block: + - name: Step_1_1 - Remove WWNs from DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ drTestHostWwnsItem.value }}" + hostName: "{{ drTestHostWwnsItem.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: drTestHostWwnsItem + + - set_fact: + Step_1_1_Completed: True + + # End Step_1_1 + + # End block + when: Step_1_1_Execute + + - block: + - name: Step_1_2 - Add WWNs to DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ drHostWwnsItem.value }}" + hostName: "{{ drHostWwnsItem.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: drHostWwnsItem + + - set_fact: + Step_1_2_Completed: True + + # End Step_1_2 + + # End block + when: Step_1_2_Execute + + - block: + - name: Step_2_1 - Remove DR Test LUNs from Class + debug: + msg: + params: + drTestLunsInTier: "{{ drTestLunsInTier }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/volume/remove_volumes_from_tier.yml" + vars: + volumeIds: "{{ drTestLunsInTierItem.value }}" + loop: "{{ lookup('dict', drTestLunsInTier, wantlist=True) }}" + loop_control: + loop_var: drTestLunsInTierItem + + - set_fact: + Step_2_1_Completed: True + + # End Step_2_1 + + # End block + when: Step_2_1_Execute + + - block: + - name: Step_2_2 - Remove DR Test LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ drTestLunNames }}" + lgName: "{{ drTestLgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_luns_from_lg.yml" + vars: + lgName: "{{ checkedDrLgLunsItem.key }}" + lunNames: "{{ checkedDrLgLunsItem.value | json_query('[*].NAME') }}" + loop: "{{ lookup('dict', checkedDrLgLuns, wantlist=True) }}" + loop_control: + loop_var: checkedDrLgLunsItem + + - set_fact: + Step_2_2_Completed: True + + # End Step_2_2 + + # End block + when: Step_2_2_Execute + + - block: + - name: Step_2_3 - Delete DR Test Snapshot CGs + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + loop: "{{ drTestCgNames }}" + loop_control: + loop_var: drTestCgName + + - set_fact: + Step_2_3_Completed: True + + # End Step_2_3 + + # End block + when: Step_2_3_Execute + + - block: + - name: Step_2_4 - Re-Create DR Test Snapshot CGs + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/create_snapshot_cg.yml" + vars: + pgName: "{{ targetLuns.pgName }}" + cgName: "{{ targetLuns.cgName }}" + snapNames: "{{ targetLuns.lunNames }}" + activate: False + snapDescs: "{{ targetLuns.lunDescs }}" + loop: "{{ targetLunsAll }}" + loop_control: + loop_var: targetLuns + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_luns.yml" + vars: + lunNames: "{{ targetLunNamesAll }}" + + - set_fact: + targetLunIdsAll: "{{ checkedLuns | json_query('[*].ID') }}" + targetLunSectorsAll: "{{ checkedLuns | json_query('[*].CAPACITY') }}" + + - set_fact: + Step_2_4_Completed: True + + # End Step_2_4 + + # End block + when: Step_2_4_Execute + + - block: + - name: Step_2_5 - Add DR Test LUNs to LUN Group + debug: + msg: + params: + luns: "{{ targetLunsAll }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_luns_to_lg.yml" + vars: + lunNames: "{{ targetLuns.lunNames }}" + lgName: "{{ targetLuns.lgName }}" + addLunScsiIds: "{{ targetLuns.lunScsiIds }}" + loop: "{{ targetLunsAll }}" + loop_control: + loop_var: targetLuns + + - set_fact: + Step_2_5_Completed: True + + # End Step_2_5 + + # End block + when: Step_2_5_Execute + + - block: + - name: Step_2_6 - Delete Orphan DR LUNs + debug: + msg: + params: + lunNames: "{{ orphanDrLunNames }}" + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/delete_luns.yml" + vars: + lunNames: "{{ orphanDrLunNames }}" + + - set_fact: + Step_2_6_Completed: True + + # End Step_2_6 + + # End block + when: Step_2_6_Execute + + - block: + - name: Step_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + lunNames: "{{ drTestLunNames }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "delete" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ '_'.join(drTestLunNames[index].split('_')[:4]) }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "{{ drTestLunClass[index] }}" + CAPACITY_GB: "-{{ (drTestLunSectors[index]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ lunId }}" + loop: "{{ drTestLunIds }}" + loop_control: + index_var: index + loop_var: lunId + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "create" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ '_'.join(targetLunNamesAll[index].split('_')[:4]) }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "{{ (targetLunSectorsAll[index]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ lunId }}" + loop: "{{ targetLunIdsAll }}" + loop_control: + index_var: index + loop_var: lunId + when: targetLunIdsAll|default([])|length > 0 + + - set_fact: + Step_3_1_Completed: True + + # End Step_3_1 + + # End block + when: Step_3_1_Execute + + - block: + - name: Step_3_2 - Update Orphan DR LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ orphanDrLunIds }}" + + - include_tasks: update_lun_kpi_table.yml + vars: + TYPE_OF_OPERATION: "delete" + WBE_CODE: "{{ WBE_CODE }}" + TICKET_NUMBER: "{{ TICKET_NUMBER }}" + SYSTEM_NAME: "{{ '_'.join(orphanDrLunNames[index].split('_')[:4]) }}" + SITE: "{{ site }}" + ENVIRONMENT: "{{ osType }}" + STORAGE_CLASS: "" + CAPACITY_GB: "-{{ (orphanDrLunSectors[index]|int / 1024 / 1024 / 2)|int }}" + STORAGE: "{{ deviceName }}" + VDISK_UID: "{{ lunId }}" + with_indexed_items: "{{ orphanDrLunIds }}" + loop: "{{ orphanDrLunIds }}" + loop_control: + index_var: index + loop_var: lunId + + - set_fact: + Step_3_2_Completed: True + + # End Step_3_2 + + # End block + when: Step_3_2_Execute + + # End Steps + rescue: + + # Unable to rollback DR Test CGs, need to manually rollback + + - block: + - name: Rollback_2_4 - Reactivated DR Test Snapshot CG + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/activate_snapshot_cg.yml" + vars: + cgName: "{{ drTestCgName }}" + loop: "{{ drTestCgNames }}" + loop_control: + loop_var: drTestCgName + + - set_fact: + Step_2_3_Rollbacked: True + Step_2_4_Rollbacked: True + + # End Rollback_2_4 + + # End block + when: Step_2_4_Completed + + - block: + - name: Rollback_1_2 - Remove WWNs from DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + hostNames: "{{ drHostNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/remove_ports_from_host.yml" + vars: + wwns: "{{ drHostWwnsItem.value }}" + hostName: "{{ drHostWwnsItem.key }}" + loop: "{{ lookup('dict', drHostWwns, wantlist=True) }}" + loop_control: + loop_var: drHostWwnsItem + + - set_fact: + Step_1_2_Rollbacked: True + + # End Rollback_1_2 + + # End block + when: Step_1_2_Completed + + - block: + - name: Rollback_1_1 - Add WWNs to DR Test Hosts + debug: + msg: + params: + wwns: "{{ drTestHostWwns }}" + hostNames: "{{ drTestHostNames }}" + device: "{{ deviceName }}" + + - include_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/add_ports_to_host.yml" + vars: + wwns: "{{ drTestHostWwnsItem.value }}" + hostName: "{{ drTestHostWwnsItem.key }}" + loop: "{{ lookup('dict', drTestHostWwns, wantlist=True) }}" + loop_control: + loop_var: drTestHostWwnsItem + + - set_fact: + Step_1_1_Rollbacked: True + + # End Rollback_1_1 + + # End block + when: Step_1_1_Completed + + # End Rollbacks + always: + + - name: Final_Step_1 - Sync DR Device + debug: + msg: + device: "{{ deviceName }}" + + - import_tasks: "{{GLOBAL.baseDir}}/task/storage/sync_storage.yml" + + # End Final Steps + + # End Workflow + + + - block: + + # Begin Validate Results + + - name: Result_1_1 - Remove WWNs from DR Test Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + hostNames: "{{ drTestHostNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_1_1_Completed }}" + rollbacked: "{{ Step_1_1_Rollbacked }}" + failed_when: Step_1_1_Completed|bool == False + when: Step_1_1_Execute + + - name: Result_1_2 - Add WWNs to DR Hosts + debug: + msg: + params: + wwns: "{{ drHostWwns }}" + hostNames: "{{ drHostNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_1_2_Completed }}" + rollbacked: "{{ Step_1_2_Rollbacked }}" + failed_when: Step_1_2_Completed|bool == False + when: Step_1_2_Execute + + - name: Result_2_1 - Remove DR Test LUNs from Class + debug: + msg: + params: + drTestLunsInTier: "{{ drTestLunsInTier }}" + result: + succeeded: "{{ Step_2_1_Completed }}" + rollbacked: "{{ Step_2_1_Rollbacked }}" + failed_when: Step_2_1_Completed|bool == False + when: Step_2_1_Execute + + - name: Result_2_2 - Remove DR Test LUNs from LUN Group + debug: + msg: + params: + lunNames: "{{ drTestLunNames }}" + lgNames: "{{ drTestLgNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_2_Completed }}" + rollbacked: "{{ Step_2_2_Rollbacked }}" + failed_when: Step_2_2_Completed|bool == False + when: Step_2_2_Execute + + - name: Result_2_3 - Delete DR Test Snapshot CG + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_3_Completed }}" + rollbacked: "{{ Step_2_3_Rollbacked }}" + failed_when: Step_2_3_Completed|bool == False + when: Step_2_3_Execute + + - name: Result_2_4 - Re-Create DR Test Snapshots + debug: + msg: + params: + cgNames: "{{ drTestCgNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_4_Completed }}" + rollbacked: "{{ Step_2_4_Rollbacked }}" + failed_when: Step_2_4_Completed|bool == False + when: Step_2_4_Execute + + - name: Result_2_5 - Add DR Test LUNs to LUN Group + debug: + msg: + params: + luns: "{{ targetLunsAll }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_5_Completed }}" + rollbacked: "{{ Step_2_5_Rollbacked }}" + failed_when: Step_2_5_Completed|bool == False + when: Step_2_5_Execute + + - name: Result_2_6 - Delete Orphan DR LUNs + debug: + msg: + params: + lunNames: "{{ orphanDrLunNames }}" + device: "{{ deviceName }}" + result: + succeeded: "{{ Step_2_6_Completed }}" + rollbacked: "{{ Step_2_6_Rollbacked }}" + failed_when: Step_2_6_Completed|bool == False + when: Step_2_6_Execute + + - name: Result_3_1 - Update DR Test LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ drTestLunIds }}" + result: + succeeded: "{{ Step_3_1_Completed }}" + failed_when: Step_3_1_Completed|bool == False + when: Step_3_1_Execute + + - name: Result_3_2 - Update Orphan DR LUNs to KPI table + debug: + msg: + params: + lunIds: "{{ orphanDrLunIds }}" + result: + succeeded: "{{ Step_3_2_Completed }}" + failed_when: Step_3_2_Completed|bool == False + when: Step_3_2_Execute + diff --git a/rundeck/workflow/project001/loop_helper/50_exsit_dr_test_luns_loop_helper.yml b/rundeck/workflow/project001/loop_helper/50_exsit_dr_test_luns_loop_helper.yml new file mode 100644 index 0000000..6ecffe6 --- /dev/null +++ b/rundeck/workflow/project001/loop_helper/50_exsit_dr_test_luns_loop_helper.yml @@ -0,0 +1,34 @@ +# Build dr test lun parameter, cooperator with nested loop +# +# Required parameters: +# drTestLun: # Lun Info +# checkedHostLuns: # Host Lun info +# +# Generated variables: +# existDrTestLuns: # Exist Dr Test Lun Parameter Info, need clean before used. +- set_fact: + filteredHostLun: "{{ item.value | json_query(queryLun) | first }}" + filteredLgName: "{{ item.key }}" + vars: + queryLun: "[? lunName == '{{drTestLun.NAME}}']" + with_dict: "{{ checkedHostLuns }}" + when: + - item.value|length > 0 + - item.value|json_query(queryLun)|length > 0 + +- name: Check Filtered Host Lun + debug: + msg: "{{checkedHostLuns}}" + failed_when: filteredHostLun|default(none) is none + +- set_fact: + existDrTestLun: + lunName: "{{ drTestLun.NAME }}" + lunDesc: "{{ drTestLun.DESCRIPTION }}" + lunScsiId: "{{ filteredHostLun.hostLunId }}" + lunSector: "{{ drTestLun.USERCAPACITY }}" + lgName: "{{ filteredLgName }}" + +- set_fact: + existDrTestLuns: "{{ existDrTestLuns + [existDrTestLun] }}" + diff --git a/rundeck/workflow/project001/loop_helper/56_target_luns_loop_helper.yml b/rundeck/workflow/project001/loop_helper/56_target_luns_loop_helper.yml new file mode 100644 index 0000000..159329e --- /dev/null +++ b/rundeck/workflow/project001/loop_helper/56_target_luns_loop_helper.yml @@ -0,0 +1,40 @@ +# Build dr test lun parameter classified by lun group, cooperator with nested loop +# +# Required parameters: +# luns: # Lun Info +# hostLuns: # Host Lun Info +# pgName: # Protection Group Name +# cgName: # Snapshot Consistence Group Name +# lgName: # Lun Group Name +# className: # Service Level Name +# +# Generated variables: +# targetLunsAll: # Target Dr Test Lun Parameter Info, need clean before used. + +- set_fact: + existDrLunNames: "{{ luns | json_query('[*].NAME') }}" + lunScsiIds: "{{ hostLuns | json_query('[*].hostLunId') }}" + targetLunNames: [] + targetLunDescs: "{{ luns | json_query('[*].DESCRIPTION') }}" + +- set_fact: + targetLunNames: "{{ targetLunNames + [ targetLunName ] }}" + vars: + targetLunName: "{% set field = item.split('_') %}{% set output = field[:3] + ['3', field[4], className, '00', '00'] + field[8:-1] + [ field[-1][0] + '3' ] %}{{'_'.join(output)}}" + with_items: + - "{{ existDrLunNames }}" + +- set_fact: + targetLuns: + lunNames: "{{ targetLunNames }}" + lunDescs: "{{ targetLunDescs }}" + lunScsiIds: "{{ lunScsiIds }}" + pgName: "{{ pgName }}" + cgName: "{{ cgName }}" + hostName: "{{ hostName }}" + lgName: "{{ lgName }}" + +- set_fact: + targetLunsAll: "{{ targetLunsAll + [targetLuns] }}" + + diff --git a/rundeck/workflow/project001/update_lun_kpi_table.yml b/rundeck/workflow/project001/update_lun_kpi_table.yml index 42d358a..4918c36 100644 --- a/rundeck/workflow/project001/update_lun_kpi_table.yml +++ b/rundeck/workflow/project001/update_lun_kpi_table.yml @@ -11,8 +11,17 @@ # (e.g. in case of expansion the value is the delta between the current size and the expanded size) # STORAGE: the name of the storage in which the Volume is created/modified/deleted # VDISK_UID: the Universal Unique ID (UUID) of the Volume created/modified/deleted +# Country the country code +# Job_User the user name -- local_action: command psql automation -c "insert into activity(TYPE_OF_OPERATION,WBE_CODE,TICKET_NUMBER,SYSTEM_NAME,SITE,ENVIRONMENT,STORAGE_CLASS,CAPACITY_GB,STORAGE,VDISK_UID) values('{{TYPE_OF_OPERATION}}','{{WBE_CODE}}','{{TICKET_NUMBER}}','{{SYSTEM_NAME}}','{{SITE}}','{{ENVIRONMENT}}','{{STORAGE_CLASS}}','{{CAPACITY_GB}}','{{STORAGE}}','{{VDISK_UID}}')" +- local_action: command psql automation -c "insert into activity(TYPE_OF_OPERATION,WBE_CODE,TICKET_NUMBER,SYSTEM_NAME,SITE,ENVIRONMENT,STORAGE_CLASS,CAPACITY_GB,STORAGE,VDISK_UID,COUNTRY,\"USER\") values('{{TYPE_OF_OPERATION}}','{{WBE_CODE}}','{{TICKET_NUMBER}}','{{SYSTEM_NAME}}','{{SITE}}','{{ENVIRONMENT}}','{{STORAGE_CLASS}}','{{CAPACITY_GB}}','{{STORAGE}}','{{VDISK_UID}}','{{Country|default("")}}','{{Job_User}}')" -- local_action: command /opt/mssql-tools/bin/sqlcmd -S "{{KPIDB.host}}" -U "{{KPIDB.user}}" -P "{{KPIDB.pswd}}" -d "{{KPIDB.database}}" -Q "insert into {{KPIDB.table}}(TYPE_OF_OPERATION,WBE_CODE,TICKET_NUMBER,SYSTEM_NAME,SITE,ENVIRONMENT,STORAGE_CLASS,CAPACITY_GB,STORAGE,VDISK_UID) values('{{TYPE_OF_OPERATION}}','{{WBE_CODE}}','{{TICKET_NUMBER}}','{{SYSTEM_NAME}}','{{SITE}}','{{ENVIRONMENT}}','{{STORAGE_CLASS}}','{{CAPACITY_GB}}','{{STORAGE}}','{{VDISK_UID}}')" - when: KPIDB.enable|bool == True \ No newline at end of file +- block: + - local_action: command psql -qAt -d automation -c "select max(GSA_ID) AS NEXTID from activity" + register: KPI_NEXTID + + - local_action: command date +"%Y-%m-%d %H:%M:%S" + register: KPI_TIMESTAMP + + - local_action: command /opt/mssql-tools/bin/sqlcmd -S "{{KPIDB.host}}" -U "{{KPIDB.user}}" -P "{{KPIDB.pswd}}" -d "{{KPIDB.database}}" -Q "insert into {{KPIDB.table}}(ID,OPERATION_TYPE,WBE_CODE,TICKET_NUMBER,SYSTEM_NAME,SITE,ENVIRONMENT,STORAGE_CLASS,CAPACITY_GB,STORAGE,VDISK_UID,COUNTRY,[User],[Timestamp]) values('{{KPI_NEXTID.stdout}}','{{TYPE_OF_OPERATION}}','{{WBE_CODE}}','{{TICKET_NUMBER}}','{{SYSTEM_NAME}}','{{SITE}}','{{ENVIRONMENT}}','{{STORAGE_CLASS}}','{{CAPACITY_GB}}','{{STORAGE}}','{{VDISK_UID}}','{{Country|default("")}}','{{Job_User}}','{{KPI_TIMESTAMP.stdout}}')" + when: KPIDB.enable|bool == True diff --git a/service/dj_data_service.py b/service/dj_data_service.py index ea7cf2e..86b76a8 100644 --- a/service/dj_data_service.py +++ b/service/dj_data_service.py @@ -1,31 +1,34 @@ #!/usr/bin/python +import argparse +import ast import json -import yaml +import logging import os import time -import sys -import logging -import socket -import ast -import argparse import urllib -import requests +from operator import itemgetter + import BaseHTTPServer +import requests +import yaml from requests.packages.urllib3.exceptions import InsecureRequestWarning -from operator import itemgetter + +import log + class DJRestAPI(): - def __init__(self, host, user, pswd, port = 26335, verify = False, timeout = 10): + def __init__(self, host, user, pswd, port=26335, verify=False, timeout=10): self.user = user self.pswd = pswd self.verify = verify self.timeout = timeout self.url = "https://%s:%d" % (host, port) self.headers = { - 'Content-Type': 'application/json;charset=utf8', + 'Content-Type': 'application/json;charset=utf8', 'Accept': 'application/json' } self.connected = False + # end __init__ def login(self): @@ -35,17 +38,23 @@ def login(self): 'userName': self.user, 'value': self.pswd } - response = requests.put(url, json = data, headers = self.headers, verify = self.verify, timeout = self.timeout) - logging.debug('DJRestAPI.login - %d %s' %(response.status_code, url) ) + response = requests.put(url, json=data, headers=self.headers, + verify=self.verify, timeout=self.timeout) + logging.debug('DJRestAPI.login - %d %s' % (response.status_code, url)) body = response.json() if response.status_code == 200: self.headers['X-Auth-Token'] = body['accessSession'] self.connected = True else: - logging.error('DJRestAPI.login - %d %s' %( response.status_code, json.dumps(body, sort_keys=True, indent=4, ensure_ascii=False) ) ) + logging.error('DJRestAPI.login - %d %s' % (response.status_code, + json.dumps(body, + sort_keys=True, + indent=4, + ensure_ascii=False))) self.connected = False del self.headers['X-Auth-Token'] + # end login def get(self, uri): @@ -53,15 +62,18 @@ def get(self, uri): self.login() url = self.url + uri - response = requests.get(url, headers = self.headers, verify = self.verify, timeout = self.timeout) - logging.debug('DJRestAPI.get - %d %s' %( response.status_code, url) ) + response = requests.get(url, headers=self.headers, verify=self.verify, + timeout=self.timeout) + logging.debug('DJRestAPI.get - %d %s' % (response.status_code, url)) # token expired, login and try again if response.status_code == 401: self.login() - response = requests.get(url, headers = self.headers, verify = self.verify, timeout = self.timeout) + response = requests.get(url, headers=self.headers, + verify=self.verify, timeout=self.timeout) return response + # end get def put(self, uri, data): @@ -69,31 +81,37 @@ def put(self, uri, data): self.login() url = self.url + uri - response = requests.put(url, json = data, headers = self.headers, verify = self.verify, timeout = self.timeout) - logging.debug('DJRestAPI.put - %d %s' %( response.status_code, url) ) + response = requests.put(url, json=data, headers=self.headers, + verify=self.verify, timeout=self.timeout) + logging.debug('DJRestAPI.put - %d %s' % (response.status_code, url)) # token expired, login and try again if response.status_code == 401: self.login() - response = requests.put(url, json = data, headers = self.headers, verify = self.verify, timeout = self.timeout) + response = requests.put(url, json=data, headers=self.headers, + verify=self.verify, timeout=self.timeout) return response + # end put def post(self, uri, data): if self.connected == False: self.login() - + url = self.url + uri - response = requests.post(url, json = data, headers = self.headers, verify = self.verify, timeout = self.timeout) - logging.debug('DJRestAPI.post - %d %s' %( response.status_code, url) ) + response = requests.post(url, json=data, headers=self.headers, + verify=self.verify, timeout=self.timeout) + logging.debug('DJRestAPI.post - %d %s' % (response.status_code, url)) # token expired, login and try again if response.status_code == 401: self.login() - response = requests.put(url, json = data, headers = self.headers, verify = self.verify, timeout = self.timeout) + response = requests.put(url, json=data, headers=self.headers, + verify=self.verify, timeout=self.timeout) return response + # end post def delete(self, uri): @@ -101,27 +119,33 @@ def delete(self, uri): self.login() url = self.url + uri - response = requests.delete(url, headers = self.headers, verify = self.verify, timeout = self.timeout) - logging.debug('DJRestAPI.delete - %d %s' %( response.status_code, url) ) + response = requests.delete(url, headers=self.headers, + verify=self.verify, timeout=self.timeout) + logging.debug('DJRestAPI.delete - %d %s' % (response.status_code, url)) # token expired, login and try again if response.status_code == 401: self.login() - response = requests.delete(url, headers = self.headers, verify = self.verify, timeout = self.timeout) + response = requests.delete(url, headers=self.headers, + verify=self.verify, timeout=self.timeout) return response + # end delete def logout(self): if self.connected == True: - self.delete( '/rest/plat/smapp/v1/sessions' ) + self.delete('/rest/plat/smapp/v1/sessions') self.connected = False del self.headers['X-Auth-Token'] + # end logout def __del__(self): self.logout() # end __del__ + + # end DJRestAPI # Disable https warnings @@ -134,31 +158,27 @@ def __del__(self): for file in files: if file.endswith(".yml"): fd = open(os.path.join(root, file)) - VARS.update( yaml.load(fd) ) + VARS.update(yaml.load(fd)) fd.close() -# Set logging format -logging.basicConfig( - format = VARS['LOGGING']['format'], - level = VARS['LOGGING']['levelno'][ VARS['LOGGING']['level'] ], - datefmt = VARS['LOGGING']['datefmt'] -) - # load DJ credential -DJAPI = DJRestAPI(VARS['DJ']['host'], VARS['DJ']['user'], VARS['DJ']['pswd'], VARS['DJ']['port']) +DJAPI = DJRestAPI(VARS['DJ']['host'], VARS['DJ']['user'], VARS['DJ']['pswd'], + VARS['DJ']['port']) + class DjDataService(BaseHTTPServer.BaseHTTPRequestHandler): - - def _response(self, data, code = 200): - self.send_response( code ) - self.send_header('Content-type','application/json;charset=utf8') - self.send_header('Accept','application/json') + + def _response(self, data, code=200): + self.send_response(code) + self.send_header('Content-type', 'application/json;charset=utf8') + self.send_header('Accept', 'application/json') self.end_headers() - self.wfile.write(json.dumps(data, sort_keys=False, indent=4, ensure_ascii=False).encode('utf-8')) + self.wfile.write(json.dumps(data, sort_keys=False, indent=4, + ensure_ascii=False).encode('utf-8')) # /rest/data/v1/echo?k1=msg1&k2=msg2 def data_echo(self): - assert self.path.startswith( VARS['DJDATASERVICE']['API']['echo'] + '?' ) + assert self.path.startswith(VARS['DJDATASERVICE']['API']['echo'] + '?') query = urllib.unquote(self.path).split('?') @@ -166,25 +186,27 @@ def data_echo(self): for param in query[1].split('&'): kv = param.split('=') if len(kv) == 2: - resp.append( { 'name': kv[1], 'value': kv[0] } ) - + resp.append({'name': kv[1], 'value': kv[0]}) + if len(resp) == 0: self._response({}, 404) else: - self._response(sorted(resp, key=itemgetter('name')) ) + self._response(sorted(resp, key=itemgetter('name'))) + # end /rest/data/v1/echo # /rest/data/v1/enum/{type}?nameAttr=desc&valueAttr=key&filter={"attr1":"value1","attr2":"value2"} def data_enum(self): - assert self.path.startswith( VARS['DJDATASERVICE']['API']['enum'] + '/' ) + assert self.path.startswith(VARS['DJDATASERVICE']['API']['enum'] + '/') query = urllib.unquote(self.path).split('?') uri = query[0] - enumType = uri[uri.rfind('/')+1:] + enumType = uri[uri.rfind('/') + 1:] if enumType not in VARS: - self._response([{'name': VARS['DEFAULT']['noneName'],'value': VARS['DEFAULT']['noneValue']}]) + self._response([{'name': VARS['DEFAULT']['noneName'], + 'value': VARS['DEFAULT']['noneValue']}]) if len(query) == 2: params = dict(qc.split('=') for qc in query[1].split('&')) @@ -198,14 +220,14 @@ def data_enum(self): nameAttrs = params['nameAttr'].split(':') nameAttr = nameAttrs[0] if len(nameAttrs) > 1: - nameField = int(nameAttrs[1]) + nameField = int(nameAttrs[1]) if len(nameAttrs) > 2: nameFieldStart = int(nameAttrs[2]) if len(nameAttrs) > 3: nameFieldEnd = int(nameAttrs[3]) else: nameAttr = 'desc' - + valueField = None valueFieldStart = None valueFieldEnd = None @@ -278,7 +300,8 @@ def data_enum(self): if nameField is None: name = VARS[enumType][key][nameAttr] else: - name = VARS[enumType][key][nameAttr].split(nameSplit)[nameField][nameFieldStart:nameFieldEnd] + name = VARS[enumType][key][nameAttr].split(nameSplit)[ + nameField][nameFieldStart:nameFieldEnd] if valueAttr == 'key': value = key @@ -286,40 +309,46 @@ def data_enum(self): if valueField is None: value = VARS[enumType][key][valueAttr] else: - value = VARS[enumType][key][valueAttr].split(valueSplit)[valueField][valueFieldStart:valueFieldEnd] + value = VARS[enumType][key][valueAttr].split(valueSplit)[ + valueField][valueFieldStart:valueFieldEnd] matched = True - for attr,expect in filterAttrs.items(): + for attr, expect in filterAttrs.items(): if (attr == 'key') and (key == expect): break - if (attr not in VARS[enumType][key]) or (VARS[enumType][key][attr] != expect): + if (attr not in VARS[enumType][key]) or ( + VARS[enumType][key][attr] != expect): matched = False break # end filter if matched == True and len(name) > 0: - resp.append( { 'name': '{}{}{}'.format(namePrefix,name,nameSuffix), 'value': '{}{}{}'.format(valuePrefix,value,valueSuffix) } ) + resp.append( + {'name': '{}{}{}'.format(namePrefix, name, nameSuffix), + 'value': '{}{}{}'.format(valuePrefix, value, valueSuffix)}) # end keys if len(resp) == 0: self._response([{'name': nameDefault, 'value': valueDefault}]) else: - self._response(sorted(resp, key=itemgetter('name')) ) - # end /rest/data/v1/enum + self._response(sorted(resp, key=itemgetter('name'))) + # end /rest/data/v1/enum # /rest/data/v1/search/{objtype}?pageNo=1&pageSize=20&orderBy=last_Modified&orderAsc=False&nameAttr=name&valueAttr=id&descAttr=CAPACITY&descDivide=2097152&descUnit=GiB&condition={see cmdb api}&changedBefore=&changedAfter= def data_search(self): - assert self.path.startswith( VARS['DJDATASERVICE']['API']['search'] + '/' ) + assert self.path.startswith( + VARS['DJDATASERVICE']['API']['search'] + '/') query = urllib.unquote(self.path).split('?') uri = query[0] - objType = uri[uri.rfind('/')+1:] - + objType = uri[uri.rfind('/') + 1:] + if objType not in VARS['INVENTORY']: - logging.error( 'DjDataService.data_search - unsupported object type: ' + objType ) + logging.error( + 'DjDataService.data_search - unsupported object type: ' + objType) self._response({}, 404) return @@ -345,18 +374,19 @@ def data_search(self): pageSize = 20 djUri += '&pageSize=%d' % (pageSize) - + if 'orderBy' in params: orderBy = params['orderBy'] else: orderBy = 'last_Modified' if 'orderAsc' in params: - orderAsc = bool( params['orderAsc'] ) + orderAsc = bool(params['orderAsc']) else: orderAsc = False - djUri += '&orderBy=[{\"field\":\"%s\",\"asc\":\"%s\"}]' % (orderBy, orderAsc) + djUri += '&orderBy=[{\"field\":\"%s\",\"asc\":\"%s\"}]' % ( + orderBy, orderAsc) nameField = None nameFieldStart = None @@ -365,14 +395,14 @@ def data_search(self): nameAttrs = params['nameAttr'].split(':') nameAttr = nameAttrs[0] if len(nameAttrs) > 1: - nameField = int(nameAttrs[1]) + nameField = int(nameAttrs[1]) if len(nameAttrs) > 2: nameFieldStart = int(nameAttrs[2]) if len(nameAttrs) > 3: nameFieldEnd = int(nameAttrs[3]) else: nameAttr = 'name' - + valueField = None valueFieldStart = None valueFieldEnd = None @@ -419,12 +449,12 @@ def data_search(self): valueSuffix = '' if 'nameUnique' in params: - nameUnique = bool( params['nameUnique'] ) + nameUnique = bool(params['nameUnique']) else: nameUnique = False if 'valueUnique' in params: - valueUnique = bool( params['valueUnique'] ) + valueUnique = bool(params['valueUnique']) else: valueUnique = False @@ -450,12 +480,13 @@ def data_search(self): if 'descUnit' in params: descUnit = params['descUnit'] - contentSelector = [nameAttr,valueAttr] + contentSelector = [nameAttr, valueAttr] if (descAttr is not None) and (descAttr not in contentSelector): contentSelector.append(descAttr) - djUri += '&contentSelector=%s' % ( json.dumps(contentSelector, ensure_ascii=False) ) + djUri += '&contentSelector=%s' % ( + json.dumps(contentSelector, ensure_ascii=False)) condition = {} @@ -468,15 +499,23 @@ def data_search(self): currentTime = time.time() if 'changedBefore' in params: - last_Modified_Before = int( ( currentTime - int(params['changedBefore']) ) * 1000 ) - condition['constraint'].append( {"logOp":"and","simple":{"name":"last_Modified","operator":"less than","value":last_Modified_Before}} ) + last_Modified_Before = int( + (currentTime - int(params['changedBefore'])) * 1000) + condition['constraint'].append({"logOp": "and", + "simple": {"name": "last_Modified", + "operator": "less than", + "value": last_Modified_Before}}) if 'changedAfter' in params: - last_Modified_After = int( ( currentTime - int(params['changedAfter']) ) * 1000 ) - condition['constraint'].append( {"logOp":"and","simple":{"name":"last_Modified","operator":"not less than","value":last_Modified_After}} ) + last_Modified_After = int( + (currentTime - int(params['changedAfter'])) * 1000) + condition['constraint'].append({"logOp": "and", + "simple": {"name": "last_Modified", + "operator": "not less than", + "value": last_Modified_After}}) + + djUri += '&condition=%s' % (json.dumps(condition, ensure_ascii=False)) - djUri += '&condition=%s' %( json.dumps(condition, ensure_ascii=False) ) - # Forward request to DJ API response = DJAPI.get(djUri); body = response.json() @@ -489,42 +528,54 @@ def data_search(self): if nameField is None: name = obj[nameAttr] else: - name = obj[nameAttr].split(nameSplit)[nameField][nameFieldStart:nameFieldEnd] + name = obj[nameAttr].split(nameSplit)[nameField][ + nameFieldStart:nameFieldEnd] if descAttr is not None: desc = obj[descAttr] if descDivide is not None: desc = float(desc) / float(descDivide) - name = '{} ( {} {} )'.format(name,desc,descUnit) + name = '{} ( {} {} )'.format(name, desc, descUnit) if valueField is None: value = obj[valueAttr] else: - value = obj[valueAttr].split(valueSplit)[valueField][valueFieldStart:valueFieldEnd] - - if len(name) > 0 and ( (nameUnique == True and name not in names) or (nameUnique == False) ) and ( (valueUnique == True and value not in values) or (valueUnique == False) ): + value = obj[valueAttr].split(valueSplit)[valueField][ + valueFieldStart:valueFieldEnd] + + if len(name) > 0 and ( + (nameUnique == True and name not in names) or ( + nameUnique == False)) and ( + (valueUnique == True and value not in values) or ( + valueUnique == False)): names.append(name) values.append(value) - resp.append( { 'name': '{}{}{}'.format(namePrefix,name,nameSuffix), 'value': '{}{}{}'.format(valuePrefix,value,valueSuffix) } ) + resp.append({'name': '{}{}{}'.format(namePrefix, name, + nameSuffix), + 'value': '{}{}{}'.format(valuePrefix, + value, + valueSuffix)}) # end objList # end body if len(resp) == 0: self._response([{'name': nameDefault, 'value': valueDefault}]) else: - self._response(sorted(resp, key=itemgetter('name')) ) + self._response(sorted(resp, key=itemgetter('name'))) + # end search # /rest/data/v1/join/{objtype}?pageNo=1&pageSize=20&orderBy=last_Modified&orderAsc=False&nameAttr=name&valueAttr=id&descAttr=CAPACITY&descDivide=2097152&descUnit=GiB&relations=[{"obj":"releventObjType","condition":"queryCondition"},...]&joins=[{"joinAttr","sourceAttribute","obj":"{targetObjectType}","attr":"targetAttribute","condition":"targetObjectCondition"},...] def data_join(self): - assert self.path.startswith( VARS['DJDATASERVICE']['API']['join'] + '/' ) + assert self.path.startswith(VARS['DJDATASERVICE']['API']['join'] + '/') query = urllib.unquote(self.path).split('?') uri = query[0] - objType = uri[uri.rfind('/')+1:] + objType = uri[uri.rfind('/') + 1:] if objType not in VARS['INVENTORY']: - logging.error( 'DjDataService.data_join - unsupported object type: ' + objType ) + logging.error( + 'DjDataService.data_join - unsupported object type: ' + objType) self._response({}, 404) return @@ -550,18 +601,19 @@ def data_join(self): pageSize = 20 djUri += '&pageSize=%d' % (pageSize) - + if 'orderBy' in params: orderBy = params['orderBy'] else: orderBy = 'last_Modified' if 'orderAsc' in params: - orderAsc = bool( params['orderAsc'] ) + orderAsc = bool(params['orderAsc']) else: orderAsc = False - djUri += '&orderBy=[{\"field\":\"%s\",\"asc\":\"%s\"}]' % (orderBy, orderAsc) + djUri += '&orderBy=[{\"field\":\"%s\",\"asc\":\"%s\"}]' % ( + orderBy, orderAsc) nameField = None nameFieldStart = None @@ -570,14 +622,14 @@ def data_join(self): nameAttrs = params['nameAttr'].split(':') nameAttr = nameAttrs[0] if len(nameAttrs) > 1: - nameField = int(nameAttrs[1]) + nameField = int(nameAttrs[1]) if len(nameAttrs) > 2: nameFieldStart = int(nameAttrs[2]) if len(nameAttrs) > 3: nameFieldEnd = int(nameAttrs[3]) else: nameAttr = 'name' - + valueField = None valueFieldStart = None valueFieldEnd = None @@ -645,12 +697,13 @@ def data_join(self): if 'descUnit' in params: descUnit = params['descUnit'] - contentSelector = [nameAttr,valueAttr] + contentSelector = [nameAttr, valueAttr] if (descAttr is not None) and (descAttr not in contentSelector): contentSelector.append(descAttr) - djUri += '&contentSelector=%s' % ( json.dumps(contentSelector, ensure_ascii=False) ) + djUri += '&contentSelector=%s' % ( + json.dumps(contentSelector, ensure_ascii=False)) # more conditions if 'condition' in params: @@ -660,29 +713,39 @@ def data_join(self): if 'relationConstraint' not in condition: condition['relationConstraint'] = [] else: - condition = {"constraint":[],"relationConstraint":[]} + condition = {"constraint": [], "relationConstraint": []} if 'joins' in params: joins = ast.literal_eval(params['joins']) for join in joins: joinObjType = join['obj'] - joinCondition = json.dumps(join['condition'], ensure_ascii=False) + joinCondition = json.dumps(join['condition'], + ensure_ascii=False) joinAttr = join['joinAttr'] if joinObjType not in VARS['INVENTORY']: - logging.error( 'DjDataService.data_search - unsupported object type: ' + joinObjType ) + logging.error( + 'DjDataService.data_search - unsupported object type: ' + joinObjType) self._response({}, 404) return joinClassName = VARS['INVENTORY'][joinObjType]['className'] - joinUri = '%s/%s?condition=%s' %(VARS['DJSERVICE']['API']['instances'], joinClassName, joinCondition) - joinResponse = DJAPI.get( joinUri ); - logging.debug( 'DjDataService.data_join - %d %s' %(joinResponse.status_code, joinUri) ) + joinUri = '%s/%s?condition=%s' % ( + VARS['DJSERVICE']['API']['instances'], joinClassName, + joinCondition) + joinResponse = DJAPI.get(joinUri); + logging.debug('DjDataService.data_join - %d %s' % ( + joinResponse.status_code, joinUri)) joinBody = joinResponse.json() - if (joinResponse.status_code == 200) and ('totalNum' in joinBody) and (joinBody['totalNum'] > 0): + if (joinResponse.status_code == 200) and ( + 'totalNum' in joinBody) and (joinBody['totalNum'] > 0): joinValues = [] for joinObj in joinBody['objList']: joinValues.append(joinObj[join['attr']]) if len(joinValues) > 0: - condition['constraint'].append( {"logOp":"and","simple":{"name":joinAttr,"operator":"in","value":joinValues}} ) + condition['constraint'].append({"logOp": "and", + "simple": { + "name": joinAttr, + "operator": "in", + "value": joinValues}}) # end joinValues # end joinResponse # end joins @@ -691,33 +754,57 @@ def data_join(self): relations = ast.literal_eval(params['relations']) for relation in relations: relObjType = relation['obj'] - if (relObjType not in VARS['INVENTORY']) or (relObjType not in VARS['INVENTORY'][objType]['relations']): - logging.error( 'DjDataService.data_search - unsupported object type: ' + relObjType ) + if (relObjType not in VARS['INVENTORY']) or ( + relObjType not in VARS['INVENTORY'][objType][ + 'relations']): + logging.error( + 'DjDataService.data_search - unsupported object type: ' + relObjType) self._response({}, 404) return relClassName = VARS['INVENTORY'][relObjType]['className'] - relationName = VARS['INVENTORY'][objType]['relations'][relObjType]['relationName'] - isSourceObj = VARS['INVENTORY'][objType]['relations'][relObjType]['source'] - relCondition = json.dumps(relation['condition'], ensure_ascii=False) - relUri = '%s/%s?condition=%s' %(VARS['DJSERVICE']['API']['instances'], relClassName, relCondition) + relationName = \ + VARS['INVENTORY'][objType]['relations'][relObjType][ + 'relationName'] + isSourceObj = \ + VARS['INVENTORY'][objType]['relations'][relObjType][ + 'source'] + relCondition = json.dumps(relation['condition'], + ensure_ascii=False) + relUri = '%s/%s?condition=%s' % ( + VARS['DJSERVICE']['API']['instances'], relClassName, + relCondition) relResponse = DJAPI.get(relUri) - logging.debug( 'DjDataService.data_join - %d %s' %(relResponse.status_code, relUri) ) + logging.debug('DjDataService.data_join - %d %s' % ( + relResponse.status_code, relUri)) relBody = relResponse.json() - if (relResponse.status_code == 200) and ('totalNum' in relBody) and (relBody['totalNum'] > 0): + if (relResponse.status_code == 200) and ( + 'totalNum' in relBody) and (relBody['totalNum'] > 0): relValues = [] for relObj in relBody['objList']: relValues.append(relObj['id']) if len(relValues) > 0: if isSourceObj == True: - condition['relationConstraint'].append( {"logOp":"and","relationName":relationName,"sourceInstance":"false","constraint":[{"simple":{"name":"source_Instance_Id","operator":"in","value":relValues}}]} ) + condition['relationConstraint'].append( + {"logOp": "and", "relationName": relationName, + "sourceInstance": "false", "constraint": [{ + "simple": { + "name": "source_Instance_Id", + "operator": "in", + "value": relValues}}]}) else: - condition['relationConstraint'].append( {"logOp":"and","relationName":relationName,"sourceInstance":"true","constraint":[{"simple":{"name":"target_Instance_Id","operator":"in","value":relValues}}]} ) + condition['relationConstraint'].append( + {"logOp": "and", "relationName": relationName, + "sourceInstance": "true", "constraint": [{ + "simple": { + "name": "target_Instance_Id", + "operator": "in", + "value": relValues}}]}) # end relValues # end relResponse # end relations - djUri += ( '&condition=' + json.dumps(condition, ensure_ascii=False) ) - + djUri += ('&condition=' + json.dumps(condition, ensure_ascii=False)) + # Forward request to DJ API response = DJAPI.get(djUri); body = response.json() @@ -728,39 +815,47 @@ def data_join(self): if nameField is None: name = obj[nameAttr] else: - name = obj[nameAttr].split(nameSplit)[nameField][nameFieldStart:nameFieldEnd] + name = obj[nameAttr].split(nameSplit)[nameField][ + nameFieldStart:nameFieldEnd] if descAttr is not None: desc = obj[descAttr] if descDivide is not None: desc = float(desc) / float(descDivide) - name = '{} ( {} {} )'.format(name,desc,descUnit) + name = '{} ( {} {} )'.format(name, desc, descUnit) if valueField is None: value = obj[valueAttr] else: - value = obj[valueAttr].split(valueSplit)[valueField][valueFieldStart:valueFieldEnd] + value = obj[valueAttr].split(valueSplit)[valueField][ + valueFieldStart:valueFieldEnd] if len(name) > 0: - resp.append( { 'name': '{}{}{}'.format(namePrefix,name,nameSuffix), 'value': '{}{}{}'.format(valuePrefix,value,valueSuffix) } ) - + resp.append({'name': '{}{}{}'.format(namePrefix, name, + nameSuffix), + 'value': '{}{}{}'.format(valuePrefix, + value, + valueSuffix)}) + if len(resp) == 0: self._response([{'name': nameDefault, 'value': valueDefault}]) else: - self._response(sorted(resp, key=itemgetter('name')) ) + self._response(sorted(resp, key=itemgetter('name'))) + # end join # /rest/data/v1/split/{objtype}?valueAttr=name&valueSplit=_&bypass=bypass&match=match&selected=true&condition={see cmdb api} def data_split(self): - assert self.path.startswith( VARS['DJDATASERVICE']['API']['split'] + '/' ) + assert self.path.startswith(VARS['DJDATASERVICE']['API']['split'] + '/') query = urllib.unquote(self.path).split('?') uri = query[0] - objType = uri[uri.rfind('/')+1:] - + objType = uri[uri.rfind('/') + 1:] + if objType not in VARS['INVENTORY']: - logging.error( 'DjDataService.data_split - unsupported object type: ' + objType ) + logging.error( + 'DjDataService.data_split - unsupported object type: ' + objType) self._response({}, 404) return @@ -808,11 +903,11 @@ def data_split(self): else: selected = True - djUri += ( '?contentSelector=[\"%s\"]' % (valueAttr) ) + djUri += ('?contentSelector=[\"%s\"]' % (valueAttr)) if 'condition' in params: - djUri += '&condition=%s' %( params['condition'] ) - + djUri += '&condition=%s' % (params['condition']) + # Forward request to DJ API response = DJAPI.get(djUri); body = response.json() @@ -820,10 +915,12 @@ def data_split(self): if ('totalNum' in body) and (body['totalNum'] > 0): for obj in body['objList']: if valueAttr in obj: - values=obj[valueAttr].split(valueSplit) + values = obj[valueAttr].split(valueSplit) for value in values: - if (len(value) > 0) and (bypass not in value) and (match in value): - resp.append( { 'name': value, 'value': value, 'selected': selected } ) + if (len(value) > 0) and (bypass not in value) and ( + match in value): + resp.append({'name': value, 'value': value, + 'selected': selected}) # end values # end valueAttr # end objList @@ -831,32 +928,33 @@ def data_split(self): if len(resp) == 0: self._response([{'name': nameDefault, 'value': valueDefault}]) else: - self._response(sorted(resp, key=itemgetter('name')) ) + self._response(sorted(resp, key=itemgetter('name'))) + # end split def do_GET(self): # /rest/data/v1/echo - if self.path.startswith( VARS['DJDATASERVICE']['API']['echo'] + '?' ): + if self.path.startswith(VARS['DJDATASERVICE']['API']['echo'] + '?'): self.data_echo() return # /rest/data/v1/enum - if self.path.startswith( VARS['DJDATASERVICE']['API']['enum'] + '/' ): + if self.path.startswith(VARS['DJDATASERVICE']['API']['enum'] + '/'): self.data_enum() return # /rest/data/v1/search - if self.path.startswith( VARS['DJDATASERVICE']['API']['search'] + '/' ): + if self.path.startswith(VARS['DJDATASERVICE']['API']['search'] + '/'): self.data_search() return # /rest/data/v1/join - if self.path.startswith( VARS['DJDATASERVICE']['API']['join'] + '/' ): + if self.path.startswith(VARS['DJDATASERVICE']['API']['join'] + '/'): self.data_join() return # /rest/data/v1/split - if self.path.startswith( VARS['DJDATASERVICE']['API']['split'] + '/' ): + if self.path.startswith(VARS['DJDATASERVICE']['API']['split'] + '/'): self.data_split() return @@ -864,36 +962,49 @@ def do_GET(self): response = DJAPI.get(self.path) self._response(response.json(), response.status_code) return + # end do_GET def do_POST(self): self._response({}, 404) + # end do_POST def do_PUT(self): self._response({}, 404) + # end do_PUT def do_DELETE(self): self._response({}, 404) # end do_DELETE + + # end DjDataService def parse_args(): parser = argparse.ArgumentParser() - parser.add_argument('-H', '--host', type=str, required=False, default=VARS['DJDATASERVICE']['host'], help='Listening host or ip address, default: %s' % ( VARS['DJDATASERVICE']['host'] ) ) - parser.add_argument('-p', '--port', type=str, required=False, default=VARS['DJDATASERVICE']['port'], help='Listening port, default: %d' % ( VARS['DJDATASERVICE']['port'] ) ) + parser.add_argument('-H', '--host', type=str, required=False, + default=VARS['DJDATASERVICE']['host'], + help='Listening host or ip address, default: %s' % ( + VARS['DJDATASERVICE']['host'])) + parser.add_argument('-p', '--port', type=str, required=False, + default=VARS['DJDATASERVICE']['port'], + help='Listening port, default: %d' % ( + VARS['DJDATASERVICE']['port'])) return parser.parse_args() + if __name__ == '__main__': args = parse_args() - + log.setup(VARS, "dj_data_service") try: - server = BaseHTTPServer.HTTPServer((args.host, args.port), DjDataService) - logging.debug( 'Started server on %s:%d' % ( args.host, args.port ) ) - - #Wait forever for incoming htto requests + server = BaseHTTPServer.HTTPServer((args.host, args.port), + DjDataService) + logging.debug('Started server on %s:%d' % (args.host, args.port)) + + # Wait forever for incoming htto requests server.serve_forever() except KeyboardInterrupt: diff --git a/service/dm_data_service.py b/service/dm_data_service.py index 0de7dae..5499c7b 100644 --- a/service/dm_data_service.py +++ b/service/dm_data_service.py @@ -1,20 +1,24 @@ #!/usr/bin/python +# -*- coding: -*- +import argparse +import ast import json -import yaml -import sys -import os import logging +import os import socket -import ast -import argparse import urllib -import requests +from operator import itemgetter + import BaseHTTPServer +import requests +import yaml from requests.packages.urllib3.exceptions import InsecureRequestWarning -from operator import itemgetter + +import log + class DMRestAPI: - def __init__(self, ipList, user, pswd, port = 8088, verify = False, timeout = 10): + def __init__(self, ipList, user, pswd, port=8088, verify=False, timeout=10): self.user = user self.pswd = pswd self.verify = verify @@ -25,9 +29,10 @@ def __init__(self, ipList, user, pswd, port = 8088, verify = False, timeout = 10 self.connected = False self.timeout = timeout self.headers = { - 'Content-Type': 'application/json;charset=utf8', + 'Content-Type': 'application/json;charset=utf8', 'Accept': 'application/json' } + # end __init__ def login(self): @@ -36,16 +41,18 @@ def login(self): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1) - s.connect( (ip, self.port) ) + s.connect((ip, self.port)) validIp = ip s.shutdown(socket.SHUT_RDWR) s.close() break except socket.error: - logging.error( 'DMRestAPI.login - %s is not accessible' %(ip) ) + logging.error('DMRestAPI.login - %s is not accessible' % (ip)) if validIp is None: - logging.error('DMRestAPI.login - storage is not accessible: ' + self.ipList.join(',')) + logging.error( + 'DMRestAPI.login - storage is not accessible: ' + self.ipList.join( + ',')) self.connected = False self.cookies = None del self.headers['iBaseToken'] @@ -54,12 +61,13 @@ def login(self): self.url = "https://%s:%d" % (validIp, self.port) url = self.url + "/deviceManager/rest/xxxxx/login" data = { - 'username' : self.user, - 'password' : self.pswd, - 'scope' : '0' + 'username': self.user, + 'password': self.pswd, + 'scope': '0' } - response = requests.post(url, json = data, headers = self.headers, verify = self.verify, timeout = self.timeout) - logging.debug('DMRestAPI.login - %d %s' %(response.status_code, url) ) + response = requests.post(url, json=data, headers=self.headers, + verify=self.verify, timeout=self.timeout) + logging.debug('DMRestAPI.login - %d %s' % (response.status_code, url)) body = response.json() if response.status_code == 200: @@ -68,16 +76,24 @@ def login(self): self.cookies = response.cookies self.connected = True else: - logging.error('DMRestAPI.login - %d %s' %( response.status_code, json.dumps(body, sort_keys=True, indent=4, ensure_ascii=False) ) ) + logging.error('DMRestAPI.login - %d %s' % (response.status_code, + json.dumps(body, + sort_keys=True, + indent=4, + ensure_ascii=False))) self.connected = False self.cookies = None del self.headers['iBaseToken'] else: - logging.error('DMRestAPI.login - %d %s' %( response.status_code, json.dumps(body, sort_keys=True, indent=4, ensure_ascii=False) ) ) + logging.error('DMRestAPI.login - %d %s' % (response.status_code, + json.dumps(body, + sort_keys=True, + indent=4, + ensure_ascii=False))) self.connected = False self.cookies = None del self.headers['iBaseToken'] - + # end login def get(self, uri): @@ -85,15 +101,20 @@ def get(self, uri): self.login() url = self.url + uri - response = requests.get(url, headers = self.headers, cookies = self.cookies, verify = self.verify, timeout = self.timeout) - logging.debug('DMRestAPI.get - %d - %s' %( response.status_code, url) ) + response = requests.get(url, headers=self.headers, cookies=self.cookies, + verify=self.verify, timeout=self.timeout) + logging.debug('DMRestAPI.get - %d - %s' % (response.status_code, url)) # token expired, login and try again - if response.status_code == 200 and response.json()['error']['code'] == -401: + if response.status_code == 200 and response.json()['error'][ + 'code'] == -401: self.login() - response = requests.get(url, headers = self.headers, cookies = self.cookies, verify = self.verify, timeout = self.timeout) + response = requests.get(url, headers=self.headers, + cookies=self.cookies, verify=self.verify, + timeout=self.timeout) return response + # end get def put(self, uri, data): @@ -101,15 +122,21 @@ def put(self, uri, data): self.login() url = self.url + uri - response = requests.put(url, json = data, headers = self.headers, cookies = self.cookies, verify = self.verify, timeout = self.timeout) - logging.debug('DMRestAPI.get - %d %s' %( response.status_code, url) ) + response = requests.put(url, json=data, headers=self.headers, + cookies=self.cookies, verify=self.verify, + timeout=self.timeout) + logging.debug('DMRestAPI.get - %d %s' % (response.status_code, url)) # token expired, login and try again - if response.status_code == 200 and response.json()['error']['code'] == -401: + if response.status_code == 200 and response.json()['error'][ + 'code'] == -401: self.login() - response = requests.put(url, json = data, headers = self.headers, cookies = self.cookies, verify = self.verify, timeout = self.timeout) + response = requests.put(url, json=data, headers=self.headers, + cookies=self.cookies, verify=self.verify, + timeout=self.timeout) return response + # end put def post(self, uri, data): @@ -117,15 +144,21 @@ def post(self, uri, data): self.login() url = self.url + uri - response = requests.post(url, json = data, headers = self.headers, cookies = self.cookies, verify = self.verify, timeout = self.timeout) - logging.debug('DMRestAPI.get - %d %s' %( response.status_code, url) ) + response = requests.post(url, json=data, headers=self.headers, + cookies=self.cookies, verify=self.verify, + timeout=self.timeout) + logging.debug('DMRestAPI.get - %d %s' % (response.status_code, url)) # token expired, login and try again - if response.status_code == 200 and response.json()['error']['code'] == -401: + if response.status_code == 200 and response.json()['error'][ + 'code'] == -401: self.login() - response = requests.post(url, json = data, headers = self.headers, cookies = self.cookies, verify = self.verify, timeout = self.timeout) + response = requests.post(url, json=data, headers=self.headers, + cookies=self.cookies, verify=self.verify, + timeout=self.timeout) return response + # end post def delete(self, uri): @@ -133,30 +166,38 @@ def delete(self, uri): self.login() url = self.url + uri - response = requests.delete(url, headers = self.headers, cookies = self.cookies, verify = self.verify, timeout = self.timeout) - logging.debug('DMRestAPI.get - %d %s' %( response.status_code, url) ) + response = requests.delete(url, headers=self.headers, + cookies=self.cookies, verify=self.verify, + timeout=self.timeout) + logging.debug('DMRestAPI.get - %d %s' % (response.status_code, url)) # token expired, login and try again - if response.status_code == 200 and response.json()['error']['code'] == -401: + if response.status_code == 200 and response.json()['error'][ + 'code'] == -401: self.login() - response = requests.delete(url, headers = self.headers, cookies = self.cookies, verify = self.verify, timeout = self.timeout) + response = requests.delete(url, headers=self.headers, + cookies=self.cookies, verify=self.verify, + timeout=self.timeout) return response + # end delete # logout session def logout(self): if self.connected == True: - self.delete( '/sessions' ) + self.delete('/sessions') self.connected = False self.cookies = None del self.headers['iBaseToken'] + # end logout def __del__(self): self.logout() # end __del__ + # end class DMRestAPI @@ -172,40 +213,39 @@ def __del__(self): for file in files: if file.endswith(".yml"): fd = open(os.path.join(root, file)) - VARS.update( yaml.load(fd) ) + VARS.update(yaml.safe_load(fd)) fd.close() -# set logging format -logging.basicConfig( - format = VARS['LOGGING']['format'], - level = VARS['LOGGING']['levelno'][ VARS['LOGGING']['level'] ], - datefmt = VARS['LOGGING']['datefmt'] -) - # load storage credential # load storage credential DMAPI = {} for storage in VARS['STORAGES']: - DMAPI[storage['sn']] = DMRestAPI(storage['ipList'], storage['user'], storage['pswd'], storage['port']) + DMAPI[storage['sn']] = DMRestAPI(storage['ipList'], storage['user'], + storage['pswd'], storage['port']) + + # end to load storage credential class DMDataService(BaseHTTPServer.BaseHTTPRequestHandler): - def _response(self, data, code = 200): - self.send_response( code ) - self.send_header('Content-type','application/json;charset=utf8') - self.send_header('Accept','application/json') + def _response(self, data, code=200): + self.send_response(code) + self.send_header('Content-type', 'application/json;charset=utf8') + self.send_header('Accept', 'application/json') self.end_headers() - self.wfile.write(json.dumps(data, sort_keys=False, indent=4, ensure_ascii=False).encode('utf-8')) + self.wfile.write(json.dumps(data, sort_keys=False, indent=4, + ensure_ascii=False).encode('utf-8')) # /data/v1/detail/{objtype}?nameAttr=NAME&valueAttr=ID&range=[0-19]&ID=objId def data_detail(self, sn): - assert self.path.startswith( '/deviceManager/rest/' + sn + VARS['DMDATASERVICE']['API']['detail'] + '/' ) + assert self.path.startswith( + '/deviceManager/rest/' + sn + VARS['DMDATASERVICE']['API'][ + 'detail'] + '/') query = urllib.unquote(self.path).split('?') uri = query[0] - objType = uri[uri.rfind('/')+1:] + objType = uri[uri.rfind('/') + 1:] dmUri = '/deviceManager/rest/' + sn + '/' + objType @@ -293,35 +333,44 @@ def data_detail(self, sn): response = DMAPI[sn].get(dmUri); body = response.json() resp = [] - if (response.status_code == 200) and (body['error']['code'] == 0) and ('data' in body): + if (response.status_code == 200) and (body['error']['code'] == 0) and ( + 'data' in body): obj = body['data'] if nameAttr in obj and valueAttr in obj: if nameField is None: name = obj[nameAttr] else: - name = obj[nameAttr].split(nameSplit)[nameField][nameFieldStart:nameFieldEnd] + name = obj[nameAttr].split(nameSplit)[nameField][ + nameFieldStart:nameFieldEnd] if valueField is None: value = obj[valueAttr] else: - value = obj[valueAttr].split(valueSplit)[valueField][valueFieldStart:valueFieldEnd] + value = obj[valueAttr].split(valueSplit)[valueField][ + valueFieldStart:valueFieldEnd] if (len(name) > 0): - resp.append( { 'name': '{}{}{}'.format(namePrefix,name,nameSuffix), 'value': '{}{}{}'.format(valuePrefix,value,valueSuffix) } ) + resp.append( + {'name': '{}{}{}'.format(namePrefix, name, nameSuffix), + 'value': '{}{}{}'.format(valuePrefix, value, + valueSuffix)}) # end body # end response if len(resp) == 0: self._response([{'name': nameDefault, 'value': valueDefault}]) else: - self._response(sorted(resp, key=itemgetter('name')) ) + self._response(sorted(resp, key=itemgetter('name'))) + # end detail # /data/v1/search/{objtype}?nameAttr=NAME&valueAttr=ID&descAttr=CAPACITY&descDivide=2097152&descUnit=GiB&range=[0-19]&filter={see DM APIs} def data_search(self, sn): - assert self.path.startswith( '/deviceManager/rest/' + sn + VARS['DMDATASERVICE']['API']['search'] + '/' ) + assert self.path.startswith( + '/deviceManager/rest/' + sn + VARS['DMDATASERVICE']['API'][ + 'search'] + '/') query = urllib.unquote(self.path).split('?') uri = query[0] - objType = uri[uri.rfind('/')+1:] + objType = uri[uri.rfind('/') + 1:] dmUri = '/deviceManager/rest/' + sn + '/' + objType @@ -337,7 +386,7 @@ def data_search(self, sn): nameAttrs = params['nameAttr'].split(':') nameAttr = nameAttrs[0] if len(nameAttrs) > 1: - nameField = int(nameAttrs[1]) + nameField = int(nameAttrs[1]) if len(nameAttrs) > 2: nameFieldStart = int(nameAttrs[2]) if len(nameAttrs) > 3: @@ -345,7 +394,7 @@ def data_search(self, sn): del params['nameAttr'] else: nameAttr = 'NAME' - + valueField = None valueFieldStart = None valueFieldEnd = None @@ -440,50 +489,60 @@ def data_search(self, sn): if len(params) > 0: dmUri += '?' - for k,v in params.items(): - dmUri += '{}={}&'.format(k,v) + for k, v in params.items(): + dmUri += '{}={}&'.format(k, v) # Forward request to DM API response = DMAPI[sn].get(dmUri); body = response.json() resp = [] - if (response.status_code == 200) and (body['error']['code'] == 0) and ('data' in body) and (len(body['data']) > 0): + if (response.status_code == 200) and (body['error']['code'] == 0) and ( + 'data' in body) and (len(body['data']) > 0): for obj in body['data']: if nameAttr in obj and valueAttr in obj and matchAttr in obj: if nameField is None: name = obj[nameAttr] else: - name = obj[nameAttr].split(nameSplit)[nameField][nameFieldStart:nameFieldEnd] + name = obj[nameAttr].split(nameSplit)[nameField][ + nameFieldStart:nameFieldEnd] if descAttr is not None: desc = obj[descAttr] if descDivide is not None: desc = float(desc) / float(descDivide) - name = '{} ( {} {} )'.format(name,desc,descUnit) + name = '{} ( {} {} )'.format(name, desc, descUnit) if valueField is None: value = obj[valueAttr] else: - value = obj[valueAttr].split(valueSplit)[valueField][valueFieldStart:valueFieldEnd] + value = obj[valueAttr].split(valueSplit)[valueField][ + valueFieldStart:valueFieldEnd] if (len(name) > 0) and (match in obj[matchAttr]): - resp.append( { 'name': '{}{}{}'.format(namePrefix,name,nameSuffix), 'value': '{}{}{}'.format(valuePrefix,value,valueSuffix) } ) + resp.append({'name': '{}{}{}'.format(namePrefix, name, + nameSuffix), + 'value': '{}{}{}'.format(valuePrefix, + value, + valueSuffix)}) # end obj # end body # end response if len(resp) == 0: self._response([{'name': nameDefault, 'value': valueDefault}]) else: - self._response(sorted(resp, key=itemgetter('name')) ) + self._response(sorted(resp, key=itemgetter('name'))) + # end search # /data/v1/join/{objtype}?nameAttr=NAME&valueAttr=ID&range=[0-19]&filter={see DM APIs}&joins=[{"joinAttr","sourceAttribute","obj":"{targetObjectType}","attr":"targetAttribute","filter":"targetObjectFilter"},...] def data_join(self, sn): - assert self.path.startswith( '/deviceManager/rest/' + sn + VARS['DMDATASERVICE']['API']['join'] + '/' ) + assert self.path.startswith( + '/deviceManager/rest/' + sn + VARS['DMDATASERVICE']['API'][ + 'join'] + '/') query = urllib.unquote(self.path).split('?') uri = query[0] - objType = uri[uri.rfind('/')+1:] + objType = uri[uri.rfind('/') + 1:] dmUri = '/deviceManager/rest/' + sn @@ -499,14 +558,14 @@ def data_join(self, sn): nameAttrs = params['nameAttr'].split(':') nameAttr = nameAttrs[0] if len(nameAttrs) > 1: - nameField = int(nameAttrs[1]) + nameField = int(nameAttrs[1]) if len(nameAttrs) > 2: nameFieldStart = int(nameAttrs[2]) if len(nameAttrs) > 3: nameFieldEnd = int(nameAttrs[3]) else: nameAttr = 'NAME' - + valueField = None valueFieldStart = None valueFieldEnd = None @@ -595,66 +654,83 @@ def data_join(self, sn): descUnit = params['descUnit'] if 'joins' not in params: - logging.error( 'DMDataService.data_join - No joins param') - self._response([{'name': nameDefault,'value': valueDefault}]) + logging.error('DMDataService.data_join - No joins param') + self._response([{'name': nameDefault, 'value': valueDefault}]) joins = ast.literal_eval(params['joins']) for join in joins: - joinResponse = DMAPI[sn].get( '%s/%s?filter=%s' %(dmUri, join['obj'], join['filter']) ); + joinResponse = DMAPI[sn].get( + '%s/%s?filter=%s' % (dmUri, join['obj'], join['filter'])); joinBody = joinResponse.json() - if (joinResponse.status_code == 200) and (joinBody['error']['code'] == 0) and ('data' in joinBody) and (len(joinBody['data']) == 1): + if (joinResponse.status_code == 200) and ( + joinBody['error']['code'] == 0) and ( + 'data' in joinBody) and (len(joinBody['data']) == 1): if filterParam is None: - filterParam = join['joinAttr'] + '::' + joinBody['data'][0][join['attr']] + filterParam = join['joinAttr'] + '::' + joinBody['data'][0][ + join['attr']] else: - filterParam = filterParam + ' and ' + join['joinAttr'] + '::' + joinBody['data'][0][join['attr']] + filterParam = filterParam + ' and ' + join[ + 'joinAttr'] + '::' + joinBody['data'][0][join['attr']] # end joins if filterParam is None: - logging.error( 'DMDataService.data_join - No matched objects or Not only 1 matched objects') - self._response([{'name': nameDefault,'value': valueDefault}]) + logging.error( + 'DMDataService.data_join - No matched objects or Not only 1 matched objects') + self._response([{'name': nameDefault, 'value': valueDefault}]) # Forward request to DM API - response = DMAPI[sn].get( '%s/%s?range=%s&filter=%s' %(dmUri, objType, rangeParam, filterParam) ); + response = DMAPI[sn].get('%s/%s?range=%s&filter=%s' % ( + dmUri, objType, rangeParam, filterParam)); body = response.json() resp = [] - if (response.status_code == 200) and (body['error']['code'] == 0) and ('data' in body) and (len(body['data']) > 0): + if (response.status_code == 200) and (body['error']['code'] == 0) and ( + 'data' in body) and (len(body['data']) > 0): for obj in body['data']: if nameAttr in obj and valueAttr in obj and matchAttr in obj: if nameField is None: name = obj[nameAttr] else: - name = obj[nameAttr].split(nameSplit)[nameField][nameFieldStart:nameFieldEnd] + name = obj[nameAttr].split(nameSplit)[nameField][ + nameFieldStart:nameFieldEnd] if descAttr is not None: desc = obj[descAttr] if descDivide is not None: desc = float(desc) / float(descDivide) - name = '{} ( {} {} )'.format(name,desc,descUnit) + name = '{} ( {} {} )'.format(name, desc, descUnit) if valueField is None: value = obj[valueAttr] else: - value = obj[valueAttr].split(valueSplit)[valueField][valueFieldStart:valueFieldEnd] + value = obj[valueAttr].split(valueSplit)[valueField][ + valueFieldStart:valueFieldEnd] if (len(name) > 0) and (match in obj[matchAttr]): - resp.append( { 'name': '{}{}{}'.format(namePrefix,name,nameSuffix), 'value': '{}{}{}'.format(valuePrefix,value,valueSuffix) } ) + resp.append({'name': '{}{}{}'.format(namePrefix, name, + nameSuffix), + 'value': '{}{}{}'.format(valuePrefix, + value, + valueSuffix)}) # end obj # end body # end response if len(resp) == 0: - self._response([{'name': nameDefault,'value': valueDefault}]) + self._response([{'name': nameDefault, 'value': valueDefault}]) else: - self._response(sorted(resp, key=itemgetter('name')) ) + self._response(sorted(resp, key=itemgetter('name'))) + # end data_join # /data/v1/associate/{objtype}?nameAttr=NAME&valueAttr=ID&matchAttr=NAME&match=xx&range=[0-19]&obj={associateObjType}&filter={associateObjFilter} def data_associate(self, sn): - assert self.path.startswith( '/deviceManager/rest/' + sn + VARS['DMDATASERVICE']['API']['associate'] + '/' ) + assert self.path.startswith( + '/deviceManager/rest/' + sn + VARS['DMDATASERVICE']['API'][ + 'associate'] + '/') query = urllib.unquote(self.path).split('?') uri = query[0] - objType = uri[uri.rfind('/')+1:] + objType = uri[uri.rfind('/') + 1:] dmUri = '/deviceManager/rest/' + sn @@ -670,14 +746,14 @@ def data_associate(self, sn): nameAttrs = params['nameAttr'].split(':') nameAttr = nameAttrs[0] if len(nameAttrs) > 1: - nameField = int(nameAttrs[1]) + nameField = int(nameAttrs[1]) if len(nameAttrs) > 2: nameFieldStart = int(nameAttrs[2]) if len(nameAttrs) > 3: nameFieldEnd = int(nameAttrs[3]) else: nameAttr = 'NAME' - + valueField = None valueFieldStart = None valueFieldEnd = None @@ -766,61 +842,87 @@ def data_associate(self, sn): objIdAttr = 'ID' if ('obj' not in params) or ('filter' not in params): - logging.error( 'DMDataService.data_associate - No associate obj or filter param') - self._response([{'name': nameDefault,'value': valueDefault}]) + logging.error( + 'DMDataService.data_associate - No associate obj or filter param') + self._response([{'name': nameDefault, 'value': valueDefault}]) resp = [] - joinResponse = DMAPI[sn].get( '%s/%s?filter=%s' %(dmUri, params['obj'], params['filter']) ); + joinResponse = DMAPI[sn].get( + '%s/%s?range=[0-100]&filter=%s' % (dmUri, params['obj'], params['filter'])); joinBody = joinResponse.json() - if (joinResponse.status_code == 200) and (joinBody['error']['code'] == 0) and ('data' in joinBody) and (len(joinBody['data']) == 1): + if (joinResponse.status_code == 200) and ( + joinBody['error']['code'] == 0) and ('data' in joinBody) and ( + len(joinBody['data']) == 1): ASSOCIATEOBJTYPE = joinBody['data'][0]['TYPE'] ASSOCIATEOBJID = joinBody['data'][0][objIdAttr] # Forward request to DM API - response = DMAPI[sn].get( '%s/%s/associate?range=%s&ASSOCIATEOBJTYPE=%s&ASSOCIATEOBJID=%s' %(dmUri, objType, rangeParam, ASSOCIATEOBJTYPE, ASSOCIATEOBJID) ); + response = DMAPI[sn].get( + '%s/%s/associate?range=%s&ASSOCIATEOBJTYPE=%s&ASSOCIATEOBJID=%s' % ( + dmUri, objType, rangeParam, ASSOCIATEOBJTYPE, + ASSOCIATEOBJID)); body = response.json() - if (response.status_code == 200) and (body['error']['code'] == 0) and ('data' in body) and (len(body['data']) > 0): + if (response.status_code == 200) and ( + body['error']['code'] == 0) and ('data' in body) and ( + len(body['data']) > 0): for obj in body['data']: if nameAttr in obj and valueAttr in obj and matchAttr in obj: if nameField is None: name = obj[nameAttr] else: - name = obj[nameAttr].split(nameSplit)[nameField][nameFieldStart:nameFieldEnd] + name = obj[nameAttr].split(nameSplit)[nameField][ + nameFieldStart:nameFieldEnd] if descAttr is not None: desc = obj[descAttr] if descDivide is not None: desc = float(desc) / float(descDivide) - name = '{} ( {} {} )'.format(name,desc,descUnit) + name = '{} ( {} {} )'.format(name, desc, descUnit) if valueField is None: value = obj[valueAttr] else: - value = obj[valueAttr].split(valueSplit)[valueField][valueFieldStart:valueFieldEnd] + value = obj[valueAttr].split(valueSplit)[ + valueField][ + valueFieldStart:valueFieldEnd] if (len(name) > 0) and (match in obj[matchAttr]): - resp.append( { 'name': '{}{}{}'.format(namePrefix,name,nameSuffix), 'value': '{}{}{}'.format(valuePrefix,value,valueSuffix) } ) + resp.append({'name': '{}{}{}'.format(namePrefix, + name, + nameSuffix), + 'value': '{}{}{}'.format(valuePrefix, + value, + valueSuffix)}) # end obj # end body # end response else: - logging.debug('DMDataService.data_associate - %d %s' %( response.status_code, json.dumps(body, sort_keys=True, indent=4, ensure_ascii=False) ) ) + logging.debug('DMDataService.data_associate - %d %s' % ( + response.status_code, + json.dumps(body, sort_keys=True, indent=4, + ensure_ascii=False))) # end join else: - logging.debug('DMDataService.data_associate - %d %s' %( joinResponse.status_code, json.dumps(joinBody, sort_keys=True, indent=4, ensure_ascii=False) ) ) + logging.debug('DMDataService.data_associate - %d %s' % ( + joinResponse.status_code, + json.dumps(joinBody, sort_keys=True, indent=4, + ensure_ascii=False))) if len(resp) == 0: - self._response([{'name': nameDefault,'value': valueDefault}]) + self._response([{'name': nameDefault, 'value': valueDefault}]) else: - self._response(sorted(resp, key=itemgetter('name')) ) + self._response(sorted(resp, key=itemgetter('name'))) + # end data_associate # /data/v1/split/{objtype}?valueAttr=name&valueSplit=_&bypass=bypass&match=match&selected=true&range=[0-19]&filter={see DM APIs} def data_split(self, sn): - assert self.path.startswith( '/deviceManager/rest/' + sn + VARS['DMDATASERVICE']['API']['split'] + '/' ) + assert self.path.startswith( + '/deviceManager/rest/' + sn + VARS['DMDATASERVICE']['API'][ + 'split'] + '/') query = urllib.unquote(self.path).split('?') uri = query[0] - objType = uri[uri.rfind('/')+1:] + objType = uri[uri.rfind('/') + 1:] dmUri = '/deviceManager/rest/' + sn + '/' + objType @@ -872,29 +974,33 @@ def data_split(self, sn): if len(params) > 0: dmUri += '?' - for k,v in params.items(): - dmUri += '{}={}&'.format(k,v) + for k, v in params.items(): + dmUri += '{}={}&'.format(k, v) # Forward request to DM API response = DMAPI[sn].get(dmUri); body = response.json() resp = [] - if (response.status_code == 200) and (body['error']['code'] == 0) and ('data' in body) and (len(body['data']) > 0): + if (response.status_code == 200) and (body['error']['code'] == 0) and ( + 'data' in body) and (len(body['data']) > 0): for obj in body['data']: if valueAttr in obj: - values=obj[valueAttr].split(valueSplit) + values = obj[valueAttr].split(valueSplit) for value in values: - if (len(value) > 0) and (bypass not in value) and (match in value): - resp.append( { 'name': value, 'value': value, 'selected': selected } ) + if (len(value) > 0) and (bypass not in value) and ( + match in value): + resp.append({'name': value, 'value': value, + 'selected': selected}) # end values # end valueAttr # end body # end response if len(resp) == 0: - self._response([{'name': nameDefault,'value': valueDefault}]) + self._response([{'name': nameDefault, 'value': valueDefault}]) else: - self._response(sorted(resp, key=itemgetter('name')) ) + self._response(sorted(resp, key=itemgetter('name'))) + # end split def do_GET(self): @@ -902,74 +1008,87 @@ def do_GET(self): uriPath = self.path.split('/') if len(uriPath) < 4 or uriPath[3] not in DMAPI: - self._response([{'name': VARS['DEFAULT']['noneName'],'value': VARS['DEFAULT']['noneValue']}]) + self._response([{'name': VARS['DEFAULT']['noneName'], + 'value': VARS['DEFAULT']['noneValue']}]) return sn = uriPath[3] uri = self.path.replace('/deviceManager/rest/' + sn, '') # /data/v1/detail - if uri.startswith( VARS['DMDATASERVICE']['API']['detail'] + '/' ): + if uri.startswith(VARS['DMDATASERVICE']['API']['detail'] + '/'): self.data_detail(sn) return # /data/v1/search - if uri.startswith( VARS['DMDATASERVICE']['API']['search'] + '/' ): + if uri.startswith(VARS['DMDATASERVICE']['API']['search'] + '/'): self.data_search(sn) return # /data/v1/join - if uri.startswith( VARS['DMDATASERVICE']['API']['join'] + '/' ): + if uri.startswith(VARS['DMDATASERVICE']['API']['join'] + '/'): self.data_join(sn) return # /data/v1/associate - if uri.startswith( VARS['DMDATASERVICE']['API']['associate'] + '/' ): + if uri.startswith(VARS['DMDATASERVICE']['API']['associate'] + '/'): self.data_associate(sn) return # /data/v1/split - if uri.startswith( VARS['DMDATASERVICE']['API']['split'] + '/' ): + if uri.startswith(VARS['DMDATASERVICE']['API']['split'] + '/'): self.data_split(sn) return - # Forward to DM API response = DMAPI[sn].get(self.path) self._response(response.json(), response.status_code) return # end of /deviceManager/rest/ self._response({}, 404) + # end do_GET def do_POST(self): self._response({}, 404) + # end do_POST def do_PUT(self): self._response({}, 404) + # end do_PUT def do_DELETE(self): self._response({}, 404) # end do_DELETE + + # end DMDataService def parse_args(): parser = argparse.ArgumentParser() - parser.add_argument('-H', '--host', type=str, required=False, default=VARS['DMDATASERVICE']['host'], help='Listening host or ip address, default: %s' % ( VARS['DMDATASERVICE']['host'] ) ) - parser.add_argument('-p', '--port', type=str, required=False, default=VARS['DMDATASERVICE']['port'], help='Listening port, default: %d' % ( VARS['DMDATASERVICE']['port'] ) ) + parser.add_argument('-H', '--host', type=str, required=False, + default=VARS['DMDATASERVICE']['host'], + help='Listening host or ip address, default: %s' % ( + VARS['DMDATASERVICE']['host'])) + parser.add_argument('-p', '--port', type=str, required=False, + default=VARS['DMDATASERVICE']['port'], + help='Listening port, default: %d' % ( + VARS['DMDATASERVICE']['port'])) return parser.parse_args() + if __name__ == '__main__': args = parse_args() - + log.setup(VARS, "dm_data_service") try: - server = BaseHTTPServer.HTTPServer((args.host, args.port), DMDataService) - logging.debug( 'Started server on %s:%d' % ( args.host, args.port ) ) - - #Wait forever for incoming htto requests + server = BaseHTTPServer.HTTPServer((args.host, args.port), + DMDataService) + logging.debug('Started server on %s:%d' % (args.host, args.port)) + + # Wait forever for incoming htto requests server.serve_forever() except KeyboardInterrupt: diff --git a/service/log.py b/service/log.py new file mode 100644 index 0000000..5502f0c --- /dev/null +++ b/service/log.py @@ -0,0 +1,46 @@ +import logging +import logging.handlers +import os + + +def _get_log_file_path(conf, logfile): + logdir = conf['LOGGING']['path'] + + if logfile and not logdir: + return logfile + + if logfile and logdir: + return os.path.join(logdir, logfile) + + return None + + +def _set_up_logging_from_conf(conf, service_name): + log_root = logging.getLogger() + + # Remove all handlers + for handler in list(log_root.handlers): + log_root.removeHandler(handler) + + logpath = _get_log_file_path(conf, service_name + ".log") + if logpath: + file_handler = logging.handlers.RotatingFileHandler + max_bytes = 50 * 1024 ** 2 # 20M + filelog = file_handler(logpath, + maxBytes=max_bytes, + backupCount=5) + log_root.addHandler(filelog) + + # user stdout + log_root.addHandler(logging.StreamHandler()) + + for handler in log_root.handlers: + handler.setFormatter(logging.Formatter( + fmt=conf['LOGGING']['format'], + datefmt=conf['LOGGING']['datefmt'])) + + log_root.setLevel(conf['LOGGING']['level']) + + +def setup(conf, service_name): + _set_up_logging_from_conf(conf, service_name) diff --git a/task/cmdb/list_instances.yml b/task/cmdb/list_instances.yml index b18e1ea..bab3ec5 100644 --- a/task/cmdb/list_instances.yml +++ b/task/cmdb/list_instances.yml @@ -20,31 +20,68 @@ # # Generated Parameters (can be overwritten): # className: CI class Name, see global.yml to get supported className in INVENTORY.objType.className -# modifiedEpochFrom: modified time from, format: epoch in seconds -# modifiedEpochTo: modified time to, format: epoch in seconds - set_fact: className: "{{ INVENTORY[objType].className if (objType is defined and objType is not none) else className }}" # map objType to className pageNo: "{{ pageNo | default(1) }}" pageSize: "{{ pageSize | default(10) }}" - modifiedEpochFrom: "{{ (ansible_date_time.epoch|int-7*24*3600) if (modifiedTimeFrom is not defined or modifiedTimeFrom is none) else (modifiedTimeFrom|to_datetime).strftime('%s') }}" - modifiedEpochTo: "{{ (ansible_date_time.epoch|int) if (modifiedTimeTo is not defined or modifiedTimeTo is none) else (modifiedTimeTo|to_datetime).strftime('%s') }}" orderBy: "{{ orderBy | default('last_Modified') }}" orderAsc: "{{ orderAsc | default(False) | bool }}" sep: "{{ sep | default('|') }}" - filterCondition: "" waitExist: "{{ waitExist | default(False) }}" waitSeconds: "{{ waitSeconds | default(600) }}" waitInterval: "{{ waitInterval | default(10) }}" + constraints: [] -- name: Set filters - set_fact: - filterCondition: "{{filterCondition}},{\"logOp\":\"and\",\"simple\":{\"name\":\"{{item.k}}\",\"operator\":\"{{item.op|urlencode}}\",\"value\":\"{{item.v|urlencode}}\"}}" +- set_fact: + constraints: "{{constraints + [timeFilter]}}" + vars: + timeFilter: + logOp: "and" + simple: + name: "last_Modified" + operator: "not less than" + value: "{{(modifiedTimeFrom|to_datetime).strftime('%s')}}000" + when: modifiedTimeFrom is defined + +- set_fact: + constraints: "{{constraints + [timeFilter]}}" + vars: + timeFilter: + logOp: "and" + simple: + name: "last_Modified" + operator: "less than" + value: "{{(modifiedTimeTo|to_datetime).strftime('%s')}}000" + when: modifiedTimeTo is defined + + +- set_fact: + constraints: "{{constraints + [constraint]}}" + vars: + constraint: + logOp: "and" + simple: + name: "{{item.k}}" + operator: "{{item.op}}" + value: "{{item.v}}" with_items: "{{ filters }}" when: filters is defined - set_fact: - params: "pageNo={{pageNo}}&pageSize={{pageSize}}&orderBy=[{\"field\":\"{{orderBy}}\",\"asc\":\"{{orderAsc}}\"}]&condition={\"constraint\":[{\"simple\":{\"name\":\"last_Modified\",\"operator\":\"not%20less%20than\",\"value\":\"{{modifiedEpochFrom}}000\"}},{\"logOp\":\"and\",\"simple\":{\"name\":\"last_Modified\",\"operator\":\"less%20than\",\"value\":\"{{modifiedEpochTo}}000\"}}{{filterCondition}}]}" + paramCondition: + constraint: "{{constraints}}" + +- set_fact: + paramOrderBy: + - field: "{{orderBy}}" + asc: "{{orderAsc}}" + +- set_fact: + params: "pageNo={{pageNo}}&pageSize={{pageSize}}&orderBy={{paramOrderBy | to_json | urlencode}}&condition={{paramCondition | to_json(ensure_ascii=False) | urlencode}}" + +- debug: + msg: "{{params}}" - name: List Instances uri: @@ -70,7 +107,7 @@ keys: "{{INVENTORY[objType].attributes}}" file: "{{export}}" sep: "{{sep}}" - when: + when: - export is defined - INSTANCES.json.objList is defined - INSTANCES.json.objList|length > 0 diff --git a/task/storage/oceanstor/activate_snapshots.yml b/task/storage/oceanstor/activate_snapshots.yml index 10a6deb..a224503 100644 --- a/task/storage/oceanstor/activate_snapshots.yml +++ b/task/storage/oceanstor/activate_snapshots.yml @@ -7,7 +7,7 @@ - block: - name: Query Snapshots uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/SNAPSHOT?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/SNAPSHOT?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/add_luns_to_clone_cg.yml b/task/storage/oceanstor/add_luns_to_clone_cg.yml index bc771b0..e94293d 100644 --- a/task/storage/oceanstor/add_luns_to_clone_cg.yml +++ b/task/storage/oceanstor/add_luns_to_clone_cg.yml @@ -34,7 +34,7 @@ - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/add_luns_to_lg.yml b/task/storage/oceanstor/add_luns_to_lg.yml index ed78211..a928315 100644 --- a/task/storage/oceanstor/add_luns_to_lg.yml +++ b/task/storage/oceanstor/add_luns_to_lg.yml @@ -33,7 +33,7 @@ - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/add_luns_to_pg.yml b/task/storage/oceanstor/add_luns_to_pg.yml index ee13c83..af9ff19 100644 --- a/task/storage/oceanstor/add_luns_to_pg.yml +++ b/task/storage/oceanstor/add_luns_to_pg.yml @@ -28,7 +28,7 @@ - block: - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: @@ -83,4 +83,7 @@ queryMsg: "[*].json.error" debug: msg: "{{ PG_ADD_LUNS.results | json_query(queryMsg) }}" - failed_when: PG_ADD_LUNS.results | json_query(queryError) | difference([0]) | length > 0 \ No newline at end of file + failed_when: PG_ADD_LUNS.results | json_query(queryError) | difference([0]) | length > 0 + +- set_fact: + addLunIds: [] \ No newline at end of file diff --git a/task/storage/oceanstor/add_ports_to_host.yml b/task/storage/oceanstor/add_ports_to_host.yml index 4a5bba2..a0db0e9 100644 --- a/task/storage/oceanstor/add_ports_to_host.yml +++ b/task/storage/oceanstor/add_ports_to_host.yml @@ -38,7 +38,7 @@ - name: Query Host by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?filter=NAME%3A%3A{{hostName|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?range=%5B0-100%5D&filter=NAME%3A%3A{{hostName|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/check_host_lun_id.yml b/task/storage/oceanstor/check_host_lun_id.yml index 707e354..2f6ef9a 100644 --- a/task/storage/oceanstor/check_host_lun_id.yml +++ b/task/storage/oceanstor/check_host_lun_id.yml @@ -24,7 +24,7 @@ - block: - name: Query Host by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?filter=NAME%3A%3A{{hostName|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?range=%5B0-100%5D&filter=NAME%3A%3A{{hostName|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/check_host_lun_id_loop_helper.yml b/task/storage/oceanstor/check_host_lun_id_loop_helper.yml new file mode 100644 index 0000000..d585869 --- /dev/null +++ b/task/storage/oceanstor/check_host_lun_id_loop_helper.yml @@ -0,0 +1,14 @@ +# Get Host LUN ID Loop Helper, cooperator with nested loop +# +# Required parameters: +# hostName: Host Name +# hostGroupName: Host Group Name +# +# Generated parameters: +# checkedHostLuns Checked Host LUNs Info, Need Clean Before Used. + + +- import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/check_host_lun_id.yml" +- set_fact: + checkedHostLuns: "{{ checkedHostLuns + [checkedLuns] }}" + diff --git a/task/storage/oceanstor/check_host_wwns.yml b/task/storage/oceanstor/check_host_wwns.yml new file mode 100644 index 0000000..0af1583 --- /dev/null +++ b/task/storage/oceanstor/check_host_wwns.yml @@ -0,0 +1,66 @@ +# Check wwns exists on hosts +# +# Required parameters: +# hostNames: # a list of host names +# +# Optional parameters: +# checkExist: # check exist or not exist, default: true, options: true/false +# +# Generated variables: +# checkedHosts: # a list of hosts on storage device +# checkedWwns: # a map of wwns in hosts + +- name: Set default variables + set_fact: + hostIds: [] + checkedHosts: [] + checkedWwns: {} + +- name: Query Hosts by Name + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: HOSTS + with_items: "{{ hostNames }}" + +- name: Get Host IDs + set_fact: + hostIds: "{{ HOSTS.results | json_query('[*].json.data[*].ID') | flatten(levels=1) }}" + checkedHosts: "{{ HOSTS.results | json_query('[*].json.data[*]') | flatten(levels=1) }}" + +- name: Check Host Name Exist + debug: + msg: + hostIds: "{{ hostIds }}" + failed_when: (hostIds|length != hostNames|length) + +- name: Get WWNs + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/fc_initiator?filter=PARENTID%3A%3A{{item}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: WWNs + failed_when: (WWNs.json.error.code|int != 0) + with_items: "{{ hostIds }}" + +- set_fact: + checkedWwns: "{{ checkedWwns | combine( { item.1: WWNs.results[item.0].json.data | default([]) | json_query('[*].ID') } ) }}" + with_indexed_items: "{{ hostNames }}" + +- name: Check WWNs Exist in Hosts + debug: + msg: + WWNs: "{{ item }}" + failed_when: (checkExist|default(True)|bool == True and item.value|length == 0) or (checkExist|default(True)|bool == False and item.value|length > 0) + loop: "{{ lookup('dict', checkedWwns, wantlist=True) }}" diff --git a/task/storage/oceanstor/check_hosts.yml b/task/storage/oceanstor/check_hosts.yml index 6c5405f..077dda7 100644 --- a/task/storage/oceanstor/check_hosts.yml +++ b/task/storage/oceanstor/check_hosts.yml @@ -21,7 +21,7 @@ - name: Query Hosts by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/check_luns.yml b/task/storage/oceanstor/check_luns.yml index 982934d..86c6d6c 100644 --- a/task/storage/oceanstor/check_luns.yml +++ b/task/storage/oceanstor/check_luns.yml @@ -16,7 +16,7 @@ - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/create_clone.yml b/task/storage/oceanstor/create_clone.yml index 88488f2..63c598f 100644 --- a/task/storage/oceanstor/create_clone.yml +++ b/task/storage/oceanstor/create_clone.yml @@ -14,7 +14,7 @@ - name: Query Source LUN by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{sourceLun|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{sourceLun|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/create_drstar.yml b/task/storage/oceanstor/create_drstar.yml index 4fb0748..8eb7087 100644 --- a/task/storage/oceanstor/create_drstar.yml +++ b/task/storage/oceanstor/create_drstar.yml @@ -71,4 +71,8 @@ - name: Check Enable DR Star Result debug: msg: "{{ ENABLE_DRSTAR.json.error }}" - failed_when: (ENABLE_DRSTAR.json.error.code|int !=0) \ No newline at end of file + failed_when: (ENABLE_DRSTAR.json.error.code|int !=0) + +- name: Wait a replication cycle + pause: + seconds: "{{GLOBAL.replication.interval}}" \ No newline at end of file diff --git a/task/storage/oceanstor/create_lg.yml b/task/storage/oceanstor/create_lg.yml index 05cb1f7..d47a617 100644 --- a/task/storage/oceanstor/create_lg.yml +++ b/task/storage/oceanstor/create_lg.yml @@ -11,7 +11,7 @@ # mapHostNames: Map LUN Group to a list of Hosts # mapHostGroupNames: Map LUN Group to a list of Host Groups # pgName: Create Protection Group for LUN Group -# pgId: Specify Protection Grup ID +# pgId: Specify Protection Group ID # # Generated Parameters: # newLgId: New LG ID diff --git a/task/storage/oceanstor/create_pg.yml b/task/storage/oceanstor/create_pg.yml index 12628c1..6c2f03c 100644 --- a/task/storage/oceanstor/create_pg.yml +++ b/task/storage/oceanstor/create_pg.yml @@ -5,8 +5,10 @@ # # Optional parameters: # lgName: LUN Group Name +# lgId: Specify LUN Group ID # desc: Description # addLunNames: Add a list of LUNs to PG +# addLunIds: Add a list of LUN IDs to LUN Group # # Generated Parameters: # newPgId: New PG ID @@ -23,15 +25,22 @@ when: - desc|default(none) is not none -- import_tasks: check_lgs.yml - vars: - lgNames: ["{{ lgName }}"] - checkExist: True - when: lgName|default(none) is not none +- block: + - import_tasks: check_lgs.yml + vars: + lgNames: ["{{ lgName }}"] + checkExist: True + + - set_fact: + lgId: "{{ lgIds[0] }}" + + # End block + when: + lgName|default(none) is not none - name: Set param - lunGroupId set_fact: - paramCreatePg: "{{ paramCreatePg | combine( { 'lunGroupId': lgIds[0] } ) }}" + paramCreatePg: "{{ paramCreatePg | combine( { 'lunGroupId': lgId } ) }}" when: lgName|default(none) is not none - name: Create PG @@ -51,7 +60,7 @@ - name: Check Create PG Result debug: msg: "{{ NEW_PG.json.error }}" - failed_when: (NEW_PG.json.error.code|int != 0) or ('data' not in NEW_LG.json) + failed_when: (NEW_PG.json.error.code|int != 0) or ('data' not in NEW_PG.json) - set_fact: newPgId: "{{ NEW_PG.json.data.protectGroupId }}" @@ -60,7 +69,17 @@ - import_tasks: check_luns.yml vars: lunNames: "{{ addLunNames }}" + checkExist: True + - set_fact: + addLunIds: "{{ lunIds }}" + + # End block + when: + - addLunNames|default(none) is not none + - addLunNames|length > 0 + +- block: - name: Add LUN to PG uri: url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/protectgroup/associate" @@ -77,7 +96,7 @@ ASSOCIATEOBJTYPE: 11 ASSOCIATEOBJID: "{{ item }}" register: PG_ADD_LUNS - with_items: "{{ lunIds }}" + with_items: "{{ addLunIds }}" - name: Check Add LUN Results vars: @@ -89,5 +108,5 @@ # End Block when: - - addLunNames|default(none) is not none - - addLunNames|length > 0 \ No newline at end of file + - addLunIds|default(none) is not none + - addLunIds|length > 0 \ No newline at end of file diff --git a/task/storage/oceanstor/create_snapshot_cg.yml b/task/storage/oceanstor/create_snapshot_cg.yml index e110e03..1b959d1 100644 --- a/task/storage/oceanstor/create_snapshot_cg.yml +++ b/task/storage/oceanstor/create_snapshot_cg.yml @@ -59,10 +59,29 @@ - set_fact: newCgId: "{{ NEW_CG.json.data.ID }}" +- name: Unset snapIds + debug: + when: snapIds|default(None) != None + +- name: Wait create Complete + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/snapshot/associate?ASSOCIATEOBJTYPE=57646&ASSOCIATEOBJID={{newCgId}}&range=%5B0-100%5D" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: SNAPSHOTS + retries: "20" + delay: "5" + until: SNAPSHOTS.json.data|default([])|length != 0 + - block: - name: Query Snapshots uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/snapshot/associate?ASSOCIATEOBJTYPE=57646&ASSOCIATEOBJID={{newCgId}}&range=[0-4096]" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/snapshot/associate?ASSOCIATEOBJTYPE=57646&ASSOCIATEOBJID={{newCgId}}&range=%5B0-100%5D" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/create_snapshots.yml b/task/storage/oceanstor/create_snapshots.yml index f08754c..09d3f15 100644 --- a/task/storage/oceanstor/create_snapshots.yml +++ b/task/storage/oceanstor/create_snapshots.yml @@ -17,7 +17,7 @@ - block: - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/deactivate_snapshots.yml b/task/storage/oceanstor/deactivate_snapshots.yml index 2ff0e01..9da073b 100644 --- a/task/storage/oceanstor/deactivate_snapshots.yml +++ b/task/storage/oceanstor/deactivate_snapshots.yml @@ -7,7 +7,7 @@ - block: - name: Query Snapshots uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/SNAPSHOT?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/SNAPSHOT?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/delete_host.yml b/task/storage/oceanstor/delete_host.yml index 4135b0f..5421e0c 100644 --- a/task/storage/oceanstor/delete_host.yml +++ b/task/storage/oceanstor/delete_host.yml @@ -6,7 +6,7 @@ - name: Query Host by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?filter=NAME%3A%3A{{hostName|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?range=%5B0-100%5D&filter=NAME%3A%3A{{hostName|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/delete_lg.yml b/task/storage/oceanstor/delete_lg.yml index 3f67972..bde7f37 100644 --- a/task/storage/oceanstor/delete_lg.yml +++ b/task/storage/oceanstor/delete_lg.yml @@ -30,7 +30,7 @@ set_fact: deletedLg: "{{ lg }}" lgMapped: "{{ lg.ISADD2MAPPINGVIEW == 'true' }}" - lgReplicaNum: "{{ lg.cdpGroupNum|int + lg.cloneGroupNum|int + lg.replicationGroupNum|int + lg.snapshotGroupNum|int + lg.drStarNum|int + lg.hyperMetroGroupNum|int}}" + lgReplicaNum: "{{ lg.cloneGroupNum|int + lg.replicationGroupNum|int + lg.snapshotGroupNum|int + lg.drStarNum|int + lg.hyperMetroGroupNum|int}}" when: lgName|default(none) is not none @@ -54,7 +54,7 @@ set_fact: deletedLg: "{{ lg }}" lgMapped: "{{ lg.ISADD2MAPPINGVIEW == 'true' }}" - lgReplicaNum: "{{ lg.cdpGroupNum|int + lg.cloneGroupNum|int + lg.replicationGroupNum|int + lg.snapshotGroupNum|int + lg.drStarNum|int + lg.hyperMetroGroupNum|int}}" + lgReplicaNum: "{{ lg.cloneGroupNum|int + lg.replicationGroupNum|int + lg.snapshotGroupNum|int + lg.drStarNum|int + lg.hyperMetroGroupNum|int}}" when: lgId|default(none) is not none diff --git a/task/storage/oceanstor/delete_luns.yml b/task/storage/oceanstor/delete_luns.yml index 1eebb98..f26d8ae 100644 --- a/task/storage/oceanstor/delete_luns.yml +++ b/task/storage/oceanstor/delete_luns.yml @@ -14,7 +14,7 @@ - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: @@ -53,7 +53,7 @@ - name: Query Snapshots uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/SNAPSHOT?filter=PARENTNAME::{{item}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/SNAPSHOT?range=%5B0-100%5D&filter=PARENTNAME::{{item}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/delete_snapshots.yml b/task/storage/oceanstor/delete_snapshots.yml index a2db2f9..1bb3c4f 100644 --- a/task/storage/oceanstor/delete_snapshots.yml +++ b/task/storage/oceanstor/delete_snapshots.yml @@ -8,7 +8,7 @@ - block: - name: Query Snapshots uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/SNAPSHOT?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/SNAPSHOT?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/enable_drstar.yml b/task/storage/oceanstor/enable_drstar.yml index 6bf93d8..8a51d8c 100644 --- a/task/storage/oceanstor/enable_drstar.yml +++ b/task/storage/oceanstor/enable_drstar.yml @@ -38,4 +38,8 @@ - name: Check Enable DR Star Result debug: msg: "{{ ENABLE_DRSTAR.json.error }}" - failed_when: (ENABLE_DRSTAR.json.error.code|int !=0) \ No newline at end of file + failed_when: (ENABLE_DRSTAR.json.error.code|int !=0) + +- name: Wait a replication cycle + pause: + seconds: "{{GLOBAL.replication.interval}}" \ No newline at end of file diff --git a/task/storage/oceanstor/get_lgs_by_host.yml b/task/storage/oceanstor/get_lgs_by_host.yml new file mode 100644 index 0000000..47ce504 --- /dev/null +++ b/task/storage/oceanstor/get_lgs_by_host.yml @@ -0,0 +1,52 @@ +# Get LUN Groups by Host +# +# Required parameters: +# hostName: # Host Name, can be replaced with hostId +# +# Optional parameters: +# hostId: # Host ID +# +# Generated variables: +# checkedLgs: # Checked LUN groups associate with host + +- set_fact: + checkedLgs: "{{ none }}" + +- block: + - name: Query Host by Name + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?range=%5B0-100%5D&filter=NAME%3A%3A{{hostName|urlencode}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: HOST + + - name: Get Host ID + vars: + host: "{{ HOST.json.data[0] }} " + set_fact: + hostId: "{{ host.ID }}" + failed_when: ('data' not in HOST.json) or (HOST.json.data|length != 1) + + when: hostName|default(none) is not none + + +- name: Get LUN Groups by Host ID + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/mapping/associate?ASSOCIATEOBJTYPE=21&ASSOCIATEOBJID={{hostId}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: DETAIL + failed_when: (DETAIL.json.error.code != 0) or ('data' not in DETAIL.json ) + +- set_fact: + checkedLgs: "{{ DETAIL.json.data }}" \ No newline at end of file diff --git a/task/storage/oceanstor/get_luns_by_pg.yml b/task/storage/oceanstor/get_luns_by_pg.yml new file mode 100644 index 0000000..f0e85e8 --- /dev/null +++ b/task/storage/oceanstor/get_luns_by_pg.yml @@ -0,0 +1,52 @@ +# Get LUNs by Protection Group +# +# Required parameters: +# pgName: # Protection Group Name, can be replaced with pgId +# +# Optional parameters: +# pgId: # Protection Group ID +# +# Generated variables: +# checkedLuns: # Checked LUNs associate with protection group + +- set_fact: + checkedLuns: "{{ none }}" + +- block: + - name: Query PG by Name + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/protectgroup?filter=protectGroupName%3A%3A{{pgName|urlencode}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: PG + + - name: Get PG ID + vars: + queryPgIds: "[? protectGroupName=='{{pgName}}'].protectGroupId" + pgIds: "{{ PG.json.data | default([]) | json_query(queryPgIds) }}" + set_fact: + pgId: "{{ pgIds | first }}" + failed_when: pgIds | length != 1 + + when: pgName|default(none) is not none + +- name: Get LUN by PG ID + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun/associate?ASSOCIATEOBJTYPE=57846&ASSOCIATEOBJID={{pgId}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: DETAIL + failed_when: (DETAIL.json.error.code != 0) or ('data' not in DETAIL.json ) + +- set_fact: + checkedLuns: "{{ DETAIL.json.data }}" \ No newline at end of file diff --git a/task/storage/oceanstor/get_replication_cgs_by_pg.yml b/task/storage/oceanstor/get_replication_cgs_by_pg.yml new file mode 100644 index 0000000..d6893a8 --- /dev/null +++ b/task/storage/oceanstor/get_replication_cgs_by_pg.yml @@ -0,0 +1,54 @@ +# Get LUNs by Protection Group +# +# Required parameters: +# pgName: # Protection Group Name, can be replaced with pgId +# +# Optional parameters: +# pgId: # Protection Group ID +# +# Generated variables: +# checkedRepCgs: # Checked Replication CGs associate with protection group + +- set_fact: + checkedRepCgs: "{{ none }}" + +- block: + - name: Query PG by Name + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/protectgroup?filter=protectGroupName%3A%3A{{pgName|urlencode}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: PG + + - name: Get PG ID + vars: + queryPgIds: "[? protectGroupName=='{{pgName}}'].protectGroupId" + pgIds: "{{ PG.json.data | default([]) | json_query(queryPgIds) }}" + set_fact: + pgId: "{{ pgIds | first }}" + failed_when: pgIds | length != 1 + + when: pgName|default(none) is not none + +- name: Get Replication CGs Associate with Protection Group + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/CONSISTENTGROUP?filter=localpgId%3A%3A{{pgId}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: RCGs + failed_when: (RCGs.json.error.code != 0) or ('data' not in RCGs.json ) + +- name: Get Replication CG IDs + set_fact: + cgIds: "{{ RCGs.json.data | json_query('[*].ID') }}" + checkedRepCgs: "{{ RCGs.json.data }}" diff --git a/task/storage/oceanstor/get_replication_cgs_by_pg_loop_helper.yml b/task/storage/oceanstor/get_replication_cgs_by_pg_loop_helper.yml new file mode 100644 index 0000000..8b6a329 --- /dev/null +++ b/task/storage/oceanstor/get_replication_cgs_by_pg_loop_helper.yml @@ -0,0 +1,14 @@ +# Get Replication CGs by Protection Group Loop Helper, cooperator with nested loop +# +# Required parameters: +# pgName: # Protection Group Name, can be replaced with pgId +# +# Optional parameters: +# pgId: # Protection Group ID +# +# Generated variables: +# checkedPgRepCgs: # Checked Replication CGs associate with protection groups, need clean before used. + +- import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_replication_cgs_by_pg.yml" +- set_fact: + checkedPgRepCgs: "{{ checkedPgRepCgs + [checkedRepCgs] }}" diff --git a/task/storage/oceanstor/get_snapshot_cgs_by_pg.yml b/task/storage/oceanstor/get_snapshot_cgs_by_pg.yml new file mode 100644 index 0000000..2feb6eb --- /dev/null +++ b/task/storage/oceanstor/get_snapshot_cgs_by_pg.yml @@ -0,0 +1,58 @@ +# Get LUNs by Protection Group +# +# Required parameters: +# pgName: # Protection Group Name, can be replaced with pgId +# +# Optional parameters: +# pgId: # Protection Group ID +# ignoreEmpty: # Ignore error when got empty snapshot cgs. +# +# Generated variables: +# checkedSnapCgs: # Checked Snapshot CGs associate with protection group + +- set_fact: + checkedSnapCgs: [] + cgIds: [] + +- block: + - name: Query PG by Name + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/protectgroup?filter=protectGroupName%3A%3A{{pgName|urlencode}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: PG + + - name: Get PG ID + vars: + queryPgIds: "[? protectGroupName=='{{pgName}}'].protectGroupId" + pgIds: "{{ PG.json.data | default([]) | json_query(queryPgIds) }}" + set_fact: + pgId: "{{ pgIds | first }}" + failed_when: pgIds | length != 1 + + when: pgName|default(none) is not none + +- name: Query Snapshot CGs Associate with Protection Group + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/snapshot_consistency_group/associate?ASSOCIATEOBJTYPE=57846&ASSOCIATEOBJID={{pgId}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: SCGs + failed_when: (SCGs.json.error.code != 0) or (not ignoreEmpty|default(false) and 'data' not in SCGs.json ) + + +- name: Get Snapshot CG IDs + set_fact: + cgIds: "{{ SCGs.json.data | json_query('[*].ID') }}" + checkedSnapCgs: "{{ SCGs.json.data }}" + when: ( 'data' in SCGs.json ) \ No newline at end of file diff --git a/task/storage/oceanstor/get_snapshot_cgs_by_pg_loop_helper.yml b/task/storage/oceanstor/get_snapshot_cgs_by_pg_loop_helper.yml new file mode 100644 index 0000000..205ed35 --- /dev/null +++ b/task/storage/oceanstor/get_snapshot_cgs_by_pg_loop_helper.yml @@ -0,0 +1,16 @@ +# Get Snapshot CGs by Protection Group Loop Helper, cooperator with nested loop +# +# Required parameters: +# pgName: # Protection Group Name, can be replaced with pgId +# +# Optional parameters: +# pgId: # Protection Group ID +# ignoreEmpty: # Ignore error when got empty snapshot cgs. +# +# Generated variables: +# checkedPgSnapCgs: # Checked Snapshot CGs associate with protection groups, need clean before used. + +- import_tasks: "{{GLOBAL.baseDir}}/task/storage/oceanstor/get_snapshot_cgs_by_pg.yml" +- set_fact: + checkedPgSnapCgs: "{{ checkedPgSnapCgs + [checkedSnapCgs] }}" + when: (checkedSnapCgs|length > 0) diff --git a/task/storage/oceanstor/get_snapshots_by_cg.yml b/task/storage/oceanstor/get_snapshots_by_cg.yml new file mode 100644 index 0000000..6b5d154 --- /dev/null +++ b/task/storage/oceanstor/get_snapshots_by_cg.yml @@ -0,0 +1,50 @@ +# Get Snapshots by Snapshot Consistent Group +# +# Required parameters: +# cgName: # Snapshot Consistent Group Name, can be replaced with cgId +# +# Optional parameters: +# cgId: # Snapshot Consistent Group ID +# +# Generated variables: +# checkedSnapshots: # Checked Snapshots associate with snapshot consistent group + +- set_fact: + checkedSnapshots: "{{ none }}" + +- block: + - name: Query Snapshot CG by Name + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/SNAPSHOT_CONSISTENCY_GROUP?filter=NAME%3A%3A{{cgName|urlencode}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: SCGs + + - name: Get CG ID + vars: + scg: "{{ SCGs.json.data[0] }} " + set_fact: + cgId: "{{ scg.ID }}" + failed_when: ('data' not in SCGs.json) or (SCGs.json.data|length != 1) + + when: cgName|default(none) is not none + +- name: Get Snapshots by CG ID + uri: + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/snapshot/associate?ASSOCIATEOBJTYPE=57646&ASSOCIATEOBJID={{cgId}}" + method: GET + validate_certs: no + headers: + Accept: "application/json" + Content-Type: "application/json;charset=utf8" + iBaseToken: "{{ deviceToken }}" + Cookie: "session={{ deviceSession }}" + register: SNAPSHOTS + +- set_fact: + checkedSnapshots: "{{ SNAPSHOTS.json.data }}" diff --git a/task/storage/oceanstor/map_luns_to_host.yml b/task/storage/oceanstor/map_luns_to_host.yml index e01b3bc..6ce56e3 100644 --- a/task/storage/oceanstor/map_luns_to_host.yml +++ b/task/storage/oceanstor/map_luns_to_host.yml @@ -17,7 +17,7 @@ - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: @@ -57,7 +57,7 @@ - name: Query Host by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?filter=NAME%3A%3A{{hostName|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?range=%5B0-100%5D&filter=NAME%3A%3A{{hostName|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/map_luns_to_hostgroup.yml b/task/storage/oceanstor/map_luns_to_hostgroup.yml index 804af26..f3a038a 100644 --- a/task/storage/oceanstor/map_luns_to_hostgroup.yml +++ b/task/storage/oceanstor/map_luns_to_hostgroup.yml @@ -18,7 +18,7 @@ - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: @@ -58,7 +58,7 @@ - name: Query Host by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?filter=NAME%3A%3A{{hostName|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?range=%5B0-100%5D&filter=NAME%3A%3A{{hostName|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/modify_host.yml b/task/storage/oceanstor/modify_host.yml index 1d50e5c..2d86d6f 100644 --- a/task/storage/oceanstor/modify_host.yml +++ b/task/storage/oceanstor/modify_host.yml @@ -13,7 +13,7 @@ - name: Query Host by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?filter=NAME%3A%3A{{hostName|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?range=%5B0-100%5D&filter=NAME%3A%3A{{hostName|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/modify_lun.yml b/task/storage/oceanstor/modify_lun.yml index 61efecd..8f48921 100644 --- a/task/storage/oceanstor/modify_lun.yml +++ b/task/storage/oceanstor/modify_lun.yml @@ -13,7 +13,7 @@ - block: - name: Query LUN by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{lunName|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{lunName|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/remove_luns_from_clone_cg.yml b/task/storage/oceanstor/remove_luns_from_clone_cg.yml index 5759b83..b1c7168 100644 --- a/task/storage/oceanstor/remove_luns_from_clone_cg.yml +++ b/task/storage/oceanstor/remove_luns_from_clone_cg.yml @@ -37,7 +37,7 @@ - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/remove_luns_from_lg.yml b/task/storage/oceanstor/remove_luns_from_lg.yml index 5d89cb0..5790768 100644 --- a/task/storage/oceanstor/remove_luns_from_lg.yml +++ b/task/storage/oceanstor/remove_luns_from_lg.yml @@ -31,7 +31,7 @@ - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/remove_luns_from_pg.yml b/task/storage/oceanstor/remove_luns_from_pg.yml index 87437d3..d6e9e93 100644 --- a/task/storage/oceanstor/remove_luns_from_pg.yml +++ b/task/storage/oceanstor/remove_luns_from_pg.yml @@ -28,7 +28,7 @@ - block: - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: @@ -53,6 +53,7 @@ debug: msg: removeLunIds: "{{ removeLunIds }}" + lunNames: "{{ lunNames }}" failed_when: removeLunIds|length != lunNames|length when: - lunNames|default(none) is not none diff --git a/task/storage/oceanstor/remove_ports_from_host.yml b/task/storage/oceanstor/remove_ports_from_host.yml index a547b8e..e1d69ec 100644 --- a/task/storage/oceanstor/remove_ports_from_host.yml +++ b/task/storage/oceanstor/remove_ports_from_host.yml @@ -30,7 +30,7 @@ - name: Query Host by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?filter=NAME%3A%3A{{hostName|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?range=%5B0-100%5D&filter=NAME%3A%3A{{hostName|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/storage/oceanstor/unmap_luns_from_host.yml b/task/storage/oceanstor/unmap_luns_from_host.yml index 9e180e0..ce60d7b 100644 --- a/task/storage/oceanstor/unmap_luns_from_host.yml +++ b/task/storage/oceanstor/unmap_luns_from_host.yml @@ -7,7 +7,7 @@ - name: Query Host by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?filter=NAME%3A%3A{{hostName|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/host?range=%5B0-100%5D&filter=NAME%3A%3A{{hostName|urlencode}}" method: GET validate_certs: no headers: @@ -48,7 +48,7 @@ - name: Query LUNs by Name uri: - url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?filter=NAME%3A%3A{{item|urlencode}}" + url: "https://{{deviceHost}}:{{devicePort}}/deviceManager/rest/{{deviceSn}}/lun?range=%5B0-100%5D&filter=NAME%3A%3A{{item|urlencode}}" method: GET validate_certs: no headers: diff --git a/task/task/wait_task_complete.yml b/task/task/wait_task_complete.yml index 6bd5433..3375738 100644 --- a/task/task/wait_task_complete.yml +++ b/task/task/wait_task_complete.yml @@ -8,7 +8,7 @@ - name: Set default variables set_fact: - statusMap: { '1': 'Not Start', '2': 'Running', '3': 'Succeeded', '4': 'Partially Succeeded', '5': 'Failed', '6': 'Timeout'} + statusMap: { '1': 'Not Start', '2': 'Running', '3': 'Succeeded', '4': 'Partially Succeeded', '5': 'Failed', '6': 'Timeout', '7': 'Warning'} - name: Wait Task Complete uri: @@ -34,7 +34,7 @@ name: "{{task.name_en}}" sequence: "{{task.seq_no}}" progress: "{{task.progress}}" - status: "{{statusMap[task.status|string]}}" + status: "{{statusMap[task.status|string]|default('Unknown')}}" with_sequence: start=0 count="{{TASKS.json|length}}" - name: Task Result @@ -43,14 +43,14 @@ status: "{{ TASKS.json | json_query(queryStatus) | first }}" debug: msg: - Result: "{{statusMap[status]}}" + Result: "{{statusMap[status]|default('Unknown')}}" - name: Check Task Success vars: query: "[?id=='{{ taskId }}'].status" status: "{{ TASKS.json | json_query(query) | first }}" debug: - msg: - Result: "{{statusMap[status]}}" - failed_when: status|int != 3 + msg: + Result: "{{statusMap[status]|default('Unknown')}}" + failed_when: status|int != 3 and status|int != 7 when: checkSuccess|default(True)|bool == True diff --git a/task/util/csv2xml.py b/task/util/csv2xml.py new file mode 100644 index 0000000..f4a836a --- /dev/null +++ b/task/util/csv2xml.py @@ -0,0 +1,50 @@ +#!/usr/bin/python +# -*- encoding:utf-8 -*- + +# Required parameters: +# csv: CSV file to read in +# xml: XML file to export to +# +# Optional parameters: +# sep: separator, default ',' +# keys: keys to export, default '' (all) +# root: root node name, default 'rows' +# item: item node name, default 'row' + +import ast +import csv +import argparse + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--csv', type=str, required=True, help='CSV file to read') + parser.add_argument('-x', '--xml', type=str, required=True, help='XML file to export to') + parser.add_argument('-s', '--sep', type=str, required=False, default=',', help='Separator, default: ","') + parser.add_argument('-k', '--keys', type=str, required=False, default='', help='Keys to export, comma separate, default: "" (all)') + parser.add_argument('-r', '--root', type=str, required=False, default='rows', help='root node name, default: "rows"') + parser.add_argument('-i', '--item', type=str, required=False, default='row', help='item node name, default: "row"') + return parser.parse_args() + +if __name__ == '__main__': + args = parse_args() + xml_file = open(args.xml, mode='w') + csv_file = open(args.csv, mode='r') + csv_reader = csv.DictReader(csv_file, delimiter = args.sep) + + if args.keys == '': + keys = csv_reader.fieldnames + else: + keys = args.keys.split(',') + + xml_file.write('' + "\n") + xml_file.write("<{}>\n".format(args.root)) + for row in csv_reader: + item = '<{}'.format(args.item) + for key in keys: + if key in row: + item += ' {}="{}"'.format(key, row[key]) + item += "/>\n" + xml_file.write(item) + xml_file.write("".format(args.root)) + + xml_file.close() \ No newline at end of file diff --git a/task/util/csv2xml.yml b/task/util/csv2xml.yml new file mode 100644 index 0000000..8bc87d3 --- /dev/null +++ b/task/util/csv2xml.yml @@ -0,0 +1,12 @@ +# Required parameters: +# csv: CSV file to read in +# xml: XML file to export to +# +# Optional parameters: +# sep: separator, default ',' +# keys: keys to export, default '' (all) +# root: root node name, default 'rows' +# item: item node name, default 'row' + +- name: Export to {{file}} + local_action: command python "{{GLOBAL.baseDir}}"/task/util/csv2xml.py -c "{{csv}}" -x "{{xml}}" -s "{{ sep | default('|') }}" -k "{{keys | default('')}}" -r "{{root | default('rows')}} -i "{{item | default('row')}}" diff --git a/task/volume/remove_volumes_from_tier.yml b/task/volume/remove_volumes_from_tier.yml index aa7f722..07de123 100644 --- a/task/volume/remove_volumes_from_tier.yml +++ b/task/volume/remove_volumes_from_tier.yml @@ -37,6 +37,7 @@ debug: msg: volumeIds: "{{ volumeIds }}" + volumeNames: "{{ volumeNames }}" failed_when: volumeIds|length < volumeNames|length when: - volumeNames is defined