[PATCH 1/2] swtiching shell env setup and renaming config_server* things to csclient things

Dan Radez dradez at redhat.com
Tue Feb 7 03:42:10 UTC 2012


---
 agent/bin/audrey                           |    2 +-
 agent/src/audrey/agent.py.in               |    8 +-
 agent/src/audrey/config_server/__init__.py |  269 --------------------
 agent/src/audrey/config_server/client.py   |  216 ----------------
 agent/src/audrey/config_server/service.py  |   48 ----
 agent/src/audrey/config_server/tooling.py  |  268 -------------------
 agent/src/audrey/csclient/__init__.py      |  269 ++++++++++++++++++++
 agent/src/audrey/csclient/client.py        |  216 ++++++++++++++++
 agent/src/audrey/csclient/service.py       |   48 ++++
 agent/src/audrey/csclient/tooling.py       |  268 +++++++++++++++++++
 agent/test_audrey_agent.py                 |    5 +-
 agent/tests/agent.py                       |   14 +-
 agent/tests/config_server.py               |  301 ----------------------
 agent/tests/config_server_client.py        |  101 --------
 agent/tests/csclient.py                    |  381 ++++++++++++++++++++++++++++
 15 files changed, 1196 insertions(+), 1218 deletions(-)
 delete mode 100644 agent/src/audrey/config_server/__init__.py
 delete mode 100644 agent/src/audrey/config_server/client.py
 delete mode 100644 agent/src/audrey/config_server/service.py
 delete mode 100644 agent/src/audrey/config_server/tooling.py
 create mode 100644 agent/src/audrey/csclient/__init__.py
 create mode 100644 agent/src/audrey/csclient/client.py
 create mode 100644 agent/src/audrey/csclient/service.py
 create mode 100644 agent/src/audrey/csclient/tooling.py
 delete mode 100644 agent/tests/config_server.py
 delete mode 100644 agent/tests/config_server_client.py
 create mode 100644 agent/tests/csclient.py

