github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/util/api.ts (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 /** 12 * This module contains all the REST endpoints for communicating with the admin UI. 13 */ 14 15 import _ from "lodash"; 16 import moment from "moment"; 17 18 import * as protos from "src/js/protos"; 19 import { FixLong } from "src/util/fixLong"; 20 21 export type DatabasesRequestMessage = protos.cockroach.server.serverpb.DatabasesRequest; 22 export type DatabasesResponseMessage = protos.cockroach.server.serverpb.DatabasesResponse; 23 24 export type DatabaseDetailsRequestMessage = protos.cockroach.server.serverpb.DatabaseDetailsRequest; 25 export type DatabaseDetailsResponseMessage = protos.cockroach.server.serverpb.DatabaseDetailsResponse; 26 27 export type TableDetailsRequestMessage = protos.cockroach.server.serverpb.TableDetailsRequest; 28 export type TableDetailsResponseMessage = protos.cockroach.server.serverpb.TableDetailsResponse; 29 30 export type EventsRequestMessage = protos.cockroach.server.serverpb.EventsRequest; 31 export type EventsResponseMessage = protos.cockroach.server.serverpb.EventsResponse; 32 33 export type LocationsRequestMessage = protos.cockroach.server.serverpb.LocationsRequest; 34 export type LocationsResponseMessage = protos.cockroach.server.serverpb.LocationsResponse; 35 36 export type NodesRequestMessage = protos.cockroach.server.serverpb.NodesRequest; 37 export type NodesResponseMessage = protos.cockroach.server.serverpb.NodesResponse; 38 39 export type GetUIDataRequestMessage = protos.cockroach.server.serverpb.GetUIDataRequest; 40 export type GetUIDataResponseMessage = protos.cockroach.server.serverpb.GetUIDataResponse; 41 42 export type SetUIDataRequestMessage = protos.cockroach.server.serverpb.SetUIDataRequest; 43 export type SetUIDataResponseMessage = protos.cockroach.server.serverpb.SetUIDataResponse; 44 45 export type RaftDebugRequestMessage = protos.cockroach.server.serverpb.RaftDebugRequest; 46 export type RaftDebugResponseMessage = protos.cockroach.server.serverpb.RaftDebugResponse; 47 48 export type TimeSeriesQueryRequestMessage = protos.cockroach.ts.tspb.TimeSeriesQueryRequest; 49 export type TimeSeriesQueryResponseMessage = protos.cockroach.ts.tspb.TimeSeriesQueryResponse; 50 51 export type HealthRequestMessage = protos.cockroach.server.serverpb.HealthRequest; 52 export type HealthResponseMessage = protos.cockroach.server.serverpb.HealthResponse; 53 54 export type ClusterRequestMessage = protos.cockroach.server.serverpb.ClusterRequest; 55 export type ClusterResponseMessage = protos.cockroach.server.serverpb.ClusterResponse; 56 57 export type TableStatsRequestMessage = protos.cockroach.server.serverpb.TableStatsRequest; 58 export type TableStatsResponseMessage = protos.cockroach.server.serverpb.TableStatsResponse; 59 60 export type NonTableStatsRequestMessage = protos.cockroach.server.serverpb.NonTableStatsRequest; 61 export type NonTableStatsResponseMessage = protos.cockroach.server.serverpb.NonTableStatsResponse; 62 63 export type LogsRequestMessage = protos.cockroach.server.serverpb.LogsRequest; 64 export type LogEntriesResponseMessage = protos.cockroach.server.serverpb.LogEntriesResponse; 65 66 export type LivenessRequestMessage = protos.cockroach.server.serverpb.LivenessRequest; 67 export type LivenessResponseMessage = protos.cockroach.server.serverpb.LivenessResponse; 68 69 export type JobsRequestMessage = protos.cockroach.server.serverpb.JobsRequest; 70 export type JobsResponseMessage = protos.cockroach.server.serverpb.JobsResponse; 71 72 export type QueryPlanRequestMessage = protos.cockroach.server.serverpb.QueryPlanRequest; 73 export type QueryPlanResponseMessage = protos.cockroach.server.serverpb.QueryPlanResponse; 74 75 export type ProblemRangesRequestMessage = protos.cockroach.server.serverpb.ProblemRangesRequest; 76 export type ProblemRangesResponseMessage = protos.cockroach.server.serverpb.ProblemRangesResponse; 77 78 export type CertificatesRequestMessage = protos.cockroach.server.serverpb.CertificatesRequest; 79 export type CertificatesResponseMessage = protos.cockroach.server.serverpb.CertificatesResponse; 80 81 export type RangeRequestMessage = protos.cockroach.server.serverpb.RangeRequest; 82 export type RangeResponseMessage = protos.cockroach.server.serverpb.RangeResponse; 83 84 export type AllocatorRangeRequestMessage = protos.cockroach.server.serverpb.AllocatorRangeRequest; 85 export type AllocatorRangeResponseMessage = protos.cockroach.server.serverpb.AllocatorRangeResponse; 86 87 export type RangeLogRequestMessage = 88 protos.cockroach.server.serverpb.RangeLogRequest; 89 export type RangeLogResponseMessage = 90 protos.cockroach.server.serverpb.RangeLogResponse; 91 92 export type SettingsRequestMessage = protos.cockroach.server.serverpb.SettingsRequest; 93 export type SettingsResponseMessage = protos.cockroach.server.serverpb.SettingsResponse; 94 95 export type UserLoginRequestMessage = protos.cockroach.server.serverpb.UserLoginRequest; 96 export type UserLoginResponseMessage = protos.cockroach.server.serverpb.UserLoginResponse; 97 98 export type StoresRequestMessage = protos.cockroach.server.serverpb.StoresRequest; 99 export type StoresResponseMessage = protos.cockroach.server.serverpb.StoresResponse; 100 101 export type UserLogoutResponseMessage = protos.cockroach.server.serverpb.UserLogoutResponse; 102 103 export type StatementsResponseMessage = protos.cockroach.server.serverpb.StatementsResponse; 104 105 export type DataDistributionResponseMessage = protos.cockroach.server.serverpb.DataDistributionResponse; 106 107 export type EnqueueRangeRequestMessage = protos.cockroach.server.serverpb.EnqueueRangeRequest; 108 export type EnqueueRangeResponseMessage = protos.cockroach.server.serverpb.EnqueueRangeResponse; 109 110 export type MetricMetadataRequestMessage = protos.cockroach.server.serverpb.MetricMetadataRequest; 111 export type MetricMetadataResponseMessage = protos.cockroach.server.serverpb.MetricMetadataResponse; 112 113 export type StatementDiagnosticsReportsRequestMessage = protos.cockroach.server.serverpb.StatementDiagnosticsReportsRequest; 114 export type StatementDiagnosticsReportsResponseMessage = protos.cockroach.server.serverpb.StatementDiagnosticsReportsResponse; 115 116 export type CreateStatementDiagnosticsReportRequestMessage = protos.cockroach.server.serverpb.CreateStatementDiagnosticsReportRequest; 117 export type CreateStatementDiagnosticsReportResponseMessage = protos.cockroach.server.serverpb.CreateStatementDiagnosticsReportResponse; 118 119 export type StatementDiagnosticsRequestMessage = protos.cockroach.server.serverpb.StatementDiagnosticsRequest; 120 export type StatementDiagnosticsResponseMessage = protos.cockroach.server.serverpb.StatementDiagnosticsResponse; 121 122 // API constants 123 124 export const API_PREFIX = "_admin/v1"; 125 export const STATUS_PREFIX = "_status"; 126 127 // HELPER FUNCTIONS 128 129 // Inspired by https://github.com/github/fetch/issues/175 130 // 131 // withTimeout wraps a promise in a timeout. 132 export function withTimeout<T>(promise: Promise<T>, timeout?: moment.Duration): Promise<T> { 133 if (timeout) { 134 return new Promise<T>((resolve, reject) => { 135 setTimeout(() => reject(new Error(`Promise timed out after ${timeout.asMilliseconds()} ms`)), timeout.asMilliseconds()); 136 promise.then(resolve, reject); 137 }); 138 } else { 139 return promise; 140 } 141 } 142 143 interface TRequest { 144 constructor: { 145 encode(message: TRequest, writer?: protobuf.Writer): protobuf.Writer; 146 }; 147 toObject(): { [k: string]: any }; 148 } 149 150 export function toArrayBuffer(encodedRequest: Uint8Array): ArrayBuffer { 151 return encodedRequest.buffer.slice(encodedRequest.byteOffset, encodedRequest.byteOffset + encodedRequest.byteLength); 152 } 153 154 export class RequestError extends Error { 155 status: number; 156 constructor(statusText: string, status: number) { 157 super(statusText); 158 this.status = status; 159 this.name = "RequestError"; 160 } 161 } 162 163 // timeoutFetch is a wrapper around fetch that provides timeout and protocol 164 // buffer marshaling and unmarshalling. 165 // 166 // This function is intended for use with generated protocol buffers. In 167 // particular, TResponse$Properties is a generated interface that describes the JSON 168 // representation of the response, while TRequest and TResponse 169 // are generated interfaces which are implemented by the protocol buffer 170 // objects themselves. TResponseBuilder is an interface implemented by 171 // the builder objects provided at runtime by protobuf.js. 172 function timeoutFetch<TResponse$Properties, TResponse, TResponseBuilder extends { 173 new(properties?: TResponse$Properties): TResponse 174 encode(message: TResponse$Properties, writer?: protobuf.Writer): protobuf.Writer 175 decode(reader: (protobuf.Reader | Uint8Array), length?: number): TResponse; 176 }>(builder: TResponseBuilder, url: string, req: TRequest = null, timeout: moment.Duration = moment.duration(30, "s")): Promise<TResponse> { 177 const params: RequestInit = { 178 headers: { 179 "Accept": "application/x-protobuf", 180 "Content-Type": "application/x-protobuf", 181 "Grpc-Timeout": timeout ? timeout.asMilliseconds() + "m" : undefined, 182 }, 183 credentials: "same-origin", 184 }; 185 186 if (req) { 187 const encodedRequest = req.constructor.encode(req).finish(); 188 params.method = "POST"; 189 params.body = toArrayBuffer(encodedRequest); 190 } 191 192 return withTimeout(fetch(url, params), timeout).then((res) => { 193 if (!res.ok) { 194 throw new RequestError(res.statusText, res.status); 195 } 196 return res.arrayBuffer().then((buffer) => builder.decode(new Uint8Array(buffer))); 197 }); 198 } 199 200 export type APIRequestFn<TReq, TResponse> = (req: TReq, timeout?: moment.Duration) => Promise<TResponse>; 201 202 // propsToQueryString is a helper function that converts a set of object 203 // properties to a query string 204 // - keys with null or undefined values will be skipped 205 // - non-string values will be toString'd 206 export function propsToQueryString(props: { [k: string]: any }) { 207 return _.compact(_.map(props, (v: any, k: string) => !_.isNull(v) && !_.isUndefined(v) ? `${encodeURIComponent(k)}=${encodeURIComponent(v.toString())}` : null)).join("&"); 208 } 209 /** 210 * ENDPOINTS 211 */ 212 213 const serverpb = protos.cockroach.server.serverpb; 214 const tspb = protos.cockroach.ts.tspb; 215 216 // getDatabaseList gets a list of all database names 217 export function getDatabaseList(_req: DatabasesRequestMessage, timeout?: moment.Duration): Promise<DatabasesResponseMessage> { 218 return timeoutFetch(serverpb.DatabasesResponse, `${API_PREFIX}/databases`, null, timeout); 219 } 220 221 // getDatabaseDetails gets details for a specific database 222 export function getDatabaseDetails(req: DatabaseDetailsRequestMessage, timeout?: moment.Duration): Promise<DatabaseDetailsResponseMessage> { 223 return timeoutFetch(serverpb.DatabaseDetailsResponse, `${API_PREFIX}/databases/${req.database}`, null, timeout); 224 } 225 226 // getTableDetails gets details for a specific table 227 export function getTableDetails(req: TableDetailsRequestMessage, timeout?: moment.Duration): Promise<TableDetailsResponseMessage> { 228 return timeoutFetch(serverpb.TableDetailsResponse, `${API_PREFIX}/databases/${req.database}/tables/${req.table}`, null, timeout); 229 } 230 231 // getUIData gets UI data 232 export function getUIData(req: GetUIDataRequestMessage, timeout?: moment.Duration): Promise<GetUIDataResponseMessage> { 233 const queryString = _.map(req.keys, (key) => "keys=" + encodeURIComponent(key)).join("&"); 234 return timeoutFetch(serverpb.GetUIDataResponse, `${API_PREFIX}/uidata?${queryString}`, null, timeout); 235 } 236 237 // setUIData sets UI data 238 export function setUIData(req: SetUIDataRequestMessage, timeout?: moment.Duration): Promise<SetUIDataResponseMessage> { 239 return timeoutFetch(serverpb.SetUIDataResponse, `${API_PREFIX}/uidata`, req as any, timeout); 240 } 241 242 // getEvents gets event data 243 export function getEvents(req: EventsRequestMessage, timeout?: moment.Duration): Promise<EventsResponseMessage> { 244 const queryString = propsToQueryString(_.pick(req, ["type", "target_id"])); 245 return timeoutFetch(serverpb.EventsResponse, `${API_PREFIX}/events?unredacted_events=true&${queryString}`, null, timeout); 246 } 247 248 export function getLocations(_req: LocationsRequestMessage, timeout?: moment.Duration): Promise<LocationsResponseMessage> { 249 return timeoutFetch(serverpb.LocationsResponse, `${API_PREFIX}/locations`, null, timeout); 250 } 251 252 // getNodes gets node data 253 export function getNodes(_req: NodesRequestMessage, timeout?: moment.Duration): Promise<NodesResponseMessage> { 254 return timeoutFetch(serverpb.NodesResponse, `${STATUS_PREFIX}/nodes`, null, timeout); 255 } 256 257 export function raftDebug(_req: RaftDebugRequestMessage): Promise<RaftDebugResponseMessage> { 258 // NB: raftDebug intentionally does not pass a timeout through. 259 return timeoutFetch(serverpb.RaftDebugResponse, `${STATUS_PREFIX}/raft`); 260 } 261 262 // queryTimeSeries queries for time series data 263 export function queryTimeSeries(req: TimeSeriesQueryRequestMessage, timeout?: moment.Duration): Promise<TimeSeriesQueryResponseMessage> { 264 return timeoutFetch(tspb.TimeSeriesQueryResponse, `ts/query`, req as any, timeout); 265 } 266 267 // getHealth gets health data 268 export function getHealth(_req: HealthRequestMessage, timeout?: moment.Duration): Promise<HealthResponseMessage> { 269 return timeoutFetch(serverpb.HealthResponse, `${API_PREFIX}/health`, null, timeout); 270 } 271 272 export function getJobs(req: JobsRequestMessage, timeout?: moment.Duration): Promise<JobsResponseMessage> { 273 return timeoutFetch(serverpb.JobsResponse, `${API_PREFIX}/jobs?status=${req.status}&type=${req.type}&limit=${req.limit}`, null, timeout); 274 } 275 276 // getCluster gets info about the cluster 277 export function getCluster(_req: ClusterRequestMessage, timeout?: moment.Duration): Promise<ClusterResponseMessage> { 278 return timeoutFetch(serverpb.ClusterResponse, `${API_PREFIX}/cluster`, null, timeout); 279 } 280 281 // getTableStats gets detailed stats about the current table 282 export function getTableStats(req: TableStatsRequestMessage, timeout?: moment.Duration): Promise<TableStatsResponseMessage> { 283 return timeoutFetch(serverpb.TableStatsResponse, `${API_PREFIX}/databases/${req.database}/tables/${req.table}/stats`, null, timeout); 284 } 285 286 // getNonTableStats gets detailed stats about non-table data ranges on the 287 // cluster. 288 export function getNonTableStats(_req: NonTableStatsRequestMessage, timeout?: moment.Duration): Promise<NonTableStatsResponseMessage> { 289 return timeoutFetch(serverpb.NonTableStatsResponse, `${API_PREFIX}/nontablestats`, null, timeout); 290 } 291 292 // TODO (maxlang): add filtering 293 // getLogs gets the logs for a specific node 294 export function getLogs(req: LogsRequestMessage, timeout?: moment.Duration): Promise<LogEntriesResponseMessage> { 295 return timeoutFetch(serverpb.LogEntriesResponse, `${STATUS_PREFIX}/logs/${req.node_id}`, null, timeout); 296 } 297 298 // getLiveness gets cluster liveness information from the current node. 299 export function getLiveness(_req: LivenessRequestMessage, timeout?: moment.Duration): Promise<LivenessResponseMessage> { 300 return timeoutFetch(serverpb.LivenessResponse, `${API_PREFIX}/liveness`, null, timeout); 301 } 302 303 // getQueryPlan gets physical query plan JSON for the provided query. 304 export function getQueryPlan(req: QueryPlanRequestMessage, timeout?: moment.Duration): Promise<QueryPlanResponseMessage> { 305 return timeoutFetch(serverpb.QueryPlanResponse, `${API_PREFIX}/queryplan?query=${encodeURIComponent(req.query)}`, null, timeout); 306 } 307 308 // getProblemRanges returns information needed by the problem range debug page. 309 export function getProblemRanges(req: ProblemRangesRequestMessage, timeout?: moment.Duration): Promise<ProblemRangesResponseMessage> { 310 const query = (!_.isEmpty(req.node_id)) ? `?node_id=${req.node_id}` : ""; 311 return timeoutFetch(serverpb.ProblemRangesResponse, `${STATUS_PREFIX}/problemranges${query}`, null, timeout); 312 } 313 314 // getCertificates returns information about a node's certificates. 315 export function getCertificates(req: CertificatesRequestMessage, timeout?: moment.Duration): Promise<CertificatesResponseMessage> { 316 return timeoutFetch(serverpb.CertificatesResponse, `${STATUS_PREFIX}/certificates/${req.node_id}`, null, timeout); 317 } 318 319 // getRange returns information about a range form all nodes. 320 export function getRange(req: RangeRequestMessage, timeout?: moment.Duration): Promise<RangeResponseMessage> { 321 return timeoutFetch(serverpb.RangeResponse, `${STATUS_PREFIX}/range/${req.range_id}`, null, timeout); 322 } 323 324 // getAllocatorRange returns simulated Allocator info for the requested range 325 export function getAllocatorRange(req: AllocatorRangeRequestMessage, timeout?: moment.Duration): Promise<AllocatorRangeResponseMessage> { 326 return timeoutFetch(serverpb.AllocatorRangeResponse, `${STATUS_PREFIX}/allocator/range/${req.range_id}`, null, timeout); 327 } 328 329 // getRangeLog returns the range log for all ranges or a specific range 330 export function getRangeLog( 331 req: RangeLogRequestMessage, 332 timeout?: moment.Duration, 333 ): Promise<RangeLogResponseMessage> { 334 const rangeID = FixLong(req.range_id); 335 const rangeIDQuery = (rangeID.eq(0)) ? "" : `/${rangeID.toString()}`; 336 const limit = (!_.isNil(req.limit)) ? `?limit=${req.limit}` : ""; 337 return timeoutFetch( 338 serverpb.RangeLogResponse, 339 `${API_PREFIX}/rangelog${rangeIDQuery}${limit}`, 340 null, 341 timeout, 342 ); 343 } 344 345 // getSettings gets all cluster settings. We request unredacted_values, which will attempt 346 // to obtain all values from the server. The server will only accept to do so if 347 // the user also happens to have admin privilege. 348 export function getSettings(_req: SettingsRequestMessage, timeout?: moment.Duration): Promise<SettingsResponseMessage> { 349 return timeoutFetch(serverpb.SettingsResponse, `${API_PREFIX}/settings?unredacted_values=true`, null, timeout); 350 } 351 352 export function userLogin(req: UserLoginRequestMessage, timeout?: moment.Duration): Promise<UserLoginResponseMessage> { 353 return timeoutFetch(serverpb.UserLoginResponse, `login`, req as any, timeout); 354 } 355 356 export function userLogout(timeout?: moment.Duration): Promise<UserLogoutResponseMessage> { 357 return timeoutFetch(serverpb.UserLogoutResponse, `logout`, null, timeout); 358 } 359 360 // getStores returns information about a node's stores. 361 export function getStores(req: StoresRequestMessage, timeout?: moment.Duration): Promise<StoresResponseMessage> { 362 return timeoutFetch(serverpb.StoresResponse, `${STATUS_PREFIX}/stores/${req.node_id}`, null, timeout); 363 } 364 365 // getStatements returns statements the cluster has recently executed, and some stats about them. 366 export function getStatements(timeout?: moment.Duration): Promise<StatementsResponseMessage> { 367 return timeoutFetch(serverpb.StatementsResponse, `${STATUS_PREFIX}/statements`, null, timeout); 368 } 369 370 export function getStatementDiagnosticsReports(timeout?: moment.Duration): Promise<StatementDiagnosticsReportsResponseMessage> { 371 return timeoutFetch(serverpb.StatementDiagnosticsReportsResponse, `${STATUS_PREFIX}/stmtdiagreports`, null, timeout); 372 } 373 374 export function createStatementDiagnosticsReport(req: CreateStatementDiagnosticsReportRequestMessage, timeout?: moment.Duration): Promise<CreateStatementDiagnosticsReportResponseMessage> { 375 return timeoutFetch(serverpb.CreateStatementDiagnosticsReportResponse, `${STATUS_PREFIX}/stmtdiagreports`, req as any, timeout); 376 } 377 378 export function getStatementDiagnostics(req: StatementDiagnosticsRequestMessage, timeout?: moment.Duration): Promise<StatementDiagnosticsResponseMessage> { 379 return timeoutFetch(serverpb.StatementDiagnosticsResponse, `${STATUS_PREFIX}/stmtdiag/${req.statement_diagnostics_id}`, null, timeout); 380 } 381 382 // getDataDistribution returns information about how replicas are distributed across nodes. 383 export function getDataDistribution(timeout?: moment.Duration): Promise<DataDistributionResponseMessage> { 384 return timeoutFetch(serverpb.DataDistributionResponse, `${API_PREFIX}/data_distribution`, null, timeout); 385 } 386 387 export function enqueueRange(req: EnqueueRangeRequestMessage, timeout?: moment.Duration): Promise<EnqueueRangeResponseMessage> { 388 return timeoutFetch(serverpb.EnqueueRangeResponse, `${API_PREFIX}/enqueue_range`, req as any, timeout); 389 } 390 391 export function getAllMetricMetadata(_req: MetricMetadataRequestMessage = null, timeout?: moment.Duration): Promise<MetricMetadataResponseMessage> { 392 return timeoutFetch(serverpb.MetricMetadataResponse, `${API_PREFIX}/metricmetadata`, null, timeout); 393 }