commit 16977c1ac2bcb6a7b466e3c6cd0b6a8e3a9b59f4
Author: cindy oneill <cindy.o-neill@hp.com>
Date:   Mon Mar 17 16:15:44 2014 -0600

    Initial monclient files.  Help, and versioning is working.

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f2ec436
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+.coverage
+.venv
+cover
+*.pyc
+AUTHORS
+build
+dist
+ChangeLog
+run_tests.err.log
+.tox
+doc/source/api
+*.egg
+monclient/versioninfo
+*.egg-info
+*.log
+.testrepository
+.pydevproject
+.project
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..9570c67
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,6 @@
+Python bindings to the Monitoring API
+=============================================
+
+This is a client library for Monitoring built on the Heat orchestration API. It
+provides a Python API (the ``monclient`` module) and a command-line tool
+(``mon``).
diff --git a/monclient/__init__.py b/monclient/__init__.py
new file mode 100644
index 0000000..a2c5707
--- /dev/null
+++ b/monclient/__init__.py
@@ -0,0 +1,16 @@
+#   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 pbr.version
+
+
+__version__ = pbr.version.VersionInfo('python-monclient').version_string()
diff --git a/monclient/client.py b/monclient/client.py
new file mode 100644
index 0000000..61b911f
--- /dev/null
+++ b/monclient/client.py
@@ -0,0 +1,19 @@
+#    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.
+
+from monclient.common import utils
+
+
+def Client(version, *args, **kwargs):
+    module = utils.import_versioned_module(version, 'client')
+    client_class = getattr(module, 'Client')
+    return client_class(*args, **kwargs)
diff --git a/monclient/common/__init__.py b/monclient/common/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/monclient/common/http.py b/monclient/common/http.py
new file mode 100644
index 0000000..02beba7
--- /dev/null
+++ b/monclient/common/http.py
@@ -0,0 +1,261 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    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 copy
+import logging
+import os
+import socket
+
+import requests
+import six
+
+from monclient import exc
+from monclient.openstack.common import jsonutils
+from monclient.openstack.common.py3kcompat import urlutils
+from monclient.openstack.common import strutils
+
+LOG = logging.getLogger(__name__)
+if not LOG.handlers:
+    LOG.addHandler(logging.StreamHandler())
+USER_AGENT = 'python-monclient'
+CHUNKSIZE = 1024 * 64  # 64kB
+
+
+def get_system_ca_file():
+    """Return path to system default CA file."""
+    # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
+    # Suse, FreeBSD/OpenBSD, MacOSX, and the bundled ca
+    ca_path = ['/etc/ssl/certs/ca-certificates.crt',
+               '/etc/pki/tls/certs/ca-bundle.crt',
+               '/etc/ssl/ca-bundle.pem',
+               '/etc/ssl/cert.pem',
+               '/System/Library/OpenSSL/certs/cacert.pem',
+               requests.certs.where()]
+    for ca in ca_path:
+        LOG.debug("Looking for ca file %s", ca)
+        if os.path.exists(ca):
+            LOG.debug("Using ca file %s", ca)
+            return ca
+    LOG.warn("System ca file could not be found.")
+
+
+class HTTPClient(object):
+
+    def __init__(self, endpoint, **kwargs):
+        self.endpoint = endpoint
+        self.auth_url = kwargs.get('auth_url')
+        self.auth_token = kwargs.get('token')
+        self.username = kwargs.get('username')
+        self.password = kwargs.get('password')
+        self.region_name = kwargs.get('region_name')
+        self.include_pass = kwargs.get('include_pass')
+        self.endpoint_url = endpoint
+
+        self.cert_file = kwargs.get('cert_file')
+        self.key_file = kwargs.get('key_file')
+
+        self.ssl_connection_params = {
+            'ca_file': kwargs.get('ca_file'),
+            'cert_file': kwargs.get('cert_file'),
+            'key_file': kwargs.get('key_file'),
+            'insecure': kwargs.get('insecure'),
+        }
+
+        self.verify_cert = None
+        if urlutils.urlparse(endpoint).scheme == "https":
+            if kwargs.get('insecure'):
+                self.verify_cert = False
+            else:
+                self.verify_cert = kwargs.get('ca_file', get_system_ca_file())
+
+    def log_curl_request(self, method, url, kwargs):
+        curl = ['curl -i -X %s' % method]
+
+        for (key, value) in kwargs['headers'].items():
+            header = '-H \'%s: %s\'' % (strutils.safe_decode(key),
+                                        strutils.safe_decode(value))
+            curl.append(header)
+
+        conn_params_fmt = [
+            ('key_file', '--key %s'),
+            ('cert_file', '--cert %s'),
+            ('ca_file', '--cacert %s'),
+        ]
+        for (key, fmt) in conn_params_fmt:
+            value = self.ssl_connection_params.get(key)
+            if value:
+                curl.append(fmt % value)
+
+        if self.ssl_connection_params.get('insecure'):
+            curl.append('-k')
+
+        if 'data' in kwargs:
+            curl.append('-d \'%s\'' % kwargs['data'])
+
+        curl.append('%s%s' % (self.endpoint, url))
+        LOG.debug(' '.join(curl))
+
+    @staticmethod
+    def log_http_response(resp):
+        status = (resp.raw.version / 10.0, resp.status_code, resp.reason)
+        dump = ['\nHTTP/%.1f %s %s' % status]
+        dump.extend(['%s: %s' % (k, v) for k, v in resp.headers.items()])
+        dump.append('')
+        if resp.content:
+            content = resp.content
+            if isinstance(content, six.binary_type):
+                content = content.decode()
+            dump.extend([content, ''])
+        LOG.debug('\n'.join(dump))
+
+    def _http_request(self, url, method, **kwargs):
+        """Send an http request with the specified characteristics.
+
+        Wrapper around requests.request to handle tasks such as
+        setting headers and error handling.
+        """
+        # Copy the kwargs so we can reuse the original in case of redirects
+        kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
+        kwargs['headers'].setdefault('User-Agent', USER_AGENT)
+        if self.auth_token:
+            kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
+        else:
+            kwargs['headers'].update(self.credentials_headers())
+        if self.auth_url:
+            kwargs['headers'].setdefault('X-Auth-Url', self.auth_url)
+        if self.region_name:
+            kwargs['headers'].setdefault('X-Region-Name', self.region_name)
+        if self.include_pass and not 'X-Auth-Key' in kwargs['headers']:
+            kwargs['headers'].update(self.credentials_headers())
+
+        self.log_curl_request(method, url, kwargs)
+
+        if self.cert_file and self.key_file:
+            kwargs['cert'] = (self.cert_file, self.key_file)
+
+        if self.verify_cert is not None:
+            kwargs['verify'] = self.verify_cert
+
+        # Since requests does not follow the RFC when doing redirection to sent
+        # back the same method on a redirect we are simply bypassing it.  For
+        # example if we do a DELETE/POST/PUT on a URL and we get a 302 RFC says
+        # that we should follow that URL with the same method as before,
+        # requests doesn't follow that and send a GET instead for the method.
+        # Hopefully this could be fixed as they say in a comment in a future
+        # point version i.e: 3.x
+        # See issue: https://github.com/kennethreitz/requests/issues/1704
+        allow_redirects = False
+
+        try:
+            resp = requests.request(
+                method,
+                self.endpoint_url + url,
+                allow_redirects=allow_redirects,
+                **kwargs)
+        except socket.gaierror as e:
+            message = ("Error finding address for %(url)s: %(e)s" %
+                       {'url': self.endpoint_url + url, 'e': e})
+            raise exc.InvalidEndpoint(message=message)
+        except (socket.error, socket.timeout) as e:
+            endpoint = self.endpoint
+            message = ("Error communicating with %(endpoint)s %(e)s" %
+                       {'endpoint': endpoint, 'e': e})
+            raise exc.CommunicationError(message=message)
+
+        self.log_http_response(resp)
+
+        if not 'X-Auth-Key' in kwargs['headers'] and \
+                (resp.status_code == 401 or
+                 (resp.status_code == 500 and "(HTTP 401)" in resp.content)):
+            raise exc.HTTPUnauthorized("Authentication failed. Please try"
+                                       " again with option "
+                                       "--include-password or export "
+                                       "MON_INCLUDE_PASSWORD=1\n%s"
+                                       % resp.content)
+        elif 400 <= resp.status_code < 600:
+            raise exc.from_response(resp)
+        elif resp.status_code in (301, 302, 305):
+            # Redirected. Reissue the request to the new location.
+            location = resp.headers.get('location')
+            if location is None:
+                message = "Location not returned with 302"
+                raise exc.InvalidEndpoint(message=message)
+            elif location.startswith(self.endpoint):
+                # shave off the endpoint, it will be prepended when we recurse
+                location = location[len(self.endpoint):]
+            else:
+                message = "Prohibited endpoint redirect %s" % location
+                raise exc.InvalidEndpoint(message=message)
+            return self._http_request(location, method, **kwargs)
+        elif resp.status_code == 300:
+            raise exc.from_response(resp)
+
+        return resp
+
+    def credentials_headers(self):
+        creds = {}
+        if self.username:
+            creds['X-Auth-User'] = self.username
+        if self.password:
+            creds['X-Auth-Key'] = self.password
+        return creds
+
+    def json_request(self, method, url, **kwargs):
+        kwargs.setdefault('headers', {})
+        kwargs['headers'].setdefault('Content-Type', 'application/json')
+        kwargs['headers'].setdefault('Accept', 'application/json')
+
+        if 'data' in kwargs:
+            kwargs['data'] = jsonutils.dumps(kwargs['data'])
+
+        resp = self._http_request(url, method, **kwargs)
+        body = resp.content
+        if 'application/json' in resp.headers.get('content-type', ''):
+            try:
+                body = resp.json()
+            except ValueError:
+                LOG.error('Could not decode response body as JSON')
+        else:
+            body = None
+
+        return resp, body
+
+    def raw_request(self, method, url, **kwargs):
+        kwargs.setdefault('headers', {})
+        kwargs['headers'].setdefault('Content-Type',
+                                     'application/octet-stream')
+        return self._http_request(url, method, **kwargs)
+
+    def client_request(self, method, url, **kwargs):
+        resp, body = self.json_request(method, url, **kwargs)
+        return resp
+
+    def head(self, url, **kwargs):
+        return self.client_request("HEAD", url, **kwargs)
+
+    def get(self, url, **kwargs):
+        return self.client_request("GET", url, **kwargs)
+
+    def post(self, url, **kwargs):
+        return self.client_request("POST", url, **kwargs)
+
+    def put(self, url, **kwargs):
+        return self.client_request("PUT", url, **kwargs)
+
+    def delete(self, url, **kwargs):
+        return self.raw_request("DELETE", url, **kwargs)
+
+    def patch(self, url, **kwargs):
+        return self.client_request("PATCH", url, **kwargs)
diff --git a/monclient/common/utils.py b/monclient/common/utils.py
new file mode 100644
index 0000000..b13c295
--- /dev/null
+++ b/monclient/common/utils.py
@@ -0,0 +1,182 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    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.
+from __future__ import print_function
+
+import os
+import prettytable
+import sys
+import textwrap
+import uuid
+import yaml
+
+from monclient import exc
+from monclient.openstack.common import importutils
+from monclient.openstack.common import jsonutils
+
+supported_formats = {
+    "json": lambda x: jsonutils.dumps(x, indent=2),
+    "yaml": yaml.safe_dump
+}
+
+
+# Decorator for cli-args
+def arg(*args, **kwargs):
+    def _decorator(func):
+        # Because of the semantics of decorator composition if we just append
+        # to the options list positional options will appear to be backwards.
+        func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs))
+        return func
+    return _decorator
+
+
+def link_formatter(links):
+    return '\n'.join([l.get('href', '') for l in links or []])
+
+
+def json_formatter(js):
+    return jsonutils.dumps(js, indent=2)
+
+
+def text_wrap_formatter(d):
+    return '\n'.join(textwrap.wrap(d or '', 55))
+
+
+def newline_list_formatter(r):
+    return '\n'.join(r or [])
+
+
+def print_list(objs, fields, field_labels=None, formatters={}, sortby=None):
+    field_labels = field_labels or fields
+    pt = prettytable.PrettyTable([f for f in field_labels],
+                                 caching=False, print_empty=False)
+    pt.align = 'l'
+
+    for o in objs:
+        row = []
+        for field in fields:
+            if field in formatters:
+                row.append(formatters[field](o))
+            else:
+                data = getattr(o, field, None) or ''
+                row.append(data)
+        pt.add_row(row)
+    if sortby is None:
+        print(pt.get_string())
+    else:
+        print(pt.get_string(sortby=field_labels[sortby]))
+
+
+def print_dict(d, formatters={}):
+    pt = prettytable.PrettyTable(['Property', 'Value'],
+                                 caching=False, print_empty=False)
+    pt.align = 'l'
+
+    for field in d.keys():
+        if field in formatters:
+            pt.add_row([field, formatters[field](d[field])])
+        else:
+            pt.add_row([field, d[field]])
+    print(pt.get_string(sortby='Property'))
+
+
+def find_resource(manager, name_or_id):
+    """Helper for the _find_* methods."""
+    # first try to get entity as integer id
+    try:
+        if isinstance(name_or_id, int) or name_or_id.isdigit():
+            return manager.get(int(name_or_id))
+    except exc.NotFound:
+        pass
+
+    # now try to get entity as uuid
+    try:
+        uuid.UUID(str(name_or_id))
+        return manager.get(name_or_id)
+    except (ValueError, exc.NotFound):
+        pass
+
+    # finally try to find entity by name
+    try:
+        return manager.find(name=name_or_id)
+    except exc.NotFound:
+        msg = "No %s with a name or ID of '%s' exists." % \
+              (manager.resource_class.__name__.lower(), name_or_id)
+        raise exc.CommandError(msg)
+
+
+def env(*vars, **kwargs):
+    """Search for the first defined of possibly many env vars
+
+    Returns the first environment variable defined in vars, or
+    returns the default defined in kwargs.
+    """
+    for v in vars:
+        value = os.environ.get(v)
+        if value:
+            return value
+    return kwargs.get('default', '')
+
+
+def import_versioned_module(version, submodule=None):
+    module = 'monclient.v%s' % version
+    if submodule:
+        module = '.'.join((module, submodule))
+    return importutils.import_module(module)
+
+
+def exit(msg=''):
+    if msg:
+        print(msg, file=sys.stderr)
+    sys.exit(1)
+
+
+def format_parameters(params):
+    '''Reformat parameters into dict of format expected by the API.'''
+
+    if not params:
+        return {}
+
+    # expect multiple invocations of --parameters but fall back
+    # to ; delimited if only one --parameters is specified
+    if len(params) == 1:
+        params = params[0].split(';')
+
+    parameters = {}
+    for p in params:
+        try:
+            (n, v) = p.split(('='), 1)
+        except ValueError:
+            msg = '%s(%s). %s.' % ('Malformed parameter', p,
+                                   'Use the key=value format')
+            raise exc.CommandError(msg)
+
+        if n not in parameters:
+            parameters[n] = v
+        else:
+            if not isinstance(parameters[n], list):
+                parameters[n] = [parameters[n]]
+            parameters[n].append(v)
+
+    return parameters
+
+
+def format_output(output, format='yaml'):
+    """Format the supplied dict as specified."""
+    output_format = format.lower()
+    try:
+        return supported_formats[output_format](output)
+    except KeyError:
+        raise exc.HTTPUnsupported("The format(%s) is unsupported."
+                                  % output_format)
diff --git a/monclient/exc.py b/monclient/exc.py
new file mode 100644
index 0000000..db462a6
--- /dev/null
+++ b/monclient/exc.py
@@ -0,0 +1,185 @@
+#    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 sys
+
+from monclient.openstack.common import jsonutils
+
+verbose = 0
+
+
+class BaseException(Exception):
+    """An error occurred."""
+    def __init__(self, message=None):
+        self.message = message
+
+    def __str__(self):
+        return self.message or self.__class__.__doc__
+
+
+class CommandError(BaseException):
+    """Invalid usage of CLI."""
+
+
+class InvalidEndpoint(BaseException):
+    """The provided endpoint is invalid."""
+
+
+class CommunicationError(BaseException):
+    """Unable to communicate with server."""
+
+
+class HTTPException(BaseException):
+    """Base exception for all HTTP-derived exceptions."""
+    code = 'N/A'
+
+    def __init__(self, message=None):
+        super(HTTPException, self).__init__(message)
+        try:
+            self.error = jsonutils.loads(message)
+            if 'error' not in self.error:
+                raise KeyError('Key "error" not exists')
+        except KeyError:
+            # NOTE(jianingy): If key 'error' happens not exist,
+            # self.message becomes no sense. In this case, we
+            # return doc of current exception class instead.
+            self.error = {'error':
+                          {'message': self.__class__.__doc__}}
+        except Exception:
+            self.error = {'error':
+                          {'message': self.message or self.__class__.__doc__}}
+
+    def __str__(self):
+        message = self.error['error'].get('message', 'Internal Error')
+        if verbose:
+            traceback = self.error['error'].get('traceback', '')
+            return 'ERROR: %s\n%s' % (message, traceback)
+        else:
+            return 'ERROR: %s' % message
+
+
+class HTTPMultipleChoices(HTTPException):
+    code = 300
+
+    def __str__(self):
+        self.details = ("Requested version of Mon API is not"
+                        "available.")
+        return "%s (HTTP %s) %s" % (self.__class__.__name__, self.code,
+                                    self.details)
+
+
+class BadRequest(HTTPException):
+    """DEPRECATED."""
+    code = 400
+
+
+class HTTPBadRequest(BadRequest):
+    pass
+
+
+class Unauthorized(HTTPException):
+    """DEPRECATED."""
+    code = 401
+
+
+class HTTPUnauthorized(Unauthorized):
+    pass
+
+
+class Forbidden(HTTPException):
+    """DEPRECATED."""
+    code = 403
+
+
+class HTTPForbidden(Forbidden):
+    pass
+
+
+class NotFound(HTTPException):
+    """DEPRECATED."""
+    code = 404
+
+
+class HTTPNotFound(NotFound):
+    pass
+
+
+class HTTPMethodNotAllowed(HTTPException):
+    code = 405
+
+
+class Conflict(HTTPException):
+    """DEPRECATED."""
+    code = 409
+
+
+class HTTPConflict(Conflict):
+    pass
+
+
+class OverLimit(HTTPException):
+    """DEPRECATED."""
+    code = 413
+
+
+class HTTPOverLimit(OverLimit):
+    pass
+
+
+class HTTPUnsupported(HTTPException):
+    code = 415
+
+
+class HTTPInternalServerError(HTTPException):
+    code = 500
+
+
+class HTTPNotImplemented(HTTPException):
+    code = 501
+
+
+class HTTPBadGateway(HTTPException):
+    code = 502
+
+
+class ServiceUnavailable(HTTPException):
+    """DEPRECATED."""
+    code = 503
+
+
+class HTTPServiceUnavailable(ServiceUnavailable):
+    pass
+
+
+#NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception
+# classes
+_code_map = {}
+for obj_name in dir(sys.modules[__name__]):
+    if obj_name.startswith('HTTP'):
+        obj = getattr(sys.modules[__name__], obj_name)
+        _code_map[obj.code] = obj
+
+
+def from_response(response):
+    """Return an instance of an HTTPException based on requests response."""
+    cls = _code_map.get(response.status_code, HTTPException)
+    return cls(response.content)
+
+
+class NoTokenLookupException(Exception):
+    """DEPRECATED."""
+    pass
+
+
+class EndpointNotFound(Exception):
+    """DEPRECATED."""
+    pass
diff --git a/monclient/openstack/__init__.py b/monclient/openstack/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/monclient/openstack/common/__init__.py b/monclient/openstack/common/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/monclient/openstack/common/gettextutils.py b/monclient/openstack/common/gettextutils.py
new file mode 100644
index 0000000..47cef19
--- /dev/null
+++ b/monclient/openstack/common/gettextutils.py
@@ -0,0 +1,373 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Red Hat, Inc.
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+#    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.
+
+"""
+gettext for openstack-common modules.
+
+Usual usage in an openstack.common module:
+
+    from monclient.openstack.common.gettextutils import _
+"""
+
+import copy
+import gettext
+import logging
+import os
+import re
+try:
+    import UserString as _userString
+except ImportError:
+    import collections as _userString
+
+from babel import localedata
+import six
+
+_localedir = os.environ.get('monclient'.upper() + '_LOCALEDIR')
+_t = gettext.translation('monclient', localedir=_localedir, fallback=True)
+
+_AVAILABLE_LANGUAGES = {}
+USE_LAZY = False
+
+
+def enable_lazy():
+    """Convenience function for configuring _() to use lazy gettext
+
+    Call this at the start of execution to enable the gettextutils._
+    function to use lazy gettext functionality. This is useful if
+    your project is importing _ directly instead of using the
+    gettextutils.install() way of importing the _ function.
+    """
+    global USE_LAZY
+    USE_LAZY = True
+
+
+def _(msg):
+    if USE_LAZY:
+        return Message(msg, 'monclient')
+    else:
+        if six.PY3:
+            return _t.gettext(msg)
+        return _t.ugettext(msg)
+
+
+def install(domain, lazy=False):
+    """Install a _() function using the given translation domain.
+
+    Given a translation domain, install a _() function using gettext's
+    install() function.
+
+    The main difference from gettext.install() is that we allow
+    overriding the default localedir (e.g. /usr/share/locale) using
+    a translation-domain-specific environment variable (e.g.
+    NOVA_LOCALEDIR).
+
+    :param domain: the translation domain
+    :param lazy: indicates whether or not to install the lazy _() function.
+                 The lazy _() introduces a way to do deferred translation
+                 of messages by installing a _ that builds Message objects,
+                 instead of strings, which can then be lazily translated into
+                 any available locale.
+    """
+    if lazy:
+        # NOTE(mrodden): Lazy gettext functionality.
+        #
+        # The following introduces a deferred way to do translations on
+        # messages in OpenStack. We override the standard _() function
+        # and % (format string) operation to build Message objects that can
+        # later be translated when we have more information.
+        #
+        # Also included below is an example LocaleHandler that translates
+        # Messages to an associated locale, effectively allowing many logs,
+        # each with their own locale.
+
+        def _lazy_gettext(msg):
+            """Create and return a Message object.
+
+            Lazy gettext function for a given domain, it is a factory method
+            for a project/module to get a lazy gettext function for its own
+            translation domain (i.e. nova, glance, cinder, etc.)
+
+            Message encapsulates a string so that we can translate
+            it later when needed.
+            """
+            return Message(msg, domain)
+
+        from six import moves
+        moves.builtins.__dict__['_'] = _lazy_gettext
+    else:
+        localedir = '%s_LOCALEDIR' % domain.upper()
+        if six.PY3:
+            gettext.install(domain,
+                            localedir=os.environ.get(localedir))
+        else:
+            gettext.install(domain,
+                            localedir=os.environ.get(localedir),
+                            unicode=True)
+
+
+class Message(_userString.UserString, object):
+    """Class used to encapsulate translatable messages."""
+    def __init__(self, msg, domain):
+        # _msg is the gettext msgid and should never change
+        self._msg = msg
+        self._left_extra_msg = ''
+        self._right_extra_msg = ''
+        self._locale = None
+        self.params = None
+        self.domain = domain
+
+    @property
+    def data(self):
+        # NOTE(mrodden): this should always resolve to a unicode string
+        # that best represents the state of the message currently
+
+        localedir = os.environ.get(self.domain.upper() + '_LOCALEDIR')
+        if self.locale:
+            lang = gettext.translation(self.domain,
+                                       localedir=localedir,
+                                       languages=[self.locale],
+                                       fallback=True)
+        else:
+            # use system locale for translations
+            lang = gettext.translation(self.domain,
+                                       localedir=localedir,
+                                       fallback=True)
+
+        if six.PY3:
+            ugettext = lang.gettext
+        else:
+            ugettext = lang.ugettext
+
+        full_msg = (self._left_extra_msg +
+                    ugettext(self._msg) +
+                    self._right_extra_msg)
+
+        if self.params is not None:
+            full_msg = full_msg % self.params
+
+        return six.text_type(full_msg)
+
+    @property
+    def locale(self):
+        return self._locale
+
+    @locale.setter
+    def locale(self, value):
+        self._locale = value
+        if not self.params:
+            return
+
+        # This Message object may have been constructed with one or more
+        # Message objects as substitution parameters, given as a single
+        # Message, or a tuple or Map containing some, so when setting the
+        # locale for this Message we need to set it for those Messages too.
+        if isinstance(self.params, Message):
+            self.params.locale = value
+            return
+        if isinstance(self.params, tuple):
+            for param in self.params:
+                if isinstance(param, Message):
+                    param.locale = value
+            return
+        if isinstance(self.params, dict):
+            for param in self.params.values():
+                if isinstance(param, Message):
+                    param.locale = value
+
+    def _save_dictionary_parameter(self, dict_param):
+        full_msg = self.data
+        # look for %(blah) fields in string;
+        # ignore %% and deal with the
+        # case where % is first character on the line
+        keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', full_msg)
+
+        # if we don't find any %(blah) blocks but have a %s
+        if not keys and re.findall('(?:[^%]|^)%[a-z]', full_msg):
+            # apparently the full dictionary is the parameter
+            params = copy.deepcopy(dict_param)
+        else:
+            params = {}
+            for key in keys:
+                try:
+                    params[key] = copy.deepcopy(dict_param[key])
+                except TypeError:
+                    # cast uncopyable thing to unicode string
+                    params[key] = six.text_type(dict_param[key])
+
+        return params
+
+    def _save_parameters(self, other):
+        # we check for None later to see if
+        # we actually have parameters to inject,
+        # so encapsulate if our parameter is actually None
+        if other is None:
+            self.params = (other, )
+        elif isinstance(other, dict):
+            self.params = self._save_dictionary_parameter(other)
+        else:
+            # fallback to casting to unicode,
+            # this will handle the problematic python code-like
+            # objects that cannot be deep-copied
+            try:
+                self.params = copy.deepcopy(other)
+            except TypeError:
+                self.params = six.text_type(other)
+
+        return self
+
+    # overrides to be more string-like
+    def __unicode__(self):
+        return self.data
+
+    def __str__(self):
+        if six.PY3:
+            return self.__unicode__()
+        return self.data.encode('utf-8')
+
+    def __getstate__(self):
+        to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg',
+                   'domain', 'params', '_locale']
+        new_dict = self.__dict__.fromkeys(to_copy)
+        for attr in to_copy:
+            new_dict[attr] = copy.deepcopy(self.__dict__[attr])
+
+        return new_dict
+
+    def __setstate__(self, state):
+        for (k, v) in state.items():
+            setattr(self, k, v)
+
+    # operator overloads
+    def __add__(self, other):
+        copied = copy.deepcopy(self)
+        copied._right_extra_msg += other.__str__()
+        return copied
+
+    def __radd__(self, other):
+        copied = copy.deepcopy(self)
+        copied._left_extra_msg += other.__str__()
+        return copied
+
+    def __mod__(self, other):
+        # do a format string to catch and raise
+        # any possible KeyErrors from missing parameters
+        self.data % other
+        copied = copy.deepcopy(self)
+        return copied._save_parameters(other)
+
+    def __mul__(self, other):
+        return self.data * other
+
+    def __rmul__(self, other):
+        return other * self.data
+
+    def __getitem__(self, key):
+        return self.data[key]
+
+    def __getslice__(self, start, end):
+        return self.data.__getslice__(start, end)
+
+    def __getattribute__(self, name):
+        # NOTE(mrodden): handle lossy operations that we can't deal with yet
+        # These override the UserString implementation, since UserString
+        # uses our __class__ attribute to try and build a new message
+        # after running the inner data string through the operation.
+        # At that point, we have lost the gettext message id and can just
+        # safely resolve to a string instead.
+        ops = ['capitalize', 'center', 'decode', 'encode',
+               'expandtabs', 'ljust', 'lstrip', 'replace', 'rjust', 'rstrip',
+               'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
+        if name in ops:
+            return getattr(self.data, name)
+        else:
+            return _userString.UserString.__getattribute__(self, name)
+
+
+def get_available_languages(domain):
+    """Lists the available languages for the given translation domain.
+
+    :param domain: the domain to get languages for
+    """
+    if domain in _AVAILABLE_LANGUAGES:
+        return copy.copy(_AVAILABLE_LANGUAGES[domain])
+
+    localedir = '%s_LOCALEDIR' % domain.upper()
+    find = lambda x: gettext.find(domain,
+                                  localedir=os.environ.get(localedir),
+                                  languages=[x])
+
+    # NOTE(mrodden): en_US should always be available (and first in case
+    # order matters) since our in-line message strings are en_US
+    language_list = ['en_US']
+    # NOTE(luisg): Babel <1.0 used a function called list(), which was
+    # renamed to locale_identifiers() in >=1.0, the requirements master list
+    # requires >=0.9.6, uncapped, so defensively work with both. We can remove
+    # this check when the master list updates to >=1.0, and update all projects
+    list_identifiers = (getattr(localedata, 'list', None) or
+                        getattr(localedata, 'locale_identifiers'))
+    locale_identifiers = list_identifiers()
+    for i in locale_identifiers:
+        if find(i) is not None:
+            language_list.append(i)
+    _AVAILABLE_LANGUAGES[domain] = language_list
+    return copy.copy(language_list)
+
+
+def get_localized_message(message, user_locale):
+    """Gets a localized version of the given message in the given locale.
+
+    If the message is not a Message object the message is returned as-is.
+    If the locale is None the message is translated to the default locale.
+
+    :returns: the translated message in unicode, or the original message if
+              it could not be translated
+    """
+    translated = message
+    if isinstance(message, Message):
+        original_locale = message.locale
+        message.locale = user_locale
+        translated = six.text_type(message)
+        message.locale = original_locale
+    return translated
+
+
+class LocaleHandler(logging.Handler):
+    """Handler that can have a locale associated to translate Messages.
+
+    A quick example of how to utilize the Message class above.
+    LocaleHandler takes a locale and a target logging.Handler object
+    to forward LogRecord objects to after translating the internal Message.
+    """
+
+    def __init__(self, locale, target):
+        """Initialize a LocaleHandler
+
+        :param locale: locale to use for translating messages
+        :param target: logging.Handler object to forward
+                       LogRecord objects to after translation
+        """
+        logging.Handler.__init__(self)
+        self.locale = locale
+        self.target = target
+
+    def emit(self, record):
+        if isinstance(record.msg, Message):
+            # set the locale and resolve to a string
+            record.msg.locale = self.locale
+
+        self.target.emit(record)
diff --git a/monclient/openstack/common/importutils.py b/monclient/openstack/common/importutils.py
new file mode 100644
index 0000000..4fd9ae2
--- /dev/null
+++ b/monclient/openstack/common/importutils.py
@@ -0,0 +1,66 @@
+# Copyright 2011 OpenStack Foundation.
+# All Rights Reserved.
+#
+#    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 related utilities and helper functions.
+"""
+
+import sys
+import traceback
+
+
+def import_class(import_str):
+    """Returns a class from a string including module and class."""
+    mod_str, _sep, class_str = import_str.rpartition('.')
+    try:
+        __import__(mod_str)
+        return getattr(sys.modules[mod_str], class_str)
+    except (ValueError, AttributeError):
+        raise ImportError('Class %s cannot be found (%s)' %
+                          (class_str,
+                           traceback.format_exception(*sys.exc_info())))
+
+
+def import_object(import_str, *args, **kwargs):
+    """Import a class and return an instance of it."""
+    return import_class(import_str)(*args, **kwargs)
+
+
+def import_object_ns(name_space, import_str, *args, **kwargs):
+    """Tries to import object from default namespace.
+
+    Imports a class and return an instance of it, first by trying
+    to find the class in a default namespace, then failing back to
+    a full path if not found in the default namespace.
+    """
+    import_value = "%s.%s" % (name_space, import_str)
+    try:
+        return import_class(import_value)(*args, **kwargs)
+    except ImportError:
+        return import_class(import_str)(*args, **kwargs)
+
+
+def import_module(import_str):
+    """Import a module."""
+    __import__(import_str)
+    return sys.modules[import_str]
+
+
+def try_import(import_str, default=None):
+    """Try to import a module and if it fails return default."""
+    try:
+        return import_module(import_str)
+    except ImportError:
+        return default
diff --git a/monclient/openstack/common/jsonutils.py b/monclient/openstack/common/jsonutils.py
new file mode 100644
index 0000000..77fbeb5
--- /dev/null
+++ b/monclient/openstack/common/jsonutils.py
@@ -0,0 +1,182 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Justin Santa Barbara
+# All Rights Reserved.
+#
+#    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.
+
+'''
+JSON related utilities.
+
+This module provides a few things:
+
+    1) A handy function for getting an object down to something that can be
+    JSON serialized.  See to_primitive().
+
+    2) Wrappers around loads() and dumps().  The dumps() wrapper will
+    automatically use to_primitive() for you if needed.
+
+    3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson
+    is available.
+'''
+
+
+import datetime
+import functools
+import inspect
+import itertools
+import json
+try:
+    import xmlrpclib
+except ImportError:
+    # NOTE(jaypipes): xmlrpclib was renamed to xmlrpc.client in Python3
+    #                 however the function and object call signatures
+    #                 remained the same. This whole try/except block should
+    #                 be removed and replaced with a call to six.moves once
+    #                 six 1.4.2 is released. See http://bit.ly/1bqrVzu
+    import xmlrpc.client as xmlrpclib
+
+import six
+
+from monclient.openstack.common import gettextutils
+from monclient.openstack.common import importutils
+from monclient.openstack.common import timeutils
+
+netaddr = importutils.try_import("netaddr")
+
+_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod,
+                     inspect.isfunction, inspect.isgeneratorfunction,
+                     inspect.isgenerator, inspect.istraceback, inspect.isframe,
+                     inspect.iscode, inspect.isbuiltin, inspect.isroutine,
+                     inspect.isabstract]
+
+_simple_types = (six.string_types + six.integer_types
+                 + (type(None), bool, float))
+
+
+def to_primitive(value, convert_instances=False, convert_datetime=True,
+                 level=0, max_depth=3):
+    """Convert a complex object into primitives.
+
+    Handy for JSON serialization. We can optionally handle instances,
+    but since this is a recursive function, we could have cyclical
+    data structures.
+
+    To handle cyclical data structures we could track the actual objects
+    visited in a set, but not all objects are hashable. Instead we just
+    track the depth of the object inspections and don't go too deep.
+
+    Therefore, convert_instances=True is lossy ... be aware.
+
+    """
+    # handle obvious types first - order of basic types determined by running
+    # full tests on nova project, resulting in the following counts:
+    # 572754 <type 'NoneType'>
+    # 460353 <type 'int'>
+    # 379632 <type 'unicode'>
+    # 274610 <type 'str'>
+    # 199918 <type 'dict'>
+    # 114200 <type 'datetime.datetime'>
+    #  51817 <type 'bool'>
+    #  26164 <type 'list'>
+    #   6491 <type 'float'>
+    #    283 <type 'tuple'>
+    #     19 <type 'long'>
+    if isinstance(value, _simple_types):
+        return value
+
+    if isinstance(value, datetime.datetime):
+        if convert_datetime:
+            return timeutils.strtime(value)
+        else:
+            return value
+
+    # value of itertools.count doesn't get caught by nasty_type_tests
+    # and results in infinite loop when list(value) is called.
+    if type(value) == itertools.count:
+        return six.text_type(value)
+
+    # FIXME(vish): Workaround for LP bug 852095. Without this workaround,
+    #              tests that raise an exception in a mocked method that
+    #              has a @wrap_exception with a notifier will fail. If
+    #              we up the dependency to 0.5.4 (when it is released) we
+    #              can remove this workaround.
+    if getattr(value, '__module__', None) == 'mox':
+        return 'mock'
+
+    if level > max_depth:
+        return '?'
+
+    # The try block may not be necessary after the class check above,
+    # but just in case ...
+    try:
+        recursive = functools.partial(to_primitive,
+                                      convert_instances=convert_instances,
+                                      convert_datetime=convert_datetime,
+                                      level=level,
+                                      max_depth=max_depth)
+        if isinstance(value, dict):
+            return dict((k, recursive(v)) for k, v in six.iteritems(value))
+        elif isinstance(value, (list, tuple)):
+            return [recursive(lv) for lv in value]
+
+        # It's not clear why xmlrpclib created their own DateTime type, but
+        # for our purposes, make it a datetime type which is explicitly
+        # handled
+        if isinstance(value, xmlrpclib.DateTime):
+            value = datetime.datetime(*tuple(value.timetuple())[:6])
+
+        if convert_datetime and isinstance(value, datetime.datetime):
+            return timeutils.strtime(value)
+        elif isinstance(value, gettextutils.Message):
+            return value.data
+        elif hasattr(value, 'iteritems'):
+            return recursive(dict(value.iteritems()), level=level + 1)
+        elif hasattr(value, '__iter__'):
+            return recursive(list(value))
+        elif convert_instances and hasattr(value, '__dict__'):
+            # Likely an instance of something. Watch for cycles.
+            # Ignore class member vars.
+            return recursive(value.__dict__, level=level + 1)
+        elif netaddr and isinstance(value, netaddr.IPAddress):
+            return six.text_type(value)
+        else:
+            if any(test(value) for test in _nasty_type_tests):
+                return six.text_type(value)
+            return value
+    except TypeError:
+        # Class objects are tricky since they may define something like
+        # __iter__ defined but it isn't callable as list().
+        return six.text_type(value)
+
+
+def dumps(value, default=to_primitive, **kwargs):
+    return json.dumps(value, default=default, **kwargs)
+
+
+def loads(s):
+    return json.loads(s)
+
+
+def load(s):
+    return json.load(s)
+
+
+try:
+    import anyjson
+except ImportError:
+    pass
+else:
+    anyjson._modules.append((__name__, 'dumps', TypeError,
+                                       'loads', ValueError, 'load'))
+    anyjson.force_implementation(__name__)
diff --git a/monclient/openstack/common/py3kcompat/__init__.py b/monclient/openstack/common/py3kcompat/__init__.py
new file mode 100644
index 0000000..be894cf
--- /dev/null
+++ b/monclient/openstack/common/py3kcompat/__init__.py
@@ -0,0 +1,17 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Canonical Ltd.
+# All Rights Reserved.
+#
+#    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.
+#
diff --git a/monclient/openstack/common/py3kcompat/urlutils.py b/monclient/openstack/common/py3kcompat/urlutils.py
new file mode 100644
index 0000000..c2f0ae7
--- /dev/null
+++ b/monclient/openstack/common/py3kcompat/urlutils.py
@@ -0,0 +1,62 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Canonical Ltd.
+# All Rights Reserved.
+#
+#    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.
+#
+
+"""
+Python2/Python3 compatibility layer for OpenStack
+"""
+
+import six
+
+if six.PY3:
+    # python3
+    import urllib.error
+    import urllib.parse
+    import urllib.request
+
+    urlencode = urllib.parse.urlencode
+    urljoin = urllib.parse.urljoin
+    quote = urllib.parse.quote
+    parse_qsl = urllib.parse.parse_qsl
+    unquote = urllib.parse.unquote
+    urlparse = urllib.parse.urlparse
+    urlsplit = urllib.parse.urlsplit
+    urlunsplit = urllib.parse.urlunsplit
+
+    urlopen = urllib.request.urlopen
+    URLError = urllib.error.URLError
+    pathname2url = urllib.request.pathname2url
+else:
+    # python2
+    import urllib
+    import urllib2
+    import urlparse
+
+    urlencode = urllib.urlencode
+    quote = urllib.quote
+    unquote = urllib.unquote
+
+    parse = urlparse
+    parse_qsl = parse.parse_qsl
+    urljoin = parse.urljoin
+    urlparse = parse.urlparse
+    urlsplit = parse.urlsplit
+    urlunsplit = parse.urlunsplit
+
+    urlopen = urllib2.urlopen
+    URLError = urllib2.URLError
+    pathname2url = urllib.pathname2url
diff --git a/monclient/openstack/common/strutils.py b/monclient/openstack/common/strutils.py
new file mode 100644
index 0000000..1c2a09b
--- /dev/null
+++ b/monclient/openstack/common/strutils.py
@@ -0,0 +1,222 @@
+# Copyright 2011 OpenStack Foundation.
+# All Rights Reserved.
+#
+#    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.
+
+"""
+System-level utilities and helper functions.
+"""
+
+import re
+import sys
+import unicodedata
+
+import six
+
+from monclient.openstack.common.gettextutils import _  # noqa
+
+
+# Used for looking up extensions of text
+# to their 'multiplied' byte amount
+BYTE_MULTIPLIERS = {
+    '': 1,
+    't': 1024 ** 4,
+    'g': 1024 ** 3,
+    'm': 1024 ** 2,
+    'k': 1024,
+}
+BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)')
+
+TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
+FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
+
+SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
+SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
+
+
+def int_from_bool_as_string(subject):
+    """Interpret a string as a boolean and return either 1 or 0.
+
+    Any string value in:
+
+        ('True', 'true', 'On', 'on', '1')
+
+    is interpreted as a boolean True.
+
+    Useful for JSON-decoded stuff and config file parsing
+    """
+    return bool_from_string(subject) and 1 or 0
+
+
+def bool_from_string(subject, strict=False):
+    """Interpret a string as a boolean.
+
+    A case-insensitive match is performed such that strings matching 't',
+    'true', 'on', 'y', 'yes', or '1' are considered True and, when
+    `strict=False`, anything else is considered False.
+
+    Useful for JSON-decoded stuff and config file parsing.
+
+    If `strict=True`, unrecognized values, including None, will raise a
+    ValueError which is useful when parsing values passed in from an API call.
+    Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
+    """
+    if not isinstance(subject, six.string_types):
+        subject = str(subject)
+
+    lowered = subject.strip().lower()
+
+    if lowered in TRUE_STRINGS:
+        return True
+    elif lowered in FALSE_STRINGS:
+        return False
+    elif strict:
+        acceptable = ', '.join(
+            "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
+        msg = _("Unrecognized value '%(val)s', acceptable values are:"
+                " %(acceptable)s") % {'val': subject,
+                                      'acceptable': acceptable}
+        raise ValueError(msg)
+    else:
+        return False
+
+
+def safe_decode(text, incoming=None, errors='strict'):
+    """Decodes incoming str using `incoming` if they're not already unicode.
+
+    :param incoming: Text's current encoding
+    :param errors: Errors handling policy. See here for valid
+        values http://docs.python.org/2/library/codecs.html
+    :returns: text or a unicode `incoming` encoded
+                representation of it.
+    :raises TypeError: If text is not an instance of str
+    """
+    if not isinstance(text, six.string_types):
+        raise TypeError("%s can't be decoded" % type(text))
+
+    if isinstance(text, six.text_type):
+        return text
+
+    if not incoming:
+        incoming = (sys.stdin.encoding or
+                    sys.getdefaultencoding())
+
+    try:
+        return text.decode(incoming, errors)
+    except UnicodeDecodeError:
+        # Note(flaper87) If we get here, it means that
+        # sys.stdin.encoding / sys.getdefaultencoding
+        # didn't return a suitable encoding to decode
+        # text. This happens mostly when global LANG
+        # var is not set correctly and there's no
+        # default encoding. In this case, most likely
+        # python will use ASCII or ANSI encoders as
+        # default encodings but they won't be capable
+        # of decoding non-ASCII characters.
+        #
+        # Also, UTF-8 is being used since it's an ASCII
+        # extension.
+        return text.decode('utf-8', errors)
+
+
+def safe_encode(text, incoming=None,
+                encoding='utf-8', errors='strict'):
+    """Encodes incoming str/unicode using `encoding`.
+
+    If incoming is not specified, text is expected to be encoded with
+    current python's default encoding. (`sys.getdefaultencoding`)
+
+    :param incoming: Text's current encoding
+    :param encoding: Expected encoding for text (Default UTF-8)
+    :param errors: Errors handling policy. See here for valid
+        values http://docs.python.org/2/library/codecs.html
+    :returns: text or a bytestring `encoding` encoded
+                representation of it.
+    :raises TypeError: If text is not an instance of str
+    """
+    if not isinstance(text, six.string_types):
+        raise TypeError("%s can't be encoded" % type(text))
+
+    if not incoming:
+        incoming = (sys.stdin.encoding or
+                    sys.getdefaultencoding())
+
+    if isinstance(text, six.text_type):
+        if six.PY3:
+            return text.encode(encoding, errors).decode(incoming)
+        else:
+            return text.encode(encoding, errors)
+    elif text and encoding != incoming:
+        # Decode text before encoding it with `encoding`
+        text = safe_decode(text, incoming, errors)
+        if six.PY3:
+            return text.encode(encoding, errors).decode(incoming)
+        else:
+            return text.encode(encoding, errors)
+
+    return text
+
+
+def to_bytes(text, default=0):
+    """Converts a string into an integer of bytes.
+
+    Looks at the last characters of the text to determine
+    what conversion is needed to turn the input text into a byte number.
+    Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive)
+
+    :param text: String input for bytes size conversion.
+    :param default: Default return value when text is blank.
+
+    """
+    match = BYTE_REGEX.search(text)
+    if match:
+        magnitude = int(match.group(1))
+        mult_key_org = match.group(2)
+        if not mult_key_org:
+            return magnitude
+    elif text:
+        msg = _('Invalid string format: %s') % text
+        raise TypeError(msg)
+    else:
+        return default
+    mult_key = mult_key_org.lower().replace('b', '', 1)
+    multiplier = BYTE_MULTIPLIERS.get(mult_key)
+    if multiplier is None:
+        msg = _('Unknown byte multiplier: %s') % mult_key_org
+        raise TypeError(msg)
+    return magnitude * multiplier
+
+
+def to_slug(value, incoming=None, errors="strict"):
+    """Normalize string.
+
+    Convert to lowercase, remove non-word characters, and convert spaces
+    to hyphens.
+
+    Inspired by Django's `slugify` filter.
+
+    :param value: Text to slugify
+    :param incoming: Text's current encoding
+    :param errors: Errors handling policy. See here for valid
+        values http://docs.python.org/2/library/codecs.html
+    :returns: slugified unicode representation of `value`
+    :raises TypeError: If text is not an instance of str
+    """
+    value = safe_decode(value, incoming, errors)
+    # NOTE(aababilov): no need to use safe_(encode|decode) here:
+    # encodings are always "ascii", error handling is always "ignore"
+    # and types are always known (first: unicode; second: str)
+    value = unicodedata.normalize("NFKD", value).encode(
+        "ascii", "ignore").decode("ascii")
+    value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
+    return SLUGIFY_HYPHENATE_RE.sub("-", value)
diff --git a/monclient/openstack/common/timeutils.py b/monclient/openstack/common/timeutils.py
new file mode 100644
index 0000000..c8b0b15
--- /dev/null
+++ b/monclient/openstack/common/timeutils.py
@@ -0,0 +1,204 @@
+# Copyright 2011 OpenStack Foundation.
+# All Rights Reserved.
+#
+#    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.
+
+"""
+Time related utilities and helper functions.
+"""
+
+import calendar
+import datetime
+import time
+
+import iso8601
+import six
+
+
+# ISO 8601 extended time format with microseconds
+_ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f'
+_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
+PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND
+
+
+def isotime(at=None, subsecond=False):
+    """Stringify time in ISO 8601 format."""
+    if not at:
+        at = utcnow()
+    st = at.strftime(_ISO8601_TIME_FORMAT
+                     if not subsecond
+                     else _ISO8601_TIME_FORMAT_SUBSECOND)
+    tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC'
+    st += ('Z' if tz == 'UTC' else tz)
+    return st
+
+
+def parse_isotime(timestr):
+    """Parse time from ISO 8601 format."""
+    try:
+        return iso8601.parse_date(timestr)
+    except iso8601.ParseError as e:
+        raise ValueError(six.text_type(e))
+    except TypeError as e:
+        raise ValueError(six.text_type(e))
+
+
+def strtime(at=None, fmt=PERFECT_TIME_FORMAT):
+    """Returns formatted utcnow."""
+    if not at:
+        at = utcnow()
+    return at.strftime(fmt)
+
+
+def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT):
+    """Turn a formatted time back into a datetime."""
+    return datetime.datetime.strptime(timestr, fmt)
+
+
+def normalize_time(timestamp):
+    """Normalize time in arbitrary timezone to UTC naive object."""
+    offset = timestamp.utcoffset()
+    if offset is None:
+        return timestamp
+    return timestamp.replace(tzinfo=None) - offset
+
+
+def is_older_than(before, seconds):
+    """Return True if before is older than seconds."""
+    if isinstance(before, six.string_types):
+        before = parse_strtime(before).replace(tzinfo=None)
+    return utcnow() - before > datetime.timedelta(seconds=seconds)
+
+
+def is_newer_than(after, seconds):
+    """Return True if after is newer than seconds."""
+    if isinstance(after, six.string_types):
+        after = parse_strtime(after).replace(tzinfo=None)
+    return after - utcnow() > datetime.timedelta(seconds=seconds)
+
+
+def utcnow_ts():
+    """Timestamp version of our utcnow function."""
+    if utcnow.override_time is None:
+        # NOTE(kgriffs): This is several times faster
+        # than going through calendar.timegm(...)
+        return int(time.time())
+
+    return calendar.timegm(utcnow().timetuple())
+
+
+def utcnow():
+    """Overridable version of utils.utcnow."""
+    if utcnow.override_time:
+        try:
+            return utcnow.override_time.pop(0)
+        except AttributeError:
+            return utcnow.override_time
+    return datetime.datetime.utcnow()
+
+
+def iso8601_from_timestamp(timestamp):
+    """Returns a iso8601 formated date from timestamp."""
+    return isotime(datetime.datetime.utcfromtimestamp(timestamp))
+
+
+utcnow.override_time = None
+
+
+def set_time_override(override_time=None):
+    """Overrides utils.utcnow.
+
+    Make it return a constant time or a list thereof, one at a time.
+
+    :param override_time: datetime instance or list thereof. If not
+                          given, defaults to the current UTC time.
+    """
+    utcnow.override_time = override_time or datetime.datetime.utcnow()
+
+
+def advance_time_delta(timedelta):
+    """Advance overridden time using a datetime.timedelta."""
+    assert(not utcnow.override_time is None)
+    try:
+        for dt in utcnow.override_time:
+            dt += timedelta
+    except TypeError:
+        utcnow.override_time += timedelta
+
+
+def advance_time_seconds(seconds):
+    """Advance overridden time by seconds."""
+    advance_time_delta(datetime.timedelta(0, seconds))
+
+
+def clear_time_override():
+    """Remove the overridden time."""
+    utcnow.override_time = None
+
+
+def marshall_now(now=None):
+    """Make an rpc-safe datetime with microseconds.
+
+    Note: tzinfo is stripped, but not required for relative times.
+    """
+    if not now:
+        now = utcnow()
+    return dict(day=now.day, month=now.month, year=now.year, hour=now.hour,
+                minute=now.minute, second=now.second,
+                microsecond=now.microsecond)
+
+
+def unmarshall_time(tyme):
+    """Unmarshall a datetime dict."""
+    return datetime.datetime(day=tyme['day'],
+                             month=tyme['month'],
+                             year=tyme['year'],
+                             hour=tyme['hour'],
+                             minute=tyme['minute'],
+                             second=tyme['second'],
+                             microsecond=tyme['microsecond'])
+
+
+def delta_seconds(before, after):
+    """Return the difference between two timing objects.
+
+    Compute the difference in seconds between two date, time, or
+    datetime objects (as a float, to microsecond resolution).
+    """
+    delta = after - before
+    return total_seconds(delta)
+
+
+def total_seconds(delta):
+    """Return the total seconds of datetime.timedelta object.
+
+    Compute total seconds of datetime.timedelta, datetime.timedelta
+    doesn't have method total_seconds in Python2.6, calculate it manually.
+    """
+    try:
+        return delta.total_seconds()
+    except AttributeError:
+        return ((delta.days * 24 * 3600) + delta.seconds +
+                float(delta.microseconds) / (10 ** 6))
+
+
+def is_soon(dt, window):
+    """Determines if time is going to happen in the next window seconds.
+
+    :params dt: the time
+    :params window: minimum seconds to remain to consider the time not soon
+
+    :return: True if expiration is within the given duration
+    """
+    soon = (utcnow() + datetime.timedelta(seconds=window))
+    return normalize_time(dt) <= soon
diff --git a/monclient/shell.py b/monclient/shell.py
new file mode 100644
index 0000000..427e0da
--- /dev/null
+++ b/monclient/shell.py
@@ -0,0 +1,425 @@
+#    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.
+
+"""
+Command-line interface to the mon-client API.
+"""
+
+from __future__ import print_function
+
+import argparse
+import logging
+import six
+import sys
+
+from keystoneclient.v2_0 import client as ksclient
+
+import monclient
+from monclient import client as mon_client
+from monclient.common import utils
+from monclient import exc
+from monclient.openstack.common import strutils
+
+logger = logging.getLogger(__name__)
+
+
+class MonShell(object):
+
+    def get_base_parser(self):
+        parser = argparse.ArgumentParser(
+            prog='mon',
+            description=__doc__.strip(),
+            epilog='See "mon-client help COMMAND" '
+                   'for help on a specific command.',
+            add_help=False,
+            formatter_class=HelpFormatter,
+        )
+
+        # Global arguments
+        parser.add_argument('-h', '--help',
+                            action='store_true',
+                            help=argparse.SUPPRESS)
+
+        parser.add_argument('--version',
+                            action='version',
+                            version=monclient.__version__,
+                            help="Shows the client version and exits.")
+
+        parser.add_argument('-d', '--debug',
+                            default=bool(utils.env('MON_DEBUG')),
+                            action='store_true',
+                            help='Defaults to env[MON_DEBUG].')
+
+        parser.add_argument('-v', '--verbose',
+                            default=False, action="store_true",
+                            help="Print more verbose output.")
+
+        parser.add_argument('-k', '--insecure',
+                            default=False,
+                            action='store_true',
+                            help="Explicitly allow the client to perform "
+                            "\"insecure\" SSL (https) requests. The server's "
+                            "certificate will not be verified against any "
+                            "certificate authorities. "
+                            "This option should be used with caution.")
+
+        parser.add_argument('--cert-file',
+                            help='Path of certificate file to use in SSL '
+                            'connection. This file can optionally be '
+                            'prepended with the private key.')
+
+        parser.add_argument('--key-file',
+                            help='Path of client key to use in SSL connection.'
+                            'This option is not necessary if your key is'
+                            ' prepended to your cert file.')
+
+        parser.add_argument('--ca-file',
+                            help='Path of CA SSL certificate(s) used to verify'
+                            ' the remote server\'s certificate. Without this'
+                            ' option the client looks'
+                            ' for the default system CA certificates.')
+
+        parser.add_argument('--timeout',
+                            default=600,
+                            help='Number of seconds to wait for a response.')
+
+        parser.add_argument('--os-username',
+                            default=utils.env('OS_USERNAME'),
+                            help='Defaults to env[OS_USERNAME].')
+
+        parser.add_argument('--os_username',
+                            help=argparse.SUPPRESS)
+
+        parser.add_argument('--os-password',
+                            default=utils.env('OS_PASSWORD'),
+                            help='Defaults to env[OS_PASSWORD].')
+
+        parser.add_argument('--os_password',
+                            help=argparse.SUPPRESS)
+
+        parser.add_argument('--os-tenant-id',
+                            default=utils.env('OS_TENANT_ID'),
+                            help='Defaults to env[OS_TENANT_ID].')
+
+        parser.add_argument('--os_tenant_id',
+                            help=argparse.SUPPRESS)
+
+        parser.add_argument('--os-tenant-name',
+                            default=utils.env('OS_TENANT_NAME'),
+                            help='Defaults to env[OS_TENANT_NAME].')
+
+        parser.add_argument('--os_tenant_name',
+                            help=argparse.SUPPRESS)
+
+        parser.add_argument('--os-auth-url',
+                            default=utils.env('OS_AUTH_URL'),
+                            help='Defaults to env[OS_AUTH_URL].')
+
+        parser.add_argument('--os_auth_url',
+                            help=argparse.SUPPRESS)
+
+        parser.add_argument('--os-region-name',
+                            default=utils.env('OS_REGION_NAME'),
+                            help='Defaults to env[OS_REGION_NAME].')
+
+        parser.add_argument('--os_region_name',
+                            help=argparse.SUPPRESS)
+
+        parser.add_argument('--os-auth-token',
+                            default=utils.env('OS_AUTH_TOKEN'),
+                            help='Defaults to env[OS_AUTH_TOKEN].')
+
+        parser.add_argument('--os_auth_token',
+                            help=argparse.SUPPRESS)
+
+        parser.add_argument('--os-no-client-auth',
+                            default=utils.env('OS_NO_CLIENT_AUTH'),
+                            action='store_true',
+                            help="Do not contact keystone for a token. "
+                                 "Defaults to env[OS_NO_CLIENT_AUTH].")
+
+        parser.add_argument('--mon-api-url',
+                            default=utils.env('MON_API_URL'),
+                            help='Defaults to env[MON_API_URL].')
+
+        parser.add_argument('--mon_api_url',
+                            help=argparse.SUPPRESS)
+
+        parser.add_argument('--mon-api-version',
+                            default=utils.env('MON_API_VERSION', default='2_0'),
+                            help='Defaults to env[MON_API_VERSION] or 2_0')
+
+        parser.add_argument('--mon_api_version',
+                            help=argparse.SUPPRESS)
+
+        parser.add_argument('--os-service-type',
+                            default=utils.env('OS_SERVICE_TYPE'),
+                            help='Defaults to env[OS_SERVICE_TYPE].')
+
+        parser.add_argument('--os_service_type',
+                            help=argparse.SUPPRESS)
+
+        parser.add_argument('--os-endpoint-type',
+                            default=utils.env('OS_ENDPOINT_TYPE'),
+                            help='Defaults to env[OS_ENDPOINT_TYPE].')
+
+        parser.add_argument('--os_endpoint_type',
+                            help=argparse.SUPPRESS)
+
+        return parser
+
+    def get_subcommand_parser(self, version):
+        parser = self.get_base_parser()
+
+        self.subcommands = {}
+        subparsers = parser.add_subparsers(metavar='<subcommand>')
+        submodule = utils.import_versioned_module(version, 'shell')
+        self._find_actions(subparsers, submodule)
+        self._find_actions(subparsers, self)
+        self._add_bash_completion_subparser(subparsers)
+
+        return parser
+
+    def _add_bash_completion_subparser(self, subparsers):
+        subparser = subparsers.add_parser(
+            'bash_completion',
+            add_help=False,
+            formatter_class=HelpFormatter
+        )
+        self.subcommands['bash_completion'] = subparser
+        subparser.set_defaults(func=self.do_bash_completion)
+
+    def _find_actions(self, subparsers, actions_module):
+        for attr in (a for a in dir(actions_module) if a.startswith('do_')):
+            # I prefer to be hyphen-separated instead of underscores.
+            command = attr[3:].replace('_', '-')
+            callback = getattr(actions_module, attr)
+            desc = callback.__doc__ or ''
+            help = desc.strip().split('\n')[0]
+            arguments = getattr(callback, 'arguments', [])
+
+            subparser = subparsers.add_parser(command,
+                                              help=help,
+                                              description=desc,
+                                              add_help=False,
+                                              formatter_class=HelpFormatter)
+            subparser.add_argument('-h', '--help',
+                                   action='help',
+                                   help=argparse.SUPPRESS)
+            self.subcommands[command] = subparser
+            for (args, kwargs) in arguments:
+                subparser.add_argument(*args, **kwargs)
+            subparser.set_defaults(func=callback)
+
+    def _get_ksclient(self, **kwargs):
+        """Get an endpoint and auth token from Keystone.
+
+        :param username: name of user
+        :param password: user's password
+        :param tenant_id: unique identifier of tenant
+        :param tenant_name: name of tenant
+        :param auth_url: endpoint to authenticate against
+        :param token: token to use instead of username/password
+        """
+        kc_args = {'auth_url': kwargs.get('auth_url'),
+                   'insecure': kwargs.get('insecure')}
+
+        if kwargs.get('tenant_id'):
+            kc_args['tenant_id'] = kwargs.get('tenant_id')
+        else:
+            kc_args['tenant_name'] = kwargs.get('tenant_name')
+
+        if kwargs.get('token'):
+            kc_args['token'] = kwargs.get('token')
+        else:
+            kc_args['username'] = kwargs.get('username')
+            kc_args['password'] = kwargs.get('password')
+
+        return ksclient.Client(**kc_args)
+
+    def _get_endpoint(self, client, **kwargs):
+        """Get an endpoint using the provided keystone client."""
+        if kwargs.get('region_name'):
+            return client.service_catalog.url_for(
+                service_type=kwargs.get('service_type') or 'monitoring',
+                attr='region',
+                filter_value=kwargs.get('region_name'),
+                endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
+        return client.service_catalog.url_for(
+            service_type=kwargs.get('service_type') or 'orchestration',
+            endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
+
+    def _setup_logging(self, debug):
+        log_lvl = logging.DEBUG if debug else logging.ERROR
+        logging.basicConfig(
+            format="%(levelname)s (%(module)s:%(lineno)d) %(message)s",
+            level=log_lvl)
+
+    def _setup_verbose(self, verbose):
+        if verbose:
+            exc.verbose = 1
+
+    def main(self, argv):
+        # Parse args once to find version
+        parser = self.get_base_parser()
+        (options, args) = parser.parse_known_args(argv)
+        self._setup_logging(options.debug)
+        self._setup_verbose(options.verbose)
+
+        # build available subcommands based on version
+        api_version = options.mon_api_version
+        subcommand_parser = self.get_subcommand_parser(api_version)
+        self.parser = subcommand_parser
+
+        # Handle top-level --help/-h before attempting to parse
+        # a command off the command line
+        if not args and options.help or not argv:
+            self.do_help(options)
+            return 0
+
+        # Parse args again and call whatever callback was selected
+        args = subcommand_parser.parse_args(argv)
+
+        # Short-circuit and deal with help command right away.
+        if args.func == self.do_help:
+            self.do_help(args)
+            return 0
+        elif args.func == self.do_bash_completion:
+            self.do_bash_completion(args)
+            return 0
+
+        if not args.os_username and not args.os_auth_token:
+            raise exc.CommandError("You must provide a username via"
+                                   " either --os-username or env[OS_USERNAME]"
+                                   " or a token via --os-auth-token or"
+                                   " env[OS_AUTH_TOKEN]")
+
+        if not args.os_password and not args.os_auth_token:
+            raise exc.CommandError("You must provide a password via"
+                                   " either --os-password or env[OS_PASSWORD]"
+                                   " or a token via --os-auth-token or"
+                                   " env[OS_AUTH_TOKEN]")
+
+        if args.os_no_client_auth:
+            if not args.mon_api_url:
+                raise exc.CommandError("If you specify --os-no-client-auth"
+                                       " you must also specify a Monitoring API URL"
+                                       " via either --mon-api-url or"
+                                       " env[MON_API_URL]")
+        else:
+            # Tenant name or ID is needed to make keystoneclient retrieve a
+            # service catalog, it's not required if os_no_client_auth is
+            # specified, neither is the auth URL
+            if not (args.os_tenant_id or args.os_tenant_name):
+                raise exc.CommandError("You must provide a tenant_id via"
+                                       " either --os-tenant-id or via"
+                                       " env[OS_TENANT_ID]")
+
+            if not args.os_auth_url:
+                raise exc.CommandError("You must provide an auth url via"
+                                       " either --os-auth-url or via"
+                                       " env[OS_AUTH_URL]")
+
+        kwargs = {
+            'username': args.os_username,
+            'password': args.os_password,
+            'token': args.os_auth_token,
+            'tenant_id': args.os_tenant_id,
+            'tenant_name': args.os_tenant_name,
+            'auth_url': args.os_auth_url,
+            'service_type': args.os_service_type,
+            'endpoint_type': args.os_endpoint_type,
+            'insecure': args.insecure,
+            'include_pass': args.include_password
+        }
+
+        endpoint = args.mon_api_url
+
+        if not args.os_no_client_auth:
+            _ksclient = self._get_ksclient(**kwargs)
+            token = args.os_auth_token or _ksclient.auth_token
+
+            kwargs = {
+                'token': token,
+                'insecure': args.insecure,
+                'timeout': args.timeout,
+                'ca_file': args.ca_file,
+                'cert_file': args.cert_file,
+                'key_file': args.key_file,
+                'username': args.os_username,
+                'password': args.os_password,
+                'endpoint_type': args.os_endpoint_type,
+                'include_pass': args.include_password
+            }
+
+            if args.os_region_name:
+                kwargs['region_name'] = args.os_region_name
+
+            if not endpoint:
+                endpoint = self._get_endpoint(_ksclient, **kwargs)
+
+        client = mon_client.Client(api_version, endpoint, **kwargs)
+
+        args.func(client, args)
+
+    def do_bash_completion(self, args):
+        """Prints all of the commands and options to stdout.
+
+        The mon.bash_completion script doesn't have to hard code them.
+        """
+        commands = set()
+        options = set()
+        for sc_str, sc in self.subcommands.items():
+            commands.add(sc_str)
+            for option in list(sc._optionals._option_string_actions):
+                options.add(option)
+
+        commands.remove('bash-completion')
+        commands.remove('bash_completion')
+        print(' '.join(commands | options))
+
+    @utils.arg('command', metavar='<subcommand>', nargs='?',
+               help='Display help for <subcommand>.')
+    def do_help(self, args):
+        """Display help about this program or one of its subcommands."""
+        if getattr(args, 'command', None):
+            if args.command in self.subcommands:
+                self.subcommands[args.command].print_help()
+            else:
+                raise exc.CommandError("'%s' is not a valid subcommand" %
+                                       args.command)
+        else:
+            self.parser.print_help()
+
+
+class HelpFormatter(argparse.HelpFormatter):
+    def start_section(self, heading):
+        # Title-case the headings
+        heading = '%s%s' % (heading[0].upper(), heading[1:])
+        super(HelpFormatter, self).start_section(heading)
+
+
+def main(args=None):
+    try:
+        if args is None:
+            args = sys.argv[1:]
+
+        MonShell().main(args)
+    except Exception as e:
+        if '--debug' in args or '-d' in args:
+            raise
+        else:
+            print(strutils.safe_encode(six.text_type(e)), file=sys.stderr)
+        sys.exit(1)
+
+if __name__ == "__main__":
+    main()
diff --git a/monclient/v2_0/__init__.py b/monclient/v2_0/__init__.py
new file mode 100644
index 0000000..bc4dac8
--- /dev/null
+++ b/monclient/v2_0/__init__.py
@@ -0,0 +1,18 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    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.
+
+__all__ = ['Client']
+
+from monclient.v2_0.client import Client
diff --git a/monclient/v2_0/client.py b/monclient/v2_0/client.py
new file mode 100644
index 0000000..7ff7584
--- /dev/null
+++ b/monclient/v2_0/client.py
@@ -0,0 +1,33 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    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.
+
+from monclient.common import http
+
+
+
+class Client(object):
+    """Client for the Mon v1 API.
+
+    :param string endpoint: A user-supplied endpoint URL for the monitoring api 
+                            service.
+    :param string token: Token for authentication.
+    :param integer timeout: Allows customization of the timeout for client
+                            http requests. (optional)
+    """
+
+    def __init__(self, *args, **kwargs):
+        """Initialize a new client for the Heat v1 API."""
+        self.http_client = http.HTTPClient(*args, **kwargs)
+        
diff --git a/monclient/v2_0/shell.py b/monclient/v2_0/shell.py
new file mode 100644
index 0000000..49eaacc
--- /dev/null
+++ b/monclient/v2_0/shell.py
@@ -0,0 +1,31 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    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 yaml
+
+from monclient.common import utils
+from monclient.openstack.common import jsonutils
+from monclient.openstack.common.py3kcompat import urlutils
+
+import monclient.exc as exc
+
+@utils.arg('json_body', metavar='<json body>',
+           help='the json body for the http request')
+def do_metrics_create(hc, args):
+    '''Create one or more metrics.'''
+    fields = {'json_body': args.json_body}
+    print("in do_metrics_create")
+
+
diff --git a/openstack-common.conf b/openstack-common.conf
new file mode 100644
index 0000000..e96b1d9
--- /dev/null
+++ b/openstack-common.conf
@@ -0,0 +1,8 @@
+[DEFAULT]
+
+# The list of modules to copy from openstack-common
+modules=importutils,gettextutils,strutils,apiclient.base,apiclient.exceptions
+module=py3kcompat
+
+# The base module to hold the copy of openstack.common
+base=monclient
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..2f54179
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,8 @@
+pbr>=0.6,<1.0
+argparse
+iso8601>=0.1.8
+PrettyTable>=0.7,<0.8
+python-keystoneclient>=0.6.0
+PyYAML>=3.1.0
+six>=1.4.1
+requests>=1.1
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..4bacf9a
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,37 @@
+[metadata]
+name = python-monclient
+summary = OpenStack Monitoring API Client Library
+description-file = README.rst
+author = OpenStack
+author-email = cindy.o-neill@hp.com
+home-page = https://git.hpcloud.net/mon/python-monclient
+classifier =
+    Environment :: OpenStack
+    Intended Audience :: Information Technology
+    Intended Audience :: System Administrators
+    License :: OSI Approved :: Apache Software License
+    Operating System :: POSIX :: Linux
+    Programming Language :: Python
+    Programming Language :: Python :: 2
+    Programming Language :: Python :: 2.7
+    Programming Language :: Python :: 2.6
+    Programming Language :: Python :: 3
+    Programming Language :: Python :: 3.3
+
+[files]
+packages = monclient
+
+[entry_points]
+console_scripts =
+    mon = monclient.shell:main
+
+[pbr]
+autodoc_index_modules = True
+
+[build_sphinx]
+all_files = 1
+build-dir = doc/build
+source-dir = doc/source
+
+[wheel]
+universal = 1
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..530ac2e
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,23 @@
+import os
+from setuptools import setup
+
+# Utility function to read the README file.
+# Used for the long_description.  It's nice, because now 1) we have a top level
+# README file and 2) it's easier to type in the README file than to put a raw
+# string in below ...
+def read(fname):
+    return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+setup(
+    name = "python-monclient",
+    version = "2.0",
+    author = "cindy",
+    author_email = "cindy.o-neill@hp.com",
+    description = ("The Monitoring API client"),
+    url = "https://git.hpcloud.net/mon/python-monclient",
+    packages=['monclient','monclient.common','monclient.openstack.common'],
+    long_description=read('README.rst'),
+    classifiers=[
+        "Development Status :: Planning",
+    ],
+)
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100644
index 0000000..8202de3
--- /dev/null
+++ b/test-requirements.txt
@@ -0,0 +1,11 @@
+# Hacking already pins down pep8, pyflakes and flake8
+hacking>=0.8.0,<0.9
+coverage>=3.6
+discover
+fixtures>=0.3.14
+mock>=1.0
+mox3>=0.7.0
+sphinx>=1.1.2,<1.2
+testscenarios>=0.4
+testrepository>=0.0.18
+testtools>=0.9.34
diff --git a/tools/install_venv.py b/tools/install_venv.py
new file mode 100644
index 0000000..4d8feea
--- /dev/null
+++ b/tools/install_venv.py
@@ -0,0 +1,75 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Copyright 2010 OpenStack Foundation
+# Copyright 2013 IBM Corp.
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# 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 ConfigParser
+import os
+import sys
+
+import install_venv_common as install_venv  # flake8: noqa
+
+
+def print_help(project, venv, root):
+    help = """
+    %(project)s development environment setup is complete.
+
+    %(project)s development uses virtualenv to track and manage Python
+    dependencies while in development and testing.
+
+    To activate the %(project)s virtualenv for the extent of your current
+    shell session you can run:
+
+    $ source %(venv)s/bin/activate
+
+    Or, if you prefer, you can run commands in the virtualenv on a case by
+    case basis by running:
+
+    $ %(root)s/tools/with_venv.sh <your command>
+    """
+    print help % dict(project=project, venv=venv, root=root)
+
+
+def main(argv):
+    root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+
+    if os.environ.get('tools_path'):
+        root = os.environ['tools_path']
+    venv = os.path.join(root, '.venv')
+    if os.environ.get('venv'):
+        venv = os.environ['venv']
+
+    pip_requires = os.path.join(root, 'requirements.txt')
+    test_requires = os.path.join(root, 'test-requirements.txt')
+    py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
+    setup_cfg = ConfigParser.ConfigParser()
+    setup_cfg.read('setup.cfg')
+    project = setup_cfg.get('metadata', 'name')
+
+    install = install_venv.InstallVenv(
+        root, venv, pip_requires, test_requires, py_version, project)
+    options = install.parse_args(argv)
+    install.check_python_version()
+    install.check_dependencies()
+    install.create_virtualenv(no_site_packages=options.no_site_packages)
+    install.install_dependencies()
+    install.post_process()
+    print_help(project, venv, root)
+
+if __name__ == '__main__':
+    main(sys.argv)
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
new file mode 100644
index 0000000..46822e3
--- /dev/null
+++ b/tools/install_venv_common.py
@@ -0,0 +1,172 @@
+# Copyright 2013 OpenStack Foundation
+# Copyright 2013 IBM Corp.
+#
+#    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.
+
+"""Provides methods needed by installation script for OpenStack development
+virtual environments.
+
+Since this script is used to bootstrap a virtualenv from the system's Python
+environment, it should be kept strictly compatible with Python 2.6.
+
+Synced in from openstack-common
+"""
+
+from __future__ import print_function
+
+import optparse
+import os
+import subprocess
+import sys
+
+
+class InstallVenv(object):
+
+    def __init__(self, root, venv, requirements,
+                 test_requirements, py_version,
+                 project):
+        self.root = root
+        self.venv = venv
+        self.requirements = requirements
+        self.test_requirements = test_requirements
+        self.py_version = py_version
+        self.project = project
+
+    def die(self, message, *args):
+        print(message % args, file=sys.stderr)
+        sys.exit(1)
+
+    def check_python_version(self):
+        if sys.version_info < (2, 6):
+            self.die("Need Python Version >= 2.6")
+
+    def run_command_with_code(self, cmd, redirect_output=True,
+                              check_exit_code=True):
+        """Runs a command in an out-of-process shell.
+
+        Returns the output of that command. Working directory is self.root.
+        """
+        if redirect_output:
+            stdout = subprocess.PIPE
+        else:
+            stdout = None
+
+        proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout)
+        output = proc.communicate()[0]
+        if check_exit_code and proc.returncode != 0:
+            self.die('Command "%s" failed.\n%s', ' '.join(cmd), output)
+        return (output, proc.returncode)
+
+    def run_command(self, cmd, redirect_output=True, check_exit_code=True):
+        return self.run_command_with_code(cmd, redirect_output,
+                                          check_exit_code)[0]
+
+    def get_distro(self):
+        if (os.path.exists('/etc/fedora-release') or
+                os.path.exists('/etc/redhat-release')):
+            return Fedora(
+                self.root, self.venv, self.requirements,
+                self.test_requirements, self.py_version, self.project)
+        else:
+            return Distro(
+                self.root, self.venv, self.requirements,
+                self.test_requirements, self.py_version, self.project)
+
+    def check_dependencies(self):
+        self.get_distro().install_virtualenv()
+
+    def create_virtualenv(self, no_site_packages=True):
+        """Creates the virtual environment and installs PIP.
+
+        Creates the virtual environment and installs PIP only into the
+        virtual environment.
+        """
+        if not os.path.isdir(self.venv):
+            print('Creating venv...', end=' ')
+            if no_site_packages:
+                self.run_command(['virtualenv', '-q', '--no-site-packages',
+                                 self.venv])
+            else:
+                self.run_command(['virtualenv', '-q', self.venv])
+            print('done.')
+        else:
+            print("venv already exists...")
+            pass
+
+    def pip_install(self, *args):
+        self.run_command(['tools/with_venv.sh',
+                         'pip', 'install', '--upgrade'] + list(args),
+                         redirect_output=False)
+
+    def install_dependencies(self):
+        print('Installing dependencies with pip (this can take a while)...')
+
+        # First things first, make sure our venv has the latest pip and
+        # setuptools and pbr
+        self.pip_install('pip>=1.4')
+        self.pip_install('setuptools')
+        self.pip_install('pbr')
+
+        self.pip_install('-r', self.requirements, '-r', self.test_requirements)
+
+    def parse_args(self, argv):
+        """Parses command-line arguments."""
+        parser = optparse.OptionParser()
+        parser.add_option('-n', '--no-site-packages',
+                          action='store_true',
+                          help="Do not inherit packages from global Python "
+                               "install")
+        return parser.parse_args(argv[1:])[0]
+
+
+class Distro(InstallVenv):
+
+    def check_cmd(self, cmd):
+        return bool(self.run_command(['which', cmd],
+                    check_exit_code=False).strip())
+
+    def install_virtualenv(self):
+        if self.check_cmd('virtualenv'):
+            return
+
+        if self.check_cmd('easy_install'):
+            print('Installing virtualenv via easy_install...', end=' ')
+            if self.run_command(['easy_install', 'virtualenv']):
+                print('Succeeded')
+                return
+            else:
+                print('Failed')
+
+        self.die('ERROR: virtualenv not found.\n\n%s development'
+                 ' requires virtualenv, please install it using your'
+                 ' favorite package management tool' % self.project)
+
+
+class Fedora(Distro):
+    """This covers all Fedora-based distributions.
+
+    Includes: Fedora, RHEL, CentOS, Scientific Linux
+    """
+
+    def check_pkg(self, pkg):
+        return self.run_command_with_code(['rpm', '-q', pkg],
+                                          check_exit_code=False)[1] == 0
+
+    def install_virtualenv(self):
+        if self.check_cmd('virtualenv'):
+            return
+
+        if not self.check_pkg('python-virtualenv'):
+            self.die("Please install 'python-virtualenv'.")
+
+        super(Fedora, self).install_virtualenv()
diff --git a/tools/mon.bash_completion b/tools/mon.bash_completion
new file mode 100644
index 0000000..9a9b62f
--- /dev/null
+++ b/tools/mon.bash_completion
@@ -0,0 +1,27 @@
+# bash completion for openstack mon
+
+_mon_opts="" # lazy init
+_mon_flags="" # lazy init
+_mon_opts_exp="" # lazy init
+_mon()
+{
+    local cur prev kbc
+    COMPREPLY=()
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    prev="${COMP_WORDS[COMP_CWORD-1]}"
+
+    if [ "x$_mon_opts" == "x" ] ; then
+        kbc="`mon bash-completion | sed -e "s/ -h / /"`"
+        _mon_opts="`echo "$kbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`"
+        _mon_flags="`echo " $kbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`"
+        _mon_opts_exp="`echo $_mon_opts | sed -e "s/[ ]/|/g"`"
+    fi
+
+    if [[ " ${COMP_WORDS[@]} " =~ " "($_mon_opts_exp)" " && "$prev" != "help" ]] ; then
+        COMPREPLY=($(compgen -W "${_mon_flags}" -- ${cur}))
+    else
+        COMPREPLY=($(compgen -W "${_mon_opts}" -- ${cur}))
+    fi
+    return 0
+}
+complete -o default -o nospace -F _mon mon
diff --git a/tools/with_venv.sh b/tools/with_venv.sh
new file mode 100755
index 0000000..e6e44f5
--- /dev/null
+++ b/tools/with_venv.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+command -v tox > /dev/null 2>&1
+if [ $? -ne 0 ]; then
+  echo 'This script requires "tox" to run.'
+  echo 'You can install it with "pip install tox".'
+  exit 1; 
+fi
+
+tox -evenv -- $@
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..98100ae
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,32 @@
+[tox]
+envlist = py26,py27,pypy,pep8
+minversion = 1.6
+skipsdist = True
+
+[testenv]
+setenv = VIRTUAL_ENV={envdir}
+usedevelop = True
+install_command = pip install -U {opts} {packages}
+deps = -r{toxinidir}/requirements.txt
+       -r{toxinidir}/test-requirements.txt
+commands = python setup.py testr --slowest --testr-args='{posargs}'
+
+[testenv:pep8]
+commands = flake8
+
+[testenv:venv]
+commands = {posargs}
+
+[testenv:cover]
+commands = python setup.py testr --coverage --testr-args='{posargs}'
+
+
+[tox:jenkins]
+downloadcache = ~/cache/pip
+
+[flake8]
+show-source = True
+# H302: Do not import objects, only modules
+ignore = H302
+builtins = _
+exclude=.venv,.git,.tox,dist,*openstack/common*,*lib/python*,*egg,build