
# 通过VMware备份主机批量恢复脚本
#### 场景介绍
针对VMware备份上云场景，云备份（Cloud Backup and Recovery，CBR）增加VMware版本兼容性。但是，VMware混合云备份界面只能操作单个虚拟机进行备份数据恢复，在虚拟机较多时，界面操作步骤繁琐且并发太少。通过该章节内容实现脚本批量执行备份数据恢复，以增加备份副本恢复的并发数，提高效率。
#### 脚本说明
脚本基于Python语言开发，主要实现备份数据批量恢复功能、批量回滚功能等。
|  config.py  |      配置文件       |
|---|---|
| main.py     | 备份数据批量恢复主流程     |
| rollback.py | 批量回滚，用于批量删除云服务器 |
   
#### 前提条件
- 熟悉Python语言，并有Python环境搭建基础。
- 熟悉华为云备份数据恢复功能。
 
#### 方案使用到的接口
| **URL**                                                                     | **所属服务** | **用途**                    | **API** **文档**                                                                |
|:---|:---|:---|:---|
| POST /v3/auth/tokens                           | IAM      | 认证鉴权                      | [认证鉴权](https://support.huaweicloud.com/api-cbr/cbr_04_0011.html)              |
| GET /v3/{project_id}/backups/{backup_id}                                    | CBR      | 查询备份详情                    | [查询指定备份](https://support.huaweicloud.com/api-cbr/ShowBackup.html)             |
| POST /v1/{project_id}/cloudservers           | ECS      | 创建ECS                     | [创建云服务器](https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212668.html) |
| GET /v1/{project_id}/jobs/{job_id}                                          | ECS      | 查询ECS是否创建完成               | [查询任务的执行状态](https://support.huaweicloud.com/api-ecs/ecs_02_0901.html)         |
| GET /v1/{project_id}/cloudservers/{server_id} | ECS      | 查询ECS详情，获取创建好的虚拟机的挂载的磁盘信息 | [查询服务器详情](https://support.huaweicloud.com/api-ecs/ecs_02_0104.html)           |
| POST /v3/{project_id}/backups/{backup_id}/restore                           | CBR      | 使用备份恢复数据                  | [备份恢复](https://support.huaweicloud.com/api-cbr/RestoreBackup.html)            |
   
#### 参数获取
用户需要收集配置数据，完成config.py中变量的初始赋值，主要包含IAM鉴权参数、项目公共参数、服务器参数、备份数据参数、回滚参数以及监控任务参数。
**IAM鉴权参数**
1. [登录管理控制台](https://console.huaweicloud.com/console/?locale=zh-cn#/cbr/manager/csbs/vaultList)。
2. 在"控制台"页面，鼠标移动至右上方的用户名，在下拉列表中选择"我的凭证"。
3. 在"我的凭证"界面，单击API凭证界面查看。
![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031978712.png)
表1IAM鉴权参数说明 
| **参数**      | **是否必选** | **参数类型** | **描述**     |
|:---|:---|:---|:---|
| iamDomain   | 是        | String   | IAM用户名     |
| iamUser     | 是        | String   | IAM用户所属账号名 |
| iamPassword | 是        | String   | IAM用户密码    |
   
**公共参数**
1. [登录管理控制台](https://console.huaweicloud.com/console/?locale=zh-cn#/cbr/manager/csbs/vaultList)。
2. 在"控制台"页面，鼠标移动至右上方的用户名，在下拉列表中选择"我的凭证"。
3. 在"我的凭证"界面，单击API凭证界面查看。
![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067963145.png)
表2公共参数说明 
| **参数**      | **是否必选** | **参数类型** | **描述** |
|:---|:---|:---|:---|
| projectName | 是        | String   | 项目     |
| projectId   | 是        | String   | 项目ID   |
   
**服务器参数**
- **获取imageRef参数**
  1. 登录云服务器管理控制台。
     1. [登录管理控制台](https://console.huaweicloud.com/console/?locale=zh-cn#/cbr/manager/csbs/vaultList)。
     
     2. 单击管理控制台左上角的![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067841801.png)，选择区域。
     
     3. 单击"![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031803762.jpg)"，选择"计算 \> 弹性云服务器"。
      
  
  2. 在左侧导航栏选择"镜像服务"，找到对应镜像并复制镜像ID。 ![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031962158.png)
     
   

- **获取flavorRef参数**
  在弹性云服务器控制台界面，单击"购买弹性云服务器"，在"规格"项中找到对应的待创建云服务器的系统规格ID。
  ![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067963149.png)
  
- **获取vpcId** **参数**
  1. 登录云服务器管理控制台。
     1. [登录管理控制台](https://console.huaweicloud.com/console/?locale=zh-cn#/cbr/manager/csbs/vaultList)。
     
     2. 单击管理控制台左上角的![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067841809.png)，选择区域。
     
     3. 单击"![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031803766.jpg)"，选择"网络 \> 虚拟私有云"。
      
  
  2. 在左侧导航栏选择"虚拟私有云 \> 我的VPC"，找到对应vpcid。
  
  
  ![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031962162.png)
  
- **获取subnetId参数**
  在网络控制台界面，左侧导航栏选择"虚拟私有云 \> 子网"，找到对应VPC下已创建的子网（subnet）的网络ID。
  ![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067963153.png)
  
- **获取securityGroups** **参数**
  在网络控制台界面，左侧导航栏选择"访问控制 \> 安全组"，找到云服务器对应安全组信息。
  
![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067841813.png)
- **获取volumeType** **参数**
  云硬盘类型参数，目前支持"SATA"，"SAS"，"GPSSD"，"SSD"，"ESSD"，"GPSSD2"，"ESSD2"七种。
  具体信息可参见[创建云硬盘](https://support.huaweicloud.com/api-evs/evs_04_2003.html)volume_type参数说明。
  
- **获取enterpriseProjectId参数**
  企业项目ID，默认为'0'。
  
- **获取keyName参数**
  密钥名称，默认不需要配置。如果需要使用SSH密钥方式登录云服务器，请指定已创建密钥的名称。
  表3服务器参数说明 
  | **参数**              | **是否必选** | **参数类型** | **描述**                                                                                    |
  |:---|:---|:---|:---|
  | imageRef            | 是        | String   | 待创建云服务器的系统镜像，需要指定已创建镜像的ID，ID格式为通用唯一识别码（Universally Unique Identifier，简称UUID）。             |
  | volumeType          | 是        | String   | 云服务器系统盘对应的磁盘类型，需要与系统所提供的磁盘类型相匹配。 默认值："SSD"                  |
  | flavorRef           | 是        | String   | 待创建云服务器的系统规格的ID。 示例： "s7n.small.1"                           |
  | vpcId               | 是        | String   | 待创建云服务器所属虚拟私有云（简称VPC），需要指定已创建VPC的ID，UUID格式。                                               |
  | subnetId            | 是        | String   | 待创建云服务器所在的子网信息。 需要指定vpcid对应VPC下已创建的子网（subnet）的网络ID，UUID格式。 |
  | enterpriseProjectId | 是        | Integer  | 企业项目ID。 默认值：0                                               |
  | securityGroups      | 是        | List     | 云服务器对应安全组信息。 \["xxx", "xxx"\]                              |
  | keyName             | 否        | String   | 如果需要使用SSH密钥方式登录云服务器，请指定已创建密钥的名称。                                                          |
     
  
**备份数据参数**
1. [登录管理控制台](https://console.huaweicloud.com/console/?locale=zh-cn#/cbr/manager/csbs/vaultList)。
2. 单击管理控制台左上角的![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031803770.png)，选择区域。
3. 单击"![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031962166.jpg)"，选择"存储 \> 云备份 CBR"。
4. 选择"混合云备份 \> VMware备份"，在备份副本列表获取备份数据ID。
![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067963157.png)
表4备份数据参数说明 
| **参数**    | **是否必选** | **参数类型** | **描述**    |
|:---|:---|:---|:---|
| backupIds | 是        | List     | 备份数据ID列表。 |
   
**回滚参数**
执行备份数据恢复脚本后，待数据恢复完成，通过返回的日志中的serverId获取云服务器ID列表。
![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067858377.png)
表5回滚参数说明 
| **参数**         | **是否必选** | **参数类型** | **描述**                                                              |
|:---|:---|:---|:---|
| serverIds      | 是        | List     | 云服务器ID列表。 备份数据恢复完成后获取。               |
| deletePublicip | 否        | Boolean  | 配置删除云服务器是否删除云服务器绑定的弹性公网IP。 默认：False |
| deleteVolume   | 否        | Boolean  | 配置删除云服务器是否删除云服务器对应的数据盘。 默认：True        |
   
**监控任务参数**
表6监控任务参数说明 
| **参数**         | **是否必选** | **参数类型** | **描述**                                                  |
|:---|:---|:---|:---|
| delayInSeconds | 是        | Integer  | 循环监控任务状态时间间隔，单位为秒。 默认：10 |
   
#### 备份数据恢复操作
**前提条件**
- 已准备好备份数据。
- 完成config.py脚本中的配置项赋值。
  config.py示例如下：
  ```
  # IAM鉴权参数
  iamDomain = ""
  iamUser = ""
  iamPassword = ""
  # 项目参数
  projectName = ""
  projectId = ""
  # 服务器参数
  # CentOS 7.9 64bit
  imageRef = ""
  volumeType = "SSD"
  flavorRef = "s7n.small.1"
  vpcId = ""
  subnetId = ""
  enterpriseProjectId = 0
  securityGroups = [
      ""
  ]
  keyName = ""
  # 备份数据参数
  backupIds = ["",
               "",
               "",
               ""
               ]
  # 备份数据回退参数
  serverIds = ["", ""]
  deletePublicip = False
  deleteVolume = True
  # 监控任务参数
  delayInSeconds = 10
  ```
  
 
#### 执行备份数据恢复脚本
通过命令python .\\main.py执行main.py文件，开始备份数据恢复主流程。main.py示例如下：
```
import requests
import json
import time
import config
class IamLogin:
    """
    login by iam, return token
    """
    def __init__(self, iamDomain: str, iamUser: str, iamPassword: str, projectName: str):
        self.iamDomain = iamDomain
        self.iamUser = iamUser
        self.iamPassword = iamPassword
        self.projectName = projectName
        self.iamTokenUrl = "https://iam.{}.myhuaweicloud.com/v3/auth/tokens".format(self.projectName)
    def get_auth_token(self) -> dict:
        """
        return token
        """
        payload = {
            "auth": {
                "identity": {
                    "methods": [
                        "password"
                    ],
                    "password": {
                        "user": {
                            "domain": {
                                "name": self.iamDomain
                            },
                            "name": self.iamUser,
                            "password": self.iamPassword
                        }
                    }
                },
                "scope": {
                    "project": {
                        "name": self.projectName
                    }
                }
            }
        }
        json_data = json.dumps(payload)
        try:
            response = requests.post(self.iamTokenUrl,
                                     data=json_data,
                                     headers={'Content-Type': 'application/json'},
                                     verify=False)
            # 检查请求是否成功
            response.raise_for_status()
            # 获取响应头的token
            access_token = response.headers.get("X-Subject-Token")
            return {
                "token": access_token,
                "code": 0
            }
        except requests.exceptions.RequestException as e:
            print("get token error: ", e)
            return {
                "code": 1,
                "msg": "get token error"
            }
class GetBackup:
    """
    获取Backup信息
    """
    def __init__(self, token, projectName, projectId, backupId) -> None:
        self.token = token
        self.header = {
            'Content-Type': 'application/json', 'X-Auth-Token': self.token
        }
        self.projectName = projectName
        self.projectId = projectId
        self.backupId = backupId
    def get_backup(self) -> dict:
        """
        根据id查询备份数据
        """
        print("get backup data")
        self.getBackupDataUrl = "https://cbr.{}.myhuaweicloud.com/v3/{}/backups/{}".format(self.projectName,
                                                                                         self.projectId,
                                                                                         self.backupId)
        try:
            response = requests.get(url=self.getBackupDataUrl, headers=self.header, verify=False)
            # 检查请求是否成功
            response.raise_for_status()
            # 获取响应数据
            result = json.loads(str(response.content, encoding="utf-8"))
            # 解析响应数据
            return {
                "code": 0,
                "backups": result
            }
        except requests.exceptions.RequestException as e:
            print("get backup data error: ", e)
            return {
                "code": 1,
                "msg": "get backup data error"
            }
class CreateServer:
    """
    创建服务器
    """
    def __init__(self, token, projectName, projectId, backupId, backups, imageRef, flavorRef, vpcId,
                 subnetId, enterpriseProjectId, securityGroups, keyName) -> None:
        self.token = token
        self.header = {
            'Content-Type': 'application/json', 'X-Auth-Token': self.token
        }
        self.projectName = projectName
        self.projectId = projectId
        self.backupId = backupId
        self.backups = backups
        self.imageRef = imageRef
        self.flavorRef = flavorRef
        self.vpcId = vpcId
        self.subnetId = subnetId
        self.enterpriseProjectId = enterpriseProjectId
        self.securityGroups = securityGroups
        self.keyName = keyName
    def transfor_metadata_to_server(self) -> None:
        """
        构造server参数，返回backup_volumes_attached
        """
        print("transfor metadata to server")
        backup = self.backups["backup"]
        root_volume, data_volumes = handle_backup_volumes(backup["children"])
        # 构建创建服务器的参数
        payload = {
            "server": {
                "name": backup["name"],
                "imageRef": self.imageRef,
                "root_volume": root_volume,
                "data_volumes": data_volumes,
                "flavorRef": self.flavorRef,
                "vpcid": self.vpcId,
                "security_groups": self.securityGroups,
                "nics": [{
                    "subnet_id": self.subnetId
                }
                ],
                "key_name": self.keyName,
                "count": 1,
                "extendparam": {
                    "enterprise_project_id": self.enterpriseProjectId
                }
            }
        }
        self.payload = payload
        print(payload)
    def create_server(self) -> dict:
        print("create server")
        self.createServerUrl = "https://ecs.{}.myhuaweicloud.com/v1/{}/cloudservers".format(self.projectName, self.projectId)
        json_data = json.dumps(self.payload)
        try:
            response = requests.post(self.createServerUrl,
                                     data=json_data,
                                     headers=self.header,
                                     verify=False)
            print(json.loads(str(response.content, encoding="utf-8")))
            # 检查请求是否成功
            response.raise_for_status()
            # 获取响应数据
            result = json.loads(str(response.content, encoding="utf-8"))
            # {'job_id': 'ff8080828ee22cea018f27bdd23c6477', 'serverIds': ['6cb36d34-b111-48be-9577-b52dbb74adae']}
            return {
                "job_id": result["job_id"],
                "serverIds": result["serverIds"],
                "code": 0
            }
        except requests.exceptions.RequestException as e:
            print("create server error: ", e)
            return {
                "msg": "create server error",
                "code": 1
            }
    def get_server(self, serverId):
        """
        根据id查询ecs信息
        """
        print("get server, serverId is ", serverId)
        getServerdataUrl = "https://ecs.{}.myhuaweicloud.com/v1/{}/cloudservers/{}".format(self.projectName,
                                                                                           self.projectId,
                                                                                           serverId)
        try:
            response = requests.get(url=getServerdataUrl, headers=self.header, verify=False)
            # 检查请求是否成功
            response.raise_for_status()
            # 获取响应数据
            result = json.loads(str(response.content, encoding="utf-8"))
            # 解析响应数据
            server_volumes_attached = result["server"]["os-extended-volumes:volumes_attached"]
            # [{'id': '66fc89cd-874c-480a-8e49-bd1d361ff0a0', 'delete_on_termination': 'true', 'device': '/dev/vda', 'bootIndex': '0'}]
            return server_volumes_attached
        except requests.exceptions.RequestException as e:
            print("get server data error: ", e)
def handle_backup_volumes(backupChildren):
    # 单独处理用于构造root_volume和data_volumes
    root_volume = {}
    data_volumes = []
    # 根据Children顺序赋值root_volume和data_volumes
    for child in backupChildren:
        bootable = child["extend_info"]["bootable"]
        volumeSize = max(40, child["resource_size"])
        if bootable:
            root_volume["volumetype"] = config.volumeType
            root_volume["size"] = volumeSize
        else:
            data_volumes.append({
                "volumetype": config.volumeType,
                "size": volumeSize
            })
    return root_volume, data_volumes
def build_restore_mappings(server_volumes_attached, backup_children):
    print("build restore mappings")
    print(server_volumes_attached)
    print(backup_children)
    # 根据bootIndex构建restore的mappings
    restore_mappings = []
    for server_volume in server_volumes_attached:
        server_volume_id = server_volume["id"]
        server_bootIndex = server_volume["bootIndex"]
        backup_child = backup_children[int(server_bootIndex)]
        restore_mappings.append({
            "backup_id": backup_child["id"],
            "volume_id": server_volume_id
        })
    return restore_mappings
class Job:
    """
    获取Job信息
    """
    def __init__(self, token, projectName, projectId, jobId) -> None:
        self.token = token
        self.header = {
            'Content-Type': 'application/json', 'X-Auth-Token': self.token
        }
        self.projectName = projectName
        self.projectId = projectId
        self.jobId = jobId
        self.getJobUrl = "https://ecs.{}.myhuaweicloud.com/v1/{}/jobs/{}".format(self.projectName,
                                                                                 self.projectId,
                                                                                 self.jobId)
    def get_job_status(self) -> dict:
        """
        根据jobId查询任务信息
        """
        print("get job status, jobId is ", self.jobId)
        try:
            response = requests.get(url=self.getJobUrl, headers=self.header, verify=False)
            # 检查请求是否成功
            response.raise_for_status()
            # 获取响应数据
            result = json.loads(str(response.content, encoding="utf-8"))
            # 解析响应数据
            return {
                "code": 0,
                "status": result["status"],
            }
        except requests.exceptions.RequestException as e:
            print("get job status error: ", e)
            return {
                "code": 1,
                "msg": "get job status error",
            }
class Restore:
    """
    恢复备份数据
    """
    def __init__(self, token, projectName, projectId, backupId, serverId, restore_mappings) -> dict:
        self.token = token
        self.header = {
            'Content-Type': 'application/json', 'X-Auth-Token': self.token
        }
        self.projectName = projectName
        self.projectId = projectId
        self.backupId = backupId
        self.serverId = serverId
        self.restore_mappings = restore_mappings
        self.backupRestoreUrl = "https://cbr.{}.myhuaweicloud.com/v3/{}/backups/{}/restore".format(self.projectName,
                                                                                                   self.projectId,
                                                                                                   self.backupId)
    def backup_restore(self) -> dict:
        """
        恢复备份数据
        """
        print("backup restore, backupId is {}, serverId is {}".format(self.backupId, self.serverId))
        payload = {
            "restore": {
                "mappings": self.restore_mappings,
                "power_on": True,
                "server_id": self.serverId
            }
        }
        json_data = json.dumps(payload)
        try:
            response = requests.post(self.backupRestoreUrl,
                                     data=json_data,
                                     headers=self.header,
                                     verify=False)
            # 检查请求是否成功
            response.raise_for_status()
            # 获取响应数据
            result = json.loads(str(response.content, encoding="utf-8"))
            # {}
            return {
                "code": 0
            }
        except requests.exceptions.RequestException as e:
            print("backup restore error: ", e)
            return {
                "code": 1,
                "msg": "backup restore error",
            }
def http_error(code):
    return code == 1
if __name__ == '__main__':
    # IAM鉴权获取token
    print("start to get token")
    tokenObj = IamLogin(config.iamDomain, config.iamUser, config.iamPassword, config.projectName).get_auth_token()
    if http_error(tokenObj["code"]):
        print("backup failed, msg is {}".format(tokenObj["msg"]))
    else:
        token = tokenObj["token"]
        # 记录创建服务器的job信息
        jobInfos = []
        # 根据备份副本ID列表，批量恢复数据
        for backupId in config.backupIds:
            print("start the backup process, backup id is ", backupId)
            resultData = {"backupId": backupId}
            # 初始化GetBackup参数
            get_backup = GetBackup(token, config.projectName, config.projectId, backupId)
            # 根据backupId查询备份元数据
            backupsObj = get_backup.get_backup()
            if http_error(backupsObj["code"]):
                resultData["status"] = "FAILED"
                resultData["msg"] = backupsObj["msg"]
                print("backup process failed, msg is {}".format(backupsObj["msg"]))
                jobInfos.append(resultData)
                continue
            else:
                backups = backupsObj["backups"]
                # 初始化CreateServer参数
                create_server = CreateServer(token, config.projectName, config.projectId, backupId, backups,
                                             config.imageRef, config.flavorRef, config.vpcId,
                                             config.subnetId, config.enterpriseProjectId, config.securityGroups,
                                             config.keyName)
                create_server.transfor_metadata_to_server()
                # 创建服务器
                createServerData = create_server.create_server()
                if http_error(createServerData["code"]):
                    resultData["status"] = "FAILED"
                    resultData["msg"] = createServerData["msg"]
                    print("backup process failed, msg is {}".format(createServerData["msg"]))
                    jobInfos.append(resultData)
                    continue
                else:
                    serverId = createServerData["serverIds"][0]
                    resultData["serverId"] = serverId
                    resultData["status"] = "START"
                    # 监控创建服务器任务状态
                    print("start to get create server job status, please wait...")
                    get_job = Job(token, config.projectName, config.projectId, createServerData["job_id"])
                    while True:
                        time.sleep(config.delayInSeconds)
                        jobStatusObj = get_job.get_job_status()
                        if http_error(jobStatusObj["code"]):
                            resultData["status"] = "FAILED"
                            resultData["msg"] = createServerData["msg"]
                            print("backup process failed, msg is {}".format(createServerData["msg"]))
                            jobInfos.append(resultData)
                            break
                        else:
                            jobStatus = jobStatusObj["status"]
                            if jobStatus == "SUCCESS":
                                createServerData["status"] = "SUCCESS"
                                print("The create server job is executed successfully.")
                                break
                            elif jobStatus == "RUNNING" or jobStatus == "INIT":
                                print("job status is {}, please wait...".format(jobStatus))
                            if jobStatus is None or jobStatus == "FAIL":
                                print("The job failed.")
                                break
                    # 恢复备份数据
                    if createServerData["status"] == "SUCCESS":
                        # 根据bootIndex构建restore的mappings
                        server_volumes_attached = create_server.get_server(serverId)
                        backup_children = backups["backup"]["children"]
                        restore_mappings = build_restore_mappings(server_volumes_attached, backup_children)
                        restore = Restore(token, config.projectName, config.projectId, backupId, serverId, restore_mappings)
                        restoreObj = restore.backup_restore()
                        if http_error(restoreObj["code"]):
                            resultData["status"] = "FAILED"
                            resultData["msg"] = restoreObj["msg"]
                            print("backup process failed, msg is {}".format(restoreObj["msg"]))
                            jobInfos.append(resultData)
                        else:
                            resultData["status"] = "SUCCESS"
                            jobInfos.append(resultData)
                    else:
                        resultData["status"] = "FAILED"
                        resultData["msg"] = createServerData["msg"]
                        print("backup process failed, msg is {}".format(createServerData["msg"]))
                        jobInfos.append(resultData)
                        break
        # 输出成功和失败的数据
        print(jobInfos)
        successJobList = []
        failedJobList = []
        for jobInfo in jobInfos:
            if jobInfo["status"] == "SUCCESS":
                successJobList.append(jobInfo)
            else:
                failedJobList.append(jobInfo)
        print("backup process end")
        print({
            "successJobList": successJobList,
            "failedJobList": failedJobList
        })
```
执行完成后，回显信息如下：
![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031803774.png)
![](https://support.huaweicloud.com/bestpractice-cbr/public_sys-resources/note_3.0-zh-cn.png)
其中，successJobList为触发备份数据恢复成功的列表，failedJobList为触发备份数据恢复失败的列表。
#### 备份恢复结果验证操作
- **备份副本列表**
1. [登录管理控制台](https://console.huaweicloud.com/console/?locale=zh-cn#/cbr/manager/csbs/vaultList)。
2. 单击管理控制台左上角的![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031962170.png)，选择区域。
3. 单击"![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067963165.jpg)"，选择"存储 \> 云备份 CBR"。
4. 左侧导航栏选择"混合云备份 \> VMware备份"，备份副本列表的状态下显示"正在恢复"。恢复完成后，状态变为"可用"。 ![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067841825.png)
   
- **云服务器列表**
1. 单击"![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031820328.jpg)"，选择"计算 \> 弹性云服务器ECS"。
2. 左侧导航栏选择"弹性云服务器 \> 弹性云服务器"，云服务器列表的状态下显示"运行中"。 ![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067979725.png)
   
- **云服务器磁盘详情**
在弹性云服务器控制台界面，点击对应的云服务器名称，查看云服务器磁盘数据均正常。
![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067963169.png)
![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002067841829.png)
#### 执行回滚
**前提条件**
1. config.py中的回滚参数（serverIds参数）已经配置完毕。
2. 备份数据恢复流程已经执行完毕。
 
#### 执行回滚操作脚本
通过命令python .\\rollback.py执行rollback.py文件，开始备份数据恢复主流程。
rollback.py示例如下：
```
import requests
import json
import time
import config
class IamLogin:
    """
    login by iam, return token
    """
    def __init__(self, iamDomain: str, iamUser: str, iamPassword: str, projectName: str):
        self.iamDomain = iamDomain
        self.iamUser = iamUser
        self.iamPassword = iamPassword
        self.projectName = projectName
        self.iamTokenUrl = "https://iam.{}.myhuaweicloud.com/v3/auth/tokens".format(self.projectName)
    def get_auth_token(self) -> dict:
        """
        return token
        """
        payload = {
            "auth": {
                "identity": {
                    "methods": [
                        "password"
                    ],
                    "password": {
                        "user": {
                            "domain": {
                                "name": self.iamDomain
                            },
                            "name": self.iamUser,
                            "password": self.iamPassword
                        }
                    }
                },
                "scope": {
                    "project": {
                        "name": self.projectName
                    }
                }
            }
        }
        json_data = json.dumps(payload)
        try:
            response = requests.post(self.iamTokenUrl,
                                     data=json_data,
                                     headers={'Content-Type': 'application/json'},
                                     verify=False)
            # 检查请求是否成功
            response.raise_for_status()
            # 获取响应头的token
            access_token = response.headers.get("X-Subject-Token")
            return {
                "token": access_token,
                "code": 0
            }
        except requests.exceptions.RequestException as e:
            print("get token error: ", e)
            return {
                "msg": "get token error",
                "code": 1
            }
class Rollback:
    """
    服务器回滚
    """
    def __init__(self, token, projectName, projectId, serverIds, deletePublicIp, deleteVolume) -> None:
        self.token = token
        self.projectName = projectName
        self.projectId = projectId
        self.header = {
            'Content-Type': 'application/json',
            'X-Project-Id': self.projectId,
            'X-Auth-Token': self.token
        }
        self.serverIds = serverIds
        self.deletePublicIp = deletePublicIp
        self.deleteVolume = deleteVolume
        self.deleteServerUrl = "https://ecs.{}.myhuaweicloud.com/v1/{}/cloudservers/delete".format(self.projectName,
                                                                                                   self.projectId)
    def delete_cloudservers(self) -> dict:
        """
        根据指定的云服务器ID列表，删除云服务器。可以单个删除，也可以批量删除。
        """
        print("batch delete servers, serverIds are ", self.serverIds)
        if len(self.serverIds) == 0:
            return {
                "code": 1,
                "msg": "serverIds is empty"
            }
        servers = []
        for serverId in self.serverIds:
            servers.append({"id": serverId})
        payload = {
            "servers": servers,
            "delete_publicip": self.deletePublicIp,
            "delete_volume": self.deleteVolume
        }
        json_data = json.dumps(payload)
        try:
            response = requests.post(self.deleteServerUrl,
                                     data=json_data,
                                     headers=self.header,
                                     verify=False)
            # 检查请求是否成功
            response.raise_for_status()
            # 获取响应数据
            result = json.loads(str(response.content, encoding="utf-8"))
            # {'job_id': 'ff8080828ee21983018f27b42f310e0f'}
            # 解析响应数据
            return {
                "code": 0,
                "job_id": result["job_id"]
            }
        except requests.exceptions.RequestException as e:
            print("batch delete servers error: ", e)
            return {
                "code": 1,
                "msg": "batch delete servers error"
            }
class Job:
    """
    获取Job信息
    """
    def __init__(self, token, projectName, projectId, jobId) -> None:
        self.token = token
        self.header = {
            'Content-Type': 'application/json', 'X-Auth-Token': self.token
        }
        self.projectName = projectName
        self.projectId = projectId
        self.jobId = jobId
        self.getJobUrl = "https://ecs.{}.myhuaweicloud.com/v1/{}/jobs/{}".format(self.projectName,
                                                                                 self.projectId,
                                                                                 self.jobId)
    def get_job_status(self) -> dict:
        """
        根据jobId查询任务信息
        """
        print("get job status, jobId is ", self.jobId)
        try:
            response = requests.get(url=self.getJobUrl, headers=self.header, verify=False)
            # 检查请求是否成功
            response.raise_for_status()
            # 获取响应数据
            result = json.loads(str(response.content, encoding="utf-8"))
            # 解析响应数据
            return {
                "code": 0,
                "status": result["status"],
            }
        except requests.exceptions.RequestException as e:
            print("get job status error: ", e)
            return {
                "code": 1,
                "msg": "get job status error",
            }
def http_error(code):
    return code == 1
if __name__ == '__main__':
    # IAM鉴权获取token
    print("start to get token")
    tokenObj = IamLogin(config.iamDomain, config.iamUser, config.iamPassword, config.projectName).get_auth_token()
    if http_error(tokenObj["code"]):
        print("rollback failed, msg is {}".format(tokenObj["msg"]))
    else:
        token = tokenObj["token"]
        # 根据服务器ID列表，批量删除数据
        print("start to rollback")
        rollback = Rollback(token, config.projectName, config.projectId, config.serverIds, config.deletePublicip,
                            config.deleteVolume)
        jobObj = rollback.delete_cloudservers()
        if http_error(jobObj["code"]):
            print("rollback failed, msg is {}".format(jobObj["msg"]))
        else:
            get_job = Job(token, config.projectName, config.projectId, jobObj["job_id"])
            # 监控任务状态
            print("start to get rollback job status, please wait...")
            while True:
                time.sleep(config.delayInSeconds)
                jobStatusObj = get_job.get_job_status()
                if http_error(jobStatusObj["code"]):
                    print("rollback failed, msg is {}".format(jobStatusObj["msg"]))
                    break
                else:
                    jobStatus = jobStatusObj["status"]
                    if jobStatus == "SUCCESS":
                        print("The job is executed successfully.")
                        break
                    elif jobStatus == "RUNNING" or jobStatus == "INIT":
                        print("job status is {}, please wait...".format(jobStatus))
                    if jobStatus is None or jobStatus == "FAIL":
                        print("The job failed.")
                        break
```
执行完成后，回显信息如下
![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031803786.png)
#### 回滚结果验证
在弹性云服务器控制台界面，左侧导航栏选择"弹性云服务器 \> 弹性云服务器"，云服务器已被清退。
![](https://support.huaweicloud.com/bestpractice-cbr/zh-cn_image_0000002031962182.png)
