Air API 和 Python SDK

本项目提供了一个 Python SDK,用于与 NVIDIA Air API 交互。

使用 SDK

先决条件

SDK 需要 Python 3.7 或更高版本。安装 SDK 最安全的方法是在 Python 3.7 中设置虚拟环境。例如

$ apt-get install python3.7
$ python3.7 -m pip install virtualenv
$ python3.7 -m virtualenv venv37
$ . venv37/bin/activate

安装 SDK

要安装 SDK,请使用 pip

$ python3 -m pip install air-sdk

身份验证选项

身份验证需要 API 令牌、用户名/密码或持有者令牌。

API 令牌

生成 API 令牌的最简单方法是使用 Air UI

生成令牌后

>>> air = AirApi(username='<username>', password='<api_token>')

API 需要使用 API 令牌来生成持有者令牌

curl --request POST 'https://air.nvidia.com/api/v1/login/' --form 'username="<user>"' --form 'password="<api_token>"'

返回的持有者令牌用于后续的身份验证

curl --location --request <http_request>' \
--header 'Authorization: Bearer <bearer_token>'
其他身份验证选项

用户名和密码

用户名和密码身份验证仅对 Air 服务帐户有效,该帐户只能由 NVIDIA Air 管理员创建。管理员提供用户名和密码后

>>> air = AirApi(username='<username>', password='<password>')

持有者令牌

对于 SDK 使用,NVIDIA 建议使用 API 令牌 而不是持有者令牌。但是,持有者令牌可用于测试和执行不需要长期 API 令牌的操作。要使用持有者令牌,调用用户必须拥有 nvidia.com 帐户,并且之前已批准访问 NVIDIA Air。获得令牌后

>>> air = AirApi(bearer_token='<bearer_token>')

使用持有者令牌进行身份验证并获取所有模拟的列表

curl --location --request GET 'https://air.nvidia.com/api/v1/simulation/' \
--header 'Authorization: Bearer <bearer_token>'

服务帐户

NVIDIA 内部用户可以使用 SSA 客户端 ID 作为服务帐户进行身份验证。首先,必须生成有效的持有者令牌。

curl --user "$CLIENT_ID:$CLIENT_SECRET" --request POST $AIR_TOKEN_URL --header "Content-Type: application/x-www-form-urlencoded" --data-urlencode "grant_type=client_credentials" --data-urlencode "scope=api-access"
{"access_token":"eyJraWQ...","token_type":"bearer","expires_in":900,"scope":"api-access"}

将 $CLIENT_ID、$CLIENT_SECRET 和 $AIR_TOKEN_URL 替换为您在客户端注册期间生成的值。有关更多详细信息,请参阅关于使用服务帐户的内部文档。

获得持有者令牌后,可以像 Air 持有者令牌 一样使用它

示例

列出所有模拟

SDK 提供了各种辅助方法用于与 API 交互。下面的示例展示了如何使用 SDK 和 cURL 通过 API 列出所有模拟

>>> air.simulations.list()
[<Simulation sim1 c51b49b6-94a7-4c93-950c-e7fa4883591>, <Simulation sim2 3134711d-015e-49fb-a6ca-68248a8d4aff>]
curl --request GET 'https://air.nvidia.com/api/v1/simulation/' \
--header 'Authorization: Bearer <bearer_token>' 

获取特定模拟

要获取 ID 为 c51b49b6-94a7-4c93-950c-e7fa4883591 的模拟

>>> sim1 = air.simulations.get('c51b49b6-94a7-4c93-950c-e7fa4883591')
>>> print(sim1.title)
My Sim
curl --request GET 'https://air.nvidia.com/api/v1/simulation/?id=c51b49b6-94a7-4c93-950c-e7fa4883591' \
--header 'Authorization: Bearer <bearer_token>'

创建模拟

使用自定义拓扑和自定义组织创建一个模拟。

示例 topology.dot 文件

graph "sample_topology" {
  "cumulus0" [ memory="1024" os="cumulus-vx-4.4.0" cpu="1"]
  "cumulus1" [ memory="1024" os="cumulus-vx-4.4.0" cpu="1"]
    "cumulus0":"swp1" -- "cumulus1":"swp1"
    "cumulus0":"swp2" -- "cumulus1":"swp2"
}

创建组织,然后创建并启动模拟

目前仅支持 NVIDIA 用户创建组织。

>>> from air_sdk import AirApi
>>> user = 'user@nvidia.com'
>>> api_token = 'fake_api_token'
>>> air = AirApi(username=user, password=api_token)
>>> dot_file_path = '/tmp/topology.dot'
>>> org_name = 'My Organization'
>>> org = air.organizations.create(name=org_name, members=[{'username': f'{user}', 'roles': ['Organization Admin']}])
>>> simulation = air.simulations.create(topology_data=dot_file_path, organization=org)

创建组织

curl --location --request POST 'https://air.nvidia.com/api/v1/organization/' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <bearer_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
  "name": "My Organization",
  "members": [
    {
        "username": "user@nvidia.com"
    }
  ]
}'

创建并启动模拟

curl --location --request POST 'https://air.nvidia.com/api/v2/simulation/' \
--header 'Authorization: Bearer <bearer_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
  "topology_data": "graph \"My Simulation\" {\n  \"cumulus0\" [ memory=\"1024\" os=\"cumulus-vx-5.7.0\" cpu=\"1\" ]\n  \"cumulus1\" [ memory=\"1024\" os=\"cumulus-vx-5.7.0\" cpu=\"1\"]\n    \"cumulus0\":\"swp1\" -- \"cumulus1\":\"swp1\"\n    \"cumulus0\":\"swp2\" -- \"cumulus1\":\"swp2\"\n}\n",
  "organization": "<organization_uuid>"
}'

可选地,在模拟创建期间还可以包含 ZTP 脚本。该脚本将自动由 oob-mgmt-server 托管,并由支持 ZTP 的节点获取。

>>> ztp_contents = '<ztp_script_content_here>'
>>> simulation = air.simulations.create(topology_data=dot_file_path, ztp_script=ztp_contents)
curl --location --request POST 'https://air.nvidia.com/api/v2/simulation/' \
--header 'Authorization: Bearer <bearer_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
  "topology_data": "graph \"My Simulation\" {\n  \"cumulus0\" [ memory=\"1024\" os=\"cumulus-vx-5.7.0\" cpu=\"1\" ]\n  \"cumulus1\" [ memory=\"1024\" os=\"cumulus-vx-5.7.0\" cpu=\"1\"]\n    \"cumulus0\":\"swp1\" -- \"cumulus1\":\"swp1\"\n    \"cumulus0\":\"swp2\" -- \"cumulus1\":\"swp2\"\n}\n",
  "ztp_script": "<ztp_script_content_here>"
}'

删除模拟

>>> from air_sdk import AirApi
>>> air = AirApi(username='<user>', password='<api_token>')
>>> simulation = air.simulations.get('<simulation_uuid>')
>>> simulation.delete()

查找模拟

