HTTP Client refactoring
Library `requests` can handle files, pipes, dictionaries and iterators as `data` argument. Use 'json' argument to send json requests. Rewrite some unittests using mock. Change-Id: I95b71eb2716dc57708ed105659ffece376bd8344
This commit is contained in:
		
				
					committed by
					
						
						Mike Fedosin
					
				
			
			
				
	
			
			
			
						parent
						
							7c62625b2a
						
					
				
				
					commit
					5e85f0fc3c
				
			@@ -26,7 +26,6 @@ import requests
 | 
			
		||||
import six
 | 
			
		||||
from six.moves import urllib
 | 
			
		||||
 | 
			
		||||
from glareclient._i18n import _
 | 
			
		||||
from glareclient.common import exceptions as exc
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
@@ -52,43 +51,6 @@ def get_system_ca_file():
 | 
			
		||||
    LOG.warning("System ca file could not be found.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _chunk_body(body):
 | 
			
		||||
    chunk = body
 | 
			
		||||
    while chunk:
 | 
			
		||||
        chunk = body.read(CHUNKSIZE)
 | 
			
		||||
        if not chunk:
 | 
			
		||||
            break
 | 
			
		||||
        yield chunk
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _set_request_params(kwargs_params):
 | 
			
		||||
    """Handle the common parameters used to send the request."""
 | 
			
		||||
 | 
			
		||||
    data = kwargs_params.pop('data', None)
 | 
			
		||||
    params = copy.deepcopy(kwargs_params)
 | 
			
		||||
    headers = params.get('headers', {})
 | 
			
		||||
    content_type = headers.get('Content-Type')
 | 
			
		||||
    stream = params.get("stream", False)
 | 
			
		||||
 | 
			
		||||
    if stream:
 | 
			
		||||
        if data is not None:
 | 
			
		||||
            data = _chunk_body(data)
 | 
			
		||||
        content_type = content_type or 'application/octet-stream'
 | 
			
		||||
    elif data is not None and not isinstance(data, six.string_types):
 | 
			
		||||
        try:
 | 
			
		||||
            data = jsonutils.dumps(data)
 | 
			
		||||
        except TypeError:
 | 
			
		||||
            raise exc.HTTPBadRequest("json is malformed.")
 | 
			
		||||
 | 
			
		||||
    params['data'] = data
 | 
			
		||||
    headers.update(
 | 
			
		||||
        {'Content-Type': content_type or 'application/json'})
 | 
			
		||||
    params['headers'] = headers
 | 
			
		||||
    params['stream'] = stream
 | 
			
		||||
 | 
			
		||||
    return params
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _handle_response(resp):
 | 
			
		||||
        content_type = resp.headers.get('Content-Type')
 | 
			
		||||
        if not content_type:
 | 
			
		||||
@@ -107,17 +69,6 @@ def _handle_response(resp):
 | 
			
		||||
        return resp, body_iter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _close_after_stream(response, chunk_size):
 | 
			
		||||
    """Iterate over the content and ensure the response is closed after."""
 | 
			
		||||
    # Yield each chunk in the response body
 | 
			
		||||
    for chunk in response.iter_content(chunk_size=chunk_size):
 | 
			
		||||
        yield chunk
 | 
			
		||||
    # Once we're done streaming the body, ensure everything is closed.
 | 
			
		||||
    # This will return the connection to the HTTPConnectionPool in urllib3
 | 
			
		||||
    # and ideally reduce the number of HTTPConnectionPool full warnings.
 | 
			
		||||
    response.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPClient(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, endpoint, **kwargs):
 | 
			
		||||
@@ -305,8 +256,7 @@ class HTTPClient(object):
 | 
			
		||||
        return creds
 | 
			
		||||
 | 
			
		||||
    def json_request(self, url, method, **kwargs):
 | 
			
		||||
        params = _set_request_params(kwargs)
 | 
			
		||||
        resp = self.request(url, method, **params)
 | 
			
		||||
        resp = self.request(url, method, **kwargs)
 | 
			
		||||
        return _handle_response(resp)
 | 
			
		||||
 | 
			
		||||
    def json_patch_request(self, url, method='PATCH', **kwargs):
 | 
			
		||||
@@ -336,37 +286,14 @@ class SessionClient(adapter.LegacyJsonAdapter):
 | 
			
		||||
    """HTTP client based on Keystone client session."""
 | 
			
		||||
 | 
			
		||||
    def request(self, url, method, **kwargs):
 | 
			
		||||
        params = _set_request_params(kwargs)
 | 
			
		||||
        redirect = kwargs.get('redirect')
 | 
			
		||||
 | 
			
		||||
        resp, body = super(SessionClient, self).request(
 | 
			
		||||
            url, method,
 | 
			
		||||
            **params)
 | 
			
		||||
            url, method, **kwargs)
 | 
			
		||||
 | 
			
		||||
        if 400 <= resp.status_code < 600:
 | 
			
		||||
            raise exc.from_response(resp)
 | 
			
		||||
        elif resp.status_code in (301, 302, 305):
 | 
			
		||||
            if redirect:
 | 
			
		||||
                location = resp.headers.get('location')
 | 
			
		||||
                path = self.strip_endpoint(location)
 | 
			
		||||
                resp = self.request(path, method, **kwargs)
 | 
			
		||||
        elif resp.status_code == 300:
 | 
			
		||||
        if resp.status_code == 300 or (400 <= resp.status_code < 600):
 | 
			
		||||
            raise exc.from_response(resp)
 | 
			
		||||
 | 
			
		||||
        if resp.headers.get('Content-Type') == 'application/octet-stream':
 | 
			
		||||
            body = _close_after_stream(resp, CHUNKSIZE)
 | 
			
		||||
        return resp, body
 | 
			
		||||
 | 
			
		||||
    def strip_endpoint(self, location):
 | 
			
		||||
        if location is None:
 | 
			
		||||
            message = _("Location not returned with 302")
 | 
			
		||||
            raise exc.InvalidEndpoint(message=message)
 | 
			
		||||
        if (self.endpoint_override is not None and
 | 
			
		||||
                location.lower().startswith(self.endpoint_override.lower())):
 | 
			
		||||
                return location[len(self.endpoint_override):]
 | 
			
		||||
        else:
 | 
			
		||||
            return location
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def construct_http_client(*args, **kwargs):
 | 
			
		||||
    session = kwargs.pop('session', None)
 | 
			
		||||
 
 | 
			
		||||
@@ -13,9 +13,12 @@
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import decimal
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
SPIN_CHARS = ('-', '\\', '|', '/')
 | 
			
		||||
CHUNKSIZE = 1024 * 64  # 64kB
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _ProgressBarBase(object):
 | 
			
		||||
@@ -30,21 +33,37 @@ class _ProgressBarBase(object):
 | 
			
		||||
    :note: The progress will be displayed only if sys.stdout is a tty.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, wrapped, totalsize):
 | 
			
		||||
    def __init__(self, wrapped):
 | 
			
		||||
        self._wrapped = wrapped
 | 
			
		||||
        self._totalsize = float(totalsize)
 | 
			
		||||
        self._show_progress = sys.stdout.isatty()
 | 
			
		||||
        self._percent = 0
 | 
			
		||||
        self._totalread = 0
 | 
			
		||||
        self._spin_index = 0
 | 
			
		||||
        if hasattr(wrapped, "len"):
 | 
			
		||||
            self._totalsize = wrapped.len
 | 
			
		||||
        elif hasattr(wrapped, "fileno"):
 | 
			
		||||
            self._totalsize = os.fstat(wrapped.fileno()).st_size
 | 
			
		||||
        else:
 | 
			
		||||
            self._totalsize = 0
 | 
			
		||||
 | 
			
		||||
    def _display_progress_bar(self, size_read):
 | 
			
		||||
        self._totalread += size_read
 | 
			
		||||
        if self._show_progress:
 | 
			
		||||
            if self._totalsize == 0:
 | 
			
		||||
                self._totalsize = size_read
 | 
			
		||||
            self._percent += size_read / self._totalsize
 | 
			
		||||
            if self._totalsize:
 | 
			
		||||
                percent = float(self._totalread) / float(self._totalsize)
 | 
			
		||||
                # Output something like this: [==========>             ] 49%
 | 
			
		||||
                sys.stdout.write('\r[{0:<30}] {1:.0%}'.format(
 | 
			
		||||
                '=' * int(round(self._percent * 29)) + '>', self._percent
 | 
			
		||||
                    '=' * int(decimal.Decimal(percent * 29).quantize(
 | 
			
		||||
                        decimal.Decimal('1'),
 | 
			
		||||
                        rounding=decimal.ROUND_HALF_UP)) + '>', percent
 | 
			
		||||
                ))
 | 
			
		||||
            else:
 | 
			
		||||
                sys.stdout.write(
 | 
			
		||||
                    '\r[%s] %d bytes' % (SPIN_CHARS[self._spin_index],
 | 
			
		||||
                                         self._totalread))
 | 
			
		||||
                self._spin_index += 1
 | 
			
		||||
                if self._spin_index == len(SPIN_CHARS):
 | 
			
		||||
                    self._spin_index = 0
 | 
			
		||||
            sys.stdout.flush()
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, attr):
 | 
			
		||||
@@ -70,24 +89,12 @@ class VerboseFileWrapper(_ProgressBarBase):
 | 
			
		||||
                sys.stdout.write('\n')
 | 
			
		||||
        return data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VerboseIteratorWrapper(_ProgressBarBase):
 | 
			
		||||
    """An iterator wrapper with a progress bar.
 | 
			
		||||
 | 
			
		||||
    The iterator wrapper shows and advances a progress bar whenever the
 | 
			
		||||
    wrapped data is consumed from the iterator.
 | 
			
		||||
 | 
			
		||||
    :note: Use only with iterator that yield strings.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def next(self):
 | 
			
		||||
        try:
 | 
			
		||||
            data = six.next(self._wrapped)
 | 
			
		||||
            # NOTE(mouad): Assuming that data is a string b/c otherwise calling
 | 
			
		||||
            # len function will not make any sense.
 | 
			
		||||
            data = self._wrapped.next()
 | 
			
		||||
            self._display_progress_bar(len(data))
 | 
			
		||||
            return data
 | 
			
		||||
        except StopIteration:
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@ from __future__ import print_function
 | 
			
		||||
import errno
 | 
			
		||||
import hashlib
 | 
			
		||||
import os
 | 
			
		||||
import six
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
if os.name == 'nt':
 | 
			
		||||
@@ -28,6 +27,7 @@ else:
 | 
			
		||||
 | 
			
		||||
from oslo_utils import encodeutils
 | 
			
		||||
from oslo_utils import importutils
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
SENSITIVE_HEADERS = ('X-Auth-Token', )
 | 
			
		||||
 | 
			
		||||
@@ -58,41 +58,40 @@ def exit(msg='', exit_code=1):
 | 
			
		||||
    sys.exit(exit_code)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def integrity_iter(iter, checksum):
 | 
			
		||||
    """Check blob integrity.
 | 
			
		||||
