diff --git a/engine/hatchery/swarm/swarm.go b/engine/hatchery/swarm/swarm.go index 2e41bda135..d8e6ddbbc7 100644 --- a/engine/hatchery/swarm/swarm.go +++ b/engine/hatchery/swarm/swarm.go @@ -294,6 +294,10 @@ func (h *HatcherySwarm) SpawnWorker(ctx context.Context, spawnArgs hatchery.Spaw serviceMemory = int64(i) } } + serviceMemorySwap := int64(-1) + if h.Config.DisableMemorySwap { + serviceMemorySwap = 0 + } var cmdArgs []string if sa, ok := envm["CDS_SERVICE_ARGS"]; ok { @@ -328,7 +332,6 @@ func (h *HatcherySwarm) SpawnWorker(ctx context.Context, spawnArgs hatchery.Spaw labels[hatchery.LabelServiceJobID] = fmt.Sprintf("%d", spawnArgs.JobID) labels[hatchery.LabelServiceID] = fmt.Sprintf("%d", r.ID) labels[hatchery.LabelServiceReqName] = r.Name - } //Start the services @@ -341,6 +344,7 @@ func (h *HatcherySwarm) SpawnWorker(ctx context.Context, spawnArgs hatchery.Spaw env: env, labels: labels, memory: serviceMemory, + memorySwap: serviceMemorySwap, entryPoint: nil, } diff --git a/engine/hatchery/swarm/swarm_util_create.go b/engine/hatchery/swarm/swarm_util_create.go index bb8a488322..6f16206c74 100644 --- a/engine/hatchery/swarm/swarm_util_create.go +++ b/engine/hatchery/swarm/swarm_util_create.go @@ -44,6 +44,7 @@ type containerArgs struct { cmd, env []string labels map[string]string memory int64 + memorySwap int64 dockerOpts dockerOpts entryPoint strslice.StrSlice } @@ -86,7 +87,7 @@ func (h *HatcherySwarm) createAndStartContainer(ctx context.Context, dockerClien } hostConfig.Resources = container.Resources{ Memory: cArgs.memory * 1024 * 1024, //from MB to B - MemorySwap: -1, + MemorySwap: cArgs.memorySwap, } networkingConfig := &network.NetworkingConfig{ diff --git a/engine/hatchery/swarm/swarm_util_create_test.go b/engine/hatchery/swarm/swarm_util_create_test.go index 27cb9bf34f..00de791e0c 100644 --- a/engine/hatchery/swarm/swarm_util_create_test.go +++ b/engine/hatchery/swarm/swarm_util_create_test.go @@ -110,11 +110,12 @@ func Test_computeDockerOpts(t *testing.T) { func TestHatcherySwarm_createAndStartContainer(t *testing.T) { h := testSwarmHatchery(t) args := containerArgs{ - name: "my-nginx", - image: "nginx:latest", - env: []string{"FROM_CDS", "FROM_CDS"}, - labels: map[string]string{"FROM_CDS": "FROM_CDS"}, - memory: 256, + name: "my-nginx", + image: "nginx:latest", + env: []string{"FROM_CDS", "FROM_CDS"}, + labels: map[string]string{"FROM_CDS": "FROM_CDS"}, + memory: 256, + memorySwap: -1, } // RegisterOnly = true, this will pull image if image is not found @@ -138,12 +139,13 @@ func TestHatcherySwarm_createAndStartContainer(t *testing.T) { func TestHatcherySwarm_createAndStartContainerWithMount(t *testing.T) { h := testSwarmHatchery(t) args := containerArgs{ - name: "my-nginx", - image: "nginx:latest", - cmd: []string{"uname"}, - env: []string{"FROM_CDS", "FROM_CDS"}, - labels: map[string]string{"FROM_CDS": "FROM_CDS"}, - memory: 256, + name: "my-nginx", + image: "nginx:latest", + cmd: []string{"uname"}, + env: []string{"FROM_CDS", "FROM_CDS"}, + labels: map[string]string{"FROM_CDS": "FROM_CDS"}, + memory: 256, + memorySwap: -1, dockerOpts: dockerOpts{ mounts: []mount.Mount{ { @@ -188,6 +190,7 @@ func TestHatcherySwarm_createAndStartContainerWithNetwork(t *testing.T) { env: []string{"FROM_CDS", "FROM_CDS"}, labels: map[string]string{"FROM_CDS": "FROM_CDS"}, memory: 256, + memorySwap: -1, network: "my-network", networkAlias: "my-container", } diff --git a/engine/hatchery/swarm/swarm_util_kill.go b/engine/hatchery/swarm/swarm_util_kill.go index a9aa4c00fc..3e4f15a114 100644 --- a/engine/hatchery/swarm/swarm_util_kill.go +++ b/engine/hatchery/swarm/swarm_util_kill.go @@ -47,19 +47,18 @@ func (h *HatcherySwarm) killAndRemove(ctx context.Context, dockerClient *dockerC Since: "10s", } var spawnErr = sdk.SpawnErrorForm{ - Error: err.Error(), + Error: sdk.NewErrorFrom(err, "an error occurred when registering the model with container name: %s", container.Name).Error(), } logsReader, errL := dockerClient.ContainerLogs(ctx, container.ID, logsOpts) if errL != nil { log.Error(ctx, "hatchery> swarm> killAndRemove> cannot get logs from docker for containers service %s %v : %v", container.ID, container.Name, errL) spawnErr.Logs = []byte(fmt.Sprintf("unable to get container logs: %v", errL)) - } else if logsReader != nil { defer logsReader.Close() logs, errR := ioutil.ReadAll(logsReader) if errR != nil { - log.Error(ctx, "hatchery> swarm> killAndRemove> cannot read logs for containers service %s %v : %v", container.ID, container.Name, errR) + spawnErr.Logs = []byte(fmt.Sprintf("unable to get read container logs: %v", errR)) } else if logs != nil { spawnErr.Logs = logs } diff --git a/engine/hatchery/swarm/types.go b/engine/hatchery/swarm/types.go index ea4805ecbb..f6e5c978c2 100644 --- a/engine/hatchery/swarm/types.go +++ b/engine/hatchery/swarm/types.go @@ -15,7 +15,8 @@ type HatcheryConfiguration struct { MaxContainers int `mapstructure:"maxContainers" toml:"maxContainers" default:"10" commented:"false" comment:"Max Containers on Host managed by this Hatchery" json:"maxContainers"` // DefaultMemory Worker default memory - DefaultMemory int `mapstructure:"defaultMemory" toml:"defaultMemory" default:"1024" commented:"false" comment:"Worker default memory in Mo" json:"defaultMemory"` + DefaultMemory int `mapstructure:"defaultMemory" toml:"defaultMemory" default:"1024" commented:"false" comment:"Worker default memory in Mo" json:"defaultMemory"` + DisableMemorySwap bool `mapstructure:"disableMemorySwap" toml:"disableMemorySwap" default:"false" commented:"true" comment:"Set to true to disable memory swap" json:"disableMemorySwap"` // WorkerTTL Worker TTL (minutes) WorkerTTL int `mapstructure:"workerTTL" toml:"workerTTL" default:"10" commented:"false" comment:"Worker TTL (minutes)" json:"workerTTL"` diff --git a/engine/worker/internal/action/builtin_script.go b/engine/worker/internal/action/builtin_script.go index b8e63f265e..28c90c99cd 100644 --- a/engine/worker/internal/action/builtin_script.go +++ b/engine/worker/internal/action/builtin_script.go @@ -30,8 +30,15 @@ type script struct { } func prepareScriptContent(parameters []sdk.Parameter, basedir afero.Fs, workdir afero.File) (*script, error) { - var script = script{ - shell: "/bin/sh", + var script = script{} + + // Set default shell based on os + if isWindows() { + script.shell = "PowerShell" + script.opts = []string{"-ExecutionPolicy", "Bypass", "-Command"} + } else { + script.shell = "/bin/sh" + script.opts = []string{"-e"} } // Get script content @@ -39,12 +46,7 @@ func prepareScriptContent(parameters []sdk.Parameter, basedir afero.Fs, workdir a := sdk.ParameterFind(parameters, "script") scriptContent = a.Value - // except on windows where it's powershell - if isWindows() { - script.shell = "PowerShell" - script.opts = []string{"-ExecutionPolicy", "Bypass", "-Command"} - // on windows, we add ErrorActionPreference just below - } else if strings.HasPrefix(scriptContent, "#!") { // If user wants a specific shell, use it + if strings.HasPrefix(scriptContent, "#!") { // If user wants a specific shell, use it t := strings.SplitN(scriptContent, "\n", 2) script.shell = strings.TrimPrefix(t[0], "#!") // Find out the shebang script.shell = strings.TrimRight(script.shell, " \t\r\n") // Remove all the trailing shit @@ -52,15 +54,15 @@ func prepareScriptContent(parameters []sdk.Parameter, basedir afero.Fs, workdir script.shell = splittedShell[0] script.opts = splittedShell[1:] // if it's a shell, we add set -e to failed job when a command is failed - if isShell(script.shell) && len(splittedShell) == 1 { - script.opts = append(script.opts, "-e") + if !isWindows() && isShell(script.shell) && len(splittedShell) == 1 { + script.opts = []string{"-e"} + } + if isWindows() && isPowerShell(script.shell) && len(splittedShell) == 1 { + script.opts = []string{"-ExecutionPolicy", "Bypass", "-Command"} } if len(t) > 1 { scriptContent = t[1] } - - } else { - script.opts = []string{"-e"} } script.content = []byte(scriptContent) @@ -143,8 +145,8 @@ func writeScriptContent(ctx context.Context, script *script, fs afero.Fs, basedi } } - if isWindows() { - //This aims to stop a the very first error and return the right exit code + if isWindows() && isPowerShell(script.shell) { + // This aims to stop a the very first error and return the right exit code psCommand := fmt.Sprintf("& { $ErrorActionPreference='Stop'; & %s ;exit $LastExitCode}", realScriptPath) script.opts = append(script.opts, psCommand) } else { @@ -313,3 +315,12 @@ func isShell(in string) bool { } return false } + +func isPowerShell(in string) bool { + for _, v := range []string{"PowerShell", "pwsh.exe", "powershell.exe"} { + if strings.HasSuffix(in, v) { + return true + } + } + return false +} diff --git a/sdk/hatchery/register.go b/sdk/hatchery/register.go index 5cf9314453..2728ec07e9 100644 --- a/sdk/hatchery/register.go +++ b/sdk/hatchery/register.go @@ -92,15 +92,13 @@ loopModels: // CheckWorkerModelRegister checks if a model has been registered, if not it raises an error on the API func CheckWorkerModelRegister(h Interface, modelPath string) error { var sendError bool - var m *sdk.Model for i := range models { - m = &models[i] - if m.Group.Name+"/"+m.Name == modelPath { - sendError = m.NeedRegistration + if models[i].Group.Name+"/"+models[i].Name == modelPath { + sendError = models[i].NeedRegistration break } } - if m != nil && sendError { + if sendError { return sdk.WithStack(sdk.ErrWorkerModelDeploymentFailed) } return nil