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,75 @@
1
+ require 'base/service'
2
+ require 'base/plan'
3
+
4
+ module VCAP::Services
5
+ class CloudControllerServices
6
+ def initialize(http_client, headers, logger)
7
+ @http_client = http_client
8
+ @headers = headers
9
+ @logger = logger
10
+ end
11
+
12
+ attr_reader :logger
13
+
14
+ def load_registered_services(service_list_uri)
15
+ logger.debug("Getting services listing from cloud_controller")
16
+ registered_services = []
17
+
18
+ self.each(service_list_uri, "Registered Offerings") do |s|
19
+ entity = s["entity"]
20
+ plans = []
21
+
22
+ logger.debug("Getting service plans for: #{entity["label"]}/#{entity["provider"]}")
23
+ self.each(entity.fetch("service_plans_url"), "Service Plans") do |p|
24
+ plan_entity = p.fetch('entity')
25
+ plan_metadata = p.fetch('metadata')
26
+ plans << Plan.new(
27
+ :unique_id => plan_entity.fetch("unique_id"),
28
+ :guid => plan_metadata.fetch("guid"),
29
+ :name => plan_entity.fetch("name"),
30
+ :description => plan_entity.fetch("description"),
31
+ :free => plan_entity.fetch("free"),
32
+ )
33
+ end
34
+
35
+ registered_services << Service.new(
36
+ 'guid' => s["metadata"]["guid"],
37
+ 'label' => entity["label"],
38
+ 'unique_id' => entity["unique_id"],
39
+ 'description' => entity["description"],
40
+ 'provider' => entity["provider"],
41
+ 'version' => entity['version'],
42
+ 'url' => entity["url"],
43
+ 'info_url' => entity["info_url"],
44
+ 'extra' => entity['extra'],
45
+ 'plans' => plans,
46
+ 'bindable' => entity['bindable']
47
+ )
48
+ end
49
+
50
+ registered_services
51
+ end
52
+
53
+ def each(seed_url, description, &block)
54
+ url = seed_url
55
+ logger.info("Fetching #{description} from: #{seed_url}")
56
+
57
+ while !url.nil? do
58
+ logger.debug("#{self.class.name}: Fetching #{description} from: #{url}")
59
+ @http_client.call(:uri => url,
60
+ :method => "get",
61
+ :head => @headers,
62
+ :need_raise => true) do |http|
63
+ result = nil
64
+ if (200..299) === http.response_header.status
65
+ result = JSON.parse(http.response)
66
+ else
67
+ raise "CCNG Catalog Manager: - Multiple page fetch via: #{url} failed: (#{http.response_header.status}) - #{http.response}"
68
+ end
69
+ result.fetch("resources").each { |r| block.yield r }
70
+ url = result["next_url"]
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,148 @@
1
+ require "fileutils"
2
+ require "monitor"
3
+ require "data_mapper"
4
+
5
+ # Export Monitor's count
6
+ class Monitor
7
+ def count
8
+ @mon_count
9
+ end
10
+ end
11
+
12
+ module DataMapper
13
+
14
+ class GlobalMutex
15
+ def initialize(lockfile)
16
+ @lockfile = lockfile
17
+ @monitor = Monitor.new
18
+ end
19
+
20
+ def synchronize
21
+ @monitor.synchronize do
22
+ File.open(@lockfile, 'r') do |file|
23
+ # Only Lock/Unlock on first entrance of synchronize to avoid
24
+ # deadlock on flock
25
+ file.flock(File::LOCK_EX) if @monitor.count == 1
26
+ begin
27
+ yield
28
+ ensure
29
+ file.flock(File::LOCK_UN) if @monitor.count == 1
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ class << self
37
+ attr_reader :lock
38
+
39
+ # extend DataMapper.setup parameters for a new :lock_file options
40
+ # new setup can be called as following:
41
+ # DataMapper.setup(<name>, <String>, :lock_file => file)
42
+ # DataMapper.setup(<name>, <Addressable::URI>, :lock_file => file)
43
+ # DataMapper.setup(<name>, <other_connection_options, :lock_file => file>)
44
+ alias original_setup setup
45
+ def setup(*args)
46
+ unless @lock
47
+ lock_file = args[1][:lock_file] if args.size == 2 && args[1].kind_of?(Hash)
48
+ lock_file = args[2][:lock_file] if args.size == 3
49
+ lock_file ||= '/var/vcap/sys/run/LOCK'
50
+ initialize_lock_file(lock_file)
51
+ end
52
+ original_setup(*(args[0..1]))
53
+ end
54
+
55
+ def initialize_lock_file(lock_file)
56
+ FileUtils.mkdir_p(File.dirname(lock_file))
57
+ File.open(lock_file, 'w') do |file|
58
+ file.truncate(0)
59
+ end
60
+ @lock = GlobalMutex.new(lock_file)
61
+ end
62
+ end
63
+
64
+ # The following code will overwrite DataMapper's functions, and replace
65
+ # them with a synchronized version of the same function.
66
+ module Resource
67
+ alias original_save save
68
+ alias original_destroy destroy
69
+
70
+ def save
71
+ DataMapper.lock.synchronize do
72
+ original_save
73
+ end
74
+ end
75
+
76
+ def destroy
77
+ DataMapper.lock.synchronize do
78
+ original_destroy
79
+ end
80
+ end
81
+ end
82
+
83
+ module Model
84
+ alias original_get get
85
+ alias original_all all
86
+
87
+ def get(*args)
88
+ DataMapper.lock.synchronize do
89
+ original_get(*args)
90
+ end
91
+ end
92
+
93
+ def all(*args)
94
+ DataMapper.lock.synchronize do
95
+ original_all(*args)
96
+ end
97
+ end
98
+ end
99
+
100
+ class Collection
101
+ alias original_each each
102
+ alias original_at []
103
+ alias original_get get
104
+ alias original_empty? empty?
105
+
106
+ def each(&block)
107
+ instances = []
108
+ DataMapper.lock.synchronize do
109
+ original_each do |instance|
110
+ instances << instance
111
+ end
112
+ end
113
+ instances.each(&block)
114
+ end
115
+
116
+ def [](*args)
117
+ DataMapper.lock.synchronize do
118
+ original_at(*args)
119
+ end
120
+ end
121
+
122
+ def get(*args)
123
+ DataMapper.lock.synchronize do
124
+ original_get(*args)
125
+ end
126
+ end
127
+
128
+ def empty?()
129
+ DataMapper.lock.synchronize do
130
+ original_empty?()
131
+ end
132
+ end
133
+ end
134
+
135
+ # For auto_upgrade!
136
+ module Migrations
137
+ module SingletonMethods
138
+ alias original_repository_execute repository_execute
139
+
140
+ def repository_execute(*args)
141
+ DataMapper.lock.synchronize do
142
+ original_repository_execute(*args)
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ end
@@ -0,0 +1,167 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+
5
+ require 'optparse'
6
+ require 'net/http'
7
+ require 'thin'
8
+ require 'yaml'
9
+ require 'steno'
10
+
11
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', '..')
12
+ require 'vcap/common'
13
+
14
+ $LOAD_PATH.unshift File.dirname(__FILE__)
15
+ require 'asynchronous_service_gateway'
16
+ require 'job/config'
17
+ require 'abstract'
18
+
19
+ module VCAP
20
+ module Services
21
+ module Base
22
+ end
23
+ end
24
+ end
25
+
26
+
27
+ class VCAP::Services::Base::Gateway
28
+
29
+ abstract :default_config_file
30
+ abstract :provisioner_class
31
+
32
+ def parse_config
33
+ config_file = default_config_file
34
+
35
+ OptionParser.new do |opts|
36
+ opts.banner = "Usage: $0 [options]"
37
+ opts.on("-c", "--config [ARG]", "Configuration File") do |opt|
38
+ config_file = opt
39
+ end
40
+ opts.on("-h", "--help", "Help") do
41
+ puts opts
42
+ exit
43
+ end
44
+ end.parse!
45
+
46
+ begin
47
+ @config = parse_gateway_config(config_file)
48
+ rescue => e
49
+ puts "Couldn't read config file: #{e}"
50
+ exit
51
+ end
52
+ end
53
+
54
+ def setup_vcap_logging
55
+ steno_config = Steno::Config.to_config_hash(@config[:logging])
56
+ steno_config[:context] = Steno::Context::FiberLocal.new
57
+ Steno.init(Steno::Config.new(steno_config))
58
+ # Use the current running binary name for logger identity name, since service gateway only has one instance now.
59
+ logger = Steno.logger(File.basename($0))
60
+ @config[:logger] = logger
61
+ end
62
+
63
+ def setup_async_job_config
64
+ resque = @config[:resque]
65
+ if resque
66
+ resque = VCAP.symbolize_keys(resque)
67
+ VCAP::Services::Base::AsyncJob::Config.redis_config = resque
68
+ VCAP::Services::Base::AsyncJob::Config.logger = @config[:logger]
69
+ end
70
+ end
71
+
72
+ def setup_pid
73
+ if @config[:pid]
74
+ pf = VCAP::PidFile.new(@config[:pid])
75
+ pf.unlink_at_exit
76
+ end
77
+ end
78
+
79
+ def start
80
+ parse_config
81
+
82
+ setup_vcap_logging
83
+
84
+ setup_pid
85
+
86
+ setup_async_job_config
87
+
88
+ @config[:host] = VCAP.local_ip(@config[:ip_route])
89
+ @config[:port] ||= VCAP.grab_ephemeral_port
90
+ @config[:service][:label] = "#{@config[:service][:name]}-#{@config[:service][:version]}"
91
+ @config[:service][:url] = "http://#{@config[:host]}:#{@config[:port]}"
92
+ node_timeout = @config[:node_timeout] || 5
93
+
94
+ EM.error_handler do |ex|
95
+ @config[:logger].fatal("#{ex} #{ex.backtrace.join("|")}")
96
+ exit
97
+ end
98
+
99
+ # Go!
100
+ EM.run do
101
+ sp = provisioner_class.new(
102
+ :logger => @config[:logger],
103
+ :index => @config[:index],
104
+ :ip_route => @config[:ip_route],
105
+ :mbus => @config[:mbus],
106
+ :node_timeout => node_timeout,
107
+ :z_interval => @config[:z_interval],
108
+ :max_nats_payload => @config[:max_nats_payload],
109
+ :additional_options => additional_options,
110
+ :status => @config[:status],
111
+ :plan_management => @config[:plan_management],
112
+ :service => @config[:service],
113
+ :download_url_template => @config[:download_url_template],
114
+ :cc_api_version => @config[:cc_api_version] || "v1" ,
115
+ :snapshot_db => @config[:resque],
116
+ )
117
+
118
+ opts = @config.dup
119
+ opts[:provisioner] = sp
120
+ opts[:node_timeout] = node_timeout
121
+ opts[:cloud_controller_uri] = @config[:cloud_controller_uri] || "api.vcap.me"
122
+
123
+ sg = async_gateway_class.new(opts)
124
+
125
+ server = Thin::Server.new(@config[:host], @config[:port], sg)
126
+ if @config[:service][:timeout]
127
+ server.timeout = [@config[:service][:timeout] + 1, Thin::Server::DEFAULT_TIMEOUT].max
128
+ end
129
+ server.start!
130
+ end
131
+ end
132
+
133
+ def async_gateway_class
134
+ VCAP::Services::AsynchronousServiceGateway
135
+ end
136
+
137
+ def parse_gateway_config(config_file)
138
+ config = YAML.load_file(config_file)
139
+ config = VCAP.symbolize_keys(config)
140
+
141
+
142
+ cc_api_version = config[:cc_api_version] || "v1"
143
+
144
+ if cc_api_version == "v1"
145
+ token = config[:token]
146
+ raise "Token missing" unless token
147
+ raise "Token must be a String or Int, #{token.class} given" unless (token.is_a?(Integer) || token.is_a?(String))
148
+ config[:token] = token.to_s
149
+ else
150
+ service_auth_tokens = config[:service_auth_tokens]
151
+ raise "Service auth token missing" unless service_auth_tokens
152
+ raise "Token must be hash of the form: label_provider => token" unless service_auth_tokens.is_a?(Hash)
153
+
154
+ # Each gateway only handles one service, so service_auth_tokens is expected to have just 1 entry
155
+ raise "Unable to manage multiple services" unless service_auth_tokens.size == 1
156
+
157
+ # Used by legacy services for validating incoming request (and temporarily for handle fetch/update v1 api)
158
+ config[:token] = service_auth_tokens.values[0].to_s # For legacy services
159
+ end
160
+
161
+ config
162
+ end
163
+
164
+ def additional_options
165
+ {}
166
+ end
167
+ end
@@ -0,0 +1,68 @@
1
+ module VCAP::Services
2
+ class GatewayServiceCatalog
3
+ attr_reader :service
4
+
5
+ def initialize(services)
6
+ raise ArgumentError.new('a service list is required') unless services and services.is_a?(Array)
7
+ @service = services.fetch(0)
8
+ end
9
+
10
+ def to_hash
11
+ id, version = VCAP::Services::Api::Util.parse_label(service[:label])
12
+ version = service[:version_aliases][:current] if service[:version_aliases][:current]
13
+ provider = service[:provider] || 'core'
14
+
15
+ catalog_key = "#{id}_#{provider}"
16
+
17
+ unique_id = service[:unique_id] ? {"unique_id" => service[:unique_id]} : {}
18
+ catalog = {}
19
+
20
+ plans = service.fetch(:plans)
21
+ unless plans.is_a?(Array)
22
+ plans.each do |name, plan|
23
+ plan[:name] = name
24
+ end
25
+ end
26
+
27
+ catalog[catalog_key] = {
28
+ "id" => id,
29
+ "version" => version,
30
+ "label" => service[:label],
31
+ "url" => service[:url],
32
+ "plans" => plans,
33
+ "cf_plan_id" => service[:cf_plan_id],
34
+ "tags" => service[:tags],
35
+ "active" => true,
36
+ "description" => service[:description],
37
+ "plan_options" => service[:plan_options],
38
+ "acls" => service[:acls],
39
+ "timeout" => service[:timeout],
40
+ "provider" => provider,
41
+ "default_plan" => service[:default_plan],
42
+ "supported_versions" => service[:supported_versions],
43
+ "version_aliases" => service[:version_aliases],
44
+ }.merge(extra).merge(unique_id)
45
+
46
+ return catalog
47
+ end
48
+
49
+ private
50
+
51
+ def extra
52
+ if (service.keys & [:logo_url, :blurb, :provider_name]).empty?
53
+ {}
54
+ else
55
+ {"extra" => Yajl::Encoder.encode(
56
+ "listing" => {
57
+ "imageUrl" => service[:logo_url],
58
+ "blurb" => service[:blurb]
59
+ },
60
+ "provider" => {
61
+ "name" => service[:provider_name]
62
+ }
63
+ )
64
+ }
65
+ end
66
+ end
67
+ end
68
+ end