# SmartX CloudTower 管理平台 API 调用指南 欢迎使用 CloudTower API。通过 CloudTower API,您可以管理 SMTX OS 集群及 CloudTower 相关的所有资源。 CloudTower API 基于 OpenApi v3.0.0 规范进行开发,可以使用 cURL 或任何 HTTP 客户端进行调用。 > 注:开始调用前请确保您已阅读 `通用指南` 部分。 ## 通用指南 ### 请求地址 所有 API 需通过 CloudTower_URL + /v2/api 来访问。 ### 请求类型 CloudTower API 提供了极为灵活的请求参数以方便进行批量操作和条件查询,但考虑到参数灵活性和使用上的便捷性(如 `GET` 具有请求限制等)所有 API 一律使用了 `POST` 请求类型来发送 json 类型格式的数据。 ### 鉴权 除登录以外,所有的 API 都需要加上鉴权信息才可被正常调用。 鉴权信息通过名为 `Authorization` 的 HTTP header 来传递,其值(token)的获取方式如下(以 cURL 为例): ```json= curl -X 'POST' \ 'http://localhost:3000/v2/api/login' \ -H 'accept: application/json' \ -H 'content-language: en-US' \ -H 'Content-Type: application/json' \ -d '{ "username": "string", "source": "LOCAL", "password": "string" }' ``` 该请求的返回值如下所示,将 `data.token` 的值加入到后续请求的 HTTP header 中即可。 ```json= { "task_id": "string", "data": { "token": "string" } } ``` ### 分页查询 当请求一个资源列表(List) 时,需使用分页查询的方式,其请求参数如下表所述: | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **after** | string | 否 | 填入单个资源的 id。表示从该资源之后开始获取,不包含该资源。| | **before** | string | 否 | 填入单个资源的 id。表示从该资源之前开始获取,不包含该资源。| | **first** | number | 否| 可与 after / before 搭配使用,表示获取指定资源后的多少个数据。| | **last** | number | 否 | 非必填项。可与 after / before 搭配使用,表示获取指定资源前的多少个数据。| | **skip** | number | 否 | 非必填项。可与 after / before 搭配使用,表示跳过指定资源的 n 项后开始查询。| | **orderBy** | enum | 否 | 所有的值都在 api 各自的 schema 中可以查询到。表示查询顺序,通常包含了资源所有字段的降序(`_DESC`)或者升序 (`_ASC`)。| | **where** | object | 否| 条件查询,表示查询符合该条件的资源。where 的具体类型定义可在对应 api 的 schema 中查询到。| 以 /get-users 为例,API 提供针对 id 值的多种查询条件。 ```graphql { # 满足 id 为 1 的资源 id: "1", # 满足 id 不为 1 的资源 id_not: "1" # 满足 id 在给定范围内的资源 id_in: ["1","2","3","4"] # 满足 id 不在给定范围内的资源 id_not_in: ["1","2","3","4"] # 满足 id 小于 1 的资源, lt = less than (<) id_lt: "1", # 满足 id 小于等于 1 的资源, lte = less than or equals (≤) id_lte: "1", # 满足 id 大于 1 的资源, gt = greater than (>) id_gt: "1", # 满足 id 大于等于 1 的资源, gte = greater than or equals ( ≥) id_gte: "1" # 满足 id 值中包含 1 的资源 id_contains: "1", # 满足 id 值中不包含 1 的资源 id_not_contains: "1" # 满足 id 值以 1 为起始的资源 id_starts_with: "1", # 满足 id 值不以 1 为起始的资源 id_not_starts_with: "1", # 满足 id 值以 1 为结尾的资源 id_ends_with: "1", # 满足 id 值不以 1 为结尾 的资源 id_not_ends_with: "1" } ``` 下文将对`after, before,first, last, skip`查询条件进行进一步说明。 #### first ![first](https://i.imgur.com/O1Jj3Z2.png=x120) ``` graphql # 获取资源中的前三条记录 { first: 3 } ``` #### first + skip ![first-and-skip](https://i.imgur.com/PpI5X0X.png=x120) ```graphql # 获取第 5 条记录之后的前 5 条记录,即第 6 ~ 10 条记录 { skip: 5 first: 5 } ``` #### last ![last](https://i.imgur.com/pkuYCrV.png=x120) ```graphql # 获取资源的后三条记录 { last: 3 } ``` #### last + skip ![last-and-skip](https://i.imgur.com/iSl9Y07.png=x120) ```graphql # 获取倒数第 3 条记录之前的 7 条记录,即倒数第 4 ~ 10 条记录 { last: 7, skip: 3 } ``` #### after + skip ![after-first](https://i.imgur.com/InYSSkQ.png=x120) ```graphql # 获取 id 为 cixnen24p33lo0143bexvr52n 的记录之后的前 3 条记录 { after: "cixnen24p33lo0143bexvr52n", first: 3 } ``` #### after + skip + first ![after-skip-first](https://i.imgur.com/u4WEAJv.png=x120) ```graphql # 获取 id 为 cixnen24p33lo0143bexvr52n 的记录之后的第 3 条资源之后的前 5 条记录,即第 4~8 条记录 { first: 5, after: 'cixnen24p33lo0143bexvr52n', skip: 3, } ``` #### last + before ![before-last](https://i.imgur.com/306eghw.png=x120) ```graphql # 获取 id 为 cixnen24p33lo0143bexvr52n 的记录之前的 5 条记录 { last: 5, before: 'cixnen24p33lo0143bexvr52n', } ``` #### last + skip + before ![before-skip-last](https://i.imgur.com/iZGUiHJ.png=x120) ```graphql # 获取 id 为 cixnen24p33lo0143bexvr52n 的记录之前的倒数第 5 条资源之前的 3 条记录,即倒数第 6 ~ 9 条记录 { last: 3, before: 'cixnen24p33lo0143bexvr52n', skip: 5, } ``` ### 返回值 #### 查询类请求 CloudTower API 中的查询请求是支持批量查询的,统一返回该资源类型的数组。 例如,查询虚拟机,调用 `/get-vms` API,获取到的参数均为 `Vm[]` 类型。即使查询请求中,带有唯一值参数 `{where: { id: "VM_ID" }}` 这样的筛选条件,也返回 `Vm[]` 类型。 #### 更新与创建类请求 CloudTower API 中的更新与创建请求同样是支持批量操作的,统一返回该资源类型的数组。 以虚拟机为例,创建虚拟机,调用 `/create-vm` API ,获取到的参数类型为 `Vm[]`。 同样的,以更新虚拟机为例,调用 `/update-vm-basic-info` 或更新虚拟机电源状态, 例如开机 `/start-vm`,关机 `/shutdown-vm`, 重启 `restart-vm` 等涉及到更新虚拟机相关字段的请求,获取到的参数类型均为 `Vm[]`。 > 注意,CloudTower API 中的更新与创建类操作均会触发异步任务,如果需要对这些字段进行查询或其它操作,请在异步任务完成之后通过 id 再次进行操作。(详见通用指南下的异步任务说明)。 #### 删除类请求 CloudTower API 中删除请求同样是支持批量操作的,统一返回被删除资源的 ID 数组,即 `{id: string}[]`。 以虚拟机为例,删除虚拟机,调用 `/delete-vm` API, 获取到的参数类型为 `{id: string}[]`。 ### 异步任务 在管理资源时,CloudTower 一般会将实际的操作过程放入异步任务中执行。因此当一次涉及创建、删除、修改的 API 返回结果后,其对应的实际操作可能还在执行中,调用者需要通过查询对应的异步任务状态来获得后续更新。 为了保持 API 调用的简洁与一致,此类 API 会将其产生的异步任务 id 以 `{ task_id: string }` 的参数返回。调用者在获得 `task_id` 后可以进一步通过 `/get-tasks` 查询异步任务的状态和结果。具体参数类型请查看 `任务中心` 相关的 API介绍。 需要注意的是,在资源执行异步任务时,只有资源的 id 是可信稳定的,资源的其他字段及其关联资源的字段都有可能在异步任务中进行更改。如果需要对这些字段进行查询或其它操作,请在异步任务完成之后通过 id 再次进行操作。 ## API 详解 > 注:下文所给出的返回值示例均是调用 `成功`(HTTP code 为 200) 时的结果。 ### 登录 /login | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **username** | string | 是 | 用户名| | **password** | string | 是| 密码| | **source** | string | 是| 当前版本请默认使用 "LOCAL",后续版本将增加 LDAP 认证| ### 虚拟机生命周期管理 #### 创建空白虚拟机 /create-vm 在实际使用场景中,因还需为虚拟机安装操作系统、进行必要的配置后才可正常使用,因此自动化程序较多调用 `从模板创建虚拟机` API。 > 注:虚拟机模板可由虚拟机转化而来。 | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **name** | string | 是 | 名称| | **cluster_id** | string | 是| 所属集群 ID| | **vcpu** |integer | 是| vCPU 个数| | **cpu_cores** | integer | 是 | vCPU 每插槽的核数,请确保 cpu_cores * cpu_sockets = vcpu| | **cpu_sockets** | integer | 是| vCPU 插槽数,设定后不可变更,请确保 cpu_cores * cpu_sockets = vcpu| | **memory** | double | 是| 内存的 byte 数,即 1GiB 时,取值为 1073741824| | **ha** | boolean | 是 | 是否开启高可用| | **status** | string | 是| "RUNNING"会在创建完自动开机,"STOPPED"则在创建完保持关机状态| | **vm_nics** | object | 是| 虚拟网卡,详见对应资源的 API| | **vm_disks** | object | 是 | 虚拟磁盘,详见对应资源的 API| | **firmware** | string | 是| 固件信息,"BIOS" 或 "UEFI"| | **io_policy** | string | 否| 对虚拟机整体或虚拟盘单独进行限速,取值为 "RESTRICT_WHOLE_VM" 或 "RESTRICT_EACH_DISK"| | **max_iops** | integer | 否 | 对虚拟机整体限速时的最大 IOPS 值| | **max_bandwidth** | double | 否| 对虚拟机整体限速时的最大带宽,bytes/s| | **max_iops_policy** | string | 否| 限速模式,"DYNAMIC" 或 "FORCED"| | **max_bandwidth_policy** | string | 否 | 限速模式,"DYNAMIC" 或 "FORCED"| | **guest_os_type** | string | 否| 操作系统类型,"LINUX"、"WINDOWS"、"UNKNOWN"| | **folder_id** | string | 否|所属分组 ID| | **description** | string | 否 | 描述| | **host_id** | string | 否| 目标运行主机,为空则由系统自动调度| #### 从模板创建虚拟机 /create-vm-from-template | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **template_id** | string | 是| 虚拟机模板 ID| | **name** | string | 是 | 名称| | **is_full_copy** | boolean | 是| 是否全拷贝克隆,是则完整复制所有虚拟磁盘数据,否则采用 COW 秒级克隆| > 注:其它字段同 `创建空白虚拟机`,但均为非必须。 #### 启动虚拟机 /start-vm | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **where** | object | 是| 虚拟机 ID| | **data** | object | 否 | 虚拟机启动后所运行的目标主机,为空则为自动调度| 参数示例 ```json= { "data": { "host_id": "ckxzj98m594xh0758p70z1812" }, "where": { "id": "ckxzj98m594xh0758p70z1812", "id_in": [ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] } } ``` #### 强制关闭虚拟机(下电) /poweroff-vm | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟机 ID| 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812", "id_in": [ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] } } ``` #### 关闭虚拟机 /shutdown-vm | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟机 ID| 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812", "id_in": [ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] } } ``` #### 重启虚拟机 /restart-vm | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟机 ID| 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812", "id_in": [ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] } } ``` #### 暂停虚拟机 /suspend-vm | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟机 ID| 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812", "id_in": [ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] } } ``` #### 恢复虚拟机 /resume-vm | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟机 ID| 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812", "id_in": [ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] } } ``` #### 迁移虚拟机 /migrate-vm | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 待迁移虚拟机的 ID| | **data** | object | 否 | 目标主机,为空则为自动调度| 参数示例 ```json= { "data": { "host_id": "ckxzj98m594xh0758p70z1812" }, "where": { "id": "ckxzj98m594xh0758p70z1812", "id_in": [ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] } } ``` #### 删除虚拟机 /delete-vm | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟机 ID| 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812", "id_in": [ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] } } ``` #### 扩容虚拟机 /update-vm-basic-info | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟机 ID | | **data** | object | 是| 扩容后的虚拟机资源分配| 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812", "data": { "vcpu": 4, "memory": 8589934592 } } } ``` ### 快照操作 #### 创建快照 /create-vm-snapshot | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **vm_id** | string | 是| 虚拟机 ID | | **name** | string | 是| 快照名称| | **consistent_type** | string | 是| 快照类型,崩溃一致性 CRASH_CONSISTENT 或文件系统一致性 FILE_SYSTEM_CONSISTENT| 参数示例 ```json= { "data": [{ "consistent_type": "CRASH_CONSISTENT", "name": "sampe snapshot", "vm_id": "ckxzj98m594xh0758p70z1812", }] } ``` #### 删除虚拟机快照 /delete-vm-snapshot | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 快照 ID| 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812", "id_in": [ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] } } ``` #### 根据 ID 获取快照或快照列表 /get-vm-snapshots | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 快照 ID| 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812", "id_in": [ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] } } ``` #### 查询虚拟机当前快照 /get-vms | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟机 ID| 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812" } } ``` 虚拟机快照信息可从返回数据中的 `snapshots` 字段获取。 ### 虚拟磁盘操作 #### 挂载 CD-ROM /add-vm-cd-rom | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **elf_image_id** | string | 是| ISO 映像的 ID | | **key** | integer | 否| CD-ROM 的标识| | **boot** | integer | 是| 启动顺序| | **id** | string | 是| 虚拟机 ID | 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812" }, "data": { "vm_cd_roms": [ { "elf_image_id": "ckxzj98m594xh0758p70z1812", "key": "unique_id", "boot": 1 } ] } } ``` #### 卸载 CD-ROM /remove-vm-cd-rom | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **cd_rom_ids** | string[] | 是| CD-ROM ID | | **id** | string | 是| 虚拟机 ID | 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812" }, "data": { "cd_rom_ids": [ "ckxzj98m594xh0758p70z1812", "ckxzj98m594xh0758p70z1812" ] } } ``` #### 获取 ISO 映像列表 /get-elf-images | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| ISO 映像所属集群的 ID | 参数示例 ```json= { "where": { "cluster": { "id": "ckxzj98m594xh0758p70z1812" } } } ``` #### 获取 ISO 映像列表 /get-elf-images | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| ISO 映像所属集群的 ID | 参数示例 ```json= { "where": { "cluster": { "id": "ckxzj98m594xh0758p70z1812" } } } ``` #### 创建虚拟磁盘 /create-vm-volume | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **elf_storage_policy** | string | 是| 存储策略,指定副本数和置备类型,REPLICA_2_THIN_PROVISION : 2 副本,精简置备,REPLICA_3_THIN_PROVISION : 3 副本,精简置备,REPLICA_2_THICK_PROVISION : 2 副本,厚置备,REPLICA_3_THICK_PROVISION : 3 副本,厚置备 | | **size** | double | 是| 大小,取 byte 数 | | **sharing** | boolean | 是| 是否共享卷 | | **cluster_id** | string | 是| 所属集群 ID | | **name** | string | 是| 名称 | 参数示例 ```json= [ { "elf_storage_policy": "REPLICA_2_THICK_PROVISION", "size": 0, "sharing": false, "cluster_id": "ckxzj98m594xh0758p70z1812", "name": "sample volume" } ] ``` #### 获取虚拟磁盘列表 /get-vm-volumes | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 所属集群 ID | 参数示例 ```json= { "where": { "cluster_id": "ckxzj98m594xh0758p70z1812" } } ``` #### 删除虚拟磁盘 /delete-vm-volume | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟磁盘 ID | 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812", "id_in": [ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] } } ``` #### 挂载虚拟磁盘 /add-vm-volume | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟机 ID | | **vm_disks** | object | 是| 要挂载的虚拟磁盘,可挂载已有虚拟盘 mount_disks, 或新创建虚拟盘并挂载 mount_new_create_disks | 参数示例 ```json= { "where": { "id": "ckxzj98m594xh0758p70z1812", "data": { "vm_disks":{ "mount_disks":{ "vm_volume_id":"ckxzj98m594xh0758p70z1812", "bus": "VIRTIO", "boot":1 }, "mount_new_create_disks":{ "vm_volume":{ "name": "new-disk-name", "size": 8589934592, "elf_storage_policy":"REPLICA_2_THIN_PROVISION" }, "bus": "SCSI", "boot": 2 } } } } ``` #### 卸载虚拟磁盘 /remove-vm-volume | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟机 ID | | **disk_ids** | string[] | 是| 虚拟磁盘 ID | 参数示例 ```json= { "data":{ "disk_ids":[ "ckxzj98m594xh0758p70z1812","ckxzj98m594xh0758p70z1812" ] }, "where": { "id": "ckxzj98m594xh0758p70z1812" } } ``` ### 物理机操作 #### 获取物理机列表 /get-hosts 参数示例 > 示例中依次对应集群下的所有物理机、数据中心下的物理机。当不指定任何 where 条件时,返回所有物理机 ```json= { "where":{ "cluster": { "id": "ckxzj98m594xh0758p70z1812" }, "cluster":{ "datacenters_some": { "id":"ckxzj98m594xh0758p70z1812" } } } } ``` ### 集群操作 #### 获取集群列表 /get-clusters ### 虚拟机模板操作 在 SMTX OS 中,可以通过模板来批量创建虚拟机。 #### 查询模板 /get-vm-templates 参数示例 ```json= { "where":{ "id": "ckxzj98m594xh0758p70z1812" } } ``` ### 虚拟网络 #### 创建虚拟网络 /create-vm-vlan | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **vds_id** | string | 是| 虚拟交换机 ID | | **vlan_id** | string[] | 是| VLAN ID | | **name** | string | 是| 虚拟网络名称 | 参数示例 ```json= [ { "vds_id": "ckxzj98m594xh0758p70z1812", "vlan_id": 0, "name": "sample name" } ] ``` #### 删除虚拟网络 /delete-vm-vlan | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **id** | string | 是| 虚拟网络 ID | 参数示例 ```json= { "where":{ "id": "ckxzj98m594xh0758p70z1812" } } ``` #### 查询虚拟网络 /get-vlans #### 编辑虚拟网络 /update-vm-vlan | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **vlan_id** | string | 否| VLAN ID | | **name** | string | 否| 虚拟网络名称 | | **id** | string | 是| 虚拟机 ID| 参数示例 ```json= { "data":{ "vlan_id":"ckxzj98m594xh0758p70z1812", "name": "new name" }, "where": { "id": "ckxzj98m594xh0758p70z1812" } } ``` ### 虚拟网卡 #### 为虚拟机增加虚拟网卡 /add-vm-nic | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **connect_vlan_id** | string | 是| VLAN ID | | **id** | string | 是| 虚拟机 ID | 参数示例 ```json= { "data":{ "vm_nics": { "connect_vlan_id":"ckxzj98m594xh0758p70z1812" } }, "where": { "id": "ckxzj98m594xh0758p70z1812" } } ``` #### 为虚拟机移除虚拟网卡 /remove-vm-nic | 字段名 | 类型 | 是否必需 | 说明 | | ----- | :---: | :-------: | --- | | **connect_vlan_id** | string | 是| VLAN ID | | **id** | string | 是| 虚拟机 ID | 参数示例 ```json= { "data":{ "vm_nics": { "connect_vlan_id":"ckxzj98m594xh0758p70z1812" } }, "where": { "id": "ckxzj98m594xh0758p70z1812" } } ``` ### 事件 #### 获取系统事件 /get-system-audit-logs #### 获取用户事件 /get-user-audit-logs ### 通过 VNC 控制台访问虚拟机 1. 首先要获得 VNC 信息,示例请求如下: POST /api ```json= { "operationName": "vnc", "variables": { "input": { "vm": { "id": $VM_ID } } }, "query": "query vnc($input: VncInput) { vnc(input: $input) { vm_uuid host_ip cluster_ip raw_token }}" } ``` > 其中 $VM_ID 为虚拟机 ID 该请求的返回示例如下: ```json= { "data": { "vnc": { "vm_uuid": "75d75e5d-88df-4f16-bfce-230974bba855", "host_ip": "192.168.17.102", "cluster_ip": "192.168.17.97", "raw_token": "8dcad47b9b084be6b97903e805b38386" } } } ``` 2.可以使用 [novnc](https://novnc.com/info.html) 来访问虚拟机控制台,核心是将代码中的 `url`修改为对应的 VNC URL: ```javascript= rfb = new RFB(document.getElementById('screen'), url, { credentials: { password: password } }); ``` CloudTower VNC API URL 是 `wss://$cluster_ip/websockify/?uuid=$vm_uuid&token=$raw_token&host=$host_ip` VNC 页面的示例代码如下: ```htmlembedded= <!DOCTYPE html> <html lang="en"> <head> <!-- noVNC example: lightweight example using minimal UI and features This is a self-contained file which doesn't import WebUtil or external CSS. Copyright (C) 2019 The noVNC Authors noVNC is licensed under the MPL 2.0 (see LICENSE.txt) This file is licensed under the 2-Clause BSD license (see LICENSE.txt). Connect parameters are provided in query string: http://example.com/?host=HOST&port=PORT&scale=true --> <title>noVNC</title> <meta charset="utf-8"> <style> body { margin: 0; background-color: dimgrey; height: 100%; display: flex; flex-direction: column; } html { height: 100%; } #top_bar { background-color: #6e84a3; color: white; font: bold 12px Helvetica; padding: 6px 5px 4px 5px; border-bottom: 1px outset; } #status { text-align: center; } #sendCtrlAltDelButton { position: fixed; top: 0px; right: 0px; border: 1px outset; padding: 5px 5px 4px 5px; cursor: pointer; } #screen { flex: 1; /* fill remaining space */ overflow: hidden; } </style> <script type="module" crossorigin="anonymous"> // RFB holds the API to connect and communicate with a VNC server import RFB from './core/rfb.js'; let rfb; let desktopName; // When this function is called we have // successfully connected to a server function connectedToServer(e) { status("Connected to " + desktopName); } // This function is called when we are disconnected function disconnectedFromServer(e) { if (e.detail.clean) { status("Disconnected"); } else { status("Something went wrong, connection is closed"); } } // When this function is called, the server requires // credentials to authenticate function credentialsAreRequired(e) { const password = prompt("Password Required:"); rfb.sendCredentials({ password: password }); } // When this function is called we have received // a desktop name from the server function updateDesktopName(e) { desktopName = e.detail.name; } // Since most operating systems will catch Ctrl+Alt+Del // before they get a chance to be intercepted by the browser, // we provide a way to emulate this key sequence. function sendCtrlAltDel() { rfb.sendCtrlAltDel(); return false; } // Show a status text in the top bar function status(text) { document.getElementById('status').textContent = text; } // This function extracts the value of one variable from the // query string. If the variable isn't defined in the URL // it returns the default value instead. function readQueryVariable(name, defaultValue) { // A URL with a query parameter can look like this (But will most probably get logged on the http server): // https://www.example.com?myqueryparam=myvalue // // For privacy (Using a hastag #, the parameters will not be sent to the server) // the url can be requested in the following way: // https://www.example.com#myqueryparam=myvalue&password=secreatvalue // // Even Mixing public and non public parameters will work: // https://www.example.com?nonsecretparam=example.com#password=secreatvalue // // Note that we use location.href instead of location.search // because Firefox < 53 has a bug w.r.t location.search const re = new RegExp('.*[?&]' + name + '=([^&#]*)'), match = ''.concat(document.location.href, window.location.hash).match(re); if (match) { // We have to decode the URL since want the cleartext value return decodeURIComponent(match[1]); } return defaultValue; } document.getElementById('sendCtrlAltDelButton') .onclick = sendCtrlAltDel; // Read parameters specified in the URL query string // By default, use the host and port of server that served this file const host = readQueryVariable('host', window.location.hostname); let port = readQueryVariable('port', window.location.port); const password = readQueryVariable('password'); const path = readQueryVariable('path', 'websockify'); // | | | | | | // | | | Connect | | | // v v v v v v status("Connecting"); // Build the websocket URL used to connect let url; if (window.location.protocol === "https:") { url = 'wss'; } else { url = 'ws'; } url += '://' + host; if(port) { url += ':' + port; } url += '/' + path; // Creating a new RFB object will start a new connection rfb = new RFB(document.getElementById('screen'), url, { credentials: { password: password } }); // Add listeners to important events from the RFB module rfb.addEventListener("connect", connectedToServer); rfb.addEventListener("disconnect", disconnectedFromServer); rfb.addEventListener("credentialsrequired", credentialsAreRequired); rfb.addEventListener("desktopname", updateDesktopName); // Set parameters that can be changed on an active connection rfb.viewOnly = readQueryVariable('view_only', false); rfb.scaleViewport = readQueryVariable('scale', false); </script> </head> <body> <div id="top_bar"> <div id="status">Loading</div> <div id="sendCtrlAltDelButton">Send CtrlAltDel</div> </div> <div id="screen"> <!-- This is where the remote screen will appear --> </div> </body> </html> ```