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

Commit b5a5e89

Browse files
sweinstein22monamohebbi
authored andcommitted
**V3** client can assign **router groups** to domains
Router groups are mutually exclusive to internal and org scoped domains. [finishes #169276053]
2 parents f186ac4 + ae9eb0f commit b5a5e89

13 files changed

Lines changed: 350 additions & 31 deletions

File tree

app/actions/domain_create.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ def create(message:, shared_organizations: [])
2020
)
2121
end
2222

23+
domain.router_group_guid = message.router_group_guid
24+
2325
Domain.db.transaction do
2426
domain.save
2527

app/controllers/v3/domains_controller.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ def create
4242
unauthorized! unless permission_queryer.can_write_globally?
4343
end
4444

45+
if message.router_group_guid.present? && router_group_not_found?(message.router_group_guid)
46+
unprocessable!("Router group with guid '#{message.router_group_guid}' not found.")
47+
end
4548
domain = DomainCreate.new.create(message: message, shared_organizations: shared_org_objects)
4649

4750
render status: :created, json: Presenters::V3::DomainPresenter.new(domain, visible_org_guids: permission_queryer.readable_org_guids)
@@ -207,4 +210,8 @@ def domain_not_found!
207210
def find_missing_guid(db_organizations, message_shared_org_guids)
208211
(message_shared_org_guids - db_organizations.map(&:guid)).first
209212
end
213+
214+
def router_group_not_found?(router_group_guid)
215+
!CloudController::DependencyLocator.instance.routing_api_client.router_group(router_group_guid)
216+
end
210217
end

app/messages/domain_create_message.rb

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ class DomainCreateMessage < MetadataBaseMessage
1414
register_allowed_keys [
1515
:name,
1616
:internal,
17-
:relationships
17+
:relationships,
18+
:router_group
1819
]
1920

2021
def self.relationships_requested?
@@ -50,7 +51,9 @@ def self.relationships_requested?
5051

5152
validate :alpha_numeric
5253

53-
validate :mutually_exclusive_organization_and_internal
54+
validate :router_group_validation
55+
56+
validate :mutually_exclusive_fields
5457

5558
validates :internal,
5659
allow_nil: true,
@@ -64,6 +67,10 @@ def relationships_message
6467
@relationships_message ||= Relationships.new(relationships&.deep_symbolize_keys)
6568
end
6669

70+
def router_group_guid
71+
HashUtils.dig(router_group, :guid)
72+
end
73+
6774
private
6875

6976
def alpha_numeric
@@ -72,10 +79,28 @@ def alpha_numeric
7279
end
7380
end
7481

75-
def mutually_exclusive_organization_and_internal
82+
def mutually_exclusive_fields
7683
if requested?(:internal) && internal == true && requested?(:relationships)
7784
errors.add(:base, 'Cannot associate an internal domain with an organization')
7885
end
86+
if requested?(:internal) && internal == true && requested?(:router_group)
87+
errors.add(:base, 'Internal domains cannot be associated to a router group.')
88+
end
89+
if requested?(:relationships) && requested?(:router_group)
90+
errors.add(:base, 'Domains scoped to an organization cannot be associated to a router group.')
91+
end
92+
end
93+
94+
def router_group_validation
95+
return if router_group.nil?
96+
return errors.add(:router_group, 'must be an object') unless router_group.is_a?(Hash)
97+
98+
extra_keys = router_group.keys - [:guid]
99+
if extra_keys.any?
100+
errors.add(:router_group, "Unknown field(s): '#{extra_keys.join("', '")}'")
101+
end
102+
103+
errors.add(:router_group, 'guid must be a string') unless router_group_guid.is_a?(String)
79104
end
80105

81106
class Relationships < BaseMessage

