magellan-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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