pantry 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +19 -0
  5. data/Gemfile +15 -0
  6. data/Guardfile +16 -0
  7. data/LICENSE +20 -0
  8. data/README.md +53 -0
  9. data/Rakefile +18 -0
  10. data/Vagrantfile +86 -0
  11. data/bin/pantry +11 -0
  12. data/bin/pantry-client +38 -0
  13. data/bin/pantry-server +33 -0
  14. data/dist/client.yml +79 -0
  15. data/dist/server.yml +56 -0
  16. data/dist/upstart/pantry-client.conf +12 -0
  17. data/dist/upstart/pantry-server.conf +12 -0
  18. data/doc/message_packet.dot +19 -0
  19. data/doc/message_packet.dot.png +0 -0
  20. data/doc/network_topology.dot +42 -0
  21. data/doc/network_topology.dot.png +0 -0
  22. data/lib/celluloid_zmq_patches.rb +16 -0
  23. data/lib/opt_parse_plus.rb +184 -0
  24. data/lib/pantry.rb +197 -0
  25. data/lib/pantry/cli.rb +154 -0
  26. data/lib/pantry/client.rb +131 -0
  27. data/lib/pantry/client_info.rb +34 -0
  28. data/lib/pantry/client_registry.rb +104 -0
  29. data/lib/pantry/command.rb +194 -0
  30. data/lib/pantry/command_handler.rb +53 -0
  31. data/lib/pantry/command_line.rb +115 -0
  32. data/lib/pantry/commands/create_client.rb +30 -0
  33. data/lib/pantry/commands/download_directory.rb +35 -0
  34. data/lib/pantry/commands/echo.rb +32 -0
  35. data/lib/pantry/commands/edit_application.rb +60 -0
  36. data/lib/pantry/commands/register_client.rb +38 -0
  37. data/lib/pantry/commands/status.rb +78 -0
  38. data/lib/pantry/commands/sync_directory.rb +50 -0
  39. data/lib/pantry/commands/update_application.rb +45 -0
  40. data/lib/pantry/commands/upload_file.rb +68 -0
  41. data/lib/pantry/communication.rb +20 -0
  42. data/lib/pantry/communication/client.rb +75 -0
  43. data/lib/pantry/communication/client_filter.rb +117 -0
  44. data/lib/pantry/communication/file_service.rb +125 -0
  45. data/lib/pantry/communication/file_service/file_progress.rb +164 -0
  46. data/lib/pantry/communication/file_service/receive_file.rb +97 -0
  47. data/lib/pantry/communication/file_service/send_file.rb +74 -0
  48. data/lib/pantry/communication/publish_socket.rb +20 -0
  49. data/lib/pantry/communication/reading_socket.rb +89 -0
  50. data/lib/pantry/communication/receive_socket.rb +23 -0
  51. data/lib/pantry/communication/security.rb +44 -0
  52. data/lib/pantry/communication/security/authentication.rb +98 -0
  53. data/lib/pantry/communication/security/curve_key_store.rb +120 -0
  54. data/lib/pantry/communication/security/curve_security.rb +70 -0
  55. data/lib/pantry/communication/security/null_security.rb +32 -0
  56. data/lib/pantry/communication/send_socket.rb +19 -0
  57. data/lib/pantry/communication/serialize_message.rb +84 -0
  58. data/lib/pantry/communication/server.rb +97 -0
  59. data/lib/pantry/communication/subscribe_socket.rb +33 -0
  60. data/lib/pantry/communication/wait_list.rb +45 -0
  61. data/lib/pantry/communication/writing_socket.rb +46 -0
  62. data/lib/pantry/config.rb +182 -0
  63. data/lib/pantry/file_editor.rb +67 -0
  64. data/lib/pantry/logger.rb +78 -0
  65. data/lib/pantry/message.rb +134 -0
  66. data/lib/pantry/multi_command.rb +36 -0
  67. data/lib/pantry/server.rb +132 -0
  68. data/lib/pantry/test/acceptance.rb +83 -0
  69. data/lib/pantry/test/support/fake_fs.rb +31 -0
  70. data/lib/pantry/test/support/matchers.rb +13 -0
  71. data/lib/pantry/test/support/minitest.rb +13 -0
  72. data/lib/pantry/test/support/mock_ui.rb +23 -0
  73. data/lib/pantry/test/unit.rb +13 -0
  74. data/lib/pantry/ui.rb +68 -0
  75. data/lib/pantry/version.rb +3 -0
  76. data/pantry.gemspec +40 -0
  77. data/test/acceptance/cli/error_handling_test.rb +7 -0
  78. data/test/acceptance/cli/execute_command_on_clients_test.rb +32 -0
  79. data/test/acceptance/cli/request_info_from_server_test.rb +44 -0
  80. data/test/acceptance/communication/client_requests_info_from_server_test.rb +28 -0
  81. data/test/acceptance/communication/heartbeat_test.rb +19 -0
  82. data/test/acceptance/communication/pub_sub_communication_test.rb +53 -0
  83. data/test/acceptance/communication/security_test.rb +117 -0
  84. data/test/acceptance/communication/server_requests_info_from_client_test.rb +41 -0
  85. data/test/acceptance/test_helper.rb +25 -0
  86. data/test/fixtures/config.yml +22 -0
  87. data/test/fixtures/empty.yml +2 -0
  88. data/test/fixtures/file_to_upload +3 -0
  89. data/test/root_dir/.gitkeep +0 -0
  90. data/test/unit/cli_test.rb +173 -0
  91. data/test/unit/client_registry_test.rb +61 -0
  92. data/test/unit/client_test.rb +128 -0
  93. data/test/unit/command_handler_test.rb +79 -0
  94. data/test/unit/command_line_test.rb +5 -0
  95. data/test/unit/command_test.rb +206 -0
  96. data/test/unit/commands/create_client_test.rb +25 -0
  97. data/test/unit/commands/download_directory_test.rb +58 -0
  98. data/test/unit/commands/echo_test.rb +22 -0
  99. data/test/unit/commands/edit_application_test.rb +84 -0
  100. data/test/unit/commands/register_client_test.rb +41 -0
  101. data/test/unit/commands/status_test.rb +81 -0
  102. data/test/unit/commands/sync_directory_test.rb +75 -0
  103. data/test/unit/commands/update_application_test.rb +35 -0
  104. data/test/unit/commands/upload_file_test.rb +51 -0
  105. data/test/unit/communication/client_filter_test.rb +262 -0
  106. data/test/unit/communication/client_test.rb +99 -0
  107. data/test/unit/communication/file_service/receive_file_test.rb +214 -0
  108. data/test/unit/communication/file_service/send_file_test.rb +110 -0
  109. data/test/unit/communication/file_service_test.rb +56 -0
  110. data/test/unit/communication/publish_socket_test.rb +19 -0
  111. data/test/unit/communication/reading_socket_test.rb +110 -0
  112. data/test/unit/communication/receive_socket_test.rb +20 -0
  113. data/test/unit/communication/security/authentication_test.rb +97 -0
  114. data/test/unit/communication/security/curve_key_store_test.rb +110 -0
  115. data/test/unit/communication/security/curve_security_test.rb +44 -0
  116. data/test/unit/communication/security/null_security_test.rb +15 -0
  117. data/test/unit/communication/security_test.rb +49 -0
  118. data/test/unit/communication/send_socket_test.rb +19 -0
  119. data/test/unit/communication/serialize_message_test.rb +128 -0
  120. data/test/unit/communication/server_test.rb +106 -0
  121. data/test/unit/communication/subscribe_socket_test.rb +46 -0
  122. data/test/unit/communication/wait_list_test.rb +60 -0
  123. data/test/unit/communication/writing_socket_test.rb +46 -0
  124. data/test/unit/config_test.rb +150 -0
  125. data/test/unit/logger_test.rb +79 -0
  126. data/test/unit/message_test.rb +179 -0
  127. data/test/unit/multi_command_test.rb +45 -0
  128. data/test/unit/opt_parse_plus_test.rb +218 -0
  129. data/test/unit/pantry_test.rb +82 -0
  130. data/test/unit/server_test.rb +166 -0
  131. data/test/unit/test_helper.rb +25 -0
  132. data/test/unit/ui_test.rb +58 -0
  133. metadata +389 -13
