d1e1f8d8d6
There were two basic problems: - We'd try to import on every attempt at getting auth, even when we already know keystoneclient isn't available. - Sometimes devs would hit some crazy import race involving (some combination of?) greenthreads and OS threads. So let's just try the imports *once*, at import time, and have None sentinels if it fails. Try both versions separately to decouple failures; this should let us support a wider range of keystoneclient versions. Change-Id: I2367310aac74f1b7c5ea0cb1a822a491e4ba8e68
3314 lines
142 KiB
Python
3314 lines
142 KiB
Python
# Copyright (c) 2014 Christian Schwede <christian.schwede@enovance.com>
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
from __future__ import unicode_literals
|
|
|
|
import contextlib
|
|
from genericpath import getmtime
|
|
import getpass
|
|
import hashlib
|
|
import json
|
|
import logging
|
|
import mock
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
import textwrap
|
|
from time import localtime, mktime, strftime, strptime
|
|
|
|
import six
|
|
import sys
|
|
|
|
import swiftclient
|
|
from swiftclient.service import SwiftError
|
|
import swiftclient.shell
|
|
import swiftclient.utils
|
|
|
|
from os.path import basename, dirname
|
|
from .utils import (
|
|
CaptureOutput, fake_get_auth_keystone,
|
|
FakeKeystone, StubResponse, MockHttpTest)
|
|
from swiftclient.utils import (
|
|
EMPTY_ETAG, EXPIRES_ISO8601_FORMAT,
|
|
SHORT_EXPIRES_ISO8601_FORMAT, TIME_ERRMSG)
|
|
|
|
try:
|
|
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
|
except ImportError:
|
|
InsecureRequestWarning = None
|
|
|
|
if six.PY2:
|
|
BUILTIN_OPEN = '__builtin__.open'
|
|
else:
|
|
BUILTIN_OPEN = 'builtins.open'
|
|
|
|
mocked_os_environ = {
|
|
'ST_AUTH': 'http://localhost:8080/auth/v1.0',
|
|
'ST_USER': 'test:tester',
|
|
'ST_KEY': 'testing'
|
|
}
|
|
clean_os_environ = {}
|
|
environ_prefixes = ('ST_', 'OS_')
|
|
for key in os.environ:
|
|
if any(key.startswith(m) for m in environ_prefixes):
|
|
clean_os_environ[key] = ''
|
|
|
|
|
|
def _make_args(cmd, opts, os_opts, separator='-', flags=None, cmd_args=None):
|
|
"""
|
|
Construct command line arguments for given options.
|
|
"""
|
|
args = [""]
|
|
flags = flags or []
|
|
for k, v in opts.items():
|
|
args.append("--" + k.replace("_", "-"))
|
|
if v is not None:
|
|
args.append(v)
|
|
for k, v in os_opts.items():
|
|
args.append("--os" + separator + k.replace("_", separator))
|
|
if v is not None:
|
|
args.append(v)
|
|
for flag in flags:
|
|
args.append('--%s' % flag)
|
|
if cmd:
|
|
args.append(cmd)
|
|
if cmd_args:
|
|
args.extend(cmd_args)
|
|
return args
|
|
|
|
|
|
def _make_env(opts, os_opts):
|
|
"""
|
|
Construct a dict of environment variables for given options.
|
|
"""
|
|
env = {}
|
|
for k, v in opts.items():
|
|
key = 'ST_' + k.upper().replace('-', '_')
|
|
env[key] = v
|
|
for k, v in os_opts.items():
|
|
key = 'OS_' + k.upper().replace('-', '_')
|
|
env[key] = v
|
|
return env
|
|
|
|
|
|
def _make_cmd(cmd, opts, os_opts, use_env=False, flags=None, cmd_args=None):
|
|
flags = flags or []
|
|
if use_env:
|
|
# set up fake environment variables and make a minimal command line
|
|
env = _make_env(opts, os_opts)
|
|
args = _make_args(cmd, {}, {}, separator='-', flags=flags,
|
|
cmd_args=cmd_args)
|
|
else:
|
|
# set up empty environment and make full command line
|
|
env = {}
|
|
args = _make_args(cmd, opts, os_opts, separator='-', flags=flags,
|
|
cmd_args=cmd_args)
|
|
return args, env
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def patch_disable_warnings():
|
|
if InsecureRequestWarning is None:
|
|
# If InsecureRequestWarning isn't available, disbale_warnings won't
|
|
# be either; they both came in with
|
|
# https://github.com/requests/requests/commit/811ee4e and left again
|
|
# in https://github.com/requests/requests/commit/8e17600
|
|
yield None
|
|
else:
|
|
with mock.patch('requests.packages.urllib3.disable_warnings') \
|
|
as patched:
|
|
yield patched
|
|
|
|
|
|
@mock.patch.dict(os.environ, mocked_os_environ)
|
|
class TestShell(unittest.TestCase):
|
|
def setUp(self):
|
|
super(TestShell, self).setUp()
|
|
tmpfile = tempfile.NamedTemporaryFile(delete=False)
|
|
self.tmpfile = tmpfile.name
|
|
|
|
def tearDown(self):
|
|
try:
|
|
os.remove(self.tmpfile)
|
|
except OSError:
|
|
pass
|
|
super(TestShell, self).tearDown()
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_stat_account(self, connection):
|
|
argv = ["", "stat"]
|
|
return_headers = {
|
|
'x-account-container-count': '1',
|
|
'x-account-object-count': '2',
|
|
'x-account-bytes-used': '3',
|
|
'content-length': 0,
|
|
'date': ''}
|
|
connection.return_value.head_account.return_value = return_headers
|
|
connection.return_value.url = 'http://127.0.0.1/v1/AUTH_account'
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.out,
|
|
' Account: AUTH_account\n'
|
|
'Containers: 1\n'
|
|
' Objects: 2\n'
|
|
' Bytes: 3\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_stat_account_with_headers(self, connection):
|
|
argv = ["", "stat", "-H", "Skip-Middleware: Test"]
|
|
return_headers = {
|
|
'x-account-container-count': '1',
|
|
'x-account-object-count': '2',
|
|
'x-account-bytes-used': '3',
|
|
'content-length': 0,
|
|
'date': ''}
|
|
connection.return_value.head_account.return_value = return_headers
|
|
connection.return_value.url = 'http://127.0.0.1/v1/AUTH_account'
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.out,
|
|
' Account: AUTH_account\n'
|
|
'Containers: 1\n'
|
|
' Objects: 2\n'
|
|
' Bytes: 3\n')
|
|
self.assertEqual(connection.return_value.head_account.mock_calls, [
|
|
mock.call(headers={'Skip-Middleware': 'Test'})])
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_stat_container(self, connection):
|
|
return_headers = {
|
|
'x-container-object-count': '1',
|
|
'x-container-bytes-used': '2',
|
|
'x-container-read': 'test2:tester2',
|
|
'x-container-write': 'test3:tester3',
|
|
'x-container-sync-to': 'other',
|
|
'x-container-sync-key': 'secret',
|
|
}
|
|
argv = ["", "stat", "container"]
|
|
connection.return_value.head_container.return_value = return_headers
|
|
connection.return_value.url = 'http://127.0.0.1/v1/AUTH_account'
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.out,
|
|
' Account: AUTH_account\n'
|
|
'Container: container\n'
|
|
' Objects: 1\n'
|
|
' Bytes: 2\n'
|
|
' Read ACL: test2:tester2\n'
|
|
'Write ACL: test3:tester3\n'
|
|
' Sync To: other\n'
|
|
' Sync Key: secret\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_stat_container_with_headers(self, connection):
|
|
return_headers = {
|
|
'x-container-object-count': '1',
|
|
'x-container-bytes-used': '2',
|
|
'x-container-read': 'test2:tester2',
|
|
'x-container-write': 'test3:tester3',
|
|
'x-container-sync-to': 'other',
|
|
'x-container-sync-key': 'secret',
|
|
}
|
|
argv = ["", "stat", "container", "-H", "Skip-Middleware: Test"]
|
|
connection.return_value.head_container.return_value = return_headers
|
|
connection.return_value.url = 'http://127.0.0.1/v1/AUTH_account'
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.out,
|
|
' Account: AUTH_account\n'
|
|
'Container: container\n'
|
|
' Objects: 1\n'
|
|
' Bytes: 2\n'
|
|
' Read ACL: test2:tester2\n'
|
|
'Write ACL: test3:tester3\n'
|
|
' Sync To: other\n'
|
|
' Sync Key: secret\n')
|
|
self.assertEqual(connection.return_value.head_container.mock_calls, [
|
|
mock.call('container', headers={'Skip-Middleware': 'Test'})])
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_stat_object(self, connection):
|
|
return_headers = {
|
|
'x-object-manifest': 'manifest',
|
|
'etag': 'md5',
|
|
'last-modified': 'yesterday',
|
|
'content-type': 'text/plain',
|
|
'content-length': 42,
|
|
}
|
|
argv = ["", "stat", "container", "object"]
|
|
connection.return_value.head_object.return_value = return_headers
|
|
connection.return_value.url = 'http://127.0.0.1/v1/AUTH_account'
|
|
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.out,
|
|
' Account: AUTH_account\n'
|
|
' Container: container\n'
|
|
' Object: object\n'
|
|
' Content Type: text/plain\n'
|
|
'Content Length: 42\n'
|
|
' Last Modified: yesterday\n'
|
|
' ETag: md5\n'
|
|
' Manifest: manifest\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_stat_object_with_headers(self, connection):
|
|
return_headers = {
|
|
'x-object-manifest': 'manifest',
|
|
'etag': 'md5',
|
|
'last-modified': 'yesterday',
|
|
'content-type': 'text/plain',
|
|
'content-length': 42,
|
|
}
|
|
argv = ["", "stat", "container", "object",
|
|
"-H", "Skip-Middleware: Test"]
|
|
connection.return_value.head_object.return_value = return_headers
|
|
connection.return_value.url = 'http://127.0.0.1/v1/AUTH_account'
|
|
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.out,
|
|
' Account: AUTH_account\n'
|
|
' Container: container\n'
|
|
' Object: object\n'
|
|
' Content Type: text/plain\n'
|
|
'Content Length: 42\n'
|
|
' Last Modified: yesterday\n'
|
|
' ETag: md5\n'
|
|
' Manifest: manifest\n')
|
|
self.assertEqual(connection.return_value.head_object.mock_calls, [
|
|
mock.call('container', 'object',
|
|
headers={'Skip-Middleware': 'Test'})])
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_list_account(self, connection):
|
|
# Test account listing
|
|
connection.return_value.get_account.side_effect = [
|
|
[None, [{'name': 'container'}]],
|
|
[None, []],
|
|
]
|
|
|
|
argv = ["", "list"]
|
|
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
calls = [mock.call(marker='', prefix=None, headers={}),
|
|
mock.call(marker='container', prefix=None, headers={})]
|
|
connection.return_value.get_account.assert_has_calls(calls)
|
|
|
|
self.assertEqual(output.out, 'container\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_list_account_with_headers(self, connection):
|
|
# Test account listing
|
|
connection.return_value.get_account.side_effect = [
|
|
[None, [{'name': 'container'}]],
|
|
[None, []],
|
|
]
|
|
|
|
argv = ["", "list", '-H', 'Skip-Custom-Middleware: True']
|
|
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
calls = [mock.call(marker='', prefix=None,
|
|
headers={'Skip-Custom-Middleware': 'True'}),
|
|
mock.call(marker='container', prefix=None,
|
|
headers={'Skip-Custom-Middleware': 'True'})]
|
|
connection.return_value.get_account.assert_has_calls(calls)
|
|
|
|
self.assertEqual(output.out, 'container\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_list_account_long(self, connection):
|
|
# Test account listing
|
|
connection.return_value.get_account.side_effect = [
|
|
[None, [{'name': 'container', 'bytes': 0, 'count': 0}]],
|
|
[None, []],
|
|
]
|
|
|
|
argv = ["", "list", "--lh"]
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
calls = [mock.call(marker='', prefix=None, headers={}),
|
|
mock.call(marker='container', prefix=None, headers={})]
|
|
connection.return_value.get_account.assert_has_calls(calls)
|
|
|
|
self.assertEqual(output.out,
|
|
' 0 0 1970-01-01 00:00:01 container\n'
|
|
' 0 0\n')
|
|
|
|
# Now test again, this time without returning metadata
|
|
connection.return_value.head_container.return_value = {}
|
|
|
|
# Test account listing
|
|
connection.return_value.get_account.side_effect = [
|
|
[None, [{'name': 'container', 'bytes': 0, 'count': 0}]],
|
|
[None, []],
|
|
]
|
|
|
|
argv = ["", "list", "--lh"]
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
calls = [mock.call(marker='', prefix=None, headers={}),
|
|
mock.call(marker='container', prefix=None, headers={})]
|
|
connection.return_value.get_account.assert_has_calls(calls)
|
|
|
|
self.assertEqual(output.out,
|
|
' 0 0 ????-??-?? ??:??:?? container\n'
|
|
' 0 0\n')
|
|
|
|
def test_list_account_totals_error(self):
|
|
# No --lh provided: expect info message about incorrect --totals use
|
|
argv = ["", "list", "--totals"]
|
|
|
|
with CaptureOutput() as output:
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, argv)
|
|
self.assertEqual(output.err,
|
|
"Listing totals only works with -l or --lh.\n")
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_list_account_totals(self, connection):
|
|
|
|
# Test account listing, only total count and size
|
|
connection.return_value.get_account.side_effect = [
|
|
[None, [{'name': 'container1', 'bytes': 1, 'count': 2},
|
|
{'name': 'container2', 'bytes': 2, 'count': 4}]],
|
|
[None, []],
|
|
]
|
|
|
|
argv = ["", "list", "--lh", "--totals"]
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
calls = [mock.call(marker='', prefix=None, headers={})]
|
|
connection.return_value.get_account.assert_has_calls(calls)
|
|
self.assertEqual(output.out, ' 6 3\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_list_container(self, connection):
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object_a'}]],
|
|
[None, []],
|
|
]
|
|
argv = ["", "list", "container"]
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
calls = [
|
|
mock.call('container', marker='',
|
|
delimiter=None, prefix=None, headers={}),
|
|
mock.call('container', marker='object_a',
|
|
delimiter=None, prefix=None, headers={})]
|
|
connection.return_value.get_container.assert_has_calls(calls)
|
|
|
|
self.assertEqual(output.out, 'object_a\n')
|
|
|
|
# Test container listing with --long
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object_a', 'bytes': 0,
|
|
'content_type': 'type/content',
|
|
'last_modified': '123T456'}]],
|
|
[None, []],
|
|
]
|
|
argv = ["", "list", "container", "--long"]
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
calls = [
|
|
mock.call('container', marker='',
|
|
delimiter=None, prefix=None, headers={}),
|
|
mock.call('container', marker='object_a',
|
|
delimiter=None, prefix=None, headers={})]
|
|
connection.return_value.get_container.assert_has_calls(calls)
|
|
|
|
self.assertEqual(output.out,
|
|
' 0 123 456'
|
|
' type/content object_a\n'
|
|
' 0\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_list_container_with_headers(self, connection):
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object_a'}]],
|
|
[None, []],
|
|
]
|
|
argv = ["", "list", "container", "-H", "Skip-Middleware: Test"]
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
calls = [
|
|
mock.call('container', marker='',
|
|
delimiter=None, prefix=None,
|
|
headers={'Skip-Middleware': 'Test'}),
|
|
mock.call('container', marker='object_a',
|
|
delimiter=None, prefix=None,
|
|
headers={'Skip-Middleware': 'Test'})]
|
|
connection.return_value.get_container.assert_has_calls(calls)
|
|
|
|
self.assertEqual(output.out, 'object_a\n')
|
|
|
|
@mock.patch('swiftclient.service.makedirs')
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_download(self, connection, makedirs):
|
|
objcontent = six.BytesIO(b'objcontent')
|
|
connection.return_value.get_object.side_effect = [
|
|
({'content-type': 'text/plain',
|
|
'etag': '2cbbfe139a744d6abbe695e17f3c1991'},
|
|
objcontent),
|
|
({'content-type': 'text/plain',
|
|
'etag': EMPTY_ETAG},
|
|
'')
|
|
]
|
|
|
|
# Test downloading whole container
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object'}]],
|
|
[None, [{'name': 'pseudo/'}]],
|
|
[None, []],
|
|
]
|
|
connection.return_value.auth_end_time = 0
|
|
connection.return_value.attempts = 0
|
|
|
|
with mock.patch(BUILTIN_OPEN) as mock_open:
|
|
argv = ["", "download", "container"]
|
|
swiftclient.shell.main(argv)
|
|
calls = [mock.call('container', 'object',
|
|
headers={}, resp_chunk_size=65536,
|
|
response_dict={}),
|
|
mock.call('container', 'pseudo/',
|
|
headers={}, resp_chunk_size=65536,
|
|
response_dict={})]
|
|
connection.return_value.get_object.assert_has_calls(
|
|
calls, any_order=True)
|
|
mock_open.assert_called_once_with('object', 'wb', 65536)
|
|
self.assertEqual([mock.call('pseudo')], makedirs.mock_calls)
|
|
makedirs.reset_mock()
|
|
|
|
# Test downloading single object
|
|
objcontent = six.BytesIO(b'objcontent')
|
|
connection.return_value.get_object.side_effect = [
|
|
({'content-type': 'text/plain',
|
|
'etag': '2cbbfe139a744d6abbe695e17f3c1991'},
|
|
objcontent)
|
|
]
|
|
with mock.patch(BUILTIN_OPEN) as mock_open:
|
|
argv = ["", "download", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.get_object.assert_called_with(
|
|
'container', 'object', headers={}, resp_chunk_size=65536,
|
|
response_dict={})
|
|
mock_open.assert_called_with('object', 'wb', 65536)
|
|
self.assertEqual([], makedirs.mock_calls)
|
|
|
|
# Test downloading without md5 checks
|
|
objcontent = six.BytesIO(b'objcontent')
|
|
connection.return_value.get_object.side_effect = [
|
|
({'content-type': 'text/plain',
|
|
'etag': '2cbbfe139a744d6abbe695e17f3c1991'},
|
|
objcontent)
|
|
]
|
|
with mock.patch(BUILTIN_OPEN) as mock_open, mock.patch(
|
|
'swiftclient.service._SwiftReader') as sr:
|
|
argv = ["", "download", "container", "object", "--ignore-check"]
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.get_object.assert_called_with(
|
|
'container', 'object', headers={}, resp_chunk_size=65536,
|
|
response_dict={})
|
|
mock_open.assert_called_with('object', 'wb', 65536)
|
|
sr.assert_called_once_with('object', mock.ANY, mock.ANY, False)
|
|
self.assertEqual([], makedirs.mock_calls)
|
|
|
|
# Test downloading single object to stdout
|
|
objcontent = six.BytesIO(b'objcontent')
|
|
connection.return_value.get_object.side_effect = [
|
|
({'content-type': 'text/plain',
|
|
'etag': '2cbbfe139a744d6abbe695e17f3c1991'},
|
|
objcontent)
|
|
]
|
|
with CaptureOutput() as output:
|
|
argv = ["", "download", "--output", "-", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual('objcontent', output.out)
|
|
|
|
@mock.patch('swiftclient.service.shuffle')
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_download_shuffle(self, connection, mock_shuffle):
|
|
# Test that the container and object lists are shuffled
|
|
mock_shuffle.side_effect = lambda l: l
|
|
connection.return_value.get_object.return_value = [
|
|
{'content-type': 'text/plain',
|
|
'etag': EMPTY_ETAG},
|
|
'']
|
|
|
|
connection.return_value.get_container.side_effect = [
|
|
(None, [{'name': 'object'}]),
|
|
(None, [{'name': 'pseudo/'}]),
|
|
(None, []),
|
|
]
|
|
connection.return_value.auth_end_time = 0
|
|
connection.return_value.attempts = 0
|
|
connection.return_value.get_account.side_effect = [
|
|
(None, [{'name': 'container'}]),
|
|
(None, [])
|
|
]
|
|
|
|
with mock.patch(BUILTIN_OPEN) as mock_open:
|
|
with mock.patch('swiftclient.service.makedirs') as mock_mkdir:
|
|
argv = ["", "download", "--all"]
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(3, mock_shuffle.call_count)
|
|
mock_shuffle.assert_any_call(['container'])
|
|
mock_shuffle.assert_any_call(['object'])
|
|
mock_shuffle.assert_any_call(['pseudo/'])
|
|
mock_open.assert_called_once_with('container/object', 'wb', 65536)
|
|
self.assertEqual([
|
|
mock.call('container'),
|
|
mock.call('container/pseudo'),
|
|
], mock_mkdir.mock_calls)
|
|
|
|
# Test that the container and object lists are not shuffled
|
|
mock_shuffle.reset_mock()
|
|
|
|
connection.return_value.get_container.side_effect = [
|
|
(None, [{'name': 'object'}]),
|
|
(None, [{'name': 'pseudo/'}]),
|
|
(None, []),
|
|
]
|
|
connection.return_value.get_account.side_effect = [
|
|
(None, [{'name': 'container'}]),
|
|
(None, [])
|
|
]
|
|
|
|
with mock.patch(BUILTIN_OPEN) as mock_open:
|
|
with mock.patch('swiftclient.service.makedirs') as mock_mkdir:
|
|
argv = ["", "download", "--all", "--no-shuffle"]
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(0, mock_shuffle.call_count)
|
|
mock_open.assert_called_once_with('container/object', 'wb', 65536)
|
|
self.assertEqual([
|
|
mock.call('container'),
|
|
mock.call('container/pseudo'),
|
|
], mock_mkdir.mock_calls)
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_download_no_content_type(self, connection):
|
|
connection.return_value.get_object.return_value = [
|
|
{'etag': EMPTY_ETAG},
|
|
'']
|
|
|
|
# Test downloading whole container
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object'}]],
|
|
[None, [{'name': 'pseudo/'}]],
|
|
[None, []],
|
|
]
|
|
connection.return_value.auth_end_time = 0
|
|
connection.return_value.attempts = 0
|
|
|
|
with mock.patch(BUILTIN_OPEN) as mock_open:
|
|
with mock.patch('swiftclient.service.makedirs') as mock_mkdir:
|
|
argv = ["", "download", "container"]
|
|
swiftclient.shell.main(argv)
|
|
calls = [mock.call('container', 'object',
|
|
headers={}, resp_chunk_size=65536,
|
|
response_dict={}),
|
|
mock.call('container', 'pseudo/',
|
|
headers={}, resp_chunk_size=65536,
|
|
response_dict={})]
|
|
connection.return_value.get_object.assert_has_calls(
|
|
calls, any_order=True)
|
|
mock_open.assert_called_once_with('object', 'wb', 65536)
|
|
self.assertEqual([
|
|
mock.call('pseudo'),
|
|
], mock_mkdir.mock_calls)
|
|
|
|
@mock.patch('swiftclient.shell.walk')
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_upload(self, connection, walk):
|
|
connection.return_value.head_object.return_value = {
|
|
'content-length': '0'}
|
|
connection.return_value.put_object.return_value = EMPTY_ETAG
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "upload", "container", self.tmpfile,
|
|
"-H", "X-Storage-Policy:one",
|
|
"--meta", "Color:Blue"]
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.put_container.assert_called_once_with(
|
|
'container',
|
|
{'X-Storage-Policy': 'one'},
|
|
response_dict={})
|
|
|
|
connection.return_value.put_object.assert_called_with(
|
|
'container',
|
|
self.tmpfile.lstrip('/'),
|
|
mock.ANY,
|
|
content_length=0,
|
|
headers={'x-object-meta-mtime': mock.ANY,
|
|
'X-Storage-Policy': 'one',
|
|
'X-Object-Meta-Color': 'Blue'},
|
|
response_dict={})
|
|
|
|
# upload to pseudo-folder (via <container> param)
|
|
argv = ["", "upload", "container/pseudo-folder/nested", self.tmpfile,
|
|
"-H", "X-Storage-Policy:one"]
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.put_container.assert_called_with(
|
|
'container',
|
|
{'X-Storage-Policy': 'one'},
|
|
response_dict={})
|
|
|
|
connection.return_value.put_object.assert_called_with(
|
|
'container',
|
|
'pseudo-folder/nested' + self.tmpfile,
|
|
mock.ANY,
|
|
content_length=0,
|
|
headers={'x-object-meta-mtime': mock.ANY,
|
|
'X-Storage-Policy': 'one'},
|
|
response_dict={})
|
|
|
|
# Upload whole directory
|
|
argv = ["", "upload", "container", "/tmp"]
|
|
_tmpfile = self.tmpfile
|
|
_tmpfile_dir = dirname(_tmpfile)
|
|
_tmpfile_base = basename(_tmpfile)
|
|
walk.return_value = [(_tmpfile_dir, [], [_tmpfile_base])]
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.put_object.assert_called_with(
|
|
'container',
|
|
self.tmpfile.lstrip('/'),
|
|
mock.ANY,
|
|
content_length=0,
|
|
headers={'x-object-meta-mtime': mock.ANY},
|
|
response_dict={})
|
|
|
|
# Upload in segments
|
|
connection.return_value.head_container.return_value = {
|
|
'x-storage-policy': 'one'}
|
|
argv = ["", "upload", "container", self.tmpfile, "-S", "10"]
|
|
with open(self.tmpfile, "wb") as fh:
|
|
fh.write(b'12345678901234567890')
|
|
swiftclient.shell.main(argv)
|
|
expected_calls = [mock.call('container',
|
|
{'X-Storage-Policy': mock.ANY},
|
|
response_dict={}),
|
|
mock.call('container_segments',
|
|
{'X-Storage-Policy': mock.ANY},
|
|
response_dict={})]
|
|
connection.return_value.put_container.has_calls(expected_calls)
|
|
connection.return_value.put_object.assert_called_with(
|
|
'container',
|
|
self.tmpfile.lstrip('/'),
|
|
'',
|
|
content_length=0,
|
|
headers={'x-object-manifest': mock.ANY,
|
|
'x-object-meta-mtime': mock.ANY},
|
|
response_dict={})
|
|
|
|
# upload in segments to pseudo-folder (via <container> param)
|
|
connection.reset_mock()
|
|
connection.return_value.head_container.return_value = {
|
|
'x-storage-policy': 'one'}
|
|
argv = ["", "upload", "container/pseudo-folder/nested",
|
|
self.tmpfile, "-S", "10", "--use-slo"]
|
|
with open(self.tmpfile, "wb") as fh:
|
|
fh.write(b'12345678901234567890')
|
|
swiftclient.shell.main(argv)
|
|
expected_calls = [mock.call('container',
|
|
{},
|
|
response_dict={}),
|
|
mock.call('container_segments',
|
|
{'X-Storage-Policy': 'one'},
|
|
response_dict={})]
|
|
connection.return_value.put_container.assert_has_calls(expected_calls)
|
|
connection.return_value.put_object.assert_called_with(
|
|
'container',
|
|
'pseudo-folder/nested' + self.tmpfile,
|
|
mock.ANY,
|
|
headers={
|
|
'x-object-meta-mtime': mock.ANY,
|
|
},
|
|
query_string='multipart-manifest=put',
|
|
response_dict=mock.ANY)
|
|
|
|
@mock.patch('swiftclient.service.SwiftService.upload')
|
|
def test_upload_object_with_account_readonly(self, upload):
|
|
argv = ["", "upload", "container", self.tmpfile]
|
|
upload.return_value = [
|
|
{"success": False,
|
|
"headers": {},
|
|
"container": 'container',
|
|
"action": 'create_container',
|
|
"error": swiftclient.ClientException(
|
|
'Container PUT failed',
|
|
http_status=403,
|
|
http_reason='Forbidden',
|
|
http_response_content=b'<html><h1>Forbidden</h1>')
|
|
}]
|
|
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
self.assertTrue(output.err != '')
|
|
warning_msg = "Warning: failed to create container 'container': " \
|
|
"403 Forbidden"
|
|
self.assertTrue(output.err.startswith(warning_msg))
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_upload_delete_slo_segments(self, connection):
|
|
# Upload delete existing segments
|
|
connection.return_value.head_container.return_value = {
|
|
'x-storage-policy': 'one'}
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "upload", "container", self.tmpfile]
|
|
connection.return_value.head_object.side_effect = [
|
|
{'x-static-large-object': 'true', # For the upload call
|
|
'content-length': '2'},
|
|
{'x-static-large-object': 'false', # For the 1st delete call
|
|
'content-length': '2'},
|
|
{'x-static-large-object': 'false', # For the 2nd delete call
|
|
'content-length': '2'}
|
|
]
|
|
connection.return_value.get_object.return_value = (
|
|
{},
|
|
b'[{"name": "container1/old_seg1"},'
|
|
b' {"name": "container2/old_seg2"}]'
|
|
)
|
|
connection.return_value.put_object.return_value = EMPTY_ETAG
|
|
# create the delete_object child mock here in attempt to fix
|
|
# https://bugs.launchpad.net/python-swiftclient/+bug/1480223
|
|
connection.return_value.delete_object.return_value = None
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.put_object.assert_called_with(
|
|
'container',
|
|
self.tmpfile.lstrip('/'),
|
|
mock.ANY,
|
|
content_length=0,
|
|
headers={'x-object-meta-mtime': mock.ANY},
|
|
response_dict={})
|
|
expected_delete_calls = [
|
|
mock.call(
|
|
b'container1', b'old_seg1',
|
|
response_dict={}
|
|
),
|
|
mock.call(
|
|
b'container2', b'old_seg2',
|
|
response_dict={}
|
|
)
|
|
]
|
|
self.assertEqual(
|
|
sorted(expected_delete_calls),
|
|
sorted(connection.return_value.delete_object.mock_calls)
|
|
)
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_upload_leave_slo_segments(self, connection):
|
|
# Test upload overwriting a manifest respects --leave-segments
|
|
connection.return_value.head_container.return_value = {
|
|
'x-storage-policy': 'one'}
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "upload", "container", self.tmpfile, "--leave-segments"]
|
|
connection.return_value.head_object.side_effect = [
|
|
{'x-static-large-object': 'true', # For the upload call
|
|
'content-length': '2'}]
|
|
connection.return_value.put_object.return_value = (
|
|
'd41d8cd98f00b204e9800998ecf8427e')
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.put_object.assert_called_with(
|
|
'container',
|
|
self.tmpfile.lstrip('/'),
|
|
mock.ANY,
|
|
content_length=0,
|
|
headers={'x-object-meta-mtime': mock.ANY},
|
|
response_dict={})
|
|
self.assertFalse(connection.return_value.delete_object.mock_calls)
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_upload_delete_dlo_segments(self, connection):
|
|
# Upload delete existing segments
|
|
connection.return_value.head_container.return_value = {
|
|
'x-storage-policy': 'one'}
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "upload", "container", self.tmpfile]
|
|
connection.return_value.head_object.side_effect = [
|
|
{'x-object-manifest': 'container1/prefix',
|
|
'content-length': '0'},
|
|
{},
|
|
{}
|
|
]
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'prefix_a', 'bytes': 0,
|
|
'last_modified': '123T456'}]],
|
|
# Have multiple pages worth of DLO segments
|
|
[None, [{'name': 'prefix_b', 'bytes': 0,
|
|
'last_modified': '123T456'}]],
|
|
[None, []]
|
|
]
|
|
connection.return_value.put_object.return_value = EMPTY_ETAG
|
|
# create the delete_object child mock here in attempt to fix
|
|
# https://bugs.launchpad.net/python-swiftclient/+bug/1480223
|
|
connection.return_value.delete_object.return_value = None
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.put_object.assert_called_with(
|
|
'container',
|
|
self.tmpfile.lstrip('/'),
|
|
mock.ANY,
|
|
content_length=0,
|
|
headers={'x-object-meta-mtime': mock.ANY},
|
|
response_dict={})
|
|
expected_delete_calls = [
|
|
mock.call(
|
|
'container1', 'prefix_a',
|
|
response_dict={}
|
|
),
|
|
mock.call(
|
|
'container1', 'prefix_b',
|
|
response_dict={}
|
|
)
|
|
]
|
|
self.assertEqual(
|
|
sorted(expected_delete_calls),
|
|
sorted(connection.return_value.delete_object.mock_calls)
|
|
)
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_upload_leave_dlo_segments(self, connection):
|
|
# Upload delete existing segments
|
|
connection.return_value.head_container.return_value = {
|
|
'x-storage-policy': 'one'}
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "upload", "container", self.tmpfile, "--leave-segments"]
|
|
connection.return_value.head_object.side_effect = [
|
|
{'x-object-manifest': 'container1/prefix',
|
|
'content-length': '0'}]
|
|
connection.return_value.put_object.return_value = (
|
|
'd41d8cd98f00b204e9800998ecf8427e')
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.put_object.assert_called_with(
|
|
'container',
|
|
self.tmpfile.lstrip('/'),
|
|
mock.ANY,
|
|
content_length=0,
|
|
headers={'x-object-meta-mtime': mock.ANY},
|
|
response_dict={})
|
|
self.assertFalse(connection.return_value.delete_object.mock_calls)
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_upload_segments_to_same_container(self, connection):
|
|
# Upload in segments to same container
|
|
connection.return_value.head_object.return_value = {
|
|
'content-length': '0'}
|
|
connection.return_value.attempts = 0
|
|
connection.return_value.put_object.return_value = EMPTY_ETAG
|
|
argv = ["", "upload", "container", self.tmpfile, "-S", "10",
|
|
"-C", "container"]
|
|
with open(self.tmpfile, "wb") as fh:
|
|
fh.write(b'12345678901234567890')
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.put_container.assert_called_once_with(
|
|
'container', {}, response_dict={})
|
|
connection.return_value.put_object.assert_called_with(
|
|
'container',
|
|
self.tmpfile.lstrip('/'),
|
|
'',
|
|
content_length=0,
|
|
headers={'x-object-manifest': mock.ANY,
|
|
'x-object-meta-mtime': mock.ANY},
|
|
response_dict={})
|
|
|
|
@mock.patch('swiftclient.shell.io.open')
|
|
@mock.patch('swiftclient.service.SwiftService.upload')
|
|
def test_upload_from_stdin(self, upload_mock, io_open_mock):
|
|
def fake_open(fd, mode):
|
|
mock_io = mock.Mock()
|
|
mock_io.fileno.return_value = fd
|
|
return mock_io
|
|
|
|
io_open_mock.side_effect = fake_open
|
|
|
|
argv = ["", "upload", "container", "-", "--object-name", "foo"]
|
|
swiftclient.shell.main(argv)
|
|
upload_mock.assert_called_once_with("container", mock.ANY)
|
|
# This is a little convoluted: we want to examine the first call ([0]),
|
|
# the argv list([1]), the second parameter ([1]), and the first
|
|
# element. This is because the upload method takes a container and a
|
|
# list of SwiftUploadObjects.
|
|
swift_upload_obj = upload_mock.mock_calls[0][1][1][0]
|
|
self.assertEqual(sys.stdin.fileno(), swift_upload_obj.source.fileno())
|
|
io_open_mock.assert_called_once_with(sys.stdin.fileno(), mode='rb')
|
|
|
|
@mock.patch('swiftclient.service.SwiftService.upload')
|
|
def test_upload_from_stdin_no_name(self, upload_mock):
|
|
argv = ["", "upload", "container", "-"]
|
|
with CaptureOutput() as out:
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, argv)
|
|
self.assertEqual(0, len(upload_mock.mock_calls))
|
|
self.assertTrue(out.err.find('object-name must be specified') >= 0)
|
|
|
|
@mock.patch('swiftclient.service.SwiftService.upload')
|
|
def test_upload_from_stdin_and_others(self, upload_mock):
|
|
argv = ["", "upload", "container", "-", "foo", "--object-name", "bar"]
|
|
with CaptureOutput() as out:
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, argv)
|
|
self.assertEqual(0, len(upload_mock.mock_calls))
|
|
self.assertTrue(out.err.find(
|
|
'upload from stdin cannot be used') >= 0)
|
|
|
|
@mock.patch.object(swiftclient.service.SwiftService,
|
|
'_bulk_delete_page_size', lambda *a: 0)
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_delete_bad_threads(self, mock_connection):
|
|
mock_connection.return_value.get_container.return_value = (None, [])
|
|
mock_connection.return_value.attempts = 0
|
|
|
|
def check_bad(argv):
|
|
args, env = _make_cmd(
|
|
'delete', {}, {}, cmd_args=['cont'] + argv)
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as output:
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
self.assertIn(
|
|
'ERROR: option %s should be a positive integer.' % argv[0],
|
|
output.err)
|
|
|
|
def check_good(argv):
|
|
args, env = _make_cmd(
|
|
'delete', {}, {}, cmd_args=['cont'] + argv)
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(args)
|
|
self.assertEqual('', output.err)
|
|
check_bad(["--object-threads", "-1"])
|
|
check_bad(["--object-threads", "0"])
|
|
check_bad(["--container-threads", "-1"])
|
|
check_bad(["--container-threads", "0"])
|
|
check_good(["--object-threads", "1"])
|
|
check_good(["--container-threads", "1"])
|
|
|
|
@mock.patch.object(swiftclient.service.SwiftService,
|
|
'_bulk_delete_page_size', lambda *a: 1)
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_delete_account(self, connection):
|
|
connection.return_value.get_account.side_effect = [
|
|
[None, [{'name': 'container'}, {'name': 'container2'}]],
|
|
[None, [{'name': 'empty_container'}]],
|
|
[None, []],
|
|
]
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object'}, {'name': 'obj\xe9ct2'}]],
|
|
[None, []],
|
|
[None, [{'name': 'object'}]],
|
|
[None, []],
|
|
[None, []],
|
|
]
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "delete", "--all"]
|
|
connection.return_value.head_object.return_value = {}
|
|
connection.return_value.delete_object.return_value = None
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.delete_object.assert_has_calls([
|
|
mock.call('container', 'object', query_string=None,
|
|
response_dict={}, headers={}),
|
|
mock.call('container', 'obj\xe9ct2', query_string=None,
|
|
response_dict={}, headers={}),
|
|
mock.call('container2', 'object', query_string=None,
|
|
response_dict={}, headers={})], any_order=True)
|
|
self.assertEqual(3, connection.return_value.delete_object.call_count,
|
|
'Expected 3 calls but found\n%r'
|
|
% connection.return_value.delete_object.mock_calls)
|
|
self.assertEqual(
|
|
connection.return_value.delete_container.mock_calls, [
|
|
mock.call('container', response_dict={}, headers={}),
|
|
mock.call('container2', response_dict={}, headers={}),
|
|
mock.call('empty_container', response_dict={}, headers={})])
|
|
|
|
@mock.patch.object(swiftclient.service.SwiftService,
|
|
'_bulk_delete_page_size', lambda *a: 10)
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_delete_bulk_account(self, connection):
|
|
connection.return_value.get_account.side_effect = [
|
|
[None, [{'name': 'container'}, {'name': 'container2'}]],
|
|
[None, [{'name': 'empty_container'}]],
|
|
[None, []],
|
|
]
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object'}, {'name': 'obj\xe9ct2'},
|
|
{'name': 'object3'}]],
|
|
[None, []],
|
|
[None, [{'name': 'object'}]],
|
|
[None, []],
|
|
[None, []],
|
|
]
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "delete", "--all", "--object-threads", "2"]
|
|
connection.return_value.post_account.return_value = {}, (
|
|
b'{"Number Not Found": 0, "Response Status": "200 OK", '
|
|
b'"Errors": [], "Number Deleted": 1, "Response Body": ""}')
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(
|
|
3, len(connection.return_value.post_account.mock_calls),
|
|
'Expected 3 calls but found\n%r'
|
|
% connection.return_value.post_account.mock_calls)
|
|
# POSTs for same container are made in parallel so expect any order
|
|
for expected in [
|
|
mock.call(query_string='bulk-delete',
|
|
data=b'/container/object\n/container/obj%C3%A9ct2\n',
|
|
headers={'Content-Type': 'text/plain',
|
|
'Accept': 'application/json'},
|
|
response_dict={}),
|
|
mock.call(query_string='bulk-delete',
|
|
data=b'/container/object3\n',
|
|
headers={'Content-Type': 'text/plain',
|
|
'Accept': 'application/json'},
|
|
response_dict={})]:
|
|
self.assertIn(expected,
|
|
connection.return_value.post_account.mock_calls[:2])
|
|
# POSTs for different containers are made sequentially so expect order
|
|
self.assertEqual(
|
|
mock.call(query_string='bulk-delete',
|
|
data=b'/container2/object\n',
|
|
headers={'Content-Type': 'text/plain',
|
|
'Accept': 'application/json'},
|
|
response_dict={}),
|
|
connection.return_value.post_account.mock_calls[2])
|
|
self.assertEqual(
|
|
connection.return_value.delete_container.mock_calls, [
|
|
mock.call('container', response_dict={}, headers={}),
|
|
mock.call('container2', response_dict={}, headers={}),
|
|
mock.call('empty_container', response_dict={}, headers={})])
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_delete_bulk_account_with_capabilities(self, connection):
|
|
connection.return_value.get_capabilities.return_value = {
|
|
'bulk_delete': {
|
|
'max_deletes_per_request': 10000,
|
|
'max_failed_deletes': 1000,
|
|
},
|
|
}
|
|
connection.return_value.get_account.side_effect = [
|
|
[None, [{'name': 'container'}]],
|
|
[None, [{'name': 'container2'}]],
|
|
[None, [{'name': 'empty_container'}]],
|
|
[None, []],
|
|
]
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object'}, {'name': 'obj\xe9ct2'},
|
|
{'name': 'z_object'}, {'name': 'z_obj\xe9ct2'}]],
|
|
[None, []],
|
|
[None, [{'name': 'object'}, {'name': 'obj\xe9ct2'},
|
|
{'name': 'z_object'}, {'name': 'z_obj\xe9ct2'}]],
|
|
[None, []],
|
|
[None, []],
|
|
]
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "delete", "--all", "--object-threads", "1"]
|
|
connection.return_value.post_account.return_value = {}, (
|
|
b'{"Number Not Found": 0, "Response Status": "200 OK", '
|
|
b'"Errors": [], "Number Deleted": 1, "Response Body": ""}')
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(
|
|
connection.return_value.post_account.mock_calls, [
|
|
mock.call(query_string='bulk-delete',
|
|
data=b''.join([
|
|
b'/container/object\n',
|
|
b'/container/obj%C3%A9ct2\n',
|
|
b'/container/z_object\n',
|
|
b'/container/z_obj%C3%A9ct2\n'
|
|
]),
|
|
headers={'Content-Type': 'text/plain',
|
|
'Accept': 'application/json'},
|
|
response_dict={}),
|
|
mock.call(query_string='bulk-delete',
|
|
data=b''.join([
|
|
b'/container2/object\n',
|
|
b'/container2/obj%C3%A9ct2\n',
|
|
b'/container2/z_object\n',
|
|
b'/container2/z_obj%C3%A9ct2\n'
|
|
]),
|
|
headers={'Content-Type': 'text/plain',
|
|
'Accept': 'application/json'},
|
|
response_dict={})])
|
|
self.assertEqual(
|
|
connection.return_value.delete_container.mock_calls, [
|
|
mock.call('container', response_dict={}, headers={}),
|
|
mock.call('container2', response_dict={}, headers={}),
|
|
mock.call('empty_container', response_dict={}, headers={})])
|
|
self.assertEqual(connection.return_value.get_capabilities.mock_calls,
|
|
[mock.call(None)]) # only one /info request
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_delete_bulk_account_with_capabilities_and_pages(self, connection):
|
|
connection.return_value.get_capabilities.return_value = {
|
|
'bulk_delete': {
|
|
'max_deletes_per_request': 2,
|
|
'max_failed_deletes': 1000,
|
|
},
|
|
}
|
|
connection.return_value.get_account.side_effect = [
|
|
[None, [{'name': 'container'}]],
|
|
[None, [{'name': 'container2'}]],
|
|
[None, [{'name': 'empty_container'}]],
|
|
[None, []],
|
|
]
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object'}, {'name': 'obj\xe9ct2'},
|
|
{'name': 'z_object'}, {'name': 'z_obj\xe9ct2'}]],
|
|
[None, []],
|
|
[None, [{'name': 'object'}, {'name': 'obj\xe9ct2'},
|
|
{'name': 'z_object'}, {'name': 'z_obj\xe9ct2'}]],
|
|
[None, []],
|
|
[None, []],
|
|
]
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "delete", "--all", "--object-threads", "1"]
|
|
connection.return_value.post_account.return_value = {}, (
|
|
b'{"Number Not Found": 0, "Response Status": "200 OK", '
|
|
b'"Errors": [], "Number Deleted": 1, "Response Body": ""}')
|
|
swiftclient.shell.main(argv)
|
|
# check that each bulk call was only called with 2 objects
|
|
self.assertEqual(
|
|
connection.return_value.post_account.mock_calls, [
|
|
mock.call(query_string='bulk-delete',
|
|
data=b''.join([
|
|
b'/container/object\n',
|
|
b'/container/obj%C3%A9ct2\n',
|
|
]),
|
|
headers={'Content-Type': 'text/plain',
|
|
'Accept': 'application/json'},
|
|
response_dict={}),
|
|
mock.call(query_string='bulk-delete',
|
|
data=b''.join([
|
|
b'/container/z_object\n',
|
|
b'/container/z_obj%C3%A9ct2\n'
|
|
]),
|
|
headers={'Content-Type': 'text/plain',
|
|
'Accept': 'application/json'},
|
|
response_dict={}),
|
|
mock.call(query_string='bulk-delete',
|
|
data=b''.join([
|
|
b'/container2/object\n',
|
|
b'/container2/obj%C3%A9ct2\n',
|
|
]),
|
|
headers={'Content-Type': 'text/plain',
|
|
'Accept': 'application/json'},
|
|
response_dict={}),
|
|
mock.call(query_string='bulk-delete',
|
|
data=b''.join([
|
|
b'/container2/z_object\n',
|
|
b'/container2/z_obj%C3%A9ct2\n'
|
|
]),
|
|
headers={'Content-Type': 'text/plain',
|
|
'Accept': 'application/json'},
|
|
response_dict={})])
|
|
self.assertEqual(
|
|
connection.return_value.delete_container.mock_calls, [
|
|
mock.call('container', response_dict={}, headers={}),
|
|
mock.call('container2', response_dict={}, headers={}),
|
|
mock.call('empty_container', response_dict={}, headers={})])
|
|
self.assertEqual(connection.return_value.get_capabilities.mock_calls,
|
|
[mock.call(None)]) # only one /info request
|
|
|
|
@mock.patch.object(swiftclient.service.SwiftService,
|
|
'_bulk_delete_page_size', lambda *a: 1)
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_delete_container(self, connection):
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object'}]],
|
|
[None, []],
|
|
]
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "delete", "container"]
|
|
connection.return_value.head_object.return_value = {}
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.delete_container.assert_called_with(
|
|
'container', response_dict={}, headers={})
|
|
connection.return_value.delete_object.assert_called_with(
|
|
'container', 'object', query_string=None, response_dict={},
|
|
headers={})
|
|
|
|
@mock.patch.object(swiftclient.service.SwiftService,
|
|
'_bulk_delete_page_size', lambda *a: 1)
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_delete_container_headers(self, connection):
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object'}]],
|
|
[None, []],
|
|
]
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "delete", "container", "-H", "Skip-Middleware: Test"]
|
|
connection.return_value.head_object.return_value = {}
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.delete_container.assert_called_with(
|
|
'container', response_dict={},
|
|
headers={'Skip-Middleware': 'Test'})
|
|
connection.return_value.delete_object.assert_called_with(
|
|
'container', 'object', query_string=None, response_dict={},
|
|
headers={'Skip-Middleware': 'Test'})
|
|
|
|
@mock.patch.object(swiftclient.service.SwiftService,
|
|
'_bulk_delete_page_size', lambda *a: 10)
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_delete_bulk_container(self, connection):
|
|
connection.return_value.get_container.side_effect = [
|
|
[None, [{'name': 'object'}]],
|
|
[None, []],
|
|
]
|
|
connection.return_value.attempts = 0
|
|
argv = ["", "delete", "container"]
|
|
connection.return_value.post_account.return_value = {}, (
|
|
b'{"Number Not Found": 0, "Response Status": "200 OK", '
|
|
b'"Errors": [], "Number Deleted": 1, "Response Body": ""}')
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.post_account.assert_called_with(
|
|
query_string='bulk-delete', data=b'/container/object\n',
|
|
headers={'Content-Type': 'text/plain',
|
|
'Accept': 'application/json'},
|
|
response_dict={})
|
|
connection.return_value.delete_container.assert_called_with(
|
|
'container', response_dict={}, headers={})
|
|
|
|
def test_delete_verbose_output_utf8(self):
|
|
container = 't\u00e9st_c'
|
|
base_argv = ['', '--verbose', 'delete']
|
|
|
|
# simulate container having an object with utf-8 code points in name,
|
|
# just returning the object delete result
|
|
res = {'success': True, 'response_dict': {}, 'attempts': 2,
|
|
'container': container, 'action': 'delete_object',
|
|
'object': 'obj_t\u00east_o'}
|
|
|
|
with mock.patch('swiftclient.shell.SwiftService.delete') as mock_func:
|
|
with CaptureOutput() as out:
|
|
mock_func.return_value = [res]
|
|
swiftclient.shell.main(base_argv + [container.encode('utf-8')])
|
|
|
|
mock_func.assert_called_once_with(container=container)
|
|
self.assertTrue(out.out.find(
|
|
'obj_t\u00east_o [after 2 attempts]') >= 0, out)
|
|
|
|
# simulate empty container
|
|
res = {'success': True, 'response_dict': {}, 'attempts': 2,
|
|
'container': container, 'action': 'delete_container'}
|
|
|
|
with mock.patch('swiftclient.shell.SwiftService.delete') as mock_func:
|
|
with CaptureOutput() as out:
|
|
mock_func.return_value = [res]
|
|
swiftclient.shell.main(base_argv + [container.encode('utf-8')])
|
|
|
|
mock_func.assert_called_once_with(container=container)
|
|
self.assertTrue(out.out.find(
|
|
't\u00e9st_c [after 2 attempts]') >= 0, out)
|
|
|
|
@mock.patch.object(swiftclient.service.SwiftService,
|
|
'_bulk_delete_page_size', lambda *a: 1)
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_delete_per_object(self, connection):
|
|
argv = ["", "delete", "container", "object"]
|
|
connection.return_value.head_object.return_value = {}
|
|
connection.return_value.attempts = 0
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.delete_object.assert_called_with(
|
|
'container', 'object', query_string=None, response_dict={},
|
|
headers={})
|
|
|
|
@mock.patch.object(swiftclient.service.SwiftService,
|
|
'_bulk_delete_page_size', lambda *a: 10)
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_delete_bulk_object(self, connection):
|
|
argv = ["", "delete", "container", "object"]
|
|
connection.return_value.post_account.return_value = {}, (
|
|
b'{"Number Not Found": 0, "Response Status": "200 OK", '
|
|
b'"Errors": [], "Number Deleted": 1, "Response Body": ""}')
|
|
connection.return_value.attempts = 0
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.post_account.assert_called_with(
|
|
query_string='bulk-delete', data=b'/container/object\n',
|
|
headers={'Content-Type': 'text/plain',
|
|
'Accept': 'application/json'},
|
|
response_dict={})
|
|
|
|
def test_delete_verbose_output(self):
|
|
del_obj_res = {'success': True, 'response_dict': {}, 'attempts': 2,
|
|
'container': 't\xe9st_c', 'action': 'delete_object',
|
|
'object': 't\xe9st_o'}
|
|
|
|
del_seg_res = del_obj_res.copy()
|
|
del_seg_res.update({'action': 'delete_segment'})
|
|
|
|
del_con_res = del_obj_res.copy()
|
|
del_con_res.update({'action': 'delete_container', 'object': None})
|
|
|
|
test_exc = Exception('t\xe9st_exc')
|
|
error_res = del_obj_res.copy()
|
|
error_res.update({'success': False, 'error': test_exc, 'object': None})
|
|
|
|
mock_delete = mock.Mock()
|
|
base_argv = ['', '--verbose', 'delete']
|
|
|
|
with mock.patch('swiftclient.shell.SwiftService.delete', mock_delete):
|
|
with CaptureOutput() as out:
|
|
mock_delete.return_value = [del_obj_res]
|
|
swiftclient.shell.main(base_argv + ['t\xe9st_c', 't\xe9st_o'])
|
|
|
|
mock_delete.assert_called_once_with(container='t\xe9st_c',
|
|
objects=['t\xe9st_o'])
|
|
self.assertTrue(out.out.find(
|
|
't\xe9st_o [after 2 attempts]') >= 0)
|
|
|
|
with CaptureOutput() as out:
|
|
mock_delete.return_value = [del_seg_res]
|
|
swiftclient.shell.main(base_argv + ['t\xe9st_c', 't\xe9st_o'])
|
|
|
|
mock_delete.assert_called_with(container='t\xe9st_c',
|
|
objects=['t\xe9st_o'])
|
|
self.assertTrue(out.out.find(
|
|
't\xe9st_c/t\xe9st_o [after 2 attempts]') >= 0)
|
|
|
|
with CaptureOutput() as out:
|
|
mock_delete.return_value = [del_con_res]
|
|
swiftclient.shell.main(base_argv + ['t\xe9st_c'])
|
|
|
|
mock_delete.assert_called_with(container='t\xe9st_c')
|
|
self.assertTrue(out.out.find(
|
|
't\xe9st_c [after 2 attempts]') >= 0)
|
|
|
|
with CaptureOutput() as out:
|
|
mock_delete.return_value = [error_res]
|
|
self.assertRaises(SystemExit,
|
|
swiftclient.shell.main,
|
|
base_argv + ['t\xe9st_c'])
|
|
|
|
mock_delete.assert_called_with(container='t\xe9st_c')
|
|
self.assertTrue(out.err.find(
|
|
'Error Deleting: t\xe9st_c: t\xe9st_exc') >= 0)
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_post_account(self, connection):
|
|
argv = ["", "post"]
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.post_account.assert_called_with(
|
|
headers={}, response_dict={})
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_post_account_bad_auth(self, connection):
|
|
argv = ["", "post"]
|
|
connection.return_value.post_account.side_effect = \
|
|
swiftclient.ClientException(
|
|
'bad auth', http_response_headers={'X-Trans-Id': 'trans_id'})
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.err,
|
|
'bad auth\nFailed Transaction ID: trans_id\n')
|
|
|
|
# do it again with a unicode token
|
|
connection.return_value.post_account.side_effect = \
|
|
swiftclient.ClientException(
|
|
'bad auth', http_response_headers={
|
|
'X-Trans-Id': 'non\u2011utf8'})
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.err,
|
|
'bad auth\n'
|
|
'Failed Transaction ID: non\u2011utf8\n')
|
|
|
|
# do it again with a wonky token
|
|
connection.return_value.post_account.side_effect = \
|
|
swiftclient.ClientException(
|
|
'bad auth', http_response_headers={
|
|
'X-Trans-Id': b'non\xffutf8'})
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.err,
|
|
'bad auth\nFailed Transaction ID: non%FFutf8\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_post_account_not_found(self, connection):
|
|
argv = ["", "post"]
|
|
connection.return_value.post_account.side_effect = \
|
|
swiftclient.ClientException('test', http_status=404)
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.err, 'Account not found\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_post_container(self, connection):
|
|
argv = ["", "post", "container"]
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.post_container.assert_called_with(
|
|
'container', headers={}, response_dict={})
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_post_container_bad_auth(self, connection):
|
|
argv = ["", "post", "container"]
|
|
connection.return_value.post_container.side_effect = \
|
|
swiftclient.ClientException('bad auth')
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.err, 'bad auth\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_post_container_not_found_causes_put(self, connection):
|
|
argv = ["", "post", "container"]
|
|
connection.return_value.post_container.side_effect = \
|
|
swiftclient.ClientException('test', http_status=404)
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual('container',
|
|
connection.return_value.put_container.call_args[0][0])
|
|
|
|
def test_post_container_with_bad_name(self):
|
|
argv = ["", "post", "conta/iner"]
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
self.assertTrue(output.err != '')
|
|
self.assertTrue(output.err.startswith('WARNING: / in'))
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_post_container_with_options(self, connection):
|
|
argv = ["", "post", "container",
|
|
"--read-acl", "test2:tester2",
|
|
"--write-acl", "test3:tester3 test4",
|
|
"--sync-to", "othersite",
|
|
"--sync-key", "secret",
|
|
]
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.post_container.assert_called_with(
|
|
'container', headers={
|
|
'X-Container-Write': 'test3:tester3 test4',
|
|
'X-Container-Read': 'test2:tester2',
|
|
'X-Container-Sync-Key': 'secret',
|
|
'X-Container-Sync-To': 'othersite'}, response_dict={})
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_post_object(self, connection):
|
|
argv = ["", "post", "container", "object",
|
|
"--meta", "Color:Blue",
|
|
"--header", "content-type:text/plain"
|
|
]
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.post_object.assert_called_with(
|
|
'container', 'object', headers={
|
|
'Content-Type': 'text/plain',
|
|
'X-Object-Meta-Color': 'Blue'}, response_dict={})
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_post_object_bad_auth(self, connection):
|
|
argv = ["", "post", "container", "object"]
|
|
connection.return_value.post_object.side_effect = \
|
|
swiftclient.ClientException("bad auth")
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.err, 'bad auth\n')
|
|
|
|
def test_post_object_too_many_args(self):
|
|
argv = ["", "post", "container", "object", "bad_arg"]
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertTrue(output.err != '')
|
|
self.assertTrue(output.err.startswith('Usage'))
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_copy_object_no_destination(self, connection):
|
|
argv = ["", "copy", "container", "object",
|
|
"--meta", "Color:Blue",
|
|
"--header", "content-type:text/plain"
|
|
]
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.copy_object.assert_called_with(
|
|
'container', 'object', destination=None, fresh_metadata=False,
|
|
headers={
|
|
'Content-Type': 'text/plain',
|
|
'X-Object-Meta-Color': 'Blue'}, response_dict={})
|
|
self.assertEqual(output.out, 'container/object copied to <self>\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_copy_object(self, connection):
|
|
argv = ["", "copy", "container", "object",
|
|
"--meta", "Color:Blue",
|
|
"--header", "content-type:text/plain",
|
|
"--destination", "/c/o"
|
|
]
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.copy_object.assert_called_with(
|
|
'container', 'object', destination="/c/o",
|
|
fresh_metadata=False,
|
|
headers={
|
|
'Content-Type': 'text/plain',
|
|
'X-Object-Meta-Color': 'Blue'}, response_dict={})
|
|
self.assertEqual(
|
|
output.out,
|
|
'created container c\ncontainer/object copied to /c/o\n'
|
|
)
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_copy_object_fresh_metadata(self, connection):
|
|
argv = ["", "copy", "container", "object",
|
|
"--meta", "Color:Blue", "--fresh-metadata",
|
|
"--header", "content-type:text/plain",
|
|
"--destination", "/c/o"
|
|
]
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.copy_object.assert_called_with(
|
|
'container', 'object', destination="/c/o", fresh_metadata=True,
|
|
headers={
|
|
'Content-Type': 'text/plain',
|
|
'X-Object-Meta-Color': 'Blue'}, response_dict={})
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_copy_two_objects(self, connection):
|
|
argv = ["", "copy", "container", "object", "object2",
|
|
"--meta", "Color:Blue"]
|
|
connection.return_value.copy_object.return_value = None
|
|
swiftclient.shell.main(argv)
|
|
calls = [
|
|
mock.call(
|
|
'container', 'object', destination=None,
|
|
fresh_metadata=False, headers={'X-Object-Meta-Color': 'Blue'},
|
|
response_dict={}),
|
|
mock.call(
|
|
'container', 'object2', destination=None,
|
|
fresh_metadata=False, headers={'X-Object-Meta-Color': 'Blue'},
|
|
response_dict={})
|
|
]
|
|
connection.return_value.copy_object.assert_has_calls(
|
|
calls, any_order=True)
|
|
self.assertEqual(len(connection.return_value.copy_object.mock_calls),
|
|
len(calls))
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_copy_two_objects_destination(self, connection):
|
|
argv = ["", "copy", "container", "object", "object2",
|
|
"--meta", "Color:Blue", "--destination", "/c"]
|
|
connection.return_value.copy_object.return_value = None
|
|
swiftclient.shell.main(argv)
|
|
calls = [
|
|
mock.call(
|
|
'container', 'object', destination="/c/object",
|
|
fresh_metadata=False, headers={'X-Object-Meta-Color': 'Blue'},
|
|
response_dict={}),
|
|
mock.call(
|
|
'container', 'object2', destination="/c/object2",
|
|
fresh_metadata=False, headers={'X-Object-Meta-Color': 'Blue'},
|
|
response_dict={})
|
|
]
|
|
connection.return_value.copy_object.assert_has_calls(
|
|
calls, any_order=True)
|
|
self.assertEqual(len(connection.return_value.copy_object.mock_calls),
|
|
len(calls))
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_copy_two_objects_bad_destination(self, connection):
|
|
argv = ["", "copy", "container", "object", "object2",
|
|
"--meta", "Color:Blue", "--destination", "/c/o"]
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(
|
|
output.err,
|
|
'Combination of multiple objects and destination '
|
|
'including object is invalid\n')
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_copy_object_bad_auth(self, connection):
|
|
argv = ["", "copy", "container", "object"]
|
|
connection.return_value.copy_object.side_effect = \
|
|
swiftclient.ClientException("bad auth")
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertEqual(output.err, 'bad auth\n')
|
|
|
|
def test_copy_object_not_enough_args(self):
|
|
argv = ["", "copy", "container"]
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertTrue(output.err != '')
|
|
self.assertTrue(output.err.startswith('Usage'))
|
|
|
|
def test_copy_bad_container(self):
|
|
argv = ["", "copy", "cont/ainer", "object"]
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
swiftclient.shell.main(argv)
|
|
|
|
self.assertTrue(output.err != '')
|
|
self.assertTrue(output.err.startswith('WARN'))
|
|
|
|
@mock.patch('swiftclient.shell.generate_temp_url', return_value='')
|
|
def test_temp_url(self, temp_url):
|
|
argv = ["", "tempurl", "GET", "60", "/v1/AUTH_account/c/o",
|
|
"secret_key"]
|
|
swiftclient.shell.main(argv)
|
|
temp_url.assert_called_with(
|
|
'/v1/AUTH_account/c/o', "60", 'secret_key', 'GET', absolute=False,
|
|
iso8601=False, prefix=False, ip_range=None)
|
|
|
|
@mock.patch('swiftclient.shell.generate_temp_url', return_value='')
|
|
def test_temp_url_prefix_based(self, temp_url):
|
|
argv = ["", "tempurl", "GET", "60", "/v1/AUTH_account/c/",
|
|
"secret_key", "--prefix-based"]
|
|
swiftclient.shell.main(argv)
|
|
temp_url.assert_called_with(
|
|
'/v1/AUTH_account/c/', "60", 'secret_key', 'GET', absolute=False,
|
|
iso8601=False, prefix=True, ip_range=None)
|
|
|
|
@mock.patch('swiftclient.shell.generate_temp_url', return_value='')
|
|
def test_temp_url_iso8601_in(self, temp_url):
|
|
dates = ('1970-01-01T00:01:00Z', '1970-01-01T00:01:00',
|
|
'1970-01-01')
|
|
for d in dates:
|
|
argv = ["", "tempurl", "GET", d, "/v1/AUTH_account/c/",
|
|
"secret_key"]
|
|
swiftclient.shell.main(argv)
|
|
temp_url.assert_called_with(
|
|
'/v1/AUTH_account/c/', d, 'secret_key', 'GET', absolute=False,
|
|
iso8601=False, prefix=False, ip_range=None)
|
|
|
|
@mock.patch('swiftclient.shell.generate_temp_url', return_value='')
|
|
def test_temp_url_iso8601_out(self, temp_url):
|
|
argv = ["", "tempurl", "GET", "60", "/v1/AUTH_account/c/",
|
|
"secret_key", "--iso8601"]
|
|
swiftclient.shell.main(argv)
|
|
temp_url.assert_called_with(
|
|
'/v1/AUTH_account/c/', "60", 'secret_key', 'GET', absolute=False,
|
|
iso8601=True, prefix=False, ip_range=None)
|
|
|
|
@mock.patch('swiftclient.shell.generate_temp_url', return_value='')
|
|
def test_absolute_expiry_temp_url(self, temp_url):
|
|
argv = ["", "tempurl", "GET", "60", "/v1/AUTH_account/c/o",
|
|
"secret_key", "--absolute"]
|
|
swiftclient.shell.main(argv)
|
|
temp_url.assert_called_with(
|
|
'/v1/AUTH_account/c/o', "60", 'secret_key', 'GET', absolute=True,
|
|
iso8601=False, prefix=False, ip_range=None)
|
|
|
|
@mock.patch('swiftclient.shell.generate_temp_url', return_value='')
|
|
def test_temp_url_with_ip_range(self, temp_url):
|
|
argv = ["", "tempurl", "GET", "60", "/v1/AUTH_account/c/o",
|
|
"secret_key", "--ip-range", "1.2.3.4"]
|
|
swiftclient.shell.main(argv)
|
|
temp_url.assert_called_with(
|
|
'/v1/AUTH_account/c/o', "60", 'secret_key', 'GET', absolute=False,
|
|
iso8601=False, prefix=False, ip_range='1.2.3.4')
|
|
|
|
def test_temp_url_output(self):
|
|
argv = ["", "tempurl", "GET", "60", "/v1/a/c/o",
|
|
"secret_key", "--absolute"]
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
sig = "63bc77a473a1c2ce956548cacf916f292eb9eac3"
|
|
expected = "/v1/a/c/o?temp_url_sig=%s&temp_url_expires=60\n" % sig
|
|
self.assertEqual(expected, output.out)
|
|
|
|
argv = ["", "tempurl", "GET", "60", "http://saio:8080/v1/a/c/o",
|
|
"secret_key", "--absolute"]
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
expected = "http://saio:8080%s" % expected
|
|
self.assertEqual(expected, output.out)
|
|
|
|
argv = ["", "tempurl", "GET", "60", "/v1/a/c/",
|
|
"secret_key", "--absolute", "--prefix"]
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
sig = '00008c4be1573ba74fc2ab9bce02e3a93d04b349'
|
|
expected = ("/v1/a/c/?temp_url_sig=%s&temp_url_expires=60"
|
|
"&temp_url_prefix=\n" % sig)
|
|
self.assertEqual(expected, output.out)
|
|
|
|
argv = ["", "tempurl", "GET", "60", "/v1/a/c/",
|
|
"secret_key", "--absolute", "--prefix", '--iso8601']
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
sig = '00008c4be1573ba74fc2ab9bce02e3a93d04b349'
|
|
expires = '1970-01-01T00:01:00Z'
|
|
expected = ("/v1/a/c/?temp_url_sig=%s&temp_url_expires=%s"
|
|
"&temp_url_prefix=\n" % (sig, expires))
|
|
self.assertEqual(expected, output.out)
|
|
|
|
dates = ("1970-01-01T00:01:00Z",
|
|
strftime(EXPIRES_ISO8601_FORMAT[:-1], localtime(60)))
|
|
for d in dates:
|
|
argv = ["", "tempurl", "GET", d, "/v1/a/c/o",
|
|
"secret_key"]
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
sig = "63bc77a473a1c2ce956548cacf916f292eb9eac3"
|
|
expected = "/v1/a/c/o?temp_url_sig=%s&temp_url_expires=60\n" % sig
|
|
self.assertEqual(expected, output.out)
|
|
|
|
ts = str(int(
|
|
mktime(strptime('2005-05-01', SHORT_EXPIRES_ISO8601_FORMAT))))
|
|
|
|
argv = ["", "tempurl", "GET", ts, "/v1/a/c/",
|
|
"secret_key", "--absolute"]
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
expected = output.out
|
|
|
|
argv = ["", "tempurl", "GET", '2005-05-01', "/v1/a/c/",
|
|
"secret_key", "--absolute"]
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(expected, output.out)
|
|
|
|
argv = ["", "tempurl", "GET", "60", "/v1/a/c/o",
|
|
"secret_key", "--absolute", "--ip-range", "1.2.3.4"]
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
sig = "6a6ec8efa4be53904ecba8d055d841e24a937c98"
|
|
expected = (
|
|
"/v1/a/c/o?temp_url_sig=%s&temp_url_expires=60"
|
|
"&temp_url_ip_range=1.2.3.4\n" % sig
|
|
)
|
|
self.assertEqual(expected, output.out)
|
|
|
|
def test_temp_url_error_output(self):
|
|
expected = 'path must be full path to an object e.g. /v1/a/c/o\n'
|
|
for bad_path in ('/v1/a/c', 'v1/a/c/o', '/v1/a/c/', '/v1/a//o',
|
|
'http://saio/v1/a/c', 'http://v1/a/c/o'):
|
|
argv = ["", "tempurl", "GET", "60", bad_path,
|
|
"secret_key", "--absolute"]
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(expected, output.err,
|
|
'Expected %r but got %r for path %r' %
|
|
(expected, output.err, bad_path))
|
|
|
|
expected = 'path must at least contain /v1/a/c/\n'
|
|
argv = ["", "tempurl", "GET", "60", '/v1/a/c',
|
|
"secret_key", "--absolute", '--prefix-based']
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(expected, output.err,
|
|
'Expected %r but got %r for path %r' %
|
|
(expected, output.err, '/v1/a/c'))
|
|
|
|
expected = TIME_ERRMSG + '\n'
|
|
for bad_time in ('not_an_int', '-1', '2015-05', '2015-05-01T01:00'):
|
|
argv = ["", "tempurl", "GET", bad_time, '/v1/a/c/o',
|
|
"secret_key", "--absolute"]
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(expected, output.err,
|
|
'Expected %r but got %r for time %r' %
|
|
(expected, output.err, bad_time))
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_capabilities(self, connection):
|
|
argv = ["", "capabilities"]
|
|
connection.return_value.get_capabilities.return_value = {'swift': None}
|
|
swiftclient.shell.main(argv)
|
|
connection.return_value.get_capabilities.assert_called_with(None)
|
|
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_capabilities_json(self, connection):
|
|
capabilities = {
|
|
'slo': {'min_segment_size': 1000000},
|
|
'some': [{'arbitrary': 'nested'}, {'crazy': 'structure'}],
|
|
'swift': {'version': '2.5.0'}}
|
|
|
|
connection.return_value.get_capabilities.return_value = capabilities
|
|
argv = ["", "capabilities", "--json"]
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
swiftclient.shell.main(argv)
|
|
expected = json.dumps(capabilities, sort_keys=True, indent=2) + '\n'
|
|
self.assertEqual(expected, output.out)
|
|
connection.return_value.get_capabilities.assert_called_with(None)
|
|
|
|
def test_human_readable_upload_segment_size(self):
|
|
def _check_expected(x, expected):
|
|
actual = x.call_args_list[-1][1]["options"]["segment_size"]
|
|
self.assertEqual(int(actual), expected)
|
|
|
|
mock_swift = mock.MagicMock(spec=swiftclient.shell.SwiftService)
|
|
with mock.patch("swiftclient.shell.SwiftService", mock_swift):
|
|
with CaptureOutput(suppress_systemexit=True) as output:
|
|
# Test new behaviour with both upper and lower case
|
|
# trailing characters
|
|
argv = ["", "upload", "-S", "1B", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
_check_expected(mock_swift, 1)
|
|
|
|
argv = ["", "upload", "-S", "1K", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
_check_expected(mock_swift, 1024)
|
|
|
|
argv = ["", "upload", "-S", "1m", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
_check_expected(mock_swift, 1048576)
|
|
|
|
argv = ["", "upload", "-S", "1G", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
_check_expected(mock_swift, 1073741824)
|
|
|
|
# Test old behaviour is not affected
|
|
argv = ["", "upload", "-S", "12345", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
_check_expected(mock_swift, 12345)
|
|
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
# Test invalid states
|
|
argv = ["", "upload", "-S", "1234X", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(output.err, "Invalid segment size\n")
|
|
output.clear()
|
|
|
|
with self.assertRaises(SystemExit):
|
|
argv = ["", "upload", "-S", "K1234", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(output.err, "Invalid segment size\n")
|
|
output.clear()
|
|
|
|
with self.assertRaises(SystemExit):
|
|
argv = ["", "upload", "-S", "K", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(output.err, "Invalid segment size\n")
|
|
|
|
def test_negative_upload_segment_size(self):
|
|
with CaptureOutput() as output:
|
|
with self.assertRaises(SystemExit):
|
|
argv = ["", "upload", "-S", "-40", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(output.err, "segment-size should be positive\n")
|
|
output.clear()
|
|
with self.assertRaises(SystemExit):
|
|
argv = ["", "upload", "-S=-40K", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(output.err, "segment-size should be positive\n")
|
|
output.clear()
|
|
with self.assertRaises(SystemExit):
|
|
argv = ["", "upload", "-S=-40M", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(output.err, "segment-size should be positive\n")
|
|
output.clear()
|
|
with self.assertRaises(SystemExit):
|
|
argv = ["", "upload", "-S=-40G", "container", "object"]
|
|
swiftclient.shell.main(argv)
|
|
self.assertEqual(output.err, "segment-size should be positive\n")
|
|
output.clear()
|
|
|
|
|
|
class TestSubcommandHelp(unittest.TestCase):
|
|
|
|
def test_subcommand_help(self):
|
|
for command in swiftclient.shell.commands:
|
|
help_var = 'st_%s_help' % command
|
|
options_var = 'st_%s_options' % command
|
|
self.assertTrue(hasattr(swiftclient.shell, help_var))
|
|
with CaptureOutput() as out:
|
|
argv = ['', command, '--help']
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, argv)
|
|
expected = 'Usage: swift %s %s\n%s' % (
|
|
command, vars(swiftclient.shell).get(options_var, "\n"),
|
|
vars(swiftclient.shell)[help_var])
|
|
self.assertEqual(out.strip('\n'), expected)
|
|
|
|
def test_no_help(self):
|
|
with CaptureOutput() as out:
|
|
argv = ['', 'bad_command', '--help']
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, argv)
|
|
expected = 'no such command: bad_command'
|
|
self.assertEqual(out.strip('\n'), expected)
|
|
|
|
|
|
@mock.patch.dict(os.environ, mocked_os_environ)
|
|
class TestDebugAndInfoOptions(unittest.TestCase):
|
|
@mock.patch('logging.basicConfig')
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_option_after_posarg(self, connection, mock_logging):
|
|
argv = ["", "stat", "--info"]
|
|
swiftclient.shell.main(argv)
|
|
mock_logging.assert_called_with(level=logging.INFO)
|
|
|
|
argv = ["", "stat", "--debug"]
|
|
swiftclient.shell.main(argv)
|
|
mock_logging.assert_called_with(level=logging.DEBUG)
|
|
|
|
@mock.patch('logging.basicConfig')
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_debug_trumps_info(self, connection, mock_logging):
|
|
argv_scenarios = (["", "stat", "--info", "--debug"],
|
|
["", "stat", "--debug", "--info"],
|
|
["", "--info", "stat", "--debug"],
|
|
["", "--debug", "stat", "--info"],
|
|
["", "--info", "--debug", "stat"],
|
|
["", "--debug", "--info", "stat"])
|
|
for argv in argv_scenarios:
|
|
mock_logging.reset_mock()
|
|
swiftclient.shell.main(argv)
|
|
try:
|
|
mock_logging.assert_called_once_with(level=logging.DEBUG)
|
|
except AssertionError:
|
|
self.fail('Unexpected call(s) %r for args %r'
|
|
% (mock_logging.call_args_list, argv))
|
|
|
|
|
|
class TestBase(unittest.TestCase):
|
|
"""
|
|
Provide some common methods to subclasses
|
|
"""
|
|
def _remove_swift_env_vars(self):
|
|
self._environ_vars = {}
|
|
keys = list(os.environ.keys())
|
|
for k in keys:
|
|
if (k in ('ST_KEY', 'ST_USER', 'ST_AUTH')
|
|
or k.startswith('OS_')):
|
|
self._environ_vars[k] = os.environ.pop(k)
|
|
|
|
def _replace_swift_env_vars(self):
|
|
os.environ.update(self._environ_vars)
|
|
|
|
|
|
class TestParsing(TestBase):
|
|
|
|
def setUp(self):
|
|
super(TestParsing, self).setUp()
|
|
self._remove_swift_env_vars()
|
|
|
|
def tearDown(self):
|
|
self._replace_swift_env_vars()
|
|
super(TestParsing, self).tearDown()
|
|
|
|
def _make_fake_command(self, result):
|
|
def fake_command(parser, args, thread_manager):
|
|
result[0], result[1] = swiftclient.shell.parse_args(parser, args)
|
|
return fake_command
|
|
|
|
def _verify_opts(self, actual_opts, expected_opts, expected_os_opts=None,
|
|
expected_os_opts_dict=None):
|
|
"""
|
|
Check parsed options are correct.
|
|
|
|
:param expected_opts: v1 style options.
|
|
:param expected_os_opts: openstack style options.
|
|
:param expected_os_opts_dict: openstack options that should be found in
|
|
the os_options dict.
|
|
"""
|
|
expected_os_opts = expected_os_opts or {}
|
|
expected_os_opts_dict = expected_os_opts_dict or {}
|
|
# check the expected opts are set
|
|
for key, v in expected_opts.items():
|
|
actual = actual_opts.get(key)
|
|
self.assertEqual(v, actual, 'Expected %s for key %s, found %s' %
|
|
(v, key, actual))
|
|
|
|
for key, v in expected_os_opts.items():
|
|
actual = actual_opts.get("os_" + key)
|
|
self.assertEqual(v, actual, 'Expected %s for key %s, found %s' %
|
|
(v, key, actual))
|
|
|
|
# check the os_options dict values are set
|
|
self.assertIn('os_options', actual_opts)
|
|
actual_os_opts_dict = actual_opts['os_options']
|
|
expected_os_opts_keys = ['project_name', 'region_name',
|
|
'tenant_name',
|
|
'user_domain_name', 'endpoint_type',
|
|
'object_storage_url', 'project_domain_id',
|
|
'user_id', 'user_domain_id', 'tenant_id',
|
|
'service_type', 'project_id', 'auth_token',
|
|
'project_domain_name']
|
|
for key in expected_os_opts_keys:
|
|
self.assertIn(key, actual_os_opts_dict)
|
|
cli_key = key
|
|
if key == 'object_storage_url':
|
|
# exceptions to the pattern...
|
|
cli_key = 'storage_url'
|
|
if cli_key in expected_os_opts_dict:
|
|
expect = expected_os_opts_dict[cli_key]
|
|
else:
|
|
expect = None
|
|
actual = actual_os_opts_dict[key]
|
|
self.assertEqual(expect, actual, 'Expected %s for %s, got %s'
|
|
% (expect, key, actual))
|
|
for key in actual_os_opts_dict:
|
|
self.assertIn(key, expected_os_opts_keys)
|
|
|
|
# check that equivalent keys have equal values
|
|
equivalents = [('os_username', 'user'),
|
|
('os_auth_url', 'auth'),
|
|
('os_password', 'key')]
|
|
for pair in equivalents:
|
|
self.assertEqual(actual_opts.get(pair[0]),
|
|
actual_opts.get(pair[1]))
|
|
|
|
def test_minimum_required_args_v3(self):
|
|
opts = {"auth_version": "3"}
|
|
os_opts = {"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
|
|
# username with domain is sufficient in args because keystone will
|
|
# assume user is in default domain
|
|
args = _make_args("stat", opts, os_opts, '-')
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
self._verify_opts(result[0], opts, os_opts, {})
|
|
|
|
# check its ok to have user_id instead of username
|
|
os_opts = {"password": "secret",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
os_opts_dict = {"user_id": "user_ID"}
|
|
all_os_opts = os_opts.copy()
|
|
all_os_opts.update(os_opts_dict)
|
|
|
|
args = _make_args("stat", opts, all_os_opts, '-')
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
|
|
|
|
# check no user credentials required if token and url supplied
|
|
os_opts = {}
|
|
os_opts_dict = {"storage_url": "http://example.com:8080/v1",
|
|
"auth_token": "0123abcd"}
|
|
|
|
args = _make_args("stat", opts, os_opts_dict, '-')
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
|
|
|
|
def test_sloppy_versions(self):
|
|
os_opts = {"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v3",
|
|
"identity-api-version": "3.0"}
|
|
|
|
# check os_identity_api_version=3.0 is mapped to auth_version=3
|
|
args = _make_args("stat", {}, os_opts, '-')
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch.dict(os.environ, {}):
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
expected_opts = {'auth_version': '3'} # NB: not '3.0'
|
|
expected_os_opts = {"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
self._verify_opts(result[0], expected_opts, expected_os_opts, {})
|
|
|
|
# check os_identity_api_version=2 is mapped to auth_version=2.0
|
|
# A somewhat contrived scenario - we need to pass in the v1 style opts
|
|
# to prevent auth version defaulting to 2.0 due to lack of v1 style
|
|
# options. That way we can actually verify that the sloppy 2 was
|
|
# interpreted and mapped to 2.0
|
|
os_opts = {"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v2.0",
|
|
"identity-api-version": "2"}
|
|
opts = {"key": "secret",
|
|
"user": "user",
|
|
"auth": "http://example.com:5000/v2.0"}
|
|
args = _make_args("stat", opts, os_opts, '-')
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch.dict(os.environ, {}):
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
expected_opts = {'auth_version': '2.0'} # NB: not '2'
|
|
expected_os_opts = {"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v2.0"}
|
|
self._verify_opts(result[0], expected_opts, expected_os_opts, {})
|
|
|
|
def test_os_identity_api_version(self):
|
|
os_opts = {"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v3",
|
|
"identity-api-version": "3"}
|
|
|
|
# check os_identity_api_version is sufficient in place of auth_version
|
|
args = _make_args("stat", {}, os_opts, '-')
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch.dict(os.environ, {}):
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
expected_opts = {'auth_version': '3'}
|
|
expected_os_opts = {"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
self._verify_opts(result[0], expected_opts, expected_os_opts, {})
|
|
|
|
# check again using environment variables
|
|
args = _make_args("stat", {}, {})
|
|
env = _make_env({}, os_opts)
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch.dict(os.environ, env):
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
self._verify_opts(result[0], expected_opts, expected_os_opts, {})
|
|
|
|
# check that last of auth-version, os-identity-api-version is preferred
|
|
args = _make_args("stat", {}, os_opts, '-') + ['--auth-version', '2.0']
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch.dict(os.environ, {}):
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
expected_opts = {'auth_version': '2.0'}
|
|
self._verify_opts(result[0], expected_opts, expected_os_opts, {})
|
|
|
|
# now put auth_version ahead of os-identity-api-version
|
|
args = _make_args("stat", {"auth_version": "2.0"}, os_opts, '-')
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch.dict(os.environ, {}):
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
expected_opts = {'auth_version': '3'}
|
|
self._verify_opts(result[0], expected_opts, expected_os_opts, {})
|
|
|
|
# check that OS_AUTH_VERSION overrides OS_IDENTITY_API_VERSION
|
|
args = _make_args("stat", {}, {})
|
|
env = _make_env({}, os_opts)
|
|
env.update({'OS_AUTH_VERSION': '2.0'})
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch.dict(os.environ, env):
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
expected_opts = {'auth_version': '2.0'}
|
|
self._verify_opts(result[0], expected_opts, expected_os_opts, {})
|
|
|
|
# check that ST_AUTH_VERSION overrides OS_IDENTITY_API_VERSION
|
|
args = _make_args("stat", {}, {})
|
|
env = _make_env({}, os_opts)
|
|
env.update({'ST_AUTH_VERSION': '2.0'})
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch.dict(os.environ, env):
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
self._verify_opts(result[0], expected_opts, expected_os_opts, {})
|
|
|
|
# check that ST_AUTH_VERSION overrides OS_AUTH_VERSION
|
|
args = _make_args("stat", {}, {})
|
|
env = _make_env({}, os_opts)
|
|
env.update({'ST_AUTH_VERSION': '2.0', 'OS_AUTH_VERSION': '3'})
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch.dict(os.environ, env):
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
self._verify_opts(result[0], expected_opts, expected_os_opts, {})
|
|
|
|
def test_args_v3(self):
|
|
opts = {"auth_version": "3"}
|
|
os_opts = {"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
os_opts_dict = {"user_id": "user_ID",
|
|
"project_id": "project_ID",
|
|
"tenant_id": "tenant_ID",
|
|
"project_domain_id": "project_domain_ID",
|
|
"user_domain_id": "user_domain_ID",
|
|
"tenant_name": "tenant",
|
|
"project_name": "project",
|
|
"project_domain_name": "project_domain",
|
|
"user_domain_name": "user_domain",
|
|
"auth_token": "token",
|
|
"storage_url": "http://example.com:8080/v1",
|
|
"region_name": "region",
|
|
"service_type": "service",
|
|
"endpoint_type": "endpoint"}
|
|
all_os_opts = os_opts.copy()
|
|
all_os_opts.update(os_opts_dict)
|
|
|
|
# check using hyphen separator
|
|
args = _make_args("stat", opts, all_os_opts, '-')
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
|
|
|
|
# check using underscore separator
|
|
args = _make_args("stat", opts, all_os_opts, '_')
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
|
|
|
|
# check using environment variables
|
|
args = _make_args("stat", {}, {})
|
|
env = _make_env(opts, all_os_opts)
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch.dict(os.environ, env):
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
|
|
|
|
# check again using OS_AUTH_VERSION instead of ST_AUTH_VERSION
|
|
env = _make_env({}, all_os_opts)
|
|
env.update({'OS_AUTH_VERSION': '3'})
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch.dict(os.environ, env):
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
self._verify_opts(result[0], opts, os_opts, os_opts_dict)
|
|
|
|
def test_command_args_v3(self):
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
opts = {"auth_version": "3"}
|
|
os_opts = {"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
args = _make_args("stat", opts, os_opts)
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
swiftclient.shell.main(args)
|
|
self.assertEqual(['stat'], result[1])
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
args = args + ["container_name"]
|
|
swiftclient.shell.main(args)
|
|
self.assertEqual(["stat", "container_name"], result[1])
|
|
|
|
def test_insufficient_args_v3(self):
|
|
opts = {"auth_version": "3"}
|
|
os_opts = {"password": "secret",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
args = _make_args("stat", opts, os_opts)
|
|
with self.assertRaises(SystemExit) as cm:
|
|
swiftclient.shell.main(args)
|
|
self.assertIn(
|
|
'Auth version 3 requires either OS_USERNAME or OS_USER_ID',
|
|
str(cm.exception))
|
|
|
|
os_opts = {"username": "user",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
args = _make_args("stat", opts, os_opts)
|
|
with self.assertRaises(SystemExit) as cm:
|
|
swiftclient.shell.main(args)
|
|
self.assertIn('Auth version 3 requires OS_PASSWORD', str(cm.exception))
|
|
|
|
os_opts = {"username": "user",
|
|
"password": "secret"}
|
|
args = _make_args("stat", opts, os_opts)
|
|
with self.assertRaises(SystemExit) as cm:
|
|
swiftclient.shell.main(args)
|
|
self.assertIn('Auth version 3 requires OS_AUTH_URL', str(cm.exception))
|
|
|
|
def test_password_prompt(self):
|
|
def do_test(opts, os_opts, auth_version):
|
|
args = _make_args("stat", opts, os_opts)
|
|
result = [None, None]
|
|
fake_command = self._make_fake_command(result)
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
with mock.patch('getpass.getpass',
|
|
return_value='input_pwd') as mock_getpass:
|
|
swiftclient.shell.main(args)
|
|
mock_getpass.assert_called_once_with()
|
|
self.assertEqual('input_pwd', result[0]['key'])
|
|
self.assertEqual('input_pwd', result[0]['os_password'])
|
|
|
|
# ctrl-D
|
|
with self.assertRaises(SystemExit) as cm:
|
|
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
|
with mock.patch('getpass.getpass',
|
|
side_effect=EOFError) as mock_getpass:
|
|
swiftclient.shell.main(args)
|
|
mock_getpass.assert_called_once_with()
|
|
self.assertIn(
|
|
'Auth version %s requires' % auth_version, str(cm.exception))
|
|
|
|
# force getpass to think it needs to use raw input
|
|
with self.assertRaises(SystemExit) as cm:
|
|
with mock.patch('getpass.getpass', getpass.fallback_getpass):
|
|
swiftclient.shell.main(args)
|
|
self.assertIn(
|
|
'Input stream incompatible', str(cm.exception))
|
|
|
|
opts = {"prompt": None, "user": "bob", "key": "secret",
|
|
"auth": "http://example.com:8080/auth/v1.0"}
|
|
do_test(opts, {}, '1.0')
|
|
os_opts = {"username": "user",
|
|
"password": "secret",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
opts = {"auth_version": "2.0", "prompt": None}
|
|
do_test(opts, os_opts, '2.0')
|
|
opts = {"auth_version": "3", "prompt": None}
|
|
do_test(opts, os_opts, '3')
|
|
|
|
def test_no_tenant_name_or_id_v2(self):
|
|
os_opts = {"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v3",
|
|
"tenant_name": "",
|
|
"tenant_id": ""}
|
|
|
|
with CaptureOutput() as output:
|
|
args = _make_args("stat", {}, os_opts)
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
self.assertEqual(output.err.strip(), 'No tenant specified')
|
|
|
|
with CaptureOutput() as output:
|
|
args = _make_args("stat", {}, os_opts, cmd_args=["testcontainer"])
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
self.assertEqual(output.err.strip(), 'No tenant specified')
|
|
|
|
def test_no_tenant_name_or_id_v3(self):
|
|
os_opts = {"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v3",
|
|
"tenant_name": "",
|
|
"tenant_id": ""}
|
|
|
|
with CaptureOutput() as output:
|
|
args = _make_args("stat", {"auth_version": "3"}, os_opts)
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
self.assertEqual(output.err.strip(),
|
|
'No project name or project id specified.')
|
|
|
|
with CaptureOutput() as output:
|
|
args = _make_args("stat", {"auth_version": "3"},
|
|
os_opts, cmd_args=["testcontainer"])
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
self.assertEqual(output.err.strip(),
|
|
'No project name or project id specified.')
|
|
|
|
def test_insufficient_env_vars_v3(self):
|
|
args = _make_args("stat", {}, {})
|
|
opts = {"auth_version": "3"}
|
|
os_opts = {"password": "secret",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
env = _make_env(opts, os_opts)
|
|
with mock.patch.dict(os.environ, env):
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
|
|
os_opts = {"username": "user",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
env = _make_env(opts, os_opts)
|
|
with mock.patch.dict(os.environ, env):
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
|
|
os_opts = {"username": "user",
|
|
"password": "secret"}
|
|
env = _make_env(opts, os_opts)
|
|
with mock.patch.dict(os.environ, env):
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
|
|
def test_help(self):
|
|
# --help returns condensed help message
|
|
opts = {"help": None}
|
|
os_opts = {}
|
|
args = _make_args(None, opts, os_opts)
|
|
with CaptureOutput() as out:
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
self.assertTrue(out.find('[--key <api_key>]') > 0)
|
|
self.assertEqual(-1, out.find('--os-username=<auth-user-name>'))
|
|
|
|
# --help returns condensed help message, overrides --os-help
|
|
opts = {"help": None}
|
|
os_opts = {"help": None}
|
|
args = _make_args("", opts, os_opts)
|
|
with CaptureOutput() as out:
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
self.assertTrue(out.find('[--key <api_key>]') > 0)
|
|
self.assertEqual(-1, out.find('--os-username=<auth-user-name>'))
|
|
|
|
# --os-password, --os-username and --os-auth_url should be ignored
|
|
# because --help overrides it
|
|
opts = {"help": None}
|
|
os_opts = {"help": None,
|
|
"password": "secret",
|
|
"username": "user",
|
|
"auth_url": "http://example.com:5000/v3"}
|
|
args = _make_args("", opts, os_opts)
|
|
with CaptureOutput() as out:
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
self.assertTrue(out.find('[--key <api_key>]') > 0)
|
|
self.assertEqual(-1, out.find('--os-username=<auth-user-name>'))
|
|
|
|
# --os-help return os options help
|
|
opts = {}
|
|
args = _make_args("", opts, os_opts)
|
|
with CaptureOutput() as out:
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
self.assertTrue(out.find('[--key <api_key>]') > 0)
|
|
self.assertTrue(out.find('--os-username=<auth-user-name>') > 0)
|
|
|
|
|
|
class TestKeystoneOptions(MockHttpTest):
|
|
"""
|
|
Tests to check that options are passed from the command line or
|
|
environment variables through to the keystone client interface.
|
|
"""
|
|
all_os_opts = {'password': 'secret',
|
|
'username': 'user',
|
|
'auth-url': 'http://example.com:5000/v3',
|
|
'user-domain-name': 'userdomain',
|
|
'user-id': 'userid',
|
|
'user-domain-id': 'userdomainid',
|
|
'tenant-name': 'tenantname',
|
|
'tenant-id': 'tenantid',
|
|
'project-name': 'projectname',
|
|
'project-id': 'projectid',
|
|
'project-domain-id': 'projectdomainid',
|
|
'project-domain-name': 'projectdomain',
|
|
'cacert': 'foo',
|
|
'cert': 'minnie',
|
|
'key': 'mickey'}
|
|
catalog_opts = {'service-type': 'my-object-store',
|
|
'endpoint-type': 'public',
|
|
'region-name': 'my-region'}
|
|
flags = ['insecure', 'debug']
|
|
|
|
# options that are given default values in code if missing from CLI
|
|
defaults = {'auth-version': '2.0',
|
|
'service-type': 'object-store',
|
|
'endpoint-type': 'publicURL'}
|
|
|
|
def _build_os_opts(self, keys):
|
|
os_opts = {}
|
|
for k in keys:
|
|
os_opts[k] = self.all_os_opts.get(k, self.catalog_opts.get(k))
|
|
return os_opts
|
|
|
|
def _test_options_passed_to_keystone(self, cmd, opts, os_opts,
|
|
flags=None, use_env=False,
|
|
cmd_args=None, no_auth=False):
|
|
flags = flags or []
|
|
if use_env:
|
|
# set up fake environment variables and make a minimal command line
|
|
env = _make_env(opts, os_opts)
|
|
args = _make_args(cmd, {}, {}, separator='-', flags=flags,
|
|
cmd_args=cmd_args)
|
|
else:
|
|
# set up empty environment and make full command line
|
|
env = {}
|
|
args = _make_args(cmd, opts, os_opts, separator='-', flags=flags,
|
|
cmd_args=cmd_args)
|
|
ks_endpoint = 'http://example.com:8080/v1/AUTH_acc'
|
|
ks_token = 'fake_auth_token'
|
|
# check correct auth version gets used
|
|
key = 'auth-version'
|
|
fake_ks = FakeKeystone(endpoint=ks_endpoint, token=ks_token)
|
|
if no_auth:
|
|
fake_ks2 = fake_ks3 = None
|
|
elif opts.get(key, self.defaults.get(key)) == '2.0':
|
|
fake_ks2 = fake_ks
|
|
fake_ks3 = None
|
|
else:
|
|
fake_ks2 = None
|
|
fake_ks3 = fake_ks
|
|
# fake_conn will check that storage_url and auth_token are as expected
|
|
endpoint = os_opts.get('storage-url', ks_endpoint)
|
|
token = os_opts.get('auth-token', ks_token)
|
|
fake_conn = self.fake_http_connection(204, headers={},
|
|
storage_url=endpoint,
|
|
auth_token=token)
|
|
|
|
with mock.patch('swiftclient.client.ksclient_v2', fake_ks2), \
|
|
mock.patch('swiftclient.client.ksclient_v3', fake_ks3), \
|
|
mock.patch('swiftclient.client.http_connection', fake_conn), \
|
|
mock.patch.dict(os.environ, env, clear=True), \
|
|
patch_disable_warnings() as mock_disable_warnings:
|
|
try:
|
|
swiftclient.shell.main(args)
|
|
except SystemExit as e:
|
|
self.fail('Unexpected SystemExit: %s' % e)
|
|
except SwiftError as err:
|
|
self.fail('Unexpected SwiftError: %s' % err)
|
|
|
|
if InsecureRequestWarning is not None:
|
|
if 'insecure' in flags:
|
|
self.assertEqual([mock.call(InsecureRequestWarning)],
|
|
mock_disable_warnings.mock_calls)
|
|
else:
|
|
self.assertEqual([], mock_disable_warnings.mock_calls)
|
|
|
|
if no_auth:
|
|
# We patched out both keystoneclient versions to be None;
|
|
# they *can't* have been used and if we tried to, we would
|
|
# have raised ClientExceptions
|
|
return
|
|
|
|
# check args passed to keystone Client __init__
|
|
self.assertEqual(len(fake_ks.calls), 1)
|
|
actual_args = fake_ks.calls[0]
|
|
for key in self.all_os_opts.keys():
|
|
expected = os_opts.get(key, self.defaults.get(key))
|
|
key = key.replace('-', '_')
|
|
self.assertIn(key, actual_args)
|
|
self.assertEqual(expected, actual_args[key],
|
|
'Expected %s for key %s, found %s'
|
|
% (expected, key, actual_args[key]))
|
|
for flag in flags:
|
|
self.assertIn(flag, actual_args)
|
|
self.assertTrue(actual_args[flag])
|
|
|
|
check_attr = True
|
|
# check args passed to ServiceCatalog.url_for() method
|
|
self.assertEqual(len(fake_ks.client.service_catalog.calls), 1)
|
|
actual_args = fake_ks.client.service_catalog.calls[0]
|
|
for key in self.catalog_opts.keys():
|
|
expected = os_opts.get(key, self.defaults.get(key))
|
|
key = key.replace('-', '_')
|
|
if key == 'region_name':
|
|
key = 'filter_value'
|
|
if expected is None:
|
|
check_attr = False
|
|
self.assertNotIn(key, actual_args)
|
|
self.assertNotIn('attr', actual_args)
|
|
continue
|
|
self.assertIn(key, actual_args)
|
|
self.assertEqual(expected, actual_args[key],
|
|
'Expected %s for key %s, found %s'
|
|
% (expected, key, actual_args[key]))
|
|
if check_attr:
|
|
key, v = 'attr', 'region'
|
|
self.assertIn(key, actual_args)
|
|
self.assertEqual(v, actual_args[key],
|
|
'Expected %s for key %s, found %s'
|
|
% (v, key, actual_args[key]))
|
|
|
|
def _test_options(self, opts, os_opts, flags=None, no_auth=False):
|
|
# repeat test for different commands using env and command line options
|
|
for cmd in ('stat', 'post'):
|
|
self._test_options_passed_to_keystone(cmd, opts, os_opts,
|
|
flags=flags, no_auth=no_auth)
|
|
self._test_options_passed_to_keystone(cmd, opts, os_opts,
|
|
flags=flags, use_env=True,
|
|
no_auth=no_auth)
|
|
|
|
def test_all_args_passed_to_keystone(self):
|
|
# check that all possible command line args are passed to keystone
|
|
opts = {'auth-version': '3'}
|
|
os_opts = dict(self.all_os_opts)
|
|
os_opts.update(self.catalog_opts)
|
|
self._test_options(opts, os_opts, flags=self.flags)
|
|
|
|
opts = {'auth-version': '2.0'}
|
|
self._test_options(opts, os_opts, flags=self.flags)
|
|
|
|
opts = {}
|
|
self.defaults['auth-version'] = '3'
|
|
self._test_options(opts, os_opts, flags=self.flags)
|
|
|
|
for o in ('user-domain-name', 'user-domain-id',
|
|
'project-domain-name', 'project-domain-id'):
|
|
os_opts.pop(o)
|
|
self.defaults['auth-version'] = '2.0'
|
|
self._test_options(opts, os_opts, flags=self.flags)
|
|
|
|
def test_catalog_options_and_flags_not_required_v3(self):
|
|
# check that all possible command line args are passed to keystone
|
|
opts = {'auth-version': '3'}
|
|
os_opts = dict(self.all_os_opts)
|
|
self._test_options(opts, os_opts, flags=None)
|
|
|
|
def test_ok_option_combinations_v3(self):
|
|
opts = {'auth-version': '3'}
|
|
keys = ('username', 'password', 'tenant-name', 'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
keys = ('user-id', 'password', 'tenant-name', 'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
keys = ('user-id', 'password', 'tenant-id', 'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
keys = ('user-id', 'password', 'project-name', 'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
keys = ('user-id', 'password', 'project-id', 'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
def test_ok_option_combinations_v2(self):
|
|
opts = {'auth-version': '2.0'}
|
|
keys = ('username', 'password', 'tenant-name', 'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
keys = ('username', 'password', 'tenant-id', 'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
# allow auth_version to default to 2.0
|
|
opts = {}
|
|
keys = ('username', 'password', 'tenant-name', 'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
keys = ('username', 'password', 'tenant-id', 'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
# ...except when it should be 3
|
|
self.defaults['auth-version'] = '3'
|
|
keys = ('username', 'user-domain-name', 'password', 'project-name',
|
|
'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
keys = ('username', 'user-domain-id', 'password', 'project-name',
|
|
'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
keys = ('username', 'project-domain-name', 'password', 'project-name',
|
|
'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
keys = ('username', 'project-domain-id', 'password', 'project-name',
|
|
'auth-url')
|
|
os_opts = self._build_os_opts(keys)
|
|
self._test_options(opts, os_opts)
|
|
|
|
def test_url_and_token_provided_on_command_line(self):
|
|
endpoint = 'http://alternate.com:8080/v1/AUTH_another'
|
|
token = 'alternate_auth_token'
|
|
os_opts = {'auth-token': token,
|
|
'storage-url': endpoint}
|
|
opts = {'auth-version': '3'}
|
|
self._test_options(opts, os_opts, no_auth=True)
|
|
|
|
opts = {'auth-version': '2.0'}
|
|
self._test_options(opts, os_opts, no_auth=True)
|
|
|
|
def test_url_provided_on_command_line(self):
|
|
endpoint = 'http://alternate.com:8080/v1/AUTH_another'
|
|
os_opts = {'username': 'username',
|
|
'password': 'password',
|
|
'project-name': 'projectname',
|
|
'auth-url': 'http://example.com:5000/v3',
|
|
'storage-url': endpoint}
|
|
opts = {'auth-version': '3'}
|
|
self._test_options(opts, os_opts)
|
|
|
|
opts = {'auth-version': '2.0'}
|
|
self._test_options(opts, os_opts)
|
|
|
|
|
|
@mock.patch.dict(os.environ, clean_os_environ)
|
|
class TestAuth(MockHttpTest):
|
|
|
|
def test_pre_authed_request(self):
|
|
url = 'https://swift.storage.example.com/v1/AUTH_test'
|
|
token = 'AUTH_tk5b6b12'
|
|
|
|
pre_auth_env = {
|
|
'OS_STORAGE_URL': url,
|
|
'OS_AUTH_TOKEN': token,
|
|
}
|
|
fake_conn = self.fake_http_connection(200)
|
|
with mock.patch('swiftclient.client.http_connection', new=fake_conn):
|
|
with mock.patch.dict(os.environ, pre_auth_env):
|
|
argv = ['', 'stat']
|
|
swiftclient.shell.main(argv)
|
|
self.assertRequests([
|
|
('HEAD', url, '', {'x-auth-token': token}),
|
|
])
|
|
|
|
# and again with re-auth
|
|
pre_auth_env.update(mocked_os_environ)
|
|
pre_auth_env['OS_AUTH_TOKEN'] = 'expired'
|
|
fake_conn = self.fake_http_connection(401, 200, 200, headers={
|
|
'x-auth-token': token + '_new',
|
|
'x-storage-url': url + '_not_used',
|
|
})
|
|
with mock.patch.multiple('swiftclient.client',
|
|
http_connection=fake_conn,
|
|
sleep=mock.DEFAULT):
|
|
with mock.patch.dict(os.environ, pre_auth_env):
|
|
argv = ['', 'stat']
|
|
swiftclient.shell.main(argv)
|
|
self.assertRequests([
|
|
('HEAD', url, '', {
|
|
'x-auth-token': 'expired',
|
|
}),
|
|
('GET', mocked_os_environ['ST_AUTH'], '', {
|
|
'x-auth-user': mocked_os_environ['ST_USER'],
|
|
'x-auth-key': mocked_os_environ['ST_KEY'],
|
|
}),
|
|
('HEAD', url, '', {
|
|
'x-auth-token': token + '_new',
|
|
}),
|
|
])
|
|
|
|
def test_os_pre_authed_request(self):
|
|
url = 'https://swift.storage.example.com/v1/AUTH_test'
|
|
token = 'AUTH_tk5b6b12'
|
|
|
|
pre_auth_env = {
|
|
'OS_STORAGE_URL': url,
|
|
'OS_AUTH_TOKEN': token,
|
|
}
|
|
fake_conn = self.fake_http_connection(200)
|
|
with mock.patch('swiftclient.client.http_connection', new=fake_conn):
|
|
with mock.patch.dict(os.environ, pre_auth_env):
|
|
argv = ['', 'stat']
|
|
swiftclient.shell.main(argv)
|
|
self.assertRequests([
|
|
('HEAD', url, '', {'x-auth-token': token}),
|
|
])
|
|
|
|
# and again with re-auth
|
|
os_environ = {
|
|
'OS_AUTH_URL': 'https://keystone.example.com/v2.0/',
|
|
'OS_TENANT_NAME': 'demo',
|
|
'OS_USERNAME': 'demo',
|
|
'OS_PASSWORD': 'admin',
|
|
}
|
|
os_environ.update(pre_auth_env)
|
|
os_environ['OS_AUTH_TOKEN'] = 'expired'
|
|
|
|
fake_conn = self.fake_http_connection(401, 200)
|
|
fake_keystone = fake_get_auth_keystone(storage_url=url + '_not_used',
|
|
token=token + '_new')
|
|
with mock.patch.multiple('swiftclient.client',
|
|
http_connection=fake_conn,
|
|
get_auth_keystone=fake_keystone,
|
|
sleep=mock.DEFAULT):
|
|
with mock.patch.dict(os.environ, os_environ):
|
|
argv = ['', 'stat']
|
|
swiftclient.shell.main(argv)
|
|
self.assertRequests([
|
|
('HEAD', url, '', {
|
|
'x-auth-token': 'expired',
|
|
}),
|
|
('HEAD', url, '', {
|
|
'x-auth-token': token + '_new',
|
|
}),
|
|
])
|
|
|
|
def test_auth(self):
|
|
headers = {
|
|
'x-auth-token': 'AUTH_tk5b6b12',
|
|
'x-storage-url': 'https://swift.storage.example.com/v1/AUTH_test',
|
|
}
|
|
mock_resp = self.fake_http_connection(200, headers=headers)
|
|
with mock.patch('swiftclient.client.http_connection', new=mock_resp):
|
|
stdout = six.StringIO()
|
|
with mock.patch('sys.stdout', new=stdout):
|
|
argv = [
|
|
'',
|
|
'auth',
|
|
'--auth', 'https://swift.storage.example.com/auth/v1.0',
|
|
'--user', 'test:tester', '--key', 'testing',
|
|
]
|
|
swiftclient.shell.main(argv)
|
|
|
|
expected = """
|
|
export OS_STORAGE_URL=https://swift.storage.example.com/v1/AUTH_test
|
|
export OS_AUTH_TOKEN=AUTH_tk5b6b12
|
|
"""
|
|
self.assertEqual(textwrap.dedent(expected).lstrip(),
|
|
stdout.getvalue())
|
|
|
|
def test_auth_verbose(self):
|
|
with mock.patch('swiftclient.client.http_connection') as mock_conn:
|
|
stdout = six.StringIO()
|
|
with mock.patch('sys.stdout', new=stdout):
|
|
argv = [
|
|
'',
|
|
'auth',
|
|
'--auth', 'https://swift.storage.example.com/auth/v1.0',
|
|
'--user', 'test:tester', '--key', 'te$tin&',
|
|
'--verbose',
|
|
]
|
|
swiftclient.shell.main(argv)
|
|
|
|
expected = """
|
|
export ST_AUTH=https://swift.storage.example.com/auth/v1.0
|
|
export ST_USER=test:tester
|
|
export ST_KEY='te$tin&'
|
|
"""
|
|
self.assertEqual(textwrap.dedent(expected).lstrip(),
|
|
stdout.getvalue())
|
|
self.assertEqual([], mock_conn.mock_calls)
|
|
|
|
def test_auth_v2(self):
|
|
os_options = {'tenant_name': 'demo'}
|
|
with mock.patch('swiftclient.client.get_auth_keystone',
|
|
new=fake_get_auth_keystone(os_options)):
|
|
stdout = six.StringIO()
|
|
with mock.patch('sys.stdout', new=stdout):
|
|
argv = [
|
|
'',
|
|
'auth', '-V2',
|
|
'--auth', 'https://keystone.example.com/v2.0/',
|
|
'--os-tenant-name', 'demo',
|
|
'--os-username', 'demo', '--os-password', 'admin',
|
|
]
|
|
swiftclient.shell.main(argv)
|
|
|
|
expected = """
|
|
export OS_STORAGE_URL=http://url/
|
|
export OS_AUTH_TOKEN=token
|
|
"""
|
|
self.assertEqual(textwrap.dedent(expected).lstrip(),
|
|
stdout.getvalue())
|
|
|
|
def test_auth_verbose_v2(self):
|
|
with mock.patch('swiftclient.client.get_auth_keystone') \
|
|
as mock_keystone:
|
|
stdout = six.StringIO()
|
|
with mock.patch('sys.stdout', new=stdout):
|
|
argv = [
|
|
'',
|
|
'auth', '-V2',
|
|
'--auth', 'https://keystone.example.com/v2.0/',
|
|
'--os-tenant-name', 'demo',
|
|
'--os-username', 'demo', '--os-password', '$eKr3t',
|
|
'--verbose',
|
|
]
|
|
swiftclient.shell.main(argv)
|
|
|
|
expected = """
|
|
export OS_IDENTITY_API_VERSION=2.0
|
|
export OS_AUTH_VERSION=2.0
|
|
export OS_AUTH_URL=https://keystone.example.com/v2.0/
|
|
export OS_PASSWORD='$eKr3t'
|
|
export OS_TENANT_NAME=demo
|
|
export OS_USERNAME=demo
|
|
"""
|
|
self.assertEqual(textwrap.dedent(expected).lstrip(),
|
|
stdout.getvalue())
|
|
self.assertEqual([], mock_keystone.mock_calls)
|
|
|
|
|
|
class TestCrossAccountObjectAccess(TestBase, MockHttpTest):
|
|
"""
|
|
Tests to verify use of --os-storage-url will actually
|
|
result in the object request being sent despite account
|
|
read/write access and container write access being denied.
|
|
"""
|
|
def setUp(self):
|
|
super(TestCrossAccountObjectAccess, self).setUp()
|
|
self._remove_swift_env_vars()
|
|
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
|
temp_file.file.write(b'01234567890123456789')
|
|
temp_file.file.flush()
|
|
self.obj = temp_file.name
|
|
self.url = 'http://alternate.com:8080/v1'
|
|
|
|
# account tests will attempt to access
|
|
self.account = 'AUTH_alice'
|
|
|
|
# keystone returns endpoint for another account
|
|
self.fake_ks = FakeKeystone(
|
|
endpoint='http://example.com:8080/v1/AUTH_bob',
|
|
token='bob_token')
|
|
|
|
self.cont = 'c1'
|
|
self.cont_path = '/v1/%s/%s' % (self.account, self.cont)
|
|
self.obj_path = '%s%s' % (self.cont_path, self.obj)
|
|
|
|
self.os_opts = {'username': 'bob',
|
|
'password': 'password',
|
|
'project-name': 'proj_bob',
|
|
'auth-url': 'http://example.com:5000/v3',
|
|
'storage-url': '%s/%s' % (self.url, self.account)}
|
|
self.opts = {'auth-version': '3'}
|
|
|
|
def tearDown(self):
|
|
try:
|
|
os.remove(self.obj)
|
|
except OSError:
|
|
pass
|
|
self._replace_swift_env_vars()
|
|
super(TestCrossAccountObjectAccess, self).tearDown()
|
|
|
|
def _make_cmd(self, cmd, cmd_args=None):
|
|
return _make_cmd(cmd, self.opts, self.os_opts, cmd_args=cmd_args)
|
|
|
|
def _fake_cross_account_auth(self, read_ok, write_ok):
|
|
def on_request(method, path, *args, **kwargs):
|
|
"""
|
|
Modify response code to 200 if cross account permissions match.
|
|
"""
|
|
status = 403
|
|
if (path.startswith('/v1/%s/%s' % (self.account, self.cont))
|
|
and read_ok and method in ('GET', 'HEAD')):
|
|
status = 200
|
|
elif (path.startswith('/v1/%s/%s%s'
|
|
% (self.account, self.cont, self.obj))
|
|
and write_ok and method in ('PUT', 'POST', 'DELETE')):
|
|
status = 200
|
|
return status
|
|
return on_request
|
|
|
|
@mock.patch.object(swiftclient.service.SwiftService,
|
|
'_bulk_delete_page_size', lambda *a: 1)
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_upload_bad_threads(self, mock_connection):
|
|
mock_connection.return_value.put_object.return_value = EMPTY_ETAG
|
|
mock_connection.return_value.attempts = 0
|
|
|
|
def check_bad(argv):
|
|
args, env = self._make_cmd(
|
|
'upload', cmd_args=[self.cont, self.obj] + argv)
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as output:
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
self.assertIn(
|
|
'ERROR: option %s should be a positive integer.' % argv[0],
|
|
output.err)
|
|
|
|
def check_good(argv):
|
|
args, env = self._make_cmd(
|
|
'upload',
|
|
cmd_args=[self.cont, self.obj, '--leave-segments'] + argv)
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(args)
|
|
self.assertEqual('', output.err)
|
|
check_bad(["--object-threads", "-1"])
|
|
check_bad(["--object-threads", "0"])
|
|
check_bad(["--segment-threads", "-1"])
|
|
check_bad(["--segment-threads", "0"])
|
|
check_good(["--object-threads", "1"])
|
|
check_good(["--segment-threads", "1"])
|
|
|
|
def test_upload_with_read_write_access(self):
|
|
req_handler = self._fake_cross_account_auth(True, True)
|
|
fake_conn = self.fake_http_connection(403, 403,
|
|
on_request=req_handler)
|
|
|
|
args, env = self._make_cmd('upload', cmd_args=[self.cont, self.obj,
|
|
'--leave-segments'])
|
|
with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
|
|
with mock.patch('swiftclient.client.http_connection', fake_conn):
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as out:
|
|
try:
|
|
swiftclient.shell.main(args)
|
|
except SystemExit as e:
|
|
self.fail('Unexpected SystemExit: %s' % e)
|
|
|
|
self.assertRequests([('PUT', self.cont_path),
|
|
('PUT', self.obj_path)])
|
|
self.assertEqual(self.obj[1:], out.strip())
|
|
expected_err = "Warning: failed to create container '%s': 403 Fake" \
|
|
% self.cont
|
|
self.assertEqual(expected_err, out.err.strip())
|
|
|
|
def test_upload_with_write_only_access(self):
|
|
req_handler = self._fake_cross_account_auth(False, True)
|
|
fake_conn = self.fake_http_connection(403, 403,
|
|
on_request=req_handler)
|
|
args, env = self._make_cmd('upload', cmd_args=[self.cont, self.obj,
|
|
'--leave-segments'])
|
|
with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
|
|
with mock.patch('swiftclient.client.http_connection', fake_conn):
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as out:
|
|
try:
|
|
swiftclient.shell.main(args)
|
|
except SystemExit as e:
|
|
self.fail('Unexpected SystemExit: %s' % e)
|
|
self.assertRequests([('PUT', self.cont_path),
|
|
('PUT', self.obj_path)])
|
|
self.assertEqual(self.obj[1:], out.strip())
|
|
expected_err = "Warning: failed to create container '%s': 403 Fake" \
|
|
% self.cont
|
|
self.assertEqual(expected_err, out.err.strip())
|
|
|
|
def test_segment_upload_with_write_only_access(self):
|
|
req_handler = self._fake_cross_account_auth(False, True)
|
|
fake_conn = self.fake_http_connection(403, 403, 403, 403,
|
|
on_request=req_handler)
|
|
|
|
args, env = self._make_cmd('upload',
|
|
cmd_args=[self.cont, self.obj,
|
|
'--leave-segments',
|
|
'--segment-size=10',
|
|
'--segment-container=%s'
|
|
% self.cont])
|
|
with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
|
|
with mock.patch('swiftclient.client.http_connection', fake_conn):
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as out:
|
|
try:
|
|
swiftclient.shell.main(args)
|
|
except SystemExit as e:
|
|
self.fail('Unexpected SystemExit: %s' % e)
|
|
|
|
segment_time = getmtime(self.obj)
|
|
segment_path_0 = '%s/%f/20/10/00000000' % (self.obj_path, segment_time)
|
|
segment_path_1 = '%s/%f/20/10/00000001' % (self.obj_path, segment_time)
|
|
# Note that the order of segment PUTs cannot be asserted, so test for
|
|
# existence in request log individually
|
|
self.assert_request(('PUT', self.cont_path))
|
|
self.assert_request(('PUT', segment_path_0))
|
|
self.assert_request(('PUT', segment_path_1))
|
|
self.assert_request(('PUT', self.obj_path))
|
|
self.assertIn(self.obj[1:], out.out)
|
|
expected_err = "Warning: failed to create container '%s': 403 Fake" \
|
|
% self.cont
|
|
self.assertEqual(expected_err, out.err.strip())
|
|
|
|
def test_segment_upload_with_write_only_access_segments_container(self):
|
|
fake_conn = self.fake_http_connection(
|
|
403, # PUT c1
|
|
# HEAD c1 to get storage policy
|
|
StubResponse(200, headers={'X-Storage-Policy': 'foo'}),
|
|
403, # PUT c1_segments
|
|
201, # PUT c1_segments/...00
|
|
201, # PUT c1_segments/...01
|
|
201, # PUT c1/...
|
|
)
|
|
|
|
args, env = self._make_cmd('upload',
|
|
cmd_args=[self.cont, self.obj,
|
|
'--leave-segments',
|
|
'--segment-size=10'])
|
|
with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
|
|
with mock.patch('swiftclient.client.http_connection', fake_conn):
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as out:
|
|
try:
|
|
swiftclient.shell.main(args)
|
|
except SystemExit as e:
|
|
self.fail('Unexpected SystemExit: %s' % e)
|
|
|
|
segment_time = getmtime(self.obj)
|
|
segment_path_0 = '%s_segments%s/%f/20/10/00000000' % (
|
|
self.cont_path, self.obj, segment_time)
|
|
segment_path_1 = '%s_segments%s/%f/20/10/00000001' % (
|
|
self.cont_path, self.obj, segment_time)
|
|
# Note that the order of segment PUTs cannot be asserted, so test for
|
|
# existence in request log individually
|
|
self.assert_request(('PUT', self.cont_path))
|
|
self.assert_request(('PUT', self.cont_path + '_segments', '', {
|
|
'X-Auth-Token': 'bob_token',
|
|
'X-Storage-Policy': 'foo',
|
|
'Content-Length': '0',
|
|
}))
|
|
self.assert_request(('PUT', segment_path_0))
|
|
self.assert_request(('PUT', segment_path_1))
|
|
self.assert_request(('PUT', self.obj_path))
|
|
self.assertIn(self.obj[1:], out.out)
|
|
expected_err = ("Warning: failed to create container '%s': 403 Fake\n"
|
|
"Warning: failed to create container '%s': 403 Fake"
|
|
) % (self.cont, self.cont + '_segments')
|
|
self.assertEqual(expected_err, out.err.strip())
|
|
|
|
def test_upload_with_no_access(self):
|
|
fake_conn = self.fake_http_connection(403, 403)
|
|
|
|
args, env = self._make_cmd('upload', cmd_args=[self.cont, self.obj,
|
|
'--leave-segments'])
|
|
with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
|
|
with mock.patch('swiftclient.client.http_connection', fake_conn):
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as out:
|
|
try:
|
|
swiftclient.shell.main(args)
|
|
self.fail('Expected SystemExit')
|
|
except SystemExit:
|
|
pass
|
|
|
|
self.assertRequests([('PUT', self.cont_path),
|
|
('PUT', self.obj_path)])
|
|
expected_err = 'Object PUT failed: http://1.2.3.4%s 403 Fake' \
|
|
% self.obj_path
|
|
self.assertIn(expected_err, out.err)
|
|
self.assertEqual('', out)
|
|
|
|
@mock.patch.object(swiftclient.service.SwiftService,
|
|
'_bulk_delete_page_size', lambda *a: 1)
|
|
@mock.patch('swiftclient.service.Connection')
|
|
def test_download_bad_threads(self, mock_connection):
|
|
mock_connection.return_value.get_object.return_value = [{}, '']
|
|
mock_connection.return_value.attempts = 0
|
|
|
|
def check_bad(argv):
|
|
args, env = self._make_cmd(
|
|
'download', cmd_args=[self.cont, self.obj] + argv)
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as output:
|
|
self.assertRaises(SystemExit, swiftclient.shell.main, args)
|
|
self.assertIn(
|
|
'ERROR: option %s should be a positive integer.' % argv[0],
|
|
output.err)
|
|
|
|
def check_good(argv):
|
|
args, env = self._make_cmd(
|
|
'download',
|
|
cmd_args=[self.cont, self.obj, '--no-download'] + argv)
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as output:
|
|
swiftclient.shell.main(args)
|
|
self.assertEqual('', output.err)
|
|
check_bad(["--object-threads", "-1"])
|
|
check_bad(["--object-threads", "0"])
|
|
check_bad(["--container-threads", "-1"])
|
|
check_bad(["--container-threads", "0"])
|
|
check_good(["--object-threads", "1"])
|
|
check_good(["--container-threads", "1"])
|
|
|
|
def test_download_with_read_write_access(self):
|
|
req_handler = self._fake_cross_account_auth(True, True)
|
|
fake_conn = self.fake_http_connection(403, on_request=req_handler,
|
|
etags=[EMPTY_ETAG])
|
|
|
|
args, env = self._make_cmd('download', cmd_args=[self.cont,
|
|
self.obj.lstrip('/'),
|
|
'--no-download'])
|
|
with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
|
|
with mock.patch('swiftclient.client.http_connection', fake_conn):
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as out:
|
|
try:
|
|
swiftclient.shell.main(args)
|
|
except SystemExit as e:
|
|
self.fail('Unexpected SystemExit: %s' % e)
|
|
|
|
self.assertRequests([('GET', self.obj_path)])
|
|
self.assertTrue(out.out.startswith(self.obj.lstrip('/')))
|
|
self.assertEqual('', out.err)
|
|
|
|
def test_download_with_read_only_access(self):
|
|
req_handler = self._fake_cross_account_auth(True, False)
|
|
fake_conn = self.fake_http_connection(403, on_request=req_handler,
|
|
etags=[EMPTY_ETAG])
|
|
|
|
args, env = self._make_cmd('download', cmd_args=[self.cont,
|
|
self.obj.lstrip('/'),
|
|
'--no-download'])
|
|
with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
|
|
with mock.patch('swiftclient.client.http_connection', fake_conn):
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as out:
|
|
try:
|
|
swiftclient.shell.main(args)
|
|
except SystemExit as e:
|
|
self.fail('Unexpected SystemExit: %s' % e)
|
|
|
|
self.assertRequests([('GET', self.obj_path)])
|
|
self.assertTrue(out.out.startswith(self.obj.lstrip('/')))
|
|
self.assertEqual('', out.err)
|
|
|
|
def test_download_with_no_access(self):
|
|
fake_conn = self.fake_http_connection(403)
|
|
args, env = self._make_cmd('download', cmd_args=[self.cont,
|
|
self.obj.lstrip('/'),
|
|
'--no-download'])
|
|
with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
|
|
with mock.patch('swiftclient.client.http_connection', fake_conn):
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as out:
|
|
try:
|
|
swiftclient.shell.main(args)
|
|
self.fail('Expected SystemExit')
|
|
except SystemExit:
|
|
pass
|
|
|
|
self.assertRequests([('GET', self.obj_path)])
|
|
path = '%s%s' % (self.cont, self.obj)
|
|
expected_err = "Error downloading object '%s'" % path
|
|
self.assertTrue(out.err.startswith(expected_err))
|
|
self.assertEqual('', out)
|
|
|
|
def test_list_with_read_access(self):
|
|
req_handler = self._fake_cross_account_auth(True, False)
|
|
resp_body = b'{}'
|
|
resp = StubResponse(403, resp_body, {
|
|
'etag': hashlib.md5(resp_body).hexdigest()})
|
|
fake_conn = self.fake_http_connection(resp, on_request=req_handler)
|
|
|
|
args, env = self._make_cmd('download', cmd_args=[self.cont])
|
|
with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
|
|
with mock.patch('swiftclient.client.http_connection', fake_conn):
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as out:
|
|
try:
|
|
swiftclient.shell.main(args)
|
|
except SystemExit as e:
|
|
self.fail('Unexpected SystemExit: %s' % e)
|
|
|
|
self.assertRequests([('GET', '%s?format=json' % self.cont_path)])
|
|
self.assertEqual('', out)
|
|
self.assertEqual('', out.err)
|
|
|
|
def test_list_with_no_access(self):
|
|
fake_conn = self.fake_http_connection(403)
|
|
|
|
args, env = self._make_cmd('download', cmd_args=[self.cont])
|
|
with mock.patch('swiftclient.client.ksclient_v3', self.fake_ks):
|
|
with mock.patch('swiftclient.client.http_connection', fake_conn):
|
|
with mock.patch.dict(os.environ, env):
|
|
with CaptureOutput() as out:
|
|
try:
|
|
swiftclient.shell.main(args)
|
|
self.fail('Expected SystemExit')
|
|
except SystemExit:
|
|
pass
|
|
|
|
self.assertRequests([('GET', '%s?format=json' % self.cont_path)])
|
|
self.assertEqual('', out)
|
|
self.assertTrue(out.err.startswith('Container GET failed:'))
|
|
|
|
|
|
class TestCrossAccountObjectAccessUsingEnv(TestCrossAccountObjectAccess):
|
|
"""
|
|
Repeat super-class tests using environment variables rather than command
|
|
line to set options.
|
|
"""
|
|
|
|
def _make_cmd(self, cmd, cmd_args=None):
|
|
return _make_cmd(cmd, self.opts, self.os_opts, cmd_args=cmd_args,
|
|
use_env=True)
|