siffer 0.0.4

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.
Files changed (91) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +65 -0
  3. data/Rakefile +64 -0
  4. data/bin/siffer +71 -0
  5. data/doc/SIF ImplementationSpecification.pdf +0 -0
  6. data/doc/rdoc/classes/Siffer.html +374 -0
  7. data/doc/rdoc/classes/Siffer/Agent.html +296 -0
  8. data/doc/rdoc/classes/Siffer/Container.html +286 -0
  9. data/doc/rdoc/classes/Siffer/Messages.html +141 -0
  10. data/doc/rdoc/classes/Siffer/Messages/Ack.html +216 -0
  11. data/doc/rdoc/classes/Siffer/Messages/Acl.html +160 -0
  12. data/doc/rdoc/classes/Siffer/Messages/Error.html +248 -0
  13. data/doc/rdoc/classes/Siffer/Messages/Message.html +359 -0
  14. data/doc/rdoc/classes/Siffer/Messages/Message/Header.html +181 -0
  15. data/doc/rdoc/classes/Siffer/Messages/Register.html +257 -0
  16. data/doc/rdoc/classes/Siffer/Messages/RequestBody.html +300 -0
  17. data/doc/rdoc/classes/Siffer/Messages/Status.html +269 -0
  18. data/doc/rdoc/classes/Siffer/Messaging.html +331 -0
  19. data/doc/rdoc/classes/Siffer/Protocol.html +388 -0
  20. data/doc/rdoc/classes/Siffer/Protocol/NonPostRequest.html +111 -0
  21. data/doc/rdoc/classes/Siffer/Protocol/UnknownPath.html +111 -0
  22. data/doc/rdoc/classes/Siffer/Registration.html +391 -0
  23. data/doc/rdoc/classes/Siffer/Request.html +209 -0
  24. data/doc/rdoc/classes/Siffer/RequestLogger.html +211 -0
  25. data/doc/rdoc/classes/Siffer/Response.html +182 -0
  26. data/doc/rdoc/classes/Siffer/Server.html +242 -0
  27. data/doc/rdoc/created.rid +1 -0
  28. data/doc/rdoc/files/LICENSE.html +129 -0
  29. data/doc/rdoc/files/README_rdoc.html +184 -0
  30. data/doc/rdoc/files/lib/agent_rb.html +101 -0
  31. data/doc/rdoc/files/lib/container_rb.html +108 -0
  32. data/doc/rdoc/files/lib/messages/ack_rb.html +101 -0
  33. data/doc/rdoc/files/lib/messages/acl_rb.html +101 -0
  34. data/doc/rdoc/files/lib/messages/error_rb.html +101 -0
  35. data/doc/rdoc/files/lib/messages/message_rb.html +101 -0
  36. data/doc/rdoc/files/lib/messages/register_rb.html +101 -0
  37. data/doc/rdoc/files/lib/messages/request_body_rb.html +101 -0
  38. data/doc/rdoc/files/lib/messages/status_rb.html +101 -0
  39. data/doc/rdoc/files/lib/messages_rb.html +114 -0
  40. data/doc/rdoc/files/lib/messaging_rb.html +101 -0
  41. data/doc/rdoc/files/lib/protocol_rb.html +101 -0
  42. data/doc/rdoc/files/lib/registration_rb.html +101 -0
  43. data/doc/rdoc/files/lib/request_logger_rb.html +101 -0
  44. data/doc/rdoc/files/lib/request_rb.html +101 -0
  45. data/doc/rdoc/files/lib/response_rb.html +101 -0
  46. data/doc/rdoc/files/lib/server_rb.html +101 -0
  47. data/doc/rdoc/files/lib/siffer_rb.html +115 -0
  48. data/doc/rdoc/fr_class_index.html +47 -0
  49. data/doc/rdoc/fr_file_index.html +46 -0
  50. data/doc/rdoc/fr_method_index.html +96 -0
  51. data/doc/rdoc/index.html +24 -0
  52. data/doc/rdoc/rdoc-style.css +208 -0
  53. data/lib/agent.rb +43 -0
  54. data/lib/container.rb +96 -0
  55. data/lib/messages.rb +7 -0
  56. data/lib/messages/ack.rb +43 -0
  57. data/lib/messages/acl.rb +25 -0
  58. data/lib/messages/error.rb +174 -0
  59. data/lib/messages/message.rb +71 -0
  60. data/lib/messages/register.rb +60 -0
  61. data/lib/messages/request_body.rb +66 -0
  62. data/lib/messages/status.rb +55 -0
  63. data/lib/messaging.rb +96 -0
  64. data/lib/protocol.rb +159 -0
  65. data/lib/registration.rb +87 -0
  66. data/lib/request.rb +25 -0
  67. data/lib/request_logger.rb +31 -0
  68. data/lib/response.rb +26 -0
  69. data/lib/server.rb +40 -0
  70. data/lib/siffer.rb +44 -0
  71. data/spec/agent_spec.rb +53 -0
  72. data/spec/cli_spec.rb +40 -0
  73. data/spec/container_spec.rb +103 -0
  74. data/spec/default_agent +6 -0
  75. data/spec/default_server +5 -0
  76. data/spec/message_specs/ack_spec.rb +28 -0
  77. data/spec/message_specs/error_spec.rb +24 -0
  78. data/spec/message_specs/header_spec.rb +25 -0
  79. data/spec/message_specs/message_spec.rb +57 -0
  80. data/spec/message_specs/register_spec.rb +86 -0
  81. data/spec/message_specs/request_body_spec.rb +58 -0
  82. data/spec/message_specs/status_spec.rb +25 -0
  83. data/spec/messaging_spec.rb +88 -0
  84. data/spec/protocol_spec.rb +49 -0
  85. data/spec/registration_spec.rb +33 -0
  86. data/spec/request_logger_spec.rb +15 -0
  87. data/spec/request_spec.rb +10 -0
  88. data/spec/response_spec.rb +24 -0
  89. data/spec/server_spec.rb +35 -0
  90. data/spec/spec_helper.rb +26 -0
  91. metadata +191 -0
