Skip to content

Commit 9e2efc8

Browse files
authored
Dynamically allow tenancies access to Machines, Volumes, Kubernetes and Kubernetes Apps based on discovery of the portal-internal network (#425)
### Internal network is a shared network Add a new config option to the openstack provider - `internalNetIsShared` - which allows a network shared into a project to be used as the portal-internal network. If `internalNetIsShared` is True, shared networks that are available to the project and have the `portal-internal` tag are preferentially chosen as the portal-internal network over those that are owned by the project. There are no changes to the default behaviour, and projects that do not have access to a **shared** network with the `portal-internal` tag will use an existing network owned by the project if it is available, or a new network will be created if `createInternalNet` is True (the default). ### Internal network doesn't exist If the internal network can not be detected and `createInternalNet` is False, the capabilites returned for a scoped session are updated to disable machines, volumes, Kubernetes and Kubernetes apps API endpoints, because creating resources of these types relies on the internal network being determined. The UI reacts to the disabling of these endpoints by removing the relevant items from the sidebar (Advanced>Machines, Volumes) or from the platforms catalogue (Kubernetes and Kubernetes apps). ### Machines functionality disabled The "machines" functionality may be disabled for the OpenStack provider by setting `supportsMachines` to False (default is True). This will disable operations on machines and volumes across all projects, and will cause the "advanced" menu element to be removed from the UI. ### Dynamic project capabilities vs configured capabilities Configuration options remain for enabling and disabling CaaS, Kubernetes and Kubernetes apps, alongside a new configuration option for Machines. If a capability is disabled in config, it will not be enabled dynamically on a per-tenant basis. If a capability is enabled in config, it may be disabled on a per-tenant basis if the prerequisites for the capability are not present (for example, the internal network was not able to be determined for a project, so machines, volumes, Kubernetes and Kubernetes Apps are dynamically disabled even though they are enabled in configuration).
1 parent 3ccaa68 commit 9e2efc8

File tree

9 files changed

+482
-213
lines changed

9 files changed

+482
-213
lines changed

api/azimuth/provider/dto.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ class Capabilities:
1717
#: Indicates if the cloud supports volumes
1818
supports_volumes: bool = False
1919

20+
#: Indicates if machines are enabled for the cloud
21+
supports_machines: bool = True
22+
23+
#: Indicates if kubernetes is enabled for the cloud
24+
supports_kubernetes: bool = True
25+
26+
#: Indicates if kubernetes apps are enabled for the cloud
27+
supports_apps: bool = True
28+
2029

2130
@dataclass(frozen=True)
2231
class Tenancy:

api/azimuth/provider/null.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,7 @@ class ScopedSession(base.ScopedSession):
3535
provider_name = "null"
3636

3737
def capabilities(self):
38-
return dto.Capabilities(supports_volumes=False)
38+
return dto.Capabilities(
39+
supports_volumes=False,
40+
supports_machines=False,
41+
)

api/azimuth/provider/openstack/provider.py

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,18 @@ class Provider(base.Provider):
137137
The current tenancy name can be templated in using the
138138
fragment ``{tenant_name}``.
139139
create_internal_net: If ``True`` (the default), then the internal network is
140-
auto-createdwhen a tagged network or templated network
140+
auto-created when a tagged network or templated network
141141
cannot be found.
142+
allow_shared_internal_net: If ``True`` (the default is False), then networks
143+
that are not owned by the project are searched for
144+
the correct tag.
142145
manila_project_share_gb: If >0 (the default is 0), then manila project share is
143146
auto created with specified size.
144147
internal_net_cidr: The CIDR for the internal network when it is auto-created
145148
(default ``192.168.3.0/24``).
146149
internal_net_dns_nameservers: The DNS nameservers for the internal network when
147150
it is auto-created (default ``None``).
151+
supports_machines: If True (the default), then users can manipulate machines
148152
"""
149153

150154
provider_name = "openstack"
@@ -155,19 +159,23 @@ def __init__(
155159
internal_net_template=None,
156160
external_net_template=None,
157161
create_internal_net=True,
162+
allow_shared_internal_net=False,
158163
manila_project_share_gb=0,
159164
internal_net_cidr="192.168.3.0/24",
160165
internal_net_dns_nameservers=None,
166+
supports_machines=True,
161167
):
162168
self._metadata_prefix = metadata_prefix
163169
self._internal_net_template = internal_net_template
164170
self._external_net_template = external_net_template
165171
self._create_internal_net = create_internal_net
172+
self._allow_shared_internal_net = allow_shared_internal_net
166173
self._manila_project_share_gb = 0
167174
if manila_project_share_gb:
168175
self._manila_project_share_gb = int(manila_project_share_gb)
169176
self._internal_net_cidr = internal_net_cidr
170177
self._internal_net_dns_nameservers = internal_net_dns_nameservers
178+
self._supports_machines = supports_machines
171179

172180
def _from_auth_session(self, auth_session, auth_user):
173181
return UnscopedSession(
@@ -177,9 +185,11 @@ def _from_auth_session(self, auth_session, auth_user):
177185
self._internal_net_template,
178186
self._external_net_template,
179187
self._create_internal_net,
188+
self._allow_shared_internal_net,
180189
self._manila_project_share_gb,
181190
self._internal_net_cidr,
182191
self._internal_net_dns_nameservers,
192+
self._supports_machines,
183193
)
184194

185195

@@ -198,18 +208,22 @@ def __init__(
198208
internal_net_template=None,
199209
external_net_template=None,
200210
create_internal_net=True,
211+
allow_shared_internal_net=False,
201212
manila_project_share_gb=0,
202213
internal_net_cidr="192.168.3.0/24",
203214
internal_net_dns_nameservers=None,
215+
supports_machines=True,
204216
):
205217
super().__init__(auth_session, auth_user)
206218
self._metadata_prefix = metadata_prefix
207219
self._internal_net_template = internal_net_template
208220
self._external_net_template = external_net_template
209221
self._create_internal_net = create_internal_net
222+
self._allow_shared_internal_net = allow_shared_internal_net
210223
self._manila_project_share_gb = manila_project_share_gb
211224
self._internal_net_cidr = internal_net_cidr
212225
self._internal_net_dns_nameservers = internal_net_dns_nameservers
226+
self._supports_machines = supports_machines
213227

214228
@convert_exceptions
215229
def _scoped_session(self, auth_user, tenancy, credential_data):
@@ -221,9 +235,11 @@ def _scoped_session(self, auth_user, tenancy, credential_data):
221235
self._internal_net_template,
222236
self._external_net_template,
223237
self._create_internal_net,
238+
self._allow_shared_internal_net,
224239
self._manila_project_share_gb,
225240
self._internal_net_cidr,
226241
self._internal_net_dns_nameservers,
242+
self._supports_machines,
227243
)
228244

229245

@@ -243,19 +259,23 @@ def __init__(
243259
internal_net_template=None,
244260
external_net_template=None,
245261
create_internal_net=True,
262+
allow_shared_internal_net=False,
246263
manila_project_share_gb=0,
247264
internal_net_cidr="192.168.3.0/24",
248265
internal_net_dns_nameservers=None,
266+
supports_machines=True,
249267
):
250268
super().__init__(auth_user, tenancy)
251269
self._connection = connection
252270
self._metadata_prefix = metadata_prefix
253271
self._internal_net_template = internal_net_template
254272
self._external_net_template = external_net_template
255273
self._create_internal_net = create_internal_net
274+
self._allow_shared_internal_net = allow_shared_internal_net
256275
self._manila_project_share_gb = manila_project_share_gb
257276
self._internal_net_cidr = internal_net_cidr
258277
self._internal_net_dns_nameservers = internal_net_dns_nameservers
278+
self._supports_machines = supports_machines
259279

260280
# TODO(johngarbutt): consider moving some of this to config
261281
# and/or hopefully having this feature on by default
@@ -281,13 +301,25 @@ def capabilities(self):
281301
See :py:meth:`.base.ScopedSession.capabilities`.
282302
"""
283303
# Check if the relevant services are available to the project
284-
try:
285-
_ = self._connection.block_store
286-
except api.ServiceNotSupported:
287-
supports_volumes = False
288-
else:
304+
self._log("Fetching tenancy capabilities ")
305+
306+
if self._supports_machines:
307+
# If machines support is enabled, then volumes are supported
308+
# unless the connection to the openstack block-store fails.
289309
supports_volumes = True
290-
return dto.Capabilities(supports_volumes=supports_volumes)
310+
311+
try:
312+
_ = self._connection.block_store
313+
except api.ServiceNotSupported:
314+
supports_volumes = False
315+
else:
316+
# If machine support is disabled, then volumes are too
317+
supports_volumes = False
318+
319+
return dto.Capabilities(
320+
supports_volumes=supports_volumes,
321+
supports_machines=self._supports_machines,
322+
)
291323

292324
@convert_exceptions
293325
def quotas(self):
@@ -460,14 +492,32 @@ def _tagged_network(self, net_type):
460492
# For the internal network this is what we want, but for all other types of
461493
# network (e.g. external, storage) we want to allow shared networks from other
462494
# projects to be selected - setting "project_id = None" allows this to happen
463-
kwargs = {} if net_type == "internal" else {"project_id": None}
495+
kwargs = (
496+
{}
497+
if net_type == "internal" and not self._allow_shared_internal_net
498+
else {"project_id": None}
499+
)
500+
464501
networks = list(self._connection.network.networks.all(tags=tag, **kwargs))
502+
465503
if len(networks) == 1:
466-
self._log("Using tagged %s network '%s'", net_type, networks[0].name)
504+
net_owner = (
505+
"project"
506+
if networks[0].project_id == self._connection.project_id
507+
else "shared"
508+
)
509+
510+
self._log(
511+
"Using tagged %s %s network '%s'", net_owner, net_type, networks[0].name
512+
)
467513
return networks[0]
468514
elif len(networks) > 1:
515+
net_names = [network.name for network in networks]
469516
self._log(
470-
"Found multiple networks with tag '%s'.", tag, level=logging.ERROR
517+
"Found multiple networks with tag '%s': %s.",
518+
tag,
519+
",".join(net_names),
520+
level=logging.ERROR,
471521
)
472522
raise errors.InvalidOperationError(
473523
f"Found multiple networks with tag '{tag}'."
@@ -485,16 +535,30 @@ def _templated_network(self, template, net_type):
485535
raised.
486536
"""
487537
net_name = template.format(tenant_name=self._connection.project_name)
538+
488539
# By default, networks.all() will only return networks that belong to the
489540
# project
490541
# For the internal network this is what we want, but for all other types of
491542
# network (e.g. external, storage) we want to allow shared networks from other
492543
# projects to be selected - setting "project_id = None" allows this to happen
493-
kwargs = {} if net_type == "internal" else {"project_id": None}
544+
kwargs = (
545+
{}
546+
if net_type == "internal" and not self._allow_shared_internal_net
547+
else {"project_id": None}
548+
)
494549
networks = list(self._connection.network.networks.all(name=net_name, **kwargs))
550+
495551
if len(networks) == 1:
552+
net_owner = (
553+
"project"
554+
if networks[0].project_id == self._connection.project_id
555+
else "shared"
556+
)
496557
self._log(
497-
"Found %s network '%s' using template.", net_type, networks[0].name
558+
"Found %s %s network '%s' using template.",
559+
net_type,
560+
net_owner,
561+
networks[0].name,
498562
)
499563
return networks[0]
500564
elif len(networks) > 1:

0 commit comments

Comments
 (0)