rhcp 0.1.9 → 0.2.14

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.
@@ -23,6 +23,8 @@ module RHCP
23
23
  attr_reader :allows_multiple_values
24
24
  # "true" if this parameter is the default parameter for the enclosing command
25
25
  attr_reader :is_default_param
26
+ # the key to a context value that is used for filling this parameter
27
+ attr_reader :autofill_context_key
26
28
 
27
29
  # creates a new command parameter
28
30
  # options is a hash, possibly holding the following values (all optional)
@@ -34,19 +36,44 @@ module RHCP
34
36
  @name = name
35
37
  @description = description
36
38
 
39
+ # TODO add default value for optional params
40
+ # TODO also, it should be possible to allow extra values for params with lookups
37
41
  @mandatory = options[:mandatory] || false
38
42
  @lookup_value_block = options[:lookup_method]
39
43
  @has_lookup_values = @lookup_value_block != nil
40
44
  @allows_multiple_values = options[:allows_multiple_values] || false
41
45
  @is_default_param = options[:is_default_param] || false
46
+ @autofill_context_key = options[:autofill_context_key] || nil
47
+ end
48
+
49
+ # searches the context for a values that can be auto-filled
50
+ # if a value is found in the context, it is returned
51
+ # if this parameter cannot be filled from the context, nil is returned
52
+ def find_value_in_context(context)
53
+ result = nil
54
+ if @autofill_context_key != nil
55
+ if context.cookies.has_key?(@autofill_context_key)
56
+ result = context.cookies[@autofill_context_key]
57
+ end
58
+ end
59
+ result
42
60
  end
43
61
 
44
62
  # returns lookup values for this parameter
45
63
  # if "partial_value" is specified, only those values are returned that start
46
- # with "partial_value"
47
- def get_lookup_values(partial_value = "")
64
+ # with "partial_value"
65
+ def get_lookup_values(request, partial_value = "")
48
66
  if @has_lookup_values
