From 05f8a8ebef99c02dd3628422575543dc633860c6 Mon Sep 17 00:00:00 2001 From: Yvonnick Esnault <yvonnick.esnault@corp.ovh.com> Date: Mon, 4 May 2020 08:51:18 +0200 Subject: [PATCH] feat(hatchery): region prerequisite (#5151) Signed-off-by: Yvonnick Esnault <yvonnick.esnault@corp.ovh.com> --- .../docs/concepts/requirement/_index.md | 2 ++ .../requirement/requirement_region.md | 19 ++++++++++ engine/api/workermodel/registration.go | 16 +++++---- engine/hatchery/local/local.go | 31 ++++------------ engine/hatchery/marathon/helper_test.go | 6 ++-- engine/hatchery/marathon/marathon.go | 3 +- engine/service/types.go | 12 +++---- engine/worker/internal/requirement.go | 6 ++++ sdk/exportentities/pipeline.go | 7 ++++ sdk/exportentities/pipeline_test.go | 8 ++++- sdk/hatchery/hatchery.go | 35 ++++++++++++------- sdk/hatchery/pool.go | 26 +++++++------- sdk/requirement.go | 11 +++--- tests/06_sc_workflow_with_marathon.yml | 1 + tests/fixtures/ITSCWRKFLW17/pipeline.yml | 1 + tests/test.sh | 4 +++ ui/src/app/shared/action/action.component.ts | 11 +++++- .../form/requirements.form.component.ts | 8 +++-- .../requirements/form/requirements.form.html | 4 +-- .../action/form/action.form.component.ts | 11 +++++- ui/src/assets/i18n/en.json | 1 + ui/src/assets/i18n/fr.json | 3 +- 22 files changed, 146 insertions(+), 80 deletions(-) create mode 100644 docs/content/docs/concepts/requirement/requirement_region.md diff --git a/docs/content/docs/concepts/requirement/_index.md b/docs/content/docs/concepts/requirement/_index.md index 9af2efb1a7..7eca529647 100644 --- a/docs/content/docs/concepts/requirement/_index.md +++ b/docs/content/docs/concepts/requirement/_index.md @@ -15,6 +15,7 @@ Requirement types: - [Service]({{< relref "/docs/concepts/requirement/requirement_service.md" >}}) - [Memory]({{< relref "/docs/concepts/requirement/requirement_memory.md" >}}) - [OS & Architecture]({{< relref "/docs/concepts/requirement/requirement_os_arch.md" >}}) +- [Region]({{< relref "/docs/concepts/requirement/requirement_region.md" >}}) A [Job]({{< relref "/docs/concepts/job.md" >}}) will be executed by a **worker**. @@ -26,3 +27,4 @@ You can set as many requirements as you want, following these rules: - Only one hostname can be set as requirement - Only one OS & Architecture requirement can be set at a time - Memory and Services requirements are available only on Docker models +- Only one region can be set as requirement diff --git a/docs/content/docs/concepts/requirement/requirement_region.md b/docs/content/docs/concepts/requirement/requirement_region.md new file mode 100644 index 0000000000..6b0776f89a --- /dev/null +++ b/docs/content/docs/concepts/requirement/requirement_region.md @@ -0,0 +1,19 @@ +--- +title: "Region Requirement" +weight: 4 +--- + +The `Region` prerequisite allows you to require a worker to have access to a specific region. + +A `Region` can be configured on each hatchery. With a free text as `myregion` in hatchery configuration, +user can set a prerequisite 'region' with value `myregion` on CDS Job. + +Example of job configuration: +``` +jobs: +- job: build + requirements: + - region: myregion + steps: + ... +``` diff --git a/engine/api/workermodel/registration.go b/engine/api/workermodel/registration.go index 7787f5a041..b81ef555af 100644 --- a/engine/api/workermodel/registration.go +++ b/engine/api/workermodel/registration.go @@ -2,7 +2,6 @@ package workermodel import ( "context" - "errors" "strconv" "strings" "time" @@ -24,9 +23,7 @@ const ( // setting flag need_registration to true in DB. func ComputeRegistrationNeeds(db gorp.SqlExecutor, allBinaryReqs sdk.RequirementList, reqs sdk.RequirementList) error { log.Debug("ComputeRegistrationNeeds>") - var nbModelReq int - var nbOSArchReq int - var nbHostnameReq int + var nbModelReq, nbOSArchReq, nbHostnameReq, nbRegionReq int for _, r := range reqs { switch r.Type { @@ -47,17 +44,22 @@ func ComputeRegistrationNeeds(db gorp.SqlExecutor, allBinaryReqs sdk.Requirement nbModelReq++ case sdk.HostnameRequirement: nbHostnameReq++ + case sdk.RegionRequirement: + nbRegionReq++ } } if nbOSArchReq > 1 { - return sdk.NewError(sdk.ErrWrongRequest, errors.New("invalid os-architecture requirement usage")) + return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid os-architecture requirement usage") } if nbModelReq > 1 { - return sdk.NewError(sdk.ErrWrongRequest, errors.New("invalid model requirement usage")) + return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid model requirement usage") } if nbHostnameReq > 1 { - return sdk.NewError(sdk.ErrWrongRequest, errors.New("invalid hostname requirement usage")) + return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid hostname requirement usage") + } + if nbRegionReq > 1 { + return sdk.NewErrorFrom(sdk.ErrWrongRequest, "invalid region requirement usage") } return nil diff --git a/engine/hatchery/local/local.go b/engine/hatchery/local/local.go index d4d1db9b4f..42dff52035 100644 --- a/engine/hatchery/local/local.go +++ b/engine/hatchery/local/local.go @@ -178,31 +178,6 @@ func (h *HatcheryLocal) getWorkerBinaryName() string { return workerName } -// checkCapabilities checks all requirements, foreach type binary, check if binary is on current host -// returns an error "Exit status X" if current host misses one requirement -func (h *HatcheryLocal) checkCapabilities(req []sdk.Requirement) ([]sdk.Requirement, error) { - var tmp map[string]sdk.Requirement - - tmp = make(map[string]sdk.Requirement) - for _, r := range req { - ok, err := h.checkRequirement(r) - if err != nil { - return nil, err - } - - if ok { - tmp[r.Name] = r - } - } - - capa := make([]sdk.Requirement, 0, len(tmp)) - for _, r := range tmp { - capa = append(capa, r) - } - - return capa, nil -} - //Configuration returns Hatchery CommonConfiguration func (h *HatcheryLocal) Configuration() service.HatcheryCommonConfiguration { return h.Config.HatcheryCommonConfiguration @@ -350,6 +325,12 @@ func (h *HatcheryLocal) checkRequirement(r sdk.Requirement) (bool, error) { return true, nil case sdk.PluginRequirement: return true, nil + case sdk.RegionRequirement: + if r.Value != h.Configuration().Provision.Region { + log.Debug("checkRequirement> job with region requirement: cannot spawn. hatchery-region:%s prerequisite:%s", h.Configuration().Provision.Region, r.Value) + return false, nil + } + return true, nil case sdk.OSArchRequirement: osarch := strings.Split(r.Value, "/") if len(osarch) != 2 { diff --git a/engine/hatchery/marathon/helper_test.go b/engine/hatchery/marathon/helper_test.go index 6fa2473e71..9cc10340b6 100644 --- a/engine/hatchery/marathon/helper_test.go +++ b/engine/hatchery/marathon/helper_test.go @@ -1,10 +1,12 @@ package marathon import ( + "time" + "github.com/gambol99/go-marathon" - "github.com/ovh/cds/sdk/cdsclient" "gopkg.in/h2non/gock.v1" - "time" + + "github.com/ovh/cds/sdk/cdsclient" ) type marathonJDD struct { diff --git a/engine/hatchery/marathon/marathon.go b/engine/hatchery/marathon/marathon.go index a09ccb4024..c0c793973b 100644 --- a/engine/hatchery/marathon/marathon.go +++ b/engine/hatchery/marathon/marathon.go @@ -13,14 +13,13 @@ import ( "time" "github.com/dgrijalva/jwt-go" - "github.com/ovh/cds/engine/service" - "github.com/gambol99/go-marathon" "github.com/gorilla/mux" "github.com/ovh/cds/engine/api" "github.com/ovh/cds/engine/api/observability" "github.com/ovh/cds/engine/api/services" + "github.com/ovh/cds/engine/service" "github.com/ovh/cds/sdk" "github.com/ovh/cds/sdk/cdsclient" "github.com/ovh/cds/sdk/hatchery" diff --git a/engine/service/types.go b/engine/service/types.go index 2c9b16b44a..a905aab252 100644 --- a/engine/service/types.go +++ b/engine/service/types.go @@ -40,12 +40,12 @@ type HatcheryCommonConfiguration struct { MaxHeartbeatFailures int `toml:"maxHeartbeatFailures" default:"10" comment:"Maximum allowed consecutives failures on heatbeat routine" json:"maxHeartbeatFailures"` } `toml:"api" json:"api"` Provision struct { - Disabled bool `toml:"disabled" default:"false" comment:"Disabled provisioning. Format:true or false" json:"disabled"` - RatioService *int `toml:"ratioService" default:"50" commented:"true" comment:"Percent reserved for spawning worker with service requirement" json:"ratioService,omitempty" mapstructure:"ratioService"` - MaxWorker int `toml:"maxWorker" default:"10" comment:"Maximum allowed simultaneous workers" json:"maxWorker"` - MaxConcurrentProvisioning int `toml:"maxConcurrentProvisioning" default:"10" comment:"Maximum allowed simultaneous workers provisioning" json:"maxConcurrentProvisioning"` - MaxConcurrentRegistering int `toml:"maxConcurrentRegistering" default:"2" comment:"Maximum allowed simultaneous workers registering. -1 to disable registering on this hatchery" json:"maxConcurrentRegistering"` - RegisterFrequency int `toml:"registerFrequency" default:"60" comment:"Check if some worker model have to be registered each n Seconds" json:"registerFrequency"` + RatioService *int `toml:"ratioService" default:"50" commented:"true" comment:"Percent reserved for spawning worker with service requirement" json:"ratioService,omitempty" mapstructure:"ratioService"` + MaxWorker int `toml:"maxWorker" default:"10" comment:"Maximum allowed simultaneous workers" json:"maxWorker"` + MaxConcurrentProvisioning int `toml:"maxConcurrentProvisioning" default:"10" comment:"Maximum allowed simultaneous workers provisioning" json:"maxConcurrentProvisioning"` + MaxConcurrentRegistering int `toml:"maxConcurrentRegistering" default:"2" comment:"Maximum allowed simultaneous workers registering. -1 to disable registering on this hatchery" json:"maxConcurrentRegistering"` + RegisterFrequency int `toml:"registerFrequency" default:"60" comment:"Check if some worker model have to be registered each n Seconds" json:"registerFrequency"` + 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"` WorkerLogsOptions struct { Graylog struct { Host string `toml:"host" comment:"Example: thot.ovh.com" json:"host"` diff --git a/engine/worker/internal/requirement.go b/engine/worker/internal/requirement.go index e3bbff1c20..3cacb004e2 100644 --- a/engine/worker/internal/requirement.go +++ b/engine/worker/internal/requirement.go @@ -26,6 +26,7 @@ var requirementCheckFuncs = map[string]func(w *CurrentWorker, r sdk.Requirement) sdk.MemoryRequirement: checkMemoryRequirement, sdk.VolumeRequirement: checkVolumeRequirement, sdk.OSArchRequirement: checkOSArchRequirement, + sdk.RegionRequirement: checkRegionRequirement, } 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) { return osarch[0] == strings.ToLower(sdk.GOOS) && osarch[1] == strings.ToLower(sdk.GOARCH), nil } +// region is checked by hatchery only +func checkRegionRequirement(w *CurrentWorker, r sdk.Requirement) (bool, error) { + return true, nil +} + // checkPluginDeployment returns true if current job: // - is not linked to a deployment integration // - is linked to a deployement integration, plugin well downloaded (in this func) and diff --git a/sdk/exportentities/pipeline.go b/sdk/exportentities/pipeline.go index f620e23926..85bd538e94 100644 --- a/sdk/exportentities/pipeline.go +++ b/sdk/exportentities/pipeline.go @@ -60,6 +60,7 @@ type Requirement struct { Service ServiceRequirement `json:"service,omitempty" yaml:"service,omitempty"` Memory string `json:"memory,omitempty" yaml:"memory,omitempty"` OSArchRequirement string `json:"os-architecture,omitempty" yaml:"os-architecture,omitempty"` + RegionRequirement string `json:"region,omitempty" yaml:"region,omitempty"` } // ServiceRequirement represents an exported sdk.Requirement of type ServiceRequirement @@ -151,6 +152,8 @@ func newRequirements(req []sdk.Requirement) []Requirement { res = append(res, Requirement{Service: ServiceRequirement{Name: r.Name, Value: r.Value}}) case sdk.OSArchRequirement: res = append(res, Requirement{OSArchRequirement: r.Value}) + case sdk.RegionRequirement: + res = append(res, Requirement{RegionRequirement: r.Value}) case sdk.MemoryRequirement: res = append(res, Requirement{Memory: r.Value}) } @@ -222,6 +225,10 @@ func computeJobRequirements(req []Requirement) []sdk.Requirement { name = r.OSArchRequirement val = r.OSArchRequirement tpe = sdk.OSArchRequirement + } else if r.RegionRequirement != "" { + name = "region" + val = r.RegionRequirement + tpe = sdk.RegionRequirement } else if r.Plugin != "" { name = r.Plugin val = r.Plugin diff --git a/sdk/exportentities/pipeline_test.go b/sdk/exportentities/pipeline_test.go index 6d81750762..955af45190 100644 --- a/sdk/exportentities/pipeline_test.go +++ b/sdk/exportentities/pipeline_test.go @@ -41,6 +41,11 @@ var ( Type: sdk.OSArchRequirement, Value: "freebsd/amd64", }, + { + Name: sdk.RegionRequirement, + Type: sdk.RegionRequirement, + Value: "graxyz", + }, }, Actions: []sdk.Action{ { @@ -542,6 +547,7 @@ jobs: requirements: - binary: git - os-archicture: freebsd/amd64 + - region: graxyz steps: - gitClone: branch: '{{.git.branch}}' @@ -568,7 +574,7 @@ jobs: test.NoError(t, err) assert.Len(t, p.Stages[0].Jobs[0].Action.Actions, 3) - assert.Len(t, p.Stages[0].Jobs[0].Action.Requirements, 2) + assert.Len(t, p.Stages[0].Jobs[0].Action.Requirements, 3) assert.Equal(t, sdk.GitCloneAction, p.Stages[0].Jobs[0].Action.Actions[0].Name) assert.Equal(t, sdk.ArtifactUpload, p.Stages[0].Jobs[0].Action.Actions[1].Name) assert.Equal(t, sdk.ServeStaticFiles, p.Stages[0].Jobs[0].Action.Actions[2].Name) diff --git a/sdk/hatchery/hatchery.go b/sdk/hatchery/hatchery.go index 0d085e4403..9d45ddfab3 100644 --- a/sdk/hatchery/hatchery.go +++ b/sdk/hatchery/hatchery.go @@ -278,11 +278,17 @@ func canRunJob(ctx context.Context, h Interface, j workerStarterRequest) bool { return false } + if r.Type == sdk.RegionRequirement && r.Value != h.Configuration().Provision.Region { + log.Debug("canRunJob> %d - job %d - job with region requirement: cannot spawn. hatchery-region:%s prerequisite:%s", j.timestamp, j.id, h.Configuration().Provision.Region, r.Value) + return false + } + // Skip others requirement as we can't check it if r.Type == sdk.PluginRequirement || r.Type == sdk.ServiceRequirement || r.Type == sdk.MemoryRequirement { - log.Debug("canRunJob> %d - job %d - job with service, plugin, network or memory requirement. Skip these check as we can't checkt it on hatchery routine", j.timestamp, j.id) + log.Debug("canRunJob> %d - job %d - job with service, plugin, network or memory requirement. Skip these check as we can't check it on hatchery routine", j.timestamp, j.id) continue } + } return h.CanSpawn(ctx, nil, j.id, j.requirements) } @@ -293,18 +299,18 @@ const MemoryRegisterContainer int64 = 128 func canRunJobWithModel(ctx context.Context, h InterfaceWithModels, j workerStarterRequest, model *sdk.Model) bool { if model.Type != h.ModelType() { - log.Debug("canRunJob> model %s type:%s current hatchery modelType: %s", model.Name, model.Type, h.ModelType()) + log.Debug("canRunJobWithModel> model %s type:%s current hatchery modelType: %s", model.Name, model.Type, h.ModelType()) return false } // If the model needs registration, don't spawn for now if h.NeedRegistration(ctx, model) { - log.Debug("canRunJob> model %s needs registration", model.Name) + log.Debug("canRunJobWithModel> model %s needs registration", model.Name) return false } if model.NbSpawnErr > 5 { - log.Warning(ctx, "canRunJob> Too many errors on spawn with model %s, please check this worker model", model.Name) + log.Warning(ctx, "canRunJobWithModel> Too many errors on spawn with model %s, please check this worker model", model.Name) return false } @@ -317,7 +323,7 @@ func canRunJobWithModel(ctx context.Context, h InterfaceWithModels, j workerStar } } if !checkGroup { - log.Debug("canRunJob> job %d - model %s attached to group %d can't run this job", j.id, model.Name, model.GroupID) + log.Debug("canRunJobWithModel> job %d - model %s attached to group %d can't run this job", j.id, model.Name, model.GroupID) return false } } @@ -333,7 +339,7 @@ func canRunJobWithModel(ctx context.Context, h InterfaceWithModels, j workerStar } if model.IsDeprecated && !containsModelRequirement { - log.Debug("canRunJob> %d - job %d - Cannot launch this model because it is deprecated", j.timestamp, j.id) + log.Debug("canRunJobWithModel> %d - job %d - Cannot launch this model because it is deprecated", j.timestamp, j.id) return false } @@ -348,30 +354,35 @@ func canRunJobWithModel(ctx context.Context, h InterfaceWithModels, j workerStar isSharedInfraModel := model.Group.Name == sdk.SharedInfraGroupName && modelName == model.Name isSameName := modelName == model.Name // for backward compatibility with runs, if only the name match we considered that the model can be used, keep this condition until the workflow runs were not migrated. if !isGroupModel && !isSharedInfraModel && !isSameName { - log.Debug("canRunJob> %d - job %d - model requirement r.Value(%s) do not match model.Name(%s) and model.Group(%s)", j.timestamp, j.id, strings.Split(r.Value, " ")[0], model.Name, model.Group.Name) + log.Debug("canRunJobWithModel> %d - job %d - model requirement r.Value(%s) do not match model.Name(%s) and model.Group(%s)", j.timestamp, j.id, strings.Split(r.Value, " ")[0], model.Name, model.Group.Name) return false } } // service and memory requirements are only supported by docker model if model.Type != sdk.Docker && (r.Type == sdk.ServiceRequirement || r.Type == sdk.MemoryRequirement) { - log.Debug("canRunJob> %d - job %d - job with service requirement or memory requirement: only for model docker. current model:%s", j.timestamp, j.id, model.Type) + log.Debug("canRunJobWithModel> %d - job %d - job with service requirement or memory requirement: only for model docker. current model:%s", j.timestamp, j.id, model.Type) return false } if r.Type == sdk.NetworkAccessRequirement && !sdk.CheckNetworkAccessRequirement(r) { - log.Debug("canRunJob> %d - job %d - network requirement failed: %v", j.timestamp, j.id, r.Value) + log.Debug("canRunJobWithModel> %d - job %d - network requirement failed: %v", j.timestamp, j.id, r.Value) return false } // Skip other requirement as we can't check it if r.Type == sdk.PluginRequirement || r.Type == sdk.ServiceRequirement || r.Type == sdk.MemoryRequirement { - log.Debug("canRunJob> %d - job %d - job with service, plugin, network or memory requirement. Skip these check as we can't check it on hatchery routine", j.timestamp, j.id) + log.Debug("canRunJobWithModel> %d - job %d - job with service, plugin, network or memory requirement. Skip these check as we can't check it on hatchery routine", j.timestamp, j.id) continue } if r.Type == sdk.OSArchRequirement && model.RegisteredOS != "" && model.RegisteredArch != "" && r.Value != (model.RegisteredOS+"/"+model.RegisteredArch) { - log.Debug("canRunJob> %d - job %d - job with OSArch requirement: cannot spawn on this OSArch. current model: %s/%s", j.timestamp, j.id, model.RegisteredOS, model.RegisteredArch) + log.Debug("canRunJobWithModel> %d - job %d - job with OSArch requirement: cannot spawn on this OSArch. current model: %s/%s", j.timestamp, j.id, model.RegisteredOS, model.RegisteredArch) + return false + } + + if r.Type == sdk.RegionRequirement && r.Value != h.Configuration().Provision.Region { + log.Debug("canRunJobWithModel> %d - job %d - job with region requirement: cannot spawn. hatchery-region:%s prerequisite:%s", j.timestamp, j.id, h.Configuration().Provision.Region, r.Value) return false } @@ -387,7 +398,7 @@ func canRunJobWithModel(ctx context.Context, h InterfaceWithModels, j workerStar } if !found { - log.Debug("canRunJob> %d - job %d - model(%s) does not have binary %s(%s) for this job.", j.timestamp, j.id, model.Name, r.Name, r.Value) + log.Debug("canRunJobWithModel> %d - job %d - model(%s) does not have binary %s(%s) for this job.", j.timestamp, j.id, model.Name, r.Name, r.Value) return false } } diff --git a/sdk/hatchery/pool.go b/sdk/hatchery/pool.go index 19f62dc52c..074195e062 100644 --- a/sdk/hatchery/pool.go +++ b/sdk/hatchery/pool.go @@ -14,7 +14,7 @@ import ( ) // WorkerPool returns all the worker owned by the hatchery h, registered or not on the CDS API -func WorkerPool(ctx context.Context, h Interface, status ...string) ([]sdk.Worker, error) { +func WorkerPool(ctx context.Context, h Interface, statusFilter ...string) ([]sdk.Worker, error) { ctx = observability.ContextWithTag(ctx, observability.TagServiceName, h.Name(), observability.TagServiceType, h.Type(), @@ -30,9 +30,6 @@ func WorkerPool(ctx context.Context, h Interface, status ...string) ([]sdk.Worke // Then: get all workers in the orchestrator queue startedWorkers := h.WorkersStarted(ctx) - if err != nil { - return nil, fmt.Errorf("unable to get started workers: %v", err) - } // Make the union of the two slices allWorkers := make([]sdk.Worker, 0, len(startedWorkers)+len(registeredWorkers)) @@ -106,17 +103,18 @@ func WorkerPool(ctx context.Context, h Interface, status ...string) ([]sdk.Worke } stats.Record(ctx, measures...) - // Filter by status + // no filter on status, returns the workers list as is. + if len(statusFilter) == 0 { + return allWorkers, nil + } + + // return workers list filtered by status res := make([]sdk.Worker, 0, len(allWorkers)) - if len(status) == 0 { - res = allWorkers - } else { - for _, w := range allWorkers { - for _, s := range status { - if s == w.Status { - res = append(res, w) - break - } + for _, w := range allWorkers { + for _, s := range statusFilter { + if s == w.Status { + res = append(res, w) + break } } } diff --git a/sdk/requirement.go b/sdk/requirement.go index 03e566e80e..a1bd972b15 100644 --- a/sdk/requirement.go +++ b/sdk/requirement.go @@ -25,6 +25,8 @@ const ( VolumeRequirement = "volume" // OSArchRequirement checks the 'dist' of a worker eg {GOOS}/{GOARCH} OSArchRequirement = "os-architecture" + // RegionRequirement lets a use to force a job running in a hatchery's region + RegionRequirement = "region" ) // RequirementList is a list of requirement @@ -89,14 +91,15 @@ var ( // AvailableRequirementsType List of all requirements AvailableRequirementsType = []string{ BinaryRequirement, - NetworkAccessRequirement, - ModelRequirement, HostnameRequirement, + MemoryRequirement, + ModelRequirement, + NetworkAccessRequirement, + OSArchRequirement, PluginRequirement, + RegionRequirement, ServiceRequirement, - MemoryRequirement, VolumeRequirement, - OSArchRequirement, } // OSArchRequirementValues comes from go tool dist list diff --git a/tests/06_sc_workflow_with_marathon.yml b/tests/06_sc_workflow_with_marathon.yml index 7651a6cca7..b2a3830760 100644 --- a/tests/06_sc_workflow_with_marathon.yml +++ b/tests/06_sc_workflow_with_marathon.yml @@ -8,6 +8,7 @@ testcases: - script: '[ -f ./fixtures/ITSCWRKFLW17/workflow.yml ]' # check file exists - script: '[ -z "${CDS_MODEL_REQ}" ] && exit 1 || exit 0' # check that the env variable is set - script: '[ -z "${CDS_NETWORK_REQ}" ] && exit 1 || exit 0' # check that the env variable is set + - script: '[ -z "${CDS_REGION_REQ}" ] && exit 1 || exit 0' # check that the env variable is set - name: prepare_tests steps: diff --git a/tests/fixtures/ITSCWRKFLW17/pipeline.yml b/tests/fixtures/ITSCWRKFLW17/pipeline.yml index c2e36c2cbd..0a49a5f0a0 100644 --- a/tests/fixtures/ITSCWRKFLW17/pipeline.yml +++ b/tests/fixtures/ITSCWRKFLW17/pipeline.yml @@ -10,3 +10,4 @@ jobs: requirements: - model: "${CDS_MODEL_REQ}" - network: "${CDS_NETWORK_REQ}" + - region: "${CDS_REGION_REQ}" diff --git a/tests/test.sh b/tests/test.sh index d5e948c035..6120074517 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -44,6 +44,8 @@ INIT_TOKEN="${INIT_TOKEN:-}" CDS_MODEL_REQ="${CDS_MODEL_REQ:-buildpack-deps}" # If you want to run some tests with a specific network requirements, set CDS_NETWORK_REQ CDS_NETWORK_REQ="${CDS_NETWORK_REQ:-$CDS_API_URL}" +# If you want to run some tests with a specific region requirement, set CDS_REGION_REQ +CDS_REGION_REQ="${CDS_REGION_REQ:-""}" # The default values below fit to default minio installation. # Run "make minio_start" to start a minio docker container @@ -148,6 +150,7 @@ workflow_with_integration_tests() { workflow_with_third_parties() { if [ -z "$CDS_MODEL_REQ" ]; then echo "missing CDS_MODEL_REQ variable"; exit 1; fi if [ -z "$CDS_NETWORK_REQ" ]; then echo "missing CDS_NETWORK_REQ variable"; exit 1; fi + if [ -z "$CDS_REGION_REQ" ]; then echo "missing CDS_REGION_REQ variable"; exit 1; fi echo "Running Workflow with third parties:" for f in $(ls -1 06_*.yml); do CMD="${VENOM} run ${VENOM_OPTS} ${f} --var cdsctl=${CDSCTL} --var cdsctl.config=${CDSCTL_CONFIG}" @@ -182,6 +185,7 @@ for target in $@; do workflow_with_third_parties) export CDS_MODEL_REQ export CDS_NETWORK_REQ + export CDS_REGION_REQ workflow_with_third_parties;; *) echo -e "${RED}Error: unknown target: $target${NOCOLOR}" usage diff --git a/ui/src/app/shared/action/action.component.ts b/ui/src/app/shared/action/action.component.ts index cc4c2f443f..ef23eeb45c 100644 --- a/ui/src/app/shared/action/action.component.ts +++ b/ui/src/app/shared/action/action.component.ts @@ -67,7 +67,7 @@ export class ActionComponent implements OnDestroy, OnInit { @Output() actionEvent = new EventEmitter<ActionEvent>(); collapsed = true; - configRequirements: { disableModel?: boolean, disableHostname?: boolean } = {}; + configRequirements: { disableModel?: boolean, disableHostname?: boolean, disableRegion?: boolean } = {}; workerModels: Array<WorkerModel>; constructor( @@ -143,6 +143,9 @@ export class ActionComponent implements OnDestroy, OnInit { if (r.requirement.type === 'hostname') { this.configRequirements.disableHostname = true; } + if (r.requirement.type === 'region') { + this.configRequirements.disableRegion = true; + } break; case 'delete': let indexDelete = this.editableAction.requirements.indexOf(r.requirement); @@ -155,6 +158,9 @@ export class ActionComponent implements OnDestroy, OnInit { if (r.requirement.type === 'hostname') { this.configRequirements.disableHostname = false; } + if (r.requirement.type === 'region') { + this.configRequirements.disableRegion = false; + } break; } } @@ -177,6 +183,9 @@ export class ActionComponent implements OnDestroy, OnInit { if (req.type === 'hostname') { this.configRequirements.disableHostname = true; } + if (req.type === 'region') { + this.configRequirements.disableRegion = true; + } }); } diff --git a/ui/src/app/shared/requirements/form/requirements.form.component.ts b/ui/src/app/shared/requirements/form/requirements.form.component.ts index 5c71b9b996..89e815c317 100644 --- a/ui/src/app/shared/requirements/form/requirements.form.component.ts +++ b/ui/src/app/shared/requirements/form/requirements.form.component.ts @@ -44,7 +44,7 @@ export class RequirementsFormComponent implements OnInit { @Input() modal: SemanticModalComponent; - @Input() config: { disableModel?: boolean, disableHostname?: boolean }; + @Input() config: { disableModel?: boolean, disableHostname?: boolean, disableRegion?: boolean }; @Output() event = new EventEmitter<RequirementEvent>(); @@ -139,14 +139,18 @@ export class RequirementsFormComponent implements OnInit { this.popupText = ''; let goodModel = this.newRequirement.type !== 'model' || !this.config.disableModel; let goodHostname = this.newRequirement.type !== 'hostname' || !this.config.disableHostname; + let goodRegion = this.newRequirement.type !== 'region' || !this.config.disableRegion; this.isFormValid = (form.valid === true && this.newRequirement.name !== '' && this.newRequirement.value !== '') - && goodModel && goodHostname; + && goodModel && goodHostname && goodRegion; if (!goodModel) { this.popupText = this._translate.instant('requirement_error_model'); } if (!goodHostname) { this.popupText = this._translate.instant('requirement_error_hostname'); } + if (!goodRegion) { + this.popupText = this._translate.instant('requirement_error_region'); + } } selectType(): void { diff --git a/ui/src/app/shared/requirements/form/requirements.form.html b/ui/src/app/shared/requirements/form/requirements.form.html index 0490b8225e..9c5b23354c 100644 --- a/ui/src/app/shared/requirements/form/requirements.form.html +++ b/ui/src/app/shared/requirements/form/requirements.form.html @@ -64,14 +64,14 @@ <div suiPopup [popupText]="popupText" popupPlacement="top" - *ngIf="(newRequirement.type === 'model' && config.disableModel) || (newRequirement.type === 'hostname' && config.disableHostname)"> + *ngIf="(newRequirement.type === 'model' && config.disableModel) || (newRequirement.type === 'hostname' && config.disableHostname) || (newRequirement.type === 'region' && config.disableRegion)"> <button class="ui blue icon button" [disabled]="!isFormValid" (click)="onSubmitAddRequirement(formAddRequirement)"> <i class="plus icon"></i> </button> </div> - <div *ngIf="(newRequirement.type !== 'model' || !config.disableModel) && (newRequirement.type !== 'hostname' || !config.disableHostname)"> + <div *ngIf="(newRequirement.type !== 'model' || !config.disableModel) && (newRequirement.type !== 'hostname' || !config.disableHostname) && (newRequirement.type !== 'region' || !config.disableRegion)"> <button class="ui blue icon button" [disabled]="!isFormValid" (click)="onSubmitAddRequirement(formAddRequirement)"> diff --git a/ui/src/app/views/settings/action/form/action.form.component.ts b/ui/src/app/views/settings/action/form/action.form.component.ts index 7d3a1e8cf3..4fa5aade3a 100644 --- a/ui/src/app/views/settings/action/form/action.form.component.ts +++ b/ui/src/app/views/settings/action/form/action.form.component.ts @@ -63,7 +63,7 @@ export class ActionFormComponent implements OnDestroy { steps: Array<Action> = new Array<Action>(); actions: Array<Action> = new Array<Action>(); collapsed = true; - configRequirements: { disableModel?: boolean, disableHostname?: boolean } = {}; + configRequirements: { disableModel?: boolean, disableHostname?: boolean, disableRegion?: boolean } = {}; stepFormExpended: boolean; workerModels: Array<WorkerModel>; @@ -146,6 +146,9 @@ export class ActionFormComponent implements OnDestroy { if (r.requirement.type === 'hostname') { this.configRequirements.disableHostname = true; } + if (r.requirement.type === 'region') { + this.configRequirements.disableRegion = true; + } break; case 'delete': let indexDelete = this.action.requirements.indexOf(r.requirement); @@ -158,6 +161,9 @@ export class ActionFormComponent implements OnDestroy { if (r.requirement.type === 'hostname') { this.configRequirements.disableHostname = false; } + if (r.requirement.type === 'region') { + this.configRequirements.disableRegion = false; + } break; } } @@ -180,6 +186,9 @@ export class ActionFormComponent implements OnDestroy { if (req.type === 'hostname') { this.configRequirements.disableHostname = true; } + if (req.type === 'region') { + this.configRequirements.disableRegion = true; + } }); } diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index e58d8500c5..87e9c0b3e9 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -599,6 +599,7 @@ "requirement_documentation": "Documentation about requirements", "requirement_error_model": "You can't have multiple requirements of type model", "requirement_error_hostname": "You can't have multiple requirements of type hostname", + "requirement_error_region": "You can't have multiple requirements of type region", "requirement_placeholder_name_os-architecture": "os/arch", "requirement_placeholder_name_service": "hostnameOfService", "requirement_placeholder_value_model": "Choose a worker model", diff --git a/ui/src/assets/i18n/fr.json b/ui/src/assets/i18n/fr.json index 4c7db55fdc..b38cd51462 100644 --- a/ui/src/assets/i18n/fr.json +++ b/ui/src/assets/i18n/fr.json @@ -578,8 +578,9 @@ "repoman_verif_msg_ok": "Le gestionnaire de dépôt est désormais connecté à CDS.", "requirement_add": "Ajouter un pré-requis", "requirement_documentation": "À propos des pré-requis", - "requirement_error_hostname": "Vous ne pouvez pas ajouter plusieurs pré-requis de type hostname", "requirement_error_model": "Vous ne pouvez pas ajouter plusieurs pré-requis de type modèle", + "requirement_error_hostname": "Vous ne pouvez pas ajouter plusieurs pré-requis de type hostname", + "requirement_error_region": "Vous ne pouvez pas ajouter plusieurs pré-requis de type region", "requirement_help_binary": "Pré-requis type 'binary': CDS choisira un worker possédant ce binaire dans son PATH.", "requirement_help_hostname": "Pré-requis type 'hostname': <ul><li>Ce job sera lancé par un worker possédant ce Hostname</li></ul>", "requirement_help_memory": "Pré-requis type 'memory': <ul><li>Si vous souhaitez 5Go, entrez la valeur suivante: <b>4096</b></li><li>Le prérequis memory est disponible uniquement avec les <a target=\"_blank\" href=\"https://ovh.github.io/cds/docs/concepts/worker-model/\">Worker Model</a> de type Docker</li></ul>",