shade/shade/tests/unit/base.py
Monty Taylor b7ea6c7150
Replace SwiftService with direct REST uploads
SwiftService uploads large objects using a thread pool. (The pool
defaults to 5 and we're not currently configuring it larger or smaller)
Instead of using that, spin up upload threads on our own so that we can
get rid of the swiftclient depend.

A few notes:
- We're using the new async feature of the Adapter wrapper, which rate
  limits at the _start_ of a REST call. This is sane as far as we can
  tell, but also might not be what someone is expecting.
- We'll skip the thread pool uploader for objects that are smaller than
  the default max segment size.
- In splitting the file into segments, we'd like to avoid reading all of
  the segments into RAM when we don't need to - so there is a file-like
  wrapper class which can be passed to requests. This implements a
  read-view of a portion of the file. In a pathological case, this could
  be slower due to disk seeking on the read side. However, let's go back
  and deal with buffering when we have a problem - I imagine that the
  REST upload will be the bottleneck long before the overhead of
  interleaved disk seeks will be.

Change-Id: Id9258980d2e0782e4e3c0ac26c7f11dc4db80354
2017-01-21 16:21:28 +01:00

198 lines
7.4 KiB
Python

# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import time
import fixtures
import mock
import os
import os_client_config as occ
from requests_mock.contrib import fixture as rm_fixture
import tempfile
import shade.openstackcloud
from shade.tests import base
class BaseTestCase(base.TestCase):
def setUp(self, cloud_config_fixture='clouds.yaml'):
"""Run before each test method to initialize test environment."""
super(BaseTestCase, self).setUp()
# Sleeps are for real testing, but unit tests shouldn't need them
realsleep = time.sleep
def _nosleep(seconds):
return realsleep(seconds * 0.0001)
self.sleep_fixture = self.useFixture(fixtures.MonkeyPatch(
'time.sleep',
_nosleep))
self.fixtures_directory = 'shade/tests/unit/fixtures'
# Isolate os-client-config from test environment
config = tempfile.NamedTemporaryFile(delete=False)
cloud_path = '%s/clouds/%s' % (self.fixtures_directory,
cloud_config_fixture)
with open(cloud_path, 'rb') as f:
content = f.read()
config.write(content)
config.close()
vendor = tempfile.NamedTemporaryFile(delete=False)
vendor.write(b'{}')
vendor.close()
# set record mode depending on environment
record_mode = os.environ.get('BETAMAX_RECORD_FIXTURES', False)
if record_mode:
self.record_fixtures = 'new_episodes'
else:
self.record_fixtures = None
test_cloud = os.environ.get('SHADE_OS_CLOUD', '_test_cloud_')
self.config = occ.OpenStackConfig(
config_files=[config.name],
vendor_files=[vendor.name],
secure_files=['non-existant'])
self.cloud_config = self.config.get_one_cloud(
cloud=test_cloud, validate=False)
self.cloud = shade.OpenStackCloud(
cloud_config=self.cloud_config,
log_inner_exceptions=True)
self.strict_cloud = shade.OpenStackCloud(
cloud_config=self.cloud_config,
log_inner_exceptions=True,
strict=True)
self.op_cloud = shade.OperatorCloud(
cloud_config=self.cloud_config,
log_inner_exceptions=True)
# Any unit tests using betamax directly need a ksa.Session with
# an auth dict. The cassette is currently written with v2 as well
self.full_cloud_config = self.config.get_one_cloud(
cloud='_test_cloud_v2_')
self.full_cloud = shade.OpenStackCloud(
cloud_config=self.full_cloud_config,
log_inner_exceptions=True)
self.full_op_cloud = shade.OperatorCloud(
cloud_config=self.full_cloud_config,
log_inner_exceptions=True)
class TestCase(BaseTestCase):
def setUp(self, cloud_config_fixture='clouds.yaml'):
super(TestCase, self).setUp(cloud_config_fixture=cloud_config_fixture)
self.session_fixture = self.useFixture(fixtures.MonkeyPatch(
'os_client_config.cloud_config.CloudConfig.get_session',
mock.Mock()))
class RequestsMockTestCase(BaseTestCase):
def setUp(self, cloud_config_fixture='clouds.yaml'):
super(RequestsMockTestCase, self).setUp(
cloud_config_fixture=cloud_config_fixture)
self.discovery_json = os.path.join(
self.fixtures_directory, 'discovery.json')
self.use_keystone_v3()
def use_keystone_v3(self):
self.adapter = self.useFixture(rm_fixture.Fixture())
self.adapter.get(
'http://192.168.0.19:35357/',
text=open(self.discovery_json, 'r').read())
self.adapter.post(
'https://example.com/v3/auth/tokens',
headers={
'X-Subject-Token': self.getUniqueString()},
text=open(
os.path.join(
self.fixtures_directory,
'catalog-v3.json'),
'r').read())
self.calls = [
dict(method='GET', url='http://192.168.0.19:35357/'),
dict(method='POST', url='https://example.com/v3/auth/tokens'),
]
self._make_test_cloud(identity_api_version='3')
def use_keystone_v2(self):
self.adapter = self.useFixture(rm_fixture.Fixture())
self.adapter.get(
'http://192.168.0.19:35357/',
text=open(self.discovery_json, 'r').read())
self.adapter.post(
'https://example.com/v2.0/tokens',
text=open(
os.path.join(
self.fixtures_directory,
'catalog-v2.json'),
'r').read())
self.calls = [
dict(method='GET', url='http://192.168.0.19:35357/'),
dict(method='POST', url='https://example.com/v2.0/tokens'),
]
self._make_test_cloud(identity_api_version='2.0')
def _make_test_cloud(self, **kwargs):
test_cloud = os.environ.get('SHADE_OS_CLOUD', '_test_cloud_')
self.cloud_config = self.config.get_one_cloud(
cloud=test_cloud, validate=True, **kwargs)
self.cloud = shade.OpenStackCloud(
cloud_config=self.cloud_config,
log_inner_exceptions=True)
def use_glance(self, image_version_json='image-version.json'):
discovery_fixture = os.path.join(
self.fixtures_directory, image_version_json)
self.adapter.get(
'https://image.example.com/',
text=open(discovery_fixture, 'r').read())
self.calls += [
dict(method='GET', url='https://image.example.com/'),
]
def assert_calls(self, stop_after=None):
self.assertEqual(len(self.calls), len(self.adapter.request_history))
for (x, (call, history)) in enumerate(
zip(self.calls, self.adapter.request_history)):
if stop_after and x > stop_after:
break
self.assertEqual(
call['method'], history.method,
'Method mismatch on call {index}'.format(index=x))
self.assertEqual(
call['url'], history.url,
'URL mismatch on call {index}'.format(index=x))
if 'json' in call:
self.assertEqual(
call['json'], history.json(),
'json content mismatch in call {index}'.format(index=x))
# headers in a call isn't exhaustive - it's checking to make sure
# a specific header or headers are there, not that they are the
# only headers
if 'headers' in call:
for key, value in call['headers'].items():
self.assertEqual(
value, history.headers[key],
'header mismatch in call {index}'.format(index=x))