curl --request GET 'https://air.nvidia.com/api/v1/simulation/?id=<simulation_uuid>' \
--header 'Authorization: Bearer <bearer_token>'

删除模拟

curl --request POST 'https://air.nvidia.com/api/v1/simulation/<simulation_uuid>/control/' \
--header 'Authorization: Bearer <bearer_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
  "action": "destroy",
}'

启用 SSH 服务

唤醒休眠的模拟并启用 SSH 连接到 oob-mgmt-server。

查找并加载模拟

>>> from air_sdk import AirApi
>>> air = AirApi(username='<user>', password='<api_token>')
>>> simulation = air.simulations.get('<simulation_uuid>')
>>> simulation.load()

加载模拟

curl --request POST 'https://air.nvidia.com/api/v1/simulation/<simulation_id>/control/' \
--header 'Authorization: Bearer <bearer_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
  "action": "load",
  "start": true
}'

模拟 ID 将在稍后再次使用。

启用 ssh 连接到 oob-mgmt-server 的管理端口,并打印 ssh 到设备的命令

>>> service_name = 'oob-mgmt-server SSH'
>>> interface = 'oob-mgmt-server:eth0'
>>> dest_port = 22
>>> service = air.services.create(name=service_name, interface=interface, simulation=simulation, dest_port=dest_port)
>>> print(f'ssh -p {service.src_port} {service.os_default_username}@{service.host}')
ssh -p 15738 ubuntu@worker.air.nvidia.com

要启用 SSH 连接到节点的接口,API 需要特定的 simulation-interface 对象的 uuid 或资源 URL。在此示例中,simulation_id 为 47c91cdd-93d2-42b7-9c94-1580a9e49a88,simulation-interface 是 oob-mgmt-server 节点上的 eth0。

要查找模拟接口 uuid,首先查找节点对象

curl --request GET 'https://air.nvidia.com/api/v1/node/?name=oob-mgmt-server&simulation=47c91cdd-93d2-42b7-9c94-1580a9e49a88' \
--header 'Authorization: Bearer <bearer_token>' 

这将返回一个包含一个节点的列表。该节点有一个接口列表

[{
    "url": "https://air.nvidia.com/api/v1/node/f2b54dc7-2ec0-40de-b04f-8a2b8655814a/",
    "id": "f2b54dc7-2ec0-40de-b04f-8a2b8655814a",
    "name": "oob-mgmt-server",
    "os": "https://air.nvidia.com/api/v1/image/40000000-0000-0000-8050-000000000001/",
    "interfaces": [
        {
            "url": "https://air.nvidia.com/api/v1/interface/fc92eb67-0abb-4a36-8458-2ecf5cc8ec75/",
            "id": "fc92eb67-0abb-4a36-8458-2ecf5cc8ec75", <--------
            "name": "eth0",
            "mac_address": "04:ca:04:5a:6c:17",
            "outbound": true,
            "index": 5
        }
    ],
    "topology": "https://air.nvidia.com/api/v1/topology/0fbdfdef-c284-4287-85aa-1499fef18a3b/"
}]

查找接口 ID 并使用它来解析 simulation-interface

curl --request GET 'https://air.nvidia.com/api/v1/simulation-interface/?original=fc92eb67-0abb-4a36-8458-2ecf5cc8ec75' \
--header 'Authorization: Bearer <bearer_token>'
[{
    "url": "https://air.nvidia.com/api/v1/simulation-interface/bc084dc3-b009-430e-a49a-0699362f955a/",
    "id": "bc084dc3-b009-430e-a49a-0699362f955a", <--------
    "node": "https://air.nvidia.com/api/v1/simulation-node/fbfe474d-44ba-4ff5-b933-658a33857c96/",
    "original": "https://air.nvidia.com/api/v1/interface/fc92eb67-0abb-4a36-8458-2ecf5cc8ec75/",
    "link_up": false,
    "services": [
        "https://air.nvidia.com/api/v1/service/af474fff-4bc9-4590-9f68-0cd3c3b021da/"
    ],
    ...
}]

最后,在 interface 参数中使用 simulation-interface ID,并在 simulation ID 中创建 ssh 服务

curl --request POST 'https://air.nvidia.com/api/v1/service/' \
--header 'Authorization: Bearer <bearer_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
  "name": "oob-mgmt-server SSH",
  "simulation": "47c91cdd-93d2-42b7-9c94-1580a9e49a88",
  "interface": "bc084dc3-b009-430e-a49a-0699362f955a",
  "dest_port": 22
}'

响应类似于以下内容

{
    "url": "https://air.nvidia.com/api/v1/service/af474fff-4bc9-4590-9f68-0cd3c3b021da/",
    "id": "af474fff-4bc9-4590-9f68-0cd3c3b021da",
    "name": "oob-mgmt-server SSH",
    "simulation": "https://air.nvidia.com/api/v1/simulation/47c91cdd-93d2-42b7-9c94-1580a9e49a88/",
    "interface": "https://air.nvidia.com/api/v1/simulation-interface/bc084dc3-b009-430e-a49a-0699362f955a/",
    "dest_port": 22,
    "src_port": 15502, <--------
    "link": "",
    "service_type": "other",
    "node_name": "oob-mgmt-server",
    "interface_name": "eth0",
    "host": "worker.air.nvidia.com", <--------
    "os_default_username": "ubuntu" <--------
}

使用以下模板生成 SSH 命令

ssh -p <src_port> <os_default_username>@<host>

该模板生成类似于以下的命令

ssh -p 15502 ubuntu@worker.air.nvidia.com

使用 oob-mgmt-server 作为跳板机,并通过 oob-mgmt-server ssh 连接到模拟中的另一个节点

>>> ssh -J ubuntu@worker.air.nvidia.com:15738 user@hostname

上传镜像并创建拓扑

上传自定义镜像并使用该镜像创建拓扑。

目前仅支持 NVIDIA 用户上传镜像。

上传并创建镜像对象

>>> from air_sdk import AirApi
>>> user = 'user@nvidia.com'
>>> api_token = 'fake_api_token'
>>> air = AirApi(username=user, password=api_token)
>>> image_name = 'My Image'
>>> filename = '/Users/user/my_image.qcow2'
>>> # Is the air-agent enabled in the Image by default?
>>> agent_enabled = False
>>> # Should the image be published and accessible to all users? 
>>> default_username = 'admin'
>>> default_password = 'admin'
>>> organization = '<organization_uuid>'
>>> version = '1.0.0'
>>> cpu_arch = 'x86'
>>> image = air.images.create(name=image_name, filename=filename, agent_enabled=agent_enabled, default_username=default_username, default_password=default_password, organization=organization, version=version, cpu_arch=cpu_arch)

创建镜像对象

curl --request POST 'https://air.nvidia.com/api/v1/image/' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <bearer_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
  "name": "<image_name>",
  "base": "false",
  "agent_enabled": "false",
  "cpu_arch": "x86",
  "default_username": "admin",
  "organization": "<organization_id>",
  "simx": "false",
  "provider": "VM",
  "version": "1.0.0"
}'

