Add space doctor endpoint with deterministic checks and tests
This commit is contained in:
parent
af11665a35
commit
f3a065c8ab
|
|
@ -27,7 +27,7 @@ target_link_libraries(amduat_federation
|
|||
set(amduatd_sources src/amduatd.c src/amduatd_http.c src/amduatd_caps.c
|
||||
src/amduatd_space.c src/amduatd_concepts.c
|
||||
src/amduatd_store.c src/amduatd_derivation_index.c
|
||||
src/amduatd_fed.c)
|
||||
src/amduatd_fed.c src/amduatd_space_doctor.c)
|
||||
if(AMDUATD_ENABLE_UI)
|
||||
list(APPEND amduatd_sources src/amduatd_ui.c)
|
||||
endif()
|
||||
|
|
@ -46,8 +46,8 @@ target_compile_definitions(amduatd
|
|||
target_link_libraries(amduatd
|
||||
PRIVATE amduat_tgk amduat_pel amduat_format amduat_asl_store_fs
|
||||
amduat_asl_store_index_fs amduat_asl_derivation_index_fs
|
||||
amduat_asl amduat_enc amduat_hash_asl1 amduat_util
|
||||
amduat_federation
|
||||
amduat_asl_record amduat_asl amduat_enc amduat_hash_asl1
|
||||
amduat_util amduat_federation
|
||||
)
|
||||
|
||||
add_executable(amduat_pel_gc
|
||||
|
|
@ -136,3 +136,24 @@ target_link_libraries(amduatd_test_fed_cfg
|
|||
)
|
||||
|
||||
add_test(NAME amduatd_fed_cfg COMMAND amduatd_test_fed_cfg)
|
||||
|
||||
add_executable(amduatd_test_space_doctor
|
||||
tests/test_amduatd_space_doctor.c
|
||||
src/amduatd_space_doctor.c
|
||||
src/amduatd_space.c
|
||||
src/amduatd_store.c
|
||||
)
|
||||
|
||||
target_include_directories(amduatd_test_space_doctor
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor/amduat/include
|
||||
)
|
||||
|
||||
target_link_libraries(amduatd_test_space_doctor
|
||||
PRIVATE amduat_asl_store_fs amduat_asl_pointer_fs amduat_asl_record
|
||||
amduat_asl_store_index_fs amduat_asl amduat_enc amduat_util
|
||||
amduat_hash_asl1
|
||||
)
|
||||
|
||||
add_test(NAME amduatd_space_doctor COMMAND amduatd_test_space_doctor)
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -208,11 +208,25 @@ Precedence rules:
|
|||
When capability tokens are used, the requested space must match the token's
|
||||
space (or the token must be unscoped), otherwise the request is rejected.
|
||||
|
||||
## Space doctor
|
||||
|
||||
Check deterministic invariants for the effective space:
|
||||
|
||||
```sh
|
||||
curl --unix-socket amduatd.sock http://localhost/v1/space/doctor
|
||||
curl --unix-socket amduatd.sock http://localhost/v1/space/doctor \
|
||||
-H 'X-Amduat-Space: demo'
|
||||
```
|
||||
|
||||
When the daemon uses the `fs` store backend, index-only checks are reported as
|
||||
`"skipped"`; the `index` backend runs them.
|
||||
|
||||
## Current endpoints
|
||||
|
||||
- `GET /v1/meta` → `{store_id, encoding_profile_id, hash_id, api_contract_ref}`
|
||||
- `GET /v1/contract` → contract bytes (JSON) (+ `X-Amduat-Contract-Ref` header)
|
||||
- `GET /v1/contract?format=ref` → `{ref}`
|
||||
- `GET /v1/space/doctor` → deterministic space health checks
|
||||
- `GET /v1/ui` → browser UI for authoring/running programs
|
||||
- `GET /v1/fed/records?domain_id=...&from_logseq=...&limit=...` → `{domain_id, snapshot_id, log_prefix, next_logseq, records[]}` (published artifacts + tombstones + PER + TGK edges)
|
||||
- `GET /v1/fed/artifacts/{ref}` → raw bytes for federation resolve
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
#include "amduatd_fed.h"
|
||||
#include "amduatd_store.h"
|
||||
#include "amduatd_derivation_index.h"
|
||||
#include "amduatd_space_doctor.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
|
@ -112,6 +113,7 @@ static const char k_amduatd_contract_v1_json[] =
|
|||
"{\"method\":\"GET\",\"path\":\"/v1/meta\"},"
|
||||
"{\"method\":\"HEAD\",\"path\":\"/v1/meta\"},"
|
||||
"{\"method\":\"GET\",\"path\":\"/v1/contract\"},"
|
||||
"{\"method\":\"GET\",\"path\":\"/v1/space/doctor\"},"
|
||||
"{\"method\":\"POST\",\"path\":\"/v1/capabilities\"},"
|
||||
"{\"method\":\"GET\",\"path\":\"/v1/cap/resolve\"},"
|
||||
"{\"method\":\"GET\",\"path\":\"/v1/fed/records\"},"
|
||||
|
|
@ -786,6 +788,67 @@ static bool amduatd_handle_get_contract(int fd,
|
|||
}
|
||||
}
|
||||
|
||||
static bool amduatd_handle_get_space_doctor(int fd,
|
||||
amduat_asl_store_t *store,
|
||||
const amduatd_http_req_t *req,
|
||||
const amduatd_cfg_t *dcfg,
|
||||
const amduatd_fed_cfg_t *fed_cfg,
|
||||
const amduatd_caps_t *caps,
|
||||
const char *root_path) {
|
||||
amduat_asl_pointer_store_t pointer_store;
|
||||
amduatd_space_doctor_report_t report;
|
||||
char *json = NULL;
|
||||
bool ok = false;
|
||||
int status = 200;
|
||||
|
||||
if (store == NULL || req == NULL || dcfg == NULL || root_path == NULL) {
|
||||
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
||||
"internal error");
|
||||
}
|
||||
|
||||
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
|
||||
const char *reason = NULL;
|
||||
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
|
||||
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
|
||||
return amduatd_send_json_error(fd, 403, "Forbidden",
|
||||
"space not permitted by capability");
|
||||
}
|
||||
return amduatd_send_json_error(fd, 403, "Forbidden",
|
||||
"invalid capability");
|
||||
}
|
||||
}
|
||||
|
||||
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
|
||||
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
||||
"pointer store error");
|
||||
}
|
||||
if (!amduatd_space_doctor_run(store,
|
||||
&pointer_store,
|
||||
req->effective_space,
|
||||
dcfg,
|
||||
fed_cfg,
|
||||
&report)) {
|
||||
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
||||
"doctor failed");
|
||||
}
|
||||
if (!amduatd_space_doctor_report_json(&report, &json)) {
|
||||
amduatd_space_doctor_report_free(&report);
|
||||
return amduatd_send_json_error(fd, 500, "Internal Server Error",
|
||||
"encode error");
|
||||
}
|
||||
if (report.fail_count != 0u) {
|
||||
status = 500;
|
||||
}
|
||||
ok = amduatd_http_send_json(fd,
|
||||
status,
|
||||
status == 200 ? "OK" : "Internal Server Error",
|
||||
json,
|
||||
false);
|
||||
free(json);
|
||||
amduatd_space_doctor_report_free(&report);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool amduatd_seed_api_contract(amduat_asl_store_t *store,
|
||||
const amduat_asl_store_fs_config_t *cfg,
|
||||
amduat_reference_t *out_ref) {
|
||||
|
|
@ -4015,6 +4078,17 @@ static bool amduatd_handle_conn(int fd,
|
|||
ok = amduatd_handle_get_contract(fd, store, &req, api_contract_ref);
|
||||
goto conn_cleanup;
|
||||
}
|
||||
if (strcmp(req.method, "GET") == 0 &&
|
||||
strcmp(no_query, "/v1/space/doctor") == 0) {
|
||||
ok = amduatd_handle_get_space_doctor(fd,
|
||||
store,
|
||||
&req,
|
||||
effective_cfg,
|
||||
fed_cfg,
|
||||
caps,
|
||||
root_path);
|
||||
goto conn_cleanup;
|
||||
}
|
||||
if (strcmp(req.method, "GET") == 0 &&
|
||||
strcmp(no_query, "/v1/fed/records") == 0) {
|
||||
ok = amduatd_handle_get_fed_records(fd, store, fed_cfg, &req);
|
||||
|
|
|
|||
|
|
@ -1114,6 +1114,67 @@ static bool amduatd_cap_verify_token(const amduatd_caps_t *cap,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool amduatd_caps_check_space(const amduatd_caps_t *cap,
|
||||
const amduatd_cfg_t *dcfg,
|
||||
const amduatd_http_req_t *req,
|
||||
const char **out_reason) {
|
||||
const char *cap_header = NULL;
|
||||
uint8_t *token_bytes = NULL;
|
||||
size_t token_len = 0u;
|
||||
amduatd_cap_token_t token;
|
||||
const char *reason = "invalid";
|
||||
bool ok = false;
|
||||
|
||||
if (out_reason != NULL) {
|
||||
*out_reason = NULL;
|
||||
}
|
||||
if (cap == NULL || dcfg == NULL || req == NULL) {
|
||||
if (out_reason != NULL) {
|
||||
*out_reason = "internal";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!cap->enabled) {
|
||||
return true;
|
||||
}
|
||||
cap_header = req->x_capability;
|
||||
if (cap_header == NULL || cap_header[0] == '\0') {
|
||||
return true;
|
||||
}
|
||||
if (strncmp(cap_header, "cap_", 4u) != 0) {
|
||||
if (out_reason != NULL) {
|
||||
*out_reason = "invalid";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!amduatd_b64url_decode(cap_header + 4u, &token_bytes, &token_len)) {
|
||||
if (out_reason != NULL) {
|
||||
*out_reason = "invalid";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(&token, 0, sizeof(token));
|
||||
if (!amduatd_cap_verify_token(cap,
|
||||
dcfg,
|
||||
token_bytes,
|
||||
token_len,
|
||||
&token,
|
||||
&reason)) {
|
||||
if (out_reason != NULL) {
|
||||
*out_reason = reason != NULL ? reason : "invalid";
|
||||
}
|
||||
free(token_bytes);
|
||||
amduatd_cap_token_free(&token);
|
||||
return false;
|
||||
}
|
||||
ok = true;
|
||||
|
||||
free(token_bytes);
|
||||
amduatd_cap_token_free(&token);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool amduatd_handle_post_capabilities(
|
||||
int fd,
|
||||
amduat_asl_store_t *store,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ bool amduatd_caps_handle(amduatd_ctx_t *ctx,
|
|||
const amduatd_http_req_t *req,
|
||||
amduatd_http_resp_t *resp);
|
||||
|
||||
bool amduatd_caps_check_space(const amduatd_caps_t *caps,
|
||||
const amduatd_cfg_t *dcfg,
|
||||
const amduatd_http_req_t *req,
|
||||
const char **out_reason);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
|
|
|||
860
src/amduatd_space_doctor.c
Normal file
860
src/amduatd_space_doctor.c
Normal file
|
|
@ -0,0 +1,860 @@
|
|||
#include "amduatd_space_doctor.h"
|
||||
|
||||
#include "amduat/asl/record.h"
|
||||
#include "amduat/enc/asl_log.h"
|
||||
#include "amduat/enc/asl1_core_codec.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} amduatd_doctor_strbuf_t;
|
||||
|
||||
static void amduatd_doctor_strbuf_free(amduatd_doctor_strbuf_t *b) {
|
||||
if (b == NULL) {
|
||||
return;
|
||||
}
|
||||
free(b->data);
|
||||
b->data = NULL;
|
||||
b->len = 0u;
|
||||
b->cap = 0u;
|
||||
}
|
||||
|
||||
static bool amduatd_doctor_strbuf_reserve(amduatd_doctor_strbuf_t *b,
|
||||
size_t extra) {
|
||||
size_t need;
|
||||
size_t next_cap;
|
||||
char *next;
|
||||
|
||||
if (b == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (extra == 0u) {
|
||||
return true;
|
||||
}
|
||||
if (b->len > SIZE_MAX - extra) {
|
||||
return false;
|
||||
}
|
||||
need = b->len + extra;
|
||||
if (need <= b->cap) {
|
||||
return true;
|
||||
}
|
||||
next_cap = b->cap != 0u ? b->cap : 256u;
|
||||
while (next_cap < need) {
|
||||
if (next_cap > (SIZE_MAX / 2u)) {
|
||||
next_cap = need;
|
||||
break;
|
||||
}
|
||||
next_cap *= 2u;
|
||||
}
|
||||
next = (char *)realloc(b->data, next_cap);
|
||||
if (next == NULL) {
|
||||
return false;
|
||||
}
|
||||
b->data = next;
|
||||
b->cap = next_cap;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool amduatd_doctor_strbuf_append(amduatd_doctor_strbuf_t *b,
|
||||
const char *data,
|
||||
size_t len) {
|
||||
if (b == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (data == NULL) {
|
||||
len = 0u;
|
||||
}
|
||||
if (!amduatd_doctor_strbuf_reserve(b, len + 1u)) {
|
||||
return false;
|
||||
}
|
||||
if (len != 0u) {
|
||||
memcpy(b->data + b->len, data, len);
|
||||
}
|
||||
b->len += len;
|
||||
b->data[b->len] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool amduatd_doctor_strbuf_append_cstr(amduatd_doctor_strbuf_t *b,
|
||||
const char *s) {
|
||||
return amduatd_doctor_strbuf_append(b,
|
||||
s != NULL ? s : "",
|
||||
s != NULL ? strlen(s) : 0u);
|
||||
}
|
||||
|
||||
static bool amduatd_doctor_strbuf_append_char(amduatd_doctor_strbuf_t *b,
|
||||
char c) {
|
||||
return amduatd_doctor_strbuf_append(b, &c, 1u);
|
||||
}
|
||||
|
||||
static char *amduatd_doctor_strdup(const char *s) {
|
||||
size_t len;
|
||||
char *copy;
|
||||
if (s == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
len = strlen(s);
|
||||
copy = (char *)malloc(len + 1u);
|
||||
if (copy == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(copy, s, len);
|
||||
copy[len] = '\0';
|
||||
return copy;
|
||||
}
|
||||
|
||||
static const char *amduatd_space_doctor_status_name(
|
||||
amduatd_space_doctor_status_t status) {
|
||||
switch (status) {
|
||||
case AMDUATD_DOCTOR_OK:
|
||||
return "ok";
|
||||
case AMDUATD_DOCTOR_WARN:
|
||||
return "warn";
|
||||
case AMDUATD_DOCTOR_FAIL:
|
||||
return "fail";
|
||||
case AMDUATD_DOCTOR_SKIPPED:
|
||||
return "skipped";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void amduatd_space_doctor_report_init(
|
||||
amduatd_space_doctor_report_t *report) {
|
||||
if (report == NULL) {
|
||||
return;
|
||||
}
|
||||
memset(report, 0, sizeof(*report));
|
||||
report->backend = AMDUATD_STORE_BACKEND_FS;
|
||||
}
|
||||
|
||||
static void amduatd_space_doctor_report_clear(
|
||||
amduatd_space_doctor_report_t *report) {
|
||||
if (report == NULL) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0u; i < report->checks_len; ++i) {
|
||||
free(report->checks[i].name);
|
||||
free(report->checks[i].detail);
|
||||
}
|
||||
free(report->checks);
|
||||
amduatd_space_doctor_report_init(report);
|
||||
}
|
||||
|
||||
void amduatd_space_doctor_report_free(amduatd_space_doctor_report_t *report) {
|
||||
amduatd_space_doctor_report_clear(report);
|
||||
}
|
||||
|
||||
static bool amduatd_space_doctor_report_add(
|
||||
amduatd_space_doctor_report_t *report,
|
||||
const char *name,
|
||||
amduatd_space_doctor_status_t status,
|
||||
const char *detail) {
|
||||
size_t next_len;
|
||||
amduatd_space_doctor_check_t *next;
|
||||
amduatd_space_doctor_check_t *entry;
|
||||
|
||||
if (report == NULL || name == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (report->checks_len > SIZE_MAX - 1u) {
|
||||
return false;
|
||||
}
|
||||
next_len = report->checks_len + 1u;
|
||||
next = (amduatd_space_doctor_check_t *)realloc(
|
||||
report->checks, next_len * sizeof(*next));
|
||||
if (next == NULL) {
|
||||
return false;
|
||||
}
|
||||
report->checks = next;
|
||||
entry = &report->checks[report->checks_len];
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
entry->name = amduatd_doctor_strdup(name);
|
||||
entry->detail = amduatd_doctor_strdup(detail != NULL ? detail : "");
|
||||
if (entry->name == NULL || entry->detail == NULL) {
|
||||
free(entry->name);
|
||||
free(entry->detail);
|
||||
return false;
|
||||
}
|
||||
entry->status = status;
|
||||
report->checks_len = next_len;
|
||||
switch (status) {
|
||||
case AMDUATD_DOCTOR_OK:
|
||||
report->ok_count++;
|
||||
break;
|
||||
case AMDUATD_DOCTOR_WARN:
|
||||
report->warn_count++;
|
||||
break;
|
||||
case AMDUATD_DOCTOR_FAIL:
|
||||
report->fail_count++;
|
||||
break;
|
||||
case AMDUATD_DOCTOR_SKIPPED:
|
||||
report->skipped_count++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool amduatd_space_doctor_build_collection_head_name(
|
||||
const char *name,
|
||||
char **out_name) {
|
||||
size_t name_len;
|
||||
size_t total_len;
|
||||
char *buffer;
|
||||
size_t offset = 0u;
|
||||
|
||||
if (name == NULL || out_name == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (!amduat_asl_pointer_name_is_valid(name)) {
|
||||
return false;
|
||||
}
|
||||
name_len = strlen(name);
|
||||
total_len = 11u + name_len + 5u + 1u;
|
||||
buffer = (char *)malloc(total_len);
|
||||
if (buffer == NULL) {
|
||||
return false;
|
||||
}
|
||||
memcpy(buffer + offset, "collection/", 11u);
|
||||
offset += 11u;
|
||||
memcpy(buffer + offset, name, name_len);
|
||||
offset += name_len;
|
||||
memcpy(buffer + offset, "/head", 5u);
|
||||
offset += 5u;
|
||||
buffer[offset] = '\0';
|
||||
*out_name = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool amduatd_space_doctor_build_collection_log_head_name(
|
||||
const char *name,
|
||||
char **out_name) {
|
||||
size_t name_len;
|
||||
size_t log_name_len;
|
||||
size_t total_len;
|
||||
char *buffer;
|
||||
size_t offset = 0u;
|
||||
|
||||
if (name == NULL || out_name == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (!amduat_asl_pointer_name_is_valid(name)) {
|
||||
return false;
|
||||
}
|
||||
name_len = strlen(name);
|
||||
log_name_len = 11u + name_len + 4u;
|
||||
total_len = 4u + log_name_len + 5u + 1u;
|
||||
buffer = (char *)malloc(total_len);
|
||||
if (buffer == NULL) {
|
||||
return false;
|
||||
}
|
||||
memcpy(buffer + offset, "log/", 4u);
|
||||
offset += 4u;
|
||||
memcpy(buffer + offset, "collection/", 11u);
|
||||
offset += 11u;
|
||||
memcpy(buffer + offset, name, name_len);
|
||||
offset += name_len;
|
||||
memcpy(buffer + offset, "/log", 4u);
|
||||
offset += 4u;
|
||||
memcpy(buffer + offset, "/head", 5u);
|
||||
offset += 5u;
|
||||
buffer[offset] = '\0';
|
||||
*out_name = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
enum {
|
||||
AMDUATD_EDGE_INDEX_MAGIC_LEN = 8,
|
||||
AMDUATD_EDGE_INDEX_VERSION = 1
|
||||
};
|
||||
|
||||
static const uint8_t k_amduatd_edge_index_magic[AMDUATD_EDGE_INDEX_MAGIC_LEN] = {
|
||||
'A', 'S', 'L', 'E', 'I', 'X', '1', '\0'
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool has_graph_ref;
|
||||
amduat_reference_t graph_ref;
|
||||
} amduatd_edge_index_state_view_t;
|
||||
|
||||
static void amduatd_edge_index_state_view_free(
|
||||
amduatd_edge_index_state_view_t *state) {
|
||||
if (state == NULL) {
|
||||
return;
|
||||
}
|
||||
if (state->has_graph_ref) {
|
||||
amduat_reference_free(&state->graph_ref);
|
||||
}
|
||||
memset(state, 0, sizeof(*state));
|
||||
}
|
||||
|
||||
static bool amduatd_doctor_read_u32_le(const uint8_t *data,
|
||||
size_t len,
|
||||
size_t *offset,
|
||||
uint32_t *out) {
|
||||
if (len - *offset < 4u) {
|
||||
return false;
|
||||
}
|
||||
*out = (uint32_t)data[*offset] |
|
||||
((uint32_t)data[*offset + 1u] << 8) |
|
||||
((uint32_t)data[*offset + 2u] << 16) |
|
||||
((uint32_t)data[*offset + 3u] << 24);
|
||||
*offset += 4u;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool amduatd_doctor_read_u64_le(const uint8_t *data,
|
||||
size_t len,
|
||||
size_t *offset,
|
||||
uint64_t *out) {
|
||||
if (len - *offset < 8u) {
|
||||
return false;
|
||||
}
|
||||
*out = (uint64_t)data[*offset] |
|
||||
((uint64_t)data[*offset + 1u] << 8) |
|
||||
((uint64_t)data[*offset + 2u] << 16) |
|
||||
((uint64_t)data[*offset + 3u] << 24) |
|
||||
((uint64_t)data[*offset + 4u] << 32) |
|
||||
((uint64_t)data[*offset + 5u] << 40) |
|
||||
((uint64_t)data[*offset + 6u] << 48) |
|
||||
((uint64_t)data[*offset + 7u] << 56);
|
||||
*offset += 8u;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool amduatd_edge_index_state_view_decode(
|
||||
amduat_octets_t payload,
|
||||
amduatd_edge_index_state_view_t *out_state) {
|
||||
size_t offset = 0u;
|
||||
uint32_t version = 0u;
|
||||
uint32_t ref_len = 0u;
|
||||
amduat_octets_t ref_bytes;
|
||||
uint64_t ignored_offset = 0u;
|
||||
|
||||
if (out_state == NULL) {
|
||||
return false;
|
||||
}
|
||||
memset(out_state, 0, sizeof(*out_state));
|
||||
if (payload.data == NULL ||
|
||||
payload.len < AMDUATD_EDGE_INDEX_MAGIC_LEN + 4u + 8u + 4u) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(payload.data, k_amduatd_edge_index_magic,
|
||||
AMDUATD_EDGE_INDEX_MAGIC_LEN) != 0) {
|
||||
return false;
|
||||
}
|
||||
offset += AMDUATD_EDGE_INDEX_MAGIC_LEN;
|
||||
if (!amduatd_doctor_read_u32_le(payload.data, payload.len, &offset, &version) ||
|
||||
version != AMDUATD_EDGE_INDEX_VERSION) {
|
||||
return false;
|
||||
}
|
||||
if (!amduatd_doctor_read_u64_le(payload.data, payload.len, &offset,
|
||||
&ignored_offset) ||
|
||||
!amduatd_doctor_read_u32_le(payload.data, payload.len, &offset, &ref_len)) {
|
||||
return false;
|
||||
}
|
||||
(void)ignored_offset;
|
||||
if (payload.len - offset < ref_len) {
|
||||
return false;
|
||||
}
|
||||
if (ref_len != 0u) {
|
||||
ref_bytes = amduat_octets(payload.data + offset, ref_len);
|
||||
if (!amduat_enc_asl1_core_decode_reference_v1(ref_bytes,
|
||||
&out_state->graph_ref)) {
|
||||
amduatd_edge_index_state_view_free(out_state);
|
||||
return false;
|
||||
}
|
||||
out_state->has_graph_ref = true;
|
||||
offset += ref_len;
|
||||
}
|
||||
return offset == payload.len;
|
||||
}
|
||||
|
||||
static amduatd_store_backend_t amduatd_space_doctor_backend_from_store(
|
||||
const amduat_asl_store_t *store) {
|
||||
if (store == NULL) {
|
||||
return AMDUATD_STORE_BACKEND_FS;
|
||||
}
|
||||
if (store->ops.log_scan != NULL && store->ops.current_state != NULL) {
|
||||
return AMDUATD_STORE_BACKEND_INDEX;
|
||||
}
|
||||
return AMDUATD_STORE_BACKEND_FS;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *label;
|
||||
char *pointer_name;
|
||||
bool pointer_exists;
|
||||
amduat_reference_t pointer_ref;
|
||||
} amduatd_doctor_pointer_t;
|
||||
|
||||
static void amduatd_space_doctor_pointer_free(amduatd_doctor_pointer_t *ptr) {
|
||||
if (ptr == NULL) {
|
||||
return;
|
||||
}
|
||||
free(ptr->pointer_name);
|
||||
ptr->pointer_name = NULL;
|
||||
ptr->pointer_exists = false;
|
||||
amduat_reference_free(&ptr->pointer_ref);
|
||||
}
|
||||
|
||||
bool amduatd_space_doctor_run(amduat_asl_store_t *store,
|
||||
amduat_asl_pointer_store_t *pointer_store,
|
||||
const amduatd_space_t *effective_space,
|
||||
const amduatd_cfg_t *cfg,
|
||||
const amduatd_fed_cfg_t *fed_cfg,
|
||||
amduatd_space_doctor_report_t *out_report) {
|
||||
amduatd_doctor_pointer_t pointers[3];
|
||||
amduat_octets_t edges_collection = amduat_octets(NULL, 0u);
|
||||
amduat_octets_t edges_index_head = amduat_octets(NULL, 0u);
|
||||
char *collection_head = NULL;
|
||||
char *collection_log_head = NULL;
|
||||
bool names_ok = true;
|
||||
bool ok = false;
|
||||
size_t i;
|
||||
|
||||
if (out_report == NULL) {
|
||||
return false;
|
||||
}
|
||||
amduatd_space_doctor_report_init(out_report);
|
||||
if (store == NULL || pointer_store == NULL || cfg == NULL) {
|
||||
return false;
|
||||
}
|
||||
(void)cfg;
|
||||
|
||||
out_report->backend = amduatd_space_doctor_backend_from_store(store);
|
||||
if (effective_space != NULL &&
|
||||
effective_space->enabled &&
|
||||
effective_space->space_id.data != NULL) {
|
||||
out_report->scoped = true;
|
||||
snprintf(out_report->space_id, sizeof(out_report->space_id), "%s",
|
||||
(const char *)effective_space->space_id.data);
|
||||
}
|
||||
|
||||
memset(pointers, 0, sizeof(pointers));
|
||||
pointers[0].label = "edges_index_head";
|
||||
pointers[1].label = "edges_collection_snapshot_head";
|
||||
pointers[2].label = "edges_collection_log_head";
|
||||
for (i = 0u; i < 3u; ++i) {
|
||||
memset(&pointers[i].pointer_ref, 0, sizeof(pointers[i].pointer_ref));
|
||||
}
|
||||
|
||||
if (!amduatd_space_edges_collection_name(effective_space, &edges_collection) ||
|
||||
!amduatd_space_edges_index_head_name(effective_space, &edges_index_head)) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"pointer_name_validation",
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
"failed to scope pointer names");
|
||||
names_ok = false;
|
||||
goto doctor_after_names;
|
||||
}
|
||||
if (!amduatd_space_doctor_build_collection_head_name(
|
||||
(const char *)edges_collection.data, &collection_head) ||
|
||||
!amduatd_space_doctor_build_collection_log_head_name(
|
||||
(const char *)edges_collection.data, &collection_log_head)) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"pointer_name_validation",
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
"failed to build collection heads");
|
||||
names_ok = false;
|
||||
goto doctor_after_names;
|
||||
}
|
||||
if (!amduat_asl_pointer_name_is_valid(
|
||||
(const char *)edges_collection.data) ||
|
||||
!amduat_asl_pointer_name_is_valid(
|
||||
(const char *)edges_index_head.data) ||
|
||||
!amduat_asl_pointer_name_is_valid(collection_head) ||
|
||||
!amduat_asl_pointer_name_is_valid(collection_log_head)) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"pointer_name_validation",
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
"invalid pointer name encoding");
|
||||
names_ok = false;
|
||||
goto doctor_after_names;
|
||||
}
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"pointer_name_validation",
|
||||
AMDUATD_DOCTOR_OK,
|
||||
"scoped pointer names valid");
|
||||
|
||||
doctor_after_names:
|
||||
if (!names_ok) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edges_index_head",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"invalid pointer names");
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edges_collection_snapshot_head",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"invalid pointer names");
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edges_collection_log_head",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"invalid pointer names");
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"cas_edges_index_head",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"invalid pointer names");
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"cas_edges_collection_snapshot_head",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"invalid pointer names");
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"cas_edges_collection_log_head",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"invalid pointer names");
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_state_parse",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"invalid pointer names");
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_graph_ref",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"invalid pointer names");
|
||||
goto doctor_index_checks;
|
||||
}
|
||||
|
||||
pointers[0].pointer_name = amduatd_doctor_strdup(
|
||||
(const char *)edges_index_head.data);
|
||||
pointers[1].pointer_name = amduatd_doctor_strdup(collection_head);
|
||||
pointers[2].pointer_name = amduatd_doctor_strdup(collection_log_head);
|
||||
if (pointers[0].pointer_name == NULL ||
|
||||
pointers[1].pointer_name == NULL ||
|
||||
pointers[2].pointer_name == NULL) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"pointer_store",
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
"oom");
|
||||
goto doctor_cleanup;
|
||||
}
|
||||
|
||||
for (i = 0u; i < 3u; ++i) {
|
||||
bool exists = false;
|
||||
amduat_asl_pointer_error_t perr =
|
||||
amduat_asl_pointer_get(pointer_store,
|
||||
pointers[i].pointer_name,
|
||||
&exists,
|
||||
&pointers[i].pointer_ref);
|
||||
if (perr != AMDUAT_ASL_POINTER_OK) {
|
||||
char detail[256];
|
||||
snprintf(detail, sizeof(detail), "pointer error: %s",
|
||||
pointers[i].pointer_name);
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
pointers[i].label,
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
detail);
|
||||
continue;
|
||||
}
|
||||
pointers[i].pointer_exists = exists;
|
||||
if (exists) {
|
||||
char detail[256];
|
||||
snprintf(detail, sizeof(detail), "present: %s",
|
||||
pointers[i].pointer_name);
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
pointers[i].label,
|
||||
AMDUATD_DOCTOR_OK,
|
||||
detail);
|
||||
} else {
|
||||
char detail[256];
|
||||
snprintf(detail, sizeof(detail), "missing: %s",
|
||||
pointers[i].pointer_name);
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
pointers[i].label,
|
||||
AMDUATD_DOCTOR_WARN,
|
||||
detail);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0u; i < 3u; ++i) {
|
||||
const char *check_name = NULL;
|
||||
switch (i) {
|
||||
case 0u:
|
||||
check_name = "cas_edges_index_head";
|
||||
break;
|
||||
case 1u:
|
||||
check_name = "cas_edges_collection_snapshot_head";
|
||||
break;
|
||||
case 2u:
|
||||
check_name = "cas_edges_collection_log_head";
|
||||
break;
|
||||
default:
|
||||
check_name = "cas_pointer_ref";
|
||||
break;
|
||||
}
|
||||
if (!pointers[i].pointer_exists) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
check_name,
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"pointer missing");
|
||||
continue;
|
||||
}
|
||||
{
|
||||
amduat_artifact_t artifact;
|
||||
memset(&artifact, 0, sizeof(artifact));
|
||||
if (amduat_asl_store_get(store, pointers[i].pointer_ref, &artifact) !=
|
||||
AMDUAT_ASL_STORE_OK) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
check_name,
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
"store get failed");
|
||||
} else {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
check_name,
|
||||
AMDUATD_DOCTOR_OK,
|
||||
"ref readable");
|
||||
}
|
||||
amduat_artifact_free(&artifact);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pointers[0].pointer_exists) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_state_parse",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"pointer missing");
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_graph_ref",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"pointer missing");
|
||||
} else {
|
||||
amduat_asl_record_t record;
|
||||
amduatd_edge_index_state_view_t state;
|
||||
const char *schema = "tgk/edge_index_state";
|
||||
bool parsed = false;
|
||||
memset(&record, 0, sizeof(record));
|
||||
memset(&state, 0, sizeof(state));
|
||||
if (amduat_asl_record_store_get(store,
|
||||
pointers[0].pointer_ref,
|
||||
&record) != AMDUAT_ASL_STORE_OK) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_state_parse",
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
"record load failed");
|
||||
} else if (record.schema.len != strlen(schema) ||
|
||||
memcmp(record.schema.data, schema, record.schema.len) != 0) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_state_parse",
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
"unexpected schema");
|
||||
} else if (!amduatd_edge_index_state_view_decode(record.payload, &state)) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_state_parse",
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
"payload decode failed");
|
||||
} else {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_state_parse",
|
||||
AMDUATD_DOCTOR_OK,
|
||||
"edge index state decoded");
|
||||
parsed = true;
|
||||
}
|
||||
|
||||
if (parsed && state.has_graph_ref) {
|
||||
amduat_artifact_t graph_artifact;
|
||||
memset(&graph_artifact, 0, sizeof(graph_artifact));
|
||||
if (amduat_asl_store_get(store, state.graph_ref, &graph_artifact) !=
|
||||
AMDUAT_ASL_STORE_OK) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_graph_ref",
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
"graph ref missing");
|
||||
} else {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_graph_ref",
|
||||
AMDUATD_DOCTOR_OK,
|
||||
"graph ref readable");
|
||||
}
|
||||
amduat_artifact_free(&graph_artifact);
|
||||
} else if (parsed) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_graph_ref",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"no graph ref");
|
||||
} else {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"edge_index_graph_ref",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"edge index state unreadable");
|
||||
}
|
||||
|
||||
amduatd_edge_index_state_view_free(&state);
|
||||
amduat_asl_record_free(&record);
|
||||
}
|
||||
|
||||
doctor_index_checks:
|
||||
if (out_report->backend == AMDUATD_STORE_BACKEND_INDEX) {
|
||||
amduat_asl_index_state_t state;
|
||||
amduat_asl_log_record_t *records = NULL;
|
||||
size_t record_count = 0u;
|
||||
if (!amduat_asl_index_current_state(store, &state)) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"index_current_state",
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
"current_state failed");
|
||||
} else {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"index_current_state",
|
||||
AMDUATD_DOCTOR_OK,
|
||||
"current_state ok");
|
||||
}
|
||||
{
|
||||
amduat_asl_store_error_t scan_err =
|
||||
amduat_asl_log_scan(store, &records, &record_count);
|
||||
if (scan_err == AMDUAT_ASL_STORE_OK) {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"index_log_scan",
|
||||
AMDUATD_DOCTOR_OK,
|
||||
"log_scan ok");
|
||||
} else {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"index_log_scan",
|
||||
AMDUATD_DOCTOR_FAIL,
|
||||
"log_scan failed");
|
||||
}
|
||||
if (records != NULL) {
|
||||
amduat_enc_asl_log_free(records, record_count);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"index_current_state",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"requires index backend");
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"index_log_scan",
|
||||
AMDUATD_DOCTOR_SKIPPED,
|
||||
"requires index backend");
|
||||
}
|
||||
|
||||
{
|
||||
char detail[256];
|
||||
bool fed_enabled = fed_cfg != NULL && fed_cfg->enabled;
|
||||
bool require_index_backend = fed_enabled;
|
||||
snprintf(detail, sizeof(detail),
|
||||
"enabled=%s require_index_backend=%s",
|
||||
fed_enabled ? "true" : "false",
|
||||
require_index_backend ? "true" : "false");
|
||||
(void)amduatd_space_doctor_report_add(out_report,
|
||||
"federation",
|
||||
AMDUATD_DOCTOR_OK,
|
||||
detail);
|
||||
}
|
||||
|
||||
ok = true;
|
||||
|
||||
doctor_cleanup:
|
||||
for (i = 0u; i < 3u; ++i) {
|
||||
amduatd_space_doctor_pointer_free(&pointers[i]);
|
||||
}
|
||||
free((void *)edges_collection.data);
|
||||
free((void *)edges_index_head.data);
|
||||
free(collection_head);
|
||||
free(collection_log_head);
|
||||
if (!ok) {
|
||||
amduatd_space_doctor_report_clear(out_report);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool amduatd_space_doctor_report_json(
|
||||
const amduatd_space_doctor_report_t *report,
|
||||
char **out_json) {
|
||||
amduatd_doctor_strbuf_t b;
|
||||
char header[256];
|
||||
|
||||
if (out_json != NULL) {
|
||||
*out_json = NULL;
|
||||
}
|
||||
if (report == NULL || out_json == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(&b, 0, sizeof(b));
|
||||
if (!amduatd_doctor_strbuf_append_cstr(&b, "{")) {
|
||||
amduatd_doctor_strbuf_free(&b);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!amduatd_doctor_strbuf_append_cstr(&b, "\"effective_space\":{")) {
|
||||
amduatd_doctor_strbuf_free(&b);
|
||||
return false;
|
||||
}
|
||||
if (report->scoped) {
|
||||
if (snprintf(header, sizeof(header),
|
||||
"\"mode\":\"scoped\",\"space_id\":\"%s\"",
|
||||
report->space_id) <= 0 ||
|
||||
!amduatd_doctor_strbuf_append_cstr(&b, header)) {
|
||||
amduatd_doctor_strbuf_free(&b);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!amduatd_doctor_strbuf_append_cstr(&b, "\"mode\":\"unscoped\"")) {
|
||||
amduatd_doctor_strbuf_free(&b);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!amduatd_doctor_strbuf_append_cstr(&b, "},")) {
|
||||
amduatd_doctor_strbuf_free(&b);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (snprintf(header, sizeof(header),
|
||||
"\"store_backend\":\"%s\",",
|
||||
amduatd_store_backend_name(report->backend)) <= 0 ||
|
||||
!amduatd_doctor_strbuf_append_cstr(&b, header)) {
|
||||
amduatd_doctor_strbuf_free(&b);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!amduatd_doctor_strbuf_append_cstr(&b, "\"checks\":[")) {
|
||||
amduatd_doctor_strbuf_free(&b);
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0u; i < report->checks_len; ++i) {
|
||||
const amduatd_space_doctor_check_t *check = &report->checks[i];
|
||||
if (i != 0u) {
|
||||
(void)amduatd_doctor_strbuf_append_char(&b, ',');
|
||||
}
|
||||
if (!amduatd_doctor_strbuf_append_cstr(&b, "{\"name\":\"") ||
|
||||
!amduatd_doctor_strbuf_append_cstr(&b, check->name) ||
|
||||
!amduatd_doctor_strbuf_append_cstr(&b, "\",\"status\":\"") ||
|
||||
!amduatd_doctor_strbuf_append_cstr(
|
||||
&b,
|
||||
amduatd_space_doctor_status_name(check->status)) ||
|
||||
!amduatd_doctor_strbuf_append_cstr(&b, "\",\"detail\":\"") ||
|
||||
!amduatd_doctor_strbuf_append_cstr(&b, check->detail) ||
|
||||
!amduatd_doctor_strbuf_append_cstr(&b, "\"}")) {
|
||||
amduatd_doctor_strbuf_free(&b);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!amduatd_doctor_strbuf_append_cstr(&b, "],")) {
|
||||
amduatd_doctor_strbuf_free(&b);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (snprintf(header, sizeof(header),
|
||||
"\"summary\":{"
|
||||
"\"ok_count\":%llu,"
|
||||
"\"warn_count\":%llu,"
|
||||
"\"fail_count\":%llu,"
|
||||
"\"skipped_count\":%llu"
|
||||
"}}\n",
|
||||
(unsigned long long)report->ok_count,
|
||||
(unsigned long long)report->warn_count,
|
||||
(unsigned long long)report->fail_count,
|
||||
(unsigned long long)report->skipped_count) <= 0 ||
|
||||
!amduatd_doctor_strbuf_append_cstr(&b, header)) {
|
||||
amduatd_doctor_strbuf_free(&b);
|
||||
return false;
|
||||
}
|
||||
|
||||
*out_json = b.data;
|
||||
return true;
|
||||
}
|
||||
60
src/amduatd_space_doctor.h
Normal file
60
src/amduatd_space_doctor.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#ifndef AMDUATD_SPACE_DOCTOR_H
|
||||
#define AMDUATD_SPACE_DOCTOR_H
|
||||
|
||||
#include "amduat/asl/asl_pointer_fs.h"
|
||||
#include "amduat/asl/core.h"
|
||||
#include "amduat/asl/store.h"
|
||||
#include "amduatd_caps.h"
|
||||
#include "amduatd_fed.h"
|
||||
#include "amduatd_store.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
AMDUATD_DOCTOR_OK = 0,
|
||||
AMDUATD_DOCTOR_WARN = 1,
|
||||
AMDUATD_DOCTOR_FAIL = 2,
|
||||
AMDUATD_DOCTOR_SKIPPED = 3
|
||||
} amduatd_space_doctor_status_t;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
amduatd_space_doctor_status_t status;
|
||||
char *detail;
|
||||
} amduatd_space_doctor_check_t;
|
||||
|
||||
typedef struct {
|
||||
bool scoped;
|
||||
char space_id[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
|
||||
amduatd_store_backend_t backend;
|
||||
amduatd_space_doctor_check_t *checks;
|
||||
size_t checks_len;
|
||||
size_t ok_count;
|
||||
size_t warn_count;
|
||||
size_t fail_count;
|
||||
size_t skipped_count;
|
||||
} amduatd_space_doctor_report_t;
|
||||
|
||||
bool amduatd_space_doctor_run(amduat_asl_store_t *store,
|
||||
amduat_asl_pointer_store_t *pointer_store,
|
||||
const amduatd_space_t *effective_space,
|
||||
const amduatd_cfg_t *cfg,
|
||||
const amduatd_fed_cfg_t *fed_cfg,
|
||||
amduatd_space_doctor_report_t *out_report);
|
||||
|
||||
bool amduatd_space_doctor_report_json(
|
||||
const amduatd_space_doctor_report_t *report,
|
||||
char **out_json);
|
||||
|
||||
void amduatd_space_doctor_report_free(amduatd_space_doctor_report_t *report);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* AMDUATD_SPACE_DOCTOR_H */
|
||||
465
tests/test_amduatd_space_doctor.c
Normal file
465
tests/test_amduatd_space_doctor.c
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
|
||||
#include "amduatd_space_doctor.h"
|
||||
|
||||
#include "amduat/asl/asl_store_fs_meta.h"
|
||||
#include "amduat/asl/core.h"
|
||||
#include "amduat/asl/none.h"
|
||||
#include "amduat/asl/record.h"
|
||||
#include "amduat/enc/asl1_core_codec.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static char *amduatd_test_make_temp_dir(void) {
|
||||
char tmpl[] = "/tmp/amduatd-doctor-XXXXXX";
|
||||
char *dir = mkdtemp(tmpl);
|
||||
size_t len;
|
||||
char *copy;
|
||||
if (dir == NULL) {
|
||||
perror("mkdtemp");
|
||||
return NULL;
|
||||
}
|
||||
len = strlen(dir);
|
||||
copy = (char *)malloc(len + 1u);
|
||||
if (copy == NULL) {
|
||||
fprintf(stderr, "failed to allocate temp dir copy\n");
|
||||
return NULL;
|
||||
}
|
||||
memcpy(copy, dir, len + 1u);
|
||||
return copy;
|
||||
}
|
||||
|
||||
static bool amduatd_check_status(const amduatd_space_doctor_report_t *report,
|
||||
const char *name,
|
||||
amduatd_space_doctor_status_t expected) {
|
||||
if (report == NULL || name == NULL) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0u; i < report->checks_len; ++i) {
|
||||
if (strcmp(report->checks[i].name, name) == 0) {
|
||||
return report->checks[i].status == expected;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool amduatd_build_collection_head_name(const char *name,
|
||||
char **out_name) {
|
||||
size_t name_len;
|
||||
size_t total_len;
|
||||
char *buffer;
|
||||
size_t offset = 0u;
|
||||
|
||||
if (name == NULL || out_name == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (!amduat_asl_pointer_name_is_valid(name)) {
|
||||
return false;
|
||||
}
|
||||
name_len = strlen(name);
|
||||
total_len = 11u + name_len + 5u + 1u;
|
||||
buffer = (char *)malloc(total_len);
|
||||
if (buffer == NULL) {
|
||||
return false;
|
||||
}
|
||||
memcpy(buffer + offset, "collection/", 11u);
|
||||
offset += 11u;
|
||||
memcpy(buffer + offset, name, name_len);
|
||||
offset += name_len;
|
||||
memcpy(buffer + offset, "/head", 5u);
|
||||
offset += 5u;
|
||||
buffer[offset] = '\0';
|
||||
*out_name = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool amduatd_build_collection_log_head_name(const char *name,
|
||||
char **out_name) {
|
||||
size_t name_len;
|
||||
size_t log_name_len;
|
||||
size_t total_len;
|
||||
char *buffer;
|
||||
size_t offset = 0u;
|
||||
|
||||
if (name == NULL || out_name == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (!amduat_asl_pointer_name_is_valid(name)) {
|
||||
return false;
|
||||
}
|
||||
name_len = strlen(name);
|
||||
log_name_len = 11u + name_len + 4u;
|
||||
total_len = 4u + log_name_len + 5u + 1u;
|
||||
buffer = (char *)malloc(total_len);
|
||||
if (buffer == NULL) {
|
||||
return false;
|
||||
}
|
||||
memcpy(buffer + offset, "log/", 4u);
|
||||
offset += 4u;
|
||||
memcpy(buffer + offset, "collection/", 11u);
|
||||
offset += 11u;
|
||||
memcpy(buffer + offset, name, name_len);
|
||||
offset += name_len;
|
||||
memcpy(buffer + offset, "/log", 4u);
|
||||
offset += 4u;
|
||||
memcpy(buffer + offset, "/head", 5u);
|
||||
offset += 5u;
|
||||
buffer[offset] = '\0';
|
||||
*out_name = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void amduatd_store_u32_le(uint8_t *out, uint32_t value) {
|
||||
out[0] = (uint8_t)(value & 0xffu);
|
||||
out[1] = (uint8_t)((value >> 8) & 0xffu);
|
||||
out[2] = (uint8_t)((value >> 16) & 0xffu);
|
||||
out[3] = (uint8_t)((value >> 24) & 0xffu);
|
||||
}
|
||||
|
||||
static void amduatd_store_u64_le(uint8_t *out, uint64_t value) {
|
||||
out[0] = (uint8_t)(value & 0xffu);
|
||||
out[1] = (uint8_t)((value >> 8) & 0xffu);
|
||||
out[2] = (uint8_t)((value >> 16) & 0xffu);
|
||||
out[3] = (uint8_t)((value >> 24) & 0xffu);
|
||||
out[4] = (uint8_t)((value >> 32) & 0xffu);
|
||||
out[5] = (uint8_t)((value >> 40) & 0xffu);
|
||||
out[6] = (uint8_t)((value >> 48) & 0xffu);
|
||||
out[7] = (uint8_t)((value >> 56) & 0xffu);
|
||||
}
|
||||
|
||||
static bool amduatd_build_edge_index_state_payload(
|
||||
amduat_reference_t graph_ref,
|
||||
amduat_octets_t *out_payload) {
|
||||
static const uint8_t k_magic[8] = {
|
||||
'A', 'S', 'L', 'E', 'I', 'X', '1', '\0'
|
||||
};
|
||||
amduat_octets_t ref_bytes = amduat_octets(NULL, 0u);
|
||||
uint8_t *payload = NULL;
|
||||
size_t payload_len = 0u;
|
||||
size_t offset = 0u;
|
||||
|
||||
if (out_payload == NULL) {
|
||||
return false;
|
||||
}
|
||||
*out_payload = amduat_octets(NULL, 0u);
|
||||
if (!amduat_enc_asl1_core_encode_reference_v1(graph_ref, &ref_bytes)) {
|
||||
return false;
|
||||
}
|
||||
if (ref_bytes.len > UINT32_MAX) {
|
||||
free((void *)ref_bytes.data);
|
||||
return false;
|
||||
}
|
||||
payload_len = 8u + 4u + 8u + 4u + ref_bytes.len;
|
||||
payload = (uint8_t *)malloc(payload_len);
|
||||
if (payload == NULL) {
|
||||
free((void *)ref_bytes.data);
|
||||
return false;
|
||||
}
|
||||
memcpy(payload + offset, k_magic, 8u);
|
||||
offset += 8u;
|
||||
amduatd_store_u32_le(payload + offset, 1u);
|
||||
offset += 4u;
|
||||
amduatd_store_u64_le(payload + offset, 0u);
|
||||
offset += 8u;
|
||||
amduatd_store_u32_le(payload + offset, (uint32_t)ref_bytes.len);
|
||||
offset += 4u;
|
||||
memcpy(payload + offset, ref_bytes.data, ref_bytes.len);
|
||||
offset += ref_bytes.len;
|
||||
free((void *)ref_bytes.data);
|
||||
*out_payload = amduat_octets(payload, payload_len);
|
||||
return offset == payload_len;
|
||||
}
|
||||
|
||||
static int amduatd_test_empty_store(void) {
|
||||
char *root = amduatd_test_make_temp_dir();
|
||||
amduat_asl_store_fs_config_t cfg;
|
||||
amduatd_store_ctx_t store_ctx;
|
||||
amduat_asl_store_t store;
|
||||
amduat_asl_pointer_store_t pointer_store;
|
||||
amduatd_space_t space;
|
||||
amduatd_cfg_t dcfg;
|
||||
amduatd_fed_cfg_t fed_cfg;
|
||||
amduatd_space_doctor_report_t report;
|
||||
|
||||
if (root == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
if (!amduat_asl_store_fs_init_root(root, NULL, &cfg)) {
|
||||
fprintf(stderr, "failed to init store root\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(&store_ctx, 0, sizeof(store_ctx));
|
||||
memset(&store, 0, sizeof(store));
|
||||
if (!amduatd_store_init(&store,
|
||||
&cfg,
|
||||
&store_ctx,
|
||||
root,
|
||||
AMDUATD_STORE_BACKEND_FS)) {
|
||||
fprintf(stderr, "failed to init fs backend\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!amduat_asl_pointer_store_init(&pointer_store, root)) {
|
||||
fprintf(stderr, "failed to init pointer store\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!amduatd_space_init(&space, NULL, false)) {
|
||||
fprintf(stderr, "failed to init space\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
memset(&dcfg, 0, sizeof(dcfg));
|
||||
dcfg.space = space;
|
||||
memset(&fed_cfg, 0, sizeof(fed_cfg));
|
||||
|
||||
if (!amduatd_space_doctor_run(&store,
|
||||
&pointer_store,
|
||||
&dcfg.space,
|
||||
&dcfg,
|
||||
&fed_cfg,
|
||||
&report)) {
|
||||
fprintf(stderr, "doctor run failed\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (report.backend != AMDUATD_STORE_BACKEND_FS) {
|
||||
fprintf(stderr, "expected fs backend\n");
|
||||
amduatd_space_doctor_report_free(&report);
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!amduatd_check_status(&report,
|
||||
"pointer_name_validation",
|
||||
AMDUATD_DOCTOR_OK) ||
|
||||
!amduatd_check_status(&report,
|
||||
"edges_index_head",
|
||||
AMDUATD_DOCTOR_WARN) ||
|
||||
!amduatd_check_status(&report,
|
||||
"edges_collection_snapshot_head",
|
||||
AMDUATD_DOCTOR_WARN) ||
|
||||
!amduatd_check_status(&report,
|
||||
"edges_collection_log_head",
|
||||
AMDUATD_DOCTOR_WARN) ||
|
||||
!amduatd_check_status(&report,
|
||||
"index_current_state",
|
||||
AMDUATD_DOCTOR_SKIPPED) ||
|
||||
!amduatd_check_status(&report,
|
||||
"index_log_scan",
|
||||
AMDUATD_DOCTOR_SKIPPED)) {
|
||||
fprintf(stderr, "unexpected doctor status for empty store\n");
|
||||
amduatd_space_doctor_report_free(&report);
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
amduatd_space_doctor_report_free(&report);
|
||||
free(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amduatd_test_minimal_fixture(void) {
|
||||
char *root = amduatd_test_make_temp_dir();
|
||||
amduat_asl_store_fs_config_t cfg;
|
||||
amduatd_store_ctx_t store_ctx;
|
||||
amduat_asl_store_t store;
|
||||
amduat_asl_pointer_store_t pointer_store;
|
||||
amduatd_space_t space;
|
||||
amduatd_cfg_t dcfg;
|
||||
amduatd_fed_cfg_t fed_cfg;
|
||||
amduatd_space_doctor_report_t report;
|
||||
amduat_reference_t none_ref;
|
||||
amduat_reference_t graph_ref;
|
||||
amduat_reference_t edge_index_ref;
|
||||
amduat_octets_t collection_name = amduat_octets(NULL, 0u);
|
||||
amduat_octets_t index_head_name = amduat_octets(NULL, 0u);
|
||||
char *collection_head = NULL;
|
||||
char *collection_log_head = NULL;
|
||||
amduat_octets_t payload = amduat_octets(NULL, 0u);
|
||||
bool swapped = false;
|
||||
|
||||
if (root == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
if (!amduat_asl_store_fs_init_root(root, NULL, &cfg)) {
|
||||
fprintf(stderr, "failed to init store root\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(&store_ctx, 0, sizeof(store_ctx));
|
||||
memset(&store, 0, sizeof(store));
|
||||
if (!amduatd_store_init(&store,
|
||||
&cfg,
|
||||
&store_ctx,
|
||||
root,
|
||||
AMDUATD_STORE_BACKEND_FS)) {
|
||||
fprintf(stderr, "failed to init fs backend\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!amduat_asl_pointer_store_init(&pointer_store, root)) {
|
||||
fprintf(stderr, "failed to init pointer store\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!amduatd_space_init(&space, NULL, false)) {
|
||||
fprintf(stderr, "failed to init space\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
memset(&dcfg, 0, sizeof(dcfg));
|
||||
dcfg.space = space;
|
||||
memset(&fed_cfg, 0, sizeof(fed_cfg));
|
||||
|
||||
{
|
||||
amduat_artifact_t none_artifact;
|
||||
if (!amduat_asl_none_artifact(&none_artifact)) {
|
||||
fprintf(stderr, "failed to build none artifact\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
if (amduat_asl_store_put(&store, none_artifact, &none_ref) !=
|
||||
AMDUAT_ASL_STORE_OK) {
|
||||
fprintf(stderr, "failed to store none artifact\n");
|
||||
amduat_artifact_free(&none_artifact);
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
amduat_artifact_free(&none_artifact);
|
||||
}
|
||||
|
||||
{
|
||||
amduat_artifact_t graph_artifact =
|
||||
amduat_artifact(amduat_octets("graph", 5u));
|
||||
if (amduat_asl_store_put(&store, graph_artifact, &graph_ref) !=
|
||||
AMDUAT_ASL_STORE_OK) {
|
||||
fprintf(stderr, "failed to store graph artifact\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!amduatd_build_edge_index_state_payload(graph_ref, &payload)) {
|
||||
fprintf(stderr, "failed to encode edge index state\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
if (amduat_asl_record_store_put(&store,
|
||||
amduat_octets("tgk/edge_index_state", 20u),
|
||||
payload,
|
||||
&edge_index_ref) != AMDUAT_ASL_STORE_OK) {
|
||||
fprintf(stderr, "failed to store edge index record\n");
|
||||
free((void *)payload.data);
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
free((void *)payload.data);
|
||||
|
||||
if (!amduatd_space_edges_collection_name(&dcfg.space, &collection_name) ||
|
||||
!amduatd_space_edges_index_head_name(&dcfg.space, &index_head_name)) {
|
||||
fprintf(stderr, "failed to build scoped names\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!amduatd_build_collection_head_name((const char *)collection_name.data,
|
||||
&collection_head) ||
|
||||
!amduatd_build_collection_log_head_name(
|
||||
(const char *)collection_name.data, &collection_log_head)) {
|
||||
fprintf(stderr, "failed to build collection heads\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (amduat_asl_pointer_cas(&pointer_store,
|
||||
(const char *)index_head_name.data,
|
||||
false,
|
||||
NULL,
|
||||
&edge_index_ref,
|
||||
&swapped) != AMDUAT_ASL_POINTER_OK || !swapped ||
|
||||
amduat_asl_pointer_cas(&pointer_store,
|
||||
collection_head,
|
||||
false,
|
||||
NULL,
|
||||
&none_ref,
|
||||
&swapped) != AMDUAT_ASL_POINTER_OK || !swapped ||
|
||||
amduat_asl_pointer_cas(&pointer_store,
|
||||
collection_log_head,
|
||||
false,
|
||||
NULL,
|
||||
&none_ref,
|
||||
&swapped) != AMDUAT_ASL_POINTER_OK || !swapped) {
|
||||
fprintf(stderr, "failed to seed pointers\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!amduatd_space_doctor_run(&store,
|
||||
&pointer_store,
|
||||
&dcfg.space,
|
||||
&dcfg,
|
||||
&fed_cfg,
|
||||
&report)) {
|
||||
fprintf(stderr, "doctor run failed\n");
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!amduatd_check_status(&report,
|
||||
"edges_index_head",
|
||||
AMDUATD_DOCTOR_OK) ||
|
||||
!amduatd_check_status(&report,
|
||||
"edges_collection_snapshot_head",
|
||||
AMDUATD_DOCTOR_OK) ||
|
||||
!amduatd_check_status(&report,
|
||||
"edges_collection_log_head",
|
||||
AMDUATD_DOCTOR_OK) ||
|
||||
!amduatd_check_status(&report,
|
||||
"edge_index_state_parse",
|
||||
AMDUATD_DOCTOR_OK) ||
|
||||
!amduatd_check_status(&report,
|
||||
"edge_index_graph_ref",
|
||||
AMDUATD_DOCTOR_OK) ||
|
||||
!amduatd_check_status(&report,
|
||||
"index_current_state",
|
||||
AMDUATD_DOCTOR_SKIPPED)) {
|
||||
fprintf(stderr, "unexpected doctor status for fixture\n");
|
||||
amduatd_space_doctor_report_free(&report);
|
||||
free(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
amduatd_space_doctor_report_free(&report);
|
||||
free((void *)collection_name.data);
|
||||
free((void *)index_head_name.data);
|
||||
free(collection_head);
|
||||
free(collection_log_head);
|
||||
free(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
if (amduatd_test_empty_store() != 0) {
|
||||
return 1;
|
||||
}
|
||||
if (amduatd_test_minimal_fixture() != 0) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in a new issue