node_registry.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. #include "node_registry.hpp"
  2. #include "logging/logger.hpp"
  3. #include "common/time_utils.hpp"
  4. namespace smartbotic::runner {
  5. using namespace common;
  6. nlohmann::json NodeDefinition::toJson() const {
  7. nlohmann::json j;
  8. j["id"] = id;
  9. j["name"] = name;
  10. j["category"] = category;
  11. j["version"] = version;
  12. j["description"] = description;
  13. j["icon"] = icon;
  14. j["isTrigger"] = is_trigger;
  15. j["configSchema"] = config_schema;
  16. j["inputSchema"] = input_schema;
  17. j["outputSchema"] = output_schema;
  18. j["inputs"] = nlohmann::json::array();
  19. for (const auto& input : inputs) {
  20. j["inputs"].push_back({
  21. {"name", input.name},
  22. {"displayName", input.display_name},
  23. {"type", input.type},
  24. {"required", input.required}
  25. });
  26. }
  27. j["outputs"] = nlohmann::json::array();
  28. for (const auto& output : outputs) {
  29. nlohmann::json out_obj = {
  30. {"name", output.name},
  31. {"displayName", output.display_name},
  32. {"type", output.type}
  33. };
  34. if (!output.color.empty()) {
  35. out_obj["color"] = output.color;
  36. }
  37. j["outputs"].push_back(out_obj);
  38. }
  39. return j;
  40. }
  41. NodeDefinition NodeDefinition::fromProto(const proto::NodeDefinitionWithCode& proto) {
  42. NodeDefinition node;
  43. node.id = proto.id();
  44. node.name = proto.name();
  45. node.category = proto.category();
  46. node.version = proto.version();
  47. node.description = proto.description();
  48. node.icon = proto.icon();
  49. node.code = proto.code();
  50. node.is_trigger = proto.is_trigger();
  51. node.last_modified = proto.updated_at();
  52. // Parse schemas
  53. try {
  54. if (!proto.config_schema().empty() && proto.config_schema() != "null") {
  55. node.config_schema = nlohmann::json::parse(proto.config_schema());
  56. }
  57. } catch (...) {}
  58. try {
  59. if (!proto.input_schema().empty() && proto.input_schema() != "null") {
  60. node.input_schema = nlohmann::json::parse(proto.input_schema());
  61. }
  62. } catch (...) {}
  63. try {
  64. if (!proto.output_schema().empty() && proto.output_schema() != "null") {
  65. node.output_schema = nlohmann::json::parse(proto.output_schema());
  66. }
  67. } catch (...) {}
  68. // Parse inputs
  69. for (const auto& input : proto.inputs()) {
  70. NodeIO io;
  71. io.name = input.name();
  72. io.display_name = input.display_name();
  73. io.type = input.type();
  74. io.required = input.required();
  75. node.inputs.push_back(io);
  76. }
  77. // Parse outputs
  78. for (const auto& output : proto.outputs()) {
  79. NodeIO io;
  80. io.name = output.name();
  81. io.display_name = output.display_name();
  82. io.type = output.type();
  83. io.color = output.color();
  84. node.outputs.push_back(io);
  85. }
  86. return node;
  87. }
  88. NodeRegistry::NodeRegistry(const NodeRegistryConfig& config)
  89. : config_(config) {
  90. // Create gRPC channel
  91. channel_ = grpc::CreateChannel(config_.webserver_address, grpc::InsecureChannelCredentials());
  92. stub_ = proto::NodeSyncService::NewStub(channel_);
  93. }
  94. NodeRegistry::~NodeRegistry() {
  95. stop();
  96. }
  97. void NodeRegistry::start() {
  98. if (running_ || !config_.sync_enabled) {
  99. return;
  100. }
  101. running_ = true;
  102. // Load initial nodes
  103. auto result = loadNodesFromWebServer();
  104. if (result.failed()) {
  105. LOG_WARN("Failed to load initial nodes: {}", result.error().message());
  106. }
  107. // Start subscription thread for live updates
  108. subscribe_thread_ = std::thread(&NodeRegistry::subscribeLoop, this);
  109. LOG_INFO("Node registry sync started with {}", config_.webserver_address);
  110. }
  111. void NodeRegistry::stop() {
  112. if (!running_) {
  113. return;
  114. }
  115. LOG_INFO("Stopping node registry...");
  116. running_ = false;
  117. // Cancel any active gRPC stream to unblock subscribeLoop
  118. {
  119. std::lock_guard<std::mutex> lock(context_mutex_);
  120. if (active_context_) {
  121. active_context_->TryCancel();
  122. }
  123. }
  124. if (subscribe_thread_.joinable()) {
  125. subscribe_thread_.join();
  126. }
  127. LOG_INFO("Node registry sync stopped");
  128. }
  129. Result<void> NodeRegistry::loadNodesFromWebServer() {
  130. proto::GetAllNodesRequest request;
  131. proto::GetAllNodesResponse response;
  132. grpc::ClientContext context;
  133. context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(30));
  134. auto status = stub_->GetAllNodes(&context, request, &response);
  135. if (!status.ok()) {
  136. connected_ = false;
  137. return Error(ErrorCode::Internal, "Failed to get nodes: " + status.error_message());
  138. }
  139. connected_ = true;
  140. std::unique_lock lock(mutex_);
  141. nodes_.clear();
  142. for (const auto& proto_node : response.nodes()) {
  143. NodeDefinition node = NodeDefinition::fromProto(proto_node);
  144. nodes_[node.id] = node;
  145. LOG_DEBUG("Loaded node: {} ({})", node.id, node.name);
  146. }
  147. LOG_INFO("Loaded {} node definitions from webserver", nodes_.size());
  148. return {};
  149. }
  150. void NodeRegistry::subscribeLoop() {
  151. while (running_) {
  152. proto::SubscribeToNodeChangesRequest request;
  153. request.set_runner_id("runner_" + std::to_string(reinterpret_cast<uintptr_t>(this)));
  154. grpc::ClientContext context;
  155. // Store context pointer so stop() can cancel it
  156. {
  157. std::lock_guard<std::mutex> lock(context_mutex_);
  158. active_context_ = &context;
  159. }
  160. auto reader = stub_->SubscribeToNodeChanges(&context, request);
  161. if (!reader) {
  162. {
  163. std::lock_guard<std::mutex> lock(context_mutex_);
  164. active_context_ = nullptr;
  165. }
  166. if (!running_) break;
  167. LOG_WARN("Failed to subscribe to node changes, retrying...");
  168. std::this_thread::sleep_for(std::chrono::milliseconds(config_.reconnect_interval_ms));
  169. continue;
  170. }
  171. connected_ = true;
  172. LOG_INFO("Subscribed to node changes from webserver");
  173. proto::NodeChangeEvent event;
  174. while (running_ && reader->Read(&event)) {
  175. handleNodeChange(event);
  176. }
  177. // Clear context pointer
  178. {
  179. std::lock_guard<std::mutex> lock(context_mutex_);
  180. active_context_ = nullptr;
  181. }
  182. connected_ = false;
  183. auto status = reader->Finish();
  184. if (!status.ok() && running_) {
  185. LOG_WARN("Node change subscription ended: {}, reconnecting...", status.error_message());
  186. std::this_thread::sleep_for(std::chrono::milliseconds(config_.reconnect_interval_ms));
  187. }
  188. }
  189. }
  190. void NodeRegistry::handleNodeChange(const proto::NodeChangeEvent& event) {
  191. switch (event.type()) {
  192. case proto::NodeChangeEvent::CHANGE_TYPE_CREATED:
  193. case proto::NodeChangeEvent::CHANGE_TYPE_UPDATED: {
  194. NodeDefinition node = NodeDefinition::fromProto(event.node());
  195. {
  196. std::unique_lock lock(mutex_);
  197. nodes_[node.id] = node;
  198. }
  199. LOG_INFO("Node {}: {}",
  200. event.type() == proto::NodeChangeEvent::CHANGE_TYPE_CREATED ? "created" : "updated",
  201. node.id);
  202. if (reload_callback_) {
  203. reload_callback_(node.id);
  204. }
  205. break;
  206. }
  207. case proto::NodeChangeEvent::CHANGE_TYPE_DELETED: {
  208. {
  209. std::unique_lock lock(mutex_);
  210. nodes_.erase(event.node_id());
  211. }
  212. LOG_INFO("Node deleted: {}", event.node_id());
  213. if (reload_callback_) {
  214. reload_callback_(event.node_id());
  215. }
  216. break;
  217. }
  218. default:
  219. break;
  220. }
  221. }
  222. std::optional<NodeDefinition> NodeRegistry::getNode(const std::string& id) const {
  223. std::shared_lock lock(mutex_);
  224. auto it = nodes_.find(id);
  225. if (it != nodes_.end()) {
  226. return it->second;
  227. }
  228. return std::nullopt;
  229. }
  230. std::vector<NodeDefinition> NodeRegistry::getAllNodes() const {
  231. std::shared_lock lock(mutex_);
  232. std::vector<NodeDefinition> result;
  233. result.reserve(nodes_.size());
  234. for (const auto& [id, node] : nodes_) {
  235. result.push_back(node);
  236. }
  237. return result;
  238. }
  239. std::vector<NodeDefinition> NodeRegistry::getNodesByCategory(const std::string& category) const {
  240. std::shared_lock lock(mutex_);
  241. std::vector<NodeDefinition> result;
  242. for (const auto& [id, node] : nodes_) {
  243. if (node.category == category) {
  244. result.push_back(node);
  245. }
  246. }
  247. return result;
  248. }
  249. std::vector<NodeDefinition> NodeRegistry::getTriggerNodes() const {
  250. std::shared_lock lock(mutex_);
  251. std::vector<NodeDefinition> result;
  252. for (const auto& [id, node] : nodes_) {
  253. if (node.is_trigger) {
  254. result.push_back(node);
  255. }
  256. }
  257. return result;
  258. }
  259. Result<std::string> NodeRegistry::getNodeCode(const std::string& id) const {
  260. std::shared_lock lock(mutex_);
  261. auto it = nodes_.find(id);
  262. if (it == nodes_.end()) {
  263. return Error(ErrorCode::NotFound, "Node not found: " + id);
  264. }
  265. return it->second.code;
  266. }
  267. void NodeRegistry::setReloadCallback(NodeReloadCallback callback) {
  268. reload_callback_ = std::move(callback);
  269. }
  270. } // namespace smartbotic::runner