First scenario test
This test performs a backup followed by a restore and compares the restored data with the original data. This code was previously in the freezer-api repo. It fits better into the freezer repo because: - It interacts mostly with freezer-scheduler - The freezer devstack gate also includes the freezer-api so it is easier to run an end-to-end test Moved from change: 319351 Implements bp: freezer-integration-tests Change-Id: I74448e75905d2d950a5d0483a9fe27a212f7fe21
This commit is contained in:
parent
569334f6fb
commit
8b8c766ed4
|
@ -13,9 +13,18 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
set -ex
|
||||
set -x
|
||||
|
||||
echo "Start Gate Hook"
|
||||
|
||||
# Install freezer devstack integration
|
||||
export DEVSTACK_LOCAL_CONFIG="enable_plugin freezer https://git.openstack.org/openstack/freezer"
|
||||
export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin freezer-api https://git.openstack.org/openstack/freezer-api"
|
||||
# Swift is needed for some of the integration tests
|
||||
export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_service s-proxy s-object s-container s-account"
|
||||
|
||||
$BASE/new/devstack-gate/devstack-vm-gate.sh
|
||||
|
||||
# Copy log file so it will be collected by the CI system
|
||||
sudo cp /home/tempest/.freezer/freezer.log $BASE/logs/
|
||||
|
||||
echo "End Gate Hook"
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
<<<<<<< HEAD
|
||||
==============================
|
||||
Tempest Integration of Freezer
|
||||
==============================
|
||||
=======
|
||||
Freezer Tempest Tests
|
||||
=====================
|
||||
>>>>>>> First scenario test
|
||||
|
||||
This directory contains Tempest tests to cover the freezer project.
|
||||
Integration tests in freezer are implemented using tempest. There are typically two approaches to run these tests in a development environment:
|
||||
|
||||
<<<<<<< HEAD
|
||||
Instructions for Running/Developing Tempest Tests with Freezer Project
|
||||
|
||||
#. Need to make sure that there is a Devstack or other environment for running Keystone and Swift.
|
||||
|
@ -112,3 +118,142 @@ 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 all tests inside a devstack VM
|
||||
* Run the tests outside of a devstack VM (e.g. in PyCharm) but use services (keystone, swift, ...) inside a VM
|
||||
|
||||
For both approaches one needs a devstack VM with freezer an swift running.
|
||||
|
||||
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
|
||||
-------------------------------
|
||||
|
||||
#. 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 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`
|
||||
|
||||
|
||||
>>>>>>> First scenario test
|
||||
|
|
|
@ -24,24 +24,36 @@ class BaseFreezerTest(tempest.test.BaseTestCase):
|
|||
|
||||
super(BaseFreezerTest, self).__init__(*args, **kwargs)
|
||||
|
||||
def setUp(self):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseFreezerTest, self).setUp()
|
||||
|
||||
self.get_environ()
|
||||
|
||||
def tearDown(self):
|
||||
|
||||
super(BaseFreezerTest, self).tearDown()
|
||||
|
||||
def get_environ(self):
|
||||
os.environ['OS_PASSWORD'] = self.os.credentials.password
|
||||
os.environ['OS_USERNAME'] = self.os.credentials.username
|
||||
os.environ['OS_PROJECT_NAME'] = self.os.credentials.tenant_name
|
||||
os.environ['OS_TENANT_NAME'] = self.os.credentials.tenant_name
|
||||
@classmethod
|
||||
def get_auth_url(cls):
|
||||
return cls.os_primary.auth_provider.auth_client.auth_url[:-len('/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
|
||||
|
||||
# Allow developers to set OS_AUTH_URL when developing so that
|
||||
# Keystone may be on a host other than localhost.
|
||||
if not 'OS_AUTH_URL' in os.environ:
|
||||
os.environ['OS_AUTH_URL'] = 'http://localhost:5000/v2.0'
|
||||
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']
|
||||
|
@ -68,4 +80,4 @@ class BaseFreezerTest(tempest.test.BaseTestCase):
|
|||
|
||||
self.assertEqual('', err,
|
||||
fail_message + " Output: {0}. "
|
||||
"Error: {1}".format(out, err))
|
||||
"Error: {1}".format(out, err))
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
# (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 re
|
||||
import time
|
||||
|
||||
import subprocess
|
||||
|
||||
from tempest import test
|
||||
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 = 6
|
||||
JOB_TABLE_STATUS_COLUMN = 4
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
def delete_job(self, job_id):
|
||||
self.cli.freezer_scheduler(action='job-delete', flags='-c test_node -j {}'.format(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_scheduler(action='job-create', flags='-c test_node --file {}'.format(job_file.name))
|
||||
self.assertTrue(output.startswith('Created job'))
|
||||
job_id = output[len('Created job '):]
|
||||
|
||||
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_scheduler(action='job-list', flags='-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, status, timeout=360):
|
||||
start = time.time()
|
||||
|
||||
while True:
|
||||
row = self.find_job_in_job_list(job_id)
|
||||
|
||||
if row[JOB_TABLE_STATUS_COLUMN] == status:
|
||||
return
|
||||
elif time.time() - start > timeout:
|
||||
self.fail("Status of job '{}' is '{}'. Expected '{}'".format(job_id, row[JOB_TABLE_STATUS_COLUMN], status))
|
||||
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
|
||||
return self.cmd_with_auth(
|
||||
'freezer-scheduler', 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')
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def test_simple_backup(self):
|
||||
backup_job = {
|
||||
"job_actions": [
|
||||
{
|
||||
"freezer_action": {
|
||||
"action": "backup",
|
||||
"mode": "fs",
|
||||
"backup_name": "backup1",
|
||||
"path_to_backup": self.source_tree.path,
|
||||
"container": "freezer_test",
|
||||
},
|
||||
"max_retries": 3,
|
||||
"max_retries_interval": 60
|
||||
}
|
||||
],
|
||||
"description": "a test backup"
|
||||
}
|
||||
restore_job = {
|
||||
"job_actions": [
|
||||
{
|
||||
"freezer_action": {
|
||||
"action": "restore",
|
||||
"restore_abs_path": self.dest_tree.path,
|
||||
"backup_name": "backup1",
|
||||
"container": "freezer_test",
|
||||
},
|
||||
"max_retries": 3,
|
||||
"max_retries_interval": 60
|
||||
}
|
||||
],
|
||||
"description": "a test restore"
|
||||
}
|
||||
|
||||
backup_job_id = self.create_job(backup_job)
|
||||
self.wait_for_job_status(backup_job_id, 'completed')
|
||||
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, 'completed')
|
||||
self.assertJobColumnEqual(restore_job_id, JOB_TABLE_RESULT_COLUMN, 'success')
|
||||
|
||||
|
||||
self.assertTrue(self.source_tree.is_equal(self.dest_tree))
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue