因为 XHERE 的 Auth 方式是使用 API Key,需要在 HTTP 请求的 Header 设置,参数名是 x-sddc-token ,token 值可以通过两种方法获取:
通过 SSH 登录已安装 XHERE 的主节点,然后使用命令行创建获取 Raw Token。
在 XHERE 管理平台中的 Access Token 界面中创建得到,此方法需要 V2.2 版本才支持。
可以在 XHERE 管理面上的“全局设置”-“访问令牌”页面中,可以生成访问令牌。
获得 token 之后,可以使用 curl 命令,向 XHERE 管理平台发起 HTTP 请求 GET /overview/ 。假设 XHERE 集群的主节点是 10.16.11.91 。
# curl --location --request \
GET 'http://10.16.11.91:6012/sddc/v1/overview/' \
--header 'x-sddc-token: sddc-***'
得到响应如下。
{
"stats": {
"vm_num": 5,
"vm_running_num": 5,
"vm_stopped_num": 0,
"vm_running_cpu_num": 28,
"vm_available_cpu_num": 24,
"vm_running_memory_mb": 20480,
"vm_available_memory_mb": 114688,
"vm_disk_num": 6,
"vm_nic_num": 7,
"vm_image_num": 4,
"vm_snap_num": 1,
"bs_volume_num": 11,
"node_num": 2,
"node_cpu_num": 32,
"node_memory_mb": 131072
}
可以使用 Swagger/Postman/Apifox 工具导入本产品的 OpenAPI Json 文件。
本产品的 OpenAPI/Swagger 数据 URL 地址是:
SDDC API: http://{XHERE管理节点IP地址}/docs/sddc_openapi.json
SDS API: http://{XHERE管理节点IP地址}/docs/sds_openapi.json
可以在 XHERE 产品中各个资源的审计日志中看到“非 GET 的 API 请求”,可以点击【请求内容】,查看具体 API 请求的参数内容。因此可以通过查看操作路径和请求内容,在情景中快速学习到此 API 请求的具体用法,方便第三方系统对接。
XHERE 的 API Auth 方式是使用 API Key,需要在 HTTP 请求的 Header 设置,参数名是 x-sddc-token ,token 值可以通过两种方法获取:
通过 SSH 登录已安装 XHERE 的主节点,然后使用命令行创建获取 Raw Token。
在 XHERE 管理平台中的 Access Token 界面中创建得到,此方法需要 V2.2 版本才支持。
这里介绍下使用命令行方式创建获取 Raw Token,假设 XHERE 集群的主节点是 10.16.11.91,通过 SSH 登录此节点,然后通过 sddc-cli user login -u admin -p {password} 获得 Raw Token。注意 Raw Token 的默认生效时间是 1 小时。
➜ ~ ssh root@10.16.11.91
Last login: Tue Nov 8 00:12:03 2022 from 10.3.0.205
[root@node-91 ~]#
[root@node-91 ~]# sddc-cli user login -u admin -p admin
******
[root@node-91 ~]#
XHERE API 中对敏感字段需要进行加密。对涉及敏感数据的接口调用,需要做如下处理:
用户登录操作需要发送2个请求
样例:
POST /sddc/rsa-keys
Response Body
{
"data": {
"rsaKeyCreateOne": {
"expiration": "2022-02-10T14:13:48.165748404+08:00",
"publicKey": "MIGJAoGBAORO/+TrJSnoaR2bXlf4SqYr7EXiUjxnDAl1oOvPaC3RuiB/Fjx+rsAnDWoDS195d6jyd2V//m3KN7m8s+/c6kKtInC8inAjzhRJHlBO0j11bOxIH1ynD/Dn866bBPCGJiY8rWx3+cjnQfbQ+rG9qJFExPzlgJhip5baWYLkh3b/AgMBAAE=",
"uuid": "f494dd2184a14551ab96ed04412edb98",
"__typename": "RsaKey"
}
}
}
样例:
POST /sddc/tokens/:login
Request Body
{"data":{"name":"admin","password":"GTEDpzHHbuhvk1uZE8eexuPSeDLb1H52xOhuh7XfaRYEzr5vEuJPIFeAtRAShlL/FCaRuaNIG5AFZLEbOgOKqhdXZGazhqGQ1g/51ZCFwQkfLllxulJpesXfgRbQAXjN/cXkq9J7CctiK07hgwYn1IgsfK/2PSfvGdLZQ+Or8Fc=","rsaKeyUuid":"f494dd2184a14551ab96ed04412edb98"}}
Response Header
set-cookie: X_SDDC_TOKEN=******; max-age=3600; Path=/;
Response Body
{"data":{"tokenLogin":{"data":{"id":4,"name":"","etag":"6c3057ef-54fe-40b4-a222-076b561910bc","uuid":"d8ac595019894363a6f1350f0df80d61","valid":true,"__typename":"TokenData"},"__typename":"Token"}}}
| 接口名称 | URI | 敏感字段 |
|---|---|---|
| 用户登录 | POST /sddc/tokens/:login | UserData.Password |
| 创建用户 | POST /sddc/users/ | UserData.Password |
| 更新用户 | PATCH /sddc/users/{id} | UserData.Password |
| 验证用户密码 | POST /sddc/users/:verify-password | UserData.Password |
| 创建email服务器配置 | POST /sddc/email-configs/ | EmailConfigData.Password |
| 更新email服务器配置 | PATCH /sddc/email-configs/{id} | EmailConfigData.Password |
| 创建虚拟机 | POST /sddc/virtual-machines/ | VirtualMachineSpec.GraphicsPassword |
| VirtualMachineSpec.OsUserPassword | ||
| 更新虚拟机 | PATCH /sddc/virtual-machines/{id} | VirtualMachineSpec.GraphicsPassword |
import requests
import base64
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
api = "http://localhost:6012/sddc/v1"
user_api = f"{api}/users"
rsa_key_api = f"{api}/rsa-keys"
login_api = f"{api}/tokens/:login"
def get_rsa_key():
resp = requests.post(rsa_key_api, headers={"content-type": "application/json"})
return resp.json()
def login(name, password, rsa_key):
uuid = rsa_key["uuid"]
key = base64.b64decode(rsa_key["public_key"])
key = serialization.load_der_public_key(key)
crypted = key.encrypt(password.encode(), padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
encrypted_passwd = base64.b64encode(crypted).decode()
data = {"data": {"name": name, "password": encrypted_passwd, "rsa_key_uuid": uuid}}
resp = requests.post(login_api, json=data)
return resp.json()
rsa_key = get_rsa_key()
token = login("admin", "admin", rsa_key)
print(token["data"]["uuid"])
Kubernetes 流行的一个原因是采用了声明式 API。声明式 API 是一套基于期望状态的系统,用户通过向该系统提出一个期望状态来达成特定的目的。使用声明式 API,用户不需要告诉该系统具体要执行哪些步骤,只需要告诉该系统:我希望哪个资源处于哪种状态。
XHERE 产品从设计开始就采用声明式 API,声明式 API 可以对用户屏蔽系统实现的复杂性,提升 API 的使用体验,有助于实现 IaC。但是,对于一套复杂系统来说,无差别的使用声明式 API 可能会带来不必要的复杂性,因此 XHERE 也在必要的地方使用命令式 API。
在 SDDC 平台中,我们以 resource 的粒度来管理业务。Resource 可能是虚拟机,存储,网络等硬件资源,也可能是告警,角色等平台所需的软件虚拟资源。SDDC 会对外暴露 REST API 来管理这些资源。
术语
Model Layout 是指我们如何为一个 resource 设计它的 model 使用方式。
ResourceType 是指 SDDC 中管理的某种资源,例如 VirtualMachine, VmDisk, AlertRule, Alert 等。
Resource 则是指单个资源
ResourceList 是指某个资源类型的列表
Resource 是个抽象的概念,包含一个资源的全部信息。Resource 分为两个部分:metadata and data:
Metadata:包含可以用于定位这个资源的相关信息,以及可以确定这个资源整体状态的相关信息。这些信息本身都是存在 data 中的某些对象里,只是构建 resource 的过程中被提取出来放到 metadata 中。
Data:包含实际存放在数据库中的信息。一个资源的 data 部分可能会存在多个表中,这样的一个表我们称为一个 object。data 部分中的 object 在 resource 中如何摆放,属于下文要提到的 model layout 讨论的范畴。
所以,一个 resource 的结构示例如下:
{
"metadata": {},
"object1": {},
"object2": {}
}
ResourceList 就是由 resource 组成的列表。ResourceList 分为两个部分:metadata and items:
Metadata:包含这个列表相关的信息,比如分页信息 Pagination 等。
Items:包含这个列表的元素。其中每个元素都是一个 resource。
一个 ResourceList 的结构示例如下:
{
"metadata": {
"pagination": {}
},
"items": [
{},
{}
]
}
在 SDDC 中,一开始就要在资源的数据中表达出资源的当前状态和期望状态。但是,有些资源并不需要区分当前状态和期望状态,例如 alert,事件日志等,这些资源,我们也需要设计一个适合的 model 形式来表达。
这样,我们就有两种 model 表达需求:
区分当前状态和期望状态
不区分当前状态和期望状态(当前状态 = 期望状态)
不同的表达需求,对应不同的 model 设计形式,这种设计形式,我们称为 layout,即每种表达形式都有自己的 model layout。Model layout 描述的是 Resource 的 data 部分的结构。
这种类型的 model 分为 3 个部分:
Metadata
Spec:包含用户对于资源的期望状态
Status:包含资源的当前状态
{
"metadata": {},
"spec": {},
"status": {}
}
无论是 GET 一个资源,或者 LIST 多个资源,都是以资源的 Spec 表为准。
当用户要创建一个资源时,提交的是 Spec。
对于新建的资源,Spec.CreationFinish = nil,表示资源创建还未完成;当资源创建完成后,这个值就会被设置为一个有效的时间。
我们说用户要修改一个资源,指的是 修改这个资源的期望状态,即修改 Spec。
我们会通过 update-to-deleted 来实现删除操作,避免直接删除。
但是当用户通过 API 发起一个删除请求时,资源是异步删除的(Spec/Stats layout 一定是这样的),所以,在删除完成之前,用户还要能够从 API 里读取到这个资源。我们使用如下实现方案。
Spec 表增加两个字段:DeletionBegin 和 DeletionFinish。这两个字段都是时间类型,允许为空,非空表示设置过。
API 请求删除一个资源时,设置 Spec.DeletionBegin 字段,表示这个资源开始要被删除。
当系统后台执行完资源的实际删除操作后,设置 Spec.DeletionFinish。
系统后台发现 Spec.DeletionFinish 已经被设置,则设置 deleted_at 字段的值。这个字段执行过后,API 无法再查询到这个记录,虽然它还在数据库中。
| Propety | Required | Description |
|---|---|---|
| id or uuid | yes. 这两个二选一 | 资源 id |
| name | 资源名称 | |
| created_at | yes | 资源的创建时间 |
| creation_finish | 资源创建完成的时间 | |
| deletion_begin | 资源被标记为删除的时间 | |
| deletion_finish | 资源删除操作执行完成的时间。注意,不是从数据库中删除的时间。 |
根据 metadata,可以得到一个资源的状态:
| 条件 | 状态 |
|---|---|
| created_at != nil AND creation_finish == nil | 创建中 |
| created_at != nil AND creation_finish != nil | 创建完成 |
| deletion_begin != nil AND deletion_finish == nil | 删除中 |
| deletion_begin != nil AND deletion_finish != nil | 删除操作完成 |
| deletion_begin != nil AND deletion_finish != nil | ORM 已删除。 |
| Row deleted | Deleted from database |
可能在回收站中
AND deleted_at != nil
Soft deleted, wait GC
这种类型的 model 分为两个部分:
Metadata
Data:resource 的 data 部分不再进行细分。
包含资源的数据表的相关内容。
这种类型的 model 在 API 操作是比较直接的,所有的操作都作用于 data 字段,data 字段只是为了规范 model layout 而增加的一层抽象。
这个 layout 适用于最简单的资源表达方式:资源只需要一张数据库表,每个资源对应这个表的一行。
直接调用 model 层封装的 list 接口即可。
无论是 GET 一个资源,或者 LIST 多个资源,都是以资源的 Spec 表为准。
当用户要创建一个资源的时候,提交的是一个 data。
如果是用户提出修改请求,可以通过 PUT,PATCH 等请求提交一个 data。
通过 API 的 delete 请求实现。
每个 resource 都具有如下的元数据:
| Propety | Required | Description |
|---|---|---|
| id or uuid | yes. 这两个二选一 | 资源 id |
| name | 资源名称 | |
| created_at | yes | 资源的创建时间 |
根据 metadata,可以得到一个资源的状态:
| 条件 | 状态 |
|---|---|
| created_at != nil | 创建完成 |
| deletion_begin != nil AND deletion_finish != nil | ORM 已删除。 |
| Row deleted | Deleted from database |
AND deleted_at != nil
Soft deleted, wait GC
我们的 API 是 REST ( Representational state transfer) 风格的,我们设计的 API 在路径、动词的使用、以及返回结果上都需要尽量满足 REST 标准,并在 REST 标准之外提供一些灵活性(这里最主要是指 POST action 这种操作,见下文描述)
XHERE 包括两种类型的 API,本文只讨论 SDDC 类型。XHERE API 监听端口是 6012。
SDDC API: http://{XHERE管理节点IP地址}:6012/sddc/v1/
SDDC Sample API:
SDS API: http://{XHERE管理节点IP地址:6012/sds/v1/
SDS Sample API:
这个 PATH 的完整结构如下:
/sddc/v1/{resource-type}/{uuid|id}[:action-name]?[&queries]
参数解释:
resource_type : 我们的资源类型的路径形式,例如 virtual-machiens, vm-disks 等。
uuid | id : 资源的标识符。
action=action_name : 表示一个 action,具体使用方法见下文。
其他 query 参数。
API 请求和响应的 body 都是 JSON object。在 Body 形式上,在 SDDC 中,我们将 Resource 或者 ResouceList 的 JSON 格式直接返回。
API 请求时的 body 包含的内容根据 model layout 的不同而不同,但是都是直接包含对应的 object 对象。与 SDS 的对比如下:
SDS API
// 相关数据会放在一个资源名称(单数)的子 object 下。
POST /sds/v1/osds
{
"osd": {
"disk_id": 1
}
}
SDDC API
以 Spec/Status layout 举例:
// 相关数据直接作为 resource object
POST /sddc/v1/virtual-machines
{
"spec": {}
}
响应内容的对比如下:
SDS 的 API
// 获取单个资源,相关数据会放在一个资源名称(单数)的子 object 下。
GET /sds/v1/osds/1/
{
"osd": {
"id"
}
}
// 获取列表,相关数据会放在一个资源名称(复数)的子 object 下,同时还有一个 paging 的子 object 返回分页相关的信息。
GET /sds/v1/osds
{
"osds": [
{
"id": 1
},
{
"id": 2
}
],
"paging": {
}
}
SDDC 的 API
以 Spec/Status layout 举例:
// 获取单个资源,相关数据直接作为返回的 JSON object。
GET /sddc/v1/virtual-machines/{uuid}
{
"metadata": {
"uuid": "uuid1"
},
"spec": {},
"status": {}
}
// 获取列表,会有一个统一的列表数据返回格式作为返回的 JSON object。
GET /sddc/v1/virtual-machines
{
"metadata": {
"pagination": {}
},
"items": [
{
"metadata": {
"uuid": "uuid1"
},
"spec": {},
"status": {]
},
{
"metadata": {
"uuid": "uuid2"
},
"spec": {},
"status": {]
}
]
}
总的来说,API 有两种响应形式:
单个资源
单个资源主要是返回资源类型所定义的 model 数据,根据 model layout 来返回。
多个资源
其返回格式是全局定义的,见下文。
响应的 body 定义如下:
{
"metadata": {
"pagination": {}
},
"items": [
{ /* item1 */ },
{ /* item2 */ }
]
}
每个 item 内的数据同单个资源的响应,与单独 get 这个资源时获得的数据一致。
我们可以对资源或者资源类型发起一个操作,即 operation。operation 是由 method, path, query, req body 形成的一个组合。
| METHOD | PATH | Query Limit | Req Body Limit | Resp Code | Resp Body | Description |
|---|---|---|---|---|---|---|
| POST | /sddc/v1/{resource-type} | - metadata (optional) | - 201 | Resource | 创建一个资源。 | |
| GET | /sddc/v1/{resource-type}/{uuid, id} | null | - 200 | Resource | 获取一个资源。 | |
| GET | /sddc/v1/{resource-type} | - paging parameters (optional) | null | - 200 | ResourceList | 获取多个资源 |
| PATCH | /sddc/v1/{resource-type}/{uuid, id} | - metadata (optional) | - 200 如果资源没有修改 | Resource | 修改一个资源的期望状态。只能用于修改 spec 中的标量字段,即普通类型字段,不能是 array 类型或者 object 类型的字段。 | |
| DELETE | /sddc/v1/{resource-type}/{uuid, id} | null | - 202 | Resource | 删除一个资源 | |
| POST | /sddc/v1/{resource-type}/{uuid, id}:{action} | - 202 | Resource | 对某个资源执行一个操作。 |
no: 禁止
optional: 可选
must: 必选
spec (must)
status (no)
spec (must)
status (no)
202 如果资源被修改了
Ref: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/
可以用于修改 spec 中的非标量字段,即数组和类型。
如果 action 以 get- 开头,表示这个 API 是只读 API。
| METHOD | PATH | Query Limit | Req Body Limit | Resp Code | Resp Body | Description |
|---|---|---|---|---|---|---|
| POST | /sddc/v1/{resource-type} | - metadata (optional) | - 201 | Resource | 创建一个资源。 | |
| GET | /sddc/v1/{resource-type}/{uuid, id} | null | - 200 | Resource | 获取一个资源。 | |
| GET | /sddc/v1/{resource-type} | - paging parameters (optional) | null | - 200 | Resource List | 获取多个资源 |
| PATCH | /sddc/v1/{resource-type}/{uuid, id} | - metadata (optional) | - 200 | Resource | 修改一个资源的指定字段。 | |
| DELETE | /sddc/v1/{resource-type}/{uuid, id} | null | - 204 | Resource | 获取一个资源 | |
| POST | /sddc/v1/{resource-type}/{uuid, id}:{action} | - 202 | Resource | 对某个资源执行一个操作。 |
no: 禁止
optional: 可选
must: 必选
data (must)
data (must)
如果 action 以 get- 开头,表示这个 API 是只读 API。
所有的 List API 都支持此种类型的分页。API 在处理时,会按照如下的顺序处理请求里的 query 参数:
字段查询参数。
排序参数。
分页参数。
Offset based 分页方式是我们产品里提供的一种遍历所有资源的方式。作为一个基础架构的产品,我们需要让用户在必要的时候可以处理所有的资源,所以必须提供一种遍历所有资源的方式。
| 参数 | 位置 | 合法值 | 默认值 | 说明 |
|---|---|---|---|---|
| offset | query | >= 0 | 0 | |
| limit | query | > 0 | 100 |
分页的返回值存放在 ListMeta 的一个子对象中:
{
"metadata": {
"pagination": {
"offset": {offset},
"limit": {limit},
"count": {count},
"total_count": {total_count}
}
}
}
GET /sddc/v1/compute/virutal-machines?offset=11&limit=10
{
"metadata": {
"pagination": {
"offset": 11,
"limit": 10,
"count": 10,
"total_count": 99
}
},
"items": {
}
}
Venus 针对资源列表 api,实现了通用的查询语法。在 api 查询参数中,通过设置“q”查询参数的值,就可以实现对资源的列表过滤。比如:列出 vm id 为 1 的虚拟机所有的硬盘,示例如下:
GET /sddc/v1/vm-disks/?q=spec.virtual_machine_id:1
查询参数 q 会被解析为一组基本字段条件、逻辑运算符(可选)和括号(可选)组成。
基本字段条件包括四部分:字段名称、":"分隔符、比较运算符(可选)和值。
其中,资源的所以字段都有对应的字段名称,包括如下三种类型:
data.,比如告警的严重等级,对应的字段名称为 data.severity
spec.,比如虚拟机名称,对应的字段名称为 spec.name
status.,比如虚拟机的当前运行状态,对应的字段名称为 status.power_state
比较运算符,包括如下七种类型:
=,等于(默认值)
<>,不等于
,大于
=,大于等于
<,小于
<=,小于等于
~,正则匹配
比如:列出 size_mb 大于 1024 的所有虚拟机硬盘,示例如下:
GET /sddc/v1/vm-disks/?q=spec.size_mb:>1024
特别的,有时我们需要查询字段为空,或者非空的资源,我们引入了"null" 和 "exists"两个特殊字段名称。
GET /sddc/v1/vm-disks/?q=exists:status.virtual_machine_id
GET /sddc/v1/vm-disks/?q=null:status.virtual_machine_id
GET /sddc/v1/virtual-machines/?q=spec.name:~^test
GET /sddc/v1/virtual-machines/?q=spec.name:~test
除了基本的比较运算符以外,我们还基于数据库的 IN 查询语法实现了基于多个值的集合查询。注意,集合查询只支持 =(等于)和 <>(不等于)两种运算符。
示例如下,筛选出操作系统名称为 CentOS 7 或者 Centos 8 的镜像。
GET /sddc/v1/images/?q=spec.os_name:("CentOS 7" "CentOS 8")
筛选出操作系统名称不为 CentOS 7 或者 Centos 8 的镜像。
GET /sddc/v1/images/?q=spec.os_name:<>("CentOS 7" "CentOS 8")
如果值中不包含空格字符,可以省略掉引号,不同值之间只用空格分隔。示例如下,筛选出名称为 image-1 或者 image-3 的镜像。
GET /sddc/v1/images/?q=spec.name:(image-1 image-2)
示例,查询 id 为 1 或者 2 的存储策略。
GET /sddc/v1/bs-policys/?q=data.id:(1 2)
SDS 资源,比如 pool 和 osd,多值查询使用 SDS 的语法;SDDC 资源使用上面所示的 SDDC 语法,不要带 OR。
GET /sds/v1/pools/?q=id:(1 OR 2 OR 3)
对于日期类型,我们目前要求日期格式满足 RFC3339,并通过如下语法实现 range 查询。使用中括号,表示包含边界的 range 查询 [min TO max];使用花括号,表示不包含边界的 range 查询 {min TO max}。* 表示通配符,比如:
GET /sddc/v1/vm-disks/?q=spec.created_at:[2021-01-01T00:00:00.00Z TO 2022-01-01T00:00:00.00Z}
GET /sddc/v1/vm-disks/?q=spec.created_at:[2021-01-01T00:00:00.00Z TO *}
除了基本字段条件以外,通用查询还支持通过使用逻辑运算符和括号,对多个基本字段条件进行组合。逻辑运算符目前支持 AND 和 OR 两种。其中 AND 运算符高于 OR 运算符,比如
GET /sddc/v1/vm-disks/?q=size_mb:>1024 OR spec.virtual_machine_id:1 AND status.virtual_machine_id:1
等同于
GET /sddc/v1/vm-disks/?q=size_mb:>1024 OR (spec.virtual_machine_id:1 AND status.virtual_machine_id:1)
如果查询条件中值包含有空格,则需要使用双引号将值扩起来,比如:
GET /sddc/v1/os-releases/?q=data.name:"Windows 7"
GET /sddc/v1/xxxxxx/?q=spec.yyyy:"aaaa bbb ccc"
在 api 查询参数中,通过设置 "sort" 排序参数的值实现对资源列表的排序,值选项如下:
data.:desc,data.:desc,...... 降序
data.:asc,data.:asc,...... 升序
spec.:desc,spec.:desc,...... 降序
spec.:asc,spec.:asc,...... 升序
data.:desc,data.:asc,...... 混合
spec.:desc,spec.:asc,...... 混合
支持多个字段排序,每一个字段排序可选为 desc、asc,如果没有指定,则默认为 desc 即降序。如果 sort 不设置,则查询结果默认以 id desc 排序。
示例 1,列出虚拟机硬盘,根据 id 降序:
/sddc/v1/vm-disks/?sort=spec.id:desc
示例 2,列出节点,根据名称降序:
/sddc/v1/nodes/?sort=spec.name:desc
声明式 API
SDDC 和 SDS
Access Token
| 分类 | 中文 | 英文 | 所属功能模块 |
|---|---|---|---|
| 集群 | 数据中心 | DataCenter | 拓扑 |
| 机房 | Room | 拓扑 | |
| 机架 | Rack | 拓扑 | |
| 机框 | Chassis | 拓扑 | |
| 节点 | Node | 节点 | |
| 弹性存储 | 物理盘 | Disk | 存储池 |
| 硬盘 | Osd | 硬盘 | |
| 存储池 | Pool | 存储池 | |
| 块存储 | 块存储策略 | BsPolicy | 块存储策略 |
| 块存储卷 | BsVolume | 块存储卷 | |
| 卷快照 | BsSnap | 卷快照 | |
| 网络 | 虚拟交换机 | Vsw | 虚拟交换机 |
| 网卡 | Nic | 虚拟交换机 | |
| 桥接网络 | BrNet | 桥接网络 | |
| 桥接网络命名空间 | BrNetNs | 桥接网络 | |
| 网桥 | Bridge | 桥接网络 | |
| 三层网络 | L3Net | 桥接网络 | |
| 三层网路 IP 区间 | L3NetIpRange | 桥接网络 | |
| 计算 | 镜像 | VmImage | 镜像 |
| 虚拟机 | VirtualMachine | 虚拟机 | |
| 虚拟网卡 | VmNic | 虚拟机 | |
| 虚拟硬盘 | VmDisk | 虚拟机 | |
| 虚拟光盘 | VmCdRom | 虚拟机 | |
| 虚拟机快照 | VmSnap | 虚拟机快照 | |
| 虚拟网卡快照 | VmNicSnap | 虚拟机快照 | |
| 虚拟硬盘快照 | VmDiskSnap | 虚拟机快照 | |
| 虚拟光盘快照 | VmCdRomSnap | 虚拟机快照 | |
| 虚拟机调度规则 | VmSchedulingRule | 虚拟机 | |
| 虚拟机迁移任务 | VmMigrationJob | 虚拟机 | |
| 运维管理 | 告警信息 | Alert | 告警信息 |
| 告警规则 | AlertRule | 告警规则 | |
| 审计日志 | AuditLog | 审计日志 | |
| 告警邮件配置 | EmailConfig | ||
| 事件 | Event | 事件 | |
| 监控指标 | Metric | 监控分析 | |
| 监控视图 | MonitorView | 监控分析 | |
| 监控数据点 | Sample | 监控分析 | |
| 告警信息组 | AlertInfoGroup | 告警信息 | |
| 告警规则组 | AlertRuleGroup | 告警规则 | |
| 操作日志 | ActionLog |
XHERE 的资源有两种 Model Layout(参考“二、API 设计规范”)。但是不论哪种 layout,我们都没有一个专门的字段来表示这个资源的当前状态。这主要是因为我们这套系统,资源的状态很复杂,不适合只使用一个字段来表达资源的当前状态了。但对于用户来说,还是需要知道一个资源的当前状态。
我们先来看看 第一种 Model Layout(Metadata/Spec/Status)的字段的特点:字段的收敛。当我们讨论一个字段是否可以收敛时,说明该字段在 Spec 和 Status 中都存在。
当一个字段在 Spec/Status 的值不同时,有如下两种情况:
| 可收敛字段 | 对于大部分的资源和大部分的字段来说,Spec 和 Status 不一致都是可收敛的。比如虚拟机的 CPU number 这种,系统会在合适的时候进行收敛尝试,直到该差异被收敛为止。 |
|---|---|
| 不可收敛字段 | 某些资源的某些字段,在操作失败后,会处于一个无法继续尝试操作的情况,这种字段,就属于不可收敛的字段,系统不会再进行任何尝试。修复的办法是改变 Spec。 |
对于第二种 Model Layout(Metadata/Data)来说,我们就不用考虑字段差异收敛的问题了。
虽然我们不能将资源的状态存储在一个字段中,但是我们还是可以表达出资源的当前状态。
首先,我们将一个资源的状态进行分类。资源的状态分为两类:
中间状态
稳定状态
不论是哪种 Model Layout 的资源,都含有以下的中间状态:
creating:即资源还未完成创建流程。
deleting:资源正在进行删除流程。
对于 Metadata/Spec/Status,还增加了如下状态:
辅助状态:irreconcilable
对于所有的中间状态,都会包含一个特殊的字段,表示这个资源是否有无法收敛的字段。如果存在无法收敛的字段,那么该资源会一直停在当前状态,知道做出一些操作为止,才有可能发生状态变化。
上述的三个中间状态配合上 irreconcilable 这个辅助状态后,资源的状态解释如下:
creating + irreconcilable: 资源创建失败
deleting + irreconcilable: 资源删除失败
reconciling + irreconcilable: 资源无法收敛
以下状态时是稳定状态:
deleted(deletion_finish != nil):资源已经完成删除流程。虽然你还能查到该资源,但是它已经不能提供任何服务。
active:其他情况下,都属于该状态。
状态的展示,基本思路是在 API 的 Metadata 中返回资源的总体状态。如果资源正在发生更新,还要返回有差异的字段的信息。
在 API 响应的 Metadata 中增加两个字段:
state:是一个字符串,表示该资源的总体状态,可选值如下:
creating
active
deleting
deleted
reconciling:表示资源正在更新中。
diff_fields: 是一个对象,目前还有两个 key
reconciling: 内容是一个字段名组成的列表,表示需要收敛的字段有哪些。
irreconcilable: 内容是一个字段名组成的列表,表示有哪些字段是不可收敛的。
当这个列表不为空时,表示辅助状态 irreconcilable 为 true。
这个列表的字段都会在 reconciling 列表中。
返回值示例如下:
{
"metadata": {
"state": {
"state": "reconciling",
"diff_fields": {
"reconciling": [
"bios",
"core_num_per_socket",
"cpu_mode",
"memory_mb"
],
"irreconcilable": [
"filed"
]
}
}
}
}
资源列表页
用独立字段表示 metadata 中的 state,并且可以 hover 展示 diff 的字段信息。
可以根据字段需要,单独设计字段的 diff 样式。
对于 diff 的字段,优先展示 Status 中的值。
资源详情
展示 Spec 和 Status 中的所有信息。
| 方法 | 说明 |
|---|---|
| GET /overview/ | 物理资源和虚拟资源的数量统计信息 |
| GET /settings/ | 集群信息,包括集群名称、集群地址、集群网络段配置、全局 CPU 和内存超分比、全局 VM HA 设置等。 |
| GET /license-summary/ | 产品许可信息 |
| GET /version/ | 产品版本信息 |
| 方法 | 说明 |
|---|---|
| GET /nodes/ | LIST 节点 |
| GET /modes/{id} | 获取节点信息 |
| 方法 | 说明 |
|---|---|
| GET /virtual-machines/ | LIST 虚拟机 |
| GET /virtual-machines/{id} | 获取虚拟机信息 |
获取虚拟机信息 API 返回数据如下所示。
{
"metadata": {
"id": 10,
"name": "rongze-test",
"created_at": "2022-10-31T19:17:56.192352+08:00",
"creation_finish": "2022-10-31T19:18:08.033611+08:00",
"project_id": 1,
"state": {
"state": "active",
"diff_fields": {
"reconciling": null,
"irreconcilable": null
}
},
"labels": []
},
"spec": {
"id": 10,
"name": "rongze-test",
"created_at": "2022-10-31T19:17:56.192352+08:00",
"updated_at": "2022-11-03T18:05:47.976022+08:00",
"deleted_at": null,
"creation_finish": "2022-10-31T19:18:08.033611+08:00",
"deletion_begin": null,
"deletion_finish": null,
"etag": "cb1d052e-1d27-4ada-8533-2dafb02ce7fb",
"project_id": 1,
"uuid": "89860d8f-37e2-4efc-945b-cbb8ac14c711",
"description": "",
"time_zone": "UTC",
"arch": "x86_64",
"cpu_num": 4,
"socket_num": 1,
"core_num_per_socket": 4,
"thread_num_per_core": 1,
"cpu_mode": "host-model",
"memory_mb": 4096,
"power_state": "RUNNING",
"ha_enabled": false,
"bios": "Legacy",
"boot_device_order": [
"DISK",
"CDROM",
"NIC"
],
"node_id": 1,
"dest_node_id": null,
"graphics_auth_enabled": false,
"graphics_spice_enabled": false,
"os_type": "Linux",
"os_distribution": "CentOS",
"os_name": "CentOS 7",
"cloud_init_uuid": "146a1131-83aa-46e9-90ed-b086dbc002d9",
"hostname": "",
"os_user_name": "",
"ssh_public_key": "",
"user_data": "",
"hugepage_enabled": false,
"vm_snap_id": null,
"flattened": false,
"rollback_vm_snap_id": null,
"rollback_time": null,
"power_action_type": "start",
"power_action_forced": false,
"power_action_time": "2022-11-01T11:47:58.964162+08:00"
},
"status": {
"id": 10,
"created_at": "2022-10-31T19:17:56.193285+08:00",
"updated_at": "2022-11-03T18:06:05.009698+08:00",
"deleted_at": null,
"etag": "d3e23514-9161-4cc9-8c5b-528ac91b08bd",
"time_zone": "UTC",
"cpu_num": 4,
"socket_num": 1,
"core_num_per_socket": 4,
"thread_num_per_core": 1,
"cpu_mode": "host-model",
"memory_mb": 4096,
"power_state": "RUNNING",
"power_state_reason": "migrated",
"bios": "Legacy",
"boot_device_order": [
"DISK",
"CDROM",
"NIC"
],
"node_id": 1,
"dest_node_id": null,
"vm_migration_job_id": null,
"launched_at": null,
"terminated_at": null,
"graphics_auth_enabled": false,
"graphics_spice_enabled": false,
"last_node_id": 1,
"machine_type": "pc-i440fx-rhel7.6.0",
"graphics_vnc_port": 5900,
"graphics_spice_port": null,
"unschedulable": false,
"unstoppable": false,
"flattened": false,
"rollback_time": null,
"power_action_time": "2022-11-01T11:47:58.964162+08:00",
"storage_fenced": false,
"ip_addresses": [
"10.16.58.3"
],
"vm_snap_num": 0
}
}
| 方法 | 说明 |
|---|---|
| GET /vm-disks/?q=spec.virtual_machine_id:{id} | 查看指定虚拟机下的虚拟硬盘列表 |
查看指定虚拟机下的虚拟硬盘列表 API 返回数据如下所示。
{
"metadata": {
"pagination": {
"offset": 0,
"limit": 100,
"count": 2,
"total_count": 2
}
},
"items": [
{
"metadata": {
"id": 11,
"name": "disk-Bg45d",
"created_at": "2022-10-31T19:17:56.204107+08:00",
"creation_finish": "2022-10-31T19:18:08.020337+08:00",
"project_id": 1,
"state": {
"state": "active",
"diff_fields": {
"reconciling": null,
"irreconcilable": null
}
},
"labels": []
},
"spec": {
"id": 11,
"name": "disk-Bg45d",
"created_at": "2022-10-31T19:17:56.204107+08:00",
"updated_at": "2022-11-03T18:05:47.98081+08:00",
"deleted_at": null,
"creation_finish": "2022-10-31T19:18:08.020337+08:00",
"deletion_begin": null,
"deletion_finish": null,
"etag": "1e6d6531-45fd-4552-a0d0-b07bb46e79b8",
"project_id": 1,
"virtual_machine_id": 10,
"node_id": 1,
"dest_node_id": null,
"bus_type": "Virtio",
"size_mb": 102400,
"storage_type": "rbd",
"bs_policy_id": 2,
"bs_volume_id": 19,
"vm_image_id": null,
"device_index": 1,
"root": false,
"del_vol_on_del": false,
"vhost_enabled": false,
"serial": "129c0eebd18c12f7",
"vm_disk_snap_id": null,
"flattened": false,
"rollback_vm_disk_snap_id": null,
"rollback_time": null
},
"status": {
"id": 11,
"created_at": "2022-10-31T19:17:56.204394+08:00",
"updated_at": "2022-11-03T18:05:48.973948+08:00",
"deleted_at": null,
"etag": "77631dc8-de90-4601-b589-f0dcc131e294",
"size_mb": 102400,
"bs_volume_size_mb": 102400,
"attached": true,
"node_id": 1,
"dest_node_id": null,
"bus_type": "Virtio",
"device_index": 1,
"source_name": "pool-290e9a3e56f545d0969a624661b94c0e/129c0eebd18c12f7",
"vhost_enabled": false,
"flattened": false,
"rollback_time": null,
"device_address": "0000:00:08.0",
"fenced_epoch": null
}
},
{
"metadata": {
"id": 10,
"name": "disk-wFdA3",
"created_at": "2022-10-31T19:17:56.201656+08:00",
"creation_finish": "2022-10-31T19:18:08.02746+08:00",
"project_id": 1,
"state": {
"state": "active",
"diff_fields": {
"reconciling": null,
"irreconcilable": null
}
},
"labels": []
},
"spec": {
"id": 10,
"name": "disk-wFdA3",
"created_at": "2022-10-31T19:17:56.201656+08:00",
"updated_at": "2022-11-03T18:05:47.981856+08:00",
"deleted_at": null,
"creation_finish": "2022-10-31T19:18:08.02746+08:00",
"deletion_begin": null,
"deletion_finish": null,
"etag": "2b4cd4d6-710e-45f0-afd3-d0ff605dfcdf",
"project_id": 1,
"virtual_machine_id": 10,
"node_id": 1,
"dest_node_id": null,
"bus_type": "Virtio",
"size_mb": 102400,
"storage_type": "rbd",
"bs_policy_id": 2,
"bs_volume_id": 18,
"vm_image_id": 6,
"device_index": 0,
"root": true,
"del_vol_on_del": false,
"vhost_enabled": false,
"serial": "117f756ad5123b70",
"vm_disk_snap_id": null,
"flattened": false,
"rollback_vm_disk_snap_id": null,
"rollback_time": null
},
"status": {
"id": 10,
"created_at": "2022-10-31T19:17:56.20222+08:00",
"updated_at": "2022-11-03T18:05:48.976945+08:00",
"deleted_at": null,
"etag": "dcfc98f8-2225-4393-a21b-c9cd0557189f",
"size_mb": 102400,
"bs_volume_size_mb": 102400,
"attached": true,
"node_id": 1,
"dest_node_id": null,
"bus_type": "Virtio",
"device_index": 0,
"source_name": "pool-290e9a3e56f545d0969a624661b94c0e/117f756ad5123b70",
"vhost_enabled": false,
"flattened": false,
"rollback_time": null,
"device_address": "0000:00:07.0",
"fenced_epoch": null
}
}
]
}
| 方法 | 说明 |
|---|---|
| GET /bs-policies/{id} | 获取块存储策略信息 |
获取块存储策略信息 API 返回数据如下所示。
{
"metadata": {
"id": 2,
"name": "bs-policy",
"created_at": "2022-10-31T14:46:27.389292+08:00",
"creation_finish": "2022-10-31T14:46:27.389292+08:00",
"state": {
"state": "active"
},
"labels": []
},
"data": {
"id": 2,
"name": "bs-policy",
"created_at": "2022-10-31T14:46:27.389292+08:00",
"updated_at": "2022-10-31T14:46:27.389292+08:00",
"deleted_at": null,
"creation_finish": "2022-10-31T14:46:27.389292+08:00",
"deletion_begin": null,
"deletion_finish": null,
"etag": "6e29645e-3924-450d-8e33-3ec3801db597",
"description": "",
"storage_type": "rbd",
"sds_pool_id": 3,
"pool_name": "pool-290e9a3e56f545d0969a624661b94c0e",
"max_total_bps": 0,
"burst_total_bps": 0,
"max_total_iops": 0,
"burst_total_iops": 0,
"performance_priority": false,
"crc_check": false,
"bs_volume_num": 11,
"provisioned_size_mb": 798832
}
}
虚拟机镜像上传分两个步骤:
| 方法 | 说明 |
|---|---|
| POST /vm-images/ | 创建镜像 |
| POST /vm-images/{id}:upload | 上传镜像文件 |
创建上传镜像举例:
curl -H 'x-sddc-token: 651af9ee5aba447cb365a9b75f9dcba2' \
-H 'Content-Type: application/json' \
-X POST -L 'http://127.0.0.1:6012/sddc/v1/vm-images/' -d '
{
"spec": {
"arch": "x86_64",
"bios": "Legacy",
"bootable": true,
"bs_policy_id": 1,
"cloud_config_enabled": true,
"name": "test",
"os_name": "CentOS",
"os_type": "Linux",
"os_distribution": "CentOS",
"size_byte": 619511808,
"type": "vm_disk"
}
}'
上传镜像文件举例:
curl -H 'x-sddc-token: 651af9ee5aba447cb365a9b75f9dcba2' \
-F 'image=@./CentOS-8-GenericCloud-8.4.2105-20210603.0.x86_64.qcow2' \
-X POST -L 'http://127.0.0.1:6012/sddc/v1/vm-images/4:upload'
在 V2.2 版本会支持 /sddc/v1/batch/ 批量操作 API。
TODO
使用 OpenAPI 自动生成。