class ResponseBlobWrapper(object):
 | 
			
		||||
    """Represent HTTP response as iterator with known length."""
 | 
			
		||||
 | 
			
		||||
    :raises: IOError
 | 
			
		||||
    """
 | 
			
		||||
    md5sum = hashlib.md5()
 | 
			
		||||
    for chunk in iter:
 | 
			
		||||
        yield chunk
 | 
			
		||||
        if isinstance(chunk, six.string_types):
 | 
			
		||||
            chunk = six.b(chunk)
 | 
			
		||||
        md5sum.update(chunk)
 | 
			
		||||
    md5sum = md5sum.hexdigest()
 | 
			
		||||
    if md5sum != checksum:
 | 
			
		||||
        raise IOError(errno.EPIPE,
 | 
			
		||||
                      'Corrupt blob download. Checksum was %s expected %s' %
 | 
			
		||||
                      (md5sum, checksum))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IterableWithLength(object):
 | 
			
		||||
    def __init__(self, iterable, length):
 | 
			
		||||
        self.iterable = iterable
 | 
			
		||||
        self.length = length
 | 
			
		||||
    def __init__(self, resp, verify_md5=True):
 | 
			
		||||
        self.hash_md5 = resp.headers.get("Content-MD5")
 | 
			
		||||
        self.check_md5 = hashlib.md5()
 | 
			
		||||
        if 301 <= resp.status_code <= 302:
 | 
			
		||||
            # NOTE(sskripnick): handle redirect manually to prevent sending
 | 
			
		||||
            # auth token to external resource.
 | 
			
		||||
            # Use stream=True to prevent reading whole response into memory.
 | 
			
		||||
            # Set Accept-Encoding explicitly to "identity" because setting
 | 
			
		||||
            # stream=True forces Accept-Encoding to be "gzip, defalate".
 | 
			
		||||
            # It should be "identity" because we should know Content-Length.
 | 
			
		||||
            resp = requests.get(resp.headers.get("Location"),
 | 
			
		||||
                                headers={"Accept-Encoding": "identity"})
 | 
			
		||||
        self.len = resp.headers.get("Content-Length", 0)
 | 
			
		||||
        self.iter = resp.iter_content(65536)
 | 
			
		||||
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        try:
 | 
			
		||||
            for chunk in self.iterable:
 | 
			
		||||
                yield chunk
 | 
			
		||||
        finally:
 | 
			
		||||
            self.iterable.close()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def next(self):
 | 
			
		||||
        return next(self.iterable)
 | 
			
		||||
        try:
 | 
			
		||||
            data = self.iter.next()
 | 
			
		||||
            self.check_md5.update(data)
 | 
			
		||||
            return data
 | 
			
		||||
        except StopIteration:
 | 
			
		||||
            if self.check_md5.hexdigest() != self.hash_md5:
 | 
			
		||||
                raise IOError(errno.EPIPE,
 | 
			
		||||
                              'Checksum mismatch: %s (expected %s)' %
 | 
			
		||||
                              (self.check_md5.hexdigest(), self.hash_md5))
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
    def __len__(self):
 | 
			
		||||
        return self.length
 | 
			
		||||
    __next__ = next
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_item_properties(item, fields, mixed_case_fields=None, formatters=None):
 | 
			
		||||
@@ -158,60 +157,3 @@ def save_blob(data, path):
 | 
			
		||||
    finally:
 | 
			
		||||
        if path is not None:
 | 
			
		||||
            blob.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_data_file(blob):
 | 
			
		||||
    if blob:
 | 
			
		||||
        return open(blob, 'rb')
 | 
			
		||||
    else:
 | 
			
		||||
        # distinguish cases where:
 | 
			
		||||
        # (1) stdin is not valid (as in cron jobs):
 | 
			
		||||
        #     glare ... <&-
 | 
			
		||||
        # (2) blob is provided through standard input:
 | 
			
		||||
        #     glare ... < /tmp/file or cat /tmp/file | glare ...
 | 
			
		||||
        # (3) no blob provided:
 | 
			
		||||
        #     glare ...
 | 
			
		||||
        try:
 | 
			
		||||
            os.fstat(0)
 | 
			
		||||
        except OSError:
 | 
			
		||||
            # (1) stdin is not valid (closed...)
 | 
			
		||||
            return None
 | 
			
		||||
        if not sys.stdin.isatty():
 | 
			
		||||
            # (2) blob data is provided through standard input
 | 
			
		||||
            blob_data = sys.stdin
 | 
			
		||||
            if hasattr(sys.stdin, 'buffer'):
 | 
			
		||||
                blob_data = sys.stdin.buffer
 | 
			
		||||
            if msvcrt:
 | 
			
		||||
                msvcrt.setmode(blob_data.fileno(), os.O_BINARY)
 | 
			
		||||
            return blob_data
 | 
			
		||||
        else:
 | 
			
		||||
            # (3) no blob data provided
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_file_size(file_obj):
 | 
			
		||||
    """Analyze file-like object and attempt to determine its size.
 | 
			
		||||
 | 
			
		||||
    :param file_obj: file-like object.
 | 
			
		||||
    :retval The file's size or None if it cannot be determined.
 | 
			
		||||
    """
 | 
			
		||||
    if (hasattr(file_obj, 'seek') and hasattr(file_obj, 'tell') and
 | 
			
		||||
            (six.PY2 or six.PY3 and file_obj.seekable())):
 | 
			
		||||
        try:
 | 
			
		||||
            curr = file_obj.tell()
 | 
			
		||||
            file_obj.seek(0, os.SEEK_END)
 | 
			
		||||
            size = file_obj.tell()
 | 
			
		||||
            file_obj.seek(curr)
 | 
			
		||||
            return size
 | 
			
		||||
        except IOError as e:
 | 
			
		||||
            if e.errno == errno.ESPIPE:
 | 
			
		||||
                # Illegal seek. This means the file object
 | 
			
		||||
                # is a pipe (e.g. the user is trying
 | 
			
		||||
                # to pipe blob to the client,
 | 
			
		||||
                # echo testdata | bin/glare add blah...), or
 | 
			
		||||
                # that file object is empty, or that a file-like
 | 
			
		||||
                # object which doesn't support 'seek/tell' has
 | 
			
		||||
                # been supplied.
 | 
			
		||||
                return
 | 
			
		||||
            else:
 | 
			
		||||
                raise
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ from osc_lib.command import command
 | 
			
		||||
 | 
			
		||||
from glareclient.common import progressbar
 | 
			
		||||
from glareclient.common import utils
 | 
			
		||||
from glareclient import exc
 | 
			
		||||
from glareclient.osc.v1 import TypeMapperAction
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
@@ -107,11 +108,15 @@ class UploadBlob(command.ShowOne):
 | 
			
		||||
            parsed_args.blob_property = _default_blob_property(
 | 
			
		||||
                parsed_args.type_name)
 | 
			
		||||
 | 
			
		||||
        blob = utils.get_data_file(parsed_args.file)
 | 
			
		||||
        if parsed_args.file is None:
 | 
			
		||||
            if sys.stdin.isatty():
 | 
			
		||||
                raise exc.CommandError('Blob file should be specified or '
 | 
			
		||||
                                       'explicitly connected to stdin')
 | 
			
		||||
            blob = sys.stdin
 | 
			
		||||
        else:
 | 
			
		||||
            blob = open(parsed_args.file, 'rb')
 | 
			
		||||
        if parsed_args.progress:
 | 
			
		||||
            file_size = utils.get_file_size(blob)
 | 
			
		||||
            if file_size is not None:
 | 
			
		||||
                blob = progressbar.VerboseFileWrapper(blob, file_size)
 | 
			
		||||
            blob = progressbar.VerboseFileWrapper(blob)
 | 
			
		||||
 | 
			
		||||
        client.artifacts.upload_blob(af_id, parsed_args.blob_property, blob,
 | 
			
		||||
                                     content_type=parsed_args.content_type,
 | 
			
		||||
@@ -186,7 +191,7 @@ class DownloadBlob(command.Command):
 | 
			
		||||
                                              parsed_args.blob_property,
 | 
			
		||||
                                              type_name=parsed_args.type_name)
 | 
			
		||||
        if parsed_args.progress:
 | 
			
		||||
            data = progressbar.VerboseIteratorWrapper(data, len(data))
 | 
			
		||||
            data = progressbar.VerboseFileWrapper(data)
 | 
			
		||||
        if not (sys.stdout.isatty() and parsed_args.file is None):
 | 
			
		||||
            utils.save_blob(data, parsed_args.file)
 | 
			
		||||
        else:
 | 
			
		||||
 
 | 
			
		||||
@@ -14,12 +14,108 @@
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
 | 
			
		||||
from glareclient import exc
 | 
			
		||||
from glareclient.osc.v1 import blobs as osc_blob
 | 
			
		||||
from glareclient.tests.unit.osc.v1 import fakes
 | 
			
		||||
from glareclient.v1 import artifacts as api_art
 | 
			
		||||
import testtools
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUpload(testtools.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.mock_app = mock.Mock()
 | 
			
		||||
        self.mock_args = mock.Mock()
 | 
			
		||||
        self.mock_manager = mock.Mock()
 | 
			
		||||
        self.mock_manager.artifacts.get.return_value = {'image': {}}
 | 
			
		||||
        super(TestUpload, self).setUp()
 | 
			
		||||
 | 
			
		||||
    @mock.patch('glareclient.osc.v1.blobs.progressbar')
 | 
			
		||||
    @mock.patch('glareclient.osc.v1.blobs.sys')
 | 
			
		||||
    @mock.patch('glareclient.osc.v1.blobs.open', create=True)
 | 
			
		||||
    @mock.patch('glareclient.osc.v1.blobs.get_artifact_id')
 | 
			
		||||
    def test_upload_file_progress(self, mock_get_id,
 | 
			
		||||
                                  mock_open, mock_sys, mock_progressbar):
 | 
			
		||||
        mock_parsed_args = mock.Mock(name='test-id',
 | 
			
		||||
                                     id=True,
 | 
			
		||||
                                     blob_property='image',
 | 
			
		||||
                                     file='/path/file',
 | 
			
		||||
                                     progress=True,
 | 
			
		||||
                                     content_type='application/test',
 | 
			
		||||
                                     type_name='test-type')
 | 
			
		||||
        mock_get_id.return_value = 'test-id'
 | 
			
		||||
        cli = osc_blob.UploadBlob(self.mock_app, self.mock_args)
 | 
			
		||||
        cli.app.client_manager.artifact = self.mock_manager
 | 
			
		||||
        cli.dict2columns = mock.Mock(return_value=42)
 | 
			
		||||
        self.assertEqual(42, cli.take_action(mock_parsed_args))
 | 
			
		||||
        cli.dict2columns.assert_called_once_with({'blob_property': 'image'})
 | 
			
		||||
        upload_args = ['test-id', 'image',
 | 
			
		||||
                       mock_progressbar.VerboseFileWrapper.return_value]
 | 
			
		||||
        upload_kwargs = {'content_type': 'application/test',
 | 
			
		||||
                         'type_name': 'test-type'}
 | 
			
		||||
        self.mock_manager.artifacts.upload_blob.\
 | 
			
		||||
            assert_called_once_with(*upload_args, **upload_kwargs)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('glareclient.osc.v1.blobs.sys')
 | 
			
		||||
    @mock.patch('glareclient.osc.v1.blobs.open', create=True)
 | 
			
		||||
    @mock.patch('glareclient.osc.v1.blobs.get_artifact_id')
 | 
			
		||||
    def test_upload_file_no_progress(self, mock_get_id, mock_open, mock_sys):
 | 
			
		||||
        mock_parsed_args = mock.Mock(name='test-id',
 | 
			
		||||
                                     id=True,
 | 
			
		||||
                                     blob_property='image',
 | 
			
		||||
                                     progress=False,
 | 
			
		||||
                                     file='/path/file',
 | 
			
		||||
                                     content_type='application/test',
 | 
			
		||||
                                     type_name='test-type')
 | 
			
		||||
        mock_get_id.return_value = 'test-id'
 | 
			
		||||
        cli = osc_blob.UploadBlob(self.mock_app, self.mock_args)
 | 
			
		||||
        cli.app.client_manager.artifact = self.mock_manager
 | 
			
		||||
        cli.dict2columns = mock.Mock(return_value=42)
 | 
			
		||||
        self.assertEqual(42, cli.take_action(mock_parsed_args))
 | 
			
		||||
        cli.dict2columns.assert_called_once_with({'blob_property': 'image'})
 | 
			
		||||
        upload_args = ['test-id', 'image', mock_open.return_value]
 | 
			
		||||
        upload_kwargs = {'content_type': 'application/test',
 | 
			
		||||
                         'type_name': 'test-type'}
 | 
			
		||||
        self.mock_manager.artifacts.upload_blob.\
 | 
			
		||||
            assert_called_once_with(*upload_args, **upload_kwargs)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('glareclient.osc.v1.blobs.sys')
 | 
			
		||||
    @mock.patch('glareclient.osc.v1.blobs.get_artifact_id')
 | 
			
		||||
    def test_upload_file_stdin(self, mock_get_id, mock_sys):
 | 
			
		||||
        mock_sys.stdin.isatty.return_value = False
 | 
			
		||||
        mock_parsed_args = mock.Mock(name='test-id',
 | 
			
		||||
                                     id=True,
 | 
			
		||||
                                     blob_property='image',
 | 
			
		||||
                                     progress=False,
 | 
			
		||||
                                     file=None,
 | 
			
		||||
                                     content_type='application/test',
 | 
			
		||||
                                     type_name='test-type')
 | 
			
		||||
        mock_get_id.return_value = 'test-id'
 | 
			
		||||
        cli = osc_blob.UploadBlob(self.mock_app, self.mock_args)
 | 
			
		||||
        cli.app.client_manager.artifact = self.mock_manager
 | 
			
		||||
        cli.dict2columns = mock.Mock(return_value=42)
 | 
			
		||||
        self.assertEqual(42, cli.take_action(mock_parsed_args))
 | 
			
		||||
        cli.dict2columns.assert_called_once_with({'blob_property': 'image'})
 | 
			
		||||
        upload_args = ['test-id', 'image', mock_sys.stdin]
 | 
			
		||||
        upload_kwargs = {'content_type': 'application/test',
 | 
			
		||||
                         'type_name': 'test-type'}
 | 
			
		||||
        self.mock_manager.artifacts.upload_blob.\
 | 
			
		||||
            assert_called_once_with(*upload_args, **upload_kwargs)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('glareclient.osc.v1.blobs.sys')
 | 
			
		||||
    def test_upload_file_stdin_isatty(self, mock_sys):
 | 
			
		||||
        mock_sys.stdin.isatty.return_value = True
 | 
			
		||||
        mock_parsed_args = mock.Mock(id='test-id',
 | 
			
		||||
                                     blob_property='image',
 | 
			
		||||
                                     progress=False,
 | 
			
		||||
                                     file=None,
 | 
			
		||||
                                     content_type='application/test',
 | 
			
		||||
                                     type_name='test-type')
 | 
			
		||||
        cli = osc_blob.UploadBlob(self.mock_app, self.mock_args)
 | 
			
		||||
        cli.app.client_manager.artifact = self.mock_manager
 | 
			
		||||
        self.assertRaises(exc.CommandError, cli.take_action, mock_parsed_args)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestBlobs(fakes.TestArtifacts):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestBlobs, self).setUp()
 | 
			
		||||
@@ -28,187 +124,6 @@ class TestBlobs(fakes.TestArtifacts):
 | 
			
		||||
        self.http = mock.MagicMock()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUploadBlob(TestBlobs):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestUploadBlob, self).setUp()
 | 
			
		||||
        self.blob_mock.call.return_value = \
 | 
			
		||||
            api_art.Controller(self.http, type_name='images')
 | 
			
		||||
 | 
			
		||||
        # Command to test
 | 
			
		||||
        self.cmd = osc_blob.UploadBlob(self.app, None)
 | 
			
		||||
 | 
			
		||||
        self.COLUMNS = ('blob_property', 'content_type', 'external',
 | 
			
		||||
                        'md5', 'sha1', 'sha256', 'size', 'status', 'url')
 | 
			
		||||
 | 
			
		||||
    def test_upload_images(self):
 | 
			
		||||
        exp_data = ('image', 'application/octet-stream', False,
 | 
			
		||||
                    '35d83e8eedfbdb87ff97d1f2761f8ebf',
 | 
			
		||||
                    '942854360eeec1335537702399c5aed940401602',
 | 
			
		||||
                    'd8a7834fc6652f316322d80196f6dcf2'
 | 
			
		||||
                    '94417030e37c15412e4deb7a67a367dd',
 | 
			
		||||
                    594, 'active', 'fake_url')
 | 
			
		||||
        arglist = ['images',
 | 
			
		||||
                   'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba',
 | 
			
		||||
                   '--file', '/path/to/file']
 | 
			
		||||
        verify = [('type_name', 'images')]
 | 
			
		||||
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verify)
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.assertEqual(self.COLUMNS, columns)
 | 
			
		||||
        self.assertEqual(exp_data, data)
 | 
			
		||||
 | 
			
		||||
    def test_upload_tosca_template(self):
 | 
			
		||||
        exp_data = ('template', 'application/octet-stream', False,
 | 
			
		||||
                    '35d83e8eedfbdb87ff97d1f2761f8ebf',
 | 
			
		||||
                    '942854360eeec1335537702399c5aed940401602',
 | 
			
		||||
                    'd8a7834fc6652f316322d80196f6dcf2'
 | 
			
		||||
                    '94417030e37c15412e4deb7a67a367dd',
 | 
			
		||||
                    594, 'active', 'fake_url')
 | 
			
		||||
        arglist = ['tosca_templates',
 | 
			
		||||
                   'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba',
 | 
			
		||||
                   '--file', '/path/to/file']
 | 
			
		||||
        verify = [('type_name', 'tosca_templates')]
 | 
			
		||||
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verify)
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.assertEqual(self.COLUMNS, columns)
 | 
			
		||||
        self.assertEqual(exp_data, data)
 | 
			
		||||
 | 
			
		||||
    def test_upload_heat_template(self):
 | 
			
		||||
        exp_data = ('template', 'application/octet-stream', False,
 | 
			
		||||
                    '35d83e8eedfbdb87ff97d1f2761f8ebf',
 | 
			
		||||
                    '942854360eeec1335537702399c5aed940401602',
 | 
			
		||||
                    'd8a7834fc6652f316322d80196f6dcf2'
 | 
			
		||||
                    '94417030e37c15412e4deb7a67a367dd',
 | 
			
		||||
                    594, 'active', 'fake_url')
 | 
			
		||||
        arglist = ['heat_templates',
 | 
			
		||||
                   'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba',
 | 
			
		||||
                   '--file', '/path/to/file']
 | 
			
		||||
        verify = [('type_name', 'heat_templates')]
 | 
			
		||||
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verify)
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.assertEqual(self.COLUMNS, columns)
 | 
			
		||||
        self.assertEqual(exp_data, data)
 | 
			
		||||
 | 
			
		||||
    def test_upload_environment(self):
 | 
			
		||||
        exp_data = ('environment', 'application/octet-stream', False,
 | 
			
		||||
                    '35d83e8eedfbdb87ff97d1f2761f8ebf',
 | 
			
		||||
                    '942854360eeec1335537702399c5aed940401602',
 | 
			
		||||
                    'd8a7834fc6652f316322d80196f6dcf2'
 | 
			
		||||
                    '94417030e37c15412e4deb7a67a367dd',
 | 
			
		||||
                    594, 'active', 'fake_url')
 | 
			
		||||
        arglist = ['heat_environments',
 | 
			
		||||
                   'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba',
 | 
			
		||||
                   '--file', '/path/to/file']
 | 
			
		||||
        verify = [('type_name', 'heat_environments')]
 | 
			
		||||
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verify)
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.assertEqual(self.COLUMNS, columns)
 | 
			
		||||
        self.assertEqual(exp_data, data)
 | 
			
		||||
 | 
			
		||||
    def test_upload_package(self):
 | 
			
		||||
        exp_data = ('package', 'application/octet-stream', False,
 | 
			
		||||
                    '35d83e8eedfbdb87ff97d1f2761f8ebf',
 | 
			
		||||
                    '942854360eeec1335537702399c5aed940401602',
 | 
			
		||||
                    'd8a7834fc6652f316322d80196f6dcf2'
 | 
			
		||||
                    '94417030e37c15412e4deb7a67a367dd',
 | 
			
		||||
                    594, 'active', 'fake_url')
 | 
			
		||||
        arglist = ['murano_packages',
 | 
			
		||||
                   'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba',
 | 
			
		||||
                   '--file', '/path/to/file']
 | 
			
		||||
        verify = [('type_name', 'murano_packages')]
 | 
			
		||||
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verify)
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.assertEqual(self.COLUMNS, columns)
 | 
			
		||||
        self.assertEqual(exp_data, data)
 | 
			
		||||
 | 
			
		||||
    def test_upload_bad(self):
 | 
			
		||||
        arglist = ['user_type',
 | 
			
		||||
                   'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba',
 | 
			
		||||
                   '--file', '/path/to/file']
 | 
			
		||||
        verify = [('type_name', 'user_type')]
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verify)
 | 
			
		||||
        with testtools.ExpectedException(SystemExit):
 | 
			
		||||
            self.cmd.take_action(parsed_args)
 | 
			
		||||
 | 
			
		||||
    def test_upload_with_custom_content_type(self):
 | 
			
		||||
        exp_data = ('template', 'application/x-yaml', False,
 | 
			
		||||
                    '35d83e8eedfbdb87ff97d1f2761f8ebf',
 | 
			
		||||
                    '942854360eeec1335537702399c5aed940401602',
 | 
			
		||||
                    'd8a7834fc6652f316322d80196f6dcf2'
 | 
			
		||||
                    '94417030e37c15412e4deb7a67a367dd',
 | 
			
		||||
                    594, 'active', 'fake_url')
 | 
			
		||||
 | 
			
		||||
        mocked_get = {
 | 
			
		||||
            "status": "active",
 | 
			
		||||
            "url": "fake_url",
 | 
			
		||||
            "md5": "35d83e8eedfbdb87ff97d1f2761f8ebf",
 | 
			
		||||
            "sha1": "942854360eeec1335537702399c5aed940401602",
 | 
			
		||||
            "sha256": "d8a7834fc6652f316322d80196f6dcf2"
 | 
			
		||||
                      "94417030e37c15412e4deb7a67a367dd",
 | 
			
		||||
            "external": False,
 | 
			
		||||
            "content_type": "application/x-yaml",
 | 
			
		||||
            "size": 594}
 | 
			
		||||
        self.app.client_manager.artifact.artifacts.get = \
 | 
			
		||||
            lambda id, type_name: {'template': mocked_get}
 | 
			
		||||
 | 
			
		||||
        arglist = ['tosca_templates',
 | 
			
		||||
                   'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba',
 | 
			
		||||
                   '--file', '/path/to/file',
 | 
			
		||||
                   '--content-type', 'application/x-yaml']
 | 
			
		||||
        verify = [('type_name', 'tosca_templates')]
 | 
			
		||||
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verify)
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.assertEqual(self.COLUMNS, columns)
 | 
			
		||||
        self.assertEqual(exp_data, data)
 | 
			
		||||
 | 
			
		||||
    def test_upload_blob_with_blob_prop(self):
 | 
			
		||||
        exp_data = ('blob', 'application/octet-stream', False,
 | 
			
		||||
                    '35d83e8eedfbdb87ff97d1f2761f8ebf',
 | 
			
		||||
                    '942854360eeec1335537702399c5aed940401602',
 | 
			
		||||
                    'd8a7834fc6652f316322d80196f6dcf2'
 | 
			
		||||
                    '94417030e37c15412e4deb7a67a367dd',
 | 
			
		||||
                    594, 'active', 'fake_url')
 | 
			
		||||
        arglist = ['images',
 | 
			
		||||
                   'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba',
 | 
			
		||||
                   '--file', '/path/to/file',
 | 
			
		||||
                   '--blob-property', 'blob']
 | 
			
		||||
        verify = [('type_name', 'images')]
 | 
			
		||||
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verify)
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.assertEqual(self.COLUMNS, columns)
 | 
			
		||||
        self.assertEqual(exp_data, data)
 | 
			
		||||
 | 
			
		||||
    def test_upload_blob_dict(self):
 | 
			
		||||
        exp_data = ('nested_templates/blob', 'application/octet-stream',
 | 
			
		||||
                    False,
 | 
			
		||||
                    '35d83e8eedfbdb87ff97d1f2761f8ebf',
 | 
			
		||||
                    '942854360eeec1335537702399c5aed940401602',
 | 
			
		||||
                    'd8a7834fc6652f316322d80196f6dcf2'
 | 
			
		||||
                    '94417030e37c15412e4deb7a67a367dd',
 | 
			
		||||
                    594, 'active', 'fake_url')
 | 
			
		||||
        arglist = ['images',
 | 
			
		||||
                   'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba',
 | 
			
		||||
                   '--file', '/path/to/file',
 | 
			
		||||
                   '--blob-property', 'nested_templates/blob']
 | 
			
		||||
        verify = [('type_name', 'images')]
 | 
			
		||||
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verify)
 | 
			
		||||
        self.app.client_manager.artifact.artifacts.get = \
 | 
			
		||||
            lambda *args, **kwargs: {
 | 
			
		||||
                'nested_templates': {'blob': fakes.blob_fixture}
 | 
			
		||||
            }
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.app.client_manager.artifact.artifacts.get = fakes.mock_get
 | 
			
		||||
        self.assertEqual(self.COLUMNS, columns)
 | 
			
		||||
        self.assertEqual(exp_data, data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestDownloadBlob(TestBlobs):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestDownloadBlob, self).setUp()
 | 
			
		||||
 
 | 
			
		||||
@@ -119,10 +119,7 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
        mock_request.assert_called_once_with(
 | 
			
		||||
            'GET', 'http://example.com:9494',
 | 
			
		||||
            allow_redirects=False,
 | 
			
		||||
            stream=False,
 | 
			
		||||
            data=None,
 | 
			
		||||
            headers={'Content-Type': 'application/json',
 | 
			
		||||
                     'User-Agent': 'python-glareclient'})
 | 
			
		||||
            headers={'User-Agent': 'python-glareclient'})
 | 
			
		||||
 | 
			
		||||
    def test_http_json_request_argument_passed_to_requests(self, mock_request):
 | 
			
		||||
        """Check that we have sent the proper arguments to requests."""
 | 
			
		||||
@@ -148,9 +145,7 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
            cert=('RANDOM_CERT_FILE', 'RANDOM_KEY_FILE'),
 | 
			
		||||
            verify=True,
 | 
			
		||||
            data='text',
 | 
			
		||||
            stream=False,
 | 
			
		||||
            headers={'Content-Type': 'application/json',
 | 
			
		||||
                     'X-Auth-Url': 'http://AUTH_URL',
 | 
			
		||||
            headers={'X-Auth-Url': 'http://AUTH_URL',
 | 
			
		||||
                     'User-Agent': 'python-glareclient'})
 | 
			
		||||
 | 
			
		||||
    def test_http_json_request_w_req_body(self, mock_request):
 | 
			
		||||
@@ -169,9 +164,7 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
            'GET', 'http://example.com:9494',
 | 
			
		||||
            data='test-body',
 | 
			
		||||
            allow_redirects=False,
 | 
			
		||||
            stream=False,
 | 
			
		||||
            headers={'Content-Type': 'application/json',
 | 
			
		||||
                     'User-Agent': 'python-glareclient'})
 | 
			
		||||
            headers={'User-Agent': 'python-glareclient'})
 | 
			
		||||
 | 
			
		||||
    def test_http_json_request_non_json_resp_cont_type(self, mock_request):
 | 
			
		||||
        # Record a 200
 | 
			
		||||
@@ -187,9 +180,7 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
        mock_request.assert_called_once_with(
 | 
			
		||||
            'GET', 'http://example.com:9494', data='test-data',
 | 
			
		||||
            allow_redirects=False,
 | 
			
		||||
            stream=False,
 | 
			
		||||
            headers={'Content-Type': 'application/json',
 | 
			
		||||
                     'User-Agent': 'python-glareclient'})
 | 
			
		||||
            headers={'User-Agent': 'python-glareclient'})
 | 
			
		||||
 | 
			
		||||
    def test_http_json_request_invalid_json(self, mock_request):
 | 
			
		||||
        # Record a 200
 | 
			
		||||
@@ -206,10 +197,7 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
        mock_request.assert_called_once_with(
 | 
			
		||||
            'GET', 'http://example.com:9494',
 | 
			
		||||
            allow_redirects=False,
 | 
			
		||||
            stream=False,
 | 
			
		||||
            data=None,
 | 
			
		||||
            headers={'Content-Type': 'application/json',
 | 
			
		||||
                     'User-Agent': 'python-glareclient'})
 | 
			
		||||
            headers={'User-Agent': 'python-glareclient'})
 | 
			
		||||
 | 
			
		||||
    def test_http_manual_redirect_delete(self, mock_request):
 | 
			
		||||
        mock_request.side_effect = [
 | 
			
		||||
@@ -229,16 +217,10 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
        mock_request.assert_has_calls([
 | 
			
		||||
            mock.call('DELETE', 'http://example.com:9494/foo',
 | 
			
		||||
                      allow_redirects=False,
 | 
			
		||||
                      stream=False,
 | 
			
		||||
                      data=None,
 | 
			
		||||
                      headers={'Content-Type': 'application/json',
 | 
			
		||||
                               'User-Agent': 'python-glareclient'}),
 | 
			
		||||
                      headers={'User-Agent': 'python-glareclient'}),
 | 
			
		||||
            mock.call('DELETE', 'http://example.com:9494/foo/bar',
 | 
			
		||||
                      allow_redirects=False,
 | 
			
		||||
                      stream=False,
 | 
			
		||||
                      data=None,
 | 
			
		||||
                      headers={'Content-Type': 'application/json',
 | 
			
		||||
                               'User-Agent': 'python-glareclient'})
 | 
			
		||||
                      headers={'User-Agent': 'python-glareclient'})
 | 
			
		||||
        ])
 | 
			
		||||
 | 
			
		||||
    def test_http_manual_redirect_post(self, mock_request):
 | 
			
		||||
@@ -253,22 +235,18 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
                '{}')]
 | 
			
		||||
 | 
			
		||||
        client = http.HTTPClient('http://example.com:9494/foo')
 | 
			
		||||
        resp, body = client.json_request('', 'POST')
 | 
			
		||||
        resp, body = client.json_request('', 'POST', json={})
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(200, resp.status_code)
 | 
			
		||||
        mock_request.assert_has_calls([
 | 
			
		||||
            mock.call('POST', 'http://example.com:9494/foo',
 | 
			
		||||
                      allow_redirects=False,
 | 
			
		||||
                      stream=False,
 | 
			
		||||
                      data=None,
 | 
			
		||||
                      headers={'Content-Type': 'application/json',
 | 
			
		||||
                               'User-Agent': 'python-glareclient'}),
 | 
			
		||||
                      headers={'User-Agent': 'python-glareclient'},
 | 
			
		||||
                      json={}),
 | 
			
		||||
            mock.call('POST', 'http://example.com:9494/foo/bar',
 | 
			
		||||
                      allow_redirects=False,
 | 
			
		||||
                      stream=False,
 | 
			
		||||
                      data=None,
 | 
			
		||||
                      headers={'Content-Type': 'application/json',
 | 
			
		||||
                               'User-Agent': 'python-glareclient'})
 | 
			
		||||
                      headers={'User-Agent': 'python-glareclient'},
 | 
			
		||||
                      json={})
 | 
			
		||||
        ])
 | 
			
		||||
 | 
			
		||||
    def test_http_manual_redirect_put(self, mock_request):
 | 
			
		||||
@@ -283,22 +261,18 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
                '{}')]
 | 
			
		||||
 | 
			
		||||
        client = http.HTTPClient('http://example.com:9494/foo')
 | 
			
		||||
        resp, body = client.json_request('', 'PUT')
 | 
			
		||||
        resp, body = client.json_request('', 'PUT', json={})
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(200, resp.status_code)
 | 
			
		||||
        mock_request.assert_has_calls([
 | 
			
		||||
            mock.call('PUT', 'http://example.com:9494/foo',
 | 
			
		||||
                      allow_redirects=False,
 | 
			
		||||
                      stream=False,
 | 
			
		||||
                      data=None,
 | 
			
		||||
                      headers={'Content-Type': 'application/json',
 | 
			
		||||
                               'User-Agent': 'python-glareclient'}),
 | 
			
		||||
                      headers={'User-Agent': 'python-glareclient'},
 | 
			
		||||
                      json={}),
 | 
			
		||||
            mock.call('PUT', 'http://example.com:9494/foo/bar',
 | 
			
		||||
                      allow_redirects=False,
 | 
			
		||||
                      stream=False,
 | 
			
		||||
                      data=None,
 | 
			
		||||
                      headers={'Content-Type': 'application/json',
 | 
			
		||||
                               'User-Agent': 'python-glareclient'})
 | 
			
		||||
                      headers={'User-Agent': 'python-glareclient'},
 | 
			
		||||
                      json={})
 | 
			
		||||
        ])
 | 
			
		||||
 | 
			
		||||
    def test_http_manual_redirect_prohibited(self, mock_request):
 | 
			
		||||
@@ -313,10 +287,7 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
        mock_request.assert_called_once_with(
 | 
			
		||||
            'DELETE', 'http://example.com:9494/foo',
 | 
			
		||||
            allow_redirects=False,
 | 
			
		||||
            stream=False,
 | 
			
		||||
            data=None,
 | 
			
		||||
            headers={'Content-Type': 'application/json',
 | 
			
		||||
                     'User-Agent': 'python-glareclient'})
 | 
			
		||||
            headers={'User-Agent': 'python-glareclient'})
 | 
			
		||||
 | 
			
		||||
    def test_http_manual_redirect_error_without_location(self, mock_request):
 | 
			
		||||
        mock_request.return_value = \
 | 
			
		||||
@@ -330,10 +301,7 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
        mock_request.assert_called_once_with(
 | 
			
		||||
            'DELETE', 'http://example.com:9494/foo',
 | 
			
		||||
            allow_redirects=False,
 | 
			
		||||
            stream=False,
 | 
			
		||||
            data=None,
 | 
			
		||||
            headers={'Content-Type': 'application/json',
 | 
			
		||||
                     'User-Agent': 'python-glareclient'})
 | 
			
		||||
            headers={'User-Agent': 'python-glareclient'})
 | 
			
		||||
 | 
			
		||||
    def test_http_json_request_redirect(self, mock_request):
 | 
			
		||||
        # Record the 302
 | 
			
		||||
@@ -344,7 +312,7 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
                ''),
 | 
			
		||||
            fakes.FakeHTTPResponse(
 | 
			
		||||
                200, 'OK',
 | 
			
		||||
                {'content-type': 'application/json'},
 | 
			
		||||
                {},
 | 
			
		||||
                '{}')]
 | 
			
		||||
 | 
			
		||||
        client = http.HTTPClient('http://example.com:9494')
 | 
			
		||||
@@ -355,16 +323,10 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
        mock_request.assert_has_calls([
 | 
			
		||||
            mock.call('GET', 'http://example.com:9494',
 | 
			
		||||
                      allow_redirects=False,
 | 
			
		||||
                      stream=False,
 | 
			
		||||
                      data=None,
 | 
			
		||||
                      headers={'Content-Type': 'application/json',
 | 
			
		||||
                               'User-Agent': 'python-glareclient'}),
 | 
			
		||||
                      headers={'User-Agent': 'python-glareclient'}),
 | 
			
		||||
            mock.call('GET', 'http://example.com:9494',
 | 
			
		||||
                      allow_redirects=False,
 | 
			
		||||
                      stream=False,
 | 
			
		||||
                      data=None,
 | 
			
		||||
                      headers={'Content-Type': 'application/json',
 | 
			
		||||
                               'User-Agent': 'python-glareclient'})
 | 
			
		||||
                      headers={'User-Agent': 'python-glareclient'})
 | 
			
		||||
        ])
 | 
			
		||||
 | 
			
		||||
    def test_http_404_json_request(self, mock_request):
 | 
			
		||||
@@ -381,10 +343,7 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
        mock_request.assert_called_once_with(
 | 
			
		||||
            'GET', 'http://example.com:9494',
 | 
			
		||||
            allow_redirects=False,
 | 
			
		||||
            stream=False,
 | 
			
		||||
            data=None,
 | 
			
		||||
            headers={'Content-Type': 'application/json',
 | 
			
		||||
                     'User-Agent': 'python-glareclient'})
 | 
			
		||||
            headers={'User-Agent': 'python-glareclient'})
 | 
			
		||||
 | 
			
		||||
    def test_http_300_json_request(self, mock_request):
 | 
			
		||||
        mock_request.return_value = \
 | 
			
		||||
@@ -401,10 +360,7 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
        mock_request.assert_called_once_with(
 | 
			
		||||
            'GET', 'http://example.com:9494',
 | 
			
		||||
            allow_redirects=False,
 | 
			
		||||
            stream=False,
 | 
			
		||||
            data=None,
 | 
			
		||||
            headers={'Content-Type': 'application/json',
 | 
			
		||||
                     'User-Agent': 'python-glareclient'})
 | 
			
		||||
            headers={'User-Agent': 'python-glareclient'})
 | 
			
		||||
 | 
			
		||||
    def test_fake_json_request(self, mock_request):
 | 
			
		||||
        headers = {'User-Agent': 'python-glareclient'}
 | 
			
		||||
@@ -453,10 +409,7 @@ class HttpClientTest(testtools.TestCase):
 | 
			
		||||
        mock_request.assert_called_once_with(
 | 
			
		||||
            'GET', 'http://example.com:9494',
 | 
			
		||||
            allow_redirects=False,
 | 
			
		||||
            stream=False,
 | 
			
		||||
            data=None,
 | 
			
		||||
            headers={'Content-Type': 'application/json',
 | 
			
		||||
                     'User-Agent': 'python-glareclient'},
 | 
			
		||||
            headers={'User-Agent': 'python-glareclient'},
 | 
			
		||||
            timeout=float(123))
 | 
			
		||||
 | 
			
		||||
    def test_get_system_ca_file(self, mock_request):
 | 
			
		||||
 
 | 
			
		||||
@@ -13,63 +13,65 @@
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
import mock
 | 
			
		||||
from six import StringIO
 | 
			
		||||
import testtools
 | 
			
		||||
 | 
			
		||||
from glareclient.common import progressbar
 | 
			
		||||
from glareclient.tests import utils as test_utils
 | 
			
		||||
 | 
			
		||||
MOD = 'glareclient.common.progressbar'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestProgressBarWrapper(testtools.TestCase):
 | 
			
		||||
class TestProgressBar(testtools.TestCase):
 | 
			
		||||
 | 
			
		||||
    def test_iter_iterator_display_progress_bar(self):
 | 
			
		||||
        size = 100
 | 
			
		||||
        iterator = iter('X' * 100)
 | 
			
		||||
        saved_stdout = sys.stdout
 | 
			
		||||
        try:
 | 
			
		||||
            sys.stdout = output = test_utils.FakeTTYStdout()
 | 
			
		||||
            # Consume iterator.
 | 
			
		||||
            data = list(progressbar.VerboseIteratorWrapper(iterator, size))
 | 
			
		||||
            self.assertEqual(['X'] * 100, data)
 | 
			
		||||
            self.assertEqual(
 | 
			
		||||
                '[%s>] 100%%\n' % ('=' * 29),
 | 
			
		||||
                output.getvalue()
 | 
			
		||||
            )
 | 
			
		||||
        finally:
 | 
			
		||||
            sys.stdout = saved_stdout
 | 
			
		||||
    @mock.patch(MOD + '.os')
 | 
			
		||||
    def test_totalsize_fileno(self, mock_os):
 | 
			
		||||
        mock_os.fstat.return_value.st_size = 43
 | 
			
		||||
        fake_file = mock.Mock()
 | 
			
		||||
        del fake_file.len
 | 
			
		||||
        fake_file.fileno.return_value = 42
 | 
			
		||||
        pb = progressbar.VerboseFileWrapper(fake_file)
 | 
			
		||||
        self.assertEqual(43, pb._totalsize)
 | 
			
		||||
        mock_os.fstat.assert_called_once_with(42)
 | 
			
		||||
 | 
			
		||||
    def test_iter_file_display_progress_bar(self):
 | 
			
		||||
        size = 98304
 | 
			
		||||
        file_obj = six.StringIO('X' * size)
 | 
			
		||||
        saved_stdout = sys.stdout
 | 
			
		||||
        try:
 | 
			
		||||
            sys.stdout = output = test_utils.FakeTTYStdout()
 | 
			
		||||
            file_obj = progressbar.VerboseFileWrapper(file_obj, size)
 | 
			
		||||
            chunksize = 1024
 | 
			
		||||
            chunk = file_obj.read(chunksize)
 | 
			
		||||
            while chunk:
 | 
			
		||||
                chunk = file_obj.read(chunksize)
 | 
			
		||||
            self.assertEqual(
 | 
			
		||||
                '[%s>] 100%%\n' % ('=' * 29),
 | 
			
		||||
                output.getvalue()
 | 
			
		||||
            )
 | 
			
		||||
        finally:
 | 
			
		||||
            sys.stdout = saved_stdout
 | 
			
		||||
    @mock.patch(MOD + '.sys')
 | 
			
		||||
    def test__display_progress_bar(self, mock_sys):
 | 
			
		||||
        fake_file = StringIO('test')  # 4 bytes
 | 
			
		||||
        fake_file.len = 4
 | 
			
		||||
        pb = progressbar.VerboseFileWrapper(fake_file)
 | 
			
		||||
        pb._display_progress_bar(2)  # 2 of 4 bytes = 50%
 | 
			
		||||
        pb._display_progress_bar(1)  # 3 of 4 bytes = 75%
 | 
			
		||||
        pb._display_progress_bar(1)  # 4 of 4 bytes = 100%
 | 
			
		||||
        expected = [
 | 
			
		||||
            mock.call('\r[===============>              ] 50%'),
 | 
			
		||||
            mock.call('\r[======================>       ] 75%'),
 | 
			
		||||
            mock.call('\r[=============================>] 100%'),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expected, mock_sys.stdout.write.mock_calls)
 | 
			
		||||
 | 
			
		||||
    def test_iter_file_no_tty(self):
 | 
			
		||||
        size = 98304
 | 
			
		||||
        file_obj = six.StringIO('X' * size)
 | 
			
		||||
        saved_stdout = sys.stdout
 | 
			
		||||
        try:
 | 
			
		||||
            sys.stdout = output = test_utils.FakeNoTTYStdout()
 | 
			
		||||
            file_obj = progressbar.VerboseFileWrapper(file_obj, size)
 | 
			
		||||
            chunksize = 1024
 | 
			
		||||
            chunk = file_obj.read(chunksize)
 | 
			
		||||
            while chunk:
 | 
			
		||||
                chunk = file_obj.read(chunksize)
 | 
			
		||||
            # If stdout is not a tty progress bar should do nothing.
 | 
			
		||||
            self.assertEqual('', output.getvalue())
 | 
			
		||||
        finally:
 | 
			
		||||
            sys.stdout = saved_stdout
 | 
			
		||||
    @mock.patch(MOD + '.sys')
 | 
			
		||||
    def test__display_progress_bar_unknown_len(self, mock_sys):
 | 
			
		||||
        fake_file = StringIO('')
 | 
			
		||||
        fake_file.len = 0
 | 
			
		||||
        pb = progressbar.VerboseFileWrapper(fake_file)
 | 
			
		||||
        for i in range(6):
 | 
			
		||||
            pb._display_progress_bar(1)
 | 
			
		||||
        expected = [
 | 
			
		||||
            mock.call('\r[-] 1 bytes'),
 | 
			
		||||
            mock.call('\r[\\] 2 bytes'),
 | 
			
		||||
            mock.call('\r[|] 3 bytes'),
 | 
			
		||||
            mock.call('\r[/] 4 bytes'),
 | 
			
		||||
            mock.call('\r[-] 5 bytes'),
 | 
			
		||||
            mock.call('\r[\\] 6 bytes'),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expected, mock_sys.stdout.write.mock_calls)
 | 
			
		||||
 | 
			
		||||
    @mock.patch(MOD + '._ProgressBarBase.__init__')
 | 
			
		||||
    @mock.patch(MOD + '._ProgressBarBase._display_progress_bar')
 | 
			
		||||
    def test_read(self, mock_display_progress_bar, mock_init):
 | 
			
		||||
        mock_init.return_value = None
 | 
			
		||||
        pb = progressbar.VerboseFileWrapper()
 | 
			
		||||
        pb._wrapped = mock.Mock(len=42)
 | 
			
		||||
        pb._wrapped.read.return_value = 'ok'
 | 
			
		||||
        pb.read(2)
 | 
			
		||||
        mock_display_progress_bar.assert_called_once_with(2)
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,6 @@
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
import six
 | 
			
		||||
import testtools
 | 
			
		||||
 | 
			
		||||
from glareclient.common import utils
 | 
			
		||||
@@ -30,36 +28,3 @@ class TestUtils(testtools.TestCase):
 | 
			
		||||
        self.assertEqual("1.4GB", utils.make_size_human_readable(1476395008))
 | 
			
		||||
        self.assertEqual("9.3MB", utils.make_size_human_readable(9761280))
 | 
			
		||||
        self.assertEqual("0B", utils.make_size_human_readable(None))
 | 
			
		||||
 | 
			
		||||
    def test_get_new_file_size(self):
 | 
			
		||||
        size = 98304
 | 
			
		||||
        file_obj = six.StringIO('X' * size)
 | 
			
		||||
        try:
 | 
			
		||||
            self.assertEqual(size, utils.get_file_size(file_obj))
 | 
			
		||||
            # Check that get_file_size didn't change original file position.
 | 
			
		||||
            self.assertEqual(0, file_obj.tell())
 | 
			
		||||
        finally:
 | 
			
		||||
            file_obj.close()
 | 
			
		||||
 | 
			
		||||
    def test_get_consumed_file_size(self):
 | 
			
		||||
        size, consumed = 98304, 304
 | 
			
		||||
        file_obj = six.StringIO('X' * size)
 | 
			
		||||
        file_obj.seek(consumed)
 | 
			
		||||
        try:
 | 
			
		||||
            self.assertEqual(size, utils.get_file_size(file_obj))
 | 
			
		||||
            # Check that get_file_size didn't change original file position.
 | 
			
		||||
            self.assertEqual(consumed, file_obj.tell())
 | 
			
		||||
        finally:
 | 
			
		||||
            file_obj.close()
 | 
			
		||||
 | 
			
		||||
    def test_iterable_closes(self):
 | 
			
		||||
        # Regression test for bug 1461678.
 | 
			
		||||
        def _iterate(i):
 | 
			
		||||
            for chunk in i:
 | 
			
		||||
                raise(IOError)
 | 
			
		||||
 | 
			
		||||
        data = six.moves.StringIO('somestring')
 | 
			
		||||
        data.close = mock.Mock()
 | 
			
		||||
        i = utils.IterableWithLength(data, 10)
 | 
			
		||||
        self.assertRaises(IOError, _iterate, i)
 | 
			
		||||
        data.close.assert_called_with()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,261 +0,0 @@
 | 
			
		||||
# Copyright 2016 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.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
data_fixtures = {
 | 
			
		||||
    '/artifacts/images?limit=20': {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            # headers
 | 
			
		||||
            {},
 | 
			
		||||
            # response
 | 
			
		||||
            {'images': [
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art1',
 | 
			
		||||
                    'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art2',
 | 
			
		||||
                    'id': 'db721fb0-5b85-4738-9401-f161d541de5e',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art3',
 | 
			
		||||
                    'id': 'e4f027d2-bff3-4084-a2ba-f31cb5e3067f',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                },
 | 
			
		||||
            ]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images?page_size=2': {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {'images': [
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art1',
 | 
			
		||||
                    'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art2',
 | 
			
		||||
                    'id': 'db721fb0-5b85-4738-9401-f161d541de5e',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                },
 | 
			
		||||
            ]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images?limit=2': {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {'images': [
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art1',
 | 
			
		||||
                    'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art2',
 | 
			
		||||
                    'id': 'db721fb0-5b85-4738-9401-f161d541de5e',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                }],
 | 
			
		||||
             'next': '/artifacts/images?'
 | 
			
		||||
                     'marker=e1090471-1d12-4935-a8d8-a9351266ece8&limit=2'},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images?'
 | 
			
		||||
    'limit=2&marker=e1090471-1d12-4935-a8d8-a9351266ece8': {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {'images': [
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art3',
 | 
			
		||||
                    'id': 'e1090471-1d12-4935-a8d8-a9351266ece8',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                }
 | 
			
		||||
            ]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images?limit=20&sort=name%3Adesc': {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {'images': [
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art2',
 | 
			
		||||
                    'id': 'e4f027d2-bff3-4084-a2ba-f31cb5e3067f',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art1',
 | 
			
		||||
                    'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
                'next': '/artifacts/images?'
 | 
			
		||||
                'marker=3a4560a1-e585-443e-9b39-553b46ec92d1&limit=20'},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images?limit=20&sort=name': {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {'images': [
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art2',
 | 
			
		||||
                    'id': 'e4f027d2-bff3-4084-a2ba-f31cb5e3067f',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art1',
 | 
			
		||||
                    'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
                'next': '/artifacts/images?'
 | 
			
		||||
                'marker=3a4560a1-e585-443e-9b39-553b46ec92d1&limit=20'},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images?'
 | 
			
		||||
    'limit=20&marker=3a4560a1-e585-443e-9b39-553b46ec92d1': {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {'images': [
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art1',
 | 
			
		||||
                    'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                }
 | 
			
		||||
            ]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images': {
 | 
			
		||||
        'POST': (
 | 
			
		||||
            {},
 | 
			
		||||
            {'images': [
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art_1',
 | 
			
		||||
                    'id': '3a4560a1-e585-443e-9b39-553b46ec92a3',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                }
 | 
			
		||||
            ]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images/3a4560a1-e585-443e-9b39-553b46ec92a3': {
 | 
			
		||||
        'DELETE': (
 | 
			
		||||
            {},
 | 
			
		||||
            {}
 | 
			
		||||
        ),
 | 
			
		||||
        'PATCH': (
 | 
			
		||||
            {},
 | 
			
		||||
            ''
 | 
			
		||||
        ),
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {'images': [
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'art_1',
 | 
			
		||||
                    'id': '3a4560a1-e585-443e-9b39-553b46ec92a3',
 | 
			
		||||
                    'version': '0.0.0'
 | 
			
		||||
                }
 | 
			
		||||
            ]}
 | 
			
		||||
        )
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images/3a4560a1-e585-443e-9b39-553b46ec92a3/image': {
 | 
			
		||||
        'PUT': (
 | 
			
		||||
            {},
 | 
			
		||||
            ''
 | 
			
		||||
        ),
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {'content-md5': '5cc4bebc-db27-11e1-a1eb-080027cbe205'},
 | 
			
		||||
            {}
 | 
			
		||||
        )
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images/3a4560a1-e585-443e-9b39-553b46ec92a2/image': {
 | 
			
		||||
        'PUT': (
 | 
			
		||||
            {},
 | 
			
		||||
            ''
 | 
			
		||||
        ),
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {'content-md5': '5cc4bebc-db27-11e1-a1eb-080027cbe205'},
 | 
			
		||||
            {}
 | 
			
		||||
        )
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images/3a4560a1-e585-443e-9b39-553b46ec92a8/image': {
 | 
			
		||||
        'PUT': (
 | 
			
		||||
            {},
 | 
			
		||||
            ''
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/schemas': {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {'schemas': {
 | 
			
		||||
                'images': {'name': 'images', 'version': '1.0'},
 | 
			
		||||
                'heat_environments': {'name': 'heat_environments',
 | 
			
		||||
                                      'version': '1.0'}}}
 | 
			
		||||
        )
 | 
			
		||||
    },
 | 
			
		||||
    '/schemas/images': {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {'schemas': {
 | 
			
		||||
                'images': {'name': 'images',
 | 
			
		||||
                           'version': '1.0',
 | 
			
		||||
                           'properties': {'foo': 'bar'}
 | 
			
		||||
                           }}}
 | 
			
		||||
        )
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images?name=name1&version=latest': {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            # headers
 | 
			
		||||
            {},
 | 
			
		||||
            # response
 | 
			
		||||
            {'images': [
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'name1',
 | 
			
		||||
                    'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                    'version': '3.0.0'
 | 
			
		||||
                }
 | 
			
		||||
            ]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images?name=name1&version=1.0.0': {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            # headers
 | 
			
		||||
            {},
 | 
			
		||||
            # response
 | 
			
		||||
            {'images': [
 | 
			
		||||
                {
 | 
			
		||||
                    'name': 'name1',
 | 
			
		||||
                    'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                    'version': '1.0.0'
 | 
			
		||||
                }
 | 
			
		||||
            ]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c': {
 | 
			
		||||
        'PATCH': (
 | 
			
		||||
            {},
 | 
			
		||||
            ''
 | 
			
		||||
        ),
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {
 | 
			
		||||
                'name': 'art_1',
 | 
			
		||||
                'id': '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c',
 | 
			
		||||
                'version': '0.0.0',
 | 
			
		||||
                'tags': ["a", "b", "c"]
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
@@ -13,454 +13,194 @@
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
from oslo_serialization import jsonutils
 | 
			
		||||
import testtools
 | 
			
		||||
 | 
			
		||||
from glareclient.exc import HTTPBadRequest
 | 
			
		||||
from glareclient.tests.unit.v1 import fixtures
 | 
			
		||||
from glareclient.tests import utils
 | 
			
		||||
from glareclient.v1 import artifacts
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestController(testtools.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.mock_resp = mock.MagicMock()
 | 
			
		||||
        self.mock_body = mock.MagicMock()
 | 
			
		||||
        self.mock_http_client = mock.MagicMock()
 | 
			
		||||
        for method in ('get', 'post', 'patch', 'delete'):
 | 
			
		||||
            method = getattr(self.mock_http_client, method)
 | 
			
		||||
            method.return_value = (self.mock_resp, self.mock_body)
 | 
			
		||||
        self.c = artifacts.Controller(self.mock_http_client, 'test_name')
 | 
			
		||||
        self.c._check_type_name = mock.Mock(return_value='checked_name')
 | 
			
		||||
        super(TestController, self).setUp()
 | 
			
		||||
        self.api = utils.FakeAPI(fixtures.data_fixtures)
 | 
			
		||||
        self.controller = artifacts.Controller(self.api)
 | 
			
		||||
 | 
			
		||||
    def test_list_artifacts(self):
 | 
			
		||||
        artifacts = list(self.controller.list(type_name='images'))
 | 
			
		||||
        self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                         artifacts[0]['id'])
 | 
			
		||||
        self.assertEqual('art1', artifacts[0]['name'])
 | 
			
		||||
        self.assertEqual('db721fb0-5b85-4738-9401-f161d541de5e',
 | 
			
		||||
                         artifacts[1]['id'])
 | 
			
		||||
        self.assertEqual('art2', artifacts[1]['name'])
 | 
			
		||||
        self.assertEqual('e4f027d2-bff3-4084-a2ba-f31cb5e3067f',
 | 
			
		||||
                         artifacts[2]['id'])
 | 
			
		||||
        self.assertEqual('art3', artifacts[2]['name'])
 | 
			
		||||
    def test_create(self):
 | 
			
		||||
        body = self.c.create('name', version='0.1.2', type_name='ok')
 | 
			
		||||
        self.assertEqual(self.mock_body, body)
 | 
			
		||||
        self.mock_http_client.post.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name',
 | 
			
		||||
            json={'version': '0.1.2', 'name': 'name'})
 | 
			
		||||
        self.c._check_type_name.assert_called_once_with('ok')
 | 
			
		||||
 | 
			
		||||
        exp_headers = {}
 | 
			
		||||
        expect_body = None
 | 
			
		||||
        expect = [('GET', '/artifacts/images?limit=20',
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_list_with_paginate(self):
 | 
			
		||||
        artifacts = list(self.controller.list(type_name='images',
 | 
			
		||||
                                              page_size=2))
 | 
			
		||||
        self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                         artifacts[0]['id'])
 | 
			
		||||
        self.assertEqual('art1', artifacts[0]['name'])
 | 
			
		||||
        self.assertEqual('art2', artifacts[1]['name'])
 | 
			
		||||
        self.assertEqual('db721fb0-5b85-4738-9401-f161d541de5e',
 | 
			
		||||
                         artifacts[1]['id'])
 | 
			
		||||
        exp_headers = {}
 | 
			
		||||
        expect_body = None
 | 
			
		||||
        expect = [('GET', '/artifacts/images?limit=2',
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body),
 | 
			
		||||
                  ('GET', '/artifacts/images?limit=2'
 | 
			
		||||
                          '&marker=e1090471-1d12-4935-a8d8-a9351266ece8',
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_list_artifacts_limit(self):
 | 
			
		||||
        artifacts = list(self.controller.list(type_name='images',
 | 
			
		||||
                                              limit=2))
 | 
			
		||||
        self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                         artifacts[0]['id'])
 | 
			
		||||
        self.assertEqual('art1', artifacts[0]['name'])
 | 
			
		||||
        self.assertEqual('art2', artifacts[1]['name'])
 | 
			
		||||
        self.assertEqual('db721fb0-5b85-4738-9401-f161d541de5e',
 | 
			
		||||
                         artifacts[1]['id'])
 | 
			
		||||
        exp_headers = {}
 | 
			
		||||
        expect_body = None
 | 
			
		||||
        expect = [('GET', '/artifacts/images?limit=2',
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_list_artifact_sort_name(self):
 | 
			
		||||
 | 
			
		||||
        artifacts = list(self.controller.list(type_name='images',
 | 
			
		||||
                                              sort='name:desc'))
 | 
			
		||||
        self.assertEqual('e4f027d2-bff3-4084-a2ba-f31cb5e3067f',
 | 
			
		||||
                         artifacts[0]['id'])
 | 
			
		||||
        self.assertEqual('art2', artifacts[0]['name'])
 | 
			
		||||
        self.assertEqual('art1', artifacts[1]['name'])
 | 
			
		||||
        self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                         artifacts[1]['id'])
 | 
			
		||||
        exp_headers = {}
 | 
			
		||||
        expect_body = None
 | 
			
		||||
        expect = [('GET', '/artifacts/images?limit=20'
 | 
			
		||||
                          '&sort=name%3Adesc',
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body),
 | 
			
		||||
                  ('GET', '/artifacts/images?limit=20'
 | 
			
		||||
                          '&marker=3a4560a1-e585-443e-9b39-553b46ec92d1',
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_list_artifact_sort_badrequest(self):
 | 
			
		||||
        with testtools.ExpectedException(HTTPBadRequest):
 | 
			
		||||
            list(self.controller.list(type_name='images',
 | 
			
		||||
                                      sort='name:KAK'))
 | 
			
		||||
 | 
			
		||||
    def test_create_artifact(self):
 | 
			
		||||
        properties = {
 | 
			
		||||
            'name': 'art_1',
 | 
			
		||||
            'type_name': 'images'
 | 
			
		||||
    def test_update(self):
 | 
			
		||||
        remove_props = ['remove1', 'remove2']
 | 
			
		||||
        body = self.c.update('test-id', type_name='test_name',
 | 
			
		||||
                             remove_props=remove_props, update1=1, update2=2)
 | 
			
		||||
        self.assertEqual(self.mock_body, body)
 | 
			
		||||
        patch_kwargs = {
 | 
			
		||||
            'headers': {'Content-Type': 'application/json-patch+json'},
 | 
			
		||||
            'json': [
 | 
			
		||||
                {'path': '/remove1', 'value': None, 'op': 'replace'},
 | 
			
		||||
                {'path': '/remove2', 'value': None, 'op': 'replace'},
 | 
			
		||||
                {'path': '/update2', 'value': 2, 'op': 'add'},
 | 
			
		||||
                {'path': '/update1', 'value': 1, 'op': 'add'}
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
        self.mock_http_client.patch.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name/test-id', **patch_kwargs)
 | 
			
		||||
        self.c._check_type_name.assert_called_once_with('test_name')
 | 
			
		||||
 | 
			
		||||
        art = self.controller.create(**properties)
 | 
			
		||||
        self.assertEqual('art_1', art['images'][0]['name'])
 | 
			
		||||
        self.assertEqual('0.0.0', art['images'][0]['version'])
 | 
			
		||||
        self.assertIsNotNone(art['images'][0]['id'])
 | 
			
		||||
        exp_headers = {}
 | 
			
		||||
        expect_body = [('name', 'art_1'), ('version', '0.0.0')]
 | 
			
		||||
        expect = [('POST', '/artifacts/images',
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
    def test_get(self):
 | 
			
		||||
        body = self.c.get('test-id', type_name='test_name')
 | 
			
		||||
        self.assertEqual(self.mock_body, body)
 | 
			
		||||
        self.mock_http_client.get.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name/test-id')
 | 
			
		||||
        self.c._check_type_name.assert_called_once_with('test_name')
 | 
			
		||||
 | 
			
		||||
    def test_create_artifact_bad_prop(self):
 | 
			
		||||
        properties = {
 | 
			
		||||
            'name': 'art_1',
 | 
			
		||||
            'type_name': 'bad_type_name',
 | 
			
		||||
        }
 | 
			
		||||
        with testtools.ExpectedException(KeyError):
 | 
			
		||||
            self.controller.create(**properties)
 | 
			
		||||
    def test_list(self):
 | 
			
		||||
        self.mock_http_client.get.side_effect = [
 | 
			
		||||
            (None, {'checked_name': [10, 11, 12], "next": "next1"}),
 | 
			
		||||
            (None, {'checked_name': [13, 14, 15], "next": "next2"}),
 | 
			
		||||
            (None, {'checked_name': [16, 17, 18], "next": "next3"}),
 | 
			
		||||
            (None, {'checked_name': [19, 20, 21]}),
 | 
			
		||||
        ]
 | 
			
		||||
        data = list(self.c.list(type_name='test-type', limit=10, page_size=3))
 | 
			
		||||
        expected = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
 | 
			
		||||
        self.assertEqual(expected, data)
 | 
			
		||||
        expected_calls = [
 | 
			
		||||
            mock.call.get('/artifacts/checked_name?&limit=3'),
 | 
			
		||||
            mock.call.get('next1'),
 | 
			
		||||
            mock.call.get('next2'),
 | 
			
		||||
            mock.call.get('next3'),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expected_calls, self.mock_http_client.mock_calls)
 | 
			
		||||
 | 
			
		||||
    def test_delete_artifact(self):
 | 
			
		||||
        self.controller.delete(
 | 
			
		||||
            artifact_id='3a4560a1-e585-443e-9b39-553b46ec92a3',
 | 
			
		||||
            type_name='images')
 | 
			
		||||
    def test_activate(self):
 | 
			
		||||
        self.c.update = mock.Mock()
 | 
			
		||||
        self.assertEqual(self.c.update.return_value,
 | 
			
		||||
                         self.c.activate('test-id', type_name='test-type'))
 | 
			
		||||
        self.c.update.assert_called_once_with('test-id', 'test-type',
 | 
			
		||||
                                              status='active')
 | 
			
		||||
 | 
			
		||||
        expect = [('DELETE', '/artifacts/images/'
 | 
			
		||||
                             '3a4560a1-e585-443e-9b39-553b46ec92a3',
 | 
			
		||||
                   {},
 | 
			
		||||
                   None)]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
    def test_deactivate(self):
 | 
			
		||||
        self.c.update = mock.Mock()
 | 
			
		||||
        self.assertEqual(self.c.update.return_value,
 | 
			
		||||
                         self.c.deactivate('test-id', type_name='test-type'))
 | 
			
		||||
        self.c.update.assert_called_once_with('test-id', 'test-type',
 | 
			
		||||
                                              status='deactivated')
 | 
			
		||||
 | 
			
		||||
    def test_update_prop(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a3'
 | 
			
		||||
        param = {'type_name': 'images',
 | 
			
		||||
                 'name': 'new_name'}
 | 
			
		||||
    def test_reactivate(self):
 | 
			
		||||
        self.c.update = mock.Mock()
 | 
			
		||||
        self.assertEqual(self.c.update.return_value,
 | 
			
		||||
                         self.c.reactivate('test-id', type_name='test-type'))
 | 
			
		||||
        self.c.update.assert_called_once_with('test-id', 'test-type',
 | 
			
		||||
                                              status='active')
 | 
			
		||||
 | 
			
		||||
        self.controller.update(artifact_id=art_id,
 | 
			
		||||
                               **param)
 | 
			
		||||
 | 
			
		||||
        exp_headers = {
 | 
			
		||||
            'Content-Type': 'application/json-patch+json'
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expect_body = [{'path': '/name',
 | 
			
		||||
                        'value': 'new_name',
 | 
			
		||||
                        'op': 'add'}]
 | 
			
		||||
 | 
			
		||||
        expect = [('PATCH', '/artifacts/images/%s' % art_id,
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_remove_prop(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a3'
 | 
			
		||||
 | 
			
		||||
        self.controller.update(artifact_id=art_id,
 | 
			
		||||
                               remove_props=['name'],
 | 
			
		||||
                               type_name='images')
 | 
			
		||||
        exp_headers = {
 | 
			
		||||
            'Content-Type': 'application/json-patch+json'
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expect_body = [{'path': '/name',
 | 
			
		||||
                        'op': 'replace',
 | 
			
		||||
                        'value': None}]
 | 
			
		||||
 | 
			
		||||
        expect = [('PATCH', '/artifacts/images/%s' % art_id,
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.api.calls = []
 | 
			
		||||
 | 
			
		||||
        self.controller.update(artifact_id=art_id,
 | 
			
		||||
                               remove_props=['metadata/key1'],
 | 
			
		||||
                               type_name='images')
 | 
			
		||||
 | 
			
		||||
        expect_body = [{'path': '/metadata/key1',
 | 
			
		||||
                        'op': 'remove'}]
 | 
			
		||||
 | 
			
		||||
        expect = [('PATCH', '/artifacts/images/%s' % art_id,
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.api.calls = []
 | 
			
		||||
 | 
			
		||||
        self.controller.update(artifact_id=art_id,
 | 
			
		||||
                               remove_props=['releases/1'],
 | 
			
		||||
                               type_name='images')
 | 
			
		||||
 | 
			
		||||
        expect_body = [{'path': '/releases/1',
 | 
			
		||||
                        'op': 'remove'}]
 | 
			
		||||
 | 
			
		||||
        expect = [('PATCH', '/artifacts/images/%s' % art_id,
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_nontype_type_name(self):
 | 
			
		||||
        with testtools.ExpectedException(HTTPBadRequest):
 | 
			
		||||
            self.controller.create(name='art')
 | 
			
		||||
 | 
			
		||||
    def test_active_artifact(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a3'
 | 
			
		||||
        self.controller.activate(artifact_id=art_id,
 | 
			
		||||
                                 type_name='images')
 | 
			
		||||
        exp_headers = {
 | 
			
		||||
            'Content-Type': 'application/json-patch+json'
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expect_body = [{'path': '/status',
 | 
			
		||||
                        'value': 'active',
 | 
			
		||||
                        'op': 'add'}]
 | 
			
		||||
 | 
			
		||||
        expect = [('PATCH', '/artifacts/images/%s' % art_id,
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_deactivate_artifact(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a3'
 | 
			
		||||
        self.controller.deactivate(artifact_id=art_id,
 | 
			
		||||
                                   type_name='images')
 | 
			
		||||
        exp_headers = {
 | 
			
		||||
            'Content-Type': 'application/json-patch+json'
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expect_body = [{'path': '/status',
 | 
			
		||||
                        'value': 'deactivated',
 | 
			
		||||
                        'op': 'add'}]
 | 
			
		||||
 | 
			
		||||
        expect = [('PATCH', '/artifacts/images/%s' % art_id,
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_reactivate_artifact(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a3'
 | 
			
		||||
        self.controller.reactivate(artifact_id=art_id,
 | 
			
		||||
                                   type_name='images')
 | 
			
		||||
        exp_headers = {
 | 
			
		||||
            'Content-Type': 'application/json-patch+json'
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expect_body = [{'path': '/status',
 | 
			
		||||
                        'value': 'active',
 | 
			
		||||
                        'op': 'add'}]
 | 
			
		||||
 | 
			
		||||
        expect = [('PATCH', '/artifacts/images/%s' % art_id,
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_publish_artifact(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a3'
 | 
			
		||||
        self.controller.publish(artifact_id=art_id,
 | 
			
		||||
                                type_name='images')
 | 
			
		||||
        exp_headers = {
 | 
			
		||||
            'Content-Type': 'application/json-patch+json'
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expect_body = [{'path': '/visibility',
 | 
			
		||||
                        'value': 'public',
 | 
			
		||||
                        'op': 'add'}]
 | 
			
		||||
 | 
			
		||||
        expect = [('PATCH', '/artifacts/images/%s' % art_id,
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   expect_body)]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
    def test_delete(self):
 | 
			
		||||
        self.assertEqual(None, self.c.delete('test-id', type_name='test-name'))
 | 
			
		||||
        self.mock_http_client.delete.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name/test-id')
 | 
			
		||||
 | 
			
		||||
    def test_upload_blob(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a3'
 | 
			
		||||
        self.controller.upload_blob(artifact_id=art_id,
 | 
			
		||||
                                    type_name='images',
 | 
			
		||||
                                    blob_property='image',
 | 
			
		||||
                                    data='data')
 | 
			
		||||
        self.c.upload_blob('test-id', 'blob-prop', 'data',
 | 
			
		||||
                           type_name='test-type',
 | 
			
		||||
                           content_type='application/test')
 | 
			
		||||
        self.mock_http_client.put.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name/test-id/blob-prop',
 | 
			
		||||
            data='data', headers={'Content-Type': 'application/test'})
 | 
			
		||||
 | 
			
		||||
        exp_headers = {
 | 
			
		||||
            'Content-Type': 'application/octet-stream'
 | 
			
		||||
        }
 | 
			
		||||
    def test_get_type_list(self):
 | 
			
		||||
        schemas = {'schemas': {'a': {'version': 1}, 'b': {'version': 2}}}
 | 
			
		||||
        self.mock_http_client.get.return_value = (None, schemas)
 | 
			
		||||
        expected_types = [('a', 1), ('b', 2)]
 | 
			
		||||
        self.assertEqual(expected_types, self.c.get_type_list())
 | 
			
		||||
 | 
			
		||||
        expect = [('PUT', '/artifacts/images/%s/image' % art_id,
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   'data')]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_upload_blob_custom_content_type(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a3'
 | 
			
		||||
        self.controller.upload_blob(artifact_id=art_id,
 | 
			
		||||
                                    type_name='images',
 | 
			
		||||
                                    blob_property='image',
 | 
			
		||||
                                    data='{"a":"b"}',
 | 
			
		||||
                                    content_type='application/json',)
 | 
			
		||||
 | 
			
		||||
        exp_headers = {
 | 
			
		||||
            'Content-Type': 'application/json'
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expect = [('PUT', '/artifacts/images/%s/image' % art_id,
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   {"a": "b"})]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_download_blob(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a3'
 | 
			
		||||
        self.controller.download_blob(artifact_id=art_id,
 | 
			
		||||
                                      type_name='images',
 | 
			
		||||
                                      blob_property='image')
 | 
			
		||||
 | 
			
		||||
        exp_headers = {}
 | 
			
		||||
 | 
			
		||||
        expect = [('GET', '/artifacts/images/%s/image' % art_id,
 | 
			
		||||
                   exp_headers,
 | 
			
		||||
                   None)]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_download_blob_with_checksum(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a2'
 | 
			
		||||
        data = self.controller.download_blob(artifact_id=art_id,
 | 
			
		||||
                                             type_name='images',
 | 
			
		||||
                                             blob_property='image')
 | 
			
		||||
        self.assertIsNotNone(data.iterable)
 | 
			
		||||
 | 
			
		||||
        expect = [('GET', '/artifacts/images/%s/image' % art_id,
 | 
			
		||||
                   {},
 | 
			
		||||
                   None)]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_download_blob_without_checksum(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a2'
 | 
			
		||||
        data = self.controller.download_blob(artifact_id=art_id,
 | 
			
		||||
                                             type_name='images',
 | 
			
		||||
                                             blob_property='image',
 | 
			
		||||
                                             do_checksum=False)
 | 
			
		||||
        self.assertIsNotNone(data.iterable)
 | 
			
		||||
 | 
			
		||||
        expect = [('GET', '/artifacts/images/%s/image' % art_id,
 | 
			
		||||
                   {},
 | 
			
		||||
                   None)]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_get_artifact(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a3'
 | 
			
		||||
        art = self.controller.get(artifact_id=art_id,
 | 
			
		||||
                                  type_name='images')
 | 
			
		||||
        self.assertEqual(art_id, art['images'][0]['id'])
 | 
			
		||||
        self.assertEqual('art_1', art['images'][0]['name'])
 | 
			
		||||
 | 
			
		||||
    def test_get_by_name(self):
 | 
			
		||||
        art_name = 'name1'
 | 
			
		||||
        art = self.controller.get_by_name(name=art_name,
 | 
			
		||||
                                          type_name='images')
 | 
			
		||||
        self.assertEqual(art_name, art['name'])
 | 
			
		||||
        self.assertEqual('3.0.0', art['version'])
 | 
			
		||||
 | 
			
		||||
    def test_get_by_name_with_version(self):
 | 
			
		||||
        art_name = 'name1'
 | 
			
		||||
        art = self.controller.get_by_name(name=art_name,
 | 
			
		||||
                                          version='1.0.0',
 | 
			
		||||
                                          type_name='images')
 | 
			
		||||
        self.assertEqual(art_name, art['name'])
 | 
			
		||||
        self.assertEqual('1.0.0', art['version'])
 | 
			
		||||
 | 
			
		||||
    def test_type_list(self):
 | 
			
		||||
        data = self.controller.get_type_list()
 | 
			
		||||
        expect_data = [('images', '1.0'), ('heat_environments', '1.0')]
 | 
			
		||||
        expect_call = [('GET', '/schemas', {}, None)]
 | 
			
		||||
        self.assertEqual(expect_call, self.api.calls)
 | 
			
		||||
        self.assertEqual(expect_data, data)
 | 
			
		||||
 | 
			
		||||
    def test_get_schema(self):
 | 
			
		||||
        data = self.controller.get_type_schema(type_name='images')
 | 
			
		||||
        expect_data = {'name': 'images', 'version': '1.0',
 | 
			
		||||
                       'properties': {'foo': 'bar'}}
 | 
			
		||||
        expect_call = [('GET', '/schemas/images', {}, None)]
 | 
			
		||||
        self.assertEqual(expect_call, self.api.calls)
 | 
			
		||||
        self.assertEqual(expect_data, data)
 | 
			
		||||
    def test_get_type_schema(self):
 | 
			
		||||
        test_schema = {'schemas': {'checked_name': 'test-schema'}}
 | 
			
		||||
        self.mock_http_client.get.return_value = (None, test_schema)
 | 
			
		||||
        self.assertEqual('test-schema',
 | 
			
		||||
                         self.c.get_type_schema(type_name='test-type'))
 | 
			
		||||
        self.mock_http_client.get.assert_called_once_with(
 | 
			
		||||
            '/schemas/checked_name')
 | 
			
		||||
 | 
			
		||||
    def test_add_external_location(self):
 | 
			
		||||
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a8'
 | 
			
		||||
        data = self.controller.add_external_location(art_id,
 | 
			
		||||
                                                     'image',
 | 
			
		||||
                                                     'http://fake_url',
 | 
			
		||||
        data = {
 | 
			
		||||
            'url': 'http://fake_url',
 | 
			
		||||
            'md5': '7CA772EE98D5CAF99F3674085D5E4124',
 | 
			
		||||
            'sha1': None,
 | 
			
		||||
            'sha256': None},
 | 
			
		||||
        resp = self.c.add_external_location(
 | 
			
		||||
            art_id, 'image',
 | 
			
		||||
            data=data,
 | 
			
		||||
            type_name='images')
 | 
			
		||||
        expect_call = [
 | 
			
		||||
            ('PUT',
 | 
			
		||||
             '/artifacts/images/3a4560a1-e585-443e-9b39-553b46ec92a8/image',
 | 
			
		||||
             {'Content-Type': 'application/vnd+openstack.'
 | 
			
		||||
                              'glare-custom-location+json'},
 | 
			
		||||
             'http://fake_url')]
 | 
			
		||||
        self.assertEqual(expect_call, self.api.calls)
 | 
			
		||||
        self.assertIsNone(data)
 | 
			
		||||
        self.c.http_client.put.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name/'
 | 
			
		||||
            '3a4560a1-e585-443e-9b39-553b46ec92a8/image',
 | 
			
		||||
            data=jsonutils.dumps(data),
 | 
			
		||||
            headers={'Content-Type':
 | 
			
		||||
                     'application/vnd+openstack.glare-custom-location+json'})
 | 
			
		||||
        self.assertIsNone(resp)
 | 
			
		||||
 | 
			
		||||
    def test_add_tag(self):
 | 
			
		||||
        art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c'
 | 
			
		||||
        data = self.controller.add_tag(
 | 
			
		||||
        d = {'tags': ['a', 'b', 'c']}
 | 
			
		||||
        self.mock_body.__getitem__.side_effect = d.__getitem__
 | 
			
		||||
        data = self.c.add_tag(
 | 
			
		||||
            art_id, tag_value="123", type_name='images')
 | 
			
		||||
        expect_call = [
 | 
			
		||||
            ('GET', '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c',
 | 
			
		||||
             {}, None),
 | 
			
		||||
            ('PATCH',
 | 
			
		||||
             '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c',
 | 
			
		||||
             {'Content-Type': 'application/json-patch+json'},
 | 
			
		||||
             [{'op': 'add',
 | 
			
		||||
               'path': '/tags',
 | 
			
		||||
               'value': ['a', 'b', 'c', '123']}])]
 | 
			
		||||
        self.assertEqual(expect_call, self.api.calls)
 | 
			
		||||
        self.c.http_client.get.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c')
 | 
			
		||||
        self.c.http_client.patch.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c',
 | 
			
		||||
            headers={'Content-Type': 'application/json-patch+json'},
 | 
			
		||||
            json=[{'path': '/tags',
 | 
			
		||||
                   'value': ['a', 'b', 'c', '123'],
 | 
			
		||||
                   'op': 'add'}])
 | 
			
		||||
        self.assertIsNotNone(data)
 | 
			
		||||
 | 
			
		||||
    def test_add_existing_tag(self):
 | 
			
		||||
        art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c'
 | 
			
		||||
        data = self.controller.add_tag(
 | 
			
		||||
        d = {'tags': ['a', 'b', 'c']}
 | 
			
		||||
        self.mock_body.__getitem__.side_effect = d.__getitem__
 | 
			
		||||
        data = self.c.add_tag(
 | 
			
		||||
            art_id, tag_value="a", type_name='images')
 | 
			
		||||
        expect_call = [
 | 
			
		||||
            ('GET', '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c',
 | 
			
		||||
             {}, None)]
 | 
			
		||||
        self.assertEqual(expect_call, self.api.calls)
 | 
			
		||||
        self.c.http_client.get.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c')
 | 
			
		||||
        self.assertEqual(0, self.c.http_client.patch.call_count)
 | 
			
		||||
        self.assertIsNotNone(data)
 | 
			
		||||
 | 
			
		||||
    def test_remove_tag(self):
 | 
			
		||||
        art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c'
 | 
			
		||||
        data = self.controller.remove_tag(
 | 
			
		||||
        d = {'tags': ['a', 'b', 'c']}
 | 
			
		||||
        self.mock_body.__getitem__.side_effect = d.__getitem__
 | 
			
		||||
        data = self.c.remove_tag(
 | 
			
		||||
            art_id, tag_value="a", type_name='images')
 | 
			
		||||
        expect_call = [
 | 
			
		||||
            ('GET', '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c',
 | 
			
		||||
             {}, None),
 | 
			
		||||
            ('PATCH',
 | 
			
		||||
             '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c',
 | 
			
		||||
             {'Content-Type': 'application/json-patch+json'},
 | 
			
		||||
             [{'op': 'add',
 | 
			
		||||
               'path': '/tags',
 | 
			
		||||
               'value': ['b', 'c']}])]
 | 
			
		||||
        self.assertEqual(expect_call, self.api.calls)
 | 
			
		||||
        self.c.http_client.get.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c')
 | 
			
		||||
        self.c.http_client.patch.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c',
 | 
			
		||||
            headers={'Content-Type': 'application/json-patch+json'},
 | 
			
		||||
            json=[{'path': '/tags',
 | 
			
		||||
                   'value': ['b', 'c'],
 | 
			
		||||
                   'op': 'add'}])
 | 
			
		||||
        self.assertIsNotNone(data)
 | 
			
		||||
 | 
			
		||||
    def test_remove_nonexisting_tag(self):
 | 
			
		||||
        art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c'
 | 
			
		||||
        data = self.controller.remove_tag(
 | 
			
		||||
        d = {'tags': ['a', 'b', 'c']}
 | 
			
		||||
        self.mock_body.__getitem__.side_effect = d.__getitem__
 | 
			
		||||
        data = self.c.remove_tag(
 | 
			
		||||
            art_id, tag_value="123", type_name='images')
 | 
			
		||||
        expect_call = [
 | 
			
		||||
            ('GET', '/artifacts/images/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c',
 | 
			
		||||
             {}, None)]
 | 
			
		||||
        self.assertEqual(expect_call, self.api.calls)
 | 
			
		||||
        self.c.http_client.get.assert_called_once_with(
 | 
			
		||||
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c')
 | 
			
		||||
        self.assertEqual(0, self.c.http_client.patch.call_count)
 | 
			
		||||
        self.assertIsNotNone(data)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,213 +0,0 @@
 | 
			
		||||
# 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 json
 | 
			
		||||
import six
 | 
			
		||||
import six.moves.urllib.parse as urlparse
 | 
			
		||||
import testtools
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeAPI(object):
 | 
			
		||||
    def __init__(self, fixtures):
 | 
			
		||||
        self.fixtures = fixtures
 | 
			
		||||
        self.calls = []
 | 
			
		||||
 | 
			
		||||
    def _request(self, method, url, headers=None, data=None,
 | 
			
		||||
                 content_length=None, **kwargs):
 | 
			
		||||
        call = build_call_record(method, sort_url_by_query_keys(url),
 | 
			
		||||
                                 headers or {}, data)
 | 
			
		||||
        if content_length is not None:
 | 
			
		||||
            call = tuple(list(call) + [content_length])
 | 
			
		||||
        self.calls.append(call)
 | 
			
		||||
 | 
			
		||||
        fixture = self.fixtures[sort_url_by_query_keys(url)][method]
 | 
			
		||||
 | 
			
		||||
        data = fixture[1]
 | 
			
		||||
        if isinstance(fixture[1], six.string_types):
 | 
			
		||||
            try:
 | 
			
		||||
                data = json.loads(fixture[1])
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                data = six.StringIO(fixture[1])
 | 
			
		||||
 | 
			
		||||
        return FakeResponse(fixture[0], fixture[1]), data
 | 
			
		||||
 | 
			
		||||
    def get(self, *args, **kwargs):
 | 
			
		||||
        return self._request('GET', *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def post(self, *args, **kwargs):
 | 
			
		||||
        return self._request('POST', *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def put(self, *args, **kwargs):
 | 
			
		||||
        return self._request('PUT', *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def patch(self, *args, **kwargs):
 | 
			
		||||
        return self._request('PATCH', *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def delete(self, *args, **kwargs):
 | 
			
		||||
        return self._request('DELETE', *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def head(self, *args, **kwargs):
 | 
			
		||||
        return self._request('HEAD', *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RawRequest(object):
 | 
			
		||||
    def __init__(self, headers, body=None,
 | 
			
		||||
                 version=1.0, status=200, reason="Ok"):
 | 
			
		||||
        """A crafted request object used for testing.
 | 
			
		||||
 | 
			
		||||
        :param headers: dict representing HTTP response headers
 | 
			
		||||
        :param body: file-like object
 | 
			
		||||
        :param version: HTTP Version
 | 
			
		||||
        :param status: Response status code
 | 
			
		||||
        :param reason: Status code related message.
 | 
			
		||||
        """
 | 
			
		||||
        self.body = body
 | 
			
		||||
        self.status = status
 | 
			
		||||
        self.reason = reason
 | 
			
		||||
        self.version = version
 | 
			
		||||
        self.headers = headers
 | 
			
		||||
 | 
			
		||||
    def getheaders(self):
 | 
			
		||||
        return copy.deepcopy(self.headers).items()
 | 
			
		||||
 | 
			
		||||
    def getheader(self, key, default):
 | 
			
		||||
        return self.headers.get(key, default)
 | 
			
		||||
 | 
			
		||||
    def read(self, amt):
 | 
			
		||||
        return self.body.read(amt)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeResponse(object):
 | 
			
		||||
    def __init__(self, headers=None, body=None,
 | 
			
		||||
                 version=1.0, status_code=200, reason="Ok"):
 | 
			
		||||
        """A crafted response object used for testing.
 | 
			
		||||
 | 
			
		||||
        :param headers: dict representing HTTP response headers
 | 
			
		||||
        :param body: file-like object
 | 
			
		||||
        :param version: HTTP Version
 | 
			
		||||
        :param status: Response status code
 | 
			
		||||
        :param reason: Status code related message.
 | 
			
		||||
        """
 | 
			
		||||
        self.body = body
 | 
			
		||||
        self.reason = reason
 | 
			
		||||
        self.version = version
 | 
			
		||||
        self.headers = headers
 | 
			
		||||
        self.status_code = status_code
 | 
			
		||||
        self.raw = RawRequest(headers, body=body, reason=reason,
 | 
			
		||||
                              version=version, status=status_code)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def status(self):
 | 
			
		||||
        return self.status_code
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def ok(self):
 | 
			
		||||
        return (self.status_code < 400 or
 | 
			
		||||
                self.status_code >= 600)
 | 
			
		||||
 | 
			
		||||
    def read(self, amt):
 | 
			
		||||
        return self.body.read(amt)
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def content(self):
 | 
			
		||||
        if hasattr(self.body, "read"):
 | 
			
		||||
            return self.body.read()
 | 
			
		||||
        return self.body
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def text(self):
 | 
			
		||||
        if isinstance(self.content, six.binary_type):
 | 
			
		||||
            return self.content.decode('utf-8')
 | 
			
		||||
 | 
			
		||||
        return self.content
 | 
			
		||||
 | 
			
		||||
    def json(self, **kwargs):
 | 
			
		||||
        return self.body and json.loads(self.text) or ""
 | 
			
		||||
 | 
			
		||||
    def iter_content(self, chunk_size=1, decode_unicode=False):
 | 
			
		||||
        while True:
 | 
			
		||||
            chunk = self.raw.read(chunk_size)
 | 
			
		||||
            if not chunk:
 | 
			
		||||
                break
 | 
			
		||||
            yield chunk
 | 
			
		||||
 | 
			
		||||
    def release_conn(self, **kwargs):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestCase(testtools.TestCase):
 | 
			
		||||
    TEST_REQUEST_BASE = {
 | 
			
		||||
        'config': {'danger_mode': False},
 | 
			
		||||
        'verify': True}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeTTYStdout(six.StringIO):
 | 
			
		||||
    """A Fake stdout that try to emulate a TTY device as much as possible."""
 | 
			
		||||
 | 
			
		||||
    def isatty(self):
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def write(self, data):
 | 
			
		||||
        # When a CR (carriage return) is found reset file.
 | 
			
		||||
        if data.startswith('\r'):
 | 
			
		||||
            self.seek(0)
 | 
			
		||||
            data = data[1:]
 | 
			
		||||
        return six.StringIO.write(self, data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeNoTTYStdout(FakeTTYStdout):
 | 
			
		||||
    """A Fake stdout that is not a TTY device."""
 | 
			
		||||
 | 
			
		||||
    def isatty(self):
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def sort_url_by_query_keys(url):
 | 
			
		||||
    """A helper function which sorts the keys of the query string of a url.
 | 
			
		||||
 | 
			
		||||
       For example, an input of '/v2/tasks?sort_key=id&sort_dir=asc&limit=10'
 | 
			
		||||
       returns '/v2/tasks?limit=10&sort_dir=asc&sort_key=id'. This is to
 | 
			
		||||
       prevent non-deterministic ordering of the query string causing
 | 
			
		||||
       problems with unit tests.
 | 
			
		||||
    :param url: url which will be ordered by query keys
 | 
			
		||||
    :returns url: url with ordered query keys
 | 
			
		||||
    """
 | 
			
		||||
    parsed = urlparse.urlparse(url)
 | 
			
		||||
    queries = urlparse.parse_qsl(parsed.query, True)
 | 
			
		||||
    sorted_query = sorted(queries, key=lambda x: x[0])
 | 
			
		||||
 | 
			
		||||
    encoded_sorted_query = urlparse.urlencode(sorted_query, True)
 | 
			
		||||
 | 
			
		||||
    url_parts = (parsed.scheme, parsed.netloc, parsed.path,
 | 
			
		||||
                 parsed.params, encoded_sorted_query,
 | 
			
		||||
                 parsed.fragment)
 | 
			
		||||
 | 
			
		||||
    return urlparse.urlunparse(url_parts)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def build_call_record(method, url, headers, data):
 | 
			
		||||
    """Key the request body be ordered if it's a dict type."""
 | 
			
		||||
    if isinstance(data, dict):
 | 
			
		||||
        data = sorted(data.items())
 | 
			
		||||
    if isinstance(data, six.string_types):
 | 
			
		||||
        try:
 | 
			
		||||
            data = json.loads(data)
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            return (method, url, headers or {}, data)
 | 
			
		||||
    return (method, url, headers or {}, data)
 | 
			
		||||
