Skip to content

feat: Allow defining meaningful hostnames on multi-tesseract network #206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

johnbcoughlin
Copy link
Contributor

@johnbcoughlin johnbcoughlin commented Jun 5, 2025

Relevant issue or PR

Description of changes

Tesseracts are able to communicate with each other over the multi-tesseract network, but the host names by which they can be addressed are assigned by the SDK, are not human-readable, and are hard to discover. This change allows the user to pass a list of meaningful tesseract "service names" to tesseract serve or engine.serve. These service names function as meaningful identifiers by which tesseracts can refer to each other.

As an example, suppose we have started two tesseracts using the following tesseract serve command:

tesseract serve tesseract-a tesseract-b --service-names tesseract-a,tesseract-b

From within Tesseract A, we can run

with Tesseract.from_url("http://tesseract-b:8000") as tx:
    tx.apply(...)

Testing done

  • Added an end-to-end test verifying that tesseract-b is reachable at the expected hostname from tesseract-a.
  • Verified same manually using the example given above.

@johnbcoughlin johnbcoughlin force-pushed the jack-service-names branch 2 times, most recently from 04fb9a2 to 11a2173 Compare June 5, 2025 17:52
@xalelax
Copy link
Contributor

xalelax commented Jun 6, 2025

Nice idea 👍 should we also validate whether service names are distinct?

@zmheiko
Copy link
Contributor

zmheiko commented Jun 6, 2025

I think we might also want to restrict service names such that the corresponding urls are safe.

@johnbcoughlin
Copy link
Contributor Author

Nice idea 👍 should we also validate whether service names are distinct?

Yes, good point. Will add this.

@PasteurBot
Copy link
Contributor

PasteurBot commented Jun 6, 2025

CLA signatures confirmed

All contributors have signed the Contributor License Agreement.
Posted by the CLA Assistant Lite bot.

Copy link

codecov bot commented Jun 6, 2025

Codecov Report

Attention: Patch coverage is 26.08696% with 17 lines in your changes missing coverage. Please review.

Project coverage is 25.68%. Comparing base (8997bd0) to head (3f61ec2).
Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
tesseract_core/sdk/engine.py 26.08% 16 Missing and 1 partial ⚠️

❗ There is a different number of reports uploaded between BASE (8997bd0) and HEAD (3f61ec2). Click for more details.

HEAD has 12 uploads less than BASE
Flag BASE (8997bd0) HEAD (3f61ec2)
24 12
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #206       +/-   ##
===========================================
- Coverage   76.21%   25.68%   -50.54%     
===========================================
  Files          28       24        -4     
  Lines        3078     3002       -76     
  Branches      480      487        +7     
===========================================
- Hits         2346      771     -1575     
- Misses        519     2143     +1624     
+ Partials      213       88      -125     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@johnbcoughlin
Copy link
Contributor Author

I think we might also want to restrict service names such that the corresponding urls are safe.

