Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package main | |
| 6 | |
| 7 /* | |
| 8 Generate the tasks.json file. | |
| 9 */ | |
| 10 | |
| 11 import ( | |
| 12 "bytes" | |
| 13 "encoding/json" | |
| 14 "fmt" | |
| 15 "io/ioutil" | |
| 16 "os" | |
| 17 "path" | |
| 18 "path/filepath" | |
| 19 "strings" | |
| 20 | |
| 21 "github.com/skia-dev/glog" | |
| 22 "go.skia.org/infra/go/common" | |
| 23 "go.skia.org/infra/go/util" | |
| 24 "go.skia.org/infra/task_scheduler/go/specs" | |
| 25 ) | |
| 26 | |
| 27 const ( | |
| 28 DEFAULT_OS = "Ubuntu" | |
| 29 | |
| 30 // Pool for Skia bots. | |
| 31 POOL_SKIA = "Skia" | |
| 32 | |
| 33 // Name prefix for upload jobs. | |
| 34 PREFIX_UPLOAD = "Upload" | |
| 35 ) | |
| 36 | |
| 37 var ( | |
| 38 // "Constants" | |
| 39 | |
| 40 // Top-level list of all jobs to run at each commit. | |
| 41 JOBS = []string{ | |
| 42 "Build-Ubuntu-GCC-x86_64-Release-GN", | |
| 43 "Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-GN", | |
| 44 "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-GN", | |
| 45 } | |
| 46 | |
| 47 // UPLOAD_DIMENSIONS are the Swarming dimensions for upload tasks. | |
| 48 UPLOAD_DIMENSIONS = []string{ | |
| 49 fmt.Sprintf("pool:%s", POOL_SKIA), | |
| 50 "os:Ubuntu", | |
| 51 "gpu:none", | |
| 52 "cpu:x86-64-avx2", | |
| 53 } | |
| 54 | |
| 55 // Defines the structure of job names. | |
| 56 jobNameSchema *JobNameSchema | |
| 57 | |
| 58 // Caches CIPD package info so that we don't have to re-read VERSION | |
| 59 // files. | |
| 60 cipdPackages = map[string]*specs.CipdPackage{} | |
| 61 | |
| 62 // Path to the infra/bots directory. | |
| 63 infrabotsDir = "" | |
| 64 ) | |
| 65 | |
| 66 // deriveCompileTaskName returns the name of a compile task based on the given | |
| 67 // job name. | |
| 68 func deriveCompileTaskName(jobName string, parts map[string]string) string { | |
| 69 if parts["role"] == "Housekeeper" { | |
| 70 return "Build-Ubuntu-GCC-x86_64-Release-Shared" | |
| 71 } else if parts["role"] == "Test" || parts["role"] == "Perf" { | |
| 72 task_os := parts["os"] | |
| 73 ec := parts["extra_config"] | |
| 74 if task_os == "Android" { | |
|
dogben
2016/09/30 17:35:14
Did you intend to set task_os in this if?
borenet
2016/09/30 19:13:16
Yep, thanks!
| |
| 75 if ec == "Vulkan" { | |
| 76 ec = "Android_Vulkan" | |
| 77 } else if !strings.Contains(ec, "GN_Android") { | |
| 78 ec = task_os | |
| 79 } | |
| 80 } else if task_os == "iOS" { | |
| 81 ec = task_os | |
| 82 task_os = "Mac" | |
| 83 } else if strings.Contains(task_os, "Win") { | |
| 84 task_os = "Win" | |
| 85 } | |
| 86 name, err := jobNameSchema.MakeJobName(map[string]string{ | |
| 87 "role": "Build", | |
| 88 "os": task_os, | |
| 89 "compiler": parts["compiler"], | |
| 90 "target_arch": parts["arch"], | |
| 91 "configuration": parts["configuration"], | |
| 92 "extra_config": ec, | |
| 93 }) | |
| 94 if err != nil { | |
| 95 glog.Fatal(err) | |
| 96 } | |
| 97 return name | |
| 98 } else { | |
| 99 return jobName | |
| 100 } | |
| 101 } | |
| 102 | |
| 103 // swarmDimensions generates swarming bot dimensions for the given task. | |
| 104 func swarmDimensions(parts map[string]string) []string { | |
| 105 if parts["extra_config"] == "SkiaCT" { | |
| 106 return []string{ | |
| 107 "pool:SkiaCT", | |
| 108 } | |
| 109 } | |
| 110 d := map[string]string{ | |
| 111 "pool": "Skia", | |
|
dogben
2016/09/30 17:35:14
nit: fmt.Sprintf("pool:%s", POOL_SKIA)
(Although
borenet
2016/09/30 19:13:16
Done-ish. Kept POOL_SKIA since it's used in multip
| |
| 112 } | |
| 113 if os, ok := parts["os"]; ok { | |
| 114 d["os"] = os | |
| 115 } else { | |
| 116 d["os"] = DEFAULT_OS | |
| 117 } | |
| 118 if strings.Contains(d["os"], "Win") { | |
| 119 d["os"] = "Windows" | |
| 120 } | |
| 121 if parts["role"] == "Test" || parts["role"] == "Perf" { | |
| 122 if strings.Contains(parts["os"], "Android") { | |
| 123 // For Android, the device type is a better dimension | |
| 124 // than CPU or GPU. | |
| 125 d["device_type"] = map[string]string{ | |
| 126 "AndroidOne": "sprout", | |
| 127 "GalaxyS3": "m0", // "smdk4x12", Detected i ncorrectly by swarming? | |
| 128 "GalaxyS4": "", // TODO(borenet,kjlubick) | |
|
dogben
2016/09/30 17:35:14
In swarm_trigger.py, this is None, which I'm guess
borenet
2016/09/30 19:13:16
Yeah, I'm not sure what the behavior is in either
| |
| 129 "GalaxyS7": "heroqlteatt", | |
| 130 "NVIDIA_Shield": "foster", | |
| 131 "Nexus10": "manta", | |
| 132 "Nexus5": "hammerhead", | |
| 133 "Nexus6": "shamu", | |
| 134 "Nexus6p": "angler", | |
| 135 "Nexus7": "grouper", | |
| 136 "Nexus7v2": "flo", | |
| 137 "Nexus9": "flounder", | |
| 138 "NexusPlayer": "fugu", | |
| 139 }[parts["model"]] | |
| 140 } else if strings.Contains(parts["os"], "iOS") { | |
| 141 d["device"] = map[string]string{ | |
| 142 "iPad4": "iPad4,1", | |
| 143 }[parts["model"]] | |
| 144 // TODO(borenet): Replace this hack with something | |
| 145 // better. | |
| 146 d["os"] = "iOS-9.2" | |
| 147 } else if parts["cpu_or_gpu"] == "CPU" { | |
| 148 d["gpu"] = "none" | |
| 149 d["cpu"] = map[string]string{ | |
| 150 "AVX": "x86-64", | |
| 151 "AVX2": "x86-64-avx2", | |
| 152 "SSE4": "x86-64", | |
| 153 }[parts["cpu_or_gpu_value"]] | |
| 154 if strings.Contains(parts["os"], "Win") && parts["cpu_or _gpu_value"] == "AVX2" { | |
| 155 // AVX2 is not correctly detected on Windows. Fa ll back on other | |
| 156 // dimensions to ensure that we correctly target machines which we know | |
| 157 // have AVX2 support. | |
| 158 d["cpu"] = "x86-64" | |
| 159 d["os"] = "Windows-2008ServerR2-SP1" | |
| 160 } | |
| 161 } else { | |
| 162 d["gpu"] = map[string]string{ | |
| 163 "GeForce320M": "10de:08a4", | |
| 164 "GT610": "10de:104a", | |
| 165 "GTX550Ti": "10de:1244", | |
| 166 "GTX660": "10de:11c0", | |
| 167 "GTX960": "10de:1401", | |
| 168 "HD4000": "8086:0a2e", | |
| 169 "HD4600": "8086:0412", | |
| 170 "HD7770": "1002:683d", | |
| 171 "iHD530": "8086:1912", | |
| 172 }[parts["cpu_or_gpu_value"]] | |
| 173 } | |
| 174 } else { | |
| 175 d["gpu"] = "none" | |
| 176 } | |
| 177 rv := make([]string, 0, len(d)) | |
| 178 for k, v := range d { | |
| 179 rv = append(rv, fmt.Sprintf("%s:%s", k, v)) | |
| 180 } | |
| 181 return rv | |
|
dogben
2016/09/30 17:35:14
Sort?
borenet
2016/09/30 19:13:16
Done.
| |
| 182 } | |
| 183 | |
| 184 // getCipdPackage finds and returns the given CIPD package and version. | |
| 185 func getCipdPackage(assetName string) *specs.CipdPackage { | |
| 186 if pkg, ok := cipdPackages[assetName]; ok { | |
| 187 return pkg | |
| 188 } | |
| 189 versionFile := path.Join(infrabotsDir, "assets", assetName, "VERSION") | |
| 190 contents, err := ioutil.ReadFile(versionFile) | |
| 191 if err != nil { | |
| 192 glog.Fatal(err) | |
| 193 } | |
| 194 version := strings.TrimSpace(string(contents)) | |
| 195 pkg := &specs.CipdPackage{ | |
| 196 Name: fmt.Sprintf("skia/bots/%s", assetName), | |
| 197 Path: assetName, | |
| 198 Version: fmt.Sprintf("version:%s", version), | |
| 199 } | |
| 200 if assetName == "win_toolchain" { | |
| 201 pkg.Path = "t" // Workaround for path length limit on Windows. | |
| 202 } | |
| 203 cipdPackages[assetName] = pkg | |
| 204 return pkg | |
| 205 } | |
| 206 | |
| 207 // compile generates a compile task. | |
| 208 func compile(cfg *specs.TasksCfg, name string, parts map[string]string) string { | |
| 209 // Collect the necessary CIPD packages. | |
| 210 pkgs := []*specs.CipdPackage{} | |
| 211 | |
| 212 // Android bots require a toolchain. | |
| 213 if strings.Contains(name, "Android") { | |
| 214 pkgs = append(pkgs, getCipdPackage("android_sdk")) | |
| 215 if strings.Contains(name, "Mac") { | |
| 216 pkgs = append(pkgs, getCipdPackage("android_ndk_darwin") ) | |
| 217 } else { | |
| 218 pkgs = append(pkgs, getCipdPackage("android_ndk_linux")) | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 // Clang on Linux. | |
| 223 if strings.Contains(name, "Ubuntu") && strings.Contains(name, "Clang") { | |
| 224 pkgs = append(pkgs, getCipdPackage("clang_linux")) | |
| 225 } | |
| 226 | |
| 227 // Windows toolchain. | |
| 228 if strings.Contains(name, "Win") { | |
| 229 pkgs = append(pkgs, getCipdPackage("win_toolchain")) | |
| 230 if strings.Contains(name, "Vulkan") { | |
| 231 pkgs = append(pkgs, getCipdPackage("win_vulkan_sdk")) | |
| 232 } | |
| 233 } | |
| 234 | |
| 235 // Add the task. | |
| 236 cfg.Tasks[name] = &specs.TaskSpec{ | |
| 237 CipdPackages: pkgs, | |
| 238 Dimensions: swarmDimensions(parts), | |
| 239 ExtraArgs: []string{ | |
| 240 "--workdir", "../../..", "swarm_compile", | |
| 241 fmt.Sprintf("buildername=%s", name), | |
| 242 "mastername=fake-master", | |
| 243 "buildnumber=2", | |
| 244 "slavename=fake-buildslave", | |
| 245 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLAT ED_OUTDIR), | |
| 246 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION), | |
| 247 }, | |
| 248 Isolate: "compile_skia.isolate", | |
| 249 Priority: 0.8, | |
| 250 } | |
| 251 return name | |
| 252 } | |
| 253 | |
| 254 // recreateSKPs generates a RecreateSKPs task. | |
| 255 func recreateSKPs(cfg *specs.TasksCfg, name string) string { | |
| 256 // TODO | |
| 257 return name | |
| 258 } | |
| 259 | |
| 260 // ctSKPs generates a CT SKPs task. | |
| 261 func ctSKPs(cfg *specs.TasksCfg, name string) string { | |
| 262 // TODO | |
| 263 return name | |
| 264 } | |
| 265 | |
| 266 // housekeeper generates a Housekeeper task. | |
| 267 func housekeeper(cfg *specs.TasksCfg, name, compileTaskName string) string { | |
| 268 // TODO | |
| 269 return name | |
| 270 } | |
| 271 | |
| 272 // test generates a Test task. | |
|
dogben
2016/09/30 17:35:14
nit: say what return value is
same for perf
borenet
2016/09/30 19:13:16
Done here and elsewhere.
| |
| 273 func test(cfg *specs.TasksCfg, name string, parts map[string]string, compileTask Name string, pkgs []*specs.CipdPackage) string { | |
| 274 cfg.Tasks[name] = &specs.TaskSpec{ | |
| 275 CipdPackages: pkgs, | |
| 276 Dependencies: []string{compileTaskName}, | |
| 277 Dimensions: swarmDimensions(parts), | |
| 278 ExtraArgs: []string{ | |
| 279 "--workdir", "../../..", "swarm_test", | |
| 280 fmt.Sprintf("buildername=%s", name), | |
| 281 "mastername=fake-master", | |
| 282 "buildnumber=2", | |
| 283 "slavename=fake-buildslave", | |
| 284 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLAT ED_OUTDIR), | |
| 285 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION), | |
| 286 }, | |
| 287 Isolate: "test_skia.isolate", | |
| 288 Priority: 0.8, | |
| 289 } | |
| 290 // Upload results if necessary. | |
| 291 skipUploadBots := []string{ | |
| 292 "ASAN", | |
| 293 "Coverage", | |
| 294 "MSAN", | |
| 295 "TSAN", | |
| 296 "UBSAN", | |
| 297 "Valgrind", | |
| 298 } | |
| 299 upload := true | |
| 300 for _, s := range skipUploadBots { | |
| 301 if strings.Contains(name, s) { | |
| 302 upload = false | |
| 303 break | |
| 304 } | |
| 305 } | |
| 306 if upload { | |
| 307 uploadName := fmt.Sprintf("%s%s%s", PREFIX_UPLOAD, jobNameSchema .Sep, name) | |
| 308 cfg.Tasks[uploadName] = &specs.TaskSpec{ | |
| 309 Dependencies: []string{name}, | |
| 310 Dimensions: UPLOAD_DIMENSIONS, | |
| 311 ExtraArgs: []string{ | |
| 312 "--workdir", "../../..", "upload_dm_results", | |
| 313 fmt.Sprintf("buildername=%s", name), | |
| 314 "mastername=fake-master", | |
| 315 "buildnumber=2", | |
| 316 "slavename=fake-buildslave", | |
| 317 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDE R_ISOLATED_OUTDIR), | |
| 318 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REV ISION), | |
| 319 }, | |
| 320 Isolate: "upload_dm_results.isolate", | |
| 321 Priority: 0.8, | |
| 322 } | |
| 323 return uploadName | |
| 324 } | |
| 325 return name | |
| 326 } | |
| 327 | |
| 328 // perf generates a Perf task. | |
| 329 func perf(cfg *specs.TasksCfg, name string, parts map[string]string, compileTask Name string, pkgs []*specs.CipdPackage) string { | |
| 330 cfg.Tasks[name] = &specs.TaskSpec{ | |
| 331 CipdPackages: pkgs, | |
| 332 Dependencies: []string{compileTaskName}, | |
| 333 Dimensions: swarmDimensions(parts), | |
| 334 ExtraArgs: []string{ | |
| 335 "--workdir", "../../..", "swarm_perf", | |
| 336 fmt.Sprintf("buildername=%s", name), | |
| 337 "mastername=fake-master", | |
| 338 "buildnumber=2", | |
| 339 "slavename=fake-buildslave", | |
| 340 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLAT ED_OUTDIR), | |
| 341 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION), | |
| 342 }, | |
| 343 Isolate: "perf_skia.isolate", | |
| 344 Priority: 0.8, | |
| 345 } | |
| 346 // Upload results if necessary. | |
| 347 if strings.Contains(name, "Release") { | |
|
dogben
2016/09/30 17:35:14
This agrees with the recipe, but just so you're aw
borenet
2016/09/30 19:13:16
Ouch. That's a bug. Fixed here, and will make a se
| |
| 348 uploadName := fmt.Sprintf("%s%s%s", PREFIX_UPLOAD, jobNameSchema .Sep, name) | |
| 349 cfg.Tasks[uploadName] = &specs.TaskSpec{ | |
| 350 Dependencies: []string{name}, | |
| 351 Dimensions: UPLOAD_DIMENSIONS, | |
| 352 ExtraArgs: []string{ | |
| 353 "--workdir", "../../..", "upload_dm_results", | |
|
dogben
2016/09/30 17:35:14
s/dm/nano/
borenet
2016/09/30 19:13:16
Done.
| |
| 354 fmt.Sprintf("buildername=%s", name), | |
| 355 "mastername=fake-master", | |
| 356 "buildnumber=2", | |
| 357 "slavename=fake-buildslave", | |
| 358 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDE R_ISOLATED_OUTDIR), | |
| 359 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REV ISION), | |
| 360 }, | |
| 361 Isolate: "upload_dm_results.isolate", | |
| 362 Priority: 0.8, | |
| 363 } | |
| 364 return uploadName | |
| 365 } | |
| 366 return name | |
| 367 } | |
| 368 | |
| 369 // process generates tasks and jobs for the given job name. | |
| 370 func process(cfg *specs.TasksCfg, name string) { | |
| 371 if _, ok := cfg.Jobs[name]; ok { | |
| 372 glog.Fatalf("Duplicate job %q", name) | |
| 373 } | |
| 374 deps := []string{} | |
| 375 | |
| 376 parts, err := jobNameSchema.ParseJobName(name) | |
| 377 if err != nil { | |
| 378 glog.Fatal(err) | |
| 379 } | |
| 380 | |
| 381 // RecreateSKPs. | |
| 382 if strings.Contains(name, "RecreateSKPs") { | |
| 383 deps = append(deps, recreateSKPs(cfg, name)) | |
| 384 } | |
| 385 | |
| 386 // CT bots. | |
| 387 if strings.Contains(name, "-CT_") { | |
| 388 deps = append(deps, ctSKPs(cfg, name)) | |
| 389 } | |
| 390 | |
| 391 // Compile bots. | |
| 392 if parts["role"] == "Build" { | |
| 393 deps = append(deps, compile(cfg, name, parts)) | |
| 394 } | |
| 395 | |
| 396 // Any remaining bots need a compile task. | |
| 397 compileTaskName := deriveCompileTaskName(name, parts) | |
|
dogben
2016/09/30 17:35:14
This seems to assume we will have Build jobs for e
borenet
2016/09/30 19:13:16
Yeah, that was my intention, although I can defini
| |
| 398 | |
| 399 // Housekeeper. | |
| 400 if parts["role"] == "Housekeeper" { | |
| 401 deps = append(deps, housekeeper(cfg, name, compileTaskName)) | |
| 402 } | |
| 403 | |
| 404 // Common assets needed by the remaining bots. | |
| 405 pkgs := []*specs.CipdPackage{ | |
| 406 getCipdPackage("skimage"), | |
| 407 getCipdPackage("skp"), | |
| 408 getCipdPackage("svg"), | |
| 409 } | |
| 410 | |
| 411 // Test bots. | |
| 412 if parts["role"] == "Test" { | |
| 413 deps = append(deps, test(cfg, name, parts, compileTaskName, pkgs )) | |
| 414 } | |
| 415 | |
| 416 // Perf bots. | |
| 417 if parts["role"] == "Perf" { | |
| 418 deps = append(deps, perf(cfg, name, parts, compileTaskName, pkgs )) | |
| 419 } | |
| 420 | |
| 421 // Add the Job spec. | |
| 422 cfg.Jobs[name] = &specs.JobSpec{ | |
| 423 Priority: 0.8, | |
| 424 TaskSpecs: deps, | |
| 425 } | |
| 426 } | |
| 427 | |
| 428 // getCheckoutRoot returns the path of the root of the Skia checkout, or an | |
| 429 // error if it cannot be found. | |
| 430 func getCheckoutRoot() string { | |
| 431 cwd, err := os.Getwd() | |
| 432 if err != nil { | |
| 433 glog.Fatal(err) | |
| 434 } | |
| 435 for { | |
| 436 if _, err := os.Stat(cwd); err != nil { | |
| 437 glog.Fatal(err) | |
| 438 } | |
| 439 s, err := os.Stat(path.Join(cwd, ".git")) | |
| 440 if err == nil && s.IsDir() { | |
| 441 // TODO(borenet): Should we verify that this is a Skia | |
| 442 // checkout and not something else? | |
| 443 return cwd | |
| 444 } | |
| 445 cwd = filepath.Clean(path.Join(cwd, "..")) | |
| 446 } | |
| 447 } | |
| 448 | |
| 449 // Regenerate the tasks.json file. | |
| 450 func main() { | |
| 451 common.Init() | |
| 452 defer common.LogPanic() | |
| 453 | |
| 454 // Where are we? | |
| 455 root := getCheckoutRoot() | |
| 456 infrabotsDir = path.Join(root, "infra", "bots") | |
| 457 | |
| 458 // Create the JobNameSchema. | |
| 459 schema, err := NewJobNameSchema(path.Join(infrabotsDir, "recipe_modules" , "builder_name_schema", "builder_name_schema.json")) | |
| 460 if err != nil { | |
| 461 glog.Fatal(err) | |
| 462 } | |
| 463 jobNameSchema = schema | |
| 464 | |
| 465 // Create the config. | |
| 466 cfg := &specs.TasksCfg{ | |
| 467 Jobs: map[string]*specs.JobSpec{}, | |
| 468 Tasks: map[string]*specs.TaskSpec{}, | |
| 469 } | |
| 470 | |
| 471 // Create Tasks and Jobs. | |
| 472 for _, j := range JOBS { | |
| 473 process(cfg, j) | |
| 474 } | |
| 475 | |
| 476 // Validate the config. | |
| 477 if err := cfg.Validate(); err != nil { | |
| 478 glog.Fatal(err) | |
| 479 } | |
| 480 | |
| 481 // Write the tasks.json file. | |
| 482 outFile := path.Join(root, specs.TASKS_CFG_FILE) | |
| 483 b, err := json.MarshalIndent(cfg, "", " ") | |
| 484 if err != nil { | |
| 485 glog.Fatal(err) | |
| 486 } | |
| 487 b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1) | |
|
dogben
2016/09/30 17:35:14
Please add a comment.
borenet
2016/09/30 19:13:16
Done.
| |
| 488 if err := ioutil.WriteFile(outFile, b, os.ModePerm); err != nil { | |
| 489 glog.Fatal(err) | |
| 490 } | |
| 491 } | |
| 492 | |
| 493 // TODO(borenet): The below really belongs in its own file, probably next to the | |
| 494 // builder_name_schema.json file. | |
| 495 | |
| 496 // JobNameSchema is a struct used for (de)constructing Job names in a | |
| 497 // predictable format. | |
| 498 type JobNameSchema struct { | |
| 499 Schema map[string][]string `json:"builder_name_schema"` | |
| 500 Sep string `json:"builder_name_sep"` | |
| 501 } | |
| 502 | |
| 503 // NewJobNameSchema returns a JobNameSchema instance based on the given JSON | |
| 504 // file. | |
| 505 func NewJobNameSchema(jsonFile string) (*JobNameSchema, error) { | |
| 506 var rv JobNameSchema | |
| 507 f, err := os.Open(jsonFile) | |
| 508 if err != nil { | |
| 509 return nil, err | |
| 510 } | |
| 511 defer util.Close(f) | |
| 512 if err := json.NewDecoder(f).Decode(&rv); err != nil { | |
| 513 return nil, err | |
| 514 } | |
| 515 return &rv, nil | |
| 516 } | |
| 517 | |
| 518 // ParseJobName splits the given Job name into its component parts, according | |
| 519 // to the schema. | |
| 520 func (s *JobNameSchema) ParseJobName(n string) (map[string]string, error) { | |
| 521 split := strings.Split(n, s.Sep) | |
| 522 if len(split) < 2 { | |
| 523 return nil, fmt.Errorf("Invalid job name: %q", n) | |
| 524 } | |
| 525 role := split[0] | |
| 526 split = split[1:] | |
| 527 keys, ok := s.Schema[role] | |
| 528 if !ok { | |
| 529 return nil, fmt.Errorf("Invalid job name; %q is not a valid role .", role) | |
| 530 } | |
| 531 extraConfig := "" | |
| 532 if len(split) == len(keys)+1 { | |
| 533 extraConfig = split[len(split)-1] | |
| 534 split = split[:len(split)-1] | |
| 535 } | |
| 536 if len(split) != len(keys) { | |
| 537 return nil, fmt.Errorf("Invalid job name; %q has incorrect numbe r of parts.", n) | |
| 538 } | |
| 539 rv := make(map[string]string, len(keys)+2) | |
| 540 rv["role"] = role | |
| 541 if extraConfig != "" { | |
| 542 rv["extra_config"] = extraConfig | |
| 543 } | |
| 544 for i, k := range keys { | |
| 545 rv[k] = split[i] | |
| 546 } | |
| 547 return rv, nil | |
| 548 } | |
| 549 | |
| 550 // MakeJobName assembles the given parts of a Job name, according to the schema. | |
| 551 func (s *JobNameSchema) MakeJobName(parts map[string]string) (string, error) { | |
| 552 role, ok := parts["role"] | |
| 553 if !ok { | |
| 554 return "", fmt.Errorf("Invalid job parts; jobs must have a role. ") | |
| 555 } | |
| 556 keys, ok := s.Schema[role] | |
| 557 if !ok { | |
| 558 return "", fmt.Errorf("Invalid job parts; unknown role %q", role ) | |
| 559 } | |
| 560 rvParts := make([]string, 0, len(parts)) | |
| 561 rvParts = append(rvParts, role) | |
| 562 for _, k := range keys { | |
| 563 v, ok := parts[k] | |
| 564 if !ok { | |
| 565 return "", fmt.Errorf("Invalid job parts; missing %q", k ) | |
| 566 } | |
| 567 rvParts = append(rvParts, v) | |
| 568 } | |
| 569 if _, ok := parts["extra_config"]; ok { | |
| 570 rvParts = append(rvParts, parts["extra_config"]) | |
| 571 } | |
| 572 return strings.Join(rvParts, s.Sep), nil | |
| 573 } | |
| OLD | NEW |