feat: Add Aliyun provider support for desktop environment (#304)

* Adding support for aliyun as a provider

* feat: enhance Aliyun provider support

- Added Aliyun as a new provider in the desktop environment.
- Updated the environment configuration guidelines for Aliyun, including prerequisites and environment variables.
- Implemented instance allocation and management functions for Aliyun ECS, including signal handling for graceful termination.
- Improved logging and error handling during instance creation and status checks.
- Adjusted the provider's methods to utilize the new instance management functions.
This commit is contained in:
Quyu Kong
2025-08-12 14:31:08 +08:00
committed by GitHub
parent d2ae0f697d
commit 893b059e55
7 changed files with 668 additions and 1 deletions

View File

@@ -158,7 +158,7 @@ class DesktopEnv(gym.Env):
# Track whether environment has been used (step/setup) to optimize snapshot revert
# docker, aws, gcp, azure are always unused as the emulator starts from a clean state
# vmware, virtualbox are always used as the emulator starts from a dirty state
if self.provider_name in {"docker", "aws", "gcp", "azure"}:
if self.provider_name in {"docker", "aws", "gcp", "azure", "aliyun"}:
self.is_environment_used = False
elif self.provider_name in {"vmware", "virtualbox"}:
self.is_environment_used = True

View File

@@ -31,5 +31,9 @@ def create_vm_manager_and_provider(provider_name: str, region: str, use_proxy: b
from desktop_env.providers.docker.manager import DockerVMManager
from desktop_env.providers.docker.provider import DockerProvider
return DockerVMManager(), DockerProvider(region)
elif provider_name == "aliyun":
from desktop_env.providers.aliyun.manager import AliyunVMManager
from desktop_env.providers.aliyun.provider import AliyunProvider
return AliyunVMManager(), AliyunProvider()
else:
raise NotImplementedError(f"{provider_name} not implemented!")

View File

@@ -0,0 +1,79 @@
# Aliyun ECS Provider Configuration Guide
This guide explains how to configure and use the Aliyun ECS provider for OSWorld desktop environments.
## Configuration Process
1. **Aliyun Account**: You need an active Aliyun Cloud account. This script uses pay-as-you-go billing by default, so ensure your account balance is above 100.
2. **Access Keys**: Create AccessKey ID and AccessKey Secret in Aliyun RAM Access Control Console and grant ECS control permissions
3. **VPC Setup**: Create a VPC, VSwitch, and Security Group in your target region
4. **Custom Images**: Create OSWorld custom images
5. It is recommended to manually complete the ECS creation process once to record all required environment variable information.
## Environment Variables
Set the following environment variables in your `.env` file:
```bash
# Aliyun Access Credentials
ALIYUN_ACCESS_KEY_ID=your_access_key_id
ALIYUN_ACCESS_KEY_SECRET=your_access_key_secret
# ECS Configuration Information
ALIYUN_REGION=eu-central-1
ALIYUN_IMAGE_ID=your_image_id
ALIYUN_INSTANCE_TYPE=ecs.e-c1m2.large
ALIYUN_VSWITCH_ID=vsw-xxxxxxxxx
ALIYUN_SECURITY_GROUP_ID=sg-xxxxxxxxx
```
## Required Aliyun Resources
### 1. VPC and VSwitch
- Create a VPC in your target region
- Create a VSwitch within the VPC
- Ensure the VSwitch has internet access for VNC connectivity
### 2. Security Group
**⚠️ Important**: Please strictly follow the port settings below to prevent OSWorld tasks from failing due to connection issues:
#### Inbound Rules (8 rules required)
| Type | Protocol | Port Range | Source | Description |
|------|----------|------------|--------|-------------|
| SSH | TCP | 22 | 0.0.0.0/0 | SSH access |
| HTTP | TCP | 80 | 172.31.0.0/16 | HTTP traffic |
| Custom TCP | TCP | 5000 | 172.31.0.0/16 | OSWorld backend service |
| Custom TCP | TCP | 5910 | 0.0.0.0/0 | NoVNC visualization port |
| Custom TCP | TCP | 8006 | 172.31.0.0/16 | VNC service port |
| Custom TCP | TCP | 8080 | 172.31.0.0/16 | VLC service port |
| Custom TCP | TCP | 8081 | 172.31.0.0/16 | Additional service port |
| Custom TCP | TCP | 9222 | 172.31.0.0/16 | Chrome control port |
#### Outbound Rules (1 rule required)
| Type | Protocol | Port Range | Destination | Description |
|------|----------|------------|-------------|-------------|
| All traffic | All | All | 0.0.0.0/0 | Allow all outbound traffic |
### 3. Custom Images
You need to create a custom OSWorld image for Aliyun ECS. Please follow the instructions in the "Creating Custom ECS Images for OSWorld" section.
## Creating Custom ECS Images for OSWorld
This section provides guidance on how to create the custom ECS images required for OSWorld desktop environments. The process involves setting up a base instance with desktop environment and VNC server, then creating a custom image from it.
### Step-by-Step Image Creation Process
#### Step 1: Upload existing qcow2 image to Aliyun
- Download the provided qcow2 image from the link in `desktop_env/providers/docker/manager.py`: https://huggingface.co/datasets/xlangai/ubuntu_osworld/resolve/main/Ubuntu.qcow2.zip
- Unzip the downloaded file and upload it to Aliyun Object Storage Service (OSS). Make sure the OSS is in the same region as your target region to launch ECS instance.
- In your ECS dashboard, go to "Images" and You will see the "Import Image" button. Click it and follow the instructions to import the qcow2 image from OSS.
- After the import is complete, you will see the imported image in the "Images" list.
#### Step 2: Create a new image
Note that the image you created in Step 1 will have a different resolution than the one you want to use for OSWorld (1920x1080). We need to customize the image to have the correct resolution and setup noVNC.
- Go to `Instances` tab and create a new instance with the imported image.
- Connect to the running instance via VNC.
- After connecting to the instance, please open the terminal and download this configuration script: `https://gist.githubusercontent.com/qykong/bea58ff98f20057d3a69921276dd4553/raw/cd1a91a0840c4192d793f43cfb90553370343b08/config.sh`.
- Run the script and reboot your instance.
- After rebooting, the instance will have the correct resolution and noVNC setup. You can connect to the instance via "http://<your_instance_public_ip>:5910/vnc.html" (make sure your security group allows port 5910).
- Save the running instance as a new image. The new image will be used as the OSWorld image.

View File

@@ -0,0 +1,81 @@
# 阿里云ECS提供商配置指南
本指南介绍如何为OSWorld桌面环境配置和使用阿里云ECS。
## 配置流程
1. **阿里云账户**您需要一个有效的阿里云账户本脚本默认ECS通过按量付费方式拉起需保证账户余额在100以上。
2. **访问密钥**在阿里云RAM访问控制控制台中创建AccessKey ID和AccessKey Secret并授权ECS控制权限
3. **VPC设置**在目标地域创建VPC、交换机和安全组
4. **自定义镜像**创建OSWorld自定义镜像。
5. 建议手动完成一次ECS创建流程后记录所有需要的环境变量信息。
## 环境变量
在您的`.env`文件中设置以下环境变量:
```bash
# 阿里云访问凭证
ALIYUN_ACCESS_KEY_ID=your_access_key_id
ALIYUN_ACCESS_KEY_SECRET=your_access_key_secret
# ECS配置信息
ALIYUN_REGION=eu-central-1
ALIYUN_IMAGE_ID=your_image_id
ALIYUN_INSTANCE_TYPE=ecs.e-c1m2.large
ALIYUN_VSWITCH_ID=vsw-xxxxxxxxx
ALIYUN_SECURITY_GROUP_ID=sg-xxxxxxxxx
```
## 所需阿里云资源
### 1. VPC和交换机
- 在目标地域创建VPC
- 在VPC内创建交换机
- 确保交换机具有互联网访问能力以支持VNC连接
### 2. 安全组
**⚠️ 重要提示**请严格按照以下端口设置以防止OSWorld任务因连接问题而失败
#### 入方向规则需要8条规则
| 类型 | 协议 | 端口范围 | 源地址 | 描述 |
|------|------|----------|--------|------|
| SSH | TCP | 22 | 0.0.0.0/0 | SSH访问 |
| HTTP | TCP | 80 | 172.31.0.0/16 | HTTP流量 |
| 自定义TCP | TCP | 5000 | 172.31.0.0/16 | OSWorld后端服务 |
| 自定义TCP | TCP | 5910 | 0.0.0.0/0 | NoVNC可视化端口 |
| 自定义TCP | TCP | 8006 | 172.31.0.0/16 | VNC服务端口 |
| 自定义TCP | TCP | 8080 | 172.31.0.0/16 | VLC服务端口 |
| 自定义TCP | TCP | 8081 | 172.31.0.0/16 | 附加服务端口 |
| 自定义TCP | TCP | 9222 | 172.31.0.0/16 | Chrome控制端口 |
#### 出方向规则需要1条规则
| 类型 | 协议 | 端口范围 | 目标地址 | 描述 |
|------|------|----------|----------|------|
| 全部流量 | 全部 | 全部 | 0.0.0.0/0 | 允许所有出站流量 |
### 3. 自定义镜像
您需要为阿里云ECS创建自定义OSWorld镜像。请按照"为OSWorld创建自定义ECS镜像"部分的说明进行操作。
## 为OSWorld创建自定义ECS镜像
本部分提供如何创建OSWorld桌面环境所需的自定义ECS镜像的指导。该过程包括设置带有桌面环境和VNC服务器的基础实例然后从中创建自定义镜像。
### 分步镜像创建过程
#### 步骤1上传现有qcow2镜像到阿里云
-`desktop_env/providers/docker/manager.py`中的链接下载提供的qcow2镜像https://huggingface.co/datasets/xlangai/ubuntu_osworld/resolve/main/Ubuntu.qcow2.zip
- 解压下载的文件并上传到阿里云对象存储服务OSS。确保OSS与您要启动ECS实例的目标地域在同一地域。
- 在您的ECS控制台中转到"镜像"页面,您将看到"导入镜像"按钮。点击它并按照说明从OSS导入qcow2镜像。
- 导入完成后,您将在"镜像"列表中看到导入的镜像。
#### 步骤2创建新镜像
请注意您在步骤1中创建的镜像分辨率与您想要用于OSWorld的分辨率1920x1080不同。我们需要自定义镜像以具有正确的分辨率并设置noVNC。
- 转到"实例"选项卡,使用导入的镜像创建新实例。
- 通过VNC连接到正在运行的实例。
- 连接到实例后,请打开终端并下载此配置脚本:`https://gist.githubusercontent.com/qykong/bea58ff98f20057d3a69921276dd4553/raw/cd1a91a0840c4192d793f43cfb90553370343b08/config.sh`
- 运行脚本并重启您的实例。
- 重启后实例将具有正确的分辨率和noVNC设置。您可以通过"http://<your_instance_public_ip>:5910/vnc.html"连接到实例确保您的安全组允许端口5910
- 将正在运行的实例保存为新镜像。新镜像将用作OSWorld镜像。

View File

View File

@@ -0,0 +1,289 @@
import os
import logging
import dotenv
import time
import signal
import requests
from alibabacloud_ecs20140526.client import Client as ECSClient
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_ecs20140526 import models as ecs_models
from alibabacloud_tea_util.client import Client as UtilClient
from desktop_env.providers.base import VMManager
dotenv.load_dotenv()
for env_name in [
"ALIYUN_REGION",
"ALIYUN_VSWITCH_ID",
"ALIYUN_SECURITY_GROUP_ID",
"ALIYUN_IMAGE_ID",
"ALIYUN_ACCESS_KEY_ID",
"ALIYUN_ACCESS_KEY_SECRET",
"ALIYUN_INSTANCE_TYPE",
]:
if not os.getenv(env_name):
raise EnvironmentError(f"{env_name} must be set in the environment variables.")
logger = logging.getLogger("desktopenv.providers.aliyun.AliyunVMManager")
logger.setLevel(logging.INFO)
ALIYUN_INSTANCE_TYPE = os.getenv("ALIYUN_INSTANCE_TYPE")
ALIYUN_ACCESS_KEY_ID = os.getenv("ALIYUN_ACCESS_KEY_ID")
ALIYUN_ACCESS_KEY_SECRET = os.getenv("ALIYUN_ACCESS_KEY_SECRET")
ALIYUN_REGION = os.getenv("ALIYUN_REGION")
ALIYUN_IMAGE_ID = os.getenv("ALIYUN_IMAGE_ID")
ALIYUN_SECURITY_GROUP_ID = os.getenv("ALIYUN_SECURITY_GROUP_ID")
ALIYUN_VSWITCH_ID = os.getenv("ALIYUN_VSWITCH_ID")
WAIT_DELAY = 20
MAX_ATTEMPTS = 15
def _allocate_vm(screen_size=(1920, 1080)):
"""
Allocate a new Aliyun ECS instance
"""
assert screen_size == (1920, 1080), "Only 1920x1080 screen size is supported"
config = open_api_models.Config(
access_key_id=ALIYUN_ACCESS_KEY_ID,
access_key_secret=ALIYUN_ACCESS_KEY_SECRET,
region_id=ALIYUN_REGION,
)
client = ECSClient(config)
instance_id = None
original_sigint_handler = signal.getsignal(signal.SIGINT)
original_sigterm_handler = signal.getsignal(signal.SIGTERM)
def signal_handler(sig, frame):
if instance_id:
signal_name = "SIGINT" if sig == signal.SIGINT else "SIGTERM"
logger.warning(
f"Received {signal_name} signal, terminating instance {instance_id}..."
)
try:
delete_request = ecs_models.DeleteInstancesRequest(
region_id=ALIYUN_REGION,
instance_ids=UtilClient.to_jsonstring([instance_id]),
force=True,
)
client.delete_instances(delete_request)
logger.info(
f"Successfully terminated instance {instance_id} after {signal_name}."
)
except Exception as cleanup_error:
logger.error(
f"Failed to terminate instance {instance_id} after {signal_name}: {str(cleanup_error)}"
)
# Restore original signal handlers
signal.signal(signal.SIGINT, original_sigint_handler)
signal.signal(signal.SIGTERM, original_sigterm_handler)
# Raise appropriate exception based on signal type
if sig == signal.SIGINT:
raise KeyboardInterrupt
else:
# For SIGTERM, exit gracefully
import sys
sys.exit(0)
try:
# Set up signal handlers for both SIGINT and SIGTERM
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
logger.info(
f"Creating new ECS instance in region {ALIYUN_REGION} with image {ALIYUN_IMAGE_ID}"
)
# Create instance request
request = ecs_models.RunInstancesRequest(
region_id=ALIYUN_REGION,
image_id=ALIYUN_IMAGE_ID,
instance_type=ALIYUN_INSTANCE_TYPE,
security_group_id=ALIYUN_SECURITY_GROUP_ID,
v_switch_id=ALIYUN_VSWITCH_ID,
instance_name=f"OSWorld-Desktop-{int(time.time())}",
description="OSWorld Desktop Environment Instance",
internet_max_bandwidth_out=10,
internet_charge_type="PayByTraffic",
instance_charge_type="PostPaid",
system_disk=ecs_models.RunInstancesRequestSystemDisk(
size="50",
category="cloud_essd",
),
deletion_protection=False,
)
# Create the instance
response = client.run_instances(request)
instance_ids = response.body.instance_id_sets.instance_id_set
if not instance_ids:
raise RuntimeError(
"Failed to create ECS instance - no instance ID returned"
)
instance_id = instance_ids[0]
logger.info(f"ECS instance {instance_id} created successfully")
# Wait for the instance to be running
logger.info(f"Waiting for instance {instance_id} to be running...")
_wait_for_instance_running(client, instance_id)
logger.info(f"Instance {instance_id} is now running and ready")
except KeyboardInterrupt:
logger.warning("VM allocation interrupted by user (SIGINT).")
if instance_id:
logger.info(f"Terminating instance {instance_id} due to interruption.")
try:
delete_request = ecs_models.DeleteInstancesRequest(
region_id=ALIYUN_REGION,
instance_ids=UtilClient.to_jsonstring([instance_id]),
force=True,
)
client.delete_instances(delete_request)
except Exception as cleanup_error:
logger.error(
f"Failed to cleanup instance {instance_id}: {str(cleanup_error)}"
)
raise
except Exception as e:
logger.error(f"Failed to allocate ECS instance: {str(e)}")
if instance_id:
logger.info(f"Terminating instance {instance_id} due to an error.")
try:
delete_request = ecs_models.DeleteInstancesRequest(
region_id=ALIYUN_REGION,
instance_ids=UtilClient.to_jsonstring([instance_id]),
force=True,
)
client.delete_instances(delete_request)
except Exception as cleanup_error:
logger.error(
f"Failed to cleanup instance {instance_id}: {str(cleanup_error)}"
)
raise
finally:
# Restore original signal handlers
signal.signal(signal.SIGINT, original_sigint_handler)
signal.signal(signal.SIGTERM, original_sigterm_handler)
return instance_id
def _wait_for_instance_running(
client: ECSClient, instance_id: str, max_attempts: int = MAX_ATTEMPTS
):
"""Wait for instance to reach Running state"""
for _ in range(max_attempts):
try:
req = ecs_models.DescribeInstancesRequest(
region_id=ALIYUN_REGION,
instance_ids=UtilClient.to_jsonstring([instance_id]),
)
response = client.describe_instances(req)
if response.body.instances.instance:
instance = response.body.instances.instance[0]
status = instance.status
logger.info(f"Instance {instance_id} status: {status}")
if status == "Running":
return
elif status in ["Stopped", "Stopping"]:
start_req = ecs_models.StartInstanceRequest(instance_id=instance_id)
client.start_instance(start_req)
logger.info(f"Started instance {instance_id}")
time.sleep(WAIT_DELAY)
except Exception as e:
logger.warning(f"Error checking instance status: {e}")
time.sleep(WAIT_DELAY)
raise TimeoutError(
f"Instance {instance_id} did not reach Running state within {max_attempts * WAIT_DELAY} seconds"
)
def _wait_until_server_ready(public_ip: str):
"""Wait until the server is ready"""
for _ in range(MAX_ATTEMPTS):
try:
logger.info(f"Checking server status on {public_ip}...")
response = requests.get(f"http://{public_ip}:5000/", timeout=2)
if response.status_code == 404:
logger.info(f"Server {public_ip} is ready")
return
except Exception:
time.sleep(WAIT_DELAY)
raise TimeoutError(
f"Server {public_ip} did not respond within {MAX_ATTEMPTS * WAIT_DELAY} seconds"
)
class AliyunVMManager(VMManager):
"""
Aliyun ECS VM Manager for managing virtual machines on Aliyun Cloud.
Aliyun ECS does not need to maintain a registry of VMs, as it can dynamically allocate and deallocate VMs.
"""
def __init__(self, **kwargs):
self.initialize_registry()
def initialize_registry(self, **kwargs):
pass
def add_vm(self, vm_path, lock_needed=True, **kwargs):
pass
def _add_vm(self, vm_path):
pass
def delete_vm(self, vm_path, lock_needed=True, **kwargs):
pass
def _delete_vm(self, vm_path):
pass
def occupy_vm(self, vm_path, pid, lock_needed=True, **kwargs):
pass
def _occupy_vm(self, vm_path, pid):
pass
def check_and_clean(self, lock_needed=True, **kwargs):
pass
def _check_and_clean(self):
pass
def list_free_vms(self, lock_needed=True, **kwargs):
pass
def _list_free_vms(self):
pass
def get_vm_path(self, screen_size=(1920, 1080), **kwargs):
"""Get a VM path (instance ID) for use"""
logger.info(
f"Allocating new ECS instance in region {ALIYUN_REGION} with screen size {screen_size}"
)
try:
instance_id = _allocate_vm(screen_size)
logger.info(f"Successfully allocated instance {instance_id}")
return instance_id
except Exception as e:
logger.error(f"Failed to allocate instance: {str(e)}")
raise

View File

@@ -0,0 +1,214 @@
import os
import logging
from datetime import datetime
from alibabacloud_ecs20140526.client import Client as ECSClient
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_ecs20140526 import models as ecs_models
from alibabacloud_tea_util.client import Client as UtilClient
from desktop_env.providers.base import Provider
from desktop_env.providers.aliyun.manager import (
_allocate_vm,
_wait_for_instance_running,
_wait_until_server_ready,
)
logger = logging.getLogger("desktopenv.providers.aliyun.AliyunProvider")
logger.setLevel(logging.INFO)
class AliyunProvider(Provider):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.region = os.getenv("ALIYUN_REGION", "eu-central-1")
self.client = self._create_client()
def _create_client(self) -> ECSClient:
config = open_api_models.Config(
access_key_id=os.getenv("ALIYUN_ACCESS_KEY_ID"),
access_key_secret=os.getenv("ALIYUN_ACCESS_KEY_SECRET"),
region_id=self.region,
)
return ECSClient(config)
def start_emulator(self, path_to_vm: str, headless: bool, *args, **kwargs):
logger.info("Starting Aliyun ECS instance...")
try:
# Check the current state of the instance
response = self._describe_instance(path_to_vm)
if not response.body.instances.instance:
logger.error(f"Instance {path_to_vm} not found")
return
instance = response.body.instances.instance[0]
state = instance.status
logger.info(f"Instance {path_to_vm} current state: {state}")
if state == "Running":
# If the instance is already running, skip starting it
logger.info(
f"Instance {path_to_vm} is already running. Skipping start."
)
return
if state == "Stopped":
# Start the instance if it's currently stopped
req = ecs_models.StartInstanceRequest(instance_id=path_to_vm)
self.client.start_instance(req)
logger.info(f"Instance {path_to_vm} is starting...")
# Wait until the instance reaches 'Running' state
_wait_for_instance_running(self.client, path_to_vm)
logger.info(f"Instance {path_to_vm} is now running.")
else:
# For all other states (Pending, Starting, etc.), log a warning
logger.warning(
f"Instance {path_to_vm} is in state '{state}' and cannot be started."
)
except Exception as e:
logger.error(
f"Failed to start the Aliyun ECS instance {path_to_vm}: {str(e)}"
)
raise
def get_ip_address(self, path_to_vm: str) -> str:
logger.info("Getting Aliyun ECS instance IP address...")
try:
response = self._describe_instance(path_to_vm)
if not response.body.instances.instance:
logger.error(f"Instance {path_to_vm} not found")
return ""
instance = response.body.instances.instance[0]
# Get private and public IP addresses
private_ip = ""
public_ip = ""
if hasattr(instance, "vpc_attributes") and instance.vpc_attributes:
private_ip = (
instance.vpc_attributes.private_ip_address.ip_address[0]
if instance.vpc_attributes.private_ip_address.ip_address
else ""
)
if hasattr(instance, "public_ip_address") and instance.public_ip_address:
public_ip = (
instance.public_ip_address.ip_address[0]
if instance.public_ip_address.ip_address
else ""
)
if hasattr(instance, "eip_address") and instance.eip_address:
public_ip = instance.eip_address.ip_address or public_ip
_wait_until_server_ready(public_ip)
if public_ip:
vnc_url = f"http://{public_ip}:5910/vnc.html"
logger.info("=" * 80)
logger.info(f"🖥️ VNC Web Access URL: {vnc_url}")
logger.info(f"📡 Public IP: {public_ip}")
logger.info(f"🏠 Private IP: {private_ip}")
logger.info("=" * 80)
print(f"\n🌐 VNC Web Access URL: {vnc_url}")
print(
"📍 Please open the above address in the browser "
"for remote desktop access\n"
)
else:
logger.warning("No public IP address available for VNC access")
return public_ip
except Exception as e:
logger.error(
f"Failed to retrieve IP address for the instance {path_to_vm}: {str(e)}"
)
raise
def save_state(self, path_to_vm: str, snapshot_name: str):
logger.info("Saving Aliyun ECS instance state...")
try:
req = ecs_models.CreateImageRequest(
region_id=self.region,
instance_id=path_to_vm,
image_name=snapshot_name,
description=f"Snapshot created at {datetime.now().isoformat()}",
)
response = self.client.create_image(req)
image_id = response.body.image_id
logger.info(
f"Image {image_id} created successfully from instance {path_to_vm}."
)
return image_id
except Exception as e:
logger.error(
f"Failed to create image from the instance {path_to_vm}: {str(e)}"
)
raise
def revert_to_snapshot(self, path_to_vm: str, snapshot_name: str):
logger.info(
f"Reverting Aliyun ECS instance to snapshot image: {snapshot_name}..."
)
try:
# Step 1: Retrieve the original instance details
response = self._describe_instance(path_to_vm)
if not response.body.instances.instance:
logger.error(f"Instance {path_to_vm} not found")
return
# Step 2: Delete the old instance
req = ecs_models.DeleteInstancesRequest(
region_id=self.region, instance_id=[path_to_vm], force=True
)
self.client.delete_instances(req)
logger.info(f"Old instance {path_to_vm} has been deleted.")
# Step 3: Launch a new instance from the snapshot image
new_instance_id = _allocate_vm()
logger.info(f"Instance {new_instance_id} is ready.")
# Get VNC access information
self.get_ip_address(new_instance_id)
return new_instance_id
except Exception as e:
logger.error(
f"Failed to revert to snapshot {snapshot_name} for the instance {path_to_vm}: {str(e)}"
)
raise
def stop_emulator(self, path_to_vm: str, region: str = None):
logger.info(f"Stopping Aliyun ECS instance {path_to_vm}...")
try:
req = ecs_models.DeleteInstancesRequest(
region_id=self.region, instance_id=[path_to_vm], force=True
)
self.client.delete_instances(req)
logger.info(f"Instance {path_to_vm} has been deleted.")
except Exception as e:
logger.error(
f"Failed to stop the Aliyun ECS instance {path_to_vm}: {str(e)}"
)
raise
def _describe_instance(
self, instance_id: str
) -> ecs_models.DescribeInstancesResponse:
"""Get instance details"""
req = ecs_models.DescribeInstancesRequest(
region_id=self.region, instance_ids=UtilClient.to_jsonstring([instance_id])
)
return self.client.describe_instances(req)