diff --git a/agent/bin/audrey b/agent/bin/audrey
index 3cb939b..3fde1e4 100755
--- a/agent/bin/audrey
+++ b/agent/bin/audrey
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 '''
 *
 *   Copyright [2011] [Red Hat, Inc.]
diff --git a/agent/src/audrey/agent.py.in b/agent/src/audrey/agent.py.in
index cf8a5ce..a7f3dd3 100644
--- a/agent/src/audrey/agent.py.in
+++ b/agent/src/audrey/agent.py.in
@@ -47,10 +47,10 @@ from time import sleep
 
 from audrey import ASError
 from audrey import user_data
-from audrey.config_server.client import CSClient
-from audrey.config_server.tooling import ConfigTooling
-from audrey.config_server import parse_require_config
-from audrey.config_server import generate_provides
+from audrey.csclient.client import CSClient
+from audrey.csclient.tooling import ConfigTooling
+from audrey.csclient import parse_require_config
+from audrey.csclient import generate_provides
 from audrey import setup_logging
 
 SLEEP_SECS = 10
diff --git a/agent/src/audrey/config_server/__init__.py b/agent/src/audrey/config_server/__init__.py
deleted file mode 100644
index ea0448b..0000000
--- a/agent/src/audrey/config_server/__init__.py
+++ /dev/null
@@ -1,269 +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.
-*
-'''
-
-import os
-import logging
-import base64
-import urllib
-
-from collections import deque
-
-from audrey import ASError
-from audrey.config_server.service import ServiceParams
-from audrey.shell import run_cmd
-from audrey.shell import get_system_info
-
-logger = logging.getLogger('Audrey')
-
-#
-# 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 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)})
diff --git a/agent/src/audrey/config_server/client.py b/agent/src/audrey/config_server/client.py
deleted file mode 100644
index 33283c4..0000000
--- a/agent/src/audrey/config_server/client.py
+++ /dev/null
@@ -1,216 +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.
-*
-'''
-
-import shutil
-import logging
-import tempfile
-import oauth2 as oauth
-
-from audrey import ASError
-
-logger = logging.getLogger('Audrey')
-
-TOOLING_URL = 'files'
-CONFIGS_URL = 'configs'
-PARAMS_URL = 'params'
-
-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 = 1
-        self.cs_endpoint = endpoint
-        self.cs_oauth_key = oauth_key
-        self.cs_oauth_secret = oauth_secret
-        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.
-        '''
-        endpoint = '%s/%s' % (self.cs_endpoint.lower(), url_type)
-        if url_type != 'version':
-            endpoint = '%s/%s/%s' % (endpoint, self.version, self.cs_oauth_key)
-        return endpoint
-
-    def _get(self, url, headers=None):
-        '''
-        Description:
-            Issue the http get to the the Config Server.
-        '''
-        try:
-            return self.http.request(url, method='GET', headers=headers)
-        except Exception, e:
-            return (e, None)
-
-    def _put(self, url, body=None, headers=None):
-        '''
-        Description:
-            Issue the http put to the the Config Server.
-        '''
-        try:
-            return self.http.request(url, method='PUT',
-                                body=body, headers=headers)
-        except Exception, e:
-            return (e, None)
-
-    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_URL)
-        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_URL)
-        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_URL)
-        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(TOOLING_URL)
-        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
diff --git a/agent/src/audrey/config_server/service.py b/agent/src/audrey/config_server/service.py
deleted file mode 100644
index 22b0aac..0000000
--- a/agent/src/audrey/config_server/service.py
+++ /dev/null
@@ -1,48 +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.
-*
-'''
-
-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))
diff --git a/agent/src/audrey/config_server/tooling.py b/agent/src/audrey/config_server/tooling.py
deleted file mode 100644
index 0878372..0000000
--- a/agent/src/audrey/config_server/tooling.py
+++ /dev/null
@@ -1,268 +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.
-*
-'''
-
-import os
-import logging
-import tarfile
-
-from audrey import ASError
-
-TOOLING_DIR = '/var/audrey/tooling/'
-
-logger = logging.getLogger('Audrey')
-
-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 (tarfile.TarError, tarfile.ReadError, tarfile.CompressionError, \
-                tarfile.StreamError, tarfile.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))
-
diff --git a/agent/src/audrey/csclient/__init__.py b/agent/src/audrey/csclient/__init__.py
new file mode 100644
index 0000000..021fd54
--- /dev/null
+++ b/agent/src/audrey/csclient/__init__.py
@@ -0,0 +1,269 @@
+'''
+*
+*   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.
+*
+'''
+
+import os
+import logging
+import base64
+import urllib
+
+from collections import deque
+
+from audrey import ASError
+from audrey.csclient.service import ServiceParams
+from audrey.shell import run_cmd
+from audrey.shell import get_system_info
+
+logger = logging.getLogger('Audrey')
+
+#
+# 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 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)})
diff --git a/agent/src/audrey/csclient/client.py b/agent/src/audrey/csclient/client.py
new file mode 100644
index 0000000..33283c4
--- /dev/null
+++ b/agent/src/audrey/csclient/client.py
@@ -0,0 +1,216 @@
+'''
+*
+*   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.
+*
+'''
+
+import shutil
+import logging
+import tempfile
+import oauth2 as oauth
+
+from audrey import ASError
+
+logger = logging.getLogger('Audrey')
+
+TOOLING_URL = 'files'
+CONFIGS_URL = 'configs'
+PARAMS_URL = 'params'
+
+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 = 1
+        self.cs_endpoint = endpoint
+        self.cs_oauth_key = oauth_key
+        self.cs_oauth_secret = oauth_secret
+        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.
+        '''
+        endpoint = '%s/%s' % (self.cs_endpoint.lower(), url_type)
+        if url_type != 'version':
+            endpoint = '%s/%s/%s' % (endpoint, self.version, self.cs_oauth_key)
+        return endpoint
+
+    def _get(self, url, headers=None):
+        '''
+        Description:
+            Issue the http get to the the Config Server.
+        '''
+        try:
+            return self.http.request(url, method='GET', headers=headers)
+        except Exception, e:
+            return (e, None)
+
+    def _put(self, url, body=None, headers=None):
+        '''
+        Description:
+            Issue the http put to the the Config Server.
+        '''
+        try:
+            return self.http.request(url, method='PUT',
+                                body=body, headers=headers)
+        except Exception, e:
+            return (e, None)
+
+    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_URL)
+        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_URL)
+        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_URL)
+        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(TOOLING_URL)
+        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
diff --git a/agent/src/audrey/csclient/service.py b/agent/src/audrey/csclient/service.py
new file mode 100644
index 0000000..22b0aac
--- /dev/null
+++ b/agent/src/audrey/csclient/service.py
@@ -0,0 +1,48 @@
+'''
+*
+*   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.
+*
+'''
+
+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))
diff --git a/agent/src/audrey/csclient/tooling.py b/agent/src/audrey/csclient/tooling.py
new file mode 100644
index 0000000..0878372
--- /dev/null
+++ b/agent/src/audrey/csclient/tooling.py
@@ -0,0 +1,268 @@
+'''
+*
+*   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.
+*
+'''
+
+import os
+import logging
+import tarfile
+
+from audrey import ASError
+
+TOOLING_DIR = '/var/audrey/tooling/'
+
+logger = logging.getLogger('Audrey')
+
+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 (tarfile.TarError, tarfile.ReadError, tarfile.CompressionError, \
+                tarfile.StreamError, tarfile.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))
+
diff --git a/agent/test_audrey_agent.py b/agent/test_audrey_agent.py
index abfdcbf..570795b 100644
--- a/agent/test_audrey_agent.py
+++ b/agent/test_audrey_agent.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 '''
 *
 *   Copyright [2011] [Red Hat, Inc.]