app/presenters/v3/domain_presenter.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def to_hash
2323
updated_at: domain.updated_at,
2424
name: domain.name,
2525
internal: domain.internal,
26+
router_group: hashified_router_group(domain.router_group_guid),
2627
metadata: {
2728
labels: hashified_labels(domain.labels),
2829
annotations: hashified_annotations(domain.annotations),
@@ -57,6 +58,10 @@ def domain
5758
@resource
5859
end
5960

61+
def hashified_router_group(router_group_guid)
62+
router_group_guid ? { guid: router_group_guid } : nil
63+
end
64+
6065
def build_links
6166
url_builder = VCAP::CloudController::Presenters::ApiUrlBuilder.new
6267
links = {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"updated_at": "2019-03-08T01:06:19Z",
66
"name": "test-domain.com",
77
"internal": false,
8+
"router_group": null,
89
"metadata": {
910
"labels": <%= metadata.fetch(:labels, {}).to_json(space: ' ', object_nl: ' ')%>,
1011
"annotations": <%= metadata.fetch(:annotations, {}).to_json(space: ' ', object_nl: ' ')%>
@@ -60,6 +61,7 @@
6061
"updated_at": "2019-03-08T01:06:19Z",
6162
"name": "test-domain.com",
6263
"internal": false,
64+
"router_group": { "guid": "5806148f-cce6-4d86-7fbd-aa269e3f6f3f" },
6365
"metadata": {
6466
"labels": {},
6567
"annotations": {}
@@ -80,7 +82,7 @@
8082
"href": "https://api.example.org/v3/domains/3a5d3d89-3f89-4f05-8188-8a2b298c79d5/route_reservations"
8183
}
8284
}
83-
}
85+
},
8486
]
8587
}
8688
<% end %>

docs/v3/source/includes/resources/domains/_create.md.erb

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ Name | Type | Description
3737

3838
#### Optional parameters
3939

