Skip to content

Commit 12e7ff3

Browse files
paul-gsdasvetlovpre-commit-ci[bot]neuro-auto-bot
authored
Remove no org (#2368)
Co-authored-by: Andrew Svetlov <andrew.svetlov@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: neuro-auto-bot <68340147+neuro-auto-bot@users.noreply.github.com>
1 parent b4405c6 commit 12e7ff3

22 files changed

+722
-525
lines changed

PLATFORMSECRETS_IMAGE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ghcr.io/neuro-inc/platformsecrets:25.8.1
1+
ghcr.io/neuro-inc/platformsecrets:latest

platform_api/api.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,6 @@ async def handle_config(self, request: aiohttp.web.Request) -> aiohttp.web.Respo
8282
8383
If the requesting user is authorized, the response will contain the details
8484
about the user's orgs, clusters, and projects.
85-
86-
In case the user has direct access to a cluster outside of any org,
87-
the list of orgs will not have a None entry, but the cluster will have a None
88-
entry in its orgs list.
89-
90-
Similarly, a project in the response can have a None org.
9185
"""
9286
data: dict[str, Any] = {}
9387

platform_api/config.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@
1111

1212
STORAGE_URI_SCHEME = "storage"
1313

14-
NO_ORG = "NO_ORG"
15-
NO_ORG_NORMALIZED = "no-org"
16-
1714

1815
@dataclass(frozen=True)
1916
class RegistryConfig:

platform_api/handlers/jobs_handler.py

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from neuro_config_client import Cluster, ResourcePreset, TPUResource
2222
from yarl import URL
2323

24-
from platform_api.config import NO_ORG, STORAGE_URI_SCHEME, Config
24+
from platform_api.config import STORAGE_URI_SCHEME, Config
2525
from platform_api.log import log_debug_time
2626
from platform_api.orchestrator.job import (
2727
JOB_NAME_SEPARATOR,
@@ -250,16 +250,15 @@ def _set_preset_resources(payload: dict[str, Any]) -> dict[str, Any]:
250250
def create_job_cluster_org_name_validator(
251251
*,
252252
default_cluster_name: str,
253-
default_org_name: str | None,
253+
default_org_name: str,
254254
default_project_name: str,
255255
) -> t.Trafaret:
256256
return t.Dict(
257257
{
258258
t.Key(
259259
"cluster_name", default=default_cluster_name
260260
): create_cluster_name_validator(),
261-
t.Key("org_name", default=default_org_name): create_org_name_validator()
262-
| t.Null,
261+
t.Key("org_name", default=default_org_name): create_org_name_validator(),
263262
t.Key(
264263
"project_name", default=default_project_name
265264
): create_project_name_validator(),
@@ -550,7 +549,7 @@ def infer_permissions_from_container(
550549
container: Container,
551550
registry_host: str,
552551
cluster_name: str,
553-
org_name: str | None,
552+
org_name: str,
554553
*,
555554
project_name: str,
556555
) -> list[Permission]:
@@ -563,7 +562,7 @@ def infer_permissions_from_container(
563562
if container.belongs_to_registry(registry_host):
564563
permissions.append(
565564
Permission(
566-
uri=str(container.to_image_uri(registry_host, cluster_name)),
565+
uri=str(container.to_image_uri(registry_host, cluster_name, org_name)),
567566
action="read",
568567
)
569568
)
@@ -695,26 +694,51 @@ async def create_job(self, request: aiohttp.web.Request) -> aiohttp.web.Response
695694
cluster_configs = await self._jobs_service.get_user_cluster_configs(user)
696695
self._check_user_can_submit_jobs(cluster_configs)
697696
default_cluster_name = cluster_configs[0].config.name
698-
cluster_for_default_org = (
699-
orig_payload.get("cluster_name") or default_cluster_name
700-
)
701-
cluster_config_for_default_org = next(
702-
(
703-
cluster_config
704-
for cluster_config in cluster_configs
705-
if cluster_config.config.name == cluster_for_default_org
706-
),
707-
None,
708-
)
709-
# always use NO_ORG as default if a user has direct access to cluster.
710-
# if cluster_config_for_default_org is None,
711-
# the validator below will raise an error
712-
default_org_name = None
713-
if (
714-
cluster_config_for_default_org is not None
715-
and None not in cluster_config_for_default_org.orgs
716-
):
717-
default_org_name = cluster_config_for_default_org.orgs[0]
697+
698+
# Check if user explicitly specified a cluster they don't have access to
699+
requested_cluster = orig_payload.get("cluster_name")
700+
if requested_cluster is not None:
701+
cluster_config_for_default_org = next(
702+
(
703+
cluster_config
704+
for cluster_config in cluster_configs
705+
if cluster_config.config.name == requested_cluster
706+
),
707+
None,
708+
)
709+
if cluster_config_for_default_org is None:
710+
raise aiohttp.web.HTTPForbidden(
711+
text=json.dumps(
712+
{
713+
"error": (
714+
"User is not allowed to submit jobs to the "
715+
"specified cluster"
716+
)
717+
}
718+
),
719+
content_type="application/json",
720+
)
721+
if not cluster_config_for_default_org.orgs:
722+
raise aiohttp.web.HTTPForbidden(
723+
text=json.dumps(
724+
{
725+
"error": (
726+
"User is not allowed to submit jobs to the "
727+
"specified cluster as a member of given organization"
728+
)
729+
}
730+
),
731+
content_type="application/json",
732+
)
733+
else:
734+
cluster_config_for_default_org = cluster_configs[0]
735+
if not cluster_config_for_default_org.orgs:
736+
raise aiohttp.web.HTTPForbidden(
737+
text=json.dumps({"error": "User must have at least one org"}),
738+
content_type="application/json",
739+
)
740+
741+
default_org_name = cluster_config_for_default_org.orgs[0]
718742

719743
job_cluster_org_name_validator = create_job_cluster_org_name_validator(
720744
default_cluster_name=default_cluster_name,
@@ -1108,7 +1132,7 @@ def create_from_query(self, query: MultiDictProxy) -> JobFilter: # type: ignore
11081132
for cluster_name in query.getall("cluster_name", [])
11091133
}
11101134
orgs = {
1111-
self._parse_org_name(org_name)
1135+
self._org_name_validator.check(org_name)
11121136
for org_name in query.getall("org_name", [])
11131137
}
11141138
projects = {
@@ -1154,13 +1178,6 @@ def create_from_query(self, query: MultiDictProxy) -> JobFilter: # type: ignore
11541178
**bool_filters, # type: ignore
11551179
)
11561180

1157-
def _parse_org_name(self, org_name: str) -> str | None:
1158-
return (
1159-
None
1160-
if org_name.upper() == NO_ORG
1161-
else self._org_name_validator.check(org_name)
1162-
)
1163-
11641181

11651182
@dataclass(frozen=True)
11661183
class BulkJobFilter:
@@ -1180,11 +1197,11 @@ def __init__(
11801197
self._has_access_to_all: bool = False
11811198
self._has_clusters_shared_all: bool = False
11821199
self._has_orgs_shared_all: bool = False
1183-
self._clusters_shared_any: dict[str, dict[str | None, dict[str, set[str]]]] = (
1200+
self._clusters_shared_any: dict[str, dict[str, dict[str, set[str]]]] = (
11841201
defaultdict(lambda: defaultdict(lambda: defaultdict(set)))
11851202
)
11861203
self._projects_shared_any: set[str] = set()
1187-
self._orgs_shared_any: set[str | None] = set()
1204+
self._orgs_shared_any: set[str] = set()
11881205
self._shared_ids: set[str] = set()
11891206

11901207
def build(self) -> BulkJobFilter:
@@ -1239,10 +1256,6 @@ def _traverse_clusters(self, tree: ClientAccessSubTreeView) -> None:
12391256
self._clusters_shared_any[cluster_name] = {}
12401257
else:
12411258
self._traverse_orgs(sub_tree, cluster_name)
1242-
if self._query_filter.orgs and None not in self._query_filter.orgs:
1243-
# skipping None org
1244-
continue
1245-
self._traverse_projects(sub_tree, cluster_name, None)
12461259

12471260
def _traverse_orgs(self, tree: ClientAccessSubTreeView, cluster_name: str) -> None:
12481261
for org_name, sub_tree in tree.children.items():
@@ -1267,7 +1280,7 @@ def _traverse_projects(
12671280
self,
12681281
tree: ClientAccessSubTreeView,
12691282
cluster_name: str,
1270-
org_name: str | None,
1283+
org_name: str,
12711284
) -> None:
12721285
for project, sub_tree in tree.children.items():
12731286
if not sub_tree.can_list():
@@ -1294,7 +1307,7 @@ def _traverse_jobs(
12941307
self,
12951308
tree: ClientAccessSubTreeView,
12961309
cluster_name: str,
1297-
org_name: str | None,
1310+
org_name: str,
12981311
project_name: str,
12991312
) -> None:
13001313
for name, sub_tree in tree.children.items():
@@ -1365,7 +1378,7 @@ def _create_bulk_filter(self) -> JobFilter | None:
13651378

13661379
def _optimize_clusters_projects(
13671380
self,
1368-
orgs: AbstractSet[str | None],
1381+
orgs: AbstractSet[str],
13691382
projects: AbstractSet[str],
13701383
name: str | None,
13711384
) -> None:

platform_api/orchestrator/job.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from neuro_config_client.entities import DEFAULT_ENERGY_SCHEDULE_NAME
1616
from yarl import URL
1717

18-
from platform_api.config import NO_ORG
1918
from platform_api.old_kube_client.apolo import generate_namespace_name
2019

2120
from .job_request import (
@@ -290,7 +289,7 @@ class JobRecord:
290289
project_name: str
291290
org_project_hash: bytes
292291
namespace: str
293-
org_name: str | None = None
292+
org_name: str
294293
name: str | None = None
295294
preset_name: str | None = None
296295
tags: Sequence[str] = ()
@@ -335,18 +334,18 @@ def create(
335334
if not kwargs.get("project_name"):
336335
kwargs["project_name"] = get_base_owner(kwargs["owner"])
337336

338-
org_name = kwargs.get("org_name")
337+
org_name = kwargs["org_name"]
339338
project_name = kwargs["project_name"]
340339

341340
kwargs["org_project_hash"] = cls._create_org_project_hash(
342341
org_name, project_name
343342
)
344-
kwargs["namespace"] = generate_namespace_name(org_name or NO_ORG, project_name)
343+
kwargs["namespace"] = generate_namespace_name(org_name, project_name)
345344
return cls(**kwargs)
346345

347346
@classmethod
348-
def _create_org_project_hash(cls, org_name: str | None, project_name: str) -> bytes:
349-
return cls._create_hash(org_name or NO_ORG, project_name)[:5]
347+
def _create_org_project_hash(cls, org_name: str, project_name: str) -> bytes:
348+
return cls._create_hash(org_name, project_name)[:5]
350349

351350
@classmethod
352351
def _create_hash(cls, *args: str) -> bytes:
@@ -540,7 +539,7 @@ def from_primitive(
540539
request.job_id, payload
541540
)
542541
owner = payload.get("owner") or orphaned_job_owner
543-
org_name = payload.get("org_name", None)
542+
org_name = payload["org_name"]
544543
project_name = payload["project_name"]
545544
org_project_hash = payload.get("org_project_hash")
546545
if org_project_hash and isinstance(org_project_hash, str):
@@ -949,7 +948,7 @@ def get_total_price_credits(self) -> Decimal:
949948
return runtime_hours * self.price_credits_per_hour
950949

951950
@property
952-
def org_name(self) -> str | None:
951+
def org_name(self) -> str:
953952
return self._record.org_name
954953

955954
@property

platform_api/orchestrator/job_request.py

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
from neuro_config_client import ResourcePoolType, ResourcePreset
1111
from yarl import URL
1212

13-
from platform_api.config import NO_ORG_NORMALIZED
14-
1513

1614
class JobException(Exception):
1715
pass
@@ -83,23 +81,8 @@ def to_uri(self) -> URL:
8381
)
8482

8583
def to_permission_uri(self) -> str:
86-
"""
87-
Permission URI must not include a `no-org`.
88-
This method can be removed whenever we'll get rid of `no-org` concept.
89-
"""
90-
joined_path = self.path
91-
if "/" in self.path:
92-
path = []
93-
org_name, project_name, *parts = self.path.split("/")
94-
if org_name == NO_ORG_NORMALIZED:
95-
path.extend([project_name, *parts])
96-
else:
97-
path.extend([org_name, project_name, *parts])
98-
joined_path = "/".join(path)
9984
return str(
100-
URL.build(scheme="disk", host=self.cluster_name)
101-
/ joined_path
102-
/ self.disk_id
85+
URL.build(scheme="disk", host=self.cluster_name) / self.path / self.disk_id
10386
)
10487

10588
@classmethod
@@ -175,8 +158,6 @@ def create(cls, secret_uri: str | URL) -> "Secret":
175158

176159
@staticmethod
177160
def path_with_org(path: str) -> str:
178-
if "/" not in path:
179-
path = f"{NO_ORG_NORMALIZED}/{path}"
180161
return path
181162

182163

@@ -448,13 +429,14 @@ def belongs_to_registry(self, registry_host: str) -> bool:
448429
prefix = f"{registry_host}/"
449430
return self.image.startswith(prefix)
450431

451-
def to_image_uri(self, registry_host: str, cluster_name: str) -> URL:
432+
def to_image_uri(self, registry_host: str, cluster_name: str, org_name: str) -> URL:
452433
assert self.belongs_to_registry(registry_host), "Unknown registry"
453434
prefix = f"{registry_host}/"
454435
repo = self.image[len(prefix) :]
455436
path, *_ = repo.split(":", 1)
456437
assert cluster_name
457-
return URL.build(scheme="image", host=cluster_name) / path
438+
assert org_name
439+
return URL.build(scheme="image", host=cluster_name) / org_name / path
458440

459441
def get_secrets(self) -> list[Secret]:
460442
return list(

platform_api/orchestrator/jobs_poller.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ def _parse_container(data: Mapping[str, Any]) -> Container:
131131
http_server=http_server,
132132
)
133133

134-
project_name = payload["project_name"]
135134
return JobRecord(
136135
request=JobRequest(
137136
job_id=payload["id"],
@@ -143,8 +142,8 @@ def _parse_container(data: Mapping[str, Any]) -> Container:
143142
[_parse_status_item(item) for item in payload["statuses"]]
144143
),
145144
cluster_name=payload["cluster_name"],
146-
org_name=payload.get("org_name"),
147-
project_name=project_name,
145+
org_name=payload["org_name"],
146+
project_name=payload["project_name"],
148147
org_project_hash=bytes.fromhex(payload["org_project_hash"]),
149148
namespace=payload["namespace"],
150149
name=payload.get("name"),

platform_api/orchestrator/jobs_service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,7 @@ def create_for_org(cls, org: str) -> "NoCreditsError":
9191
@dataclass(frozen=True)
9292
class UserClusterConfig:
9393
config: Cluster
94-
# None value means the direct access to cluster without any or:
95-
orgs: list[str | None]
94+
orgs: list[str]
9695

9796

9897
@dataclass(frozen=True)
@@ -503,7 +502,8 @@ async def _get_user_cluster_configs(
503502
cluster_configs = await self._get_user_cluster_configs_by_name(response)
504503
cluster_to_orgs = defaultdict(list)
505504
for user_cluster in response.clusters:
506-
cluster_to_orgs[user_cluster.cluster_name].append(user_cluster.org_name)
505+
if user_cluster.org_name is not None:
506+
cluster_to_orgs[user_cluster.cluster_name].append(user_cluster.org_name)
507507
for cluster_name, orgs in cluster_to_orgs.items():
508508
if cluster_config := cluster_configs.get(cluster_name):
509509
configs.append(UserClusterConfig(config=cluster_config, orgs=orgs))

platform_api/orchestrator/jobs_storage/base.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __init__(self, job_name: str, project_name: str, found_job_id: str):
2828
)
2929

3030

31-
ClusterOrgProjectNameSet = dict[str, dict[str | None, dict[str, AbstractSet[str]]]]
31+
ClusterOrgProjectNameSet = dict[str, dict[str, dict[str, AbstractSet[str]]]]
3232

3333

3434
@dataclass(frozen=True)
@@ -39,9 +39,7 @@ class JobFilter:
3939
clusters: ClusterOrgProjectNameSet = field(
4040
default_factory=cast(type[ClusterOrgProjectNameSet], dict)
4141
)
42-
orgs: AbstractSet[str | None] = field(
43-
default_factory=cast(type[AbstractSet[str | None]], set)
44-
)
42+
orgs: AbstractSet[str] = field(default_factory=cast(type[AbstractSet[str]], set))
4543
owners: AbstractSet[str] = field(default_factory=cast(type[AbstractSet[str]], set))
4644
projects: AbstractSet[str] = field(
4745
default_factory=cast(type[AbstractSet[str]], set)

0 commit comments

Comments
 (0)