Remove bundled intree freezer tempest plugin

* https://review.openstack.org/#/c/526664/ moves the intree tempest
  plugin to freezer-tempest-plugin repo.

* Excluding freezer/tests/freezer_tempest_plugin/tests/api/test_version.py as
  it is dependent on freezer project and moving under integration tests.

Depends-On: I6967f915758728827e8ddcd1a45a7023904b694e
Change-Id: I4625d55a768f1ad0762fc2d8554998825f0d6716
This commit is contained in:
Chandan Kumar 2017-12-08 17:59:24 +05:30
parent 3bbebaa416
commit 01da61b237
21 changed files with 12 additions and 1412 deletions

View File

@ -21,6 +21,7 @@
- openstack/freezer-api
- openstack/freezer-web-ui
- openstack/python-freezerclient
- openstack/freezer-tempest-plugin
- job:
name: freezer-centos-7
@ -35,6 +36,7 @@
- openstack/freezer-api
- openstack/freezer-web-ui
- openstack/python-freezerclient
- openstack/freezer-tempest-plugin
- job:
name: freezer-opensuse-423
@ -49,3 +51,4 @@
- openstack/freezer-api
- openstack/freezer-web-ui
- openstack/python-freezerclient
- openstack/freezer-tempest-plugin

View File

@ -1,259 +0,0 @@
Freezer Tempest Tests
=====================
Integration tests in Freezer are implemented using tempest. This document describes different approaches to run these tests.
Where to start?
* If you just want to run the tests as quickly as possible, start with `Run tests inside a devstack VM`_.
* If you want to run tests on your local machine (with services running in devstack), start with `Run tests outside a devstack VM`_.
Alternatively there is a slightly different version that uses nose as a testrunner: `Run tests outside a devstack VM (alternative instructions using nose)`_.
* If you want to run tests on your local machine in PyCharm, start with `Run tests in PyCharm`_.
* If you want to run the tests on Mac OS X, start with `Mac OS X Instructions`_ and continue with one of the options above.
Setting up a devstack VM
------------------------
Install devstack with swift and the freezer [1]_ as well as the freezer-api [2]_ plugins by adding the following lines to you `local.conf`:
::
enable_plugin freezer https://git.openstack.org/openstack/freezer master
enable_plugin freezer-api https://git.openstack.org/openstack/freezer-api master
enable_service s-proxy s-object s-container s-account
.. [1] https://github.com/openstack/freezer/blob/master/devstack/README.rst
.. [2] https://github.com/openstack/freezer-api/blob/master/devstack/README.rst
Run tests inside a devstack VM
-------------------------------
#. Create a devstack VM as described in `Setting up a devstack VM`_
#. Inside your devstack VM, navigate to `/opt/stack/tempest`.
#. Run `ostestr -r freezer`
Debugging tests inside a devstack VM
------------------------------------
Often a devstack VM is used via SSH without graphical interface. Python has multiple command line debuggers. The out-of-the-box pdb works fine but I recommend pudb [3]_ which looks a bit like the old Turbo-Pascal/C IDE. The following steps are necessary to get it running:
#. Follow the steps in `Run tests inside a devstack VM`_.
#. Log into the devstack VM
#. Install pudb:
::
pip install pudb
#. Open the test file were you want to set the first breakpoint (more breakpoints can be set interactively later) and add the following line
::
import pudb;pu.db
#. Navigate to `/opt/stack/tempest`.
#. `ostestr` runs tests in parallel which causes issues with debuggers. To work around that you need to run the relevant test directly. E.g.:
::
python -m unittest freezer.tests.freezer_tempest_plugin.tests.scenario.test_backups.TestFreezerScenario
#. It should drop you into the debugger!
.. [3] https://pypi.python.org/pypi/pudb
Run tests outside a devstack VM
-------------------------------
This section describes how to run the tests outside of a devstack VM (e.g. in PyCharm) while using services (keystone, swift, ...) inside a VM.
#. Create a devstack VM as described in `Setting up a devstack VM`_.
#. Create and activate a virtual environment for Tempest:
::
virtualenv --no-site-packages tempest-venv
. tempest-venv/bin/activate
#. Clone and install the Tempest project into the virtual environment:
::
git clone https://github.com/openstack/tempest
pip install tempest/
#. Clone and install the Freezer project into the virtual environment:
::
git clone https://github.com/openstack/freezer
pip install -e freezer/
#. Clone and install the Freezer API project into the virtual environment:
::
git clone https://github.com/openstack/freezer-api
pip install -e freezer-api/
#. Initialise a Tempest working directory:
::
mkdir tempest-working
cd tempest-working
tempest init .
#. Configure `tempest-working/etc/tempest.conf`. The easiest way to do this is to just copy the config from `/opt/stack/tempest/etc/tempest.conf` inside the devstack VM.
#. Run the freezer test inside the tempest working directory:
::
cd tempest-working
ostestr -r freezer
Run tests outside a devstack VM (alternative instructions using nose)
---------------------------------------------------------------------
#. Need to make sure that there is a Devstack or other environment for running Keystone and Swift.
#. Clone the Tempest Repo::
run 'git clone https://github.com/openstack/tempest.git'
#. Create a virtual environment for Tempest. In these instructions, the Tempest virtual environment is ``~/virtualenvs/tempest-freezer``.
#. Activate the Tempest virtual environment::
run 'source ~/virtualenvs/tempest-freezer/bin/activate'
#. Make sure you have latest pip installed::
run 'pip install --upgrade pip'
#. Install Tempest requirements.txt and test-requirements.txt in the Tempest virtual environment::
run 'pip install -r requirements.txt -r test-requirements.txt'
#. Install Tempest project into the virtual environment in develop mode::
run python setup.py develop
#. Create logging.conf in Tempest Repo home dir/etc
Make a copy of logging.conf.sample as logging.conf
In logging configuration
You will see this error on Mac OS X
socket.error: [Errno 2] No such file or directory
To fix this, edit logging.conf
Change /dev/log/ to '/var/run/syslog in logging.conf
see: https://github.com/baremetal/python-backoff/issues/1 for details
#. Create tempest.conf in Tempest Repo home dir/etc::
run 'oslo-config-generator --config-file etc/config-generator.tempest.conf --output-file etc/tempest.conf'
Add the following sections to tempest.conf and modify uri and uri_v3 to point to the host where Keystone is running::
[identity]
username = freezer
password = secretservice
tenant_name = service
domain_name = default
admin_username = admin
admin_password = secretadmin
admin_domain_name = default
admin_tenant_name = admin
alt_username = admin
alt_password = secretadmin
alt_tenant_name = admin
use_ssl = False
auth_version = v3
uri = http://10.10.10.6:5000/v2.0/
uri_v3 = http://10.10.10.6:35357/v3/
[auth]
allow_tenant_isolation = true
tempest_roles = admin
#. Clone freezer Repo::
run 'git clone https://github.com/openstack/freezer.git'
#. Set the virtual environment to the Tempest virtual environment::
run 'source ~/virtualenvs/tempest-freezer/bin/activate'
#. pip install freezer requirements.txt and test-requirements.txt in Tempest virtual environment::
run 'pip install -r requirements.txt -r test-requirements.txt'
#. Install nose in the Temptest virtual environment::
run 'pip install nose'
#. Install freezer project into the Tempest virtual environment in develop mode::
run python setup.py develop
#. Set project interpreter (pycharm) to Tempest virtual environment.
#. Create test config (pycharm) using the Tempest virtual environment as python interpreter::
Set the environment variable OS_AUTH_URL to the URI where Keystone is running. For example, OS_AUTH_URL=http://10.10.10.6:5000/v2.0.
Set the Working Directory to the Tempest home dir. This will allow Tempest to find the etc/tempest.conf file.
#. Run the tests in the api directory in the freezer_tempest_plugin directory.
Mac OS X Instructions
---------------------
For Mac OS X users you will need to install gnu-tar in ``/usr/local/bin`` and make sure that ``/usr/local/bin`` is in the PATH environment variable before any other directories where a different version of tar can be found. Gnu-tar can be installed as ``gtar`` or ``tar``, either name works.
Also, currently for Mac OS X users, the latest version of gnu-tar (1.29) will not allow ``--unlink-first`` and ``--overwrite`` options to be used together. Also, gnu-tar will complain about the ``--unlink-first`` argument. To get around these limitations, you will need to modify ``tar_builders.py`` and remove the ``--unlink-first`` option from the ``UNIX_TEMPLATE`` variable.
Run tests in PyCharm
--------------------
#. Set up the test environment as described in `Run tests outside a devstack VM`_.
#. Start PyCharm and open a new project pointing to the cloned freezer directory.
#. Click `File > Settings > Project: freezer > Project Interpreter`.
#. Click the gear-wheel icon next to `Project Interpreter` and choose `Add Local`.
#. Navigate to your virtual environment and select the Python interpreter under `bin/python` and confirm with `OK`
#. In the left pane, navigate to one of the test scripts in `freezer/tests/freezer_tempest_plugin/tests/[api or scenario]/*.py`.
#. Right-click the file and choose `Run 'Unittests in [..]'`
#. This test run will most likely fail because it is started from the wrong directory. To fix this, open the dropdown box next to the run button in the top-right corner. Choose `Edit Configurations ..`
#. Point `Working directory:` to your tempest working directory.
#. Run the test again, this time it should work!
Troubleshooting
---------------
If tests fail these are good places to check:
* freezer-api log: `/var/log/apache2/freezer-api.log`
* freezer-agent log: `$HOME/.freezer/freezer.log`

