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

Commit 8eaf17d

Browse files
author
Derik Evangelista
authored
v3(services): SI usage summary in shared spaces (cloudfoundry#1765)
[finishes #171937145](https://www.pivotaltracker.com/story/show/171937145)
1 parent bced2a9 commit 8eaf17d

8 files changed

Lines changed: 283 additions & 36 deletions

File tree

app/controllers/v3/service_instances_controller.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
require 'presenters/v3/to_many_relationship_presenter'
1212
require 'presenters/v3/paginated_list_presenter'
1313
require 'presenters/v3/service_instance_presenter'
14+
require 'presenters/v3/shared_spaces_usage_summary_presenter'
1415
require 'actions/service_instance_share'
1516
require 'actions/service_instance_unshare'
1617
require 'actions/service_instance_update_managed'
@@ -182,6 +183,13 @@ def relationships_shared_spaces
182183
)
183184
end
184185

186+
def shared_spaces_usage_summary
187+
service_instance = ServiceInstance.first(guid: hashed_params[:guid])
188+
service_instance_not_found! unless service_instance.present? && can_read_space?(service_instance.space)
189+
190+
render status: :ok, json: Presenters::V3::SharedSpacesUsageSummaryPresenter.new(service_instance)
191+
end
192+
185193
def credentials
186194
service_instance = UserProvidedServiceInstance.first(guid: hashed_params[:guid])
187195
service_instance_not_found! unless service_instance && can_read_service_instance?(service_instance)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
require 'presenters/v3/base_presenter'
2+
3+
module VCAP::CloudController
4+
module Presenters
5+
module V3
6+
class SharedSpacesUsageSummaryPresenter < BasePresenter
7+
def to_hash
8+
{
9+
usage_summary: build_usage_summary,
10+
links: build_links
11+
}
12+
end
13+
14+
private
15+
16+
def service_instance
17+
@resource
18+
end
19+
20+
def build_usage_summary
21+
count = Hash.new(0).tap do |h|
22+
service_instance.service_bindings.each do |binding|
23+
h[binding.app.space.guid] += 1
24+
end
25+
end
26+
27+
service_instance.shared_spaces.sort_by(&:id).map do |space|
28+
{
29+
space: { guid: space.guid },
30+
bound_app_count: count[space.guid]
31+
}
32+
end
33+
end
34+
35+
def build_links
36+
{
37+
self: {
38+
href: url_builder.build_url(path: "/v3/service_instances/#{service_instance.guid}/relationships/shared_spaces/usage_summary")
39+
},
40+
shared_spaces: {
41+
href: url_builder.build_url(path: "/v3/service_instances/#{service_instance.guid}/relationships/shared_spaces")
42+
},
43+
service_instance: {
44+
href: url_builder.build_url(path: "/v3/service_instances/#{service_instance.guid}")
45+
}
46+
}
47+
end
48+
end
49+
end
50+
end
51+
end

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@
218218
get '/service_instances', to: 'service_instances_v3#index'
219219
get '/service_instances/:guid', to: 'service_instances_v3#show'
220220
get '/service_instances/:service_instance_guid/relationships/shared_spaces', to: 'service_instances_v3#relationships_shared_spaces'
221+
get '/service_instances/:guid/relationships/shared_spaces/usage_summary', to: 'service_instances_v3#shared_spaces_usage_summary'
221222
get '/service_instances/:guid/credentials', to: 'service_instances_v3#credentials'
222223
get '/service_instances/:guid/parameters', to: 'service_instances_v3#parameters'
223224
post '/service_instances', to: 'service_instances_v3#create'

docs/v3/source/includes/api_resources/_service_instances.erb

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,32 @@
214214
}
215215
<% end %>
216216

217-
217+
<% content_for :shared_spaces_usage_summary do %>
218+
{
219+
"usage_summary": [
220+
{
221+
"space": {
222+
"guid": "68d54d31-9b3a-463b-ba94-e8e4c32edbac"
223+
},
224+
"bound_app_count": 2
225+
},
226+
{
227+
"space": {
228+
"guid": "b19f6525-cbd3-4155-b156-dc0c2a431b4c"
229+
},
230+
"bound_app_count": 0
231+
}
232+
],
233+
"links": {
234+
"self": {
235+
"href": "https://api.example.org/v3/service_instances/bdeg4371-cbd3-4155-b156-dc0c2a431b4c/relationships/shared_spaces/usage_summary"
236+
},
237+
"shared_spaces": {
238+
"href": "https://api.example.org/v3/service_instances/bdeg4371-cbd3-4155-b156-dc0c2a431b4c/relationships/shared_spaces"
239+
},
240+
"service_instance": {
241+
"href": "https://api.example.org/v3/service_instances/bdeg4371-cbd3-4155-b156-dc0c2a431b4c"
242+
}
243+
}
244+
}
245+
<% end %>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
### Get usage summary in shared spaces
2+
3+
```
4+
Example Request
5+
```
6+
7+
```shell
8+
curl "https://api.example.org/v3/service_instances/[guid]/relationships/shared_spaces/usage_summary" \
9+
-X GET \
10+
-H "Authorization: bearer [token]"
11+
```
12+
13+
```
14+
Example Response
15+
```
16+
17+
```http
18+
HTTP/1.1 200 OK
19+
Content-Type: application/json
20+
21+
<%= yield_content :shared_spaces_usage_summary %>
22+
```
23+
24+
This endpoint returns the number of bound apps in spaces where the service instance has been shared to.
25+
26+
#### Definition
27+
`GET /v3/service_instances/:guid/relationships/shared_spaces/usage_summary`
28+
29+
#### Permitted roles (in the service instance's originating space)
30+
|
31+
--- | ---
32+
Admin |
33+
Admin Read-Only |
34+
Global Auditor |
35+
Org Manager |
36+
Space Auditor |
37+
Space Developer |
38+
Space Manager |

docs/v3/source/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ includes:
364364
- experimental_resources/service_instances/credentials
365365
- experimental_resources/service_instances/parameters
366366
- experimental_resources/service_instances/delete
367+
- experimental_resources/service_instances/get_shared_spaces_usage_summary
367368
- experimental_resources/sidecars/header
368369
- experimental_resources/sidecars/object
369370
- experimental_resources/sidecars/create_from_app

spec/request/service_instances_spec.rb

Lines changed: 84 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,90 @@ def check_filtered_instances(*instances)
753753
end
754754
end
755755

756+
describe 'GET /v3/service_instances/:guid/relationships/shared_spaces/usage_summary' do
757+
let(:guid) { instance.guid }
758+
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space) }
759+
let(:space_1) { VCAP::CloudController::Space.make }
760+
let(:space_2) { VCAP::CloudController::Space.make }
761+
let(:space_3) { VCAP::CloudController::Space.make }
762+
let(:url) { "/v3/service_instances/#{guid}/relationships/shared_spaces/usage_summary" }
763+
let(:api_call) { lambda { |user_headers| get url, nil, user_headers } }
764+
let(:bindings_on_space_1) { 1 }
765+
let(:bindings_on_space_2) { 3 }
766+
767+
def create_bindings(instance, space:, count:)
768+
(1..count).each do
769+
VCAP::CloudController::ServiceBinding.make(
770+
app: VCAP::CloudController::AppModel.make(space: space),
771+
service_instance: instance
772+
)
773+
end
774+
end
775+
776+
before do
777+
share_service_instance(instance, space_1)
778+
share_service_instance(instance, space_2)
779+
share_service_instance(instance, space_3)
780+
781+
create_bindings(instance, space: space_1, count: bindings_on_space_1)
782+
create_bindings(instance, space: space_2, count: bindings_on_space_2)
783+
end
784+
785+
context 'permissions' do
786+
let(:response_object) {
787+
{
788+
usage_summary: [
789+
{ space: { guid: space_1.guid }, bound_app_count: bindings_on_space_1 },
790+
{ space: { guid: space_2.guid }, bound_app_count: bindings_on_space_2 },
791+
{ space: { guid: space_3.guid }, bound_app_count: 0 }
792+
],
793+
links: {
794+
self: { href: "#{link_prefix}/v3/service_instances/#{instance.guid}/relationships/shared_spaces/usage_summary" },
795+
shared_spaces: { href: "#{link_prefix}/v3/service_instances/#{instance.guid}/relationships/shared_spaces" },
796+
service_instance: { href: "#{link_prefix}/v3/service_instances/#{instance.guid}" }
797+
}
798+
}
799+
}
800+
let(:usage_summary_response) { { code: 200, response_object: response_object } }
801+
802+
let(:expected_codes_and_responses) do
803+
Hash.new(
804+
code: 404,
805+
response_objects: []
806+
).tap do |h|
807+
h['admin'] = usage_summary_response
808+
h['admin_read_only'] = usage_summary_response
809+
h['global_auditor'] = usage_summary_response
810+
h['space_developer'] = usage_summary_response
811+
h['space_manager'] = usage_summary_response
812+
h['space_auditor'] = usage_summary_response
813+
h['org_manager'] = usage_summary_response
814+
end
815+
end
816+
817+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
818+
end
819+
820+
context 'when the instance does not exist' do
821+
let(:guid) { 'a-fake-guid' }
822+
it 'responds with 404 Not Found' do
823+
api_call.call(admin_headers)
824+
expect(last_response).to have_status_code(404)
825+
end
826+
end
827+
828+
context 'when the user cannot read from the originating space' do
829+
it 'responds with 404 Not Found' do
830+
user = VCAP::CloudController::User.make
831+
set_current_user_as_role(role: 'space_developer', org: space_2.organization, space: space_2, user: user)
832+
833+
api_call.call(headers_for(user))
834+
835+
expect(last_response).to have_status_code(404)
836+
end
837+
end
838+
end
839+
756840
describe 'POST /v3/service_instances' do
757841
let(:api_call) { lambda { |user_headers| post '/v3/service_instances', request_body.to_json, user_headers } }
758842
let(:space_guid) { space.guid }
@@ -3401,41 +3485,6 @@ def enable_sharing!
34013485
let!(:service_instance2) { VCAP::CloudController::ManagedServiceInstance.make(space: space, name: 'redis') }
34023486
let!(:service_instance3) { VCAP::CloudController::ManagedServiceInstance.make(space: another_space, name: 'mysql') }
34033487