This is a surprisingly frustrating validation to perform. :(

  • We might want to validate that the string is a valid host name for a URL. The most correct python way to do that seems to be to use the validators package. I haven't explored this package in detail, but neither urllib nor requests seem to expose this API.
  • More specifically, we want to validate that the string is a valid docker-compose service name. Neither I nor Claude could find any official specification on docker-compose service names. The only thing was a PR from 2015 (!) which changed it to the following:
VALID_NAME_CHARS = '[a-zA-Z0-9\._\-]'

Unfortunately the docker compose implementation has changed completely since then, and I couldn't find the corresponding validation logic in the golang codebase.

What do people think about enforcing the regex

[a-zA-Z0-9][a-zA-Z0-9-]*

which I believe would be more restrictive than the docker-compose service name, and include only characters that can appear in valid host names.

@zmheiko
Copy link
Contributor

zmheiko commented Jun 7, 2025

What do people think about enforcing the regex

[a-zA-Z0-9][a-zA-Z0-9-]*

which I believe would be more restrictive than the docker-compose service name, and include only characters that can appear in valid host names.

I think that would probably work just fine. If we run into cases that require less restrictive names, we can always relax the constraints later without breaking anything.

@johnbcoughlin
Copy link
Contributor Author

@PasteurBot I have read the CLA Document and I hereby sign the CLA

@dionhaefner
Copy link
Contributor

Thanks @johnbcoughlin. That looks like a useful feature.

Is there a precedent for this feature in other applications / libraries (such as the Docker CLI, or docker-py)? I.e., is service-names a common name for this feature?

@johnbcoughlin
Copy link
Contributor Author

Is there a precedent for this feature in other applications / libraries (such as the Docker CLI, or docker-py)? I.e., is service-names a common name for this feature?

Yes, I believe "service" is a very common name for this concept. Docker CLI or docker-py isn't the right level of abstraction though, we want to look at container orchestrators.

In e.g. Kubernetes or docker-compose you would be providing these strings as part of a yaml config, so there is no direct analogue for the CLI argument name "service-names".

typer.Option(
"--service-names",
help=(
"Comma-separated list of service names by which each tesseract should be exposed "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Comma-separated list of service names by which each tesseract should be exposed "
"Comma-separated list of service names by which each Tesseract should be exposed "

help=(
"Comma-separated list of service names by which each tesseract should be exposed "
"in the shared network. "
"Tesseracts are reachable from one another at http://{service_name}:8000"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Tesseracts are reachable from one another at http://{service_name}:8000"
"Tesseracts are reachable from one another at http://{service_name}:8000. "
"Not supported when using --no-compose."

@@ -491,6 +502,11 @@ def serve(
else:
ports = None

if service_names is not None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's check here if --no-compose is given and error out with a helpful message.

if no_compose:
if len(images) > 1:
raise ValueError(
"Docker Compose is required to serve multiple Tesseracts. "
f"Currently attempting to serve `{len(images)}` Tesseracts."
)
if service_names is not None:
raise ValueError(
"Tesseract service names are only meaningful with Docker Compose."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Tesseract service names are only meaningful with Docker Compose."
"Tesseract service names can only be set when using Docker Compose."

if len(set(service_names)) != len(service_names):
raise ValueError("Service names must be unique")

print(service_names)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print(service_names)

print(service_names)
invalid_names = []
for name in service_names:
print(name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print(name)

if len(invalid_names) != 0:
raise ValueError(
"Service names must contain only alphanumeric characters and hyphens, and must "
f"not begin with a hyphen. Found invalid names: f{invalid_names}."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
f"not begin with a hyphen. Found invalid names: f{invalid_names}."
f"not begin with a hyphen. Found invalid names: {invalid_names}."

print(name)
if not re.match(r'^[A-Za-z0-9][A-Za-z0-9-]*$', name):
invalid_names.append(name)
if len(invalid_names) != 0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if len(invalid_names) != 0:
if invalid_names:

invalid_names = []
for name in service_names:
print(name)
if not re.match(r'^[A-Za-z0-9][A-Za-z0-9-]*$', name):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not even underscores?

Comment on lines +480 to +481
# I get a "tesseract_core has no attribute Tesseract" error from this??
#"import tesseract_core; tesseract_core.Tesseract.from_url(\"http://t-9000:8000\").health()"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is expected, see comment thread

Suggested change
# I get a "tesseract_core has no attribute Tesseract" error from this??
#"import tesseract_core; tesseract_core.Tesseract.from_url(\"http://t-9000:8000\").health()"

project_id = project_meta["project_id"]
project_containers = [project_meta["containers"][i]["name"] for i in range(2)]

T1 = docker_client.containers.get(project_containers[0])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use lower case for non-class variable names

Suggested change
T1 = docker_client.containers.get(project_containers[0])
tess_1 = docker_client.containers.get(project_containers[0])

except Exception as e:
assert False, e
finally:
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prevents cleanup from running!

Comment on lines +484 to +485
except Exception as e:
assert False, e
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to do anything, just let the exception bubble up.

Suggested change
except Exception as e:
assert False, e

Comment on lines +488 to +497
if project_id:
run_res = cli_runner.invoke(
app,
[
"teardown",
project_id,
],
catch_exceptions=False,
)
assert run_res.exit_code == 0, run_res.stderr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest you use the docker_cleanup fixture instead

Copy link
Contributor

@dionhaefner dionhaefner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, but overall this looks like a solid feature. Thanks @johnbcoughlin!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants