1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
15 "git.arvados.org/arvados.git/sdk/go/arvados"
16 "git.arvados.org/arvados.git/sdk/go/arvadostest"
20 var _ = Suite(&singularitySuite{})
22 type singularitySuite struct {
26 func (s *singularitySuite) SetUpSuite(c *C) {
27 _, err := exec.LookPath("singularity")
29 c.Skip("looks like singularity is not installed")
31 s.newExecutor = func(c *C) {
33 s.executor, err = newSingularityExecutor(c.Logf)
36 arvadostest.StartKeep(2, true)
39 func (s *singularitySuite) TearDownSuite(c *C) {
40 if s.executor != nil {
45 func (s *singularitySuite) TestEnableNetwork_Listen(c *C) {
46 // With modern iptables, singularity (as of 4.2.1) cannot
47 // enable networking when invoked by a regular user. Under
48 // arvados-dispatch-cloud, crunch-run runs as root, so it's
49 // OK. For testing, assuming tests are not running as root, we
50 // use sudo -- but only if requested via environment variable.
53 } else if os.Getenv("ARVADOS_TEST_PRIVESC") == "sudo" {
54 c.Logf("ARVADOS_TEST_PRIVESC is 'sudo', invoking 'sudo singularity ...'")
55 s.executor.(*singularityExecutor).sudo = true
57 c.Skip("test case needs to run singularity as root -- set ARVADOS_TEST_PRIVESC=sudo to enable this test")
59 s.executorSuite.TestEnableNetwork_Listen(c)
62 func (s *singularitySuite) TestInject(c *C) {
63 path, err := exec.LookPath("nsenter")
64 if err != nil || path != "/var/lib/arvados/bin/nsenter" {
65 c.Skip("looks like /var/lib/arvados/bin/nsenter is not installed -- re-run `arvados-server install`?")
67 s.executorSuite.TestInject(c)
70 var _ = Suite(&singularityStubSuite{})
72 // singularityStubSuite tests don't really invoke singularity, so we
73 // can run them even if singularity is not installed.
74 type singularityStubSuite struct{}
76 func (s *singularityStubSuite) TestSingularityExecArgs(c *C) {
77 e, err := newSingularityExecutor(c.Logf)
79 err = e.Create(containerSpec{
80 WorkingDir: "/WorkingDir",
81 Env: map[string]string{"FOO": "bar"},
82 BindMounts: map[string]bindmount{"/mnt": {HostPath: "/hostpath", ReadOnly: true}},
90 e.imageFilename = "/fake/image.sif"
91 cmd := e.execCmd("./singularity")
92 expectArgs := []string{"./singularity", "exec", "--containall", "--cleanenv", "--pwd=/WorkingDir", "--net", "--network=none", "--nv"}
93 if cgroupSupport["cpu"] {
94 expectArgs = append(expectArgs, "--cpus", "2")
96 if cgroupSupport["memory"] {
97 expectArgs = append(expectArgs, "--memory", "12345678")
99 expectArgs = append(expectArgs, "--bind", "/hostpath:/mnt:ro", "/fake/image.sif")
100 c.Check(cmd.Args, DeepEquals, expectArgs)
101 c.Check(cmd.Env, DeepEquals, []string{
102 "SINGULARITYENV_FOO=bar",
103 "SINGULARITY_NO_EVAL=1",
104 "XDG_RUNTIME_DIR=" + os.Getenv("XDG_RUNTIME_DIR"),
105 "DBUS_SESSION_BUS_ADDRESS=" + os.Getenv("DBUS_SESSION_BUS_ADDRESS"),
109 func (s *singularitySuite) setupMount(c *C) (mountdir string) {
111 cmd := exec.Command("arv-mount",
112 "--foreground", "--read-write",
113 "--storage-classes", "default",
114 "--mount-by-pdh", "by_id", "--mount-by-id", "by_uuid",
115 "--disable-event-listening",
117 cmd.Stdout = os.Stderr
118 cmd.Stderr = os.Stderr
124 func (s *singularitySuite) teardownMount(c *C, mountdir string) {
125 exec.Command("arv-mount", "--unmount", mountdir).Run()
128 type singularitySuiteLoadTestSetup struct {
129 containerClient *arvados.Client
130 imageCacheProject *arvados.Group
132 collectionName string
135 func (s *singularitySuite) setupLoadTest(c *C, e *singularityExecutor) (setup singularitySuiteLoadTestSetup) {
136 // remove symlink and converted image already written by
137 // (executorSuite)SetupTest
138 os.Remove(e.tmpdir + "/image.tar")
139 os.Remove(e.tmpdir + "/image.sif")
141 setup.containerClient = arvados.NewClientFromEnv()
142 setup.containerClient.AuthToken = arvadostest.ActiveTokenV2
145 setup.imageCacheProject, err = e.getImageCacheProject(arvadostest.ActiveUserUUID, setup.containerClient)
148 setup.dockerImageID = "sha256:388056c9a6838deea3792e8f00705b35b439cf57b3c9c2634fb4e95cfc896de6"
149 setup.collectionName = fmt.Sprintf("singularity image for %s", setup.dockerImageID)
151 // Remove existing cache entry, if any.
152 var cl arvados.CollectionList
153 err = setup.containerClient.RequestAndDecode(&cl,
154 arvados.EndpointCollectionList.Method,
155 arvados.EndpointCollectionList.Path,
156 nil, arvados.ListOptions{Filters: []arvados.Filter{
157 arvados.Filter{"owner_uuid", "=", setup.imageCacheProject.UUID},
158 arvados.Filter{"name", "=", setup.collectionName},
162 if len(cl.Items) == 1 {
163 setup.containerClient.RequestAndDecode(nil, "DELETE", "arvados/v1/collections/"+cl.Items[0].UUID, nil, nil)
169 func (s *singularitySuite) checkCacheCollectionExists(c *C, setup singularitySuiteLoadTestSetup) {
170 var cl arvados.CollectionList
171 err := setup.containerClient.RequestAndDecode(&cl,
172 arvados.EndpointCollectionList.Method,
173 arvados.EndpointCollectionList.Path,
174 nil, arvados.ListOptions{Filters: []arvados.Filter{
175 arvados.Filter{"owner_uuid", "=", setup.imageCacheProject.UUID},
176 arvados.Filter{"name", "=", setup.collectionName},
180 if !c.Check(cl.Items, HasLen, 1) {
183 c.Check(cl.Items[0].PortableDataHash, Not(Equals), "d41d8cd98f00b204e9800998ecf8427e+0")
186 func (s *singularitySuite) TestImageCache_New(c *C) {
187 mountdir := s.setupMount(c)
188 defer s.teardownMount(c, mountdir)
189 e, err := newSingularityExecutor(c.Logf)
191 setup := s.setupLoadTest(c, e)
192 err = e.LoadImage(setup.dockerImageID, arvadostest.BusyboxDockerImage(c), arvados.Container{RuntimeUserUUID: arvadostest.ActiveUserUUID}, mountdir, setup.containerClient)
194 _, err = os.Stat(e.tmpdir + "/image.sif")
196 c.Check(os.IsNotExist(err), Equals, true)
197 s.checkCacheCollectionExists(c, setup)
200 func (s *singularitySuite) TestImageCache_SkipEmpty(c *C) {
201 mountdir := s.setupMount(c)
202 defer s.teardownMount(c, mountdir)
203 e, err := newSingularityExecutor(c.Logf)
205 setup := s.setupLoadTest(c, e)
207 var emptyCollection arvados.Collection
208 exp := time.Now().Add(24 * 7 * 2 * time.Hour)
209 err = setup.containerClient.RequestAndDecode(&emptyCollection,
210 arvados.EndpointCollectionCreate.Method,
211 arvados.EndpointCollectionCreate.Path,
212 nil, map[string]interface{}{
213 "collection": map[string]string{
214 "owner_uuid": setup.imageCacheProject.UUID,
215 "name": setup.collectionName,
216 "trash_at": exp.UTC().Format(time.RFC3339),
221 err = e.LoadImage(setup.dockerImageID, arvadostest.BusyboxDockerImage(c), arvados.Container{RuntimeUserUUID: arvadostest.ActiveUserUUID}, mountdir, setup.containerClient)
223 c.Check(e.imageFilename, Equals, e.tmpdir+"/image.sif")
225 // tmpdir should contain symlink to docker image archive.
226 tarListing, err := exec.Command("tar", "tvf", e.tmpdir+"/image.tar").CombinedOutput()
228 c.Check(string(tarListing), Matches, `(?ms).*/layer.tar.*`)
230 // converted singularity image should be non-empty.
231 fi, err := os.Stat(e.imageFilename)
232 if c.Check(err, IsNil) {
233 c.Check(int(fi.Size()), Not(Equals), 0)
237 func (s *singularitySuite) TestImageCache_Concurrency_1(c *C) {
238 s.testImageCache(c, 1)
241 func (s *singularitySuite) TestImageCache_Concurrency_2(c *C) {
242 s.testImageCache(c, 2)
245 func (s *singularitySuite) TestImageCache_Concurrency_10(c *C) {
246 s.testImageCache(c, 10)
249 func (s *singularitySuite) testImageCache(c *C, concurrency int) {
250 mountdirs := make([]string, concurrency)
251 execs := make([]*singularityExecutor, concurrency)
252 setups := make([]singularitySuiteLoadTestSetup, concurrency)
253 for i := range execs {
254 mountdirs[i] = s.setupMount(c)
255 defer s.teardownMount(c, mountdirs[i])
256 e, err := newSingularityExecutor(c.Logf)
260 setups[i] = s.setupLoadTest(c, e)
263 var wg sync.WaitGroup
264 for i, e := range execs {
269 err := e.LoadImage(setups[i].dockerImageID, arvadostest.BusyboxDockerImage(c), arvados.Container{RuntimeUserUUID: arvadostest.ActiveUserUUID}, mountdirs[i], setups[i].containerClient)
275 for i, e := range execs {
276 fusepath := strings.TrimPrefix(e.imageFilename, mountdirs[i])
277 // imageFilename should be in the fuse mount, not
279 c.Check(fusepath, Not(Equals), execs[0].imageFilename)
280 // Below fuse mountpoint, paths should all be equal.
281 fusepath0 := strings.TrimPrefix(execs[0].imageFilename, mountdirs[0])
282 c.Check(fusepath, Equals, fusepath0)