This patch creates a condor classad plugin that allows us to check quotas as a final step in the condor matching process.
In order to enable quotas, you will have to add the following to your /var/lib/condor/condor_local.config: CLASSAD_USER_LIBS = /usr/share/deltacloud-aggregator/classad_plugin/deltacloud_classad_plugin.so You must also set up the plugin correctly depending on how you plan to run the aggregator. The easiest thing to do is just use 'make rpms' and install the resulting rpms and run the whole thing installed. If you wish to run it from the src dir as most developers do, you will have to run configure with: ./configure --enable-local --prefix=/usr and then do a 'make install'. This will install the deltacloud_classad_plugin.so in /usr/share/deltacloud-aggregator/classad_plugin directory, but it will continue to use the database and model files from the local directory. Signed-off-by: Ian Main <[email protected]> --- Makefile.am | 4 + autogen.sh | 4 +- configure.ac | 14 ++- deltacloud-aggregator.spec.in | 17 +++- src/app/util/condormatic.rb | 4 + src/classad_plugin/Makefile.am | 12 ++ src/classad_plugin/classad_plugin.rb | 19 +++ src/classad_plugin/deltacloud_classad_plugin.cpp | 158 ++++++++++++++++++++++ 8 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 src/classad_plugin/Makefile.am create mode 100644 src/classad_plugin/classad_plugin.rb create mode 100644 src/classad_plugin/deltacloud_classad_plugin.cpp diff --git a/Makefile.am b/Makefile.am index 7984035..b9de310 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,6 +38,10 @@ RPMDIR = $$(rpm --eval '%{_rpmdir}') RPM_FLAGS = --define "deltacloud_cache_dir $(DELTACLOUD_CACHE_DIR)" RPM_FLAGS += $(if $(_deltacloud_dev),--define "extra_release .$(GIT_RELEASE)") +SUBDIRS = src/classad_plugin + +ACLOCAL_AMFLAGS = -I m4 + rpms: dist rpmbuild $(RPM_FLAGS) -ta $(distdir).tar.gz diff --git a/autogen.sh b/autogen.sh index 72f9114..bce073a 100755 --- a/autogen.sh +++ b/autogen.sh @@ -50,13 +50,15 @@ THEDIR=`pwd` echo "to pass any to it, please specify them on the $0 command line." fi + libtoolize --force aclocal # Run autoheader only if needed grep '^[[:blank:]]*AC_CONFIG_HEADERS' configure.ac >/dev/null && autoheader - automake --add-missing + autoheader autoconf + automake --add-missing ./configure "$@" ) diff --git a/configure.ac b/configure.ac index e11158b..1f1c212 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,21 @@ AC_INIT([deltacloud-aggregator], [0.0.2], [[email protected]]) AM_INIT_AUTOMAKE([-Wall -Werror foreign -Wno-portability tar-pax]) -AC_PROG_CC AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +AC_PROG_CXX +AC_PROG_CC +AC_PROG_LIBTOOL +AC_LANG([C++]) + +AC_ARG_ENABLE(local, + [ --enable-local Set this flag if you are planning to run the aggregator without installing it.], + [ DELTACLOUD_INSTALL_DIR=`pwd`/src ], [ DELTACLOUD_INSTALL_DIR=$pkgdatadir ]) +AC_SUBST(DELTACLOUD_INSTALL_DIR) # If using gcc and default CFLAGS, enable some warnings. test x"$ac_ct_CC:$CFLAGS" = 'xgcc:-g -O2' \ && CFLAGS="$CFLAGS -Wshadow -Wall -Werror" AC_CONFIG_FILES([Makefile deltacloud-aggregator.spec]) -AC_OUTPUT +AC_OUTPUT(src/classad_plugin/Makefile) diff --git a/deltacloud-aggregator.spec.in b/deltacloud-aggregator.spec.in index ca0db90..de45b34 100644 --- a/deltacloud-aggregator.spec.in +++ b/deltacloud-aggregator.spec.in @@ -15,7 +15,6 @@ URL: http://deltacloud.org # tar czvf deltacloud-aggregator-0.0.2.tar.gz deltacloud-aggregator-0.0.2 Source0: deltacloud-aggregator-%{version}.tar.gz BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) -BuildArch: noarch Requires: ruby >= 1.8.1 Requires: ruby(abi) = 1.8 @@ -29,6 +28,9 @@ Requires: postgresql-server Requires: ruby-postgres Requires: gnuplot >= 4.2.6 Requires: ruby-RMagick +Requires: classads >= 1.0 + +BuildRequires: classads-devel >= 1.0 %package daemons Summary: Deltacloud Aggregator daemons config @@ -60,9 +62,12 @@ Documentation and tests for the Deltacloud Aggregator %setup -q %build +%configure +make %install %{__rm} -rf %{buildroot} + %{__mkdir} -p %{buildroot} %{__mkdir} -p %{buildroot}%{app_root} %{__mkdir} -p %{buildroot}%{doc_root} @@ -79,6 +84,11 @@ Documentation and tests for the Deltacloud Aggregator # copy over all of the src directory... %{__cp} -R src/* %{buildroot}/%{app_root} +# Remove this directory as the make install below will install the correct files to this dir. +%{__rm} -rf %{buildroot}%{app_root}/classad_plugin + +%makeinstall + # move documentation to the correct place mv %{buildroot}/%{app_root}/doc %{buildroot}/%{app_root}/test %{buildroot}/%{doc_root} @@ -115,6 +125,11 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/rails.log %{__mkdir} %{buildroot}%{_localstatedir}/lib/%{name}/tmp %{__ln_s} %{_localstatedir}/lib/%{name}/tmp %{buildroot}%{app_root}/tmp +%{__rm} -rf %{buildroot}%{app_root}/tmp + +# Delete unused build stuff. +%{__rm} -rf %{buildroot}%{app_root}/classad_plugin/.libs +%{__rm} -rf %{buildroot}%{app_root}/classad_plugin/.deps %clean %{__rm} -rf %{buildroot} diff --git a/src/app/util/condormatic.rb b/src/app/util/condormatic.rb index d93a4b1..f6c2ba8 100644 --- a/src/app/util/condormatic.rb +++ b/src/app/util/condormatic.rb @@ -51,6 +51,9 @@ def condormatic_instance_create(task) requirements = "requirements = hardwareprofile == \"#{instance.hardware_profile.id}\" && image == \"#{instance.image.id}\"" requirements += " && realm == \"#{realm.name}\"" if realm != nil + # We may need to add some stuff to the provider classads like pool id, provider id etc. This is mostly just + # to test and make sure this works for now. + requirements += " && deltacloud_quota_check(\"#{job_name}\", other.cloud_account_id)" requirements += "\n" pipe.puts requirements @@ -198,6 +201,7 @@ def condormatic_classads_sync pipe.puts "provider_url=\"#{account.provider.url}\"" pipe.puts "username=\"#{account.username}\"" pipe.puts "password=\"#{account.password}\"" + pipe.puts "cloud_account_id=\"#{account.id}\"" pipe.close_write out = pipe.read diff --git a/src/classad_plugin/Makefile.am b/src/classad_plugin/Makefile.am new file mode 100644 index 0000000..9334f5f --- /dev/null +++ b/src/classad_plugin/Makefile.am @@ -0,0 +1,12 @@ +AM_CPPFLAGS=-I`ruby -e "require 'rbconfig' ; puts Config::CONFIG['archdir']"` \ + -DDELTACLOUD_INSTALL_DIR=\"$(DELTACLOUD_INSTALL_DIR)\" -DLOGFILE=\"/var/log/condor/deltacloud_condor_plugin.log\" + +classadplugindir=$(pkgdatadir)/classad_plugin + +classadplugin_LTLIBRARIES = deltacloud_classad_plugin.la + +deltacloud_classad_plugin_la_SOURCES = deltacloud_classad_plugin.cpp +deltacloud_classad_plugin_la_LIBADD = -lclassad -lruby +deltacloud_classad_plugin_la_LDFLAGS = -module -avoid-version -shared + +classadplugin_DATA = classad_plugin.rb diff --git a/src/classad_plugin/classad_plugin.rb b/src/classad_plugin/classad_plugin.rb new file mode 100644 index 0000000..fce7d2c --- /dev/null +++ b/src/classad_plugin/classad_plugin.rb @@ -0,0 +1,19 @@ + +require 'dutils' + +def classad_plugin(instance_key, account_id) + + # I left the puts in here because you can run condor_negotiator -f from the command + # line and it will print this stuff out. Very nice for debugging. + #puts "getting instance from key #{instance_key}" + instance = Instance.find(:first, :conditions => [ "condor_job_id = ?", instance_key ]) + #puts "getting cloud account from id #{account_id}" + cloud_account = CloudAccount.find(:first, :conditions => [ "id = ?", account_id ]) + + #puts "instance is: #{instance}, cloud account is #{cloud_account}" + + return false if instance.nil? + return false if cloud_account.nil? + #puts "checking quota.." + return Quota.can_start_instance?(instance, cloud_account) +end diff --git a/src/classad_plugin/deltacloud_classad_plugin.cpp b/src/classad_plugin/deltacloud_classad_plugin.cpp new file mode 100644 index 0000000..af35bc0 --- /dev/null +++ b/src/classad_plugin/deltacloud_classad_plugin.cpp @@ -0,0 +1,158 @@ +#include <ruby.h> +#include <stdio.h> + +#include <iostream> +#include <sstream> + +#include "classad/classad_distribution.h" +#include "classad/fnCall.h" + +using namespace std; +#ifdef WANT_CLASSAD_NAMESPACE +using namespace classad; +#endif + +void print_value(FILE *fp, Value val, const char *name) +{ + /* AFAICT the only way to get at the string in a 'Value' object is to use + * the C++ stream operator <<, so we set up a stringstream and write + * stuff we want into that.. + */ + std::stringstream sstr; + + sstr << name << " is " << val; + fprintf(fp, "%s\n", sstr.str().c_str()); +} + +/* + * Perform our quota check against the deltacloud aggregator database. + * This function expects: + * + * - Instance executable name as handed to condor so that we can map to the + * instance found in the database. + * - The username + * - The provider url so we can map back to the provider. + * - The realm key so we know what realm this is in. + * + * ... at least for now. Need to better analyze what is required to do all + * the quota matching but that's the idea. + */ +bool +deltacloud_quota_check(const char *name, const ArgumentList &arglist, + EvalState &state, Value &result) +{ + Value instance_key; + Value account_id; + FILE *fp; + char msg[1024]; + VALUE res; + bool val = false; + char *ruby_string; + std::stringstream method_args; + static int ruby_initialized = 0; + + result.SetBooleanValue(false); + + fp = fopen(LOGFILE, "a"); + + if (arglist.size() != 2) { + result.SetErrorValue(); + fprintf(fp, "Expected 2 arguments, saw %d\n", arglist.size()); + goto do_ret; + } + + if (!arglist[0]->Evaluate(state, instance_key)) { + result.SetErrorValue(); + fprintf(fp, "Could not evaluate argument 0 to instance key\n"); + goto do_ret; + } + if (!arglist[1]->Evaluate(state, account_id)) { + result.SetErrorValue(); + fprintf(fp, "Could not evaluate argument 1 to account_id\n"); + goto do_ret; + } + + if (instance_key.GetType() != Value::STRING_VALUE) { + result.SetErrorValue(); + fprintf(fp, "Instance type was not a string\n"); + goto do_ret; + } + //print_value(fp, instance_key, "instance_key"); + + if (account_id.GetType() != Value::STRING_VALUE) { + result.SetErrorValue(); + fprintf(fp, "Account ID type was not a string\n"); + goto do_ret; + } + //print_value(fp, account_id, "account_id"); + + if (!ruby_initialized) { + ruby_init(); + ruby_init_loadpath(); + } + + method_args << "'" << instance_key << "', " << account_id; + + asprintf(&ruby_string, + "$: << '%s/classad_plugin'\n" + "$: << '%s/dutils'\n" + "$: << '%s/models'\n" + "begin\n" + " require 'classad_plugin.rb'\n" + " classad_plugin(%s)\n" + "rescue Exception => ex\n" + " f = File.new('%s', 'a')\n" + " f.write \"Error running classad plugin: #{ex.message}\"\n" + " f.write ex.backtrace\n" + " f.close\n" + " false\n" + "end\n", + DELTACLOUD_INSTALL_DIR, + DELTACLOUD_INSTALL_DIR, + DELTACLOUD_INSTALL_DIR, + method_args.str().c_str(), + LOGFILE); + + fflush(fp); + + res = rb_eval_string(ruby_string); + free(ruby_string); + + if (res == Qtrue) { + result.SetBooleanValue(true); + val = true; + } else { + fprintf(fp, "Returned result from ruby code was %s\n", (res == Qtrue) ? "true" : "false"); + } + + do_ret: + fclose(fp); + + return val; +} + +/* + * Struct containing the names of the functions provided here and a pointer + * to the function that implements them. Third arg is an int flags that appears + * to be unused. + * + * This is found in fnCall.h + */ +static ClassAdFunctionMapping classad_functions[] = +{ + { "deltacloud_quota_check", (void *) deltacloud_quota_check, 0 }, + { "", NULL, 0 } +}; + +/* + * This is the 'Init' function that is called after the library is dlopen()'d. + * This just returns the struct defined above. + * + */ +extern "C" +{ + ClassAdFunctionMapping * + Init(void) { + return classad_functions; + } +} -- 1.7.1.1 _______________________________________________ deltacloud-devel mailing list [email protected] https://fedorahosted.org/mailman/listinfo/deltacloud-devel