@@ -30,8 +30,7 @@ sys.path.append('src')
 ####
 from tests.agent import *
 from tests.shell import *
-from tests.config_server_client import *
-from tests.config_server import *
+from tests.csclient import *
 from tests.user_data import *
 
 ####
diff --git a/agent/tests/agent.py b/agent/tests/agent.py
index 6f1fc68..0ca549d 100644
--- a/agent/tests/agent.py
+++ b/agent/tests/agent.py
@@ -27,7 +27,7 @@ from audrey import ASError
 from audrey import setup_logging
 from audrey.shell import run_cmd
 from audrey.agent import main
-from audrey.config_server import gen_env
+from audrey.csclient import gen_env
 
 from tests.mocks import CLOUD_INFO_FILE
 from tests.user_data import _write_file
@@ -39,9 +39,9 @@ class TestAudreyAgent(unittest.TestCase):
 
     def setUp(self):
         audrey.user_data_ec2.EC2_USER_DATA_URL='http://169.254.169.254/latest/user-data'
-        audrey.config_server.client.TOOLING_URL = 'files'
-        audrey.config_server.client.PARAMS_URL = 'params'
-        audrey.config_server.client.CONFIGS_URL = 'configs'
+        audrey.csclient.client.TOOLING_URL = 'files'
+        audrey.csclient.client.PARAMS_URL = 'params'
+        audrey.csclient.client.CONFIGS_URL = 'configs'
         # make a copy of argv
         self.argv = list(sys.argv)
         # clean out args before you run me
@@ -110,15 +110,15 @@ class TestAudreyAgent(unittest.TestCase):
 
     def test_404_from_tooling(self):
         _write_file(CLOUD_INFO_FILE, 'EC2')