View File

@ -1,21 +0,0 @@
# Copyright 2015
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
service_option = cfg.BoolOpt('freezer',
default=True,
help="Whether or not freezer is expected to be "
"available")

View File

@ -1,36 +0,0 @@
# Copyright 2015
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from tempest.test_discover import plugins
from freezer.tests.freezer_tempest_plugin import config as freezer_config
class FreezerTempestPlugin(plugins.TempestPlugin):
def load_tests(self):
base_path = os.path.split(os.path.dirname(
os.path.abspath(__file__)))[0]
test_dir = "freezer_tempest_plugin/tests"
full_test_dir = os.path.join(base_path, test_dir)
return full_test_dir, base_path
def register_opts(self, conf):
conf.register_opt(freezer_config.service_option,
group='service_available')
def get_opt_lists(self):
return [('service_available', [freezer_config.service_option])]

View File

@ -1,269 +0,0 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 datetime import datetime
from datetime import timedelta
import json
import os
import subprocess
from time import mktime
from tempest import test
from freezer.tests.integration.common import Temp_Tree
def resolve_paths(metadata):
"""Find all paths associated with a particular backup
freezer-agent stores all backups in the timestamped sub-directory of the
first backup created with a particular (name, hostname) pair, so it isn't
possible to guess the true location of backup data. This function searches
the backup container to find both the true parent directory and a list of
all files associated with the given metadata.
:param metadata: the metadata associated with the backup to resolve
:return: a tuple containing the parent directory and a list of associated
files, or (None, None)
"""
base_name = '{}_{}'.format(metadata['hostname'], metadata['backup_name'])
expected_name = '{}_{}'.format(base_name, metadata['time_stamp'])
backup_base_path = os.path.join(metadata['container'], base_name)
for timestamp in os.listdir(backup_base_path):
timestamp_abs = os.path.join(backup_base_path, timestamp)
matching = filter(lambda p: expected_name in p,
os.listdir(timestamp_abs))
if matching:
return timestamp_abs, matching
return None, None
def mutate_timestamp(metadata, days_old):
"""Alter all timestamps of an existing backup
Since there's no proper way to assign a timestamp to a backup, this method
takes an existing backup and modifies all associated timestamps to make it
otherwise indistinguishable from a backup actually created in the past.
:param metadata: the metadata associated with the backup to mutate
:param days_old: the age (i.e. days before now) that should be set
"""
date = datetime.now() - timedelta(days=days_old)
old_time_stamp = metadata['time_stamp']
new_time_stamp = int(mktime(date.timetuple()))
parent_dir, files = resolve_paths(metadata)
if os.path.basename(parent_dir) == str(old_time_stamp):
# rename the parent dir, but only if it was created for this
# backup (the dir may contain other backups with different
# timestamps that we shouldn't touch)
new_path = os.path.join(os.path.dirname(parent_dir),
str(new_time_stamp))
os.rename(parent_dir, new_path)
parent_dir = new_path
# rename each file associated with the backup, since each filename
# contains the timestamp as well
for old_file in files:
new_file = old_file.replace(str(old_time_stamp), str(new_time_stamp))
os.rename(os.path.join(parent_dir, old_file),
os.path.join(parent_dir, new_file))
# update the metadata before saving to keep things consistent
metadata['time_stamp'] = new_time_stamp
def load_metadata(path):
"""Given a metadata path, return a dict containing parsed values.
:param path: the path to load
:return: a metadata dict
"""
with open(path, 'r') as f:
return json.load(f)
def save_metadata(metadata, path):
"""Write the given metadata object to the provided path.
:param metadata: the metadata dict to write
:param path: the path at which to write the metadata
"""
with open(path, 'w') as f:
json.dump(metadata, f)
class BaseFreezerTest(test.BaseTestCase):
credentials = ['primary']
def __init__(self, *args, **kwargs):
super(BaseFreezerTest, self).__init__(*args, **kwargs)
# noinspection PyAttributeOutsideInit
def setUp(self):
super(BaseFreezerTest, self).setUp()
self.storage = Temp_Tree()
self.source_trees = []
self.backup_count = 0
self.backup_name = 'backup_test'
self.get_environ()
def tearDown(self):
super(BaseFreezerTest, self).tearDown()
for tree in self.source_trees:
tree.cleanup()
self.storage.cleanup()
@classmethod
def get_auth_url(cls):
return cls.os_primary.auth_provider.auth_client.auth_url[:-len(
'/auth/tokens')]
@classmethod
def setup_clients(cls):
super(BaseFreezerTest, cls).setup_clients()
cls.get_environ()
@classmethod
def get_environ(cls):
os.environ['OS_PASSWORD'] = cls.os_primary.credentials.password
os.environ['OS_USERNAME'] = cls.os_primary.credentials.username
os.environ['OS_PROJECT_NAME'] = cls.os_primary.credentials.tenant_name
os.environ['OS_TENANT_NAME'] = cls.os_primary.credentials.tenant_name
os.environ['OS_PROJECT_DOMAIN_NAME'] = \
cls.os_primary.credentials.project_domain_name
os.environ['OS_USER_DOMAIN_NAME'] = \
cls.os_primary.credentials.user_domain_name
# Allow developers to set OS_AUTH_URL when developing so that
# Keystone may be on a host other than localhost.
if 'OS_AUTH_URL' not in os.environ:
os.environ['OS_AUTH_URL'] = cls.get_auth_url()
# Mac OS X uses gtar located in /usr/local/bin
os.environ['PATH'] = '/usr/local/bin:' + os.environ['PATH']
return os.environ
def run_subprocess(self, sub_process_args, fail_message):
proc = subprocess.Popen(sub_process_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self.environ, shell=False)
out, err = proc.communicate()
self.assertEqual(0, proc.returncode,
fail_message + " Output: {0}. "
"Error: {1}".format(out, err))
self.assertEqual('', err,
fail_message + " Output: {0}. "
"Error: {1}".format(out, err))
def create_local_backup(self, hostname=None, compression=None,
consistency_check=None, incremental=True,
always_level=None, restart_always_level=None,
max_level=None):
"""Creates a new backup with the given parameters.
The backup will immediately be created using a randomly-generated
source tree on the local filesystem, and will be stored in a random
temporary directory using the 'local' storage mode. All generated data
files will be automatically removed during `tearDown()`, though
implementations are responsible for cleaning up any additional copies
or restores created via other methods.
:param hostname: if set, set `--hostname` to the given value
:param compression: if set, set `--compression` to the given value
:param consistency_check: if True, set `--consistency_check`
:param incremental: if False, set `--no-incremental`
:param always_level: sets `--always-level` to the given value
:param restart_always_level: sets `--restart-always-level`
:param max_level: sets `--max-level` to the given value
:return: the path to the stored backup metadata
"""
metadata_path = os.path.join(
self.storage.path,
'metadata-{}.json'.format(self.backup_count))
self.backup_count += 1
tree = Temp_Tree()
tree.add_random_data()
self.source_trees.append(tree)
backup_args = [
'freezer-agent',
'--path-to-backup', tree.path,
'--container', self.storage.path,
'--backup-name', self.backup_name,
'--storage', 'local',
'--metadata-out', metadata_path,
]
if hostname:
backup_args += ['--hostname', hostname]
if compression:
backup_args += ['--compression', compression]
if consistency_check:
backup_args += ['--consistency-check']
if incremental:
if always_level is not None:
backup_args += ['--always-level', str(always_level)]
if max_level is not None:
backup_args += ['--max-level', str(max_level)]
if restart_always_level:
backup_args += ['--restart-always-level',
str(restart_always_level)]
else:
backup_args += ['--no-incremental', 'NO_INCREMENTAL']
self.run_subprocess(backup_args, 'Test backup to local storage.')
return metadata_path
def create_mutated_backup(self, days_old=30, **kwargs):
"""Create a local backup with a mutated timestamp
This creates a new backup using `create_local_backup()`, modifies it
using `mutate_timestamp()`, and then returns the resulting (loaded)
metadata dict.
:param days_old: the age of the backup to create
:param kwargs: arguments to pass to `create_local_backup()`
:return: the loaded metadata
"""
metadata_path = self.create_local_backup(**kwargs)
metadata = load_metadata(metadata_path)
mutate_timestamp(metadata, days_old)
save_metadata(metadata, metadata_path)
return metadata

