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,158 @@
1
+ require "redis"
2
+ require "time"
3
+ require_relative "../service_error"
4
+
5
+ module VCAP::Services::Base::SnapshotV2
6
+ class SnapshotClient
7
+ include VCAP::Services::Base::Error
8
+
9
+ SNAPSHOT_KEY_PREFIX = "vcap:snapshotv2".freeze
10
+ SNAPSHOT_ID = "maxid".freeze
11
+ FILTER_KEYS = %w(snapshot_id date size name).freeze
12
+ MAX_NAME_LENGTH = 512
13
+
14
+ def initialize(redis_config)
15
+ @redis = ::Redis.new(redis_config)
16
+ # FIXME: use UUID?
17
+ redis_init
18
+ end
19
+
20
+ def create_empty_snapshot(service_id, name)
21
+ snapshot = {
22
+ 'state' => 'empty',
23
+ 'size' => 0,
24
+ 'name' => name,
25
+ 'snapshot_id' => new_snapshot_id,
26
+ }
27
+ msg = Yajl::Encoder.encode(snapshot)
28
+ client.hset(redis_key(service_id), snapshot['snapshot_id'], msg)
29
+ snapshot
30
+ end
31
+
32
+ # Get all snapshots related to a service instance
33
+ #
34
+ def service_snapshots(service_id)
35
+ return unless service_id
36
+ res = client.hgetall(redis_key(service_id))
37
+ res.values.map{|v| Yajl::Parser.parse(v)}
38
+ end
39
+
40
+ # Return total snapshots count
41
+ #
42
+ def service_snapshots_count(service_id)
43
+ return unless service_id
44
+ client.hlen(redis_key(service_id))
45
+ end
46
+
47
+ # Get detail information for a single snapshot
48
+ #
49
+ def snapshot_details(service_id, snapshot_id)
50
+ return unless service_id && snapshot_id
51
+ res = client.hget(redis_key(service_id), snapshot_id)
52
+ raise ServiceError.new(ServiceError::NOT_FOUND, "snapshot #{snapshot_id}") unless res
53
+ Yajl::Parser.parse(res)
54
+ end
55
+
56
+ # filter internal keys of a given snapshot object, return a new snapshot object in canonical format
57
+ def self.filter_keys(snapshot)
58
+ return unless snapshot.is_a? Hash
59
+ snapshot.select {|k,v| FILTER_KEYS.include? k.to_s}
60
+ end
61
+
62
+ # Generate a new unique id for a snapshot
63
+ def new_snapshot_id
64
+ client.incr(redis_key(SNAPSHOT_ID)).to_s
65
+ end
66
+
67
+ # Get the snapshot file path that service should save the dump file to.
68
+ # the snapshot path structure looks like <base_dir>\snapshots\<service-name>\<aa>\<bb>\<cc>\<aabbcc-rest-of-instance-guid>\snapshot_id\<service specific data>
69
+ def self.snapshot_filepath(base_dir, service_name, service_id, snapshot_id)
70
+ File.join(base_dir, "snapshots", service_name, service_id[0,2], service_id[2,2], service_id[4,2], service_id, snapshot_id.to_s)
71
+ end
72
+
73
+ # Update the name of given snapshot.
74
+ # This function is not protected by redis lock so a optimistic lock
75
+ # is applied to prevent concurrent update.
76
+ #
77
+ def update_name(service_id, snapshot_id, name)
78
+ return unless service_id && snapshot_id && name
79
+ verify_input_name(name)
80
+
81
+ key = self.class.redis_key(service_id)
82
+ # NOTE: idealy should watch on combination of (service_id, snapshot_id)
83
+ # but current design doesn't support such fine-grained watching.
84
+ client.watch(key)
85
+
86
+ snapshot = client.hget(redis_key(service_id), snapshot_id)
87
+ return nil unless snapshot
88
+ snapshot = Yajl::Parser.parse(snapshot)
89
+ snapshot["name"] = name
90
+
91
+ res = client.multi do
92
+ save_snapshot(service_id, snapshot)
93
+ end
94
+
95
+ unless res
96
+ raise ServiceError.new(ServiceError::REDIS_CONCURRENT_UPDATE)
97
+ end
98
+ true
99
+ end
100
+
101
+ def save_snapshot(service_id , snapshot)
102
+ return unless service_id && snapshot
103
+ # FIXME: srsly? where are we using symbols?
104
+ sid = snapshot[:snapshot_id] || snapshot["snapshot_id"]
105
+ return unless sid
106
+ msg = Yajl::Encoder.encode(snapshot)
107
+ client.hset(redis_key(service_id), sid, msg)
108
+ end
109
+
110
+ def delete_snapshot(service_id , snapshot_id)
111
+ return unless service_id && snapshot_id
112
+ client.hdel(redis_key(service_id), snapshot_id)
113
+ end
114
+
115
+
116
+ def self.fmt_time()
117
+ # UTC time in ISO 8601 format.
118
+ Time.now.utc.strftime("%FT%TZ")
119
+ end
120
+
121
+ def self.redis_key(key)
122
+ "#{SNAPSHOT_KEY_PREFIX}:#{key}"
123
+ end
124
+
125
+ private
126
+ def filter_keys(snapshot)
127
+ self.class.filter_keys(snapshot)
128
+ end
129
+
130
+ def snapshot_filepath(base_dir, service_name, service_id, snapshot_id)
131
+ self.class.snapshot_filepath(base_dir, service_name, service_id, snapshot_id)
132
+ end
133
+
134
+ def redis_key(key)
135
+ self.class.redis_key(key)
136
+ end
137
+
138
+ attr_reader :redis
139
+
140
+ # initialize necessary keys
141
+ def redis_init
142
+ @redis.setnx("#{SNAPSHOT_KEY_PREFIX}:#{SNAPSHOT_ID}", 1)
143
+ end
144
+
145
+ def client
146
+ redis
147
+ end
148
+
149
+ def verify_input_name(name)
150
+ return unless name
151
+
152
+ raise ServiceError.new(ServiceError::INVALID_SNAPSHOT_NAME,
153
+ "Input name exceed the max allowed #{MAX_NAME_LENGTH} characters.") if name.size > MAX_NAME_LENGTH
154
+
155
+ #TODO: shall we sanitize the input?
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,95 @@
1
+ require "resque-status"
2
+ require "fileutils"
3
+
4
+ require_relative "../service_error"
5
+ require_relative "snapshot_client"
6
+
7
+
8
+ module VCAP::Services::Base::SnapshotV2
9
+ class MySqlSnapshotJob
10
+ def self.queue_lookup_key
11
+ 'mysql'
12
+ end
13
+ end
14
+
15
+ # common utils for snapshot job
16
+ class SnapshotJob
17
+ include Resque::Plugins::Status
18
+
19
+ class QueueResolver
20
+ extend Forwardable
21
+ def_delegator :@job_class, :queue_lookup_key
22
+
23
+ def initialize(job_class)
24
+ @job_class = job_class
25
+ end
26
+
27
+ def resolve(*args)
28
+ result = nil
29
+ args.each do |arg|
30
+ result = arg[queue_lookup_key] if (arg.is_a? Hash) && (arg.has_key?(queue_lookup_key))
31
+ end
32
+ raise "no queue matched for look up key #{queue_lookup_key} and args #{args}" unless result
33
+ result
34
+ end
35
+ end
36
+
37
+ class << self
38
+ def queue_lookup_key
39
+ :node_id
40
+ end
41
+
42
+ def select_queue(*args)
43
+ QueueResolver.new(self).resolve(*args)
44
+ end
45
+ end
46
+
47
+ def initialize(*args)
48
+ super(*args)
49
+ parse_config
50
+ #client = SnapshotClient.new(Config.redis_config)
51
+ # @logger = Config.logger
52
+ # Snapshot.redis_connect
53
+ end
54
+
55
+ # def fmt_error(e)
56
+ # "#{e}: [#{e.backtrace.join(" | ")}]"
57
+ # end
58
+
59
+ # def required_options(*args)
60
+ # missing_opts = args.select{|arg| !options.has_key? arg.to_s}
61
+ # raise ArgumentError, "Missing #{missing_opts.join(', ')} in options: #{options.inspect}" unless missing_opts.empty?
62
+ # end
63
+
64
+ # def create_lock(lock_name)
65
+ # # lock_name = "lock:lifecycle:#{name}"
66
+ # ttl = @config['job_ttl'] || 600
67
+ # lock = Lock.new(lock_name, :logger => @logger, :ttl => ttl)
68
+ # lock
69
+ # end
70
+
71
+ # def get_dump_path(name, snapshot_id)
72
+ # snapshot_filepath(@config["snapshots_base_dir"], @config["service_name"], name, snapshot_id)
73
+ # end
74
+
75
+ # def cleanup(name, snapshot_id)
76
+ # return unless name && snapshot_id
77
+ # @logger.info("Clean up snapshot and files for #{name}, snapshot id: #{snapshot_id}")
78
+ # client.delete_snapshot(name, snapshot_id)
79
+ # FileUtils.rm_rf(get_dump_path(name, snapshot_id))
80
+ # end
81
+
82
+ # def handle_error(e)
83
+ # @logger.error("Error in #{self.class} uuid:#{@uuid}: #{fmt_error(e)}")
84
+ # err = (e.instance_of?(ServiceError)? e : ServiceError.new(ServiceError::INTERNAL_ERROR)).to_hash
85
+ # err_msg = Yajl::Encoder.encode(err["msg"])
86
+ # failed(err_msg)
87
+ # end
88
+
89
+ def parse_config
90
+ @config = Yajl::Parser.parse(ENV['WORKER_CONFIG'])
91
+ raise "Need environment variable: WORKER_CONFIG" unless @config
92
+ @config
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,63 @@
1
+ require "open3"
2
+
3
+ module VCAP::Services::Base::Utils
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def sh(*args)
11
+ options = args[-1].respond_to?(:to_hash) ? args.pop.to_hash: {}
12
+ options = { :timeout => 5.0, :max => 1024 * 1024, :sudo => true, :block => true }.merge(options)
13
+ arg = options[:sudo] == false ? args[0] : "sudo " << args[0]
14
+
15
+ begin
16
+ stdin, stdout, stderr, status = Open3.popen3(arg)
17
+ pid = status[:pid]
18
+ out_buf = ""
19
+ err_buf = ""
20
+ if options[:block]
21
+ start = Time.now
22
+ # Manually ping the process per second to check whether the process is alive or not
23
+ while (Time.now - start) < options[:timeout] && status.alive?
24
+ begin
25
+ out_buf << stdout.read_nonblock(4096)
26
+ err_buf << stderr.read_nonblock(4096)
27
+ rescue IO::WaitReadable, EOFError
28
+ end
29
+ sleep 0.2
30
+ end
31
+
32
+ if status.alive?
33
+ Process.kill("TERM", pid)
34
+ Process.detach(pid)
35
+ raise RuntimeError, "sh #{args} executed with failure and process with pid #{pid} timed out:\nstdout:\n#{out_buf}\nstderr:\n#{err_buf}"
36
+ end
37
+ exit_status = status.value.exitstatus
38
+ raise RuntimeError, "sh #{args} executed with failure and process with pid #{pid} exited with #{status.value.exitstatus}:\nstdout:\n#{out_buf}\nstderr:\n#{err_buf}" unless exit_status == 0
39
+ exit_status
40
+ else
41
+ # If the work is still not done after timeout, then kill the process and record an erorr log
42
+ Thread.new do
43
+ sleep options[:timeout]
44
+ if status.alive?
45
+ Process.kill("TERM", pid)
46
+ Process.detach(pid)
47
+ logger.error "sh #{args} executed with pid #{pid} timed out" if logger
48
+ else
49
+ logger.error "sh #{args} executed with failure, the exit status is #{status.value.exitstatus}" if status.value.exitstatus != 0 && logger
50
+ end
51
+ end
52
+ return 0
53
+ end
54
+ rescue Errno::EPERM
55
+ raise RuntimeError, "sh #{args} executed with failure and process with pid #{pid} cannot be killed (privilege issue?):\nstdout:\n#{out_buf}\nstderr:\n#{err_buf}"
56
+ rescue Errno::ESRCH
57
+ raise RuntimeError, "sh #{args} executed with failure and process with pid #{pid} does not exist:\nstdout:\n#{out_buf}\nstderr:\n#{err_buf}"
58
+ rescue => e
59
+ raise e
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ module VCAP
2
+ module Services
3
+ module Base
4
+ VERSION = "0.2.10"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,161 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require "warden/client"
3
+ require "warden/protocol"
4
+
5
+ $LOAD_PATH.unshift File.join("..", File.dirname(__FILE__))
6
+ require "base/utils"
7
+ require "base/abstract"
8
+ require "base/service_error"
9
+
10
+ module VCAP
11
+ module Services
12
+ module Base
13
+ module Warden
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ module VCAP::Services::Base::Warden::InstanceUtils
20
+
21
+ def self.included(base)
22
+ base.extend(ClassMethods)
23
+ end
24
+
25
+ module ClassMethods
26
+ def warden_connect
27
+ warden_client = Warden::Client.new(warden_socket_path)
28
+ warden_client.connect
29
+ warden_client
30
+ end
31
+
32
+ def warden_socket_path
33
+ "/tmp/warden.sock"
34
+ end
35
+ end
36
+
37
+ # warden container operation helper
38
+ def container_start(bind_mounts=[])
39
+ warden = self.class.warden_connect
40
+ req = Warden::Protocol::CreateRequest.new
41
+ unless bind_mounts.empty?
42
+ req.bind_mounts = bind_mounts
43
+ end
44
+ rsp = warden.call(req)
45
+ handle = rsp.handle
46
+ handle
47
+ ensure
48
+ warden.disconnect if warden
49
+ end
50
+
51
+ def container_stop(handle, force=true)
52
+ warden = self.class.warden_connect
53
+ req = Warden::Protocol::StopRequest.new
54
+ req.handle = handle
55
+ req.background = !force
56
+ warden.call(req)
57
+ true
58
+ ensure
59
+ warden.disconnect if warden
60
+ end
61
+
62
+ def container_destroy(handle)
63
+ warden = self.class.warden_connect
64
+ req = Warden::Protocol::DestroyRequest.new
65
+ req.handle = handle
66
+ warden.call(req)
67
+ true
68
+ ensure
69
+ warden.disconnect if warden
70
+ end
71
+
72
+ def container_running?(handle)
73
+ handle != "" && container_info(handle) != nil
74
+ end
75
+
76
+ def container_run_command(handle, cmd, is_privileged=false)
77
+ warden = self.class.warden_connect
78
+ req = Warden::Protocol::RunRequest.new
79
+ req.handle = handle
80
+ req.script = cmd
81
+ req.privileged = is_privileged
82
+ res = warden.call(req)
83
+ if res.exit_status == 0
84
+ res
85
+ else
86
+ raise VCAP::Services::Base::Error::ServiceError::new(VCAP::Services::Base::Error::ServiceError::WARDEN_RUN_COMMAND_FAILURE, cmd, handle, res.exit_status, res.stdout, res.stderr)
87
+ end
88
+ ensure
89
+ warden.disconnect if warden
90
+ end
91
+
92
+ def container_spawn_command(handle, cmd, is_privileged=false)
93
+ warden = self.class.warden_connect
94
+ req = Warden::Protocol::SpawnRequest.new
95
+ req.handle = handle
96
+ req.script = cmd
97
+ req.privileged = is_privileged
98
+ res = warden.call(req)
99
+ res
100
+ ensure
101
+ warden.disconnect if warden
102
+ end
103
+
104
+ def container_info(handle)
105
+ warden = self.class.warden_connect
106
+ req = Warden::Protocol::InfoRequest.new
107
+ req.handle = handle
108
+ warden.call(req)
109
+ rescue => e
110
+ nil
111
+ ensure
112
+ warden.disconnect if warden
113
+ end
114
+
115
+ def limit_memory(handle, limit)
116
+ warden = self.class.warden_connect
117
+ req = Warden::Protocol::LimitMemoryRequest.new
118
+ req.handle = handle
119
+ req.limit_in_bytes = limit * 1024 * 1024
120
+ warden.call(req)
121
+ true
122
+ ensure
123
+ warden.disconnect if warden
124
+ end
125
+
126
+ def limit_bandwidth(handle, rate)
127
+ warden = self.class.warden_connect
128
+ req = Warden::Protocol::LimitBandwidthRequest.new
129
+ req.handle = handle
130
+ req.rate = (rate * 1024 * 1024).to_i
131
+ req.burst = (rate * 1 * 1024 * 1024).to_i # Set burst the same size as rate
132
+ warden.call(req)
133
+ true
134
+ ensure
135
+ warden.disconnect if warden
136
+ end
137
+
138
+ def map_port(handle, src_port, dest_port)
139
+ warden = self.class.warden_connect
140
+ req = Warden::Protocol::NetInRequest.new
141
+ req.handle = handle
142
+ req.host_port = src_port
143
+ req.container_port = dest_port
144
+ res = warden.call(req)
145
+ res
146
+ ensure
147
+ warden.disconnect if warden
148
+ end
149
+
150
+ def bind_mount_request(bind_dir)
151
+ bind = Warden::Protocol::CreateRequest::BindMount.new
152
+ bind.src_path = bind_dir[:src]
153
+ bind.dst_path = bind_dir[:dst] || bind_dir[:src]
154
+ if bind_dir[:read_only]
155
+ bind.mode = Warden::Protocol::CreateRequest::BindMount::Mode::RO
156
+ else
157
+ bind.mode = Warden::Protocol::CreateRequest::BindMount::Mode::RW
158
+ end
159
+ bind
160
+ end
161
+ end