Skip to content

feat(huntsman)!: Replace endpoint's IP address with general hostname.#401

Merged
sitaowang1998 merged 7 commits into
y-scope:mainfrom
sitaowang1998:hostname
Jul 17, 2026
Merged

feat(huntsman)!: Replace endpoint's IP address with general hostname.#401
sitaowang1998 merged 7 commits into
y-scope:mainfrom
sitaowang1998:hostname

Conversation

@sitaowang1998

@sitaowang1998 sitaowang1998 commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Description

Note

This PR is a breaking change because it changes configuration.

Background

Spider's storage and scheduler use the same IP address for them to listen on and for other components to connect to them. However, for deployment in docker compose and helm, the network are split and each service's network typically expose a hostname for other service to connect to, which is different from the IP address inside the network where each server bind on.

Solution

This PR resolves #400 by separating IP address to bind on with the connecting hostname.

  • Adds a shared Host alias backed by NonEmptyString, representing a DNS name or an IP address.
  • Updates EndpointConfig to accept the Host, so all components use Host to connect to storage and scheduler.
  • Separates the scheduler's bind address from its advertised endpoint:
    • ServerConfig.host and port remain the socket bind address.
    • RuntimeConfig.advertised_endpoint identifies the scheduler endpoint registered with storage.
  • Propagates scheduler hostnames through the registration protobuf, core types, storage, and database.

Scheduler Configuration

This PR changes the shape of the scheduler's configuration. Below is an example of the configuration after change. host can be a DNS name, an IPv4 address of an IPv6 address surrounded by bracket (e.g. [::1]).

host: "0.0.0.0"
port: 50052

storage_endpoint:
  host: "spider-storage"
  port: 50051

connection_pool_size: 4

runtime:
  advertised_endpoint:
    host: "spider-scheduler"
    port: 50052

  scheduler: !round_robin
    active_job_queue_capacity: 64
    cleanup_ready_task_capacity: 256
    commit_ready_task_capacity: 256
    dispatch_queue_capacity: 64
    finalizing_job_expiration_timeout_sec: 300
    ready_task_capacity: 65536
    storage_poll_timeout_ms: 10
    tick_interval_ms: 5

  stop_timeout_sec: 30

  em_registry:
    dead_em_cutoff_sec: 60
    liveness_tracking_interval_ms: 1000

Checklist

  • The PR satisfies the contribution guidelines.
  • This is a breaking change and that has been indicated in the PR title, OR this isn't a
    breaking change.
  • Necessary docs have been updated, OR no docs need to be updated.

Validation performed

  • GitHub workflows pass.

Summary by CodeRabbit

  • New Features

    • Scheduler endpoints now support validated hostnames as well as IP addresses.
    • Scheduler configuration separates the advertised endpoint from the server’s listening address.
    • Scheduler registration and storage now preserve host and port information consistently.
  • Bug Fixes

    • Improved validation prevents empty scheduler hosts from being registered.
    • Scheduler replacement and persistence now work with DNS hostnames.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Scheduler endpoint addressing now supports validated hosts instead of IP-only values. Configuration separates gRPC binding from advertised scheduler endpoints, while protobuf, gRPC, runtime, storage APIs, MariaDB persistence, and tests use host-plus-port fields.

Changes

Scheduler host registration

Layer / File(s) Summary
Endpoint and scheduler address contracts
components/spider-utils/..., components/spider-core/src/types/scheduler.rs, components/spider-scheduler/src/config.rs
Adds the validated Host type, changes endpoint and registered scheduler shapes, and adds dedicated scheduler bind host and port configuration.
Protobuf host conversion and validation
components/spider-proto/storage/storage.proto, components/spider-proto-rust/...
Renames scheduler address fields to host and validates host values while converting protobuf requests and scheduler records.
Scheduler runtime and client wiring
components/spider-scheduler/src/runtime.rs, components/spider-scheduler/src/storage_client/..., components/spider-scheduler/src/bin/grpc_server.rs, components/spider-scheduler/src/core_impl/...
Registers the advertised endpoint through the host-based client API and binds the gRPC server using the separate server configuration.
Storage registration and persistence
components/spider-storage/src/..., components/spider-storage/tests/mariadb_test.rs
Propagates hosts through storage services, stores them in MariaDB, validates retrieved values, and updates mocks and integration tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SchedulerRuntime
  participant GrpcStorageClient
  participant StorageRegistrationService
  participant MariaDbStorageConnector

  SchedulerRuntime->>GrpcStorageClient: register advertised Host and port
  GrpcStorageClient->>StorageRegistrationService: send RegisterSchedulerRequest
  StorageRegistrationService->>MariaDbStorageConnector: register_scheduler host and port
  MariaDbStorageConnector-->>StorageRegistrationService: return SchedulerId
  StorageRegistrationService-->>GrpcStorageClient: return registration response
