vcap_services_base 0.2.10

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