Skip to content

Commit

Permalink
Use valid GCP resource names (#1248)
Browse files Browse the repository at this point in the history
  • Loading branch information
r4victor authored May 20, 2024
1 parent f0b41b0 commit a0bc33c
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/dstack/_internal/core/backends/base/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def create_gateway(


def get_instance_name(run: Run, job: Job) -> str:
return f"{run.project_name}-{job.job_spec.job_name}"
return f"{run.project_name.lower()}-{job.job_spec.job_name}"


def get_user_data(authorized_keys: List[str]) -> str:
Expand Down
26 changes: 21 additions & 5 deletions src/dstack/_internal/core/backends/gcp/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
SSHKey,
)
from dstack._internal.core.models.runs import Job, JobProvisioningData, Requirements, Run
from dstack._internal.utils.logging import get_logger

logger = get_logger(__name__)


class GCPCompute(Compute):
Expand Down Expand Up @@ -94,6 +97,16 @@ def create_instance(
) -> JobProvisioningData:
instance_name = instance_config.instance_name

if not gcp_resources.is_valid_resource_name(instance_name):
# In a rare case the instance name is invalid in GCP,
# we better use a random instance name than fail provisioning.
instance_name = gcp_resources.generate_random_resource_name()
logger.warning(
"Invalid GCP instance name: %s. A new valid name is generated: %s",
instance_config.instance_name,
instance_name,
)

authorized_keys = instance_config.get_public_keys()

gcp_resources.create_runner_firewall_rules(
Expand All @@ -102,6 +115,13 @@ def create_instance(
)
disk_size = round(instance_offer.instance.resources.disk.size_mib / 1024)

labels = {
"owner": "dstack",
"dstack_project": instance_config.project_name.lower(),
"dstack_user": instance_config.user.lower(),
}
labels = {k: v for k, v in labels.items() if gcp_resources.is_valid_label_value(v)}

for zone in _get_instance_zones(instance_offer):
request = compute_v1.InsertInstanceRequest()
request.zone = zone
Expand All @@ -120,11 +140,7 @@ def create_instance(
spot=instance_offer.instance.resources.spot,
user_data=get_user_data(authorized_keys),
authorized_keys=authorized_keys,
labels={
"owner": "dstack",
"dstack_project": instance_config.project_name.lower(),
"dstack_user": instance_config.user.lower(),
},
labels=labels,
tags=[gcp_resources.DSTACK_INSTANCE_TAG],
instance_name=instance_name,
zone=zone,
Expand Down
26 changes: 26 additions & 0 deletions src/dstack/_internal/core/backends/gcp/resources.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import random
import re
import string
from typing import Dict, List, Optional

import google.api_core.exceptions
Expand Down Expand Up @@ -200,3 +203,26 @@ def wait_for_extended_operation(
raise operation.exception() or RuntimeError(operation.error_message)

return result


NAME_PATTERN = re.compile(r"^[a-z]([-a-z0-9]*[a-z0-9])?$")

LABEL_VALUE_PATTERN = re.compile(r"^[-a-z0-9]{0,63}$")


def is_valid_resource_name(name: str) -> bool:
if len(name) < 1 or len(name) > 63:
return False
match = re.match(NAME_PATTERN, name)
return match is not None


def is_valid_label_value(value: str) -> bool:
match = re.match(LABEL_VALUE_PATTERN, value)
return match is not None


def generate_random_resource_name() -> str:
return random.choice(string.ascii_lowercase) + "".join(
random.choice(string.ascii_lowercase + string.digits) for _ in range(40)
)
Empty file.
41 changes: 41 additions & 0 deletions src/tests/_internal/core/backends/gcp/test_resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest

from dstack._internal.core.backends.gcp import resources as gcp_resources


class TestIsValidResourceName:
@pytest.mark.parametrize(
"name",
[
"",
"1",
"a" * 64,
"-startswithdash",
"1startswithdigit",
"asd_asd",
"Uppercase",
],
)
def test_invalid_name(self, name):
assert not gcp_resources.is_valid_resource_name(name)

@pytest.mark.parametrize("name", ["a", "some-name-with-dashes-123"])
def test_valid_name(self, name):
assert gcp_resources.is_valid_resource_name(name)


class TestIsValidLabelValue:
@pytest.mark.parametrize(
"name",
[
"a" * 64,
"asd_asd",
"Uppercase",
],
)
def test_invalid_label_value(self, name):
assert not gcp_resources.is_valid_label_value(name)

@pytest.mark.parametrize("name", ["", "a", "---", "some-lable-with-dashes-123"])
def test_valid_label_value(self, name):
assert gcp_resources.is_valid_label_value(name)

0 comments on commit a0bc33c

Please sign in to comment.