@@ -0,0 +1,159 @@
1
+ module Siffer
2
+ # The Protocol module deals with the transport validation. Provides
3
+ # constants for HTTP statuses as well as acceptable paths for Siffer
4
+ # Servers and Agents.
5
+ module Protocol
6
+
7
+ include Siffer::Messages
8
+ include Siffer::Messaging
9
+
10
+ class UnknownPath < Exception; end #:nodoc:
11
+ class NonPostRequest < Exception; end #:nodoc:
12
+
13
+ # Checks the incoming request against the following protocol constraints:
14
+ # * Unknown PATH
15
+ # * Non-POST request
16
+ def request_failed_protocol?
17
+ begin
18
+ check_path_against_protocol
19
+ rescue UnknownPath
20
+ # TODO: Make a better Not Found response
21
+ @response = Response.new(HTTP_STATUS_CODES[404],
22
+ 404,
23
+ {"Content-Type" => Siffer::Messaging::MIME_TYPES["htm"]})
24
+ rescue NonPostRequest
25
+ # TODO: Make a better Method Not Allowed response
26
+ @response = Response.new(HTTP_STATUS_CODES[405],
27
+ 405,
28
+ {"Content-Type" => Siffer::Messaging::MIME_TYPES["htm"]})
29
+ end
30
+ end
31
+
32
+ # Validates the request object against the Acceptable Paths (Siffer spec)
33
+ # and that the request is a POST.
34
+ def check_path_against_protocol
35
+ unless ACCEPTABLE_PATHS.has_value? @request.path_info
36
+ raise UnknownPath
37
+ end
38
+ unless @request.post?
39
+ raise NonPostRequest
40
+ end
41
+ end
42
+
43
+ # Returns the URI of the component (Server or Agent)
44
+ def uri
45
+ URI.parse("http://#{host}:#{port}").to_s
46
+ end
47
+
48
+ # Provides a context for each request. Creates the @request, @response and
49
+ # @original objects to use throughout the call.
50
+ # Validates against Protocol as well as Messaging using #request_failed
51
+ # predicates respectively.
52
+ #
53
+ # Yields the block if provided to allow further processing.
54
+ #
55
+ #Finishes by calling #finish on the response object or returns the generic
56
+ # response message.
57
+ def with_each_request(env, &block)
58
+ @request = Request.new(env)
59
+ unless request_failed_protocol? or request_failed_messaging?
60
+ using_message_from @request do # possibly a concurrency issue here
61
+ yield if block_given?
62
+ end
63
+ end
64
+ @response.finish unless no_response_available
65
+ end
66
+
67
+ # Sets the @response to a Siffer::Messages::Ack message with the
68
+ # appropriate Error category and code. Optional description is allowed
69
+ # and placed in the ExtendedDesc node.
70
+ def error_response(category,code, desc = nil)
71
+ error = Error.new(category,code,desc)
72
+ ack = Ack.new(self.name, @request.original, :error => error)
73
+ @response = Response.new(ack)
74
+ end
75
+
76
+ # Determines if there is a response instance created. If not
77
+ # it creates one with an Ack/Error describing not having
78
+ # anything to process.
79
+ def no_response_available
80
+ if @response.nil?
81
+ err = <<"ERR"
82
+ You are receiving this message because you failed to
83
+ provide enough information to respond to. This is likely due
84
+ to the message type missing (i.e. Register,Provide) or possibly
85
+ not enough information to process effectively. Please check the
86
+ message you are sending and resend after any corrections.
87
+ ERR
88
+ !error_response(12,1,err)
89
+ end
90
+ end
91
+
92
+ # Paths that comply with the messaging protocol determined
93
+ # by the SIF Specification. These are Siffer specific and not
94
+ # spelled out in SIF Specification (rather implied). Each path
95
+ # will generate it's own predicate method (i.e. #ping?) that can
96
+ # be used to determine the requests path based on the content of the
97
+ # message or the path-info of the request.
98
+ ACCEPTABLE_PATHS = {
99
+ :root => "/",
100
+ :ping => "/ping",
101
+ :status => "/status",
102
+ :register => "/register"
103
+ }
104
+
105
+ ACCEPTABLE_PATHS.each do |name,path|
106
+ define_method("#{name.to_s}?") do
107
+ @request.message.type.downcase == name.to_s
108
+ # The difference in these lines is the difference between allowing
109
+ # any message to come through any PATH or forcing PATH and message
110
+ # to match. What is better?
111
+ # TODO: Decide on protocol validations !
112
+ #@request.path_info == path || req_body.type.downcase == name.to_s
113
+ end
114
+ end
115
+
116
+ # Every standard HTTP code mapped to the appropriate message.
117
+ # Stolen from Mongrel.
118
+ HTTP_STATUS_CODES = {
119
+ 100 => 'Continue',
120
+ 101 => 'Switching Protocols',
121
+ 200 => 'OK',
122
+ 201 => 'Created',
123
+ 202 => 'Accepted',
124
+ 203 => 'Non-Authoritative Information',
125
+ 204 => 'No Content',
126
+ 205 => 'Reset Content',
127
+ 206 => 'Partial Content',
128
+ 300 => 'Multiple Choices',
129
+ 301 => 'Moved Permanently',
130
+ 302 => 'Moved Temporarily',
131
+ 303 => 'See Other',
132
+ 304 => 'Not Modified',
133
+ 305 => 'Use Proxy',
134
+ 400 => 'Bad Request',
135
+ 401 => 'Unauthorized',
136
+ 402 => 'Payment Required',
137
+ 403 => 'Forbidden',
138
+ 404 => 'Not Found',
139
+ 405 => 'Method Not Allowed',
140
+ 406 => 'Not Acceptable',
141
+ 407 => 'Proxy Authentication Required',
142
+ 408 => 'Request Time-out',
143
+ 409 => 'Conflict',
144
+ 410 => 'Gone',
145
+ 411 => 'Length Required',
146
+ 412 => 'Precondition Failed',
147
+ 413 => 'Request Entity Too Large',
148
+ 414 => 'Request-URI Too Large',
149
+ 415 => 'Unsupported Media Type',
150
+ 500 => 'Internal Server Error',
151
+ 501 => 'Not Implemented',
152
+ 502 => 'Bad Gateway',
153
+ 503 => 'Service Unavailable',
154
+ 504 => 'Gateway Time-out',
155
+ 505 => 'HTTP Version not supported'
156
+ }
157
+
158
+ end
159
+ end
@@ -0,0 +1,87 @@
1
+ module Siffer
2
+ # The Registration module manages Register message types and processes
3
+ # registration requests.
4
+ module Registration
5
+
6
+ include Siffer::Messages
7
+
8
+ class AgentNotRegistered < Exception #:nodoc:
9
+ end
10
+
11
+ # The request (message) is processed for registration requests.
12
+ def process_registration
13
+ begin
14
+ check_for_registration
15
+ register
16
+ rescue AgentNotRegistered
17
+ error_response(4,9)
18
+ end
19
+ end
20
+
21
+ # Parses the request body with RequestBody to determine if the
22
+ # the request is a registration message. If it is or if the
23
+ # source is already registered it passes through, otherwise raises
24
+ # AgentNotRegistered exception.
25
+ def check_for_registration
26
+ unless register? or registered?(@request.message.source_id)
27
+ raise AgentNotRegistered
28
+ end
29
+ end
30
+
31
+ # Process the message for registration by validating:
32
+ # * source_id
33
+ # * permissions to register with ZIS
34
+ # * version compatibility
35
+ # * buffer size compatibility
36
+ # If all validations pass the registration is stored by the ZIS and
37
+ # the agent is added to the list of agents available to the zone.
38
+ def register
39
+ if register?
40
+ @registration = Register.parse(@request.body)
41
+ validate_source_id
42
+ validate_permission
43
+ validate_version
44
+ validate_max_buffer_size
45
+ # ignore the fact we haven't stored away any registration information !!!!!!
46
+ # ignore the fact we haven't even built a data model for objects and permissions for the acl !!!!!
47
+ ack = Ack.new(name, @request.original, :status => Status.success(Acl.new))
48
+ @response = Response.new(ack)
49
+ end
50
+ end
51
+
52
+ # We may not do anything here - will we pre-permit sources to register?
53
+ # We could possibly call the Central Admin to qualify the source there?
54
+ def validate_source_id
55
+ end
56
+
57
+
58
+ # We need to check for permissions previously assigned for this
59
+ # agent (possibly from Central Admin).
60
+ def validate_permission
61
+ end
62
+
63
+ # Currently only validating that the registration version matches
64
+ # identically to the SIF version implemented by Siffer.
65
+ # TODO: This will need refinement to provide broader version support.
66
+ def validate_version
67
+ if @registration.version != Siffer.sif_version
68
+ error_response(5,4,"Unsupported version: #{@registration.version}")
69
+ end
70
+ end
71
+
72
+ # Validates the registration buffer size is greater than the
73
+ # minimum buffer set by the ZIS.
74
+ def validate_max_buffer_size
75
+ if @registration.max_buffer < min_buffer
76
+ error_response(5,6)
77
+ end
78
+ end
79
+
80
+ # Returns true if the agent is included in the list of Agents
81
+ # previously registered.
82
+ def registered?(agent)
83
+ agents.has_key? agent
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,25 @@
1
+ module Siffer
2
+
3
+ # Request represents each message received by the Server or Agent in Siffer.
4
+ class Request < Rack::Request
5
+
6
+ # Constructor will assure that #message is always populated with the body
7
+ # of the request for easy access throughout the call.
8
+ def initialize(env)
9
+ env["CONTENT_TYPE"] ||= Siffer::Messaging::MIME_TYPES["appxml"]
10
+ super(env)
11
+ end
12
+
13
+ # Provides access to the request message in Siffer::Messages::Message
14
+ # format.
15
+ def message
16
+ if body.is_a?(StringIO) && body.pos == body.length
17
+ body.rewind
18
+ end
19
+ Siffer::Messages::RequestBody.parse(body)
20
+ end
21
+ alias :original :message
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,31 @@
1
+ module Siffer
2
+ class RequestLogger
3
+ def initialize(app, log=nil)
4
+ @app = app
5
+ @log = log || STDERR
6
+ end
7
+
8
+ def call(env)
9
+ @env = env
10
+ # Maybe there is a better way to pick the body of the input
11
+ # instead of reading it then stuffing it back in ??
12
+ @msg = env["rack.input"].read
13
+ log_request
14
+ env["rack.input"] = StringIO.new(@msg)
15
+ @app.call(env)
16
+ end
17
+
18
+ def log_request
19
+ @now = Time.now
20
+ message = Siffer::Messages::RequestBody.parse(@msg)
21
+ @log << %{%s request made by %s on %s at %s\n} %
22
+ [
23
+ message.type,
24
+ @env["REMOTE_USER"],
25
+ @now.strftime("%b/%d/%Y"),
26
+ @now.strftime("%H:%M:%S")
27
+ ]
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ module Siffer
2
+
3
+ class Response < Rack::Response
4
+
5
+ def initialize(body=[], status=200, header={}, &block)
6
+ header["Content-Type"] ||= Siffer::Messaging::MIME_TYPES["appxml"]
7
+ super(body,status,header,&block)
8
+ end
9
+
10
+ def self.from(url,data)
11
+ uri = URI.parse(url)
12
+ begin
13
+ response = Net::HTTP.start(uri.host,uri.port) { |http|
14
+ post = Net::HTTP::Post.new(uri.path, {})
15
+ post.body = (data.respond_to?("read")) ? data.read : data
16
+ post.content_type = Siffer::Messaging::MIME_TYPES["appxml"]
17
+ http.request(post)
18
+ }
19
+ Response.new(response.body,response.code.to_i,response.header.to_hash)
20
+ rescue Errno::ECONNREFUSED => e
21
+ Response.new(e.message, 500, {})
22
+ end
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,40 @@
1
+ module Siffer
2
+
3
+ # Zone Integration Server
4
+ # Facilitates communication between Agents. Acts as a central point
5
+ # where Agents can send their events/requests and receive the response
6
+ # from other Agents.
7
+ class Server
8
+ include Siffer::Protocol
9
+ include Siffer::Registration
10
+
11
+ attr_reader :name, :host, :port, :min_buffer, :agents
12
+
13
+ ## options Parameter
14
+ # name = The name of the ZIS
15
+ # host = The host this instance is started on
16
+ # port = The port to connect to this ZIS
17
+ # admin = The administration site managing this ZIS
18
+ # min_buffer = The minimum buffer size this ZIS will facilitate
19
+ def initialize(options = {})
20
+ raise ArgumentError, "Administration URL required" unless options.include? "admin"
21
+ @name = options["name"] || "Default Server"
22
+ @host = options["host"] || "localhost"
23
+ @port = options["port"] || 8300
24
+ @min_buffer = options["min_buffer"] || 1024
25
+ @agents = {}
26
+ end
27
+
28
+ # Process the request with all the SIF protocols
29
+ def call(env)
30
+ with_each_request(env) do
31
+ process_registration
32
+ # process_provision
33
+ # process_subscription
34
+ # process_event
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'uuid'
3
+ require 'rack'
4
+ require 'builder'
5
+ require 'net/http'
6
+ require 'net/https'
7
+ require 'rexml/document'
8
+ require 'hpricot'
9
+
10
+ $: << File.expand_path(File.dirname(__FILE__))
11
+
12
+ module Siffer
13
+
14
+ VENDOR = "h3o(software)" unless defined?(Siffer::VENDOR)
15
+ VERSION = [0,0,4] unless defined?(Siffer::VERSION)
16
+ SIF_VERSION = [2,2,0] unless defined?(Siffer::SIF_VERSION)
17
+ SIF_XMLNS = "http://www.sifinfo.org/infrastructure/2.x" unless defined?(Siffer::SIF_XMLNS)
18
+
19
+ # The vendor of this SIF implementation (self describing for Agents)
20
+ def self.vendor() VENDOR end
21
+
22
+ # The version of the h3o(software) SIF implementation
23
+ def self.version() VERSION.join(".") end
24
+
25
+ # The version of SIF being implemented - based on the specification
26
+ def self.sif_version() SIF_VERSION.join(".") end
27
+
28
+ # The SIF XML namespace to be used across this implementation
29
+ def self.sif_xmlns() SIF_XMLNS end
30
+
31
+ # The root directory that the SIF implementation is running from
32
+ def self.root() @root ||= Dir.pwd end
33
+ def self.root=(value) @root = value end
34
+
35
+ end
36
+
37
+ Siffer.autoload :Messages, "messages"
38
+ Siffer.autoload :Protocol, "protocol"
39
+ Siffer.autoload :Messaging, "messaging"
40
+ Siffer.autoload :Registration, "registration"
41
+
42
+ %w(server agent response request container request_logger).each do |component|
43
+ require component
44
+ end
@@ -0,0 +1,53 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+ require 'webrick'
3
+
4
+ describe Siffer::Agent do
5
+ it "should instantiate with default host, port, name" do
6
+ agent = Siffer::Agent.new("servers" => 'none', "admin" => 'none')
7
+ agent.host.should == "localhost"
8
+ agent.port.should == 8300
9
+ agent.name.should == "Default Agent"
10
+ end
11
+
12
+ it "should allow defaults to be overriden" do
13
+ agent = Siffer::Agent.new("admin" => 'none',
14
+ "servers" => 'none',
15
+ "name" => "name",
16
+ "host" => "test",
17
+ "port" => 222)
18
+ agent.host.should == "test"
19
+ agent.name.should == "name"
20
+ agent.port.should == 222
21
+ end
22
+
23
+ it "should require a server url to register with" do
24
+ lambda {
25
+ agent = Siffer::Agent.new
26
+ }.should raise_error("Server URL(s) required")
27
+ end
28
+
29
+ it "should require an admin URL" do
30
+ lambda {
31
+ agent = Siffer::Agent.new("servers" => "none")
32
+ }.should raise_error("Administration URL required")
33
+ end
34
+
35
+ it "should respond to uri" do
36
+ agent = Siffer::Agent.new("admin" => 'none', "servers" => 'none')
37
+ agent.uri.should == "http://localhost:8300"
38
+ end
39
+
40
+ it "should register with server(s) on wake-up" do
41
+ with_fake_server(Siffer::Server.new("admin" => "none")) do |url|
42
+ agent = Siffer::Agent.new("admin" => 'none', "servers" => url)
43
+ agent.wake_up
44
+ agent.should be_registered
45
+ end
46
+ end
47
+
48
+ it "should be unregistered by default" do
49
+ agent = Siffer::Agent.new("admin" => 'none', "servers" => 'none')
50
+ agent.should_not be_registered
51
+ end
52
+
53
+ end