@@ -12,12 +12,14 @@
 | 
			
		||||
# License for the specific language governing permissions and limitations
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
from glareclient.common import utils
 | 
			
		||||
from glareclient import exc
 | 
			
		||||
from oslo_serialization import jsonutils
 | 
			
		||||
from oslo_utils import encodeutils
 | 
			
		||||
import six
 | 
			
		||||
from six.moves.urllib import parse
 | 
			
		||||
 | 
			
		||||
from glareclient.common import utils
 | 
			
		||||
from glareclient import exc
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Controller(object):
 | 
			
		||||
    def __init__(self, http_client, type_name=None):
 | 
			
		||||
@@ -59,7 +61,7 @@ class Controller(object):
 | 
			
		||||
        type_name = self._check_type_name(type_name)
 | 
			
		||||
        kwargs.update({'name': name, 'version': version})
 | 
			
		||||
        url = '/artifacts/%s' % type_name
 | 
			
		||||
        resp, body = self.http_client.post(url, data=kwargs)
 | 
			
		||||
        resp, body = self.http_client.post(url, json=kwargs)
 | 
			
		||||
        return body
 | 
			
		||||
 | 
			
		||||
    def update(self, artifact_id, type_name=None, remove_props=None,
 | 
			
		||||
@@ -90,7 +92,7 @@ class Controller(object):
 | 
			
		||||
        for prop_name in kwargs:
 | 
			
		||||
            changes.append({'op': 'add', 'path': '/%s' % prop_name,
 | 
			
		||||
                            'value': kwargs[prop_name]})
 | 
			
		||||
        resp, body = self.http_client.patch(url, headers=hdrs, data=changes)
 | 
			
		||||
        resp, body = self.http_client.patch(url, headers=hdrs, json=changes)
 | 
			
		||||
        return body
 | 
			
		||||
 | 
			
		||||
    def get(self, artifact_id, type_name=None):
 | 
			
		||||
@@ -226,7 +228,7 @@ class Controller(object):
 | 
			
		||||
        type_name = self._check_type_name(type_name)
 | 
			
		||||
        hdrs = {'Content-Type': content_type}
 | 
			
		||||
        url = '/artifacts/%s/%s/%s' % (type_name, artifact_id, blob_property)
 | 
			
		||||
        self.http_client.put(url, headers=hdrs, data=data, stream=True)
 | 
			
		||||
        self.http_client.put(url, headers=hdrs, data=data)
 | 
			
		||||
 | 
			
		||||
    def add_external_location(self, artifact_id, blob_property, data,
 | 
			
		||||
                              type_name=None):
 | 
			
		||||
@@ -240,6 +242,10 @@ class Controller(object):
 | 
			
		||||
        type_name = self._check_type_name(type_name)
 | 
			
		||||
        hdrs = {'Content-Type': content_type}
 | 
			
		||||
        url = '/artifacts/%s/%s/%s' % (type_name, artifact_id, blob_property)
 | 
			
		||||
        try:
 | 
			
		||||
            data = jsonutils.dumps(data)
 | 
			
		||||
        except TypeError:
 | 
			
		||||
            raise exc.HTTPBadRequest("json is malformed.")
 | 
			
		||||
        self.http_client.put(url, headers=hdrs, data=data)
 | 
			
		||||
 | 
			
		||||
    def download_blob(self, artifact_id, blob_property, type_name=None,
 | 
			
		||||
@@ -252,12 +258,10 @@ class Controller(object):
 | 
			
		||||
        """
 | 
			
		||||
        type_name = self._check_type_name(type_name)
 | 
			
		||||
        url = '/artifacts/%s/%s/%s' % (type_name, artifact_id, blob_property)
 | 
			
		||||
        resp, body = self.http_client.get(url)
 | 
			
		||||
        checksum = resp.headers.get('content-md5', None)
 | 
			
		||||
        content_length = int(resp.headers.get('content-length', 0))
 | 
			
		||||
        if checksum is not None and do_checksum:
 | 
			
		||||
            body = utils.integrity_iter(body, checksum)
 | 
			
		||||
        return utils.IterableWithLength(body, content_length)
 | 
			
		||||
        resp, body = self.http_client.get(url, redirect=False,
 | 
			
		||||
                                          stream=True,
 | 
			
		||||
                                          headers={"Accept": "*/*"})
 | 
			
		||||
        return utils.ResponseBlobWrapper(resp, do_checksum)
 | 
			
		||||
 | 
			
		||||
    def get_type_list(self):
 | 
			
		||||
        """Get list of type names."""
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user