Air SDK V2
NVIDIA Air SDK V2 提供了一个 Python SDK,用于与大多数 NVIDIA Air API V2 端点进行交互。
API V1 和 API V2 概述
Air API V2 端点提供了用于创建和管理模拟的强大方法。您可以通过 JSON 文件上传或按顺序使用 RESTful CRUD 操作来启动模拟:首先创建模拟,然后添加节点,为每个节点定义接口,最后链接这些接口。这种结构化方法允许灵活和精确的模拟设计。
在 Air API V1 端点中,模拟通过“拓扑”和“模拟”实例的组合进行结构化。一个模拟包含一个“拓扑”实例以及一个引用此拓扑的“模拟”实例。
模拟中的各个节点由“节点”实例(引用拓扑)和“simulation_node”实例(引用模拟)表示。每个节点的接口由“接口”实例(链接到“节点”实例)和“simulation_interface”实例(链接到“simulation_node”实例)表示。
使用 Air API V2,这些表示形式为客户端进行了简化。一个模拟直接包含“节点”,而“节点”又包含彼此连接的“接口”。“拓扑”的单独概念几乎完全被移除,从而提供了更直接的结构。
拓扑的遗留引用
尽管大多数 API V2 端点不引用拓扑,但 API V2 中仍包含一些例外情况,这些例外情况是在新约定实施和执行之前创建的。核心端点
V1 和 V2 之间的主要区别
单独的导入路径
SDK 的 V2 实现与 air_sdk 包的导入路径不同
from air_sdk import AirApi as AirApiV1 # Imports the original SDK
from air_sdk.v2 import AirApi # Imports the V2 SDK
api = AirApi(username=..., password=...)
迭代器与列表
由于大多数列出数据的 API V2 端点都已分页,因此 V2 SDK 方法通常返回迭代器(例如,api.simulations.list()),这提高了性能,但需要迭代。在需要索引时,将迭代器转换为列表
from air_sdk.v2 import AirApi
api = AirApi(username=..., password=...)
simulation_list = list(api.simulations.list()) # Returns a list of simulation objects
simulation_iterator = api.simulations.list() # Returns an Iterator
for simulation in simulation_iterator: # The SDK will walk through the pagination to obtain all objects, potentially across multiple requests
do_something(simulation)
SDK 使用默认页面大小 200。您可以通过在迭代器上调用 list
来调整页面大小,以减少或增加请求次数
from air_sdk.v2 import AirApi
api = AirApi(username=..., password=...)
api.set_page_size(10000000) # Set the page size to be arbitarily large to obtain all objects in one request
sims = list(api.simulations.list()) # will most likely only make 1 call to the Air API
类型提示和检查
大多数 SDK V2 都带有类型提示,可在创建或更新对象时提供帮助和验证
>>> from typing import get_type_hints
>>> for key, value in get_type_hints(air.simulations.create).items():
... print(key, ':', value)
...
title : <class 'str'>
documentation : typing.Union[str, NoneType]
expires : typing.Union[bool, NoneType]
expires_at : typing.Union[datetime.datetime, NoneType]
metadata : typing.Union[str, NoneType]
organization : typing.Union[air_sdk.v2.endpoints.organizations.Organization, str, uuid.UUID, NoneType]
owner : typing.Union[str, NoneType]
preferred_worker : typing.Union[air_sdk.v2.endpoints.workers.Worker, str, uuid.UUID, NoneType]
sleep : typing.Union[bool, NoneType]
sleep_at : typing.Union[datetime.datetime, NoneType]
return : <class 'air_sdk.v2.endpoints.simulations.Simulation'>
设置自定义连接超时
客户端可以为 SDK V2 设置自定义连接超时
from datetime import timedelta
from air_sdk.v2 import AirApi
api = AirApi(...)
api.set_connect_timeout(timedelta(minutes=2))
可以单独设置自定义读取超时
api.set_read_timeout(timedelta(minutes=2))
额外的身份验证支持
原始 SDK 和 V2 SDK 的初始化过程几乎相同
from air_sdk import AirApi as AirApiV1
from air_sdk.v2 import AirApi as AirApiV2
air_v1 = AirApiV1(
api_url=...,
username=...,
password=...,
)
air_v2 = AirApiV2(
api_url=...,
username=...,
password=...,
)
还有一个额外的选项可以在 SDK V2 的初始化期间跳过身份验证,并在稍后提供身份验证凭据
from air_sdk.v2 import AirApi
api = AirApi(api_url=..., authenticate=False)
api.client.authenticate(username=..., password=...)
您还可以使用此方法来切换经过身份验证的客户端。
与数据类对象交互
SDK V2 引入了数据类,用于在 Python 中表示各种对象,如模拟、节点、图像和组织。
>>> sim
Simulation(id='95bbbf37-a6d4-42b2-ab62-0234cc86370d', title='2k links', state='NEW', documentation=None, write_ok=True, metadata=None)
>>> sim.id
'95bbbf37-a6d4-42b2-ab62-0234cc86370d'
>>> sim.title
'2k links'
>>> sim.created
datetime.datetime(2024, 10, 18, 16, 11, 12, 659424, tzinfo=datetime.timezone.utc)
您可以使用 .dict()
方法轻松地将这些对象转换为原生 Python 字典
>>> sim.dict()
{'id': '95bbbf37-a6d4-42b2-ab62-0234cc86370d', 'title': '2k links', 'state': 'NEW', 'sleep': True, 'owner': 'tiparker@nvidia.com', 'cloned': False, 'expires': False, 'created': datetime.datetime(2024, 10, 18, 16, 11, 12, 659424, tzinfo=datetime.timezone.utc), 'modified': datetime.datetime(2024, 10, 31, 17, 50, 28, 905146, tzinfo=datetime.timezone.utc), 'sleep_at': datetime.datetime(2024, 10, 19, 4, 11, 12, 649304, tzinfo=datetime.timezone.utc), 'expires_at': datetime.datetime(2024, 11, 1, 16, 11, 12, 649000, tzinfo=datetime.timezone.utc), 'organization': '3b7c20c9-e525-46ac-96e3-a9a332aef774', 'preferred_worker': None, 'documentation': None, 'write_ok': True, 'metadata': None}
要转换为 JSON 字符串,请使用 .json()
方法
>>> sim.json()
'{"id":"95bbbf37-a6d4-42b2-ab62-0234cc86370d","title":"2k links","state":"NEW","sleep":true,"owner":"tiparker@nvidia.com","cloned":false,"expires":false,"created":"2024-10-18T16:11:12.659424Z","modified":"2024-10-31T17:50:28.905146Z","sleep_at":"2024-10-19T04:11:12.649304Z","expires_at":"2024-11-01T16:11:12.649000Z","organization":"3b7c20c9-e525-46ac-96e3-a9a332aef774","preferred_worker":null,"documentation":null,"write_ok":true,"metadata":null}'
要将对象的数据与最新的 API 状态同步,请使用 .refresh()
方法
>>> sim.title
'2k links'
>>> sim.title = 'New Name'
>>> sim.title
'New Name'
>>> sim.refresh() # Refreshes the data from the API
>>> sim.title
'2k links'
直接访问相关对象
如上文调用 .json()
或 .dict()
时所见,Simulation
实例可能引用关联的 organization
。
通常可以直接访问相关对象。例如
>>> sim.organization
Organization(id='3b7c20c9-e525-46ac-96e3-a9a332aef774', name='Tim test org', member_count=8)
这些相关对象是延迟创建的,这意味着 Organization
对象在首次访问时按需获取。这允许在连接对象之间无缝遍历关系
>>> sim
Simulation(id='95bbbf37-a6d4-42b2-ab62-0234cc86370d', title='2k links', state='NEW', documentation=None, write_ok=True, metadata=None)
>>> sim.organization
Organization(id='3b7c20c9-e525-46ac-96e3-a9a332aef774', name='Tim test org', member_count=8)
>>> sim.organization.dict()
{
'id': '3b7c20c9-e525-46ac-96e3-a9a332aef774',
'name': 'Tim test org',
'member_count': 8,
'resource_budget': 'b0c2a464-f6c5-4a9c-a65c-d8645d6fa01f'
}
>>> sim.organization.resource_budget
ResourceBudget(id='b0c2a464-f6c5-4a9c-a65c-d8645d6fa01f')
>>> sim.organization.resource_budget.dict()
{
'id': 'b0c2a464-f6c5-4a9c-a65c-d8645d6fa01f',
'cpu': 300,
'cpu_used': 0,
'image_uploads': 10000000000,
'image_uploads_used': 111804416,
'memory': 300000,
'memory_used': 0,
'simulations': 15,
'simulations_used': 0,
'storage': 3000,
'storage_used': 0,
'userconfigs': 10,
'userconfigs_used': 0
}
当比较不同进程访问的对象时,您应该比较对象的 id
(或其他主键)
>>> id(sim) == id(node.simulation) # Different objects in Python
False
>>> sim == node.simulation
False
>>> sim.id == node.simulation.id
True
查询相关对象
在 Air 中,模拟由多个节点构成,每个节点可以包含多个接口。在 SDK V2 中,这些“多对一”关系(即一个模拟包含多个节点,一个节点包含多个接口)必须显式查询才能访问所有相关实体。例如
from air_sdk.v2 import AirApi
air = AirApi(username=..., password=...)
```python
>>> sim = air.simulations.get('1ebf9958-a01e-4396-88f6-946e93299cf2')
>>> hasattr(sim, 'nodes')
False
迭代模拟的节点列表
>>> for node in air.nodes.list(simulation=sim):
... print(node.name)
...
oob-mgmt-switch
node7
node2
node3
node4
node1
node6
node10
oob-mgmt-server
node8
node5
node9
或者,通过调用 list
获取节点列表
>>> nodes = list(air.nodes.list(simulation=sim))
>>> len(nodes)
12
接口可以通过单个节点或模拟进行筛选
>>> sim = air.simulations.get('<simulation-id>')
>>> node = next(air.nodes.list(simulation=sim))
>>> node_interfaces = list(air.interfaces.list(node=node))
>>> len(node_interfaces)
1
>>> sim_interfaces = list(air.interfaces.list(simulation=node.simulation))
>>> len(sim_interfaces)
29
创建模拟
使用 SDK V2 创建模拟有两种途径
- 文件导入
- 创建空白模拟
文件导入
您可以通过导入文件来高效可靠地创建完整的模拟。此过程类似于原始 SDK 支持的 DOT 文件上传过程,并镜像了 simulation import 端点。
from air_sdk.v2 import AirApi
air = AirApi(username=..., password=...)
simulation = air.simulations.create_from(
'my-simulation', # The Title
'JSON', # The format of the content. Only JSON is supported currently.
{
'nodes': {
'node-1': {
'os': 'generic/ubuntu2204',
},
'node-2': {
'os': 'generic/ubuntu2204',
},
},
'links': [
[{'node': 'node-1', 'interface': 'eth1'}, {'node': 'node-2', 'interface': 'eth1'}]
]
},
)
创建空白模拟
可以通过基本的 create simulation 端点来创建空白模拟(即没有节点的模拟)。
from air_sdk.v2 import AirApi
air = AirApi(username=..., password=...)
personal_sim = air.simulations.create(title="Blank Simulation for myself")
org = next(air.organizations.list(search="My Favorite Organization"))
sim_for_my_org = air.simulations.create(
title="Blank Simulation for my Favorite Org",
organization=org,
)
创建模拟端点指定的大多数字段都可以传递到 air.simulations.create
方法中。
修改模拟
您可以通过调整字段、添加或删除节点以及更新节点接口来自定义现有模拟。您可以根据需要从节点添加或删除新接口并连接它们。
调整模拟对象上的字段
选择现有模拟
您可以使用其 ID 检索现有模拟
from air_sdk.v2 import AirApi
air = AirApi(username=..., password=...)
simulation = air.simulations.get('<simulation-id>')
或者,您可以通过 list simulations 端点查询模拟
simulation = next(air.simulations.list(title="My Simulation Title")) # using `next` gets the first result
您可以通过 list simulations 中指定的任何值查询模拟。
my_favorite_org = next(air.organizations.list(search="My Favorite Org")) # using `next` returns the first result
simulation = next(air.simulations.list(title="My Simulation's Title", organization=my_favorite_org))
更新现有模拟
通过调用 .update
更新特定字段
>>> sim = air.simulations.get('1ebf9958-a01e-4396-88f6-946e93299cf2')
>>> sim.title
'Sam Personal 10 w OOB'
>>> sim.update(title="Sam's Personal 10 node sim with OOB")
>>> sim.title
"Sam's Personal 10 node sim with OOB"
在模拟对象上调用 .update
对应于 PATCH simulation V2。
模拟上还有一个 .full_update
方法,用于更新模拟上的所有字段
sim.full_update(
title='New Title',
documentation=sim.documentation,
expires=sim.expires,
expires_at=sim.expires_at,
metadata=sim.metadata,
preferred_worker=sim.preferred_worker,
sleep=sim.sleep,
sleep_at=sim.sleep_at,
)
Node
和 Interface
对象具有类似的 .update
和 .full_update
方法来修改其数据。
向模拟添加新节点
>>> image = next(air.images.list(name='generic/ubuntu2204')) # Obtain an image for the node
>>> image
Image(name='generic/ubuntu2204', version='22.04', organization_name=None)
>>> new_node = air.nodes.create(simulation=sim, name='node13', os=image)
>>> new_node.os.id == image.id
True
>>> new_node.simulation.id == sim.id
True
导出模拟
您可以将现有模拟导出为 JSON 表示形式,您可以共享该表示形式并将其重新导入到 Air 中。
from air_sdk.v2 import AirApi
air = AirApi(username=..., password=...)
sim_export_json = air.simulations.export(
simulation='<simulation-id>',
format='JSON',
image_ids=True, # defaults to False
)
# Or call `export` on a simulation object
simulation = air.simulations.get('<simulation-id>')
sim_export_json = simulation.export(format="JSON")