Skip to content

Commit 3f58fbb

Browse files
authored
Release candidate: v1.91.0 (#5651)
2 parents 8fb2919 + c5e27e9 commit 3f58fbb

142 files changed

Lines changed: 5927 additions & 1773 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/cache-metadata.yml

Lines changed: 0 additions & 65 deletions
This file was deleted.

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,16 @@ it is not supported for Toolkit development due to GNU specific shell scripts.
439439
If developing on a mac, a workaround is to install GNU tooling by installing
440440
`coreutils` and `findutils` from a package manager such as homebrew or conda.
441441
442+
## Privacy notice
443+
444+
To help improve Cluster Toolkit, feature usage statistics are collected and sent to Google. You can opt-out at any time by executing the following command:
445+
446+
```shell
447+
./gcluster telemetry off
448+
```
449+
450+
Cluster Toolkit telemetry overall is handled in accordance with the [Google Privacy Policy](https://policies.google.com/privacy). When you use Cluster Toolkit to interact with or utilize GCP Services, your information is handled in accordance with the [Google Cloud Privacy Notice](https://cloud.google.com/terms/cloud-privacy-notice).
451+
442452
### Contributing
443453
444454
Please refer to the [contributing file](CONTRIBUTING.md) in our GitHub

cmd/job/prereq.go

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,42 @@ func checkK8sDependencies(state *PrereqState, missing *[]missingPrereq) {
188188
}
189189
}
190190

191+
// isDockerCredsConfigured checks if Docker is configured to use gcloud credentials for the required registries.
192+
func isDockerCredsConfigured(region string) bool {
193+
configDir := os.Getenv("DOCKER_CONFIG")
194+
if configDir == "" {
195+
homeDir, err := os.UserHomeDir()
196+
if err != nil {
197+
return false
198+
}
199+
configDir = filepath.Join(homeDir, ".docker")
200+
}
201+
configPath := filepath.Join(configDir, "config.json")
202+
data, err := os.ReadFile(configPath)
203+
if err != nil {
204+
return false
205+
}
206+
207+
var config struct {
208+
CredHelpers map[string]string `json:"credHelpers"`
209+
CredsStore string `json:"credsStore"`
210+
}
211+
if err := json.Unmarshal(data, &config); err != nil {
212+
logging.Error("Failed to unmarshal Docker config from %s: %v", configPath, err)
213+
return false
214+
}
215+
216+
if config.CredsStore == "gcloud" {
217+
return true
218+
}
219+
220+
if config.CredHelpers["gcr.io"] != "gcloud" {
221+
return false
222+
}
223+
pkgDevReg := fmt.Sprintf("%s-docker.pkg.dev", region)
224+
return config.CredHelpers[pkgDevReg] == "gcloud"
225+
}
226+
191227
// EnsurePrerequisites checks all necessary gcloud and kubectl prerequisites.
192228
func ensurePrerequisites(cmd *cobra.Command, projectID *string, location string) error {
193229
if dryRunManifest != "" {
@@ -228,15 +264,17 @@ func ensurePrerequisites(cmd *cobra.Command, projectID *string, location string)
228264
checkK8sDependencies(&state, &missing)
229265

230266
// Check Docker creds
231-
if !state.DockerCredsConfigured {
232-
region := shell.ExtractRegion(location)
267+
region := shell.ExtractRegion(location)
268+
if !isDockerCredsConfigured(region) {
269+
cmds := []string{"gcloud auth configure-docker gcr.io --quiet"}
270+
if region != "" {
271+
cmds = append(cmds, fmt.Sprintf("gcloud auth configure-docker %s-docker.pkg.dev --quiet", region))
272+
}
233273
missing = append(missing, missingPrereq{
234-
name: "Docker Credentials",
235-
commands: []string{
236-
"gcloud auth configure-docker gcr.io --quiet",
237-
fmt.Sprintf("gcloud auth configure-docker %s-docker.pkg.dev --quiet", region),
238-
},
274+
name: "Docker Credentials",
275+
commands: cmds,
239276
})
277+
} else {
240278
state.DockerCredsConfigured = true
241279
}
242280

cmd/job/prereq_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,105 @@ func TestEnsureApplicationDefaultCredentials_Failure(t *testing.T) {
320320
}
321321
}
322322

323+
func TestIsDockerCredsConfigured(t *testing.T) {
324+
tempDir := t.TempDir()
325+
t.Setenv("HOME", tempDir)
326+
327+
dockerDir := filepath.Join(tempDir, ".docker")
328+
configPath := filepath.Join(dockerDir, "config.json")
329+
330+
tests := []struct {
331+
name string
332+
configContent string
333+
shouldExist bool
334+
region string
335+
expected bool
336+
}{
337+
{
338+
name: "No config file",
339+
shouldExist: false,
340+
region: "us-central1",
341+
expected: false,
342+
},
343+
{
344+
name: "Corrupted config file",
345+
configContent: "invalid",
346+
shouldExist: true,
347+
region: "us-central1",
348+
expected: false,
349+
},
350+
{
351+
name: "CredsStore set to gcloud",
352+
configContent: `{"credsStore": "gcloud"}`,
353+
shouldExist: true,
354+
region: "us-central1",
355+
expected: true,
356+
},
357+
{
358+
name: "CredHelpers configured correctly",
359+
configContent: `{"credHelpers": {"gcr.io": "gcloud", "us-central1-docker.pkg.dev": "gcloud"}}`,
360+
shouldExist: true,
361+
region: "us-central1",
362+
expected: true,
363+
},
364+
{
365+
name: "CredHelpers missing gcr.io",
366+
configContent: `{"credHelpers": {"us-central1-docker.pkg.dev": "gcloud"}}`,
367+
shouldExist: true,
368+
region: "us-central1",
369+
expected: false,
370+
},
371+
{
372+
name: "CredHelpers missing regional registry",
373+
configContent: `{"credHelpers": {"gcr.io": "gcloud"}}`,
374+
shouldExist: true,
375+
region: "us-central1",
376+
expected: false,
377+
},
378+
{
379+
name: "CredHelpers wrong helper",
380+
configContent: `{"credHelpers": {"gcr.io": "desktop", "us-central1-docker.pkg.dev": "gcloud"}}`,
381+
shouldExist: true,
382+
region: "us-central1",
383+
expected: false,
384+
},
385+
}
386+
387+
for _, tt := range tests {
388+
t.Run(tt.name, func(t *testing.T) {
389+
if !tt.shouldExist {
390+
os.Remove(configPath)
391+
} else {
392+
if err := os.MkdirAll(dockerDir, 0755); err != nil {
393+
t.Fatal(err)
394+
}
395+
if err := os.WriteFile(configPath, []byte(tt.configContent), 0644); err != nil {
396+
t.Fatal(err)
397+
}
398+
}
399+
400+
got := isDockerCredsConfigured(tt.region)
401+
if got != tt.expected {
402+
t.Errorf("isDockerCredsConfigured() = %v, want %v", got, tt.expected)
403+
}
404+
})
405+
}
406+
}
407+
323408
func TestEnsurePrerequisites_DockerCreds(t *testing.T) {
409+
tempDir := t.TempDir()
410+
t.Setenv("HOME", tempDir)
411+
324412
origExecuteCommand := shell.ExecuteCommand
325413
defer func() { shell.ExecuteCommand = origExecuteCommand }()
326414

327415
shell.ExecuteCommand = func(name string, args ...string) shell.CommandResult {
416+
if name == "gcloud" && len(args) > 1 && args[0] == "auth" && args[1] == "list" {
417+
return shell.CommandResult{ExitCode: 0, Stdout: "user@example.com"}
418+
}
419+
if name == "gcloud" && len(args) > 1 && args[0] == "services" && args[1] == "list" {
420+
return shell.CommandResult{ExitCode: 0, Stdout: "artifactregistry.googleapis.com"}
421+
}
328422
return shell.CommandResult{ExitCode: 0}
329423
}
330424

cmd/job/submit.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package job
1616

1717
import (
1818
"fmt"
19+
"hpc-toolkit/pkg/config"
1920
"os"
2021
"slices"
2122
"time"
@@ -28,21 +29,23 @@ import (
2829
)
2930

3031
var (
31-
imageName string
32-
baseImage string
33-
buildContext string
34-
commandToRun string
35-
acceleratorType string
36-
dryRunManifest string
32+
imageName string
33+
baseImage string
34+
buildContext string
35+
commandToRun string
36+
computeType string
37+
dryRunManifest string
3738

3839
workloadName string
3940
kueueQueueName string
40-
numSlicesOrNodes int
41-
vmsPerSlice int
42-
maxRestarts int
41+
numNodes int
42+
numSlices int
43+
restarts int
4344
ttlAfterFinished string
4445
gracePeriodStr string
4546

47+
gkeDisableParallelContainers bool
48+
4649
placementPolicy string
4750
nodeConstraint map[string]string
4851

@@ -104,17 +107,18 @@ func init() {
104107
SubmitCmd.Flags().StringVarP(&baseImage, "base-image", "B", "", "Name of the base image for Crane to build upon (e.g., python:3.9-slim). Requires --build-context.")
105108
SubmitCmd.Flags().StringVarP(&buildContext, "build-context", "b", "", "Path to the build context directory for Crane (e.g., .). Required with --base-image.")
106109
SubmitCmd.Flags().StringVarP(&commandToRun, "command", "e", "", "Command to execute in the container (e.g., 'python train.py'). Required.")
107-
SubmitCmd.Flags().StringVarP(&acceleratorType, "accelerator", "a", "", "Type of accelerator to request (e.g., 'nvidia-tesla-a100').")
110+
SubmitCmd.Flags().StringVar(&computeType, "compute-type", "", "Type of compute to request (e.g., 'n2-standard-32', 'nvidia-l4', 'v6e-8').")
108111
SubmitCmd.Flags().StringVarP(&dryRunManifest, "dry-run-out", "o", "", "Path to output the generated Kubernetes manifest instead of applying it.")
109112
SubmitCmd.Flags().StringVarP(&platform, "platform", "f", "linux/amd64", "Target platform for the image build (e.g., 'linux/amd64', 'linux/arm64'). Used with --base-image.")
110113

111114
SubmitCmd.Flags().StringVarP(&workloadName, "name", "n", "", "Name of the workload to create. Required.")
112115
SubmitCmd.Flags().StringVarP(&kueueQueueName, "queue", "q", "", "Name of the Kueue LocalQueue to submit the workload to. If empty, it will be auto-discovered.")
113-
SubmitCmd.Flags().IntVar(&numSlicesOrNodes, "nodes", 1, "Number of JobSet replicas (or Slices for TPUs).")
114-
SubmitCmd.Flags().IntVar(&vmsPerSlice, "vms-per-slice", 0, "Number of VMs (pods) per slice. Defaults to auto-calculated value for TPUs.")
115-
SubmitCmd.Flags().IntVar(&maxRestarts, "max-restarts", 1, "Maximum number of restarts for the JobSet before failing.")
116+
SubmitCmd.Flags().IntVar(&numNodes, "num-nodes", 1, "The number of nodes to use per group/slice. Defaults to 1 for CPU/GPU, or auto-calculated for TPUs.")
117+
SubmitCmd.Flags().IntVar(&numSlices, "num-slices", 1, "The number of independent groups/slices to use.")
118+
SubmitCmd.Flags().IntVar(&restarts, "restarts", 1, "Maximum number of restarts for the JobSet before failing.")
116119
SubmitCmd.Flags().StringVar(&ttlAfterFinished, "gke-ttl-after-finished", "1h", "Time to retain the JobSet after it finishes (e.g. 5m, 1h).")
117120
SubmitCmd.Flags().StringVar(&gracePeriodStr, "grace-period", "30s", "Time to wait before forcefully terminating a pod (e.g. 30s, 2m). Gives the workload time to save checkpoints or clean up distributed state during cancellation or preemption events (like Spot VM evictions).")
121+
SubmitCmd.Flags().BoolVar(&gkeDisableParallelContainers, "gke-disable-parallel-containers", false, "Disable parallel containers for TPU v7/v7x on GKE.")
118122

119123
SubmitCmd.Flags().StringVar(&placementPolicy, "placement-policy", "", "Name of the GKE placement policy to use.")
120124
SubmitCmd.Flags().StringToStringVar(&nodeConstraint, "node-constraint", nil, "Key=value pairs for node labels to target specific nodes. Maps to nodeSelector in GKE, and to SLURM's --constraint.")
@@ -147,10 +151,13 @@ func init() {
147151

148152
_ = SubmitCmd.MarkFlagRequired("command")
149153
_ = SubmitCmd.MarkFlagRequired("name")
150-
_ = SubmitCmd.MarkFlagRequired("accelerator")
154+
_ = SubmitCmd.MarkFlagRequired("compute-type")
151155
}
152156

153157
func runSubmitCmd(cmd *cobra.Command, args []string) error {
158+
if err := config.ValidateHardwareRequest(computeType, topology, placementPolicy); err != nil {
159+
return err
160+
}
154161
ttlSeconds, err := parseDurationToSeconds(ttlAfterFinished, "--gke-ttl-after-finished")
155162
if err != nil {
156163
return err
@@ -175,22 +182,26 @@ func runSubmitCmd(cmd *cobra.Command, args []string) error {
175182
awaitJobCompletion = true
176183
}
177184

185+
if config.IsTPU(computeType) && cmd.Flags().Changed("num-nodes") {
186+
return fmt.Errorf("--num-nodes cannot be used with TPU jobs (it is calculated automatically from topology)")
187+
}
188+
178189
jobDef := orchestrator.JobDefinition{
179190
ImageName: imageName,
180191
BaseImage: baseImage,
181192
BuildContext: buildContext,
182193
Platform: platform,
183194
CommandToRun: commandToRun,
184-
AcceleratorType: acceleratorType,
195+
ComputeType: computeType,
185196
DryRunManifest: dryRunManifest,
186197
ProjectID: projectID,
187198
ClusterName: clusterName,
188199
ClusterLocation: location,
189200
WorkloadName: workloadName,
190201
KueueQueueName: kueueQueueName,
191-
NumSlices: numSlicesOrNodes,
192-
VmsPerSlice: vmsPerSlice,
193-
MaxRestarts: maxRestarts,
202+
NumSlices: numSlices,
203+
NodesPerSlice: numNodes,
204+
MaxRestarts: restarts,
194205
TtlSecondsAfterFinished: ttlSeconds,
195206
TerminationGracePeriodSeconds: gracePeriodSeconds,
196207
PlacementPolicy: placementPolicy,
@@ -202,6 +213,7 @@ func runSubmitCmd(cmd *cobra.Command, args []string) error {
202213
Topology: topology,
203214
GKEScheduler: gkeScheduler,
204215
AwaitJobCompletion: awaitJobCompletion,
216+
UseParallelContainers: !gkeDisableParallelContainers,
205217
Timeout: timeoutStr,
206218
PriorityClassName: priorityClassName,
207219
IsPathwaysJob: isPathwaysJob,

0 commit comments

Comments
 (0)