vcap_services_base 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/lib/base/abstract.rb +11 -0
  3. data/lib/base/api/message.rb +31 -0
  4. data/lib/base/asynchronous_service_gateway.rb +529 -0
  5. data/lib/base/backup.rb +206 -0
  6. data/lib/base/barrier.rb +54 -0
  7. data/lib/base/base.rb +159 -0
  8. data/lib/base/base_async_gateway.rb +164 -0
  9. data/lib/base/base_job.rb +5 -0
  10. data/lib/base/catalog_manager_base.rb +67 -0
  11. data/lib/base/catalog_manager_v1.rb +225 -0
  12. data/lib/base/catalog_manager_v2.rb +291 -0
  13. data/lib/base/cloud_controller_services.rb +75 -0
  14. data/lib/base/datamapper_l.rb +148 -0
  15. data/lib/base/gateway.rb +167 -0
  16. data/lib/base/gateway_service_catalog.rb +68 -0
  17. data/lib/base/http_handler.rb +101 -0
  18. data/lib/base/job/async_job.rb +71 -0
  19. data/lib/base/job/config.rb +27 -0
  20. data/lib/base/job/lock.rb +153 -0
  21. data/lib/base/job/package.rb +112 -0
  22. data/lib/base/job/serialization.rb +365 -0
  23. data/lib/base/job/snapshot.rb +354 -0
  24. data/lib/base/node.rb +471 -0
  25. data/lib/base/node_bin.rb +154 -0
  26. data/lib/base/plan.rb +63 -0
  27. data/lib/base/provisioner.rb +1120 -0
  28. data/lib/base/provisioner_v1.rb +125 -0
  29. data/lib/base/provisioner_v2.rb +193 -0
  30. data/lib/base/service.rb +93 -0
  31. data/lib/base/service_advertiser.rb +184 -0
  32. data/lib/base/service_error.rb +122 -0
  33. data/lib/base/service_message.rb +94 -0
  34. data/lib/base/service_plan_change_set.rb +11 -0
  35. data/lib/base/simple_aop.rb +63 -0
  36. data/lib/base/snapshot_v2/snapshot.rb +227 -0
  37. data/lib/base/snapshot_v2/snapshot_client.rb +158 -0
  38. data/lib/base/snapshot_v2/snapshot_job.rb +95 -0
  39. data/lib/base/utils.rb +63 -0
  40. data/lib/base/version.rb +7 -0
  41. data/lib/base/warden/instance_utils.rb +161 -0
  42. data/lib/base/warden/node_utils.rb +205 -0
  43. data/lib/base/warden/service.rb +426 -0
  44. data/lib/base/worker_bin.rb +76 -0
  45. data/lib/vcap_services_base.rb +16 -0
  46. metadata +364 -0
