lybic-guiagents 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lybic-guiagents might be problematic. Click here for more details.

Files changed (85) hide show
  1. desktop_env/__init__.py +1 -0
  2. desktop_env/actions.py +203 -0
  3. desktop_env/controllers/__init__.py +0 -0
  4. desktop_env/controllers/python.py +471 -0
  5. desktop_env/controllers/setup.py +882 -0
  6. desktop_env/desktop_env.py +509 -0
  7. desktop_env/evaluators/__init__.py +5 -0
  8. desktop_env/evaluators/getters/__init__.py +41 -0
  9. desktop_env/evaluators/getters/calc.py +15 -0
  10. desktop_env/evaluators/getters/chrome.py +1774 -0
  11. desktop_env/evaluators/getters/file.py +154 -0
  12. desktop_env/evaluators/getters/general.py +42 -0
  13. desktop_env/evaluators/getters/gimp.py +38 -0
  14. desktop_env/evaluators/getters/impress.py +126 -0
  15. desktop_env/evaluators/getters/info.py +24 -0
  16. desktop_env/evaluators/getters/misc.py +406 -0
  17. desktop_env/evaluators/getters/replay.py +20 -0
  18. desktop_env/evaluators/getters/vlc.py +86 -0
  19. desktop_env/evaluators/getters/vscode.py +35 -0
  20. desktop_env/evaluators/metrics/__init__.py +160 -0
  21. desktop_env/evaluators/metrics/basic_os.py +68 -0
  22. desktop_env/evaluators/metrics/chrome.py +493 -0
  23. desktop_env/evaluators/metrics/docs.py +1011 -0
  24. desktop_env/evaluators/metrics/general.py +665 -0
  25. desktop_env/evaluators/metrics/gimp.py +637 -0
  26. desktop_env/evaluators/metrics/libreoffice.py +28 -0
  27. desktop_env/evaluators/metrics/others.py +92 -0
  28. desktop_env/evaluators/metrics/pdf.py +31 -0
  29. desktop_env/evaluators/metrics/slides.py +957 -0
  30. desktop_env/evaluators/metrics/table.py +585 -0
  31. desktop_env/evaluators/metrics/thunderbird.py +176 -0
  32. desktop_env/evaluators/metrics/utils.py +719 -0
  33. desktop_env/evaluators/metrics/vlc.py +524 -0
  34. desktop_env/evaluators/metrics/vscode.py +283 -0
  35. desktop_env/providers/__init__.py +35 -0
  36. desktop_env/providers/aws/__init__.py +0 -0
  37. desktop_env/providers/aws/manager.py +278 -0
  38. desktop_env/providers/aws/provider.py +186 -0
  39. desktop_env/providers/aws/provider_with_proxy.py +315 -0
  40. desktop_env/providers/aws/proxy_pool.py +193 -0
  41. desktop_env/providers/azure/__init__.py +0 -0
  42. desktop_env/providers/azure/manager.py +87 -0
  43. desktop_env/providers/azure/provider.py +207 -0
  44. desktop_env/providers/base.py +97 -0
  45. desktop_env/providers/gcp/__init__.py +0 -0
  46. desktop_env/providers/gcp/manager.py +0 -0
  47. desktop_env/providers/gcp/provider.py +0 -0
  48. desktop_env/providers/virtualbox/__init__.py +0 -0
  49. desktop_env/providers/virtualbox/manager.py +463 -0
  50. desktop_env/providers/virtualbox/provider.py +124 -0
  51. desktop_env/providers/vmware/__init__.py +0 -0
  52. desktop_env/providers/vmware/manager.py +455 -0
  53. desktop_env/providers/vmware/provider.py +105 -0
  54. gui_agents/__init__.py +0 -0
  55. gui_agents/agents/Action.py +209 -0
  56. gui_agents/agents/__init__.py +0 -0
  57. gui_agents/agents/agent_s.py +832 -0
  58. gui_agents/agents/global_state.py +610 -0
  59. gui_agents/agents/grounding.py +651 -0
  60. gui_agents/agents/hardware_interface.py +129 -0
  61. gui_agents/agents/manager.py +568 -0
  62. gui_agents/agents/translator.py +132 -0
  63. gui_agents/agents/worker.py +355 -0
  64. gui_agents/cli_app.py +560 -0
  65. gui_agents/core/__init__.py +0 -0
  66. gui_agents/core/engine.py +1496 -0
  67. gui_agents/core/knowledge.py +449 -0
  68. gui_agents/core/mllm.py +555 -0
  69. gui_agents/tools/__init__.py +0 -0
  70. gui_agents/tools/tools.py +727 -0
  71. gui_agents/unit_test/__init__.py +0 -0
  72. gui_agents/unit_test/run_tests.py +65 -0
  73. gui_agents/unit_test/test_manager.py +330 -0
  74. gui_agents/unit_test/test_worker.py +269 -0
  75. gui_agents/utils/__init__.py +0 -0
  76. gui_agents/utils/analyze_display.py +301 -0
  77. gui_agents/utils/common_utils.py +263 -0
  78. gui_agents/utils/display_viewer.py +281 -0
  79. gui_agents/utils/embedding_manager.py +53 -0
  80. gui_agents/utils/image_axis_utils.py +27 -0
  81. lybic_guiagents-0.1.0.dist-info/METADATA +416 -0
  82. lybic_guiagents-0.1.0.dist-info/RECORD +85 -0
  83. lybic_guiagents-0.1.0.dist-info/WHEEL +5 -0
  84. lybic_guiagents-0.1.0.dist-info/licenses/LICENSE +201 -0
  85. lybic_guiagents-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,193 @@