3404-
describe 'GET /v3/service_instances/:guid/relationships/shared_spaces' do
3405-
before do
3406-
share_request = {
3407-
'data' => [
3408-
{ 'guid' => target_space.guid }
3409-
]
3410-
}
3411-
3412-
enable_feature_flag!
3413-
post "/v3/service_instances/#{service_instance1.guid}/relationships/shared_spaces", share_request.to_json, admin_header
3414-
expect(last_response.status).to eq(200)
3415-
3416-
disable_feature_flag!
3417-
end
3418-
3419-
it 'returns a list of space guids where the service instance is shared to' do
3420-
set_current_user_as_role(role: 'space_developer', org: space.organization, space: space, user: user)
3421-
3422-
get "/v3/service_instances/#{service_instance1.guid}/relationships/shared_spaces", nil, user_header
3423-
3424-
expect(last_response.status).to eq(200)
3425-
3426-
expected_response = {
3427-
'data' => [
3428-
{ 'guid' => target_space.guid }
3429-
],
3430-
'links' => {
3431-
'self' => { 'href' => "#{link_prefix}/v3/service_instances/#{service_instance1.guid}/relationships/shared_spaces" },
3432-
}
3433-
}
3434-
3435-
expect(parsed_response).to be_a_response_like(expected_response)
3436-
end
3437-
end
3438-
34393488
describe 'POST /v3/service_instances/:guid/relationships/shared_spaces' do
34403489
before do
34413490
VCAP::CloudController::FeatureFlag.make(name: 'service_instance_sharing', enabled: true, error_message: nil)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
require 'spec_helper'
2+
require 'presenters/v3/shared_spaces_usage_summary_presenter'
3+
4+
module VCAP::CloudController::Presenters::V3
5+
RSpec.describe SharedSpacesUsageSummaryPresenter do
6+
let(:presenter) { described_class.new(instance) }
7+
let(:result) { presenter.to_hash.deep_symbolize_keys }
8+
9+
let(:space) { VCAP::CloudController::Space.make }
10+
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space) }
11+
12+
let(:space_1) { VCAP::CloudController::Space.make }
13+
let(:space_2) { VCAP::CloudController::Space.make }
14+
let(:space_3) { VCAP::CloudController::Space.make }
15+
16+
def create_bindings(instance, space:, count:)
17+
(1..count).each do
18+
VCAP::CloudController::ServiceBinding.make(
19+
app: VCAP::CloudController::AppModel.make(space: space),
20+
service_instance: instance
21+
)
22+
end
23+
end
24+
25+
before do
26+
instance.add_shared_space(space_1)
27+
instance.add_shared_space(space_2)
28+
instance.add_shared_space(space_3)
29+
30+
create_bindings(instance, space: space, count: 2)
31+
create_bindings(instance, space: space_1, count: 3)
32+
create_bindings(instance, space: space_2, count: 1)
33+
end
34+
35+
it 'presents the usage summary' do
36+
expect(result).to eq({
37+
usage_summary: [{
38+
space: { guid: space_1.guid },
39+
bound_app_count: 3
40+
}, {
41+
space: { guid: space_2.guid },
42+
bound_app_count: 1
43+
}, {
44+
space: { guid: space_3.guid },
45+
bound_app_count: 0
46+
}],
47+
links: {
48+
self: { href: "#{link_prefix}/v3/service_instances/#{instance.guid}/relationships/shared_spaces/usage_summary" },
49+
shared_spaces: { href: "#{link_prefix}/v3/service_instances/#{instance.guid}/relationships/shared_spaces" },
50+
service_instance: { href: "#{link_prefix}/v3/service_instances/#{instance.guid}" }
51+
}
52+
})
53+
end
54+
55+
context 'when there are no shared spaces' do
56+
let(:another_instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space) }
57+
let(:presenter) { described_class.new(another_instance) }
58+
59+
it 'presents an empty usage summary' do
60+
expect(result).to eq({
61+
usage_summary: [],
62+
links: {
63+
self: { href: "#{link_prefix}/v3/service_instances/#{another_instance.guid}/relationships/shared_spaces/usage_summary" },
64+
shared_spaces: { href: "#{link_prefix}/v3/service_instances/#{another_instance.guid}/relationships/shared_spaces" },
65+
service_instance: { href: "#{link_prefix}/v3/service_instances/#{another_instance.guid}" }
66+
}
67+
})
68+
end
69+
end
70+
end
71+
end

0 commit comments

Comments
 (0)