View File

@ -1,188 +0,0 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 json
import os
import subprocess
from tempest.lib import decorators
from freezer.tests.freezer_tempest_plugin.tests.api import base
from freezer.tests.integration import common
class TestFreezerCompressGzip(base.BaseFreezerTest):
def __init__(self, *args, **kwargs):
super(TestFreezerCompressGzip, self).__init__(*args, **kwargs)
# noinspection PyAttributeOutsideInit
def setUp(self):
super(TestFreezerCompressGzip, self).setUp()
# create a source tree to backup with a few empty files
# (files must be empty to avoid encoding errors with pure random data)
self.source_tree = common.Temp_Tree()
self.source_tree.add_random_data(size=0)
self.storage_tree = common.Temp_Tree()
self.dest_tree = common.Temp_Tree()
self.environ = super(TestFreezerCompressGzip, self).get_environ()
def tearDown(self):
super(TestFreezerCompressGzip, self).tearDown()
self.source_tree.cleanup()
self.dest_tree.cleanup()
self.storage_tree.cleanup()
def _backup(self, name, method):
# perform a normal backup, with gzip specified
backup_args = ['freezer-agent',
'--path-to-backup',
self.source_tree.path,
'--container',
self.storage_tree.path,
'--backup-name',
name,
'--storage',
'local',
'--compress',
method,
'--metadata-out',
os.path.join(self.storage_tree.path, 'metadata.json')]
self.run_subprocess(backup_args, 'Test gzip backup to local storage.')
def _restore(self, name, method):
restore_args = ['freezer-agent',
'--action',
'restore',
'--restore-abs-path',
self.dest_tree.path,
'--container',
self.storage_tree.path,
'--backup-name',
name,
'--storage',
'local',
'--compress',
method]
self.run_subprocess(restore_args, 'Test restore from local storage.')
def _metadata(self):
path = os.path.join(self.storage_tree.path, 'metadata.json')
with open(path, 'r') as f:
return json.load(f)
def _file_get_mimetype(self, metadata):
"""Given some file metadata, find its mimetype using the file command
:param metadata: the parsed json file metadata
:return: the mimetype
"""
"""
Data is stored like data/tar/localhost_False/1469786264/0_1469786264 so
we need build the same directory structure.
data: the directory that holds the backup data
tar: the engine used to create backup
localhost: the hostname of the machine where the backup was taken
False: it should be backup name or False is backup is not provided
1469786264: timestamp
0_1469786264: level zero timestamp
"""
data_file_path = 'data{0}{1}{0}{2}_{3}{0}{4}{0}{5}_{4}{0}data'.format(
os.path.sep,
"tar", # currently we support only tar
metadata['hostname'],
metadata['backup_name'],
metadata['time_stamp'],
metadata['curr_backup_level']
)
data_file_path = os.path.join(self.storage_tree.path,
data_file_path)
self.assertEqual(True, os.path.exists(data_file_path))
# run 'file' in brief mode to only output the values we want
proc = subprocess.Popen(['file', '-b', '--mime-type', data_file_path],
stdout=subprocess.PIPE)
out, err = proc.communicate()
self.assertEqual(0, proc.returncode)
return out.strip()
@decorators.attr(type="gate")
def test_freezer_backup_compress_gzip(self):
backup_name = 'freezer-test-backup-gzip-0'
self._backup(backup_name, 'gzip')
self._restore(backup_name, 'gzip')
# metadata should show the correct algorithm
metadata = self._metadata()
self.assertIn('compression', metadata)
self.assertEqual('gzip', metadata['compression'])
# file utility should detect the correct mimetype
gizp_mimetypes = ['application/gzip', 'application/x-gzip']
mimetype = self._file_get_mimetype(metadata)
self.assertIn(mimetype, gizp_mimetypes)
# actual contents should be the same
diff_args = ['diff', '-r', '-q',
self.source_tree.path,
self.dest_tree.path]
self.run_subprocess(diff_args, 'Verify restored copy is identical to '
'original.')
@decorators.attr(type="gate")
def test_freezer_backup_compress_bzip2(self):
backup_name = 'freezer-test-backup-bzip2-0'
self._backup(backup_name, 'bzip2')
self._restore(backup_name, 'bzip2')
metadata = self._metadata()
self.assertIn('compression', metadata)
self.assertEqual('bzip2', metadata['compression'])
mimetype = self._file_get_mimetype(metadata)
self.assertEqual('application/x-bzip2', mimetype)
diff_args = ['diff', '-r', '-q',
self.source_tree.path,
self.dest_tree.path]
self.run_subprocess(diff_args, 'Verify restored copy is identical to '
'original.')
@decorators.attr(type="gate")
def test_freezer_backup_compress_xz(self):
backup_name = 'freezer-test-backup-xz-0'
self._backup(backup_name, 'xz')
self._restore(backup_name, 'xz')
metadata = self._metadata()
self.assertIn('compression', metadata)
self.assertEqual('xz', metadata['compression'])
mimetype = self._file_get_mimetype(metadata)
self.assertEqual('application/x-xz', mimetype)
diff_args = ['diff', '-r', '-q',
self.source_tree.path,
self.dest_tree.path]
self.run_subprocess(diff_args, 'Verify restored copy is identical to '
'original.')