40-
| Name | Type | Description | Default |
41-
| ----------- | -------- | ------------------------------------------------------------------------ | ------- |
42-
| **internal** | _boolean_ | Whether the domain is used for internal (container-to-container) traffic. | false |
43-
| **organization** | [_to-one relationship_](#to-one-relationships) | A relationship to the organization the domain will be scoped to. <br>_Note: can not be used when `internal` is set to `true`_ | |
44-
| **shared_organizations** | [_to-many relationship_](#to-many-relationships) | A relationship to organizations the domain will be shared with. <br>_Note: can not be used without an organization relationship_ | |
45-
| **metadata.labels** | [_label object_](#labels) | Labels applied to the domain. | |
46-
| **metadata.annotations** | [_annotation object_](#annotations) | Annotations applied to the domain. | |
40+
| Name | Type | Description | Default |
41+
| ----------- | -------- | ------------------------------------------------------------------------ | ------- |
42+
| **internal** | _boolean_ | Whether the domain is used for internal (container-to-container) traffic. | false |
43+
| **router_group.guid** | _uuid_ | The desired router group guid. <br>_Note: cannot be used when `internal` is set to `true` or domain is scoped to an org._ | null |
44+
| **organization** | [_to-one relationship_](#to-one-relationships) | A relationship to the organization the domain will be scoped to. <br>_Note: cannot be used when `internal` is set to `true` or domain is associated with a router group._ | |
45+
| **shared_organizations** | [_to-many relationship_](#to-many-relationships) | A relationship to organizations the domain will be shared with. <br>_Note: cannot be used without an organization relationship_ | |
46+
| **metadata.labels** | [_label object_](#labels) | Labels applied to the domain. | |
47+
| **metadata.annotations** | [_annotation object_](#annotations) | Annotations applied to the domain. | |
4748

4849
#### Permitted roles
4950

docs/v3/source/includes/resources/domains/_object.md.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Example Domain object
1515
| **updated_at** | _datetime_ | The time with zone when the object was last updated. |
1616
| **name** | _string_ | The name of the domain.<br>Must be between 3 ~ 253 characters and follow [RFC 1035](https://tools.ietf.org/html/rfc1035). |
1717
| **internal** | _boolean_ | Whether the domain is used for internal (container-to-container) traffic. |
18+
| **router_group.guid** | _uuid_ | The guid of the desired router group to route TCP traffic through. If set, the domain will only be available for TCP traffic. |
1819
| **organization** | [_to-one relationship_](#to-one-relationships) | The organization the domain is scoped to. If set, the domain will only be available in that organization. Otherwise, the domain will be globally available. |
1920
| **shared_organizations** | [_to-many relationship_](#to-many-relationships) | Organizations the domain is shared with. If set, the domain will be available in these organizations in addition to the organization the domain is scoped to. |
2021
| **links** | [_links object_](#links) | Links to related resources. |

spec/request/domains_spec.rb

Lines changed: 104 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@
77
let(:org) { space.organization }
88
let(:admin_header) { headers_for(user, scopes: %w(cloud_controller.admin)) }
99
let(:user_header) { headers_for(user, scopes: []) }
10+
let(:routing_api_client) { instance_double(VCAP::CloudController::RoutingApi::Client) }
11+
let(:router_group) { instance_double(VCAP::CloudController::RoutingApi::RouterGroup) }
1012

1113
before do
1214
VCAP::CloudController::Domain.dataset.destroy # this will clean up the seeded test domains
15+
allow(VCAP::CloudController::RoutingApi::Client).to receive(:new).and_return(routing_api_client)
16+
allow(routing_api_client).to receive(:router_group).with('some-router-guid').and_return router_group
17+
allow(routing_api_client).to receive(:router_group).with('some-other-router-guid').and_return nil
1318
end
1419

1520
describe 'GET /v3/domains' do
@@ -66,6 +71,7 @@
6671
updated_at: iso8601,
6772
name: visible_owned_private_domain.name,
6873
internal: false,
74+
router_group: nil,
6975
metadata: {
7076
labels: {},
7177
annotations: {}
@@ -94,6 +100,7 @@
94100
updated_at: iso8601,
95101
name: visible_shared_private_domain.name,
96102
internal: false,
103+
router_group: nil,
97104
metadata: {
98105
labels: {},
99106
annotations: {}
@@ -122,6 +129,7 @@
122129
updated_at: iso8601,
123130
name: not_visible_private_domain.name,
124131
internal: false,
132+
router_group: nil,
125133
metadata: {
126134
labels: {},
127135
annotations: {}
@@ -150,6 +158,7 @@
150158
updated_at: iso8601,
151159
name: shared_domain.name,
152160
internal: false,
161+
router_group: nil,
153162
metadata: {
154163
labels: {},
155164
annotations: {}
@@ -732,30 +741,37 @@
732741
end
733742

734743
describe 'when creating a shared domain' do
735-
let(:api_call) { lambda { |user_headers| post '/v3/domains', params.to_json, user_headers } }
744+
let(:api_call) { lambda { |user_headers| post '/v3/domains', domain_params.to_json, user_headers } }
745+
746+
let(:domain_params) do
747+
{
748+
router_group: { guid: 'some-router-guid' },
749+
}.merge(params)
750+
end
736751

737752
let(:domain_json) do
738753
{
739-
guid: UUID_REGEX,
740-
created_at: iso8601,
741-
updated_at: iso8601,
742-
name: params[:name],
743-
internal: false,
744-
metadata: {
745-
labels: { key: 'value' },
746-
annotations: { key2: 'value2' }
747-
},
748-
relationships: {
749-
organization: {
750-
data: nil
751-
},
752-
shared_organizations: {
753-
data: []
754-
}
754+
guid: UUID_REGEX,
755+
created_at: iso8601,
756+
updated_at: iso8601,
757+
name: params[:name],
758+
internal: false,
759+
router_group: { guid: 'some-router-guid' },
760+
metadata: {
761+
labels: { key: 'value' },
762+
annotations: { key2: 'value2' }
763+
},
764+
relationships: {
765+
organization: {
766+
data: nil
755767
},
756-
links: {
757-
self: { href: %r(#{Regexp.escape(link_prefix)}\/v3\/domains\/#{UUID_REGEX}) },
758-
route_reservations: { href: %r(#{Regexp.escape(link_prefix)}\/v3/domains/#{UUID_REGEX}/route_reservations) },
768+
shared_organizations: {
769+
data: []
770+
}
771+
},
772+
links: {
773+
self: { href: %r(#{Regexp.escape(link_prefix)}\/v3\/domains\/#{UUID_REGEX}) },
774+
route_reservations: { href: %r(#{Regexp.escape(link_prefix)}\/v3/domains/#{UUID_REGEX}/route_reservations) },
759775
}
760776
}
761777
end
@@ -785,6 +801,7 @@
785801
updated_at: iso8601,
786802
name: params[:name],
787803
internal: false,
804+
router_group: nil,
788805
metadata: {
789806
labels: { key: 'value' },
790807
annotations: { key2: 'value2' }
@@ -1070,6 +1087,30 @@
10701087
expect(parsed_response['errors'][0]['detail']).to eq 'Relationships cannot contain shared_organizations without an owning organization.'
10711088
end
10721089
end
1090+
1091+
context 'when a router group is provided' do
1092+
let(:params) do
1093+
{
1094+
name: 'my-domain.biz',
1095+
router_group: { guid: 'some-router-guid' },
1096+
relationships: {
1097+
organization: {
1098+
data: {
1099+
guid: org.guid
1100+
}
1101+
}
1102+
}
1103+
}
1104+
end
1105+
1106+
it 'returns a 422 and a helpful error message' do
1107+
post '/v3/domains', params.to_json, headers
1108+
1109+
expect(last_response.status).to eq(422)
1110+
1111+
expect(parsed_response['errors'][0]['detail']).to eq 'Domains scoped to an organization cannot be associated to a router group.'
1112+
end
1113+
end
10731114
end
10741115
end
10751116

@@ -1225,6 +1266,43 @@
12251266
expect(last_response.status).to eq 201
12261267
end
12271268
end
1269+
1270+
describe 'when specifying a router group that does not exist' do
1271+
let(:user_header) { admin_headers_for(user) }
1272+
let(:domain_params) do
1273+
{
1274+
name: 'my-domain.com',
1275+
router_group: { guid: 'some-other-router-guid' },
1276+
}
1277+
end
1278+
1279+
it 'returns a 422 and a helpful error message' do
1280+
post '/v3/domains', domain_params.to_json, user_header
1281+
1282+
expect(last_response.status).to eq(422)
1283+
1284+
expect(parsed_response['errors'][0]['detail']).to eq "Router group with guid 'some-other-router-guid' not found."
1285+
end
1286+
end
1287+
1288+
describe 'when specifying a router group with internal: true' do
1289+
let(:user_header) { admin_headers_for(user) }
1290+
let(:domain_params) do
1291+
{
1292+
name: 'my-domain.com',
1293+
internal: true,
1294+
router_group: { guid: 'some-router-guid' },
1295+
}
1296+
end
1297+
1298+
it 'returns a 422 and a helpful error message' do
1299+
post '/v3/domains', domain_params.to_json, user_header
1300+
1301+
expect(last_response.status).to eq(422)
1302+
1303+
expect(parsed_response['errors'][0]['detail']).to eq 'Internal domains cannot be associated to a router group.'
1304+
end
1305+
end
12281306
end
12291307

12301308
describe 'POST /v3/domains/:guid/relationships/shared_organizations' do
@@ -1667,6 +1745,7 @@
16671745
updated_at: iso8601,
16681746
name: shared_domain.name,
16691747
internal: false,
1748+
router_group: nil,
16701749
metadata: {
16711750
labels: {},
16721751
annotations: {}
@@ -1710,6 +1789,7 @@
17101789
updated_at: iso8601,
17111790
name: private_domain.name,
17121791
internal: false,
1792+
router_group: nil,
17131793
metadata: {
17141794
labels: {},
17151795
annotations: {}
@@ -1769,6 +1849,7 @@
17691849
updated_at: iso8601,
17701850
name: private_domain.name,
17711851
internal: false,
1852+
router_group: nil,
17721853
metadata: {
17731854
labels: {},
17741855
annotations: {}
@@ -1865,6 +1946,7 @@
18651946
updated_at: iso8601,
18661947
name: domain.name,
18671948
internal: false,
1949+
router_group: nil,
18681950
relationships: {
18691951
organization: {
18701952
data: nil
@@ -1916,6 +1998,7 @@
19161998
updated_at: iso8601,
19171999
name: domain.name,
19182000
internal: false,
2001+
router_group: nil,
19192002
relationships: {
19202003
organization: {
19212004
data: { guid: org.guid }
@@ -1972,6 +2055,7 @@
19722055
updated_at: iso8601,
19732056
name: domain.name,
19742057
internal: false,
2058+
router_group: nil,
19752059
relationships: {
19762060
organization: {
19772061
data: { guid: domain.owning_organization_guid }

0 commit comments

Comments
 (0)