spec/arborist/client_spec.rb in arborist-0.2.0.pre20170519125456 vs spec/arborist/client_spec.rb in arborist-0.2.0

- old
+ new

@@ -7,10 +7,17 @@ describe Arborist::Client do let( :client ) { described_class.new } + it "can return a singleton instance" do + one = described_class.instance + two = described_class.instance + expect( one ).to be_a( described_class ) + expect( one ).to equal( two ) + end + describe "synchronous API", :testing_manager do before( :each ) do @manager_thread = Thread.new do @manager = make_testing_manager() @@ -45,30 +52,56 @@ let( :manager ) { @manager } - describe "high-level methods" do + describe "convenience API" do - it "provides a convenience method for acknowledging" do - manager.nodes['sidonie'].update( error: "Clown apocalypse" ) + it "can fetch a single node" do + res = client.fetch_node( 'duir' ) + expect( res ).to be_a( Hash ) + expect( res['identifier'] ).to eq( 'duir' ) + end - res = client.acknowledge( :sidonie, "I'm on it.", "ged" ) - expect( manager.nodes['sidonie'] ).to be_acked + it "has a convenience method for fetching dependencies" do + res = client.dependencies_of( 'sidonie' ) + expect( res ).to be_a( Hash ).and include( 'sidonie-postgresql', 'sidonie-ssh' ) end - it "provides a convenience method for clearing acknowledgments" do - manager.nodes['sidonie'].update( error: "Clown apocalypse" ) + it "can pivot dependencies on node attributes" do + res = client.dependencies_of( 'sidonie', partition: 'type' ) + expect( res ).to be_a( Hash ) + expect( res['service'] ).to include( + a_hash_including( { 'identifier' => 'sidonie-ssh' } ), + a_hash_including( { 'identifier' => 'sidonie-postgresql' } ) + ) + end - res = client.acknowledge( :sidonie, "I'm on it.", "ged" ) - res = client.clear_acknowledgement( :sidonie ) - expect( manager.nodes['sidonie'] ).to_not be_acked + it "can fetch a subset of node dependency attributes" do + res = client.dependencies_of( 'sidonie', properties: %w[ description type ] ) + expect( res ).to be_a( Hash ).and include( + 'sandbox01', + 'sandbox01-canary', + 'sidonie-couchpotato', + 'sidonie-demon-http', + 'sidonie-http', + 'sidonie-iscsi', + 'sidonie-pms', + 'sidonie-postgresql', + 'sidonie-sabnzbd', + 'sidonie-sickbeard', + 'sidonie-smtp', + 'sidonie-ssh', + 'vhost01', + 'yevaud-cozy_frontend' + ) + expect( res.values ).to all( have_attributes(length: a_value_between(1, 2)) ) + expect( res.values.map(&:keys) ).to all( contain_exactly 'type', 'description' ) end - end describe "protocol-level API" do @@ -76,92 +109,105 @@ res = client.status expect( res ).to include( 'server_version', 'state', 'uptime', 'nodecount' ) end - it "can list the nodes of the manager it's connected to" do - res = client.list + it "can fetch the nodes of the manager it's connected to" do + res = client.fetch expect( res ).to be_an( Array ) expect( res.length ).to eq( manager.nodes.length ) end - it "can list a subtree of the nodes of the manager it's connected to" do - res = client.list( from: 'duir' ) + it "can fetch a subtree of the nodes of the manager it's connected to" do + res = client.fetch( from: 'duir' ) expect( res ).to be_an( Array ) expect( res.length ).to be < manager.nodes.length end - it "can list a depth-limited subtree of the node of the managed it's connected to" do - res = client.list( depth: 2 ) + it "can fetch a depth-limited subtree of the node of the managed it's connected to" do + res = client.fetch( depth: 2 ) expect( res ).to be_an( Array ) - expect( res.length ).to eq( 8 ) + expect( res.length ).to eq( 9 ) end - it "can list a depth-limited subtree of the nodes of the manager it's connected to" do - res = client.list( from: 'duir', depth: 1 ) + it "can fetch a depth-limited subtree of the nodes of the manager it's connected to" do + res = client.fetch( from: 'duir', depth: 1 ) expect( res ).to be_an( Array ) - expect( res.length ).to eq( 5 ) + expect( res.length ).to eq( 6 ) end - it "can fetch all node properties for all 'up' nodes" do - res = client.fetch + it "can get a Hash of all nodes keyed by identifier" do + res = client.search expect( res ).to be_a( Hash ) expect( res.length ).to be == manager.nodes.length expect( res.values ).to all( be_a(Hash) ) end - it "can fetch identifiers for all 'up' nodes" do - res = client.fetch( {}, properties: nil ) + it "includes downed nodes by default in the results of a search" do + manager.nodes['sidonie'].update( error: 'something happened' ) + res = client.search expect( res ).to be_a( Hash ) expect( res.length ).to be == manager.nodes.length + expect( res.values ).to all( be_a(Hash) ) + end + + + it "can get a Hash of all nodes without user properties" do + res = client.search( options: { properties: nil } ) + expect( res ).to be_a( Hash ) + expect( res.length ).to be == manager.nodes.length expect( res.values ).to all( be_empty ) end - it "can fetch a subset of properties for all 'up' nodes" do - res = client.fetch( {}, properties: [:addresses, :status] ) + it "can get a Hash of all nodes with a subset of properties" do + res = client.search( options: { properties: [:addresses, :status] }) expect( res ).to be_a( Hash ) expect( res.length ).to be == manager.nodes.length expect( res.values ).to all( be_a(Hash) ) expect( res.values.map(&:length) ).to all( be <= 2 ) end - it "can fetch a subset of properties for all 'up' nodes matching specified criteria" do - res = client.fetch( {type: 'host'}, properties: [:addresses, :status] ) + it "can get a Hash of all nodes with a subset of properties that match specified criteria" do + res = client.search( criteria: {type: 'host'}, options: {properties: [:addresses, :status]} ) expect( res ).to be_a( Hash ) expect( res.length ).to be == manager.nodes.values.count {|n| n.type == 'host' } expect( res.values ).to all( include('addresses', 'status') ) end - it "can fetch all node properties for 'up' nodes that don't match specified criteria" do - res = client.fetch( {}, properties: [:addresses, :status], exclude: {tag: 'testing'} ) + it "can get a Hash of all nodes with a subset of properties that don't match specified criteria" do + res = client.search( + options: { + properties: [:addresses, :status], + exclude: {tag: 'testing'} + } + ) testing_nodes = manager.nodes.values.select {|n| n.tags.include?('testing') } expect( res ).to be_a( Hash ) expect( res ).to_not be_empty() expect( res.length ).to eq( manager.nodes.length - testing_nodes.length ) expect( res.values ).to all( be_a(Hash) ) end - it "can fetch all properties for all nodes regardless of their status" do + it "can get a Hash of nodes that exclude nodes that are down" do # Down a node manager.nodes['duir'].update( error: 'something happened' ) - res = client.fetch( {type: 'host'}, include_down: true ) + res = client.search( criteria: {type: 'host'}, options: {exclude_down: true} ) expect( res ).to be_a( Hash ) - expect( res ).to include( 'duir' ) - expect( res['duir']['status'] ).to eq( 'down' ) + expect( res ).to_not include( 'duir' ) end it "can update the properties of managed nodes", :no_ci do res = client.update( duir: { ping: {rtt: 24} } ) @@ -171,10 +217,21 @@ expect( manager.nodes['duir'].properties['ping'] ).to include( 'rtt' ) expect( manager.nodes['duir'].properties['ping']['rtt'] ).to eq( 24 ) end + it "can fetch a list of all nodes which have a dependency on a target node" do + res = client.deps( identifier: 'sidonie-postgresql' ) + + expected_ids = manager.nodes['sidonie-postgresql'].node_subscribers.to_a + + expect( res ).to be_a( Hash ).and( include('deps') ) + expect( res['deps'] ).to be_an( Array ) + expect( res['deps'] ).to contain_exactly( *expected_ids ) + end + + it "can subscribe to all events" do sub_id = client.subscribe expect( sub_id ).to be_a( String ) expect( sub_id ).to match( /^[\w\-]{16,}/ ) @@ -259,39 +316,42 @@ expect( res ).to be_nil end it "can prune nodes from the tree" do - res = client.prune( 'sidonie-ssh' ) + res = client.prune( identifier: 'sidonie-ssh' ) expect( res ).to be_a( Hash ) expect( res ).to include( 'identifier' => 'sidonie-ssh' ) expect( manager.nodes ).to_not include( 'sidonie-ssh' ) end it "returns nil without error when pruning a node that doesn't exist" do - res = client.prune( 'carrigor' ) + res = client.prune( identifier: 'carrigor' ) expect( res ).to be_nil end it "can graft new nodes onto the tree" do - res = client.graft( 'breakfast-burrito', type: 'host' ) + res = client.graft( identifier: 'breakfast-burrito', type: 'host' ) expect( res ).to eq({ 'identifier' => 'breakfast-burrito' }) expect( manager.nodes ).to include( 'breakfast-burrito' ) expect( manager.nodes['breakfast-burrito'] ).to be_a( Arborist::Node::Host ) expect( manager.nodes['breakfast-burrito'].parent ).to eq( '_' ) end it "can graft nodes with attributes onto the tree" do - res = client.graft( 'breakfast-burrito', + res = client.graft( + identifier: 'breakfast-burrito', type: 'service', parent: 'duir', - port: 9999, - tags: ['yusss'] + attributes: { + port: 9999, + tags: ['yusss'] + } ) expect( res ).to eq({ 'identifier' => 'duir-breakfast-burrito' }) expect( manager.nodes ).to include( 'duir-breakfast-burrito' ) expect( manager.nodes['duir-breakfast-burrito'] ).to be_a( Arborist::Node::Service ) expect( manager.nodes['duir-breakfast-burrito'].parent ).to eq( 'duir' ) @@ -299,15 +359,40 @@ expect( manager.nodes['duir-breakfast-burrito'].tags ).to include( 'yusss' ) end it "can modify operational attributes of a node" do - res = client.modify( "duir", tags: 'girlrobot' ) + res = client.modify( identifier: "duir", attributes: { tags: 'girlrobot' }) expect( res ).to be_truthy expect( manager.nodes['duir'].tags ).to eq( ['girlrobot'] ) end + + it "can acknowledge a node" do + manager.nodes['sidonie'].update( error: "Clown apocalypse" ) + + res = client.acknowledge( identifier: 'sidonie', message: "I'm on it.", sender: "ged" ) + + expect( manager.nodes['sidonie'] ).to be_acked + end + + + it "can clear a node's acknowledgment" do + manager.nodes['sidonie'].update( error: "Clown apocalypse" ) + + res = client.acknowledge( identifier: 'sidonie', message: "I'm on it.", sender: "ged" ) + res = client.clear_acknowledgement( identifier: 'sidonie' ) + + expect( manager.nodes['sidonie'] ).to_not be_acked + end + + + it "acking raises an appropriate error when it's missing arguments" do + expect { + client.acknowledge( identifier: 'sidonie', message: "I'm on it." ) + }.to raise_error( ArgumentError, /missing keyword: sender/ ) + end end end @@ -324,49 +409,49 @@ expect( header['version'] ).to eq( Arborist::Client::API_VERSION ) expect( header['action'] ).to eq( 'status' ) end - it "can make a raw list request" do - req = client.make_list_request + it "can make a raw fetch request" do + req = client.make_fetch_request expect( req ).to be_a( CZTop::Message ) header, body = Arborist::TreeAPI.decode( req ) expect( header ).to be_a( Hash ) expect( header ).to include( 'version', 'action' ) expect( header ).to_not include( 'from' ) expect( header['version'] ).to eq( Arborist::Client::API_VERSION ) - expect( header['action'] ).to eq( 'list' ) + expect( header['action'] ).to eq( 'fetch' ) end - it "can make a raw fetch request" do - req = client.make_fetch_request( {} ) + it "can make a raw search request" do + req = client.make_search_request( {} ) expect( req ).to be_a( CZTop::Message ) header, body = Arborist::TreeAPI.decode( req ) expect( header ).to be_a( Hash ) expect( header ).to include( 'version', 'action' ) expect( header['version'] ).to eq( Arborist::Client::API_VERSION ) - expect( header['action'] ).to eq( 'fetch' ) + expect( header['action'] ).to eq( 'search' ) expect( body ).to eq([ {}, {} ]) end - it "can make a raw fetch request with criteria" do - req = client.make_fetch_request( {type: 'host'} ) + it "can make a raw search request with criteria" do + req = client.make_search_request( {type: 'host'} ) expect( req ).to be_a( CZTop::Message ) header, body = Arborist::TreeAPI.decode( req ) expect( header ).to be_a( Hash ) expect( header ).to include( 'version', 'action' ) expect( header['version'] ).to eq( Arborist::Client::API_VERSION ) - expect( header['action'] ).to eq( 'fetch' ) + expect( header['action'] ).to eq( 'search' ) body = body expect( body.first ).to be_a( Hash ) expect( body.first ).to include( 'type' ) expect( body.first['type'] ).to eq( 'host' ) @@ -381,9 +466,30 @@ expect( header ).to be_a( Hash ) expect( header ).to include( 'version', 'action' ) expect( header['version'] ).to eq( Arborist::Client::API_VERSION ) expect( header['action'] ).to eq( 'update' ) + + expect( body ).to be_a( Hash ) + expect( body ).to include( 'duir' ) + expect( body['duir'] ).to eq( 'error' => 'Something happened.' ) + end + + + it "can make a raw update request with headers" do + req = client.make_update_request( + {duir: {error: "Something happened."}}, + {monitor_key: 'foom'} + ) + expect( req ).to be_a( CZTop::Message ) + + header, body = Arborist::TreeAPI.decode( req ) + + expect( header ).to be_a( Hash ) + expect( header ).to include( 'version', 'action', 'monitor_key' ) + expect( header['version'] ).to eq( Arborist::Client::API_VERSION ) + expect( header['action'] ).to eq( 'update' ) + expect( header['monitor_key'] ).to eq( 'foom' ) expect( body ).to be_a( Hash ) expect( body ).to include( 'duir' ) expect( body['duir'] ).to eq( 'error' => 'Something happened.' ) end