rhcp 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +6 -0
- data/Manifest.txt +4 -0
- data/README.txt +48 -0
- data/Rakefile +10 -1
- metadata +56 -74
- data/lib/rhcp.rb +0 -47
- data/lib/rhcp/broker.rb +0 -37
- data/lib/rhcp/client/command_param_stub.rb +0 -51
- data/lib/rhcp/client/command_stub.rb +0 -56
- data/lib/rhcp/client/http_broker.rb +0 -86
- data/lib/rhcp/command.rb +0 -120
- data/lib/rhcp/command_param.rb +0 -93
- data/lib/rhcp/dispatching_broker.rb +0 -25
- data/lib/rhcp/http_exporter.rb +0 -157
- data/lib/rhcp/request.rb +0 -93
- data/lib/rhcp/response.rb +0 -62
- data/lib/rhcp/rhcp_exception.rb +0 -6
- data/test/rhcp/broker_test.rb +0 -29
- data/test/rhcp/client/command_param_stub_test.rb +0 -73
- data/test/rhcp/client/command_stub_test.rb +0 -40
- data/test/rhcp/command_param_test.rb +0 -72
- data/test/rhcp/command_test.rb +0 -136
- data/test/rhcp/dispatching_broker_test.rb +0 -41
- data/test/rhcp/http_exporter_test.rb +0 -113
- data/test/rhcp/http_registry_test.rb +0 -73
- data/test/rhcp/http_test_server.rb +0 -47
- data/test/rhcp/request_test.rb +0 -124
- data/test/rhcp/response_test.rb +0 -45
data/lib/rhcp/command.rb
DELETED
@@ -1,120 +0,0 @@
|
|
1
|
-
module RHCP
|
2
|
-
|
3
|
-
require 'rubygems'
|
4
|
-
require 'json'
|
5
|
-
|
6
|
-
#require 'rhcp/rhcp'
|
7
|
-
require 'rhcp/response'
|
8
|
-
|
9
|
-
# This class represents a single RHCP command, i.e. a functionality that
|
10
|
-
# should be exposed to clients.
|
11
|
-
class Command
|
12
|
-
|
13
|
-
# TODO add read_write/read_only marker
|
14
|
-
|
15
|
-
# TODO add metadata about the command's result (display type ("table"), column order etc.)
|
16
|
-
|
17
|
-
# a unique name for this command
|
18
|
-
attr_reader :name
|
19
|
-
|
20
|
-
# textual description of what this command is doing
|
21
|
-
attr_reader :description
|
22
|
-
|
23
|
-
# the parameters supported by this command
|
24
|
-
attr_reader :params
|
25
|
-
|
26
|
-
# the block that should be executed if this command is invoked
|
27
|
-
attr_reader :block
|
28
|
-
|
29
|
-
# the parameter that is the default param for this command; might be nil
|
30
|
-
attr_reader :default_param
|
31
|
-
|
32
|
-
def initialize(name, description, block)
|
33
|
-
@logger = RHCP::ModuleHelper.instance().logger
|
34
|
-
|
35
|
-
@name = name
|
36
|
-
@description = description
|
37
|
-
@block = block
|
38
|
-
@params = Hash.new()
|
39
|
-
@default_param = nil
|
40
|
-
end
|
41
|
-
|
42
|
-
# adds a new parameter and returns the command
|
43
|
-
def add_param(new_param)
|
44
|
-
raise "duplicate parameter name '#{new_param.name}'" if @params.has_key?(new_param.name)
|
45
|
-
@params[new_param.name] = new_param
|
46
|
-
if new_param.is_default_param
|
47
|
-
if @default_param != nil
|
48
|
-
raise RHCP::RhcpException.new("there can be only one default parameter per command, and the parameter '#{@default_param.name}' has already been marked as default")
|
49
|
-
end
|
50
|
-
@default_param = new_param
|
51
|
-
end
|
52
|
-
self
|
53
|
-
end
|
54
|
-
|
55
|
-
# returns the specified CommandParam
|
56
|
-
# or throws an RhcpException if the parameter does not exist
|
57
|
-
def get_param(param_name)
|
58
|
-
raise RHCP::RhcpException.new("no such parameter : #{param_name}") unless @params.has_key?(param_name)
|
59
|
-
@params[param_name]
|
60
|
-
end
|
61
|
-
|
62
|
-
# invokes this command
|
63
|
-
def execute_request(request)
|
64
|
-
@logger.debug "gonna execute request >>#{request}<<"
|
65
|
-
response = RHCP::Response.new()
|
66
|
-
begin
|
67
|
-
# TODO redirect the block's output (both Logger and STDOUT/STDERR) and send it back with the response
|
68
|
-
result = block.call(request, response)
|
69
|
-
response.set_payload(result)
|
70
|
-
rescue => ex
|
71
|
-
response.mark_as_error(ex.to_s, ex.backtrace.join("\n"))
|
72
|
-
end
|
73
|
-
|
74
|
-
fire_post_exec_event(request, response)
|
75
|
-
|
76
|
-
response
|
77
|
-
end
|
78
|
-
|
79
|
-
def execute(param_values = {})
|
80
|
-
req = RHCP::Request.new(self, param_values)
|
81
|
-
execute_request(req)
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
|
-
# we do not serialize the block (no sense in this) nor the default param
|
86
|
-
# when the command is unmarshalled as stub, the default param will be set again
|
87
|
-
# automatically ba add_param(), and the block will be replaced by the block
|
88
|
-
# that does the remote invocation
|
89
|
-
def to_json(*args)
|
90
|
-
{
|
91
|
-
'name' => @name,
|
92
|
-
'description' => @description,
|
93
|
-
'params' => @params.values
|
94
|
-
}.to_json(*args)
|
95
|
-
end
|
96
|
-
|
97
|
-
# TODO think about moving this and the whole command thing to the broker
|
98
|
-
def Command.register_post_exec_listener(block)
|
99
|
-
@@listener = [] unless defined?(@@listener)
|
100
|
-
@@listener << block
|
101
|
-
end
|
102
|
-
|
103
|
-
def Command.clear_post_exec_listeners()
|
104
|
-
@@listener = []
|
105
|
-
end
|
106
|
-
|
107
|
-
def fire_post_exec_event(request, response)
|
108
|
-
return unless defined? @@listener
|
109
|
-
@@listener.each do |listener|
|
110
|
-
begin
|
111
|
-
listener.call(request, response)
|
112
|
-
rescue => ex
|
113
|
-
@logger.warn "a post-exec listener failed with the following message : #{ex}"
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
end
|
119
|
-
|
120
|
-
end
|
data/lib/rhcp/command_param.rb
DELETED
@@ -1,93 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'json'
|
3
|
-
|
4
|
-
require 'rhcp/rhcp_exception'
|
5
|
-
|
6
|
-
module RHCP
|
7
|
-
|
8
|
-
# This class represents a single parameter that can be specified for
|
9
|
-
# a certain command. It is used by the server side to define which
|
10
|
-
# commands are available for clients.
|
11
|
-
class CommandParam
|
12
|
-
|
13
|
-
# a unique name for this parameter
|
14
|
-
attr_reader :name
|
15
|
-
# a textual description of this parameter's meaning
|
16
|
-
attr_reader :description
|
17
|
-
# "true" if the command can not be invoked without this parameter
|
18
|
-
attr_reader :mandatory
|
19
|
-
# "true" if there are lookup values available for this parameter
|
20
|
-
attr_reader :has_lookup_values
|
21
|
-
# "true" if this parameter might be specified multiple times, resulting
|
22
|
-
# in a list of values for this parameter
|
23
|
-
attr_reader :allows_multiple_values
|
24
|
-
# "true" if this parameter is the default parameter for the enclosing command
|
25
|
-
attr_reader :is_default_param
|
26
|
-
|
27
|
-
# creates a new command parameter
|
28
|
-
# options is a hash, possibly holding the following values (all optional)
|
29
|
-
# :mandatory true if the parameter is necessary for this command
|
30
|
-
# :allows_multiple_values true if this parameter might be specified multiple times
|
31
|
-
# :lookup_method a block that returns an array of lookup values valid for this param
|
32
|
-
# :is_default_param true if this parameter is the default param for the enclosing command
|
33
|
-
def initialize(name, description, options = Hash.new)
|
34
|
-
@name = name
|
35
|
-
@description = description
|
36
|
-
|
37
|
-
@mandatory = options[:mandatory] || false
|
38
|
-
@lookup_value_block = options[:lookup_method]
|
39
|
-
@has_lookup_values = @lookup_value_block != nil
|
40
|
-
@allows_multiple_values = options[:allows_multiple_values] || false
|
41
|
-
@is_default_param = options[:is_default_param] || false
|
42
|
-
end
|
43
|
-
|
44
|
-
# returns lookup values for this parameter
|
45
|
-
# if "partial_value" is specified, only those values are returned that start
|
46
|
-
# with "partial_value"
|
47
|
-
def get_lookup_values(partial_value = "")
|
48
|
-
if @has_lookup_values
|
49
|
-
@lookup_value_block.call().grep(/^#{partial_value}/)
|
50
|
-
else
|
51
|
-
[]
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# throws an exception if +possible_value+ is not a valid value for this parameter
|
56
|
-
# returns true otherwise
|
57
|
-
# note that this methods expects +possible_value+ to be an array since all param values
|
58
|
-
# are specified as arrays
|
59
|
-
def check_param_is_valid(possible_value)
|
60
|
-
|
61
|
-
# formal check : single-value params against multi-value params
|
62
|
-
if ((! @allows_multiple_values) && (possible_value.size > 1))
|
63
|
-
raise RHCP::RhcpException.new("multiple values specified for single-value parameter '#{@name}'")
|
64
|
-
end
|
65
|
-
|
66
|
-
# check against lookup values
|
67
|
-
if @has_lookup_values
|
68
|
-
possible_value.each do |value|
|
69
|
-
if ! get_lookup_values().include?(value)
|
70
|
-
raise RHCP::RhcpException.new("invalid value '#{value}' for parameter '#{@name}'")
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
true
|
76
|
-
end
|
77
|
-
|
78
|
-
# we do not serialize the lookup_method (it couldn't be invoked remotely
|
79
|
-
# anyway)
|
80
|
-
def to_json(*args)
|
81
|
-
{
|
82
|
-
'name' => @name,
|
83
|
-
'description' => @description,
|
84
|
-
'allows_multiple_values' => @allows_multiple_values,
|
85
|
-
'has_lookup_values' => @has_lookup_values,
|
86
|
-
'is_default_param' => @is_default_param,
|
87
|
-
'mandatory' => @mandatory
|
88
|
-
}.to_json(*args)
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'rhcp/rhcp_exception'
|
2
|
-
require 'rhcp/broker'
|
3
|
-
|
4
|
-
module RHCP
|
5
|
-
|
6
|
-
class DispatchingBroker < RHCP::Broker
|
7
|
-
def initialize
|
8
|
-
@known_commands = Hash.new()
|
9
|
-
end
|
10
|
-
|
11
|
-
# returns a list of all known commands
|
12
|
-
def get_command_list()
|
13
|
-
@known_commands
|
14
|
-
end
|
15
|
-
|
16
|
-
def add_broker(new_broker)
|
17
|
-
new_broker.get_command_list.each do |name, command|
|
18
|
-
raise RHCP::RhcpException.new("duplicate command: '#{name}' has already been defined.") if @known_commands.has_key?(name)
|
19
|
-
@known_commands[name] = command
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
end
|
data/lib/rhcp/http_exporter.rb
DELETED
@@ -1,157 +0,0 @@
|
|
1
|
-
require 'webrick'
|
2
|
-
require 'json'
|
3
|
-
require 'net/http'
|
4
|
-
|
5
|
-
require 'rhcp/broker'
|
6
|
-
require 'rhcp/request'
|
7
|
-
require 'rhcp/rhcp_exception'
|
8
|
-
|
9
|
-
include WEBrick
|
10
|
-
|
11
|
-
module RHCP
|
12
|
-
|
13
|
-
# TODO add some logging
|
14
|
-
|
15
|
-
# The +HttpExporter+ connects to the RHCP::Broker and exports all commands
|
16
|
-
# registered there via http, i.e. it servers requests to the URLs
|
17
|
-
#
|
18
|
-
# /rhcp/get_commands
|
19
|
-
# /rhcp/get_lookup_values
|
20
|
-
# /rhcp/execute
|
21
|
-
#
|
22
|
-
# All data is transferred in JSON format.
|
23
|
-
class HttpExporter
|
24
|
-
|
25
|
-
DEFAULT_PORT = 42000
|
26
|
-
DEFAULT_PREFIX = "rhcp"
|
27
|
-
|
28
|
-
# by default, a new HttpExporter will create it's own +HTTPServer+
|
29
|
-
# on port 42000 - if you want to change this port, you can pass the port
|
30
|
-
# to use as +port+ option. If you want to use an existing +HTTPServer+
|
31
|
-
# instance, you can pass it as +server+ option instead.
|
32
|
-
#
|
33
|
-
# Also, you can specify the prefix for the rhcp URLs using the +prefix+
|
34
|
-
# option - otherwise, all
|
35
|
-
# rhcp actions will be exported at
|
36
|
-
# /rhcp/get_commands
|
37
|
-
# /rhcp/get_lookup_values
|
38
|
-
# /rhcp/execute
|
39
|
-
# If you change this, please be aware that all clients that want to connect
|
40
|
-
# to this server need to be configured accordingly.
|
41
|
-
def initialize(broker, options = Hash.new())
|
42
|
-
|
43
|
-
@logger = RHCP::ModuleHelper::instance.logger
|
44
|
-
@broker = broker
|
45
|
-
|
46
|
-
# build your own server or use the on passed in as param
|
47
|
-
port = options.has_key?(:port) ? options[:port] : DEFAULT_PORT
|
48
|
-
|
49
|
-
if options.has_key?(:server)
|
50
|
-
@server = options[:server]
|
51
|
-
@logger.debug("using existing server #{@server}")
|
52
|
-
else
|
53
|
-
@logger.debug("opening own server on port #{port}")
|
54
|
-
@server = HTTPServer.new( :Port => port )
|
55
|
-
end
|
56
|
-
|
57
|
-
@url_prefix = options.has_key?(:prefix) ? options[:prefix] : DEFAULT_PREFIX
|
58
|
-
@server.mount_proc("/#{@url_prefix}/") {|req, res|
|
59
|
-
res.body = "<HTML>hello (again)</HTML>"
|
60
|
-
res['Content-Type'] = "text/html"
|
61
|
-
}
|
62
|
-
# TODO this path should probably be quite relative
|
63
|
-
@server.mount "/#{@url_prefix}/info", HTTPServlet::FileHandler, "docroot"
|
64
|
-
@server.mount "/#{@url_prefix}/get_commands", GetCommandsServlet, @broker
|
65
|
-
@server.mount "/#{@url_prefix}/get_lookup_values", GetLookupValuesServlet, @broker
|
66
|
-
@server.mount "/#{@url_prefix}/execute", ExecuteServlet, @broker
|
67
|
-
@logger.info("http exporter has been initialized - once started, it will listen on port '#{port}' for URLs starting with prefix '#{@url_prefix}'")
|
68
|
-
end
|
69
|
-
|
70
|
-
class BaseServlet < HTTPServlet::AbstractServlet
|
71
|
-
|
72
|
-
def initialize(server, broker)
|
73
|
-
super(server)
|
74
|
-
@broker = broker
|
75
|
-
end
|
76
|
-
|
77
|
-
def do_GET(req, res)
|
78
|
-
res['Content-Type'] = "application/json"
|
79
|
-
begin
|
80
|
-
#@logger.info("executing #{req}")
|
81
|
-
do_it(req, res)
|
82
|
-
#@logger.info("finished executing #{req}")
|
83
|
-
rescue RHCP::RhcpException => ex
|
84
|
-
# TODO define error format (should we send back a JSON-ified response object here?)
|
85
|
-
@logger.error("got an exception while executing request . #{ex}")
|
86
|
-
res.status = 500
|
87
|
-
res.body = [ ex.to_s ].to_json()
|
88
|
-
#puts ex
|
89
|
-
#puts ex.backtrace.join("\n")
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
# TODO is this a good idea?
|
94
|
-
def do_POST(req, res)
|
95
|
-
do_GET(req, res)
|
96
|
-
end
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
class GetCommandsServlet < BaseServlet
|
101
|
-
|
102
|
-
def do_it(req, res)
|
103
|
-
commands = @broker.get_command_list
|
104
|
-
puts "about to send back commands : #{commands.values.to_json()}"
|
105
|
-
res.body = commands.values.to_json()
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
class GetLookupValuesServlet < BaseServlet
|
110
|
-
|
111
|
-
def do_it(req, res)
|
112
|
-
partial_value = req.query.has_key?('partial') ? req.query['partial'] : ''
|
113
|
-
command = @broker.get_command(req.query['command'])
|
114
|
-
param = command.get_param(req.query['param'])
|
115
|
-
lookup_values = param.get_lookup_values(partial_value)
|
116
|
-
res.body = lookup_values.to_json()
|
117
|
-
end
|
118
|
-
|
119
|
-
end
|
120
|
-
|
121
|
-
class ExecuteServlet < BaseServlet
|
122
|
-
|
123
|
-
def do_it(req, res)
|
124
|
-
@logger.info("got an execute request : #{req}")
|
125
|
-
request = RHCP::Request.reconstruct_from_json(@broker, req.body)
|
126
|
-
|
127
|
-
response = request.execute()
|
128
|
-
|
129
|
-
@logger.debug("sending back response : #{response.to_json()}")
|
130
|
-
res.body = response.to_json()
|
131
|
-
end
|
132
|
-
|
133
|
-
end
|
134
|
-
|
135
|
-
def run
|
136
|
-
@server.start
|
137
|
-
end
|
138
|
-
|
139
|
-
def start
|
140
|
-
puts "about to start server..."
|
141
|
-
@thread = Thread.new {
|
142
|
-
puts "in thread; starting server..."
|
143
|
-
@server.start
|
144
|
-
puts "in thread: continuing..."
|
145
|
-
}
|
146
|
-
end
|
147
|
-
|
148
|
-
def stop
|
149
|
-
puts "about to stop server"
|
150
|
-
@server.stop
|
151
|
-
puts "thread stopped"
|
152
|
-
# TODO wait until after the server has ended - when this method exits, the server is still shutting down
|
153
|
-
end
|
154
|
-
|
155
|
-
end
|
156
|
-
|
157
|
-
end
|
data/lib/rhcp/request.rb
DELETED
@@ -1,93 +0,0 @@
|
|
1
|
-
require 'rhcp/rhcp_exception'
|
2
|
-
require 'rhcp/broker'
|
3
|
-
|
4
|
-
require 'rubygems'
|
5
|
-
require 'json'
|
6
|
-
|
7
|
-
module RHCP
|
8
|
-
|
9
|
-
# This class represents a request made by a RHCP client to a RHCP server.
|
10
|
-
# It is passed as an argument to the method that should execute the requested
|
11
|
-
# command
|
12
|
-
class Request
|
13
|
-
|
14
|
-
attr_reader :command
|
15
|
-
attr_reader :param_values
|
16
|
-
|
17
|
-
# default constructor; will throw exceptions on invalid values
|
18
|
-
def initialize(command, param_values = {})
|
19
|
-
raise RHCP::RhcpException.new("command may not be null") if command == nil
|
20
|
-
|
21
|
-
# autobox the parameters if necessary
|
22
|
-
param_values.each do |k,v|
|
23
|
-
if ! v.instance_of?(Array)
|
24
|
-
param_values[k] = [ v ]
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# check all param values for plausibility
|
29
|
-
param_values.each do |key,value|
|
30
|
-
command.get_param(key).check_param_is_valid(value)
|
31
|
-
end
|
32
|
-
|
33
|
-
# check that we've got all mandatory params
|
34
|
-
command.params.each do |name, param|
|
35
|
-
raise RHCP::RhcpException.new("missing mandatory parameter '#{name}'") if param.mandatory && ! param_values.has_key?(name)
|
36
|
-
end
|
37
|
-
|
38
|
-
@command = command
|
39
|
-
@param_values = param_values
|
40
|
-
end
|
41
|
-
|
42
|
-
# used to retrieve the value for the specified parameter
|
43
|
-
# returns either the value or an array of values if the parameter allows
|
44
|
-
# multiple values
|
45
|
-
def get_param_value(param_name)
|
46
|
-
raise "no such parameter : #{param_name}" unless @param_values.has_key?(param_name)
|
47
|
-
param = @command.get_param(param_name)
|
48
|
-
if (param.allows_multiple_values)
|
49
|
-
@param_values[param_name]
|
50
|
-
else
|
51
|
-
@param_values[param_name][0]
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def has_param_value(param_name)
|
56
|
-
@param_values.has_key?(param_name)
|
57
|
-
end
|
58
|
-
|
59
|
-
# convenience method that executes the command that actually delegates to the
|
60
|
-
# command that's inside this request
|
61
|
-
def execute
|
62
|
-
@command.execute_request(self)
|
63
|
-
end
|
64
|
-
|
65
|
-
# reconstructs the request from it's JSON representation
|
66
|
-
# Since the JSON version of a request does hold the command name instead
|
67
|
-
# of the full command only, a broker is needed to lookup the command by
|
68
|
-
# it's name
|
69
|
-
#
|
70
|
-
# Params:
|
71
|
-
# +broker+ is the broker to use for command lookup
|
72
|
-
# +json_data+ is the JSON data that represents the request
|
73
|
-
def self.reconstruct_from_json(broker, json_data)
|
74
|
-
object = JSON.parse(json_data)
|
75
|
-
command = broker.get_command(object['command_name'])
|
76
|
-
self.new(command, object['param_values'])
|
77
|
-
end
|
78
|
-
|
79
|
-
# returns a JSON representation of this request.
|
80
|
-
def to_json(*args)
|
81
|
-
{
|
82
|
-
'command_name' => @command.name,
|
83
|
-
'param_values' => @param_values
|
84
|
-
}.to_json(*args)
|
85
|
-
end
|
86
|
-
|
87
|
-
def to_s
|
88
|
-
"#{@command.name} (#{@param_values})"
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
end
|