@@ -0,0 +1,164 @@
1
+ module Pantry
2
+ module Communication
3
+ class FileService
4
+
5
+ # Informational object for keeping track of file upload progress and
6
+ # important information.
7
+ class UploadInfo
8
+
9
+ # Identity of the Receiver we're sending a file to
10
+ attr_accessor :receiver_uuid
11
+
12
+ # The file session identity from the Receiver
13
+ attr_accessor :file_uuid
14
+
15
+ def initialize
16
+ @finish_future = Celluloid::Future.new
17
+ end
18
+
19
+ # Block and wait for the file upload to finish
20
+ def wait_for_finish(timeout = nil)
21
+ @finish_future.value(timeout)
22
+ end
23
+
24
+ def finished!
25
+ @finish_future.signal(OpenStruct.new(:value => self))
26
+ end
27
+
28
+ end
29
+
30
+ # Sending-side version of UploadInfo
31
+ class SendingFile < UploadInfo
32
+ attr_reader :path, :file
33
+
34
+ def initialize(file_path, receiver_uuid, file_uuid)
35
+ super()
36
+ @path = file_path
37
+ @file_uuid = file_uuid
38
+ @file = File.open(@path, "r")
39
+
40
+ @receiver_uuid = receiver_uuid
41
+
42
+ @file_size = @file.size
43
+ @total_bytes_sent = 0
44
+
45
+ Pantry.ui.progress_start(@file_size)
46
+ end
47
+
48
+ def read(offset, bytes_to_read)
49
+ @total_bytes_sent += bytes_to_read
50
+ Pantry.ui.progress_step(bytes_to_read)
51
+
52
+ @file.seek(offset)
53
+ @file.read(bytes_to_read)
54
+ end
55
+
56
+ def finished!
57
+ Pantry.ui.progress_finish
58
+
59
+ @file.close
60
+ super
61
+ end
62
+
63
+ def finished?
64
+ @total_bytes_sent == @file_size || @file.closed?
65
+ end
66
+
67
+ end
68
+
69
+ # Receiving-side version of UploadInfo
70
+ # Can be configured with a completion block that will be executed once the
71
+ # file has been fully received and checksum verified.
72
+ class ReceivingFile < UploadInfo
73
+
74
+ # Location of the tempfile containing the contents of the uploaded file
75
+ attr_reader :uploaded_path
76
+
77
+ attr_reader :file_size, :checksum, :uploaded_path
78
+ attr_accessor :sender_uuid
79
+
80
+ def initialize(file_size, checksum, chunk_size, pipeline_size)
81
+ super()
82
+ @file_uuid = SecureRandom.uuid
83
+ @file_size = file_size
84
+ @checksum = checksum
85
+
86
+ @chunk_size = chunk_size
87
+ @pipeline_size = pipeline_size
88
+
89
+ @uploaded_file = Tempfile.new(file_uuid)
90
+ @uploaded_path = @uploaded_file.path
91
+
92
+ @next_requested_file_offset = 0
93
+ @current_pipeline_size = 0
94
+
95
+ @chunk_count = (@file_size.to_f / @chunk_size.to_f).ceil
96
+ @requested_chunks = 0
97
+ @received_chunks = 0
98
+ end
99
+
100
+ def on_complete(&block)
101
+ @completion_block = block
102
+ end
103
+
104
+ def chunks_to_fetch(&block)
105
+ chunks_to_fill_pipeline = [
106
+ (@pipeline_size - @current_pipeline_size),
107
+ @chunk_count - @requested_chunks
108
+ ].min
109
+
110
+ chunks_to_fill_pipeline.times do
111
+ block.call(@next_requested_file_offset, @chunk_size)
112
+
113
+ @next_requested_file_offset += @chunk_size
114
+ @current_pipeline_size += 1
115
+ @requested_chunks += 1
116
+ end
117
+ end
118
+
119
+ def write_chunk(offset, size, data)
120
+ @current_pipeline_size -= 1
121
+ @received_chunks += 1
122
+
123
+ @uploaded_file.seek(offset)
124
+ @uploaded_file.write(data)
125
+
126
+ if @received_chunks == @chunk_count
127
+ @uploaded_file.close
128
+ end
129
+ end
130
+
131
+ def finished!
132
+ @uploaded_file.close
133
+
134
+ if @completion_block && valid?
135
+ begin
136
+ @completion_block.call
137
+ rescue => ex
138
+ Pantry.logger.debug("[Receive File] Error running completion block #{ex.inspect}")
139
+ end
140
+ end
141
+
142
+ super
143
+ end
144
+
145
+ def complete?
146
+ @uploaded_file.closed?
147
+ end
148
+ alias finished? complete?
149
+
150
+ def valid?
151
+ return @is_valid if defined?(@is_valid)
152
+ uploaded_checksum = Pantry.file_checksum(@uploaded_file.path)
153
+ @is_valid = (uploaded_checksum == @checksum)
154
+ end
155
+
156
+ def remove
157
+ @uploaded_file.unlink
158
+ end
159
+
160
+ end
161
+
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,97 @@
1
+ module Pantry
2
+ module Communication
3
+
4
+ # Chunk file receiving tool that implements the protocol as described here
5
+ # http://zguide.zeromq.org/page:all#Transferring-Files
6
+ #
7
+ # In short, this tool requests chunks in a pipeline flow, writing out
8
+ # the received chunks to the file system at the given path.
9
+ class FileService::ReceiveFile
10
+ include Celluloid
11
+
12
+ attr_accessor :pipeline_size, :chunk_size
13
+
14
+ def initialize(service, chunk_size: 250_000, pipeline_size: 10)
15
+ @service = service
16
+
17
+ @chunk_size = chunk_size
18
+ @pipeline_size = pipeline_size
19
+
20
+ @receiving = {}
21
+ end
22
+
23
+ def receive_file(file_size, checksum)
24
+ FileService::ReceivingFile.new(
25
+ file_size, checksum, chunk_size, pipeline_size
26
+ ).tap do |info|
27
+ @receiving[info.file_uuid] = info
28
+ end
29
+ end
30
+
31
+ def receive_message(from_identity, message)
32
+ if current_file = @receiving[message.to]
33
+ current_file.sender_uuid = from_identity
34
+ else
35
+ return
36
+ end
37
+
38
+ case message.body[0]
39
+ when "START"
40
+ Pantry.logger.debug("[Receive File] Received START message #{message.inspect}")
41
+ fill_the_pipeline(current_file, message)
42
+ when "CHUNK"
43
+ Pantry.logger.debug("[Receive File] Received CHUNK message #{message.metadata}")
44
+ process_chunk(current_file, message)
45
+ end
46
+ end
47
+
48
+ protected
49
+
50
+ def fill_the_pipeline(current_file, message)
51
+ current_file.chunks_to_fetch do |offset, size|
52
+ Pantry.logger.debug("[Receive File] Fetching #{offset} x #{size} for #{current_file.file_uuid}")
53
+ send_message(current_file, "FETCH", offset, size)
54
+ end
55
+ end
56
+
57
+ def process_chunk(current_file, message)
58
+ chunk_offset = message[:chunk_offset]
59
+ chunk_size = message[:chunk_size]
60
+ chunk_data = message.body[1]
61
+
62
+ current_file.write_chunk(chunk_offset, chunk_size, chunk_data)
63
+
64
+ if current_file.complete?
65
+ finalize_file(current_file)
66
+ else
67
+ fill_the_pipeline(current_file, message)
68
+ end
69
+ end
70
+
71
+ def finalize_file(current_file)
72
+ if current_file.valid?
73
+ Pantry.logger.debug("[Receive File] File #{current_file.file_uuid} finished")
74
+ send_message(current_file, "FINISH")
75
+ else
76
+ Pantry.logger.debug("[Receive File] File #{current_file.file_uuid} did not upload successfully")
77
+ current_file.remove
78
+ send_message(current_file, "ERROR", "Checksum did not match the uploaded file")
79
+ end
80
+
81
+ current_file.finished!
82
+ @receiving.delete(current_file.file_uuid)
83
+ end
84
+
85
+ def send_message(current_file, *body)
86
+ message = Pantry::Message.new
87
+ message.to = current_file.file_uuid
88
+
89
+ body.each {|part| message << part }
90
+
91
+ @service.send_message(current_file.sender_uuid, message)
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,74 @@
1
+ module Pantry
2
+ module Communication
3
+
4
+ # Chunk file sending tool that implements the protocol as described here
5
+ # http://zguide.zeromq.org/page:all#Transferring-Files
6
+ #
7
+ # As this actor receives chunk requests from the Receiver, it reads that chunk
8
+ # from the given file and sends it along.
9
+ class FileService::SendFile
10
+ include Celluloid
11
+
12
+ def initialize(service)
13
+ @service = service
14
+ @sending = {}
15
+ end
16
+
17
+ def send_file(file_path, receiver_uuid, file_uuid)
18
+ sender_info = FileService::SendingFile.new(file_path, receiver_uuid, file_uuid)
19
+
20
+ @sending[file_uuid] = sender_info
21
+ send_message(sender_info, "START")
22
+
23
+ sender_info
24
+ end
25
+
26
+ def receive_message(from_identity, message)
27
+ current_file_info = @sending[message.to]
28
+ return unless current_file_info
29
+
30
+ case message.body[0]
31
+ when "FETCH"
32
+ Pantry.logger.debug("[Send File] FETCH requested #{message.inspect}")
33
+ fetch_and_return_chunk(current_file_info, message)
34
+ when "FINISH"
35
+ Pantry.logger.debug("[Send File] FINISHED cleaning up for #{message.inspect}")
36
+ clean_up(current_file_info, message)
37
+ when "ERROR"
38
+ Pantry.logger.debug("[Send File] ERROR #{message.inspect}")
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ def fetch_and_return_chunk(current_file, message)
45
+ chunk_offset = message.body[1].to_i
46
+ chunk_size = message.body[2].to_i
47
+
48
+ chunk = current_file.read(chunk_offset, chunk_size)
49
+
50
+ send_message(current_file, ["CHUNK", chunk], chunk_offset: chunk_offset, chunk_size: chunk_size)
51
+ end
52
+
53
+ def clean_up(current_file, message)
54
+ current_file.finished!
55
+ @sending.delete(message.to)
56
+ end
57
+
58
+ def send_message(sender_info, body, metadata = {})
59
+ message = Pantry::Message.new
60
+ message.to = sender_info.file_uuid
61
+
62
+ [body].flatten.each {|part| message << part }
63
+
64
+ metadata.each do |key, value|
65
+ message[key] = value
66
+ end
67
+
68
+ @service.send_message(sender_info.receiver_uuid, message)
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,20 @@
1
+ module Pantry
2
+ module Communication
3
+
4
+ # The PublishSocket handles the Publish side of Pub/Sub using
5
+ # a 0MQ PUB socket. Messages can be published to all listening clients
6
+ # or can be filtered to certain clients using a ClientFilter.
7
+ # See SubscribeSocket for the receiving end.
8
+ class PublishSocket < WritingSocket
9
+
10
+ def build_socket
11
+ Celluloid::ZMQ::PubSocket.new
12
+ end
13
+
14
+ def open_socket(socket)
15
+ socket.bind("tcp://#{host}:#{port}")
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,89 @@
1
+ module Pantry
2
+ module Communication
3
+
4
+ # Base class of all sockets that read messages from ZMQ.
5
+ # Not meant for direct use, please use one of the subclasses for specific
6
+ # functionality.
7
+ class ReadingSocket
8
+ include Celluloid::ZMQ
9
+ finalizer :shutdown
10
+
11
+ attr_reader :host, :port
12
+
13
+ def initialize(host, port, security)
14
+ @host = host
15
+ @port = port
16
+ @listener = nil
17
+ @security = security
18
+ end
19
+
20
+ def add_listener(listener)
21
+ @listener = listener
22
+ end
23
+
24
+ def open
25
+ @socket = build_socket
26
+ Communication.configure_socket(@socket)
27
+ @security.configure_socket(@socket)
28
+ open_socket(@socket)
29
+
30
+ @running = true
31
+ self.async.process_messages
32
+ end
33
+
34
+ def build_socket
35
+ raise "Implement the socket setup."
36
+ end
37
+
38
+ def open_socket(socket)
39
+ raise "Connect / Bind the socket built in #build_socket"
40
+ end
41
+
42
+ def shutdown
43
+ @running = false
44
+ end
45
+
46
+ # Some ZMQ socket types include the source as the first packet of a message.
47
+ # We need to know if the socket in question does this so we can properly
48
+ # build the Message coming in.
49
+ def has_source_header?
50
+ false
51
+ end
52
+
53
+ protected
54
+
55
+ def process_messages
56
+ while @running
57
+ process_next_message
58
+ end
59
+
60
+ @socket.close
61
+ end
62
+
63
+ def process_next_message
64
+ next_message = []
65
+
66
+ # Drop the ZMQ given source packet, it's extraneous for our purposes
67
+ if has_source_header?
68
+ @socket.read
69
+ end
70
+
71
+ next_message << @socket.read
72
+
73
+ while @socket.more_parts?
74
+ next_message << @socket.read
75
+ end
76
+
77
+ async.handle_message(
78
+ SerializeMessage.from_zeromq(next_message)
79
+ )
80
+ end
81
+
82
+ def handle_message(message)
83
+ @listener.handle_message(message) if @listener
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,23 @@
1
+ module Pantry
2
+ module Communication
3
+
4
+ # The ReceiveSocket receives communication from Clients via the
5
+ # Dealer / Router socket pair. This class is the Server's Router side.
6
+ class ReceiveSocket < ReadingSocket
7
+
8
+ def build_socket
9
+ Celluloid::ZMQ::RouterSocket.new
10
+ end
11
+
12
+ def open_socket(socket)
13
+ socket.bind("tcp://#{host}:#{port}")
14
+ end
15
+
16
+ def has_source_header?
17
+ true
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end