8a8e5bf56c
There are several places in the source code where HTTP response codes are used as numeric values. These values are used from six.moves and the numeric values are replaced by constants. All of the used status codes were replaced with symbolic constants from six.moves.http_client. More about six.moves.http_client can be found at [2], under the table "Supported renames:". Also, this change improves code readibility. This patchset does not extract numeric values from the code itself, but it can be found at [1]. [1]: Ib9e26dcea927e96e65c626c18421621d3a29a64d [2]: https://pythonhosted.org/six/#module-six.moves Change-Id: Idfc7b043552f428f01ac3e47b270ee0639a8f5bc Closes-Bug: #1520159
615 lines
23 KiB
Python
615 lines
23 KiB
Python
# Copyright 2012 Michael Still and Canonical Inc
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import copy
|
|
import os
|
|
import sys
|
|
import uuid
|
|
|
|
import fixtures
|
|
import mock
|
|
from oslo_serialization import jsonutils
|
|
import six
|
|
from six import moves
|
|
from six.moves import http_client as http
|
|
import webob
|
|
|
|
from glance.cmd import replicator as glance_replicator
|
|
from glance.common import exception
|
|
from glance.tests.unit import utils as unit_test_utils
|
|
from glance.tests import utils as test_utils
|
|
|
|
|
|
IMG_RESPONSE_ACTIVE = {
|
|
'content-length': '0',
|
|
'property-image_state': 'available',
|
|
'min_ram': '0',
|
|
'disk_format': 'aki',
|
|
'updated_at': '2012-06-25T02:10:36',
|
|
'date': 'Thu, 28 Jun 2012 07:20:05 GMT',
|
|
'owner': '8aef75b5c0074a59aa99188fdb4b9e90',
|
|
'id': '6d55dd55-053a-4765-b7bc-b30df0ea3861',
|
|
'size': '4660272',
|
|
'property-image_location': 'ubuntu-bucket/oneiric-server-cloudimg-amd64-'
|
|
'vmlinuz-generic.manifest.xml',
|
|
'property-architecture': 'x86_64',
|
|
'etag': 'f46cfe7fb3acaff49a3567031b9b53bb',
|
|
'location': 'http://127.0.0.1:9292/v1/images/'
|
|
'6d55dd55-053a-4765-b7bc-b30df0ea3861',
|
|
'container_format': 'aki',
|
|
'status': 'active',
|
|
'deleted': 'False',
|
|
'min_disk': '0',
|
|
'is_public': 'False',
|
|
'name': 'ubuntu-bucket/oneiric-server-cloudimg-amd64-vmlinuz-generic',
|
|
'checksum': 'f46cfe7fb3acaff49a3567031b9b53bb',
|
|
'created_at': '2012-06-25T02:10:32',
|
|
'protected': 'False',
|
|
'content-type': 'text/html; charset=UTF-8'
|
|
}
|
|
|
|
IMG_RESPONSE_QUEUED = copy.copy(IMG_RESPONSE_ACTIVE)
|
|
IMG_RESPONSE_QUEUED['status'] = 'queued'
|
|
IMG_RESPONSE_QUEUED['id'] = '49b2c782-ee10-4692-84f8-3942e9432c4b'
|
|
IMG_RESPONSE_QUEUED['location'] = ('http://127.0.0.1:9292/v1/images/'
|
|
+ IMG_RESPONSE_QUEUED['id'])
|
|
|
|
|
|
class FakeHTTPConnection(object):
|
|
def __init__(self):
|
|
self.count = 0
|
|
self.reqs = {}
|
|
self.last_req = None
|
|
self.host = 'localhost'
|
|
self.port = 9292
|
|
|
|
def prime_request(self, method, url, in_body, in_headers,
|
|
out_code, out_body, out_headers):
|
|
if not url.startswith('/'):
|
|
url = '/' + url
|
|
url = unit_test_utils.sort_url_by_qs_keys(url)
|
|
hkeys = sorted(in_headers.keys())
|
|
hashable = (method, url, in_body, ' '.join(hkeys))
|
|
|
|
flat_headers = []
|
|
for key in out_headers:
|
|
flat_headers.append((key, out_headers[key]))
|
|
|
|
self.reqs[hashable] = (out_code, out_body, flat_headers)
|
|
|
|
def request(self, method, url, body, headers):
|
|
self.count += 1
|
|
url = unit_test_utils.sort_url_by_qs_keys(url)
|
|
hkeys = sorted(headers.keys())
|
|
hashable = (method, url, body, ' '.join(hkeys))
|
|
|
|
if hashable not in self.reqs:
|
|
options = []
|
|
for h in self.reqs:
|
|
options.append(repr(h))
|
|
|
|
raise Exception('No such primed request: %s "%s"\n'
|
|
'%s\n\n'
|
|
'Available:\n'
|
|
'%s'
|
|
% (method, url, hashable, '\n\n'.join(options)))
|
|
self.last_req = hashable
|
|
|
|
def getresponse(self):
|
|
class FakeResponse(object):
|
|
def __init__(self, args):
|
|
(code, body, headers) = args
|
|
self.body = six.StringIO(body)
|
|
self.headers = headers
|
|
self.status = code
|
|
|
|
def read(self, count=1000000):
|
|
return self.body.read(count)
|
|
|
|
def getheaders(self):
|
|
return self.headers
|
|
|
|
return FakeResponse(self.reqs[self.last_req])
|
|
|
|
|
|
class ImageServiceTestCase(test_utils.BaseTestCase):
|
|
def test_rest_errors(self):
|
|
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
|
|
|
|
for code, exc in [(http.BAD_REQUEST, webob.exc.HTTPBadRequest),
|
|
(http.UNAUTHORIZED, webob.exc.HTTPUnauthorized),
|
|
(http.FORBIDDEN, webob.exc.HTTPForbidden),
|
|
(http.CONFLICT, webob.exc.HTTPConflict),
|
|
(http.INTERNAL_SERVER_ERROR,
|
|
webob.exc.HTTPInternalServerError)]:
|
|
c.conn.prime_request('GET',
|
|
('v1/images/'
|
|
'5dcddce0-cba5-4f18-9cf4-9853c7b207a6'), '',
|
|
{'x-auth-token': 'noauth'}, code, '', {})
|
|
self.assertRaises(exc, c.get_image,
|
|
'5dcddce0-cba5-4f18-9cf4-9853c7b207a6')
|
|
|
|
def test_rest_get_images(self):
|
|
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
|
|
|
|
# Two images, one of which is queued
|
|
resp = {'images': [IMG_RESPONSE_ACTIVE, IMG_RESPONSE_QUEUED]}
|
|
c.conn.prime_request('GET', 'v1/images/detail?is_public=None', '',
|
|
{'x-auth-token': 'noauth'},
|
|
http.OK, jsonutils.dumps(resp), {})
|
|
c.conn.prime_request('GET',
|
|
('v1/images/detail?marker=%s&is_public=None'
|
|
% IMG_RESPONSE_QUEUED['id']),
|
|
'', {'x-auth-token': 'noauth'},
|
|
http.OK, jsonutils.dumps({'images': []}), {})
|
|
|
|
imgs = list(c.get_images())
|
|
self.assertEqual(2, len(imgs))
|
|
self.assertEqual(2, c.conn.count)
|
|
|
|
def test_rest_get_image(self):
|
|
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
|
|
|
|
image_contents = 'THISISTHEIMAGEBODY'
|
|
c.conn.prime_request('GET',
|
|
'v1/images/%s' % IMG_RESPONSE_ACTIVE['id'],
|
|
'', {'x-auth-token': 'noauth'},
|
|
http.OK, image_contents, IMG_RESPONSE_ACTIVE)
|
|
|
|
body = c.get_image(IMG_RESPONSE_ACTIVE['id'])
|
|
self.assertEqual(image_contents, body.read())
|
|
|
|
def test_rest_header_list_to_dict(self):
|
|
i = [('x-image-meta-banana', 42),
|
|
('gerkin', 12),
|
|
('x-image-meta-property-frog', 11),
|
|
('x-image-meta-property-duck', 12)]
|
|
o = glance_replicator.ImageService._header_list_to_dict(i)
|
|
self.assertIn('banana', o)
|
|
self.assertIn('gerkin', o)
|
|
self.assertIn('properties', o)
|
|
self.assertIn('frog', o['properties'])
|
|
self.assertIn('duck', o['properties'])
|
|
self.assertNotIn('x-image-meta-banana', o)
|
|
|
|
def test_rest_get_image_meta(self):
|
|
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
|
|
|
|
c.conn.prime_request('HEAD',
|
|
'v1/images/%s' % IMG_RESPONSE_ACTIVE['id'],
|
|
'', {'x-auth-token': 'noauth'},
|
|
http.OK, '', IMG_RESPONSE_ACTIVE)
|
|
|
|
header = c.get_image_meta(IMG_RESPONSE_ACTIVE['id'])
|
|
self.assertIn('id', header)
|
|
|
|
def test_rest_dict_to_headers(self):
|
|
i = {'banana': 42,
|
|
'gerkin': 12,
|
|
'properties': {'frog': 1,
|
|
'kernel_id': None}
|
|
}
|
|
o = glance_replicator.ImageService._dict_to_headers(i)
|
|
self.assertIn('x-image-meta-banana', o)
|
|
self.assertIn('x-image-meta-gerkin', o)
|
|
self.assertIn('x-image-meta-property-frog', o)
|
|
self.assertIn('x-image-meta-property-kernel_id', o)
|
|
self.assertEqual(o['x-image-meta-property-kernel_id'], '')
|
|
self.assertNotIn('properties', o)
|
|
|
|
def test_rest_add_image(self):
|
|
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
|
|
|
|
image_body = 'THISISANIMAGEBODYFORSURE!'
|
|
image_meta_with_proto = {
|
|
'x-auth-token': 'noauth',
|
|
'Content-Type': 'application/octet-stream',
|
|
'Content-Length': len(image_body)
|
|
}
|
|
|
|
for key in IMG_RESPONSE_ACTIVE:
|
|
image_meta_with_proto[
|
|
'x-image-meta-%s' % key] = IMG_RESPONSE_ACTIVE[key]
|
|
|
|
c.conn.prime_request('POST', 'v1/images',
|
|
image_body, image_meta_with_proto,
|
|
http.OK, '', IMG_RESPONSE_ACTIVE)
|
|
|
|
headers, body = c.add_image(IMG_RESPONSE_ACTIVE, image_body)
|
|
self.assertEqual(IMG_RESPONSE_ACTIVE, headers)
|
|
self.assertEqual(1, c.conn.count)
|
|
|
|
def test_rest_add_image_meta(self):
|
|
c = glance_replicator.ImageService(FakeHTTPConnection(), 'noauth')
|
|
|
|
image_meta = {'id': '5dcddce0-cba5-4f18-9cf4-9853c7b207a6'}
|
|
image_meta_headers = glance_replicator.ImageService._dict_to_headers(
|
|
image_meta)
|
|
image_meta_headers['x-auth-token'] = 'noauth'
|
|
image_meta_headers['Content-Type'] = 'application/octet-stream'
|
|
c.conn.prime_request('PUT', 'v1/images/%s' % image_meta['id'],
|
|
'', image_meta_headers, http.OK, '', '')
|
|
headers, body = c.add_image_meta(image_meta)
|
|
|
|
|
|
class FakeHttpResponse(object):
|
|
def __init__(self, headers, data):
|
|
self.headers = headers
|
|
self.data = six.BytesIO(data)
|
|
|
|
def getheaders(self):
|
|
return self.headers
|
|
|
|
def read(self, amt=None):
|
|
return self.data.read(amt)
|
|
|
|
|
|
FAKEIMAGES = [{'status': 'active', 'size': 100, 'dontrepl': 'banana',
|
|
'id': '5dcddce0-cba5-4f18-9cf4-9853c7b207a6', 'name': 'x1'},
|
|
{'status': 'deleted', 'size': 200, 'dontrepl': 'banana',
|
|
'id': 'f4da1d2a-40e8-4710-b3aa-0222a4cc887b', 'name': 'x2'},
|
|
{'status': 'active', 'size': 300, 'dontrepl': 'banana',
|
|
'id': '37ff82db-afca-48c7-ae0b-ddc7cf83e3db', 'name': 'x3'}]
|
|
FAKEIMAGES_LIVEMASTER = [{'status': 'active', 'size': 100,
|
|
'dontrepl': 'banana', 'name': 'x1',
|
|
'id': '5dcddce0-cba5-4f18-9cf4-9853c7b207a6'},
|
|
{'status': 'deleted', 'size': 200,
|
|
'dontrepl': 'banana', 'name': 'x2',
|
|
'id': 'f4da1d2a-40e8-4710-b3aa-0222a4cc887b'},
|
|
{'status': 'deleted', 'size': 300,
|
|
'dontrepl': 'banana', 'name': 'x3',
|
|
'id': '37ff82db-afca-48c7-ae0b-ddc7cf83e3db'},
|
|
{'status': 'active', 'size': 100,
|
|
'dontrepl': 'banana', 'name': 'x4',
|
|
'id': '15648dd7-8dd0-401c-bd51-550e1ba9a088'}]
|
|
|
|
|
|
class FakeImageService(object):
|
|
def __init__(self, http_conn, authtoken):
|
|
self.authtoken = authtoken
|
|
|
|
def get_images(self):
|
|
if self.authtoken == 'livemastertoken':
|
|
return FAKEIMAGES_LIVEMASTER
|
|
return FAKEIMAGES
|
|
|
|
def get_image(self, id):
|
|
return FakeHttpResponse({}, b'data')
|
|
|
|
def get_image_meta(self, id):
|
|
for img in FAKEIMAGES:
|
|
if img['id'] == id:
|
|
return img
|
|
return {}
|
|
|
|
def add_image_meta(self, meta):
|
|
return {'status': http.OK}, None
|
|
|
|
def add_image(self, meta, data):
|
|
return {'status': http.OK}, None
|
|
|
|
|
|
def get_image_service():
|
|
return FakeImageService
|
|
|
|
|
|
def check_no_args(command, args):
|
|
options = moves.UserDict()
|
|
no_args_error = False
|
|
|
|
orig_img_service = glance_replicator.get_image_service
|
|
try:
|
|
glance_replicator.get_image_service = get_image_service
|
|
command(options, args)
|
|
except TypeError as e:
|
|
if str(e) == "Too few arguments.":
|
|
no_args_error = True
|
|
finally:
|
|
glance_replicator.get_image_service = orig_img_service
|
|
|
|
return no_args_error
|
|
|
|
|
|
def check_bad_args(command, args):
|
|
options = moves.UserDict()
|
|
bad_args_error = False
|
|
|
|
orig_img_service = glance_replicator.get_image_service
|
|
try:
|
|
glance_replicator.get_image_service = get_image_service
|
|
command(options, args)
|
|
except ValueError:
|
|
bad_args_error = True
|
|
finally:
|
|
glance_replicator.get_image_service = orig_img_service
|
|
|
|
return bad_args_error
|
|
|
|
|
|
class ReplicationCommandsTestCase(test_utils.BaseTestCase):
|
|
|
|
@mock.patch.object(glance_replicator, 'lookup_command')
|
|
def test_help(self, mock_lookup_command):
|
|
option = mock.Mock()
|
|
mock_lookup_command.return_value = "fake_return"
|
|
|
|
glance_replicator.print_help(option, [])
|
|
glance_replicator.print_help(option, ['dump'])
|
|
glance_replicator.print_help(option, ['fake_command'])
|
|
self.assertEqual(2, mock_lookup_command.call_count)
|
|
|
|
def test_replication_size(self):
|
|
options = moves.UserDict()
|
|
options.slavetoken = 'slavetoken'
|
|
args = ['localhost:9292']
|
|
|
|
stdout = sys.stdout
|
|
orig_img_service = glance_replicator.get_image_service
|
|
sys.stdout = six.StringIO()
|
|
try:
|
|
glance_replicator.get_image_service = get_image_service
|
|
glance_replicator.replication_size(options, args)
|
|
sys.stdout.seek(0)
|
|
output = sys.stdout.read()
|
|
finally:
|
|
sys.stdout = stdout
|
|
glance_replicator.get_image_service = orig_img_service
|
|
|
|
output = output.rstrip()
|
|
self.assertEqual(
|
|
'Total size is 400 bytes (400.0 B) across 2 images',
|
|
output
|
|
)
|
|
|
|
def test_replication_size_with_no_args(self):
|
|
args = []
|
|
command = glance_replicator.replication_size
|
|
self.assertTrue(check_no_args(command, args))
|
|
|
|
def test_replication_size_with_args_is_None(self):
|
|
args = None
|
|
command = glance_replicator.replication_size
|
|
self.assertTrue(check_no_args(command, args))
|
|
|
|
def test_replication_size_with_bad_args(self):
|
|
args = ['aaa']
|
|
command = glance_replicator.replication_size
|
|
self.assertTrue(check_bad_args(command, args))
|
|
|
|
def test_human_readable_size(self):
|
|
_human_readable_size = glance_replicator._human_readable_size
|
|
|
|
self.assertEqual('0.0 B', _human_readable_size(0))
|
|
self.assertEqual('1.0 B', _human_readable_size(1))
|
|
self.assertEqual('512.0 B', _human_readable_size(512))
|
|
self.assertEqual('1.0 KiB', _human_readable_size(1024))
|
|
self.assertEqual('2.0 KiB', _human_readable_size(2048))
|
|
self.assertEqual('8.0 KiB', _human_readable_size(8192))
|
|
self.assertEqual('64.0 KiB', _human_readable_size(65536))
|
|
self.assertEqual('93.3 KiB', _human_readable_size(95536))
|
|
self.assertEqual('117.7 MiB', _human_readable_size(123456789))
|
|
self.assertEqual('36.3 GiB', _human_readable_size(39022543360))
|
|
|
|
def test_replication_dump(self):
|
|
tempdir = self.useFixture(fixtures.TempDir()).path
|
|
|
|
options = moves.UserDict()
|
|
options.chunksize = 4096
|
|
options.mastertoken = 'mastertoken'
|
|
options.metaonly = False
|
|
args = ['localhost:9292', tempdir]
|
|
|
|
orig_img_service = glance_replicator.get_image_service
|
|
self.addCleanup(setattr, glance_replicator,
|
|
'get_image_service', orig_img_service)
|
|
glance_replicator.get_image_service = get_image_service
|
|
glance_replicator.replication_dump(options, args)
|
|
|
|
for active in ['5dcddce0-cba5-4f18-9cf4-9853c7b207a6',
|
|
'37ff82db-afca-48c7-ae0b-ddc7cf83e3db']:
|
|
imgfile = os.path.join(tempdir, active)
|
|
self.assertTrue(os.path.exists(imgfile))
|
|
self.assertTrue(os.path.exists('%s.img' % imgfile))
|
|
|
|
with open(imgfile) as f:
|
|
d = jsonutils.loads(f.read())
|
|
self.assertIn('status', d)
|
|
self.assertIn('id', d)
|
|
self.assertIn('size', d)
|
|
|
|
for inactive in ['f4da1d2a-40e8-4710-b3aa-0222a4cc887b']:
|
|
imgfile = os.path.join(tempdir, inactive)
|
|
self.assertTrue(os.path.exists(imgfile))
|
|
self.assertFalse(os.path.exists('%s.img' % imgfile))
|
|
|
|
with open(imgfile) as f:
|
|
d = jsonutils.loads(f.read())
|
|
self.assertIn('status', d)
|
|
self.assertIn('id', d)
|
|
self.assertIn('size', d)
|
|
|
|
def test_replication_dump_with_no_args(self):
|
|
args = []
|
|
command = glance_replicator.replication_dump
|
|
self.assertTrue(check_no_args(command, args))
|
|
|
|
def test_replication_dump_with_bad_args(self):
|
|
args = ['aaa', 'bbb']
|
|
command = glance_replicator.replication_dump
|
|
self.assertTrue(check_bad_args(command, args))
|
|
|
|
def test_replication_load(self):
|
|
tempdir = self.useFixture(fixtures.TempDir()).path
|
|
|
|
def write_image(img, data):
|
|
imgfile = os.path.join(tempdir, img['id'])
|
|
with open(imgfile, 'w') as f:
|
|
f.write(jsonutils.dumps(img))
|
|
|
|
if data:
|
|
with open('%s.img' % imgfile, 'w') as f:
|
|
f.write(data)
|
|
|
|
for img in FAKEIMAGES:
|
|
cimg = copy.copy(img)
|
|
# We need at least one image where the stashed metadata on disk
|
|
# is newer than what the fake has
|
|
if cimg['id'] == '5dcddce0-cba5-4f18-9cf4-9853c7b207a6':
|
|
cimg['extra'] = 'thisissomeextra'
|
|
|
|
# This is an image where the metadata change should be ignored
|
|
if cimg['id'] == 'f4da1d2a-40e8-4710-b3aa-0222a4cc887b':
|
|
cimg['dontrepl'] = 'thisisyetmoreextra'
|
|
|
|
write_image(cimg, 'kjdhfkjshdfkjhsdkfd')
|
|
|
|
# And an image which isn't on the destination at all
|
|
new_id = str(uuid.uuid4())
|
|
cimg['id'] = new_id
|
|
write_image(cimg, 'dskjfhskjhfkfdhksjdhf')
|
|
|
|
# And an image which isn't on the destination, but lacks image
|
|
# data
|
|
new_id_missing_data = str(uuid.uuid4())
|
|
cimg['id'] = new_id_missing_data
|
|
write_image(cimg, None)
|
|
|
|
# A file which should be ignored
|
|
badfile = os.path.join(tempdir, 'kjdfhf')
|
|
with open(badfile, 'w') as f:
|
|
f.write(jsonutils.dumps([1, 2, 3, 4, 5]))
|
|
|
|
# Finally, we're ready to test
|
|
options = moves.UserDict()
|
|
options.dontreplicate = 'dontrepl dontreplabsent'
|
|
options.slavetoken = 'slavetoken'
|
|
args = ['localhost:9292', tempdir]
|
|
|
|
orig_img_service = glance_replicator.get_image_service
|
|
try:
|
|
glance_replicator.get_image_service = get_image_service
|
|
updated = glance_replicator.replication_load(options, args)
|
|
finally:
|
|
glance_replicator.get_image_service = orig_img_service
|
|
|
|
self.assertIn('5dcddce0-cba5-4f18-9cf4-9853c7b207a6', updated)
|
|
self.assertNotIn('f4da1d2a-40e8-4710-b3aa-0222a4cc887b', updated)
|
|
self.assertIn(new_id, updated)
|
|
self.assertNotIn(new_id_missing_data, updated)
|
|
|
|
def test_replication_load_with_no_args(self):
|
|
args = []
|
|
command = glance_replicator.replication_load
|
|
self.assertTrue(check_no_args(command, args))
|
|
|
|
def test_replication_load_with_bad_args(self):
|
|
args = ['aaa', 'bbb']
|
|
command = glance_replicator.replication_load
|
|
self.assertTrue(check_bad_args(command, args))
|
|
|
|
def test_replication_livecopy(self):
|
|
options = moves.UserDict()
|
|
options.chunksize = 4096
|
|
options.dontreplicate = 'dontrepl dontreplabsent'
|
|
options.mastertoken = 'livemastertoken'
|
|
options.slavetoken = 'liveslavetoken'
|
|
options.metaonly = False
|
|
args = ['localhost:9292', 'localhost:9393']
|
|
|
|
orig_img_service = glance_replicator.get_image_service
|
|
try:
|
|
glance_replicator.get_image_service = get_image_service
|
|
updated = glance_replicator.replication_livecopy(options, args)
|
|
finally:
|
|
glance_replicator.get_image_service = orig_img_service
|
|
|
|
self.assertEqual(2, len(updated))
|
|
|
|
def test_replication_livecopy_with_no_args(self):
|
|
args = []
|
|
command = glance_replicator.replication_livecopy
|
|
self.assertTrue(check_no_args(command, args))
|
|
|
|
def test_replication_livecopy_with_bad_args(self):
|
|
args = ['aaa', 'bbb']
|
|
command = glance_replicator.replication_livecopy
|
|
self.assertTrue(check_bad_args(command, args))
|
|
|
|
def test_replication_compare(self):
|
|
options = moves.UserDict()
|
|
options.chunksize = 4096
|
|
options.dontreplicate = 'dontrepl dontreplabsent'
|
|
options.mastertoken = 'livemastertoken'
|
|
options.slavetoken = 'liveslavetoken'
|
|
options.metaonly = False
|
|
args = ['localhost:9292', 'localhost:9393']
|
|
|
|
orig_img_service = glance_replicator.get_image_service
|
|
try:
|
|
glance_replicator.get_image_service = get_image_service
|
|
differences = glance_replicator.replication_compare(options, args)
|
|
finally:
|
|
glance_replicator.get_image_service = orig_img_service
|
|
|
|
self.assertIn('15648dd7-8dd0-401c-bd51-550e1ba9a088', differences)
|
|
self.assertEqual(differences['15648dd7-8dd0-401c-bd51-550e1ba9a088'],
|
|
'missing')
|
|
self.assertIn('37ff82db-afca-48c7-ae0b-ddc7cf83e3db', differences)
|
|
self.assertEqual(differences['37ff82db-afca-48c7-ae0b-ddc7cf83e3db'],
|
|
'diff')
|
|
|
|
def test_replication_compare_with_no_args(self):
|
|
args = []
|
|
command = glance_replicator.replication_compare
|
|
self.assertTrue(check_no_args(command, args))
|
|
|
|
def test_replication_compare_with_bad_args(self):
|
|
args = ['aaa', 'bbb']
|
|
command = glance_replicator.replication_compare
|
|
self.assertTrue(check_bad_args(command, args))
|
|
|
|
|
|
class ReplicationUtilitiesTestCase(test_utils.BaseTestCase):
|
|
def test_check_upload_response_headers(self):
|
|
glance_replicator._check_upload_response_headers({'status': 'active'},
|
|
None)
|
|
|
|
d = {'image': {'status': 'active'}}
|
|
glance_replicator._check_upload_response_headers({},
|
|
jsonutils.dumps(d))
|
|
|
|
self.assertRaises(
|
|
exception.UploadException,
|
|
glance_replicator._check_upload_response_headers, {}, None)
|
|
|
|
def test_image_present(self):
|
|
client = FakeImageService(None, 'noauth')
|
|
self.assertTrue(glance_replicator._image_present(
|
|
client, '5dcddce0-cba5-4f18-9cf4-9853c7b207a6'))
|
|
self.assertFalse(glance_replicator._image_present(
|
|
client, uuid.uuid4()))
|
|
|
|
def test_dict_diff(self):
|
|
a = {'a': 1, 'b': 2, 'c': 3}
|
|
b = {'a': 1, 'b': 2}
|
|
c = {'a': 1, 'b': 1, 'c': 3}
|
|
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
|
|
|
|
# Only things that the first dict has which the second dict doesn't
|
|
# matter here.
|
|
self.assertFalse(glance_replicator._dict_diff(a, a))
|
|
self.assertTrue(glance_replicator._dict_diff(a, b))
|
|
self.assertTrue(glance_replicator._dict_diff(a, c))
|
|
self.assertFalse(glance_replicator._dict_diff(a, d))
|