Loading

Possibly related PRs

  • y-scope/spider#347: Introduced the scheduler registration and storage paths later updated from ip_address to host.
  • y-scope/spider#362: Added the EndpointConfig host handling changed here to use validated hosts.
  • y-scope/spider#364: Added registration conversion paths updated here for Host values.

Suggested reviewers: linzhihao-723

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR updates endpoint/config types and scheduler storage paths to use Host, and separates bind and advertised addresses as required.
Out of Scope Changes check ✅ Passed The modified files all support hostname handling and scheduler endpoint separation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main breaking change: replacing endpoint IP addresses with a shared hostname/Host type.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sitaowang1998
sitaowang1998 marked this pull request as ready for review July 16, 2026 15:39
@sitaowang1998
sitaowang1998 requested a review from a team as a code owner July 16, 2026 15:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/spider-storage/src/db/mariadb.rs`:
- Around line 719-725: The scheduler schema change is not migrated for existing
databases, leaving the legacy address column incompatible with host-based
queries. Update the migration or initialization flow around the schedulers table
creation to transform existing tables from the address schema to the host
schema, while preserving correct creation for new databases; apply the change
consistently wherever SCHEDULERS_TABLE_NAME is created.

In `@components/spider-utils/src/config.rs`:
- Around line 71-73: Update Config::endpoint to format IPv6 hosts correctly:
when self.host contains a colon and is not already bracketed, surround it with
square brackets before appending the port; leave hostnames and already-bracketed
addresses unchanged. Ensure Endpoint::from_shared receives valid URIs such as
http://[::1]:50052.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: dc12d7bd-a4ab-40e8-93f8-667bbad4c190

📥 Commits

Reviewing files that changed from the base of the PR and between aaa824b and 016bf89.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • components/spider-proto-rust/src/generated/storage.rs is excluded by !**/generated/**
📒 Files selected for processing (19)
  • components/spider-core/src/types/scheduler.rs
  • components/spider-proto-rust/Cargo.toml
  • components/spider-proto-rust/src/scheduler_registration.rs
  • components/spider-proto-rust/src/unpack/storage.rs
  • components/spider-proto/storage/storage.proto
  • components/spider-scheduler/src/bin/grpc_server.rs
  • components/spider-scheduler/src/config.rs
  • components/spider-scheduler/src/core_impl/round_robin/tests.rs
  • components/spider-scheduler/src/runtime.rs
  • components/spider-scheduler/src/storage_client/grpc.rs
  • components/spider-scheduler/src/storage_client/mod.rs
  • components/spider-storage/src/db/mariadb.rs
  • components/spider-storage/src/db/protocol.rs
  • components/spider-storage/src/grpc.rs
  • components/spider-storage/src/state/service.rs
  • components/spider-storage/src/state/test_utils.rs
  • components/spider-storage/tests/mariadb_test.rs
  • components/spider-utils/Cargo.toml
  • components/spider-utils/src/config.rs

Comment on lines 719 to 725
r"
CREATE TABLE IF NOT EXISTS `{SCHEDULERS_TABLE_NAME}` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`ip_address` VARCHAR(45) NOT NULL,
`host` VARCHAR(255) NOT NULL,
`port` SMALLINT UNSIGNED NOT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check for existing database migration mechanisms.

fd -e sql
fd -t d migrations
rg -i 'ALTER TABLE'

Repository: y-scope/spider

Length of output: 152


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## repo files related to migrations\n'
fd -a -t d migrations . || true
fd -a -e sql . || true

printf '\n## search for migration keywords\n'
rg -n -i 'ALTER TABLE|CREATE TABLE IF NOT EXISTS|migrat' . || true

printf '\n## inspect mariadb.rs around scheduler schema and queries\n'
sed -n '680,790p' components/spider-storage/src/db/mariadb.rs

printf '\n## inspect any migration-related files if present\n'
git ls-files | rg 'migrat|schema|sql$'

Repository: y-scope/spider

Length of output: 8373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## scheduler table definitions across repo\n'
rg -n 'schedulers|SCHEDULERS_TABLE_NAME|scheduler' components/spider-storage/src/db/mariadb.rs src/spider/storage/mysql/mysql_stmt.hpp tools/scripts/mariadb/wolf/init_db.py || true

printf '\n## search for upgrade/version/migration docs\n'
rg -n -i 'upgrade|migration|migrate|schema version|breaking change|database' README* docs .github components src tools || true

printf '\n## inspect MySQL init script around scheduler table\n'
sed -n '1,80p' tools/scripts/mariadb/wolf/init_db.py

printf '\n## inspect mysql stmt scheduler creation\n'
sed -n '1,40p' src/spider/storage/mysql/mysql_stmt.hpp

Repository: y-scope/spider

Length of output: 21596


Add a schedulers table migration
CREATE TABLE IF NOT EXISTS won’t alter existing tables, so upgraded databases will keep the old address schema and the new host queries will fail. Update the schema everywhere this table is created, or document that this release requires a fresh database.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/spider-storage/src/db/mariadb.rs` around lines 719 - 725, The
scheduler schema change is not migrated for existing databases, leaving the
legacy address column incompatible with host-based queries. Update the migration
or initialization flow around the schedulers table creation to transform
existing tables from the address schema to the host schema, while preserving
correct creation for new databases; apply the change consistently wherever
SCHEDULERS_TABLE_NAME is created.