响应包含一个镜像上传 URL

{
    "url": "https://air.nvidia.co,/api/v1/image/3d9a34e6-fd64-47bc-a65d-8a30f9f3ddc7/",
    "id": "3d9a34e6-fd64-47bc-a65d-8a30f9f3ddc7",
    ...
    "upload_url": "https://air.nvidia.com/api/v1/image/3d9a34e6-fd64-47bc-a65d-8a30f9f3ddc7/upload/", <--------
    ...
}

使用提供的 upload_url 将本地镜像文件上传到 Air

curl --request PUT 'https://air.nvidia.com/api/v1/image/3d9a34e6-fd64-47bc-a65d-8a30f9f3ddc7/upload/' -F filename='@/Users/admin/fake_image.qcow2' \
--header 'Authorization: Bearer <bearer_token>'

在自定义拓扑中使用您创建的镜像

>>> topology_name = 'My Topology'
>>> node_name = 'server01'
>>> dot_graph = f'graph \"{topology_name}\" {{ \"{node_name}\" [ os=\"{image_name}\"] }}'
>>> simulation = air.simulations.create(topology_data=dot_graph)
curl --request POST 'https://air.nvidia.com/api/v2/simulation/' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <bearer_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "topology_data": "graph \"My Topology\" {\n  \"cumulus0\" [ memory=\"1024\" os=\"<image_uuid>\" cpu=\"1\" ]\n  \"cumulus1\" [ memory=\"1024\" os=\"<image_uuid>\" cpu=\"1\" ]\n    \"cumulus0\":\"swp1\" -- \"cumulus1\":\"swp1\"\n    \"cumulus0\":\"swp2\" -- \"cumulus1\":\"swp2"\n}\n"
  }'

节点指令

您可以使用节点指令通过 API 配置已安装 Air 代理的模拟节点。Air 代理存在于使用 agent_enabled 标志设置为 true 的镜像的模拟节点中。

在创建(但未启动)模拟后,获取要配置的模拟节点的 ID

查找模拟的模拟节点,并获取要配置的特定模拟节点。在本例中,节点名为 sample-node

>>> simnodes = air.simulation_nodes.list(simulation=simulation)
>>> for simnode in simnodes:
>>>    if 'sample-node' == simnode.name:
>>>        sample_node = simnode

编辑 sample-node 上的 /etc/network/interfaces 并使用 ifreload 应用配置

>>> eni_contents = '<string_containing_desired_etc_network_interfaces_content>''
>>> post_cmd = 'ifreload -a'
>>> data = {'/etc/network/interfaces': eni_contents, 'post_cmd': post_cmd}
>>> sample_node.create_instructions(data=json.dumps(data), executor='file')

编辑 sample-node 上的 /etc/frr/frr.conf 并重启 FRR

>>> frr_contents = '<frr_conf_config_here>'
>>> post_cmd = 'systemctl restart frr'
>>> data = {'/etc/frr/frr.conf': frr_contents, 'post_cmd':post_cmd}
>>> sample_node.create_instructions(data=json.dumps(data), executor='file')

要执行命令而不是填充文件内容,请使用 shell 执行器而不是 file

>>> data = 'ip link set swp1 ip address 192.168.100.2/24'
>>> sample_node.create_instructions(data=data, executor='shell')

向名为 oob-mgmt-server 的节点添加 ZTP 脚本

>>> oob_mgmt_server = air.simulation_nodes.list(simulation=simulation, name='oob-mgmt-server')[0]
>>> ztp_contents = '<ztp_script_content_here>'
>>> data = {'/var/www/html/cumulus-ztp': ztp_contents}
>>> oob_mgmt_server.create_instructions(data=json.dumps(data), executor='file')

最后,启动模拟

>>> simulation.start()

查找模拟的节点

curl --request GET 'https://air.nvidia.com/api/v1/simulation-node/?simulation=<simulation_id>' \
--header 'Authorization: Bearer <bearer_token>'

创建并发送节点指令

curl --request POST 'https://air.nvidia.com/api/v1/simulation-node/<simulation_node_id>/instructions/' \
--header 'Authorization: Bearer <bearer_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "executor": "file",
    "data": "{'\''/etc/frr/frr.conf'\'': '\''<frr.conf contents>'\'', '\''post_cmd'\'':,'\''systemctl restart frr'\''}"
}'

或者,使用 shell 执行器而不是 file 来运行命令

curl --request POST 'https://air.nvidia.com/api/v1/simulation-node/<simulation_node_id>/instructions/' \
--header 'Authorization: <bearer_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "executor": "shell",
    "data": "ip link set swp1 ip address 192.168.100.2/24"
}'

为了避免在运行 5.0.0 或更早版本的 Cumulus Linux 节点上出现竞争条件,请在启动模拟之前安排节点指令。如果您不按此顺序执行步骤,则指令可能无法完成。

使用 Cloud-init

Cloud-init 允许用户在首次启动时配置其节点。Cloud-init 可以运行用户脚本来执行各种配置任务。

有关用户数据和元数据文件的信息和示例,请参阅 cloud-init 文档

在创建(但未启动)模拟后,获取要配置的特定模拟节点。在本例中,节点名为 node-1 和 node-2

>>> sim_node_1 = air.simulation_nodes.list(name='node-1', simulation=simulation).pop()
>>> sim_node_2 = air.simulation_nodes.list(name='node-2', simulation=simulation).pop()

创建用户数据和元数据用户配置

>>> USER_DATA = """#cloud-config
... 
... users:
... - default
... - name: custom-user
...   passwd: "$6$kW4vfBM9kGgq4hr$TFtHW7.3jOECR9UCBuw9NrdSMJETzSVoNQGcVv2y.RqRUzWDEtYhYRkGvIpB6ml1fh/fZEVIgKbSXI9L1B6xF." # 'possible'
...   shell: /bin/bash
...   lock-passwd: false
...   ssh_pwauth: True
...   chpasswd: { expire: False }
...   sudo: ALL=(ALL) NOPASSWD:ALL
...   groups: users, admin
... """
>>> sim_node_1_metadata = air.user_configs.create(name='sim-node-1-metadata', kind='cloud-init-meta-data', organization=org, content='local-hostname: cloud-init-node-1')
>>> sim_node_2_metadata = air.user_configs.create(name='sim-node-1-metadata', kind='cloud-init-meta-data', organization=org, content='local-hostname: cloud-init-node-2')
>>> sim_node_shared_userdata = air.user_configs.create(name='sim-node-shared-userdata', kind='cloud-init-user-data', organization=org, content=USER_DATA)

为模拟节点设置 cloud-init 分配

>>> sim_node_1.set_cloud_init_assignment({'user_data': sim_node_shared_userdata, 'meta_data': sim_node_1_metadata})
>>> sim_node_2.set_cloud_init_assignment({'user_data': sim_node_shared_userdata, 'meta_data': sim_node_2_metadata})

最后,启动模拟

>>> simulation.start()

调整请求超时

