diff --git a/.gitignore b/.gitignore index aff1acab3993a5999a483308dbfecbf678f76fc1..d105c7706a83ed12b5544dc8f5156b0ba94003b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ test/lua_runtime -test/node_modules -test/package-lock.json +test/daemon/node_modules +test/daemon/package-lock.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e77ca8f3c6ef648c6e62d87a0e4fdde5cbbd2b1a..d3b73e06a56cae92589d8ef3fd557010d53bbbd6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -71,27 +71,29 @@ lint: image: repo.tecgraf.puc-rio.br:18082/node:14.15-stretch cache: paths: - - test/node_modules + - test/daemon/node_modules script: - - cd test + - cd test/daemon - npm install - npx eslint --ext js . test: stage: test - image: repo.tecgraf.puc-rio.br:18082/node:14.15-stretch + image: repo.tecgraf.puc-rio.br:18089/soma/sga-test-ubuntu:20.04 cache: paths: - test/lua_runtime - - test/node_modules + - test/daemon/node_modules script: - bash install.sh --force --posix --pbs --slurm $(pwd)/test/lua_runtime - - for rockspec in *.rockspec; do - - test/lua_runtime/bin/luarocks --force remove ${rockspec%-*-*.rockspec} - - done - - cd test + - cd test/daemon - npm install - npm run test + - cd .. + - lua_runtime/bin/luarocks install --server=https://luarocks.org busted + - useradd -M tester -p '' + - chown tester:tester $(pwd) + - su -c $(pwd)/test_drivers.sh tester #------------------------- # Docker Jobs @@ -108,18 +110,17 @@ test: - export DOCKER_REGISTRY_IMAGE_DIND="${CI_REGISTRY_IMAGE}-dind" - *docker_login - docker pull $DOCKER_REGISTRY$CI_REGISTRY_IMAGE:$LAST || true - - docker pull $DOCKER_REGISTRY$DOCKER_REGISTRY_IMAGE_DIND:$LAST || true + - docker pull $DOCKER_REGISTRY$DOCKER_REGISTRY_IMAGE_DIND:$LAST || true - | docker build \ --tag $DOCKER_REGISTRY$CI_REGISTRY_IMAGE:$VERSION \ --tag $DOCKER_REGISTRY$CI_REGISTRY_IMAGE:$LAST \ - -f Docker/Dockerfile . - - sed -r "s%^FROM .*%FROM $DOCKER_REGISTRY$CI_REGISTRY_IMAGE:$VERSION%" Docker/Dockerfile_withdocker > Docker/Dockerfile_dind + -f Docker/Dockerfile --target deploy . - | docker build \ --tag $DOCKER_REGISTRY$DOCKER_REGISTRY_IMAGE_DIND:$VERSION \ --tag $DOCKER_REGISTRY$DOCKER_REGISTRY_IMAGE_DIND:$LAST \ - -f Docker/Dockerfile_dind . + -f Docker/Dockerfile --target deploy-with-docker . - docker push $DOCKER_REGISTRY$CI_REGISTRY_IMAGE:$VERSION - docker push $DOCKER_REGISTRY$CI_REGISTRY_IMAGE:$LAST - docker push $DOCKER_REGISTRY$DOCKER_REGISTRY_IMAGE_DIND:$VERSION diff --git a/Docker/Dockerfile b/Docker/Dockerfile index c932dfae27133a9732f3bc7f65ab6a81f83cb891..628d3f7a5933ccc5108c9b135762485d11225f17 100644 --- a/Docker/Dockerfile +++ b/Docker/Dockerfile @@ -3,7 +3,7 @@ FROM repo.tecgraf.puc-rio.br:18089/soma/sga-build-ubuntu:20.04 as build COPY . /tmp/sgarest-daemon RUN cd /tmp/sgarest-daemon && ./install.sh --posix /sgad -FROM repo.tecgraf.puc-rio.br:18089/soma/sga-runtime-ubuntu:20.04 +FROM repo.tecgraf.puc-rio.br:18089/soma/sga-runtime-ubuntu:20.04 as deploy COPY --from=build /sgad /sgad @@ -24,3 +24,6 @@ ENV SGAD_LOGS_DIR $SGAD_HOME/data/logs ENTRYPOINT ["./entrypoint.sh"] CMD ["sgad.cfg"] + +FROM deploy as deploy-with-docker +RUN apt-get update && apt-get install -y docker.io diff --git a/Docker/Dockerfile_base b/Docker/Dockerfile_base new file mode 100644 index 0000000000000000000000000000000000000000..5363c61e64a7fe61dc252d724393edb9549b6d4f --- /dev/null +++ b/Docker/Dockerfile_base @@ -0,0 +1,28 @@ +FROM repo.tecgraf.puc-rio.br:18082/ubuntu:20.04 as system +RUN apt-get update + +FROM system as build +RUN apt-get install -y \ + curl \ + g++ \ + gcc \ + libreadline-dev \ + libssh-dev \ + make \ + unzip + +FROM system as runtime +RUN apt-get install -y \ + bc \ + ksh \ + libssl1.1 \ + readline-common + +FROM build as test +ARG DEBIAN_FRONTEND=noninteractive +ENV TZ=Etc/UTC +RUN apt-get install -y \ + bc \ + ksh \ + nodejs \ + npm diff --git a/Docker/Dockerfile_build b/Docker/Dockerfile_build deleted file mode 100644 index b6b055104cca81afd2ce9637c408d666c7e20447..0000000000000000000000000000000000000000 --- a/Docker/Dockerfile_build +++ /dev/null @@ -1,10 +0,0 @@ -FROM repo.tecgraf.puc-rio.br:18082/ubuntu:20.04 - -RUN apt-get update && apt-get install -y \ - curl \ - g++ \ - gcc \ - libreadline-dev \ - libssh-dev \ - make \ - unzip diff --git a/Docker/Dockerfile_runtime b/Docker/Dockerfile_runtime deleted file mode 100644 index e74f2ed7462d521953c63e36739cf96ccd1575c9..0000000000000000000000000000000000000000 --- a/Docker/Dockerfile_runtime +++ /dev/null @@ -1,7 +0,0 @@ -FROM repo.tecgraf.puc-rio.br:18082/ubuntu:20.04 - -RUN apt-get update && apt-get install -y \ - bc \ - ksh \ - libssl1.1 \ - readline-common diff --git a/Docker/Dockerfile_withdocker b/Docker/Dockerfile_withdocker deleted file mode 100755 index 25ce54587c61f3b1698c2bef95299a988ac3d049..0000000000000000000000000000000000000000 --- a/Docker/Dockerfile_withdocker +++ /dev/null @@ -1,3 +0,0 @@ -FROM SGA_IMAGE_NAME_TO_BE_REPLACED - -RUN apt-get update && apt-get install -y docker.io diff --git a/Docker/README.md b/Docker/README.md index 387b78a75980dda36cf0bb94a719622dbf0ed1c3..84b9896db8445b0fdb5b5a1f12a6d8954867daab 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -4,8 +4,50 @@ The Docker image only works with the POSIX driver. ### Build +#### Base Images + +The build of SGA Docker image makes use of other base images that provides the necessary dependencies to make the build of a new release faster. +Unless the system dependencies of SGA does not change, +there is no need to rebuild such the base images. +We use the follwing pre-built images: + +- Build + +```console +$ docker build -f Docker/Dockerfile_base --target build \ + -t repo.tecgraf.puc-rio.br:18089/soma/sga-build-ubuntu:20.04 . +``` + +- Execution + +```console +$ docker build -f Docker/Dockerfile_base --target runtime \ + -t repo.tecgraf.puc-rio.br:18089/soma/sga-runtime-ubuntu:20.04 . +``` + +- Testing + +```console +$ docker build -f Docker/Dockerfile_base --target test \ + -t repo.tecgraf.puc-rio.br:18089/soma/sga-test-ubuntu:20.04 . +``` + +#### Release Images + +We release two Docker images for the SGA Daemon: + +- Standard + +```console +$ docker build -f Docker/Dockerfile --target deploy \ + -t soma/sga . +``` + +- With Docker Support (_Docker in Docker_) + ```console -$ docker build -f Docker/Dockerfile -t soma/sga . +$ docker build -f Docker/Dockerfile --target deploy-with-docker \ + -t soma/sga-dind . ``` ### Run diff --git a/sga-daemon-scm-1.rockspec b/sga-daemon-scm-1.rockspec index 393d7c925390cd28642c0acb3b48c8eb2a42c414..54b5bbf29c839643070c4cad8a48e722004abd24 100644 --- a/sga-daemon-scm-1.rockspec +++ b/sga-daemon-scm-1.rockspec @@ -30,6 +30,7 @@ dependencies = { "restserver-xavante == 0.2", "lanes >= 3.10.0", "lualogging", + "luaposix", "serpent", "lua-schema", -- https://raw.githubusercontent.com/hishamhm/lua-schema/features/lua-schema-scm-1.rockspec } diff --git a/sga/driver/pbs.lua b/sga/driver/pbs.lua index bb592a755a958649c925752f681a538b93606dca..c2dd4916c1cae4238a26a084257315763145cac0 100644 --- a/sga/driver/pbs.lua +++ b/sga/driver/pbs.lua @@ -46,7 +46,7 @@ function pbs.execute_command(self, job, cmd_string, user_token) self.exec:chmod(sandbox_path, "rwxrwxrwx") end - local token_env + local token_env = "" if user_token then token_env = "CSBASE_USER_TOKEN=" .. user_token .. " " end diff --git a/test/.eslintrc.json b/test/daemon/.eslintrc.json similarity index 100% rename from test/.eslintrc.json rename to test/daemon/.eslintrc.json diff --git a/test/lua_custom/sga/driver/mockup.lua b/test/daemon/lua_custom/sga/driver/mockup.lua similarity index 100% rename from test/lua_custom/sga/driver/mockup.lua rename to test/daemon/lua_custom/sga/driver/mockup.lua diff --git a/test/package.json b/test/daemon/package.json similarity index 100% rename from test/package.json rename to test/daemon/package.json diff --git a/test/spec/rest_spec.js b/test/daemon/spec/rest_spec.js similarity index 100% rename from test/spec/rest_spec.js rename to test/daemon/spec/rest_spec.js diff --git a/test/spec/support/jasmine.json b/test/daemon/spec/support/jasmine.json similarity index 100% rename from test/spec/support/jasmine.json rename to test/daemon/spec/support/jasmine.json diff --git a/test/spec/utils.js b/test/daemon/spec/utils.js similarity index 98% rename from test/spec/utils.js rename to test/daemon/spec/utils.js index c4eb7e16f3f707e74561710d53c862c5fbcefeef..d6b194d0eaca458ff645a57a5f311b0cc943be38 100644 --- a/test/spec/utils.js +++ b/test/daemon/spec/utils.js @@ -152,16 +152,16 @@ class SgaDaemon { }); await events.once(sgaOutput, "open"); - const luaHome = "./lua_runtime"; + const luaHome = "../lua_runtime"; const luaVer = "5.2"; const luaBin = `${luaHome}/bin/lua`; const luaLPath = `${luaHome}/share/lua/${luaVer}/?.lua;${luaHome}/share/lua/${luaVer}/?/init.lua`; const luaCPath = `${luaHome}/lib/lua/${luaVer}/?.so`; - this.process = child_process.spawn(luaBin, ["../sgad.lua", configPath], { + this.process = child_process.spawn(luaBin, ["../../sgad.lua", configPath], { stdio: ["ignore", sgaOutput, sgaOutput], env: { - LUA_PATH: `./lua_custom/?.lua;../?.lua;${luaLPath}`, + LUA_PATH: `./lua_custom/?.lua;../../?.lua;${luaLPath}`, LUA_CPATH: luaCPath, SGA_TESTDIR: testDir, }, diff --git a/test/driver/pbs.lua b/test/driver/pbs.lua new file mode 100644 index 0000000000000000000000000000000000000000..5037f7d25a1144dbbb8085ca5fa8923fe4792b2c --- /dev/null +++ b/test/driver/pbs.lua @@ -0,0 +1,87 @@ +local copas = require "copas" +local module = require "sga.driver.pbs" +local utils = require "test.driver.utils" + +local threadErrors = {} + +local function errorHandler(...) + local tracemsg = debug.traceback(...) + print(tracemsg) + table.insert(threadErrors, tracemsg) +end + +local function newThread(f, ...) + copas.addthread(xpcall, f, errorHandler, ...) +end + +describe("SGA driver for PBS", function() + local logger = { + error = function () end, + debug = function () end, + } + + before_each(function() + snapshot = assert:snapshot() + mock(logger) + end) + + after_each(function() + snapshot:revert() + end) + + + it("should not block the entire process while invoking 'qsub'", function() + local copas = require "copas" + + local driver = module.new({ runtime_data_dir = utils.tmpdir }, logger) + + local job = { + jid = 171, + cmd_id = "User@AProj.CBOWZWVCYE", + sandboxes = {}, + parameters = {}, + data = {} + } + + local assertQsubWasCalled = utils.expectCommand{ + name = "qsub", + delay = 0.1, + stdout = "12345", + arguments = { + "-N", job.cmd_id, + "-V", + "-o", utils.tmpdir.."/qsub_"..job.jid..".out", + "-e", utils.tmpdir.."/qsub_"..job.jid..".err", + utils.tmpdir.."/qsub_"..job.jid..".script", + }, + files = { + [utils.tmpdir.."/qsub_"..job.jid..".script"] = [[ +#!/bin/sh +umask 002 +my fake command +]] + } + } + + local stage = 0 + + newThread(function () + copas.sleep(0) -- yield + assert.equal(0, stage) + stage = 1 + end) + + newThread(function () + local ok = module.execute_command(driver, job, "my fake command") + assertQsubWasCalled() + assert.is_true(ok) + assert.equal(1, stage) + stage = 2 + end) + + copas.loop() + + assert.equal(2, stage) + assert.same({}, threadErrors) + end) +end) diff --git a/test/driver/posix.lua b/test/driver/posix.lua new file mode 100644 index 0000000000000000000000000000000000000000..2c80d12799f08b0520f33ab3d7a4e2a99e236d88 --- /dev/null +++ b/test/driver/posix.lua @@ -0,0 +1,151 @@ +local procdata = require "procdata" +local module = require "sga.driver.posix" +local utils = require "test.driver.utils" + +describe("SGA driver for POSIX", function() + local logger = { + error = function () end, + debug = function () end, + } + + before_each(function() + snapshot = assert:snapshot() + mock(logger) + end) + + after_each(function() + snapshot:revert() + end) + + it("should report job with ended processes", function() + local driver = module.new({ runtime_data_dir = utils.tmpdir }, logger) + + local job = { + jid = 171, + cmd_id = "User@AProj.CBOWZWVCYE", + sandboxes = { + utils.tmpdir.."/posixDriverSandbox", + }, + parameters = { + csbase_command_path = ".", + csbase_command_output_path = utils.tmpdir, + csbase_command_root_path = utils.tmpdir, + }, + data = {} + } + + local cmdSpec = { + name = "myExecutedCommand", + childdelays = { 0.01, 0.05, 0.1 }, -- fast processes are not reported. + arguments = { + "one", + "other", + "another", + }, + directories = job.sandboxes, + } + + local function checkStatus(done) + local ok, result = module.actions.status(driver, job) + assert.is_true(ok) + assert.is_table(result) + local count = 0 + local running = 0 + local sgapid + local cmdprocids = {} + for i, procinfo in ipairs(result.processes) do + count = count+1 + if procinfo.state == "RUNNING" then + running = running+1 + else + assert.equal("FINISHED", procinfo.state) + end + + assert.is_number(procinfo.pid) + assert.is_number(procinfo.ppid) + assert.equal("", procinfo.exec_host) + assert.is_string(procinfo.string) + assert.equal("", procinfo.processor_id) + assert.is_number(procinfo.memory_ram_size_mb) + assert.equal(0, procinfo.memory_swap_size_mb) + assert.equal(0, procinfo.cpu_perc) + assert.equal(0, procinfo.cpu_time_sec) + assert.is_number(procinfo.wall_time_sec) + assert.is_number(procinfo.system_time_sec) + assert.is_number(procinfo.user_time_sec) + assert.is_number(procinfo.virtual_memory_size_mb) + assert.equal(0, procinfo.bytes_in_kb) + assert.equal(0, procinfo.bytes_out_kb) + assert.equal(0, procinfo.disk_bytes_read_kb) + assert.equal(0, procinfo.disk_bytes_write_kb) + + if i == 1 then + sgapid = procinfo.ppid + else + assert.truthy(cmdprocids[procinfo.ppid]) + end + cmdprocids[procinfo.pid] = true + end + assert.truthy(count > 0) + assert.truthy(count <= 3+2*#cmdSpec.childdelays) -- fork+sh+cmd+n*(sh+sleep) + assert.truthy(running <= count) + if done then + assert.equal(0, running) + end + end + + local cmdLine = cmdSpec.name.." "..table.concat(cmdSpec.arguments, " ") + local assertCmdWasCalled = utils.expectCommand(cmdSpec) + local ok = module.execute_command(driver, job, cmdLine) + assert.is_true(ok) + + while not module.is_command_done(driver, job) do + checkStatus() + end + checkStatus("done") + assertCmdWasCalled() + end) + + it("should list a single node", function() + local driver = module.new({ sga_name = "MySgaNameForTest" }, logger) + + local expected = { + num_of_cpus = math.random(256), + clock_mhz = math.random(256), + ram_mb = math.random(256), + swap_mb = math.random(256), + } + local megabyte = 1024 * 1024 + stub(procdata, "get_num_cpus").returns(expected.num_of_cpus) + stub(procdata, "get_clock_speed").returns(expected.clock_mhz) + stub(procdata, "get_total_memory").returns{ + ram = expected.ram_mb * megabyte, + swap = expected.swap_mb * megabyte, + } + + local nodes = module.get_nodes(driver) + assert.same(nodes, { MySgaNameForTest = expected }) + + assert.stub(procdata.get_num_cpus).was.called() + assert.stub(procdata.get_clock_speed).was.called() + assert.stub(procdata.get_total_memory).was.called() + end) + + for fname, errmsg in pairs{ + get_num_cpus = "CPU information error", + get_clock_speed = "CPU clock information error", + get_total_memory = "system memory information error", + } do + it("should report error on 'procdata."..fname.."' failure", function() + local driver = module.new(nil, logger) + + stub(procdata, fname).returns(nil, errmsg) + + local nodes, extra = module.get_nodes(driver) + assert.is_nil(nodes) + assert.equal(errmsg, extra) + + assert.stub(procdata[fname]).was.called() + end) + end +end) diff --git a/test/driver/utils.lua b/test/driver/utils.lua new file mode 100644 index 0000000000000000000000000000000000000000..9c95e5cb611eebee2b22f6c63f12fe4fef1c5367 --- /dev/null +++ b/test/driver/utils.lua @@ -0,0 +1,116 @@ +local lfs = require "lfs" +local assert = require "luassert" + +local utils = { luabin = "" } + +for i = 0, -math.huge, -1 do + if not arg[i] then + break + end + utils.luabin = arg[i] +end + +utils.tmpdir = os.tmpname() +os.remove(utils.tmpdir) +lfs.mkdir(utils.tmpdir) + +function utils.expectCommand(spec) + local resultPath = utils.tmpdir.."/"..spec.name..".out" + local cmdPath = "./"..spec.name + local file = assert(io.open(cmdPath, "w")) + file:write([[ +#!]]..utils.luabin..[[ + +package.path = "]]..package.path..[[" +package.cpath = "]]..package.cpath..[[" + +local lfs = require "lfs" +local socket = require "socket.core" + +local cmd = "]]..spec.name..[[" +io.output("]]..resultPath..[[") + +local function fail(message, ...) + io.write(string.format(message, ...)) + os.exit(127) +end + +local function expectArg(index, expected) + local actual = arg[index] + if actual ~= expected then + fail("%q argument #%d expected to be %q, but was %q", + cmd, index, expected, tostring(actual)) + end +end + +local function expectDir(path) + if lfs.attributes(path, "mode") ~= "directory" then + fail("%q expected directory %q to be available", cmd, path) + end +end + +local function expectFile(path, expected) + if lfs.attributes(path, "mode") ~= "file" then + fail("%q expected file %q to exist", cmd, path) + elseif expected then + local file = assert(io.open(path)) + local actual = file:read("*a") + if actual ~= expected then + fail("%q expected file %q to have different contents", cmd, path) + end + end +end +]]) + for i, value in ipairs(spec.arguments or {}) do + assert(type(value) == "string") + file:write(string.format('expectArg(%d, %q)\n', i, value)) + end + for i, value in ipairs(spec.directories or {}) do + assert(type(value) == "string") + file:write(string.format('expectDir(%q)\n', value)) + end + for path, contents in pairs(spec.files or {}) do + if type(contents) == "string" then + file:write(string.format('expectFile(%q, %q)\n', path, contents)) + else + file:write(string.format('expectFile(%q)\n', path)) + end + end + + if spec.code then + file:write(spec.code) + else + if spec.delay then + file:write(string.format('socket.sleep(%g)\n', spec.delay)) + end + if spec.stdout then + file:write(string.format('io.stdout:write(%q)\n', spec.stdout)) + end + if spec.stderr then + file:write(string.format('io.stderr:write(%q)\n', spec.stderr)) + end + file:write('local pipes = {}\n') + for i, delay in ipairs(spec.childdelays or {}) do + file:write(string.format('table.insert(pipes, assert(io.popen("sleep %g")))\n', delay)) + end + file:write('for _, pipe in ipairs(pipes) do\n', + ' pipe:close()\n', + 'end\n') + end + file:write('io.write(table.concat(arg, " "))\n') + file:close() + + os.execute("chmod 755 "..cmdPath) + + local expected = string.format(table.concat(spec.arguments, " ")) + return function () + local file = assert(io.open(resultPath)) + local args = file:read("*a") + file:close() + os.remove(resultPath) + os.remove(cmdPath) + assert.equal(expected, args) + end +end + +return utils diff --git a/test/test_drivers.sh b/test/test_drivers.sh new file mode 100755 index 0000000000000000000000000000000000000000..8da1d945d36be9be3998d1fd6f59c488dfa97b86 --- /dev/null +++ b/test/test_drivers.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +export PATH=".:${PATH}" +export LUA_PATH="../?.lua" +lua_runtime/bin/busted driver/posix.lua +lua_runtime/bin/busted driver/pbs.lua