Skip to content
This repository was archived by the owner on Jun 2, 2021. It is now read-only.

Commit a93f778

Browse files
authored
v3(services): introduce service route binding endpoint (cloudfoundry#1789)
[#174063444](https://www.pivotaltracker.com/story/show/174063444)
1 parent 1cf6e68 commit a93f778

5 files changed

Lines changed: 289 additions & 0 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
require 'messages/service_route_binding_create_message'
2+
3+
class ServiceRouteBindingsController < ApplicationController
4+
def create
5+
message = parse_request
6+
7+
service_instance = fetch_service_instance(message.service_instance_guid)
8+
route = fetch_route(message.route_guid)
9+
check_space(service_instance, route)
10+
11+
head :not_implemented
12+
end
13+
14+
private
15+
16+
def parse_request
17+
message = ServiceRouteBindingCreateMessage.new(hashed_params[:body])
18+
unprocessable!(message.errors.full_messages) unless message.valid?
19+
message
20+
end
21+
22+
def fetch_service_instance(guid)
23+
service_instance = VCAP::CloudController::ServiceInstance.first(guid: guid)
24+
unless service_instance && can_read_space?(service_instance.space)
25+
unprocessable_service_instance!(guid)
26+
end
27+
service_instance
28+
end
29+
30+
def fetch_route(guid)
31+
route = VCAP::CloudController::Route.first(guid: guid)
32+
unless route && can_read_space?(route.space)
33+
unprocessable_route!(guid)
34+
end
35+
route
36+
end
37+
38+
def check_space(service_instance, route)
39+
space = service_instance.space
40+
space_mismatch! unless space == route.space
41+
unauthorized! unless can_write_space?(space)
42+
end
43+
44+
def can_read_space?(space)
45+
permission_queryer.can_read_from_space?(space.guid, space.organization_guid)
46+
end
47+
48+
def can_write_space?(space)
49+
permission_queryer.can_write_to_space?(space.guid)
50+
end
51+
52+
def unprocessable_service_instance!(guid)
53+
unprocessable!("The service instance could not be found: #{guid}")
54+
end
55+
56+
def unprocessable_route!(guid)
57+
unprocessable!("The route could not be found: #{guid}")
58+
end
59+
60+
def space_mismatch!
61+
unprocessable!('The service instance and the route are in different spaces.')
62+
end
63+
end
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
require 'messages/base_message'
2+
require 'utils/hash_utils'
3+
4+
module VCAP::CloudController
5+
class ServiceRouteBindingCreateMessage < BaseMessage
6+
register_allowed_keys [:relationships]
7+
8+
validates_with NoAdditionalKeysValidator, RelationshipValidator
9+
10+
delegate :route_guid, :service_instance_guid, to: :relationships_message
11+
12+
def relationships_message
13+
@relationships_message ||= Relationships.new(relationships.deep_symbolize_keys)
14+
end
15+
16+
class Relationships < BaseMessage
17+
register_allowed_keys [:service_instance, :route]
18+
19+
def route_guid
20+
HashUtils.dig(route, :data, :guid)
21+
end
22+
23+
def service_instance_guid
24+
HashUtils.dig(service_instance, :data, :guid)
25+
end
26+
27+
validates_with NoAdditionalKeysValidator
28+
29+
validates :service_instance, presence: true, allow_nil: false, to_one_relationship: true
30+
validates :route, presence: true, allow_nil: false, to_one_relationship: true
31+
end
32+
end
33+
end

config/routes.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@
189189
param: :guid,
190190
only: [:show, :index]
191191

192+
# service_route_bindings
193+
resources :service_route_bindings,
194+
param: :guid,
195+
only: [:create]
196+
192197
# service_brokers
193198
get '/service_brokers', to: 'service_brokers#index'
194199
get '/service_brokers/:guid', to: 'service_brokers#show'
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
require 'spec_helper'
2+
require 'request_spec_shared_examples'
3+
4+
RSpec.describe 'v3 service route bindings' do
5+
describe 'POST /v3/service_route_bindings' do
6+
let(:api_call) { ->(user_headers) { post '/v3/service_route_bindings', request.to_json, user_headers } }
7+
8+
let(:service_instance) { VCAP::CloudController::UserProvidedServiceInstance.make(space: space) }
9+
let(:route) { VCAP::CloudController::Route.make(space: space) }
10+
let(:request) do
11+
{
12+
relationships: {
13+
service_instance: {
14+
data: {
15+
guid: service_instance.guid
16+
}
17+
},
18+
route: {
19+
data: {
20+
guid: route.guid
21+
}
22+
}
23+
}
24+
}
25+
end
26+
27+
describe 'permissions' do
28+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS do
29+
let(:expected_codes_and_responses) do
30+
Hash.new(code: 403).tap do |h|
31+
h['admin'] = { code: 501 }
32+
h['space_developer'] = { code: 501 }
33+
34+
h['no_role'] = { code: 422 }
35+
h['org_auditor'] = { code: 422 }
36+
h['org_billing_manager'] = { code: 422 }
37+
end
38+
end
39+
end
40+
end
41+
42+
describe 'errors' do
43+
context 'invalid body' do
44+
let(:request) do
45+
{ foo: 'bar' }
46+
end
47+
48+
it 'fails with a 422 unprocessable' do
49+
api_call.call(space_dev_headers)
50+
51+
expect(last_response).to have_status_code(422)
52+
expect(parsed_response['errors']).to include(
53+
include({
54+
'detail' => "Unknown field(s): 'foo', Relationships 'relationships' is not an object",
55+
'title' => 'CF-UnprocessableEntity',
56+
'code' => 10008,
57+
})
58+
)
59+
end
60+
end
61+
62+
context 'cannot read service instance' do
63+
let(:service_instance) { VCAP::CloudController::UserProvidedServiceInstance.make }
64+
65+
it 'fails with a 422 unprocessable' do
66+
api_call.call(space_dev_headers)
67+
68+
expect(last_response).to have_status_code(422)
69+
expect(parsed_response['errors']).to include(
70+
include({
71+
'detail' => "The service instance could not be found: #{service_instance.guid}",
72+
'title' => 'CF-UnprocessableEntity',
73+
'code' => 10008,
74+
})
75+
)
76+
end
77+
end
78+
79+
context 'cannot read route' do
80+
let(:route) { VCAP::CloudController::Route.make }
81+
82+
it 'fails with a 422 unprocessable' do
83+
api_call.call(space_dev_headers)
84+
85+
expect(last_response).to have_status_code(422)
86+
expect(parsed_response['errors']).to include(
87+
include({
88+
'detail' => "The route could not be found: #{route.guid}",
89+
'title' => 'CF-UnprocessableEntity',
90+
'code' => 10008,
91+
})
92+
)
93+
end
94+
end
95+
96+
context 'route and service instance in different spaces' do
97+
let(:route) { VCAP::CloudController::Route.make }
98+
99+
it 'fails with a 422 unprocessable' do
100+
api_call.call(admin_headers)
101+
102+
expect(last_response).to have_status_code(422)
103+
expect(parsed_response['errors']).to include(
104+
include({
105+
'detail' => 'The service instance and the route are in different spaces.',
106+
'title' => 'CF-UnprocessableEntity',
107+
'code' => 10008,
108+
})
109+
)
110+
end
111+
end
112+
end
113+
end
114+
115+
let(:user) { VCAP::CloudController::User.make }
116+
let(:org) { VCAP::CloudController::Organization.make }
117+
let(:space) { VCAP::CloudController::Space.make(organization: org) }
118+
119+
let(:space_dev_headers) do
120+
org.add_user(user)
121+
space.add_developer(user)
122+
headers_for(user)
123+
end
124+
end
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
require 'lightweight_spec_helper'
2+
require 'messages/service_route_binding_create_message'
3+
4+
module VCAP::CloudController
5+
RSpec.describe ServiceRouteBindingCreateMessage do
6+
let(:body) do
7+
{
8+
relationships: {
9+
service_instance: {
10+
data: { guid: 'service-instance-guid' }
11+
},
12+
route: {
13+
data: { guid: 'route-guid' }
14+
}
15+
}
16+
}
17+
end
18+
19+
let(:message) { described_class.new(body) }
20+
21+
it 'accepts the allowed keys' do
22+
expect(message).to be_valid
23+
expect(message.requested?(:relationships)).to be_truthy
24+
end
25+
26+
it 'builds the right message' do
27+
expect(message.service_instance_guid).to eq('service-instance-guid')
28+
expect(message.route_guid).to eq('route-guid')
29+
end
30+
31+
describe 'validations' do
32+
it 'is invalid when there are unknown keys' do
33+
body[:parameters] = 'foo'
34+
expect(message).to_not be_valid
35+
expect(message.errors.full_messages).to include("Unknown field(s): 'parameters'")
36+
end
37+
38+
describe 'service instance relationship' do
39+
it 'fails when not present' do
40+
body[:relationships][:service_instance] = nil
41+
message.valid?
42+
expect(message).to_not be_valid
43+
expect(message.errors[:relationships]).to include(
44+
"Service instance can't be blank",
45+
/Service instance must be structured like this.*/
46+
)
47+
expect(message.errors[:relationships].count).to eq(2)
48+
end
49+
end
50+
51+
describe 'route relationship' do
52+
it 'fails when not present' do
53+
body[:relationships][:route] = nil
54+
expect(message).to_not be_valid
55+
expect(message.errors[:relationships]).to include(
56+
"Route can't be blank",
57+
/Route must be structured like this.*/,
58+
)
59+
expect(message.errors[:relationships].count).to eq(2)
60+
end
61+
end
62+
end
63+
end
64+
end

0 commit comments

Comments
 (0)