**CMDB接口文档v0.1**   @ [维易科技](https://veops.cn)

# <div style="text-align: center;">CMDB接口文档</div>

### 一、CI接口

#### 1. CI查询接口

**条件搜索CI**, 按照模型的属性进行条件过滤、统计、排序等查询

* GET `/api/v0.1/ci/s`
* 参数
  
  | 参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
  | ----  | ----   | ------   | ------ | ----|
  | **q** | q=private_ip:192* | string | 是 | 搜索表达式 |
  | **fl** |  | string | 否 | 返回的属性字段, 逗号分隔 |
  | **facet** | facet=idc | string | 否 | 属性字段，逗号分隔，返回属性对应的所有值的统计 |
  | **count** | count=1 | int | 否 | 一页返回的CI数, 默认是25 |
  | **page** | page=1 | int | 否 | 页数, 默认是1 |
  | **sort** |  sort=-private_ip| string | 否 | 属性的排序，降序字段前面加负号- |
  | **ret_key** | ret_key=name | enum | 否 | 返回字段类型, 可以是`id`、`name`、`alias`, 默认`name` |
  
* 参数**q**说明：
    * `_type` 指定CI模型, 多个用分号分隔. 例如: `_type:(server;vserver)`
    * `attribute:value` 指定属性搜索， `attribute`可以是`id`,`attr_name`和`attr_alias`
    * 以上的组合，逗号分隔
    * 组合查询使用方法
        * **`与`** 关系: `默认关系`
        * **`或`**关系: 属性字段前加`-`, 例如: `-hostname:cmdb*`、
        * **`非`**关系: 属性字段前加`~` 例如: `~hostname:cmdb*`
        * **`或非`**关系: 属性字段前加`-~` 例如: `-~hostname:*`
        * **`IN`**查询: 例如: `hostname:(cmdb*;cmdb-web*)` 小括号, 分号分隔
        * **`范围`**查询: 例如: `hostname:[cmdb* _TO_ cmdb-web*]` `_TO_`分隔
        * **`比较`**查询: 例如: `cpu_count:>5` 支持`>, >=, <, <=`

* 结果字段说明

  | 字段名 | 值的类型 | 说明 |
  | ----  | ----  | ----|
  | **numfound** |  int | CI总数 |
  | **total** |  int | 当前页的CI数 |
  | **page** |  int |分页 |
  | **result** | list | 返回的CI列表 |
  | **facet** | dict| 根据参数facet做的聚合统计|
  | **counter** |  dict | 当前页按模型的分类统计 |
  
* 返回结果
    * 搜索示例 `/api/v0.1/ci/s?q=_type:server,private_ip:192.*,idc:*,status:在线&sort=-private_ip&facet=idc&page=1&count=1`
    * 返回数据（默认json）
---
```json
{
  "counter": {
    "server": 1
  },
  "facet": {
    "idc": [
      [
        "南汇",
        600,
        "idc"
      ],
      [
        "外高桥",
        600,
        "idc"
      ],
      [
        "张江",
        600,
        "idc"
      ]
    ]
  },
  "numfound": 1800,
  "page": 1,
  "result": [
    {
      "_id": 7238,
      "_type": 4,
      "buy_date": null,
      "ci_type": "server",
      "cpu": "Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz",
      "cpu_count": 20,
      "device_spec": "PowerEdge R630",
      "env": "test",
      "idc": "外高桥",
      "ilo_ip": "192.168.0.120",
      "ilo_mac": "82:7b:eb:f8:cb:03",
      "kernel_version": "4.1.12-61.1.33.el6uek.x86_64",
      "logic_cpu_count": 40,
      "maintain_enddate": null,
      "maintain_startdate": null,
      "manufacturer": "DELL",
      "op_duty": "张三",
      "os_version": "CentOS Linux release 7.6.1810 (Core)",
      "perm": null,
      "pos": null,
      "private_ip": "192.168.66.99",
      "rack": "12086",
      "raid": "1.089TB/RAID5",
      "ram": "128GB",
      "ram_size": "128GB",
      "rd_duty": "李四",
      "server_name": "192.168.66.99",
      "sn": "8cbe16404c11",
      "status": "在线",
      "unique": "sn",
      "vnc_port": null
    }
  ],
  "total": 1
}
```

#### 2. 新增CI接口

**创建或者修改CI**

* POST `/api/v0.1/ci` 
* 参数

| 参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
| ----  | ----   | ------   | ------ | ----|
| **ci_type** | ci_type=server | string | 是 | 创建CI所属的模型名 |
| **no_attribute_policy** | no_attribute_policy=ignore | string | 否 | 当添加不存在的attribute时的策略, 可选: `reject`、`ignore`, 默认`ignore` |
| **exist_policy** | exist_policy=reject | string | 否 | CI已经存在的处理策略, 可选: `need`、`reject`、`replace` 默认`reject` |
| **模型的属性名** | sn=xxxx | string | 否 | 属性名(id或别名亦可) |

> 注意: 请求的参数里必须包含该CI的唯一标识

* 返回结果

  ```json
  {
      "ci_id": 1
  }
  ```

#### 3. 修改CI接口

**修改CI**, 可以使用新增CI的接口, exist_policy=replace, 或者根据ci_id来修改

* PUT `/api/v0.1/ci` 或者 `/api/v0.1/ci/<int:ci_id>`
* 参数

| 参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
| ----  | ----   | ------   | ------ | ----|
| **ci_type** | ci_type=server | string | 是 | 创建CI所属的模型名 |
| **no_attribute_policy** | no_attribute_policy=ignore | string | 否 | 当添加不存在的attribute时的策略, 可选: `reject`、`ignore`, 默认`ignore` |
| **模型的属性名** | sn=xxxx | string | 否 | 属性名(id或别名亦可) |

> 注意: 如果使用`/api/v0.1/ci`, 请求的参数里必须包含该CI的唯一标识

* 返回结果

  ```json
  {
      "ci_id": 1
  }
  ```

#### 4. 删除CI接口

**根据ci_id删除CI**, 硬删除操作

* DELETE `/api/v0.1/ci/<int:ci_id>`
* 参数 无
* 返回结果

  ```json
  {
      "message": "ok"
  }
  ```

<div STYLE="page-break-after: always;"></div>

### 二、CI关系接口

#### 1. CI关系查询接口

**搜索所有的CI之间的关系**, 比如某一个事业部的所有应用或者是所有服务器

* GET `/api/v0.1/ci_relations/s`
* 参数
  
    | 参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
    | ----  | ----   | ------   | ------ | ----|
    | **root_id** | root_id=1 | int | 是 | 根节点的ci_id |
    | **level** | level=1 | string | 否 | 关系的层级，多层用逗号分隔 |
    | **reverse** | reverse=0 | int | 否 | 是否反向搜索, 0或者1, 默认是0,  |
    | **q** | q=hostname:cmdb* | string | 否 | 搜索表达式 |
    | **fl** |  | string | 否 | 返回的属性字段, 逗号分隔 |
    | **facet** |  | string | 否 | 属性字段，逗号分隔，返回属性对应的所有值的统计 |
    | **count** | count=25 | int | 否 | 一页返回的CI数, 默认是25 |
    | **page** | page=1 | int | 否 | 页数, 默认是1 |
    | **sort** |  | string | 否 | 属性的排序，降序字段前面加负号- |
    | **ret_key** | | enum | 否 | 返回字段类型, 可以是`id`、`name`、`alias`, 默认`name` |
    
> 搜索表达式`q` 和 `CI查询接口`的搜索表达式q 完全一样!

* 结果字段说明

  | 字段名 | 值的类型 | 说明 |
  | ----  | ----  | ----|
  | **numfound** |  int | CI总数 |
  | **total** |  int | 当前页的CI数 |
  | **page** |  int |分页 |
  | **result** | list | 返回的CI列表 |
  | **facet** | dict| 根据参数facet做的聚合统计|
  | **counter** |  dict | 当前页按模型的分类统计 |
  
* 返回结果
    * 搜索某个事业部下面的物理机 `/api/v0.1/ci_relations/s?root_id=5&level=3&count=1&q=_type:server,idc:南汇`
    * 返回数据（默认json)    
---

```json
{
  "counter": {
    "server": 1
  },
  "facet": {},
  "numfound": 400,
  "page": 1,
  "result": [
    {
      "_id": 159,
      "_type": 4,
      "bu": null,
      "buy_date": null,
      "ci_type": "server",
      "cmc_ip": null,
      "cnc_ip": null,
      "cpu": "Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz",
      "cpu_count": 20,
      "ctc_ip": null,
      "device_spec": "PowerEdge R630",
      "env": "prod",
      "idc": "南汇",
      "ilo_ip": "192.168.0.120",
      "ilo_mac": "82:7b:eb:f8:cb:03",
      "kernel_version": "4.1.12-61.1.33.el6uek.x86_64",
      "logic_cpu_count": 40,
      "maintain_enddate": null,
      "maintain_startdate": null,
      "manufacturer": "DELL",
      "oneagent_id": null,
      "op_duty": "张三",
      "os_version": "Microsoft Windows Server 2019 Standard",
      "perm": null,
      "pos": null,
      "private_ip": "192.168.1.2",
      "rack": "12086",
      "raid": "1.089TB/RAID5",
      "ram": "128GB",
      "ram_size": "128GB",
      "rd_duty": "李四",
      "server_name": "192.168.1.2",
      "server_room": null,
      "sn": "1fd3b1d5c253",
      "ssh_port": null,
      "status": "在线",
      "unique": "sn",
      "vnc_port": null
    }
  ],
  "total": 1
}
```

