[PATCH 2/2] rename startup to agent to match the name change from the fedora packaging updates

Dan Radez dradez at redhat.com
Tue Dec 6 18:57:18 UTC 2011


---
 .gitignore                               |    2 +-
 README                                   |    2 +-
 agent/COPYING                            |  158 ++++
 agent/Makefile                           |   94 ++
 agent/aeolus-audrey-agent.spec.in        |  100 +++
 agent/audrey_agent.in.py                 | 1353 ++++++++++++++++++++++++++++++
 agent/test_audrey_startup.py             |  692 +++++++++++++++
 audrey_start/COPYING                     |  158 ----
 audrey_start/Makefile                    |   94 --
 audrey_start/aeolus-audrey-agent.spec.in |  100 ---
 audrey_start/audrey_startup.in.py        | 1353 ------------------------------
 audrey_start/test_audrey_startup.py      |  692 ---------------
 12 files changed, 2399 insertions(+), 2399 deletions(-)
 create mode 100644 agent/COPYING
 create mode 100644 agent/Makefile
 create mode 100644 agent/aeolus-audrey-agent.spec.in
 create mode 100755 agent/audrey_agent.in.py
 create mode 100644 agent/test_audrey_startup.py
 delete mode 100644 audrey_start/COPYING
 delete mode 100644 audrey_start/Makefile
 delete mode 100644 audrey_start/aeolus-audrey-agent.spec.in
 delete mode 100755 audrey_start/audrey_startup.in.py
 delete mode 100644 audrey_start/test_audrey_startup.py

