pantry 0.0.0 → 0.1.0

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 (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