View File

@ -1,110 +0,0 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 os
import shutil
from oslo_utils import uuidutils
from tempest.lib import decorators
from freezer.tests.freezer_tempest_plugin.tests.api import base
class TestFreezerFSBackup(base.BaseFreezerTest):
def __init__(self, *args, **kwargs):
super(TestFreezerFSBackup, self).__init__(*args, **kwargs)
def setUp(self):
super(TestFreezerFSBackup, self).setUp()
test_id = uuidutils.generate_uuid(dashed=False)
self.backup_source_dir = (
"/tmp/freezer-test-backup-source/" + test_id
)
self.backup_source_sub_dir = self.backup_source_dir + "/subdir"
self.restore_target_dir = (
"/tmp/freezer-test-backup-restore/" + test_id
)
self.backup_local_storage_dir = (
"/tmp/freezer-test-backup-local-storage/" + test_id
)
self.freezer_backup_name = 'freezer-test-backup-fs-0'
shutil.rmtree(self.backup_source_dir, True)
os.makedirs(self.backup_source_dir)
open(self.backup_source_dir + "/a", 'w').close()
open(self.backup_source_dir + "/b", 'w').close()
open(self.backup_source_dir + "/c", 'w').close()
os.makedirs(self.backup_source_sub_dir)
open(self.backup_source_sub_dir + "/x", 'w').close()
open(self.backup_source_sub_dir + "/y", 'w').close()
open(self.backup_source_sub_dir + "/z", 'w').close()
shutil.rmtree(self.restore_target_dir, True)
os.makedirs(self.restore_target_dir)
shutil.rmtree(self.backup_local_storage_dir, True)
os.makedirs(self.backup_local_storage_dir)
self.environ = super(TestFreezerFSBackup, self).get_environ()
def tearDown(self):
super(TestFreezerFSBackup, self).tearDown()
shutil.rmtree(self.backup_source_dir, True)
shutil.rmtree(self.restore_target_dir, True)
shutil.rmtree(self.backup_local_storage_dir)
@decorators.attr(type="gate")
def test_freezer_fs_backup(self):
backup_args = ['freezer-agent',
'--path-to-backup',
self.backup_source_dir,
'--container',
self.backup_local_storage_dir,
'--backup-name',
self.freezer_backup_name,
'--storage',
'local']
self.run_subprocess(backup_args, "Test backup to local storage.")
restore_args = ['freezer-agent',
'--action',
'restore',
'--restore-abs-path',
self.restore_target_dir,
'--container',
self.backup_local_storage_dir,
'--backup-name',
self.freezer_backup_name,
'--storage',
'local']
self.run_subprocess(restore_args, "Test restore from local storage.")
diff_args = ['diff',
'-r',
'-q',
self.backup_source_dir,
self.restore_target_dir]
self.run_subprocess(diff_args,
"Test backup restore from local storage "
"diff.")

