Răsfoiți Sursa

fix(storage): align client with upstream smartbotic-database 2.3

Three related corrections to the upstream-database migration.

Address: the repo carried three different values for one endpoint.
config/*.json used 9010, the C++ fallbacks in webserver_service.cpp and
runner_service.cpp still used 9001 from the removed in-repo database, and
the daemon actually binds 9004. As shipped the services could not connect
without a DATABASE_ADDRESS override. Everything now defaults to 9004,
matching both the upstream client header and smartbotic-db-cli.

Project namespace: one smartbotic-database instance serves several
projects, but the adapter never set Client::Config::project, so every
project collided in the shared "default" namespace. Adds database_project
(default "smartbotic-automation"), threaded through both services and
overridable via DATABASE_PROJECT.

listCollections: upstream's call is instance-wide, not project-scoped, and
was passed straight through. That leaked every other project's collections
to the WebUI, and those names then read back empty because the client
applies Config::project to them. Now filtered to the configured project
with the prefix stripped, so names round-trip through the other methods.

Version floor raised to 2.3.1, the release this is verified against.
fszontagh 1 lună în urmă
părinte
comite
ac7b466108

+ 2 - 1
config/runner.json

@@ -4,7 +4,8 @@
   "webserver_address": "${WEBSERVER_ADDRESS:localhost:8090}",
   "node_sync_address": "${NODE_SYNC_ADDRESS:localhost:9012}",
   "credential_service_address": "${CREDENTIAL_SERVICE_ADDRESS:localhost:9013}",
-  "database_address": "${DATABASE_ADDRESS:localhost:9010}",
+  "database_address": "${DATABASE_ADDRESS:localhost:9004}",
+  "database_project": "${DATABASE_PROJECT:smartbotic-automation}",
   "max_message_size_mb": 64,
   "node_sync": {
     "enabled": true,

+ 2 - 1
config/webserver.json

@@ -3,7 +3,8 @@
   "node_sync_port": 9012,
   "credential_service_port": 9013,
   "static_files_path": "${WEBUI_PATH:./webui/dist}",
-  "database_address": "${DATABASE_ADDRESS:localhost:9010}",
+  "database_address": "${DATABASE_ADDRESS:localhost:9004}",
+  "database_project": "${DATABASE_PROJECT:smartbotic-automation}",
   "runners": {
     "load_balancing": "least-connections",
     "heartbeat_timeout_sec": 30,

+ 35 - 3
lib/storage/storage_client.cpp

@@ -1,5 +1,6 @@
 #include "storage/storage_client.hpp"
 
+#include <algorithm>
 #include <chrono>
 #include <smartbotic/database/client.hpp>
 
@@ -14,6 +15,11 @@ using common::Result;
 
 namespace {
 
+// Upstream treats an empty project and "default" as the same back-compat namespace.
+bool project_isDefault(const std::string& project) {
+    return project.empty() || project == "default";
+}
+
 uint32_t msToSec(int64_t ms) {
     if (ms <= 0) return 0;
     return static_cast<uint32_t>((ms + 999) / 1000);
@@ -52,17 +58,22 @@ dbc::Client::Filter buildFilter(const std::string& field, const nlohmann::json&
 
 class StorageClient::Impl {
 public:
-    explicit Impl(const StorageClientConfig& cfg) {
+    explicit Impl(const StorageClientConfig& cfg) : project_(cfg.project) {
         dbc::Client::Config config;
         config.address = cfg.address;
         config.timeoutMs = static_cast<uint32_t>(cfg.timeout_ms);
+        config.project = cfg.project;
         client_ = std::make_unique<dbc::Client>(std::move(config));
         if (!client_->connect()) {
-            LOG_WARN("StorageClient: connect() to {} returned false", cfg.address);
+            LOG_WARN("StorageClient: connect() to {} (project '{}') returned false",
+                     cfg.address, cfg.project);
+        } else {
+            LOG_INFO("StorageClient: connected to {} (project '{}')", cfg.address, cfg.project);
         }
     }
 
     std::unique_ptr<dbc::Client> client_;
+    std::string project_;
 };
 
 StorageClient::StorageClient(const StorageClientConfig& config)
@@ -188,8 +199,29 @@ Result<void> StorageClient::dropCollection(const std::string& name) {
     return {};
 }
 
+// Upstream's listCollections() is instance-wide, not project-scoped: it returns
+// every collection on the daemon, qualified as "<project>:<collection>" for named
+// projects and bare for the default one. Every other method here applies
+// Config::project itself, so an unfiltered list both leaks other projects'
+// collections and hands callers names that resolve to nothing when read back.
+// Scope to our own project and strip the prefix, so the names round-trip.
 std::vector<std::string> StorageClient::listCollections() {
-    return impl_->client_->listCollections();
+    const bool is_default = project_isDefault(impl_->project_);
+    const std::string prefix = (is_default ? std::string("default") : impl_->project_) + ":";
+
+    std::vector<std::string> out;
+    for (const auto& name : impl_->client_->listCollections()) {
+        if (name.rfind(prefix, 0) == 0) {
+            out.push_back(name.substr(prefix.size()));
+        } else if (is_default && name.find(':') == std::string::npos) {
+            // Legacy unqualified collections belong to the default project.
+            out.push_back(name);
+        }
+    }
+
+    std::sort(out.begin(), out.end());
+    out.erase(std::unique(out.begin(), out.end()), out.end());
+    return out;
 }
 
 Result<nlohmann::json> StorageClient::getVersion(const std::string& collection,

+ 9 - 1
lib/storage/storage_client.hpp

@@ -12,7 +12,15 @@ namespace smartbotic::storage {
 
 // Storage client configuration
 struct StorageClientConfig {
-    std::string address = "localhost:9010";  // upstream smartbotic-database default in our deployments
+    std::string address = "localhost:9004";  // upstream smartbotic-database default; override via DATABASE_ADDRESS
+
+    // Multi-tenant project namespace (upstream client >= 2.3). One database daemon
+    // is shared by several projects; every collection this client touches is sent
+    // as "<project>:<collection>", keeping our data isolated from other services
+    // on the same instance. "default" would put us in the shared back-compat
+    // namespace — do not use it. Override via DATABASE_PROJECT.
+    std::string project = "smartbotic-automation";
+
     int timeout_ms = 5000;
     int max_message_size_mb = 64;            // kept for back-compat; ignored by adapter
 };

+ 2 - 1
packaging/deb/config/env

@@ -1,7 +1,8 @@
 # /etc/smartbotic-automation/env
 # Override config values with environment variables here.
 # Example:
-# DATABASE_ADDRESS=localhost:9010
+# DATABASE_ADDRESS=localhost:9004
+# DATABASE_PROJECT=smartbotic-automation
 # JWT_SECRET=...
 # CREDENTIALS_MASTER_KEY=...
 # LOG_LEVEL=info

+ 2 - 1
packaging/deb/config/runner.json

@@ -4,7 +4,8 @@
   "webserver_address": "${WEBSERVER_ADDRESS:localhost:8090}",
   "node_sync_address": "${NODE_SYNC_ADDRESS:localhost:9012}",
   "credential_service_address": "${CREDENTIAL_SERVICE_ADDRESS:localhost:9013}",
-  "database_address": "${DATABASE_ADDRESS:localhost:9010}",
+  "database_address": "${DATABASE_ADDRESS:localhost:9004}",
+  "database_project": "${DATABASE_PROJECT:smartbotic-automation}",
   "max_message_size_mb": 64,
   "node_sync": {
     "enabled": true,

+ 2 - 1
packaging/deb/config/webserver.json

@@ -3,7 +3,8 @@
   "node_sync_port": 9012,
   "credential_service_port": 9013,
   "static_files_path": "${WEBUI_PATH:/usr/share/smartbotic-automation/webui}",
-  "database_address": "${DATABASE_ADDRESS:localhost:9010}",
+  "database_address": "${DATABASE_ADDRESS:localhost:9004}",
+  "database_project": "${DATABASE_PROJECT:smartbotic-automation}",
   "runners": {
     "load_balancing": "least-connections",
     "heartbeat_timeout_sec": 30,

+ 1 - 1
packaging/deb/templates/control.smartbotic-automation

@@ -12,5 +12,5 @@ Homepage: https://smartbotics.ai
 Section: contrib/utils
 Priority: optional
 License: proprietary
-Depends: smartbotic-database (>= 1.7.5), libsmartbotic-db-client (>= 1.7.5), libc6 (>= 2.17), adduser, libssl3t64, libprotobuf32t64, libgrpc++1.51t64, libspdlog1.15, libfmt10, libuuid1, libcurl4t64, zlib1g, libbrotli1, libwebsockets19t64, nodejs
+Depends: smartbotic-database (>= 2.3.1), libsmartbotic-db-client (>= 2.3.1), libc6 (>= 2.17), adduser, libssl3t64, libprotobuf32t64, libgrpc++1.51t64, libspdlog1.15, libfmt10, libuuid1, libcurl4t64, zlib1g, libbrotli1, libwebsockets19t64, nodejs
 Installed-Size: {{INSTALLED_SIZE}}

+ 3 - 1
src/runner/runner_service.cpp

@@ -306,6 +306,7 @@ RunnerService::RunnerService(const RunnerServiceConfig& config)
     // Initialize storage client
     storage::StorageClientConfig storage_config;
     storage_config.address = config_.database_address;
+    storage_config.project = config_.database_project;
     storage_config.max_message_size_mb = config_.max_message_size_mb;
     storage_ = std::make_unique<storage::StorageClient>(storage_config);
 
@@ -484,7 +485,8 @@ RunnerServiceConfig RunnerService::loadConfig(const std::filesystem::path& path)
         config.webserver_address = cfg.getOr<std::string>("webserver_address", "localhost:8080");
         config.node_sync_address = cfg.getOr<std::string>("node_sync_address", "localhost:9002");
         config.credential_service_address = cfg.getOr<std::string>("credential_service_address", "localhost:9003");
-        config.database_address = cfg.getOr<std::string>("database_address", "localhost:9001");
+        config.database_address = cfg.getOr<std::string>("database_address", "localhost:9004");
+        config.database_project = cfg.getOr<std::string>("database_project", "smartbotic-automation");
         config.max_message_size_mb = cfg.getOr<int>("max_message_size_mb", 64);
 
         // Node sync configuration

+ 2 - 1
src/runner/runner_service.hpp

@@ -80,7 +80,8 @@ struct RunnerServiceConfig {
     std::string webserver_address = "localhost:8080";    // HTTP for registration
     std::string node_sync_address = "localhost:9002";    // gRPC for node sync
     std::string credential_service_address = "localhost:9003";  // gRPC for credentials
-    std::string database_address = "localhost:9001";
+    std::string database_address = "localhost:9004";
+    std::string database_project = "smartbotic-automation";
     int max_message_size_mb = 64;      // Max gRPC message size in MB
     NodeRegistryConfig node_registry_config;
     WorkflowEngineConfig workflow_engine_config;

+ 3 - 1
src/webserver/webserver_service.cpp

@@ -26,6 +26,7 @@ WebServerService::WebServerService(const WebServerServiceConfig& config)
     // Initialize storage client
     storage::StorageClientConfig storage_config;
     storage_config.address = config_.database_address;
+    storage_config.project = config_.database_project;
     storage_ = std::make_unique<storage::StorageClient>(storage_config);
 
     // Initialize JWT
@@ -103,7 +104,8 @@ WebServerServiceConfig WebServerService::loadConfig(const std::filesystem::path&
         config.node_sync_port = cfg.getOr<int>("node_sync_port", 9002);
         config.credential_service_port = cfg.getOr<int>("credential_service_port", 9003);
         config.static_files_path = cfg.getOr<std::string>("static_files_path", "./webui/dist");
-        config.database_address = cfg.getOr<std::string>("database_address", "localhost:9001");
+        config.database_address = cfg.getOr<std::string>("database_address", "localhost:9004");
+        config.database_project = cfg.getOr<std::string>("database_project", "smartbotic-automation");
 
         // Runner config
         config.runner_config.heartbeat_timeout_sec =

+ 2 - 1
src/webserver/webserver_service.hpp

@@ -50,7 +50,8 @@ struct WebServerServiceConfig {
     int node_sync_port = 9002;      // gRPC port for node sync service
     int credential_service_port = 9003;  // gRPC port for credential service
     std::string static_files_path = "./webui/dist";
-    std::string database_address = "localhost:9001";
+    std::string database_address = "localhost:9004";
+    std::string database_project = "smartbotic-automation";
     runners::RunnerRegistryConfig runner_config;
     runners::LoadBalancerConfig load_balancer_config;
     auth::JwtUtils::Config jwt_config;