magellan-rails 0.0.1

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +37 -0
  3. data/Gemfile +7 -0
  4. data/Gemfile.lock +76 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +29 -0
  7. data/Rakefile +7 -0
  8. data/bin/magellan-rails +7 -0
  9. data/gems/magellan-publisher/.gitignore +37 -0
  10. data/gems/magellan-publisher/Gemfile +11 -0
  11. data/gems/magellan-publisher/Gemfile.lock +51 -0
  12. data/gems/magellan-publisher/LICENSE.txt +22 -0
  13. data/gems/magellan-publisher/README.md +29 -0
  14. data/gems/magellan-publisher/Rakefile +14 -0
  15. data/gems/magellan-publisher/lib/magellan/publisher/version.rb +5 -0
  16. data/gems/magellan-publisher/lib/magellan/publisher.rb +37 -0
  17. data/gems/magellan-publisher/magellan-publisher.gemspec +27 -0
  18. data/gems/magellan-publisher/spec/magellan/publisher_spec.rb +39 -0
  19. data/gems/magellan-publisher/spec/spec_helper.rb +8 -0
  20. data/lib/generators/install_generator.rb +20 -0
  21. data/lib/generators/templates/Magellan.yml +115 -0
  22. data/lib/magellan/extentions/rails/engine.rb +28 -0
  23. data/lib/magellan/extentions/rails.rb +2 -0
  24. data/lib/magellan/extentions.rb +2 -0
  25. data/lib/magellan/rails/executor.rb +35 -0
  26. data/lib/magellan/rails/railtie.rb +12 -0
  27. data/lib/magellan/rails/request.rb +111 -0
  28. data/lib/magellan/rails/response.rb +35 -0
  29. data/lib/magellan/rails/version.rb +5 -0
  30. data/lib/magellan/rails.rb +10 -0
  31. data/lib/magellan/subscriber/base.rb +26 -0
  32. data/lib/magellan/subscriber/executor.rb +21 -0
  33. data/lib/magellan/subscriber/mapper.rb +105 -0
  34. data/lib/magellan/subscriber/request.rb +25 -0
  35. data/lib/magellan/subscriber/rspec.rb +7 -0
  36. data/lib/magellan/subscriber/testing/integration.rb +22 -0
  37. data/lib/magellan/subscriber.rb +10 -0
  38. data/lib/magellan/worker/config.rb +33 -0
  39. data/lib/magellan/worker/core.rb +70 -0
  40. data/lib/magellan/worker/executor.rb +38 -0
  41. data/lib/magellan/worker.rb +14 -0
  42. data/lib/magellan.rb +28 -0
  43. data/magellan-rails.gemspec +28 -0
  44. data/spec/magellan/rails/request_spec.rb +103 -0
  45. data/spec/magellan/rails/response_spec.rb +58 -0
  46. data/spec/magellan/subscriber/mapper_spec.rb +264 -0
  47. data/spec/magellan/worker/core_spec.rb +70 -0
  48. data/spec/spec_helper.rb +4 -0
  49. metadata +167 -0
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+
3
+ require "spec_helper"
4
+ require 'bunny'
5
+ require 'bunny_mock'
6
+ require 'json'
7
+
8
+ describe Magellan::Publisher do
9
+
10
+ describe "#publish" do
11
+
12
+ before do
13
+ @topic = "topic/aaa"
14
+ @rabbitmq_topic = "topic.aaa"
15
+ @payload = "Hello World"
16
+ @bunny = BunnyMock.new
17
+ channel = @bunny.create_channel
18
+ @exchange = @bunny.exchange('test_exchange', type: :direct, durable: false, auto_delete: false)
19
+ expect(@exchange).to receive(:publish).with(@payload, routing_key: @rabbitmq_topic)
20
+
21
+ allow(Bunny ).to receive(:new).and_return(@bunny)
22
+ allow(@bunny ).to receive(:create_channel).and_return(channel)
23
+ allow(channel).to receive(:exchange).with('test_exchange', no_declare: true).and_return(@exchange)
24
+
25
+ ENV["MAGELLAN_PUBLISH_EXCHANGE"] = "test_exchange"
26
+ @publisher = Magellan::Publisher.new(host: "127.0.0.1",
27
+ port: 5672,
28
+ vhost: "customer1.sample_project",
29
+ user: "customer1.sample_project",
30
+ pass: "pasw1")
31
+ end
32
+
33
+ it do
34
+ @publisher.publish(@topic, @payload)
35
+ end
36
+
37
+
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ if ENV["COVERAGE"] and not(ENV["COVERAGE"].empty?)
4
+ require "simplecov"
5
+ SimpleCov.start "rails"
6
+ end
7
+
8
+ require 'magellan/publisher'
@@ -0,0 +1,20 @@
1
+ require 'rails/generators/base'
2
+ require 'securerandom'
3
+
4
+ module Magellan
5
+ module Generators
6
+ class InstallGenerator < ::Rails::Generators::Base
7
+ source_root File.expand_path("../templates", __FILE__)
8
+
9
+ desc "Creates config files for Magellan"
10
+
11
+ def copy_magellan_yaml
12
+ template "Magellan.yml", "Magellan.yml"
13
+ end
14
+
15
+ def rails_4?
16
+ ::Rails::VERSION::MAJOR == 4
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,115 @@
1
+ trdb:
2
+ image: redis:2.8.13
3
+ # Dockerfile: https://raw.githubusercontent.com/docker-library/redis/docker-2.8.12/Dockerfile
4
+ after_tasks:
5
+ - name: "setup TD data"
6
+ docker:
7
+ detach: False
8
+ name: magellan-tr-data
9
+ image: groovenauts/magellan-tr-data
10
+ links:
11
+ - trdb:redis
12
+
13
+
14
+ rabbitmq:
15
+ before_tasks:
16
+ - name: "stop RabbitMQ container"
17
+ command: docker stop rabbitmq
18
+ ignore_errors: yes
19
+ - name: "remove RabbitMQ container"
20
+ command: docker rm rabbitmq
21
+ ignore_errors: yes
22
+
23
+ - name: "make RabbitMQ log directory"
24
+ file:
25
+ path: /var/log/rabbitmq
26
+ force: yes
27
+ state: directory
28
+
29
+ - name: "change owner /var/log/rabbitmq to rabbitmq"
30
+ docker:
31
+ name: rabbitmq_dir_owner
32
+ image: tutum/rabbitmq
33
+ volumes:
34
+ - /var/log/rabbitmq:/var/log/rabbitmq
35
+ command: "chown -R rabbitmq:rabbitmq /var/log/rabbitmq"
36
+
37
+ image: tutum/rabbitmq
38
+ # Dockerfile: https://raw.githubusercontent.com/tutumcloud/tutum-docker-rabbitmq/master/Dockerfile
39
+ ports:
40
+ - 5672:5672
41
+ - 15672:15672
42
+ env:
43
+ - RABBITMQ_USER=magellan
44
+ - RABBITMQ_PASS=mypass
45
+ volumes:
46
+ - /var/log/rabbitmq:/var/log/rabbitmq
47
+
48
+ after_tasks:
49
+ - name: "wait to ready rabbitmq"
50
+ wait_for: delay=3 port=5672
51
+ - name: "wait to ready rabbitmq admin"
52
+ wait_for: port=15672
53
+ - name: "setup Queues and Exchanges"
54
+ docker:
55
+ detach: False
56
+ name: magellan-setup_tools
57
+ image: groovenauts/magellan-setup_tools:0.1.1
58
+ links:
59
+ - trdb:redis
60
+ - rabbitmq:rabbitmq
61
+ command: ruby ./rabbitmq customer1.magellan-rails-example 0.0.1 rails transaction-router pswd1 pswd2
62
+
63
+
64
+ tr:
65
+ before_tasks:
66
+ - name: "stop tr container"
67
+ command: docker stop tr
68
+ ignore_errors: yes
69
+ - name: "remove tr container"
70
+ command: docker rm tr
71
+ ignore_errors: yes
72
+
73
+ image: groovenauts/magellan-transaction-router:0.0.1-with_signature
74
+ # Dockerfile: https://raw.githubusercontent.com/tengine/magellan-transaction-router-broadleaf/feature/integration01/Dockerfile?token=18912__eyJzY29wZSI6IlJhd0Jsb2I6dGVuZ2luZS9tYWdlbGxhbi10cmFuc2FjdGlvbi1yb3V0ZXItYnJvYWRsZWFmL2ZlYXR1cmUvaW50ZWdyYXRpb24wMS9Eb2NrZXJmaWxlIiwiZXhwaXJlcyI6MTQwNzIzMDg4MX0%3D--2ca5fb4de69eca8cbc5cda747b8716ed85ce89a2
75
+ ports:
76
+ - 3000:3000
77
+ links:
78
+ - trdb:redis
79
+ - rabbitmq:rabbitmq
80
+ tty: True
81
+ stdin_open: True
82
+ command: /bin/bash ./boot.sh
83
+
84
+
85
+
86
+
87
+ app:
88
+ before_tasks:
89
+ - name: "stop app container"
90
+ command: docker stop app
91
+ ignore_errors: yes
92
+ - name: "remove app container"
93
+ command: docker rm app
94
+ ignore_errors: yes
95
+
96
+ - name: "make application log directory"
97
+ file:
98
+ path: /var/log/application
99
+ force: yes
100
+ state: directory
101
+
102
+ image: groovenauts/magellan-rails-example
103
+ # Dockerfile: "./Dockerfile"
104
+ links:
105
+ - rabbitmq:rabbitmq
106
+ env:
107
+ - VHOST=/customer1.magellan-rails-example
108
+ - REQUEST_QUEUE=customer1.magellan-rails-example.0.0.1.rails
109
+ - RESPONSE_EXCHANGE=customer1.magellan-rails-example.reply
110
+ - RABBITMQ_USER=customer1.magellan-rails-example
111
+ - RABBITMQ_PASS=pswd2
112
+ - SECRET_KEY_BASE={{ app_secret_key_base }}
113
+ volumes:
114
+ - /var/log/application:/usr/src/app/log
115
+ command: bundle exec magellan-rails
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+
3
+ require "rails/engine"
4
+ require "magellan/worker/config"
5
+
6
+ module Magellan
7
+ module Extentions
8
+ module Rails
9
+ module Engine
10
+ def eager_load!
11
+ config = Magellan::Worker.worker.config
12
+ unless config[:http_worker]
13
+ self.config.eager_load_paths -= [::Rails.root.join("app/controllers").to_s]
14
+ end
15
+ unless config[:subscriber_worker]
16
+ self.config.eager_load_paths -= [::Rails.root.join("app/subscribers").to_s]
17
+ end
18
+ super
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ class ::Rails::Engine
26
+ prepend Magellan::Extentions::Rails::Engine
27
+ end
28
+
@@ -0,0 +1,2 @@
1
+
2
+ require "magellan/extentions/rails/engine"
@@ -0,0 +1,2 @@
1
+
2
+ require "magellan/extentions/rails"
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'magellan/rails'
3
+ require "bunny"
4
+
5
+ class Magellan::Rails::Executor
6
+ def initialize(exchange)
7
+ @exchange = exchange
8
+ @app = ::Rails.application
9
+ end
10
+
11
+ def execute(reply_to, correlation_id, delivery_tag, request_message)
12
+ response = Magellan::Rails::Response.new()
13
+
14
+ request = Magellan::Rails::Request.new()
15
+ request.parse_message(request_message)
16
+
17
+ begin
18
+ rack_response = @app.call(request.to_rack_env)
19
+ response.parse_rack_response(rack_response)
20
+ rescue Exception => e
21
+ response.status = '500'
22
+ response.headers = {'Content-Type' => 'text/plain', 'charset' => 'utf-8'}
23
+ response.body = 'Internal Server Error'
24
+ Magellan.logger.error(e)
25
+ ensure
26
+ @exchange.publish(
27
+ response.to_message,
28
+ {
29
+ expiration: request.reply_ttl,
30
+ correlation_id: correlation_id,
31
+ routing_key: reply_to
32
+ })
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ require 'magellan/rails'
2
+ require 'rails'
3
+
4
+ module Magellan::Rails
5
+ class Railtie < ::Rails::Railtie
6
+
7
+ generators do
8
+ Dir[File.expand_path("../../../generators/*_generator.rb", __FILE__)].each{|f| require f }
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,111 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'magellan/rails'
3
+ require 'set'
4
+ require 'base64'
5
+
6
+ class Magellan::Rails::Request
7
+ attr_reader :headers, :options, :body, :body_encoding
8
+
9
+ RACK_HEADER = Set.new ['CONTENT_LENGTH', 'CONTENT_TYPE', 'GATEWAY_INTERFACE',
10
+ 'Path-Info', 'Query-String', "Method", "Server-Name", "Server-Port"]
11
+
12
+ VALID_ENCODING = %w(plain base64)
13
+
14
+ def option(key)
15
+ options[key]
16
+ end
17
+
18
+ def reply_ttl
19
+ # TRからのメッセージにワーカー処理結果の送信時に指定するttlが含まれていない場合, 1秒を設定します
20
+ ttl = options['reply_ttl'] || 1000
21
+ ttl.to_i
22
+ end
23
+
24
+ def parse_message(request_message)
25
+ # @options = request_message['options']
26
+ # TRから送られるメッセージのoptionsの型が本来はHash型なのですが、
27
+ # 配列型になってしまっているため、暫定的に空のHashをセットします
28
+ @options = {}
29
+ @headers = request_message['headers']
30
+ @body_encoding = request_message['body_encoding']
31
+ @body_encoding = (@body_encoding.present? and VALID_ENCODING.include?(@body_encoding)) ? @body_encoding.to_sym : :plain
32
+ @body = decode_body(request_message['body'], @body_encoding)
33
+ end
34
+
35
+ def to_rack_env
36
+ env = Hash.new
37
+
38
+ scripe_name = @headers['Path-Info'][@headers['Path-Info'].rindex('/') + 1..-1]
39
+ query_string = @headers['Query-String']
40
+
41
+ # Rackにクエリーストリングを渡す時は
42
+ query_string.slice!(0) if (query_string[0] == '?')
43
+
44
+ ct = @headers["Content-Type"]
45
+
46
+ env["CONTENT_LENGTH"] = @body.bytesize
47
+ env["CONTENT_TYPE"] = ct.dup if ct
48
+ env["GATEWAY_INTERFACE"] = "CGI/1.1"
49
+ env["PATH_INFO"] = @headers['Path-Info'] # "/hello/index",
50
+ env["QUERY_STRING"] = @headers['Query-String'] #
51
+
52
+ # TRから送られてきていないので一旦コメントアウト
53
+ # env["REMOTE_ADDR"] = @peeraddr[3]
54
+ # env["REMOTE_HOST"] = @peeraddr[2]
55
+ # env["REMOTE_USER"] = @user
56
+
57
+ env["REQUEST_METHOD"] = @headers['Method'] # GET, PUT, DELETE...
58
+ env["REQUEST_URI"] = "#{env["PATH_INFO"]}?#{env["QUERY_STRING"]}"
59
+ env["SCRIPT_NAME"] = '' # /scripts/sample?a=c の場合 ルートなので ''
60
+ env["SERVER_NAME"] = @headers['Server-Name'] # "localhost",
61
+ env["SERVER_PORT"] = @headers['Server-Port'].to_s # "3000",
62
+
63
+ # 一旦決め打ち
64
+ env["SERVER_PROTOCOL"] = "HTTP/1.1"
65
+
66
+ env["SERVER_SOFTWARE"] = 'magellan'
67
+
68
+ @headers.each{|key, val|
69
+ next if RACK_HEADER.include? key
70
+ name = "HTTP_" + key
71
+ name.gsub!(/-/o, "_")
72
+ name.upcase!
73
+ env[name] = val
74
+ }
75
+
76
+ env["rack.version"] = [1, 5, 2]
77
+ env["rack.url_scheme"] = "http"
78
+ env["rack.multithread"] = true
79
+ env["rack.multiprocess"] = false
80
+ env["rack.run_once"] = false
81
+ env["rack.hijack?"] = false
82
+
83
+ env['rack.input'] = StringIO.new(@body.to_s)
84
+
85
+ # 本来はロガーに渡すべきだが、設計するまで一旦標準エラー出力に出します
86
+ env["rack.errors"] = $stderr
87
+
88
+ env["HTTP_VERSION"] = env["SERVER_PROTOCOL"]
89
+ env["REQUEST_PATH"] = env["PATH_INFO"]
90
+ env['rack.logger'] = Magellan.logger
91
+
92
+ env
93
+ end
94
+
95
+
96
+ # @doc レクエストボディをencodingに応じてデコードする。
97
+ # 現在対応しているencodingは以下。
98
+ # - base64: Base64デコードする
99
+ # - plain: デコードせずそのままの値を返す
100
+ #
101
+ # @param [String] body リクエストボディ
102
+ # @param [String] encoding エンコード形式
103
+ def decode_body(body, encoding)
104
+ case encoding.to_sym
105
+ when :base64 then
106
+ Base64.decode64(body)
107
+ else
108
+ body
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'magellan/rails'
3
+
4
+ require 'json'
5
+ require 'base64'
6
+
7
+ class Magellan::Rails::Response
8
+ attr_accessor :headers, :body, :status, :body_encoding
9
+
10
+ def parse_rack_response(response)
11
+ @status = response[0]
12
+ @headers = response[1]
13
+ @body = ''
14
+ @body_encoding = :plain
15
+ body_proxy = response[2]
16
+ body_proxy.each{|b| @body << b}
17
+ body_proxy.close
18
+ end
19
+
20
+ def to_message
21
+ json_generate_errors = [JSON::GeneratorError, Encoding::UndefinedConversionError]
22
+ begin
23
+ {
24
+ headers: @headers,
25
+ status: @status,
26
+ body: @body,
27
+ body_encoding: @body_encoding.to_s,
28
+ }.to_json
29
+ rescue *json_generate_errors
30
+ @body = Base64.strict_encode64(@body)
31
+ @body_encoding = :base64
32
+ to_message
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ module Magellan
2
+ module Rails
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "magellan"
3
+ require 'magellan/rails/railtie'
4
+
5
+ module Magellan::Rails
6
+ autoload :Executor, 'magellan/rails/executor'
7
+ autoload :Request, 'magellan/rails/request'
8
+ autoload :Response, 'magellan/rails/response'
9
+ autoload :VERSION, 'magellan/rails/version'
10
+ end
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'magellan/rails'
3
+
4
+ class Magellan::Subscriber::Base < AbstractController::Base
5
+
6
+ include AbstractController::Callbacks # for (before,after,around)_filter
7
+
8
+ def self.logger=(logger)
9
+ @logger = logger
10
+ end
11
+
12
+ def self.logger
13
+ @logger || ::Rails.logger
14
+ end
15
+
16
+ attr_reader :request, :topic, :body
17
+ def initialize(request)
18
+ @request = request
19
+ @topic = request.topic
20
+ @body = request.body
21
+ end
22
+
23
+ def logger
24
+ Magellan::Subscriber::Base.logger
25
+ end
26
+ end