View File

@ -1,94 +0,0 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 subprocess
from tempest.lib import decorators
from freezer.tests.freezer_tempest_plugin.tests.api import base
from freezer.tests.integration import common
class TestFreezerMetadataChecksum(base.BaseFreezerTest):
def __init__(self, *args, **kwargs):
super(TestFreezerMetadataChecksum, self).__init__(*args, **kwargs)
# noinspection PyAttributeOutsideInit
def setUp(self):
super(TestFreezerMetadataChecksum, self).setUp()
self.environ = super(TestFreezerMetadataChecksum, self).get_environ()
self.dest_tree = common.Temp_Tree()
self.backup_name = 'backup_checksum_test'
def tearDown(self):
super(TestFreezerMetadataChecksum, self).tearDown()
self.dest_tree.cleanup()
@decorators.attr(type="gate")
def test_freezer_fs_backup_valid_checksum(self):
# perform a normal backup, but enable consistency checks and save the
# metadata to disk
metadata_path = self.create_local_backup(consistency_check=True)
metadata = base.load_metadata(metadata_path)
# load the stored metadata to retrieve the computed checksum
self.assertIn('consistency_checksum', metadata,
'Checksum must exist in stored metadata.')
checksum = metadata['consistency_checksum']
restore_args = ['freezer-agent',
'--action', 'restore',
'--restore-abs-path', self.dest_tree.path,
'--container', metadata['container'],
'--backup-name', self.backup_name,
'--storage', 'local',
'--consistency-checksum', checksum]
self.run_subprocess(restore_args,
'Test restore from local storage with '
'computed checksum.')
@decorators.attr(type="gate")
def test_freezer_fs_backup_bad_checksum(self):
# as above, but we'll ignore the computed checksum
metadata_path = self.create_local_backup(consistency_check=True)
metadata = base.load_metadata(metadata_path)
# make a failing sha256 checksum (assuming no added path string)
bad_checksum = '0' * 64
# attempt to restore using the bad checksum
restore_args = ['freezer-agent',
'--action', 'restore',
'--restore-abs-path', self.dest_tree.path,
'--container', metadata['container'],
'--backup-name', self.backup_name,
'--storage', 'local',
'--consistency-checksum', bad_checksum]
process = subprocess.Popen(restore_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self.environ, shell=False)
out, err = process.communicate()
# make sure the subprocess exist with an error due to checksum mismatch
message = '{0} Output: {1} Error: {2}'.format(
'Restore process should fail with checksum error.',
out, err)
self.assertEqual(1, process.returncode, message)
self.assertEqual('', out, message)
self.assertNotEqual('', err, message)

