```
                _                                _
 _ __ __ _  ___| | ___ __ ___   ___  _   _ _ __ | |_
| '__/ _` |/ __| |/ / '_ ` _ \ / _ \| | | | '_ \| __|
| | | (_| | (__|   <| | | | | | (_) | |_| | | | | |_
|_|  \__,_|\___|_|\_\_| |_| |_|\___/ \__,_|_| |_|\__|

```
# rackmount

# 功能简介

为满足产品化机型的差异处理和伙伴开放场景的使用，需要将系统配置按管理模型进行划分，每种形态单独仓库管理，存放该形态的基础配置。本仓库描述的是**机架形态**形态配置数据（rackmount）的基础配置，北向接口配置的构建打包方式可参考wiki[映射器配置的机型可灵活拓展](https://openubmc.cn/docs/zh/development/api/rackmount.html#%E4%B9%9D%E3%80%81%E6%8E%A5%E5%8F%A3%E6%98%A0%E5%B0%84%E9%85%8D%E7%BD%AE%E5%A4%9A%E5%B1%82%E7%BA%A7%E5%AE%9A%E5%88%B6)

## 目录层级划分

``` shell
└── interface_config                        # 北向接口映射器配置
    ├── board_info_collector                # 支持正向采集映射器配置，注：详情看该目录下的README.md
    ├── cli                                 # 命令行接口配置
    │   ├── echoes                          # 回显模板
    │   ├── ipmcget                         # ipmcget命令映射配置
    │   ├── ipmcset                         # ipmcset命令映射配置
    │   ├── plugins                         # 插件
    │   └── script                          # 脚本
    ├── redfish                             # redfish接口映射配置
    │   ├── mapping_config                  # 映射配置
    │   ├── plugins                         # 插件
    │   ├── script                          # 脚本
    │   ├── static_resource                 # 静态文件
    │   └── config.json                     # 批量字段替换配置文件
    ├── snmp                                # snmp接口映射配置
    │   ├── mapping_config                  # 映射配置
    │   ├── mib                             # mib文件
    │   ├── plugins                         # 插件
    │   ├── script                          # 脚本
    │   └── config.json                     # 批量字段替换配置文件
    └── web_backend                         # web_backend接口映射配置
        ├── mapping_config                  # 映射配置
        ├── plugins                         # 插件
        ├── script                          # 脚本
        └── config.json                     # 批量字段替换配置文件
```

# 关键特性

支持redfish、web_backend、cli、snmp的北向接口映射配置

支持正向采集映射器配置

# 配置介绍

北向接口redfish、web-backend、cli、snmp的设计，引入了数据映射机制，框架解析数据映射配置，将接口请求转发到资源协作接口，拼装返回对应格式的数据。本文介绍接口映射公共配置规则，部分规则只在单一接口使用，可见其对应的接口配置指南。

① 封装接口协议，代码与业务分离，后续扩展接口无需修改代码

② 抽象接口数据模型，建立映射规则，扩展接口仅需修改配置

③ 业务逻辑处理，数据上树，提供标准的资源协作接口

接口映射配置的通用格式：

```json
{
    "Resources": [
        {
            "Uri": "xxxx",
            "Interfaces": [
                {
                    "Type": "GET/PATCH/POST/DELETE",
                    "ResourceExist": {xxxx},
                    "ReqBody": {xxxx},
                    "RspBody": {xxxx},
                    "RspHeader": {xxxx},
                    "Statements": {xxxx},
                    "ProcessingFlow": [xxxx]
                }
            ]
        }
    ]
}
```

* `Resources`: Uri配置对象数组
* `Interfaces`：Uri所对应的接口配置，由于存在多种Type的请求，所以是个对象数组
* `Type`：请求方法，当前支持Get、Patch、Post、Delete（不区分大小写）
* `ResourceExist`: Uri有效性校验
* `ReqBody`：请求体配置，需要对请求体做Schema声明
* `RspBody`：响应体配置，所见即所得
* `RspHeader`: 响应头配置，可按需给响应头加入指定的键值对
* `Statements`: 数据处理
* `ProcessingFlow`：资源协作接口映射配置

Interfaces中除Type外其他字段按需配置

## 一、Uri有效性校验

当前redfish等接口Uri存在动态字段，例如/redfish/v1/Managers/:managersid，:managersid就是动态参数，对于机架机型，一般有效取值为1，那么当输入的Uri为/redfish/v1/Managers/2就应该报404错误。

```json
"ResourceExist": {
    "${Uri/managersid}": "1",
    "${Statements/GetId()}": "#WITH",
    "${ProcessingFlow[1]/Destination/Valid}": true
},
"Statements": {
    "GetName": {
        "Steps": [
            {
                "Type": "Script",
                "Formula": "return nil"
            }
        ]
    }
},
"ProcessingFlow": [
    {
        "Type": "Method",
        "Path": "/bmc/kepler/Mdb",
        "Interface": "bmc.kepler.Mdb",
        "Name": "IsValidPath",
        "Params": [
            "/bmc/kepler/Managers/${Uri/managerid}"
        ],
        "Destination": {
            "Valid": "Valid"
        },
        "CallIf": "CheckUri"
    }
]
```

`只有当每对keyvalue判断均成立，ResourceExist条件才成立，Uri校验才成功`。引入`#WITH`和`#WITHOUT`来表示属性是否为nil。
上述例子表明，只有当Uri/managersid取值为"1"、Statements/GetId()不为nil、ProcessingFlow[1]/Destination/Valid取值为true，所有条件都满足时Uri校验成功。（ProcessingFlow的配置被ResourceExist直接或间接引用时，需要加上配置`"CallIf": "CheckUri"`）
注：由于Patch接口在处理前会会调用一次对应Get接口，所以Patch无需再配置ResourceExist。

## 二、ReqBody声明

用户在发送请求时可能会携带请求体，由于请求体是用户输入，在实际使用中未必能按照预期输入数据，例如属性类型要求是字符串类型，但是用户输入却是数字。所以需要一种语法来声明请求体的数据内容，对请求体格式做最基本的校验，这里使用ReqBody来声明。（ReqBody声明类似标准JSON Schema的语法规范，请参考[JSON Schema官方文档](https://json-schema.apifox.cn/)）

以如下请求体为例（假设UserName是必填属性，且只允许取值Administrator或root）：

```json
{
    "UserName": "root",
    "Locked": true,
    "Oem": {
        "openUBMC": {
            "FirstLoginPolicy": "ForcePasswordReset"
        }
    }
}
```

其对应的ReqBody配置应该为

```json
"ReqBody": {
    "Type": "object",
    "Required": true,
    "Properties": {
        "UserName": {
            "Type": "string",
            "Required": true,
            "Validator": [
                {
                    "Type": "Enum",
                    "Formula": ["Administrator", "root"]
                }
            ]
        },
        "Locked": {
            "Type": "boolean"
        },
        "Oem": {
            "Type": "object",
            "Properties": {
                "openUBMC": {
                    "Type": "object",
                    "Properties": {
                        "LoginInterface": {
                            "Type": "string"
                        }
                    }
                }
            }
        }
    }
}
```

## 完整性校验

关键字`Required`用于声明请求体属性是否为必填参数，取值true/false，未配置时取默认值false。

```json
"ReqBody": {
    "Type": "object",
    "Required": true,
    "Properties": {
        "PropA": {
            "Required": true
        },
        "PropB": {
            "Required": false
        },
        "PropC": {
            "Type": "object",
            "Properties": {
                "Prop1": {
                    "Required": true
                }
            }
        }
    }
}
```

例如上述声明，请求体为`{"PropA": 1}`或者`{"PropA": 1, "PropB": 2}`是符合要求的（PropC对象不存在时，不需要对PropC/Prop1做完整性校验）；请求体为`{"PropA": 1}, "PropC": {}`或者`"PropB": 2}`是不符合要求的，前者缺少PropC/Prop1，后者缺少PropA。

### 数据类型校验

`Type`关键字是数据类型校验的基础，可以是一个字符串或数组：

* 字符串，指定数据类型，可能取值array、boolean、integer、number、null、object、string
* 字符串数组，允许多种类型，其中每个字符串是其中一种基本类型的名称，每个元素都是唯一的。在这种情况下，请求体数据与任一元素类型匹配上即可。

```json
"ReqBody": {
    "Type": "object",
    "Required": true,
    "Properties": {
        "PropA": {
            "Type": "string"
        },
        "PropB": {
            "Type": ["number", "boolean"]
        }
    }
}
```

例如上述声明，请求体`{"PropA": "str", "PropB": 1}`符合要求，`{"PropA": 1, "PropB": "str"}`不符合要求。PropA必须是字符串类型，PropB必须是数字或者布尔类型。

#### 对象及Properties关键字

对象（`object`）是 JSON 中的映射类型。他们将“键”映射到“值”。在 JSON 中，“键”必须始终是字符串。这些对中的每一组通常被称为“属性”。
对象的属性（键值对）是使用`Properties`关键字定义的 。`Properties`的值是一个对象数组，数组的每个元素用于声明属性的特性，其内部同样也可以包含Type、Required、Properties等关键字。

#### 数组及Items关键字

数组用于有序元素。在 JSON 中，数组中的每个元素可能是不同的类型。
JSON 中数组的使用一般有两种方式：

* **列表验证**：任意长度的序列，其中每个元素都匹配相同的模式。
* **元组验证**：一个固定长度的序列，其中每个元素可能有不同的模式。

##### 列表验证

列表验证对于任意长度的数组很有用，其中每个元素都匹配相同的模式。
列表的数据类型（Type）为array，列表包含Items、minItems、maxItems和uniqueItems等关键字。

* **Items**: 定义列表元素。
* **maxItems**: 定义列表的最大元素个数。缺省maxItems情况，默认不校验列表参数的最大元素个数。
* **minItems**: 定义列表的最小元素个数。缺省minItems情况，默认不校验列表参数的最小元素个数。
* **uniqueItems**: 定义是否需要保证每个数组元素不相同。配置为true，数组中的每一个元素必须唯一，配置为false，则允许数组包含重复元素。缺省uniqueItems情况下默认配置为false。

```json
"ReqBody": {
    "Type": "object",
    "Required": true,
    "Properties": {
        "PropA": {
            "Type": "array",
            "Items": {
                "Type": "number"
            },
            "minItems": 2,
            "maxItems": 5,
            "uniqueItems": true
        }
    }
}
```

上述配置表明，属性PropA是一个数组，数组的每个元素都是数字，最少包含2个元素，最多包含5个元素，且数组中每个元素都必须不相同。那么`[1, 2, 3, 4, 5]`是符合要求的，`[1, 2, "3", 4, 5]`、`[1]`、`[1, 2, 3, 4, 5, 6]`、`[1, 1]`是不符合要求的。

##### 元组验证

当数组是一个元素的集合时，元组验证很有用，每个元素可能有不同的模式。例如一个三元组 [编号、用户名、是否锁定]，这些字段中的每一个都将具有不同的模式：

* Id：编号，必须是数字
* UserName: 用户名，必须是字符串
* Locked：是否锁定，必须是布尔值true/false

为此，我们将Items关键字设置为一个数组，其中每个项目都是一个模式，对应于元组的每个索引。也就是说，一个数组，其中第一个元素验证输入数组的第一个元素，第二个元素验证输入数组的第二个元素，依此类推。

```json
"ReqBody": [
    {
        "Name": "PropA",
        "Type": "array",
        "Items": [
            {
                "Type": "number"
            },
            {
                "Type": "string"
            },
            {
                "Type": "boolean"
            }
        ]
    },
]
```

（上述配置中，简单类型的元素，Name可不配置）
当PropA的取值为`[10086, 10001, true]`是不符合要求的，第二个元素必须是字符串，而`10001`是一个数字。`[10086, "root", true]`时是符合要求的，`[10086, "root"]`和`[10086, "root", true, "str"]`也是符合要求的。

### 数据内容校验

有时需要对请求体属性内容做一些简单的校验，例如字符串长度符合范围或者限制为一组固定的值，这里使用关键字`Validator`来声明相关内容校验，其中校验的种类由Type字段来表征，当前支持的Type可见下述各小节

```json
{
    "Name": "Prop",
    "Validator": [
        {
            "Type": xxx,
            "Formula": xxx
        }
    ]
}
```

#### Enum

```json
{
    "Type": "Enum",
    "Formula": ["Administrator", "root", "Admin"]
}
```

枚举校验，Formula配置符合范围的取值，它必须是一个包含至少一个元素的数组，其中每个元素都是唯一的。可用于boolean、integer、number、null、string这几个简单数据类型的检验。（也可配置object或者array复杂结构，不推荐使用，复杂结构也要进行对应声明）
上述例子中，属性取值`"Administrator"`符合要求，取值`"Adminxxx"`不符合要求

#### Length

```json
{
    "Type": "Length",
    "Formula": [1, 16]
}
```

校验字符串长度。Formula指定长度上下限（闭合区间），若上限或下限无要求，可配置为null，例如[null, 16]表示长度范围上限为16（包含16）
`"Formula": [1, 16]`的声明，取值为`"string123"`符合要求，取值为`"stringstringstring"`(长度18)不符合要求

#### Nonempty

```json
{
    "Type": "Nonempty"
}
```

字符串非空校验，取值`""`是不符合要求的。

#### Range

```json
{
    "Type": "Range",
    "Formula": [1, 16]
}
```

数字范围校验，可用于integer和number数据类型，Formula指定范围上下限（闭合区间），若上限或下限无要求，可配置为null

#### Regex

```json
{
    "Type": "Regex",
    "Formula": "^xx[0-9]"
}
```

正则匹配校验（标准正则，非Lua正则），可用于string数据类型，如上述声明，取值`"xx1"`符合要求，取值`"xxx"`不符合要求

#### IPFormat

```json
{
    "Type": "IPFormat",
}
```

IP格式校验，，可用于string数据类型，取值`"127.0.0.1"`符合要求，取值`"9.9.0"`不符合要求

#### Script

```json
{
    "Type": "Script",
    "Formula"： "if Input % 5 == 0 then local err = base_messages.PropertyValueFormatError(Input, PropertyName) err.RelatedProperties = {'#/' .. PropertyName} error(err) end return true"
}
```

复用数据处理中的`Script`，具体使用规则请参考[Script](#Process_Script "点击跳转")。校验失败则执行error抛出错误。校验通过返回true或nil。
相比较数据处理的Script，环境变量变化如下：

* `PropertyName`: 新增变量，表示校验的属性名，例如 Oem/openUBMC/PropA
* `Input`：校验的属性取值
* `ProcessingFlow`: 校验是并未进行资源协作接口访问，故无此变量

### 敏感信息打码Sensitive

当请求体存在敏感数据，错误Message中应该将该字段的值打码为`******`。引入`Sensitive`关键字，用于表示需要将该属性的取值打码，避免敏感数据直接呈现在Message信息中。
**<font color="red">敏感信息必须显示配置`"Sensitive": true`</font>**，涉及的敏感信息包括但不限于：用户密码、会话token、包含密码的远程文件传输URL、包含密码的虚拟媒体挂载地址、SMTP登录密码、SNMP团体名、SNMP加密密码、SP升级的ImageURI和SignalURI、SP系统部署配置中的CDKey和RootPwd、NTP组秘钥、KerberOs密钥表、证书私钥、证书加密密码、加密密钥（包括根密钥、主密钥和工作密钥）、Redfish事件订阅请求头、VNC密码、BIOS密码、LDAP绑定密码（BindDNPsw）、KVM加密密钥、SSH Host key。

例如有如下配置：

```json
"ReqBody": {
    "Type": "object",
    "Required": true,
    "Properties": {
        "Password": {
            "Type": "string",
            "Sensitive": true
        }
    }
}
```

当请求体取值`{"Password": 111}`，类型校验失败，其拿到的错误信息为：

```json
"MessageId": "Base.1.0.PropertyValueTypeError",
"Message": "The value ****** for the property Password is of a different type than the property can accept."，
"MessageArgs": [
    "******",
    "Password"
],
...
```

注：此配置只对映射器框架拦截的错误生效

## 三、响应体定义
### 3.1、RspBody定义

响应体的定义做到所见即所得
假设访问某接口获取到响应体为（假设UserName需要从资源协作接口中获取）：

```json
{
    "@odata.context": "/redfish/v1/$metadata#AccountService/Accounts/Members/$entity",
    "Name": "User Account",
    "UserName": "Administrator",
    "Oem": {
        "openUBMC": {
            "LoginRule": null
        }
    }
}
```

其对应的RspBody配置为：

```json

"RspBody": {
    "@odata.context": "/redfish/v1/$metadata#AccountService/Accounts/Members/$entity",
    "Name": "User Account",
    "UserName": "${ProcessingFlow[1]/Destination/UserName}",
    "Oem": {
        "openUBMC": {
            "LoginRule": null
        }
    }
}
```

`${ProcessingFlow[1]/Destination/UserName}`表示数据需要从别处获取，详细规则请参考[数据引用](#reference "点击跳转")

### 3.2、ActionResponseBody定义

ActionResponseBody用于自定义redfish接口中Actions类型接口的响应体，用法与RspBody相同，相比起RspBody，配置ActionResponseBody后响应体能做到所见即所得。
ActionResponseBody只能用于Actions类型接口配置，且与RspBody互斥
配置ActionResponseBody后，对应的schema文件需要配置上相应的actionResponse（actionResponse依据redfish标准规范定义，可参考rackmount仓中GenerateCSR的schema配置）

例如使用ActionResponseBody配置Actions类型redfish接口的响应体
```json
"ActionResponseBody": {
    "CSRString": "${ProcessingFlow[1]/Destination/CSRString}",
    "Certificate": "${ReqBody/Certificate}"
}
```
其响应体为
```json
{
    "CSRString": "----BEGIN CERTIFICATE REQUEST-----...----END CERTIFICATE REQUEST------",
    "Certificate": {
        "@odata.id": "/redfish/v1/Managers/BMC/NetworkProtocol/Https/Certifacates/1"
    }
}
```

使用RspBody配置Actions类型redfish接口的响应体
```json
"RspBody": {
    "CSRString": "${ProcessingFlow[1]/Destination/CSRString}",
    "Certificate": "${ReqBody/Certificate}"
}
```
其响应体为
```json
{
    "error": {
        "code": "Base.1.0.GeneralError",
        "message": "A general error has occurred. See ExtendedInfo for more information.",
        "@Message.ExtendedInfo": [
            {
                "CSRString": "----BEGIN CERTIFICATE REQUEST-----...----END CERTIFICATE REQUEST------",
                "Certificate": {
                    "@odata.id": "/redfish/v1/Managers/BMC/NetworkProtocol/Https/Certifacates/1"
                }
            }
        ]
    }
}
```

## 四、资源协作接口映射配置

资源协作接口映射配置ProcessingFlow的一般格式为：

```json
"ProcessingFlow": [
    {
        "Type": "Property/Method/List/Task/Paging"
        "Path": "xx",
        "Interface": "xxx",
        "Name": "xxx",
        "Params": [xxx],
        "Destination": {xxx},
        "Source": {xxx},
        "CallIf": {xxx},
        "Foreach": "xxx"
    },
    xxx
]
```

通用字段说明如下：

* `ProcessingFlow`：取值必须为对象数组，每个对象指明一个资源协作接口映射，对象的映射数据按序执行
* `Type`: 映射类型，当前支持Property/Method/List/Task/Paging，具体使用方式可下述各小节（Paging仅CLI接口使用）
* `Path`：资源协作接口对象
* `Interface`： 资源协作接口
* `Name`: 方法名
* `Params`：方法参数
* `Destination`：返回值配置
* `Source`: 设置属性时使用
* `CallIf`：资源协作接口是否调用，适用于所有映射类型，规则请参考[CallIf](#CallIf "点击跳转")
* `Foreach`: 资源协作接口多次调用，适用于所有映射类型，规则请参考[Foreach](#Foreach "点击跳转")

各字段按映射类型按需配置，具体配置说明可见下列各小节

### Property

* 读取属性

```json
{
    "Type": "Property",
    "Path": "/bmc/kepler/Managers/1/EthernetInterfaces/Ipv4",
    "Interface": "bmc.kepler.Managers.EthernetInterfaces.Ipv4",
    "Destination": {
        "IpMode": "IpModeIpv4",
        "IpAddr": "IpAddrIpv4",
        "SubnetMask": "SubnetMask"
    }
},
{
    "Type": "Property",
    "Path": "/bmc/kepler/Managers/1/EthernetInterfaces/Ipv6",
    "Interface": "bmc.kepler.Managers.EthernetInterfaces.Ipv6",
    "Destination": {
        "IpMode": "IpModeIpv6"
    }
}
```

`Destination`配置了一系列键值对，key表示资源协作接口上的属性名，获取该属性，该将属性名称重名为value。
（因为不同资源协作接口可能存在相同的属性名，有重命名的存在，就可以避免命名冲突）
如上例，在别处有配置\${ProcessingFlow[1]/Destination/IpModeIpv4}说明获取的是IPv4资源协作接口的IpMode属性，配置\${ProcessingFlow[2]/Destination/IpModeIpv6}说明获取的是IPv6资源协作接口的IpMode属性

* 设置属性

```json
{
    "Type": "Property",
    "Path": "/bmc/kepler/Managers/1",
    "Interface": "bmc.kepler.Managers.Ntp",
    "Source": {
        "Preferred": "${ReqBody/PreferredServer}"
    }
}
```

与获取属性的结构类似，不同的是关键字改为`Source`。上述例子是将请求体中的属性PreferredServer设置到资源协作接口的属性Preferred中

### Method

```json
{
    "Type": "Method",
    "Path": "/bmc/kepler/Systems/Events",
    "Interface": "bmc.kepler.Systems.Events",
    "Name": "GetSelInfo",
    "Params": [
        "123"
    ],
    "ContextParams": {
        "SystemId": "1"
    },
    "Destination": {
        "Version": "Version",
        "CurrentEventNumber": "CurrentEventNumber",
        "MaxEventNumber": "MaxEventNumber"
    }
}
```

* `Name`: 方法名
* `Params`：方法参数，可缺省，表示无参（不包括上下文参数）
* `ContextParams`: 可按需在RPC开头的a{ss}上下文参数中填充数据，Task类型的RPC也支持
* `Destination`：与获取属性配置类似，**左边的key对应方法返回值的变量名，右边的value对应自配置的变量名**

以上述配置为例，在实际环境使用busctl调用方法：

```
busctl --user call bmc.kepler.event /bmc/kepler/Systems/Events bmc.kepler.Systems.Events GetSelInfo a{ss} 0 
```

执行成功，返回为`suu "1.0.0" 0 10000`，suu表示后接数据类型依次是String、U32、U32。此处并未能看出Version、CurrentEventNumber、MaxEventNumber字段。
查看GetSelInfo的声明（在mdb_interface仓中有关于资源协作接口返回字段的声明），rsp包含对返回字段的声明
```json
"GetSelInfo": {
    "req": {},
    "rsp":{
        "Version":{
            "baseType": "String"
        },
        "CurrentEventNumber": {
            "baseType": "U32"
        },
        "MaxEventNumber": {
            "baseType": "U32"
        }
    }
},
```

使用代理对象调用method时，会对返回值做结构化封装，例如GetSelInfo方法的返回值应该为：

```lua
{
    Version = "1.0.0",
    CurrentEventNumber = 0,
    MaxEventNumber = 1000
}
```

### List

```json
{
    "Type": "List",
    "Path": "/bmc/kepler/Managers/1/NetworkProtocol",
    "Interface": "bmc.kepler.Managers.NetworkProtocol.Protocol",
    "Params": [1],
    "Destination": {
        "Members": "service_table"
    }
}
```

获取子资源协作接口集合。
`Interface`：可按需配置，未配置时获取所有子资源协作接口，配置时获取包含Interface的子路径。
`Params`：可按需配置。Params[1]表征获取深度，例如1表示子资源协作接口，2表示子子资源协作接口。Params未配置时默认表示深度1，获取子资源协作接口（绝大部分场景都是此情况）。

上述例子中/bmc/kepler/Managers/1/NetworkProtocol资源有如下子资源（子资源协作接口均有bmc.kepler.Managers.NetworkProtocol.Protocol的Interface）
```json
"GetSelInfo": {
    "req": {},
    "rsp":{
        "Version":{
            "baseType": "String"
        },
        "CurrentEventNumber": {
            "baseType": "U32"
        },
        "MaxEventNumber": {
            "baseType": "U32"
        }
    }
},
```

那么配置的结果为，Destination增加了如下键值对

```json
"service_table": [
    "/bmc/kepler/Managers/1/NetworkProtocol/HTTP",
    "/bmc/kepler/Managers/1/NetworkProtocol/HTTPS",
    "/bmc/kepler/Managers/1/NetworkProtocol/KVMIP",
    "/bmc/kepler/Managers/1/NetworkProtocol/SSH",
    "/bmc/kepler/Managers/1/NetworkProtocol/Video",
    "/bmc/kepler/Managers/1/NetworkProtocol/VirtualMedia",
]
```

### Task

```json
{
    "Type": "Task",
    "Path": "/bmc/kepler/Managers/1/LogServices",
    "Interface": "bmc.kepler.Managers.LogServices",
    "Name": "Dump",
    "Params": [
        0
    ],
    "Destination": {
        "TaskId": "TaskId"
    },
    "PostTaskProcess": [
        {
            "Type": "ChangeOwner",
            "Params": [
                "/tmp/web/operate.log"
            ]
         }
    ]
}
```

`Task`是一种特殊的Method，用于异步执行的方法。对任务机制做通用定义，对资源协作接口格式有一定要求，请学习通用任务机制。

* `PostTaskProcess`, 任务后置操作,用于配置在task执行完毕后执行指定操作。Type指明操作类型，目前已支持在文件导出任务后执行修改文件属主和权限。
  * `ChangeOwner`: 修改文件属主为当前用户，修改文件权限为600，参数：本地文件绝对路径（不支持远程文件路径）
    补充：一个ProcessingFlow中可以配置多个Task，但是只应该有一个生效。可以通过CallIf条件来控制具体执行的Task

### <span id="CallIf"> CallIf </span>

```json
"CallIf": {
    "${ReqBody/PropA}": "#WITH",
    "${ReqBody/PropB}": "#WITHOUT",
    "${ReqBody/PropC}": "str1",
    "${Uri/id}": 1
}
```

只有当每个属性判断均成立，CallIf条件才成立，对应的Methods等才有必要执行。引入#WITH和#WITHOUT来表示属性是否存在。
上述例子表明，只有当ReqBody/PropA存在、ReqBody/PropB不存在、ReqBody/PropC取值为"str1"、Uri/id取值为1，四个条件都满足时CallIf条件才成立。

### <span id="Foreach"> Foreach </span>

```json
"ReqBody": {
    "Type": "object",
    "Required": true,
    "Properties": {
        "SnmpTrapNotification": {
            "Type": "object",
            "Properties": {
                "TrapServer": {
                    "Type": "array",
                    "Items": {
                        "Type": "object",
                        "Properties": {
                            "TrapServerPort": {
                                "Type": "number"
                            }
                        }
                    }
                }
            }
        }
    }
},
"ProcessingFlow": [
    {
        "Type": "Property",
        "Path": "/bmc/kepler/EventService/Subscriptions/Snmp/Nmses/${#INDEX}",
        "Interface": "bmc.kepler.EventService.Subscriptions.Snmp.Nms",
        "Source": {
            "Port": "${ReqBody/SnmpTrapNotification/TrapServer[#INDEX]/TrapServerPort}"
        },
        "Foreach": "${ReqBody/SnmpTrapNotification/TrapServer}"
    }
]
```

上述配置例子，请求体中SnmpTrapNotification/TrapServer是一个数组，需要根据数组大小多次访问bmc.kepler.EventService.Subscriptions.Snmp.Nms的属性。

```json
{
    "SnmpTrapNotification": {
        "TrapServer": [
            {
                "TrapServerPort": 3162
            },
            {
                "TrapServerPort": 3163
            },
            {
                "TrapServerPort": 3164
            },
            {
                "TrapServerPort": 3165
            }
        ]
    }
}
```

假设请求体为上述配置，SnmpTrapNotification/TrapServer数组大小配置为4，通过语句`"Foreach": "${ReqBody/SnmpTrapNotification/TrapServer}"`可断定需要设置属性4次（Foreach也可直接配置数字）。第一次时，Path为`/bmc/kepler/EventService/Subscriptions/Snmp/Nmses/1`，数据源为${ReqBody/SnmpTrapNotification/TrapServer[1]/TrapServerPort}
（#INDEX和Foreach配套使用，#INDEX会被替换成迭代时的次数）。

## <span id="reference">五、数据引用</span>

数据引用：某些位置（DEST）需要使用来自外部的数据（SRC），例如响应体需要使用资源协作接口上的数据。
![image.png](../../images/rackmount/5_数据引用.png)
为了简化引用数据，以相同的格式来获取SRC数据，定义规则：**\${var}表示var的取值来自于外部数据SRC**，例如${ReqBody/xxx}表示引用请求体ReqBody中的xxx属性
数据引用得到的数据类型是**JsonObject**，即有序json，基于json-c实现。

### 数据来源

对于数据接口映射配置来说，作为外部数据的有：

* `Uri`: URI包含的动态字段
* `Query`：查询参数
* `ReqBody`：请求体中的数据
* `ReqBodyOriginal`：请求体中的原始数据；ReqBody在校验时会删除异常数据，执行ProcessingFlow配置时获取不到，此时可获取ReqBodyOriginal
* `ReaHeader`: 请求头中的数据
* `Context`: 上下文信息，包含的字段如下：
  * cli：Interface(取值CLI)、UserName、ClientIp、Privilege
  * redfish用户密码鉴权方式：Interface(取值Redfish)、UserName、ClientIp、Privilege、RoleId、AccountId
  * redfish会话鉴权方式：除用户密码鉴权方式的字段外，还额外支持AuthType、Token、SessionId
  * web_backend：Interface(取值WEB)、UserName、ClientIp、Privilege、AccountId、RoleId、AuthType、Token、SessionId
  * snmp：Interface（取值SNMP）、UserName、ClientIp、Privilege
* `ProcessingFlow[].Destination`：从资源协作接口中获取的数据

### 引用位置配置方式

* 1、属性写死

（非数据引用）理想的状态下，大部分引用位置的取值应该都是写死，是一个纯数据。

```json
"RspBody": {
    "Prop": "redfish/v1/Systems/1"
}
```

* 2、简易方式

框架会把${var}替换成外部数据中var的取值，例如var取值为1，Prop的取值则为"redfish/v1/Systems/1"

```json
"RspBody": {
    "Prop" : "redfish/v1/Systems/${var}"
}
```

* 3、Statements方式

很多时候，拿到外部数据之后，并不能直接使用，而是需要做某些操作之后才可以使用。这里定义"${Statements/Prop<font color="red">()</font>}"表示数据配置在Statements.Prop中（注意：Statements/Prop后接一对括号）。Statements.Prop配置的含义为：引用var的数据，经过多个Steps处理后的取值。Statements支持多种类型的配置，具体请参考[数据处理](#process "点击跳转")

```json
"RspBody": {
    "Prop" : "${Statements/Prop()}"
},
"Statements": {
    "Prop": {
        "Input": "${var}"
        "Steps": [ {xxxx} ]
    }
}
```

* 4、模板引擎方式

<纯模板引擎配置>规则待定，当前可用Statements方式配置

```json
"RspBody": {
    "Prop": “<纯模板引擎配置>”
}
```

## <span id="process">六、数据处理</span>

数据处理：拿到数据之后，需要做一些加工操作，才能使用

```json
"Statements": {
    "Prop": {
        "Input": "${var}"
        "Steps": [
            {
                "Type": "xxxx",
                "Formula": "xxxxx"
            },
            {
                "Type": "xxxx",
                "Formula": "xxxxx"
            },
        ]
    }
}
```

**Input**：处理的数据源，取值必须是引用外部数据
**Steps**：数组类型，可配置多个处理逻辑。如图，Input是Steps[1]的输入，Steps[1]的输出为Steps[2]的输入，依次类推；Steps[n]的输出为最终处理结果

**Steps[].Type**：指明数据处理的类型，可参考下列各小节说明
**Steps[].Formula**：数据处理的公式、规则。可嵌套配置数据引用

### Convert

```json
{
    "Type": "Convert",
    "Formula": "StringToNumber"
}
```

数据类型转换，上述样例时将字符串转换为数字类型，例如输入`"12"`,输出为`12`
Formula指明转换的类型，当前支持的取值有：

* `NumberToBool`：数字转换为布尔值（0转换为false，非0转换为true）
* `BoolToNumber`: 布尔值转换为数字（false转换为0，true转换为1）
* `NumberToString`：数字转换为字符串
* `StringToNumber`：字符串转换为数字
* `FloatToInteger`：浮点型整数转换为整数。小数点后不为0，转化为nil，例如输入3.1，输出nil
* `ToHex`：数字转为16进制字符串，字母为大写
* `Tohex`：数字转为16进制字符串，字母为小写

若输入数据类型与Formula声明的转换前类型不匹配时，输出为nil

### Count

```json
{
    "Type": "Count"
}
```

输入数据必须为数组类型，计算该数组大小，无需Formula字段。
例如输入为`["1", "2", "3"]`时，输出为`3`。

### Expand

```json
{
    "Type": "Expand",
    "Formula": "1"
}
```

* 输入数据必须为可访问的**URI字符串**，Formula默认取值"1"。Expand的作用是将URI原地替换为该URI对应的响应体。
  注意：如果Expand的并非预期单独可访问的URI，**不要以/redfish或者/UI/Rest开头**，推荐以/expand或者/bmc/kepler开头，这样此URI在访问时会被nginx直接拦截，不会重定向到接口层业务层。

假如`/redfish/v1/AccountService/Accounts/2`的响应体为

```json
{
    "UserName": "Administrator",
    "RoleId": "Administrator",
    "Locked": false,
}
```

那么输入为`/redfish/v1/AccountService/Accounts/2`时，输出与上述响应体一致。

* 输入数据也可以是**字符串数组**，每个字符串都必须是可访问的URI，Expand操作会将每个字符串都原地替换为该URI对应的响应体。
  假如`/redfish/v1/AccountService/Accounts/3`的响应体为

```json
{
    "UserName": "Admin",
    "RoleId": "Administrator",
    "Locked": false,
}
```

那么输入为
`["/redfish/v1/AccountService/Accounts/2", "/redfish/v1/AccountService/Accounts/3"]`时，输出为：

```json
[
    {
        "UserName": "Administrator",
        "RoleId": "Administrator",
        "Locked": false,
    }，
    {
        "UserName": "Admin",
        "RoleId": "Administrator",
        "Locked": false,
    }
]
```

如果是资源协作接口路径遍历处理，可以添加资源协作接口的Uri，来获取响应数据

```json
{
   "Uri": "/bmc/kepler/AccountService/Accounts/:id",
   "Interfaces": [
      {
        "Type": "Get",
        "RspBody": {
            "UserName": "${ProcessingFlow[1]/Destination/Id}",
            "RoleId": "${ProcessingFlow[1]/Destination/Source}",
            "Locked": "${ProcessingFlow[1]/Destination/Destination}",
        },
        "ProcessingFlow": [
            {
                "Type": "Property",
                "Path": "/bmc/kepler/AccountService/Accounts/${Uri/id}",
                "Interface": "bmc.kepler.AccountService.ManagerAccount",
                "Destination": {
                     "UserName": "UserName",
                     "RoleId": "RoleId",
                     "Locked": "Locked"
                }
            }
         ]
      }
   ]
}
```

* 输入数据也可以是**对象数组**，每个对象只有一个键值对，键必须为`@odata.id`, 值必须为可访问的URI字符串，Expand操作会将每个对象都原地替换为该URI对应的响应体。

### L-Pair

```json
{
    "Type": "L-Pair",
    "Formula": "@odata.id"
}
```

输入必须为数组，Formula可取任意字符串。L-Pair是将Formula作为key，数组元素作为value，拼装成对象，替换原来的元素。
例如输入为`["/redfish/v1/System/Blade1", "/redfish/v1/System/Blade2", "/redfish/v1/System/Blade3"]`，Formula为@odata.id时，输出为：

```json
[
    {"@odata.id": "/redfish/v1/System/Blade1"},
    {"@odata.id": "/redfish/v1/System/Blade2"},
    {"@odata.id": "/redfish/v1/System/Blade3"}
]
```

### Prefix-Add

```json
{
    "Type": "Prefix-Add",
    "Formula": "/redfish/v1/"
}
```

Formula为待增加的前缀字符串

1. 输入为字符串或者数字时，输出为Formula + 输入组合成的字符串
2. 输入为数组时，则为数组每个元素都增加Formula前缀作为输出

以Formula取值/redfish/v1/为例；
输入为`"System"`时，输出为`"/redfish/v1/System"`
输入为`["System/Blade1", "System/Blade2"]`时，输出为`["/redfish/v1/System/1", "/redfish/v1/System/Blade2"]`

### Prefix-Trim

```json
{
    "Type": "Prefix-Trim",
    "Formula": "/bmc/kepler/"
}
```

Prefix-Trim为删除前缀，使用规则与Prefix-Add类似

### Suffix-Add

```json
{
    "Type": "Suffix-Add",
    "Formula": "/Function/1"
}
```

Suffix-Add为增加后缀，Formula为待增加的后缀字符串，以上述配置为例：
输入为`"PCIeCard"`时，输出为`"/PCIeCard/Function/1"`
输入为`["PCIeCard1", "PCIeCard2"]`时，输出为`["/PCIeCard1/Function/1", "/PCIeCard2/Function/1"]`

### Suffix-Trim

```json
{
    "Type": "Suffix-Trim",
    "Formula": "/Function/1"
}
```

Suffix-Trim为删除后缀，使用规则与Suffix-Add类似

### Switch

```json
{
    "Type": "Switch",
    "Formula": [
        {
            "Case": "Administrator",
            "To": 1
        },
        {
            "Case": "root",
            "To": 2
        },
        {
            "Case": null,
            "To": 3
        },
        {
            "To": 0
        }
    ]
}
```

数据替换，使用有点类似C/Java的switch语句，Case为分支，To对应替换的数据（默认带break）；无Case有To配置对应default分支（可配可不配，配的话需放置在最后）。
以上述配置为例：
当输入为`"Administrator"`，输出为`1`；
当输入为`"root"`，输出为`2`；
当输入为`null`或者`nil（空数据）`，输出为`3`；
当输入为`"Admin"`，输出为`0`

### DateFormat

```json
{
    "Type": "DateFormat",
    "Formula": ["%Y-%m-%dT%H:%M:%S", true]
}
```

输入为时间戳（数字或者字符串数字），输出为时间字符串。Formula[1]指定时间格式（可使用的格式符请参考：[格式符参考资料](https://www.jianshu.com/p/76ac11863591)），未配置时或者配置为null时取默认值`"%Y-%m-%dT%H:%M:%S"`；Formula[2]表征是否显示时区，默认值为false。上述例子，当输入为`1`，系统时区为+8区时，输出为`"1970-01-01T08:00:01+08:00"`

### <span id="Process_Script"> Script </span>

```json
{
    "Type": "Script",
    "Formula": "return ReqBody.Option == 'all'"
}
```

Formula的内容是一段Lua脚本，可实现任何复杂逻辑的处理（鉴于此，不推荐与其他处理类型叠加使用）。上述例子，请求体ReqBody的Option为`"all"`时，输出为`true`，否则输出为`false`。
若脚本的内容较长，放置在一行不方便阅读，可将脚本外置在文件中，Formula配置文件名即可，例如取值`get_operate_log.lua`，那么在配置文件的同级目录下需要有文件`script/get_operate_log.lua`。

注：减少Script的使用，接口映射配置的本质是将接口以配置方式实现，而Script事实上就是代码实现。除少部分复杂逻辑无法配置，其他请不要使用Script。

### Plugin

```json
{
    "Type": "Plugin",
    "Formula": "orchestrator.bios.get_registry_version(Uri.systemid)"
}
```

Formula的内容是一个函数，支持入参，入参可使用ReqBody等数据来源。Plugin可实现复杂逻辑的业务处理（不推荐与其他处理类型叠加使用）。
以redfish为例，当前redfish插件的存放目录为`/opt/bmc/apps/redfish/interface_config/plugins`，那么上述配置要求，在存放目录下有文件`orchestrator/bios.lua`，并且文件有函数`get_registry_version
注：减少Plugin的使用，Plugin的本质是抽象函数供接口配置调用，也是代码实现。

### Script和Plugin的区别

Script用于纯逻辑上的处理，Formula配置的是一段Lua脚本或文件名，**不可复用**。
Plugin用于编写跟业务相关的逻辑，Formula配置的是函数调用，需要有配套的函数实现，**可复用**。
Script和Plugin中可使用的数据变量如表格所示：

| 变量  | <div style="width:180pt"> 含义 </div> | <div style="width:70pt"> Script环境变量 </div> | <div style="width:70pt"> Plugin函数入参 </div>  | <div style="width:70pt"> Plugin环境变量 </div>  |
|----|----|:----:|:----:|:----:|
| bus | dbus总线  |   |   | ✔  |
| mdb | mdb自省接口  |   |   | ✔  |
| require | (尽量减少使用)  |  |   | ✔  |
| libroutemapper_utils | 接口映射配置封装的C库  |  |   | ✔  |
| mapper_interface | 接口映射配置封装的lua库  |  |   | ✔  |
| string |   | ✔   |   | ✔  |
| math |   | ✔   |   | ✔  |
| type |   | ✔   |   | ✔  |
| table |   | ✔   |   | ✔  |
| ipairs |   | ✔   |   | ✔  |
| pairs |   | ✔   |   | ✔  |
| next |   | ✔   |   | ✔  |
| pcall |    |   |   | ✔  |
| xpcall |   |   |   | ✔  |
| error |   | ✔  |  | ✔  |
| base_messages | 基础消息定义  | ✔  |  | ✔  |
| custom_messages | 自定义的错误消息  | ✔  |  | ✔  |
| tonumber |   | ✔   |   | ✔  |
| tostring |   | ✔   |   | ✔  |
| cjson | cjson库 | ✔  |   | ✔  |
| null | cjson.null  | ✔  |   | ✔  |
| lua_nil | Lua的nil取值。RspBody属性获取失败会转为null，如果属性不呈现可以用lua_nil | ✔  |   | ✔  |
| Uri | 数据来源  | ✔  |  ✔  |   |
| ReqBody | 数据来源  | ✔  |  ✔  |   |
| Query | 数据来源  | ✔  |  ✔  |   |
| Context | 数据来源  | ✔  |  ✔  |   |
| ProcessingFlow | 数据来源  | ✔  |  ✔  |   |
| Input | Steps[i]的输入，i=1时是Input配置的取值，i>1时是Steps[i-1]的输出  | ✔  |  ✔  |   |

## 七、权限校验（该功能已由框架资源协作接口权限校验承载，非特殊情况无需单独配置）

权限校验（关键字Privilege）指九大用户权限（'UserMgmt', 'BasicSetting', 'KVMMgmt', 'ReadOnly', 'VMMMgmt', 'SecurityMgmt', 'PowerMgmt', 'DiagnoseMgmt', 'ConfigureSelf'）校验，此项配置只用于约束redfish层实现的接口（比如文件上传下载接口等），对于需要同资源协作接口进行交互的接口的权限校验是在资源协作接口侧进行的，则无需配置此项。此配置只支持接口层设置，不能进行属性基本的设置。配置样例如下：

## Privilege

```json
{
    "Uri": "/redfish/v1/AccountService/Accounts",
    "Interfaces": [
        {
            "Type": "POST",
            "Privilege": "BasicSetting"
        }
    ]
}
```

## 八、系统锁定校验

系统锁定校验（关键字LockdownAllow）用于配置系统锁定打开状态下操作是否允许的配置选项，值为true代表操作允许，值为false表示不允许。支持接口级和属性级的配置，支持内层覆盖外层的配置，未配置的默认值为false。配置样例如下：

### LockdownAllow

```json
{
    "Uri": "/redfish/v1/AccountService",
    "Interfaces": [
        {
            "Type": "PATCH",
            "LockdownAllow": false,
            "Properties": {
                "Oem": {
                    "Type": "object",
                    "Properties": {
                        "openUBMC": {
                            "Type": "object",
                            "Properties": {
                                "SystemLockDownEnabled": {
                                    "LockdownAllow": true,
                                },
                                "SecurityBannerEnabled": {
                                    "Type": "boolean",
                                }
                            }
                        }
                    }
                }
            }
        }
    ]
}
```

## 九、批量字段替换

接口映射配置引入`{{var}}`的用法，用连续两对大括号包围的字符串，表示是内部变量。根据在主配置文件`config.json`中配置的映射关系，接口映射配置在初始化接口时将这些内部变量替换为指导的字符串（redfish schema的批量字段替换是在访问接口时）。**替换范围包括mapping_config的json配置文件，script和plugins的lua脚本和插件，以及redfish接口的schema文件**。

### 替换关系配置

在interface_config目录下的主配置文件`config.json`中，有一个`GlobalVariable`属性与上述接口/属性层级定制平级，用于表示当前接口的批量字段替换的替换关系。配置参考如下：

``` json
"GlobalVariable": {
    "OemIdentifier": "openUBMC"
}
```
该配置即表示将接口映射配置中的`{{OemIdentifier}}`替换为`openUBMC`

# 调试方法

在rackmount仓执行`bingo build`构建，构建完成后，在命令行会有类似如下打印：

```
redfish info: package precompilation config file to xxx/config.lua successfully
redfish info: package precompilation mapper file to xxx/mapper.lua successfully
web_backend info: package precompilation config file to xxx/config.lua successfully
web_backend info: package precompilation mapper file to xxx/mapper.lua successfully
cli info: package precompilation route tree file to xxx/ipmcget/route_tree.lua successfully
cli info: package precompilation config file to xxx/ipmcget/config.lua successfully
cli info: package precompilation mapper file to xxx/ipmcget/mapper.lua successfully
cli info: package precompilation route tree file to xxx/ipmcset/route_tree.lua successfully
cli info: package precompilation config file to xxx/ipmcset/config.lua successfully
cli info: package precompilation mapper file to xxx/ipmcset/mapper.lua successfully
snmp info: package precompilation config file to xxx/config.lua successfully
snmp info: package precompilation mapper file to xxx/mapper.lua successfully
```

以上文件对应了`rackmount`仓配置接口预编译生成的映射器加载文件。

| 文件名 | 加载内容说明 |
| --- | --- |
| `config.lua` | 映射配置预编译文件 |
| `mapper.lua` | 文件目录-Uri配置映射文件 |
| `route_tree.lua` | `cli`接口路由树预编译文件 |

调试代码时，

- 仅修改接口下的字段配置，仅需要替换`config.lua`
- 修改了非`cli`接口的Uri层级配置、目录层级等，需要替换`mapper.lua`和`config.lua`
- 修改了`cli`接口的Uri层级配置、目录层级等，需要替换`route_tree.lua`、`mapper.lua`和`config.lua`

替换目标路径参考构建完成后打印的打包文件路径。

由于schema文件的补充可能会在开发者调试完成后进行，我们提供了`schema_check`编译参数，让开发者自由选择是否在本次编译时进行schema检查。执行`bingo build -o schema_check=false`即可跳过schema检查直接出包（映射器检查保证了映射配置的基本正确，不可跳过），默认打开检查。
