From 45863bd7825c2786fc9ef964d618d606327ad084 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Fri, 1 Jun 2018 10:16:17 -0700 Subject: [PATCH 01/15] libutil/fluid: 0-pad DOTHEX encoding Problem: DOTHEX-encoded FLUIDs can be used to map FLUIDs onto the KVS namespace, but without zero padding, flux-kvs ls doesn't display or sort them nicely. Pad each dotted hex number out to four digits. --- src/common/libutil/fluid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/libutil/fluid.c b/src/common/libutil/fluid.c index c29c12da74e5..3a159d453295 100644 --- a/src/common/libutil/fluid.c +++ b/src/common/libutil/fluid.c @@ -107,7 +107,7 @@ static int fluid_encode_dothex (char *buf, int bufsz, fluid_t fluid) { int rc; - rc = snprintf (buf, bufsz, "%x.%x.%x.%x", + rc = snprintf (buf, bufsz, "%04x.%04x.%04x.%04x", (unsigned int)(fluid>>48) & 0xffff, (unsigned int)(fluid>>32) & 0xffff, (unsigned int)(fluid>>16) & 0xffff, From ec62dc04f94f1eb56d88d5c4b025f124e7315af9 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Wed, 13 Jun 2018 12:32:46 -0400 Subject: [PATCH 02/15] libutil/fluid: detect bad mnemonic input Problem: fluid_decode (type=MNEMONIC) does not fail if presented with words not in its dictionary or a phrase too short to represent a uint64_t. mn_decode()'s inline documentation is incorrect: /* Return value: * This function may return all the value returned by mn_decode_word_index * plus the following result code: * * MN_EWORD - Unrecognized word. */ It actually returns the number of bytes successfully written to the output. Require mn_decode() to return 8 in order for fluid_decode() to be successful. --- src/common/libutil/fluid.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/common/libutil/fluid.c b/src/common/libutil/fluid.c index 3a159d453295..c96e6f573aa7 100644 --- a/src/common/libutil/fluid.c +++ b/src/common/libutil/fluid.c @@ -161,9 +161,13 @@ int fluid_decode (const char *s, fluid_t *fluidp, fluid_string_type_t type) break; } case FLUID_STRING_MNEMONIC: - /* first arg of mn_decode not declared const, but not modified */ + /* N.B. Contrary to its inline documentation, mn_decode() returns + * the number of bytes written to output, or MN_EWORD (-7). + * Fluids are always encoded such that 8 bytes should be written. + * Also, 's' is not modified so it is safe to cast away const. + */ rc = mn_decode ((char *)s, (void *)&fluid, sizeof (fluid_t)); - if (rc < 0) + if (rc != 8) return -1; break; From 2edc33cf7fb20a147423c834a13d97269cb81d98 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Wed, 13 Jun 2018 11:56:03 -0400 Subject: [PATCH 03/15] libutil/fluid/test: cover fluid_decode bad input --- src/common/libutil/test/fluid.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/common/libutil/test/fluid.c b/src/common/libutil/test/fluid.c index a84324e0b3b4..62191043518f 100644 --- a/src/common/libutil/test/fluid.c +++ b/src/common/libutil/test/fluid.c @@ -93,6 +93,15 @@ int main (int argc, char *argv[]) ok (decode_errors == 0, "fluid_decode type=MNEMONIC worked 4K times"); + /* Decode bad input must fail + */ + ok (fluid_decode ("bogus", &id, FLUID_STRING_DOTHEX) < 0, + "fluid_decode type=DOTHEX fails on input=bogus"); + ok (fluid_decode ("bogus", &id, FLUID_STRING_MNEMONIC) < 0, + "fluid_decode type=MNEMONIC fails on input=bogus"); + ok (fluid_decode ("a-a-a--a-a-a", &id, FLUID_STRING_MNEMONIC) < 0, + "fluid_decode type=MNEMONIC fails on unknown words xx-xx-xx--xx-xx-xx"); + done_testing (); return 0; } From 6f980aec3431a35f1f702d7e0a2eba3c947fecfe Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Tue, 12 Jun 2018 01:38:28 -0400 Subject: [PATCH 04/15] build: add jobspec sharness test to Makefile.am Problem: jobspec sharness test was not run. Add jobspec inputs to EXTRA_DIST and run jobspec test if ENABLE_JOBSPEC. --- t/Makefile.am | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/t/Makefile.am b/t/Makefile.am index 28b2245fb53e..66e6c7913b45 100644 --- a/t/Makefile.am +++ b/t/Makefile.am @@ -100,6 +100,11 @@ TESTS = \ lua/t1004-statwatcher.t \ lua/t1005-fdwatcher.t +if ENABLE_JOBSPEC +TESTS += \ + t0018-jobspec.t +endif + if HAVE_PYTHON TESTS += \ $(top_builddir)/t/t9990-python-tests.t @@ -118,7 +123,8 @@ EXTRA_DIST= \ rc/rc3-wreck \ rc/rc3-testenv \ wreck/input \ - wreck/output + wreck/output \ + jobspec clean-local: rm -fr trash-directory.* test-results .prove *.broker.log */*.broker.log *.output @@ -187,6 +193,7 @@ check_SCRIPTS = \ lua/t1003-iowatcher.t \ lua/t1004-statwatcher.t \ lua/t1005-fdwatcher.t \ + t0018-jobspec.t \ $(top_builddir)/t/t9990-python-tests.t check_PROGRAMS = \ From 66f3d625c49fe80e93901c3519140916057c6fb1 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Tue, 12 Jun 2018 18:07:45 -0400 Subject: [PATCH 05/15] build: define HAVE_JOBSPEC in config.h Problem: while there is an ENABLE_JOBSPEC Makefile conditional, there is nothing that can be tested in config.h to determine if jobspec is being built. Add HAVE_JOBSPEC to config.h --- config/x_ac_yamlcpp.m4 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/x_ac_yamlcpp.m4 b/config/x_ac_yamlcpp.m4 index 966bfaf2ddd5..4733c3b8a731 100644 --- a/config/x_ac_yamlcpp.m4 +++ b/config/x_ac_yamlcpp.m4 @@ -36,6 +36,8 @@ Add --disable-jobspec, or set the PKG_CONFIG_PATH env var appropriately.])]) AC_LANG_POP([C++]) LIBS="$ac_save_LIBS" CFLAGS="$ac_save_CFLAGS" + + AC_DEFINE([HAVE_JOBSPEC], [1], [Define to 1 if jobspec is enabled]) ]) AM_CONDITIONAL([ENABLE_JOBSPEC], [test "x$enable_jobspec" != "xno"]) From f372ed7e27600264ed5393e8cb99ac127ba3cbec Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Wed, 23 May 2018 11:59:46 -0700 Subject: [PATCH 06/15] build: add --with-flux-security (opt-in) If user requests it by specifying --with-flux-security, have configure locate flux-security using pkg-config. Defines HAVE_FLUX_SECURITY in config.h Defines HAVE_FLUX_SECURITY for Makefile.am's --- configure.ac | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/configure.ac b/configure.ac index b7385bcbcada..a28f480303be 100644 --- a/configure.ac +++ b/configure.ac @@ -206,6 +206,14 @@ AM_CONDITIONAL([HAVE_MPI], [test "$have_C_mpi" = yes]) AX_VALGRIND_H AX_CODE_COVERAGE +AC_ARG_WITH([flux-security], AS_HELP_STRING([--with-flux-security], + [Build with flux-security])) +AS_IF([test "x$with_flux_security" = "xyes"], [ + PKG_CHECK_MODULES([FLUX_SECURITY], [flux-security], [], []) + AC_DEFINE([HAVE_FLUX_SECURITY], [1], [Define flux-security is available]) +]) +AM_CONDITIONAL([HAVE_FLUX_SECURITY], [test "x$with_flux_security" = "xyes"]) + AC_ARG_ENABLE(caliper, [ --enable-caliper[=OPTS] Use caliper for profiling. [default=no] [OPTS=no/yes]], , [enable_caliper="no"]) From 63011e2668a3313f260eb9be6d2f412e8ed54240 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Mon, 11 Jun 2018 16:37:15 -0400 Subject: [PATCH 07/15] travis-ci: run autogen.sh in checkout if necessary --- src/test/travis-dep-builder.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/travis-dep-builder.sh b/src/test/travis-dep-builder.sh index 4f0b3537f721..37d002603f3b 100755 --- a/src/test/travis-dep-builder.sh +++ b/src/test/travis-dep-builder.sh @@ -200,6 +200,11 @@ for url in $checkouts; do git checkout $sha1 fi + # Autogen? + if ! test -x configure && test -x autogen.sh; then + ./autogen.sh + fi + # Do we need to create a Makefile? if ! test -f Makefile; then if test -x configure; then From 09429d128c317f827671c147df05f03c7a0dd062 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Wed, 23 May 2018 12:22:19 -0700 Subject: [PATCH 08/15] libjob: add flux job library Add a library will provides an API for job creation, monitoring, and control. For now it contains only an interface for job submission. --- configure.ac | 1 + src/common/Makefile.am | 2 ++ src/common/libjob/Makefile.am | 16 +++++++++ src/common/libjob/job.c | 67 +++++++++++++++++++++++++++++++++++ src/common/libjob/job.h | 36 +++++++++++++++++++ 5 files changed, 122 insertions(+) create mode 100644 src/common/libjob/Makefile.am create mode 100644 src/common/libjob/job.c create mode 100644 src/common/libjob/job.h diff --git a/configure.ac b/configure.ac index a28f480303be..d6a12c2c2dcc 100644 --- a/configure.ac +++ b/configure.ac @@ -309,6 +309,7 @@ AC_CONFIG_FILES( \ src/common/libkvs/Makefile \ src/common/libkz/Makefile \ src/common/libjsc/Makefile \ + src/common/libjob/Makefile \ src/common/libsubprocess/Makefile \ src/common/libcompat/Makefile \ src/common/liboptparse/Makefile \ diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 7751bc65855f..5a36b355881e 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -7,6 +7,7 @@ SUBDIRS = libtap \ libflux \ libkvs \ libjsc \ + libjob \ libsubprocess \ libcompat \ liboptparse \ @@ -50,6 +51,7 @@ libflux_core_la_LIBADD = \ $(builddir)/libflux/libflux.la \ $(builddir)/libkvs/libkvs.la \ $(builddir)/libjsc/libjsc.la \ + $(builddir)/libjob/libjob.la \ libflux-internal.la libflux_core_la_LDFLAGS = \ -Wl,--version-script=$(srcdir)/libflux-core.map \ diff --git a/src/common/libjob/Makefile.am b/src/common/libjob/Makefile.am new file mode 100644 index 000000000000..6f9f9c3bafd9 --- /dev/null +++ b/src/common/libjob/Makefile.am @@ -0,0 +1,16 @@ +AM_CFLAGS = \ + $(WARNING_CFLAGS) \ + $(CODE_COVERAGE_CFLAGS) + +AM_LDFLAGS = \ + $(CODE_COVERAGE_LIBS) + +AM_CPPFLAGS = \ + -I$(top_srcdir) -I$(top_srcdir)/src/include \ + $(ZMQ_CFLAGS) $(JANSSON_CFLAGS) + +noinst_LTLIBRARIES = libjob.la +fluxcoreinclude_HEADERS = job.h + +libjob_la_SOURCES = \ + job.c diff --git a/src/common/libjob/job.c b/src/common/libjob/job.c new file mode 100644 index 000000000000..9b0c4596c563 --- /dev/null +++ b/src/common/libjob/job.c @@ -0,0 +1,67 @@ +/*****************************************************************************\ + * Copyright (c) 2018 Lawrence Livermore National Security, LLC. Produced at + * the Lawrence Livermore National Laboratory (cf, AUTHORS, DISCLAIMER.LLNS). + * LLNL-CODE-658032 All rights reserved. + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the license, or (at your option) + * any later version. + * + * Flux is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the IMPLIED WARRANTY OF MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the terms and conditions of the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * See also: http://www.gnu.org/licenses/ +\*****************************************************************************/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "job.h" + + +flux_future_t *flux_job_submit (flux_t *h, const char *J, int flags) +{ + flux_future_t *f; + + if (!h || !J) { + errno = EINVAL; + return NULL; + } + if (!(f = flux_rpc_pack (h, "job-ingest.add", FLUX_NODEID_ANY, 0, + "{s:s s:i}", + "J", J, + "flags", flags))) + return NULL; + return f; +} + +int flux_job_submit_get_id (flux_future_t *f, flux_jobid_t *jobid) +{ + flux_jobid_t id; + + if (!f || !jobid) { + errno = EINVAL; + return -1; + } + if (flux_rpc_get_unpack (f, "{s:I}", + "id", &id) < 0) + return -1; + *jobid = id; + return 0; +} + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ diff --git a/src/common/libjob/job.h b/src/common/libjob/job.h new file mode 100644 index 000000000000..769016e82bcd --- /dev/null +++ b/src/common/libjob/job.h @@ -0,0 +1,36 @@ +#ifndef _FLUX_CORE_JOB_H +#define _FLUX_CORE_JOB_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint64_t flux_jobid_t; + +/* Submit a job to the system. + * J should be RFC 14 jobspec signed by flux_sign_wrap(), provided + * flux was built --with-flux-security. If not, then J should be bare jobspec. + * Currently the 'flags' parameter must be set to 0. + * The system assigns a jobid and returns it in the response. + */ +flux_future_t *flux_job_submit (flux_t *h, const char *J, int flags); + +/* Parse jobid from response to flux_job_submit() request. + * Returns 0 on success, -1 on failure with errno set - and an extended + * error message may be available with flux_future_error_string(). + */ +int flux_job_submit_get_id (flux_future_t *f, flux_jobid_t *id); + +#ifdef __cplusplus +} +#endif + +#endif /* !_FLUX_CORE_JOB_H */ + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ From 8debfa7b6df4e25216d4be2dd8460cdedbf0bc31 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Wed, 13 Jun 2018 17:22:39 -0400 Subject: [PATCH 09/15] libjob/test: add TAP test for flux_job invalid args --- src/common/libjob/Makefile.am | 25 +++++++++++++++++++++++++ src/common/libjob/job.c | 2 +- src/common/libjob/test/job.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/common/libjob/test/job.c diff --git a/src/common/libjob/Makefile.am b/src/common/libjob/Makefile.am index 6f9f9c3bafd9..bc74f2e17cc8 100644 --- a/src/common/libjob/Makefile.am +++ b/src/common/libjob/Makefile.am @@ -14,3 +14,28 @@ fluxcoreinclude_HEADERS = job.h libjob_la_SOURCES = \ job.c + +TESTS = \ + test_job.t + +check_PROGRAMS = \ + $(TESTS) + +TEST_EXTENSIONS = .t +T_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ + $(top_srcdir)/config/tap-driver.sh + +test_ldadd = \ + $(top_builddir)/src/common/libjob/libjob.la \ + $(top_builddir)/src/common/libflux/libflux.la \ + $(top_builddir)/src/common/libflux-internal.la \ + $(top_builddir)/src/common/libtap/libtap.la \ + $(ZMQ_LIBS) $(JANSSON_LIBS) $(LIBPTHREAD) $(LIBRT) $(LIBDL) $(LIBMUNGE) + +test_cppflags = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir)/src/common/libtap + +test_job_t_SOURCES = test/job.c +test_job_t_CPPFLAGS = $(test_cppflags) +test_job_t_LDADD = $(test_ldadd) $(LIBDL) diff --git a/src/common/libjob/job.c b/src/common/libjob/job.c index 9b0c4596c563..12e7c1490bfd 100644 --- a/src/common/libjob/job.c +++ b/src/common/libjob/job.c @@ -39,7 +39,7 @@ flux_future_t *flux_job_submit (flux_t *h, const char *J, int flags) errno = EINVAL; return NULL; } - if (!(f = flux_rpc_pack (h, "job-ingest.add", FLUX_NODEID_ANY, 0, + if (!(f = flux_rpc_pack (h, "job-ingest.submit", FLUX_NODEID_ANY, 0, "{s:s s:i}", "J", J, "flags", flags))) diff --git a/src/common/libjob/test/job.c b/src/common/libjob/test/job.c new file mode 100644 index 000000000000..f6e350ecb5c2 --- /dev/null +++ b/src/common/libjob/test/job.c @@ -0,0 +1,28 @@ +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "src/common/libtap/tap.h" +#include "src/common/libjob/job.h" + +int main (int argc, char *argv[]) +{ + plan (NO_PLAN); + + errno = 0; + ok (flux_job_submit (NULL, NULL, 0) == NULL && errno == EINVAL, + "flux_job_submit with NULL args fails with EINVAL"); + + errno = 0; + ok (flux_job_submit_get_id (NULL, NULL) < 0 && errno == EINVAL, + "flux_job_submit_get_id with NULL args fails with EINVAL"); + + done_testing (); + return 0; +} + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ From 2741ca7c26a8278350fd1169cba3cdcc695b217b Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Tue, 22 May 2018 15:03:24 -0700 Subject: [PATCH 10/15] modules/job-ingest: add module for job ingest Add a module that handles job-ingest.add RPCs to add new jobs to the KVS using a rudimentary form of the RFC 16 job schema foramt. The user is returned a jobid based on the FLUID proposal, which allows 64-bit id generation to occur in parallel across ranks, while retaining a loose ordering of ids based in the time submitted. KVS per-job directories are generated using the DOTHEX FLUID encoding, e.g. job.active.0000.011c.ae00.0002 The instance owner and any user with ROLE_USER may submit jobs. Jobs must be signed, and the user authenticated as the submitter (by the connector) must match the signature, but the job signature is not authenticated at ingest time. The connector-authenticated userid is recored in the KVS under the "userid" key. The signed blob is recorded under the "J-signed" key. The job submission consists of signed RFC 14 jobspec, which is validated by temporarily instantiating a C++ Jobspec object and recording any parse errors. The parsed Jobspec may be further validated in the future, for example to find resource requests that can not be fulfilled, but for now we ingest all valid jobspec. (Validation is performed in a standalone .cpp file linked against libjobspec.la. The standalone C++, which exports a validate function callable from C, is compiled with the C++ compiler; automake then knows to link job-ingest against libstdc++). The unwrapped jobspec is written to the KVS under the "jobspec" key. The module is completely event driven, and KVS overhead is reduced and ingest rate increased by batching job-ingest.submit requests that arrive toether within 10ms. A "job-ingest.submit" event is generated after the KVS commit which contains an array of new jobids. This can be consumed by the job-manager module in the future, which will handle listing jobs for users, and informing the scheduler when new active jobs have been submitted. --- configure.ac | 1 + src/modules/Makefile.am | 3 +- src/modules/job-ingest/Makefile.am | 29 ++ src/modules/job-ingest/job-ingest.c | 538 ++++++++++++++++++++++++ src/modules/job-ingest/jobspec.h | 15 + src/modules/job-ingest/jobspec_wrap.cpp | 36 ++ 6 files changed, 621 insertions(+), 1 deletion(-) create mode 100644 src/modules/job-ingest/Makefile.am create mode 100644 src/modules/job-ingest/job-ingest.c create mode 100644 src/modules/job-ingest/jobspec.h create mode 100644 src/modules/job-ingest/jobspec_wrap.cpp diff --git a/configure.ac b/configure.ac index d6a12c2c2dcc..63252d5206b3 100644 --- a/configure.ac +++ b/configure.ac @@ -341,6 +341,7 @@ AC_CONFIG_FILES( \ src/modules/aggregator/Makefile \ src/modules/pymod/Makefile \ src/modules/userdb/Makefile \ + src/modules/job-ingest/Makefile \ src/test/Makefile \ etc/Makefile \ etc/flux-core.pc \ diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am index 06f8eb5b184f..7c306d973778 100644 --- a/src/modules/Makefile.am +++ b/src/modules/Makefile.am @@ -8,7 +8,8 @@ SUBDIRS = \ resource-hwloc \ cron \ aggregator \ - userdb + userdb \ + job-ingest if HAVE_PYTHON SUBDIRS += pymod diff --git a/src/modules/job-ingest/Makefile.am b/src/modules/job-ingest/Makefile.am new file mode 100644 index 000000000000..bafcd4110973 --- /dev/null +++ b/src/modules/job-ingest/Makefile.am @@ -0,0 +1,29 @@ +AM_CFLAGS = \ + $(WARNING_CFLAGS) \ + $(CODE_COVERAGE_CFLAGS) + +AM_LDFLAGS = \ + $(CODE_COVERAGE_LIBS) + +AM_CPPFLAGS = \ + -I$(top_srcdir) -I$(top_srcdir)/src/include \ + $(ZMQ_CFLAGS) $(FLUX_SECURITY_CFLAGS) $(YAMLCPP_CFLAGS) + +fluxmod_LTLIBRARIES = job-ingest.la + +job_ingest_la_SOURCES = job-ingest.c +job_ingest_la_LDFLAGS = $(fluxmod_ldflags) -module +job_ingest_la_LIBADD = $(fluxmod_libadd) \ + $(top_builddir)/src/common/libjob/libjob.la \ + $(top_builddir)/src/common/libflux-internal.la \ + $(top_builddir)/src/common/libflux-core.la \ + $(top_builddir)/src/common/libflux-optparse.la \ + $(FLUX_SECURITY_LIBS) \ + $(ZMQ_LIBS) + +if ENABLE_JOBSPEC +job_ingest_la_SOURCES += jobspec_wrap.cpp jobspec.h +job_ingest_la_LIBADD += \ + $(top_builddir)/src/common/libjobspec/libjobspec.la \ + $(YAMLCPP_LIBS) +endif diff --git a/src/modules/job-ingest/job-ingest.c b/src/modules/job-ingest/job-ingest.c new file mode 100644 index 000000000000..3efcf54aa316 --- /dev/null +++ b/src/modules/job-ingest/job-ingest.c @@ -0,0 +1,538 @@ +/*****************************************************************************\ + * Copyright (c) 2018 Lawrence Livermore National Security, LLC. Produced at + * the Lawrence Livermore National Laboratory (cf, AUTHORS, DISCLAIMER.LLNS). + * LLNL-CODE-658032 All rights reserved. + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the license, or (at your option) + * any later version. + * + * Flux is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the IMPLIED WARRANTY OF MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the terms and conditions of the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * See also: http://www.gnu.org/licenses/ +\*****************************************************************************/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#if HAVE_FLUX_SECURITY +#include +#include +#endif + +#include "src/common/libjob/job.h" +#include "src/common/libutil/fluid.h" + +#if HAVE_JOBSPEC +#include "jobspec.h" +#endif + +/* job-ingest takes in signed jobspec submitted through flux_job_submit(), + * performing the following tasks for each job: + * + * 1) verify that submitting userid == userid that signed jobspec + * 2) verify that enclosed jobspec is valid per RFC 14 + * 3) assign jobid using distributed 64-bit FLUID generator + * 4) commit job data to KVS per RFC 16 (KVS Job Schema) + * 5) publish "job-ingest.submit" event announcing new jobid + * + * For performance, the above actions are batched, so that if job requests + * arrive within the 'batch_timeout' window, they are combined into one + * KVS transaction and one event message. The event message payload + * contains an array of integer jobids, e.g. + * {"ids":[I,I,...]} + * + * The jobid is returned to the user in response to the "submit" RPC. + * Responses are sent after the KVS commit for the batch is completed, + * and concurrent with event publication. + * + * Currently all KVS data is committed under job.active., + * where is the jobid converted to 16-bit, 0-padded hex + * strings delimited by periods, e.g. + * job.active.0000.0004.b200.0000 + * + * The job-ingest module can be loaded on rank 0, or on many ranks across + * the instance, rank < max FLUID id of 16384. Each rank is relatively + * independent and KVS commit scalability will ultimately limit the max + * ingest rate for an instance. + * + * Security: any user with FLUX_ROLE_USER may submit jobs. The jobspec + * must be signed, but this module (running as the instance owner) doesn't + * need to authenticate the signature. It merely unwraps the contents, + * and checks that the security envelope claims the same userid as the + * userid stamped on the request message, which was authenticated by the + * connector. + */ + + +/* The batch_timeout (seconds) is the maximum length of time + * any given job request is delayed before initiating a KVS commit. + * Too large, and individual job submit latency will suffer. + * Too small, and KVS commit overhead will increase. + */ +const double batch_timeout = 0.01; + + +struct job_ingest_ctx { + flux_t *h; +#if HAVE_FLUX_SECURITY + flux_security_t *sec; +#endif + struct fluid_generator gen; + flux_msg_handler_t **handlers; + + struct batch *batch; + flux_watcher_t *timer; +}; + +struct job { + fluid_t id; + char idstr[32]; + flux_msg_t *msg; // orig. request message +}; + +struct batch { + struct job_ingest_ctx *ctx; + flux_kvs_txn_t *txn; + zlist_t *jobs; + json_t *idlist; +}; + +static void job_destroy (struct job *job) +{ + if (job) { + int saved_errno = errno; + flux_msg_destroy (job->msg); + free (job); + errno = saved_errno; + } +} + +/* Create a 'struct job', assigning its job id and pre-caching + * its DOTHEX encoding. Original submit request message is copied + * and stuck here for delayed response. + */ +static struct job *job_create (struct fluid_generator *gen, + const flux_msg_t *msg) +{ + struct job *job; + + if (!(job = calloc (1, sizeof (*job)))) + return NULL; + if (fluid_generate (gen, &job->id) < 0) + goto error_inval; + if (fluid_encode (job->idstr, sizeof (job->idstr), job->id, + FLUID_STRING_DOTHEX) < 0) + goto error_inval; + if (!(job->msg = flux_msg_copy (msg, false))) + goto error; + return job; +error_inval: + errno = EINVAL; +error: + job_destroy (job); + return NULL; +} + +static void batch_destroy (struct batch *batch) +{ + if (batch) { + int saved_errno = errno; + if (batch->jobs) { + struct job *job; + while ((job = zlist_pop (batch->jobs))) + job_destroy (job); + zlist_destroy (&batch->jobs); + json_decref (batch->idlist); + } + free (batch); + errno = saved_errno; + } +} + +/* Create a 'struct batch', a container for a group of job submit + * requests. Prepare a KVS transaction and a json array of jobid's + * to be used for event publication. + */ +static struct batch *batch_create (struct job_ingest_ctx *ctx) +{ + struct batch *batch; + + if (!(batch = calloc (1, sizeof (*batch)))) + return NULL; + if (!(batch->jobs = zlist_new ())) + goto nomem; + if (!(batch->txn = flux_kvs_txn_create ())) + goto error; + if (!(batch->idlist = json_array ())) + goto nomem; + batch->ctx = ctx; + return batch; +nomem: + errno = ENOMEM; +error: + batch_destroy (batch); + return NULL; +} + +/* Get result of publishing event and log any error. + * Finally destroy the batch. + * + * N.B. failure to publish is treated as non-fatal at this point. + * It seemed unlikely enough to not be worth increasing the response latency, + * so we merely log it. + */ +static void batch_event_pub_continuation (flux_future_t *f, void *arg) +{ + struct batch *batch = arg; + flux_t *h = batch->ctx->h; + if (flux_future_get (f, NULL) < 0) + flux_log_error (h, "%s: event pub failed", __FUNCTION__); + batch_destroy (batch); + flux_future_destroy (f); +} + +/* Publish event containing new jobids. + */ +static void batch_event_pub (struct batch *batch) +{ + flux_t *h = batch->ctx->h; + flux_future_t *f; + + if (!(f = flux_event_publish_pack (h, "job-ingest.submit", 0, "{s:O}", + "ids", batch->idlist))) { + flux_log_error (h, "%s: flux_event_publish_pack", __FUNCTION__); + goto error; + } + if (flux_future_then (f, -1., batch_event_pub_continuation, batch) < 0) { + flux_log_error (h, "%s: flux_future_then", __FUNCTION__); + flux_future_destroy (f); + goto error; + } + return; +error: + batch_destroy (batch); +} + +/* Respond to all requestors (for each job) with errnum and errstr (required). + */ +static void batch_respond_error (struct batch *batch, + int errnum, const char *errstr) +{ + flux_t *h = batch->ctx->h; + struct job *job = zlist_first (batch->jobs); + while (job) { + if (flux_respond_error (h, job->msg, errnum, "%s", errstr) < 0) + flux_log_error (h, "%s: flux_respond_error", __FUNCTION__); + job = zlist_next (batch->jobs); + } +} + +/* Respond to all requestors (for each job) with their id. + */ +static void batch_respond_success (struct batch *batch) +{ + flux_t *h = batch->ctx->h; + struct job *job = zlist_first (batch->jobs); + while (job) { + if (flux_respond_pack (h, job->msg, "{s:I}", "id", job->id) < 0) + flux_log_error (h, "%s: flux_respond_pack", __FUNCTION__); + job = zlist_next (batch->jobs); + } +} + +/* Get result of KVS commit. + * Respond to all requestors with success or failure. + * If successful, generate event containing new jobid's. + */ +static void batch_flush_continuation (flux_future_t *f, void *arg) +{ + struct batch *batch = arg; + + if (flux_future_get (f, NULL) < 0) { + batch_respond_error (batch, errno, "KVS commit failed"); + batch_destroy (batch); + } + else { + batch_respond_success (batch); + batch_event_pub (batch); + } + flux_future_destroy (f); +} + +/* batch timer - expires 'batch_timeout' seconds after batch was created. + * Replace ctx->batch with a NULL, and pass 'batch' off to a chain of + * continuations that commit its data to the KVS, respond to requestors, + * and publish an event containing the new jobids. + */ +static void batch_flush (flux_reactor_t *r, flux_watcher_t *w, + int revents, void *arg) +{ + struct job_ingest_ctx *ctx = arg; + struct batch *batch; + flux_future_t *f; + + batch = ctx->batch; + ctx->batch = NULL; + + if (!(f = flux_kvs_commit (ctx->h, 0, batch->txn))) { + batch_respond_error (batch, errno, "flux_kvs_commit failed"); + goto error; + } + if (flux_future_then (f, -1., batch_flush_continuation, batch) < 0) { + batch_respond_error (batch, errno, "flux_future_then failed"); + flux_future_destroy (f); + goto error; + } + return; +error: + batch_destroy (batch); +} + +/* Format key within the KVS directory of 'job'. + */ +static int make_key (char *buf, int bufsz, struct job *job, const char *name) +{ + int n = snprintf (buf, bufsz, "job.active.%s%s%s", job->idstr, + (name ? "." : ""), (name ? name : "")); + if (n < 0 || n >= bufsz) { + errno = EINVAL; + return -1; + } + return 0; +} + +/* Add 'job' to 'batch'. + * On error, ensure that no remnants of job made into KVS transaction. + */ +static int batch_add_job (struct batch *batch, struct job *job, + const char *J, uint32_t userid, + const char *jobspec, int jobspecsz) +{ + char key[64]; + int saved_errno; + json_t *id; + + if (zlist_append (batch->jobs, job) < 0) { + errno = ENOMEM; + return -1; + } + if (J != NULL) { + if (make_key (key, sizeof (key), job, "J-signed") < 0) + goto error; + if (flux_kvs_txn_put (batch->txn, 0, key, J) < 0) + goto error; + } + if (make_key (key, sizeof (key), job, "jobspec") < 0) + goto error; + if (flux_kvs_txn_put_raw (batch->txn, 0, key, jobspec, jobspecsz) < 0) + goto error; + if (make_key (key, sizeof (key), job, "userid") < 0) + goto error; + if (flux_kvs_txn_pack (batch->txn, 0, key, "i", userid) < 0) + goto error; + if (!(id = json_integer (job->id))) + goto nomem; + if (json_array_append_new (batch->idlist, id) < 0) { + json_decref (id); + goto nomem; + } + return 0; +nomem: + errno = ENOMEM; +error: + saved_errno = errno; + zlist_remove (batch->jobs, job); + if (make_key (key, sizeof (key), job, NULL) == 0) + (void)flux_kvs_txn_unlink (batch->txn, 0, key); + errno = saved_errno; + return -1; +} + +/* Handle "job-ingest.submit" request to add a new job. + * Unwrap the signed jobspec and compare claimed userid to authenticated + * userid from request (they must match). Signature does not need to be + * verified here. Add job to a batch of new jobs that will be committed + * after a timer expires. + */ +static void submit_cb (flux_t *h, flux_msg_handler_t *mh, + const flux_msg_t *msg, void *arg) +{ + struct job_ingest_ctx *ctx = arg; + int flags; + struct job *job = NULL; + const char *J; + const char *jobspec; + const char *errmsg = NULL; + char errbuf[80]; + int jobspecsz; + int rc; + uint32_t userid; + uint32_t rolemask; + + if (flux_request_unpack (msg, NULL, "{s:s s:i}", + "J", &J, + "flags", &flags) < 0) + goto error; + if (flags != 0) { + errno = EPROTO; + goto error; + } + if (flux_msg_get_userid (msg, &userid) < 0) + goto error; + if (flux_msg_get_rolemask (msg, &rolemask) < 0) + goto error; +#if HAVE_FLUX_SECURITY + int64_t userid_signer; + const char *mech_type; + if (flux_sign_unwrap_anymech (ctx->sec, J, (const void **)&jobspec, + &jobspecsz, &mech_type, &userid_signer, + FLUX_SIGN_NOVERIFY) < 0) { + errmsg = flux_security_last_error (ctx->sec); + goto error; + } + /* If the signature claims to be a user other than the submitting user, + * do not allow that. + */ + if (userid_signer != userid) { + snprintf (errbuf, sizeof (errbuf), + "signer=%lu != requestor=%lu", + (unsigned long)userid_signer, (unsigned long)userid); + errmsg = errbuf; + errno = EPERM; + goto error; + } + /* If not the instance owner, a strong signature is required + * to give the imp permission to launch processes as the user. + */ + if (!(rolemask & FLUX_ROLE_OWNER) && !strcmp (mech_type, "none")) { + snprintf (errbuf, sizeof (errbuf), + "only instance owner can use sign-type=none"); + errmsg = errbuf; + errno = EPERM; + goto error; + } +#else + /* Without the IMP or a signing mechanism, users other than + * the instance owner can certainly not run. + */ + if (!(rolemask & FLUX_ROLE_OWNER)) { + snprintf (errbuf, sizeof (errbuf), + "only the instance owner can submit jobs"); + errmsg = errbuf; + errno = EPERM; + goto error; + } + /* jobspec is passed in plaintext instead of the signed J. + * J-signed will not be written to the KVS. + */ + jobspec = J; + jobspecsz = strlen (J); + J = NULL; +#endif +#if HAVE_JOBSPEC + if (jobspec_validate (jobspec, jobspecsz, errbuf, sizeof (errbuf)) < 0) { + errmsg = errbuf; + errno = EINVAL; + goto error; + } +#endif + if (!ctx->batch) { + if (!(ctx->batch = batch_create (ctx))) + goto error; + flux_timer_watcher_reset (ctx->timer, batch_timeout, 0.); + flux_watcher_start (ctx->timer); + } + if (!(job = job_create (&ctx->gen, msg))) + goto error; + if (batch_add_job (ctx->batch, job, J, userid, jobspec, jobspecsz) < 0) { + job_destroy (job); + goto error; + } + return; +error: + if (errmsg) + rc = flux_respond_error (h, msg, errno, "%s", errmsg); + else + rc = flux_respond_error (h, msg, errno, NULL); + if (rc < 0) + flux_log_error (h, "%s: flux_respond_error", __FUNCTION__); +} + +static const struct flux_msg_handler_spec htab[] = { + { FLUX_MSGTYPE_REQUEST, "job-ingest.submit", submit_cb, FLUX_ROLE_USER }, + FLUX_MSGHANDLER_TABLE_END, +}; + +int mod_main (flux_t *h, int argc, char **argv) +{ + flux_reactor_t *r = flux_get_reactor (h); + int rc = -1; + struct job_ingest_ctx ctx; + uint32_t rank; + + memset (&ctx, 0, sizeof (ctx)); + ctx.h = h; +#if HAVE_FLUX_SECURITY + if (!(ctx.sec = flux_security_create (0))) { + flux_log_error (h, "flux_security_create"); + goto done; + } + if (flux_security_configure (ctx.sec, NULL) < 0) { + flux_log_error (h, "flux_security_configure: %s", + flux_security_last_error (ctx.sec)); + goto done; + } +#endif + if (flux_msg_handler_addvec (h, htab, &ctx, &ctx.handlers) < 0) { + flux_log_error (h, "flux_msghandler_add"); + goto done; + } + if (!(ctx.timer = flux_timer_watcher_create (r, 0., 0., + batch_flush, &ctx))) { + flux_log_error (h, "flux_timer_watcher_create"); + goto done; + } + if (flux_get_rank (h, &rank) < 0) { + flux_log_error (h, "flux_get_rank"); + goto done; + } + /* fluid_init() will fail on rank > 16K. + * Just skip loading the job module on those ranks. + */ + if (fluid_init (&ctx.gen, rank) < 0) { + flux_log (h, LOG_ERR, "fluid_init failed"); + errno = EINVAL; + } + if (flux_reactor_run (r, 0) < 0) { + flux_log_error (h, "flux_reactor_run"); + goto done; + } + rc = 0; +done: + flux_msg_handler_delvec (ctx.handlers); + flux_watcher_destroy (ctx.timer); +#if HAVE_FLUX_SECURITY + flux_security_destroy (ctx.sec); +#endif + return rc; +} + +MOD_NAME ("job-ingest"); + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ diff --git a/src/modules/job-ingest/jobspec.h b/src/modules/job-ingest/jobspec.h new file mode 100644 index 000000000000..1e7a80b3db7a --- /dev/null +++ b/src/modules/job-ingest/jobspec.h @@ -0,0 +1,15 @@ +#ifndef _FLUX_JOBSPEC_WRAP_H +#define _FLUX_JOBSPEC_WRAP_H + +#ifdef __cplusplus +extern "C" { +#endif + +int jobspec_validate (const char *buf, int len, + char *errbuf, int errbufsz); + +#ifdef __cplusplus +} +#endif + +#endif /* !_FLUX_JOBSPEC_WRAP_H */ diff --git a/src/modules/job-ingest/jobspec_wrap.cpp b/src/modules/job-ingest/jobspec_wrap.cpp new file mode 100644 index 000000000000..c31f50b0362d --- /dev/null +++ b/src/modules/job-ingest/jobspec_wrap.cpp @@ -0,0 +1,36 @@ +/* C wrappers for jobspec class */ + +#include "jobspec.h" + +#include +#include "src/common/libjobspec/jobspec.hpp" + +using namespace Flux::Jobspec; + +extern "C" +int jobspec_validate (const char *buf, int len, + char *errbuf, int errbufsz) +{ + std::string str (buf, len); + Jobspec js; + + try { + js = Jobspec (str); + } catch (parse_error& e) { + if (errbuf && errbufsz > 0) { + if (e.position != -1 || e.line != -1 || e.column != -1) + snprintf (errbuf, errbufsz, + "jobspec (pos %d line %d col %d): %s", + e.position, e.line, e.column, e.what()); + else + snprintf (errbuf, errbufsz, "jobspec: %s", e.what()); + } + return -1; + } + + return 0; +} + +/* + * vi: ts=4 sw=4 expandtab + */ From 8988609b81a21721bd20c919f4c060cd76a751ee Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Tue, 22 May 2018 14:37:09 -0700 Subject: [PATCH 11/15] cmd/flux-job: add flux-job command Add a front end command that will eventually contain the primary user interfaces for submitting and managing jobs such as list, run, or cancel. For now, it contains two subcommands for testing the job-ingest module: submitbench - test ingest throughput, maintaining a minimum number of outstanding RPCs id - convert jobid's between representations. --- src/cmd/Makefile.am | 6 +- src/cmd/flux-job.c | 418 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 422 insertions(+), 2 deletions(-) create mode 100644 src/cmd/flux-job.c diff --git a/src/cmd/Makefile.am b/src/cmd/Makefile.am index 7081e53d83bd..87db8829331f 100644 --- a/src/cmd/Makefile.am +++ b/src/cmd/Makefile.am @@ -7,7 +7,7 @@ AM_LDFLAGS = \ AM_CPPFLAGS = \ -I$(top_srcdir) -I$(top_srcdir)/src/include \ - $(ZMQ_CFLAGS) + $(ZMQ_CFLAGS) $(FLUX_SECURITY_CFLAGS) AM_CXXFLAGS = \ $(WARNING_CXXFLAGS) \ @@ -18,6 +18,7 @@ fluxcmd_ldadd = \ $(top_builddir)/src/common/libflux-internal.la \ $(top_builddir)/src/common/libflux-core.la \ $(top_builddir)/src/common/libflux-optparse.la \ + $(FLUX_SECURITY_LIBS) \ $(ZMQ_LIBS) $(LIBMUNGE) $(LIBPTHREAD) $(LIBDL) $(HWLOC_LIBS) LDADD = $(fluxcmd_ldadd) @@ -72,7 +73,8 @@ fluxcmd_PROGRAMS = \ flux-comms \ flux-kvs \ flux-start \ - flux-jstat + flux-jstat \ + flux-job if ENABLE_JOBSPEC fluxcmd_PROGRAMS += \ diff --git a/src/cmd/flux-job.c b/src/cmd/flux-job.c new file mode 100644 index 000000000000..3cbfa00fcba1 --- /dev/null +++ b/src/cmd/flux-job.c @@ -0,0 +1,418 @@ +/*****************************************************************************\ + * Copyright (c) 2014 Lawrence Livermore National Security, LLC. Produced at + * the Lawrence Livermore National Laboratory (cf, AUTHORS, DISCLAIMER.LLNS). + * LLNL-CODE-658032 All rights reserved. + * + * This file is part of the Flux resource manager framework. + * For details, see https://github.com/flux-framework. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the license, or (at your option) + * any later version. + * + * Flux is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the IMPLIED WARRANTY OF MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the terms and conditions of the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * See also: http://www.gnu.org/licenses/ +\*****************************************************************************/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_FLUX_SECURITY +#include +#endif +#include "src/common/libutil/log.h" +#include "src/common/libutil/fluid.h" +#include "src/common/libjob/job.h" +#include "src/common/libutil/read_all.h" + +int cmd_submitbench (optparse_t *p, int argc, char **argv); +int cmd_id (optparse_t *p, int argc, char **argv); + +static struct optparse_option global_opts[] = { + OPTPARSE_TABLE_END +}; + +static struct optparse_option submitbench_opts[] = { + { .name = "repeat", .key = 'r', .has_arg = 1, .arginfo = "N", + .usage = "Run N instances of jobspec", + }, + { .name = "fanout", .key = 'f', .has_arg = 1, .arginfo = "N", + .usage = "Run at most N RPCs in parallel", + }, +#if HAVE_FLUX_SECURITY + { .name = "reuse-signature", .key = 'R', .has_arg = 0, + .usage = "Sign jobspec once and reuse the result for multiple RPCs", + }, + { .name = "security-config", .key = 'c', .has_arg = 1, .arginfo = "pattern", + .usage = "Use non-default security config glob", + }, + { .name = "sign-type", .key = 's', .has_arg = 1, .arginfo = "TYPE", + .usage = "Use non-default mechanism type to sign J", + }, +#endif + OPTPARSE_TABLE_END +}; + +static struct optparse_option id_opts[] = { + { .name = "from", .key = 'f', .has_arg = 1, + .arginfo = "dec|kvs-active|words", + .usage = "Convert jobid from specified form", + }, + { .name = "to", .key = 't', .has_arg = 1, + .arginfo = "dec|kvs-active|words", + .usage = "Convert jobid to specified form", + }, + OPTPARSE_TABLE_END +}; + +static struct optparse_subcommand subcommands[] = { + { "submitbench", + "[OPTIONS] jobspec", + "Run job(s)", + cmd_submitbench, + 0, + submitbench_opts + }, + { "id", + "[OPTIONS] [id ...]", + "Convert jobid(s) to another form", + cmd_id, + 0, + id_opts + }, + OPTPARSE_SUBCMD_END +}; + +int usage (optparse_t *p, struct optparse_option *o, const char *optarg) +{ + struct optparse_subcommand *s; + optparse_print_usage (p); + fprintf (stderr, "\n"); + fprintf (stderr, "Common commands from flux-job:\n"); + s = subcommands; + while (s->name) { + fprintf (stderr, " %-15s %s\n", s->name, s->doc); + s++; + } + exit (1); +} + +int main (int argc, char *argv[]) +{ + char *cmdusage = "[OPTIONS] COMMAND ARGS"; + optparse_t *p; + int optindex; + int exitval; + + log_init ("flux-job"); + + p = optparse_create ("flux-job"); + + if (optparse_add_option_table (p, global_opts) != OPTPARSE_SUCCESS) + log_msg_exit ("optparse_add_option_table() failed"); + + /* Override help option for our own */ + if (optparse_set (p, OPTPARSE_USAGE, cmdusage) != OPTPARSE_SUCCESS) + log_msg_exit ("optparse_set (USAGE)"); + + /* Override --help callback in favor of our own above */ + if (optparse_set (p, OPTPARSE_OPTION_CB, "help", usage) != OPTPARSE_SUCCESS) + log_msg_exit ("optparse_set() failed"); + + /* Don't print internal subcommands, we do it ourselves */ + if (optparse_set (p, OPTPARSE_PRINT_SUBCMDS, 0) != OPTPARSE_SUCCESS) + log_msg_exit ("optparse_set (PRINT_SUBCMDS)"); + + if (optparse_reg_subcommands (p, subcommands) != OPTPARSE_SUCCESS) + log_msg_exit ("optparse_reg_subcommands"); + + if ((optindex = optparse_parse_args (p, argc, argv)) < 0) + exit (1); + + if ((argc - optindex == 0) + || !optparse_get_subcommand (p, argv[optindex])) { + usage (p, NULL, NULL); + exit (1); + } + + if ((exitval = optparse_run_subcommand (p, argc, argv)) < 0) + exit (1); + + optparse_destroy (p); + log_fini (); + return (exitval); +} + +struct submitbench_ctx { + flux_t *h; +#if HAVE_FLUX_SECURITY + flux_security_t *sec; + const char *sign_type; +#endif + int flags; + flux_watcher_t *prep; + flux_watcher_t *check; + flux_watcher_t *idle; + int txcount; + int rxcount; + int totcount; + int max_queue_depth; + optparse_t *p; + void *jobspec; + int jobspecsz; + const char *J; +}; + +/* Read entire file 'name' ("-" for stdin). Exit program on error. + */ +size_t read_jobspec (const char *name, void **bufp) +{ + int fd; + ssize_t size; + void *buf; + + if (!strcmp (name, "-")) + fd = STDIN_FILENO; + else { + if ((fd = open (name, O_RDONLY)) < 0) + log_err_exit ("%s", name); + } + if ((size = read_all (fd, &buf)) < 0) + log_err_exit ("%s", name); + if (fd != STDIN_FILENO) + (void)close (fd); + *bufp = buf; + return size; +} + +/* handle RPC response + * Once all responses are received, stop prep/check watchers + * so reactor will stop. + */ +void submitbench_continuation (flux_future_t *f, void *arg) +{ + struct submitbench_ctx *ctx = arg; + flux_jobid_t id; + const char *errmsg; + + if (flux_job_submit_get_id (f, &id) < 0) { + if (errno == ENOSYS) + log_msg_exit ("submit: job-ingest module is not loaded"); + else if ((errmsg = flux_future_error_string (f))) + log_msg_exit ("submit: %s", errmsg); + else + log_err_exit ("submit"); + } + printf ("%llu\n", (unsigned long long)id); + flux_future_destroy (f); + + ctx->rxcount++; +} + +/* prep - called before event loop would block + * Prevent loop from blocking if 'check' could send RPCs. + * Stop the prep/check watchers if RPCs have all been sent, + * so that, once responses are received, the reactor will exit naturally. + */ +void submitbench_prep (flux_reactor_t *r, flux_watcher_t *w, + int revents, void *arg) +{ + struct submitbench_ctx *ctx = arg; + + if (ctx->txcount == ctx->totcount) { + flux_watcher_stop (ctx->prep); + flux_watcher_stop (ctx->check); + } + else if ((ctx->txcount - ctx->rxcount) < ctx->max_queue_depth) + flux_watcher_start (ctx->idle); // keeps loop from blocking +} + +/* check - called after event loop unblocks + * If there are RPCs to send, send one. + */ +void submitbench_check (flux_reactor_t *r, flux_watcher_t *w, + int revents, void *arg) +{ + struct submitbench_ctx *ctx = arg; + + flux_watcher_stop (ctx->idle); + if (ctx->txcount < ctx->totcount + && (ctx->txcount - ctx->rxcount) < ctx->max_queue_depth) { + flux_future_t *f; +#if HAVE_FLUX_SECURITY + if (!ctx->J || !optparse_hasopt (ctx->p, "reuse-signature")) { + if (!(ctx->J = flux_sign_wrap (ctx->sec, ctx->jobspec, + ctx->jobspecsz, ctx->sign_type, 0))) + log_err_exit ("flux_sign_wrap: %s", + flux_security_last_error (ctx->sec)); + } + if (!(f = flux_job_submit (ctx->h, ctx->J, ctx->flags))) + log_err_exit ("flux_job_submit"); +#else + char *cpy = strndup (ctx->jobspec, ctx->jobspecsz); + if (!cpy) + log_err_exit ("strndup"); + if (!(f = flux_job_submit (ctx->h, cpy, ctx->flags))) + log_err_exit ("flux_job_submit"); + free (cpy); +#endif + if (flux_future_then (f, -1., submitbench_continuation, ctx) < 0) + log_err_exit ("flux_future_then"); + ctx->txcount++; + } +} + +int cmd_submitbench (optparse_t *p, int argc, char **argv) +{ + flux_reactor_t *r; + int optindex = optparse_option_index (p); + struct submitbench_ctx ctx; + + memset (&ctx, 0, sizeof (ctx)); + + if (optindex != argc - 1) { + optparse_print_usage (p); + exit (1); + } +#if HAVE_FLUX_SECURITY + const char *sec_config = optparse_get_str (p, "security-config", NULL); + if (!(ctx.sec = flux_security_create (0))) + log_err_exit ("security"); + if (flux_security_configure (ctx.sec, sec_config) < 0) + log_err_exit ("security config %s", flux_security_last_error (ctx.sec)); + ctx.sign_type = optparse_get_str (p, "sign-type", NULL); +#endif + if (!(ctx.h = flux_open (NULL, 0))) + log_err_exit ("flux_open"); + r = flux_get_reactor (ctx.h); + ctx.p = p; + ctx.max_queue_depth = optparse_get_int (p, "fanout", 256); + ctx.totcount = optparse_get_int (p, "repeat", 1); + ctx.jobspecsz = read_jobspec (argv[optindex++], &ctx.jobspec); + + /* Prep/check/idle watchers perform flow control, keeping + * at most ctx.max_queue_depth RPCs outstanding. + */ + ctx.prep = flux_prepare_watcher_create (r, submitbench_prep, &ctx); + ctx.check = flux_check_watcher_create (r, submitbench_check, &ctx); + ctx.idle = flux_idle_watcher_create (r, NULL, NULL); + if (!ctx.prep || !ctx.check || !ctx.idle) + log_err_exit ("flux_watcher_create"); + flux_watcher_start (ctx.prep); + flux_watcher_start (ctx.check); + + if (flux_reactor_run (r, 0) < 0) + log_err_exit ("flux_reactor_run"); +#if HAVE_FLUX_SECURITY + flux_security_destroy (ctx.sec); // invalidates ctx.J +#endif + flux_close (ctx.h); + free (ctx.jobspec); + return 0; +} + +void id_convert (optparse_t *p, const char *src, char *dst, int dstsz) +{ + const char *from = optparse_get_str (p, "from", "dec"); + const char *to = optparse_get_str (p, "to", "dec"); + flux_jobid_t id; + + /* src to id + */ + if (!strcmp (from, "dec")) { + char *endptr; + errno = 0; + id = strtoull (src, &endptr, 10); + if (errno != 0) + log_err_exit ("%s", src); + if (*endptr != '\0') + log_msg_exit ("%s: malformed input", src); + } + else if (!strcmp (from, "kvs-active")) { + if (strncmp (src, "job.active.", 11) != 0) + log_msg_exit ("%s: missing 'job.active.' prefix", src); + if (fluid_decode (src + 11, &id, FLUID_STRING_DOTHEX) < 0) + log_msg_exit ("%s: malformed input", src); + } + else if (!strcmp (from, "words")) { + if (fluid_decode (src, &id, FLUID_STRING_MNEMONIC) < 0) + log_msg_exit ("%s: malformed input", src); + } + else + log_msg_exit ("Unknown from=%s", from); + + /* id to dst + */ + if (!strcmp (to, "dec")) { + snprintf (dst, dstsz, "%llu", (unsigned long long)id); + } + else if (!strcmp (to, "kvs-active")) { + if (snprintf (dst, dstsz, "job.active.") >= dstsz + || fluid_encode (dst + strlen (dst), dstsz - strlen (dst), + id, FLUID_STRING_DOTHEX) < 0) + log_msg_exit ("error encoding id"); + } + else if (!strcmp (to, "words")) { + if (fluid_encode (dst, dstsz, id, FLUID_STRING_MNEMONIC) < 0) + log_msg_exit ("error encoding id"); + } + else + log_msg_exit ("Unknown to=%s", to); +} + +char *trim_string (char *s) +{ + /* trailing */ + int len = strlen (s); + while (len > 1 && isspace (s[len - 1])) { + s[len - 1] = '\0'; + len--; + } + /* leading */ + char *p = s; + while (*p && isspace (*p)) + p++; + return p; +} + +int cmd_id (optparse_t *p, int argc, char **argv) +{ + int optindex = optparse_option_index (p); + char dst[256]; + + if (optindex == argc) { + char src[256]; + while ((fgets (src, sizeof (src), stdin))) { + id_convert (p, trim_string (src), dst, sizeof (dst)); + printf ("%s\n", dst); + } + } + else { + while (optindex < argc) { + id_convert (p, argv[optindex++], dst, sizeof (dst)); + printf ("%s\n", dst); + } + } + return 0; +} + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ From ebb2b2af14fbde26df3623779e701fc33223f6be Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Fri, 8 Jun 2018 15:42:00 -0700 Subject: [PATCH 12/15] sharness: add "job" rc1/rc3 scripts Ad a test_under_flux "profile" for testing the new execution system, starting with job-ingest module. --- t/Makefile.am | 2 ++ t/rc/rc1-job | 7 +++++++ t/rc/rc3-job | 7 +++++++ 3 files changed, 16 insertions(+) create mode 100755 t/rc/rc1-job create mode 100755 t/rc/rc3-job diff --git a/t/Makefile.am b/t/Makefile.am index 66e6c7913b45..d3a13c595b0b 100644 --- a/t/Makefile.am +++ b/t/Makefile.am @@ -119,9 +119,11 @@ EXTRA_DIST= \ rc/rc1-kvs \ rc/rc1-wreck \ rc/rc1-testenv \ + rc/rc1-job \ rc/rc3-kvs \ rc/rc3-wreck \ rc/rc3-testenv \ + rc/rc3-job \ wreck/input \ wreck/output \ jobspec diff --git a/t/rc/rc1-job b/t/rc/rc1-job new file mode 100755 index 000000000000..eb19d6abb921 --- /dev/null +++ b/t/rc/rc1-job @@ -0,0 +1,7 @@ +#!/bin/bash -e + +flux module load -r 0 content-sqlite +flux module load -r 0 kvs +flux module load -r all -x 0 kvs + +flux module load -r all job-ingest diff --git a/t/rc/rc3-job b/t/rc/rc3-job new file mode 100755 index 000000000000..99ee8ff037f9 --- /dev/null +++ b/t/rc/rc3-job @@ -0,0 +1,7 @@ +#!/bin/bash -e + +flux module remove -r all job-ingest + +flux module remove -r all -x 0 kvs +flux module remove -r 0 kvs +flux module remove -r 0 content-sqlite From 175783abf3ce948b96e31b58b7427a5f3129b8a8 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Fri, 8 Jun 2018 15:56:31 -0700 Subject: [PATCH 13/15] sharness: add job-ingest test script --- t/Makefile.am | 2 + t/t2200-job-ingest.t | 93 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100755 t/t2200-job-ingest.t diff --git a/t/Makefile.am b/t/Makefile.am index d3a13c595b0b..a5a9c18a8947 100644 --- a/t/Makefile.am +++ b/t/Makefile.am @@ -83,6 +83,7 @@ TESTS = \ t2008-althash.t \ t2009-hostlist.t \ t2100-aggregate.t \ + t2200-job-ingest.t \ t3000-mpi-basic.t \ t3001-mpi-personalities.t \ t4000-issues-test-driver.t \ @@ -176,6 +177,7 @@ check_SCRIPTS = \ t2008-althash.t \ t2009-hostlist.t \ t2100-aggregate.t \ + t2200-job-ingest.t \ t3000-mpi-basic.t \ t3001-mpi-personalities.t \ t4000-issues-test-driver.t \ diff --git a/t/t2200-job-ingest.t b/t/t2200-job-ingest.t new file mode 100755 index 000000000000..bd61254a7a1a --- /dev/null +++ b/t/t2200-job-ingest.t @@ -0,0 +1,93 @@ +#!/bin/sh + +test_description='Test flux job ingest service' + +. $(dirname $0)/sharness.sh + +if test "$TEST_LONG" = "t"; then + test_set_prereq LONGTEST +fi +if test -x ${FLUX_BUILD_DIR}/src/cmd/flux-jobspec-validate; then + test_set_prereq ENABLE_JOBSPEC +fi +if flux job --help 2>&1 | grep -q sign-type; then + test_set_prereq HAVE_FLUX_SECURITY + SUBMITBENCH_OPT_R="--reuse-signature" + SUBMITBENCH_OPT_NONE="--sign-type=none" +fi + +test_under_flux 4 job + +flux setattr log-stderr-level 1 + +JOBSPEC=${SHARNESS_TEST_SRCDIR}/jobspec +SUBMITBENCH="flux job submitbench $SUBMITBENCH_OPT_NONE" + +test_valid () +{ + local rc=0 + for job in $*; do + ${SUBMITBENCH} ${job} || rc=1 + done + return ${rc} +} + +test_invalid () +{ + local rc=0 + for job in $*; do + ${SUBMITBENCH} ${job} && rc=1 + done + return ${rc} +} + +test_expect_success 'job-ingest: can submit jobspec on stdin' ' + cat ${JOBSPEC}/valid/basic.yaml | ${SUBMITBENCH} - +' + +test_expect_success 'job-ingest: jobspec stored accurately in KVS' ' + jobid=$(${SUBMITBENCH} ${JOBSPEC}/valid/basic.yaml) && + kvsdir=$(flux job id --to=kvs-active $jobid) && + flux kvs get --raw ${kvsdir}.jobspec >jobspec.out && + test_cmp ${JOBSPEC}/valid/basic.yaml jobspec.out +' + +test_expect_success 'job-ingest: submitter userid stored in KVS' ' + myuserid=$(id -u) && + jobid=$(${SUBMITBENCH} ${JOBSPEC}/valid/basic.yaml) && + kvsdir=$(flux job id --to=kvs-active $jobid) && + jobuserid=$(flux kvs get --json ${kvsdir}.userid) && + test $jobuserid -eq $myuserid +' + +test_expect_success 'job-ingest: valid jobspecs accepted' ' + test_valid ${JOBSPEC}/valid/* +' + +test_expect_success ENABLE_JOBSPEC 'job-ingest: invalid jobs rejected' ' + test_invalid ${JOBSPEC}/invalid/* +' + +test_expect_success 'job-ingest: submit job 100 times' ' + ${SUBMITBENCH} -r 100 ${JOBSPEC}/valid/use_case_2.6.yaml +' + +test_expect_success 'job-ingest: submit job 100 times, reuse signature' ' + echo $SUBMITBENCH_OPT_R && + ${SUBMITBENCH} ${SUBMITBENCH_OPT_R} \ + -r 100 ${JOBSPEC}/valid/use_case_2.6.yaml +' + +test_expect_success HAVE_FLUX_SECURITY 'job-ingest: submit user != signed user fails' ' + ! FLUX_HANDLE_USERID=9999 ${SUBMITBENCH} \ + ${JOBSPEC}/valid/basic.yaml 2>baduser.out && + grep -q permitted baduser.out +' + +test_expect_success HAVE_FLUX_SECURITY 'job-ingest: non-owner mech=none fails' ' + ! FLUX_HANDLE_ROLEMASK=0x2 ${SUBMITBENCH} \ + ${JOBSPEC}/valid/basic.yaml 2>badrole.out && + grep -q permitted badrole.out +' + +test_done From e113ca22d8bfcfa6d7fa88f6f5fb5142e6cd6ad9 Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Wed, 13 Jun 2018 09:49:27 -0400 Subject: [PATCH 14/15] sharness: add flux-job test script --- t/Makefile.am | 2 + t/t2201-job-cmd.t | 149 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100755 t/t2201-job-cmd.t diff --git a/t/Makefile.am b/t/Makefile.am index a5a9c18a8947..0ca59b0dbeac 100644 --- a/t/Makefile.am +++ b/t/Makefile.am @@ -84,6 +84,7 @@ TESTS = \ t2009-hostlist.t \ t2100-aggregate.t \ t2200-job-ingest.t \ + t2201-job-cmd.t \ t3000-mpi-basic.t \ t3001-mpi-personalities.t \ t4000-issues-test-driver.t \ @@ -178,6 +179,7 @@ check_SCRIPTS = \ t2009-hostlist.t \ t2100-aggregate.t \ t2200-job-ingest.t \ + t2201-job-cmd.t \ t3000-mpi-basic.t \ t3001-mpi-personalities.t \ t4000-issues-test-driver.t \ diff --git a/t/t2201-job-cmd.t b/t/t2201-job-cmd.t new file mode 100755 index 000000000000..c40f2d4962ad --- /dev/null +++ b/t/t2201-job-cmd.t @@ -0,0 +1,149 @@ +#!/bin/sh + +test_description='Test flux job command' + +. $(dirname $0)/sharness.sh + +if test "$TEST_LONG" = "t"; then + test_set_prereq LONGTEST +fi +if flux job --help 2>&1 | grep -q sign-type; then + test_set_prereq HAVE_FLUX_SECURITY +fi + +JOBSPEC=${SHARNESS_TEST_SRCDIR}/jobspec + +# 2^64 - 1 +MAXJOBID_DEC=18446744073709551615 +MAXJOBID_KVS="job.active.ffff.ffff.ffff.ffff" +MAXJOBID_WORDS="natural-analyze-verbal--natural-analyze-verbal" + +MINJOBID_DEC=0 +MINJOBID_KVS="job.active.0000.0000.0000.0000" +MINJOBID_WORDS="academy-academy-academy--academy-academy-academy" + +test_under_flux 1 job + +flux setattr log-stderr-level 1 + +test_expect_success 'flux-job: unknown sub-command fails with usage message' ' + test_must_fail flux job wrongsubcmd 2>usage.out && + grep -q Usage: usage.out +' + +test_expect_success 'flux-job: missing sub-command fails with usage message' ' + test_must_fail flux job 2>usage2.out && + grep -q Usage: usage2.out +' + +test_expect_success 'flux-job: submitbench with no jobspec fails with usage' ' + test_must_fail flux job submitbench 2>usage3.out && + grep -q Usage: usage3.out +' + +test_expect_success 'flux-job: submitbench with nonexistent jobpsec fails' ' + test_must_fail flux job submitbench /noexist +' + +test_expect_success 'flux-job: submitbench with bad broker connection fails' ' + FLUX_URI=/wrong \ + test_must_fail flux job submitbench \ + --sign-type=none \ + ${JOBSPEC}/valid/basic.yaml +' + +test_expect_success HAVE_FLUX_SECURITY 'flux-job: submitbench with bad security config fails' ' + test_must_fail flux job submitbench \ + --sign-type=none \ + --security-config=/nonexist \ + ${JOBSPEC}/valid/basic.yaml +' + +test_expect_success HAVE_FLUX_SECURITY 'flux-job: submitbench with bad sign type fails' ' + test_must_fail flux job submitbench \ + --sign-type=notvalid \ + ${JOBSPEC}/valid/basic.yaml +' + +test_expect_success 'flux-job: id without from/to args is dec to dec' ' + jobid=$(flux job id 42) && + test "$jobid" = "42" +' + +test_expect_success 'flux-job: id from stdin works' ' + jobid=$(echo 42 | flux job id) && + test "$jobid" = "42" +' + +test_expect_success 'flux-job: id with invalid from/to arg fails' ' + test_must_fail flux job id --from=invalid 42 && + test_must_fail flux job id --to=invalid 42 +' + +test_expect_success 'flux-job: id --from=dec works' ' + jobid=$(flux job id --from=dec $MAXJOBID_DEC) && + test "$jobid" = "$MAXJOBID_DEC" && + jobid=$(flux job id --from=dec $MINJOBID_DEC) && + test "$jobid" = "$MINJOBID_DEC" +' + +test_expect_success 'flux-job: id --from=words works' ' + jobid=$(flux job id --from=words $MAXJOBID_WORDS) && + test "$jobid" = "$MAXJOBID_DEC" && + jobid=$(flux job id --from=words $MINJOBID_WORDS) && + test "$jobid" = "$MINJOBID_DEC" +' + +test_expect_success 'flux-job: id --from=kvs-active works' ' + jobid=$(flux job id --from=kvs-active $MAXJOBID_KVS) && + test "$jobid" = "$MAXJOBID_DEC" && + jobid=$(flux job id --from=kvs-active $MINJOBID_KVS) && + test "$jobid" = "$MINJOBID_DEC" +' + +test_expect_success 'flux-job: id --to=dec works' ' + jobid=$(flux job id --to=dec $MAXJOBID_DEC) && + test "$jobid" = "$MAXJOBID_DEC" && + jobid=$(flux job id --to=dec $MINJOBID_DEC) && + test "$jobid" = "$MINJOBID_DEC" +' + +test_expect_success 'flux-job: id --to=words works' ' + jobid=$(flux job id --to=words $MAXJOBID_DEC) && + test "$jobid" = "$MAXJOBID_WORDS" && + jobid=$(flux job id --to=words $MINJOBID_DEC) && + test "$jobid" = "$MINJOBID_WORDS" +' + +test_expect_success 'flux-job: id --to=kvs-active works' ' + jobid=$(flux job id --to=kvs-active $MAXJOBID_DEC) && + test "$jobid" = "$MAXJOBID_KVS" && + jobid=$(flux job id --to=kvs-active $MINJOBID_DEC) && + test "$jobid" = "$MINJOBID_KVS" +' + +test_expect_success 'flux-job: id --from=kvs-active fails on bad input' ' + test_must_fail flux job id --from=kvs-active badstring && + test_must_fail flux job id --from=kvs-active \ + job.active.0000.0000 && + test_must_fail flux job id --from=kvs-active \ + job.active.0000.0000.0000.000P +' + +test_expect_success 'flux-job: id --from=dec fails on bad input' ' + test_must_fail flux job id --from=dec 42plusbad && + test_must_fail flux job id --from=dec meep && + test_must_fail flux job id --from=dec 18446744073709551616 +' + +test_expect_success 'flux-job: id --from=words fails on bad input' ' + test_must_fail flux job id --from=words badwords +' + +test_expect_success 'flux-job: id works with spaces in input' ' + (echo "42"; echo "42") >despace.exp && + (echo "42 "; echo " 42") | flux job id >despace.out && + test_cmp despace.exp despace.out +' + +test_done From 8f405a3c253e398d393bf974fa1398e377e1c1ad Mon Sep 17 00:00:00 2001 From: Jim Garlick Date: Wed, 22 Aug 2018 14:32:21 -0700 Subject: [PATCH 15/15] travis-ci: build --with-flux-security Pull in flux-security-0.2.0 via the travis-dep-builder script, then add --with-flux-security to some builders in the travis build matrix. --- .travis.yml | 8 ++++---- src/test/travis-dep-builder.sh | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2d3d971b68c9..9f5faaeaa052 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,15 +9,15 @@ matrix: - compiler: clang env: LUA_VERSION=5.1 - compiler: gcc - env: LUA_VERSION=5.1 COVERAGE=t ARGS="--with-pmix --enable-caliper --enable-pylint" + env: LUA_VERSION=5.1 COVERAGE=t ARGS="--with-flux-security --with-pmix --enable-caliper --enable-pylint" - compiler: gcc env: LUA_VERSION=5.1 T_INSTALL=t - compiler: clang - env: LUA_VERSION=5.1 CPPCHECK=t ARGS="--with-pmix --enable-sanitizer" CC=clang-3.8 CXX=clang++-3.8 + env: LUA_VERSION=5.1 CPPCHECK=t ARGS="--with-flux-security --with-pmix --enable-sanitizer" CC=clang-3.8 CXX=clang++-3.8 - compiler: gcc - env: LUA_VERSION=5.2 CC=gcc-4.9 ARGS="--with-pmix" chain_lint=t + env: LUA_VERSION=5.2 CC=gcc-4.9 ARGS="--with-flux-security --with-pmix" chain_lint=t - compiler: clang - env: LUA_VERSION=5.2 ARGS="--with-pmix --enable-caliper" CC=clang-3.8 CXX=clang++-3.8 + env: LUA_VERSION=5.2 ARGS="--with-flux-security --with-pmix --enable-caliper" CC=clang-3.8 CXX=clang++-3.8 cache: directories: diff --git a/src/test/travis-dep-builder.sh b/src/test/travis-dep-builder.sh index 37d002603f3b..b034cbe887a7 100755 --- a/src/test/travis-dep-builder.sh +++ b/src/test/travis-dep-builder.sh @@ -17,6 +17,7 @@ http://downloads.sourceforge.net/ltp/lcov-1.10.tar.gz \ http://www.open-mpi.org/software/hwloc/v1.11/downloads/hwloc-1.11.1.tar.gz \ https://github.com/pmix/pmix/releases/download/v3.0.0/pmix-3.0.0.tar.gz \ http://www.digip.org/jansson/releases/jansson-2.9.tar.gz \ +https://github.com/flux-framework/flux-security/releases/download/v0.2.0/flux-security-0.2.0.tar.gz \ http://www.mpich.org/static/downloads/3.1.4/mpich-3.1.4.tar.gz \ https://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz \ https://ftp.gnu.org/gnu/automake/automake-1.15.tar.gz \