@@ -0,0 +1,122 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require "rubygems"
3
+ require "yajl"
4
+
5
+ module VCAP
6
+ module Services
7
+ module Base
8
+ module Error
9
+ class ServiceError < StandardError
10
+ attr_reader :http_status, :error_code, :error_msg
11
+
12
+ # HTTP status code
13
+ HTTP_BAD_REQUEST = 400
14
+ HTTP_NOT_AUTHORIZED = 401
15
+ HTTP_FORBIDDEN = 403
16
+ HTTP_NOT_FOUND = 404
17
+ HTTP_INTERNAL = 500
18
+ HTTP_NOT_IMPLEMENTED = 501
19
+ HTTP_SERVICE_UNAVAIL = 503
20
+ HTTP_GATEWAY_TIMEOUT = 504
21
+
22
+ # Error Code is defined here
23
+ #
24
+ # e.g.
25
+ # ERR_NAME = [err_code, http_status, err_message_template]
26
+ # NOT_FOUND = [30300, HTTP_NOT_FOUND, '%s not found!' ]
27
+
28
+ # 30000 - 30099 400 Bad Request
29
+ INVALID_CONTENT = [30000, HTTP_BAD_REQUEST, 'Invalid Content-Type']
30
+ MALFORMATTED_REQ = [30001, HTTP_BAD_REQUEST, 'Malformatted request']
31
+ UNKNOWN_PLAN_UNIQUE_ID = [30002, HTTP_BAD_REQUEST, 'Unknown unique_id']
32
+ UNKNOWN_PLAN = [30003, HTTP_BAD_REQUEST, 'Unknown plan %s']
33
+ UNSUPPORTED_VERSION = [30004, HTTP_BAD_REQUEST, 'Unsupported version %s']
34
+
35
+ # 30100 - 30199 401 Unauthorized
36
+ NOT_AUTHORIZED = [30100, HTTP_NOT_AUTHORIZED, 'Not authorized']
37
+
38
+ # 30200 - 30299 403 Forbidden
39
+
40
+ # 30300 - 30399 404 Not Found
41
+ NOT_FOUND = [30300, HTTP_NOT_FOUND, '%s not found']
42
+
43
+ # 30500 - 30599 500 Internal Error
44
+ INTERNAL_ERROR = [30500, HTTP_INTERNAL, 'Internal Error']
45
+ EXTENSION_NOT_IMPL = [30501, HTTP_NOT_IMPLEMENTED, "Service extension %s is not implemented."]
46
+ NODE_OPERATION_TIMEOUT = [30502, HTTP_INTERNAL, "Node operation timeout"]
47
+ SERVICE_START_TIMEOUT = [30503, HTTP_INTERNAL, "Service start timeout"]
48
+ WARDEN_RUN_COMMAND_FAILURE = [30504, HTTP_INTERNAL, "Failed to run command %s in warden container %s, exit status: %d, stdout: %s, stderr: %s"]
49
+
50
+ # 30600 - 30699 503 Service Unavailable
51
+ SERVICE_UNAVAILABLE = [30600, HTTP_SERVICE_UNAVAIL, 'Service unavailable']
52
+
53
+ # 30700 - 30799 500 Gateway Timeout
54
+ GATEWAY_TIMEOUT = [30700, HTTP_GATEWAY_TIMEOUT, 'Gateway timeout']
55
+
56
+ # 30800 - 30899 500 Lifecycle error
57
+ OVER_QUOTA = [30800, HTTP_INTERNAL, "Instance %s has %s snapshots. Quota is %s "]
58
+ JOB_QUEUE_TIMEOUT = [30801, HTTP_INTERNAL, "Job timeout after waiting for %s seconds."]
59
+ JOB_TIMEOUT = [30802, HTTP_INTERNAL, "Job is killed since it runs longer than ttl: %s seconds."]
60
+ BAD_SERIALIZED_DATAFILE = [30803, HTTP_INTERNAL, "Invalid serialized data file from: %s"]
61
+ FILESIZE_TOO_LARGE = [30804, HTTP_BAD_REQUEST, "Size of file from url %s is %s, max allowed %s"]
62
+ TOO_MANY_REDIRECTS = [30805, HTTP_BAD_REQUEST, "Too many redirects from url:%s, max redirects allowed is %s"]
63
+ FILE_CORRUPTED = [30806, HTTP_BAD_REQUEST, "Serialized file is corrupted."]
64
+ REDIS_CONCURRENT_UPDATE = [30807, HTTP_INTERNAL, "Server busy, please try again later."]
65
+ INVALID_SNAPSHOT_NAME = [30808, HTTP_BAD_REQUEST, "Invalid snapshot name. %s"]
66
+
67
+
68
+ # 31000 - 32000 Service-specific Error
69
+ # Defined in services directory, for example mongodb/lib/mongodb_service/
70
+
71
+ def initialize(code, *args)
72
+ @http_status = code[1]
73
+ @error_code = code[0]
74
+ @error_msg = sprintf(code[2], *args)
75
+ end
76
+
77
+ def to_s
78
+ "Error Code: #{@error_code}, Error Message: #{@error_msg}"
79
+ end
80
+
81
+ def to_hash
82
+ {
83
+ 'status' => @http_status,
84
+ 'msg' => {
85
+ 'code' => @error_code,
86
+ 'description' => @error_msg,
87
+ 'error' => {
88
+ 'backtrace' => backtrace,
89
+ 'types' => self.class.ancestors.map(&:name) - Object.ancestors.map(&:name)
90
+ }
91
+ }
92
+ }
93
+ end
94
+ end
95
+
96
+
97
+ def success(response = true)
98
+ {'success' => true, 'response' => response}
99
+ end
100
+
101
+ def failure(exception)
102
+ {'success' => false, 'response' => exception.to_hash}
103
+ end
104
+
105
+ def internal_fail()
106
+ e = ServiceError.new(ServiceError::INTERNAL_ERROR)
107
+ failure(e)
108
+ end
109
+
110
+ def timeout_fail()
111
+ e = ServiceError.new(ServiceError::GATEWAY_TIMEOUT)
112
+ failure(e)
113
+ end
114
+
115
+ def parse_msg(msg)
116
+ Yajl::Parser.parse(msg)
117
+ end
118
+
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,94 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ #
3
+ require 'api/message'
4
+
5
+ module VCAP::Services::Internal
6
+
7
+ # Provisioner --> Node
8
+ class ProvisionRequest < ServiceMessage
9
+ required :plan, String
10
+ optional :credentials
11
+ optional :version
12
+ end
13
+
14
+ # Node --> Provisioner
15
+ class ProvisionResponse < ServiceMessage
16
+ required :success
17
+ optional :credentials
18
+ optional :error
19
+ end
20
+
21
+ class UnprovisionRequest < ServiceMessage
22
+ required :name, String
23
+ required :bindings, [Hash]
24
+ end
25
+
26
+ class BindRequest < ServiceMessage
27
+ required :name, String
28
+ optional :bind_opts, Hash
29
+ optional :credentials
30
+ end
31
+
32
+ class BindResponse < ServiceMessage
33
+ required :success
34
+ optional :credentials
35
+ optional :error
36
+ end
37
+
38
+ class UnbindRequest < ServiceMessage
39
+ required :credentials
40
+ end
41
+
42
+ class SimpleResponse < ServiceMessage
43
+ required :success
44
+ optional :error
45
+ end
46
+
47
+ class RestoreRequest < ServiceMessage
48
+ required :instance_id
49
+ required :backup_path
50
+ end
51
+
52
+ class NodeHandlesReport < ServiceMessage
53
+ required :instances_list
54
+ required :bindings_list
55
+ required :node_id
56
+ end
57
+
58
+ class PurgeOrphanRequest < ServiceMessage
59
+ # A list of orphan instances names
60
+ required :orphan_ins_list
61
+ # A list of orphan bindings credentials
62
+ required :orphan_binding_list
63
+ end
64
+
65
+ class ServiceHandle < ServiceMessage
66
+ required :service_id, String
67
+ required :configuration, Hash
68
+ required :credentials, Hash
69
+ optional :dashboard_url, String
70
+ end
71
+
72
+ class ServiceInstanceHandleV2 < ServiceMessage
73
+ required :gateway_data, Hash
74
+ required :credentials, Hash
75
+ optional :name, String
76
+ optional :service_plan_guid, String
77
+ optional :space_guid, String
78
+ optional :service_bindings_url, String
79
+ optional :space_url, String
80
+ optional :service_plan_url, String
81
+ optional :dashboard_url, String
82
+ end
83
+
84
+ class ServiceBindingHandleV2 < ServiceMessage
85
+ required :credentials, Hash
86
+ required :gateway_data, Hash
87
+ required :gateway_name, String
88
+ optional :app_guid, String
89
+ optional :service_instance_guid, String
90
+ optional :binding_options, Hash
91
+ optional :app_url, String
92
+ optional :service_instance_url, String
93
+ end
94
+ end
@@ -0,0 +1,11 @@
1
+ module VCAP::Services
2
+ class ServicePlanChangeSet
3
+ attr_reader :service, :plans_to_add, :plans_to_update, :service_guid
4
+ def initialize(service, service_guid, options = {})
5
+ @service = service
6
+ @service_guid = service_guid
7
+ @plans_to_add = options[:plans_to_add] || []
8
+ @plans_to_update = options[:plans_to_update] || []
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,63 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+ # Simple before filter to instance methods. We can chain filters to multiple methods.
3
+ # Filter method can return nil or raise error to terminater the evoke chain.
4
+ #
5
+ # Example:
6
+ # Class MyClass
7
+ # include Before
8
+ #
9
+ # def f1(msg) puts msg end
10
+ #
11
+ # def f2
12
+ # puts "f2"
13
+ # end
14
+ #
15
+ # def before_filter
16
+ # puts "before"
17
+ #
18
+ # true # return true to proceed
19
+ # end
20
+ #
21
+ # # use before method after methods are defined
22
+ # before [:f1, :f2], :before_filter
23
+ # end
24
+ #
25
+ # c = MyClass.new
26
+ # c.f1("hello")
27
+ # c.f2
28
+ # =>
29
+ # before
30
+ # hello
31
+ # before
32
+ # f2
33
+ module Before
34
+
35
+ def self.included(base)
36
+ base.extend ClassMethods
37
+ end
38
+
39
+ module ClassMethods
40
+ PREFIX = "___".freeze
41
+
42
+ def before(methods, callbacks)
43
+ Array(methods).each do | method|
44
+ method = method.to_sym
45
+ callbacks = Array(callbacks).map{|callback| callback.to_sym}
46
+
47
+ enhance_method(method, callbacks)
48
+ end
49
+ end
50
+
51
+ # enhance single method with callbacks
52
+ def enhance_method(method, callbacks)
53
+ _method = (PREFIX + method.to_s).to_sym
54
+ alias_method _method, method
55
+
56
+ self.send(:define_method, method) do |*args, &blk|
57
+ [callbacks, _method].flatten.each do |callback|
58
+ break unless self.send(callback, *args, &blk)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,227 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require "resque-status"
3
+ require "fileutils"
4
+ require "uuid"
5
+
6
+ require_relative "../service_error"
7
+ require_relative "../../base/job/package.rb"
8
+ require_relative "snapshot_client"
9
+
10
+ module VCAP::Services::Base::SnapshotV2
11
+ # common utils for snapshot job
12
+ class SnapshotJob
13
+ attr_reader :name, :snapshot_id
14
+
15
+ # FIXME: untangle mixins
16
+ # include Snapshot
17
+ include Resque::Plugins::Status
18
+
19
+ class << self
20
+ def queue_lookup_key
21
+ :node_id
22
+ end
23
+
24
+ def select_queue(*args)
25
+ result = nil
26
+ args.each do |arg|
27
+ result = arg[queue_lookup_key]if (arg.is_a? Hash)&& (arg.has_key?(queue_lookup_key))
28
+ end
29
+ @logger = Config.logger
30
+ @logger.info("Select queue #{result} for job #{self.class} with args:#{args.inspect}") if @logger
31
+ result
32
+ end
33
+ end
34
+
35
+ def initialize(*args)
36
+ super(*args)
37
+ parse_config
38
+ init_worker_logger
39
+ Snapshot.redis_connect
40
+ end
41
+
42
+ def fmt_error(e)
43
+ "#{e}: [#{e.backtrace.join(" | ")}]"
44
+ end
45
+
46
+ def init_worker_logger
47
+ @logger = Config.logger
48
+ end
49
+
50
+ def required_options(*args)
51
+ missing_opts = args.select{|arg| !options.has_key? arg.to_s}
52
+ raise ArgumentError, "Missing #{missing_opts.join(', ')} in options: #{options.inspect}" unless missing_opts.empty?
53
+ end
54
+
55
+ def create_lock
56
+ lock_name = "lock:lifecycle:#{name}"
57
+ ttl = @config['job_ttl'] || 600
58
+ lock = Lock.new(lock_name, :logger => @logger, :ttl => ttl)
59
+ lock
60
+ end
61
+
62
+ def get_dump_path(name, snapshot_id)
63
+ snapshot_filepath(@config["snapshots_base_dir"], @config["service_name"], name, snapshot_id)
64
+ end
65
+
66
+ def parse_config
67
+ @config = Yajl::Parser.parse(ENV['WORKER_CONFIG'])
68
+ raise "Need environment variable: WORKER_CONFIG" unless @config
69
+ end
70
+
71
+ def cleanup(name, snapshot_id)
72
+ return unless name && snapshot_id
73
+ @logger.info("Clean up snapshot and files for #{name}, snapshot id: #{snapshot_id}")
74
+ delete_snapshot(name, snapshot_id)
75
+ FileUtils.rm_rf(get_dump_path(name, snapshot_id))
76
+ end
77
+
78
+ def handle_error(e)
79
+ @logger.error("Error in #{self.class} uuid:#{@uuid}: #{fmt_error(e)}")
80
+ err = (e.instance_of?(ServiceError)? e : ServiceError.new(ServiceError::INTERNAL_ERROR)).to_hash
81
+ err_msg = Yajl::Encoder.encode(err["msg"])
82
+ failed(err_msg)
83
+ end
84
+ end
85
+
86
+ class BaseCreateSnapshotJob < SnapshotJob
87
+ # workflow template
88
+ # Sub class should implement execute method which returns hash represents of snapshot like:
89
+ # {:snapshot_id => 1,
90
+ # :size => 100,
91
+ # :files => ["my_snapshot.tgz", "readme.txt"]
92
+ # :manifest => {:version => '1', :service => 'mysql'}
93
+ # }
94
+ def perform
95
+ begin
96
+ required_options :service_id
97
+ @name = options["service_id"]
98
+ @metadata = VCAP.symbolize_keys(options["metadata"])
99
+ @logger.info("Launch job: #{self.class} for #{name} with metadata: #{@metadata}")
100
+
101
+ @snapshot_id = new_snapshot_id
102
+ lock = create_lock
103
+
104
+ @snapshot_files = []
105
+ lock.lock do
106
+ quota = @config["snapshot_quota"]
107
+ if quota
108
+ current = service_snapshots_count(name)
109
+ @logger.debug("Current snapshots count for #{name}: #{current}, max: #{quota}")
110
+ raise ServiceError.new(ServiceError::OVER_QUOTA, name, current, quota) if current >= quota
111
+ end
112
+
113
+ snapshot = execute
114
+ snapshot = VCAP.symbolize_keys snapshot
115
+ snapshot[:manifest] ||= {}
116
+ snapshot[:manifest].merge! @metadata
117
+ @logger.info("Results of create snapshot: #{snapshot.inspect}")
118
+
119
+ # pack snapshot_file into package
120
+ dump_path = get_dump_path(name, snapshot_id)
121
+ FileUtils.mkdir_p(dump_path)
122
+ package_file = "#{snapshot_id}.zip"
123
+
124
+ package = Package.new(File.join(dump_path, package_file))
125
+ package.manifest = snapshot[:manifest]
126
+ files = Array(snapshot[:files])
127
+ raise "No snapshot file to package." if files.empty?
128
+ files.each do |f|
129
+ full_path = File.join(dump_path, f)
130
+ @snapshot_files << full_path
131
+ package.add_files full_path
132
+ end
133
+ package.pack(dump_path)
134
+ @logger.info("Package snapshot file: #{File.join(dump_path, package_file)}")
135
+
136
+ # update snapshot metadata for package file
137
+ snapshot.delete(:files)
138
+ snapshot[:file] = package_file
139
+ snapshot[:date] = fmt_time
140
+ # add default service name
141
+ snapshot[:name] = "Snapshot #{snapshot[:date]}"
142
+
143
+ save_snapshot(name, snapshot)
144
+
145
+ completed(Yajl::Encoder.encode(filter_keys(snapshot)))
146
+ @logger.info("Complete job: #{self.class} for #{name}")
147
+ end
148
+ rescue => e
149
+ cleanup(name, snapshot_id)
150
+ handle_error(e)
151
+ ensure
152
+ set_status({:complete_time => Time.now.to_s})
153
+ @snapshot_files.each{|f| File.delete(f) if File.exists? f} if @snapshot_files
154
+ end
155
+ end
156
+ end
157
+
158
+ class BaseDeleteSnapshotJob < SnapshotJob
159
+ def perform
160
+ begin
161
+ required_options :service_id, :snapshot_id
162
+ @name = options["service_id"]
163
+ @snapshot_id = options["snapshot_id"]
164
+ @logger.info("Launch job: #{self.class} for #{name}")
165
+
166
+ lock = create_lock
167
+
168
+ lock.lock do
169
+ result = execute
170
+ @logger.info("Results of delete snapshot: #{result}")
171
+
172
+ delete_snapshot(name, snapshot_id)
173
+
174
+ completed(Yajl::Encoder.encode({:result => :ok}))
175
+ @logger.info("Complete job: #{self.class} for #{name}")
176
+ end
177
+ rescue => e
178
+ handle_error(e)
179
+ ensure
180
+ set_status({:complete_time => Time.now.to_s})
181
+ end
182
+ end
183
+
184
+ def execute
185
+ cleanup(name, snapshot_id)
186
+ end
187
+ end
188
+
189
+ class BaseRollbackSnapshotJob < SnapshotJob
190
+ attr_reader :manifest, :snapshot_files
191
+ # workflow template
192
+ # Subclass implement execute method which returns true for a successful rollback
193
+ def perform
194
+ begin
195
+ required_options :service_id, :snapshot_id
196
+ @name = options["service_id"]
197
+ @snapshot_id = options["snapshot_id"]
198
+ @logger.info("Launch job: #{self.class} for #{name}")
199
+
200
+ lock = create_lock
201
+
202
+ @snapshot_files = []
203
+ lock.lock do
204
+ # extract origin files from package
205
+ dump_path = get_dump_path(name, snapshot_id)
206
+ package_file = "#{snapshot_id}.zip"
207
+ package = Package.load(File.join(dump_path, package_file))
208
+ @manifest = package.manifest
209
+ @snapshot_files = package.unpack(dump_path)
210
+ @logger.debug("Unpack files from #{package_file}: #{@snapshot_files}")
211
+ raise "Package file doesn't contain snapshot file." if @snapshot_files.empty?
212
+
213
+ result = execute
214
+ @logger.info("Results of rollback snapshot: #{result}")
215
+
216
+ completed(Yajl::Encoder.encode({:result => :ok}))
217
+ @logger.info("Complete job: #{self.class} for #{name}")
218
+ end
219
+ rescue => e
220
+ handle_error(e)
221
+ ensure
222
+ set_status({:complete_time => Time.now.to_s})
223
+ @snapshot_files.each{|f| File.delete(f) if File.exists? f} if @snapshot_files
224
+ end
225
+ end
226
+ end
227
+ end