View File

@ -1,100 +0,0 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 os
import shutil
from oslo_utils import uuidutils
from tempest.lib import decorators
from freezer.tests.freezer_tempest_plugin.tests.api import base
class TestFreezerSwiftBackup(base.BaseFreezerTest):
def __init__(self, *args, **kwargs):
super(TestFreezerSwiftBackup, self).__init__(*args, **kwargs)
def setUp(self):
super(TestFreezerSwiftBackup, self).setUp()
test_id = uuidutils.generate_uuid(dashed=False)
self.backup_source_dir = (
"/tmp/freezer-test-backup-source/" + test_id
)
self.backup_source_sub_dir = self.backup_source_dir + "/subdir"
self.restore_target_dir = (
"/tmp/freezer-test-backup-restore/" + test_id
)
self.freezer_container_name = 'freezer-test-container-0'
self.freezer_backup_name = 'freezer-test-backup-swift-0'
shutil.rmtree(self.backup_source_dir, True)
os.makedirs(self.backup_source_dir)
open(self.backup_source_dir + "/a", 'w').close()
open(self.backup_source_dir + "/b", 'w').close()
open(self.backup_source_dir + "/c", 'w').close()
os.makedirs(self.backup_source_sub_dir)
open(self.backup_source_sub_dir + "/x", 'w').close()
open(self.backup_source_sub_dir + "/y", 'w').close()
open(self.backup_source_sub_dir + "/z", 'w').close()
shutil.rmtree(self.restore_target_dir, True)
os.makedirs(self.restore_target_dir)
self.environ = super(TestFreezerSwiftBackup, self).get_environ()
def tearDown(self):
super(TestFreezerSwiftBackup, self).tearDown()
shutil.rmtree(self.backup_source_dir, True)
shutil.rmtree(self.restore_target_dir, True)
@decorators.attr(type="gate")
def test_freezer_swift_backup(self):
backup_args = ['freezer-agent',
'--path-to-backup',
self.backup_source_dir,
'--container',
self.freezer_container_name,
'--backup-name',
self.freezer_backup_name]
self.run_subprocess(backup_args, "Test backup to swift.")
restore_args = ['freezer-agent',
'--action',
'restore',
'--restore-abs-path',
self.restore_target_dir,
'--container',
self.freezer_container_name,
'--backup-name',
self.freezer_backup_name,
'--storage',
'swift']
self.run_subprocess(restore_args, "Test restore from swift.")
diff_args = ['diff',
'-r',
'-q',
self.backup_source_dir,
self.restore_target_dir]
self.run_subprocess(diff_args,
"Test backup to swift and restore diff.")

View File

@ -1,25 +0,0 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 tempest.lib import decorators
from freezer.tests.freezer_tempest_plugin.tests.api import base
class TestFreezerTestsRunning(base.BaseFreezerTest):
@decorators.attr(type="gate")
def test_tests_running(self):
# See if tempest plugin tests run.
self.assertEqual(1, 1, 'Tests are running')

View File

