From 9c8ecf878027f730ee141305f5e9f80ca9b499f3 Mon Sep 17 00:00:00 2001 From: Simeon Romanov Date: Wed, 15 Nov 2023 08:57:11 +0300 Subject: [PATCH 1/5] add Home Page --- api/openapi.yaml | 839 ----------------------------------------------- 1 file changed, 839 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index 3552fa2a..8b137891 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1,840 +1 @@ -openapi: 3.0.3 -info: - title: bob-management - description: Bob Management GUI - contact: - name: Romanov Simeon ArchArcheoss@proton.me - license: - name: '' - version: 0.0.0 -paths: - /api/v1/disks/count: - get: - tags: - - services::api - summary: Returns count of Physical Disks per status - description: Returns count of Physical Disks per status - operationId: get_disks_count - responses: - '200': - description: Returns a list with count of physical disks per status - content: - application/json: - schema: - $ref: '#/components/schemas/DiskCount' - '401': - description: Unauthorized - security: - - api_key: [] - /api/v1/login: - post: - tags: - - services::auth - summary: Login to a BOB cluster - description: | - Login to a BOB cluster - # Errors - This function can return the following errors - - 1. [`StatusCode::BAD_REQUEST`] - The function failed to parse hostname of the request - - 2. [`StatusCode::NOT_FOUND`] - The client was unable to reach the host - - 3. [`StatusCode::UNAUTHORIZED`] - The client couldn't authorize on the host - operationId: login - parameters: - - name: hostname - in: path - description: Address to connect to - required: true - schema: - $ref: '#/components/schemas/Hostname' - - name: credentials - in: path - description: '[Optional] Credentials used for BOB authentication' - required: true - schema: - allOf: - - $ref: '#/components/schemas/Credentials' - nullable: true - requestBody: - description: '' - content: - application/json: - schema: - $ref: '#/components/schemas/BobConnectionData' - required: true - responses: - '200': - description: Successful authorization - '400': - description: Bad Hostname - '401': - description: Bad Credentials - '404': - description: Can't reach specified hostname - /api/v1/logout: - post: - tags: - - services::auth - operationId: logout - responses: - '200': - description: Logged out - /api/v1/nodes/count: - get: - tags: - - services::api - summary: Get Nodes count per Status - description: Get Nodes count per Status - operationId: get_nodes_count - responses: - '200': - description: Node count list per status - content: - application/json: - schema: - $ref: '#/components/schemas/NodeCount' - '401': - description: Unauthorized - security: - - api_key: [] - /api/v1/nodes/rps: - get: - tags: - - services::api - summary: Returns Total RPS on cluster - description: Returns Total RPS on cluster - operationId: get_rps - responses: - '200': - description: RPS list per operation on all nodes - content: - application/json: - schema: - $ref: '#/components/schemas/RPS' - '401': - description: Unauthorized - security: - - api_key: [] - /api/v1/nodes/space: - get: - tags: - - services::api - summary: Return inforamtion about space on cluster - description: Return inforamtion about space on cluster - operationId: get_space - responses: - '200': - description: Cluster Space Information - content: - application/json: - schema: - $ref: '#/components/schemas/SpaceInfo' - '401': - description: Unauthorized - security: - - api_key: [] -components: - schemas: - BobConnectionData: - type: object - description: Data needed to connect to a BOB cluster - required: - - hostname - properties: - credentials: - allOf: - - $ref: '#/components/schemas/Credentials' - nullable: true - hostname: - $ref: '#/components/schemas/Hostname' - example: - credentials: - login: archeoss - password: '12345' - hostname: 0.0.0.0:7000 - Credentials: - type: object - description: Optional auth credentials for a BOB cluster - required: - - login - - password - properties: - login: - type: string - description: Login used during auth - password: - type: string - description: Password used during auth - example: - login: archeoss - password: '12345' - DiskCount: - type: object - description: Disk count by their status - required: - - good - - bad - - offline - properties: - bad: - type: integer - format: int64 - minimum: 0 - good: - type: integer - format: int64 - minimum: 0 - offline: - type: integer - format: int64 - minimum: 0 - example: - bad: 0 - good: 0 - offline: 0 - DiskProblem: - type: string - description: Defines kind of problem on disk - enum: - - freeSpaceRunningOut - DiskStatus: - oneOf: - - type: object - required: - - status - properties: - status: - type: string - enum: - - good - - type: object - required: - - status - - problems - properties: - problems: - type: array - items: - $ref: '#/components/schemas/DiskProblem' - status: - type: string - enum: - - bad - - type: object - required: - - status - properties: - status: - type: string - enum: - - offline - description: |- - Defines disk status - - Variant - Disk Status - Content - List of problems on disk. 'null' if status != 'bad' - discriminator: - propertyName: status - DiskStatusName: - type: string - description: Defines disk status names - enum: - - good - - bad - - offline - Hostname: - type: string - MetricsEntryModel: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - MetricsSnapshotModel: - type: object - required: - - metrics - properties: - metrics: - type: object - additionalProperties: - $ref: '#/components/schemas/MetricsEntryModel' - NodeConfiguration: - type: object - properties: - blob_file_name_prefix: - type: string - nullable: true - root_dir_name: - type: string - nullable: true - NodeCount: - type: object - description: Node count by their status - required: - - good - - bad - - offline - properties: - bad: - type: integer - format: int64 - minimum: 0 - good: - type: integer - format: int64 - minimum: 0 - offline: - type: integer - format: int64 - minimum: 0 - example: - bad: 0 - good: 0 - offline: 0 - NodeProblem: - type: string - description: Defines kind of problem on Node - enum: - - aliensExists - - corruptedExists - - freeSpaceRunningOut - - virtualMemLargerThanRAM - - highCPULoad - NodeStatus: - oneOf: - - type: object - required: - - status - properties: - status: - type: string - enum: - - good - - type: object - required: - - status - - problems - properties: - problems: - type: array - items: - $ref: '#/components/schemas/NodeProblem' - status: - type: string - enum: - - bad - - type: object - required: - - status - properties: - status: - type: string - enum: - - offline - description: |- - Defines status of node - - Variants - Node status - - Content - List of problems on node. 'null' if status != 'bad' - discriminator: - propertyName: status - NodeStatusName: - type: string - description: Defines node status names - enum: - - good - - bad - - offline - Operation: - type: string - description: Types of operations on BOB cluster - enum: - - put - - get - - exist - - delete - RPS: - type: object - description: Requests per second by operation - required: - - put - - get - - exist - - delete - properties: - delete: - type: integer - format: int64 - minimum: 0 - exist: - type: integer - format: int64 - minimum: 0 - get: - type: integer - format: int64 - minimum: 0 - put: - type: integer - format: int64 - minimum: 0 - example: - delete: 0 - exist: 0 - get: 0 - put: 0 - RawMetricEntry: - type: string - enum: - - cluster_grinder.get_count_rate - - cluster_grinder.put_count_rate - - cluster_grinder.exist_count_rate - - cluster_grinder.delete_count_rate - - pearl.exist_count_rate - - pearl.get_count_rate - - pearl.put_count_rate - - pearl.delete_count_rate - - backend.alien_count - - backend.corrupted_blob_count - - hardware.bob_virtual_ram - - hardware.total_ram - - hardware.used_ram - - hardware.bob_cpu_load - - hardware.free_space - - hardware.total_space - - hardware.descr_amount - ReplicaProblem: - type: string - description: Reasons why Replica is offline - enum: - - nodeUnavailable - - diskUnavailable - ReplicaStatus: - oneOf: - - type: object - required: - - status - properties: - status: - type: string - enum: - - good - - type: object - required: - - status - - problems - properties: - problems: - type: array - items: - $ref: '#/components/schemas/ReplicaProblem' - status: - type: string - enum: - - offline - description: |- - Replica status. It's either good or offline with the reasons why it is offline - - Variants - Replica status - - Content - List of problems on replica. 'null' if status != 'offline' - discriminator: - propertyName: status - SpaceInfo: - type: object - description: Disk space information in bytes - required: - - total_disk - - free_disk - - used_disk - - occupied_disk - properties: - free_disk: - type: integer - format: int64 - description: The amount of free disk space - minimum: 0 - occupied_disk: - type: integer - format: int64 - description: Disk space occupied only by BOB. occupied_disk should be lesser than used_disk - minimum: 0 - total_disk: - type: integer - format: int64 - description: Total disk space amount - minimum: 0 - used_disk: - type: integer - format: int64 - description: Used disk space amount - minimum: 0 - TypedMetrics: - type: object - description: Raw metrics information - required: - - cluster_grinder.get_count_rate - - cluster_grinder.put_count_rate - - cluster_grinder.exist_count_rate - - cluster_grinder.delete_count_rate - - pearl.exist_count_rate - - pearl.get_count_rate - - pearl.put_count_rate - - pearl.delete_count_rate - - backend.alien_count - - backend.corrupted_blob_count - - hardware.bob_virtual_ram - - hardware.total_ram - - hardware.used_ram - - hardware.bob_cpu_load - - hardware.free_space - - hardware.total_space - - hardware.descr_amount - properties: - backend.alien_count: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - backend.corrupted_blob_count: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - cluster_grinder.delete_count_rate: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - cluster_grinder.exist_count_rate: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - cluster_grinder.get_count_rate: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - cluster_grinder.put_count_rate: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - hardware.bob_cpu_load: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - hardware.bob_virtual_ram: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - hardware.descr_amount: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - hardware.free_space: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - hardware.total_ram: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - hardware.total_space: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - hardware.used_ram: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - pearl.delete_count_rate: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - pearl.exist_count_rate: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - pearl.get_count_rate: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - pearl.put_count_rate: - type: object - required: - - value - - timestamp - properties: - timestamp: - type: integer - format: int64 - minimum: 0 - value: - type: integer - format: int64 - minimum: 0 - example: - backend.alien_count: - timestamp: 0 - value: 0 - backend.corrupted_blob_count: - timestamp: 0 - value: 0 - cluster_grinder.delete_count_rate: - timestamp: 0 - value: 0 - cluster_grinder.exist_count_rate: - timestamp: 0 - value: 0 - cluster_grinder.get_count_rate: - timestamp: 0 - value: 0 - cluster_grinder.put_count_rate: - timestamp: 0 - value: 0 - hardware.bob_cpu_load: - timestamp: 0 - value: 0 - hardware.bob_virtual_ram: - timestamp: 0 - value: 0 - hardware.descr_amount: - timestamp: 0 - value: 0 - hardware.free_space: - timestamp: 0 - value: 0 - hardware.total_ram: - timestamp: 0 - value: 0 - hardware.total_space: - timestamp: 0 - value: 0 - hardware.used_ram: - timestamp: 0 - value: 0 - pearl.delete_count_rate: - timestamp: 0 - value: 0 - pearl.exist_count_rate: - timestamp: 0 - value: 0 - pearl.get_count_rate: - timestamp: 0 - value: 0 - pearl.put_count_rate: - timestamp: 0 - value: 0 - VDiskStatus: - oneOf: - - type: object - required: - - status - properties: - status: - type: string - enum: - - good - - type: object - required: - - status - properties: - status: - type: string - enum: - - bad - - type: object - required: - - status - properties: - status: - type: string - enum: - - offline - description: |- - Virtual disk status. - - Variants - Virtual Disk status - status == 'bad' when at least one of its replicas has problems - example: - status: good - securitySchemes: - api_key: - type: apiKey - in: header - name: bob_apikey -tags: -- name: bob - description: BOB management API From 9f574985c1c101ef336caac7c4c437d9923f4f83 Mon Sep 17 00:00:00 2001 From: Simeon Romanov Date: Wed, 15 Nov 2023 08:57:11 +0300 Subject: [PATCH 2/5] Add NodeList Page --- CHANGELOG.md | 2 + api/openapi.yaml | 839 ++++++++++++++++++ backend/src/connector/dto.rs | 8 + backend/src/connector/mod.rs | 12 +- backend/src/lib.rs | 11 + backend/src/models/api.rs | 169 +++- backend/src/services/api.rs | 275 +++++- backend/src/services/methods.rs | 111 ++- backend/src/services/mod.rs | 23 +- frontend/bindings.rs | 39 + .../components/nodeList/nodeList.module.css | 27 + frontend/src/components/nodeList/nodeList.tsx | 131 +++ .../components/nodeTable/nodeTable.module.css | 31 + .../src/components/nodeTable/nodeTable.tsx | 183 ++++ frontend/src/pages/nodelist/index.astro | 7 +- frontend/src/types/common.ts | 26 + frontend/src/types/data.d.ts | 11 + frontend/src/types/rust.d.ts | 75 +- 18 files changed, 1922 insertions(+), 58 deletions(-) create mode 100644 frontend/src/components/nodeList/nodeList.module.css create mode 100644 frontend/src/components/nodeList/nodeList.tsx create mode 100644 frontend/src/components/nodeTable/nodeTable.module.css create mode 100644 frontend/src/components/nodeTable/nodeTable.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 941aeef4..12716910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,4 +14,6 @@ Bob Management GUI changelog - Login Page, backend (#16) - Login Page, frontend (#17) - Home page, backend (#18) +- Node list page, backend (#19) - Home page, frontend (#22) +- Node list page, frontend (#23) diff --git a/api/openapi.yaml b/api/openapi.yaml index 8b137891..ebcc52a0 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1 +1,840 @@ +openapi: 3.0.3 +info: + title: bob-management + description: Bob Management GUI + contact: + name: Romanov Simeon ArchArcheoss@proton.me + license: + name: "" + version: 0.0.0 +paths: + /api/v1/disks/count: + get: + tags: + - services::api + summary: Returns count of Physical Disks per status + description: Returns count of Physical Disks per status + operationId: get_disks_count + responses: + "200": + description: Returns a list with count of physical disks per status + content: + application/json: + schema: + $ref: "#/components/schemas/DiskCount" + "401": + description: Unauthorized + security: + - api_key: [] + /api/v1/login: + post: + tags: + - services::auth + summary: Login to a BOB cluster + description: | + Login to a BOB cluster + # Errors + This function can return the following errors + + 1. [`StatusCode::BAD_REQUEST`] + The function failed to parse hostname of the request + + 2. [`StatusCode::NOT_FOUND`] + The client was unable to reach the host + + 3. [`StatusCode::UNAUTHORIZED`] + The client couldn't authorize on the host + operationId: login + parameters: + - name: hostname + in: path + description: Address to connect to + required: true + schema: + $ref: "#/components/schemas/Hostname" + - name: credentials + in: path + description: "[Optional] Credentials used for BOB authentication" + required: true + schema: + allOf: + - $ref: "#/components/schemas/Credentials" + nullable: true + requestBody: + description: "" + content: + application/json: + schema: + $ref: "#/components/schemas/BobConnectionData" + required: true + responses: + "200": + description: Successful authorization + "400": + description: Bad Hostname + "401": + description: Bad Credentials + "404": + description: Can't reach specified hostname + /api/v1/logout: + post: + tags: + - services::auth + operationId: logout + responses: + "200": + description: Logged out + /api/v1/nodes/count: + get: + tags: + - services::api + summary: Get Nodes count per Status + description: Get Nodes count per Status + operationId: get_nodes_count + responses: + "200": + description: Node count list per status + content: + application/json: + schema: + $ref: "#/components/schemas/NodeCount" + "401": + description: Unauthorized + security: + - api_key: [] + /api/v1/nodes/rps: + get: + tags: + - services::api + summary: Returns Total RPS on cluster + description: Returns Total RPS on cluster + operationId: get_rps + responses: + "200": + description: RPS list per operation on all nodes + content: + application/json: + schema: + $ref: "#/components/schemas/RPS" + "401": + description: Unauthorized + security: + - api_key: [] + /api/v1/nodes/space: + get: + tags: + - services::api + summary: Return inforamtion about space on cluster + description: Return inforamtion about space on cluster + operationId: get_space + responses: + "200": + description: Cluster Space Information + content: + application/json: + schema: + $ref: "#/components/schemas/SpaceInfo" + "401": + description: Unauthorized + security: + - api_key: [] +components: + schemas: + BobConnectionData: + type: object + description: Data needed to connect to a BOB cluster + required: + - hostname + properties: + credentials: + allOf: + - $ref: "#/components/schemas/Credentials" + nullable: true + hostname: + $ref: "#/components/schemas/Hostname" + example: + credentials: + login: archeoss + password: "12345" + hostname: 0.0.0.0:7000 + Credentials: + type: object + description: Optional auth credentials for a BOB cluster + required: + - login + - password + properties: + login: + type: string + description: Login used during auth + password: + type: string + description: Password used during auth + example: + login: archeoss + password: "12345" + DiskCount: + type: object + description: Disk count by their status + required: + - good + - bad + - offline + properties: + bad: + type: integer + format: int64 + minimum: 0 + good: + type: integer + format: int64 + minimum: 0 + offline: + type: integer + format: int64 + minimum: 0 + example: + bad: 0 + good: 0 + offline: 0 + DiskProblem: + type: string + description: Defines kind of problem on disk + enum: + - freeSpaceRunningOut + DiskStatus: + oneOf: + - type: object + required: + - status + properties: + status: + type: string + enum: + - good + - type: object + required: + - status + - problems + properties: + problems: + type: array + items: + $ref: "#/components/schemas/DiskProblem" + status: + type: string + enum: + - bad + - type: object + required: + - status + properties: + status: + type: string + enum: + - offline + description: |- + Defines disk status + + Variant - Disk Status + Content - List of problems on disk. 'null' if status != 'bad' + discriminator: + propertyName: status + DiskStatusName: + type: string + description: Defines disk status names + enum: + - good + - bad + - offline + Hostname: + type: string + MetricsEntryModel: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + MetricsSnapshotModel: + type: object + required: + - metrics + properties: + metrics: + type: object + additionalProperties: + $ref: "#/components/schemas/MetricsEntryModel" + NodeConfiguration: + type: object + properties: + blob_file_name_prefix: + type: string + nullable: true + root_dir_name: + type: string + nullable: true + NodeCount: + type: object + description: Node count by their status + required: + - good + - bad + - offline + properties: + bad: + type: integer + format: int64 + minimum: 0 + good: + type: integer + format: int64 + minimum: 0 + offline: + type: integer + format: int64 + minimum: 0 + example: + bad: 0 + good: 0 + offline: 0 + NodeProblem: + type: string + description: Defines kind of problem on Node + enum: + - aliensExists + - corruptedExists + - freeSpaceRunningOut + - virtualMemLargerThanRAM + - highCPULoad + NodeStatus: + oneOf: + - type: object + required: + - status + properties: + status: + type: string + enum: + - good + - type: object + required: + - status + - problems + properties: + problems: + type: array + items: + $ref: "#/components/schemas/NodeProblem" + status: + type: string + enum: + - bad + - type: object + required: + - status + properties: + status: + type: string + enum: + - offline + description: |- + Defines status of node + + Variants - Node status + + Content - List of problems on node. 'null' if status != 'bad' + discriminator: + propertyName: status + NodeStatusName: + type: string + description: Defines node status names + enum: + - good + - bad + - offline + Operation: + type: string + description: Types of operations on BOB cluster + enum: + - put + - get + - exist + - delete + RPS: + type: object + description: Requests per second by operation + required: + - put + - get + - exist + - delete + properties: + delete: + type: integer + format: int64 + minimum: 0 + exist: + type: integer + format: int64 + minimum: 0 + get: + type: integer + format: int64 + minimum: 0 + put: + type: integer + format: int64 + minimum: 0 + example: + delete: 0 + exist: 0 + get: 0 + put: 0 + RawMetricEntry: + type: string + enum: + - cluster_grinder.get_count_rate + - cluster_grinder.put_count_rate + - cluster_grinder.exist_count_rate + - cluster_grinder.delete_count_rate + - pearl.exist_count_rate + - pearl.get_count_rate + - pearl.put_count_rate + - pearl.delete_count_rate + - backend.alien_count + - backend.corrupted_blob_count + - hardware.bob_virtual_ram + - hardware.total_ram + - hardware.used_ram + - hardware.bob_cpu_load + - hardware.free_space + - hardware.total_space + - hardware.descr_amount + ReplicaProblem: + type: string + description: Reasons why Replica is offline + enum: + - nodeUnavailable + - diskUnavailable + ReplicaStatus: + oneOf: + - type: object + required: + - status + properties: + status: + type: string + enum: + - good + - type: object + required: + - status + - problems + properties: + problems: + type: array + items: + $ref: "#/components/schemas/ReplicaProblem" + status: + type: string + enum: + - offline + description: |- + Replica status. It's either good or offline with the reasons why it is offline + + Variants - Replica status + + Content - List of problems on replica. 'null' if status != 'offline' + discriminator: + propertyName: status + SpaceInfo: + type: object + description: Disk space information in bytes + required: + - total_disk + - free_disk + - used_disk + - occupied_disk + properties: + free_disk: + type: integer + format: int64 + description: The amount of free disk space + minimum: 0 + occupied_disk: + type: integer + format: int64 + description: Disk space occupied only by BOB. occupied_disk should be lesser than used_disk + minimum: 0 + total_disk: + type: integer + format: int64 + description: Total disk space amount + minimum: 0 + used_disk: + type: integer + format: int64 + description: Used disk space amount + minimum: 0 + TypedMetrics: + type: object + description: Raw metrics information + required: + - cluster_grinder.get_count_rate + - cluster_grinder.put_count_rate + - cluster_grinder.exist_count_rate + - cluster_grinder.delete_count_rate + - pearl.exist_count_rate + - pearl.get_count_rate + - pearl.put_count_rate + - pearl.delete_count_rate + - backend.alien_count + - backend.corrupted_blob_count + - hardware.bob_virtual_ram + - hardware.total_ram + - hardware.used_ram + - hardware.bob_cpu_load + - hardware.free_space + - hardware.total_space + - hardware.descr_amount + properties: + backend.alien_count: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + backend.corrupted_blob_count: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + cluster_grinder.delete_count_rate: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + cluster_grinder.exist_count_rate: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + cluster_grinder.get_count_rate: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + cluster_grinder.put_count_rate: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + hardware.bob_cpu_load: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + hardware.bob_virtual_ram: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + hardware.descr_amount: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + hardware.free_space: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + hardware.total_ram: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + hardware.total_space: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + hardware.used_ram: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + pearl.delete_count_rate: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + pearl.exist_count_rate: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + pearl.get_count_rate: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + pearl.put_count_rate: + type: object + required: + - value + - timestamp + properties: + timestamp: + type: integer + format: int64 + minimum: 0 + value: + type: integer + format: int64 + minimum: 0 + example: + backend.alien_count: + timestamp: 0 + value: 0 + backend.corrupted_blob_count: + timestamp: 0 + value: 0 + cluster_grinder.delete_count_rate: + timestamp: 0 + value: 0 + cluster_grinder.exist_count_rate: + timestamp: 0 + value: 0 + cluster_grinder.get_count_rate: + timestamp: 0 + value: 0 + cluster_grinder.put_count_rate: + timestamp: 0 + value: 0 + hardware.bob_cpu_load: + timestamp: 0 + value: 0 + hardware.bob_virtual_ram: + timestamp: 0 + value: 0 + hardware.descr_amount: + timestamp: 0 + value: 0 + hardware.free_space: + timestamp: 0 + value: 0 + hardware.total_ram: + timestamp: 0 + value: 0 + hardware.total_space: + timestamp: 0 + value: 0 + hardware.used_ram: + timestamp: 0 + value: 0 + pearl.delete_count_rate: + timestamp: 0 + value: 0 + pearl.exist_count_rate: + timestamp: 0 + value: 0 + pearl.get_count_rate: + timestamp: 0 + value: 0 + pearl.put_count_rate: + timestamp: 0 + value: 0 + VDiskStatus: + oneOf: + - type: object + required: + - status + properties: + status: + type: string + enum: + - good + - type: object + required: + - status + properties: + status: + type: string + enum: + - bad + - type: object + required: + - status + properties: + status: + type: string + enum: + - offline + description: |- + Virtual disk status. + + Variants - Virtual Disk status + status == 'bad' when at least one of its replicas has problems + example: + status: good + securitySchemes: + api_key: + type: apiKey + in: header + name: bob_apikey +tags: + - name: bob + description: BOB management API diff --git a/backend/src/connector/dto.rs b/backend/src/connector/dto.rs index 9ccc1f34..c8eaae3c 100644 --- a/backend/src/connector/dto.rs +++ b/backend/src/connector/dto.rs @@ -409,6 +409,8 @@ impl Ord for MetricsEntryModel { impl Eq for MetricsEntryModel {} #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] +#[cfg_attr(all(feature = "swagger", debug_assertions), schema(as = dto::Node))] pub struct Node { #[serde(rename = "name")] pub name: String, @@ -418,6 +420,7 @@ pub struct Node { #[serde(rename = "vdisks")] #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(all(feature = "swagger", debug_assertions), schema(value_type = Option>))] pub vdisks: Option>, } @@ -697,6 +700,8 @@ impl std::str::FromStr for Partition { #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] +#[cfg_attr(all(feature = "swagger", debug_assertions), schema(as = dto::Replica))] pub struct Replica { #[serde(rename = "node")] pub node: String, @@ -896,12 +901,15 @@ impl std::str::FromStr for StatusExt { #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] +#[cfg_attr(all(feature = "swagger", debug_assertions), schema(as = dto::VDisk))] pub struct VDisk { #[serde(rename = "id")] pub id: i32, #[serde(rename = "replicas")] #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(all(feature = "swagger", debug_assertions), schema(value_type = Option>))] pub replicas: Option>, } diff --git a/backend/src/connector/mod.rs b/backend/src/connector/mod.rs index 7bd7e695..f0462bb8 100644 --- a/backend/src/connector/mod.rs +++ b/backend/src/connector/mod.rs @@ -10,8 +10,9 @@ mod prelude { headers::{authorization::Credentials, Authorization, HeaderMapExt}, http::{HeaderName, HeaderValue}, }; - pub use futures::StreamExt; - pub use hyper::{service::Service, Response, Uri}; + pub use futures::{Stream, StreamExt}; + pub use hyper::{body::Bytes, service::Service, Response, Uri}; + pub use std::collections::BTreeMap; pub use std::{ str::FromStr, sync::Arc, @@ -105,7 +106,7 @@ pub struct BobClient + Send main: Arc, /// Clients for all known nodes - cluster: HashMap>, + cluster: BTreeMap>, context_marker: PhantomData, } @@ -168,7 +169,7 @@ impl + Send + Sync> .attach_printable(format!("Hostname: {}", hostname.to_string()))? }; - let cluster: HashMap> = nodes + let cluster: BTreeMap> = nodes .iter() .filter_map(|node| HttpClient::from_node(node, &bob_data.hostname, context.clone())) .collect(); @@ -177,6 +178,7 @@ impl + Send + Sync> id: Uuid::new_v4(), hostname: bob_data.hostname, main: Arc::new(client.with_context(context)), + // main: Arc::new(client), cluster, context_marker: PhantomData, }) @@ -275,7 +277,7 @@ impl + Send + Sync> } #[must_use] - pub const fn cluster_with_addr(&self) -> &HashMap> { + pub const fn cluster_with_addr(&self) -> &BTreeMap> { &self.cluster } diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 89a20739..21fb1523 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -41,25 +41,36 @@ impl Modify for SecurityAddon { services::api::get_nodes_count, services::api::get_rps, services::api::get_space, + services::api::raw_metrics_by_node, + services::api::raw_configuration_by_node, + services::api::get_node_info, + services::api::get_nodes_list, ), components( schemas(models::shared::Credentials, models::shared::Hostname, models::shared::BobConnectionData, + models::api::Disk, models::api::DiskProblem, models::api::DiskStatus, models::api::DiskStatusName, models::api::DiskCount, + models::api::NodeInfo, models::api::NodeProblem, models::api::NodeStatus, models::api::NodeStatusName, models::api::NodeCount, + models::api::Replica, models::api::ReplicaProblem, models::api::ReplicaStatus, models::api::SpaceInfo, + models::api::VDisk, models::api::VDiskStatus, models::api::Operation, models::api::RPS, models::api::RawMetricEntry, models::api::TypedMetrics, + connector::dto::Node, + connector::dto::VDisk, + connector::dto::Replica, connector::dto::MetricsEntryModel, connector::dto::MetricsSnapshotModel, connector::dto::NodeConfiguration diff --git a/backend/src/models/api.rs b/backend/src/models/api.rs index 8acc77d1..a2eb1a33 100644 --- a/backend/src/models/api.rs +++ b/backend/src/models/api.rs @@ -8,8 +8,33 @@ pub const DEFAULT_MIN_FREE_SPACE_PERCENTAGE: f64 = 0.1; /// Connection Data pub use crate::models::shared::{BobConnectionData, Credentials}; +/// Physical disk definition +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] +#[serde(rename_all = "camelCase")] +#[tsync] +pub struct Disk { + /// Disk name + pub name: String, + + /// Disk path + pub path: String, + + /// Disk status + // #[serde(flatten)] + pub status: DiskStatus, + + #[serde(rename = "totalSpace")] + pub total_space: u64, + + #[serde(rename = "usedSpace")] + pub used_space: u64, + + pub iops: u64, +} + /// Defines kind of problem on disk -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Serialize, Hash)] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] #[tsync] pub enum DiskProblem { @@ -21,7 +46,7 @@ pub enum DiskProblem { /// /// Variant - Disk Status /// Content - List of problems on disk. 'null' if status != 'bad' -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Hash)] #[serde(tag = "status", content = "problems")] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] #[tsync] @@ -29,7 +54,7 @@ pub enum DiskStatus { #[serde(rename = "good")] Good, #[serde(rename = "bad")] - Bad(Vec), + Bad { problems: Vec }, #[serde(rename = "offline")] Offline, } @@ -43,7 +68,9 @@ impl DiskStatus { / space.total_disk_space_bytes as f64) < DEFAULT_MIN_FREE_SPACE_PERCENTAGE { - Self::Bad(vec![DiskProblem::FreeSpaceRunningOut]) + Self::Bad { + problems: vec![DiskProblem::FreeSpaceRunningOut], + } } else { Self::Good } @@ -54,7 +81,7 @@ impl DiskStatus { } /// Defines disk status names -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash, EnumIter)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Hash, EnumIter)] #[serde(rename_all = "camelCase")] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] #[tsync] @@ -64,8 +91,37 @@ pub enum DiskStatusName { Offline, } +#[derive(Debug, Clone, Serialize)] +#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] +#[serde(rename_all = "camelCase")] +#[tsync] +pub struct NodeInfo { + pub name: String, + + pub hostname: String, + + pub vdisks: Vec, + // #[serde(flatten)] + pub status: NodeStatus, + + #[serde(rename = "rps")] + #[serde(skip_serializing_if = "Option::is_none")] + pub rps: Option, + + #[serde(rename = "alienCount")] + #[serde(skip_serializing_if = "Option::is_none")] + pub alien_count: Option, + + #[serde(rename = "corruptedCount")] + #[serde(skip_serializing_if = "Option::is_none")] + pub corrupted_count: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub space: Option, +} + /// Defines kind of problem on Node -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Serialize, Hash)] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] #[tsync] pub enum NodeProblem { @@ -108,10 +164,8 @@ impl NodeProblem { if node_metrics[RawMetricEntry::HardwareBobCpuLoad].value >= max_cpu { res.push(Self::HighCPULoad); } - if (1. - - (node_metrics[RawMetricEntry::HardwareTotalSpace].value - - node_metrics[RawMetricEntry::HardwareFreeSpace].value) as f64 - / node_metrics[RawMetricEntry::HardwareTotalSpace].value as f64) + if (node_metrics[RawMetricEntry::HardwareFreeSpace].value as f64 + / node_metrics[RawMetricEntry::HardwareTotalSpace].value as f64) < min_free_space_perc { res.push(Self::FreeSpaceRunningOut); @@ -131,7 +185,7 @@ impl NodeProblem { /// Variants - Node status /// /// Content - List of problems on node. 'null' if status != 'bad' -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Hash)] #[serde(tag = "status", content = "problems")] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] #[tsync] @@ -139,7 +193,7 @@ pub enum NodeStatus { #[serde(rename = "good")] Good, #[serde(rename = "bad")] - Bad(Vec), + Bad { problems: Vec }, #[serde(rename = "offline")] Offline, } @@ -150,7 +204,7 @@ impl NodeStatus { if problems.is_empty() { Self::Good } else { - Self::Bad(problems) + Self::Bad { problems } } } } @@ -172,7 +226,7 @@ impl TypedMetrics { } /// Defines node status names -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash, EnumIter)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Hash, EnumIter)] #[serde(rename_all = "camelCase")] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] #[tsync] @@ -182,8 +236,24 @@ pub enum NodeStatusName { Offline, } +/// [`VDisk`]'s replicas +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] +#[serde(rename_all = "camelCase")] +#[tsync] +pub struct Replica { + pub node: String, + + pub disk: String, + + pub path: String, + + // #[serde(flatten)] + pub status: ReplicaStatus, +} + /// Reasons why Replica is offline -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Serialize)] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] #[tsync] pub enum ReplicaProblem { @@ -198,7 +268,7 @@ pub enum ReplicaProblem { /// Variants - Replica status /// /// Content - List of problems on replica. 'null' if status != 'offline' -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] #[serde(tag = "status", content = "problems")] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] #[tsync] @@ -206,11 +276,11 @@ pub enum ReplicaStatus { #[serde(rename = "good")] Good, #[serde(rename = "offline")] - Offline(Vec), + Offline { problems: Vec }, } /// Disk space information in bytes -#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize)] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] #[tsync] pub struct SpaceInfo { @@ -227,16 +297,61 @@ pub struct SpaceInfo { pub occupied_disk: u64, } +impl From for SpaceInfo { + fn from(space: dto::SpaceInfo) -> Self { + Self { + total_disk: space.total_disk_space_bytes, + free_disk: space.total_disk_space_bytes - space.used_disk_space_bytes, + used_disk: space.used_disk_space_bytes, + occupied_disk: space.occupied_disk_space_bytes, + } + } +} + +impl AddAssign for SpaceInfo { + fn add_assign(&mut self, rhs: Self) { + self.total_disk = rhs.total_disk; + self.free_disk = rhs.free_disk; + self.used_disk = rhs.used_disk; + self.occupied_disk = rhs.occupied_disk; + } +} + +impl Add for SpaceInfo { + type Output = Self; + + fn add(mut self, rhs: Self) -> Self::Output { + self += rhs; + + self + } +} + +/// Virtual disk Component +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] +pub struct VDisk { + pub id: u64, + + // #[serde(flatten)] + pub status: VDiskStatus, + + #[serde(rename = "partitionCount")] + pub partition_count: u64, + + pub replicas: Vec, +} + /// Virtual disk status. /// /// Variants - Virtual Disk status /// status == 'bad' when at least one of its replicas has problems -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] #[serde(tag = "status")] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] -#[cfg_attr(all(feature = "swagger", debug_assertions), - schema(example = json!({"status": "good"})))] #[tsync] +// #[cfg_attr(all(feature = "swagger", debug_assertions), +// schema(example = json!({"status": "good"})))] pub enum VDiskStatus { #[serde(rename = "good")] Good, @@ -247,7 +362,7 @@ pub enum VDiskStatus { } /// Types of operations on BOB cluster -#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, EnumIter)] +#[derive(Debug, Clone, Serialize, Hash, Eq, PartialEq, PartialOrd, Ord, EnumIter)] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] #[serde(rename_all = "camelCase")] #[tsync] @@ -258,7 +373,7 @@ pub enum Operation { Delete, } -#[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, EnumIter)] +#[derive(Clone, Debug, Serialize, Hash, Eq, PartialEq, PartialOrd, Ord, EnumIter)] #[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))] #[tsync] pub enum RawMetricEntry { @@ -434,8 +549,6 @@ impl< } } -// pub type TypedMetrics = TypedMap; - impl std::ops::Index for TypedMap { type Output = V; @@ -486,7 +599,11 @@ impl From for TypedMetrics { let mut value = value.metrics; for key in RawMetricEntry::iter() { let value = value - .remove(&serde_json::to_string(&key).expect("infallible")) + .remove( + serde_json::to_string(&key) + .expect("infallible") + .trim_matches('"'), + ) .unwrap_or_default(); map.insert(key, value); } diff --git a/backend/src/services/api.rs b/backend/src/services/api.rs index cd04edaa..fee57f4d 100644 --- a/backend/src/services/api.rs +++ b/backend/src/services/api.rs @@ -1,5 +1,9 @@ use super::prelude::*; +// TODO: For methods, that requires information from all nodes (/disks/count, /nodes/rps, etc.), +// think of better method of returning info +// another thread that constantly updates info in period and cache the results? + /// Returns count of Physical Disks per status #[cfg_attr(all(feature = "swagger", debug_assertions), utoipa::path( @@ -58,7 +62,7 @@ pub async fn get_disks_count(Extension(client): Extension) -> Jso match DiskStatus::from_space_info(&space, &disk.name) { DiskStatus::Good => count[DiskStatusName::Good] += 1, DiskStatus::Offline => count[DiskStatusName::Offline] += 1, - DiskStatus::Bad(_) => count[DiskStatusName::Bad] += 1, + DiskStatus::Bad { .. } => count[DiskStatusName::Bad] += 1, } }); count[DiskStatusName::Offline] = (disks.len() - active) as u64; @@ -200,28 +204,257 @@ pub async fn get_space(Extension(client): Extension) -> Json bool { - node_metrics[RawMetricEntry::BackendAlienCount].value != 0 - || node_metrics[RawMetricEntry::BackendCorruptedBlobCount].value != 0 - || node_metrics[RawMetricEntry::HardwareBobCpuLoad].value >= DEFAULT_MAX_CPU - || (1. - - (node_metrics[RawMetricEntry::HardwareTotalSpace].value - - node_metrics[RawMetricEntry::HardwareFreeSpace].value) as f64 - / node_metrics[RawMetricEntry::HardwareTotalSpace].value as f64) - < DEFAULT_MIN_FREE_SPACE_PERCENTAGE - || node_metrics[RawMetricEntry::HardwareBobVirtualRam] - > node_metrics[RawMetricEntry::HardwareTotalRam] +/// Returns simple list of all known nodes +/// +/// # Errors +/// +/// This function will return an error if a call to the primary node will fail +#[cfg_attr(feature = "swagger", utoipa::path( + get, + context_path = ApiV1::to_path(), + path = "/nodes/list", + responses( + ( + status = 200, body = Vec, + content_type = "application/json", + description = "Simple Node List" + ), + (status = 401, description = "Unauthorized") + ), + security(("api_key" = [])) + ))] +pub async fn get_nodes_list( + Extension(client): Extension, +) -> AxumResult>> { + tracing::info!("get /nodes/list : {client:?}"); + fetch_nodes(client.api_main()).await.map(Json) } -#[allow(clippy::cast_precision_loss)] -fn disk_status_from_space(space: &dto::SpaceInfo, occupied_space: u64) -> DiskStatus { - if ((space.total_disk_space_bytes - occupied_space) as f64 - / space.total_disk_space_bytes as f64) - < DEFAULT_MIN_FREE_SPACE_PERCENTAGE +/// Returns simple list of all known vdisks +/// +/// # Errors +/// +/// This function will return an error if a call to the primary node will fail +#[cfg_attr(feature = "swagger", utoipa::path( + get, + context_path = ApiV1::to_path(), + path = "/vdisks/list", + responses( + ( + status = 200, body = Vec, + content_type = "application/json", + description = "Simple Node List" + ), + (status = 401, description = "Unauthorized") + ), + security(("api_key" = [])) + ))] +pub async fn get_vdisks_list( + Extension(client): Extension, +) -> AxumResult>> { + tracing::info!("get /vdisks/list : {client:?}"); + fetch_vdisks(client.api_main()).await.map(Json) +} +/// Returns vdisk inforamtion by their id +/// +/// # Errors +/// +/// This function will return an error if a call to the main node will fail or vdisk with +/// specified id not found +#[cfg_attr(feature = "swagger", utoipa::path( + get, + context_path = ApiV1::to_path(), + path = "/vdisks/{vdisk_id}", + responses( + ( + status = 200, body = VDisk, + content_type = "application/json", + description = "VDisk Inforamtion" + ), + (status = 401, description = "Unauthorized"), + (status = 404, description = "VDisk not found"), + ), + security(("api_key" = [])) + ))] +pub async fn get_vdisk_info( + Extension(client): Extension, + Path(vdisk_id): Path, +) -> AxumResult> { + tracing::info!("get /vdisks/{vdisk_id} : {client:?}"); + get_vdisk_by_id(&client, vdisk_id).await.map(Json) +} + +/// Returns node inforamtion by their node name +/// +/// # Errors +/// +/// This function will return an error if a call to the specified node will fail or node with +/// specified name not found +#[cfg_attr(feature = "swagger", utoipa::path( + get, + context_path = ApiV1::to_path(), + path = "/nodes/{node_name}", + responses( + ( + status = 200, body = Node, + content_type = "application/json", + description = "Node Inforamtion" + ), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Node not found"), + ), + security(("api_key" = [])) + ))] +pub async fn get_node_info( + Extension(client): Extension, + Path(node_name): Path, +) -> AxumResult> { + tracing::info!("get /nodes/{node_name} : {client:?}"); + let handle = Arc::new( + client + .api_secondary(&node_name) + .cloned() + .ok_or(StatusCode::NOT_FOUND)?, + ); + + let nodes = { + let handle = client.api_main().clone(); + tokio::spawn(async move { fetch_nodes(&handle).await }) + }; + let status = { + let handle = handle.clone(); + tokio::spawn(async move { handle.get_status().await }) + }; + let metrics = { + let handle = handle.clone(); + tokio::spawn(async move { handle.clone().get_metrics().await }) + }; + let space_info = { + let handle = handle.clone(); + tokio::spawn(async move { handle.clone().get_space_info().await }) + }; + + let mut node = NodeInfo { + name: node_name.clone(), + hostname: String::new(), + vdisks: vec![], + status: NodeStatus::Offline, + rps: None, + alien_count: None, + corrupted_count: None, + space: None, + }; + + let Ok(Ok(GetStatusResponse::AJSONWithNodeInfo(status))) = status.await else { + // Fallback to general info + node.hostname = if let Ok(Ok(nodes)) = nodes.await { + nodes + .iter() + .find(|node| node.name == node_name) + .ok_or(StatusCode::NOT_FOUND)? + .address + .clone() + } else { + String::new() + }; + return Ok(Json(node)); + }; + node.hostname = status.address; + + let mut vdisks: FuturesUnordered<_> = status + .vdisks + .iter() + .flatten() + .map(|vdisk| { + let handle = client.clone(); + let id = vdisk.id as u64; + tokio::spawn(async move { get_vdisk_by_id(&handle, id).await }) + }) + .collect(); + + if let ( + Ok(Ok(GetMetricsResponse::Metrics(metric))), + Ok(Ok(GetSpaceInfoResponse::SpaceInfo(space))), + ) = (metrics.await, space_info.await) { - DiskStatus::Bad(vec![DiskProblem::FreeSpaceRunningOut]) - } else { - DiskStatus::Good + let metric = Into::::into(metric); + node.status = NodeStatus::from_problems(NodeProblem::default_from_metrics(&metric)); + node.rps = Some(RPS::from_metrics(&metric)); + node.alien_count = Some(metric[RawMetricEntry::BackendAlienCount].value); + node.corrupted_count = Some(metric[RawMetricEntry::BackendCorruptedBlobCount].value); + node.space = Some(SpaceInfo::from(space)); } + + while let Some(vdisk) = vdisks.next().await { + if let Ok(Ok(vdisk)) = vdisk { + node.vdisks.push(vdisk); + } else { + tracing::warn!("some warning"); //TODO + } + } + + Ok(Json(node)) +} + +/// Get Raw Metrics from Node +/// +/// # Errors +/// +/// This function will return an error if the server was unable to get node'a client or the request to get metrics fails +#[cfg_attr(feature = "swagger", utoipa::path( + get, + context_path = ApiV1::to_path(), + path = "/nodes/{node_name}/metrics", + responses( + (status = 200, body = TypedMetrics, content_type = "application/json", description = "Node's metrics"), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Node Not Found") + ), + security(("api_key" = [])) + ))] +pub async fn raw_metrics_by_node( + Extension(client): Extension, + Path(node_name): Path, +) -> AxumResult> { + Ok(Json( + fetch_metrics( + &client + .api_secondary(&node_name) + .cloned() + .ok_or(StatusCode::NOT_FOUND)?, + ) + .await? + .into(), + )) +} + +/// Get Configuration from Node +/// +/// # Errors +/// +/// This function will return an error if the server was unable to get node'a client or the request to get configuration fails +#[cfg_attr(feature = "swagger", utoipa::path( + get, + context_path = ApiV1::to_path(), + path = "/nodes/{node_name}/configuration", + responses( + (status = 200, body = NodeConfiguration, content_type = "application/json", description = "Node's configuration"), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Node Not Found") + ), + security(("api_key" = [])) + ))] +pub async fn raw_configuration_by_node( + Extension(client): Extension, + Path(node_name): Path, +) -> AxumResult> { + Ok(Json( + fetch_configuration( + &client + .api_secondary(&node_name) + .cloned() + .ok_or(StatusCode::NOT_FOUND)?, + ) + .await?, + )) } diff --git a/backend/src/services/methods.rs b/backend/src/services/methods.rs index 9bfc23d2..922dbe60 100644 --- a/backend/src/services/methods.rs +++ b/backend/src/services/methods.rs @@ -67,7 +67,6 @@ pub async fn fetch_space_info< /// /// # Errors /// -/// This function will return an error if . /// This function will return an error if the request to the specified client failed pub async fn fetch_node_status< Context: Send + Sync, @@ -161,3 +160,113 @@ pub async fn fetch_nodes< Ok(nodes) } + +/// Return `VDisk` information by id +/// +/// # Errors +/// +/// This function will return an error if vdisks information couldn't be fetched or no vdisk with +/// provided id was found +pub async fn get_vdisk_by_id(client: &HttpBobClient, vdisk_id: u64) -> AxumResult { + let virtual_disks = fetch_vdisks(client.api_main()).await?; + let virtual_disks = virtual_disks + .iter() + .find(|vdisk| vdisk.id as u64 == vdisk_id) + .ok_or_else(|| StatusCode::NOT_FOUND.into_response())?; + let clients = virtual_disks + .replicas + .iter() + .flatten() + .map(|replica| replica.node.clone()) + .collect::>() + .iter() + .filter_map(|node_name| client.api_secondary(node_name)) + .collect::>(); + let partition_count = if let Some(handle) = clients.first() { + handle.get_partitions(vdisk_id as i32).await.map_or_else( + |_err| 0, + |parts| { + if let GetPartitionsResponse::NodeInfoAndJSONArrayWithPartitionsInfo(parts) = parts + { + parts.partitions.unwrap_or_default().len() + } else { + 0 + } + }, + ) + } else { + 0 + }; + let mut disks: FuturesUnordered<_> = clients + .iter() + .map(move |&node| { + let handle = node.clone(); + tokio::spawn(async move { (handle.get_status().await, handle.get_disks().await) }) + }) + .collect(); + let mut replicas: HashMap<_, _> = virtual_disks + .replicas + .clone() + .into_iter() + .flatten() + .map(|replica| { + ( + (replica.disk.clone(), replica.node.clone()), + Replica { + node: replica.node, + disk: replica.disk, + path: replica.path, + status: ReplicaStatus::Offline { + problems: vec![ReplicaProblem::NodeUnavailable], + }, + }, + ) + }) + .collect(); + while let Some(res) = disks.next().await { + if let Ok(( + Ok(GetStatusResponse::AJSONWithNodeInfo(status)), + Ok(GetDisksResponse::AJSONArrayWithDisksAndTheirStates(disks)), + )) = res + { + for disk in disks { + replicas.insert( + (disk.name.clone(), status.name.clone()), + Replica { + node: status.name.clone(), + disk: disk.name, + path: disk.path, + status: disk + .is_active + .then_some(ReplicaStatus::Good) + .unwrap_or_else(|| ReplicaStatus::Offline { + problems: vec![ReplicaProblem::DiskUnavailable], + }), + }, + ); + } + } else { + tracing::warn!("couldn't receive node's space info"); + } + } + + let replicas: Vec<_> = replicas.into_values().collect(); + let count = replicas + .iter() + .filter(|replica| matches!(replica.status, ReplicaStatus::Offline { .. })) + .count(); + let status = if count == 0 { + VDiskStatus::Good + } else if count == replicas.len() { + VDiskStatus::Offline + } else { + VDiskStatus::Bad + }; + + Ok(VDisk { + id: vdisk_id, + status, + partition_count: partition_count as u64, + replicas, + }) +} diff --git a/backend/src/services/mod.rs b/backend/src/services/mod.rs index bbdf977c..7ac21ae8 100644 --- a/backend/src/services/mod.rs +++ b/backend/src/services/mod.rs @@ -1,4 +1,7 @@ mod prelude { + pub use super::methods::{ + fetch_configuration, fetch_metrics, fetch_nodes, fetch_vdisks, get_vdisk_by_id, + }; pub use crate::{ connector::{ api::{prelude::*, ApiNoContext}, @@ -8,13 +11,12 @@ mod prelude { prelude::*, }; pub use axum::{ - extract::{FromRef, FromRequestParts}, + extract::{FromRef, FromRequestParts, Path}, http::request::Parts, middleware::{from_fn_with_state, Next}, Router, }; pub use futures::{stream::FuturesUnordered, StreamExt}; - pub use std::sync::Arc; pub use tokio::sync::Mutex; pub use tower_sessions::Session; } @@ -23,7 +25,10 @@ pub mod api; pub mod auth; pub mod methods; -use api::{get_disks_count, get_nodes_count, get_rps, get_space}; +use api::{ + get_disks_count, get_node_info, get_nodes_count, get_nodes_list, get_rps, get_space, + raw_configuration_by_node, raw_metrics_by_node, +}; use auth::{login, logout, require_auth, AuthState, BobUser, HttpBobClient, InMemorySessionStore}; use prelude::*; @@ -47,6 +52,18 @@ pub fn api_router_v1(auth_state: BobAuthState) -> Result, R .api_route("/nodes/count", &Method::GET, get_nodes_count) .api_route("/nodes/rps", &Method::GET, get_rps) .api_route("/nodes/space", &Method::GET, get_space) + .api_route("/nodes/list", &Method::GET, get_nodes_list) + .api_route("/nodes/:node_name", &Method::GET, get_node_info) + .api_route( + "/nodes/:node_name/metrics", + &Method::GET, + raw_metrics_by_node, + ) + .api_route( + "/nodes/:node_name/configuration", + &Method::GET, + raw_configuration_by_node, + ) .unwrap()? .route_layer(from_fn_with_state(auth_state, require_auth)) .with_context::() diff --git a/frontend/bindings.rs b/frontend/bindings.rs index 5514ff90..090b0b17 100644 --- a/frontend/bindings.rs +++ b/frontend/bindings.rs @@ -4,3 +4,42 @@ use tsync::tsync; #[tsync] pub type Hostname = String; + +// Same as in `backend/src/connector/dto.rs` +// Imported for bindings generation (tsync doesn't respect serde(rename)) + +/// BOB's Node interface +#[tsync] +pub struct DTONode { + pub name: String, + + pub address: String, + + pub vdisks: Option>, +} + +/// BOB's Node Configuration interface +#[tsync] +pub struct DTONodeConfiguration { + pub blob_file_name_prefix: Option, + + pub root_dir_name: Option, +} + +/// BOB's VDisk interface +#[tsync] +pub struct DTOVDisk { + pub id: i32, + + pub replicas: Option>, +} + +/// BOB's Replica interface +#[tsync] +pub struct DTOReplica { + pub node: String, + + pub disk: String, + + pub path: String, +} diff --git a/frontend/src/components/nodeList/nodeList.module.css b/frontend/src/components/nodeList/nodeList.module.css new file mode 100644 index 00000000..5ca0c0f5 --- /dev/null +++ b/frontend/src/components/nodeList/nodeList.module.css @@ -0,0 +1,27 @@ +.greendot { + height: 16px; + width: 16px; + background-color: #34b663; + border-radius: 50%; + display: inline-block; +} + +.reddot { + height: 16px; + width: 16px; + background-color: #c3234b; + border-radius: 50%; + display: inline-block; +} + +.graydot { + height: 16px; + width: 16px; + background-color: #7b817e; + border-radius: 50%; + display: inline-block; +} + +.totalspace { + color: #7b817e; +} diff --git a/frontend/src/components/nodeList/nodeList.tsx b/frontend/src/components/nodeList/nodeList.tsx new file mode 100644 index 00000000..db1fb187 --- /dev/null +++ b/frontend/src/components/nodeList/nodeList.tsx @@ -0,0 +1,131 @@ +import { Context } from '@appTypes/context.ts'; +import defaultTheme from '@layouts/DefaultTheme.ts'; +import { Box, ThemeProvider } from '@mui/system'; +import { useStore } from '@nanostores/react'; +import axios from 'axios'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + +import FetchingBackdrop from '../backdrop/backdrop.tsx'; +import NodeTable from '../nodeTable/nodeTable.tsx'; + +const stubNode: NodeInfo = { + name: 'loading...', + hostname: 'loading...', + vdisks: [], + status: { + status: 'Offline', + }, + rps: { + map: { + put: 0, + get: 0, + exist: 0, + delete: 0, + }, + }, + alienCount: 0, + corruptedCount: 0, + space: { + total_disk: 0, + free_disk: 0, + used_disk: 0, + occupied_disk: 0, + }, +}; + +const NodeListPage = () => { + const [nodes, setNodes] = useState([]); + const [nodeList, setNodeList] = useState([]); + const [isPageLoaded, setIsPageLoaded] = useState(false); + const context = useStore(Context); + + const fetchNodeList = useMemo( + () => async () => { + try { + const [res] = await Promise.all([axios.get('/api/v1/nodes/list')]); + setNodes( + res.data + .map((dtoNode: DTONode) => { + return { + ...stubNode, + name: dtoNode.name, + hostname: dtoNode.address, + } as NodeInfo; + }) + .sort((a, b) => (a.name < b.name ? -1 : 1)), + ); + setNodeList(res.data); + } catch (err) { + console.log(err); + } + }, + [], + ); + + const fetchNode = useCallback( + (nodeName: string) => async () => { + try { + const [res] = await Promise.all([axios.get('/api/v1/nodes/' + nodeName)]); + return res.data; + } catch (err) { + console.log(err); + } + }, + [], + ); + + useEffect(() => { + const fetchNodes = async () => { + const res = ( + await Promise.all( + nodeList.map(async (node) => { + return fetchNode(node.name)() + .catch(console.error) + .then((resultNode) => resultNode); + }), + ) + ).filter((node): node is NodeInfo => { + return typeof node !== undefined; + }); + setNodes(res.concat(nodes.filter((item) => !res.find((n) => (n?.name || '') == item.name)))); + }; + if (!isPageLoaded && nodeList.length !== 0) { + fetchNodes(); + setIsPageLoaded(true); + } + const interval = setInterval(() => { + fetchNodes(); + }, context.refreshTime * 1000); + + return () => clearInterval(interval); + }, [fetchNode, context.enabled, context.refreshTime, nodeList, nodes, isPageLoaded]); + + useEffect(() => { + fetchNodeList(); + }, [fetchNodeList]); + if (!isPageLoaded) { + return ; + } + return ( + + + + + + ); +}; + +export default NodeListPage; diff --git a/frontend/src/components/nodeTable/nodeTable.module.css b/frontend/src/components/nodeTable/nodeTable.module.css new file mode 100644 index 00000000..7217ac99 --- /dev/null +++ b/frontend/src/components/nodeTable/nodeTable.module.css @@ -0,0 +1,31 @@ +.greendot { + height: 16px; + width: 16px; + background-color: #34b663; + border-radius: 50%; + display: inline-block; +} + +.reddot { + height: 16px; + width: 16px; + background-color: #c3234b; + border-radius: 50%; + display: inline-block; +} + +.graydot { + height: 16px; + width: 16px; + background-color: #7b817e; + border-radius: 50%; + display: inline-block; +} + +.totalspace { + color: #7b817e; +} + +.greyHeader { + background-color: #35373c; +} diff --git a/frontend/src/components/nodeTable/nodeTable.tsx b/frontend/src/components/nodeTable/nodeTable.tsx new file mode 100644 index 00000000..c8609f86 --- /dev/null +++ b/frontend/src/components/nodeTable/nodeTable.tsx @@ -0,0 +1,183 @@ +import { formatBytes } from '@appTypes/common.ts'; +import { Link } from '@mui/material'; +import { Box } from '@mui/system'; +import type { GridColDef, GridRenderCellParams, GridValidRowModel } from '@mui/x-data-grid'; +import { DataGrid, GridToolbar } from '@mui/x-data-grid'; +import axios from 'axios'; +import React from 'react'; + +import style from './nodeTable.module.css'; + +axios.defaults.withCredentials = true; + +const DotMap: Record = { + good: style.greendot, + bad: style.graydot, + offline: style.reddot, +}; + +const defaultRps: RPS = { + map: { + put: 0, + exist: 0, + get: 0, + delete: 0, + }, +}; + +const defaultSpace: SpaceInfo = { + total_disk: 0, + used_disk: 0, + occupied_disk: 0, + free_disk: 0, +}; + +const columns: GridColDef[] = [ + { + field: 'nodename', + headerName: 'Node Name', + flex: 1, + width: 200, + align: 'center', + headerAlign: 'center', + headerClassName: style.greyHeader, + sortable: false, + renderCell: (params: GridRenderCellParams) => { + return ( + + {params.value} + + ); + }, + }, + { + field: 'hostname', + headerName: 'Hostname', + flex: 1, + width: 200, + align: 'center', + headerAlign: 'center', + headerClassName: style.greyHeader, + }, + { + field: 'status', + headerName: 'Status', + flex: 1, + width: 200, + align: 'left', + headerAlign: 'center', + headerClassName: style.greyHeader, + renderCell: (params: GridRenderCellParams) => { + const status = params.value || 'offline'; + return ( + + + {status.charAt(0).toUpperCase() + status.slice(1)} + + ); + }, + }, + { + field: 'space', + headerName: 'Occupied Space', + flex: 1, + width: 150, + align: 'center', + headerAlign: 'center', + headerClassName: style.greyHeader, + renderCell: (params: GridRenderCellParams) => { + const space = params.value || defaultSpace; + return ( +
+ {formatBytes(space.used_disk)} /{' '} + {formatBytes(space.total_disk)} +
+ ); + }, + }, + { + field: 'rps', + headerName: 'RPS', + flex: 1, + width: 150, + align: 'center', + headerAlign: 'center', + headerClassName: style.greyHeader, + renderCell: (params: GridRenderCellParams) => { + const rps = (params.value || defaultRps).map; + return
{rps.get + rps.put + rps.exist + rps.delete}
; + }, + }, + { + field: 'aliens', + headerName: 'Aliens', + flex: 1, + width: 200, + align: 'center', + headerAlign: 'center', + headerClassName: style.greyHeader, + }, + { + field: 'corruptedBlobs', + headerName: 'Corrupted BLOBs', + flex: 1, + width: 200, + align: 'center', + headerAlign: 'center', + headerClassName: style.greyHeader, + }, +]; + +const NodeTable = ({ nodes }: { nodes: NodeInfo[] }) => { + const data = nodes.sort() + ? nodes.map((node, i) => { + return { + id: i, + nodename: node.name, + hostname: node.hostname, + status: node.status.status.toLowerCase(), + space: node.space, + rps: node.rps, + aliens: node.alienCount || 0, + corruptedBlobs: node.corruptedCount || 0, + } as NodeTableCols; + }) + : []; + return ( + searchInput.split(',').map((value) => value.trim()), + }, + }, + }} + /> + ); +}; + +export default NodeTable; diff --git a/frontend/src/pages/nodelist/index.astro b/frontend/src/pages/nodelist/index.astro index 1ff9feb3..54e101a1 100644 --- a/frontend/src/pages/nodelist/index.astro +++ b/frontend/src/pages/nodelist/index.astro @@ -1,5 +1,10 @@ --- import Layout from '@layouts/Layout.astro'; +import NodeListPage from '@components/nodeList/nodeList.tsx'; --- - + +
+ +
+
diff --git a/frontend/src/types/common.ts b/frontend/src/types/common.ts index 01fc75b9..961fdb8b 100644 --- a/frontend/src/types/common.ts +++ b/frontend/src/types/common.ts @@ -31,3 +31,29 @@ export function getCookie(field: string) { export function eraseCookie(name: string) { document.cookie = name + '=; Max-Age=-99999999;'; } + +export function formatBytes(bytes: number, decimals = 0) { + if (!+bytes) return '0B'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))}${sizes[i]}`; +} + +export function proxiedPropertiesOf() { + return new Proxy( + {}, + { + get: (_, prop) => prop, + set: () => { + throw Error('Set not supported'); + }, + }, + ) as { + [P in keyof TObj]?: P; + }; +} diff --git a/frontend/src/types/data.d.ts b/frontend/src/types/data.d.ts index 439ab1e8..07e37272 100644 --- a/frontend/src/types/data.d.ts +++ b/frontend/src/types/data.d.ts @@ -11,3 +11,14 @@ interface DashboardState { rpsBreakdownList: RPSList; dataLoaded: boolean; } + +interface NodeTableCols { + id: number; + nodename: string; + hostname: string; + status: NodeStatusName; + space?: SpaceInfo; + rps?: RPS; + aliens?: number; + corruptedBlobs?: number; +} diff --git a/frontend/src/types/rust.d.ts b/frontend/src/types/rust.d.ts index 1b4483d0..294e15d9 100644 --- a/frontend/src/types/rust.d.ts +++ b/frontend/src/types/rust.d.ts @@ -1,5 +1,18 @@ /* This file is generated and managed by tsync */ +/** Physical disk definition */ +interface Disk { + /** Disk name */ + name: string; + /** Disk path */ + path: string; + /** Disk status */ + status: DiskStatus; + totalSpace: number; + usedSpace: number; + iops: number; +} + /** Defines kind of problem on disk */ type DiskProblem = | "FreeSpaceRunningOut"; @@ -12,11 +25,16 @@ type DiskProblem = */ type DiskStatus = | DiskStatus__Good + | DiskStatus__Bad | DiskStatus__Offline; type DiskStatus__Good = { status: "Good"; }; +type DiskStatus__Bad = { + status: "Bad"; + problems: Array; +}; type DiskStatus__Offline = { status: "Offline"; }; @@ -25,6 +43,17 @@ type DiskStatus__Offline = { type DiskStatusName = | "good" | "bad" | "offline"; +interface NodeInfo { + name: string; + hostname: string; + vdisks: Array; + status: NodeStatus; + rps?: RPS; + alienCount?: number; + corruptedCount?: number; + space?: SpaceInfo; +} + /** Defines kind of problem on Node */ type NodeProblem = | "AliensExists" | "CorruptedExists" | "FreeSpaceRunningOut" | "VirtualMemLargerThanRAM" | "HighCPULoad"; @@ -38,11 +67,16 @@ type NodeProblem = */ type NodeStatus = | NodeStatus__Good + | NodeStatus__Bad | NodeStatus__Offline; type NodeStatus__Good = { status: "Good"; }; +type NodeStatus__Bad = { + status: "Bad"; + problems: Array; +}; type NodeStatus__Offline = { status: "Offline"; }; @@ -51,6 +85,14 @@ type NodeStatus__Offline = { type NodeStatusName = | "good" | "bad" | "offline"; +/** [`VDisk`]'s replicas */ +interface Replica { + node: string; + disk: string; + path: string; + status: ReplicaStatus; +} + /** Reasons why Replica is offline */ type ReplicaProblem = | "NodeUnavailable" | "DiskUnavailable"; @@ -63,11 +105,16 @@ type ReplicaProblem = * Content - List of problems on replica. 'null' if status != 'offline' */ type ReplicaStatus = - | ReplicaStatus__Good; + | ReplicaStatus__Good + | ReplicaStatus__Offline; type ReplicaStatus__Good = { status: "Good"; }; +type ReplicaStatus__Offline = { + status: "Offline"; + problems: Array; +}; /** Disk space information in bytes */ interface SpaceInfo { @@ -126,3 +173,29 @@ interface Credentials { } type Hostname = string + +/** BOB's Node interface */ +interface DTONode { + name: string; + address: string; + vdisks?: Array; +} + +/** BOB's Node Configuration interface */ +interface DTONodeConfiguration { + blob_file_name_prefix?: string; + root_dir_name?: string; +} + +/** BOB's VDisk interface */ +interface DTOVDisk { + id: number; + replicas?: Array; +} + +/** BOB's Replica interface */ +interface DTOReplica { + node: string; + disk: string; + path: string; +} From 1c526b104762427617f076e6722ac76cbd667f79 Mon Sep 17 00:00:00 2001 From: archeoss Date: Fri, 2 Feb 2024 05:13:15 +0000 Subject: [PATCH 3/5] Autogenerate API --- api/openapi.yaml | 781 +++++++++++++++++++++++++++++++---------------- 1 file changed, 524 insertions(+), 257 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index ebcc52a0..3f3e594c 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -5,31 +5,31 @@ info: contact: name: Romanov Simeon ArchArcheoss@proton.me license: - name: "" + name: '' version: 0.0.0 paths: /api/v1/disks/count: get: tags: - - services::api + - services::api summary: Returns count of Physical Disks per status description: Returns count of Physical Disks per status operationId: get_disks_count responses: - "200": + '200': description: Returns a list with count of physical disks per status content: application/json: schema: - $ref: "#/components/schemas/DiskCount" - "401": + $ref: '#/components/schemas/DiskCount' + '401': description: Unauthorized security: - - api_key: [] + - api_key: [] /api/v1/login: post: tags: - - services::auth + - services::auth summary: Login to a BOB cluster description: | Login to a BOB cluster @@ -47,123 +47,224 @@ paths: The client couldn't authorize on the host operationId: login parameters: - - name: hostname - in: path - description: Address to connect to - required: true - schema: - $ref: "#/components/schemas/Hostname" - - name: credentials - in: path - description: "[Optional] Credentials used for BOB authentication" - required: true - schema: - allOf: - - $ref: "#/components/schemas/Credentials" - nullable: true + - name: hostname + in: path + description: Address to connect to + required: true + schema: + $ref: '#/components/schemas/Hostname' + - name: credentials + in: path + description: '[Optional] Credentials used for BOB authentication' + required: true + schema: + allOf: + - $ref: '#/components/schemas/Credentials' + nullable: true requestBody: - description: "" + description: '' content: application/json: schema: - $ref: "#/components/schemas/BobConnectionData" + $ref: '#/components/schemas/BobConnectionData' required: true responses: - "200": + '200': description: Successful authorization - "400": + '400': description: Bad Hostname - "401": + '401': description: Bad Credentials - "404": + '404': description: Can't reach specified hostname /api/v1/logout: post: tags: - - services::auth + - services::auth operationId: logout responses: - "200": + '200': description: Logged out /api/v1/nodes/count: get: tags: - - services::api + - services::api summary: Get Nodes count per Status description: Get Nodes count per Status operationId: get_nodes_count responses: - "200": + '200': description: Node count list per status content: application/json: schema: - $ref: "#/components/schemas/NodeCount" - "401": + $ref: '#/components/schemas/NodeCount' + '401': + description: Unauthorized + security: + - api_key: [] + /api/v1/nodes/list: + get: + tags: + - services::api + summary: Returns simple list of all known nodes + description: |- + Returns simple list of all known nodes + + # Errors + + This function will return an error if a call to the primary node will fail + operationId: get_nodes_list + responses: + '200': + description: Simple Node List + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/dto.Node' + '401': description: Unauthorized security: - - api_key: [] + - api_key: [] /api/v1/nodes/rps: get: tags: - - services::api + - services::api summary: Returns Total RPS on cluster description: Returns Total RPS on cluster operationId: get_rps responses: - "200": + '200': description: RPS list per operation on all nodes content: application/json: schema: - $ref: "#/components/schemas/RPS" - "401": + $ref: '#/components/schemas/RPS' + '401': description: Unauthorized security: - - api_key: [] + - api_key: [] /api/v1/nodes/space: get: tags: - - services::api + - services::api summary: Return inforamtion about space on cluster description: Return inforamtion about space on cluster operationId: get_space responses: - "200": + '200': description: Cluster Space Information content: application/json: schema: - $ref: "#/components/schemas/SpaceInfo" - "401": + $ref: '#/components/schemas/SpaceInfo' + '401': + description: Unauthorized + security: + - api_key: [] + /api/v1/nodes/{node_name}: + get: + tags: + - services::api + summary: Returns node inforamtion by their node name + description: |- + Returns node inforamtion by their node name + + # Errors + + This function will return an error if a call to the specified node will fail or node with + specified name not found + operationId: get_node_info + responses: + '200': + description: Node Inforamtion + content: + application/json: + schema: + $ref: '#/components/schemas/Node' + '401': + description: Unauthorized + '404': + description: Node not found + security: + - api_key: [] + /api/v1/nodes/{node_name}/configuration: + get: + tags: + - services::api + summary: Get Configuration from Node + description: |- + Get Configuration from Node + + # Errors + + This function will return an error if the server was unable to get node'a client or the request to get configuration fails + operationId: raw_configuration_by_node + responses: + '200': + description: Node's configuration + content: + application/json: + schema: + $ref: '#/components/schemas/NodeConfiguration' + '401': + description: Unauthorized + '404': + description: Node Not Found + security: + - api_key: [] + /api/v1/nodes/{node_name}/metrics: + get: + tags: + - services::api + summary: Get Raw Metrics from Node + description: |- + Get Raw Metrics from Node + + # Errors + + This function will return an error if the server was unable to get node'a client or the request to get metrics fails + operationId: raw_metrics_by_node + responses: + '200': + description: Node's metrics + content: + application/json: + schema: + $ref: '#/components/schemas/TypedMetrics' + '401': description: Unauthorized + '404': + description: Node Not Found security: - - api_key: [] + - api_key: [] components: schemas: BobConnectionData: type: object description: Data needed to connect to a BOB cluster required: - - hostname + - hostname properties: credentials: allOf: - - $ref: "#/components/schemas/Credentials" + - $ref: '#/components/schemas/Credentials' nullable: true hostname: - $ref: "#/components/schemas/Hostname" + $ref: '#/components/schemas/Hostname' example: credentials: login: archeoss - password: "12345" + password: '12345' hostname: 0.0.0.0:7000 Credentials: type: object description: Optional auth credentials for a BOB cluster required: - - login - - password + - login + - password properties: login: type: string @@ -173,14 +274,45 @@ components: description: Password used during auth example: login: archeoss - password: "12345" + password: '12345' + Disk: + type: object + description: Physical disk definition + required: + - name + - path + - status + - totalSpace + - usedSpace + - iops + properties: + iops: + type: integer + format: int64 + minimum: 0 + name: + type: string + description: Disk name + path: + type: string + description: Disk path + status: + $ref: '#/components/schemas/DiskStatus' + totalSpace: + type: integer + format: int64 + minimum: 0 + usedSpace: + type: integer + format: int64 + minimum: 0 DiskCount: type: object description: Disk count by their status required: - - good - - bad - - offline + - good + - bad + - offline properties: bad: type: integer @@ -202,38 +334,43 @@ components: type: string description: Defines kind of problem on disk enum: - - freeSpaceRunningOut + - freeSpaceRunningOut DiskStatus: oneOf: - - type: object - required: - - status - properties: - status: - type: string - enum: - - good - - type: object - required: - - status + - type: object + required: + - status + properties: + status: + type: string + enum: + - good + - type: object + required: + - status + - problems + properties: + problems: + type: object + required: - problems - properties: - problems: - type: array - items: - $ref: "#/components/schemas/DiskProblem" - status: - type: string - enum: - - bad - - type: object - required: - - status - properties: - status: - type: string - enum: - - offline + properties: + problems: + type: array + items: + $ref: '#/components/schemas/DiskProblem' + status: + type: string + enum: + - bad + - type: object + required: + - status + properties: + status: + type: string + enum: + - offline description: |- Defines disk status @@ -245,16 +382,16 @@ components: type: string description: Defines disk status names enum: - - good - - bad - - offline + - good + - bad + - offline Hostname: type: string MetricsEntryModel: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -267,12 +404,12 @@ components: MetricsSnapshotModel: type: object required: - - metrics + - metrics properties: metrics: type: object additionalProperties: - $ref: "#/components/schemas/MetricsEntryModel" + $ref: '#/components/schemas/MetricsEntryModel' NodeConfiguration: type: object properties: @@ -286,9 +423,9 @@ components: type: object description: Node count by their status required: - - good - - bad - - offline + - good + - bad + - offline properties: bad: type: integer @@ -306,46 +443,87 @@ components: bad: 0 good: 0 offline: 0 + NodeInfo: + type: object + required: + - name + - hostname + - vdisks + - status + properties: + alienCount: + type: integer + format: int64 + nullable: true + minimum: 0 + corruptedCount: + type: integer + format: int64 + nullable: true + minimum: 0 + hostname: + type: string + name: + type: string + rps: + allOf: + - $ref: '#/components/schemas/RPS' + nullable: true + space: + allOf: + - $ref: '#/components/schemas/SpaceInfo' + nullable: true + status: + $ref: '#/components/schemas/NodeStatus' + vdisks: + type: array + items: + $ref: '#/components/schemas/VDisk' NodeProblem: type: string description: Defines kind of problem on Node enum: - - aliensExists - - corruptedExists - - freeSpaceRunningOut - - virtualMemLargerThanRAM - - highCPULoad + - aliensExists + - corruptedExists + - freeSpaceRunningOut + - virtualMemLargerThanRAM + - highCPULoad NodeStatus: oneOf: - - type: object - required: - - status - properties: - status: - type: string - enum: - - good - - type: object - required: - - status + - type: object + required: + - status + properties: + status: + type: string + enum: + - good + - type: object + required: + - status + - problems + properties: + problems: + type: object + required: - problems - properties: - problems: - type: array - items: - $ref: "#/components/schemas/NodeProblem" - status: - type: string - enum: - - bad - - type: object - required: - - status - properties: - status: - type: string - enum: - - offline + properties: + problems: + type: array + items: + $ref: '#/components/schemas/NodeProblem' + status: + type: string + enum: + - bad + - type: object + required: + - status + properties: + status: + type: string + enum: + - offline description: |- Defines status of node @@ -358,25 +536,25 @@ components: type: string description: Defines node status names enum: - - good - - bad - - offline + - good + - bad + - offline Operation: type: string description: Types of operations on BOB cluster enum: - - put - - get - - exist - - delete + - put + - get + - exist + - delete RPS: type: object description: Requests per second by operation required: - - put - - get - - exist - - delete + - put + - get + - exist + - delete properties: delete: type: integer @@ -402,52 +580,74 @@ components: RawMetricEntry: type: string enum: - - cluster_grinder.get_count_rate - - cluster_grinder.put_count_rate - - cluster_grinder.exist_count_rate - - cluster_grinder.delete_count_rate - - pearl.exist_count_rate - - pearl.get_count_rate - - pearl.put_count_rate - - pearl.delete_count_rate - - backend.alien_count - - backend.corrupted_blob_count - - hardware.bob_virtual_ram - - hardware.total_ram - - hardware.used_ram - - hardware.bob_cpu_load - - hardware.free_space - - hardware.total_space - - hardware.descr_amount + - cluster_grinder.get_count_rate + - cluster_grinder.put_count_rate + - cluster_grinder.exist_count_rate + - cluster_grinder.delete_count_rate + - pearl.exist_count_rate + - pearl.get_count_rate + - pearl.put_count_rate + - pearl.delete_count_rate + - backend.alien_count + - backend.corrupted_blob_count + - hardware.bob_virtual_ram + - hardware.total_ram + - hardware.used_ram + - hardware.bob_cpu_load + - hardware.free_space + - hardware.total_space + - hardware.descr_amount + Replica: + type: object + description: '[`VDisk`]''s replicas' + required: + - node + - disk + - path + - status + properties: + disk: + type: string + node: + type: string + path: + type: string + status: + $ref: '#/components/schemas/ReplicaStatus' ReplicaProblem: type: string description: Reasons why Replica is offline enum: - - nodeUnavailable - - diskUnavailable + - nodeUnavailable + - diskUnavailable ReplicaStatus: oneOf: - - type: object - required: - - status - properties: - status: - type: string - enum: - - good - - type: object - required: - - status + - type: object + required: + - status + properties: + status: + type: string + enum: + - good + - type: object + required: + - status + - problems + properties: + problems: + type: object + required: - problems - properties: - problems: - type: array - items: - $ref: "#/components/schemas/ReplicaProblem" - status: - type: string - enum: - - offline + properties: + problems: + type: array + items: + $ref: '#/components/schemas/ReplicaProblem' + status: + type: string + enum: + - offline description: |- Replica status. It's either good or offline with the reasons why it is offline @@ -460,10 +660,10 @@ components: type: object description: Disk space information in bytes required: - - total_disk - - free_disk - - used_disk - - occupied_disk + - total_disk + - free_disk + - used_disk + - occupied_disk properties: free_disk: type: integer @@ -489,29 +689,29 @@ components: type: object description: Raw metrics information required: - - cluster_grinder.get_count_rate - - cluster_grinder.put_count_rate - - cluster_grinder.exist_count_rate - - cluster_grinder.delete_count_rate - - pearl.exist_count_rate - - pearl.get_count_rate - - pearl.put_count_rate - - pearl.delete_count_rate - - backend.alien_count - - backend.corrupted_blob_count - - hardware.bob_virtual_ram - - hardware.total_ram - - hardware.used_ram - - hardware.bob_cpu_load - - hardware.free_space - - hardware.total_space - - hardware.descr_amount + - cluster_grinder.get_count_rate + - cluster_grinder.put_count_rate + - cluster_grinder.exist_count_rate + - cluster_grinder.delete_count_rate + - pearl.exist_count_rate + - pearl.get_count_rate + - pearl.put_count_rate + - pearl.delete_count_rate + - backend.alien_count + - backend.corrupted_blob_count + - hardware.bob_virtual_ram + - hardware.total_ram + - hardware.used_ram + - hardware.bob_cpu_load + - hardware.free_space + - hardware.total_space + - hardware.descr_amount properties: backend.alien_count: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -524,8 +724,8 @@ components: backend.corrupted_blob_count: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -538,8 +738,8 @@ components: cluster_grinder.delete_count_rate: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -552,8 +752,8 @@ components: cluster_grinder.exist_count_rate: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -566,8 +766,8 @@ components: cluster_grinder.get_count_rate: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -580,8 +780,8 @@ components: cluster_grinder.put_count_rate: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -594,8 +794,8 @@ components: hardware.bob_cpu_load: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -608,8 +808,8 @@ components: hardware.bob_virtual_ram: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -622,8 +822,8 @@ components: hardware.descr_amount: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -636,8 +836,8 @@ components: hardware.free_space: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -650,8 +850,8 @@ components: hardware.total_ram: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -664,8 +864,8 @@ components: hardware.total_space: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -678,8 +878,8 @@ components: hardware.used_ram: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -692,8 +892,8 @@ components: pearl.delete_count_rate: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -706,8 +906,8 @@ components: pearl.exist_count_rate: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -720,8 +920,8 @@ components: pearl.get_count_rate: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -734,8 +934,8 @@ components: pearl.put_count_rate: type: object required: - - value - - timestamp + - value + - timestamp properties: timestamp: type: integer @@ -797,44 +997,111 @@ components: pearl.put_count_rate: timestamp: 0 value: 0 + VDisk: + type: object + description: Virtual disk Component + required: + - id + - status + - partitionCount + - replicas + properties: + id: + type: integer + format: int64 + minimum: 0 + partitionCount: + type: integer + format: int64 + minimum: 0 + replicas: + type: array + items: + $ref: '#/components/schemas/Replica' + status: + $ref: '#/components/schemas/VDiskStatus' VDiskStatus: oneOf: - - type: object - required: - - status - properties: - status: - type: string - enum: - - good - - type: object - required: - - status - properties: - status: - type: string - enum: - - bad - - type: object - required: - - status - properties: - status: - type: string - enum: - - offline + - type: object + required: + - status + properties: + status: + type: string + enum: + - good + - type: object + required: + - status + properties: + status: + type: string + enum: + - bad + - type: object + required: + - status + properties: + status: + type: string + enum: + - offline description: |- Virtual disk status. Variants - Virtual Disk status status == 'bad' when at least one of its replicas has problems - example: - status: good + dto.Node: + type: object + required: + - name + - address + properties: + address: + type: string + name: + type: string + vdisks: + type: array + items: + $ref: '#/components/schemas/dto.VDisk' + nullable: true + dto.Replica: + type: object + required: + - node + - disk + - path + properties: + disk: + type: string + node: + type: string + path: + type: string + dto.VDisk: + type: object + required: + - id + properties: + id: + type: integer + format: int32 + replicas: + type: array + items: + $ref: '#/components/schemas/dto.Replica' + nullable: true securitySchemes: api_key: type: apiKey in: header name: bob_apikey tags: +<<<<<<< HEAD - name: bob description: BOB management API +======= +- name: bob + description: BOB management API +>>>>>>> 7d85adf (Autogenerate API) From 84421b2b52c9306c7e0d00ceab423fa7af49adc6 Mon Sep 17 00:00:00 2001 From: archeoss Date: Sun, 3 Mar 2024 17:15:51 +0000 Subject: [PATCH 4/5] Autogenerate API --- api/openapi.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index 3f3e594c..1d74e2d1 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1098,10 +1098,5 @@ components: in: header name: bob_apikey tags: -<<<<<<< HEAD - - name: bob - description: BOB management API -======= - name: bob description: BOB management API ->>>>>>> 7d85adf (Autogenerate API) From ed5e76fd7b9d770984b83ff17dd91c9f85480ae0 Mon Sep 17 00:00:00 2001 From: Simeon Romanov Date: Sun, 3 Mar 2024 20:53:29 +0300 Subject: [PATCH 5/5] lint fixes --- frontend/src/types/common.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/types/common.ts b/frontend/src/types/common.ts index 961fdb8b..47d38c27 100644 --- a/frontend/src/types/common.ts +++ b/frontend/src/types/common.ts @@ -54,6 +54,6 @@ export function proxiedPropertiesOf() { }, }, ) as { - [P in keyof TObj]?: P; - }; + [P in keyof TObj]?: P; + }; }