Skip to content

Commit 05f8a8e

Browse files
authored
feat(hatchery): region prerequisite (#5151)
Signed-off-by: Yvonnick Esnault <yvonnick.esnault@corp.ovh.com>
1 parent a5e7e0e commit 05f8a8e

File tree

22 files changed

+146
-80
lines changed

22 files changed

+146
-80
lines changed

docs/content/docs/concepts/requirement/_index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Requirement types:
1515
- [Service]({{< relref "/docs/concepts/requirement/requirement_service.md" >}})
1616
- [Memory]({{< relref "/docs/concepts/requirement/requirement_memory.md" >}})
1717
- [OS & Architecture]({{< relref "/docs/concepts/requirement/requirement_os_arch.md" >}})
18+
- [Region]({{< relref "/docs/concepts/requirement/requirement_region.md" >}})
1819

1920
A [Job]({{< relref "/docs/concepts/job.md" >}}) will be executed by a **worker**.
2021

@@ -26,3 +27,4 @@ You can set as many requirements as you want, following these rules:
2627
- Only one hostname can be set as requirement
2728
- Only one OS & Architecture requirement can be set at a time
2829
- Memory and Services requirements are available only on Docker models
30+
- Only one region can be set as requirement
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
title: "Region Requirement"
3+
weight: 4
4+
---
5+
6+
The `Region` prerequisite allows you to require a worker to have access to a specific region.
7+
8+
A `Region` can be configured on each hatchery. With a free text as `myregion` in hatchery configuration,
9+
user can set a prerequisite 'region' with value `myregion` on CDS Job.
10+
11+
Example of job configuration:
12+
```
13+
jobs:
14+
- job: build
15+
requirements:
16+
- region: myregion
17+
steps:
18+
...
19+
```

engine/api/workermodel/registration.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package workermodel
22

33
import (
44
"context"
5-
"errors"
65
"strconv"
76
"strings"
87
"time"
@@ -24,9 +23,7 @@ const (
2423
// setting flag need_registration to true in DB.
2524
func ComputeRegistrationNeeds(db gorp.SqlExecutor, allBinaryReqs sdk.RequirementList, reqs sdk.RequirementList) error {
2625
log.Debug("ComputeRegistrationNeeds>")
27-
var nbModelReq int
28-
var nbOSArchReq int
29-
var nbHostnameReq int
26+
var nbModelReq, nbOSArchReq, nbHostnameReq, nbRegionReq int
3027

3128
for _, r := range reqs {
3229
switch r.Type {
@@ -47,17 +44,22 @@ func ComputeRegistrationNeeds(db gorp.SqlExecutor, allBinaryReqs sdk.Requirement
4744
nbModelReq++
4845
case sdk.HostnameRequirement:
4946
nbHostnameReq++
47+
case sdk.RegionRequirement:
48+
nbRegionReq++
5049
}
5150
}
5251

5352
if nbOSArchReq > 1 {
54-
return sdk.NewError(sdk.ErrWrongRequest, errors.New("invalid os-architecture requirement usage"))
53+
return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid os-architecture requirement usage")
5554
}
5655
if nbModelReq > 1 {
57-
return sdk.NewError(sdk.ErrWrongRequest, errors.New("invalid model requirement usage"))
56+
return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid model requirement usage")
5857
}
5958
if nbHostnameReq > 1 {
60-
return sdk.NewError(sdk.ErrWrongRequest, errors.New("invalid hostname requirement usage"))
59+
return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid hostname requirement usage")
60+
}
61+
if nbRegionReq > 1 {
62+
return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid region requirement usage")
6163
}
6264

6365
return nil

engine/hatchery/local/local.go

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -178,31 +178,6 @@ func (h *HatcheryLocal) getWorkerBinaryName() string {
178178
return workerName
179179
}
180180

181-
// checkCapabilities checks all requirements, foreach type binary, check if binary is on current host
182-
// returns an error "Exit status X" if current host misses one requirement
183-
func (h *HatcheryLocal) checkCapabilities(req []sdk.Requirement) ([]sdk.Requirement, error) {
184-
var tmp map[string]sdk.Requirement
185-
186-
tmp = make(map[string]sdk.Requirement)
187-
for _, r := range req {
188-
ok, err := h.checkRequirement(r)
189-
if err != nil {
190-
return nil, err
191-
}
192-
193-
if ok {
194-
tmp[r.Name] = r
195-
}
196-
}
197-
198-
capa := make([]sdk.Requirement, 0, len(tmp))
199-
for _, r := range tmp {
200-
capa = append(capa, r)
201-
}
202-
203-
return capa, nil
204-
}
205-
206181
//Configuration returns Hatchery CommonConfiguration
207182
func (h *HatcheryLocal) Configuration() service.HatcheryCommonConfiguration {
208183
return h.Config.HatcheryCommonConfiguration
@@ -350,6 +325,12 @@ func (h *HatcheryLocal) checkRequirement(r sdk.Requirement) (bool, error) {
350325
return true, nil
351326
case sdk.PluginRequirement:
352327
return true, nil
328+
case sdk.RegionRequirement:
329+
if r.Value != h.Configuration().Provision.Region {
330+
log.Debug("checkRequirement> job with region requirement: cannot spawn. hatchery-region:%s prerequisite:%s", h.Configuration().Provision.Region, r.Value)
331+
return false, nil
332+
}
333+
return true, nil
353334
case sdk.OSArchRequirement:
354335
osarch := strings.Split(r.Value, "/")
355336
if len(osarch) != 2 {

engine/hatchery/marathon/helper_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package marathon
22

33
import (
4+
"time"
5+
46
"github.com/gambol99/go-marathon"
5-
"github.com/ovh/cds/sdk/cdsclient"
67
"gopkg.in/h2non/gock.v1"
7-
"time"
8+
9+
"github.com/ovh/cds/sdk/cdsclient"
810
)
911

1012
type marathonJDD struct {

engine/hatchery/marathon/marathon.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@ import (
1313
"time"
1414

1515
"github.com/dgrijalva/jwt-go"
16-
"github.com/ovh/cds/engine/service"
17-
1816
"github.com/gambol99/go-marathon"
1917
"github.com/gorilla/mux"
2018

2119
"github.com/ovh/cds/engine/api"
2220
"github.com/ovh/cds/engine/api/observability"
2321
"github.com/ovh/cds/engine/api/services"
22+
"github.com/ovh/cds/engine/service"
2423
"github.com/ovh/cds/sdk"
2524
"github.com/ovh/cds/sdk/cdsclient"
2625
"github.com/ovh/cds/sdk/hatchery"

engine/service/types.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ type HatcheryCommonConfiguration struct {
4040
MaxHeartbeatFailures int `toml:"maxHeartbeatFailures" default:"10" comment:"Maximum allowed consecutives failures on heatbeat routine" json:"maxHeartbeatFailures"`
4141
} `toml:"api" json:"api"`
4242
Provision struct {
43-
Disabled bool `toml:"disabled" default:"false" comment:"Disabled provisioning. Format:true or false" json:"disabled"`
44-
RatioService *int `toml:"ratioService" default:"50" commented:"true" comment:"Percent reserved for spawning worker with service requirement" json:"ratioService,omitempty" mapstructure:"ratioService"`
45-
MaxWorker int `toml:"maxWorker" default:"10" comment:"Maximum allowed simultaneous workers" json:"maxWorker"`
46-
MaxConcurrentProvisioning int `toml:"maxConcurrentProvisioning" default:"10" comment:"Maximum allowed simultaneous workers provisioning" json:"maxConcurrentProvisioning"`
47-
MaxConcurrentRegistering int `toml:"maxConcurrentRegistering" default:"2" comment:"Maximum allowed simultaneous workers registering. -1 to disable registering on this hatchery" json:"maxConcurrentRegistering"`
48-
RegisterFrequency int `toml:"registerFrequency" default:"60" comment:"Check if some worker model have to be registered each n Seconds" json:"registerFrequency"`
43+
RatioService *int `toml:"ratioService" default:"50" commented:"true" comment:"Percent reserved for spawning worker with service requirement" json:"ratioService,omitempty" mapstructure:"ratioService"`
44+
MaxWorker int `toml:"maxWorker" default:"10" comment:"Maximum allowed simultaneous workers" json:"maxWorker"`
45+
MaxConcurrentProvisioning int `toml:"maxConcurrentProvisioning" default:"10" comment:"Maximum allowed simultaneous workers provisioning" json:"maxConcurrentProvisioning"`
46+
MaxConcurrentRegistering int `toml:"maxConcurrentRegistering" default:"2" comment:"Maximum allowed simultaneous workers registering. -1 to disable registering on this hatchery" json:"maxConcurrentRegistering"`
47+
RegisterFrequency int `toml:"registerFrequency" default:"60" comment:"Check if some worker model have to be registered each n Seconds" json:"registerFrequency"`
48+
Region string `toml:"region" default:"" comment:"region of this hatchery - optional. With a free text as 'myregion', user can set a prerequisite 'region' with value 'myregion' on CDS Job" json:"region"`
4949
WorkerLogsOptions struct {
5050
Graylog struct {
5151
Host string `toml:"host" comment:"Example: thot.ovh.com" json:"host"`

engine/worker/internal/requirement.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ var requirementCheckFuncs = map[string]func(w *CurrentWorker, r sdk.Requirement)
2626
sdk.MemoryRequirement: checkMemoryRequirement,
2727
sdk.VolumeRequirement: checkVolumeRequirement,
2828
sdk.OSArchRequirement: checkOSArchRequirement,
29+
sdk.RegionRequirement: checkRegionRequirement,
2930
}
3031

3132
func checkRequirements(ctx context.Context, w *CurrentWorker, a *sdk.Action) (bool, []sdk.Requirement) {
@@ -209,6 +210,11 @@ func checkOSArchRequirement(w *CurrentWorker, r sdk.Requirement) (bool, error) {
209210
return osarch[0] == strings.ToLower(sdk.GOOS) && osarch[1] == strings.ToLower(sdk.GOARCH), nil
210211
}
211212

213+
// region is checked by hatchery only
214+
func checkRegionRequirement(w *CurrentWorker, r sdk.Requirement) (bool, error) {
215+
return true, nil
216+
}
217+
212218
// checkPluginDeployment returns true if current job:
213219
// - is not linked to a deployment integration
214220
// - is linked to a deployement integration, plugin well downloaded (in this func) and

sdk/exportentities/pipeline.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ type Requirement struct {
6060
Service ServiceRequirement `json:"service,omitempty" yaml:"service,omitempty"`
6161
Memory string `json:"memory,omitempty" yaml:"memory,omitempty"`
6262
OSArchRequirement string `json:"os-architecture,omitempty" yaml:"os-architecture,omitempty"`
63+
RegionRequirement string `json:"region,omitempty" yaml:"region,omitempty"`
6364
}
6465

6566
// ServiceRequirement represents an exported sdk.Requirement of type ServiceRequirement
@@ -151,6 +152,8 @@ func newRequirements(req []sdk.Requirement) []Requirement {
151152
res = append(res, Requirement{Service: ServiceRequirement{Name: r.Name, Value: r.Value}})
152153
case sdk.OSArchRequirement:
153154
res = append(res, Requirement{OSArchRequirement: r.Value})
155+
case sdk.RegionRequirement:
156+
res = append(res, Requirement{RegionRequirement: r.Value})
154157
case sdk.MemoryRequirement:
155158
res = append(res, Requirement{Memory: r.Value})
156159
}
@@ -222,6 +225,10 @@ func computeJobRequirements(req []Requirement) []sdk.Requirement {
222225
name = r.OSArchRequirement
223226
val = r.OSArchRequirement
224227
tpe = sdk.OSArchRequirement
228+
} else if r.RegionRequirement != "" {
229+
name = "region"
230+
val = r.RegionRequirement
231+
tpe = sdk.RegionRequirement
225232
} else if r.Plugin != "" {
226233
name = r.Plugin
227234
val = r.Plugin

sdk/exportentities/pipeline_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ var (
4141
Type: sdk.OSArchRequirement,
4242
Value: "freebsd/amd64",
4343
},
44+
{
45+
Name: sdk.RegionRequirement,
46+
Type: sdk.RegionRequirement,
47+
Value: "graxyz",
48+
},
4449
},
4550
Actions: []sdk.Action{
4651
{
@@ -542,6 +547,7 @@ jobs:
542547
requirements:
543548
- binary: git
544549
- os-archicture: freebsd/amd64
550+
- region: graxyz
545551
steps:
546552
- gitClone:
547553
branch: '{{.git.branch}}'
@@ -568,7 +574,7 @@ jobs:
568574
test.NoError(t, err)
569575

570576
assert.Len(t, p.Stages[0].Jobs[0].Action.Actions, 3)
571-
assert.Len(t, p.Stages[0].Jobs[0].Action.Requirements, 2)
577+
assert.Len(t, p.Stages[0].Jobs[0].Action.Requirements, 3)
572578
assert.Equal(t, sdk.GitCloneAction, p.Stages[0].Jobs[0].Action.Actions[0].Name)
573579
assert.Equal(t, sdk.ArtifactUpload, p.Stages[0].Jobs[0].Action.Actions[1].Name)
574580
assert.Equal(t, sdk.ServeStaticFiles, p.Stages[0].Jobs[0].Action.Actions[2].Name)

0 commit comments

Comments
 (0)