vcap_common 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,139 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require 'rubygems'
3
+
4
+ require 'yajl'
5
+
6
+ require 'json_schema'
7
+
8
+ class JsonMessage
9
+ # Base error class that all other JsonMessage related errors should inherit from
10
+ class Error < StandardError
11
+ end
12
+
13
+ # Failed to parse json during +decode+
14
+ class ParseError < Error
15
+ end
16
+
17
+ # One or more field's values didn't match their schema
18
+ class ValidationError < Error
19
+ def initialize(field_errs)
20
+ @field_errs = field_errs
21
+ end
22
+
23
+ def to_s
24
+ err_strs = @field_errs.map{|f, e| "Field: #{f}, Error: #{e}"}
25
+ err_strs.join(', ')
26
+ end
27
+ end
28
+
29
+ class Field
30
+ attr_reader :name, :schema, :required
31
+
32
+ def initialize(name, schema, required=true)
33
+ @name = name
34
+ @schema = schema.is_a?(JsonSchema) ? schema : JsonSchema.new(schema)
35
+ @required = required
36
+ end
37
+ end
38
+
39
+ class << self
40
+ attr_reader :fields
41
+
42
+ def schema(&blk)
43
+ instance_eval &blk
44
+ end
45
+
46
+ def decode(json)
47
+ begin
48
+ dec_json = Yajl::Parser.parse(json)
49
+ rescue => e
50
+ raise ParseError, e.to_s
51
+ end
52
+
53
+ from_decoded_json(dec_json)
54
+ end
55
+
56
+ def from_decoded_json(dec_json)
57
+ raise ParseError, "Decoded JSON cannot be nil" unless dec_json
58
+
59
+ errs = {}
60
+
61
+ # Treat null values as if the keys aren't present. This isn't as strict as one would like,
62
+ # but conforms to typical use cases.
63
+ dec_json.delete_if {|k, v| v == nil}
64
+
65
+ # Collect errors by field
66
+ @fields.each do |name, field|
67
+ err = nil
68
+ name_s = name.to_s
69
+ if dec_json.has_key?(name_s)
70
+ err = field.schema.validate(dec_json[name_s])
71
+ elsif field.required
72
+ err = "Missing field #{name}"
73
+ end
74
+ errs[name] = err if err
75
+ end
76
+
77
+ raise ValidationError.new(errs) unless errs.empty?
78
+
79
+ new(dec_json)
80
+ end
81
+
82
+ def required(field_name, schema=JsonSchema::WILDCARD)
83
+ define_field(field_name, schema, true)
84
+ end
85
+
86
+ def optional(field_name, schema=JsonSchema::WILDCARD)
87
+ define_field(field_name, schema, false)
88
+ end
89
+
90
+ protected
91
+
92
+ def define_field(name, schema, required)
93
+ name = name.to_sym
94
+
95
+ @fields ||= {}
96
+ @fields[name] = Field.new(name, schema, required)
97
+
98
+ define_method name.to_sym do
99
+ @msg[name]
100
+ end
101
+
102
+ define_method "#{name}=".to_sym do |value|
103
+ set_field(name, value)
104
+ end
105
+ end
106
+ end
107
+
108
+ def initialize(fields={})
109
+ @msg = {}
110
+ fields.each {|k, v| set_field(k, v)}
111
+ end
112
+
113
+ def encode
114
+ if self.class.fields
115
+ missing_fields = {}
116
+ self.class.fields.each do |name, field|
117
+ missing_fields[name] = "Missing field #{name}" unless (!field.required || @msg.has_key?(name))
118
+ end
119
+ raise ValidationError.new(missing_fields) unless missing_fields.empty?
120
+ end
121
+
122
+ Yajl::Encoder.encode(@msg)
123
+ end
124
+
125
+ def extract
126
+ @msg.dup.freeze
127
+ end
128
+
129
+ protected
130
+
131
+ def set_field(field, value)
132
+ field = field.to_sym
133
+ raise ValidationError.new({field => "Unknown field #{field}"}) unless self.class.fields.has_key?(field)
134
+
135
+ errs = self.class.fields[field].schema.validate(value)
136
+ raise ValidationError.new({field => errs}) if errs
137
+ @msg[field] = value
138
+ end
139
+ end
@@ -0,0 +1,84 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ # This class provides dead simple declarative validation for decoded json using
3
+ # a fairly intuitive DSL like syntax.
4
+ #
5
+ # For example, the following is a sample schema that exercises all functionality
6
+ #
7
+ # {'foo' => [String], # 'foo' must be a list of strings
8
+ # 'bar' => {'baz' => Fixnum, # 'bar' must be a hash where
9
+ # 'jaz' => /foo/, # 'baz' is a Fixnum, and
10
+ # } # 'jaz' matches the regex /foo/
11
+ # }
12
+ #
13
+ class JsonSchema
14
+ WILDCARD = Object
15
+
16
+ # TODO(mjp): validate that schema is syntatically correct
17
+
18
+ def initialize(schema)
19
+ @schema = schema
20
+ end
21
+
22
+ def validate(json)
23
+ _validate(json, @schema)
24
+ end
25
+
26
+ protected
27
+
28
+ def _validate(json, schema)
29
+ case schema
30
+ when Class
31
+ # Terminal case, type check
32
+ klass = json.class
33
+ if json.is_a? schema
34
+ nil
35
+ else
36
+ "Type mismatch (expected #{schema}, got #{klass})"
37
+ end
38
+
39
+ when Hash
40
+ # Recursive case, check for required params, recursively check them against the supplied schema
41
+ missing_keys = schema.keys.select {|k| !json.has_key?(k)} if json.is_a? Hash
42
+
43
+ if !(json.is_a? Hash)
44
+ "Type mismatch (expected hash, got #{json.class})"
45
+ elsif missing_keys.length > 0
46
+ "Missing params: '#{missing_keys.join(', ')}'"
47
+ else
48
+ errs = nil
49
+ schema.each_key do |k|
50
+ sub_errs = _validate(json[k], schema[k])
51
+ if sub_errs
52
+ errs ||= {}
53
+ errs[k] = sub_errs
54
+ end
55
+ end
56
+ errs
57
+ end
58
+
59
+ when Array
60
+ # Recursive case, check that array isn't empty, recursively check array against supplied schema
61
+ if !(json.is_a? Array)
62
+ "Type mismatch (expected array, got #{json.class}"
63
+ else
64
+ errs = nil
65
+ json.each do |v|
66
+ errs = _validate(v, schema[0])
67
+ break if errs
68
+ end
69
+ errs
70
+ end
71
+
72
+ when Regexp
73
+ if schema.match(json)
74
+ nil
75
+ else
76
+ "Invalid value (doesn't match '#{schema.source})"
77
+ end
78
+
79
+ else
80
+ # Terminal case, value check
81
+ "Value mismatch (expected '#{schema}', got #{json})" unless json == schema
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,43 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require 'eventmachine'
3
+ require 'em-http-request'
4
+ require 'fiber'
5
+
6
+ require 'services/api/const'
7
+
8
+ module VCAP
9
+ module Services
10
+ module Api
11
+ end
12
+ end
13
+ end
14
+
15
+ module VCAP::Services::Api
16
+ class AsyncHttpRequest
17
+ class << self
18
+ def new(url, token, verb, timeout, msg=VCAP::Services::Api::EMPTY_REQUEST)
19
+
20
+ req = {
21
+ :head => {
22
+ VCAP::Services::Api::GATEWAY_TOKEN_HEADER => token,
23
+ 'Content-Type' => 'application/json',
24
+ },
25
+ :body => msg.encode,
26
+ }
27
+ if timeout
28
+ EM::HttpRequest.new(url, :inactivity_timeout => timeout).send(verb.to_sym, req)
29
+ else
30
+ EM::HttpRequest.new(url).send(verb.to_sym, req)
31
+ end
32
+ end
33
+
34
+ def fibered(url, token, verb, timeout, msg=VCAP::Services::Api::EMPTY_REQUEST)
35
+ req = new(url, token, verb, timeout, msg)
36
+ f = Fiber.current
37
+ req.callback { f.resume(req) }
38
+ req.errback { f.resume(req) }
39
+ Fiber.yield
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,107 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require 'net/http'
3
+
4
+ require 'services/api/const'
5
+ require 'services/api/messages'
6
+
7
+ module VCAP
8
+ module Services
9
+ module Api
10
+ end
11
+ end
12
+ end
13
+
14
+ class VCAP::Services::Api::ServiceGatewayClient
15
+
16
+ class UnexpectedResponse < StandardError
17
+ attr_reader :response
18
+
19
+ def initialize(resp)
20
+ @response = resp
21
+ end
22
+ end
23
+
24
+ attr_reader :host, :port, :token
25
+
26
+ def initialize(host, token, port=80)
27
+ @host = host
28
+ @port = port
29
+ @token = token
30
+ @hdrs = {
31
+ 'Content-Type' => 'application/json',
32
+ VCAP::Services::Api::GATEWAY_TOKEN_HEADER => @token
33
+ }
34
+ end
35
+
36
+ def provision(args)
37
+ msg = VCAP::Services::Api::GatewayProvisionRequest.new(args)
38
+ resp = perform_request(Net::HTTP::Post, '/gateway/v1/configurations', msg)
39
+ VCAP::Services::Api::GatewayProvisionResponse.decode(resp.body)
40
+ end
41
+
42
+ def unprovision(args)
43
+ perform_request(Net::HTTP::Delete, "/gateway/v1/configurations/#{args[:service_id]}")
44
+ end
45
+
46
+ def create_snapshot(args)
47
+ resp = perform_request(Net::HTTP::Post, "/gateway/v1/configurations/#{args[:service_id]}/snapshots")
48
+ VCAP::Services::Api::Job.decode(resp.body)
49
+ end
50
+
51
+ def enum_snapshots(args)
52
+ resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/snapshots")
53
+ VCAP::Services::Api::SnapshotList.decode(resp.body)
54
+ end
55
+
56
+ def snapshot_details(args)
57
+ resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}")
58
+ VCAP::Services::Api::Snapshot.decode(resp.body)
59
+ end
60
+
61
+ def rollback_snapshot(args)
62
+ resp = perform_request(Net::HTTP::Put, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}")
63
+ VCAP::Services::Api::Job.decode(resp.body)
64
+ end
65
+
66
+ def serialized_url(args)
67
+ resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url")
68
+ VCAP::Services::Api::Job.decode(resp.body)
69
+ end
70
+
71
+ def import_from_url(args)
72
+ resp = perform_request(Net::HTTP::Put, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url", args[:msg])
73
+ VCAP::Services::Api::Job.decode(resp.body)
74
+ end
75
+
76
+ def import_from_data(args)
77
+ resp = perform_request(Net::HTTP::Put, "/gateway/v1/configurations/#{args[:service_id]}/serialized/data", args[:msg])
78
+ VCAP::Services::Api::Job.decode(resp.body)
79
+ end
80
+
81
+ def job_info(args)
82
+ resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/jobs/#{args[:job_id]}")
83
+ VCAP::Services::Api::Job.decode(resp.body)
84
+ end
85
+
86
+ def bind(args)
87
+ msg = VCAP::Services::Api::GatewayBindRequest.new(args)
88
+ resp = perform_request(Net::HTTP::Post, "/gateway/v1/configurations/#{msg.service_id}/handles", msg)
89
+ VCAP::Services::Api::GatewayBindResponse.decode(resp.body)
90
+ end
91
+
92
+ def unbind(args)
93
+ msg = VCAP::Services::Api::GatewayUnbindRequest.new(args)
94
+ perform_request(Net::HTTP::Delete, "/gateway/v1/configurations/#{msg.service_id}/handles/#{msg.handle_id}", msg)
95
+ end
96
+
97
+ protected
98
+
99
+ def perform_request(klass, path, msg=VCAP::Services::Api::EMPTY_REQUEST)
100
+ req = klass.new(path, initheader=@hdrs)
101
+ req.body = msg.encode
102
+ resp = Net::HTTP.new(@host, @port).start {|http| http.request(req)}
103
+ raise UnexpectedResponse, resp unless resp.is_a? Net::HTTPOK
104
+ resp
105
+ end
106
+
107
+ end
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ module VCAP
3
+ module Services
4
+ module Api
5
+ GATEWAY_TOKEN_HEADER = 'X-VCAP-Service-Token'
6
+ SERVICE_LABEL_REGEX = /^\S+-\S+$/
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,153 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require 'uri'
3
+
4
+ require 'services/api/const'
5
+ require 'json_message'
6
+
7
+ module VCAP
8
+ module Services
9
+ module Api
10
+
11
+ class EmptyRequest < JsonMessage
12
+ end
13
+ EMPTY_REQUEST = EmptyRequest.new.freeze
14
+
15
+ #
16
+ # Tell the CloudController about a service
17
+ # NB: Deleting an offering takes all args in the url
18
+ #
19
+ class ServiceOfferingRequest < JsonMessage
20
+ required :label, SERVICE_LABEL_REGEX
21
+ required :url, URI::regexp(%w(http https))
22
+
23
+ optional :description, String
24
+ optional :info_url, URI::regexp(%w(http https))
25
+ optional :tags, [String]
26
+ optional :plans, [String]
27
+ optional :plan_options
28
+ optional :binding_options
29
+ optional :acls
30
+ optional :active
31
+ optional :timeout, Integer
32
+ end
33
+
34
+ class BrokeredServiceOfferingRequest < JsonMessage
35
+ required :label, SERVICE_LABEL_REGEX
36
+ required :options, [{"name" => String, "credentials" => Hash}]
37
+ optional :description, String
38
+ end
39
+
40
+ class HandleUpdateRequest < JsonMessage
41
+ required :service_id, String
42
+ required :configuration
43
+ required :credentials
44
+ end
45
+
46
+ class ListHandlesResponse < JsonMessage
47
+ required :handles, [::JsonSchema::WILDCARD]
48
+ end
49
+
50
+ class ListBrokeredServicesResponse < JsonMessage
51
+ required :brokered_services, [{"label" => String, "description" => String, "acls" => {"users" => [String], "wildcards" => [String]}}]
52
+ end
53
+
54
+ #
55
+ # Provision a service instance
56
+ # NB: Unprovision takes all args in the url
57
+ #
58
+ class CloudControllerProvisionRequest < JsonMessage
59
+ required :label, SERVICE_LABEL_REGEX
60
+ required :name, String
61
+ required :plan, String
62
+
63
+ optional :plan_option
64
+ end
65
+
66
+ class GatewayProvisionRequest < JsonMessage
67
+ required :label, SERVICE_LABEL_REGEX
68
+ required :name, String
69
+ required :plan, String
70
+ required :email, String
71
+
72
+ optional :plan_option
73
+ end
74
+
75
+ class GatewayProvisionResponse < JsonMessage
76
+ required :service_id, String
77
+ required :data
78
+ required :credentials
79
+ end
80
+
81
+ #
82
+ # Bind a previously provisioned service to an app
83
+ #
84
+ class CloudControllerBindRequest < JsonMessage
85
+ required :service_id, String
86
+ required :app_id, Integer
87
+ required :binding_options
88
+ end
89
+
90
+ class GatewayBindRequest < JsonMessage
91
+ required :service_id, String
92
+ required :label, String
93
+ required :email, String
94
+ required :binding_options
95
+ end
96
+
97
+ class GatewayUnbindRequest < JsonMessage
98
+ required :service_id, String
99
+ required :handle_id, String
100
+ required :binding_options
101
+ end
102
+
103
+ class CloudControllerBindResponse < JsonMessage
104
+ required :label, SERVICE_LABEL_REGEX
105
+ required :binding_token, String
106
+ end
107
+
108
+ class GatewayBindResponse < JsonMessage
109
+ required :service_id, String
110
+ required :configuration
111
+ required :credentials
112
+ end
113
+
114
+ # Bind app_name using binding_token
115
+ class BindExternalRequest < JsonMessage
116
+ required :binding_token, String
117
+ required :app_id, Integer
118
+ end
119
+
120
+ class BindingTokenRequest < JsonMessage
121
+ required :service_id, String
122
+ required :binding_options
123
+ end
124
+
125
+ class Snapshot < JsonMessage
126
+ required :snapshot_id, String
127
+ required :date, String
128
+ required :size, Integer
129
+ end
130
+
131
+ class SnapshotList < JsonMessage
132
+ required :snapshots, [::JsonSchema::WILDCARD]
133
+ end
134
+
135
+ class Job < JsonMessage
136
+ required :job_id, String
137
+ required :status, String
138
+ required :start_time, String
139
+ optional :description, String
140
+ optional :complete_time, String
141
+ optional :result, ::JsonSchema::WILDCARD
142
+ end
143
+
144
+ class SerializedURL < JsonMessage
145
+ required :url, URI::regexp(%w(http https))
146
+ end
147
+
148
+ class SerializedData < JsonMessage
149
+ required :data, String
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ module VCAP
3
+ module Services
4
+ module Api
5
+ end
6
+ end
7
+ end
8
+
9
+ class VCAP::Services::Api::Util
10
+ class << self
11
+ def parse_label(label)
12
+ raise ArgumentError, "Invalid label" unless label.match(/-/)
13
+ name, _, version = label.rpartition(/-/)
14
+ [name, version]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require 'services/api/async_requests'
3
+ require 'services/api/const'
4
+ require 'services/api/messages'
5
+ require 'services/api/clients/service_gateway_client'
6
+ require 'services/api/util'