pybioos 0.0.16__py3-none-any.whl → 0.0.19__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.
- bioos/__about__.py +1 -1
- bioos/bioos.py +32 -7
- bioos/bioos_workflow.py +71 -3
- bioos/bw_import.py +5 -24
- bioos/bw_import_status_check.py +7 -2
- bioos/bw_status_check.py +7 -2
- bioos/config.py +4 -1
- bioos/get_submission_logs.py +7 -2
- bioos/resource/__init__.py +1 -1
- bioos/resource/iesapp.py +806 -0
- bioos/resource/workflows.py +86 -2
- bioos/resource/workspaces.py +140 -0
- bioos/service/BioOsService.py +103 -0
- bioos/workflow_info.py +2 -3
- {pybioos-0.0.16.dist-info → pybioos-0.0.19.dist-info}/METADATA +12 -10
- {pybioos-0.0.16.dist-info → pybioos-0.0.19.dist-info}/RECORD +20 -19
- {pybioos-0.0.16.dist-info → pybioos-0.0.19.dist-info}/WHEEL +1 -1
- {pybioos-0.0.16.dist-info → pybioos-0.0.19.dist-info}/entry_points.txt +1 -0
- {pybioos-0.0.16.dist-info → pybioos-0.0.19.dist-info}/LICENSE +0 -0
- {pybioos-0.0.16.dist-info → pybioos-0.0.19.dist-info}/top_level.txt +0 -0
bioos/resource/iesapp.py
ADDED
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional, Dict, Any, List
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from cachetools import TTLCache, cached
|
|
6
|
+
from pandas import DataFrame
|
|
7
|
+
|
|
8
|
+
from bioos.config import Config
|
|
9
|
+
from bioos.errors import ConflictError, NotFoundError, ParameterError
|
|
10
|
+
from bioos.utils.common_tools import SingletonType, dict_str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WebInstanceApp(metaclass=SingletonType):
|
|
14
|
+
"""表示一个Web实例应用程序。
|
|
15
|
+
|
|
16
|
+
这个类封装了所有与Web实例应用程序相关的信息和操作,
|
|
17
|
+
包括其元数据、状态和创建能力。
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self,
|
|
21
|
+
name: str,
|
|
22
|
+
workspace_id: str,
|
|
23
|
+
check: bool = False):
|
|
24
|
+
"""初始化Web实例应用程序实例。
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
name: Web实例应用程序的名称
|
|
28
|
+
workspace_id: 包含此Web实例应用程序的工作空间ID
|
|
29
|
+
check: 是否立即检查Web实例应用程序的存在性
|
|
30
|
+
"""
|
|
31
|
+
self.name = name
|
|
32
|
+
self.workspace_id = workspace_id
|
|
33
|
+
self._id: str = ""
|
|
34
|
+
self._description: str = ""
|
|
35
|
+
self._status: str = ""
|
|
36
|
+
self._create_time: int = 0
|
|
37
|
+
self._update_time: int = 0
|
|
38
|
+
self._owner_name: str = ""
|
|
39
|
+
self._endpoint: str = ""
|
|
40
|
+
self._port: int = 0
|
|
41
|
+
self._app_type: str = ""
|
|
42
|
+
self._user_id: int = 0
|
|
43
|
+
self._scope: str = ""
|
|
44
|
+
self._resource_size: str = ""
|
|
45
|
+
self._storage_capacity: int = 0
|
|
46
|
+
self._ssh_info: Dict[str, Any] = {}
|
|
47
|
+
self._image: str = ""
|
|
48
|
+
self._termination: Dict[str, Any] = {}
|
|
49
|
+
self._tos_mounts: List[Dict[str, Any]] = []
|
|
50
|
+
self._status_detail: Dict[str, Any] = {}
|
|
51
|
+
self._access_urls: Dict[str, str] = {}
|
|
52
|
+
self._running_duration: int = 0
|
|
53
|
+
self._start_time: int = 0
|
|
54
|
+
|
|
55
|
+
if check:
|
|
56
|
+
self.sync()
|
|
57
|
+
|
|
58
|
+
def __repr__(self):
|
|
59
|
+
"""返回Web实例应用程序的字符串表示。"""
|
|
60
|
+
info_dict = dict_str({
|
|
61
|
+
"id": self.id,
|
|
62
|
+
"name": self.name,
|
|
63
|
+
"description": self.description,
|
|
64
|
+
"status": self.status_detail.get('State', 'Unknown'),
|
|
65
|
+
"user_id": self.user_id,
|
|
66
|
+
"scope": self.scope,
|
|
67
|
+
"resource_size": self.resource_size,
|
|
68
|
+
"storage_capacity": self.storage_capacity,
|
|
69
|
+
"image": self.image,
|
|
70
|
+
"running_duration": self.get_running_time_formatted(),
|
|
71
|
+
"is_running": self.is_running(),
|
|
72
|
+
"ssh_info": self.get_ssh_connection_info(),
|
|
73
|
+
"access_urls": self.access_urls,
|
|
74
|
+
"create_time": self.create_time,
|
|
75
|
+
"update_time": self.update_time
|
|
76
|
+
})
|
|
77
|
+
return f"WebInstanceAppInfo:\n{info_dict}"
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
@cached(cache=TTLCache(maxsize=100, ttl=1))
|
|
81
|
+
def id(self) -> str:
|
|
82
|
+
"""获取Web实例应用程序的ID
|
|
83
|
+
|
|
84
|
+
:return: Web实例应用程序的ID
|
|
85
|
+
:rtype: str
|
|
86
|
+
"""
|
|
87
|
+
res = WebInstanceAppResource(self.workspace_id).list().query(f"Name=='{self.name}'")
|
|
88
|
+
if res.empty:
|
|
89
|
+
raise ParameterError("name")
|
|
90
|
+
return res["ID"].iloc[0]
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def description(self) -> str:
|
|
94
|
+
"""获取Web实例应用程序描述。"""
|
|
95
|
+
if not self._description:
|
|
96
|
+
self.sync()
|
|
97
|
+
return self._description
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def status(self) -> str:
|
|
101
|
+
"""获取Web实例应用程序状态。"""
|
|
102
|
+
if not self._status:
|
|
103
|
+
self.sync()
|
|
104
|
+
return self._status
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def create_time(self) -> int:
|
|
108
|
+
"""获取Web实例应用程序创建时间戳。"""
|
|
109
|
+
if not self._create_time:
|
|
110
|
+
self.sync()
|
|
111
|
+
return self._create_time
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def update_time(self) -> int:
|
|
115
|
+
"""获取Web实例应用程序最后更新时间戳。"""
|
|
116
|
+
if not self._update_time:
|
|
117
|
+
self.sync()
|
|
118
|
+
return self._update_time
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def owner_name(self) -> str:
|
|
122
|
+
"""获取Web实例应用程序所有者姓名。"""
|
|
123
|
+
if not self._owner_name:
|
|
124
|
+
self.sync()
|
|
125
|
+
return self._owner_name
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def endpoint(self) -> str:
|
|
129
|
+
"""获取Web实例应用程序访问端点。"""
|
|
130
|
+
if not self._endpoint:
|
|
131
|
+
self.sync()
|
|
132
|
+
return self._endpoint
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def port(self) -> int:
|
|
136
|
+
"""获取Web实例应用程序端口。"""
|
|
137
|
+
if not self._port:
|
|
138
|
+
self.sync()
|
|
139
|
+
return self._port
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def app_type(self) -> str:
|
|
143
|
+
"""获取Web实例应用程序类型。"""
|
|
144
|
+
if not self._app_type:
|
|
145
|
+
self.sync()
|
|
146
|
+
return self._app_type
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def user_id(self) -> int:
|
|
150
|
+
"""获取用户ID。"""
|
|
151
|
+
if not self._user_id:
|
|
152
|
+
self.sync()
|
|
153
|
+
return self._user_id
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def scope(self) -> str:
|
|
157
|
+
"""获取实例范围。"""
|
|
158
|
+
if not self._scope:
|
|
159
|
+
self.sync()
|
|
160
|
+
return self._scope
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def resource_size(self) -> str:
|
|
164
|
+
"""获取资源规格。"""
|
|
165
|
+
if not self._resource_size:
|
|
166
|
+
self.sync()
|
|
167
|
+
return self._resource_size
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def storage_capacity(self) -> int:
|
|
171
|
+
"""获取存储容量。"""
|
|
172
|
+
if not self._storage_capacity:
|
|
173
|
+
self.sync()
|
|
174
|
+
return self._storage_capacity
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def ssh_info(self) -> Dict[str, Any]:
|
|
178
|
+
"""获取SSH连接信息。"""
|
|
179
|
+
if not self._ssh_info:
|
|
180
|
+
self.sync()
|
|
181
|
+
return self._ssh_info
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def image(self) -> str:
|
|
185
|
+
"""获取镜像地址。"""
|
|
186
|
+
if not self._image:
|
|
187
|
+
self.sync()
|
|
188
|
+
return self._image
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def termination(self) -> Dict[str, Any]:
|
|
192
|
+
"""获取终止策略配置。"""
|
|
193
|
+
if not self._termination:
|
|
194
|
+
self.sync()
|
|
195
|
+
return self._termination
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def tos_mounts(self) -> List[Dict[str, Any]]:
|
|
199
|
+
"""获取TOS挂载配置。"""
|
|
200
|
+
if not self._tos_mounts:
|
|
201
|
+
self.sync()
|
|
202
|
+
return self._tos_mounts
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def status_detail(self) -> Dict[str, Any]:
|
|
206
|
+
"""获取详细状态信息。"""
|
|
207
|
+
if not self._status_detail:
|
|
208
|
+
self.sync()
|
|
209
|
+
return self._status_detail
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def access_urls(self) -> Dict[str, str]:
|
|
213
|
+
"""获取访问URL。"""
|
|
214
|
+
if not self._access_urls:
|
|
215
|
+
self.sync()
|
|
216
|
+
return self._access_urls
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def running_duration(self) -> int:
|
|
220
|
+
"""获取运行持续时间(秒)。"""
|
|
221
|
+
self.sync() # 运行时间需要实时获取
|
|
222
|
+
return self._running_duration
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def start_time(self) -> int:
|
|
226
|
+
"""获取启动时间戳。"""
|
|
227
|
+
if not self._start_time:
|
|
228
|
+
self.sync()
|
|
229
|
+
return self._start_time
|
|
230
|
+
|
|
231
|
+
@cached(cache=TTLCache(maxsize=100, ttl=1))
|
|
232
|
+
def sync(self):
|
|
233
|
+
"""与远程服务同步Web实例应用程序信息。"""
|
|
234
|
+
res = WebInstanceAppResource(self.workspace_id).list().query(f"Name=='{self.name}'")
|
|
235
|
+
if res.empty:
|
|
236
|
+
raise ParameterError("name")
|
|
237
|
+
|
|
238
|
+
# 获取详细的Web实例应用程序信息
|
|
239
|
+
params = {
|
|
240
|
+
'Filter': {
|
|
241
|
+
'Application': 'ies',
|
|
242
|
+
'WorkspaceID': self.workspace_id,
|
|
243
|
+
'IDs': [res["ID"].iloc[0]]
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
webinstanceapps = Config.service().list_webinstance_apps(params).get('Items')
|
|
247
|
+
if len(webinstanceapps) != 1:
|
|
248
|
+
raise NotFoundError("webinstanceapp", self.name)
|
|
249
|
+
|
|
250
|
+
detail = webinstanceapps[0]
|
|
251
|
+
|
|
252
|
+
# 更新所有属性
|
|
253
|
+
self._id = detail.get("ID", "")
|
|
254
|
+
self._description = detail.get("Description", "")
|
|
255
|
+
self._status = detail.get("Status", "")
|
|
256
|
+
self._create_time = detail.get("CreateTime", 0)
|
|
257
|
+
self._update_time = detail.get("UpdateTime", 0)
|
|
258
|
+
self._owner_name = detail.get("OwnerName", "")
|
|
259
|
+
self._endpoint = detail.get("Endpoint", "")
|
|
260
|
+
self._port = detail.get("Port", 0)
|
|
261
|
+
self._app_type = detail.get("AppType", "")
|
|
262
|
+
|
|
263
|
+
# 新增的属性
|
|
264
|
+
self._user_id = detail.get("UserID", 0)
|
|
265
|
+
self._scope = detail.get("Scope", "")
|
|
266
|
+
self._resource_size = detail.get("ResourceSize", "")
|
|
267
|
+
self._storage_capacity = detail.get("StorageCapacity", 0)
|
|
268
|
+
self._ssh_info = detail.get("SSH", {})
|
|
269
|
+
self._image = detail.get("Image", "")
|
|
270
|
+
self._termination = detail.get("Termination", {})
|
|
271
|
+
self._tos_mounts = detail.get("TOSMounts", [])
|
|
272
|
+
|
|
273
|
+
# 状态详情处理
|
|
274
|
+
status_detail = detail.get("Status", {})
|
|
275
|
+
if isinstance(status_detail, dict):
|
|
276
|
+
self._status_detail = status_detail
|
|
277
|
+
self._access_urls = status_detail.get("AccessURLs", {})
|
|
278
|
+
self._running_duration = status_detail.get("RunningDuration", 0)
|
|
279
|
+
self._start_time = status_detail.get("StartTime", 0)
|
|
280
|
+
else:
|
|
281
|
+
self._status_detail = {"State": str(status_detail)}
|
|
282
|
+
self._access_urls = {}
|
|
283
|
+
self._running_duration = 0
|
|
284
|
+
self._start_time = 0
|
|
285
|
+
|
|
286
|
+
def check_name_exists(self) -> bool:
|
|
287
|
+
"""检查当前Web实例应用程序名称是否已存在。
|
|
288
|
+
|
|
289
|
+
*Example*:
|
|
290
|
+
::
|
|
291
|
+
|
|
292
|
+
ws = bioos.workspace("foo")
|
|
293
|
+
app = ws.webinstanceapp("my-ies-instance")
|
|
294
|
+
exists = app.check_name_exists()
|
|
295
|
+
if exists:
|
|
296
|
+
print("实例名称已存在")
|
|
297
|
+
else:
|
|
298
|
+
print("实例名称可用")
|
|
299
|
+
|
|
300
|
+
:return: 名称是否已存在
|
|
301
|
+
:rtype: bool
|
|
302
|
+
"""
|
|
303
|
+
params = {
|
|
304
|
+
"WorkspaceID": self.workspace_id,
|
|
305
|
+
"Name": self.name,
|
|
306
|
+
"Application": "ies"
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
result = Config.service().check_webinstance_app(params)
|
|
310
|
+
return result.get("IsNameExist", False)
|
|
311
|
+
|
|
312
|
+
def get_ssh_connection_info(self) -> Dict[str, str]:
|
|
313
|
+
"""获取SSH连接信息的便捷方法。
|
|
314
|
+
|
|
315
|
+
*Example*:
|
|
316
|
+
::
|
|
317
|
+
|
|
318
|
+
ws = bioos.workspace("foo")
|
|
319
|
+
app = ws.webinstanceapp("my-ies-instance")
|
|
320
|
+
ssh_info = app.get_ssh_connection_info()
|
|
321
|
+
print(f"SSH地址: {ssh_info['ip']}:{ssh_info['port']}")
|
|
322
|
+
print(f"用户名: {ssh_info['username']}")
|
|
323
|
+
print(f"密码: {ssh_info['password']}")
|
|
324
|
+
|
|
325
|
+
:return: SSH连接信息
|
|
326
|
+
:rtype: Dict[str, str]
|
|
327
|
+
"""
|
|
328
|
+
ssh_data = self.ssh_info
|
|
329
|
+
return {
|
|
330
|
+
'ip': ssh_data.get('IP', ''),
|
|
331
|
+
'port': ssh_data.get('Port', ''),
|
|
332
|
+
'username': ssh_data.get('Username', ''),
|
|
333
|
+
'password': ssh_data.get('Passwd', '')
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
def get_notebook_url(self) -> str:
|
|
337
|
+
"""获取Jupyter Notebook访问URL。
|
|
338
|
+
|
|
339
|
+
:return: Notebook访问URL
|
|
340
|
+
:rtype: str
|
|
341
|
+
"""
|
|
342
|
+
return self.access_urls.get('notebook', '')
|
|
343
|
+
|
|
344
|
+
def get_rstudio_url(self) -> str:
|
|
345
|
+
"""获取RStudio访问URL。
|
|
346
|
+
|
|
347
|
+
:return: RStudio访问URL
|
|
348
|
+
:rtype: str
|
|
349
|
+
"""
|
|
350
|
+
return self.access_urls.get('rstudio', '')
|
|
351
|
+
|
|
352
|
+
def get_vscode_url(self) -> str:
|
|
353
|
+
"""获取VSCode访问URL。
|
|
354
|
+
|
|
355
|
+
:return: VSCode访问URL
|
|
356
|
+
:rtype: str
|
|
357
|
+
"""
|
|
358
|
+
return self.access_urls.get('vscode', '')
|
|
359
|
+
|
|
360
|
+
def is_running(self) -> bool:
|
|
361
|
+
"""检查实例是否正在运行。
|
|
362
|
+
|
|
363
|
+
:return: 是否正在运行
|
|
364
|
+
:rtype: bool
|
|
365
|
+
"""
|
|
366
|
+
status_detail = self.status_detail
|
|
367
|
+
return status_detail.get('State', '') == 'Running'
|
|
368
|
+
|
|
369
|
+
def get_running_time_formatted(self) -> str:
|
|
370
|
+
"""获取格式化的运行时间。
|
|
371
|
+
|
|
372
|
+
:return: 格式化的运行时间(如"1小时23分钟")
|
|
373
|
+
:rtype: str
|
|
374
|
+
"""
|
|
375
|
+
duration = self.running_duration
|
|
376
|
+
if duration < 60:
|
|
377
|
+
return f"{duration}秒"
|
|
378
|
+
elif duration < 3600:
|
|
379
|
+
minutes = duration // 60
|
|
380
|
+
seconds = duration % 60
|
|
381
|
+
return f"{minutes}分钟{seconds}秒"
|
|
382
|
+
else:
|
|
383
|
+
hours = duration // 3600
|
|
384
|
+
minutes = (duration % 3600) // 60
|
|
385
|
+
return f"{hours}小时{minutes}分钟"
|
|
386
|
+
|
|
387
|
+
def get_events(self,
|
|
388
|
+
start_time: Optional[int] = None,
|
|
389
|
+
end_time: Optional[int] = None,
|
|
390
|
+
levels: Optional[List[str]] = None) -> List[Dict[str, Any]]:
|
|
391
|
+
"""获取Web实例应用程序的事件日志。
|
|
392
|
+
|
|
393
|
+
*Example*:
|
|
394
|
+
::
|
|
395
|
+
|
|
396
|
+
ws = bioos.workspace("foo")
|
|
397
|
+
app = ws.webinstanceapp("my-ies-instance")
|
|
398
|
+
|
|
399
|
+
# 获取所有事件
|
|
400
|
+
events = app.get_events()
|
|
401
|
+
|
|
402
|
+
# 获取指定时间范围的事件
|
|
403
|
+
events = app.get_events(start_time=1640995200, end_time=1641081600)
|
|
404
|
+
|
|
405
|
+
# 获取指定级别的事件
|
|
406
|
+
events = app.get_events(levels=["Info", "Urgency"])
|
|
407
|
+
|
|
408
|
+
:param start_time: 起始时间戳(可选)
|
|
409
|
+
:type start_time: Optional[int]
|
|
410
|
+
:param end_time: 结束时间戳(可选)
|
|
411
|
+
:type end_time: Optional[int]
|
|
412
|
+
:param levels: 事件级别列表,可选值: ["Info", "Urgency", "Priority"]
|
|
413
|
+
:type levels: Optional[List[str]]
|
|
414
|
+
:return: 事件列表
|
|
415
|
+
:rtype: List[Dict[str, Any]]
|
|
416
|
+
"""
|
|
417
|
+
params = {
|
|
418
|
+
"ID": self.id,
|
|
419
|
+
"WorkspaceID": self.workspace_id,
|
|
420
|
+
"Filter": {}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
# 构建过滤条件
|
|
424
|
+
filter_params = {}
|
|
425
|
+
if start_time is not None:
|
|
426
|
+
filter_params["StartTime"] = start_time
|
|
427
|
+
if end_time is not None:
|
|
428
|
+
filter_params["EndTime"] = end_time
|
|
429
|
+
if levels is not None:
|
|
430
|
+
filter_params["Level"] = levels
|
|
431
|
+
|
|
432
|
+
if filter_params:
|
|
433
|
+
params["Filter"] = filter_params
|
|
434
|
+
|
|
435
|
+
result = Config.service().list_webinstance_events(params)
|
|
436
|
+
return result.get("Items", [])
|
|
437
|
+
|
|
438
|
+
def commit_image(self,
|
|
439
|
+
image_name: str,
|
|
440
|
+
description: str = "",
|
|
441
|
+
replace_image: bool = False) -> dict:
|
|
442
|
+
"""将当前实例状态保存为镜像。
|
|
443
|
+
|
|
444
|
+
*Example*:
|
|
445
|
+
::
|
|
446
|
+
|
|
447
|
+
ws = bioos.workspace("foo")
|
|
448
|
+
app = ws.webinstanceapp("my-ies-instance")
|
|
449
|
+
|
|
450
|
+
# 保存为新镜像
|
|
451
|
+
result = app.commit_image(
|
|
452
|
+
image_name="my-custom-image:v1.0",
|
|
453
|
+
description="我的自定义开发环境",
|
|
454
|
+
replace_image=False
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
:param image_name: 镜像名称
|
|
458
|
+
:type image_name: str
|
|
459
|
+
:param description: 镜像描述
|
|
460
|
+
:type description: str
|
|
461
|
+
:param replace_image: 是否自动替换当前实例的镜像
|
|
462
|
+
:type replace_image: bool
|
|
463
|
+
:return: 提交结果
|
|
464
|
+
:rtype: dict
|
|
465
|
+
"""
|
|
466
|
+
if not image_name:
|
|
467
|
+
raise ParameterError("image_name", "image_name cannot be empty")
|
|
468
|
+
|
|
469
|
+
params = {
|
|
470
|
+
"WorkspaceID": self.workspace_id,
|
|
471
|
+
"WebappInstanceID": self.id,
|
|
472
|
+
"Image": image_name,
|
|
473
|
+
"Description": description,
|
|
474
|
+
"ImageReplace": replace_image
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return Config.service().commit_ies_image(params)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class WebInstanceAppResource(metaclass=SingletonType):
|
|
481
|
+
"""Web实例应用程序资源管理类。"""
|
|
482
|
+
|
|
483
|
+
def __init__(self, workspace_id: str):
|
|
484
|
+
self.workspace_id = workspace_id
|
|
485
|
+
|
|
486
|
+
def __repr__(self):
|
|
487
|
+
return f"WebInstanceAppInfo:\n{self.list()}"
|
|
488
|
+
|
|
489
|
+
def list(self) -> DataFrame:
|
|
490
|
+
"""列出所有Web实例应用程序信息。
|
|
491
|
+
|
|
492
|
+
*Example*:
|
|
493
|
+
::
|
|
494
|
+
|
|
495
|
+
ws = bioos.workspace("foo")
|
|
496
|
+
ws.webinstanceapps.list()
|
|
497
|
+
|
|
498
|
+
:return: 所有Web实例应用程序信息
|
|
499
|
+
:rtype: DataFrame
|
|
500
|
+
"""
|
|
501
|
+
content = Config.service().list_webinstance_apps({
|
|
502
|
+
'Filter': {
|
|
503
|
+
'Application': 'ies',
|
|
504
|
+
'WorkspaceID': self.workspace_id
|
|
505
|
+
}
|
|
506
|
+
})
|
|
507
|
+
res_df = pd.DataFrame.from_records(content['Items'])
|
|
508
|
+
if res_df.empty:
|
|
509
|
+
return res_df
|
|
510
|
+
res_df['CreateTime'] = pd.to_datetime(
|
|
511
|
+
res_df['CreateTime'], unit='ms', origin=pd.Timestamp('2018-07-01'))
|
|
512
|
+
res_df['UpdateTime'] = pd.to_datetime(
|
|
513
|
+
res_df['UpdateTime'], unit='ms', origin=pd.Timestamp('2018-07-01'))
|
|
514
|
+
|
|
515
|
+
return res_df
|
|
516
|
+
|
|
517
|
+
def create_new_instance(self,
|
|
518
|
+
name: str,
|
|
519
|
+
description: str,
|
|
520
|
+
resource_size: str = "1c-2gib",
|
|
521
|
+
storage_capacity: int = 21474836480,
|
|
522
|
+
image: str = "",
|
|
523
|
+
ssh_enabled: bool = True,
|
|
524
|
+
running_time_limit_seconds: int = 10800, # 3小时 = 3*60*60
|
|
525
|
+
idle_timeout_seconds: int = 10800, # 3小时 = 3*60*60
|
|
526
|
+
auto_start: bool = True) -> dict:
|
|
527
|
+
"""创建一个新的IES Web实例应用程序。
|
|
528
|
+
|
|
529
|
+
*Example*:
|
|
530
|
+
::
|
|
531
|
+
|
|
532
|
+
ws = bioos.workspace("foo")
|
|
533
|
+
ws.webinstanceapps.create_new_instance(
|
|
534
|
+
name="my-ies-instance",
|
|
535
|
+
description="我的IES实例",
|
|
536
|
+
resource_size="1c-2gib",
|
|
537
|
+
storage_capacity=21474836480,
|
|
538
|
+
image="your-image-url",
|
|
539
|
+
ssh_enabled=True,
|
|
540
|
+
running_time_limit_seconds=10800,
|
|
541
|
+
idle_timeout_seconds=10800,
|
|
542
|
+
auto_start=True
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
:param name: Web实例应用程序名称
|
|
546
|
+
:type name: str
|
|
547
|
+
:param description: Web实例应用程序描述
|
|
548
|
+
:type description: str
|
|
549
|
+
:param resource_size: 资源规格(如"1c-2gib"表示1核2GB内存)
|
|
550
|
+
:type resource_size: str
|
|
551
|
+
:param storage_capacity: 存储容量(字节,默认20GB)
|
|
552
|
+
:type storage_capacity: int
|
|
553
|
+
:param image: 镜像地址
|
|
554
|
+
:type image: str
|
|
555
|
+
:param ssh_enabled: 是否启用SSH
|
|
556
|
+
:type ssh_enabled: bool
|
|
557
|
+
:param running_time_limit_seconds: 运行时长限制(秒,5分钟-24小时,默认3小时)
|
|
558
|
+
:type running_time_limit_seconds: int
|
|
559
|
+
:param idle_timeout_seconds: 空闲超时时间(秒,5分钟-24小时,默认3小时)
|
|
560
|
+
:type idle_timeout_seconds: int
|
|
561
|
+
:param auto_start: 是否自动启动
|
|
562
|
+
:type auto_start: bool
|
|
563
|
+
:return: 创建结果
|
|
564
|
+
:rtype: dict
|
|
565
|
+
"""
|
|
566
|
+
if not name:
|
|
567
|
+
raise ParameterError("name", "name cannot be empty")
|
|
568
|
+
|
|
569
|
+
if not image:
|
|
570
|
+
raise ParameterError("image", "image cannot be empty")
|
|
571
|
+
|
|
572
|
+
# 检查名称是否已存在
|
|
573
|
+
if self.check_name_exists(name):
|
|
574
|
+
raise ConflictError("name", f"{name} already exists")
|
|
575
|
+
|
|
576
|
+
# 验证时间限制范围(5分钟到24小时)
|
|
577
|
+
min_time = 300 # 5分钟
|
|
578
|
+
max_time = 86400 # 24小时
|
|
579
|
+
|
|
580
|
+
if not (min_time <= running_time_limit_seconds <= max_time):
|
|
581
|
+
raise ParameterError("running_time_limit_seconds",
|
|
582
|
+
f"must be between {min_time} and {max_time} seconds")
|
|
583
|
+
|
|
584
|
+
if not (min_time <= idle_timeout_seconds <= max_time):
|
|
585
|
+
raise ParameterError("idle_timeout_seconds",
|
|
586
|
+
f"must be between {min_time} and {max_time} seconds")
|
|
587
|
+
|
|
588
|
+
params = {
|
|
589
|
+
"WorkspaceID": self.workspace_id,
|
|
590
|
+
"Name": name,
|
|
591
|
+
"Scope": "Private",
|
|
592
|
+
"Description": description,
|
|
593
|
+
"Application": "ies",
|
|
594
|
+
"ResourceSize": resource_size,
|
|
595
|
+
"StorageCapacity": storage_capacity,
|
|
596
|
+
"SSHEnabled": ssh_enabled,
|
|
597
|
+
"Image": image,
|
|
598
|
+
"Termination": {
|
|
599
|
+
"RunningTimeLimitSeconds": running_time_limit_seconds,
|
|
600
|
+
"IdleTimeoutSeconds": idle_timeout_seconds
|
|
601
|
+
},
|
|
602
|
+
"TOSMounts": [
|
|
603
|
+
{
|
|
604
|
+
"BucketName": f"bioos-{self.workspace_id}",
|
|
605
|
+
"MountPath": f"/home/ies/bioos-{self.workspace_id}",
|
|
606
|
+
"ReadOnly": False
|
|
607
|
+
}
|
|
608
|
+
],
|
|
609
|
+
"AutoStart": auto_start
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return Config.service().create_webinstance_app(params)
|
|
613
|
+
|
|
614
|
+
def delete(self, target: str):
|
|
615
|
+
"""从工作空间删除一个Web实例应用程序。
|
|
616
|
+
|
|
617
|
+
*Example*:
|
|
618
|
+
::
|
|
619
|
+
|
|
620
|
+
ws = bioos.workspace("foo")
|
|
621
|
+
ws.webinstanceapps.delete(target="bar")
|
|
622
|
+
|
|
623
|
+
:param target: Web实例应用程序名称
|
|
624
|
+
:type target: str
|
|
625
|
+
"""
|
|
626
|
+
res = self.list().query(f"Name=='{target}'")
|
|
627
|
+
if res.empty:
|
|
628
|
+
raise ParameterError("target")
|
|
629
|
+
|
|
630
|
+
Config.service().delete_webinstance_app({
|
|
631
|
+
"ID": res["ID"].iloc[0],
|
|
632
|
+
"WorkspaceID": self.workspace_id
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
def start(self, target: str):
|
|
636
|
+
"""启动一个Web实例应用程序。
|
|
637
|
+
|
|
638
|
+
*Example*:
|
|
639
|
+
::
|
|
640
|
+
|
|
641
|
+
ws = bioos.workspace("foo")
|
|
642
|
+
ws.webinstanceapps.start(target="bar")
|
|
643
|
+
|
|
644
|
+
:param target: Web实例应用程序名称
|
|
645
|
+
:type target: str
|
|
646
|
+
"""
|
|
647
|
+
res = self.list().query(f"Name=='{target}'")
|
|
648
|
+
if res.empty:
|
|
649
|
+
raise ParameterError("target")
|
|
650
|
+
|
|
651
|
+
Config.service().start_webinstance_app({
|
|
652
|
+
"ID": res["ID"].iloc[0],
|
|
653
|
+
"WorkspaceID": self.workspace_id
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
def stop(self, target: str):
|
|
657
|
+
"""停止一个Web实例应用程序。
|
|
658
|
+
|
|
659
|
+
*Example*:
|
|
660
|
+
::
|
|
661
|
+
|
|
662
|
+
ws = bioos.workspace("foo")
|
|
663
|
+
ws.webinstanceapps.stop(target="bar")
|
|
664
|
+
|
|
665
|
+
:param target: Web实例应用程序名称
|
|
666
|
+
:type target: str
|
|
667
|
+
"""
|
|
668
|
+
res = self.list().query(f"Name=='{target}'")
|
|
669
|
+
if res.empty:
|
|
670
|
+
raise ParameterError("target")
|
|
671
|
+
|
|
672
|
+
Config.service().stop_webinstance_app({
|
|
673
|
+
"ID": res["ID"].iloc[0],
|
|
674
|
+
"WorkspaceID": self.workspace_id
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
def get_events(self,
|
|
678
|
+
target: str,
|
|
679
|
+
start_time: Optional[int] = None,
|
|
680
|
+
end_time: Optional[int] = None,
|
|
681
|
+
levels: Optional[List[str]] = None) -> List[Dict[str, Any]]:
|
|
682
|
+
"""获取指定Web实例应用程序的事件日志。
|
|
683
|
+
|
|
684
|
+
*Example*:
|
|
685
|
+
::
|
|
686
|
+
|
|
687
|
+
ws = bioos.workspace("foo")
|
|
688
|
+
|
|
689
|
+
# 获取所有事件
|
|
690
|
+
events = ws.webinstanceapps.get_events("my-ies-instance")
|
|
691
|
+
|
|
692
|
+
# 获取指定级别的事件
|
|
693
|
+
events = ws.webinstanceapps.get_events("my-ies-instance", levels=["Info", "Urgency"])
|
|
694
|
+
|
|
695
|
+
:param target: Web实例应用程序名称
|
|
696
|
+
:type target: str
|
|
697
|
+
:param start_time: 起始时间戳(可选)
|
|
698
|
+
:type start_time: Optional[int]
|
|
699
|
+
:param end_time: 结束时间戳(可选)
|
|
700
|
+
:type end_time: Optional[int]
|
|
701
|
+
:param levels: 事件级别列表
|
|
702
|
+
:type levels: Optional[List[str]]
|
|
703
|
+
:return: 事件列表
|
|
704
|
+
:rtype: List[Dict[str, Any]]
|
|
705
|
+
"""
|
|
706
|
+
res = self.list().query(f"Name=='{target}'")
|
|
707
|
+
if res.empty:
|
|
708
|
+
raise ParameterError("target")
|
|
709
|
+
|
|
710
|
+
params = {
|
|
711
|
+
"ID": res["ID"].iloc[0],
|
|
712
|
+
"WorkspaceID": self.workspace_id
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
# 构建过滤条件
|
|
716
|
+
filter_params = {}
|
|
717
|
+
if start_time is not None:
|
|
718
|
+
filter_params["StartTime"] = start_time
|
|
719
|
+
if end_time is not None:
|
|
720
|
+
filter_params["EndTime"] = end_time
|
|
721
|
+
if levels is not None:
|
|
722
|
+
filter_params["Level"] = levels
|
|
723
|
+
|
|
724
|
+
if filter_params:
|
|
725
|
+
params["Filter"] = filter_params
|
|
726
|
+
|
|
727
|
+
result = Config.service().list_webinstance_events(params)
|
|
728
|
+
return result.get("Items", [])
|
|
729
|
+
|
|
730
|
+
def commit_image(self,
|
|
731
|
+
target: str,
|
|
732
|
+
image_name: str,
|
|
733
|
+
description: str = "",
|
|
734
|
+
replace_image: bool = False) -> dict:
|
|
735
|
+
"""将指定实例状态保存为镜像。
|
|
736
|
+
|
|
737
|
+
*Example*:
|
|
738
|
+
::
|
|
739
|
+
|
|
740
|
+
ws = bioos.workspace("foo")
|
|
741
|
+
|
|
742
|
+
# 保存为新镜像
|
|
743
|
+
result = ws.webinstanceapps.commit_image(
|
|
744
|
+
target="my-ies-instance",
|
|
745
|
+
image_name="my-custom-image:v1.0",
|
|
746
|
+
description="我的自定义开发环境",
|
|
747
|
+
replace_image=False
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
:param target: Web实例应用程序名称
|
|
751
|
+
:type target: str
|
|
752
|
+
:param image_name: 镜像名称
|
|
753
|
+
:type image_name: str
|
|
754
|
+
:param description: 镜像描述
|
|
755
|
+
:type description: str
|
|
756
|
+
:param replace_image: 是否自动替换当前实例的镜像
|
|
757
|
+
:type replace_image: bool
|
|
758
|
+
:return: 提交结果
|
|
759
|
+
:rtype: dict
|
|
760
|
+
"""
|
|
761
|
+
if not image_name:
|
|
762
|
+
raise ParameterError("image_name", "image_name cannot be empty")
|
|
763
|
+
|
|
764
|
+
res = self.list().query(f"Name=='{target}'")
|
|
765
|
+
if res.empty:
|
|
766
|
+
raise ParameterError("target")
|
|
767
|
+
|
|
768
|
+
params = {
|
|
769
|
+
"WorkspaceID": self.workspace_id,
|
|
770
|
+
"WebappInstanceID": res["ID"].iloc[0],
|
|
771
|
+
"Image": image_name,
|
|
772
|
+
"Description": description,
|
|
773
|
+
"ImageReplace": replace_image
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return Config.service().commit_ies_image(params)
|
|
777
|
+
|
|
778
|
+
def check_name_exists(self, name: str) -> bool:
|
|
779
|
+
"""检查Web实例应用程序名称是否已存在。
|
|
780
|
+
|
|
781
|
+
*Example*:
|
|
782
|
+
::
|
|
783
|
+
|
|
784
|
+
ws = bioos.workspace("foo")
|
|
785
|
+
exists = ws.webinstanceapps.check_name_exists("my-ies-instance")
|
|
786
|
+
if exists:
|
|
787
|
+
print("实例名称已存在")
|
|
788
|
+
else:
|
|
789
|
+
print("实例名称可用")
|
|
790
|
+
|
|
791
|
+
:param name: Web实例应用程序名称
|
|
792
|
+
:type name: str
|
|
793
|
+
:return: 名称是否已存在
|
|
794
|
+
:rtype: bool
|
|
795
|
+
"""
|
|
796
|
+
if not name:
|
|
797
|
+
raise ParameterError("name", "name cannot be empty")
|
|
798
|
+
|
|
799
|
+
params = {
|
|
800
|
+
"WorkspaceID": self.workspace_id,
|
|
801
|
+
"Name": name,
|
|
802
|
+
"Application": "ies"
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
result = Config.service().check_webinstance_app(params)
|
|
806
|
+
return result.get("IsNameExist", False)
|