blob: 0301034cbd7a96e79c4eb70e563180685c05ebf7 [file] [log] [blame]
borenetdb182c72016-09-30 12:53:12 -07001// 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
5package main
6
7/*
8 Generate the tasks.json file.
9*/
10
11import (
borenetdb182c72016-09-30 12:53:12 -070012 "encoding/json"
Eric Boren27225492017-02-01 15:56:55 -050013 "flag"
borenetdb182c72016-09-30 12:53:12 -070014 "fmt"
Eric Boren27225492017-02-01 15:56:55 -050015 "io/ioutil"
borenetdb182c72016-09-30 12:53:12 -070016 "os"
17 "path"
Eric Boren27225492017-02-01 15:56:55 -050018 "regexp"
borenetdb182c72016-09-30 12:53:12 -070019 "sort"
20 "strings"
Eric Boren4b254b92016-11-08 12:55:32 -050021 "time"
borenetdb182c72016-09-30 12:53:12 -070022
23 "github.com/skia-dev/glog"
borenetdb182c72016-09-30 12:53:12 -070024 "go.skia.org/infra/go/util"
25 "go.skia.org/infra/task_scheduler/go/specs"
26)
27
28const (
Eric Boren8b3f9e62017-04-04 09:06:16 -040029 BUNDLE_RECIPES_NAME = "Housekeeper-PerCommit-BundleRecipes"
30
Kevin Lubick797ef162016-12-15 10:45:08 -050031 DEFAULT_OS = DEFAULT_OS_LINUX
32 DEFAULT_OS_LINUX = "Ubuntu-14.04"
borenetdb182c72016-09-30 12:53:12 -070033
borenetdb182c72016-09-30 12:53:12 -070034 // Name prefix for upload jobs.
35 PREFIX_UPLOAD = "Upload"
36)
37
38var (
39 // "Constants"
40
Eric Boren27225492017-02-01 15:56:55 -050041 // Top-level list of all jobs to run at each commit; loaded from
42 // jobs.json.
43 JOBS []string
44
45 // Mapping of human-friendly Android device names to a pair of {device_type, device_os}.
46 ANDROID_MAPPING map[string][]string
47
48 // General configuration information.
49 CONFIG struct {
Eric Boren965861b2017-02-06 15:38:41 -050050 GsBucketGm string `json:"gs_bucket_gm"`
51 GsBucketNano string `json:"gs_bucket_nano"`
52 NoUpload []string `json:"no_upload"`
53 Pool string `json:"pool"`
borenetdb182c72016-09-30 12:53:12 -070054 }
55
Eric Boren27225492017-02-01 15:56:55 -050056 // Mapping of human-friendly GPU names to PCI IDs.
57 GPU_MAPPING map[string]string
borenetdb182c72016-09-30 12:53:12 -070058
59 // Defines the structure of job names.
60 jobNameSchema *JobNameSchema
Eric Boren27225492017-02-01 15:56:55 -050061
62 // Flags.
Eric Boren1f8be682017-02-07 09:16:30 -050063 androidMapFile = flag.String("android_map", "", "JSON file containing a mapping of human-friendly Android device names to a pair of {device_type, device_os}.")
64 builderNameSchemaFile = flag.String("builder_name_schema", "", "Path to the builder_name_schema.json file. If not specified, uses infra/bots/recipe_modules/builder_name_schema/builder_name_schema.json from this repo.")
65 assetsDir = flag.String("assets_dir", "", "Directory containing assets.")
66 cfgFile = flag.String("cfg_file", "", "JSON file containing general configuration information.")
67 gpuMapFile = flag.String("gpu_map", "", "JSON file containing a mapping of human-friendly GPU names to PCI IDs.")
68 jobsFile = flag.String("jobs", "", "JSON file containing jobs to run.")
borenetdb182c72016-09-30 12:53:12 -070069)
70
Eric Boren27225492017-02-01 15:56:55 -050071// linuxGceDimensions are the Swarming dimensions for Linux GCE
72// instances.
73func linuxGceDimensions() []string {
74 return []string{
75 "cpu:x86-64-avx2",
76 "gpu:none",
77 fmt.Sprintf("os:%s", DEFAULT_OS_LINUX),
78 fmt.Sprintf("pool:%s", CONFIG.Pool),
79 }
80}
81
borenetdb182c72016-09-30 12:53:12 -070082// deriveCompileTaskName returns the name of a compile task based on the given
83// job name.
84func deriveCompileTaskName(jobName string, parts map[string]string) string {
85 if parts["role"] == "Housekeeper" {
86 return "Build-Ubuntu-GCC-x86_64-Release-Shared"
87 } else if parts["role"] == "Test" || parts["role"] == "Perf" {
88 task_os := parts["os"]
89 ec := parts["extra_config"]
Kevin Lubickb03b5ac2016-11-14 13:42:27 -050090 ec = strings.TrimSuffix(ec, "_Skpbench")
Ben Wagner46df2a12017-02-21 19:10:24 -050091 ec = strings.TrimSuffix(ec, "_AbandonGpuContext")
92 ec = strings.TrimSuffix(ec, "_PreAbandonGpuContext")
Ben Wagneradfeaf12017-02-21 19:27:07 -050093 if ec == "Valgrind" {
94 // skia:6267
95 ec = ""
96 }
borenetdb182c72016-09-30 12:53:12 -070097 if task_os == "Android" {
98 if ec == "Vulkan" {
99 ec = "Android_Vulkan"
borenetdb182c72016-09-30 12:53:12 -0700100 }
borenet52383432016-10-17 10:17:53 -0700101 task_os = "Ubuntu"
Kevin Lubickdcd2a902017-03-08 14:01:01 -0500102 } else if task_os == "Chromecast" {
103 task_os = "Ubuntu"
104 ec = "Chromecast"
Kevin Lubick261ea192017-04-05 07:32:45 -0400105 } else if strings.HasPrefix(task_os, "Chromebook") {
106 ec = task_os
107 task_os = "Ubuntu"
borenetdb182c72016-09-30 12:53:12 -0700108 } else if task_os == "iOS" {
109 ec = task_os
110 task_os = "Mac"
111 } else if strings.Contains(task_os, "Win") {
112 task_os = "Win"
Kevin Lubick893c49e2017-01-17 15:15:40 -0500113 } else if strings.Contains(task_os, "Ubuntu") {
114 task_os = "Ubuntu"
borenetdb182c72016-09-30 12:53:12 -0700115 }
Eric Boren50831302016-11-18 13:10:51 -0500116 jobNameMap := map[string]string{
borenetdb182c72016-09-30 12:53:12 -0700117 "role": "Build",
118 "os": task_os,
119 "compiler": parts["compiler"],
120 "target_arch": parts["arch"],
121 "configuration": parts["configuration"],
Eric Boren50831302016-11-18 13:10:51 -0500122 }
123 if ec != "" {
124 jobNameMap["extra_config"] = ec
125 }
126 name, err := jobNameSchema.MakeJobName(jobNameMap)
borenetdb182c72016-09-30 12:53:12 -0700127 if err != nil {
128 glog.Fatal(err)
129 }
130 return name
131 } else {
132 return jobName
133 }
134}
135
136// swarmDimensions generates swarming bot dimensions for the given task.
137func swarmDimensions(parts map[string]string) []string {
borenetdb182c72016-09-30 12:53:12 -0700138 d := map[string]string{
Eric Boren27225492017-02-01 15:56:55 -0500139 "pool": CONFIG.Pool,
borenetdb182c72016-09-30 12:53:12 -0700140 }
141 if os, ok := parts["os"]; ok {
Eric Boren54ff2fc2016-12-02 12:09:10 -0500142 d["os"] = map[string]string{
Kevin Lubick291547d2017-03-21 09:25:34 -0400143 "Android": "Android",
144 "Chromecast": "Android",
145 "Mac": "Mac-10.11",
146 "Ubuntu": DEFAULT_OS_LINUX,
147 "Ubuntu16": "Ubuntu-16.10",
148 "Win": "Windows-2008ServerR2-SP1",
149 "Win10": "Windows-10-14393",
150 "Win2k8": "Windows-2008ServerR2-SP1",
151 "Win8": "Windows-8.1-SP0",
152 "iOS": "iOS-9.3.1",
Eric Boren54ff2fc2016-12-02 12:09:10 -0500153 }[os]
Ben Wagner73557372016-12-29 16:27:03 -0500154 // Chrome Golo has a different Windows image.
155 if parts["model"] == "Golo" && os == "Win10" {
156 d["os"] = "Windows-10-10586"
Ben Wagner17f811b2016-12-22 08:40:14 -0500157 }
borenetdb182c72016-09-30 12:53:12 -0700158 } else {
159 d["os"] = DEFAULT_OS
160 }
borenetdb182c72016-09-30 12:53:12 -0700161 if parts["role"] == "Test" || parts["role"] == "Perf" {
Kevin Lubick291547d2017-03-21 09:25:34 -0400162 if strings.Contains(parts["os"], "Android") || strings.Contains(parts["os"], "Chromecast") {
borenetdb182c72016-09-30 12:53:12 -0700163 // For Android, the device type is a better dimension
164 // than CPU or GPU.
Eric Boren27225492017-02-01 15:56:55 -0500165 deviceInfo, ok := ANDROID_MAPPING[parts["model"]]
166 if !ok {
167 glog.Fatalf("Entry %q not found in Android mapping: %v", parts["model"], ANDROID_MAPPING)
168 }
Eric Boren4b254b92016-11-08 12:55:32 -0500169 d["device_type"] = deviceInfo[0]
170 d["device_os"] = deviceInfo[1]
borenetdb182c72016-09-30 12:53:12 -0700171 } else if strings.Contains(parts["os"], "iOS") {
172 d["device"] = map[string]string{
Eric Boren792079cf2016-11-09 14:03:20 -0500173 "iPadMini4": "iPad5,1",
borenetdb182c72016-09-30 12:53:12 -0700174 }[parts["model"]]
borenetdb182c72016-09-30 12:53:12 -0700175 } else if parts["cpu_or_gpu"] == "CPU" {
176 d["gpu"] = "none"
177 d["cpu"] = map[string]string{
178 "AVX": "x86-64",
179 "AVX2": "x86-64-avx2",
180 "SSE4": "x86-64",
181 }[parts["cpu_or_gpu_value"]]
182 if strings.Contains(parts["os"], "Win") && parts["cpu_or_gpu_value"] == "AVX2" {
183 // AVX2 is not correctly detected on Windows. Fall back on other
184 // dimensions to ensure that we correctly target machines which we know
185 // have AVX2 support.
Eric Boren085bf7c2017-04-06 17:32:44 +0000186 d["cpu"] = "x86-64"
borenetdb182c72016-09-30 12:53:12 -0700187 d["os"] = "Windows-2008ServerR2-SP1"
188 }
189 } else {
Eric Boren27225492017-02-01 15:56:55 -0500190 gpu, ok := GPU_MAPPING[parts["cpu_or_gpu_value"]]
191 if !ok {
192 glog.Fatalf("Entry %q not found in GPU mapping: %v", parts["cpu_or_gpu_value"], GPU_MAPPING)
193 }
194 d["gpu"] = gpu
Ben Wagner08435892017-02-18 23:28:26 -0500195
196 // Hack: Specify machine_type dimension for NUCs and ShuttleCs. We
197 // temporarily have two types of machines with a GTX960. The only way to
198 // distinguish these bots is by machine_type.
199 machine_type, ok := map[string]string{
200 "NUC6i7KYK": "n1-highcpu-8",
201 "ShuttleC": "n1-standard-8",
202 }[parts["model"]]
203 if ok {
204 d["machine_type"] = machine_type
205 }
borenetdb182c72016-09-30 12:53:12 -0700206 }
207 } else {
208 d["gpu"] = "none"
Kevin Lubick4610a9b2017-03-22 15:54:54 -0400209 if d["os"] == DEFAULT_OS_LINUX {
210 return linuxGceDimensions()
211 }
borenetdb182c72016-09-30 12:53:12 -0700212 }
Kevin Lubick4610a9b2017-03-22 15:54:54 -0400213
borenetdb182c72016-09-30 12:53:12 -0700214 rv := make([]string, 0, len(d))
215 for k, v := range d {
216 rv = append(rv, fmt.Sprintf("%s:%s", k, v))
217 }
218 sort.Strings(rv)
219 return rv
220}
221
Eric Boren8b3f9e62017-04-04 09:06:16 -0400222// bundleRecipes generates the task to bundle and isolate the recipes.
223func bundleRecipes(b *specs.TasksCfgBuilder) string {
224 b.MustAddTask(BUNDLE_RECIPES_NAME, &specs.TaskSpec{
225 CipdPackages: []*specs.CipdPackage{},
226 Dimensions: linuxGceDimensions(),
227 ExtraArgs: []string{
228 "--workdir", "../../..", "bundle_recipes",
229 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
230 fmt.Sprintf("buildername=%s", BUNDLE_RECIPES_NAME),
231 "mastername=fake-master",
232 "buildnumber=2",
233 "slavename=fake-buildslave",
234 "nobuildbot=True",
235 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
236 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
237 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
238 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
239 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
240 },
241 Isolate: "bundle_recipes.isolate",
242 Priority: 0.95,
243 })
244 return BUNDLE_RECIPES_NAME
245}
246
borenetdb182c72016-09-30 12:53:12 -0700247// compile generates a compile task. Returns the name of the last task in the
248// generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700249func compile(b *specs.TasksCfgBuilder, name string, parts map[string]string) string {
borenetdb182c72016-09-30 12:53:12 -0700250 // Collect the necessary CIPD packages.
251 pkgs := []*specs.CipdPackage{}
252
253 // Android bots require a toolchain.
254 if strings.Contains(name, "Android") {
borenetdb182c72016-09-30 12:53:12 -0700255 if strings.Contains(name, "Mac") {
boreneted20a702016-10-20 11:04:31 -0700256 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("android_ndk_darwin"))
Mike Klein86c2c0f2016-11-02 13:13:16 -0400257 } else if strings.Contains(name, "Win") {
Mike Kleine9215f02016-11-02 15:44:26 -0400258 pkg := b.MustGetCipdPackageFromAsset("android_ndk_windows")
259 pkg.Path = "n"
260 pkgs = append(pkgs, pkg)
borenetdb182c72016-09-30 12:53:12 -0700261 } else {
boreneted20a702016-10-20 11:04:31 -0700262 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("android_ndk_linux"))
borenetdb182c72016-09-30 12:53:12 -0700263 }
Kevin Lubickdcd2a902017-03-08 14:01:01 -0500264 } else if strings.Contains(name, "Chromecast") {
265 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("cast_toolchain"))
Kevin Lubick261ea192017-04-05 07:32:45 -0400266 } else if strings.Contains(name, "Chromebook") {
267 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("clang_linux"))
268 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("armhf_sysroot"))
269 if strings.Contains(name, "Chromebook_C100p") {
270 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("chromebook_c100p_lib"))
271 }
Kevin Lubick9c7dcac2017-01-18 09:24:56 -0500272 } else if strings.Contains(name, "Ubuntu") {
273 if strings.Contains(name, "Clang") {
274 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("clang_linux"))
275 }
276 if strings.Contains(name, "Vulkan") {
277 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("linux_vulkan_sdk"))
278 }
Mike Klein27dcee12016-11-09 16:31:42 -0500279 } else if strings.Contains(name, "Win") {
boreneted20a702016-10-20 11:04:31 -0700280 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("win_toolchain"))
borenetdb182c72016-09-30 12:53:12 -0700281 if strings.Contains(name, "Vulkan") {
boreneted20a702016-10-20 11:04:31 -0700282 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("win_vulkan_sdk"))
borenetdb182c72016-09-30 12:53:12 -0700283 }
284 }
285
Stephan Altmueller5f3b9402017-03-20 13:38:45 -0400286 // TODO(stephana): Remove this once all Mac machines are on the same
287 // OS version again. Move the call to swarmDimensions back to the
288 // creation of the TaskSpec struct below.
289 dimensions := swarmDimensions(parts)
290 if strings.Contains(name, "Mac") {
291 for idx, dim := range dimensions {
292 if strings.HasPrefix(dim, "os") {
293 dimensions[idx] = "os:Mac-10.12"
294 break
295 }
296 }
297 }
298
borenetdb182c72016-09-30 12:53:12 -0700299 // Add the task.
boreneted20a702016-10-20 11:04:31 -0700300 b.MustAddTask(name, &specs.TaskSpec{
borenetdb182c72016-09-30 12:53:12 -0700301 CipdPackages: pkgs,
Stephan Altmueller5f3b9402017-03-20 13:38:45 -0400302 Dimensions: dimensions,
borenetdb182c72016-09-30 12:53:12 -0700303 ExtraArgs: []string{
304 "--workdir", "../../..", "swarm_compile",
skia.buildbots2478c732016-11-04 14:37:26 -0400305 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenetdb182c72016-09-30 12:53:12 -0700306 fmt.Sprintf("buildername=%s", name),
307 "mastername=fake-master",
308 "buildnumber=2",
309 "slavename=fake-buildslave",
borenet98b2e7a2016-10-13 06:23:45 -0700310 "nobuildbot=True",
borenetdb182c72016-09-30 12:53:12 -0700311 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
312 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
borenet98b2e7a2016-10-13 06:23:45 -0700313 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400314 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
315 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
borenetdb182c72016-09-30 12:53:12 -0700316 },
317 Isolate: "compile_skia.isolate",
318 Priority: 0.8,
boreneted20a702016-10-20 11:04:31 -0700319 })
Eric Boren8615fe52016-12-12 14:30:12 -0500320 // All compile tasks are runnable as their own Job. Assert that the Job
321 // is listed in JOBS.
322 if !util.In(name, JOBS) {
323 glog.Fatalf("Job %q is missing from the JOBS list!", name)
324 }
borenetdb182c72016-09-30 12:53:12 -0700325 return name
326}
327
328// recreateSKPs generates a RecreateSKPs task. Returns the name of the last
329// task in the generated chain of tasks, which the Job should add as a
330// dependency.
boreneted20a702016-10-20 11:04:31 -0700331func recreateSKPs(b *specs.TasksCfgBuilder, name string) string {
Eric Boren4b254b92016-11-08 12:55:32 -0500332 b.MustAddTask(name, &specs.TaskSpec{
Eric Borenf5a90e82016-11-15 15:18:20 -0500333 CipdPackages: []*specs.CipdPackage{},
Eric Boren27225492017-02-01 15:56:55 -0500334 Dimensions: linuxGceDimensions(),
Eric Borenf5a90e82016-11-15 15:18:20 -0500335 ExecutionTimeout: 4 * time.Hour,
Eric Boren4b254b92016-11-08 12:55:32 -0500336 ExtraArgs: []string{
337 "--workdir", "../../..", "swarm_RecreateSKPs",
338 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
339 fmt.Sprintf("buildername=%s", name),
340 "mastername=fake-master",
341 "buildnumber=2",
342 "slavename=fake-buildslave",
343 "nobuildbot=True",
344 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
345 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
346 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
347 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
348 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
349 },
Eric Borenf5a90e82016-11-15 15:18:20 -0500350 IoTimeout: 40 * time.Minute,
351 Isolate: "compile_skia.isolate",
352 Priority: 0.8,
Eric Boren4b254b92016-11-08 12:55:32 -0500353 })
borenetdb182c72016-09-30 12:53:12 -0700354 return name
355}
356
357// ctSKPs generates a CT SKPs task. Returns the name of the last task in the
358// generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700359func ctSKPs(b *specs.TasksCfgBuilder, name string) string {
Eric Boren4b254b92016-11-08 12:55:32 -0500360 b.MustAddTask(name, &specs.TaskSpec{
361 CipdPackages: []*specs.CipdPackage{},
362 Dimensions: []string{"pool:SkiaCT"},
363 ExecutionTimeout: 24 * time.Hour,
364 ExtraArgs: []string{
365 "--workdir", "../../..", "swarm_ct_skps",
366 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
367 fmt.Sprintf("buildername=%s", name),
368 "mastername=fake-master",
369 "buildnumber=2",
370 "slavename=fake-buildslave",
371 "nobuildbot=True",
372 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
373 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
374 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
375 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
376 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
377 },
378 IoTimeout: time.Hour,
379 Isolate: "ct_skps_skia.isolate",
380 Priority: 0.8,
381 })
borenetdb182c72016-09-30 12:53:12 -0700382 return name
383}
384
385// housekeeper generates a Housekeeper task. Returns the name of the last task
386// in the generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700387func housekeeper(b *specs.TasksCfgBuilder, name, compileTaskName string) string {
Eric Boren4b254b92016-11-08 12:55:32 -0500388 b.MustAddTask(name, &specs.TaskSpec{
Eric Boren22f5ef72016-12-02 11:01:33 -0500389 CipdPackages: []*specs.CipdPackage{b.MustGetCipdPackageFromAsset("go")},
Eric Boren4b254b92016-11-08 12:55:32 -0500390 Dependencies: []string{compileTaskName},
Eric Boren27225492017-02-01 15:56:55 -0500391 Dimensions: linuxGceDimensions(),
Eric Boren4b254b92016-11-08 12:55:32 -0500392 ExtraArgs: []string{
393 "--workdir", "../../..", "swarm_housekeeper",
394 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
395 fmt.Sprintf("buildername=%s", name),
396 "mastername=fake-master",
397 "buildnumber=2",
398 "slavename=fake-buildslave",
399 "nobuildbot=True",
400 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
401 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
402 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
403 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
404 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
405 },
406 Isolate: "housekeeper_skia.isolate",
407 Priority: 0.8,
408 })
borenetdb182c72016-09-30 12:53:12 -0700409 return name
410}
411
borenet2dbbfa52016-10-14 06:32:09 -0700412// infra generates an infra_tests task. Returns the name of the last task in the
413// generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700414func infra(b *specs.TasksCfgBuilder, name string) string {
415 b.MustAddTask(name, &specs.TaskSpec{
borenet2dbbfa52016-10-14 06:32:09 -0700416 CipdPackages: []*specs.CipdPackage{},
Eric Boren27225492017-02-01 15:56:55 -0500417 Dimensions: linuxGceDimensions(),
borenet2dbbfa52016-10-14 06:32:09 -0700418 ExtraArgs: []string{
419 "--workdir", "../../..", "swarm_infra",
skia.buildbots2478c732016-11-04 14:37:26 -0400420 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenet2dbbfa52016-10-14 06:32:09 -0700421 fmt.Sprintf("buildername=%s", name),
422 "mastername=fake-master",
423 "buildnumber=2",
424 "slavename=fake-buildslave",
425 "nobuildbot=True",
426 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
427 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
428 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400429 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
430 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
borenet2dbbfa52016-10-14 06:32:09 -0700431 },
432 Isolate: "infra_skia.isolate",
433 Priority: 0.8,
boreneted20a702016-10-20 11:04:31 -0700434 })
borenet2dbbfa52016-10-14 06:32:09 -0700435 return name
436}
437
borenetdb182c72016-09-30 12:53:12 -0700438// doUpload indicates whether the given Job should upload its results.
439func doUpload(name string) bool {
Eric Boren27225492017-02-01 15:56:55 -0500440 for _, s := range CONFIG.NoUpload {
441 m, err := regexp.MatchString(s, name)
442 if err != nil {
443 glog.Fatal(err)
444 }
445 if m {
borenetdb182c72016-09-30 12:53:12 -0700446 return false
447 }
448 }
449 return true
450}
451
452// test generates a Test task. Returns the name of the last task in the
453// generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700454func test(b *specs.TasksCfgBuilder, name string, parts map[string]string, compileTaskName string, pkgs []*specs.CipdPackage) string {
Eric Boren4b254b92016-11-08 12:55:32 -0500455 s := &specs.TaskSpec{
Eric Boren1f2f64b2016-11-09 18:35:15 -0500456 CipdPackages: pkgs,
457 Dependencies: []string{compileTaskName},
458 Dimensions: swarmDimensions(parts),
459 ExecutionTimeout: 4 * time.Hour,
460 Expiration: 20 * time.Hour,
borenetdb182c72016-09-30 12:53:12 -0700461 ExtraArgs: []string{
462 "--workdir", "../../..", "swarm_test",
skia.buildbots2478c732016-11-04 14:37:26 -0400463 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenetdb182c72016-09-30 12:53:12 -0700464 fmt.Sprintf("buildername=%s", name),
465 "mastername=fake-master",
466 "buildnumber=2",
467 "slavename=fake-buildslave",
borenet98b2e7a2016-10-13 06:23:45 -0700468 "nobuildbot=True",
borenetdb182c72016-09-30 12:53:12 -0700469 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
470 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
borenet98b2e7a2016-10-13 06:23:45 -0700471 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400472 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
473 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
borenetdb182c72016-09-30 12:53:12 -0700474 },
Eric Boren5d9f3bf2017-02-22 08:36:03 -0500475 IoTimeout: 40 * time.Minute,
476 Isolate: "test_skia.isolate",
477 MaxAttempts: 1,
478 Priority: 0.8,
Eric Boren4b254b92016-11-08 12:55:32 -0500479 }
Eric Boren6276a7c2017-04-06 12:53:31 +0000480 if parts["os"] == "Android" {
Eric Boren8b3f9e62017-04-04 09:06:16 -0400481 s.Dependencies = append(s.Dependencies, BUNDLE_RECIPES_NAME)
482 s.Isolate = "test_skia_bundled.isolate"
483 }
Eric Boren4b254b92016-11-08 12:55:32 -0500484 if strings.Contains(parts["extra_config"], "Valgrind") {
485 s.ExecutionTimeout = 9 * time.Hour
486 s.Expiration = 48 * time.Hour
487 s.IoTimeout = time.Hour
488 } else if strings.Contains(parts["extra_config"], "MSAN") {
489 s.ExecutionTimeout = 9 * time.Hour
490 }
491 b.MustAddTask(name, s)
492
borenetdb182c72016-09-30 12:53:12 -0700493 // Upload results if necessary.
494 if doUpload(name) {
495 uploadName := fmt.Sprintf("%s%s%s", PREFIX_UPLOAD, jobNameSchema.Sep, name)
boreneted20a702016-10-20 11:04:31 -0700496 b.MustAddTask(uploadName, &specs.TaskSpec{
borenetdb182c72016-09-30 12:53:12 -0700497 Dependencies: []string{name},
Eric Boren27225492017-02-01 15:56:55 -0500498 Dimensions: linuxGceDimensions(),
borenetdb182c72016-09-30 12:53:12 -0700499 ExtraArgs: []string{
500 "--workdir", "../../..", "upload_dm_results",
skia.buildbots2478c732016-11-04 14:37:26 -0400501 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenetdb182c72016-09-30 12:53:12 -0700502 fmt.Sprintf("buildername=%s", name),
503 "mastername=fake-master",
504 "buildnumber=2",
505 "slavename=fake-buildslave",
borenet98b2e7a2016-10-13 06:23:45 -0700506 "nobuildbot=True",
borenetdb182c72016-09-30 12:53:12 -0700507 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
508 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
borenet98b2e7a2016-10-13 06:23:45 -0700509 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400510 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
511 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
Eric Boren965861b2017-02-06 15:38:41 -0500512 fmt.Sprintf("gs_bucket=%s", CONFIG.GsBucketGm),
borenetdb182c72016-09-30 12:53:12 -0700513 },
514 Isolate: "upload_dm_results.isolate",
515 Priority: 0.8,
boreneted20a702016-10-20 11:04:31 -0700516 })
borenetdb182c72016-09-30 12:53:12 -0700517 return uploadName
518 }
519 return name
520}
521
522// perf generates a Perf task. Returns the name of the last task in the
523// generated chain of tasks, which the Job should add as a dependency.
boreneted20a702016-10-20 11:04:31 -0700524func perf(b *specs.TasksCfgBuilder, name string, parts map[string]string, compileTaskName string, pkgs []*specs.CipdPackage) string {
Kevin Lubickb03b5ac2016-11-14 13:42:27 -0500525 recipe := "swarm_perf"
526 isolate := "perf_skia.isolate"
527 if strings.Contains(parts["extra_config"], "Skpbench") {
528 recipe = "swarm_skpbench"
529 isolate = "skpbench_skia.isolate"
Eric Boren6276a7c2017-04-06 12:53:31 +0000530 if parts["os"] == "Android" {
Eric Boren8b3f9e62017-04-04 09:06:16 -0400531 isolate = "skpbench_skia_bundled.isolate"
532 }
Eric Boren6276a7c2017-04-06 12:53:31 +0000533 } else if parts["os"] == "Android" {
Eric Boren8b3f9e62017-04-04 09:06:16 -0400534 isolate = "perf_skia_bundled.isolate"
Kevin Lubickb03b5ac2016-11-14 13:42:27 -0500535 }
Eric Boren4b254b92016-11-08 12:55:32 -0500536 s := &specs.TaskSpec{
Eric Boren1f2f64b2016-11-09 18:35:15 -0500537 CipdPackages: pkgs,
538 Dependencies: []string{compileTaskName},
539 Dimensions: swarmDimensions(parts),
540 ExecutionTimeout: 4 * time.Hour,
541 Expiration: 20 * time.Hour,
borenetdb182c72016-09-30 12:53:12 -0700542 ExtraArgs: []string{
Kevin Lubickb03b5ac2016-11-14 13:42:27 -0500543 "--workdir", "../../..", recipe,
skia.buildbots2478c732016-11-04 14:37:26 -0400544 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenetdb182c72016-09-30 12:53:12 -0700545 fmt.Sprintf("buildername=%s", name),
546 "mastername=fake-master",
547 "buildnumber=2",
548 "slavename=fake-buildslave",
borenet98b2e7a2016-10-13 06:23:45 -0700549 "nobuildbot=True",
borenetdb182c72016-09-30 12:53:12 -0700550 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
551 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
borenet98b2e7a2016-10-13 06:23:45 -0700552 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400553 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
554 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
borenetdb182c72016-09-30 12:53:12 -0700555 },
Eric Boren5d9f3bf2017-02-22 08:36:03 -0500556 IoTimeout: 40 * time.Minute,
557 Isolate: isolate,
558 MaxAttempts: 1,
559 Priority: 0.8,
Eric Boren4b254b92016-11-08 12:55:32 -0500560 }
Eric Boren6276a7c2017-04-06 12:53:31 +0000561 if parts["os"] == "Android" {
Eric Boren8b3f9e62017-04-04 09:06:16 -0400562 s.Dependencies = append(s.Dependencies, BUNDLE_RECIPES_NAME)
563 }
Eric Boren4b254b92016-11-08 12:55:32 -0500564 if strings.Contains(parts["extra_config"], "Valgrind") {
565 s.ExecutionTimeout = 9 * time.Hour
566 s.Expiration = 48 * time.Hour
567 s.IoTimeout = time.Hour
568 } else if strings.Contains(parts["extra_config"], "MSAN") {
569 s.ExecutionTimeout = 9 * time.Hour
570 }
571 b.MustAddTask(name, s)
572
borenetdb182c72016-09-30 12:53:12 -0700573 // Upload results if necessary.
574 if strings.Contains(name, "Release") && doUpload(name) {
575 uploadName := fmt.Sprintf("%s%s%s", PREFIX_UPLOAD, jobNameSchema.Sep, name)
boreneted20a702016-10-20 11:04:31 -0700576 b.MustAddTask(uploadName, &specs.TaskSpec{
borenetdb182c72016-09-30 12:53:12 -0700577 Dependencies: []string{name},
Eric Boren27225492017-02-01 15:56:55 -0500578 Dimensions: linuxGceDimensions(),
borenetdb182c72016-09-30 12:53:12 -0700579 ExtraArgs: []string{
580 "--workdir", "../../..", "upload_nano_results",
skia.buildbots2478c732016-11-04 14:37:26 -0400581 fmt.Sprintf("repository=%s", specs.PLACEHOLDER_REPO),
borenetdb182c72016-09-30 12:53:12 -0700582 fmt.Sprintf("buildername=%s", name),
583 "mastername=fake-master",
584 "buildnumber=2",
585 "slavename=fake-buildslave",
borenet98b2e7a2016-10-13 06:23:45 -0700586 "nobuildbot=True",
borenetdb182c72016-09-30 12:53:12 -0700587 fmt.Sprintf("swarm_out_dir=%s", specs.PLACEHOLDER_ISOLATED_OUTDIR),
588 fmt.Sprintf("revision=%s", specs.PLACEHOLDER_REVISION),
borenet98b2e7a2016-10-13 06:23:45 -0700589 fmt.Sprintf("patch_storage=%s", specs.PLACEHOLDER_PATCH_STORAGE),
skia.buildbots2478c732016-11-04 14:37:26 -0400590 fmt.Sprintf("patch_issue=%s", specs.PLACEHOLDER_ISSUE),
591 fmt.Sprintf("patch_set=%s", specs.PLACEHOLDER_PATCHSET),
Eric Boren965861b2017-02-06 15:38:41 -0500592 fmt.Sprintf("gs_bucket=%s", CONFIG.GsBucketNano),
borenetdb182c72016-09-30 12:53:12 -0700593 },
594 Isolate: "upload_nano_results.isolate",
595 Priority: 0.8,
boreneted20a702016-10-20 11:04:31 -0700596 })
borenetdb182c72016-09-30 12:53:12 -0700597 return uploadName
598 }
599 return name
600}
601
602// process generates tasks and jobs for the given job name.
boreneted20a702016-10-20 11:04:31 -0700603func process(b *specs.TasksCfgBuilder, name string) {
borenetdb182c72016-09-30 12:53:12 -0700604 deps := []string{}
605
Eric Boren8b3f9e62017-04-04 09:06:16 -0400606 // Bundle Recipes.
607 if name == BUNDLE_RECIPES_NAME {
608 deps = append(deps, bundleRecipes(b))
609 }
610
borenetdb182c72016-09-30 12:53:12 -0700611 parts, err := jobNameSchema.ParseJobName(name)
612 if err != nil {
613 glog.Fatal(err)
614 }
615
616 // RecreateSKPs.
617 if strings.Contains(name, "RecreateSKPs") {
boreneted20a702016-10-20 11:04:31 -0700618 deps = append(deps, recreateSKPs(b, name))
borenetdb182c72016-09-30 12:53:12 -0700619 }
620
621 // CT bots.
622 if strings.Contains(name, "-CT_") {
boreneted20a702016-10-20 11:04:31 -0700623 deps = append(deps, ctSKPs(b, name))
borenetdb182c72016-09-30 12:53:12 -0700624 }
625
borenet2dbbfa52016-10-14 06:32:09 -0700626 // Infra tests.
627 if name == "Housekeeper-PerCommit-InfraTests" {
boreneted20a702016-10-20 11:04:31 -0700628 deps = append(deps, infra(b, name))
borenet2dbbfa52016-10-14 06:32:09 -0700629 }
630
borenetdb182c72016-09-30 12:53:12 -0700631 // Compile bots.
632 if parts["role"] == "Build" {
boreneted20a702016-10-20 11:04:31 -0700633 deps = append(deps, compile(b, name, parts))
borenetdb182c72016-09-30 12:53:12 -0700634 }
635
Eric Borenf5a90e82016-11-15 15:18:20 -0500636 // Most remaining bots need a compile task.
borenetdb182c72016-09-30 12:53:12 -0700637 compileTaskName := deriveCompileTaskName(name, parts)
borenet52383432016-10-17 10:17:53 -0700638 compileTaskParts, err := jobNameSchema.ParseJobName(compileTaskName)
639 if err != nil {
640 glog.Fatal(err)
641 }
Eric Boren628e78b2016-11-17 11:33:27 -0500642 // These bots do not need a compile task.
Eric Borenf5a90e82016-11-15 15:18:20 -0500643 if parts["role"] != "Build" &&
644 name != "Housekeeper-PerCommit-InfraTests" &&
Eric Boren71b762f2016-11-30 14:05:16 -0500645 !strings.Contains(name, "RecreateSKPs") &&
646 !strings.Contains(name, "-CT_") {
boreneted20a702016-10-20 11:04:31 -0700647 compile(b, compileTaskName, compileTaskParts)
borenet52383432016-10-17 10:17:53 -0700648 }
borenetdb182c72016-09-30 12:53:12 -0700649
650 // Housekeeper.
Eric Boren22f5ef72016-12-02 11:01:33 -0500651 if name == "Housekeeper-PerCommit" {
boreneted20a702016-10-20 11:04:31 -0700652 deps = append(deps, housekeeper(b, name, compileTaskName))
borenetdb182c72016-09-30 12:53:12 -0700653 }
654
655 // Common assets needed by the remaining bots.
656 pkgs := []*specs.CipdPackage{
boreneted20a702016-10-20 11:04:31 -0700657 b.MustGetCipdPackageFromAsset("skimage"),
658 b.MustGetCipdPackageFromAsset("skp"),
659 b.MustGetCipdPackageFromAsset("svg"),
borenetdb182c72016-09-30 12:53:12 -0700660 }
Kevin Lubick291547d2017-03-21 09:25:34 -0400661 if strings.Contains(name, "Chromecast") {
662 // Chromecasts don't have enough disk space to fit all of the content,
663 // so we do a subset of the skps.
664 pkgs = []*specs.CipdPackage{
665 b.MustGetCipdPackageFromAsset("skp"),
666 }
667 }
Eric Boren4b254b92016-11-08 12:55:32 -0500668 if strings.Contains(name, "Ubuntu") && strings.Contains(name, "SAN") {
669 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("clang_linux"))
670 }
Kevin Lubick35115eb2017-02-17 10:25:34 -0500671 if strings.Contains(name, "Ubuntu16") {
672 if strings.Contains(name, "Vulkan") {
673 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("linux_vulkan_sdk"))
674 }
Kevin Lubick0a51b482017-02-06 12:45:29 -0500675 if strings.Contains(name, "Release") {
676 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("linux_vulkan_intel_driver_release"))
677 } else {
678 pkgs = append(pkgs, b.MustGetCipdPackageFromAsset("linux_vulkan_intel_driver_debug"))
679 }
680 }
Kevin Lubickb03b5ac2016-11-14 13:42:27 -0500681 // Skpbench only needs skps
682 if strings.Contains(name, "Skpbench") {
683 pkgs = []*specs.CipdPackage{
684 b.MustGetCipdPackageFromAsset("skp"),
685 }
686 }
borenetdb182c72016-09-30 12:53:12 -0700687
688 // Test bots.
Eric Boren71b762f2016-11-30 14:05:16 -0500689 if parts["role"] == "Test" && !strings.Contains(name, "-CT_") {
boreneted20a702016-10-20 11:04:31 -0700690 deps = append(deps, test(b, name, parts, compileTaskName, pkgs))
borenetdb182c72016-09-30 12:53:12 -0700691 }
692
693 // Perf bots.
Eric Boren71b762f2016-11-30 14:05:16 -0500694 if parts["role"] == "Perf" && !strings.Contains(name, "-CT_") {
boreneted20a702016-10-20 11:04:31 -0700695 deps = append(deps, perf(b, name, parts, compileTaskName, pkgs))
borenetdb182c72016-09-30 12:53:12 -0700696 }
697
698 // Add the Job spec.
Eric Borenf5a90e82016-11-15 15:18:20 -0500699 j := &specs.JobSpec{
borenetdb182c72016-09-30 12:53:12 -0700700 Priority: 0.8,
701 TaskSpecs: deps,
Eric Borenf5a90e82016-11-15 15:18:20 -0500702 }
703 if name == "Housekeeper-Nightly-RecreateSKPs_Canary" {
704 j.Trigger = "nightly"
705 }
706 if name == "Housekeeper-Weekly-RecreateSKPs" {
707 j.Trigger = "weekly"
708 }
Eric Boren71b762f2016-11-30 14:05:16 -0500709 if name == "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-CT_DM_1m_SKPs" {
710 j.Trigger = "weekly"
711 }
Eric Boren8615fe52016-12-12 14:30:12 -0500712 b.MustAddJob(name, j)
borenetdb182c72016-09-30 12:53:12 -0700713}
714
Eric Boren27225492017-02-01 15:56:55 -0500715func loadJson(flag *string, defaultFlag string, val interface{}) {
716 if *flag == "" {
717 *flag = defaultFlag
718 }
719 b, err := ioutil.ReadFile(*flag)
720 if err != nil {
721 glog.Fatal(err)
722 }
723 if err := json.Unmarshal(b, val); err != nil {
724 glog.Fatal(err)
725 }
726}
727
borenetdb182c72016-09-30 12:53:12 -0700728// Regenerate the tasks.json file.
729func main() {
boreneted20a702016-10-20 11:04:31 -0700730 b := specs.MustNewTasksCfgBuilder()
Eric Boren27225492017-02-01 15:56:55 -0500731 b.SetAssetsDir(*assetsDir)
732 infraBots := path.Join(b.CheckoutRoot(), "infra", "bots")
733
734 // Load the jobs from a JSON file.
735 loadJson(jobsFile, path.Join(infraBots, "jobs.json"), &JOBS)
736
737 // Load the GPU mapping from a JSON file.
738 loadJson(gpuMapFile, path.Join(infraBots, "gpu_map.json"), &GPU_MAPPING)
739
740 // Load the Android device mapping from a JSON file.
741 loadJson(androidMapFile, path.Join(infraBots, "android_map.json"), &ANDROID_MAPPING)
742
743 // Load general config information from a JSON file.
744 loadJson(cfgFile, path.Join(infraBots, "cfg.json"), &CONFIG)
745
borenetdb182c72016-09-30 12:53:12 -0700746 // Create the JobNameSchema.
Eric Boren1f8be682017-02-07 09:16:30 -0500747 if *builderNameSchemaFile == "" {
748 *builderNameSchemaFile = path.Join(b.CheckoutRoot(), "infra", "bots", "recipe_modules", "builder_name_schema", "builder_name_schema.json")
749 }
750 schema, err := NewJobNameSchema(*builderNameSchemaFile)
borenetdb182c72016-09-30 12:53:12 -0700751 if err != nil {
752 glog.Fatal(err)
753 }
754 jobNameSchema = schema
755
borenetdb182c72016-09-30 12:53:12 -0700756 // Create Tasks and Jobs.
boreneted20a702016-10-20 11:04:31 -0700757 for _, name := range JOBS {
758 process(b, name)
borenetdb182c72016-09-30 12:53:12 -0700759 }
760
boreneted20a702016-10-20 11:04:31 -0700761 b.MustFinish()
borenetdb182c72016-09-30 12:53:12 -0700762}
763
764// TODO(borenet): The below really belongs in its own file, probably next to the
765// builder_name_schema.json file.
766
767// JobNameSchema is a struct used for (de)constructing Job names in a
768// predictable format.
769type JobNameSchema struct {
770 Schema map[string][]string `json:"builder_name_schema"`
771 Sep string `json:"builder_name_sep"`
772}
773
774// NewJobNameSchema returns a JobNameSchema instance based on the given JSON
775// file.
776func NewJobNameSchema(jsonFile string) (*JobNameSchema, error) {
777 var rv JobNameSchema
778 f, err := os.Open(jsonFile)
779 if err != nil {
780 return nil, err
781 }
782 defer util.Close(f)
783 if err := json.NewDecoder(f).Decode(&rv); err != nil {
784 return nil, err
785 }
786 return &rv, nil
787}
788
789// ParseJobName splits the given Job name into its component parts, according
790// to the schema.
791func (s *JobNameSchema) ParseJobName(n string) (map[string]string, error) {
792 split := strings.Split(n, s.Sep)
793 if len(split) < 2 {
794 return nil, fmt.Errorf("Invalid job name: %q", n)
795 }
796 role := split[0]
797 split = split[1:]
798 keys, ok := s.Schema[role]
799 if !ok {
800 return nil, fmt.Errorf("Invalid job name; %q is not a valid role.", role)
801 }
802 extraConfig := ""
803 if len(split) == len(keys)+1 {
804 extraConfig = split[len(split)-1]
805 split = split[:len(split)-1]
806 }
807 if len(split) != len(keys) {
808 return nil, fmt.Errorf("Invalid job name; %q has incorrect number of parts.", n)
809 }
810 rv := make(map[string]string, len(keys)+2)
811 rv["role"] = role
812 if extraConfig != "" {
813 rv["extra_config"] = extraConfig
814 }
815 for i, k := range keys {
816 rv[k] = split[i]
817 }
818 return rv, nil
819}
820
821// MakeJobName assembles the given parts of a Job name, according to the schema.
822func (s *JobNameSchema) MakeJobName(parts map[string]string) (string, error) {
823 role, ok := parts["role"]
824 if !ok {
825 return "", fmt.Errorf("Invalid job parts; jobs must have a role.")
826 }
827 keys, ok := s.Schema[role]
828 if !ok {
829 return "", fmt.Errorf("Invalid job parts; unknown role %q", role)
830 }
831 rvParts := make([]string, 0, len(parts))
832 rvParts = append(rvParts, role)
833 for _, k := range keys {
834 v, ok := parts[k]
835 if !ok {
836 return "", fmt.Errorf("Invalid job parts; missing %q", k)
837 }
838 rvParts = append(rvParts, v)
839 }
840 if _, ok := parts["extra_config"]; ok {
841 rvParts = append(rvParts, parts["extra_config"])
842 }
843 return strings.Join(rvParts, s.Sep), nil
844}