ocean-rails 1.14.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +72 -0
- data/Rakefile +38 -0
- data/lib/generators/ocean_scaffold/USAGE +8 -0
- data/lib/generators/ocean_scaffold/ocean_scaffold_generator.rb +76 -0
- data/lib/generators/ocean_scaffold/templates/controller_specs/create_spec.rb +71 -0
- data/lib/generators/ocean_scaffold/templates/controller_specs/delete_spec.rb +47 -0
- data/lib/generators/ocean_scaffold/templates/controller_specs/index_spec.rb +45 -0
- data/lib/generators/ocean_scaffold/templates/controller_specs/show_spec.rb +43 -0
- data/lib/generators/ocean_scaffold/templates/controller_specs/update_spec.rb +85 -0
- data/lib/generators/ocean_scaffold/templates/model_spec.rb +76 -0
- data/lib/generators/ocean_scaffold/templates/resource_routing_spec.rb +27 -0
- data/lib/generators/ocean_scaffold/templates/view_specs/_resource_spec.rb +55 -0
- data/lib/generators/ocean_scaffold/templates/views/_resource.json.jbuilder +8 -0
- data/lib/generators/ocean_setup/USAGE +8 -0
- data/lib/generators/ocean_setup/ocean_setup_generator.rb +93 -0
- data/lib/generators/ocean_setup/templates/Gemfile +19 -0
- data/lib/generators/ocean_setup/templates/alive_controller.rb +18 -0
- data/lib/generators/ocean_setup/templates/alive_routing_spec.rb +11 -0
- data/lib/generators/ocean_setup/templates/alive_spec.rb +12 -0
- data/lib/generators/ocean_setup/templates/api_constants.rb +19 -0
- data/lib/generators/ocean_setup/templates/application_controller.rb +8 -0
- data/lib/generators/ocean_setup/templates/application_helper.rb +34 -0
- data/lib/generators/ocean_setup/templates/config.yml.example +57 -0
- data/lib/generators/ocean_setup/templates/errors_controller.rb +14 -0
- data/lib/generators/ocean_setup/templates/gitignore +37 -0
- data/lib/generators/ocean_setup/templates/hyperlinks.rb +22 -0
- data/lib/generators/ocean_setup/templates/ocean_constants.rb +36 -0
- data/lib/generators/ocean_setup/templates/routes.rb +8 -0
- data/lib/generators/ocean_setup/templates/spec_helper.rb +47 -0
- data/lib/generators/ocean_setup/templates/zeromq_logger.rb +15 -0
- data/lib/ocean-rails.rb +38 -0
- data/lib/ocean/api.rb +263 -0
- data/lib/ocean/api_resource.rb +135 -0
- data/lib/ocean/flooding.rb +29 -0
- data/lib/ocean/ocean_application_controller.rb +214 -0
- data/lib/ocean/ocean_resource_controller.rb +76 -0
- data/lib/ocean/ocean_resource_model.rb +61 -0
- data/lib/ocean/selective_rack_logger.rb +33 -0
- data/lib/ocean/version.rb +3 -0
- data/lib/ocean/zero_log.rb +184 -0
- data/lib/ocean/zeromq_logger.rb +42 -0
- data/lib/tasks/ocean_tasks.rake +4 -0
- data/lib/template.rb +31 -0
- data/lib/templates/rails/scaffold_controller/controller.rb +91 -0
- metadata +267 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
module Ocean
|
2
|
+
module OceanResourceModel
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
# Inheritable invalidation callbacks
|
8
|
+
after_create { |model| model.try(:invalidate, true) }
|
9
|
+
after_update { |model| model.try(:invalidate) }
|
10
|
+
after_touch { |model| model.try(:invalidate) }
|
11
|
+
after_destroy { |model| model.try(:invalidate) }
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
#
|
18
|
+
# The presence of +ocean_resource_model+ in a Rails ActiveRecord model declares
|
19
|
+
# that the model is an Ocean resource. It takes five keyword parameters:
|
20
|
+
#
|
21
|
+
# +index+: defaults to <code>[:name]</code>. Enumerates the model attributes which
|
22
|
+
# may be used for parameter matching and grouping.
|
23
|
+
#
|
24
|
+
# +search+: defaults to +:description+. Names the model attribute used for substring
|
25
|
+
# searches. The attribute must be a string or text attribute.
|
26
|
+
#
|
27
|
+
# +page_size+: defaults to 25. When paginated collections are retrieved, this will
|
28
|
+
# be the default page_size for this resource.
|
29
|
+
#
|
30
|
+
# +invalidate_member+: defaults to +INVALIDATE_MEMBER_DEFAULT+. An array of strings
|
31
|
+
# enumerating the Varnish +BAN+ HTTP request URI suffixes to use to invalidate a
|
32
|
+
# resource member, including any relations derived from it.
|
33
|
+
#
|
34
|
+
# +invalidate_collection+: defaults to +INVALIDATE_COLLECTION_DEFAULT+. An array of strings
|
35
|
+
# enumerating the Varnish +BAN+ HTTP request URI suffixes to use to invalidate a
|
36
|
+
# collection of resources.
|
37
|
+
#
|
38
|
+
def ocean_resource_model(index: [:name],
|
39
|
+
search: :description,
|
40
|
+
page_size: 25,
|
41
|
+
invalidate_member: INVALIDATE_MEMBER_DEFAULT,
|
42
|
+
invalidate_collection: INVALIDATE_COLLECTION_DEFAULT
|
43
|
+
)
|
44
|
+
include ApiResource
|
45
|
+
cattr_accessor :index_only
|
46
|
+
cattr_accessor :index_search_property
|
47
|
+
cattr_accessor :collection_page_size
|
48
|
+
cattr_accessor :varnish_invalidate_member
|
49
|
+
cattr_accessor :varnish_invalidate_collection
|
50
|
+
self.index_only = index
|
51
|
+
self.index_search_property = search
|
52
|
+
self.collection_page_size = page_size
|
53
|
+
self.varnish_invalidate_member = invalidate_member
|
54
|
+
self.varnish_invalidate_collection = invalidate_collection
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
ActiveRecord::Base.send :include, Ocean::OceanResourceModel
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# This custom Rack middleware is used to turn off logging of requests made to
|
3
|
+
# <code>/alive</code> by Varnish every 15 seconds in order to detect
|
4
|
+
# failing instances for failover purposes.
|
5
|
+
#
|
6
|
+
class SelectiveRackLogger < Rails::Rack::Logger
|
7
|
+
|
8
|
+
#
|
9
|
+
# Initialises the selective Rack logger.
|
10
|
+
#
|
11
|
+
def initialize(app, opts = {})
|
12
|
+
@app = app
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Suppresses logging of /alive requests from Varnish.
|
18
|
+
#
|
19
|
+
def call(env)
|
20
|
+
if env['PATH_INFO'] == "/alive"
|
21
|
+
old_level = Rails.logger.level
|
22
|
+
Rails.logger.level = 1234567890 # > 5
|
23
|
+
begin
|
24
|
+
@app.call(env) # returns [..., ..., ...]
|
25
|
+
ensure
|
26
|
+
Rails.logger.level = old_level
|
27
|
+
end
|
28
|
+
else
|
29
|
+
super(env) # returns [..., ..., ...]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'ffi-rzmq'
|
2
|
+
|
3
|
+
|
4
|
+
#
|
5
|
+
# This is the class that provides the interface to ZeroMQ, both for sending log
|
6
|
+
# data to a cluster of log servers, and for receiving it.
|
7
|
+
#
|
8
|
+
# The Rails logger is replaced in production by the initializer zero_mq_logger.rb.
|
9
|
+
#
|
10
|
+
# The worker daemons running on the log machines use the pull_server and
|
11
|
+
# pull_worker methods in this class.
|
12
|
+
#
|
13
|
+
class ZeroLog
|
14
|
+
|
15
|
+
#
|
16
|
+
# This is the ZeroMQ context. We only need one per process, thus we
|
17
|
+
# store it in a class variable.
|
18
|
+
#
|
19
|
+
@@context = nil
|
20
|
+
|
21
|
+
|
22
|
+
#
|
23
|
+
# When the first ZeroLog instance is created, a ZeroMQ Context will be
|
24
|
+
# set up for the use of all threads in the process. A process exit
|
25
|
+
# handler will be set up to terminate the context on exit.
|
26
|
+
#
|
27
|
+
def initialize
|
28
|
+
super
|
29
|
+
unless @@context
|
30
|
+
#puts "Creating context"
|
31
|
+
@@context = ZMQ::Context.new(1)
|
32
|
+
#puts "Registering at_exit context terminator"
|
33
|
+
at_exit { @@context.terminate
|
34
|
+
#puts "Context terminated"
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
#
|
41
|
+
# This method returns the ZeroMQ Context object.
|
42
|
+
#
|
43
|
+
def context
|
44
|
+
@@context
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
#
|
49
|
+
# Register a trap handler for INT, to clean up and close any
|
50
|
+
# sockets we might have opened previously.
|
51
|
+
#
|
52
|
+
def trap_int(*sockets)
|
53
|
+
sockets.each do |s|
|
54
|
+
#puts "Registering at_exit socket closer"
|
55
|
+
at_exit { s.setsockopt(ZMQ::LINGER, 0)
|
56
|
+
s.close
|
57
|
+
#puts "Socket closed"
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# ---------------------------------------------------------------------------
|
64
|
+
#
|
65
|
+
# Log data sender side (ruby, Ruby on Rails)
|
66
|
+
#
|
67
|
+
# ---------------------------------------------------------------------------
|
68
|
+
|
69
|
+
#
|
70
|
+
# This initialises the log data sender. We begin by creating the server and
|
71
|
+
# binding it; this is required when using +inproc+ sockets: bind must precede
|
72
|
+
# any connects. (Normally in ZeroMQ, this is not the case.) The code sets up,
|
73
|
+
# in a new thread, a +SUB+ server which is then connected to a +PUSH+ socket. The
|
74
|
+
# +SUB+ server waits for log data which is then pushed via the +PUSH+ socket to
|
75
|
+
# all available +LOG_HOSTS+. ZeroMQ handles buffering, re-connection and multiplexing
|
76
|
+
# (round-robin) to the +LOG_HOSTS+ +PULL+ servers.
|
77
|
+
#
|
78
|
+
def init_log_data_sender(sub_push="sub_push")
|
79
|
+
# First create the server and let it bind, as this is required
|
80
|
+
# when using inproc: bind must precede any connects.
|
81
|
+
Thread.new(context) do |c|
|
82
|
+
# Set up the server socket
|
83
|
+
#puts "Starting SUB server"
|
84
|
+
subscriber = c.socket(ZMQ::SUB)
|
85
|
+
#puts "Binding to the SUB socket"
|
86
|
+
subscriber.bind("inproc://#{sub_push}")
|
87
|
+
subscriber.setsockopt(ZMQ::SUBSCRIBE, "")
|
88
|
+
# Set up the PUSH socket
|
89
|
+
loggers = context.socket(ZMQ::PUSH)
|
90
|
+
LOG_HOSTS.each do |host|
|
91
|
+
#puts "Connecting to the PULL server #{host}"
|
92
|
+
loggers.connect("tcp://#{host}:10000")
|
93
|
+
end
|
94
|
+
# Connect SUB to PUSH via a queue and block
|
95
|
+
ZMQ::Device.new(ZMQ::QUEUE, subscriber, loggers)
|
96
|
+
# Context has been terminated, close sockets
|
97
|
+
subscriber.setsockopt(ZMQ::LINGER, 0)
|
98
|
+
subscriber.close
|
99
|
+
loggers.setsockopt(ZMQ::LINGER, 0)
|
100
|
+
loggers.close
|
101
|
+
#puts "Closed sockets in other thread"
|
102
|
+
end
|
103
|
+
|
104
|
+
sleep 0.1 # Brute force and primitive, but it works
|
105
|
+
|
106
|
+
# Next create the PUB socket and connect it to the other thread
|
107
|
+
#puts "Creating the PUB socket"
|
108
|
+
$log_publisher = context.socket(ZMQ::PUB)
|
109
|
+
trap_int($log_publisher)
|
110
|
+
#puts "Connecting to the SUB server"
|
111
|
+
$log_publisher.connect("inproc://#{sub_push}")
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
#
|
116
|
+
# This is a combination of a PUB main thread log data sender,
|
117
|
+
# pushing directly to the pull_server without the need for a local
|
118
|
+
# aggregator.
|
119
|
+
#
|
120
|
+
def log(data)
|
121
|
+
init_log_data_sender unless $log_publisher
|
122
|
+
|
123
|
+
# Send the data
|
124
|
+
json = data.to_json
|
125
|
+
#puts "Sending message #{json}"
|
126
|
+
$log_publisher.send_string(json)
|
127
|
+
data
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
# ---------------------------------------------------------------------------
|
132
|
+
#
|
133
|
+
# Log service (aggregation) side
|
134
|
+
#
|
135
|
+
# ---------------------------------------------------------------------------
|
136
|
+
|
137
|
+
#
|
138
|
+
# This is the inter-process ZeroMQ socket to which the Log workers send
|
139
|
+
# their received data.
|
140
|
+
#
|
141
|
+
PUSH_ADDRESS = "ipc://pull_worker.ipc"
|
142
|
+
|
143
|
+
|
144
|
+
#
|
145
|
+
# This is the PULL to PUSH server. It pushes the received data over IPC
|
146
|
+
# round-robin fashion to each PULL log worker on the machine.
|
147
|
+
#
|
148
|
+
def pull_server(address=PUSH_ADDRESS)
|
149
|
+
#puts "Starting PULL server"
|
150
|
+
puller = context.socket(ZMQ::PULL)
|
151
|
+
#puts "Binding to the PULL socket"
|
152
|
+
puller.bind("tcp://*:10000")
|
153
|
+
# Set up the PUSH socket
|
154
|
+
pusher = context.socket(ZMQ::PUSH)
|
155
|
+
#puts "Binding to the PUSH socket"
|
156
|
+
pusher.bind(address)
|
157
|
+
# Trap everything
|
158
|
+
trap_int(puller, pusher)
|
159
|
+
# Connect PULL to PUSH via a queue
|
160
|
+
ZMQ::Device.new(ZMQ::QUEUE, puller, pusher)
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
#
|
165
|
+
# This is the PULL worker. This particular version just prints all
|
166
|
+
# received data to STDOUT. Overload this method for other types of
|
167
|
+
# processing (such as storing received log info in a database).
|
168
|
+
#
|
169
|
+
def pull_worker(address=PUSH_ADDRESS)
|
170
|
+
#puts "Starting PULL worker"
|
171
|
+
puller = context.socket(ZMQ::PULL)
|
172
|
+
#puts "Connect to the PUSH socket"
|
173
|
+
puller.connect(address)
|
174
|
+
trap_int(puller)
|
175
|
+
|
176
|
+
while true do
|
177
|
+
s = ''
|
178
|
+
puller.recv_string(s)
|
179
|
+
puts "Received: #{s}"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
class ZeromqLogger
|
4
|
+
|
5
|
+
attr_accessor :level, :log_tags
|
6
|
+
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
# Get info about our environment
|
11
|
+
@ip = Socket.ip_address_list.detect{|intf| intf.ipv4_private?}.getnameinfo[0]
|
12
|
+
# Set up the logger
|
13
|
+
@logger = ZeroLog.new
|
14
|
+
@logger.init_log_data_sender "/tmp/sub_push_#{Process.pid}"
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def debug?() @level <= 0; end
|
19
|
+
def info?() @level <= 1; end
|
20
|
+
def warn?() @level <= 2; end
|
21
|
+
def error?() @level <= 3; end
|
22
|
+
def fatal?() @level <= 4; end
|
23
|
+
|
24
|
+
|
25
|
+
def add(level, msg, progname)
|
26
|
+
return true if level < @level
|
27
|
+
msg = progname if msg.blank?
|
28
|
+
return true if msg.blank? # Don't send
|
29
|
+
#puts "Adding #{level} #{msg} #{progname}"
|
30
|
+
milliseconds = (Time.now.utc.to_f * 1000).to_i
|
31
|
+
data = { timestamp: milliseconds,
|
32
|
+
ip: @ip,
|
33
|
+
pid: Process.pid,
|
34
|
+
service: APP_NAME,
|
35
|
+
level: level,
|
36
|
+
msg: msg.kind_of?(String) ? msg : msg.inspect
|
37
|
+
}
|
38
|
+
@logger.log data
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/lib/template.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Update the Gemfile
|
2
|
+
gem "ocean-rails", git: "git://github.com/OceanDev/ocean-rails.git"
|
3
|
+
|
4
|
+
# Install a .rvmrc file
|
5
|
+
run "rvm rvmrc create ruby-2.0.0-p195@rails-4.0.0"
|
6
|
+
# Select the ruby and gem bag
|
7
|
+
run "rvm use ruby-2.0.0-p195@rails-4.0.0"
|
8
|
+
# Run bundle install - we need the generators in it now
|
9
|
+
run "bundle install"
|
10
|
+
|
11
|
+
# Set up the application as a SOA service Rails application
|
12
|
+
generate "ocean_setup", app_name
|
13
|
+
|
14
|
+
# Install the required gems and package them with the app
|
15
|
+
run "bundle install"
|
16
|
+
run "bundle package --all"
|
17
|
+
|
18
|
+
# Remove the asset stuff from the application conf file
|
19
|
+
gsub_file "config/application.rb",
|
20
|
+
/ # Enable the asset pipeline.+config\.assets\.version = '1\.0'\s/m, ''
|
21
|
+
|
22
|
+
# Set up SQLite to run tests in memory
|
23
|
+
gsub_file "config/database.yml",
|
24
|
+
/test:\s+adapter: sqlite3\s+database: db\/test.sqlite3/m,
|
25
|
+
'test:
|
26
|
+
adapter: sqlite3
|
27
|
+
database: ":memory:"
|
28
|
+
verbosity: quiet'
|
29
|
+
|
30
|
+
# Get the DBs in order
|
31
|
+
rake "db:migrate"
|
@@ -0,0 +1,91 @@
|
|
1
|
+
<% if namespaced? -%>
|
2
|
+
require_dependency "<%= namespaced_file_path %>/application_controller"
|
3
|
+
|
4
|
+
<% end -%>
|
5
|
+
<% module_namespacing do -%>
|
6
|
+
class <%= controller_class_name %>Controller < ApplicationController
|
7
|
+
|
8
|
+
ocean_resource_controller required_attributes: [:lock_version, :name, :description]
|
9
|
+
|
10
|
+
respond_to :json
|
11
|
+
|
12
|
+
before_action :find_<%= singular_table_name %>, :only => [:show, :update, :destroy]
|
13
|
+
|
14
|
+
|
15
|
+
# GET <%= route_url %>
|
16
|
+
def index
|
17
|
+
expires_in 0, 's-maxage' => 30.minutes
|
18
|
+
if stale?(collection_etag(<%= class_name %>))
|
19
|
+
api_render <%= class_name %>.collection(params)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
# GET <%= route_url %>/1
|
25
|
+
def show
|
26
|
+
expires_in 0, 's-maxage' => 30.minutes
|
27
|
+
if stale?(@<%= singular_table_name %>)
|
28
|
+
api_render @<%= singular_table_name %>
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# POST <%= route_url %>
|
34
|
+
def create
|
35
|
+
@<%= singular_table_name %> = <%= class_name %>.new(filtered_params <%= class_name %>)
|
36
|
+
set_updater(@<%= singular_table_name %>)
|
37
|
+
if @<%= singular_table_name %>.valid?
|
38
|
+
begin
|
39
|
+
@<%= singular_table_name %>.save!
|
40
|
+
rescue ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid,
|
41
|
+
SQLite3::ConstraintException
|
42
|
+
render_api_error 422, "<%= class_name %> already exists"
|
43
|
+
return
|
44
|
+
end
|
45
|
+
api_render @<%= singular_table_name %>, new: true
|
46
|
+
else
|
47
|
+
render_validation_errors @<%= singular_table_name %>
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# PUT <%= route_url %>/1
|
53
|
+
def update
|
54
|
+
if missing_attributes?
|
55
|
+
render_api_error 422, "Missing resource attributes"
|
56
|
+
return
|
57
|
+
end
|
58
|
+
begin
|
59
|
+
@<%= singular_table_name %>.assign_attributes(filtered_params <%= class_name %>)
|
60
|
+
set_updater(@<%= singular_table_name %>)
|
61
|
+
@<%= singular_table_name %>.save
|
62
|
+
rescue ActiveRecord::StaleObjectError
|
63
|
+
render_api_error 409, "Stale <%= class_name %>"
|
64
|
+
return
|
65
|
+
end
|
66
|
+
if @<%= singular_table_name %>.valid?
|
67
|
+
api_render @<%= singular_table_name %>
|
68
|
+
else
|
69
|
+
render_validation_errors(@<%= singular_table_name %>)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# DELETE <%= route_url %>/1
|
75
|
+
def destroy
|
76
|
+
@<%= orm_instance.destroy %>
|
77
|
+
render_head_204
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def find_<%= singular_table_name %>
|
84
|
+
@<%= singular_table_name %> = <%= class_name %>.find_by_id params[:id]
|
85
|
+
return true if @<%= singular_table_name %>
|
86
|
+
render_api_error 404, "<%= class_name %> not found"
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
<% end -%>
|