-        audrey.config_server.client.TOOLING_URL = 'gimmie-404'
+        audrey.csclient.client.TOOLING_URL = 'gimmie-404'
         main()
 
     def test_404_from_params(self):
         _write_file(CLOUD_INFO_FILE, 'EC2')
-        audrey.config_server.client.PARAMS_URL = 'gimmie-404'
+        audrey.csclient.client.PARAMS_URL = 'gimmie-404'
         main()
 
     def test_404_from_configs(self):
         _write_file(CLOUD_INFO_FILE, 'EC2')
-        audrey.config_server.client.CONFIGS_URL = 'gimmie-404'
+        audrey.csclient.client.CONFIGS_URL = 'gimmie-404'
         self.assertRaises(ASError, main)
diff --git a/agent/tests/config_server.py b/agent/tests/config_server.py
deleted file mode 100644
index c438d10..0000000
--- a/agent/tests/config_server.py
+++ /dev/null
@@ -1,301 +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.
-*
-'''
-
-import unittest
-import base64
-
-from audrey import ASError
-from audrey.shell import run_cmd
-from audrey.config_server.tooling import ConfigTooling
-from audrey.config_server import parse_require_config
-from audrey.config_server import parse_provides_params
-from audrey.config_server import generate_provides
-
-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 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' }
-
-        # 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]
-                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
-        '''
-
-        src = '||'
-        services = parse_require_config(src)
-        self.assertEqual(services, [])
-
-    def test_success_empty_service(self):
-        '''
-        Failure case:
-        - Exercise parse_require_config() with valid input
-        '''
-
-        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' }
-
-        services = parse_require_config(src)
-        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]
-                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' }
-
-        self.assertRaises(ASError, parse_require_config, src)
-
-    def test_failure_bad_service_name(self):
-        '''
-        Failure case:
-        - Exercise parse_require_config() with valid input
-        '''
-
-        src = '|service|parameters|'
-        self.assertRaises(ASError, parse_require_config, src)
-
-    def test_failure_service_tag_not_found(self):
-        '''
-        Failure Case:
-        - |service| not in src to parse_require_config()
-        '''
-        src = '|notservice|blah|'
-        self.assertRaises(ASError, parse_require_config, src)
-
-    def test_failure_no_amp_delim(self):
-        '''
-        Failure Case:
-        - no delim in param token
-        '''
-        src = '|service|blah|parameters|blah|'
-        self.assertRaises(ASError, parse_require_config, src)
-
-
-class TestAudreyStartupProvidesParameters(unittest.TestCase):
-    '''
-    Class for exercising the parsing of the Provides ParametersConfigs
-    from the CS.
-    '''
-
-    def test_success_parameters(self):
-        '''
-        Success case:
-        - Exercise parse_provides_params() and generate_provides()
-          with valid input
-        '''
-
-        src = '|operatingsystem&is_virtual|'
-        expected_params_list = ['operatingsystem', 'is_virtual']
-
-        params_list = parse_provides_params(src)
-        provides = generate_provides(src)
-        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_empty_params(self):
-        '''
-        Success case:
-        - Exercise parse_provides_params() and generate_provides()
-          with valid demlims but empty input
-        '''
-        src = '||'
-        params_list = parse_provides_params(src)
-        provides = generate_provides(src)
-        self.assertEqual(params_list, [''])
-        self.assertEqual(provides, 'audrey_data=%7C%26%7C')
-
-    def test_success_no_params(self):
-        '''
-        Success case:
-        - Exercise parse_provides_params() and generate_provides()
-          with valid input
-        - Containging an unavailable parameter
-        '''
-
-        src = '|uptime_days&unavailable_dogs&ipaddress|'
-        expected_params_list = ['uptime_days', 'unavailable_dogs', 'ipaddress']
-
-        params_list = parse_provides_params(src)
-        provides = generate_provides(src)
-
-        # 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|'
-        expected_params_list = ['uptime_days']
-
-        # Exersise code segment
-        params_list = parse_provides_params(src)
-        provides = generate_provides(src)
-
-        # 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
-        '''
-
-        src = '|unavailable_dogs|'
-        expected_params_list = ['unavailable_dogs']
-
-        params_list = parse_provides_params(src)
-        provides = generate_provides(src)
-        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
-        '''
-
-        src = 'unavailable_dogs|'
-        expected_params_list = ['unavailable_dogs']
-
-        self.assertRaises(ASError, parse_provides_params, src)
-        self.assertRaises(ASError, generate_provides, src)
diff --git a/agent/tests/config_server_client.py b/agent/tests/config_server_client.py
deleted file mode 100644
index 4a75fc0..0000000
--- a/agent/tests/config_server_client.py
+++ /dev/null
@@ -1,101 +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.
-*
-'''
-
-import unittest
-
-from audrey import ASError
-from audrey.config_server.client import CSClient
-
-from tests.mocks import HttpUnitTest
-
-DUMMY_CS_CONFIG = {'endpoint': 'http://example.com/',
-                   'oauth_key': 'oauthConsumer',
-                   'oauth_secret': 'oauthSecret',}
-
-class TestAudreyCSClient(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.
-        '''
-        # Create the client Object
-        self.cs_client = CSClient(**DUMMY_CS_CONFIG)
-        self.cs_client.http = HttpUnitTest()
-
-    def test_success_get_cs_configs(self):
-        '''
-        Success case:
-        - Exercise get_cs_configs()
-        '''
-        self.cs_client.get_cs_configs()
-
-    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()
-        '''
-        self.cs_client.get_cs_params()
-
-    def test_success_get_cs_confs_n_params(self):
-        '''
-        Success case:
-        - Exercise get_cs_configs() and get_cs_params()
-        '''
-        self.cs_client.get_cs_configs()
-        self.cs_client.get_cs_params()
-
-
-    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:
-        - Get a 401
-        '''
-        self.assertRaises(ASError, self.cs_client._validate_http_status, 401)
-
-    def test_catch_get_exception(self):
-        '''
-        Success case:
-        - get fails but audrey recovers
-        '''
-        self.cs_client._get('http://hostname/raiseException')
-
-    def test_catch_put_exception(self):
-        '''
-        Success case:
-        - put fails but audrey recovers
-        '''
-        self.cs_client._put('http://hostname/raiseException')
diff --git a/agent/tests/csclient.py b/agent/tests/csclient.py
new file mode 100644
index 0000000..24b2a13
--- /dev/null
+++ b/agent/tests/csclient.py
@@ -0,0 +1,381 @@
+'''
+*
+*   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.
+*
+'''
+
+import unittest
+import base64
+
+from audrey import ASError
+from audrey.shell import run_cmd
+from audrey.csclient.client import CSClient
+from audrey.csclient.tooling import ConfigTooling
+from audrey.csclient import parse_require_config
+from audrey.csclient import parse_provides_params
+from audrey.csclient import generate_provides
+
+from tests.mocks import HttpUnitTest
+
+DUMMY_CS_CONFIG = {'endpoint': 'http://example.com/',
+                   'oauth_key': 'oauthConsumer',
+                   'oauth_secret': 'oauthSecret',}
+
+class TestAudreyCSClient(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.
+        '''
+        # Create the client Object
+        self.cs_client = CSClient(**DUMMY_CS_CONFIG)
+        self.cs_client.http = HttpUnitTest()
+
+    def test_success_get_cs_configs(self):
+        '''
+        Success case:
+        - Exercise get_cs_configs()
+        '''
+        self.cs_client.get_cs_configs()
+
+    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()
+        '''
+        self.cs_client.get_cs_params()
+
+    def test_success_get_cs_confs_n_params(self):
+        '''
+        Success case:
+        - Exercise get_cs_configs() and get_cs_params()
+        '''
+        self.cs_client.get_cs_configs()
+        self.cs_client.get_cs_params()
+
+
+    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:
+        - Get a 401
+        '''
+        self.assertRaises(ASError, self.cs_client._validate_http_status, 401)
+
+    def test_catch_get_exception(self):
+        '''
+        Success case:
+        - get fails but audrey recovers
+        '''
+        self.cs_client._get('http://hostname/raiseException')
+
+    def test_catch_put_exception(self):
+        '''
+        Success case:
+        - put fails but audrey recovers
+        '''
+        self.cs_client._put('http://hostname/raiseException')
+
+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 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' }
+
+        # 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]
+                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
+        '''
+
+        src = '||'
+        services = parse_require_config(src)
+        self.assertEqual(services, [])
+
+    def test_success_empty_service(self):
+        '''
+        Failure case:
+        - Exercise parse_require_config() with valid input
+        '''
+
+        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' }
+
+        services = parse_require_config(src)
+        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]
+                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' }
+
+        self.assertRaises(ASError, parse_require_config, src)
+
+    def test_failure_bad_service_name(self):
+        '''
+        Failure case:
+        - Exercise parse_require_config() with valid input
+        '''
+
+        src = '|service|parameters|'
+        self.assertRaises(ASError, parse_require_config, src)
+
+    def test_failure_service_tag_not_found(self):
+        '''
+        Failure Case:
+        - |service| not in src to parse_require_config()
+        '''
+        src = '|notservice|blah|'
+        self.assertRaises(ASError, parse_require_config, src)
+
+    def test_failure_no_amp_delim(self):
+        '''
+        Failure Case:
+        - no delim in param token
+        '''
+        src = '|service|blah|parameters|blah|'
+        self.assertRaises(ASError, parse_require_config, src)
+
+
+class TestAudreyStartupProvidesParameters(unittest.TestCase):
+    '''
+    Class for exercising the parsing of the Provides ParametersConfigs
+    from the CS.
+    '''
+
+    def test_success_parameters(self):
+        '''
+        Success case:
+        - Exercise parse_provides_params() and generate_provides()
+          with valid input
+        '''
+
+        src = '|operatingsystem&is_virtual|'
+        expected_params_list = ['operatingsystem', 'is_virtual']
+
+        params_list = parse_provides_params(src)
+        provides = generate_provides(src)
+        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_empty_params(self):
+        '''
+        Success case:
+        - Exercise parse_provides_params() and generate_provides()
+          with valid demlims but empty input
+        '''
+        src = '||'
+        params_list = parse_provides_params(src)
+        provides = generate_provides(src)
+        self.assertEqual(params_list, [''])
+        self.assertEqual(provides, 'audrey_data=%7C%26%7C')
+
+    def test_success_no_params(self):
+        '''
+        Success case:
+        - Exercise parse_provides_params() and generate_provides()
+          with valid input
+        - Containging an unavailable parameter
+        '''
+
+        src = '|uptime_days&unavailable_dogs&ipaddress|'
+        expected_params_list = ['uptime_days', 'unavailable_dogs', 'ipaddress']
+
+        params_list = parse_provides_params(src)
+        provides = generate_provides(src)
+
+        # 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|'
+        expected_params_list = ['uptime_days']
+
+        # Exersise code segment
+        params_list = parse_provides_params(src)
+        provides = generate_provides(src)
+
+        # 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
+        '''
+
+        src = '|unavailable_dogs|'
+        expected_params_list = ['unavailable_dogs']
+
+        params_list = parse_provides_params(src)
+        provides = generate_provides(src)
+        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
+        '''
+
+        src = 'unavailable_dogs|'
+        expected_params_list = ['unavailable_dogs']
+
+        self.assertRaises(ASError, parse_provides_params, src)
+        self.assertRaises(ASError, generate_provides, src)
-- 
1.7.7.6




More information about the aeolus-devel mailing list