[patch iwhd 2/2] Add Condor Cloud back-end

Pete Zaitcev zaitcev at redhat.com
Thu Mar 31 00:25:19 UTC 2011


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(-)

commit 968d44fa0c88dd819c035f1b29cb32b8bad88ce2
Author: Pete Zaitcev <zaitcev at yahoo.com>
Date:   Wed Mar 30 18:09:48 2011 -0600

    Add Condor back-end.

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 6ffdfc8..2e7352b 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
 CONFIG_CLEAN_FILES = lock-dir
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


More information about the iwhd-devel mailing list