diff --git a/.gitignore b/.gitignore
index f8f7101..a566b7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 *.swp
 configserver/pkg/*
-audrey_start/pkg/*
+agent/pkg/*
 job*.cnd
 *-*-*-*-*
 *.pyc
diff --git a/README b/README
index e0854d4..90da2b7 100644
--- a/README
+++ b/README
@@ -27,7 +27,7 @@
     Required to build: help2man
 
     Summary of how to build the software: 
-    % cd <repo>/audrey_start
+    % cd <repo>/agent
     % make rpms
     % cd <repo>/configserver
     % rake rpm
diff --git a/agent/COPYING b/agent/COPYING
new file mode 100644
index 0000000..35907a1
--- /dev/null
+++ b/agent/COPYING
@@ -0,0 +1,158 @@
+Apache License
+
+Version 2.0, January 2004
+
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction,
+and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the
+copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other
+entities that control, are controlled by, or are under common control with
+that entity. For the purposes of this definition, "control" means (i) the
+power, direct or indirect, to cause the direction or management of such
+entity, whether by contract or otherwise, or (ii) ownership of fifty percent
+(50%) or more of the outstanding shares, or (iii) beneficial ownership
+of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising permissions
+granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation
+or translation of a Source form, including but not limited to compiled
+object code, generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form,
+made available under the License, as indicated by a copyright notice that
+is included in or attached to the work (an example is provided in the Appendix
+below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form,
+that is based on (or derived from) the Work and for which the editorial
+revisions, annotations, elaborations, or other modifications represent,
+as a whole, an original work of authorship. For the purposes of this License,
+Derivative Works shall not include works that remain separable from, or
+merely link (or bind by name) to the interfaces of, the Work and Derivative
+Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original
+version of the Work and any modifications or additions to that Work or
+Derivative Works thereof, that is intentionally submitted to Licensor for
+inclusion in the Work by the copyright owner or by an individual or Legal
+Entity authorized to submit on behalf of the copyright owner. For the purposes
+of this definition, "submitted" means any form of electronic, verbal, or
+written communication sent to the Licensor or its representatives, including
+but not limited to communication on electronic mailing lists, source code
+control systems, and issue tracking systems that are managed by, or on
+behalf of, the Licensor for the purpose of discussing and improving the
+Work, but excluding communication that is conspicuously marked or otherwise
+designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on
+behalf of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of this
+License, each Contributor hereby grants to You a perpetual, worldwide,
+non-exclusive, no-charge, royalty-free, irrevocable copyright license to
+reproduce, prepare Derivative Works of, publicly display, publicly perform,
+sublicense, and distribute the Work and such Derivative Works in Source
+or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of this
+License, each Contributor hereby grants to You a perpetual, worldwide,
+non-exclusive, no-charge, royalty-free, irrevocable (except as stated in
+this section) patent license to make, have made, use, offer to sell, sell,
+import, and otherwise transfer the Work, where such license applies only
+to those patent claims licensable by such Contributor that are necessarily
+infringed by their Contribution(s) alone or by combination of their Contribution(s)
+with the Work to which such Contribution(s) was submitted. If You institute
+patent litigation against any entity (including a cross-claim or counterclaim
+in a lawsuit) alleging that the Work or a Contribution incorporated within
+the Work constitutes direct or contributory patent infringement, then any
+patent licenses granted to You under this License for that Work shall terminate
+as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the Work
+or Derivative Works thereof in any medium, with or without modifications, and
+in Source or Object form, provided that You meet the following conditions:
+
+1.    You must give any other recipients of the Work or Derivative Works
+a copy of this License; and
+2.    You must cause any modified files to carry prominent notices stating
+that You changed the files; and
+3.    You must retain, in the Source form of any Derivative Works that
+You distribute, all copyright, patent, trademark, and attribution notices
+from the Source form of the Work, excluding those notices that do not pertain
+to any part of the Derivative Works; and
+4.    If the Work includes a "NOTICE" text file as part of its distribution,
+then any Derivative Works that You distribute must include a readable copy
+of the attribution notices contained within such NOTICE file, excluding
+those notices that do not pertain to any part of the Derivative Works,
+in at least one of the following places: within a NOTICE text file distributed
+as part of the Derivative Works; within the Source form or documentation,
+if provided along with the Derivative Works; or, within a display generated
+by the Derivative Works, if and wherever such third-party notices normally
+appear. The contents of the NOTICE file are for informational purposes
+only and do not modify the License. You may add Your own attribution notices
+within Derivative Works that You distribute, alongside or as an addendum
+to the NOTICE text from the Work, provided that such additional attribution
+notices cannot be construed as modifying the License. You may add Your
+own copyright statement to Your modifications and may provide additional
+or different license terms and conditions for use, reproduction, or distribution
+of Your modifications, or for any such Derivative Works as a whole, provided
+Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+any Contribution intentionally submitted for inclusion in the Work by You
+to the Licensor shall be under the terms and conditions of this License,
+without any additional terms or conditions. Notwithstanding the above,
+nothing herein shall supersede or modify the terms of any separate license
+agreement you may have executed with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+names, trademarks, service marks, or product names of the Licensor, except
+as required for reasonable and customary use in describing the origin of
+the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable
+law or agreed to in writing, Licensor provides the Work (and each Contributor
+provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied, including, without limitation,
+any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY,
+or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining
+the appropriateness of using or redistributing the Work and assume any
+risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory, whether
+in tort (including negligence), contract, or otherwise, unless required
+by applicable law (such as deliberate and grossly negligent acts) or agreed
+to in writing, shall any Contributor be liable to You for damages, including
+any direct, indirect, special, incidental, or consequential damages of
+any character arising as a result of this License or out of the use or
+inability to use the Work (including but not limited to damages for loss
+of goodwill, work stoppage, computer failure or malfunction, or any and
+all other commercial damages or losses), even if such Contributor has been
+advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing the
+Work or Derivative Works thereof, You may choose to offer, and charge a
+fee for, acceptance of support, warranty, indemnity, or other liability
+obligations and/or rights consistent with this License. However, in accepting
+such obligations, You may act only on Your own behalf and on Your sole
+responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any
+liability incurred by, or claims asserted against, such Contributor by
+reason of your accepting any such warranty or additional liability.
diff --git a/agent/Makefile b/agent/Makefile
new file mode 100644
index 0000000..537ab20
--- /dev/null
+++ b/agent/Makefile
@@ -0,0 +1,94 @@
+#
+#   Copyright [2011] [Red Hat, Inc.]
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+
+AUDREY_STARTUP_CACHE_DIR	?= $(HOME)/audrey-agent-cache
+
+VERSION = 0.4.2
+
+# For Release: 0..., set _audrey-agent_dev=1 so that we get extra_release.GIT-
+# annotated rpm version strings.
+_audrey-agent_dev =  $(shell grep -q '^[[:space:]]*Release:[[:space:]]*0' \
+   aeolus-audrey-agent.spec.in && echo 1 || :)
+
+# use $(shell...) here to collect the git head and date *once* per make target.
+# that ensures that if multiple actions happen in the same target (like the
+# multiple RPM builds in the rpms target), they all use the same date
+git_head	= $(shell git log -1 --pretty=format:%h)
+date		= $(shell date --utc +%Y%m%d%H%M%S)
+GIT_RELEASE	= $(date)git$(git_head)
+RPMDIR		= $$(rpm --eval '%{_rpmdir}')
+RELEASE		= $(shell grep ^Release: aeolus-audrey-agent.spec.in | \
+			sed -e 's/Release:[ ,	]*//' | sed -e 's/%.*//')
+# RPM_FLAGS	= --define "audrey-agent_cache_dir $(AUDREY_STARTUP_CACHE_DIR)"
+# RPM_FLAGS	+= $(if $(_audrey-agent_dev),--define "extra_release .$(GIT_RELEASE)")
+man_section = 8
+
+# Create the dist.
+#    Run the audrey_agent.py audrey.$(man_section) then:
+#        Set the version numbers in aeolus-audrey-agent.spec and audrey_agent.py
+#        Copy the desired bits to the dist directory.
+#        tar up the contents of the dist directory.
+dist: audrey_agent.py audrey.$(man_section)
+	rm -rf dist
+	sed -e 's/@VERSION@/$(VERSION)/' aeolus-audrey-agent.spec.in \
+	  > aeolus-audrey-agent.spec
+	mkdir -p dist/aeolus-audrey-agent-$(VERSION)
+	cp -a aeolus-audrey-agent.spec aeolus-audrey-agent.spec.in \
+		COPYING Makefile audrey_agent.in.py \
+		audrey.$(man_section) \
+		test_audrey_agent.py dist/aeolus-audrey-agent-$(VERSION)
+	tar -C dist -zcvf aeolus-audrey-agent-$(VERSION).tar.gz aeolus-audrey-agent-$(VERSION)
+
+# cp audrey_agent.in.py to audrey_agent.py
+audrey_agent.py: audrey_agent.in.py
+	sed -e 's/@VERSION@/$(VERSION)/' $< > $@-t
+	chmod a+x,a-w $@-t
+	mv $@-t $@
+
+# cp audrey_starup.py to audrey.
+audrey: audrey_agent.py
+	cp $< $@
+
+# Generate the man page using help2man from the --help text output from the Audrey agent.
+#    Run the audrey target before geneeerating the man page.
+audrey.$(man_section): audrey
+	help2man --name='Aeolus agent' --section $(man_section) \
+	    --no-info ./$< > $@-t
+	mv $@-t $@
+
+# Run the automated tests
+#    Run the audrey_agent.py target before running the automated tests.
+check: audrey_agent.py
+	python test_audrey_agent.py
+
+# Build the rpms
+#    Run the check target followed by the dist target before building the rpms.
+rpms: check dist
+	rpmbuild -ta aeolus-audrey-agent-$(VERSION).tar.gz
+
+# Build the srpms
+srpms: check dist
+	rpmbuild -ts aeolus-audrey-agent-$(VERSION).tar.gz
+
+# Clean up files generated by this Makefile
+clean:
+	rm -rf dist aeolus-audrey-agent-$(VERSION).tar.gz \
+	aeolus-audrey-agent.spec audrey.$(man_section) audrey \
+	audrey.log audrey_agent.py audrey_agent.pyc \
+	test_toolinglog test_toolinguser \
+	toolinglog toolinguser
+
+.PHONY: dist check rpms srpms
diff --git a/agent/aeolus-audrey-agent.spec.in b/agent/aeolus-audrey-agent.spec.in
new file mode 100644
index 0000000..c6dc369
--- /dev/null
+++ b/agent/aeolus-audrey-agent.spec.in
@@ -0,0 +1,100 @@
+#
+#   Copyright [2011] [Red Hat, Inc.]
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+Name:       aeolus-audrey-agent
+Version:    @VERSION@
+Release:    12%{?dist}
+Summary:    The Aeolus Audrey Startup Agent
+
+Group:      Applications/System
+License:    ASL 2.0
+URL:        http://aeolusproject.org
+Source0:    http://joev.fedorapeople.org/audrey-agent/aeolus-audrey-agent-%{version}.tar.gz
+
+# All of these are required for building since during the build
+# process "audrey --help" is executed to generate the man page.
+BuildRequires: help2man
+BuildRequires: facter python-httplib2 python-oauth2
+%if (0%{?fedora} <= 14 || 0%{?rhel} <= 6)
+BuildRequires: python-argparse
+%endif
+
+Requires:   facter python-httplib2 python-oauth2
+Requires:   python-argparse
+%if (0%{?fedora} <= 14 || 0%{?rhel} <= 6)
+Requires:   python-argparse
+%endif
+
+BuildArch:  noarch
+
+%description
+The Aeolus Audrey Startup Agent, a script which runs on a booting
+cloud instance to retrieve configuration data from the Aeolus
+Config Server.
+
+%prep
+%setup -q
+
+%build
+make %{?_smp_mflags}
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}%{_bindir}
+mkdir -p %{buildroot}%{_mandir}/man8
+
+# copy over the audrey agent bits
+install audrey_agent.py %{buildroot}%{_bindir}/audrey
+cp audrey.8 %{buildroot}%{_mandir}/man8
+
+%files
+%{_bindir}/audrey
+%{_mandir}/man8/audrey.8*
+%doc COPYING
+
+%changelog
+* Thu Nov 18 2011 Joe VLcek <jvlcek at redhat.com> 0.4.0-12
+- Fixed Auto help file generation induced build failure
+* Thu Nov 17 2011 Joe VLcek <jvlcek at redhat.com> 0.4.0-11
+- Remove Auto help file generation as it is currently breakin the build
+* Thu Nov 17 2011 Joe VLcek <jvlcek at redhat.com> 0.4.0-10
+- Fix bz754769 fix user data parsing and log level
+* Tue Nov 09 2011 Joe VLcek <jvlcek at redhat.com> 0.4.0-9
+- Update the licensing information
+* Tue Nov 08 2011 Joe VLcek <jvlcek at redhat.com> 0.4.0-8
+- Add man page generation and address some packaging review feedback
+* Tue Nov 08 2011 Greg Blomquist <gblomqui at redhat.com> 0.4.0-7
+- Fixing changelog history
+* Mon Nov 07 2011 Joe VLcek <jvlcek at redhat.com> 0.3.1-7
+- Address Packaging for Fedora review feedback
+* Fri Nov 05 2011 Joe VLcek <jvlcek at redhat.com> 0.3.1-6
+- Packaging for Fedora
+* Wed Nov 02 2011 Greg Blomquist <gblomqui at redhat.com> 0.4.0-6
+- Handles base64 encoded and decoded data in user data
+* Wed Nov 02 2011 Greg Blomquist <gblomqui at redhat.com> 0.4.0-3
+- Fix for audrey.log location
+* Thu Oct 27 2011 Greg Blomquist <gblomqui at redhat.com> 0.4.0-2
+- Radez doubled unit test coverage
+* Wed Oct 26 2011 Greg Blomquist <gblomqui at redhat.com> 0.4.0-1
+- Now using oauth to authenticate with Config Server
+- Parsing a new user data format that is versioned
+* Thu Oct 20 2011 Dan Radez <dradez at redhat.com> 0.3.1-3
+- adding requires for rpm installations
+* Tue Oct 11 2011 Joe VLcek <joev at redhat.com> 0.3.1-2
+- RHEVm user data injection base64 encoded.
+* Fri Sep 30 2011 Joe VLcek <joev at redhat.com> 0.3.1-1
+- RHEVm user data injection.
+* Wed May 18 2011 Joe VLcek <joev at redhat.com> 0.0.1-1
+- Initial build.
diff --git a/agent/audrey_agent.in.py b/agent/audrey_agent.in.py
new file mode 100755
index 0000000..f59ba1e
--- /dev/null
+++ b/agent/audrey_agent.in.py
@@ -0,0 +1,1353 @@
+#! /usr/bin/env python
+'''
+*
+*   Copyright [2011] [Red Hat, Inc.]
+*
+*   Licensed under the Apache License, Version 2.0 (the "License");
+*   you may not use this file except in compliance with the License.
+*   You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+*   Unless required by applicable law or agreed to in writing, software
+*   distributed under the License is distributed on an "AS IS" BASIS,
+*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*   See the License for the specific language governing permissions and
+*  limitations under the License.
+*
+'''
+
+'''
+Audrey Startup (AS)
+
+Note: The source file is named audrey_agent.in.py The make process generates
+      audrey_agent.py. audrey_agent.py should not be manually modified.
+
+Invoked at instance launch to interface with the Config Server (CS)
+
+For prototype end to end testing this file needs to be installed
+at: /usr/bin/audrey
+
+Algorithim:
+    Get CF info
+    Loop:
+        Get and validate required configuration from CS
+        Configure system using required configuration
+        Get and validate provides parameters from CS
+        Gather provided parameter data from system
+        Put provided parameter data to CS
+    if not done then goto Loop
+
+'''
+
+import argparse
+import base64
+import httplib2
+import logging
+import os
+import shutil
+import sys
+import tarfile
+import tarfile as tf # To simplify exception names.
+import tempfile
+import urllib
+import oauth2 as oauth
+
+from time import sleep
+from collections import deque
+from subprocess import Popen, PIPE
+
+EC2_USER_DATA_URL = 'http://169.254.169.254/latest/user-data'
+CLOUD_INFO_FILE = '/etc/sysconfig/cloud-info'
+
+# Location of the config tooling.
+TOOLING_DIR = '/var/audrey/tooling/'
+
+# Log file
+LOG = '/var/log/audrey.log'
+LOGGER = None
+CS_API_VER = 1
+# The VERSION string is filled in during the make process.
+AUDREY_VER = '@VERSION@'
+
+# When running on condor-cloud, the Config Server (CS) contact
+# information will be stored in the smbios.
+# These are the dmi files where the smbios information is stored.
+CONDORCLOUD_CS_ADDR = '/sys/devices/virtual/dmi/id/sys_vendor'
+CONDORCLOUD_CS_UUID = '/sys/devices/virtual/dmi/id/product_name'
+
+#
+# Error Handling methods:
+#
+class ASError(Exception):
+    '''
+    Some sort of error occurred. The exact cause of the error should
+    have been logged. So, this just indicates that something is wrong.
+    '''
+    pass
+
+def _raise_ASError(err_msg):
+    '''
+    Log an error message and raise ASError
+    '''
+    LOGGER.error(err_msg)
+    raise ASError(err_msg)
+
+class _run_cmd_return_subproc():
+    '''
+    Used to pass return code to caller if no subprocess object
+    is generated by Popen() due to an error.
+    '''
+    returncode = 127
+
+#
+# Misc. Supporting Methods
+#
+def _run_cmd(cmd, my_cwd=None):
+    '''
+    Description:
+        Run a command given by a dictionary, check for stderr output,
+        return code.
+
+        To check the return code catch SystemExit then examine:
+        ret['subproc'].returncode.
+
+    Input:
+
+        cmd - a list containing the command to execute.
+            e.g.: cmd = ['ls', '/tmp']
+
+    Returns:
+
+        ret - a dictionary upon return contains the keys:
+            'subproc', 'err', 'out'
+
+        ret['subproc'].returncode = subprocess return code
+        ret['err'] = command errors string.
+        ret['out'] = command out list.
+
+    Example:
+
+        cmd = ['ls', '/tmp']
+        ret = _run_cmd(cmd)
+
+        ret.keys()
+        ['subproc', 'err', 'out']
+        ret['subproc'].returncode
+        0
+        ret['err']
+        ''
+        ret['out']
+
+    '''
+
+    pfail = _run_cmd_return_subproc()
+
+    # Return dictionary to contain keys: 'cmd', 'subproc', 'err', 'out'
+    ret = {'subproc' : None, 'err' : '' , 'out' : ''}
+
+    try:
+        ret['subproc'] = Popen(cmd, cwd=my_cwd, stdout=PIPE, stderr=PIPE)
+
+    # unable to find command will result in an OSError
+    except OSError, err:
+        if not ret['subproc']:
+            ret['subproc'] = pfail
+
+        ret['subproc'].returncode = 127 # command not found
+        ret['err'] = str(err)
+        return ret
+
+    # fill ret['out'] with stdout and ret['err'] with stderr
+    ret.update(zip(['out', 'err'], ret['subproc'].communicate()))
+
+    return ret
+
+def _run_pipe_cmd(cmd1, cmd2):
+    '''
+    Description:
+        Run one command piped into another. Commands are given as
+        dictionaries, check for stderr output, return code.
+
+        To check the return code catch SystemExit then examine:
+        ret['subproc'].returncode.
+
+        That is this routine can be used to execute a command
+        of the form:
+
+    Input:
+
+        cmd1 - a list containing the command to execute.
+            e.g.: cmd = ['ls', '/tmp']
+
+        cmd2 - a list containing the command to pipe the output
+            of cmd1 to.
+            e.g.: cmd = ['grep', 'a_file']
+
+    Returns:
+
+        ret - a dictionary upon return contains the keys:
+            'subproc', 'err', 'out'
+
+        ret['subproc'].returncode = subprocess return code
+        ret['err'] = command errors string.
+        ret['out'] = command out list.
+
+    Example:
+
+        cmd1 = ['ls', '/tmp']
+        cmd2 = ['grep', 'a_file']
+        ret = _run_pipe_cmd(cmd1, cmd2)
+
+        ret.keys()
+        ['subproc', 'err', 'out']
+        ret['subproc'].returncode
+        0
+        ret['err']
+        ''
+        ret['out']
+
+    '''
+
+    # Return dictionary to contain keys: 'cmd', 'subproc', 'err', 'out'
+    ret = {'subproc' : None, 'err' : '' , 'out' : ''}
+
+    p1 = None
+    p2 = None
+    pfail = _run_cmd_return_subproc()
+
+    # Execute the first command:
+    try:
+        p1 = Popen(cmd1, stdout=PIPE)
+        p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE )
+        p1.stdout.close()
+
+        # fill ret['out'] with stdout and ret['err'] with stderr
+        # ret.update(zip(['out', 'err'], ret['subproc'].communicate()[0]))
+        ret.update(zip(['out', 'err'], p2.communicate()))
+        ret['subproc'] = p2
+
+    # unable to find command will result in an OSError
+    except OSError, err:
+        if p2:
+            ret['subproc'] = p2
+        elif p1:
+            ret['subproc'] = p1
+        else:
+            ret['subproc'] = pfail
+
+        ret['subproc'].returncode = 127 # command not found
+        ret['err'] = str(err)
+        return ret
+
+    return ret
+
+class ServiceParams(object):
+    '''
+    Description:
+        Used for storing a service and all of it's associated parameters
+        as provided by the Config Server in the "required" parameters
+        API message.
+
+        services = [
+                ServiceParams('serviceA', ['n&v', 'n&v', 'n&v',...]),
+                ServiceParams('serviceB', ['n&v', 'n&v', 'n&v',...]),
+                ServiceParams('serviceB', ['n&v', 'n&v', 'n&v',...]),
+        ]
+
+        This structure aids in tracking the parsed required config
+        parameters which is useful when doing UNITTESTing.
+
+    '''
+    def __init__(self, name=None):
+        if name == None:
+            name = ''
+        self.name = name # string
+        self.params = [] # start with an empty list
+    def add_param(self, param):
+        '''
+        Description:
+            Add a parameter provided by   the Config Server to the list.
+        '''
+        self.params.append(param)
+    def __repr__(self):
+        return repr((self.name, self.params))
+
+#
+# Methods used to parse the CS<->AS text based API
+#
+def _common_validate_message(src):
+    '''
+    Perform validation of the text message sent from the Config Server.
+    '''
+
+    if not src.startswith('|') or not src.endswith('|'):
+        _raise_ASError(('Invalid start and end characters: %s') % (src))
+
+def gen_env(serv_name, param_val):
+    '''
+    Description:
+        Generate the os environment variables from the required config string.
+
+    Input:
+        serv_name - A service name
+            e.g.:
+            jon_agent_config
+
+        param_val - A parameter name&val pair. The value is base64 encoded.
+            e.g.:
+            jon_server_ip&MTkyLjE2OC4wLjE=
+
+    Output:
+        Set environment variables of the form:
+        <name>=<value>
+            e.g.:
+            jon_server_ip=base64.b64decode('MTkyLjE2OC4wLjE=')
+            jon_server_ip='192.168.0.1
+
+    Raises ASError when encountering an error.
+
+    '''
+    LOGGER.debug('Invoked gen_env()')
+
+    # If the param_val is missing treat as an exception.
+    if param_val == '':
+        _raise_ASError(('Missing parameter name. %s') % \
+            (str(param_val)))
+
+    # If serv_name is not blank an extra "_" must be added to
+    # the environment variable name.
+    if serv_name != '':
+        serv_name = serv_name + '_'
+
+    name_val = param_val.split('&')
+    var_name = 'AUDREY_VAR_' + serv_name + name_val[0]
+    os.environ[var_name] = \
+        base64.b64decode(name_val[1])
+
+    # Get what was set and log it.
+    cmd = ['/usr/bin/printenv', var_name]
+    ret = _run_cmd(cmd)
+    LOGGER.debug(var_name + '=' + str(ret['out'].strip()))
+
+def parse_require_config(src):
+    '''
+    Description:
+        Parse the required config text message sent from the Config Server.
+
+    Input:
+        The required config string obtained from the Config Server,
+        delimited by an | and an &
+
+        Two tags will mark the sections of the data,
+        '|service|' and  '|parameters|'
+
+        To ensure all the data was received the entire string will be
+        terminated with an "|".
+
+        The string "|service|" will precede a service names.
+
+        The string "|parameters|" will precede the parameters for
+        the preceeding service, in the form: names&<b64 encoded values>.
+
+    This will be a continuous text string (no CR or New Line).
+
+        Format (repeating for each service):
+
+        |service|<s1>|parameters|name1&<b64val>|name2&<b64val>...|nameN&<b64v>|
+
+
+        e.g.:
+        |service|ssh::server|parameters|ssh_port&<b64('22')>
+        |service|apache2::common|apache_port&<b64('8081')>|
+
+    Returns:
+        - A list of ServiceParams objects.
+    '''
+
+    services = []
+    new = None
+
+    _common_validate_message(src)
+
+    # Message specific validation
+    if src == '||':
+        # special case indicating no required config needed.
+        return []
+
+    if src.find('|service|') != 0:
+        _raise_ASError(('|service| is not the first tag found. %s') % (src))
+
+
+    src_q = deque(src.split('|'))
+
+    # remove leading and trailing elements from the src_q since they are
+    # empty strings generated by the split('|') because of the leading
+    # and trailing '|'
+    token = src_q.popleft()
+    token = src_q.pop()
+
+    while True:
+        try:
+            token = src_q.popleft()
+            if token == 'service':
+                token = src_q.popleft() # next token is service name
+
+                # Raise an error if the service name is invalid.
+                if token.find('&') != -1 or \
+                    token == 'service' or \
+                    token == 'parameters':
+                    _raise_ASError(('ERROR invalid service name: %s') % \
+                       (str(token)))
+
+                new = ServiceParams(token)
+                services.append(new)
+            elif token == 'parameters' or token == '':
+                pass
+            else: # token is a name&value pair.
+                if token.find('&') == -1:
+                    _raise_ASError(('ERROR name&val: %s missing delimiter') % \
+                       (str(token)))
+                if new:
+                    new.add_param(token)
+                    gen_env(new.name, token)
+                else:
+                    _raise_ASError(('ERROR missing service tag %s') % \
+                         (str(src)))
+        except IndexError:
+            break
+
+    return services
+
+def _get_system_info():
+    '''
+    Description:
+        Get the system info to be used for generating this instances
+        provides back to the Config Server.
+
+        Currently utilizes Puppet's facter via a Python subprocess call.
+
+    Input:
+        None
+
+    Returns:
+        A dictionary of system info name/value pairs.
+
+    '''
+
+    cmd = ['/usr/bin/facter']
+    ret = _run_cmd(cmd)
+    if ret['subproc'].returncode != 0:
+        _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
+            (' '.join(cmd), str(ret['err'])))
+
+    facts = {}
+    for fact in ret['out'].split('\n'):
+        if fact: # Handle the new line at the end of the facter output
+            name, val = fact.split(' => ')
+            facts[ name ] = val.rstrip()
+
+    return facts
+
+def parse_provides_params(src):
+    '''
+    Description:
+        Parse the provides parameters text message sent from the
+        Config Server.
+
+    Input:
+        The provides parameters string obtained from the Config Server.
+
+        The delimiters will be an | and an &
+
+        To ensure all the data was received the entire string will be
+        terminated with an "|".
+
+        This will be a continuous text string (no CR or New Line).
+
+        Format:
+        |name1&name2...&nameN|
+
+        e.g.:
+        |ipaddress&virtual|
+
+    Returns:
+        - a list of parameter names.
+    '''
+
+    _common_validate_message(src)
+
+    # Message specific validation
+    if src == '||':
+        # special case indicating no provides parameters requested.
+        return ['']
+
+    params_str = src[src.find('|')+1:len(src)-1]
+
+    return params_str.split('&')
+
+def generate_provides(src):
+    '''
+    Description:
+        Generate the provides parameters list.
+        Uses parse_provides_params()
+
+    Input:
+        The provides parameters string obtained from the Config Server.
+
+    Returns:
+        A string to send back to the Config Server  with prifix
+        'audrey_data='<url encoded return data>'
+
+        The return portion will be delimited with an | and an &
+
+        To ensure all the data is transmitted the entire string will be
+        terminated with an "|".
+
+        This will be a continuous text string (no CR or New Line).
+
+        Data portion Format:
+        |name1&val1|name2&val...|nameN$valN|
+
+        e.g.:
+        |ipaddress&<b64/10.118.46.205>|virtual&<b64/xenu>|
+
+        The return string format:
+        "audrey_data=<url encoded data portion>"
+
+
+    '''
+    LOGGER.info('Invoked generate_provides()')
+
+    provides_dict = {}
+    params_list = parse_provides_params(src)
+
+    system_info_dict = _get_system_info()
+
+    for param in params_list:
+        try:
+            provides_dict.update( \
+                {param:base64.b64encode(system_info_dict[param])})
+        except KeyError:
+            # A specified parameter is not found. Provide value ''
+            provides_dict.update({param:''})
+
+
+    # Create string to send to Config Server
+    provides_list = ['']
+    for key in provides_dict.keys():
+        provides_list.append(str(key) + '&' + str(provides_dict[key]))
+    provides_list.append('')
+
+    return urllib.urlencode({'audrey_data':'|'.join(provides_list)})
+
+class ConfigTooling(object):
+    '''
+    TBD - Consider making this class derived from dictionary or a mutable
+    mapping.
+
+    Description:
+        Interface to configuration tooling:
+        - Getting optional user supplied tooling from CS
+        - Verify and Unpack optional user supplied tooling retrieved
+          from CS
+        - Is tooling for a given service user supplied
+        - Is tooling for a given service Red Hat supplied
+        - Find tooling for a given service Red Hat supplied
+        - List tooling for services and indicate if it is user or Red
+          Hat supplied.
+    '''
+
+    def __init__(self, tool_dir=TOOLING_DIR):
+        '''
+        Description:
+            Set initial state so it can be tracked. Valuable for
+            testing and debugging.
+        '''
+        self.tool_dir = tool_dir
+        self.user_dir = tool_dir + 'user/'
+        self.log = tool_dir + 'log'
+        self.tarball = ''
+
+        # Create the extraction destination
+        try:
+            os.makedirs(self.user_dir)
+        except OSError, (errno, strerror):
+            if errno is 17: # File exists
+                pass
+            else:
+                _raise_ASError(('Failed to create directory %s. ' + \
+                    'Error: %s') % (self.user_dir, strerror))
+
+        self.ct_logger = logging.getLogger('ConfigTooling')
+        self.ct_logger.addHandler(logging.FileHandler(self.log))
+
+    def __str__(self):
+        '''
+        Description:
+            Called by the str() function and by the print statement to
+            produce the informal string representation of an object.
+        '''
+        return('\n<Instance of: %s\n' \
+               '\tTooling Dir: %s\n' \
+               '\tUnpack User Tooling Tarball Dir: %s\n' \
+               '\tLog File: %s\n' \
+               '\ttarball Name: %s\n' \
+               'eot>' %
+            (self.__class__.__name__,
+            str(self.tool_dir),
+            str(self.user_dir),
+            str(self.log),
+            str(self.tarball),
+            ))
+
+    def log_info(self, log_str):
+        '''
+        Description:
+            Used for logging the commands that have been executed
+            along with their output and return codes.
+
+            Simply logs the provided input string.
+        '''
+        self.ct_logger.info(log_str)
+
+    def log_error(self, log_str):
+        '''
+        Description:
+            Used for logging errors encountered when attempting to
+            execute the service command.
+
+            Simply logs the provided input string.
+        '''
+        self.ct_logger.error(log_str)
+
+    def invoke_tooling(self, services):
+        '''
+        Description:
+            Invoke the configuration tooling for the specified services.
+
+        Input:
+            services - A list of ServiceParams objects.
+
+        '''
+
+        # For now invoke them all. Later versions will invoke the service
+        # based on the required params from the Config Server.
+        LOGGER.debug('Invoked ConfigTooling.invoke_tooling()')
+        LOGGER.debug(str(services))
+        for service in services:
+
+            try:
+                top_level, tooling_path = self.find_tooling(service.name)
+            except ASError:
+                # No tooling found. Try the next service.
+                continue
+
+            cmd = [tooling_path]
+            cmd_dir = os.path.dirname(tooling_path)
+            ret = _run_cmd(cmd, cmd_dir)
+            self.log_info('Execute Tooling command: ' + ' '.join(cmd))
+
+            retcode = ret['subproc'].returncode
+            if retcode == 0:
+                # Command successed, log the output.
+                self.log_info('return code: ' + str(retcode))
+                self.log_info('\n\tStart Output of: ' + ' '.join(cmd) + \
+                    ' >>>\n' +  \
+                    str(ret['out']) + \
+                    '\n\t<<< End Output')
+            else:
+                # Command failed, log the errors.
+                self.log_info('\n\tStart Output of: ' + ' '.join(cmd) + \
+                    ' >>>\n' +  \
+                    str(ret['out']) + \
+                    '\n\t<<< End Output')
+                self.log_error('error code: ' + str(retcode))
+                self.log_error('error msg:  ' + str(ret['err']))
+
+            # If tooling was provided at the top level only run it once
+            # for all services listed in the required config params.
+            if top_level:
+                break
+
+    def unpack_tooling(self, tarball):
+        '''
+        Description:
+            Methods used to untar the user provided tarball
+
+            Perform validation of the text message sent from the
+            Config Server. Validate, open and write out the contents
+            of the user provided tarball.
+        '''
+        LOGGER.info('Invoked unpack_tooling()')
+        LOGGER.debug('tarball: ' + str(tarball) + \
+            'Target Direcory: ' + str(self.user_dir))
+
+        self.tarball = tarball
+
+        # Validate the specified tarfile.
+        try:
+            if not tarfile.is_tarfile(self.tarball):
+                # If file exists but is not a tar file force IOError.
+                raise IOError
+        except IOError, (errno, strerror):
+            _raise_ASError(('File was not found or is not a tar file: %s ' + \
+                    'Error: %s %s') % (self.tarball, errno, strerror))
+
+        # Attempt to extract the contents from the specified tarfile.
+        #
+        # If tarfile access or content is bad report to the user to aid
+        # problem resolution.
+        try:
+            tarf = tarfile.open(self.tarball)
+            tarf.extractall(path=self.user_dir)
+            tarf.close()
+        except IOError, (errno, strerror):
+            _raise_ASError(('Failed to access tar file %s. Error: %s') %  \
+                (self.tarball, strerror))
+        # Capture and report errors with the tarfile
+        except (tf.TarError, tf.ReadError, tf.CompressionError, \
+            tf.StreamError, tf.ExtractError), (strerror):
+
+            _raise_ASError(('Failed to access tar file %s. Error: %s') %  \
+                (self.tarball, strerror))
+
+    def is_user_supplied(self):
+        '''
+        Description:
+            Is the the configuration tooling for the specified service
+            supplied by the user?
+
+            TBD: Take in a service_name and evaluate.
+            def is_user_supplied(self, service_name):
+        '''
+        return True
+
+    def is_rh_supplied(self):
+        '''
+        Description:
+            Is the the configuration tooling for the specified service
+            supplied by Red Hat?
+
+            TBD: Take in a service_name and evaluate.
+            def is_rh_supplied(self, service_name):
+        '''
+        return False
+
+    def find_tooling(self, service_name):
+        '''
+        Description:
+            Given a service name return the path to the configuration
+            tooling.
+
+            Search for the service start executable in the user
+            tooling directory.
+                self.tool_dir + '/user/<service name>/start'
+
+            If not found there search for the it in the documented directory
+            here built in tooling should be placed.
+                self.tool_dir + '/AUDREY_TOOLING/<service name>/start'
+
+            If not found there search for the it in the Red Hat tooling
+            directory.
+                self.tool_dir + '/REDHAT/<service name>/start'
+
+           If not found there raise an error.
+
+        Returns:
+            return 1 - True if top level tooling found, False otherwise.
+            return 2 - path to tooling
+        '''
+
+        top_path = self.tool_dir + 'user/start'
+        if os.access(top_path, os.X_OK):
+            return True, top_path
+
+        service_user_path = self.tool_dir + 'user/' + \
+            service_name + '/start'
+        if os.access(service_user_path, os.X_OK):
+            return False, service_user_path
+
+        service_redhat_path = self.tool_dir + 'AUDREY_TOOLING/' + \
+            service_name + '/start'
+        if os.access(service_redhat_path, os.X_OK):
+            return False, service_redhat_path
+
+        service_redhat_path = self.tool_dir + 'REDHAT/' + \
+            service_name + '/start'
+        if os.access(service_redhat_path, os.X_OK):
+            return False, service_redhat_path
+
+        # No tooling found. Raise an error.
+        _raise_ASError(('No configuration tooling found for service: %s') % \
+            (service_name))
+
+class CSClient(object):
+    '''
+    Description:
+        Client interface to Config Server (CS)
+    '''
+
+    def __init__(self, endpoint, oauth_key, oauth_secret, **kwargs):
+        '''
+        Description:
+            Set initial state so it can be tracked. Valuable for
+            testing and debugging.
+        '''
+
+        self.version = CS_API_VER
+        self.cs_endpoint = endpoint
+        self.cs_oauth_key = oauth_key
+        self.cs_oauth_secret = oauth_secret
+        self.ec2_user_data_url = EC2_USER_DATA_URL
+        self.cs_params = ''
+        self.cs_configs = ''
+        self.tmpdir = ''
+        self.tarball = ''
+
+        # create an oauth client for communication with the cs
+        consumer = oauth.Consumer(self.cs_oauth_key, self.cs_oauth_secret)
+        # 2 legged auth, token unnessesary
+        token = None #oauth.Token('access-key-here','access-key-secret-here')
+        client = oauth.Client(consumer, token)
+        self.http = client
+
+    def __del__(self):
+        '''
+        Description:
+            Class destructor
+        '''
+        try:
+            shutil.rmtree(self.tmpdir)
+        except OSError:
+            pass # ignore any errors when attempting to remove the temp dir.
+
+    def __str__(self):
+        '''
+        Description:
+            Called by the str() function and by the print statement to
+            produce the informal string representation of an object.
+        '''
+        return('\n<Instance of: %s\n' \
+               '\tVersion: %s\n' \
+               '\tConfig Server Endpoint: %s\n' \
+               '\tConfig Server oAuth Key: %s\n' \
+               '\tConfig Server oAuth Secret: %s\n' \
+               '\tConfig Server Params: %s\n' \
+               '\tConfig Server Configs: %s\n' \
+               '\tTemporary Directory: %s\n' \
+               '\tTarball Name: %s\n' \
+               'eot>' %
+            (self.__class__.__name__,
+            str(self.version),
+            str(self.cs_endpoint),
+            str(self.cs_oauth_key),
+            str(self.cs_oauth_secret),
+            str(self.cs_params),
+            str(self.cs_configs),
+            str(self.tmpdir),
+            str(self.tarball),
+            ))
+
+    def _cs_url(self, url_type):
+        '''
+        Description:
+            Generate the Config Server (CS) URL.
+        '''
+        return '%s/%s/%s/%s' % \
+            (self.cs_endpoint, url_type, self.version, self.cs_oauth_key)
+
+    def _get(self, url, headers=None):
+        '''
+        Description:
+            Issue the http get to the the Config Server.
+        '''
+        return self.http.request(url, method='GET', headers=headers)
+
+    def _put(self, url, body=None, headers=None):
+        '''
+        Description:
+            Issue the http put to the the Config Server.
+        '''
+        return self.http.request(url, method='PUT',
+                            body=body, headers=headers)
+
+    def _validate_http_status(self, status):
+        '''
+        Description:
+            Confirm the http status is one of:
+            200 HTTP OK - Success and no more data of this type
+            202 HTTP Accepted - Success and more data of this type
+            404 HTTP Not Found - This may be temporary so try again
+        '''
+        if (status != 200) and (status != 202) and (status != 404):
+            _raise_ASError(('Invalid HTTP status code: %s') % \
+                (str(status)))
+
+    # Public interfaces
+    def get_cs_configs(self):
+        '''
+        Description:
+            get the required configuration from the Config Server.
+        '''
+        LOGGER.info('Invoked CSClient.get_cs_configs()')
+        url = self._cs_url('configs')
+        headers = {'Accept': 'text/plain'}
+
+        response, body = self._get(url, headers=headers)
+        self.cs_configs = body
+        self._validate_http_status(response.status)
+
+        return response.status, body
+
+    def get_cs_params(self):
+        '''
+        Description:
+            get the provides parameters from the Config Server.
+        '''
+        LOGGER.info('Invoked CSClient.get_cs_params()')
+        url = self._cs_url('params')
+        headers = {'Accept': 'text/plain'}
+
+        response, body = self._get(url, headers=headers)
+        self.cs_params = body
+        self._validate_http_status(response.status)
+
+        return response.status, body
+
+    def put_cs_params_values(self, params_values):
+        '''
+        Description:
+            put the provides parameters to the Config Server.
+        '''
+        LOGGER.info('Invoked CSClient.put_cs_params_values()')
+        url = self._cs_url('params')
+        headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+
+        response, body = self._put(url, body=params_values, headers=headers)
+        return response.status, body
+
+    def get_cs_tooling(self):
+        '''
+        Description:
+            get any optional user supplied tooling which is
+            provided as a tarball
+        '''
+        LOGGER.info('Invoked CSClient.get_cs_tooling()')
+        url = self._cs_url('files')
+        headers = {'Accept': 'content-disposition'}
+
+        tarball = ''
+        response, body = self._get(url, headers=headers)
+        self._validate_http_status(response.status)
+
+        # Parse the file name burried in the response header
+        # at: response['content-disposition']
+        # as: 'attachment; tarball="tarball.tgz"'
+        if (response.status == 200) or (response.status == 202):
+            tarball = response['content-disposition']. \
+                lstrip('attachment; filename=').replace('"','')
+
+            # Create the temporary tarfile
+            try:
+                self.tmpdir = tempfile.mkdtemp()
+                self.tarball = self.tmpdir + '/' + tarball
+                f  = open(self.tarball, 'w')
+                f.write(body)
+                f.close()
+            except IOError, (errno, strerror):
+                _raise_ASError(('File not found or not a tar file: %s ' + \
+                        'Error: %s %s') % (self.tarball, errno, strerror))
+
+        return response.status, self.tarball
+
+def discover_config_server(cloud_info_file=CLOUD_INFO_FILE,
+                           condor_addr_file=CONDORCLOUD_CS_ADDR,
+                           condor_uuid_file=CONDORCLOUD_CS_UUID,
+                           ec2_user_data=EC2_USER_DATA_URL,
+                           http=httplib2.Http()):
+    '''
+    Description:
+        Discover the Config Server access info.
+        If not discover it using the cloud provider specific method.
+    '''
+    #
+    # What Cloud Backend?
+    #
+    # Read the file populated with Cloud back end type.
+    # e.g.: CLOUD_TYPE="EC2"
+    #
+
+    def _parse_user_data(data, condor=None):
+        '''
+        Take a string in form version|cs_endpoint|oauth_key|oauth_secret
+        and populate the respective self vars.
+        Conductor puts the UUID into the oauth_key field.
+        At minimum this function expects to find a | in the string
+        this is in effort not to log oauth secrets.
+        '''
+        LOGGER.debug('Parsing User Data')
+        user_data = data.split('|')
+        if len(user_data) > 1:
+            if user_data[0] == '1':
+                if condor:
+                    ud_version, endpoint, \
+                        oauth_secret = user_data
+                    oauth_key = condor
+                else:
+                    ud_version, endpoint, \
+                        oauth_key, oauth_secret = user_data
+                return {'endpoint': endpoint,
+                        'oauth_key': oauth_key,
+                        'oauth_secret': oauth_secret,}
+            #elif ud[0] == nextversion
+            #    parse code for version
+            else:
+                _raise_ASError('Invalid User Data Version: %s' % user_data[0])
+        else:
+            _raise_ASError('Could not get user data version, parse failed')
+
+    try:
+        with open(cloud_info_file, 'r') as fp:
+            read_data = fp.read()
+    except IOError:
+        _raise_ASError(('Failed accessing file %s') % \
+            (cloud_info_file))
+
+    #
+    # Discover the Config Server access info.
+    #
+    cloud_type = read_data.upper()
+    if 'EC2' in cloud_type:
+        #
+        # If on EC2 the user data will contain the Config Server
+        # access info.
+        #
+
+        try:
+            max_attempts = 5
+            headers = {'Accept': 'text/plain'}
+            for attempt in range(1, max_attempts):
+                response, body = http.request(ec2_user_data,
+                                              headers=headers)
+                if response.status == 200:
+                    break
+            if response.status != 200:
+                _raise_ASError('Max attempts to get EC2 user data \
+                        exceeded.')
+
+            if '|' not in body:
+                body = base64.b64decode(body)
+            return _parse_user_data(body)
+
+        except Exception, e:
+            _raise_ASError('Failed accessing EC2 user data: %s' % e)
+
+    elif 'CONDORCLOUD' in cloud_type:
+        #
+        # If on Condor Cloud, the user data will be in smbios
+        # Uses the dmi files to access the stored smbios information.
+        #
+        try:
+            return _parse_user_data(open(condor_addr_file, 'r').read().strip(),
+                                    open(condor_uuid_file, 'r').read().strip())
+        except Exception, e:
+            _raise_ASError('Failed accessing Config Server data: %s' % e)
+
+    elif 'RHEV' in cloud_type:
+        #
+        # If on RHEV-M the user data will be contained on the
+        # floppy device in file deltacloud-user-data.txt.
+        # To access it:
+        #    modprobe floppy
+        #    mount /dev/fd0 /media
+        #    read /media/deltacloud-user-data.txt
+        #
+        # Note:
+        # On RHEVm the deltacloud drive had been delivering the user
+        # data base64 decoded at one point that changed such that the
+        # deltacloud drive leaves the date base64 encoded. This
+        # Code segment will handle both base64 encoded and decoded
+        # user data.
+        #
+        # Since ':' is used as a field delimiter in the user data
+        # and is not a valid base64 char, if ':' is found assume
+        # the data is already base64 decoded.
+        #
+        #    modprobe floppy
+        cmd = ['/sbin/modprobe', 'floppy']
+        ret = _run_cmd(cmd)
+        if ret['subproc'].returncode != 0:
+            _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
+                (' '.join(cmd), str(ret['err'])))
+
+        cmd = ['/bin/mkdir', '/media']
+        ret = _run_cmd(cmd)
+        # If /media is already there (1) or any other error (0)
+        if (ret['subproc'].returncode != 1) and  \
+           (ret['subproc'].returncode != 0):
+            _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
+                (' '.join(cmd), str(ret['err'])))
+
+        cmd = ['/bin/mount', '/dev/fd0', '/media']
+        ret = _run_cmd(cmd)
+        # If /media is already mounted (32) or any other error (0)
+        if (ret['subproc'].returncode != 32) and  \
+           (ret['subproc'].returncode != 0):
+            _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
+                (' '.join(cmd), str(ret['err'])))
+
+        try:
+            # Condfig Server (CS) address:port.
+            with open('/media/deltacloud-user-data.txt', 'r') as fp:
+                line = fp.read().strip()
+                if '|' not in line:
+                    line = base64.b64decode(line)
+                return _parse_user_data(line)
+        except:
+            _raise_ASError('Failed accessing RHEVm user data.')
+
+    elif 'VSPHERE' in cloud_type:
+        #
+        # If on vSphere the user data will be contained on the
+        # floppy device in file deltacloud-user-data.txt.
+        # To access it:
+        #    mount /dev/fd0 /media
+        #    read /media/deltacloud-user-data.txt
+        #
+        # Note:
+        # On vSphere the deltacloud drive had been delivering the user
+        # data base64 decoded at one point that changed such that the
+        # deltacloud drive leaves the date base64 encoded. This
+        # Code segment will handle both base64 encoded and decoded
+        # user data.
+        #
+        # Since ':' is used as a field delimiter in the user data
+        # and is not a valid base64 char, if ':' is found assume
+        # the data is already base64 decoded.
+        #
+        cmd = ['/bin/mkdir', '/media']
+        ret = _run_cmd(cmd)
+        # If /media is already there (1) or any other error (0)
+        if (ret['subproc'].returncode != 1) and  \
+           (ret['subproc'].returncode != 0):
+            _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
+                (' '.join(cmd), str(ret['err'])))
+
+        cmd = ['/bin/mount', '/dev/cdrom', '/media']
+        ret = _run_cmd(cmd)
+        # If /media is already mounted (32) or any other error (0)
+        if (ret['subproc'].returncode != 32) and  \
+           (ret['subproc'].returncode != 0):
+            _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
+                (' '.join(cmd), str(ret['err'])))
+
+        try:
+            # Condfig Server (CS) address:port.
+            with open('/media/deltacloud-user-data.txt', 'r') as fp:
+                line = fp.read().strip()
+                if '|' not in line:
+                    line = base64.b64decode(line)
+                return _parse_user_data(line)
+        except:
+            _raise_ASError('Failed accessing vSphere user data.')
+
+def setup_logging(level=logging.INFO, logfile_name=LOG):
+    '''
+    Description:
+        Establish the output logging.
+    '''
+
+    global LOGGER
+
+    # If not run as root create the log file in the current directory.
+    # This allows minimal functionality, e.g.: --help
+    if not os.geteuid() == 0:
+        logfile_name = './audrey.log'
+
+    # set up logging
+    LOG_FORMAT = ('%(asctime)s - %(levelname)-8s: '
+        '%(filename)s:%(lineno)d %(message)s')
+    LOG_LEVEL_INPUT = 5
+    LOG_NAME_INPUT = 'INPUT'
+
+    logging.basicConfig(filename=logfile_name,
+        level=level, filemode='w', format=LOG_FORMAT)
+
+    logging.addLevelName(LOG_LEVEL_INPUT, LOG_NAME_INPUT)
+
+    LOGGER = logging.getLogger('Audrey')
+
+def parse_args():
+    '''
+    Description:
+        Gather any Config Server access info optionally passed
+        on the command line. If being provided on the command
+        line all of it must be provided.
+
+        oAuth Secret is prompted for and not allowed as an argument.
+        This is to avoid a ps on the system from displaying the
+        oAuth Secret argument.
+
+    Return:
+        dict - of parser keys and values
+    '''
+    desc_txt = 'The Aeolus Audrey Startup Agent, a script which ' + \
+               'runs on a booting cloud instance to retrieve ' + \
+               'configuration data from the Aeolus Config Server.'
+
+    log_level_dict={'DEBUG' : logging.DEBUG,
+        'INFO' : logging.INFO,
+        'WARNING' : logging.WARNING,
+        'ERROR' : logging.ERROR,
+        'CRITICAL' : logging.CRITICAL}
+
+    parser = argparse.ArgumentParser(description=desc_txt)
+    parser.add_argument('-e', '--endpoint', dest='endpoint',
+        required=False, help='Config Server endpoint url')
+    parser.add_argument('-k', '--key', dest='oauth_key', required=False,
+        help='oAuth Key. If specified prompt for the oAuth Secret.')
+    parser.add_argument('-p', '--pwd', action='store_true', default=False,
+        required=False, help='Log and look for configs in pwd',)
+    parser.add_argument('-L', '--log-level', dest='log_level',
+        required=False, default='INFO', help='Audrey Agent Logging Level',
+        choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']),
+    parser.add_argument('-V', '-v', '--version', dest='version',
+        action='store_true', default=False, required=False,
+        help='Displays the program\'s version number and exit.')
+
+    args = parser.parse_args()
+    args.log_level = log_level_dict[args.log_level]
+
+    if args.version:
+        print AUDREY_VER
+        sys.exit()
+
+    if args.oauth_key:
+        # Prompt for oAuth secret so ps won't display it.
+        args.oauth_secret = raw_input('oAuth Secret: ')
+
+    return args
+
+def audrey_script_main(client_http=None):
+    '''
+    Description:
+        This script will be used on EC2 for configuring the running
+        instance based on Cloud Engine configuration supplied at
+        launch time in the user data.
+
+        Config Server Status:
+        200 HTTP OK - Success and no more data of this type
+        202 HTTP Accepted - Success and more data of this type
+        404 HTTP Not Found - This may be temporary so try again
+    '''
+    # parse the args and setup logging
+    conf = parse_args()
+    if 'pwd' in conf and conf.pwd:
+        log_file = 'audrey.log'
+        tool_dir = 'tooling'
+        cloud_info = 'cloud_info'
+    else:
+        log_file = LOG
+        tool_dir = TOOLING_DIR
+        cloud_info = CLOUD_INFO_FILE
+
+    setup_logging(level=conf.log_level,
+            logfile_name=log_file)
+
+    if not conf.endpoint:
+        if client_http:
+            conf = discover_config_server(cloud_info_file=cloud_info,
+                                          http=client_http)
+        else:
+            # discover the cloud I'm on
+            conf = discover_config_server(cloud_info_file=cloud_info)
+
+    # ensure the conf it a dictionary, not a namespace
+    if hasattr(conf, '__dict__'):
+        conf = vars(conf)
+
+    LOGGER.info('Invoked audrey_script_main')
+
+    # 0 means don't run again
+    # -1 is non zero so initial runs will happen
+    config_status = -1
+    param_status = -1
+    tooling_status = -1
+
+    max_retry = 5
+    services = []
+
+    # Create the Client Object
+    cs_client = CSClient(**conf)
+    if client_http:
+        cs_client.http = client_http
+    LOGGER.info(str(cs_client))
+
+    LOGGER.debug('Get optional tooling from the Config Server')
+    # Get any optional tooling from the Config Server
+    tooling = ConfigTooling(tool_dir=tool_dir)
+    tooling_status, tarball = cs_client.get_cs_tooling()
+    if (tooling_status == 200) or (tooling_status == 202):
+        tooling.unpack_tooling(tarball)
+    else:
+        LOGGER.info('No optional config tooling provided. status: ' + \
+                str(tooling_status))
+    LOGGER.debug(str(tooling))
+
+    LOGGER.debug('Process the Requires and Provides parameters')
+
+    # Process the Requires and Provides parameters until the HTTP status
+    # from the get_cs_configs and the get_cs_params both return 200
+    while config_status or param_status:
+
+        LOGGER.debug('Config Parameter status: ' + str(config_status))
+        LOGGER.debug('Return Parameter status: ' + str(param_status))
+
+        # Get the Required Configs from the Config Server
+        if config_status:
+            config_status, configs = cs_client.get_cs_configs()
+
+            # Configure the system with the provided Required Configs
+            if config_status == 200:
+                services = parse_require_config(configs)
+                tooling.invoke_tooling(services)
+                # don't do any more config status work
+                # now that the tooling has run
+                config_status = 0
+            else:
+                LOGGER.info('No configuration parameters provided. status: ' + \
+                    str(config_status))
+
+        # Get the requested provides from the Config Server
+        if param_status:
+            get_status, params = cs_client.get_cs_params()
+
+            # Gather the values from the system for the requested provides
+            if get_status == 200:
+                params_values = generate_provides(params)
+            else:
+                params_values = '||'
+
+            # Put the requested provides with values to the Config Server
+            param_status, body = cs_client.put_cs_params_values(params_values)
+            if param_status == 200:
+                # don't operate on params anymore, all have been provided.
+                param_status = 0
+
+        # Retry a number of times if 404 HTTP Not Found is returned.
+        if config_status == 404 or param_status == 404:
+            LOGGER.error('Requiest to Config Server failed or more to come.')
+            LOGGER.error('Required Config Parameter status: ' + \
+                str(config_status))
+            LOGGER.info('Return Parameter status: ' + str(param_status))
+
+            max_retry -= 1
+            if max_retry < 0:
+                _raise_ASError('Too many erroneous Config Server responses.')
+
+        sleep(10)
+
+if __name__ == '__main__':
+
+    audrey_script_main()
diff --git a/agent/test_audrey_startup.py b/agent/test_audrey_startup.py
new file mode 100644
index 0000000..d87c05f
--- /dev/null
+++ b/agent/test_audrey_startup.py
@@ -0,0 +1,692 @@
+#!/usr/bin/python2.6
+'''
+*
+*   Copyright [2011] [Red Hat, Inc.]
+*
+*   Licensed under the Apache License, Version 2.0 (the "License");
+*   you may not use this file except in compliance with the License.
+*   You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+*   Unless required by applicable law or agreed to in writing, software
+*   distributed under the License is distributed on an "AS IS" BASIS,
+*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*   See the License for the specific language governing permissions and
+*  limitations under the License.
+*
+'''
+
+'''
+ test_audrey_agent.py
+
+ Test program for audrey_agent
+'''
+
+import base64
+import logging
+import os
+import os.path
+import tempfile
+import unittest
+import sys
+import tarfile
+
+from audrey_agent import CSClient
+from audrey_agent import ConfigTooling
+from audrey_agent import ASError
+from audrey_agent import parse_args
+from audrey_agent import parse_provides_params
+from audrey_agent import parse_require_config
+from audrey_agent import audrey_script_main
+from audrey_agent import gen_env
+from audrey_agent import _run_cmd, _run_pipe_cmd
+from audrey_agent import generate_provides
+from audrey_agent import setup_logging
+from audrey_agent import discover_config_server
+
+# Helpers and utils
+DUMMY_USER_DATA = '1|http://example.com/|oauthConsumer|oauthSecret'
+DUMMY_CS_CONFIG = {'endpoint': 'http://example.com/',
+                   'oauth_key': 'oauthConsumer',
+                   'oauth_secret': 'oauthSecret',}
+
+try:
+    from cStringIO import StringIO as BIO
+except ImportError: # python 3
+    from io import BytesIO as BIO
+
+class HttpUnitTest(object):
+    '''
+    Description:
+        When testing the http object does not exists. This class provides
+        test methods that could be preformed when doing UNITTESTing.
+    '''
+    class HttpUnitTestResponse(object):
+        '''
+        Description:
+            When testing the http object does not exists. This class
+            provides the test method response that could be preformed
+            when doing UNITTESTing.
+        '''
+        def __init__(self, status):
+            self.status = status
+
+        def add_content_disposition(self):
+            self.__dict__['content-disposition'] = \
+                                   'attachment; filename=test.tar.gz'
+
+        def __getitem__(self, key):
+            return self.__dict__[key]
+
+
+    # simple HTTP Response with 200 status code
+    ok_response = HttpUnitTestResponse(200)
+    not_found_response = HttpUnitTestResponse(404)
+
+    def request(self, url, method='GET', body=None, headers=None):
+        '''
+        Handle request when not running live but in test environment.
+        '''
+        body = ''
+        response = HttpUnitTest.ok_response
+        if method == 'GET':
+            if url.find('/configs/') > -1:
+                body = '|service|s1|parameters|param1&%s|param2&%s|' % \
+                    (base64.b64encode('value1'), base64.b64encode('value2'))
+            elif url.find('/params/') > -1:
+                body = '|param1&param2|'
+            elif url.find('/files/') > -1:
+                file_out = BIO()
+                tar = tarfile.open(mode = "w:gz", fileobj = file_out)
+                tar.add('/etc/passwd')
+                tar.close()
+                body = file_out.getvalue()
+                response.add_content_disposition()
+            elif url.endswith('/user-data'):
+                body = base64.b64encode(DUMMY_USER_DATA)
+            elif url.endswith('/no-version-user-data'):
+                body = base64.b64encode('0|endpoint')
+            elif url.endswith('/empty-user-data'):
+                body = base64.b64encode('')
+            elif url.endswith('/gimmie-404'):
+                body = base64.b64encode(DUMMY_USER_DATA)
+                response = HttpUnitTest.not_found_response
+            else:
+                print url
+                response = HttpUnitTest.not_found_response
+        #elif method == 'POST' and url.find('/params/') > -1:
+        #    body = ''
+        return response, body
+
+def _write_info_file(filepath, cloud):
+    f = open(filepath, 'w')
+    f.write(cloud)
+    f.close()
+
+# The actual tests
+
+class TestAudreyStarupRunCmds(unittest.TestCase):
+    '''
+    Test the _run*cmd functions
+    '''
+    def test_success_run_pipe_cmd(self):
+        self.assertEqual("'test'\n",
+            _run_pipe_cmd(["echo", "'test'"], ["grep", "test"])['out'])
+
+    def test_cmd2_fail_run_pipe_cmd(self):
+        self.assertEqual("[Errno 2] No such file or directory",
+            _run_pipe_cmd(["echo", "'test'"], ["notreal"])['err'])
+
+    def test_cmd1_fail_run_pipe_cmd(self):
+        self.assertEqual("[Errno 2] No such file or directory",
+            _run_pipe_cmd(["notreal"], ["echo", "'test'"])['err'])
+
+class TestAudreyStartupConfigTooling(unittest.TestCase):
+    '''
+    Make sure all the Config tooling is tested
+    '''
+    def test_is_user_supplied(self):
+        ConfigTooling('test_tooling').is_user_supplied()
+
+    def test_is_rh_supplied(self):
+        ConfigTooling('test_tooling').is_rh_supplied()
+
+    def test_empty_find_tooling(self):
+        self.assertRaises(ASError, ConfigTooling('test_tooling').find_tooling, '')
+
+    def test_fail_to_create_tooling_dir(self):
+        self.assertRaises(ASError, ConfigTooling, tool_dir='/not/real/dir')
+
+class TestAudreyStartupRequiredConfig(unittest.TestCase):
+    '''
+    Class for exercising the parsing of the Required Configs from the CS.
+    '''
+
+    def setUp(self):
+        '''
+        Perform required setup including setting up logging.
+        '''
+        setup_logging(logging.DEBUG, './test_audrey_agent.log')
+
+    def test_success_service_n_params(self):
+        '''
+        Success case:
+        - Exercise parse_require_config() with valid input
+        '''
+        # Establish valid test data:
+
+        src = '|service|jon1' + \
+            '|parameters|jon_server_ip&' + base64.b64encode('192.168.1.1') + \
+            '|jon_server_ip_2&' + base64.b64encode('192.168.1.2') + \
+            '|jon_server_ip_3&' + base64.b64encode('192.168.1.3') + \
+            '|service|jon2|'
+
+        validation_dict = {'AUDREY_VAR_jon1_jon_server_ip' : '192.168.1.1',
+            'AUDREY_VAR_jon1_jon_server_ip_2' : '192.168.1.2',
+            'AUDREY_VAR_jon1_jon_server_ip_3' : '192.168.1.3' }
+
+        print '\nTest Name: test_success_service_n_params()'
+        print 'Test input:\n' + src
+        print 'Expect: parse_require_config() success'
+
+        # Exersise code segment
+        services = parse_require_config(src)
+
+        # Validate results
+        self.assertEqual(services[0].name, 'jon1')
+        self.assertEqual(services[1].name, 'jon2')
+
+        for service in services:
+            for param in service.params:
+                name_val = param.split('&')
+                env_var  = 'AUDREY_VAR_' + service.name + '_' + name_val[0]
+                print 'name_val[0]:   ' + str(name_val[0])
+                print 'param:         ' + str(param)
+                print 'services.name: ' + str(service.name)
+
+                cmd = ['/usr/bin/printenv', env_var]
+                ret = _run_cmd(cmd)
+                self.assertEqual(ret['out'][:-1], \
+                    validation_dict[env_var])
+
+    def test_success_empty_source(self):
+        '''
+        Success case:
+        - Exercise parse_require_config() with valid empty input
+        '''
+
+        # Establish valid test data:
+        src = '||'
+        print '\nTest Name: test_success_empty_source()'
+        print 'Test input:\n' + src
+        print 'Expect: parse_require_config() success'
+
+        # Exersise code segment
+        services = parse_require_config(src)
+        print 'services: ' + str(services)
+
+        # Validate results
+        self.assertEqual(services, [])
+
+    def test_success_empty_service(self):
+        '''
+        Failure case:
+        - Exercise parse_require_config() with valid input
+        '''
+
+        # Establish valid test data:
+        src = '|service|' + \
+            '|parameters|jon_server_ip&' + base64.b64encode('192.168.1.1') + \
+            '|jon_server_ip_2&' + base64.b64encode('192.168.1.2') + \
+            '|jon_server_ip_3&' + base64.b64encode('192.168.1.3') + \
+            '|service|jon2|'
+
+        validation_dict = {'AUDREY_VAR_jon_server_ip' : '192.168.1.1',
+            'AUDREY_VAR_jon_server_ip_2' : '192.168.1.2',
+            'AUDREY_VAR_jon_server_ip_3' : '192.168.1.3' }
+
+        print '\nTest Name: test_success_empty_service()'
+        print 'Test input:\n' + src
+        print 'Expect: parse_require_config() success'
+
+        # Exersise code segment
+        services = parse_require_config(src)
+
+        # Validate results
+        self.assertEqual(services[0].name, '')
+        self.assertEqual(services[1].name, 'jon2')
+
+        for service in services:
+            for param in service.params:
+                name_val = param.split('&')
+                env_var  = 'AUDREY_VAR_' + name_val[0]
+
+                print 'name_val[0]:   ' + str(name_val[0])
+                print 'param:         ' + str(param)
+                print 'services.name: ' + str(service.name)
+
+                cmd = ['/usr/bin/printenv', env_var]
+                ret = _run_cmd(cmd)
+                self.assertEqual(ret['out'][:-1], \
+                    validation_dict[env_var])
+
+    def test_failure_no_services_name(self):
+        '''
+        Failure case:
+        - Exercise parse_require_config() with valid input
+
+        The slight difference between this test and test_success_empty_services
+        is the success case has an empty service name indicated by "||":
+        |service||paramseters
+
+        and the failure case has no service name:
+        |service|paramseters
+
+        '''
+
+        # Establish valid test data:
+        src = '|service' \
+            '|parameters|jon_server_ip&' + base64.b64encode('192.168.1.1') + \
+            '|jon_server_ip_2&' + base64.b64encode('192.168.1.2') + \
+            '|jon_server_ip_3&' + base64.b64encode('192.168.1.3') + \
+            '|service|jon2|'
+
+        validation_dict = {'AUDREY_VAR_jon_server_ip' : '192.168.1.1',
+            'AUDREY_VAR_jon_server_ip_2' : '192.168.1.2',
+            'AUDREY_VAR_jon_server_ip_3' : '192.168.1.3' }
+
+        print '\nTest Name: test_failure_no_service_names()'
+        print 'Test input:\n' + src
+        print 'Expect: parse_require_config() success'
+
+        # Exersise code segment
+        with self.assertRaises(ASError):
+            print 'parse_require_config returned: ' + \
+                str(parse_require_config(src))
+
+    def test_failure_bad_service_name(self):
+        '''
+        Failure case:
+        - Exercise parse_require_config() with valid input
+        '''
+
+        # Establish valid test data:
+        src = '|service|parameters|'
+        print '\nTest Name: test_failure_bad_service_name()'
+        print 'Test input:\n' + src
+        print 'Expect: parse_require_config() ASError'
+
+        # Exersise code segment
+        with self.assertRaises(ASError):
+            print 'parse_require_config returned: ' + \
+                str(parse_require_config(src))
+
+class TestAudreyStartupDiscovery(unittest.TestCase):
+    def setUp(self):
+        '''
+        Perform required setup including setting up logging.
+        '''
+        setup_logging(logging.DEBUG, 'test_audrey_agent.log')
+        self.cloud_info_file = 'cloud_info'
+        self.condor_addr_file = 'condor_addr'
+        self.condor_uuid_file = 'condor_uuid'
+
+    def tearDown(self):
+        os.remove(self.cloud_info_file)
+        if os.path.exists(self.condor_addr_file):
+            os.remove(self.condor_addr_file)
+        if os.path.exists(self.condor_uuid_file):
+            os.remove(self.condor_uuid_file)
+
+    def test_ec2(self):
+        _write_info_file(self.cloud_info_file, 'EC2')
+        discover_config_server(self.cloud_info_file, http=HttpUnitTest())
+
+    def test_ec2_404(self):
+        _write_info_file(self.cloud_info_file, 'EC2')
+        self.assertRaises(ASError,
+            discover_config_server, self.cloud_info_file, http=HttpUnitTest(),
+            ec2_user_data='http://169.254.169.254/gimmie-404')
+
+    def test_condorcloud(self):
+        _write_info_file(self.condor_addr_file, '1|endpoint|secret')
+        _write_info_file(self.condor_uuid_file, 'key')
+        _write_info_file(self.cloud_info_file, 'CONDORCLOUD')
+        discover_config_server(self.cloud_info_file,
+                               condor_addr_file=self.condor_addr_file,
+                               condor_uuid_file=self.condor_uuid_file)
+
+    def test_rhev(self):
+        _write_info_file(self.cloud_info_file, 'RHEV')
+        self.assertRaises(ASError,
+            discover_config_server, self.cloud_info_file)
+
+    def test_vsphere(self):
+        _write_info_file(self.cloud_info_file, 'VSPHERE')
+        self.assertRaises(ASError,
+            discover_config_server, self.cloud_info_file)
+
+    def test_invalid_user_data_version(self):
+        _write_info_file(self.cloud_info_file, 'EC2')
+        self.assertRaises(ASError,
+            discover_config_server, self.cloud_info_file, http=HttpUnitTest(),
+            ec2_user_data='http://169.254.169.254/no-version-user-data')
+
+    def test_invalid_user_data_no_delim(self):
+        _write_info_file(self.cloud_info_file, 'EC2')
+        self.assertRaises(ASError,
+            discover_config_server, self.cloud_info_file, http=HttpUnitTest(),
+            ec2_user_data='http://169.254.169.254/empty-user-data')
+
+
+class TestAudreyStartupProvidesParameters(unittest.TestCase):
+    '''
+    Class for exercising the parsing of the Provides ParametersConfigs
+    from the CS.
+    '''
+
+    def setUp(self):
+        '''
+        Perform required setup including setting up logging.
+        '''
+        setup_logging(logging.DEBUG, './test_audrey_agent.log')
+
+    def test_success_parameters(self):
+        '''
+        Success case:
+        - Exercise parse_provides_params() and generate_provides()
+          with valid input
+        '''
+
+        # Establish valid test data:
+        src = '|operatingsystem&is_virtual|'
+
+        print '\nTest Name: test_success_parameters()'
+        print 'Test input:\n' + src
+        print 'Expect: parse_provides_params() success'
+
+        expected_params_list = ['operatingsystem', 'is_virtual']
+
+        # Exersise code segment
+        params_list = parse_provides_params(src)
+        provides = generate_provides(src)
+        print 'src: ' + str(src)
+        print 'params_list: ' + str(params_list)
+        print 'provides: ' + str(provides)
+        print 'len(provides): ' + str(len(provides))
+
+        # Validate results
+        self.assertEqual(params_list, expected_params_list)
+
+        # The values are not validatable because they are unpredictable
+        # but all the expected parameters should be returned.
+        # Note: %7C is the encoded |, %26 is the encoded &
+        self.assertTrue('audrey_data=%7Coperatingsystem' in provides)
+        for param in expected_params_list:
+            self.assertTrue('%7C' + str(param) in provides)
+
+    def test_success_no_params(self):
+        '''
+        Success case:
+        - Exercise parse_provides_params() and generate_provides()
+          with valid input
+        - Containging an unavailable parameter
+        '''
+
+        # Establish valid test data:
+        src = '|uptime_days&unavailable_dogs&ipaddress|'
+
+        print '\nTest Name: test_success_no_params()'
+        print 'Test input:\n' + src
+        print 'Expect: parse_provides_params() success'
+
+        expected_params_list = ['uptime_days', 'unavailable_dogs', 'ipaddress']
+
+        # Exersise code segment
+        params_list = parse_provides_params(src)
+        provides = generate_provides(src)
+        print 'src: ' + str(src)
+        print 'params_list: ' + str(params_list)
+        print 'provides: ' + str(provides)
+
+        # Validate results
+        self.assertEqual(params_list, expected_params_list)
+
+        # The values are not validatable because they are unpredictable
+        # but all the expected parameters should be returned.
+        # Note: %7C is the encoded |, %26 is the encoded &
+        for param in expected_params_list:
+            self.assertTrue('%7C' + str(param) in provides)
+
+        # Confirm unavailable parameters return an empty string.
+        self.assertTrue('%7C' + 'unavailable_dogs' + '%26%7C' in provides)
+
+    def test_success_one_parameters(self):
+        '''
+        Success case:
+        - Exercise parse_provides_params() and generate_provides()
+          with valid input
+        - with only one parameter
+        '''
+
+        # Establish valid test data:
+        src = '|uptime_days|'
+
+        print '\nTest Name: test_success_parameters()'
+        print 'Test input:\n' + src
+        print 'Expect: parse_provides_params() success'
+
+        expected_params_list = ['uptime_days']
+
+        # Exersise code segment
+        params_list = parse_provides_params(src)
+        provides = generate_provides(src)
+        print 'src: ' + str(src)
+        print 'params_list: ' + str(params_list)
+        print 'provides: ' + str(provides)
+
+        # Validate results
+        self.assertEqual(params_list, expected_params_list)
+
+        # The values are not validatable because they are unpredictable
+        # but all the expected parameters should be returned.
+        # Note: %7C is the encoded |, %26 is the encoded &
+        for param in expected_params_list:
+            self.assertTrue('%7C' + str(param) in provides)
+
+    def test_success_one_parameter(self):
+        '''
+        Success case:
+        - Exercise parse_provides_params() and generate_provides()
+          with valid input
+        - With only one parameter which is unavailable
+        '''
+
+        # Establish valid test data:
+        src = '|unavailable_dogs|'
+
+        print '\nTest Name: test_success_one_parameter()'
+        print 'Test input:\n' + src
+        print 'Expect: parse_provides_params() success'
+
+        expected_params_list = ['unavailable_dogs']
+
+        # Exersise code segment
+        params_list = parse_provides_params(src)
+        provides = generate_provides(src)
+        print 'src: ' + str(src)
+        print 'params_list: ' + str(params_list)
+        print 'provides: ' + str(provides)
+
+        # Validate results
+        self.assertEqual(params_list, expected_params_list)
+
+        # The values are not validatable because they are unpredictable
+        # but all the expected parameters should be returned.
+        # Note: %7C is the encoded |, %26 is the encoded &
+        for param in expected_params_list:
+            self.assertTrue('%7C' + str(param) in provides)
+
+        # Confirm unavailable parameters return an empty string.
+        self.assertTrue('%7C' + 'unavailable_dogs' + '%26%7C' in provides)
+
+    def test_failure_missing_delimiter(self):
+        '''
+        Failure case:
+        - Exercise parse_provides_params() and generate_provides()
+          with invalid input
+        - missing leading delimiter
+        '''
+
+        # Establish valid test data:
+        src = 'unavailable_dogs|'
+
+        print '\nTest Name: test_failure_missing_delimiter()'
+        print 'Test input:\n' + src
+        print 'Expect: parse_require_config() ASError'
+
+        expected_params_list = ['unavailable_dogs']
+
+        # Exersise code segment and validate results
+        with self.assertRaises(ASError):
+            params_list = parse_provides_params(src)
+
+        with self.assertRaises(ASError):
+            provides = generate_provides(src)
+
+class TestConfigServerClient(unittest.TestCase):
+    '''
+    Class for exercising the gets and put to and from the CS
+    '''
+
+    def setUp(self):
+        '''
+        If the cloud info file is not present assume running in a
+        UNITTEST environment. This will allow for exercising some
+        of the code without having to be running in a cloud VM.
+
+        Set up logging.
+        '''
+
+        setup_logging(logging.DEBUG, './test_audrey_agent.log')
+
+        # Create the client Object
+        self.cs_client = CSClient(**DUMMY_CS_CONFIG)
+        self.cs_client.http = HttpUnitTest()
+
+    def tearDown(self):
+        pass
+
+    def test_success_get_cs_configs(self):
+        '''
+        Success case:
+        - Exercise get_cs_configs()
+        '''
+        print '\n\n--- Test Name: test_success_get_cs_configs ---'
+
+        self.cs_client.get_cs_configs()
+
+        # Add asserts
+        print 'test_success_get_cs_configs() Add asserts'
+        print 'self.cs_client : START \n' + str(self.cs_client) + \
+            '\nself.cs_client : END'
+
+    def test_success_get_cs_tooling(self):
+        '''
+        Success case:
+        - Exercise get_cs_tooling()
+        '''
+        self.cs_client.get_cs_tooling()
+
+    def test_success_get_cs_params(self):
+        '''
+        Success case:
+        - Exercise get_cs_params()
+        '''
+        print '\n\n--- Test Name: test_success_get_cs_params ---'
+
+        self.cs_client.get_cs_params()
+
+        # Add asserts
+        print 'test_success_get_cs_params() Add asserts'
+        print 'self.cs_client : START \n' + str(self.cs_client) + \
+            '\nself.cs_client : END'
+
+    def test_success_get_cs_confs_n_params(self):
+        '''
+        Success case:
+        - Exercise get_cs_configs() and get_cs_params()
+        '''
+        print '\n\n--- Test Name: test_success_get_cs_confs_and_params ---'
+
+        self.cs_client.get_cs_configs()
+        self.cs_client.get_cs_params()
+
+        # Add asserts
+        print 'test_success_get_cs_confs_n_params() Add asserts'
+        print 'self.cs_client : START \n' + str(self.cs_client) + \
+            '\nself.cs_client : END'
+        print 'test_success_get_cs_confs_n_params() Add asserts'
+
+    def test_success_put_cs_params_values(self):
+        '''
+        Success case:
+        - Exercise put_cs_params_values()
+        '''
+        self.cs_client.put_cs_params_values('')
+
+    def test_error_http_status(self):
+        '''
+        Success case:
+        - Exercise put_cs_params_values()
+        '''
+        self.assertRaises(ASError, self.cs_client._validate_http_status, 401)
+
+class TestAudreyScript(unittest.TestCase):
+    '''
+    Class for exercising the full audrey script functionality
+    '''
+
+    def setUp(self):
+        '''
+        Perform required setup including setting up logging.
+
+        This test currently require to be run in a cloud VM
+        with a live Config Server.
+        '''
+        setup_logging(logging.DEBUG, './test_audrey_agent.log')
+        # make a copy of argv
+        self.argv = list(sys.argv)
+
+    def tearDown(self):
+        # replace argv
+        sys.argv = list(self.argv)
+
+    def test_audrey_script_main(self):
+        '''
+        Perform what the audrey script will do.
+        '''
+        cloud_info_file = 'cloud_info'
+        sys.argv.extend(['-p'])
+        _write_info_file(cloud_info_file, 'EC2')
+        audrey_script_main(HttpUnitTest())
+        os.remove(cloud_info_file)
+
+    def test_fail_audrey_script_main(self):
+        '''
+        Perform what the audrey script will do.
+        '''
+        self.assertRaises(ASError, audrey_script_main)
+
+    def test_empty_gen_env(self):
+        self.assertRaises(ASError, gen_env, '', '')
+
+    # doesn't actually test what I wanted it to.
+    #def test_parse_require_config(self):
+    #    self.assertRaises(ASError, parse_require_config, '')
+
+if __name__ == '__main__':
+
+    setup_logging(logging.DEBUG, logfile_name='./test_audrey_agent.log')
+    unittest.main()
diff --git a/audrey_start/COPYING b/audrey_start/COPYING
deleted file mode 100644
index 35907a1..0000000
--- a/audrey_start/COPYING
+++ /dev/null
@@ -1,158 +0,0 @@
-Apache License
-
-Version 2.0, January 2004
-
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction,
-and distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the
-copyright owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other
-entities that control, are controlled by, or are under common control with
-that entity. For the purposes of this definition, "control" means (i) the
-power, direct or indirect, to cause the direction or management of such
-entity, whether by contract or otherwise, or (ii) ownership of fifty percent
-(50%) or more of the outstanding shares, or (iii) beneficial ownership
-of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising permissions
-granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation
-or translation of a Source form, including but not limited to compiled
-object code, generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form,
-made available under the License, as indicated by a copyright notice that
-is included in or attached to the work (an example is provided in the Appendix
-below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form,
-that is based on (or derived from) the Work and for which the editorial
-revisions, annotations, elaborations, or other modifications represent,
-as a whole, an original work of authorship. For the purposes of this License,
-Derivative Works shall not include works that remain separable from, or
-merely link (or bind by name) to the interfaces of, the Work and Derivative
-Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original
-version of the Work and any modifications or additions to that Work or
-Derivative Works thereof, that is intentionally submitted to Licensor for
-inclusion in the Work by the copyright owner or by an individual or Legal
-Entity authorized to submit on behalf of the copyright owner. For the purposes
-of this definition, "submitted" means any form of electronic, verbal, or
-written communication sent to the Licensor or its representatives, including
-but not limited to communication on electronic mailing lists, source code
-control systems, and issue tracking systems that are managed by, or on
-behalf of, the Licensor for the purpose of discussing and improving the
-Work, but excluding communication that is conspicuously marked or otherwise
-designated in writing by the copyright owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on
-behalf of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License. Subject to the terms and conditions of this
-License, each Contributor hereby grants to You a perpetual, worldwide,
-non-exclusive, no-charge, royalty-free, irrevocable copyright license to
-reproduce, prepare Derivative Works of, publicly display, publicly perform,
-sublicense, and distribute the Work and such Derivative Works in Source
-or Object form.
-
-3. Grant of Patent License. Subject to the terms and conditions of this
-License, each Contributor hereby grants to You a perpetual, worldwide,
-non-exclusive, no-charge, royalty-free, irrevocable (except as stated in
-this section) patent license to make, have made, use, offer to sell, sell,
-import, and otherwise transfer the Work, where such license applies only
-to those patent claims licensable by such Contributor that are necessarily
-infringed by their Contribution(s) alone or by combination of their Contribution(s)
-with the Work to which such Contribution(s) was submitted. If You institute
-patent litigation against any entity (including a cross-claim or counterclaim
-in a lawsuit) alleging that the Work or a Contribution incorporated within
-the Work constitutes direct or contributory patent infringement, then any
-patent licenses granted to You under this License for that Work shall terminate
-as of the date such litigation is filed.
-
-4. Redistribution. You may reproduce and distribute copies of the Work
-or Derivative Works thereof in any medium, with or without modifications, and
-in Source or Object form, provided that You meet the following conditions:
-
-1.    You must give any other recipients of the Work or Derivative Works
-a copy of this License; and
-2.    You must cause any modified files to carry prominent notices stating
-that You changed the files; and
-3.    You must retain, in the Source form of any Derivative Works that
-You distribute, all copyright, patent, trademark, and attribution notices
-from the Source form of the Work, excluding those notices that do not pertain
-to any part of the Derivative Works; and
-4.    If the Work includes a "NOTICE" text file as part of its distribution,
-then any Derivative Works that You distribute must include a readable copy
-of the attribution notices contained within such NOTICE file, excluding
-those notices that do not pertain to any part of the Derivative Works,
-in at least one of the following places: within a NOTICE text file distributed
-as part of the Derivative Works; within the Source form or documentation,
-if provided along with the Derivative Works; or, within a display generated
-by the Derivative Works, if and wherever such third-party notices normally
-appear. The contents of the NOTICE file are for informational purposes
-only and do not modify the License. You may add Your own attribution notices
-within Derivative Works that You distribute, alongside or as an addendum
-to the NOTICE text from the Work, provided that such additional attribution
-notices cannot be construed as modifying the License. You may add Your
-own copyright statement to Your modifications and may provide additional
-or different license terms and conditions for use, reproduction, or distribution
-of Your modifications, or for any such Derivative Works as a whole, provided
-Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions. Unless You explicitly state otherwise,
-any Contribution intentionally submitted for inclusion in the Work by You
-to the Licensor shall be under the terms and conditions of this License,
-without any additional terms or conditions. Notwithstanding the above,
-nothing herein shall supersede or modify the terms of any separate license
-agreement you may have executed with Licensor regarding such Contributions.
-
-6. Trademarks. This License does not grant permission to use the trade
-names, trademarks, service marks, or product names of the Licensor, except
-as required for reasonable and customary use in describing the origin of
-the Work and reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty. Unless required by applicable
-law or agreed to in writing, Licensor provides the Work (and each Contributor
-provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR
-CONDITIONS OF ANY KIND, either express or implied, including, without limitation,
-any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY,
-or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining
-the appropriateness of using or redistributing the Work and assume any
-risks associated with Your exercise of permissions under this License.
-
-8. Limitation of Liability. In no event and under no legal theory, whether
-in tort (including negligence), contract, or otherwise, unless required
-by applicable law (such as deliberate and grossly negligent acts) or agreed
-to in writing, shall any Contributor be liable to You for damages, including
-any direct, indirect, special, incidental, or consequential damages of
-any character arising as a result of this License or out of the use or
-inability to use the Work (including but not limited to damages for loss
-of goodwill, work stoppage, computer failure or malfunction, or any and
-all other commercial damages or losses), even if such Contributor has been
-advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability. While redistributing the
-Work or Derivative Works thereof, You may choose to offer, and charge a
-fee for, acceptance of support, warranty, indemnity, or other liability
-obligations and/or rights consistent with this License. However, in accepting
-such obligations, You may act only on Your own behalf and on Your sole
-responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any
-liability incurred by, or claims asserted against, such Contributor by
-reason of your accepting any such warranty or additional liability.
diff --git a/audrey_start/Makefile b/audrey_start/Makefile
deleted file mode 100644
index 8cd1bf5..0000000
--- a/audrey_start/Makefile
+++ /dev/null
@@ -1,94 +0,0 @@
-#
-#   Copyright [2011] [Red Hat, Inc.]
-#
-#   Licensed under the Apache License, Version 2.0 (the "License");
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#  limitations under the License.
-#
-
-AUDREY_STARTUP_CACHE_DIR	?= $(HOME)/audrey-agent-cache
-
-VERSION = 0.4.2
-
-# For Release: 0..., set _audrey-agent_dev=1 so that we get extra_release.GIT-
-# annotated rpm version strings.
-_audrey-agent_dev =  $(shell grep -q '^[[:space:]]*Release:[[:space:]]*0' \
-   aeolus-audrey-agent.spec.in && echo 1 || :)
-
-# use $(shell...) here to collect the git head and date *once* per make target.
-# that ensures that if multiple actions happen in the same target (like the
-# multiple RPM builds in the rpms target), they all use the same date
-git_head	= $(shell git log -1 --pretty=format:%h)
-date		= $(shell date --utc +%Y%m%d%H%M%S)
-GIT_RELEASE	= $(date)git$(git_head)
-RPMDIR		= $$(rpm --eval '%{_rpmdir}')
-RELEASE		= $(shell grep ^Release: aeolus-audrey-agent.spec.in | \
-			sed -e 's/Release:[ ,	]*//' | sed -e 's/%.*//')
-# RPM_FLAGS	= --define "audrey-agent_cache_dir $(AUDREY_STARTUP_CACHE_DIR)"
-# RPM_FLAGS	+= $(if $(_audrey-agent_dev),--define "extra_release .$(GIT_RELEASE)")
-man_section = 8
-
-# Create the dist.
-#    Run the audrey_startup.py audrey.$(man_section) then:
-#        Set the version numbers in aeolus-audrey-agent.spec and audrey_startup.py
-#        Copy the desired bits to the dist directory.
-#        tar up the contents of the dist directory.
-dist: audrey_startup.py audrey.$(man_section)
-	rm -rf dist
-	sed -e 's/@VERSION@/$(VERSION)/' aeolus-audrey-agent.spec.in \
-	  > aeolus-audrey-agent.spec
-	mkdir -p dist/aeolus-audrey-agent-$(VERSION)
-	cp -a aeolus-audrey-agent.spec aeolus-audrey-agent.spec.in \
-		COPYING Makefile audrey_startup.in.py \
-		audrey.$(man_section) \
-		test_audrey_startup.py dist/aeolus-audrey-agent-$(VERSION)
-	tar -C dist -zcvf aeolus-audrey-agent-$(VERSION).tar.gz aeolus-audrey-agent-$(VERSION)
-
-# cp audrey_starup.in.py to audrey_startup.py
-audrey_startup.py: audrey_startup.in.py
-	sed -e 's/@VERSION@/$(VERSION)/' $< > $@-t
-	chmod a+x,a-w $@-t
-	mv $@-t $@
-
-# cp audrey_starup.py to audrey.
-audrey: audrey_startup.py
-	cp $< $@
-
-# Generate the man page using help2man from the --help text output from the Audrey agent.
-#    Run the audrey target before geneeerating the man page.
-audrey.$(man_section): audrey
-	help2man --name='Aeolus startup agent' --section $(man_section) \
-	    --no-info ./$< > $@-t
-	mv $@-t $@
-
-# Run the automated tests
-#    Run the audrey_startup.py target before running the automated tests.
-check: audrey_startup.py
-	python test_audrey_startup.py
-
-# Build the rpms
-#    Run the check target followed by the dist target before building the rpms.
-rpms: check dist
-	rpmbuild -ta aeolus-audrey-agent-$(VERSION).tar.gz
-
-# Build the srpms
-srpms: check dist
-	rpmbuild -ts aeolus-audrey-agent-$(VERSION).tar.gz
-
-# Clean up files generated by this Makefile
-clean:
-	rm -rf dist aeolus-audrey-agent-$(VERSION).tar.gz \
-	aeolus-audrey-agent.spec audrey.$(man_section) audrey \
-	audrey.log audrey_startup.py audrey_startup.pyc \
-	test_toolinglog test_toolinguser \
-	toolinglog toolinguser
-
-.PHONY: dist check rpms srpms
diff --git a/audrey_start/aeolus-audrey-agent.spec.in b/audrey_start/aeolus-audrey-agent.spec.in
deleted file mode 100644
index a43d39d..0000000
--- a/audrey_start/aeolus-audrey-agent.spec.in
+++ /dev/null
@@ -1,100 +0,0 @@
-#
-#   Copyright [2011] [Red Hat, Inc.]
-#
-#   Licensed under the Apache License, Version 2.0 (the "License");
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#  limitations under the License.
-#
-Name:       aeolus-audrey-agent
-Version:    @VERSION@
-Release:    12%{?dist}
-Summary:    The Aeolus Audrey Startup Agent
-
-Group:      Applications/System
-License:    ASL 2.0
-URL:        http://aeolusproject.org
-Source0:    http://joev.fedorapeople.org/audrey-agent/aeolus-audrey-agent-%{version}.tar.gz
-
-# All of these are required for building since during the build
-# process "audrey --help" is executed to generate the man page.
-BuildRequires: help2man
-BuildRequires: facter python-httplib2 python-oauth2
-%if (0%{?fedora} <= 14 || 0%{?rhel} <= 6)
-BuildRequires: python-argparse
-%endif
-
-Requires:   facter python-httplib2 python-oauth2
-Requires:   python-argparse
-%if (0%{?fedora} <= 14 || 0%{?rhel} <= 6)
-Requires:   python-argparse
-%endif
-
-BuildArch:  noarch
-
-%description
-The Aeolus Audrey Startup Agent, a script which runs on a booting
-cloud instance to retrieve configuration data from the Aeolus
-Config Server.
-
-%prep
-%setup -q
-
-%build
-make %{?_smp_mflags}
-
-%install
-rm -rf %{buildroot}
-mkdir -p %{buildroot}%{_bindir}
-mkdir -p %{buildroot}%{_mandir}/man8
-
-# copy over the audrey startup agent bits
-install audrey_startup.py %{buildroot}%{_bindir}/audrey
-cp audrey.8 %{buildroot}%{_mandir}/man8
-
-%files
-%{_bindir}/audrey
-%{_mandir}/man8/audrey.8*
-%doc COPYING
-
-%changelog
-* Thu Nov 18 2011 Joe VLcek <jvlcek at redhat.com> 0.4.0-12
-- Fixed Auto help file generation induced build failure
-* Thu Nov 17 2011 Joe VLcek <jvlcek at redhat.com> 0.4.0-11
-- Remove Auto help file generation as it is currently breakin the build
-* Thu Nov 17 2011 Joe VLcek <jvlcek at redhat.com> 0.4.0-10
-- Fix bz754769 fix user data parsing and log level
-* Tue Nov 09 2011 Joe VLcek <jvlcek at redhat.com> 0.4.0-9
-- Update the licensing information
-* Tue Nov 08 2011 Joe VLcek <jvlcek at redhat.com> 0.4.0-8
-- Add man page generation and address some packaging review feedback
-* Tue Nov 08 2011 Greg Blomquist <gblomqui at redhat.com> 0.4.0-7
-- Fixing changelog history
-* Mon Nov 07 2011 Joe VLcek <jvlcek at redhat.com> 0.3.1-7
-- Address Packaging for Fedora review feedback
-* Fri Nov 05 2011 Joe VLcek <jvlcek at redhat.com> 0.3.1-6
-- Packaging for Fedora
-* Wed Nov 02 2011 Greg Blomquist <gblomqui at redhat.com> 0.4.0-6
-- Handles base64 encoded and decoded data in user data
-* Wed Nov 02 2011 Greg Blomquist <gblomqui at redhat.com> 0.4.0-3
-- Fix for audrey.log location
-* Thu Oct 27 2011 Greg Blomquist <gblomqui at redhat.com> 0.4.0-2
-- Radez doubled unit test coverage
-* Wed Oct 26 2011 Greg Blomquist <gblomqui at redhat.com> 0.4.0-1
-- Now using oauth to authenticate with Config Server
-- Parsing a new user data format that is versioned
-* Thu Oct 20 2011 Dan Radez <dradez at redhat.com> 0.3.1-3
-- adding requires for rpm installations
-* Tue Oct 11 2011 Joe VLcek <joev at redhat.com> 0.3.1-2
-- RHEVm user data injection base64 encoded.
-* Fri Sep 30 2011 Joe VLcek <joev at redhat.com> 0.3.1-1
-- RHEVm user data injection.
-* Wed May 18 2011 Joe VLcek <joev at redhat.com> 0.0.1-1
-- Initial build.
diff --git a/audrey_start/audrey_startup.in.py b/audrey_start/audrey_startup.in.py
deleted file mode 100755
index eb9d26f..0000000
--- a/audrey_start/audrey_startup.in.py
+++ /dev/null
@@ -1,1353 +0,0 @@
-#! /usr/bin/env python
-'''
-*
-*   Copyright [2011] [Red Hat, Inc.]
-*
-*   Licensed under the Apache License, Version 2.0 (the "License");
-*   you may not use this file except in compliance with the License.
-*   You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-*   Unless required by applicable law or agreed to in writing, software
-*   distributed under the License is distributed on an "AS IS" BASIS,
-*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-*   See the License for the specific language governing permissions and
-*  limitations under the License.
-*
-'''
-
-'''
-Audrey Startup (AS)
-
-Note: The source file is named audrey_start.in.py The make process generates
-      audrey_start.py. audrey_start.py should not be manually modified.
-
-Invoked at instance launch to interface with the Config Server (CS)
-
-For prototype end to end testing this file needs to be installed
-at: /usr/bin/audrey
-
-Algorithim:
-    Get CF info
-    Loop:
-        Get and validate required configuration from CS
-        Configure system using required configuration
-        Get and validate provides parameters from CS
-        Gather provided parameter data from system
-        Put provided parameter data to CS
-    if not done then goto Loop
-
-'''
-
-import argparse
-import base64
-import httplib2
-import logging
-import os
-import shutil
-import sys
-import tarfile
-import tarfile as tf # To simplify exception names.
-import tempfile
-import urllib
-import oauth2 as oauth
-
-from time import sleep
-from collections import deque
-from subprocess import Popen, PIPE
-
-EC2_USER_DATA_URL = 'http://169.254.169.254/latest/user-data'
-CLOUD_INFO_FILE = '/etc/sysconfig/cloud-info'
-
-# Location of the config tooling.
-TOOLING_DIR = '/var/audrey/tooling/'
-
-# Log file
-LOG = '/var/log/audrey.log'
-LOGGER = None
-CS_API_VER = 1
-# The VERSION string is filled in during the make process.
-AUDREY_VER = '@VERSION@'
-
-# When running on condor-cloud, the Config Server (CS) contact
-# information will be stored in the smbios.
-# These are the dmi files where the smbios information is stored.
-CONDORCLOUD_CS_ADDR = '/sys/devices/virtual/dmi/id/sys_vendor'
-CONDORCLOUD_CS_UUID = '/sys/devices/virtual/dmi/id/product_name'
-
-#
-# Error Handling methods:
-#
-class ASError(Exception):
-    '''
-    Some sort of error occurred. The exact cause of the error should
-    have been logged. So, this just indicates that something is wrong.
-    '''
-    pass
-
-def _raise_ASError(err_msg):
-    '''
-    Log an error message and raise ASError
-    '''
-    LOGGER.error(err_msg)
-    raise ASError(err_msg)
-
-class _run_cmd_return_subproc():
-    '''
-    Used to pass return code to caller if no subprocess object
-    is generated by Popen() due to an error.
-    '''
-    returncode = 127
-
-#
-# Misc. Supporting Methods
-#
-def _run_cmd(cmd, my_cwd=None):
-    '''
-    Description:
-        Run a command given by a dictionary, check for stderr output,
-        return code.
-
-        To check the return code catch SystemExit then examine:
-        ret['subproc'].returncode.
-
-    Input:
-
-        cmd - a list containing the command to execute.
-            e.g.: cmd = ['ls', '/tmp']
-
-    Returns:
-
-        ret - a dictionary upon return contains the keys:
-            'subproc', 'err', 'out'
-
-        ret['subproc'].returncode = subprocess return code
-        ret['err'] = command errors string.
-        ret['out'] = command out list.
-
-    Example:
-
-        cmd = ['ls', '/tmp']
-        ret = _run_cmd(cmd)
-
-        ret.keys()
-        ['subproc', 'err', 'out']
-        ret['subproc'].returncode
-        0
-        ret['err']
-        ''
-        ret['out']
-
-    '''
-
-    pfail = _run_cmd_return_subproc()
-
-    # Return dictionary to contain keys: 'cmd', 'subproc', 'err', 'out'
-    ret = {'subproc' : None, 'err' : '' , 'out' : ''}
-
-    try:
-        ret['subproc'] = Popen(cmd, cwd=my_cwd, stdout=PIPE, stderr=PIPE)
-
-    # unable to find command will result in an OSError
-    except OSError, err:
-        if not ret['subproc']:
-            ret['subproc'] = pfail
-
-        ret['subproc'].returncode = 127 # command not found
-        ret['err'] = str(err)
-        return ret
-
-    # fill ret['out'] with stdout and ret['err'] with stderr
-    ret.update(zip(['out', 'err'], ret['subproc'].communicate()))
-
-    return ret
-
-def _run_pipe_cmd(cmd1, cmd2):
-    '''
-    Description:
-        Run one command piped into another. Commands are given as
-        dictionaries, check for stderr output, return code.
-
-        To check the return code catch SystemExit then examine:
-        ret['subproc'].returncode.
-
-        That is this routine can be used to execute a command
-        of the form:
-
-    Input:
-
-        cmd1 - a list containing the command to execute.
-            e.g.: cmd = ['ls', '/tmp']
-
-        cmd2 - a list containing the command to pipe the output
-            of cmd1 to.
-            e.g.: cmd = ['grep', 'a_file']
-
-    Returns:
-
-        ret - a dictionary upon return contains the keys:
-            'subproc', 'err', 'out'
-
-        ret['subproc'].returncode = subprocess return code
-        ret['err'] = command errors string.
-        ret['out'] = command out list.
-
-    Example:
-
-        cmd1 = ['ls', '/tmp']
-        cmd2 = ['grep', 'a_file']
-        ret = _run_pipe_cmd(cmd1, cmd2)
-
-        ret.keys()
-        ['subproc', 'err', 'out']
-        ret['subproc'].returncode
-        0
-        ret['err']
-        ''
-        ret['out']
-
-    '''
-
-    # Return dictionary to contain keys: 'cmd', 'subproc', 'err', 'out'
-    ret = {'subproc' : None, 'err' : '' , 'out' : ''}
-
-    p1 = None
-    p2 = None
-    pfail = _run_cmd_return_subproc()
-
-    # Execute the first command:
-    try:
-        p1 = Popen(cmd1, stdout=PIPE)
-        p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE )
-        p1.stdout.close()
-
-        # fill ret['out'] with stdout and ret['err'] with stderr
-        # ret.update(zip(['out', 'err'], ret['subproc'].communicate()[0]))
-        ret.update(zip(['out', 'err'], p2.communicate()))
-        ret['subproc'] = p2
-
-    # unable to find command will result in an OSError
-    except OSError, err:
-        if p2:
-            ret['subproc'] = p2
-        elif p1:
-            ret['subproc'] = p1
-        else:
-            ret['subproc'] = pfail
-
-        ret['subproc'].returncode = 127 # command not found
-        ret['err'] = str(err)
-        return ret
-
-    return ret
-
-class ServiceParams(object):
-    '''
-    Description:
-        Used for storing a service and all of it's associated parameters
-        as provided by the Config Server in the "required" parameters
-        API message.
-
-        services = [
-                ServiceParams('serviceA', ['n&v', 'n&v', 'n&v',...]),
-                ServiceParams('serviceB', ['n&v', 'n&v', 'n&v',...]),
-                ServiceParams('serviceB', ['n&v', 'n&v', 'n&v',...]),
-        ]
-
-        This structure aids in tracking the parsed required config
-        parameters which is useful when doing UNITTESTing.
-
-    '''
-    def __init__(self, name=None):
-        if name == None:
-            name = ''
-        self.name = name # string
-        self.params = [] # start with an empty list
-    def add_param(self, param):
-        '''
-        Description:
-            Add a parameter provided by   the Config Server to the list.
-        '''
-        self.params.append(param)
-    def __repr__(self):
-        return repr((self.name, self.params))
-
-#
-# Methods used to parse the CS<->AS text based API
-#
-def _common_validate_message(src):
-    '''
-    Perform validation of the text message sent from the Config Server.
-    '''
-
-    if not src.startswith('|') or not src.endswith('|'):
-        _raise_ASError(('Invalid start and end characters: %s') % (src))
-
-def gen_env(serv_name, param_val):
-    '''
-    Description:
-        Generate the os environment variables from the required config string.
-
-    Input:
-        serv_name - A service name
-            e.g.:
-            jon_agent_config
-
-        param_val - A parameter name&val pair. The value is base64 encoded.
-            e.g.:
-            jon_server_ip&MTkyLjE2OC4wLjE=
-
-    Output:
-        Set environment variables of the form:
-        <name>=<value>
-            e.g.:
-            jon_server_ip=base64.b64decode('MTkyLjE2OC4wLjE=')
-            jon_server_ip='192.168.0.1
-
-    Raises ASError when encountering an error.
-
-    '''
-    LOGGER.debug('Invoked gen_env()')
-
-    # If the param_val is missing treat as an exception.
-    if param_val == '':
-        _raise_ASError(('Missing parameter name. %s') % \
-            (str(param_val)))
-
-    # If serv_name is not blank an extra "_" must be added to
-    # the environment variable name.
-    if serv_name != '':
-        serv_name = serv_name + '_'
-
-    name_val = param_val.split('&')
-    var_name = 'AUDREY_VAR_' + serv_name + name_val[0]
-    os.environ[var_name] = \
-        base64.b64decode(name_val[1])
-
-    # Get what was set and log it.
-    cmd = ['/usr/bin/printenv', var_name]
-    ret = _run_cmd(cmd)
-    LOGGER.debug(var_name + '=' + str(ret['out'].strip()))
-
-def parse_require_config(src):
-    '''
-    Description:
-        Parse the required config text message sent from the Config Server.
-
-    Input:
-        The required config string obtained from the Config Server,
-        delimited by an | and an &
-
-        Two tags will mark the sections of the data,
-        '|service|' and  '|parameters|'
-
-        To ensure all the data was received the entire string will be
-        terminated with an "|".
-
-        The string "|service|" will precede a service names.
-
-        The string "|parameters|" will precede the parameters for
-        the preceeding service, in the form: names&<b64 encoded values>.
-
-    This will be a continuous text string (no CR or New Line).
-
-        Format (repeating for each service):
-
-        |service|<s1>|parameters|name1&<b64val>|name2&<b64val>...|nameN&<b64v>|
-
-
-        e.g.:
-        |service|ssh::server|parameters|ssh_port&<b64('22')>
-        |service|apache2::common|apache_port&<b64('8081')>|
-
-    Returns:
-        - A list of ServiceParams objects.
-    '''
-
-    services = []
-    new = None
-
-    _common_validate_message(src)
-
-    # Message specific validation
-    if src == '||':
-        # special case indicating no required config needed.
-        return []
-
-    if src.find('|service|') != 0:
-        _raise_ASError(('|service| is not the first tag found. %s') % (src))
-
-
-    src_q = deque(src.split('|'))
-
-    # remove leading and trailing elements from the src_q since they are
-    # empty strings generated by the split('|') because of the leading
-    # and trailing '|'
-    token = src_q.popleft()
-    token = src_q.pop()
-
-    while True:
-        try:
-            token = src_q.popleft()
-            if token == 'service':
-                token = src_q.popleft() # next token is service name
-
-                # Raise an error if the service name is invalid.
-                if token.find('&') != -1 or \
-                    token == 'service' or \
-                    token == 'parameters':
-                    _raise_ASError(('ERROR invalid service name: %s') % \
-                       (str(token)))
-
-                new = ServiceParams(token)
-                services.append(new)
-            elif token == 'parameters' or token == '':
-                pass
-            else: # token is a name&value pair.
-                if token.find('&') == -1:
-                    _raise_ASError(('ERROR name&val: %s missing delimiter') % \
-                       (str(token)))
-                if new:
-                    new.add_param(token)
-                    gen_env(new.name, token)
-                else:
-                    _raise_ASError(('ERROR missing service tag %s') % \
-                         (str(src)))
-        except IndexError:
-            break
-
-    return services
-
-def _get_system_info():
-    '''
-    Description:
-        Get the system info to be used for generating this instances
-        provides back to the Config Server.
-
-        Currently utilizes Puppet's facter via a Python subprocess call.
-
-    Input:
-        None
-
-    Returns:
-        A dictionary of system info name/value pairs.
-
-    '''
-
-    cmd = ['/usr/bin/facter']
-    ret = _run_cmd(cmd)
-    if ret['subproc'].returncode != 0:
-        _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
-            (' '.join(cmd), str(ret['err'])))
-
-    facts = {}
-    for fact in ret['out'].split('\n'):
-        if fact: # Handle the new line at the end of the facter output
-            name, val = fact.split(' => ')
-            facts[ name ] = val.rstrip()
-
-    return facts
-
-def parse_provides_params(src):
-    '''
-    Description:
-        Parse the provides parameters text message sent from the
-        Config Server.
-
-    Input:
-        The provides parameters string obtained from the Config Server.
-
-        The delimiters will be an | and an &
-
-        To ensure all the data was received the entire string will be
-        terminated with an "|".
-
-        This will be a continuous text string (no CR or New Line).
-
-        Format:
-        |name1&name2...&nameN|
-
-        e.g.:
-        |ipaddress&virtual|
-
-    Returns:
-        - a list of parameter names.
-    '''
-
-    _common_validate_message(src)
-
-    # Message specific validation
-    if src == '||':
-        # special case indicating no provides parameters requested.
-        return ['']
-
-    params_str = src[src.find('|')+1:len(src)-1]
-
-    return params_str.split('&')
-
-def generate_provides(src):
-    '''
-    Description:
-        Generate the provides parameters list.
-        Uses parse_provides_params()
-
-    Input:
-        The provides parameters string obtained from the Config Server.
-
-    Returns:
-        A string to send back to the Config Server  with prifix
-        'audrey_data='<url encoded return data>'
-
-        The return portion will be delimited with an | and an &
-
-        To ensure all the data is transmitted the entire string will be
-        terminated with an "|".
-
-        This will be a continuous text string (no CR or New Line).
-
-        Data portion Format:
-        |name1&val1|name2&val...|nameN$valN|
-
-        e.g.:
-        |ipaddress&<b64/10.118.46.205>|virtual&<b64/xenu>|
-
-        The return string format:
-        "audrey_data=<url encoded data portion>"
-
-
-    '''
-    LOGGER.info('Invoked generate_provides()')
-
-    provides_dict = {}
-    params_list = parse_provides_params(src)
-
-    system_info_dict = _get_system_info()
-
-    for param in params_list:
-        try:
-            provides_dict.update( \
-                {param:base64.b64encode(system_info_dict[param])})
-        except KeyError:
-            # A specified parameter is not found. Provide value ''
-            provides_dict.update({param:''})
-
-
-    # Create string to send to Config Server
-    provides_list = ['']
-    for key in provides_dict.keys():
-        provides_list.append(str(key) + '&' + str(provides_dict[key]))
-    provides_list.append('')
-
-    return urllib.urlencode({'audrey_data':'|'.join(provides_list)})
-
-class ConfigTooling(object):
-    '''
-    TBD - Consider making this class derived from dictionary or a mutable
-    mapping.
-
-    Description:
-        Interface to configuration tooling:
-        - Getting optional user supplied tooling from CS
-        - Verify and Unpack optional user supplied tooling retrieved
-          from CS
-        - Is tooling for a given service user supplied
-        - Is tooling for a given service Red Hat supplied
-        - Find tooling for a given service Red Hat supplied
-        - List tooling for services and indicate if it is user or Red
-          Hat supplied.
-    '''
-
-    def __init__(self, tool_dir=TOOLING_DIR):
-        '''
-        Description:
-            Set initial state so it can be tracked. Valuable for
-            testing and debugging.
-        '''
-        self.tool_dir = tool_dir
-        self.user_dir = tool_dir + 'user/'
-        self.log = tool_dir + 'log'
-        self.tarball = ''
-
-        # Create the extraction destination
-        try:
-            os.makedirs(self.user_dir)
-        except OSError, (errno, strerror):
-            if errno is 17: # File exists
-                pass
-            else:
-                _raise_ASError(('Failed to create directory %s. ' + \
-                    'Error: %s') % (self.user_dir, strerror))
-
-        self.ct_logger = logging.getLogger('ConfigTooling')
-        self.ct_logger.addHandler(logging.FileHandler(self.log))
-
-    def __str__(self):
-        '''
-        Description:
-            Called by the str() function and by the print statement to
-            produce the informal string representation of an object.
-        '''
-        return('\n<Instance of: %s\n' \
-               '\tTooling Dir: %s\n' \
-               '\tUnpack User Tooling Tarball Dir: %s\n' \
-               '\tLog File: %s\n' \
-               '\ttarball Name: %s\n' \
-               'eot>' %
-            (self.__class__.__name__,
-            str(self.tool_dir),
-            str(self.user_dir),
-            str(self.log),
-            str(self.tarball),
-            ))
-
-    def log_info(self, log_str):
-        '''
-        Description:
-            Used for logging the commands that have been executed
-            along with their output and return codes.
-
-            Simply logs the provided input string.
-        '''
-        self.ct_logger.info(log_str)
-
-    def log_error(self, log_str):
-        '''
-        Description:
-            Used for logging errors encountered when attempting to
-            execute the service command.
-
-            Simply logs the provided input string.
-        '''
-        self.ct_logger.error(log_str)
-
-    def invoke_tooling(self, services):
-        '''
-        Description:
-            Invoke the configuration tooling for the specified services.
-
-        Input:
-            services - A list of ServiceParams objects.
-
-        '''
-
-        # For now invoke them all. Later versions will invoke the service
-        # based on the required params from the Config Server.
-        LOGGER.debug('Invoked ConfigTooling.invoke_tooling()')
-        LOGGER.debug(str(services))
-        for service in services:
-
-            try:
-                top_level, tooling_path = self.find_tooling(service.name)
-            except ASError:
-                # No tooling found. Try the next service.
-                continue
-
-            cmd = [tooling_path]
-            cmd_dir = os.path.dirname(tooling_path)
-            ret = _run_cmd(cmd, cmd_dir)
-            self.log_info('Execute Tooling command: ' + ' '.join(cmd))
-
-            retcode = ret['subproc'].returncode
-            if retcode == 0:
-                # Command successed, log the output.
-                self.log_info('return code: ' + str(retcode))
-                self.log_info('\n\tStart Output of: ' + ' '.join(cmd) + \
-                    ' >>>\n' +  \
-                    str(ret['out']) + \
-                    '\n\t<<< End Output')
-            else:
-                # Command failed, log the errors.
-                self.log_info('\n\tStart Output of: ' + ' '.join(cmd) + \
-                    ' >>>\n' +  \
-                    str(ret['out']) + \
-                    '\n\t<<< End Output')
-                self.log_error('error code: ' + str(retcode))
-                self.log_error('error msg:  ' + str(ret['err']))
-
-            # If tooling was provided at the top level only run it once
-            # for all services listed in the required config params.
-            if top_level:
-                break
-
-    def unpack_tooling(self, tarball):
-        '''
-        Description:
-            Methods used to untar the user provided tarball
-
-            Perform validation of the text message sent from the
-            Config Server. Validate, open and write out the contents
-            of the user provided tarball.
-        '''
-        LOGGER.info('Invoked unpack_tooling()')
-        LOGGER.debug('tarball: ' + str(tarball) + \
-            'Target Direcory: ' + str(self.user_dir))
-
-        self.tarball = tarball
-
-        # Validate the specified tarfile.
-        try:
-            if not tarfile.is_tarfile(self.tarball):
-                # If file exists but is not a tar file force IOError.
-                raise IOError
-        except IOError, (errno, strerror):
-            _raise_ASError(('File was not found or is not a tar file: %s ' + \
-                    'Error: %s %s') % (self.tarball, errno, strerror))
-
-        # Attempt to extract the contents from the specified tarfile.
-        #
-        # If tarfile access or content is bad report to the user to aid
-        # problem resolution.
-        try:
-            tarf = tarfile.open(self.tarball)
-            tarf.extractall(path=self.user_dir)
-            tarf.close()
-        except IOError, (errno, strerror):
-            _raise_ASError(('Failed to access tar file %s. Error: %s') %  \
-                (self.tarball, strerror))
-        # Capture and report errors with the tarfile
-        except (tf.TarError, tf.ReadError, tf.CompressionError, \
-            tf.StreamError, tf.ExtractError), (strerror):
-
-            _raise_ASError(('Failed to access tar file %s. Error: %s') %  \
-                (self.tarball, strerror))
-
-    def is_user_supplied(self):
-        '''
-        Description:
-            Is the the configuration tooling for the specified service
-            supplied by the user?
-
-            TBD: Take in a service_name and evaluate.
-            def is_user_supplied(self, service_name):
-        '''
-        return True
-
-    def is_rh_supplied(self):
-        '''
-        Description:
-            Is the the configuration tooling for the specified service
-            supplied by Red Hat?
-
-            TBD: Take in a service_name and evaluate.
-            def is_rh_supplied(self, service_name):
-        '''
-        return False
-
-    def find_tooling(self, service_name):
-        '''
-        Description:
-            Given a service name return the path to the configuration
-            tooling.
-
-            Search for the service start executable in the user
-            tooling directory.
-                self.tool_dir + '/user/<service name>/start'
-
-            If not found there search for the it in the documented directory
-            here built in tooling should be placed.
-                self.tool_dir + '/AUDREY_TOOLING/<service name>/start'
-
-            If not found there search for the it in the Red Hat tooling
-            directory.
-                self.tool_dir + '/REDHAT/<service name>/start'
-
-           If not found there raise an error.
-
-        Returns:
-            return 1 - True if top level tooling found, False otherwise.
-            return 2 - path to tooling
-        '''
-
-        top_path = self.tool_dir + 'user/start'
-        if os.access(top_path, os.X_OK):
-            return True, top_path
-
-        service_user_path = self.tool_dir + 'user/' + \
-            service_name + '/start'
-        if os.access(service_user_path, os.X_OK):
-            return False, service_user_path
-
-        service_redhat_path = self.tool_dir + 'AUDREY_TOOLING/' + \
-            service_name + '/start'
-        if os.access(service_redhat_path, os.X_OK):
-            return False, service_redhat_path
-
-        service_redhat_path = self.tool_dir + 'REDHAT/' + \
-            service_name + '/start'
-        if os.access(service_redhat_path, os.X_OK):
-            return False, service_redhat_path
-
-        # No tooling found. Raise an error.
-        _raise_ASError(('No configuration tooling found for service: %s') % \
-            (service_name))
-
-class CSClient(object):
-    '''
-    Description:
-        Client interface to Config Server (CS)
-    '''
-
-    def __init__(self, endpoint, oauth_key, oauth_secret, **kwargs):
-        '''
-        Description:
-            Set initial state so it can be tracked. Valuable for
-            testing and debugging.
-        '''
-
-        self.version = CS_API_VER
-        self.cs_endpoint = endpoint
-        self.cs_oauth_key = oauth_key
-        self.cs_oauth_secret = oauth_secret
-        self.ec2_user_data_url = EC2_USER_DATA_URL
-        self.cs_params = ''
-        self.cs_configs = ''
-        self.tmpdir = ''
-        self.tarball = ''
-
-        # create an oauth client for communication with the cs
-        consumer = oauth.Consumer(self.cs_oauth_key, self.cs_oauth_secret)
-        # 2 legged auth, token unnessesary
-        token = None #oauth.Token('access-key-here','access-key-secret-here')
-        client = oauth.Client(consumer, token)
-        self.http = client
-
-    def __del__(self):
-        '''
-        Description:
-            Class destructor
-        '''
-        try:
-            shutil.rmtree(self.tmpdir)
-        except OSError:
-            pass # ignore any errors when attempting to remove the temp dir.
-
-    def __str__(self):
-        '''
-        Description:
-            Called by the str() function and by the print statement to
-            produce the informal string representation of an object.
-        '''
-        return('\n<Instance of: %s\n' \
-               '\tVersion: %s\n' \
-               '\tConfig Server Endpoint: %s\n' \
-               '\tConfig Server oAuth Key: %s\n' \
-               '\tConfig Server oAuth Secret: %s\n' \
-               '\tConfig Server Params: %s\n' \
-               '\tConfig Server Configs: %s\n' \
-               '\tTemporary Directory: %s\n' \
-               '\tTarball Name: %s\n' \
-               'eot>' %
-            (self.__class__.__name__,
-            str(self.version),
-            str(self.cs_endpoint),
-            str(self.cs_oauth_key),
-            str(self.cs_oauth_secret),
-            str(self.cs_params),
-            str(self.cs_configs),
-            str(self.tmpdir),
-            str(self.tarball),
-            ))
-
-    def _cs_url(self, url_type):
-        '''
-        Description:
-            Generate the Config Server (CS) URL.
-        '''
-        return '%s/%s/%s/%s' % \
-            (self.cs_endpoint, url_type, self.version, self.cs_oauth_key)
-
-    def _get(self, url, headers=None):
-        '''
-        Description:
-            Issue the http get to the the Config Server.
-        '''
-        return self.http.request(url, method='GET', headers=headers)
-
-    def _put(self, url, body=None, headers=None):
-        '''
-        Description:
-            Issue the http put to the the Config Server.
-        '''
-        return self.http.request(url, method='PUT',
-                            body=body, headers=headers)
-
-    def _validate_http_status(self, status):
-        '''
-        Description:
-            Confirm the http status is one of:
-            200 HTTP OK - Success and no more data of this type
-            202 HTTP Accepted - Success and more data of this type
-            404 HTTP Not Found - This may be temporary so try again
-        '''
-        if (status != 200) and (status != 202) and (status != 404):
-            _raise_ASError(('Invalid HTTP status code: %s') % \
-                (str(status)))
-
-    # Public interfaces
-    def get_cs_configs(self):
-        '''
-        Description:
-            get the required configuration from the Config Server.
-        '''
-        LOGGER.info('Invoked CSClient.get_cs_configs()')
-        url = self._cs_url('configs')
-        headers = {'Accept': 'text/plain'}
-
-        response, body = self._get(url, headers=headers)
-        self.cs_configs = body
-        self._validate_http_status(response.status)
-
-        return response.status, body
-
-    def get_cs_params(self):
-        '''
-        Description:
-            get the provides parameters from the Config Server.
-        '''
-        LOGGER.info('Invoked CSClient.get_cs_params()')
-        url = self._cs_url('params')
-        headers = {'Accept': 'text/plain'}
-
-        response, body = self._get(url, headers=headers)
-        self.cs_params = body
-        self._validate_http_status(response.status)
-
-        return response.status, body
-
-    def put_cs_params_values(self, params_values):
-        '''
-        Description:
-            put the provides parameters to the Config Server.
-        '''
-        LOGGER.info('Invoked CSClient.put_cs_params_values()')
-        url = self._cs_url('params')
-        headers = {'Content-Type': 'application/x-www-form-urlencoded'}
-
-        response, body = self._put(url, body=params_values, headers=headers)
-        return response.status, body
-
-    def get_cs_tooling(self):
-        '''
-        Description:
-            get any optional user supplied tooling which is
-            provided as a tarball
-        '''
-        LOGGER.info('Invoked CSClient.get_cs_tooling()')
-        url = self._cs_url('files')
-        headers = {'Accept': 'content-disposition'}
-
-        tarball = ''
-        response, body = self._get(url, headers=headers)
-        self._validate_http_status(response.status)
-
-        # Parse the file name burried in the response header
-        # at: response['content-disposition']
-        # as: 'attachment; tarball="tarball.tgz"'
-        if (response.status == 200) or (response.status == 202):
-            tarball = response['content-disposition']. \
-                lstrip('attachment; filename=').replace('"','')
-
-            # Create the temporary tarfile
-            try:
-                self.tmpdir = tempfile.mkdtemp()
-                self.tarball = self.tmpdir + '/' + tarball
-                f  = open(self.tarball, 'w')
-                f.write(body)
-                f.close()
-            except IOError, (errno, strerror):
-                _raise_ASError(('File not found or not a tar file: %s ' + \
-                        'Error: %s %s') % (self.tarball, errno, strerror))
-
-        return response.status, self.tarball
-
-def discover_config_server(cloud_info_file=CLOUD_INFO_FILE,
-                           condor_addr_file=CONDORCLOUD_CS_ADDR,
-                           condor_uuid_file=CONDORCLOUD_CS_UUID,
-                           ec2_user_data=EC2_USER_DATA_URL,
-                           http=httplib2.Http()):
-    '''
-    Description:
-        Discover the Config Server access info.
-        If not discover it using the cloud provider specific method.
-    '''
-    #
-    # What Cloud Backend?
-    #
-    # Read the file populated with Cloud back end type.
-    # e.g.: CLOUD_TYPE="EC2"
-    #
-
-    def _parse_user_data(data, condor=None):
-        '''
-        Take a string in form version|cs_endpoint|oauth_key|oauth_secret
-        and populate the respective self vars.
-        Conductor puts the UUID into the oauth_key field.
-        At minimum this function expects to find a | in the string
-        this is in effort not to log oauth secrets.
-        '''
-        LOGGER.debug('Parsing User Data')
-        user_data = data.split('|')
-        if len(user_data) > 1:
-            if user_data[0] == '1':
-                if condor:
-                    ud_version, endpoint, \
-                        oauth_secret = user_data
-                    oauth_key = condor
-                else:
-                    ud_version, endpoint, \
-                        oauth_key, oauth_secret = user_data
-                return {'endpoint': endpoint,
-                        'oauth_key': oauth_key,
-                        'oauth_secret': oauth_secret,}
-            #elif ud[0] == nextversion
-            #    parse code for version
-            else:
-                _raise_ASError('Invalid User Data Version: %s' % user_data[0])
-        else:
-            _raise_ASError('Could not get user data version, parse failed')
-
-    try:
-        with open(cloud_info_file, 'r') as fp:
-            read_data = fp.read()
-    except IOError:
-        _raise_ASError(('Failed accessing file %s') % \
-            (cloud_info_file))
-
-    #
-    # Discover the Config Server access info.
-    #
-    cloud_type = read_data.upper()
-    if 'EC2' in cloud_type:
-        #
-        # If on EC2 the user data will contain the Config Server
-        # access info.
-        #
-
-        try:
-            max_attempts = 5
-            headers = {'Accept': 'text/plain'}
-            for attempt in range(1, max_attempts):
-                response, body = http.request(ec2_user_data,
-                                              headers=headers)
-                if response.status == 200:
-                    break
-            if response.status != 200:
-                _raise_ASError('Max attempts to get EC2 user data \
-                        exceeded.')
-
-            if '|' not in body:
-                body = base64.b64decode(body)
-            return _parse_user_data(body)
-
-        except Exception, e:
-            _raise_ASError('Failed accessing EC2 user data: %s' % e)
-
-    elif 'CONDORCLOUD' in cloud_type:
-        #
-        # If on Condor Cloud, the user data will be in smbios
-        # Uses the dmi files to access the stored smbios information.
-        #
-        try:
-            return _parse_user_data(open(condor_addr_file, 'r').read().strip(),
-                                    open(condor_uuid_file, 'r').read().strip())
-        except Exception, e:
-            _raise_ASError('Failed accessing Config Server data: %s' % e)
-
-    elif 'RHEV' in cloud_type:
-        #
-        # If on RHEV-M the user data will be contained on the
-        # floppy device in file deltacloud-user-data.txt.
-        # To access it:
-        #    modprobe floppy
-        #    mount /dev/fd0 /media
-        #    read /media/deltacloud-user-data.txt
-        #
-        # Note:
-        # On RHEVm the deltacloud drive had been delivering the user
-        # data base64 decoded at one point that changed such that the
-        # deltacloud drive leaves the date base64 encoded. This
-        # Code segment will handle both base64 encoded and decoded
-        # user data.
-        #
-        # Since ':' is used as a field delimiter in the user data
-        # and is not a valid base64 char, if ':' is found assume
-        # the data is already base64 decoded.
-        #
-        #    modprobe floppy
-        cmd = ['/sbin/modprobe', 'floppy']
-        ret = _run_cmd(cmd)
-        if ret['subproc'].returncode != 0:
-            _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
-                (' '.join(cmd), str(ret['err'])))
-
-        cmd = ['/bin/mkdir', '/media']
-        ret = _run_cmd(cmd)
-        # If /media is already there (1) or any other error (0)
-        if (ret['subproc'].returncode != 1) and  \
-           (ret['subproc'].returncode != 0):
-            _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
-                (' '.join(cmd), str(ret['err'])))
-
-        cmd = ['/bin/mount', '/dev/fd0', '/media']
-        ret = _run_cmd(cmd)
-        # If /media is already mounted (32) or any other error (0)
-        if (ret['subproc'].returncode != 32) and  \
-           (ret['subproc'].returncode != 0):
-            _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
-                (' '.join(cmd), str(ret['err'])))
-
-        try:
-            # Condfig Server (CS) address:port.
-            with open('/media/deltacloud-user-data.txt', 'r') as fp:
-                line = fp.read().strip()
-                if '|' not in line:
-                    line = base64.b64decode(line)
-                return _parse_user_data(line)
-        except:
-            _raise_ASError('Failed accessing RHEVm user data.')
-
-    elif 'VSPHERE' in cloud_type:
-        #
-        # If on vSphere the user data will be contained on the
-        # floppy device in file deltacloud-user-data.txt.
-        # To access it:
-        #    mount /dev/fd0 /media
-        #    read /media/deltacloud-user-data.txt
-        #
-        # Note:
-        # On vSphere the deltacloud drive had been delivering the user
-        # data base64 decoded at one point that changed such that the
-        # deltacloud drive leaves the date base64 encoded. This
-        # Code segment will handle both base64 encoded and decoded
-        # user data.
-        #
-        # Since ':' is used as a field delimiter in the user data
-        # and is not a valid base64 char, if ':' is found assume
-        # the data is already base64 decoded.
-        #
-        cmd = ['/bin/mkdir', '/media']
-        ret = _run_cmd(cmd)
-        # If /media is already there (1) or any other error (0)
-        if (ret['subproc'].returncode != 1) and  \
-           (ret['subproc'].returncode != 0):
-            _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
-                (' '.join(cmd), str(ret['err'])))
-
-        cmd = ['/bin/mount', '/dev/cdrom', '/media']
-        ret = _run_cmd(cmd)
-        # If /media is already mounted (32) or any other error (0)
-        if (ret['subproc'].returncode != 32) and  \
-           (ret['subproc'].returncode != 0):
-            _raise_ASError(('Failed command: \n%s \nError: \n%s') % \
-                (' '.join(cmd), str(ret['err'])))
-
-        try:
-            # Condfig Server (CS) address:port.
-            with open('/media/deltacloud-user-data.txt', 'r') as fp:
-                line = fp.read().strip()
-                if '|' not in line:
-                    line = base64.b64decode(line)
-                return _parse_user_data(line)
-        except:
-            _raise_ASError('Failed accessing vSphere user data.')
-
-def setup_logging(level=logging.INFO, logfile_name=LOG):
-    '''
-    Description:
-        Establish the output logging.
-    '''
-
-    global LOGGER
-
-    # If not run as root create the log file in the current directory.
-    # This allows minimal functionality, e.g.: --help
-    if not os.geteuid() == 0:
-        logfile_name = './audrey.log'
-
-    # set up logging
-    LOG_FORMAT = ('%(asctime)s - %(levelname)-8s: '
-        '%(filename)s:%(lineno)d %(message)s')
-    LOG_LEVEL_INPUT = 5
-    LOG_NAME_INPUT = 'INPUT'
-
-    logging.basicConfig(filename=logfile_name,
-        level=level, filemode='w', format=LOG_FORMAT)
-
-    logging.addLevelName(LOG_LEVEL_INPUT, LOG_NAME_INPUT)
-
-    LOGGER = logging.getLogger('Audrey')
-
-def parse_args():
-    '''
-    Description:
-        Gather any Config Server access info optionally passed
-        on the command line. If being provided on the command
-        line all of it must be provided.
-
-        oAuth Secret is prompted for and not allowed as an argument.
-        This is to avoid a ps on the system from displaying the
-        oAuth Secret argument.
-
-    Return:
-        dict - of parser keys and values
-    '''
-    desc_txt = 'The Aeolus Audrey Startup Agent, a script which ' + \
-               'runs on a booting cloud instance to retrieve ' + \
-               'configuration data from the Aeolus Config Server.'
-
-    log_level_dict={'DEBUG' : logging.DEBUG,
-        'INFO' : logging.INFO,
-        'WARNING' : logging.WARNING,
-        'ERROR' : logging.ERROR,
-        'CRITICAL' : logging.CRITICAL}
-
-    parser = argparse.ArgumentParser(description=desc_txt)
-    parser.add_argument('-e', '--endpoint', dest='endpoint',
-        required=False, help='Config Server endpoint url')
-    parser.add_argument('-k', '--key', dest='oauth_key', required=False,
-        help='oAuth Key. If specified prompt for the oAuth Secret.')
-    parser.add_argument('-p', '--pwd', action='store_true', default=False,
-        required=False, help='Log and look for configs in pwd',)
-    parser.add_argument('-L', '--log-level', dest='log_level',
-        required=False, default='INFO', help='Audrey Agent Logging Level',
-        choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']),
-    parser.add_argument('-V', '-v', '--version', dest='version',
-        action='store_true', default=False, required=False,
-        help='Displays the program\'s version number and exit.')
-
-    args = parser.parse_args()
-    args.log_level = log_level_dict[args.log_level]
-
-    if args.version:
-        print AUDREY_VER
-        sys.exit()
-
-    if args.oauth_key:
-        # Prompt for oAuth secret so ps won't display it.
-        args.oauth_secret = raw_input('oAuth Secret: ')
-
-    return args
-
-def audrey_script_main(client_http=None):
-    '''
-    Description:
-        This script will be used on EC2 for configuring the running
-        instance based on Cloud Engine configuration supplied at
-        launch time in the user data.
-
-        Config Server Status:
-        200 HTTP OK - Success and no more data of this type
-        202 HTTP Accepted - Success and more data of this type
-        404 HTTP Not Found - This may be temporary so try again
-    '''
-    # parse the args and setup logging
-    conf = parse_args()
-    if 'pwd' in conf and conf.pwd:
-        log_file = 'audrey.log'
-        tool_dir = 'tooling'
-        cloud_info = 'cloud_info'
-    else:
-        log_file = LOG
-        tool_dir = TOOLING_DIR
-        cloud_info = CLOUD_INFO_FILE
-
-    setup_logging(level=conf.log_level,
-            logfile_name=log_file)
-
-    if not conf.endpoint:
-        if client_http:
-            conf = discover_config_server(cloud_info_file=cloud_info,
-                                          http=client_http)
-        else:
-            # discover the cloud I'm on
-            conf = discover_config_server(cloud_info_file=cloud_info)
-
-    # ensure the conf it a dictionary, not a namespace
-    if hasattr(conf, '__dict__'):
-        conf = vars(conf)
-
-    LOGGER.info('Invoked audrey_script_main')
-
-    # 0 means don't run again
-    # -1 is non zero so initial runs will happen
-    config_status = -1
-    param_status = -1
-    tooling_status = -1
-
-    max_retry = 5
-    services = []
-
-    # Create the Client Object
-    cs_client = CSClient(**conf)
-    if client_http:
-        cs_client.http = client_http
-    LOGGER.info(str(cs_client))
-
-    LOGGER.debug('Get optional tooling from the Config Server')
-    # Get any optional tooling from the Config Server
-    tooling = ConfigTooling(tool_dir=tool_dir)
-    tooling_status, tarball = cs_client.get_cs_tooling()
-    if (tooling_status == 200) or (tooling_status == 202):
-        tooling.unpack_tooling(tarball)
-    else:
-        LOGGER.info('No optional config tooling provided. status: ' + \
-                str(tooling_status))
-    LOGGER.debug(str(tooling))
-
-    LOGGER.debug('Process the Requires and Provides parameters')
-
-    # Process the Requires and Provides parameters until the HTTP status
-    # from the get_cs_configs and the get_cs_params both return 200
-    while config_status or param_status:
-
-        LOGGER.debug('Config Parameter status: ' + str(config_status))
-        LOGGER.debug('Return Parameter status: ' + str(param_status))
-
-        # Get the Required Configs from the Config Server
-        if config_status:
-            config_status, configs = cs_client.get_cs_configs()
-
-            # Configure the system with the provided Required Configs
-            if config_status == 200:
-                services = parse_require_config(configs)
-                tooling.invoke_tooling(services)
-                # don't do any more config status work
-                # now that the tooling has run
-                config_status = 0
-            else:
-                LOGGER.info('No configuration parameters provided. status: ' + \
-                    str(config_status))
-
-        # Get the requested provides from the Config Server
-        if param_status:
-            get_status, params = cs_client.get_cs_params()
-
-            # Gather the values from the system for the requested provides
-            if get_status == 200:
-                params_values = generate_provides(params)
-            else:
-                params_values = '||'
-
-            # Put the requested provides with values to the Config Server
-            param_status, body = cs_client.put_cs_params_values(params_values)
-            if param_status == 200:
-                # don't operate on params anymore, all have been provided.
-                param_status = 0
-
-        # Retry a number of times if 404 HTTP Not Found is returned.
-        if config_status == 404 or param_status == 404:
-            LOGGER.error('Requiest to Config Server failed or more to come.')
-            LOGGER.error('Required Config Parameter status: ' + \
-                str(config_status))
-            LOGGER.info('Return Parameter status: ' + str(param_status))
-
-            max_retry -= 1
-            if max_retry < 0:
-                _raise_ASError('Too many erroneous Config Server responses.')
-
-        sleep(10)
-
-if __name__ == '__main__':
-
-    audrey_script_main()
diff --git a/audrey_start/test_audrey_startup.py b/audrey_start/test_audrey_startup.py
deleted file mode 100644
index 4980249..0000000
--- a/audrey_start/test_audrey_startup.py
+++ /dev/null
@@ -1,692 +0,0 @@
-#!/usr/bin/python2.6
-'''
-*
-*   Copyright [2011] [Red Hat, Inc.]
-*
-*   Licensed under the Apache License, Version 2.0 (the "License");
-*   you may not use this file except in compliance with the License.
-*   You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-*   Unless required by applicable law or agreed to in writing, software
-*   distributed under the License is distributed on an "AS IS" BASIS,
-*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-*   See the License for the specific language governing permissions and
-*  limitations under the License.
-*
-'''
-
-'''
- test_audrey_startup.py
-
- Test program for audrey_startup
-'''
-
-import base64
-import logging
-import os
-import os.path
-import tempfile
-import unittest
-import sys
-import tarfile
-
-from audrey_startup import CSClient
-from audrey_startup import ConfigTooling
-from audrey_startup import ASError
-from audrey_startup import parse_args
-from audrey_startup import parse_provides_params
-from audrey_startup import parse_require_config
-from audrey_startup import audrey_script_main
-from audrey_startup import gen_env
-from audrey_startup import _run_cmd, _run_pipe_cmd
-from audrey_startup import generate_provides
-from audrey_startup import setup_logging
-from audrey_startup import discover_config_server
-
-# Helpers and utils
-DUMMY_USER_DATA = '1|http://example.com/|oauthConsumer|oauthSecret'
-DUMMY_CS_CONFIG = {'endpoint': 'http://example.com/',
-                   'oauth_key': 'oauthConsumer',
-                   'oauth_secret': 'oauthSecret',}
-
-try:
-    from cStringIO import StringIO as BIO
-except ImportError: # python 3
-    from io import BytesIO as BIO
-
-class HttpUnitTest(object):
-    '''
-    Description:
-        When testing the http object does not exists. This class provides
-        test methods that could be preformed when doing UNITTESTing.
-    '''
-    class HttpUnitTestResponse(object):
-        '''
-        Description:
-            When testing the http object does not exists. This class
-            provides the test method response that could be preformed
-            when doing UNITTESTing.
-        '''
-        def __init__(self, status):
-            self.status = status
-
-        def add_content_disposition(self):
-            self.__dict__['content-disposition'] = \
-                                   'attachment; filename=test.tar.gz'
-
-        def __getitem__(self, key):
-            return self.__dict__[key]
-
-
-    # simple HTTP Response with 200 status code
-    ok_response = HttpUnitTestResponse(200)
-    not_found_response = HttpUnitTestResponse(404)
-
-    def request(self, url, method='GET', body=None, headers=None):
-        '''
-        Handle request when not running live but in test environment.
-        '''
-        body = ''
-        response = HttpUnitTest.ok_response
-        if method == 'GET':
-            if url.find('/configs/') > -1:
-                body = '|service|s1|parameters|param1&%s|param2&%s|' % \
-                    (base64.b64encode('value1'), base64.b64encode('value2'))
-            elif url.find('/params/') > -1:
-                body = '|param1&param2|'
-            elif url.find('/files/') > -1:
-                file_out = BIO()
-                tar = tarfile.open(mode = "w:gz", fileobj = file_out)
-                tar.add('/etc/passwd')
-                tar.close()
-                body = file_out.getvalue()
-                response.add_content_disposition()
-            elif url.endswith('/user-data'):
-                body = base64.b64encode(DUMMY_USER_DATA)
-            elif url.endswith('/no-version-user-data'):
-                body = base64.b64encode('0|endpoint')
-            elif url.endswith('/empty-user-data'):
-                body = base64.b64encode('')
-            elif url.endswith('/gimmie-404'):
-                body = base64.b64encode(DUMMY_USER_DATA)
-                response = HttpUnitTest.not_found_response
-            else:
-                print url
-                response = HttpUnitTest.not_found_response
-        #elif method == 'POST' and url.find('/params/') > -1:
-        #    body = ''
-        return response, body
-
-def _write_info_file(filepath, cloud):
-    f = open(filepath, 'w')
-    f.write(cloud)
-    f.close()
-
-# The actual tests
-
-class TestAudreyStarupRunCmds(unittest.TestCase):
-    '''
-    Test the _run*cmd functions
-    '''
-    def test_success_run_pipe_cmd(self):
-        self.assertEqual("'test'\n",
-            _run_pipe_cmd(["echo", "'test'"], ["grep", "test"])['out'])
-
-    def test_cmd2_fail_run_pipe_cmd(self):
-        self.assertEqual("[Errno 2] No such file or directory",
-            _run_pipe_cmd(["echo", "'test'"], ["notreal"])['err'])
-
-    def test_cmd1_fail_run_pipe_cmd(self):
-        self.assertEqual("[Errno 2] No such file or directory",
-            _run_pipe_cmd(["notreal"], ["echo", "'test'"])['err'])
-
-class TestAudreyStartupConfigTooling(unittest.TestCase):
-    '''
-    Make sure all the Config tooling is tested
-    '''
-    def test_is_user_supplied(self):
-        ConfigTooling('test_tooling').is_user_supplied()
-
-    def test_is_rh_supplied(self):
-        ConfigTooling('test_tooling').is_rh_supplied()
-
-    def test_empty_find_tooling(self):
-        self.assertRaises(ASError, ConfigTooling('test_tooling').find_tooling, '')
-
-    def test_fail_to_create_tooling_dir(self):
-        self.assertRaises(ASError, ConfigTooling, tool_dir='/not/real/dir')
-
-class TestAudreyStartupRequiredConfig(unittest.TestCase):
-    '''
-    Class for exercising the parsing of the Required Configs from the CS.
-    '''
-
-    def setUp(self):
-        '''
-        Perform required setup including setting up logging.
-        '''
-        setup_logging(logging.DEBUG, './test_audrey_startup.log')
-
-    def test_success_service_n_params(self):
-        '''
-        Success case:
-        - Exercise parse_require_config() with valid input
-        '''
-        # Establish valid test data:
-
-        src = '|service|jon1' + \
-            '|parameters|jon_server_ip&' + base64.b64encode('192.168.1.1') + \
-            '|jon_server_ip_2&' + base64.b64encode('192.168.1.2') + \
-            '|jon_server_ip_3&' + base64.b64encode('192.168.1.3') + \
-            '|service|jon2|'
-
-        validation_dict = {'AUDREY_VAR_jon1_jon_server_ip' : '192.168.1.1',
-            'AUDREY_VAR_jon1_jon_server_ip_2' : '192.168.1.2',
-            'AUDREY_VAR_jon1_jon_server_ip_3' : '192.168.1.3' }
-
-        print '\nTest Name: test_success_service_n_params()'
-        print 'Test input:\n' + src
-        print 'Expect: parse_require_config() success'
-
-        # Exersise code segment
-        services = parse_require_config(src)
-
-        # Validate results
-        self.assertEqual(services[0].name, 'jon1')
-        self.assertEqual(services[1].name, 'jon2')
-
-        for service in services:
-            for param in service.params:
-                name_val = param.split('&')
-                env_var  = 'AUDREY_VAR_' + service.name + '_' + name_val[0]
-                print 'name_val[0]:   ' + str(name_val[0])
-                print 'param:         ' + str(param)
-                print 'services.name: ' + str(service.name)
-
-                cmd = ['/usr/bin/printenv', env_var]
-                ret = _run_cmd(cmd)
-                self.assertEqual(ret['out'][:-1], \
-                    validation_dict[env_var])
-
-    def test_success_empty_source(self):
-        '''
-        Success case:
-        - Exercise parse_require_config() with valid empty input
-        '''
-
-        # Establish valid test data:
-        src = '||'
-        print '\nTest Name: test_success_empty_source()'
-        print 'Test input:\n' + src
-        print 'Expect: parse_require_config() success'
-
-        # Exersise code segment
-        services = parse_require_config(src)
-        print 'services: ' + str(services)
-
-        # Validate results
-        self.assertEqual(services, [])
-
-    def test_success_empty_service(self):
-        '''
-        Failure case:
-        - Exercise parse_require_config() with valid input
-        '''
-
-        # Establish valid test data:
-        src = '|service|' + \
-            '|parameters|jon_server_ip&' + base64.b64encode('192.168.1.1') + \
-            '|jon_server_ip_2&' + base64.b64encode('192.168.1.2') + \
-            '|jon_server_ip_3&' + base64.b64encode('192.168.1.3') + \
-            '|service|jon2|'
-
-        validation_dict = {'AUDREY_VAR_jon_server_ip' : '192.168.1.1',
-            'AUDREY_VAR_jon_server_ip_2' : '192.168.1.2',
-            'AUDREY_VAR_jon_server_ip_3' : '192.168.1.3' }
-
-        print '\nTest Name: test_success_empty_service()'
-        print 'Test input:\n' + src
-        print 'Expect: parse_require_config() success'
-
-        # Exersise code segment
-        services = parse_require_config(src)
-
-        # Validate results
-        self.assertEqual(services[0].name, '')
-        self.assertEqual(services[1].name, 'jon2')
-
-        for service in services:
-            for param in service.params:
-                name_val = param.split('&')
-                env_var  = 'AUDREY_VAR_' + name_val[0]
-
-                print 'name_val[0]:   ' + str(name_val[0])
-                print 'param:         ' + str(param)
-                print 'services.name: ' + str(service.name)
-
-                cmd = ['/usr/bin/printenv', env_var]
-                ret = _run_cmd(cmd)
-                self.assertEqual(ret['out'][:-1], \
-                    validation_dict[env_var])
-
-    def test_failure_no_services_name(self):
-        '''
-        Failure case:
-        - Exercise parse_require_config() with valid input
-
-        The slight difference between this test and test_success_empty_services
-        is the success case has an empty service name indicated by "||":
-        |service||paramseters
-
-        and the failure case has no service name:
-        |service|paramseters
-
-        '''
-
-        # Establish valid test data:
-        src = '|service' \
-            '|parameters|jon_server_ip&' + base64.b64encode('192.168.1.1') + \
-            '|jon_server_ip_2&' + base64.b64encode('192.168.1.2') + \
-            '|jon_server_ip_3&' + base64.b64encode('192.168.1.3') + \
-            '|service|jon2|'
-
-        validation_dict = {'AUDREY_VAR_jon_server_ip' : '192.168.1.1',
-            'AUDREY_VAR_jon_server_ip_2' : '192.168.1.2',
-            'AUDREY_VAR_jon_server_ip_3' : '192.168.1.3' }
-
-        print '\nTest Name: test_failure_no_service_names()'
-        print 'Test input:\n' + src
-        print 'Expect: parse_require_config() success'
-
-        # Exersise code segment
-        with self.assertRaises(ASError):
-            print 'parse_require_config returned: ' + \
-                str(parse_require_config(src))
-
-    def test_failure_bad_service_name(self):
-        '''
-        Failure case:
-        - Exercise parse_require_config() with valid input
-        '''
-
-        # Establish valid test data:
-        src = '|service|parameters|'
-        print '\nTest Name: test_failure_bad_service_name()'
-        print 'Test input:\n' + src
-        print 'Expect: parse_require_config() ASError'
-
-        # Exersise code segment
-        with self.assertRaises(ASError):
-            print 'parse_require_config returned: ' + \
-                str(parse_require_config(src))
-
-class TestAudreyStartupDiscovery(unittest.TestCase):
-    def setUp(self):
-        '''
-        Perform required setup including setting up logging.
-        '''
-        setup_logging(logging.DEBUG, 'test_audrey_startup.log')
-        self.cloud_info_file = 'cloud_info'
-        self.condor_addr_file = 'condor_addr'
-        self.condor_uuid_file = 'condor_uuid'
-
-    def tearDown(self):
-        os.remove(self.cloud_info_file)
-        if os.path.exists(self.condor_addr_file):
-            os.remove(self.condor_addr_file)
-        if os.path.exists(self.condor_uuid_file):
-            os.remove(self.condor_uuid_file)
-
-    def test_ec2(self):
-        _write_info_file(self.cloud_info_file, 'EC2')
-        discover_config_server(self.cloud_info_file, http=HttpUnitTest())
-
-    def test_ec2_404(self):
-        _write_info_file(self.cloud_info_file, 'EC2')
-        self.assertRaises(ASError,
-            discover_config_server, self.cloud_info_file, http=HttpUnitTest(),
-            ec2_user_data='http://169.254.169.254/gimmie-404')
-
-    def test_condorcloud(self):
-        _write_info_file(self.condor_addr_file, '1|endpoint|secret')
-        _write_info_file(self.condor_uuid_file, 'key')
-        _write_info_file(self.cloud_info_file, 'CONDORCLOUD')
-        discover_config_server(self.cloud_info_file,
-                               condor_addr_file=self.condor_addr_file,
-                               condor_uuid_file=self.condor_uuid_file)
-
-    def test_rhev(self):
-        _write_info_file(self.cloud_info_file, 'RHEV')
-        self.assertRaises(ASError,
-            discover_config_server, self.cloud_info_file)
-
-    def test_vsphere(self):
-        _write_info_file(self.cloud_info_file, 'VSPHERE')
-        self.assertRaises(ASError,
-            discover_config_server, self.cloud_info_file)
-
-    def test_invalid_user_data_version(self):
-        _write_info_file(self.cloud_info_file, 'EC2')
-        self.assertRaises(ASError,
-            discover_config_server, self.cloud_info_file, http=HttpUnitTest(),
-            ec2_user_data='http://169.254.169.254/no-version-user-data')
-
-    def test_invalid_user_data_no_delim(self):
-        _write_info_file(self.cloud_info_file, 'EC2')
-        self.assertRaises(ASError,
-            discover_config_server, self.cloud_info_file, http=HttpUnitTest(),
-            ec2_user_data='http://169.254.169.254/empty-user-data')
-
-
-class TestAudreyStartupProvidesParameters(unittest.TestCase):
-    '''
-    Class for exercising the parsing of the Provides ParametersConfigs
-    from the CS.
-    '''
-
-    def setUp(self):
-        '''
-        Perform required setup including setting up logging.
-        '''
-        setup_logging(logging.DEBUG, './test_audrey_startup.log')
-
-    def test_success_parameters(self):
-        '''
-        Success case:
-        - Exercise parse_provides_params() and generate_provides()
-          with valid input
-        '''
-
-        # Establish valid test data:
-        src = '|operatingsystem&is_virtual|'
-
-        print '\nTest Name: test_success_parameters()'
-        print 'Test input:\n' + src
-        print 'Expect: parse_provides_params() success'
-
-        expected_params_list = ['operatingsystem', 'is_virtual']
-
-        # Exersise code segment
-        params_list = parse_provides_params(src)
-        provides = generate_provides(src)
-        print 'src: ' + str(src)
-        print 'params_list: ' + str(params_list)
-        print 'provides: ' + str(provides)
-        print 'len(provides): ' + str(len(provides))
-
-        # Validate results
-        self.assertEqual(params_list, expected_params_list)
-
-        # The values are not validatable because they are unpredictable
-        # but all the expected parameters should be returned.
-        # Note: %7C is the encoded |, %26 is the encoded &
-        self.assertTrue('audrey_data=%7Coperatingsystem' in provides)
-        for param in expected_params_list:
-            self.assertTrue('%7C' + str(param) in provides)
-
-    def test_success_no_params(self):
-        '''
-        Success case:
-        - Exercise parse_provides_params() and generate_provides()
-          with valid input
-        - Containging an unavailable parameter
-        '''
-
-        # Establish valid test data:
-        src = '|uptime_days&unavailable_dogs&ipaddress|'
-
-        print '\nTest Name: test_success_no_params()'
-        print 'Test input:\n' + src
-        print 'Expect: parse_provides_params() success'
-
-        expected_params_list = ['uptime_days', 'unavailable_dogs', 'ipaddress']
-
-        # Exersise code segment
-        params_list = parse_provides_params(src)
-        provides = generate_provides(src)
-        print 'src: ' + str(src)
-        print 'params_list: ' + str(params_list)
-        print 'provides: ' + str(provides)
-
-        # Validate results
-        self.assertEqual(params_list, expected_params_list)
-
-        # The values are not validatable because they are unpredictable
-        # but all the expected parameters should be returned.
-        # Note: %7C is the encoded |, %26 is the encoded &
-        for param in expected_params_list:
-            self.assertTrue('%7C' + str(param) in provides)
-
-        # Confirm unavailable parameters return an empty string.
-        self.assertTrue('%7C' + 'unavailable_dogs' + '%26%7C' in provides)
-
-    def test_success_one_parameters(self):
-        '''
-        Success case:
-        - Exercise parse_provides_params() and generate_provides()
-          with valid input
-        - with only one parameter
-        '''
-
-        # Establish valid test data:
-        src = '|uptime_days|'
-
-        print '\nTest Name: test_success_parameters()'
-        print 'Test input:\n' + src
-        print 'Expect: parse_provides_params() success'
-
-        expected_params_list = ['uptime_days']
-
-        # Exersise code segment
-        params_list = parse_provides_params(src)
-        provides = generate_provides(src)
-        print 'src: ' + str(src)
-        print 'params_list: ' + str(params_list)
-        print 'provides: ' + str(provides)
-
-        # Validate results
-        self.assertEqual(params_list, expected_params_list)
-
-        # The values are not validatable because they are unpredictable
-        # but all the expected parameters should be returned.
-        # Note: %7C is the encoded |, %26 is the encoded &
-        for param in expected_params_list:
-            self.assertTrue('%7C' + str(param) in provides)
-
-    def test_success_one_parameter(self):
-        '''
-        Success case:
-        - Exercise parse_provides_params() and generate_provides()
-          with valid input
-        - With only one parameter which is unavailable
-        '''
-
-        # Establish valid test data:
-        src = '|unavailable_dogs|'
-
-        print '\nTest Name: test_success_one_parameter()'
-        print 'Test input:\n' + src
-        print 'Expect: parse_provides_params() success'
-
-        expected_params_list = ['unavailable_dogs']
-
-        # Exersise code segment
-        params_list = parse_provides_params(src)
-        provides = generate_provides(src)
-        print 'src: ' + str(src)
-        print 'params_list: ' + str(params_list)
-        print 'provides: ' + str(provides)
-
-        # Validate results
-        self.assertEqual(params_list, expected_params_list)
-
-        # The values are not validatable because they are unpredictable
-        # but all the expected parameters should be returned.
-        # Note: %7C is the encoded |, %26 is the encoded &
-        for param in expected_params_list:
-            self.assertTrue('%7C' + str(param) in provides)
-
-        # Confirm unavailable parameters return an empty string.
-        self.assertTrue('%7C' + 'unavailable_dogs' + '%26%7C' in provides)
-
-    def test_failure_missing_delimiter(self):
-        '''
-        Failure case:
-        - Exercise parse_provides_params() and generate_provides()
-          with invalid input
-        - missing leading delimiter
-        '''
-
-        # Establish valid test data:
-        src = 'unavailable_dogs|'
-
-        print '\nTest Name: test_failure_missing_delimiter()'
-        print 'Test input:\n' + src
-        print 'Expect: parse_require_config() ASError'
-
-        expected_params_list = ['unavailable_dogs']
-
-        # Exersise code segment and validate results
-        with self.assertRaises(ASError):
-            params_list = parse_provides_params(src)
-
-        with self.assertRaises(ASError):
-            provides = generate_provides(src)
-
-class TestConfigServerClient(unittest.TestCase):
-    '''
-    Class for exercising the gets and put to and from the CS
-    '''
-
-    def setUp(self):
-        '''
-        If the cloud info file is not present assume running in a
-        UNITTEST environment. This will allow for exercising some
-        of the code without having to be running in a cloud VM.
-
-        Set up logging.
-        '''
-
-        setup_logging(logging.DEBUG, './test_audrey_startup.log')
-
-        # Create the client Object
-        self.cs_client = CSClient(**DUMMY_CS_CONFIG)
-        self.cs_client.http = HttpUnitTest()
-
-    def tearDown(self):
-        pass
-
-    def test_success_get_cs_configs(self):
-        '''
-        Success case:
-        - Exercise get_cs_configs()
-        '''
-        print '\n\n--- Test Name: test_success_get_cs_configs ---'
-
-        self.cs_client.get_cs_configs()
-
-        # Add asserts
-        print 'test_success_get_cs_configs() Add asserts'
-        print 'self.cs_client : START \n' + str(self.cs_client) + \
-            '\nself.cs_client : END'
-
-    def test_success_get_cs_tooling(self):
-        '''
-        Success case:
-        - Exercise get_cs_tooling()
-        '''
-        self.cs_client.get_cs_tooling()
-
-    def test_success_get_cs_params(self):
-        '''
-        Success case:
-        - Exercise get_cs_params()
-        '''
-        print '\n\n--- Test Name: test_success_get_cs_params ---'
-
-        self.cs_client.get_cs_params()
-
-        # Add asserts
-        print 'test_success_get_cs_params() Add asserts'
-        print 'self.cs_client : START \n' + str(self.cs_client) + \
-            '\nself.cs_client : END'
-
-    def test_success_get_cs_confs_n_params(self):
-        '''
-        Success case:
-        - Exercise get_cs_configs() and get_cs_params()
-        '''
-        print '\n\n--- Test Name: test_success_get_cs_confs_and_params ---'
-
-        self.cs_client.get_cs_configs()
-        self.cs_client.get_cs_params()
-
-        # Add asserts
-        print 'test_success_get_cs_confs_n_params() Add asserts'
-        print 'self.cs_client : START \n' + str(self.cs_client) + \
-            '\nself.cs_client : END'
-        print 'test_success_get_cs_confs_n_params() Add asserts'
-
-    def test_success_put_cs_params_values(self):
-        '''
-        Success case:
-        - Exercise put_cs_params_values()
-        '''
-        self.cs_client.put_cs_params_values('')
-
-    def test_error_http_status(self):
-        '''
-        Success case:
-        - Exercise put_cs_params_values()
-        '''
-        self.assertRaises(ASError, self.cs_client._validate_http_status, 401)
-
-class TestAudreyScript(unittest.TestCase):
-    '''
-    Class for exercising the full audrey script functionality
-    '''
-
-    def setUp(self):
-        '''
-        Perform required setup including setting up logging.
-
-        This test currently require to be run in a cloud VM
-        with a live Config Server.
-        '''
-        setup_logging(logging.DEBUG, './test_audrey_startup.log')
-        # make a copy of argv
-        self.argv = list(sys.argv)
-
-    def tearDown(self):
-        # replace argv
-        sys.argv = list(self.argv)
-
-    def test_audrey_script_main(self):
-        '''
-        Perform what the audrey script will do.
-        '''
-        cloud_info_file = 'cloud_info'
-        sys.argv.extend(['-p'])
-        _write_info_file(cloud_info_file, 'EC2')
-        audrey_script_main(HttpUnitTest())
-        os.remove(cloud_info_file)
-
-    def test_fail_audrey_script_main(self):
-        '''
-        Perform what the audrey script will do.
-        '''
-        self.assertRaises(ASError, audrey_script_main)
-
-    def test_empty_gen_env(self):
-        self.assertRaises(ASError, gen_env, '', '')
-
-    # doesn't actually test what I wanted it to.
-    #def test_parse_require_config(self):
-    #    self.assertRaises(ASError, parse_require_config, '')
-
-if __name__ == '__main__':
-
-    setup_logging(logging.DEBUG, logfile_name='./test_audrey_startup.log')
-    unittest.main()
-- 
1.7.7.3




More information about the aeolus-devel mailing list