#### 2. 增加CI关系接口

**新增CI关系**, 参数`src_ci_id`是源CI的id, `dst_ci_id`是目标CI的id

* POST `/api/v0.1/ci_relations/<int:src_ci_id>/<int:dst_ci_id>`
* 参数 无
* 返回结果

  ```json
  {
      "cr_id": 1
  }
  ```

#### 3. 删除CI关系接口

**根据`cr_id`删除CI关系**, 参数`cr_id`是CI关系的id

* DELETE `/api/v0.1/ci_relations/<int:cr_id>` 
* 参数 无
* 返回结果

  ```json
  {
      "message": "CIType relation deleted"
  }
  ```

<div STYLE="page-break-after: always;"></div>

### 三、响应状态码说明

|状态码|说明|
|----|---|
|200|成功|
|400|请求参数错误或者失败|
|401|未认证|
|403|权限不够|
|404|访问的资源不存在|
|500|服务端未知错误|
|502|服务未启动或者异常退出|

> 所有错误或者失败，统一返回json格式为:
  ```json
  {
      "message": "错误描述"
  }
  ```


<div STYLE="page-break-after: always;"></div>

### 四、API鉴权方法
- 每个用户会自动生成一个 `api key` 和 一个`secret`, 在ACL系统里可查看到
- 调用API的时候，需要提供2个参数 `_key`和`_secret`
  - `_key`的值为您的`api key`
  - `_secret`的计算方法：
    - 除`_key`以外的参数，把**参数名**排序后参数值拼接在一起，并连接到`url path` + `secret`之后 
    - 求`sha1`**十六进制**值, 即sha1(`url path` + `secret` + `参数名排序后拼接的参数值`)的16进制值


### 五、Python调用样例
#### 鉴权
```python
import hashlib

key = "Your API key"
secret = "Your API secret"

def build_api_key(path, params):
    values = "".join([str(params[k]) for k in sorted(params.keys())
                      if params[k] is not None and not k.startswith('_')]) if params.keys() else ""
    _secret = "".join([path, secret, values]).encode("utf-8")
    params["_secret"] = hashlib.sha1(_secret).hexdigest()
    params["_key"] = key
    
    return params
```

#### 查询
* 以查询CI为例
```python
import hashlib

import requests
from future.moves.urllib.parse import urlparse

URL = "https://demo.veops.cn/api/v0.1/ci/s"
KEY = "Your API key"
SECRET = "Your API secret"


def build_api_key(path, params):
    values = "".join([str(params[k]) for k in sorted(params.keys())
                      if params[k] is not None and not k.startswith('_')]) if params.keys() else ""
    _secret = "".join([path, SECRET, values]).encode("utf-8")
    params["_secret"] = hashlib.sha1(_secret).hexdigest()
    params["_key"] = KEY

    return params


def get_ci(payload):
    payload = build_api_key(urlparse(URL).path, payload)

    return requests.get(URL, params=payload).json()

```

#### 增、删、改
* 以CI的增、删、改为例
```python
import hashlib

import requests
from future.moves.urllib.parse import urlparse

URL = "https://demo.veops.cn/api/v0.1/ci"
KEY = "Your API key"
SECRET = "Your API secret"


def build_api_key(path, params):
    values = "".join([str(params[k]) for k in sorted(params.keys())
                      if params[k] is not None and not k.startswith('_')]) if params.keys() else ""
    _secret = "".join([path, SECRET, values]).encode("utf-8")
    params["_secret"] = hashlib.sha1(_secret).hexdigest()
    params["_key"] = KEY

    return params


def add_ci(payload):
    payload = build_api_key(urlparse(URL).path, payload)

    return requests.post(URL, json=payload).json()


def update_ci(payload, ci_id=None):
    url = "{url}/{ci_id}".format(url=URL, ci_id=ci_id) if ci_id is not None else URL

    payload = build_api_key(urlparse(url).path, payload)

    return requests.put(url, json=payload).json()


def delete_ci(ci_id):
    url = "{url}/{ci_id}".format(url=URL, ci_id=ci_id)

    payload = build_api_key(urlparse(url).path, {})

    return requests.delete(url, json=payload).json()

```
