commit 57f831d3f2509a8a96d1b4e62b048128d4c25223
Author: Pete Zaitcev <zaitcev(a)yahoo.com>
Date: Thu Mar 31 14:13:03 2011 -0600
Add Condor Cloud back-end
The "Condor" is a toy back-end that Aeolus people use. This patch adds
a basic support for it, including a build-time test (t/registration).
The test also serves as documentation for now.
Makefile.am | 1 +
backend.c | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
dc-condor-image | 100 +++++++++++++++++++++++++++++++++++++
setup.c | 10 +++-
t/Makefile.am | 3 +-
t/registration | 97 ++++++++++++++++++++++++++++++++++++
6 files changed, 358 insertions(+), 2 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index bda9d64..a0a3c52 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -24,6 +24,7 @@ ACLOCAL_AMFLAGS = -I m4
bin_PROGRAMS = iwhd dc-rhev-image
EXTRA_DIST = \
+ dc-condor-image \
dc-register-image \
iwhd.init.in \
qparser.h \
diff --git a/backend.c b/backend.c
index 8e4a9cb..101f520 100644
--- a/backend.c
+++ b/backend.c
@@ -1558,6 +1558,144 @@ cleanup:
return rc;
}
+static int
+fs_condor_register (my_state *ms, const provider_t *prov, const char *next,
+ Hash_table *args)
+{
+ char *kernel = kv_hash_lookup(args,"kernel");
+ char *ramdisk = kv_hash_lookup(args,"ramdisk");
+ const char *nfs_dir;
+ const char *argv[12];
+ unsigned int argc = 0;
+ pid_t pid;
+ int organ[2];
+ int wstat;
+ FILE *fp;
+ char buf[LINE_SIZE];
+ char *s;
+ int rc = MHD_HTTP_BAD_REQUEST;
+ char ami_id_buf[64];
+ regmatch_t match[2];
+
+ strcpy(ami_id_buf, "none");
+
+ if (!regex_ok) {
+ return MHD_HTTP_BAD_REQUEST;
+ }
+
+ if (next) {
+ DPRINTF("Falcon register with next!=NULL\n");
+ return MHD_HTTP_BAD_REQUEST;
+ }
+
+ DPRINTF("*** register %s/%s via %s\n",
+ ms->bucket, ms->key, prov->name);
+ if (kernel) {
+ DPRINTF(" (using kernel %s)\n",kernel);
+ }
+ if (ramdisk) {
+ DPRINTF(" (using ramdisk %s)\n",ramdisk);
+ }
+
+ /*
+ * The discrepancy between POST argument key "nfs-dir" and JSON
+ * key "nfsdir" is unfortunate, but nfs-dir was there first.
+ */
+ nfs_dir = kv_hash_lookup(args,"nfs-dir");
+ if (!nfs_dir) {
+ nfs_dir = get_provider_value(prov,"nfsdir");
+ if (!nfs_dir) {
+ error (0, 0,
+ _("missing target directory (nfs-dir,nfsdir)"));
+ goto cleanup;
+ }
+ }
+
+ /*
+ * This is the point where we go from validation to execution. If we
+ * were double-forking so this could all be asynchronous, or for that
+ * matter to return an early 100-continue, this would probably be the
+ * place to do it. Even without that, we set the ami-id here so that
+ * the caller can know things are actually in progress.
+ */
+ sprintf(ami_id_buf,"pending %lld",(long long)time(NULL));
+ DPRINTF("temporary ami-id = \"%s\"\n",ami_id_buf);
+ (void)meta_set_value(ms->bucket,ms->key,"ami-id",ami_id_buf);
+ rc = MHD_HTTP_INTERNAL_SERVER_ERROR;
+
+ const char *cmd = "dc-condor-image";
+ argv[argc++] = cmd;
+ argv[argc++] = ms->bucket;
+ argv[argc++] = ms->key;
+ argv[argc++] = nfs_dir;
+ argv[argc++] = kernel ? kernel : "_default_";
+ argv[argc++] = ramdisk ? ramdisk : "_default_";
+ argv[argc] = NULL;
+ assert (argc < ARRAY_CARDINALITY (argv));
+
+ if (pipe(organ) < 0) {
+ error (0, errno, _("pipe creation failed"));
+ goto cleanup;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ error (0, errno, _("fork failed"));
+ close(organ[0]);
+ close(organ[1]);
+ goto cleanup;
+ }
+
+ if (pid == 0) {
+ (void)dup2(organ[1],STDOUT_FILENO);
+ (void)dup2(organ[1],STDERR_FILENO);
+ execvp(cmd, (char* const*)argv);
+ error (EXIT_FAILURE, errno, _("failed to run command %s"), cmd);
+ }
+
+ close(organ[1]);
+ fp = fdopen(organ[0],"r");
+ if (!fp) {
+ error (0, 0, _("could not open parent pipe stream"));
+ close(organ[0]);
+ goto cleanup;
+ }
+ while (fgets(buf,sizeof(buf)-1,fp)) {
+ if ((s = strchr(buf, '\n')) != NULL)
+ *s = '\0';
+ if (regexec(&s3_success_pat,buf,2,match,0) == 0) {
+ buf[match[1].rm_eo] = '\0';
+ DPRINTF("found AMI ID: %s\n",buf+match[1].rm_so);
+ sprintf(ami_id_buf,"OK %.60s",buf+match[1].rm_so);
+ rc = MHD_HTTP_OK;
+ }
+ else {
+ DPRINTF("ignoring line: <%s>\n",buf);
+ }
+ }
+ fclose(fp);
+
+ DPRINTF("waiting for child...\n");
+ if (waitpid(pid,&wstat,0) < 0) {
+ error (0, errno, _("waitpid failed"));
+ goto cleanup;
+ }
+ if (!WIFEXITED(wstat)) {
+ error (0, 0, _("%s is killed (status 0x%x)"), cmd, wstat);
+ goto cleanup;
+ }
+ if (WEXITSTATUS(wstat)) {
+ error (0, 0, _("%s exited with code %d"),
+ cmd, WEXITSTATUS(wstat));
+ goto cleanup;
+ }
+ DPRINTF("...child exited\n");
+
+cleanup:
+ (void)meta_set_value(ms->bucket,ms->key,"ami-id",ami_id_buf);
+ return rc;
+}
+
/***** Function tables. ****/
backend_func_tbl bad_func_tbl = {
@@ -1625,3 +1763,14 @@ backend_func_tbl fs_rhevm_func_tbl = {
fs_bcreate,
fs_rhevm_register,
};
+
+backend_func_tbl fs_condor_func_tbl = {
+ "FS-FALCON",
+ fs_init,
+ fs_get_child,
+ fs_put_child,
+ bad_cache_child,
+ fs_delete,
+ fs_bcreate,
+ fs_condor_register,
+};
diff --git a/dc-condor-image b/dc-condor-image
new file mode 100755
index 0000000..4f91435
--- /dev/null
+++ b/dc-condor-image
@@ -0,0 +1,100 @@
+#!/bin/bash
+
+# Copyright (C) 2010-2011 Red Hat, Inc.
+
+# 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 3 of the License, or
+# (at your option) any later version.
+
+# This program 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
+# 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, see <
http://www.gnu.org/licenses/>.
+
+ME=$(expr "./$0" : '.*/\(.*\)$')
+warn () { printf '%s\n' "$@" >&2; }
+die () { warn "$ME: $@"; exit 1; }
+
+# spidesc: Spit the XML at $1/$2.xml.
+#
+# This is the same XML spec that virsh uses for "dumpxml" and
"define",
+# but it cannot be fed straight to virsh on purpose. A tool has to load it,
+# then fill out things like network, RAM, clock type, and OS features.
+spitdesc () {
+ tgtdir=$1; shift
+ object=$1; shift
+ export object
+
+ cat <<EOF > $tgtdir/$object.xml
+<domain type='kvm'>
+ <name>${object}</name>
+ <uuid>00000000-0000-0000-0000-000000000000</uuid>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <devices>
+ <disk type='file' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <source file='${object}'/>
+ <target dev='vda' bus='virtio'/>
+ </disk>
+ </devices>
+</domain>
+EOF
+ # Should we print something here if $?!=0, or expect cat to complain?
+ return $?
+}
+
+# main()
+
+# $bucket is iwhd bucket.
+bucket=$1; shift
+# $object is iwhd's image name, which also serves as ID.
+# So, if $object=foo.img, then we end uploading foo.img.xml.
+# The foo.img itself is an uncompressed root filesystem, a device image.
+# Finally, $object is in local directory $bucket/ (as fs back-end works).
+object=$1; shift
+# $nfsdir is the target directory, not including staging/.
+# It has to be absolute, because we rely on $bucket/$object being enough to
+# open the source and that's only possible if $pwd is the top of fs back-end.
+nfsdir=$1; shift
+
+test -z "$bucket" && die "No bucket"
+test -z "$object" && die "No object"
+test -z "$nfsdir" && die "No nfsdir"
+test -d "$nfsdir" || die "Not a directory: $nfsdir"
+
+#
+# We require staging/ to be pre-made, because it's a useful signifier
+# of correct target, in case of a configuration error in iwhd.
+#
+test -d "$nfsdir/staging" \
+ || die "Required directory is missing: $nfsdir/staging"
+
+#
+# The API defines staging/ for a good reason: if anything goes wrong,
+# a clean-up process can collect pieces even if we crash. So, create temp
+# in staging/, but be polite and set a trap anyway.
+#
+tmpdir=
+trap "rm -rf $tmpdir" 0
+tmpdir=$(mktemp -d -p $nfsdir/staging) || exit 1
+for sig_ in 1 2 3 13 15; do
+ eval "trap 'exit $(expr $sig_ + 128)' $sig_"
+done
+
+cp "$bucket/$object" "$tmpdir/$object" || exit 1
+spitdesc $tmpdir $object || exit 1
+
+# great, we didn't run out of disk space, now rename and declare victory.
+mv $tmpdir/$object $tmpdir/$object.xml "$nfsdir"
+
+# Spit the ID like EC2. Thus far it's always the same as $object, but
+# officially it does not have to be.
+echo "IMAGE $object"
+exit 0
diff --git a/setup.c b/setup.c
index 5e32387..8a46977 100644
--- a/setup.c
+++ b/setup.c
@@ -67,6 +67,7 @@ extern backend_func_tbl curl_func_tbl;
extern backend_func_tbl cf_func_tbl;
extern backend_func_tbl fs_func_tbl;
extern backend_func_tbl fs_rhevm_func_tbl;
+extern backend_func_tbl fs_condor_func_tbl;
static json_t *config = NULL;
static Hash_table *prov_hash = NULL;
@@ -213,7 +214,9 @@ json_validate_server (unsigned int i)
else if (!strcasecmp(type,"http")) {
needs = NEED_SERVER;
}
- else if (!strcasecmp(type,"fs") || !strcasecmp(type,"fs-rhev-m")) {
+ else if (!strcasecmp(type,"fs") ||
+ !strcasecmp(type,"fs-rhev-m") ||
+ !strcasecmp(type,"fs-condor")) {
needs = NEED_PATH;
}
else {
@@ -334,6 +337,9 @@ convert_provider (int i, provider_t *out)
else if (!strcasecmp(out->type,"fs-rhev-m")) {
out->func_tbl = &fs_rhevm_func_tbl;
}
+ else if (!strcasecmp(out->type,"fs-condor")) {
+ out->func_tbl = &fs_condor_func_tbl;
+ }
else {
out->func_tbl = &bad_func_tbl;
}
@@ -436,6 +442,8 @@ add_provider (Hash_table *h)
prov->func_tbl = &fs_func_tbl;
else if (!strcasecmp(prov->type,"fs-rhev-m"))
prov->func_tbl = &fs_rhevm_func_tbl;
+ else if (!strcasecmp(prov->type,"fs-condor"))
+ prov->func_tbl = &fs_condor_func_tbl;
else
prov->func_tbl = &bad_func_tbl;
diff --git a/t/Makefile.am b/t/Makefile.am
index a671c05..d7e3fbf 100644
--- a/t/Makefile.am
+++ b/t/Makefile.am
@@ -19,7 +19,8 @@ TESTS = \
basic \
provider \
replication \
- auto
+ auto \
+ registration
lock_dir = $(abs_builddir)/lock-dir
clean-local:
diff --git a/t/registration b/t/registration
new file mode 100644
index 0000000..c5407a3
--- /dev/null
+++ b/t/registration
@@ -0,0 +1,97 @@
+#!/bin/sh
+# Test registration functionality.
+# (currently Falcon only)
+
+. "${srcdir=.}/init.sh"; path_prepend_ ..
+
+mkdir -p reg/mongod reg/_fs reg/condor/staging \
+ || framework_failure_ mkdir failed
+
+port=$(get_port 9095 $lock_dir/i-) || fail_ "failed to get iwhd port"
+m_port=$(get_port $mongo_base_port $lock_dir/m-) \
+ || fail_ "failed to get mongodb port"
+
+mongod --port $m_port --pidfilepath reg/mongod/pid --dbpath reg/mongod \
+ > reg/mongod.log 2>&1 &
+mongo_pid=$!
+cleanup_() { kill -9 $mongo_pid; }
+
+# Wait for up to 12 seconds for mongod to begin listening.
+wait_for .1 120 'mongo localhost:$m_port < /dev/null' \
+ || framework_failure_ mongod failed to start
+
+# Nobody sanity-checks nfsdir for being absolute pathname, but as it happens
+# iwhd runs dc-condor-image inside the fs back-end top directory, so
+# we must use an absolute path anyway.
+cat > iwhd_reg.conf << EOF
+[
+ {
+ "name": "main",
+ "type": "fs-condor",
+ "path": "reg/_fs",
+ "nfsdir": "$(pwd)/reg/condor"
+ }
+]
+EOF
+
+PATH=$PATH:${abs_top_srcdir} iwhd -v -p $port -c iwhd_reg.conf \
+ -d localhost:$m_port &
+iwhd_pid=$!
+cleanup_() { kill -9 $mongo_pid; kill $iwhd_pid; }
+
+wait_for .1 50 "curl -s
http://localhost:$port" \
+ || framework_failure_ iwhd failed to start
+
+# Create an empty bucket.
+curl -X PUT
http://localhost:$port/buk || fail=1
+# Create a file in that bucket.
+dd if=/dev/zero bs=8k count=1 2>/dev/null | \
+ curl -T -
http://localhost:$port/buk/test_image || fail=1
+
+# Encoding the image as a here document is not future-proof in that it's
+# entirely legal for the registration script to format XML in whatever way.
+# However, this test works for us as long as the dc-condor-image also
+# generates the XML from a similar literal (instead of, say, calling xslt).
+cat <<EOF > reg/test_image.known.xml || framework_failure_
+<domain type='kvm'>
+ <name>test_image</name>
+ <uuid>00000000-0000-0000-0000-000000000000</uuid>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <devices>
+ <disk type='file' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <source file='test_image'/>
+ <target dev='vda' bus='virtio'/>
+ </disk>
+ </devices>
+</domain>
+EOF
+
+curl -d op=register -d site=main
http://localhost:$port/buk/test_image
+# This location (condor/${ID}.xml) is future-proof as a part of Falcon API.
+compare reg/condor/test_image.xml reg/test_image.known.xml || fail=1
+# BTW, check if we saved the image, too. It's full of zeroes though...
+[ -s reg/condor/test_image ] || fail=1
+
+# One poorly understood feature of curl is that it actually exits with zero
+# upon an HTTP server error such as our favourite 500. The -f flag suppresses
+# the output to stderr _and_ enables error exit code. This is crazy, insane.
+# But it's life. We run one final curl with -f, in case iwhd spams 500 at us.
+curl -f
http://localhost:$port/buk/test_image/ami-id > reg/ami-id || fail=1
+read regstatus regid < reg/ami-id
+# There may be conditions when registration failure is not detected, but
+# produces a diagnostic message inside the ami-id attribute (e.g. "pending").
+if [ "$regstatus" != "OK" ]; then
+ echo "Registration error:" $(cat reg/ami-id) >&2
+ fail=1
+fi
+# Current dc-condor-image returns the name that we sent as an ID. Verify that.
+if [ "$regid" != "test_image" ]; then
+ echo "Registration ID mismatch:" $(cat reg/ami-id) >&2
+ fail=1
+fi
+
+Exit $fail