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