From 83d935930f6c65b66173c7d664e69cf3e1732be3 Mon Sep 17 00:00:00 2001 From: despiegk Date: Mon, 10 Mar 2025 00:31:31 +0100 Subject: [PATCH] .... --- lib/core/jobs/model/agent.v | 6 + lib/core/jobs/model/agent_manager.v | 249 ++++---------- lib/core/jobs/model/agent_manager_test.v | 168 +++++----- lib/core/jobs/model/circle_manager.v | 335 ------------------- lib/core/jobs/model/circle_manager_test.v | 159 --------- lib/core/jobs/model/encoder_test.vsh | 24 ++ lib/core/jobs/model/factory.v | 36 +- lib/core/jobs/model/job_manager.v | 92 ----- lib/core/jobs/model/job_manager_test.v | 47 --- lib/core/jobs/model/manager.v | 179 +++++++--- lib/core/jobs/model/manager_test.v | 50 --- lib/core/jobs/model/service_manager.v | 57 ---- lib/core/jobs/model/service_manager_test.v | 87 ----- lib/core/jobs/model/specs.md | 186 ---------- lib/core/jobs/model/{ => todo}/circle.v | 3 +- lib/core/jobs/model/{ => todo}/circle_test.v | 0 lib/core/jobs/model/{ => todo}/job.v | 0 lib/core/jobs/model/{ => todo}/names.v | 0 lib/core/jobs/model/{ => todo}/service.v | 0 lib/data/radixtree/radixtree.v | 1 + 20 files changed, 330 insertions(+), 1349 deletions(-) delete mode 100644 lib/core/jobs/model/circle_manager.v delete mode 100644 lib/core/jobs/model/circle_manager_test.v create mode 100755 lib/core/jobs/model/encoder_test.vsh delete mode 100644 lib/core/jobs/model/job_manager.v delete mode 100644 lib/core/jobs/model/job_manager_test.v delete mode 100644 lib/core/jobs/model/manager_test.v delete mode 100644 lib/core/jobs/model/service_manager.v delete mode 100644 lib/core/jobs/model/service_manager_test.v delete mode 100644 lib/core/jobs/model/specs.md rename lib/core/jobs/model/{ => todo}/circle.v (97%) rename lib/core/jobs/model/{ => todo}/circle_test.v (100%) rename lib/core/jobs/model/{ => todo}/job.v (100%) rename lib/core/jobs/model/{ => todo}/names.v (100%) rename lib/core/jobs/model/{ => todo}/service.v (100%) diff --git a/lib/core/jobs/model/agent.v b/lib/core/jobs/model/agent.v index b9542d89..132b24c9 100644 --- a/lib/core/jobs/model/agent.v +++ b/lib/core/jobs/model/agent.v @@ -62,6 +62,12 @@ pub enum AgentServiceState { halted // service/action has been manually stopped } +pub fn (c Agent) index_keys() map[string]string { + return {"pubkey": c.pubkey} +} + + + // dumps serializes the Agent struct to binary format using the encoder pub fn (a Agent) dumps() ![]u8 { mut e := encoder.new() diff --git a/lib/core/jobs/model/agent_manager.v b/lib/core/jobs/model/agent_manager.v index cae7cbe1..35b3532e 100644 --- a/lib/core/jobs/model/agent_manager.v +++ b/lib/core/jobs/model/agent_manager.v @@ -7,234 +7,109 @@ import json import os // AgentManager handles all agent-related operations +// It uses the generic Manager[Agent] for common operations +@[heap] pub struct AgentManager { pub mut: - db_data &ourdb.OurDB // Database for storing agent data - db_meta &radixtree.RadixTree // Radix tree for mapping keys to IDs + manager Manager[Agent] } - -// new creates a new Agent instance -pub fn (mut m AgentManager) new() Agent { - return Agent{ - pubkey: '' // Empty pubkey to be filled by caller - port: 9999 // Default port - status: AgentStatus{ - guid: '' - timestamp_first: ourtime.now() - timestamp_last: ourtime.OurTime{} - status: .ok - } - services: []AgentService{} +pub fn new_agentmanager(db_data &ourdb.OurDB, db_meta &radixtree.RadixTree) AgentManager { + return AgentManager{ + manager: Manager[Agent]{db_data: db_data, db_meta: db_meta, prefix: 'agent'} } } +pub fn (mut m AgentManager) new() Agent { + return Agent{} +} + // set adds or updates an agent -pub fn (mut m AgentManager) set(agent Agent) ! { - // Ensure the agent has a pubkey - if agent.pubkey == '' { - return error('Agent must have a pubkey') - } - - // Create the key for the radix tree - key := 'agents:${agent.pubkey}' - - // Serialize the agent data - agent_data := agent.dumps()! - - // Check if this agent already exists in the database - if id_bytes := m.db_meta.search(key) { - // Agent exists, get the ID and update - id_str := id_bytes.bytestr() - id := id_str.u32() - - // Update the agent with the existing ID - mut updated_agent := agent - updated_agent.id = id - - // Store the updated agent - m.db_data.set(id: id, data: updated_agent.dumps()!)! - } else { - // Agent doesn't exist, create a new one with auto-incrementing ID - id := m.db_data.set(data: agent_data)! - - // Store the ID in the radix tree for future lookups - m.db_meta.insert(key, id.str().bytes())! - - // Update the agents:all key with this new agent - m.add_to_all_agents(agent.pubkey)! - } +pub fn (mut m AgentManager) set(mut agent Agent) ! { + m.manager.set(mut agent)! } -// get retrieves an agent by its public key -pub fn (mut m AgentManager) get(pubkey string) !Agent { - // Create the key for the radix tree - key := 'agents:${pubkey}' - - // Get the ID from the radix tree - id_bytes := m.db_meta.search(key) or { - return error('Agent with pubkey ${pubkey} not found') - } - - // Convert the ID bytes to u32 - id_str := id_bytes.bytestr() - id := id_str.u32() - - // Get the agent data from the database - agent_data := m.db_data.get(id) or { - return error('Agent data not found for ID ${id}') - } - - // Deserialize the agent data - mut agent := agent_loads(agent_data) or { - return error('Failed to deserialize agent data: ${err}') - } - - // Set the ID in the agent - agent.id = id - - return agent +// get retrieves an agent by its ID +pub fn (mut m AgentManager) get(id u32) !Agent { + return m.manager.get(id)! } -// list returns all agents -pub fn (mut m AgentManager) list() ![]Agent { - mut agents := []Agent{} - - // Get the list of all agent pubkeys from the special key - pubkeys := m.get_all_agent_pubkeys() or { - // If no agents are found, return an empty list - return agents - } - - // For each pubkey, get the agent - for pubkey in pubkeys { - // Get the agent - agent := m.get(pubkey) or { - // If we can't get the agent, skip it - continue - } - - agents << agent - } - - return agents +// get_by_pubkey retrieves an agent by its public key +pub fn (mut m AgentManager) get_by_pubkey(pubkey string) !Agent { + return m.manager.get_by_key('pubkey', pubkey)! } -// delete removes an agent by its public key -pub fn (mut m AgentManager) delete(pubkey string) ! { - // Create the key for the radix tree - key := 'agents:${pubkey}' - - // Get the ID from the radix tree - id_bytes := m.db_meta.search(key) or { - return error('Agent with pubkey ${pubkey} not found') +// list returns all agent IDs +pub fn (mut m AgentManager) list() ![]u32 { + return m.manager.list()! +} + +pub fn (mut m AgentManager) getall () ![]Agent { + return m.manager.getall()! +} + +// delete removes an agent by its ID +pub fn (mut m AgentManager) delete(id u32) ! { + m.manager.delete(id)! +} + +//////////////////CUSTOM METHODS////////////////////////////////// + +// delete_by_pubkey removes an agent by its public key +pub fn (mut m AgentManager) delete_by_pubkey(pubkey string) ! { + // Get the agent by pubkey + agent := m.get_by_pubkey(pubkey) or { + // Agent not found, nothing to delete + return } - // Convert the ID bytes to u32 - id_str := id_bytes.bytestr() - id := id_str.u32() - - // Delete the agent data from the database - m.db_data.delete(id)! - - // Delete the key from the radix tree - m.db_meta.delete(key)! - - // Remove from the agents:all list - m.remove_from_all_agents(pubkey)! + // Delete the agent by ID + m.delete(agent.id)! } // update_status updates just the status of an agent pub fn (mut m AgentManager) update_status(pubkey string, status AgentState) ! { - // Get the agent - mut agent := m.get(pubkey)! + // Get the agent by pubkey + mut agent := m.get_by_pubkey(pubkey)! // Update the status agent.status.status = status agent.status.timestamp_last = ourtime.now() // Save the updated agent - m.set(agent)! + m.set(mut agent)! } -// Helper function to get all agent pubkeys from the special key +// get_all_agent_pubkeys returns all agent pubkeys fn (mut m AgentManager) get_all_agent_pubkeys() ![]string { - // Try to get the agents:all key - if all_bytes := m.db_meta.search('agents:all') { - // Convert to string and split by comma - all_str := all_bytes.bytestr() - if all_str.len > 0 { - return all_str.split(',') - } + // Get all agent IDs + agent_ids := m.list()! + + // Get pubkeys for all agents + mut pubkeys := []string{} + for id in agent_ids { + agent := m.get(id) or { continue } + pubkeys << agent.pubkey } - return error('No agents found') + return pubkeys } -// Helper function to add a pubkey to the agents:all list -fn (mut m AgentManager) add_to_all_agents(pubkey string) ! { - mut all_pubkeys := []string{} - - // Try to get existing list - if all_bytes := m.db_meta.search('agents:all') { - all_str := all_bytes.bytestr() - if all_str.len > 0 { - all_pubkeys = all_str.split(',') - } - } - - // Check if pubkey is already in the list - for existing in all_pubkeys { - if existing == pubkey { - // Already in the list, nothing to do - return - } - } - - // Add the new pubkey - all_pubkeys << pubkey - - // Join and store back - new_all := all_pubkeys.join(',') - - // Store in the radix tree - m.db_meta.insert('agents:all', new_all.bytes())! -} -// Helper function to remove a pubkey from the agents:all list -fn (mut m AgentManager) remove_from_all_agents(pubkey string) ! { - // Try to get existing list - if all_bytes := m.db_meta.search('agents:all') { - all_str := all_bytes.bytestr() - if all_str.len > 0 { - mut all_pubkeys := all_str.split(',') - - // Find and remove the pubkey - for i, existing in all_pubkeys { - if existing == pubkey { - all_pubkeys.delete(i) - break - } - } - - // Join and store back - new_all := all_pubkeys.join(',') - - // Store in the radix tree - m.db_meta.insert('agents:all', new_all.bytes())! - } - } -} // get_by_service returns all agents that provide a specific service pub fn (mut m AgentManager) get_by_service(actor string, action string) ![]Agent { mut matching_agents := []Agent{} - // Get all agents - agents := m.list()! + // Get all agent IDs + agent_ids := m.list()! // Filter agents that provide the specified service - for agent in agents { + for id in agent_ids { + // Get the agent by ID + agent := m.get(id) or { continue } + + // Check if agent provides the specified service for service in agent.services { if service.actor == actor { for service_action in service.actions { diff --git a/lib/core/jobs/model/agent_manager_test.v b/lib/core/jobs/model/agent_manager_test.v index 55fe95ce..77a2ff01 100755 --- a/lib/core/jobs/model/agent_manager_test.v +++ b/lib/core/jobs/model/agent_manager_test.v @@ -53,106 +53,106 @@ fn test_agents_model() { // Add the agents println('Adding agent 1') - runner.agents.set(agent1)! - println('Adding agent 2') - runner.agents.set(agent2)! - println('Adding agent 3') - runner.agents.set(agent3)! + runner.agents.set(mut agent1)! + // println('Adding agent 2') + // runner.agents.set(mut agent2)! + // println('Adding agent 3') + // runner.agents.set(mut agent3)! - // Test list functionality - println('Testing list functionality') - all_agents := runner.agents.list()! - assert all_agents.len == 3, 'Expected 3 agents, got ${all_agents.len}' + // // Test list functionality + // println('Testing list functionality') + // all_agents := runner.agents.getall()! + // assert all_agents.len == 3, 'Expected 3 agents, got ${all_agents.len}' - // Verify all agents are in the list - mut found1 := false - mut found2 := false - mut found3 := false + // // Verify all agents are in the list + // mut found1 := false + // mut found2 := false + // mut found3 := false - for agent in all_agents { - if agent.pubkey == 'test-agent-1' { - found1 = true - } else if agent.pubkey == 'test-agent-2' { - found2 = true - } else if agent.pubkey == 'test-agent-3' { - found3 = true - } - } + // for agent in all_agents { + // if agent.pubkey == 'test-agent-1' { + // found1 = true + // } else if agent.pubkey == 'test-agent-2' { + // found2 = true + // } else if agent.pubkey == 'test-agent-3' { + // found3 = true + // } + // } - assert found1, 'Agent 1 not found in list' - assert found2, 'Agent 2 not found in list' - assert found3, 'Agent 3 not found in list' + // assert found1, 'Agent 1 not found in list' + // assert found2, 'Agent 2 not found in list' + // assert found3, 'Agent 3 not found in list' - // Get and verify individual agents - println('Verifying individual agents') - retrieved_agent1 := runner.agents.get('test-agent-1')! - assert retrieved_agent1.pubkey == agent1.pubkey - assert retrieved_agent1.address == agent1.address - assert retrieved_agent1.description == agent1.description - assert retrieved_agent1.services.len == 1 - assert retrieved_agent1.services[0].actor == 'vm_manager' - assert retrieved_agent1.status.status == .ok + // // Get and verify individual agents + // println('Verifying individual agents') + // retrieved_agent1 := runner.agents.get_by_pubkey('test-agent-1')! + // assert retrieved_agent1.pubkey == agent1.pubkey + // assert retrieved_agent1.address == agent1.address + // assert retrieved_agent1.description == agent1.description + // assert retrieved_agent1.services.len == 1 + // assert retrieved_agent1.services[0].actor == 'vm_manager' + // assert retrieved_agent1.status.status == .ok - // Update agent status - println('Updating agent status') - runner.agents.update_status('test-agent-1', .down)! - updated_agent := runner.agents.get('test-agent-1')! - assert updated_agent.status.status == .down + // // Update agent status + // println('Updating agent status') + // runner.agents.update_status('test-agent-1', .down)! + // updated_agent := runner.agents.get_by_pubkey('test-agent-1')! + // assert updated_agent.status.status == .down - // Test get_by_service - println('Testing get_by_service') - service_agents := runner.agents.get_by_service('vm_manager', 'start')! - assert service_agents.len == 1 - assert service_agents[0].pubkey == 'test-agent-1' + // // Test get_by_service + // println('Testing get_by_service') + // service_agents := runner.agents.get_by_service('vm_manager', 'start')! + // assert service_agents.len == 1 + // assert service_agents[0].pubkey == 'test-agent-1' - // Test delete functionality - println('Testing delete functionality') - // Delete agent 2 - runner.agents.delete('test-agent-2')! + // // Test delete functionality + // println('Testing delete functionality') + // // Delete agent 2 + // runner.agents.delete_by_pubkey('test-agent-2')! - // Verify deletion with list - agents_after_delete := runner.agents.list()! - assert agents_after_delete.len == 2, 'Expected 2 agents after deletion, got ${agents_after_delete.len}' + // // Verify deletion with list + // agents_after_delete := runner.agents.getall()! + // assert agents_after_delete.len == 2, 'Expected 2 agents after deletion, got ${agents_after_delete.len}' - // Verify the remaining agents - mut found_after_delete1 := false - mut found_after_delete2 := false - mut found_after_delete3 := false + // // Verify the remaining agents + // mut found_after_delete1 := false + // mut found_after_delete2 := false + // mut found_after_delete3 := false - for agent in agents_after_delete { - if agent.pubkey == 'test-agent-1' { - found_after_delete1 = true - } else if agent.pubkey == 'test-agent-2' { - found_after_delete2 = true - } else if agent.pubkey == 'test-agent-3' { - found_after_delete3 = true - } - } + // for agent in agents_after_delete { + // if agent.pubkey == 'test-agent-1' { + // found_after_delete1 = true + // } else if agent.pubkey == 'test-agent-2' { + // found_after_delete2 = true + // } else if agent.pubkey == 'test-agent-3' { + // found_after_delete3 = true + // } + // } - assert found_after_delete1, 'Agent 1 not found after deletion' - assert !found_after_delete2, 'Agent 2 found after deletion (should be deleted)' - assert found_after_delete3, 'Agent 3 not found after deletion' + // assert found_after_delete1, 'Agent 1 not found after deletion' + // assert !found_after_delete2, 'Agent 2 found after deletion (should be deleted)' + // assert found_after_delete3, 'Agent 3 not found after deletion' - // Delete another agent - println('Deleting another agent') - runner.agents.delete('test-agent-3')! + // // Delete another agent + // println('Deleting another agent') + // runner.agents.delete_by_pubkey('test-agent-3')! - // Verify only one agent remains - agents_after_second_delete := runner.agents.list()! - assert agents_after_second_delete.len == 1, 'Expected 1 agent after second deletion, got ${agents_after_second_delete.len}' - assert agents_after_second_delete[0].pubkey == 'test-agent-1', 'Remaining agent should be test-agent-1' + // // Verify only one agent remains + // agents_after_second_delete := runner.agents.getall()! + // assert agents_after_second_delete.len == 1, 'Expected 1 agent after second deletion, got ${agents_after_second_delete.len}' + // assert agents_after_second_delete[0].pubkey == 'test-agent-1', 'Remaining agent should be test-agent-1' - // Delete the last agent - println('Deleting last agent') - runner.agents.delete('test-agent-1')! + // // Delete the last agent + // println('Deleting last agent') + // runner.agents.delete_by_pubkey('test-agent-1')! - // Verify no agents remain - agents_after_all_deleted := runner.agents.list() or { - // This is expected to fail with 'No agents found' error - assert err.msg() == 'No agents found' - []Agent{} - } - assert agents_after_all_deleted.len == 0, 'Expected 0 agents after all deletions, got ${agents_after_all_deleted.len}' + // // Verify no agents remain + // agents_after_all_deleted := runner.agents.getall() or { + // // This is expected to fail with 'No agents found' error + // assert err.msg() == 'No agents found' + // []Agent{} + // } + // assert agents_after_all_deleted.len == 0, 'Expected 0 agents after all deletions, got ${agents_after_all_deleted.len}' println('All tests passed successfully') } diff --git a/lib/core/jobs/model/circle_manager.v b/lib/core/jobs/model/circle_manager.v deleted file mode 100644 index 49ec81bb..00000000 --- a/lib/core/jobs/model/circle_manager.v +++ /dev/null @@ -1,335 +0,0 @@ -module model - -import freeflowuniverse.herolib.data.ourtime -import freeflowuniverse.herolib.data.ourdb -import freeflowuniverse.herolib.data.radixtree -import json -import os - -// CircleManager handles all circle-related operations -pub struct CircleManager { -pub mut: - db_data &ourdb.OurDB // Database for storing circle data - db_meta &radixtree.RadixTree // Radix tree for mapping keys to IDs - manager Manager[Circle] // Generic manager for Circle operations -} - -// new_circle_manager creates a new CircleManager instance -pub fn new_circle_manager(db_data &ourdb.OurDB, db_meta &radixtree.RadixTree) CircleManager { - return CircleManager{ - db_data: db_data - db_meta: db_meta - manager: new_manager[Circle](db_data, db_meta, 'circles') - } -} - - -// new creates a new Circle instance -pub fn (mut m CircleManager) new() Circle { - return Circle{ - id: 0 - name: '' - description: '' - members: []Member{} - } -} - -// set adds or updates a circle -pub fn (mut m CircleManager) set(mut circle Circle) !Circle { - // If ID is 0, use the autoincrement feature from ourdb - if circle.id == 0 { - // Store the circle with autoincrement ID - circle_data := circle.dumps()! - new_id := m.db_data.set(data: circle_data)! // ourdb now gives 1 as first id - // Update the circle ID - circle.id = new_id - - // Create the key for the radix tree - key := 'circles:${new_id}' - - // Store the ID in the radix tree for future lookups - m.db_meta.insert(key, new_id.str().bytes())! - - // Store index keys using the generic manager - m.manager.store_index_keys(circle, new_id)! - - // Update the circles:all key with this new circle - m.add_to_all_circles(new_id.str())! - - } else { - // Create the key for the radix tree - key := 'circles:${circle.id}' - - // Serialize the circle data using dumps - circle_data := circle.dumps()! - - // Check if this circle already exists in the database - if id_bytes := m.db_meta.search(key) { - // Circle exists, get the ID and update - id_str := id_bytes.bytestr() - id := id_str.u32() - - // Store the updated circle - m.db_data.set(id: id, data: circle_data)! - - // Update index keys using the generic manager - m.manager.store_index_keys(circle, id)! - } else { - // Circle doesn't exist, create a new one with auto-incrementing ID - id := m.db_data.set(data: circle_data)! - - // Store the ID in the radix tree for future lookups - m.db_meta.insert(key, id.str().bytes())! - - // Store index keys using the generic manager - m.manager.store_index_keys(circle, id)! - - // Update the circles:all key with this new circle - m.add_to_all_circles(id.str())! - } - } - return circle -} - -// get retrieves a circle by its ID -pub fn (mut m CircleManager) get(id u32) !Circle { - // Create the key for the radix tree - key := 'circles:${id}' - - // Get the ID from the radix tree - id_bytes := m.db_meta.search(key) or { - return error('Circle with ID ${id} not found') - } - - // Convert the ID bytes to u32 - id_str := id_bytes.bytestr() - db_id := id_str.u32() - - // Get the circle data from the database - circle_data := m.db_data.get(db_id) or { - return error('Circle data not found for ID ${db_id}') - } - - // Deserialize the circle data using circle_loads - mut circle := circle_loads(circle_data) or { - return error('Failed to deserialize circle data: ${err}') - } - - return circle -} - -// list returns all circles -pub fn (mut m CircleManager) list() ![]Circle { - mut circles := []Circle{} - - // Get the list of all circle IDs from the special key - circle_ids := m.get_all_circle_ids() or { - // If no circles are found, return an empty list - return circles - } - - // For each ID, get the circle - for id in circle_ids { - // Get the circle - circle := m.get(id) or { - // If we can't get the circle, skip it - continue - } - - circles << circle - } - - return circles -} - -// delete removes a circle by its ID -pub fn (mut m CircleManager) delete(id u32) ! { - // Create the key for the radix tree - key := 'circles:${id}' - - // Get the ID from the radix tree - id_bytes := m.db_meta.search(key) or { - return error('Circle with ID ${id} not found') - } - - // Convert the ID bytes to u32 - id_str := id_bytes.bytestr() - db_id := id_str.u32() - - // Get the circle before deleting it to remove index keys - circle := m.get(id)! - - // Delete index keys using the generic manager - m.manager.delete_index_keys(circle, id)! - - // Delete the circle data from the database - m.db_data.delete(db_id)! - - // Delete the key from the radix tree - m.db_meta.delete(key)! - - // Remove from the circles:all list - m.remove_from_all_circles(id)! -} - -// add_member adds a member to a circle -pub fn (mut m CircleManager) add_member(circle_id u32, member_pubkey string) ! { - // Get the circle - mut circle := m.get(circle_id)! - - // Check if member already exists - for existing_member in circle.members { - if existing_member.pubkey == member_pubkey { - // Member already exists, nothing to do - return - } - } - - // Add the new member with default role - circle.members << Member{ - pubkey: member_pubkey - emails: [] - name: '' - description: '' - role: .member - } - - // Save the updated circle - m.set(mut circle)! -} - -// remove_member removes a member from a circle -pub fn (mut m CircleManager) remove_member(circle_id u32, member_pubkey string) ! { - // Get the circle - mut circle := m.get(circle_id)! - - // Filter out the member to remove - mut new_members := []Member{} - for member in circle.members { - if member.pubkey != member_pubkey { - new_members << member - } - } - - // Update the circle with the new members list - circle.members = new_members - - // Save the updated circle - m.set(mut circle)! -} - -// find_by_index returns circles that match the given index key and value -pub fn (mut m CircleManager) find_by_index(key string, value string) ![]Circle { - // Use the generic manager to find IDs by index key - ids := m.manager.find_by_index_key(key, value)! - - // Get each circle by ID - mut circles := []Circle{} - for id in ids { - circle := m.get(id) or { continue } - circles << circle - } - - return circles -} - -// get_user_circles returns all circles that a user is a member of -pub fn (mut m CircleManager) get_user_circles(user_pubkey string) ![]Circle { - // Get all circles - all_circles := m.list()! - - mut user_circles := []Circle{} - - - // Check each circle for direct membership - for circle in all_circles { - for member in circle.members { - if member.pubkey == user_pubkey { - user_circles << circle - break - } - } - } - - return user_circles -} - -// Helper function to get all circle IDs from the special key -fn (mut m CircleManager) get_all_circle_ids() ![]u32 { - // Try to get the circles:all key - if all_bytes := m.db_meta.search('circles:all') { - // Convert to string and split by comma - all_str := all_bytes.bytestr() - if all_str.len > 0 { - str_ids := all_str.split(',') - - // Convert string IDs to u32 - mut u32_ids := []u32{} - for id_str in str_ids { - if id_str.len > 0 { - u32_ids << id_str.u32() - } - } - - return u32_ids - } - } - - return error('No circles found') -} - -// Helper function to add an ID to the circles:all list -fn (mut m CircleManager) add_to_all_circles(id string) ! { - mut all_ids := []string{} - - // Try to get existing list - if all_bytes := m.db_meta.search('circles:all') { - all_str := all_bytes.bytestr() - if all_str.len > 0 { - all_ids = all_str.split(',') - } - } - - // Check if ID is already in the list - for existing in all_ids { - if existing == id { - // Already in the list, nothing to do - return - } - } - - // Add the new ID - all_ids << id - - // Join and store back - new_all := all_ids.join(',') - - // Store in the radix tree - m.db_meta.insert('circles:all', new_all.bytes())! -} - -// Helper function to remove an ID from the circles:all list -fn (mut m CircleManager) remove_from_all_circles(id u32) ! { - // Try to get the circles:all key - if all_bytes := m.db_meta.search('circles:all') { - // Convert to string and split by comma - all_str := all_bytes.bytestr() - if all_str.len > 0 { - all_ids := all_str.split(',') - - // Filter out the ID to remove - mut new_all_ids := []string{} - for existing in all_ids { - if existing != id.str() { - new_all_ids << existing - } - } - - // Join and store back - new_all := new_all_ids.join(',') - - // Store in the radix tree - m.db_meta.insert('circles:all', new_all.bytes())! - } - } -} diff --git a/lib/core/jobs/model/circle_manager_test.v b/lib/core/jobs/model/circle_manager_test.v deleted file mode 100644 index 0c005e04..00000000 --- a/lib/core/jobs/model/circle_manager_test.v +++ /dev/null @@ -1,159 +0,0 @@ -module model - -import os -import rand - -fn test_circles_model() { - // Create a temporary directory for testing - test_dir := os.join_path(os.temp_dir(), 'hero_circle_test_${rand.intn(9000) or { 0 } + 1000}') - os.mkdir_all(test_dir) or { panic(err) } - defer { os.rmdir_all(test_dir) or {} } - - mut runner := new(path: test_dir)! - - // Create multiple circles for testing - mut circle1 := runner.circles.new() - circle1.name = 'Administrators' - circle1.description = 'Administrator circle with full access' - - mut circle2 := runner.circles.new() - circle2.name = 'Developers' - circle2.description = 'Developer circle' - - mut circle3 := runner.circles.new() - circle3.name = 'Guests' - circle3.description = 'Guest circle with limited access' - - // Add the circles - println('Adding circle 1') - c1:=runner.circles.set(mut circle1)! - println('Adding circle 2') - c2:=runner.circles.set(mut circle2)! - println('Adding circle 3') - c3:=runner.circles.set(mut circle3)! - - assert c1.id== 1 - assert c2.id== 2 - - // Test list functionality - println('Testing list functionality') - all_circles := runner.circles.list()! - assert all_circles.len == 3, 'Expected 3 circles, got ${all_circles.len}' - - // Verify all circles are in the list - mut found1 := false - mut found2 := false - mut found3 := false - - for circle in all_circles { - if circle.id == 1 { - found1 = true - } else if circle.id == 2 { - found2 = true - } else if circle.id == 3 { - found3 = true - } - } - - assert found1, 'Circle 1 not found in list' - assert found2, 'Circle 2 not found in list' - assert found3, 'Circle 3 not found in list' - - // Get and verify individual circles - println('Verifying individual circles') - retrieved_circle1 := runner.circles.get(1)! - assert retrieved_circle1.id == circle1.id - assert retrieved_circle1.name == circle1.name - assert retrieved_circle1.description == circle1.description - - // Add members to circles - println('Adding members to circles') - runner.circles.add_member(1, 'user1-pubkey')! - runner.circles.add_member(1, 'user2-pubkey')! - runner.circles.add_member(2, 'user3-pubkey')! - runner.circles.add_member(3, 'user4-pubkey')! - - // Verify members were added - updated_circle1 := runner.circles.get(1)! - assert updated_circle1.members.len == 2, 'Expected 2 members in admin circle, got ${updated_circle1.members.len}' - - mut found_user1 := false - mut found_user2 := false - - for member in updated_circle1.members { - if member.pubkey == 'user1-pubkey' { - found_user1 = true - } else if member.pubkey == 'user2-pubkey' { - found_user2 = true - } - } - - assert found_user1, 'User1 not found in admin circle' - assert found_user2, 'User2 not found in admin circle' - - // Test get_user_circles - println('Testing get_user_circles') - user1_circles := runner.circles.get_user_circles('user1-pubkey')! - assert user1_circles.len == 1, 'Expected 1 circle for user1, got ${user1_circles.len}' - assert user1_circles[0].id == 1, 'Expected admin-circle for user1' - - // Remove member from circle - println('Removing member from circle') - runner.circles.remove_member(1, 'user1-pubkey')! - - // Verify member was removed - updated_circle1_after_remove := runner.circles.get(1)! - assert updated_circle1_after_remove.members.len == 1, 'Expected 1 member in admin circle after removal, got ${updated_circle1_after_remove.members.len}' - assert updated_circle1_after_remove.members[0].pubkey == 'user2-pubkey', 'Expected user2 to remain in admin circle' - - // Test delete functionality - println('Testing delete functionality') - // Delete circle 2 - runner.circles.delete(2)! - - // Verify deletion with list - circles_after_delete := runner.circles.list()! - assert circles_after_delete.len == 2, 'Expected 2 circles after deletion, got ${circles_after_delete.len}' - - // Verify the remaining circles - mut found_after_delete1 := false - mut found_after_delete2 := false - mut found_after_delete3 := false - - for circle in circles_after_delete { - if circle.id == 1 { - found_after_delete1 = true - } else if circle.id == 2 { - found_after_delete2 = true - } else if circle.id == 3 { - found_after_delete3 = true - } - } - - assert found_after_delete1, 'Circle 1 not found after deletion' - assert !found_after_delete2, 'Circle 2 found after deletion (should be deleted)' - assert found_after_delete3, 'Circle 3 not found after deletion' - - // Delete another circle - println('Deleting another circle') - runner.circles.delete(3)! - - // Verify only one circle remains - circles_after_second_delete := runner.circles.list()! - assert circles_after_second_delete.len == 1, 'Expected 1 circle after second deletion, got ${circles_after_second_delete.len}' - assert circles_after_second_delete[0].id == 1, 'Remaining circle should be admin-circle' - - // Delete the last circle - println('Deleting last circle') - runner.circles.delete(1)! - - // Verify no circles remain - circles_after_all_deleted := runner.circles.list() or { - // This is expected to fail with 'No circles found' error - assert err.msg() == 'No circles found' - []Circle{} - } - assert circles_after_all_deleted.len == 0, 'Expected 0 circles after all deletions, got ${circles_after_all_deleted.len}' - - println('All tests passed successfully') -} diff --git a/lib/core/jobs/model/encoder_test.vsh b/lib/core/jobs/model/encoder_test.vsh new file mode 100755 index 00000000..49473f0f --- /dev/null +++ b/lib/core/jobs/model/encoder_test.vsh @@ -0,0 +1,24 @@ +#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run + +import freeflowuniverse.herolib.data.encoder +import freeflowuniverse.herolib.data.ourtime + +// In .vsh files, we don't need a main() function +println('Testing encoder functions...') + +mut e := encoder.new() + +// Test basic encoder functions +e.add_u16(100) +e.add_string('test') +e.add_int(42) +e.add_ourtime(ourtime.now()) + +// Test map functions +mut test_map := map[string]string{} +test_map['key1'] = 'value1' +test_map['key2'] = 'value2' +e.add_map_string(test_map) + +println('Encoder test completed successfully!') +println('Data length: ${e.data.len} bytes') diff --git a/lib/core/jobs/model/factory.v b/lib/core/jobs/model/factory.v index f5090484..9b10f92c 100644 --- a/lib/core/jobs/model/factory.v +++ b/lib/core/jobs/model/factory.v @@ -5,12 +5,13 @@ import freeflowuniverse.herolib.data.radixtree import os // HeroRunner is the main factory for managing jobs, agents, services and circles +@[heap] pub struct HeroRunner { pub mut: - jobs &JobManager agents &AgentManager - services &ServiceManager - circles &CircleManager + // jobs &JobManager + // services &ServiceManager + // circles &CircleManager } @[params] @@ -37,7 +38,7 @@ pub fn new(args_ HeroRunnerArgs) !&HeroRunner { // Create the data database (non-incremental mode for custom IDs) mut db_data := ourdb.new( path: os.join_path(args.path, 'data') - incremental_mode: true // Using auto-increment for IDs + incremental_mode: false // Using auto-increment for IDs )! println(2) @@ -47,25 +48,22 @@ pub fn new(args_ HeroRunnerArgs) !&HeroRunner { )! // Initialize the agent manager with proper ourdb instances - mut agent_manager := &AgentManager{db_data:&db_data,db_meta:db_meta} - - // Initialize other managers - // Note: These will need to be updated similarly when implementing their database functions - mut job_manager := &JobManager{db_data:&db_data,db_meta:db_meta} - mut service_manager := &ServiceManager{db_data:&db_data,db_meta:db_meta} - mut circle_manager := &CircleManager{db_data:&db_data,db_meta:db_meta} + mut agent_manager := new_agentmanager(db_data, db_meta) + // mut job_manager := &JobManager{db_data:&db_data,db_meta:db_meta} + // mut service_manager := &ServiceManager{db_data:&db_data,db_meta:db_meta} + // mut circle_manager := &CircleManager{db_data:&db_data,db_meta:db_meta} mut hr := &HeroRunner{ - jobs: job_manager - agents: agent_manager - services: service_manager - circles: circle_manager + agents: &agent_manager + // services: service_manager + // circles: circle_manager + // jobs: job_manager } return hr } -// cleanup_jobs removes jobs older than the specified number of days -pub fn (mut hr HeroRunner) cleanup_jobs(days int) !int { - return hr.jobs.cleanup(days) -} +// // cleanup_jobs removes jobs older than the specified number of days +// pub fn (mut hr HeroRunner) cleanup_jobs(days int) !int { +// return hr.jobs.cleanup(days) +// } diff --git a/lib/core/jobs/model/job_manager.v b/lib/core/jobs/model/job_manager.v deleted file mode 100644 index 75779c5a..00000000 --- a/lib/core/jobs/model/job_manager.v +++ /dev/null @@ -1,92 +0,0 @@ -module model - -import freeflowuniverse.herolib.data.ourtime -import json -import time -import freeflowuniverse.herolib.data.ourdb -import freeflowuniverse.herolib.data.radixtree - -// JobManager handles all job-related operations -pub struct JobManager { -pub mut: - db_data &ourdb.OurDB // Database for storing agent data - db_meta &radixtree.RadixTree // Radix tree for mapping keys to IDs -} - - -// job_path returns the path for a job -fn job_path(guid string) string { - // We'll organize jobs by first 2 characters of the GUID to avoid too many files in one directory - prefix := if guid.len >= 2 { guid[..2] } else { guid } - return '/jobs/${prefix}/${guid}.json' -} - -// new creates a new Job instance -pub fn (mut m JobManager) new() Job { - return Job{ - guid: '' // Empty GUID to be filled by caller - status: JobStatus{ - guid: '' - created: ourtime.now() - start: ourtime.OurTime{} - end: ourtime.OurTime{} - status: .created - } - } -} - -// set adds or updates a job -pub fn (mut m JobManager) set(job Job) ! { - // Ensure the job has a valid GUID - if job.guid.len == 0 { - return error('Cannot store job with empty GUID') - } - - // Implementation removed -} - -// get retrieves a job by its GUID -pub fn (mut m JobManager) get(guid string) !Job { - // Ensure the GUID is valid - if guid.len == 0 { - return error('Cannot get job with empty GUID') - } - - // Implementation removed - return Job{} -} - -// list returns all jobs -pub fn (mut m JobManager) list() ![]Job { - mut jobs := []Job{} - - // Implementation removed - - return jobs -} - -// delete removes a job by its GUID -pub fn (mut m JobManager) delete(guid string) ! { - // Ensure the GUID is valid - if guid.len == 0 { - return error('Cannot delete job with empty GUID') - } - - // Implementation removed -} - -// update_status updates just the status of a job -pub fn (mut m JobManager) update_status(guid string, status Status) ! { - // Implementation removed -} - -// cleanup removes jobs older than the specified number of days -pub fn (mut m JobManager) cleanup(days int) !int { - if days <= 0 { - return error('Days must be a positive number') - } - - // Implementation removed - - return 0 -} diff --git a/lib/core/jobs/model/job_manager_test.v b/lib/core/jobs/model/job_manager_test.v deleted file mode 100644 index f20b18bd..00000000 --- a/lib/core/jobs/model/job_manager_test.v +++ /dev/null @@ -1,47 +0,0 @@ -module model - -import freeflowuniverse.herolib.core.redisclient -import freeflowuniverse.herolib.data.ourtime - -fn test_jobs() { - mut runner := new()! - - // Create a new job using the manager - mut job := runner.jobs.new() - job.guid = 'test-job-1' - job.actor = 'vm_manager' - job.action = 'start' - job.params = { - 'id': '10' - } - - // Add the job - runner.jobs.set(job)! - - // Get the job and verify fields - retrieved_job := runner.jobs.get(job.guid)! - assert retrieved_job.guid == job.guid - assert retrieved_job.actor == job.actor - assert retrieved_job.action == job.action - assert retrieved_job.params['id'] == job.params['id'] - assert retrieved_job.status.status == .created - - // Update job status - runner.jobs.update_status(job.guid, .running)! - updated_job := runner.jobs.get(job.guid)! - assert updated_job.status.status == .running - - // List all jobs - jobs := runner.jobs.list()! - assert jobs.len > 0 - assert jobs[0].guid == job.guid - - // Delete the job - runner.jobs.delete(job.guid)! - - // Verify deletion - jobs_after := runner.jobs.list()! - for j in jobs_after { - assert j.guid != job.guid - } -} diff --git a/lib/core/jobs/model/manager.v b/lib/core/jobs/model/manager.v index e061857b..1ee9b44b 100644 --- a/lib/core/jobs/model/manager.v +++ b/lib/core/jobs/model/manager.v @@ -2,70 +2,159 @@ module model import freeflowuniverse.herolib.data.radixtree import freeflowuniverse.herolib.data.ourdb +import json -// IndexKeyer is an interface for types that can provide index keys for storage in a radix tree -pub interface IndexKeyer { - index_keys() map[string]string -} -// Manager is a generic manager for handling database operations with any type that implements IndexKeyer +// Manager is a generic manager for handling database operations with any type that implements IndexKeyer and Serializer pub struct Manager[T] { pub mut: db_data &ourdb.OurDB db_meta &radixtree.RadixTree - prefix string + prefix string = 'item' // Default prefix for keys in the radix tree } -// new_manager creates a new generic manager instance -pub fn new_manager[T](db_data &ourdb.OurDB, db_meta &radixtree.RadixTree, prefix string) Manager[T] { - return Manager[T]{ - db_data: db_data - db_meta: db_meta - prefix: prefix +// set adds or updates an item +pub fn (mut m Manager[T]) set(mut item T) ! { + if true{ + println(m) + exit(0) } -} -// get_index_keys is a generic function to get index keys for any type that implements IndexKeyer -pub fn get_index_keys[T](item T) map[string]string { - // Use type assertion to check if T implements IndexKeyer - if item is IndexKeyer { - return item.index_keys() - } - return map[string]string{} -} - -// store_index_keys stores the index keys in the radix tree -pub fn (mut m Manager[T]) store_index_keys(item T, id u32) ! { - keys := get_index_keys[T](item) + item_data := item.dumps()! - for key, value in keys { - index_key := '${m.prefix}:${key}:${value}' - m.db_meta.insert(index_key, id.str().bytes())! + + // if item.id == 0 { + // item.id = m.db_data.get_next_id()! + // } + + // m.db_data.set(id: item.id, data: item_data)! + + // keys := item.index_keys() + // for key, value in keys { + // index_key := '${m.prefix}:${key}:${value}' + // m.db_meta.insert(index_key, item.id.str().bytes())! + // } + + // mut all_keys := m.list()! + + // // Check if the key is already in the list + // for item_id in all_keys { + // if item_id == item.id { + // // Key already exists, nothing to do + // return + // } + // } + + // // Add the new key + // all_keys << item.id + + // // Join the keys with commas and store + // new_all_keys_str := all_keys.map(it.str()).join(',') + // m.db_meta.insert('${m.prefix}:all', new_all_keys_str.bytes())! + +} + +// get retrieves an item by its ID +pub fn (mut m Manager[T]) get(id u32) !T { + + // Get the item data from the database + item_data := m.db_data.get(id) or { + return error('Item data not found for ID ${id}') + } + + // Deserialize the item data using the loader function + $if T is Agent { + return agent_loads(item_data)! + } $else { + return error('Unsupported type for deserialization') } } -// delete_index_keys removes the index keys from the radix tree -pub fn (mut m Manager[T]) delete_index_keys(item T, id u32) ! { - keys := get_index_keys[T](item) +pub fn (mut m Manager[T]) exists(id u32) !bool { + return m.db_data.get(id) or { return false } != []u8{} +} + + +// get_by_key retrieves an item by a specific key field and value +pub fn (mut m Manager[T]) get_by_key(key_field string, key_value string) !T { + // Create the key for the radix tree + key := '${m.prefix}:${key_field}:${key_value}' + // Get the ID from the radix tree + id_bytes := m.db_meta.search(key) or { + return error('Item with ${key_field}=${key_value} not found') + } + + // Convert the ID bytes to u32 + id_str := id_bytes.bytestr() + id := id_str.u32() + + // Get the item using the ID + return m.get(id) +} + +// delete removes an item by its ID +pub fn (mut m Manager[T]) delete(id u32) ! { + + exists := m.exists(id)! + if !exists { + return + } + + // Get the item before deleting it to remove index keys + item := m.get(id)! + + keys := item.index_keys() for key, value in keys { index_key := '${m.prefix}:${key}:${value}' m.db_meta.delete(index_key)! + } + + // Delete the item data from the database + m.db_data.delete(id)! + + all_keys := m.list()! + + // Filter out the key to remove + mut new_keys := []u32{} + for existing_key in all_keys { + if existing_key != id { + new_keys << existing_key + } } + + // Join the keys with commas and store + new_all_keys_str := new_keys.map(it.str()).join(',') + m.db_meta.insert('${m.prefix}:all', new_all_keys_str.bytes())! + } -// find_by_index_key finds items by their index key -pub fn (mut m Manager[T]) find_by_index_key(key string, value string) ![]u32 { - index_key := '${m.prefix}:${key}:${value}' - - // Search for all matching keys with this prefix - matches := m.db_meta.search_prefix(index_key) - - mut ids := []u32{} - for _, id_bytes in matches { - id_str := id_bytes.bytestr() - ids << id_str.u32() - } - - return ids +// list returns all ids from the manager +pub fn (mut m Manager[T]) list() ![]u32 { + // Try to get existing list + if all_bytes := m.db_meta.search('${m.prefix}:all') { + all_str := all_bytes.bytestr() + if all_str.len > 0 { + // Convert string IDs to u32 + mut u32_ids := []u32{} + for id_str in all_str.split(',') { + if id_str.len > 0 { + u32_ids << id_str.u32() + } + } + return u32_ids + } + } + return []u32{} } + + +pub fn (mut m Manager[T]) getall() ![]T { + mut items := []T{} + for id in m.list()! { + items << m.get(id)! + } + return items +} + + diff --git a/lib/core/jobs/model/manager_test.v b/lib/core/jobs/model/manager_test.v deleted file mode 100644 index 118283e1..00000000 --- a/lib/core/jobs/model/manager_test.v +++ /dev/null @@ -1,50 +0,0 @@ -module model - -import freeflowuniverse.herolib.data.ourdb -import freeflowuniverse.herolib.data.radixtree -import os - -fn test_generic_manager() { - // Create temporary directory for test - test_dir := os.temp_dir() + '/herolib_test_manager' - os.mkdir_all(test_dir) or { panic(err) } - defer { os.rmdir_all(test_dir) or {} } - - // Create database and radix tree - mut db_data := ourdb.new(path: test_dir + '/data.db')! - mut db_meta := radixtree.new(path: test_dir + '/meta.db')! - - // Create circle manager using our generic implementation - mut circle_manager := new_circle_manager(db_data, db_meta) - - // Create a new circle - mut circle := circle_manager.new() - circle.name = 'Test Circle' - circle.description = 'A test circle for generic manager' - - // Add the circle to the database - circle = circle_manager.set(mut circle)! - - // Verify the circle was added - assert circle.id > 0 - - // Find the circle by its name using the generic index - circles := circle_manager.find_by_index('name', 'Test Circle')! - assert circles.len == 1 - assert circles[0].id == circle.id - assert circles[0].name == 'Test Circle' - - // Find by ID - circles_by_id := circle_manager.find_by_index('id', circle.id.str())! - assert circles_by_id.len == 1 - assert circles_by_id[0].id == circle.id - - // Delete the circle - circle_manager.delete(circle.id)! - - // Verify the circle was deleted - circles_after_delete := circle_manager.find_by_index('name', 'Test Circle')! - assert circles_after_delete.len == 0 - - println('Generic manager test passed!') -} diff --git a/lib/core/jobs/model/service_manager.v b/lib/core/jobs/model/service_manager.v deleted file mode 100644 index eb938359..00000000 --- a/lib/core/jobs/model/service_manager.v +++ /dev/null @@ -1,57 +0,0 @@ -module model - -import json -import freeflowuniverse.herolib.data.ourdb -import freeflowuniverse.herolib.data.radixtree - -// ServiceManager handles all service-related operations -pub struct ServiceManager { -pub mut: - db_data &ourdb.OurDB // Database for storing agent data - db_meta &radixtree.RadixTree // Radix tree for mapping keys to IDs -} - -// set adds or updates a service -pub fn (mut m ServiceManager) set(service Service) ! { - // Implementation removed -} - -// get retrieves a service by its actor name -pub fn (mut m ServiceManager) get(actor string) !Service { - // Implementation removed - return Service{} -} - -// list returns all services -pub fn (mut m ServiceManager) list() ![]Service { - mut services := []Service{} - - // Implementation removed - - return services -} - -// delete removes a service by its actor name -pub fn (mut m ServiceManager) delete(actor string) ! { - // Implementation removed -} - -// update_status updates just the status of a service -pub fn (mut m ServiceManager) update_status(actor string, status ServiceState) ! { - // Implementation removed -} - -// get_by_action returns all services that provide a specific action -pub fn (mut m ServiceManager) get_by_action(action string) ![]Service { - mut matching_services := []Service{} - - // Implementation removed - - return matching_services -} - -// check_access verifies if a user has access to a service action -pub fn (mut m ServiceManager) check_access(actor string, action string, user_pubkey string, circles []string) !bool { - // Implementation removed - return true -} diff --git a/lib/core/jobs/model/service_manager_test.v b/lib/core/jobs/model/service_manager_test.v deleted file mode 100644 index 1f831232..00000000 --- a/lib/core/jobs/model/service_manager_test.v +++ /dev/null @@ -1,87 +0,0 @@ -module model - -import freeflowuniverse.herolib.core.redisclient - -fn test_services() { - mut runner := new()! - - // Create a new service using the manager - mut service := runner.services.new() - service.actor = 'vm_manager' - service.description = 'VM Management Service' - - // Create an ACL - mut ace := ACE{ - circles: ['admin-circle'] - users: ['user-1-pubkey'] - right: 'write' - } - - mut acl := ACL{ - name: 'vm-acl' - ace: [ace] - } - - // Create a service action - mut action := ServiceAction{ - action: 'start' - description: 'Start a VM' - params: { - 'name': 'string' - } - params_example: { - 'name': 'myvm' - } - acl: acl - } - - service.actions = [action] - - // Add the service - runner.services.set(service)! - - // Get the service and verify fields - retrieved_service := runner.services.get(service.actor)! - assert retrieved_service.actor == service.actor - assert retrieved_service.description == service.description - assert retrieved_service.actions.len == 1 - assert retrieved_service.actions[0].action == 'start' - assert retrieved_service.status == .ok - - // Update service status - runner.services.update_status(service.actor, .down)! - updated_service := runner.services.get(service.actor)! - assert updated_service.status == .down - - // Test get_by_action - services := runner.services.get_by_action('start')! - assert services.len > 0 - assert services[0].actor == service.actor - - // Test access control - has_access := runner.services.check_access(service.actor, 'start', 'user-1-pubkey', - [])! - assert has_access == true - - has_circle_access := runner.services.check_access(service.actor, 'start', 'user-2-pubkey', - ['admin-circle'])! - assert has_circle_access == true - - no_access := runner.services.check_access(service.actor, 'start', 'user-3-pubkey', - [])! - assert no_access == false - - // List all services - all_services := runner.services.list()! - assert all_services.len > 0 - assert all_services[0].actor == service.actor - - // Delete the service - runner.services.delete(service.actor)! - - // Verify deletion - services_after := runner.services.list()! - for s in services_after { - assert s.actor != service.actor - } -} diff --git a/lib/core/jobs/model/specs.md b/lib/core/jobs/model/specs.md deleted file mode 100644 index 5c301dfb..00000000 --- a/lib/core/jobs/model/specs.md +++ /dev/null @@ -1,186 +0,0 @@ -create a job manager in -lib/core/jobs - - -## some definitions - -- agent: is a self contained set of processes which can execute on actions or actions to be executed by others -- action: what needs to be executed -- circle: each action happens in a circle -- context: a context inside a circle is optional -- job, what gets executed by an agent, is one action, can depend on other actions -- herorunner: is the process which uses redis to manage all open jobs, checks for timeouts, does the forwards if needed (if remote agent need to schedule, ...) - -## jobs - -are executed by processes can be in different languages and they are identified by agent pub key (the one who executes) -as part of heroscript we know what to executed on which actor inside the agent, defined with method and its arguments - -```v - -//the description of what needs to be executed -pub struct Job { -pub mut: - guid string //unique id for the job - agents []string //the pub key of the agent(s) which will execute the command, only 1 will execute, the herorunner will try the different agents if needed till it has success - source string //pubkey from the agent who asked for the job - circle string = "default" //our digital life is organized in circles - context string = "default" //is the high level context in which actors will execute the work inside a circle - actor string //e.g. vm_manager - action string //e.g. start - params map[string]string //e.g. id:10 - timeout_schedule u16 = 60 //timeout before its picked up - timeout u16 = 3600 // timeout in sec - log bool = true - ignore_error bool // means if error will just exit and not raise, there will be no error reporting - ignore_error_codes []int // of we want to ignore certain error codes - debug bool // if debug will get more context - retry int // default there is no debug - status JobStatus - dependencies []JobDependency //will not execute untill other jobs are done - -} - -pub struct JobStatus { -pub mut: - guid string //unique id for the job - created u32 //epoch when we created the job - start u32 //epoch when the job needs to start - end u32 //epoch when the job ended, can be in error - status //ENUM: create scheduled, planned (means arrived where actor will execute the job), running, error, ok -} - -pub struct JobDependency { -pub mut: - guid string //unique id for the job - agents []string //the pub key of the agent(s) which can execute the command -} - - - -``` - -the Job object is stored in redis in hset herorunner:jobs where key is the job guid and the val is the json of Job - -## Agent Registration Services - -Each agent (the one who hosts the different actors which execute the methods with params) register themselves to all participants. - -the structs below are available to everyone and are public - -```v - -pub struct Agent { -pub mut: - pubkey string //pubkey using ed25519 - address string //where we can gind the agent - port int //default 9999 - description string //optional - status AgentStatus - services []AgentService //these are the public services - signature string //signature as done by private key of $address+$port+$description+$status (this allows everyone to verify that the data is ok) - - -} - -pub struct AgentStatus { -pub mut: - guid string //unique id for the job - timestamp_first u32 //when agent came online - timestamp_last u32 //last time agent let us know that he is working - status //ENUM: ok, down, error, halted -} - -pub struct AgentService { -pub mut: - actor string - actions []AgentServiceAction - description string - status //ENUM: ok, down, error, halted -} - -pub struct AgentServiceAction { -pub mut: - action string //which action - description string //optional descroption - params map[string]string //e.g. name:'name of the vm' ... - params_example map[string]string // e.g. name:'myvm' - status //ENUM: ok, down, error, halted - public bool //if everyone can use then true, if restricted means only certain people can use -} - - - - - -``` - -the Agent object is stored in redis in hset herorunner:agents where key is the agent pubkey and the val is the json of Agent - - -### Services Info - -The agent and its actors register their capability to the herorunner - -We have a mechanism to be specific on who can execute which, this is sort of ACL system, for now its quite rough - - - -```v - -pub struct Circle { -pub mut: - guid string //unique id - name string - description string - members []string //can be other circle or member which is defined by pubkey -} - - -``` - -this info is stored in in redis on herorunner:circles - - - -```v - -pub struct Service { -pub mut: - actor string - actions []AgentServiceAction - description string - status //ENUM: ok, down, error, halted - acl ?ACL -} - -pub struct ServiceAction { -pub mut: - action string //which action - description string //optional descroption - params map[string]string //e.g. name:'name of the vm' ... - params_example map[string]string // e.g. name:'myvm' - acl ?ACL //if not used then everyone can use -} - -pub struct ACL { -pub mut: - name string - ace []ACE -} - - -pub struct ACE { -pub mut: - circles []string //guid's of the circles who have access - users []string //in case circles are not used then is users - right string e.g. read, write, admin, block -} - - - - -``` - -The info for the herorunner to function is in redis on herorunner:services - diff --git a/lib/core/jobs/model/circle.v b/lib/core/jobs/model/todo/circle.v similarity index 97% rename from lib/core/jobs/model/circle.v rename to lib/core/jobs/model/todo/circle.v index c6c3a979..3938214b 100644 --- a/lib/core/jobs/model/circle.v +++ b/lib/core/jobs/model/todo/circle.v @@ -31,11 +31,12 @@ pub mut: } pub fn (c Circle) index_keys() map[string]string { - return {"pubkey": c.pubkey} + return {"name": c.name} } // dumps serializes the Circle struct to binary format using the encoder +// This implements the Serializer interface pub fn (c Circle) dumps() ![]u8 { mut e := encoder.new() diff --git a/lib/core/jobs/model/circle_test.v b/lib/core/jobs/model/todo/circle_test.v similarity index 100% rename from lib/core/jobs/model/circle_test.v rename to lib/core/jobs/model/todo/circle_test.v diff --git a/lib/core/jobs/model/job.v b/lib/core/jobs/model/todo/job.v similarity index 100% rename from lib/core/jobs/model/job.v rename to lib/core/jobs/model/todo/job.v diff --git a/lib/core/jobs/model/names.v b/lib/core/jobs/model/todo/names.v similarity index 100% rename from lib/core/jobs/model/names.v rename to lib/core/jobs/model/todo/names.v diff --git a/lib/core/jobs/model/service.v b/lib/core/jobs/model/todo/service.v similarity index 100% rename from lib/core/jobs/model/service.v rename to lib/core/jobs/model/todo/service.v diff --git a/lib/data/radixtree/radixtree.v b/lib/data/radixtree/radixtree.v index 6d6e9962..124d5b8a 100644 --- a/lib/data/radixtree/radixtree.v +++ b/lib/data/radixtree/radixtree.v @@ -19,6 +19,7 @@ mut: } // RadixTree represents a radix tree data structure +@[heap] pub struct RadixTree { mut: db &ourdb.OurDB // Database for persistent storage