@ -1,305 +0,0 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 hashlib
import json
import os
import shutil
import tempfile
import time
from tempest.lib.cli import base as cli_base
from tempest.lib.cli import output_parser
from freezer.tests.freezer_tempest_plugin.tests.api import base
JOB_TABLE_RESULT_COLUMN = 3
class BaseFreezerCliTest(base.BaseFreezerTest):
"""Base test case class for all Freezer API tests."""
credentials = ['primary']
@classmethod
def setup_clients(cls):
super(BaseFreezerCliTest, cls).setup_clients()
cls.cli = CLIClientWithFreezer(
username=cls.os_primary.credentials.username,
# fails if the password contains an unescaped $ sign
password=cls.os_primary.credentials.password.replace('$', '$$'),
tenant_name=cls.os_primary.credentials.tenant_name,
uri=cls.get_auth_url(),
cli_dir='/usr/local/bin' # devstack default
)
cls.cli.cli_dir = ''
def delete_job(self, job_id):
self.cli.freezer_client(action='job-delete', params=job_id)
def create_job(self, job_json):
with tempfile.NamedTemporaryFile(delete=False) as job_file:
job_file.write(json.dumps(job_json))
job_file.flush()
output = self.cli.freezer_client(
action='job-create',
params='--file {} --client {}'.format(job_file.name,
job_json['client_id']))
job_id = output.split()[1]
expected = 'Job {} created'.format(job_id)
self.assertEqual(expected, output.strip())
self.addCleanup(self.delete_job, job_id)
return job_id
def find_job_in_job_list(self, job_id):
job_list = output_parser.table(
self.cli.freezer_client(action='job-list', params='-C test_node'))
for row in job_list['values']:
if row[0].strip() == job_id.strip():
return row
self.fail('Could not find job: {}'.format(job_id))
def wait_for_job_status(self, job_id, timeout=720):
start = time.time()
while True:
row = self.find_job_in_job_list(job_id)
if row[JOB_TABLE_RESULT_COLUMN]:
return
elif time.time() - start > timeout:
self.fail("Status of job '{}' is '{}'."
.format(job_id, row[JOB_TABLE_RESULT_COLUMN]))
else:
time.sleep(1)
def assertJobColumnEqual(self, job_id, column, expected):
row = self.find_job_in_job_list(job_id)
self.assertEqual(expected, row[column])
class CLIClientWithFreezer(cli_base.CLIClient):
def freezer_scheduler(self, action, flags='', params='', fail_ok=False,
endpoint_type='publicURL', merge_stderr=False):
"""Executes freezer-scheduler command for the given action.
:param action: the cli command to run using freezer-scheduler
:type action: string
:param flags: any optional cli flags to use
:type flags: string
:param params: any optional positional args to use :type params: string
:param fail_ok: if True an exception is not raised when the
cli return code is non-zero
:type fail_ok: boolean
:param endpoint_type: the type of endpoint for the service
:type endpoint_type: string
:param merge_stderr: if True the stderr buffer is merged into stdout
:type merge_stderr: boolean
"""
flags += ' --os-endpoint-type %s' % endpoint_type
flags += ' --os-cacert /etc/ssl/certs/ca-certificates.crt'
flags += ' --os-project-domain-name Default'
flags += ' --os-user-domain-name Default'
return self.cmd_with_auth(
'freezer-scheduler', action, flags, params, fail_ok, merge_stderr)
def freezer_client(self, action, flags='', params='', fail_ok=False,
endpoint_type='publicURL', merge_stderr=True):
flags += ' --os-endpoint-type %s' % endpoint_type
flags += ' --os-cacert /etc/ssl/certs/ca-certificates.crt'
flags += ' --os-project-domain-name Default'
flags += ' --os-user-domain-name Default'
return self.cmd_with_auth(
'freezer', action, flags, params, fail_ok, merge_stderr)
# This class is just copied from the freezer repo. Depending on where the
# scenario tests end up we may need to refactore this.
class Temp_Tree(object):
def __init__(self, suffix='', dir=None, create=True):
self.create = create
if create:
self.path = tempfile.mkdtemp(dir=dir, prefix='__freezer_',
suffix=suffix)
else:
self.path = dir
self.files = []
def __enter__(self):
return self
def cleanup(self):
if self.create and self.path:
shutil.rmtree(self.path)
def __exit__(self, exc_type, exc_val, exc_tb):
self.cleanup()
def add_random_data(self, ndir=5, nfile=5, size=1024):
"""
add some files containing randoma data
:param ndir: number of dirs to create
:param nfile: number of files to create in each dir
:param size: size of files
:return: None
"""
for x in range(ndir):
subdir_path = tempfile.mkdtemp(dir=self.path)
for y in range(nfile):
abs_pathname = self.create_file_with_random_data(
dir_path=subdir_path, size=size)
rel_path_name = abs_pathname[len(self.path) + 1:]
self.files.append(rel_path_name)
def create_file_with_random_data(self, dir_path, size=1024):
handle, abs_pathname = tempfile.mkstemp(dir=dir_path)
with open(abs_pathname, 'wb') as fd:
fd.write(os.urandom(size))
return abs_pathname
def get_file_hash(self, rel_filepath):
filepath = os.path.join(self.path, rel_filepath)
if os.path.isfile(filepath):
return self._filehash(filepath)
else:
return ''
def _filehash(self, filepath):
"""
Get GIT style sha1 hash for a file
:param filepath: path of file to hash
:return: hash of the file
"""
filesize_bytes = os.path.getsize(filepath)
hash_obj = hashlib.sha1()
hash_obj.update(("blob %u\0" % filesize_bytes).encode('utf-8'))
with open(filepath, 'rb') as handle:
hash_obj.update(handle.read())
return hash_obj.hexdigest()
def get_file_list(self):
"""
walks the dir tree and creates a list of relative pathnames
:return: list of relative file paths
"""
self.files = []
for root, dirs, files in os.walk(self.path):
rel_base = root[len(self.path) + 1:]
self.files.extend([os.path.join(rel_base, x) for x in files])
return self.files
def is_equal(self, other_tree):
"""
Checks whether two dir tree contain the same files
It checks the number of files and the hash of each file.
NOTE: tox puts .coverage files in the temp folder (?)
:param other_tree: dir tree to compare with
:return: true if the dir trees contain the same files
"""
lh_files = [x for x in sorted(self.get_file_list())
if not x.startswith('.coverage')]
rh_files = [x for x in sorted(other_tree.get_file_list())
if not x.startswith('.coverage')]
if lh_files != rh_files:
return False
for fname in lh_files:
if os.path.isfile(fname):
if self.get_file_hash(fname) != \
other_tree.get_file_hash(fname):
return False
return True
class TestFreezerScenario(BaseFreezerCliTest):
def setUp(self):
super(TestFreezerScenario, self).setUp()
self.source_tree = Temp_Tree()
self.source_tree.add_random_data()
self.dest_tree = Temp_Tree()
self.cli.freezer_scheduler(action='start',
flags='-c test_node '
'-f /tmp/freezer_tempest_job_dir/')
def tearDown(self):
super(TestFreezerScenario, self).tearDown()
self.source_tree.cleanup()
self.dest_tree.cleanup()
self.cli.freezer_scheduler(action='stop',
flags='-c test_node '
'-f /tmp/freezer_tempest_job_dir/')
def test_simple_backup(self):
backup_job = {
"client_id": "test_node",
"job_actions": [
{
"freezer_action": {
"action": "backup",
"mode": "fs",
"storage": "local",
"backup_name": "backup1",
"path_to_backup": self.source_tree.path,
"container": "/tmp/freezer_test/",
},
"max_retries": 3,
"max_retries_interval": 60
}
],
"description": "a test backup"
}
restore_job = {
"client_id": "test_node",
"job_actions": [
{
"freezer_action": {
"action": "restore",
"storage": "local",
"restore_abs_path": self.dest_tree.path,
"backup_name": "backup1",
"container": "/tmp/freezer_test/",
},
"max_retries": 3,
"max_retries_interval": 60
}
],
"description": "a test restore"
}
backup_job_id = self.create_job(backup_job)
self.cli.freezer_client(action='job-start', params=backup_job_id)
self.wait_for_job_status(backup_job_id)
self.assertJobColumnEqual(backup_job_id, JOB_TABLE_RESULT_COLUMN,
'success')
restore_job_id = self.create_job(restore_job)
self.wait_for_job_status(restore_job_id)
self.assertJobColumnEqual(restore_job_id, JOB_TABLE_RESULT_COLUMN,
'success')
self.assertTrue(self.source_tree.is_equal(self.dest_tree))

