github.com/wtsi-hgi/go-softpack-builder@v1.8.1/wr/wr_test.go (about)

     1  /*******************************************************************************
     2   * Copyright (c) 2023, 2024 Genome Research Ltd.
     3   *
     4   * Permission is hereby granted, free of charge, to any person obtaining
     5   * a copy of this software and associated documentation files (the
     6   * "Software"), to deal in the Software without restriction, including
     7   * without limitation the rights to use, copy, modify, merge, publish,
     8   * distribute, sublicense, and/or sell copies of the Software, and to
     9   * permit persons to whom the Software is furnished to do so, subject to
    10   * the following conditions:
    11   *
    12   * The above copyright notice and this permission notice shall be included
    13   * in all copies or substantial portions of the Software.
    14   *
    15   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    16   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    17   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    18   * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    19   * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    20   * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    21   * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    22   ******************************************************************************/
    23  
    24  package wr
    25  
    26  import (
    27  	"bytes"
    28  	"crypto/rand"
    29  	"encoding/json"
    30  	"fmt"
    31  	"os"
    32  	"os/exec"
    33  	"strings"
    34  	"testing"
    35  	"time"
    36  
    37  	. "github.com/smartystreets/goconvey/convey"
    38  )
    39  
    40  func TestWR(t *testing.T) {
    41  	// buildBase would come from our yml config, and envPath and Name
    42  	// would come from the Definition from the core post to our Server.
    43  	buildBase := "spack/builds/"
    44  	envPath := "users/user"
    45  	envName := "myenv"
    46  	s3Path := buildBase + envPath + "/" + envName
    47  
    48  	Convey("You can generate a wr input", t, func() {
    49  		const hash = "0110"
    50  		wrInput, err := SingularityBuildInS3WRInput(s3Path, hash)
    51  		So(err, ShouldBeNil)
    52  		So(wrInput, ShouldEqual, `{"cmd": "echo doing build with hash `+hash+`; `+
    53  			`if sudo singularity build $TMPDIR/singularity.sif singularity.def &> $TMPDIR/builder.out; then `+
    54  			`sudo singularity run $TMPDIR/singularity.sif cat /opt/spack-environment/executables > $TMPDIR/executables && `+
    55  			`sudo singularity run $TMPDIR/singularity.sif cat /opt/spack-environment/spack.lock > $TMPDIR/spack.lock && `+
    56  			`mv $TMPDIR/singularity.sif $TMPDIR/builder.out $TMPDIR/executables $TMPDIR/spack.lock .; else `+
    57  			`mv $TMPDIR/builder.out . && false; fi", `+
    58  			`"retries": 0, "rep_grp": "singularity_build-spack/builds/users/user/myenv", "limit_grps": ["s3cache"], `+
    59  			`"mounts": [{"Targets": [{"Path":"spack/builds/users/user/myenv","Write":true,"Cache":true}]}]}`)
    60  
    61  		var m map[string]any
    62  		err = json.NewDecoder(strings.NewReader(wrInput)).Decode(&m)
    63  		So(err, ShouldBeNil)
    64  	})
    65  
    66  	gsbWR := os.Getenv("GSB_WR_TEST")
    67  	if gsbWR == "" {
    68  		SkipConvey("Skipping WR run test, set GSB_WR_TEST to enable", t, func() {})
    69  
    70  		return
    71  	}
    72  
    73  	runner := New("development")
    74  	runner.memory = "100M"
    75  	runner.pollDuration = 10 * time.Millisecond
    76  
    77  	Convey("You can run a cmd via wr", t, func() {
    78  		now := time.Now()
    79  		runArgs, repGrp := uniqueRunArgs("sleep 2s", "")
    80  		jobID, err := runner.Add(runArgs)
    81  		So(err, ShouldBeNil)
    82  		err = runner.WaitForRunning(jobID)
    83  		So(err, ShouldBeNil)
    84  		status, err := runner.Wait(jobID)
    85  		So(err, ShouldBeNil)
    86  		So(status, ShouldEqual, WRJobStatusComplete)
    87  		So(time.Since(now), ShouldBeGreaterThan, 2*time.Second)
    88  
    89  		statusOut := getWRStatus(runner.deployment, repGrp)
    90  		So(statusOut, ShouldContainSubstring, `"State":"complete"`)
    91  
    92  		jobID2, err := runner.Add(runArgs)
    93  		So(err, ShouldBeNil)
    94  		So(jobID2, ShouldEqual, jobID)
    95  		err = runner.WaitForRunning(jobID)
    96  		So(err, ShouldBeNil)
    97  		status, err = runner.Wait(jobID)
    98  		So(err, ShouldBeNil)
    99  		So(status, ShouldEqual, WRJobStatusComplete)
   100  
   101  		runArgs, _ = uniqueRunArgs("false", "")
   102  		jobID, err = runner.Add(runArgs)
   103  		So(err, ShouldBeNil)
   104  		err = runner.WaitForRunning(jobID)
   105  		So(err, ShouldBeNil)
   106  		status, err = runner.Wait(jobID)
   107  		So(err, ShouldBeNil)
   108  		So(status, ShouldEqual, WRJobStatusBuried)
   109  	})
   110  
   111  	Convey("WaitForRunning returns when the build starts running", t, func() {
   112  		cmd := exec.Command("wr", "limit", "--deployment", runner.deployment, "-g", "limited:0") //nolint:gosec
   113  		err := cmd.Run()
   114  		So(err, ShouldBeNil)
   115  
   116  		runArgs, _ := uniqueRunArgs("sleep 2s", "limited")
   117  		jobID, err := runner.Add(runArgs)
   118  		So(err, ShouldBeNil)
   119  
   120  		runningCh := make(chan time.Time)
   121  		errCh := make(chan error, 1)
   122  		go func() {
   123  			errCh <- runner.WaitForRunning(jobID)
   124  			runningCh <- time.Now()
   125  		}()
   126  
   127  		<-time.After(100 * time.Millisecond)
   128  
   129  		startTime := time.Now()
   130  		cmd = exec.Command("wr", "limit", "--deployment", runner.deployment, "-g", "limited:1") //nolint:gosec
   131  		err = cmd.Run()
   132  		So(err, ShouldBeNil)
   133  
   134  		status, err := runner.Wait(jobID)
   135  		So(err, ShouldBeNil)
   136  		endTime := time.Now()
   137  		So(status, ShouldEqual, WRJobStatusComplete)
   138  
   139  		So(<-errCh, ShouldBeNil)
   140  		runningTime := <-runningCh
   141  		So(runningTime, ShouldHappenAfter, startTime)
   142  		So(runningTime, ShouldHappenBefore, endTime.Add(-1*time.Second))
   143  	})
   144  }
   145  
   146  func uniqueRunArgs(cmd, limitGrp string) (string, string) {
   147  	b := make([]byte, 16)
   148  	n, err := rand.Read(b)
   149  	So(n, ShouldEqual, 16)
   150  	So(err, ShouldBeNil)
   151  
   152  	repGrp := fmt.Sprintf("%x", b)
   153  
   154  	return `{"cmd":"` + cmd + ` && echo ` + repGrp + `", "rep_grp": "` + repGrp +
   155  		`", "limit_grps": ["` + limitGrp + `"], "retries": 0}`, repGrp
   156  }
   157  
   158  func getWRStatus(deployment, repGrp string) string {
   159  	cmd := exec.Command("wr", "status", "--deployment", deployment, "-i", repGrp, "-o", "json")
   160  
   161  	var stdout, stderr bytes.Buffer
   162  	cmd.Stdout = &stdout
   163  	cmd.Stderr = &stderr
   164  
   165  	err := cmd.Run()
   166  	So(err, ShouldBeNil)
   167  	So(stderr.String(), ShouldBeBlank)
   168  
   169  	return stdout.String()
   170  }