1
+ import random
2
+ import requests
3
+ import logging
4
+ import time
5
+ from typing import List, Dict, Optional
6
+ from dataclasses import dataclass
7
+ from threading import Lock
8
+ import json
9
+
10
+ logger = logging.getLogger("desktopenv.providers.aws.ProxyPool")
11
+ logger.setLevel(logging.INFO)
12
+
13
+ @dataclass
14
+ class ProxyInfo:
15
+ host: str
16
+ port: int
17
+ username: Optional[str] = None
18
+ password: Optional[str] = None
19
+ protocol: str = "http" # http, https, socks5
20
+ failed_count: int = 0
21
+ last_used: float = 0
22
+ is_active: bool = True
23
+
24
+ class ProxyPool:
25
+ def __init__(self, config_file: str = None):
26
+ self.proxies: List[ProxyInfo] = []
27
+ self.current_index = 0
28
+ self.lock = Lock()
29
+ self.max_failures = 3 # 最大失败次数
30
+ self.cooldown_time = 300 # 5分钟冷却时间
31
+
32
+ if config_file:
33
+ self.load_proxies_from_file(config_file)
34
+
35
+ def load_proxies_from_file(self, config_file: str):
36
+ """从配置文件加载代理列表"""
37
+ try:
38
+ with open(config_file, 'r') as f:
39
+ proxy_configs = json.load(f)
40
+
41
+ for config in proxy_configs:
42
+ proxy = ProxyInfo(
43
+ host=config['host'],
44
+ port=config['port'],
45
+ username=config.get('username'),
46
+ password=config.get('password'),
47
+ protocol=config.get('protocol', 'http')
48
+ )
49
+ self.proxies.append(proxy)
50
+
51
+ logger.info(f"Loaded {len(self.proxies)} proxies from {config_file}")
52
+ except Exception as e:
53
+ logger.error(f"Failed to load proxies from {config_file}: {e}")
54
+
55
+ def add_proxy(self, host: str, port: int, username: str = None,
56
+ password: str = None, protocol: str = "http"):
57
+ """添加代理到池中"""
58
+ proxy = ProxyInfo(host=host, port=port, username=username,
59
+ password=password, protocol=protocol)
60
+ with self.lock:
61
+ self.proxies.append(proxy)
62
+ logger.info(f"Added proxy {host}:{port}")
63
+
64
+ def get_next_proxy(self) -> Optional[ProxyInfo]:
65
+ """获取下一个可用的代理"""
66
+ with self.lock:
67
+ if not self.proxies:
68
+ return None
69
+
70
+ # 过滤掉失败次数过多的代理
71
+ active_proxies = [p for p in self.proxies if self._is_proxy_available(p)]
72
+
73
+ if not active_proxies:
74
+ logger.warning("No active proxies available")
75
+ return None
76
+
77
+ # 轮询选择代理
78
+ proxy = active_proxies[self.current_index % len(active_proxies)]
79
+ self.current_index += 1
80
+ proxy.last_used = time.time()
81
+
82
+ return proxy
83
+
84
+ def _is_proxy_available(self, proxy: ProxyInfo) -> bool:
85
+ """检查代理是否可用"""
86
+ if not proxy.is_active:
87
+ return False
88
+
89
+ if proxy.failed_count >= self.max_failures:
90
+ # 检查是否过了冷却时间
91
+ if time.time() - proxy.last_used < self.cooldown_time:
92
+ return False
93
+ else:
94
+ # 重置失败计数
95
+ proxy.failed_count = 0
96
+
97
+ return True
98
+
99
+ def mark_proxy_failed(self, proxy: ProxyInfo):
100
+ """标记代理失败"""
101
+ with self.lock:
102
+ proxy.failed_count += 1
103
+ if proxy.failed_count >= self.max_failures:
104
+ logger.warning(f"Proxy {proxy.host}:{proxy.port} marked as failed "
105
+ f"(failures: {proxy.failed_count})")
106
+
107
+ def mark_proxy_success(self, proxy: ProxyInfo):
108
+ """标记代理成功"""
109
+ with self.lock:
110
+ proxy.failed_count = 0
111
+
112
+ def test_proxy(self, proxy: ProxyInfo, test_url: str = "http://httpbin.org/ip",
113
+ timeout: int = 10) -> bool:
114
+ """测试代理是否正常工作"""
115
+ try:
116
+ proxy_url = self._format_proxy_url(proxy)
117
+ proxies = {
118
+ 'http': proxy_url,
119
+ 'https': proxy_url
120
+ }
121
+
122
+ response = requests.get(test_url, proxies=proxies, timeout=timeout)
123
+ if response.status_code == 200:
124
+ self.mark_proxy_success(proxy)
125
+ return True
126
+ else:
127
+ self.mark_proxy_failed(proxy)
128
+ return False
129
+
130
+ except Exception as e:
131
+ logger.debug(f"Proxy test failed for {proxy.host}:{proxy.port}: {e}")
132
+ self.mark_proxy_failed(proxy)
133
+ return False
134
+
135
+ def _format_proxy_url(self, proxy: ProxyInfo) -> str:
136
+ """格式化代理URL"""
137
+ if proxy.username and proxy.password:
138
+ return f"{proxy.protocol}://{proxy.username}:{proxy.password}@{proxy.host}:{proxy.port}"
139
+ else:
140
+ return f"{proxy.protocol}://{proxy.host}:{proxy.port}"
141
+
142
+ def get_proxy_dict(self, proxy: ProxyInfo) -> Dict[str, str]:
143
+ """获取requests库使用的代理字典"""
144
+ proxy_url = self._format_proxy_url(proxy)
145
+ return {
146
+ 'http': proxy_url,
147
+ 'https': proxy_url
148
+ }
149
+
150
+ def test_all_proxies(self, test_url: str = "http://httpbin.org/ip"):
151
+ """测试所有代理"""
152
+ logger.info("Testing all proxies...")
153
+ working_count = 0
154
+
155
+ for proxy in self.proxies:
156
+ if self.test_proxy(proxy, test_url):
157
+ working_count += 1
158
+ logger.info(f"✓ Proxy {proxy.host}:{proxy.port} is working")
159
+ else:
160
+ logger.warning(f"✗ Proxy {proxy.host}:{proxy.port} failed")
161
+
162
+ logger.info(f"Proxy test completed: {working_count}/{len(self.proxies)} working")
163
+ return working_count
164
+
165
+ def get_stats(self) -> Dict:
166
+ """获取代理池统计信息"""
167
+ with self.lock:
168
+ total = len(self.proxies)
169
+ active = len([p for p in self.proxies if self._is_proxy_available(p)])
170
+ failed = len([p for p in self.proxies if p.failed_count >= self.max_failures])
171
+
172
+ return {
173
+ 'total': total,
174
+ 'active': active,
175
+ 'failed': failed,
176
+ 'success_rate': active / total if total > 0 else 0
177
+ }
178
+
179
+ # 全局代理池实例
180
+ _proxy_pool = None
181
+
182
+ def get_global_proxy_pool() -> ProxyPool:
183
+ """获取全局代理池实例"""
184
+ global _proxy_pool
185
+ if _proxy_pool is None:
186
+ _proxy_pool = ProxyPool()
187
+ return _proxy_pool
188
+
189
+ def init_proxy_pool(config_file: str = None):
190
+ """初始化全局代理池"""
191
+ global _proxy_pool
192
+ _proxy_pool = ProxyPool(config_file)
193
+ return _proxy_pool
File without changes
@@ -0,0 +1,87 @@
1
+ import os
2
+ import threading
3
+ import boto3
4
+ import psutil
5
+
6
+ import logging
7
+
8
+ from desktop_env.providers.base import VMManager
9
+
10
+ logger = logging.getLogger("desktopenv.providers.azure.AzureVMManager")
11
+ logger.setLevel(logging.INFO)
12
+
13
+ REGISTRY_PATH = '.azure_vms'
14
+
15
+
16
+ def _allocate_vm(region):
17
+ raise NotImplementedError
18
+
19
+
20
+ class AzureVMManager(VMManager):
21
+ def __init__(self, registry_path=REGISTRY_PATH):
22
+ self.registry_path = registry_path
23
+ self.lock = threading.Lock()
24
+ self.initialize_registry()
25
+
26
+ def initialize_registry(self):
27
+ with self.lock: # Locking during initialization
28
+ if not os.path.exists(self.registry_path):
29
+ with open(self.registry_path, 'w') as file:
30
+ file.write('')
31
+
32
+ def add_vm(self, vm_path, region):
33
+ with self.lock:
34
+ with open(self.registry_path, 'r') as file:
35
+ lines = file.readlines()
36
+ vm_path_at_vm_region = "{}@{}".format(vm_path, region)
37
+ new_lines = lines + [f'{vm_path_at_vm_region}|free\n']
38
+ with open(self.registry_path, 'w') as file:
39
+ file.writelines(new_lines)
40
+
41
+ def occupy_vm(self, vm_path, pid, region):
42
+ with self.lock:
43
+ new_lines = []
44
+ with open(self.registry_path, 'r') as file:
45
+ lines = file.readlines()
46
+ for line in lines:
47
+ registered_vm_path, _ = line.strip().split('|')
48
+ if registered_vm_path == "{}@{}".format(vm_path, region):
49
+ new_lines.append(f'{registered_vm_path}|{pid}\n')
50
+ else:
51
+ new_lines.append(line)
52
+ with open(self.registry_path, 'w') as file:
53
+ file.writelines(new_lines)
54
+
55
+ def check_and_clean(self):
56
+ raise NotImplementedError
57
+
58
+ def list_free_vms(self, region):
59
+ with self.lock: # Lock when reading the registry
60
+ free_vms = []
61
+ with open(self.registry_path, 'r') as file:
62
+ lines = file.readlines()
63
+ for line in lines:
64
+ vm_path_at_vm_region, pid_str = line.strip().split('|')
65
+ vm_path, vm_region = vm_path_at_vm_region.split("@")
66
+ if pid_str == "free" and vm_region == region:
67
+ free_vms.append((vm_path, pid_str))
68
+ return free_vms
69
+
70
+ def get_vm_path(self, region, screen_size=(1920, 1080), **kwargs):
71
+ # Note: screen_size parameter is ignored for Azure provider
72
+ # but kept for interface consistency with other providers
73
+ self.check_and_clean()
74
+ free_vms_paths = self.list_free_vms(region)
75
+ if len(free_vms_paths) == 0:
76
+ # No free virtual machine available, generate a new one
77
+ logger.info("No free virtual machine available. Generating a new one, which would take a while...☕")
78
+ new_vm_path = _allocate_vm(region)
79
+ self.add_vm(new_vm_path, region)
80
+ self.occupy_vm(new_vm_path, os.getpid(), region)
81
+ return new_vm_path
82
+ else:
83
+ # Choose the first free virtual machine
84
+ chosen_vm_path = free_vms_paths[0][0]
85
+ self.occupy_vm(chosen_vm_path, os.getpid(), region)
86
+ return chosen_vm_path
87
+
@@ -0,0 +1,207 @@
1
+ import os
2
+ import time
3
+ from azure.identity import DefaultAzureCredential
4
+ from azure.mgmt.compute import ComputeManagementClient
5
+ from azure.mgmt.network import NetworkManagementClient
6
+ from azure.core.exceptions import ResourceNotFoundError
7
+
8
+ import logging
9
+
10
+ from desktop_env.providers.base import Provider
11
+
12
+ logger = logging.getLogger("desktopenv.providers.azure.AzureProvider")
13
+ logger.setLevel(logging.INFO)
14
+
15
+ WAIT_DELAY = 15
16
+ MAX_ATTEMPTS = 10
17
+
18
+ # To use the Azure provider, download azure-cli by https://learn.microsoft.com/en-us/cli/azure/install-azure-cli,
19
+ # use "az login" to log into you Azure account,
20
+ # and set environment variable "AZURE_SUBSCRIPTION_ID" to your subscription ID.
21
+ # Provide your resource group name and VM name in the format "RESOURCE_GROUP_NAME/VM_NAME" and pass as an argument for "-p".
22
+
23
+ class AzureProvider(Provider):
24
+ def __init__(self, region: str = None):
25
+ super().__init__(region)
26
+ credential = DefaultAzureCredential()
27
+ try:
28
+ self.subscription_id = os.environ["AZURE_SUBSCRIPTION_ID"]
29
+ except:
30
+ logger.error("Azure subscription ID not found. Please set environment variable \"AZURE_SUBSCRIPTION_ID\".")
31
+ raise
32
+ self.compute_client = ComputeManagementClient(credential, self.subscription_id)
33
+ self.network_client = NetworkManagementClient(credential, self.subscription_id)
34
+
35
+ def start_emulator(self, path_to_vm: str, headless: bool, os_type: str = None, *args, **kwargs):
36
+ # Note: os_type parameter is ignored for Azure provider
37
+ # but kept for interface consistency with other providers
38
+ logger.info("Starting Azure VM...")
39
+ resource_group_name, vm_name = path_to_vm.split('/')
40
+
41
+ vm = self.compute_client.virtual_machines.get(resource_group_name, vm_name, expand='instanceView')
42
+ power_state = vm.instance_view.statuses[-1].code
43
+ if power_state == "PowerState/running":
44
+ logger.info("VM is already running.")
45
+ return
46
+
47
+ try:
48
+ # Start the instance
49
+ for _ in range(MAX_ATTEMPTS):
50
+ async_vm_start = self.compute_client.virtual_machines.begin_start(resource_group_name, vm_name)
51
+ logger.info(f"VM {path_to_vm} is starting...")
52
+ # Wait for the instance to start
53
+ async_vm_start.wait(timeout=WAIT_DELAY)
54
+ vm = self.compute_client.virtual_machines.get(resource_group_name, vm_name, expand='instanceView')
55
+ power_state = vm.instance_view.statuses[-1].code
56
+ if power_state == "PowerState/running":
57
+ logger.info(f"VM {path_to_vm} is already running.")
58
+ break
59
+ except Exception as e:
60
+ logger.error(f"Failed to start the Azure VM {path_to_vm}: {str(e)}")
61
+ raise
62
+
63
+ def get_ip_address(self, path_to_vm: str) -> str:
64
+ logger.info("Getting Azure VM IP address...")
65
+ resource_group_name, vm_name = path_to_vm.split('/')
66
+
67
+ vm = self.compute_client.virtual_machines.get(resource_group_name, vm_name)
68
+
69
+ for interface in vm.network_profile.network_interfaces:
70
+ name=" ".join(interface.id.split('/')[-1:])
71
+ sub="".join(interface.id.split('/')[4])
72
+
73
+ try:
74
+ thing=self.network_client.network_interfaces.get(sub, name).ip_configurations
75
+
76
+ network_card_id = thing[0].public_ip_address.id.split('/')[-1]
77
+ public_ip_address = self.network_client.public_ip_addresses.get(resource_group_name, network_card_id)
78
+ logger.info(f"VM IP address is {public_ip_address.ip_address}")
79
+ return public_ip_address.ip_address
80
+
81
+ except Exception as e:
82
+ logger.error(f"Cannot get public IP for VM {path_to_vm}")
83
+ raise
84
+
85
+ def save_state(self, path_to_vm: str, snapshot_name: str):
86
+ print("Saving Azure VM state...")
87
+ resource_group_name, vm_name = path_to_vm.split('/')
88
+
89
+ vm = self.compute_client.virtual_machines.get(resource_group_name, vm_name)
90
+
91
+ try:
92
+ # Backup each disk attached to the VM
93
+ for disk in vm.storage_profile.data_disks + [vm.storage_profile.os_disk]:
94
+ # Create a snapshot of the disk
95
+ snapshot = {
96
+ 'location': vm.location,
97
+ 'creation_data': {
98
+ 'create_option': 'Copy',
99
+ 'source_uri': disk.managed_disk.id
100
+ }
101
+ }
102
+ async_snapshot_creation = self.compute_client.snapshots.begin_create_or_update(resource_group_name, snapshot_name, snapshot)
103
+ async_snapshot_creation.wait(timeout=WAIT_DELAY)
104
+
105
+ logger.info(f"Successfully created snapshot {snapshot_name} for VM {path_to_vm}.")
106
+ except Exception as e:
107
+ logger.error(f"Failed to create snapshot {snapshot_name} of the Azure VM {path_to_vm}: {str(e)}")
108
+ raise
109
+
110
+ def revert_to_snapshot(self, path_to_vm: str, snapshot_name: str):
111
+ logger.info(f"Reverting VM to snapshot: {snapshot_name}...")
112
+ resource_group_name, vm_name = path_to_vm.split('/')
113
+
114
+ vm = self.compute_client.virtual_machines.get(resource_group_name, vm_name)
115
+
116
+ # Stop the VM for disk creation
117
+ logger.info(f"Stopping VM: {vm_name}")
118
+ async_vm_stop = self.compute_client.virtual_machines.begin_deallocate(resource_group_name, vm_name)
119
+ async_vm_stop.wait(timeout=WAIT_DELAY) # Wait for the VM to stop
120
+
121
+ try:
122
+ # Get the snapshot
123
+ snapshot = self.compute_client.snapshots.get(resource_group_name, snapshot_name)
124
+
125
+ # Get the original disk information
126
+ original_disk_id = vm.storage_profile.os_disk.managed_disk.id
127
+ disk_name = original_disk_id.split('/')[-1]
128
+ if disk_name[-1] in ['0', '1']:
129
+ new_disk_name = disk_name[:-1] + str(int(disk_name[-1])^1)
130
+ else:
131
+ new_disk_name = disk_name + "0"
132
+
133
+ # Delete the disk if it exists
134
+ self.compute_client.disks.begin_delete(resource_group_name, new_disk_name).wait(timeout=WAIT_DELAY)
135
+
136
+ # Make sure the disk is deleted before proceeding to the next step
137
+ disk_deleted = False
138
+ polling_interval = 10
139
+ attempts = 0
140
+ while not disk_deleted and attempts < MAX_ATTEMPTS:
141
+ try:
142
+ self.compute_client.disks.get(resource_group_name, new_disk_name)
143
+ # If the above line does not raise an exception, the disk still exists
144
+ time.sleep(polling_interval)
145
+ attempts += 1
146
+ except ResourceNotFoundError:
147
+ disk_deleted = True
148
+
149
+ if not disk_deleted:
150
+ logger.error(f"Disk {new_disk_name} deletion timed out.")
151
+ raise
152
+
153
+ # Create a new managed disk from the snapshot
154
+ snapshot = self.compute_client.snapshots.get(resource_group_name, snapshot_name)
155
+ disk_creation = {
156
+ 'location': snapshot.location,
157
+ 'creation_data': {
158
+ 'create_option': 'Copy',
159
+ 'source_resource_id': snapshot.id
160
+ },
161
+ 'zones': vm.zones if vm.zones else None # Preserve the original disk's zone
162
+ }
163
+ async_disk_creation = self.compute_client.disks.begin_create_or_update(resource_group_name, new_disk_name, disk_creation)
164
+ restored_disk = async_disk_creation.result() # Wait for the disk creation to complete
165
+
166
+ vm.storage_profile.os_disk = {
167
+ 'create_option': vm.storage_profile.os_disk.create_option,
168
+ 'managed_disk': {
169
+ 'id': restored_disk.id
170
+ }
171
+ }
172
+
173
+ async_vm_creation = self.compute_client.virtual_machines.begin_create_or_update(resource_group_name, vm_name, vm)
174
+ async_vm_creation.wait(timeout=WAIT_DELAY)
175
+
176
+ # Delete the original disk
177
+ self.compute_client.disks.begin_delete(resource_group_name, disk_name).wait()
178
+
179
+ logger.info(f"Successfully reverted to snapshot {snapshot_name}.")
180
+ except Exception as e:
181
+ logger.error(f"Failed to revert the Azure VM {path_to_vm} to snapshot {snapshot_name}: {str(e)}")
182
+ raise
183
+
184
+ def stop_emulator(self, path_to_vm, region=None):
185
+ logger.info(f"Stopping Azure VM {path_to_vm}...")
186
+ resource_group_name, vm_name = path_to_vm.split('/')
187
+
188
+ vm = self.compute_client.virtual_machines.get(resource_group_name, vm_name, expand='instanceView')
189
+ power_state = vm.instance_view.statuses[-1].code
190
+ if power_state == "PowerState/deallocated":
191
+ print("VM is already stopped.")
192
+ return
193
+
194
+ try:
195
+ for _ in range(MAX_ATTEMPTS):
196
+ async_vm_deallocate = self.compute_client.virtual_machines.begin_deallocate(resource_group_name, vm_name)
197
+ logger.info(f"Stopping VM {path_to_vm}...")
198
+ # Wait for the instance to start
199
+ async_vm_deallocate.wait(timeout=WAIT_DELAY)
200
+ vm = self.compute_client.virtual_machines.get(resource_group_name, vm_name, expand='instanceView')
201
+ power_state = vm.instance_view.statuses[-1].code
202
+ if power_state == "PowerState/deallocated":
203
+ logger.info(f"VM {path_to_vm} is already stopped.")
204
+ break
205
+ except Exception as e:
206
+ logger.error(f"Failed to stop the Azure VM {path_to_vm}: {str(e)}")
207
+ raise
@@ -0,0 +1,97 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class Provider(ABC):
5
+ def __init__(self, region: str = None):
6
+ """
7
+ Region of the cloud service.
8
+ """
9
+ self.region = region
10
+
11
+ @abstractmethod
12
+ def start_emulator(self, path_to_vm: str, headless: bool):
13
+ """
14
+ Method to start the emulator.
15
+ """
16
+ pass
17
+
18
+ @abstractmethod
19
+ def get_ip_address(self, path_to_vm: str) -> str:
20
+ """
21
+ Method to get the private IP address of the VM. Private IP means inside the VPC.
22
+ """
23
+ pass
24
+
25
+ @abstractmethod
26
+ def save_state(self, path_to_vm: str, snapshot_name: str):
27
+ """
28
+ Method to save the state of the VM.
29
+ """
30
+ pass
31
+
32
+ @abstractmethod
33
+ def revert_to_snapshot(self, path_to_vm: str, snapshot_name: str) -> str:
34
+ """
35
+ Method to revert the VM to a given snapshot.
36
+ """
37
+ pass
38
+
39
+ @abstractmethod
40
+ def stop_emulator(self, path_to_vm: str):
41
+ """
42
+ Method to stop the emulator.
43
+ """
44
+ pass
45
+
46
+
47
+ class VMManager(ABC):
48
+ checked_and_cleaned = False
49
+
50
+ @abstractmethod
51
+ def initialize_registry(self, **kwargs):
52
+ """
53
+ Initialize registry.
54
+ """
55
+ pass
56
+
57
+ @abstractmethod
58
+ def add_vm(self, vm_path, **kwargs):
59
+ """
60
+ Add the path of new VM to the registration.
61
+ """
62
+ pass
63
+
64
+ @abstractmethod
65
+ def delete_vm(self, vm_path, **kwargs):
66
+ """
67
+ Delete the registration of VM by path.
68
+ """
69
+ pass
70
+
71
+ @abstractmethod
72
+ def occupy_vm(self, vm_path, pid, **kwargs):
73
+ """
74
+ Mark the path of VM occupied by the pid.
75
+ """
76
+ pass
77
+
78
+ @abstractmethod
79
+ def list_free_vms(self, **kwargs):
80
+ """
81
+ List the paths of VM that are free to use allocated.
82
+ """
83
+ pass
84
+
85
+ @abstractmethod
86
+ def check_and_clean(self, **kwargs):
87
+ """
88
+ Check the registration list, and remove the paths of VM that are not in use.
89
+ """
90
+ pass
91
+
92
+ @abstractmethod
93
+ def get_vm_path(self, **kwargs):
94
+ """
95
+ Get a virtual machine that is not occupied, generate a new one if no free VM.
96
+ """
97
+ pass
File without changes
File without changes
File without changes
File without changes