最近由于工作原因又開始搗鼓OceanBase,OceanBase云平臺(OCP)提供了強大的管理和監控功能,而且對外開放API接口,可以將部分監控整合到自己的平臺,所以寫了個Java調用OCP API的demo做為自己的技術儲備,也想分享給大家。也因為最近對Eclipse Vertx和異步編程非常興趣,所以案例使用是Vertx的WebClient,而非Apache HttpClient, 不過,不管用什么庫,原理是相似的。
先介紹下環境,我這邊用的是OCP企業版4.2.2,OB不同版本之間差異還是很大的,其它版本不一定適用。參考的是官方文檔(https://www.oceanbase.com/docs/common-ocp-1000000000585101):云平臺OCP --> “參考指南” --> “API參考”。
客戶端鑒權
OCP開放API的客戶端鑒權,支持使用AK/SK和HTTP Basic兩種認證模式。
HTTP Basic認證模式
HTTP Basic通過用戶名和密碼進行鑒權,相對比較簡單,但因為是明文傳輸,并不安全,特別是使用http時,用戶名和密碼可以在傳輸過程中被抓包解析出來。對于可控的內網,也可以做為便捷的方法。以下是實現代碼(為了文檔更好的閱讀,完整的代碼放在文章的資源中https://download.csdn.net/download/Li_Xiang_996/89517863?spm=1001.2101.3001.9499)。
// 先構建'"用戶名":"用戶密碼"字符串, 然后將字符進行Base64編碼,就可以得到authorization。
byte[] strContents = (userName.trim() + ":" + password.trim()).getBytes();
String base64Contents = Base64.getUrlEncoder().encodeToString(strContents);
String authorization = "Basic " + base64Contents;
// 然后調用API時候,將authorization放到HTTP請求消息頭Authorization中即可。
client.something.putHeader("Authorization", authorization).send();
API(AK/SK)認證模式
OB得官方文檔有詳細介紹如何使用,可以參考。一些題外話,OB的官方文檔相較之前有非常巨大的進步,值得點贊。
首先需要在OCP中創建(申請) AK/SK,登錄OCP,右邊導航點開"系統管理" ->“用戶管理”。用戶列表中選擇一個用戶或者創建一個新用戶,需要注意的是,當獲取了用戶的AccessKey,也就獲得了該用戶的權限。
選擇用戶,進入對應的用戶的設置頁面,在“AccessKey”部分,點擊"一鍵創建 AccessKey",即可獲取該用戶的AK/SK。后續我們就可以通過AK/SK來進行API訪問。
認證原理大致是: 客戶端將(將要)對API的請求按照指定的格式拼接成一個消息(字符串),然后用申請到的AK對應的SK,通過HMACSHA1算法對消息進行加簽(生成消息的哈希串)。
消息體格式
String message = "POST\n" + //HTTP請求方法, 大寫英文。包括: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS和TRACE。"186974DB33A090A16D3E2CA35F547B56\n" + // 請求體, md5編碼, 可以為空(如使用HTTP GET時候), 當換行(\n)符號不能省略。"application/json\n" + // 消息體的類型。OCP統一使用application/json類型的消息體。"Fri, 5 Jul 2024 06:49:37 GMT\n" + // 請求發起時間, 其遵循RFC1123格式, 必須是GMT時區?"10.100.6.161:8080\n" + // OCP服務器地址+端口"x-ocp-date:Fri, 5 Jul 2024 06:49:37 GMT\n" + //"/api/v2/compute/idcs" //API請求的路徑, 以及請求的查詢參數, 請求參數可以為空, 如果有多個必須按參數名(升序排序), 否則無法通過驗證
hmacSha1加簽
byte[] hash = hmacSha1(accessKeySecret, message.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getEncoder().encodeToString(hash);
String authorization = "OCP-ACCESS-KEY-HMACSHA1 " + accessKey + ":" + signature;// 然后調用API時候,將authorization放到HTTP請求消息頭Authorization中即可。
client.something.putHeader("Authorization", authorization).send();
服務器端行為沒看過代碼, 純屬個人猜測, 看個樂吧:服務器端(數據庫中)保存了用戶AK/SK表, 服務器根據接收到請求的Authorization, 可獲取客戶端的AK,就可以查詢到關聯用戶,即可進行權限檢查(是否有權執行); 進一步取出對應的SK,根據服務器獲取的客戶端請求(通過客戶端相同的格式)構建消息體, 并使用SK和相同的HASH算法(HMACSHA1)對消息體進行加簽(計算HASH),如果兩者簽名相同, 那么這通過驗證,返回結果,類似于證書的校驗過程。
調用OCP API
以查詢告警事件列表為例,請求路徑"GET /api/v2/alarm/alarms", 請求參數我們指定每頁顯示5條記錄(size=5), 顯示第一頁(page=1)。
final String baseUri = "/api/v2/alarm/alarms";
Map<String, String> queryParams = new HashMap<>();
queryParams.put("page", "1");
queryParams.put("size", "5");
String uri = AuthorizationBuilder.buildUri(baseUri, queryParams); // buildUri方法會拼接baseUri于查詢參數, 參數按參數名升序組織。 請求時間, 取當前時間, RFC1123格式, 必須為GMT時區
ZonedDateTime requestTime = ZonedDateTime.now(ZoneId.of("GMT"));
String rfc1123RequestTime = DateTimeFormatter.RFC_1123_DATE_TIME.format(requestTime);
根據請求構建authorization, 與上面描述的過程一樣,具體看源代碼。
String authorization = AuthorizationBuilder.newApiAuthorizationBuilder(accessKey, accessKeySecret).setRequestTimestamp(requestTime).setHost(server).setUri(uri).build();
WebClientOptions options = new WebClientOptions().setConnectTimeout(3000);
Vertx vertx = Vertx.vertx();
WebClient client = WebClient.create(vertx, options);
Future<HttpResponse<Buffer>> future = client.get(port, host, uri).putHeader("content-type", AuthorizationBuilder.CONTENT_TYPE_JSON).putHeader("Authorization", authorization).putHeader("x-ocp-date", rfc1123RequestTime).send();
future .onSuccess(AlarmsApi::printResult).onFailure(e -> e.printStackTrace());//解析并打印API返回結果
public static void printResult(HttpResponse<Buffer> response) {JsonObject jsonBody = response.bodyAsJsonObject();boolean successful = jsonBody.getBoolean("successful");if (successful) {JsonObject data = jsonBody.getJsonObject("data");JsonArray contents = data.getJsonArray("contents");System.out.println("###### 告警事件列表(Top 5) ######");for (int i = 0; i < contents.size(); i++) {JsonObject alert = contents.getJsonObject(i);System.out.println("------- " + alert.getLong("id") + " -------" + "\nname: " + alert.getString("name") + "\nalarmType: " + alert.getString("alarmType") + "\nstatus: " + alert.getString("status")+ "\nresolvedAt: " + alert.getString("resolvedAt") + "\ntarget: " + alert.getString("target") + "\ndescription: " + alert.getString("description") + "\n");}} else {System.out.println("API返回失敗! status = " + jsonBody.getInteger("status"));System.out.println(jsonBody.encodePrettily());}
}
執行效果如下:
##### 告警事件列表(Top 5) ######
------- 1000211 -------
name: 服務器CPU平均load1超限
alarmType: ob_host_load1_per_cpu_over_threshold
status: Inactive
resolvedAt: 2024-07-04T21:10:56Z
target: alarm_template_id=0:host=192.168.100.21
description: 集群:metadb,主機:192.168.100.21,告警:服務器CPU平均load1超限。CPU平均load1值 1.552 超過 1.5。------- 1000210 -------
name: 服務器CPU平均load1超限
alarmType: ob_host_load1_per_cpu_over_threshold
status: Inactive
resolvedAt: 2024-07-04T20:56:06Z
target: alarm_template_id=0:host=192.168.100.21
description: 集群:metadb,主機:192.168.100.21,告警:服務器CPU平均load1超限。CPU平均load1值 1.737 超過 1.5。
...
更復雜的調用
OCP API除了查詢還有管理功能(增刪改),以"主機模塊"的機型相關API為例。
- 查詢機型信息列表: GET /api/v2/compute/hostTypes
- 添加主機機型信息: POST /api/v2/compute/hostTypes
- 刪除機型信息: DELETE /api/v2/compute/hostTypes/{hostTypeId}
Demo代碼:
新增機型HuaWei_Kunpeng
String postBody = "{\"name\": \"HuaWei_Kunpeng\", \"description\": \"128C 512GB\"}";
String baseUri = "/api/v2/compute/hostTypes";
ZonedDateTime requestTime = ZonedDateTime.now(ZoneId.of("GMT"));
String rfc1123RequestTime = DateTimeFormatter.RFC_1123_DATE_TIME.format(requestTime);String authorization = AuthorizationBuilder.newApiAuthorizationBuilder(accessKey, accessKeySecret).setHttpMethod(HTTP_POST).setRequestTimestamp(requestTime).setHost(server).setUri(uri).setRequestBody(postBody).build();
WebClientOptions options = new WebClientOptions().setConnectTimeout(3000);
WebClient client = WebClient.create(vertx, options);Future<HttpResponse<Buffer>> futrue = client.post(port, host, uri).putHeader("content-type", CONTENT_TYPE_JSON).putHeader("Authorization", authorization).putHeader("x-ocp-date", rfc1123RequestTime).sendBuffer(Buffer.buffer(postBody)); 刪除機型id = 1000010
int hostTypeId = 1000010;
String uri = "/api/v2/compute/hostTypes/" + hostTypeId;
ZonedDateTime requestTime = ZonedDateTime.now(ZoneId.of("GMT"));
String rfc1123RequestTime = DateTimeFormatter.RFC_1123_DATE_TIME.format(requestTime);String authorization = AuthorizationBuilder.newApiAuthorizationBuilder().setHttpMethod(HTTP_DELETE).setAccessKey(accessKey).setAccessKeySecret(accessKeySecret).setRequestTimestamp(requestTime).setHost(server).setUri(uri).build();
WebClientOptions options = new WebClientOptions().setConnectTimeout(3000);
WebClient client = WebClient.create(vertx, options);
Future<HttpResponse<Buffer>> future = client.delete(port, host, uri).putHeader("content-type", CONTENT_TYPE_JSON).putHeader("Authorization", authorization).putHeader("x-ocp-date", rfc1123RequestTime).send();
依葫蘆畫瓢,一通百通。在調用返回信息里面有一個“traceId”,我們可以通過這個traceId在服務器端找到對應的日志信息。對于API失敗調試有一定的輔助作用。例如,執行刪除機型的返回如下:
==> 刪除新增的主機類型(1000010) ...
主機類型刪除成功。
{"duration" : 43,"server" : "b587654b4d","status" : 200,"successful" : true,"timestamp" : "2024-07-05T15:34:08.576+08:00","traceId" : "80070430b9814a99" ==>
}
對應OCP的日志信息:
### OCP日志位置
[admin@oat-ocp ocp]$ pwd
/home/admin/logs/ocp### 通過traceId為關鍵字搜索。
[admin@oat-ocp ocp]$ grep "80070430b9814a99" ocp-server.*
ocp-server.0.out:2024-07-05 15:34:08.555 INFO 9 --- [http-nio-0.0.0.0-8080-exec-2,80070430b9814a99,ce0c2accd19f] c.o.o.s.c.trace.RequestTracingAspect : API: [DELETE /api/v2/compute/hostTypes/1000010?null, client=10.100.6.16, traceId=80070430b9814a99, method=NoDataResponse com.oceanbase.ocp.server.common.controller.ComputeController.deleteHostType(Long), args=1000010,]
ocp-server.0.out:2024-07-05 15:34:08.562 INFO 9 --- [http-nio-0.0.0.0-8080-exec-2,80070430b9814a99,ce0c2accd19f] c.o.o.c.h.service.HostTypeServiceImpl : Deleted hostType: 1000010
ocp-server.0.out:2024-07-05 15:34:08.576 INFO 9 --- [http-nio-0.0.0.0-8080-exec-2,80070430b9814a99,ce0c2accd19f] c.o.o.s.c.trace.RequestTracingAspect : API OK: [DELETE /api/v2/compute/hostTypes/1000010 client=10.100.6.16, traceId=80070430b9814a99, duration=43 ms]