Comment on lines 71 to 73
pub fn endpoint(&self) -> Result<Endpoint, tonic::transport::Error> {
Endpoint::from_shared(format!("http://{}", self.socket_addr()))
Endpoint::from_shared(format!("http://{}:{}", self.host, self.port))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Format IPv6 addresses correctly in the endpoint URI.

If self.host contains a raw IPv6 address (e.g., ::1), formatting it directly into the URI will produce an invalid URI string (e.g., http://::1:50052) that will fail to parse. In standard URI formatting, IPv6 addresses must be enclosed in square brackets (e.g., http://[::1]:50052).

Since self.host is a NonEmptyString allowing both hostnames and IPs, you can safely wrap it in brackets if it contains a colon and isn't already bracketed.

🐛 Proposed fix
     pub fn endpoint(&self) -> Result<Endpoint, tonic::transport::Error> {
-        Endpoint::from_shared(format!("http://{}:{}", self.host, self.port))
+        let host_str = self.host.as_str();
+        let formatted_host = if host_str.contains(':') && !host_str.starts_with('[') {
+            format!("[{}]", host_str)
+        } else {
+            host_str.to_owned()
+        };
+        Endpoint::from_shared(format!("http://{}:{}", formatted_host, self.port))
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn endpoint(&self) -> Result<Endpoint, tonic::transport::Error> {
Endpoint::from_shared(format!("http://{}", self.socket_addr()))
Endpoint::from_shared(format!("http://{}:{}", self.host, self.port))
}
pub fn endpoint(&self) -> Result<Endpoint, tonic::transport::Error> {
let host_str = self.host.as_str();
let formatted_host = if host_str.contains(':') && !host_str.starts_with('[') {
format!("[{}]", host_str)
} else {
host_str.to_owned()
};
Endpoint::from_shared(format!("http://{}:{}", formatted_host, self.port))
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/spider-utils/src/config.rs` around lines 71 - 73, Update
Config::endpoint to format IPv6 hosts correctly: when self.host contains a colon
and is not already bracketed, surround it with square brackets before appending
the port; leave hostnames and already-bracketed addresses unchanged. Ensure
Endpoint::from_shared receives valid URIs such as http://[::1]:50052.

@20001020ycx 20001020ycx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Before diving into the code details, I wonder what is the new configuration looks like (do we need http attached to url? is this backward compatible with ipv4 or ipv6)? Can you show how this breaking change would affect user's configuration e2e?

@sitaowang1998

Copy link
Copy Markdown
Collaborator Author

Before diving into the code details, I wonder what is the new configuration looks like (do we need http attached to url? is this backward compatible with ipv4 or ipv6)? Can you show how this breaking change would affect user's configuration e2e?

Below is a configuration example. All host can be either a hostname, an IPv4 or an IPv6 with bracket around it (e.g. [::1]). The top level port and the advertised_endpoint port don't need to match, depend on how helm routes the traffic.

host: "0.0.0.0"
port: 50052

storage_endpoint:
  host: "spider-storage"
  port: 50051

connection_pool_size: 4

runtime:
  advertised_endpoint:
    host: "spider-scheduler"
    port: 50052

  scheduler: !round_robin
    active_job_queue_capacity: 64
    cleanup_ready_task_capacity: 256
    commit_ready_task_capacity: 256
    dispatch_queue_capacity: 64
    finalizing_job_expiration_timeout_sec: 300
    ready_task_capacity: 65536
    storage_poll_timeout_ms: 10
    tick_interval_ms: 5

  stop_timeout_sec: 30

  em_registry:
    dead_em_cutoff_sec: 60
    liveness_tracking_interval_ms: 1000

@20001020ycx

Copy link
Copy Markdown
Contributor

Before diving into the code details, I wonder what is the new configuration looks like (do we need http attached to url? is this backward compatible with ipv4 or ipv6)? Can you show how this breaking change would affect user's configuration e2e?

Below is a configuration example. All host can be either a hostname, an IPv4 or an IPv6 with bracket around it (e.g. [::1]). The top level port and the advertised_endpoint port don't need to match, depend on how helm routes the traffic.

host: "0.0.0.0"
port: 50052

storage_endpoint:
  host: "spider-storage"
  port: 50051

connection_pool_size: 4

runtime:
  advertised_endpoint:
    host: "spider-scheduler"
    port: 50052

  scheduler: !round_robin
    active_job_queue_capacity: 64
    cleanup_ready_task_capacity: 256
    commit_ready_task_capacity: 256
    dispatch_queue_capacity: 64
    finalizing_job_expiration_timeout_sec: 300
    ready_task_capacity: 65536
    storage_poll_timeout_ms: 10
    tick_interval_ms: 5

  stop_timeout_sec: 30

  em_registry:
    dead_em_cutoff_sec: 60
    liveness_tracking_interval_ms: 1000

Thanks for the explanation, can we also include this in the PR so the reference is more clear (unless this violate any policies you guys have in Spider).

Also a few questions:

  1. Why do we need host in the configuration? Seems conflicting with the config advertised_endpoint
  2. Why quotation mark around "spider-scheduler"? I assume spider-scheduler is the URL name, and we are omitting the http:// right? @LinZhihao-723 are you okay with this?

@20001020ycx 20001020ycx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the work and get the PR out so promptly. I don't have too much comment on the actual code itself as most of them are straight update from ip to host, but I do have a few question regarding the failure model in networking:

While reviewing https://github.com/y-scope/spider/pull/401/changes#r3600548223, I am actually wondering about the failure cases. I think it would be best if we have unit test to cover the failure cases unless we can already clearly define the behavior themselves:

  1. Invalid ipv4/ipv6 IP
  2. DNS fails to resolve a domain name

I think what I am looking for is the e2e example of how an IP/host is passed through the configs, and our handle when the input are invalid (as I often config them incorrectly at the first few attempts).

.ip_address
.parse::<IpAddr>()
.map_err(|error| invalid_argument(format!("invalid IP address: {error}")))?;
let host = Host::new(self.host)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What if DNS failed to resolve the URL. This failure is often seen in the networking, and we should be able to deal with the gracefully

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

If the DNS name is syntactically wrong, then in EndpointConfig::endpoint function, the Endpoint::from_shared will return an error, and an error will be logged in the respecting grpc_server.rs and the binary exits with an error.

If the DNS name is syntactically correct, and not an existing address that is connectable, then the gRPC client's connect will return an error, an error will be logged and the binary exits with an error.

I am not sure if these are graceful enough in your opinion.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Its good that you are thinking about this, but maybe its my problem that I dont really understand what exactly is this "error" you are talking about.

can you tell me say if I input an invalid ip or user name, what would the user see?

I am okay if you think what you presented to user can clearly show what is the root cause.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

For example, if user set an invalid storage hostname in scheduler config, scheduler will fail with "Failed to parse storage endpoint." or "Failed to connect to storage gRPC service.". It also includes the message from underlying library.

let ip_address = self
.ip_address
.parse::<IpAddr>()
.map_err(|error| invalid_argument(format!("invalid IP address: {error}")))?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So what if the input is an IP, and the IP is invalid, would deleting the check cause any regression

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

There is no good way to tell if it is an invalid IP or just not an IP address. I think the best way is to leave it to the other functions mentioned in #401 (comment) to handle the failure.

@20001020ycx 20001020ycx Jul 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

really? isnt that you are doing this in the deleted line?

Say user inputs 256.256.256.256, this is not a valid IP right? We can easily parse it (of course though some lib) and let the user knows

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Previously, it can only be an IPv4, Thus, we know for sure if it is not a valid IPv4 address, it is invalid. The AddrParseError is not specific enough to inform us if it is a invalid IP address out of range, or just something totally does not look like an IP address.

@sitaowang1998

Copy link
Copy Markdown
Collaborator Author

Thanks for the explanation, can we also include this in the PR so the reference is more clear (unless this violate any policies you guys have in Spider).

Also a few questions:

  1. Why do we need host in the configuration? Seems conflicting with the config advertised_endpoint

  2. Why quotation mark around "spider-scheduler"? I assume spider-scheduler is the URL name, and we are omitting the http:// right? @LinZhihao-723 are you okay with this?

Thanks for the suggestion. I have added the example in the PR description.

For your questions:

  1. The top level host is the address that scheduler's gRPC server binds on. It is likely to always be 0.0.0.0, but leaving it to be configurable could be useful later. The advertised_endpoint is the address that is going to be saved in database for execution managers to connect to (or passed to the em by config).
  2. This is a good question. For now, host only refers to the host name, not the full URL, which will contains port in itself. The http:// will be added in rust code when a client is trying to make connection. If there is possibility that we will have other schema than http://, then we should replace the whole EndpointConfig with a URL string.

@20001020ycx

Copy link
Copy Markdown
Contributor

It is likely to always be 0.0.0.0, but leaving it to be configurable could be useful later.

Agreed with all other points except this one, wouldn't this confuse user?

@sitaowang1998

Copy link
Copy Markdown
Collaborator Author

It is likely to always be 0.0.0.0, but leaving it to be configurable could be useful later.

Agreed with all other points except this one, wouldn't this confuse user?

We can provide 0.0.0.0 as the default value, and it should be clear what this host is expecting. We can even not expose it as a configurable value in helm.

The final solution should be a user doc that explains all the configuration, but we don't have time for it now.

@sitaowang1998
sitaowang1998 merged commit 14af9bd into y-scope:main Jul 17, 2026
16 checks passed
@sitaowang1998
sitaowang1998 deleted the hostname branch July 17, 2026 16:41
LinZhihao-723 added a commit to LinZhihao-723/spider that referenced this pull request Jul 19, 2026
…ost/port for the y-scope#401 config schema.

PR y-scope#401 split the scheduler's gRPC listen address (now top-level `host`/`port` on ServerConfig, like the storage service) from the endpoint it advertises to storage during registration (`runtime.advertised_endpoint`). The generator still emitted the pre-y-scope#401 flat `runtime.host`/`runtime.port`, so the scheduler failed to parse its config ("missing field advertised_endpoint", then "missing field host"). Emit both shapes from `scheduler_endpoint`.
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.

Allow scheduler config to accept hostnames instead of raw IP addresses

3 participants