49
- @lookup_value_block.call().grep(/^#{partial_value}/)
67
+ # TODO mix other_params and context
68
+ $logger.debug "get_lookup_values for '#{request.command.name}'; request : #{request}"
69
+ values = []
70
+ if @lookup_value_block.arity == 1 or @lookup_value_block.arity == -1
71
+ values = @lookup_value_block.call(request)
72
+ else
73
+ values = @lookup_value_block.call()
74
+ end
75
+
76
+ values.grep(/^#{partial_value}/)
50
77
  else
51
78
  []
52
79
  end
@@ -56,7 +83,7 @@ module RHCP
56
83
  # returns true otherwise
57
84
  # note that this methods expects +possible_value+ to be an array since all param values
58
85
  # are specified as arrays
59
- def check_param_is_valid(possible_value)
86
+ def check_param_is_valid(request, possible_value)
60
87
 
61
88
  # formal check : single-value params against multi-value params
62
89
  if ((! @allows_multiple_values) && (possible_value.size > 1))
@@ -66,7 +93,7 @@ module RHCP
66
93
  # check against lookup values
67
94
  if @has_lookup_values
68
95
  possible_value.each do |value|
69
- if ! get_lookup_values().include?(value)
96
+ if ! get_lookup_values(request).include?(value)
70
97
  raise RHCP::RhcpException.new("invalid value '#{value}' for parameter '#{@name}'")
71
98
  end
72
99
  end
@@ -84,7 +111,8 @@ module RHCP
84
111
  'allows_multiple_values' => @allows_multiple_values,
85
112
  'has_lookup_values' => @has_lookup_values,
86
113
  'is_default_param' => @is_default_param,
87
- 'mandatory' => @mandatory
114
+ 'mandatory' => @mandatory,
115
+ 'autofill_context_key' => @autofill_context_key
88
116
  }.to_json(*args)
89
117
  end
90
118
 
@@ -0,0 +1,43 @@
1
+ module RHCP
2
+
3
+ # The context class is used for transporting both the context hash and some
4
+ # other client state like the parameter values that have been collected during
5
+ # user input. It should be sent with all rhcp remote calls and should generally
6
+ # be treated as optional.
7
+ class Context
8
+
9
+ # hash holding context information; similar to HTTP cookies
10
+ # TODO should we actually use cookies for transporting this info?
11
+ attr_accessor :cookies
12
+
13
+ def initialize(cookies = {})
14
+ @cookies = cookies
15
+ end
16
+
17
+ def to_json(*args)
18
+ {
19
+ 'cookies' => @cookies,
20
+ }.to_json(*args)
21
+ end
22
+
23
+ def self.reconstruct_from_json(json_data)
24
+ $logger.debug "reconstructing context from json : >>#{json_data}<<"
25
+ object = JSON.parse(json_data)
26
+ instance = self.new()
27
+ instance.cookies = object['cookies']
28
+
29
+ instance
30
+ end
31
+
32
+ def to_s
33
+ result = "<Context cookies:"
34
+ @cookies.each do |k,v|
35
+ result += " '#{k}'='#{v}'"
36
+ end
37
+ result += ">"
38
+ result
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -3,23 +3,47 @@ require 'rhcp/broker'
3
3
 
4
4
  module RHCP
5
5
 
6
- class DispatchingBroker < RHCP::Broker
6
+ class DispatchingBroker < Broker
7
7
  def initialize
8
- @known_commands = Hash.new()
8
+ @brokers = []
9
9
  end
10
10
 
11
- # returns a list of all known commands
12
- def get_command_list()
13
- @known_commands
14
- end
15
-
16
11
  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
12
+ @brokers << new_broker
13
+ # TODO this is not a good way of handling duplicate detection since the broker has been added to the list already
14
+ get_command_list()
15
+ end
16
+
17
+ def get_broker_for_command_name(command_name, context)
18
+ result = nil
19
+ @brokers.each do |broker|
20
+ broker.get_command_list(context).each do |name, command|
21
+ if name == command_name
22
+ result = broker
23
+ end
24
+ end
20
25
  end
26
+ raise RHCP::RhcpException.new("no broker found that offers command '#{command_name}'") if result == nil
27
+ result
21
28
  end
22
-
29
+
30
+ # returns a list of all known commands
31
+ def get_command_list(context = RHCP::Context.new())
32
+ result = {}
33
+ @brokers.each do |broker|
34
+ broker.get_command_list(context).each do |name, command|
35
+ raise RHCP::RhcpException.new("duplicate command: '#{name}' has already been defined.") if result.has_key?(name)
36
+ result[name] = command
37
+ end
38
+ end
39
+ result
40
+ end
41
+
42
+ def execute(request)
43
+ broker = get_broker_for_command_name(request.command.name, request.context)
44
+ broker.execute(request)
45
+ end
46
+
23
47
  end
24
48
 
25
49
  end
@@ -13,7 +13,7 @@ module RHCP
13
13
  # TODO add some logging
14
14
 
15
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
16
+ # registered there via http
17
17
  #
18
18
  # /rhcp/get_commands
19
19
  # /rhcp/get_lookup_values
@@ -62,6 +62,7 @@ module RHCP
62
62
  # TODO this path should probably be quite relative
63
63
  @server.mount "/#{@url_prefix}/info", HTTPServlet::FileHandler, "docroot"
64
64
  @server.mount "/#{@url_prefix}/info2", HTTPServlet::FileHandler, "qooxdoo_rhcp/build"
65
+
65
66
  @server.mount "/#{@url_prefix}/get_commands", GetCommandsServlet, @broker
66
67
  @server.mount "/#{@url_prefix}/get_lookup_values", GetLookupValuesServlet, @broker
67
68
  @server.mount "/#{@url_prefix}/execute", ExecuteServlet, @broker
@@ -76,18 +77,28 @@ module RHCP
76
77
  end
77
78
 
78
79
  def do_GET(req, res)
79
- res['Content-Type'] = "application/json"
80
+ res['Content-Type'] = "application/json"
81
+ @broker = RHCP::Client::ContextAwareBroker.new(@broker)
82
+ Thread.current['broker'] = @broker
80
83
  begin
81
- #@logger.info("executing #{req}")
82
- do_it(req, res)
83
- #@logger.info("finished executing #{req}")
84
- rescue RHCP::RhcpException => ex
85
- # TODO define error format (should we send back a JSON-ified response object here?)
86
- @logger.error("got an exception while executing request . #{ex}")
84
+ @logger.debug("executing #{req}")
85
+
86
+ begin
87
+ pre_flight_command = @broker.get_command("pre_flight_init")
88
+ request = RHCP::Request.new(pre_flight_command, {}, @broker.context)
89
+ response = @broker.execute(request)
90
+ rescue RHCP::RhcpException
91
+ @logger.warn("no pre_flight command defined")
92
+ end
93
+ do_it(req, res)
94
+ rescue Exception => ex
95
+ @logger.error("got an exception while executing request . #{ex}\n#{ex.backtrace.join("\n")}")
96
+
97
+ response = RHCP::Response.new()
98
+ response.mark_as_error(ex.to_s, ex.backtrace.join("\n"))
99
+
87
100
  res.status = 500
88
- res.body = [ ex.to_s ].to_json()
89
- #puts ex
90
- #puts ex.backtrace.join("\n")
101
+ res.body = response.to_json()
91
102
  end
92
103
  end
93
104
 
@@ -101,8 +112,13 @@ module RHCP
101
112
  class GetCommandsServlet < BaseServlet
102
113
 
103
114
  def do_it(req, res)
104
- commands = @broker.get_command_list
105
- puts "about to send back commands : #{commands.values.to_json()}"
115
+ context = req.body == nil ?
116
+ RHCP::Context.new() :
117
+ RHCP::Context.reconstruct_from_json(req.body)
118
+
119
+ commands = @broker.get_command_list(context)
120
+ #puts "about to send back commands : #{commands.values.to_json()}"
121
+ @logger.info("/get_commands returns #{commands.values.size()} commands.")
106
122
  res.body = commands.values.to_json()
107
123
  end
108
124
  end
@@ -110,10 +126,23 @@ module RHCP
110
126
  class GetLookupValuesServlet < BaseServlet
111
127
 
112
128
  def do_it(req, res)
113
- partial_value = req.query.has_key?('partial') ? req.query['partial'] : ''
114
- command = @broker.get_command(req.query['command'])
115
- param = command.get_param(req.query['param'])
116
- lookup_values = param.get_lookup_values(partial_value)
129
+ request = RHCP::Request.reconstruct_from_json(@broker, req.body)
130
+
131
+ #command = @broker.get_command(req.query['command'])
132
+ #param = command.get_param(req.query['param'])
133
+
134
+ #partial_value = req.query.has_key?('partial') ? req.query['partial'] : ''
135
+
136
+ req_params = {}
137
+ if req.query_string != nil
138
+ req.query_string.split("&").each do |param_string|
139
+ (key, value) = param_string.split("=")
140
+ req_params[key] = value
141
+ end
142
+ end
143
+ $logger.debug("get_lookup_values for #{req_params['command']}")
144
+ lookup_values = @broker.get_lookup_values(request, req_params['param'])
145
+
117
146
  res.body = lookup_values.to_json()
118
147
  end
119
148
 
@@ -126,9 +155,13 @@ module RHCP
126
155
  # TODO filter "nocache" parameters from query string
127
156
  request = RHCP::Request.reconstruct_from_json(@broker, req.body)
128
157
 
129
- response = request.execute()
158
+ @logger.debug "http exporter delegating to next broker : #{@broker.class.to_s}"
159
+ response = @broker.execute(request)
160
+
161
+ response.set_context(@broker.context.cookies)
130
162
 
131
163
  @logger.debug("sending back response : #{response.to_json()}")
164
+ puts "response : #{response.to_json()}"
132
165
  res.body = response.to_json()
133
166
  end
134
167
 
@@ -0,0 +1,85 @@
1
+ require 'rhcp/broker'
2
+
3
+ module RHCP
4
+
5
+ class LoggingBroker < Broker
6
+
7
+ def initialize(wrapped_broker)
8
+ super()
9
+ @wrapped_broker = wrapped_broker
10
+ @logger = RHCP::ModuleHelper::instance.logger
11
+ end
12
+
13
+ def get_command_list(context = RHCP::Context.new())
14
+ @wrapped_broker.get_command_list(context)
15
+ end
16
+
17
+ def register_command(command)
18
+ @wrapped_broker.register_command(command)
19
+ end
20
+
21
+ def clear
22
+ @wrapped_broker.clear()
23
+ end
24
+
25
+ def get_blacklisted_commands
26
+ [ "log_to_jabber", "log_to_jabber_detail", "on_host", "hostname" ]
27
+ end
28
+
29
+
30
+ # TODO would be nice if this helper method would be accessible from inside vop plugins
31
+ def var_name(name)
32
+ self.class.to_s + "." + name
33
+ end
34
+
35
+ def execute(request)
36
+ blacklisted = get_blacklisted_commands.include?(request.command.name)
37
+ if not blacklisted and request.context.cookies.has_key?('__loggingbroker.blacklisted_commands') then
38
+ blacklisted = request.context.cookies['__loggingbroker.blacklisted_commands'].split(',').include?(request.command.name)
39
+ end
40
+
41
+ command = get_command(request.command.name, request.context)
42
+ mode = command.is_read_only ? 'r/o' : 'r/w'
43
+
44
+ is_new_request = Thread.current[var_name("request_id")] == nil
45
+ if is_new_request
46
+ Thread.current[var_name("request_id")] = Time.now().to_i.to_s + '_' + request.command.name
47
+ Thread.current[var_name("stack")] = []
48
+ Thread.current[var_name("id_stack")] = []
49
+ end
50
+
51
+ Thread.current[var_name("stack")] << command.name
52
+
53
+ level = Thread.current[var_name("stack")].size()
54
+
55
+ unless blacklisted
56
+ #$logger.info ">> #{command.name} (#{mode}) : #{Thread.current[var_name("stack")].join(" -> ")}"
57
+ start_ts = Time.now()
58
+ log_request_start(Thread.current[var_name("request_id")], level, mode, Thread.current[var_name("stack")], request, start_ts)
59
+ end
60
+
61
+ response = @wrapped_broker.execute(request)
62
+
63
+ unless blacklisted
64
+ stop_ts = Time.now()
65
+ duration = stop_ts.to_i - start_ts.to_i
66
+
67
+ log_request_stop(Thread.current[var_name("request_id")], level, mode, Thread.current[var_name("stack")], request, response, duration)
68
+ end
69
+
70
+ Thread.current[var_name("stack")].pop
71
+
72
+ response
73
+ end
74
+
75
+ def log_request_start(request_id, level, mode, current_stack, request, start_ts)
76
+ $logger.debug("> #{request_id} #{level} [#{mode}] #{current_stack.join('.')} #{request}")
77
+ end
78
+
79
+ def log_request_stop(request_id, level, mode, current_stack, request, response, duration)
80
+ $logger.debug("< #{request_id} #{level} [#{mode}] #{current_stack.join('.')} #{request}")
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,120 @@
1
+ require 'rhcp/broker'
2
+ require 'memcache'
3
+
4
+ module RHCP
5
+
6
+ class MemcachedBroker < Broker
7
+
8
+ def initialize(wrapped_broker, memcached_server = "127.0.0.1:11211", expiration_seconds = 3600)
9
+ super()
10
+ @wrapped_broker = wrapped_broker
11
+
12
+ @cache = MemCache.new(
13
+ [memcached_server]
14
+ )
15
+ @expiry_seconds = expiration_seconds
16
+ end
17
+
18
+ # TODO would be nice if we could turn the caching on and off through commands
19
+
20
+ def get_command_list(context = RHCP::Context.new())
21
+ @wrapped_broker.get_command_list(context)
22
+ end
23
+
24
+ def register_command(command)
25
+ @wrapped_broker.register_command(command)
26
+ end
27
+
28
+ def clear
29
+ @wrapped_broker.clear()
30
+ end
31
+
32
+ def get_lookup_values(request, param_name)
33
+ sorted_param_values = []
34
+ request.param_values.keys.sort.each do |key|
35
+ sorted_param_values << request.param_values[key]
36
+ end
37
+ cache_key = 'lookup_' + request.command.name + '_' + sorted_param_values.join('|')
38
+
39
+ result = nil
40
+ if ((request.context == nil) or (not request.context.cookies.has_key?('__caching.disable')))
41
+ result = @cache.get(cache_key)
42
+ end
43
+
44
+ if result == nil then
45
+ result = @wrapped_broker.get_lookup_values(request, param_name)
46
+ @cache.add(cache_key, result, @expiry_seconds)
47
+ end
48
+
49
+ result
50
+ end
51
+
52
+ def execute(request)
53
+ command = get_command(request.command.name, request.context)
54
+
55
+ result = nil
56
+
57
+ # construct the cache key out of the command name and all parameter values
58
+ sorted_param_values = []
59
+ request.param_values.keys.sort.each do |key|
60
+ sorted_param_values << request.param_values[key]
61
+ end
62
+ cache_key = request.command.name + '_' + sorted_param_values.join('|')
63
+
64
+ should_read_from_cache =
65
+ (command.is_read_only) &&
66
+ ( (not command.result_hints.has_key?(:cache)) || (command.result_hints[:cache]) ) &&
67
+ ((request.context == nil) or (not request.context.cookies.has_key?('__caching.disable')))
68
+
69
+ if should_read_from_cache
70
+ cached_response_json = @cache.get(cache_key)
71
+ if cached_response_json
72
+ #cached_response = JSON.parse(cached_response_json)
73
+ cached_response = RHCP::Response.reconstruct_from_json(cached_response_json)
74
+ $logger.debug("got data from cache for #{cache_key}")
75
+ cached_data = cached_response.data
76
+
77
+ # TODO why don't we just throw back the response?
78
+ response = RHCP::Response.new()
79
+ response.data = cached_data
80
+ response.status = RHCP::Response::Status::OK
81
+
82
+ # all context that has been sent with the request should be sent back
83
+ response.context = request.context.cookies
84
+
85
+ # also, we want to add all context that has been returned by the cached response
86
+ if cached_response.context != nil then
87
+ if response.context == nil then
88
+ response.context = {}
89
+ end
90
+ $logger.debug("merging in context from cached response : #{cached_response.context}")
91
+ cached_response.context.each do |k,v|
92
+ response.context[k] = v
93
+ end
94
+ end
95
+
96
+ response.created_at = cached_response.created_at
97
+
98
+ result = cached_data
99
+ else
100
+
101
+ end
102
+ end
103
+
104
+ if result == nil
105
+ response = @wrapped_broker.execute(request)
106
+
107
+ # TODO we might want to store the result in memcached nevertheless
108
+ if should_read_from_cache
109
+ # TODO do we want to cache failed command executions as well?
110
+ $logger.debug("storing data in cache for : #{cache_key}")
111
+ @cache.add(cache_key, response.to_json, @expiry_seconds)
112
+ end
113
+ end
114
+
115
+ response
116
+ end
117
+
118
+ end
119
+
120
+ end