默认情况下,SDK 为所有 API 请求实现以下超时

  • connect_timeout 为 16 秒,用于建立与服务器的连接。
  • read_timeout 为 61 秒,用于接收对请求的响应。

您可以在实例化 AirApi 客户端后调整这些值

>>> air = AirApi(username='<username>', password='<api_token>')
>>> air.client.default_connect_timeout = 30
>>> air.client.default_read_timeout = 120

开发

非常欢迎对 SDK 做出贡献。所有代码在合并之前都必须通过 linting 和单元测试。

要求

python3 -m pip install -r requirements-dev.txt

代码检查

pylint **/*.py

单元测试

./unit_test.sh

生成文档

pydoc-markdown

SDK 参考指南

帐户

管理帐户

json

返回帐户的 JSON 字符串表示形式

刷新

将帐户与 API 返回的所有值同步

AccountApi

Account API 的高级接口

get

获取现有帐户

参数:

  • account_id str - 帐户 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

帐户

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.accounts.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Account mrobertson@nvidia.com 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有帐户

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.accounts.list()
[<Account mrobertson@nvidia.com c51b49b6-94a7-4c93-950c-e7fa4883591>, <Account nmitchell@nvidia.com 3134711d-015e-49fb-a6ca-68248a8d4aff>]

首选项

获取帐户首选项

返回:

  • dict - 响应 JSON

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.accounts.preferences()
{"baz": true, "foo": false}

Cumulus AIR API 模块

AirSession

requests.Session 的包装器

rebuild_auth

仅允许在 nvidia.com 和 cumulusnetworks.com 之间共享凭据

AirApi

API 客户端实例的主要接口

__init__

创建一个新的 API 客户端实例。调用者必须提供 usernamepasswordbearer_tokenpassword 参数可以是 API 令牌或服务帐户密码。

参数:

  • username str, 可选 - 用户名
  • password str, 可选 - 密码或 API 令牌
  • bearer_token str, 可选 - 预生成的持有者令牌
  • api_url str, 可选 - 默认 = https://air.nvidia.com/api/
  • api_version str - 默认 = v1

authorize

使用预生成的 API 令牌、服务帐户用户名/密码或预生成的持有者令牌授权 API 客户端。调用者必须传递有效的 bearer_tokenusernamepasswordpassword 参数可以是 API 令牌或服务帐户密码。成功授权后,所有后续 API 调用都将包含 AIR API 提供的授权令牌。注意: 这在实例化 AirApi 对象时会自动调用一次。

参数:

  • bearer_token str, 可选 - 预生成的持有者令牌
  • username str, 可选 - 用户名
  • password str, 可选 - 密码或 API 令牌

引发:

ValueError - 调用者未传递令牌或用户名/密码

get_token

为给定的用户名和密码获取新的持有者令牌

参数:

  • username str - 用户名
  • password str - 密码

返回:

  • str - 持有者令牌

引发:

get

GET 请求的包装器方法

post

POST 请求的包装器方法

put

PUT 请求的包装器方法

patch

PATCH 请求的包装器方法

delete

DELETE 请求的包装器方法

容量

查看平台容量

json

返回容量的 JSON 字符串表示形式

刷新

将容量与 API 返回的所有值同步

CapacityApi

Simulation API 的高级接口

get

获取 Simulation 的当前平台容量

参数:

  • simulation_id str | Simulation - 模拟或 ID

返回:

容量

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.capacity.get(simulation)
<Capacity 30>

演示

查看演示

json

返回演示的 JSON 字符串表示形式

刷新

将演示与 API 返回的所有值同步

DemoApi

Demo API 的高级接口

get

获取现有演示

参数:

  • demo_id str - 演示 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

演示

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.demos.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Demo EVPN 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有演示

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.demos.list()
[<Demo EVPN c51b49b6-94a7-4c93-950c-e7fa4883591>, <Demo Challenges 3134711d-015e-49fb-a6ca-68248a8d4aff>]

AIR SDK 的自定义异常

AirError

基类异常类。所有自定义异常都应从此类继承。

AirAuthorizationError

当与 API 的授权失败时引发。

AirUnexpectedResponse

当 API 返回意外响应时引发。

AirForbiddenError

当 API 调用返回 403 Forbidden 错误时引发

AirObjectDeleted

当访问先前实例化的对象(该对象已被删除)时引发

镜像

管理镜像

复制

将镜像复制到另一个组织。

参数:

  • organization str | Organization - 应在其中复制镜像的组织

返回:

镜像

引发:

AirUnexpectedresponse - 复制失败

delete

删除镜像。成功后,该对象将不再使用,并在被引用时引发 AirDeletedObject

引发:

AirUnexpectedresponse - 删除失败

json

返回镜像的 JSON 字符串表示形式

刷新

将镜像与 API 返回的所有值同步

更新

使用提供的数据更新镜像

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

发布

发布镜像以供公开使用

参数:

  • contact str - 与此镜像关联的联系人的电子邮件地址。

引发:

AirUnexpectedresponse - 发布失败

取消发布

从公开使用中取消发布镜像

引发:

AirUnexpectedresponse - 取消发布失败

上传

上传镜像文件

参数:

  • filename str - 本地镜像的绝对路径

引发:

AirUnexpectedresponse - 上传失败

ImageApi

Image API 的高级接口

get

获取现有镜像

参数:

  • image_id str - 镜像 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

镜像

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.images.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Image cumulus-vx-4.2.1 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有镜像

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.images.list()
[<Image cumulus-vx-4.2.1 c51b49b6-94a7-4c93-950c-e7fa4883591>, <Image generic/ubuntu18.04 3134711d-015e-49fb-a6ca-68248a8d4aff>]

创建

创建新镜像

参数:

  • name str - 镜像名称
  • organization str | Organization - Organization 或 ID
  • filename str, 可选 - 应上传的本地文件的绝对路径
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

镜像

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> image = air.images.create(name='my_image', filename='/tmp/my_image.qcow2', agent_enabled=False, default_username='user', default_password='password', organization=org, version='1.0.0', cpu_arch='x86')
>>> image
<Image my_image 01298e0c-4ef1-43ec-9675-93160eb29d9f>
>>> image.upload_status
'COMPLETE'
>>> alt_img = air.images.create(name='my_alt_img', filename='/tmp/alt_img.qcow2', agent_enabled=False, default_username='user', default_password='password', organization=org, version='1.0.0', cpu_arch='x86')
>>> alt_img.upload_status
'FAILED'

接口

查看接口

json

返回接口的 JSON 字符串表示形式

刷新

将接口与 API 返回的所有值同步

InterfaceApi

Interface API 的高级接口

get

获取现有接口

参数:

  • interface_id str - 接口 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

接口

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.interfaces.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Interface eth0 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有接口

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.interfaces.list()
[<Interface eth0 c51b49b6-94a7-4c93-950c-e7fa4883591>, <Interface eth1 3134711d-015e-49fb-a6ca-68248a8d4aff>]

SimulationInterface 模块

SimulationInterface

管理模拟接口

json

返回模拟接口的 JSON 字符串表示形式

刷新

将模拟接口与 API 返回的所有值同步

更新

使用提供的数据更新模拟接口

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

SimulationInterfaceApi

SimulationInterface API 的高级接口

get

获取现有模拟接口

参数:

  • simulation_interface_id str - SimulationInterface ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

SimulationInterface

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulation_interfaces.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<SimulationInterface 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有模拟接口

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulation_interfaces.list()
[<SimulationInterface c51b49b6-94a7-4c93-950c-e7fa4883591>, <SimulationInterface 3134711d-015e-49fb-a6ca-68248a8d4aff>]

作业

管理作业

json

返回作业的 JSON 字符串表示形式

刷新

将作业与 API 返回的所有值同步

更新

使用提供的数据更新作业

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

JobApi

Job API 的高级接口

get

获取现有作业

参数:

  • job_id str - 作业 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

作业

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.jobs.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Job START 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有作业

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.jobs.list()
[<Job START c51b49b6-94a7-4c93-950c-e7fa4883591>, <Job STOP 3134711d-015e-49fb-a6ca-68248a8d4aff>]

管理链接

delete

删除链接。成功后,该对象将不再使用,并在被引用时引发 AirDeletedObject

引发:

AirUnexpectedresponse - 删除失败

json

返回链接的 JSON 字符串表示形式

刷新

将链接与 API 返回的所有值同步

更新

使用提供的数据更新链接

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

LinkApi

Link API 的高级接口

get

获取现有链接

参数:

  • link_id str - 链接 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

链接

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.links.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Link 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有链接

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.links.list()
[<Link c51b49b6-94a7-4c93-950c-e7fa4883591>, <Link 3134711d-015e-49fb-a6ca-68248a8d4aff>]

创建

创建新链接

参数:

  • topology str | Topology - Topology 或 ID
  • interfaces list - Interface 对象或 ID 的列表
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

链接

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.links.create(topology=topology, interfaces=[intf1, 'fd61e3d8-af2f-4735-8b1d-356ee6bf4abe'])
<Link 01298e0c-4ef1-43ec-9675-93160eb29d9f>

登录

查看登录信息

json

返回登录信息的 JSON 字符串表示形式

刷新

将登录信息与 API 返回的所有值同步

LoginApi

Login API 的高级接口

get

获取登录信息或启动 OAuth 请求。这等效于 login.list()

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

登录

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.login.get()
<Login>

list

获取登录信息或启动 OAuth 请求。这等效于 login.get()

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

登录

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.login.get()
<Login>

市场

查看市场演示

json

返回市场演示的 JSON 字符串表示形式

刷新

将市场演示与 API 返回的所有值同步

MarketplaceApi

Marketplace API 的高级接口

get

获取现有市场演示

参数:

  • demo_id str - 市场演示 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

市场

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.Marketplace.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Marketplace Demo EVPN 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有市场演示

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.marketplace.list()
[<Marketplace Demo EVPN c51b49b6-94a7-4c93-950c-e7fa4883591>, <Marketplace Demo Challenges 3134711d-015e-49fb-a6ca-68248a8d4aff>]

节点

管理节点

delete

删除节点。成功后,该对象将不再使用,并在被引用时引发 AirDeletedObject

引发:

AirUnexpectedresponse - 删除失败

json

返回节点的 JSON 字符串表示形式

刷新

将节点与 API 返回的所有值同步

更新

使用提供的数据更新节点

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

NodeApi

Node API 的高级接口

get

获取现有节点

参数:

  • node_id str - 节点 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

节点

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.nodes.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Node server 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有节点

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.nodes.list()
[<Node server c51b49b6-94a7-4c93-950c-e7fa4883591>, <Node switch 3134711d-015e-49fb-a6ca-68248a8d4aff>]

创建

创建新节点

参数:

  • name str - 节点名称
  • topology str | Topology - Topology 或 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

节点

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.nodes.create(name='server', topology=topology)
<Node server 01298e0c-4ef1-43ec-9675-93160eb29d9f>

SimulationNode

管理 SimulationNode

json

返回模拟节点的 JSON 字符串表示形式

刷新

将模拟节点与 API 返回的所有值同步

更新

使用提供的数据更新模拟节点

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

create_instructions

SimulationNode 的代理创建要执行的指令

参数:

  • data str | list - 指令数据
  • executor str - 代理执行器类型
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

  • dict - 响应 JSON

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> simulation_node.create_instructions(data='echo foo', executor='shell')
{'id': '67f73552-ffdf-4e5f-9881-aeae227604a3'}

list_instructions

列出 SimulationNode 的所有指令

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> simulation_node.list_instructions()
[{'id': '56abc69b-489f-429a-aed9-600f26afc956'}, {'id': '7c9c3449-f071-4bbc-bb42-bef04e44d74e'}]

delete_instructions

删除 SimulationNode 的所有指令

引发:

AirUnexpectedresponse - 指令删除失败

示例:

>>> simulation_node.delete_instructions()

set_cloud_init_assignment

设置特定节点的 cloud-init 脚本分配

参数:

  • user_data str - UserConfig ID。UserConfig 必须是 ‘cloud-init-user-data’ 类型
  • meta_data str - UserConfig ID。UserConfig 必须是 ‘cloud-init-meta-data’ 类型

返回:

  • dict - 响应 JSON

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> simulation_node.set_cloud_init_assignment({'user_data': '09b168b8-f9dd-408c-b3c6-bbecf6a43a09', 'meta_data': 'a19a8715-b46b-4599-81e6-379adebe4bb4'})
{'simulation_node': 'a44894a4-d805-42e2-a07f-7fd5fb0ea154', 'user_data': '09b168b8-f9dd-408c-b3c6-bbecf6a43a09', 'meta_data': 'a19a8715-b46b-4599-81e6-379adebe4bb4', 'user_data_name': 'userdata1', 'meta_data_name': 'metadata1'}

>>> simulation_node.set_cloud_init_assignment({'user_data': userdata, 'meta_data': metadata})
{'simulation_node': 'a44894a4-d805-42e2-a07f-7fd5fb0ea154', 'user_data': '09b168b8-f9dd-408c-b3c6-bbecf6a43a09', 'meta_data': 'a19a8715-b46b-4599-81e6-379adebe4bb4', 'user_data_name': 'userdata1', 'meta_data_name': 'metadata1'}

>>> simulation_node.set_cloud_init_assignment({'user_data': userdata, 'meta_data': None})
{'simulation_node': 'a44894a4-d805-42e2-a07f-7fd5fb0ea154', 'user_data': '09b168b8-f9dd-408c-b3c6-bbecf6a43a09', 'meta_data': None, 'user_data_name': 'userdata1', 'meta_data_name': None}
>>> sim.start()

get_cloud_init_assignment

获取特定节点的 cloud-init 分配

返回:

  • dict - 响应 JSON

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> simulation_node.get_cloud_init_assignment()
{'simulation_node': 'a44894a4-d805-42e2-a07f-7fd5fb0ea154', 'user_data': 09b168b8-f9dd-408c-b3c6-bbecf6a43a09', 'meta_data': 'a19a8715-b46b-4599-81e6-379adebe4bb4', 'user_data_name': 'userdata1', 'meta_data_name': 'metadata1'}

控制

SimulationNode 发送控制命令。

参数:

  • action str - 控制命令
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

  • dict - 响应 JSON

示例:

>>> simulation_node.control(action='reset')
{'result': 'success'}

重建

SimulationNode 重建回其初始状态。所有现有数据都将丢失。

重置

重置 SimulationNode

SimulationNodeApi

SimulationNode API 的包装器

get

获取现有模拟节点

参数:

  • simulation_node_id str - SimulationNode ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

SimulationNode

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulation_nodes.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<SimulationNode my_sim 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有模拟节点

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulation_nodes.list()
[<SimulationNode sim1 c51b49b6-94a7-4c93-950c-e7fa4883591>, <SimulationNode sim2 3134711d-015e-49fb-a6ca-68248a8d4aff>]

组织

管理组织

delete

删除组织。成功后,该对象将不再使用,并在被引用时引发 AirDeletedObject

引发:

AirUnexpectedresponse - 删除失败

json

返回组织的 JSON 字符串表示形式

刷新

将组织与 API 返回的所有值同步

更新

使用提供的数据更新组织

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

add_member

向组织添加新成员

参数:

  • username str - 要添加的用户的电子邮件地址
  • roles list, 可选 - 要分配给用户的角色列表。有效值为 ‘Organization Admin’ 或 ‘Organization Member’。如果未提供角色列表,则 ‘Organization Member’ 用作默认角色。

示例:

>>> organization.add_member('user1@nvidia.com')
>>> organization.add_member('user2@nvidia.com', roles=['Organization Admin'])

add_members

向组织添加新成员

参数:

  • members list - 组织成员字典列表,格式为 {‘username’: <email_address>, ‘roles’: []}. ‘roles’ 是可选的,默认为 [‘Organization Member’]可以是 ‘Organization Admin’ 或 ‘Organization Member’ 的值。

示例:

>>> organization.add_members([{'username': 'user1@nvidia.com', 'roles': ['Organization Admin']}, {'username': 'user2@nvidia.com'}])

remove_member

从组织中移除成员

参数:

  • username str - 要移除的用户的电子邮件地址

示例:

>>> organization.remove_member('user1@nvidia.com')

remove_members

从组织中移除多个成员

参数:

  • members list - 要移除的用户的电子邮件地址

示例:

>>> organization.remove_members(['user1@nvidia.com', 'user2@nvidia.com'])

OrganizationApi

Organization API 的高级接口

get

获取现有组织

参数:

  • organization_id str - 组织 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

组织

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.organizations.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Organization NVIDIA 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有组织

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.organizations.list()
[<Organization NVIDIA c51b49b6-94a7-4c93-950c-e7fa4883591>, <Organization Customer 3134711d-015e-49fb-a6ca-68248a8d4aff>]

创建

创建新组织

参数:

  • name str - 组织名称
  • members list, 可选 - 组织成员字典列表,格式为 {‘username’: <email_address>, ‘roles’: []}. ‘roles’ 是可选的,默认为 [‘Organization Member’]可以是 ‘Organization Admin’ 或 ‘Organization Member’ 的值。如果未提供成员列表,则默认情况下,调用用户的帐户将被设置为组织管理员。
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

组织

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.organizations.create(name='NVIDIA', members=[{'username': 'user1@nvidia.com', 'roles': ['Organization Admin']}, {'username': 'user2@nvidia.com'}])
<Organization NVIDIA 01298e0c-4ef1-43ec-9675-93160eb29d9f>

权限

管理权限

delete

删除权限。成功后,该对象将不再使用,并在被引用时引发 AirDeletedObject

引发:

AirUnexpectedresponse - 删除失败

json

返回权限的 JSON 字符串表示形式

刷新

将权限与 API 返回的所有值同步

更新

使用提供的数据更新权限

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

PermissionApi

Permission API 的高级接口

创建

创建新权限。调用者必须提供 simulationtopologysubject_id

参数:

  • email str - 被授予权限的用户的电子邮件地址
  • simulation str | Simulation, 可选 - Simulation 或 ID
  • topology str | Topology, 可选 - Topology 或 ID
  • subject_id str | AirModel, 可选 - AirModel 实例或 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

权限

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.permissions.create(email='mrobertson@nvidia.com', topology=topology, write_ok=True)
<Permission 01298e0c-4ef1-43ec-9675-93160eb29d9f>
>>> air.permissions.create(email='mrobertson@nvidia.com',
... subject_id='80cf922a-7b80-4795-8cc5-550833ab1cec', subject_model='simulation.image')
<Permission 8a09ea66-51f9-4ddd-8416-62c266cd959e>

get

获取现有权限

参数:

  • permission_id str - 权限 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

权限

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.permissions.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Permission 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有权限

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.permissions.list()
[<Permission c51b49b6-94a7-4c93-950c-e7fa4883591>, <Permission 3134711d-015e-49fb-a6ca-68248a8d4aff>]

资源预算

管理资源预算

json

返回预算的 JSON 字符串表示形式

刷新

将预算与 API 返回的所有值同步

更新

使用提供的数据更新预算

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

ResourceBudgetApi

ResourceBudget API 的高级接口

get

获取现有预算

参数:

  • budget_id str - 资源预算 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

资源预算

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.resource_budgets.get('c604c262-396a-48a0-a8f6-31708c0cff82')
<ResourceBudget c604c262-396a-48a0-a8f6-31708c0cff82>

list

列出现有预算

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.resource_budgets.list()
[<ResourceBudget c604c262-396a-48a0-a8f6-31708c0cff82>, <ResourceBudget 906675f7-8b8d-4f52-b59d-52847af2f0ef>]

服务

管理服务

delete

删除服务。成功后,该对象将不再使用,并在被引用时引发 AirDeletedObject

引发:

AirUnexpectedresponse - 删除失败

json

返回服务的 JSON 字符串表示形式

刷新

将服务与 API 返回的所有值同步

更新

使用提供的数据更新服务

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

ServiceApi

Service API 的高级接口

get

获取现有服务

参数:

  • service_id str - 服务 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

服务

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.services.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Service SSH 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有服务

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.services.list()
[<Service SSH c51b49b6-94a7-4c93-950c-e7fa4883591>, <Service HTTP 3134711d-015e-49fb-a6ca-68248a8d4aff>]

创建

创建新服务

参数:

  • name str - 服务名称
  • interface str | SimulationInterface - 应为其创建服务的接口。这可以以下列格式之一提供
  • simulation str | Simulation - Simulation 或 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

服务

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.services.create(name='myservice', interface='oob-mgmt-server:eth0', dest_port=22)
<Service myservice cc18d746-4cf0-4dd3-80c0-e7df68bbb782>
>>> air.services.create(name='myservice', interface=simulation_interface, dest_port=22)
<Service myservice 9603d0d5-5526-4a0f-91b8-a600010d0091>

模拟

管理模拟

json

返回模拟的 JSON 字符串表示形式

首选项

返回模拟首选项的 JSON 字符串表示形式

刷新

将模拟与 API 返回的所有值同步

更新

使用提供的数据更新模拟

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

create_service

为此模拟创建新服务

参数:

  • name str - 服务名称
  • interface str | SimulationInterface - 应为其创建服务的接口。这可以以下列格式之一提供
  • dest_port int - 服务端口号
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

服务

示例:

>>> simulation.create_service('myservice', 'oob-mgmt-server:eth0', 22, service_type='ssh')
<Service myservice cc18d746-4cf0-4dd3-80c0-e7df68bbb782>
>>> simulation.create_service('myservice', simulation_interface, 22, service_type='ssh')
<Service myservice 9603d0d5-5526-4a0f-91b8-a600010d0091>

add_permission

为此模拟的给定用户添加权限。

参数:

  • email str - 被授予权限的用户的电子邮件地址
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

权限

示例:

>>> simulation.add_permission('mrobertson@nvidia.com', write_ok=True)
<Permission 217bea68-7048-4262-9bbc-b98ab16c603e>

控制

向模拟发送控制命令。

参数:

  • action str - 控制命令
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

  • dict - 响应 JSON

示例:

>>> simulation.control(action='destroy')
{'result': 'success'}

加载

start() 的别名

启动

启动/加载模拟

停止

store() 的别名

存储

存储并关闭模拟电源

delete

删除模拟

SimulationApi

Simulation API 的高级接口

duplicate

复制/克隆现有模拟

参数:

  • simulation str | Simulation - 要复制的快照的模拟或 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

(Simulation, dict): 新创建的模拟和响应 JSON

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulations.duplicate(simulation=simulation)
<Simulation my_sim 5ff3f0dc-7db8-4938-8257-765c8e48623a>

get_citc_simulation

获取活动的 CITC 参考模拟

返回:

模拟

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulations.get_citc_simulation()
<Simulation my_sim b9125419-7c6e-41db-bba9-7d647d63943e>

get

获取现有模拟

参数:

  • simulation_id str - 模拟 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

模拟

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulations.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Simulation my_sim 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有模拟

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulations.list()
[<Simulation sim1 c51b49b6-94a7-4c93-950c-e7fa4883591>, <Simulation sim2 3134711d-015e-49fb-a6ca-68248a8d4aff>]

创建

创建新模拟。调用者必须提供 topologytopology_data

参数:

  • topology str | Topology, 可选 - Topology 或 ID
  • topology_data str | fd, optional - DOT 格式的拓扑。这可以作为包含原始 DOT 数据的字符串、本地磁盘上 DOT 文件的路径或本地文件的文件描述符传递
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

模拟

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulations.create(topology=topology, title='my_sim')
<Simulation my_sim 01298e0c-4ef1-43ec-9675-93160eb29d9f>
>>> air.simulations.create(topology_data='/tmp/my_net.dot', organization=my_org)
<Simulation my_sim c0a4c018-0b85-4439-979d-9814166aaeac>
>>> air.simulations.create(topology_data='graph "my_sim" { "server1" [ function="server" os="generic/ubuntu2204"] }', organization=my_org)
<Simulation my_sim b9c0c68e-d4bd-4e9e-8a49-9faf41efaf70>
>>> air.simulations.create(topology_data=open('/tmp/my_net.dot', 'r', encoding='utf-8')), organization=my_org)
<Simulation my_sim 86162934-baa7-4d9a-a826-5863f92b03ef>

SimulationInterface

管理模拟接口

json

返回模拟接口的 JSON 字符串表示形式

刷新

将模拟接口与 API 返回的所有值同步

更新

使用提供的数据更新模拟接口

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

SimulationInterfaceApi

SimulationInterface API 的高级接口

get

获取现有模拟接口

参数:

  • simulation_interface_id str - SimulationInterface ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

SimulationInterface

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulation_interfaces.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<SimulationInterface 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有模拟接口

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulation_interfaces.list()
[<SimulationInterface c51b49b6-94a7-4c93-950c-e7fa4883591>, <SimulationInterface 3134711d-015e-49fb-a6ca-68248a8d4aff>]

SimulationNode

管理模拟节点

json

返回模拟节点的 JSON 字符串表示形式

刷新

将模拟节点与 API 返回的所有值同步

更新

使用提供的数据更新模拟节点

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

create_instructions

SimulationNode 的代理创建要执行的指令

参数:

  • data str | list - 指令数据
  • executor str - 代理执行器类型
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

  • dict - 响应 JSON

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> simulation_node.create_instructions(data='echo foo', executor='shell')
{'id': '67f73552-ffdf-4e5f-9881-aeae227604a3'}

list_instructions

列出 SimulationNode 的所有指令

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> simulation_node.instructions.list()
[{'id': '56abc69b-489f-429a-aed9-600f26afc956'}, {'id': '7c9c3449-f071-4bbc-bb42-bef04e44d74e'}]

delete_instructions

删除 SimulationNode 的所有指令

引发:

AirUnexpectedresponse - 指令删除失败

示例:

>>> simulation_node.instructions.delete()

控制

SimulationNode 发送控制命令。

参数:

  • action str - 控制命令
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

  • dict - 响应 JSON

示例:

>>> simulation_node.control(action='reset')
{'result': 'success'}

重建

SimulationNode 重建回其初始状态。所有现有数据都将丢失。

重置

重置 SimulationNode

SimulationNodeApi

SimulationNode API 的包装器

get

获取现有模拟节点

参数:

  • simulation_node_id str - SimulationNode ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

SimulationNode

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulation_nodes.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<SimulationNode my_sim 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有模拟节点

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.simulation_nodes.list()
[<SimulationNode sim1 c51b49b6-94a7-4c93-950c-e7fa4883591>, <SimulationNode sim2 3134711d-015e-49fb-a6ca-68248a8d4aff>]

SSH 密钥

管理 SSH 密钥

delete

删除密钥。成功后,该对象将不再使用,并且在被引用时将引发 AirDeletedObject

引发:

AirUnexpectedresponse - 删除失败

json

返回密钥的 JSON 字符串表示形式

刷新

将密钥与 API 返回的所有值同步

SSHKeyApi

SSHKey API 的高级接口

list

列出现有密钥

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.ssh_keys.list()
[<SSHKey mykey c51b49b6-94a7-4c93-950c-e7fa4883591>, <SSHKey test_key 3134711d-015e-49fb-a6ca-68248a8d4aff>]

创建

向您的帐户添加新的公钥

参数:

  • name str - 公钥的描述性名称
  • public_key str - 公钥
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

SSH 密钥

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.ssh_keys.create(name='my_pub_key', public_key='<key_string>')
<SSHKey my_pub_key 01298e0c-4ef1-43ec-9675-93160eb29d9f>

令牌

管理 API 令牌

TokenApi

Token API 的高级接口

list

列出现有令牌

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedResponse - API 未返回 200 OK 或有效的 JSON 响应

示例:

>>> air.api_tokens.list()
[<Token mytoken c51b49b6-94a7-4c93-950c-e7fa4883591>, <Token test_token 3134711d-015e-49fb-a6ca-68248a8d4aff>]

创建

向您的帐户添加新的 API 令牌

参数:

  • name str - 公钥的描述性名称
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

令牌

引发:

AirUnexpectedResponse - API 未返回 200 OK 或有效的 JSON 响应

示例:

>>> air.api_tokens.create(name='my_api_token')
<Token my_api_token 01298e0c-4ef1-43ec-9675-93160eb29d9f>

delete

删除令牌

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

字符串

引发:

AirUnexpectedresponse - 从 Cumulus AIR API 收到意外响应 (404): {“detail”:“Not found."}

示例:

>>> air.api_tokens.delete()
'SUCCESS'

拓扑

管理拓扑

delete

删除拓扑。成功后,该对象将不再使用,并且在被引用时将引发 AirDeletedObject

引发:

AirUnexpectedresponse - 删除失败

json

返回拓扑的 JSON 字符串表示形式

刷新

将拓扑与 API 返回的所有值同步

更新

使用提供的数据更新拓扑

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

add_permission

为此拓扑添加给定用户的权限。

参数:

  • email str - 被授予权限的用户的电子邮件地址
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

权限

示例:

>>> topology.add_permission('mrobertson@nvidia.com', write_ok=True)
<Permission 217bea68-7048-4262-9bbc-b98ab16c603e>

TopologyApi

Topology API 的高级接口

get

获取现有拓扑

参数:

  • topology_id str - 拓扑 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

拓扑

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.topologies.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Topology my_network 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有拓扑

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.topologies.list()
[<Topology my_network1 c51b49b6-94a7-4c93-950c-e7fa4883591>, <Topology my_network2 3134711d-015e-49fb-a6ca-68248a8d4aff>]

创建

创建新的拓扑。调用者必须提供 dot (推荐) 或 json

参数:

  • dot str | fd, optional - DOT 格式的拓扑。这可以作为包含原始 DOT 数据的字符串、本地磁盘上 DOT 文件的路径或本地文件的文件描述符传递
  • json dict, optional - JSON 格式的拓扑

返回:

拓扑

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.topologies.create(dot='/tmp/my_net.dot')
<Topology my_net 01298e0c-4ef1-43ec-9675-93160eb29d9f>
>>> air.topologies.create(dot='graph "my sim" { "server1" [ function="server" os="generic/ubuntu1804"] }')
<Topology my_net 6256baa8-f54b-4190-85c8-1cc574590080>
>>> air.topologies.create(dot=open('/tmp/my_net.dot', 'r'))
<Topology my_net a3d09f12-56ff-4889-8e03-3b714d32c3e5>

工作节点

管理工作节点

json

返回工作节点的 JSON 字符串表示形式

刷新

将工作节点与 API 返回的所有值同步

更新

使用提供的数据更新工作节点

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

set_available

在 AIR 中设置工作节点的 available

参数:

可用 (bool)

WorkerApi

Worker API 的高级接口

get

获取现有工作节点

参数:

  • worker_id str - 工作节点 ID
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

工作节点

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.workers.get('3dadd54d-583c-432e-9383-a2b0b1d7f551')
<Worker worker01 3dadd54d-583c-432e-9383-a2b0b1d7f551>

list

列出现有工作节点

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.workers.list()
[<Worker worker01 c51b49b6-94a7-4c93-950c-e7fa4883591>, <Worker worker02 3134711d-015e-49fb-a6ca-68248a8d4aff>]

创建

创建新的工作节点

参数:

  • cpu int - 工作节点可以支持的 vCPU 数量
  • memory int - 工作节点可以支持的内存量(MB)
  • storage int - 工作节点可以支持的存储量(GB)
  • ip_address str - 内部 IP 地址
  • port_range str - 工作节点上可用的端口范围
  • username str - 用于 API 访问的工作节点用户名
  • password str - 用于 API 访问的工作节点密码
  • kwargs dict, 可选 - 所有其他可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

返回:

工作节点

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.workers.create(cpu=100, memory=200000, storage=1000, ip_address='10.1.1.1', port_range='10000-30000', username='worker01', password='secret')
<Worker my_sim 01298e0c-4ef1-43ec-9675-93160eb29d9f>

UserConfig

管理 UserConfig

delete

删除 UserConfig。成功后,该对象将不再使用,并且在被引用时将引发 AirDeletedObject

引发:

AirUnexpectedresponse - 删除失败

json

返回 UserConfig 的 JSON 字符串表示形式。

刷新

将 UserConfig 与 API 返回的所有值同步

更新

使用提供的数据更新 UserConfig

参数:

  • kwargs dict, 可选 - 所有可选关键字参数都作为请求的 JSON 有效负载中的键/值对应用

UserConfigApi

UserConfig API 的高级接口

list

列出现有 UserConfig 脚本

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

list

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.user_configs.list()
[<UserConfig userdata1 cloud-init-user-data 84ddf2da-7a09-4a3b-a2b9-09179f4668c8>]

创建

创建新的 UserConfig

参数:

  • name str - UserConfig 名称
  • kind str - 必须为 ‘cloud-init-user-data’ 或 ‘cloud-init-meta-data’
  • organization str - 组织 ID
  • content str - 数据脚本的纯文本内容或现有文件的路径或打开的文件句柄

返回:

UserConfig

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> userdata = air.user_configs.create(name='userdata1', kind='cloud-init-user-data', organization=None, content='/tmp/userConfig-ex1.YML')
<UserConfig userdata1 cloud-init-user-data 09b168b8-f9dd-408c-b3c6-bbecf6a43a09>
 
>>> metadata = air.user_configs.create(name='metadata1', kind='cloud-init-meta-data', organization='3dadd54d-583c-432e-9383-a2b0b1d7f551', content='local-hostname: cloud-init-node')
<UserConfig metadata1 cloud-init-meta-data a19a8715-b46b-4599-81e6-379adebe4bb4>

get

获取现有 UserConfig

参数:

  • kwargs dict, 可选 - 所有其他可选关键字参数都作为查询参数/过滤器应用

返回:

UserConfig

引发:

AirUnexpectedresponse - API 未返回 200 OK 或有效的响应 JSON

示例:

>>> air.user_configs.get(id='09b168b8-f9dd-408c-b3c6-bbecf6a43a09')
<UserConfig userdata1 cloud-init-user-data 09b168b8-f9dd-408c-b3c6-bbecf6a43a09>

更新

更新 UserConfig 的特定属性

参数:

  • kwargs dict, optional - 所有其他可选关键字参数都作为键/值对应用于请求的 JSON 有效负载中。Kind 和 Organization 字段无法更改。

delete

删除现有 UserConfig 脚本

引发:

AirUnexpectedresponse - Userconfig 删除失败

示例:

>>> metadata = air.user_configs.create(name='metadata', kind='cloud-init-meta-data', organization=None, content='local-hostname: cloud-init-node') 
>>> metadata.delete()