View File

@ -17,10 +17,10 @@ import subprocess
from tempest.lib import decorators
from freezer import __version__ as freezer_version
from freezer.tests.freezer_tempest_plugin.tests.api import base
from freezer.tests.integration import common
class TestFreezerVersion(base.BaseFreezerTest):
class TestFreezerVersion(common.TestFS):
@decorators.attr(type="gate")
def test_version(self):

View File

@ -35,6 +35,7 @@
enable_plugin freezer-api https://git.openstack.org/openstack/freezer-api
# enable freezer-web-ui and python-freezerclient
enable_plugin freezer-web-ui https://git.openstack.org/openstack/freezer-web-ui
TEMPEST_PLUGINS='/opt/stack/new/freezer-tempest-plugin'
EOF
executable: /bin/bash
@ -50,6 +51,7 @@
export PROJECTS="openstack/freezer-web-ui $PROJECTS"
export PROJECTS="openstack/freezer $PROJECTS"
export PROJECTS="openstack/python-freezerclient $PROJECTS"
export PROJECTS="openstack/neutron-tempest-plugin $PROJECTS"
# tempest config
export DEVSTACK_GATE_TEMPEST=1
export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1

View File

@ -35,6 +35,7 @@
enable_plugin freezer-api https://git.openstack.org/openstack/freezer-api
# enable freezer-web-ui and python-freezerclient
enable_plugin freezer-web-ui https://git.openstack.org/openstack/freezer-web-ui
TEMPEST_PLUGINS='/opt/stack/new/freezer-tempest-plugin'
EOF
executable: /bin/bash
@ -50,6 +51,7 @@
export PROJECTS="openstack/freezer-web-ui $PROJECTS"
export PROJECTS="openstack/freezer $PROJECTS"
export PROJECTS="openstack/python-freezerclient $PROJECTS"
export PROJECTS="openstack/freezer-tempest-plugin $PROJECTS"
# tempest config
export DEVSTACK_GATE_TEMPEST=1
export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1

View File

@ -35,6 +35,7 @@
enable_plugin freezer-api https://git.openstack.org/openstack/freezer-api
# enable freezer-web-ui and python-freezerclient
enable_plugin freezer-web-ui https://git.openstack.org/openstack/freezer-web-ui
TEMPEST_PLUGINS='/opt/stack/new/freezer-tempest-plugin'
EOF
executable: /bin/bash
@ -50,6 +51,7 @@
export PROJECTS="openstack/freezer-web-ui $PROJECTS"
export PROJECTS="openstack/freezer $PROJECTS"
export PROJECTS="openstack/python-freezerclient $PROJECTS"
export PROJECTS="openstack/freezer-tempest-plugin $PROJECTS"
# tempest config
export DEVSTACK_GATE_TEMPEST=1
export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1

View File

@ -63,8 +63,6 @@ oslo.config.opts =
console_scripts =
freezer-scheduler = freezer.scheduler.freezer_scheduler:main
freezer-agent = freezer.main:main
tempest.test_plugins =
freezer_tempest_tests = freezer.tests.freezer_tempest_plugin.plugin:FreezerTempestPlugin
[pbr]
@ -72,4 +70,4 @@ tempest.test_plugins =
autodoc_index_modules = True
api_doc_dir = reference/api
autodoc_exclude_modules =
freezer.scheduler.win*
freezer.scheduler.win*