Add functional tests

This commit is contained in:
Liam Young 2019-03-04 10:41:28 +00:00
parent 4e36a904c4
commit c7930ba595
6 changed files with 437 additions and 107 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ build/
func-results.json
test-charm/
interfaces
__pycache__

View File

@ -1,29 +1,2 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
charm-tools>=2.4.4
coverage>=3.6
mock>=1.2
flake8>=2.2.4,<=2.4.1
os-testr>=0.4.1
requests>=2.18.4
# BEGIN: Amulet OpenStack Charm Helper Requirements
# Liberty client lower constraints
amulet>=1.14.3,<2.0;python_version=='2.7'
bundletester>=0.6.1,<1.0;python_version=='2.7'
python-ceilometerclient>=1.5.0
python-cinderclient>=1.4.0
python-glanceclient>=1.1.0
python-heatclient>=0.8.0
python-keystoneclient>=1.7.1
python-neutronclient>=3.1.0
python-novaclient>=2.30.1
python-openstackclient>=1.7.0
python-swiftclient>=2.6.0
pika>=0.10.0,<1.0
distro-info
git+https://github.com/juju/charm-helpers.git#egg=charmhelpers
# END: Amulet OpenStack Charm Helper Requirements
# NOTE: workaround for 14.04 pip/tox
pytz
pyudev # for ceph-* charm unit tests (not mocked?)
# zaza
git+https://github.com/openstack-charmers/zaza.git#egg=zaza

View File

@ -0,0 +1,170 @@
series: bionic
relations:
- - nova-compute:amqp
- rabbitmq-server:amqp
- - neutron-gateway:amqp
- rabbitmq-server:amqp
- - neutron-gateway:amqp-nova
- rabbitmq-server:amqp
- - keystone:shared-db
- mysql:shared-db
- - cinder:identity-service
- keystone:identity-service
- - nova-cloud-controller:identity-service
- keystone:identity-service
- - glance:identity-service
- keystone:identity-service
- - neutron-api:identity-service
- keystone:identity-service
- - neutron-openvswitch:neutron-plugin-api
- neutron-api:neutron-plugin-api
- - cinder:shared-db
- mysql:shared-db
- - neutron-api:shared-db
- mysql:shared-db
- - cinder:amqp
- rabbitmq-server:amqp
- - neutron-api:amqp
- rabbitmq-server:amqp
- - neutron-gateway:neutron-plugin-api
- neutron-api:neutron-plugin-api
- - glance:shared-db
- mysql:shared-db
- - glance:amqp
- rabbitmq-server:amqp
- - nova-cloud-controller:image-service
- glance:image-service
- - nova-compute:image-service
- glance:image-service
- - nova-cloud-controller:amqp
- rabbitmq-server:amqp
- - nova-cloud-controller:quantum-network-service
- neutron-gateway:quantum-network-service
- - nova-compute:neutron-plugin
- neutron-openvswitch:neutron-plugin
- - neutron-openvswitch:amqp
- rabbitmq-server:amqp
- - nova-cloud-controller:shared-db
- mysql:shared-db
- - nova-cloud-controller:neutron-api
- neutron-api:neutron-api
- - nova-cloud-controller:cloud-compute
- nova-compute:cloud-compute
- - masakari:shared-db
- mysql:shared-db
- - masakari:amqp
- rabbitmq-server:amqp
- - masakari:identity-service
- keystone:identity-service
- - glance:ceph
- ceph-mon:client
- - ceph-mon:osd
- ceph-osd:mon
- - cinder:storage-backend
- cinder-ceph:storage-backend
- - cinder-ceph:ceph
- ceph-mon:client
- - cinder-ceph:ceph-access
- nova-compute:ceph-access
- - nova-compute:juju-info
- masakari-monitors:container
- - nova-compute:juju-info
- hacluster:juju-info
- - keystone:identity-credentials
- masakari-monitors:identity-credentials
applications:
glance:
charm: cs:~openstack-charmers-next/glance
num_units: 1
options:
openstack-origin: cloud:bionic-rocky
worker-multiplier: 0.25
cinder:
charm: cs:~openstack-charmers-next/cinder
num_units: 1
options:
block-device: "None"
glance-api-version: 2
keystone:
charm: cs:~gnuoy/keystone-36
series: bionic
num_units: 1
options:
admin-password: openstack
openstack-origin: cloud:bionic-rocky
worker-multiplier: 0.25
mysql:
charm: cs:~openstack-charmers-next/percona-cluster
num_units: 1
options:
innodb-buffer-pool-size: 256M
max-connections: 1000
neutron-api:
charm: cs:~openstack-charmers-next/neutron-api
num_units: 1
options:
flat-network-providers: physnet1
neutron-security-groups: true
openstack-origin: cloud:bionic-rocky
worker-multiplier: 0.25
neutron-gateway:
charm: cs:~openstack-charmers-next/neutron-gateway
num_units: 1
options:
bridge-mappings: physnet1:br-ex
openstack-origin: cloud:bionic-rocky
worker-multiplier: 0.25
neutron-openvswitch:
charm: cs:~openstack-charmers-next/neutron-openvswitch
num_units: 0
nova-cloud-controller:
charm: cs:~openstack-charmers-next/nova-cloud-controller
num_units: 1
options:
network-manager: Neutron
openstack-origin: cloud:bionic-rocky
worker-multiplier: 0.25
debug: true
nova-compute:
charm: cs:~openstack-charmers-next/nova-compute
num_units: 3
constraints: mem=4G
options:
config-flags: default_ephemeral_format=ext4
enable-live-migration: true
enable-resize: true
migration-auth-type: ssh
openstack-origin: cloud:bionic-rocky
debug: true
cpu-model: kvm64
cpu-mode: custom
rabbitmq-server:
charm: cs:~openstack-charmers-next/rabbitmq-server
num_units: 1
masakari:
charm: cs:~gnuoy/masakari-2
series: bionic
num_units: 1
options:
openstack-origin: cloud:bionic-rocky/proposed
ceph-mon:
charm: ceph-mon
num_units: 3
options:
expected-osd-count: 3
ceph-osd:
charm: ceph-osd
constraints: mem=1G
num_units: 3
storage:
osd-devices: cinder,40G
cinder-ceph:
charm: cinder-ceph
masakari-monitors:
charm: masakari-monitors
series: bionic
hacluster:
charm: cs:~gnuoy/hacluster-6
options:
corosync_transport: unicast
cluster_count: 3

13
src/tests/tests.yaml Normal file
View File

@ -0,0 +1,13 @@
charm_name: masakari-monitors
tests:
- tests.tests_masakari.MasakariTest
configure:
- zaza.charm_tests.glance.setup.add_cirros_image
- zaza.charm_tests.glance.setup.add_lts_image
- zaza.charm_tests.neutron.setup.basic_overcloud_network
- zaza.charm_tests.nova.setup.create_flavors
- zaza.charm_tests.nova.setup.manage_ssh_key
gate_bundles:
- bionic-rocky
smoke_bundles:
- bionic-rocky

230
src/tests/tests_masakari.py Normal file
View File

@ -0,0 +1,230 @@
#!/usr/bin/env python3
# Copyright 2019 Canonical Ltd.
#
# 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.
"""Encapsulate cinder-ns5 testing."""
import logging
import tenacity
import time
from openstack import connection
import zaza.model
import zaza.charm_tests.test_utils as test_utils
import zaza.utilities.openstack as openstack_utils
import zaza.charm_tests.nova.utils as nova_utils
class MasakariTest(test_utils.OpenStackBaseTest):
"""Encapsulate NS5 tests."""
@classmethod
def setUpClass(cls):
"""Run class setup for running tests."""
super(MasakariTest, cls).setUpClass()
cls.keystone_session = openstack_utils.get_overcloud_keystone_session()
cls.model_name = zaza.model.get_juju_model()
cls.cinder_client = openstack_utils.get_cinder_session_client(
cls.keystone_session)
cls.nova_client = openstack_utils.get_nova_session_client(
cls.keystone_session)
cls.glance_client = openstack_utils.get_glance_session_client(
cls.keystone_session)
cls.neutron_client = openstack_utils.get_neutron_session_client(
cls.keystone_session)
conn = connection.Connection(session=cls.keystone_session,
interface='public',
region_name='RegionOne')
cls.masakari_client = conn.instance_ha
def launch_instance(self, instance_key, use_boot_volume=False,
vm_name=None):
"""Launch an instance.
:param instance_key: Key to collect associated config data with.
:type instance_key: str
"""
# Collect resource information.
vm_name = vm_name or time.strftime("%Y%m%d%H%M%S")
image = self.nova_client.glance.find_image(
instance_key)
flavor = self.nova_client.flavors.find(
name='m1.small')
net = self.neutron_client.find_resource("network", "private")
nics = [{'net-id': net.get('id')}]
if use_boot_volume:
bdmv2 = [{
'boot_index': '0',
'uuid': image.id,
'source_type': 'image',
'volume_size': flavor.disk,
'destination_type': 'volume',
'delete_on_termination': True}]
image = None
# Launch instance.
logging.info('Launching instance {}'.format(vm_name))
instance = self.nova_client.servers.create(
name=vm_name,
image=image,
block_device_mapping_v2=bdmv2,
flavor=flavor,
key_name=nova_utils.KEYPAIR_NAME,
nics=nics)
# Test Instance is ready.
logging.info('Checking instance is active')
openstack_utils.resource_reaches_status(
self.nova_client.servers,
instance.id,
expected_status='ACTIVE')
logging.info('Checking cloud init is complete')
openstack_utils.cloud_init_complete(
self.nova_client,
instance.id,
'finished at')
port = openstack_utils.get_ports_from_device_id(
self.neutron_client,
instance.id)[0]
logging.info('Assigning floating ip.')
ip = openstack_utils.create_floating_ip(
self.neutron_client,
"ext_net",
port=port)['floating_ip_address']
logging.info('Assigned floating IP {} to {}'.format(ip, vm_name))
openstack_utils.ping_response(ip)
# Check ssh'ing to instance.
# logging.info('Testing ssh access.')
# openstack_utils.ssh_test(
# username='ubuntu',
# ip=ip,
# vm_name=vm_name,
# password=None,
# privkey=openstack_utils.get_private_key(nova_utils.KEYPAIR_NAME))
def configure(self):
try:
self.masakari_client.create_segment(
name='seg1',
recovery_method='auto',
service_type='COMPUTE')
hypervisors = self.nova_client.hypervisors.list()
segment_ids = [s.uuid for s in self.masakari_client.segments()]
segment_ids = segment_ids * len(hypervisors)
for hypervisor in hypervisors:
target_segment = segment_ids.pop()
hostname = hypervisor.hypervisor_hostname.split('.')[0]
self.masakari_client.create_host(
name=hostname,
segment_id=target_segment,
recovery_method='auto',
control_attributes='SSH',
type='COMPUTE')
except:
pass
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60),
reraise=True, stop=tenacity.stop_after_attempt(80))
def wait_for_server_migration(self, vm_name, original_hypervisor):
server = self.nova_client.servers.find(name=vm_name)
current_hypervisor = getattr(server, 'OS-EXT-SRV-ATTR:host')
logging.info('{} is on {} in state {}'.format(
vm_name,
current_hypervisor,
server.status))
assert (original_hypervisor != current_hypervisor and
server.status == 'ACTIVE')
logging.info('SUCCESS {} has migrated to {}'.format(
vm_name,
current_hypervisor))
def svc_control(self, unit_name, action, services):
logging.info('{} {} on {}'.format(action.title(), services, unit_name))
cmds = []
for svc in services:
cmds.append("systemctl {} {}".format(action, svc))
zaza.model.run_on_unit(
unit_name, command=';'.join(cmds),
model_name=self.model_name)
def enable_the_things(self):
logging.info("Enabling all the things")
# Start corosync et al
for u in zaza.model.get_units(application_name='nova-compute'):
self.svc_control(
u.entity_id,
'start',
['corosync', 'pacemaker', 'nova-compute'])
# Enable nova-compute in nova
for svc in self.nova_client.services.list():
if svc.status == 'disabled':
logging.info("Enabling {} on {}".format(svc.binary, svc.host))
self.nova_client.services.enable(svc.host, svc.binary)
# Enable nova-compute in masakari
for segment in self.masakari_client.segments():
for host in self.masakari_client.hosts(segment_id=segment.uuid):
if host.on_maintenance:
logging.info("Removing maintenance mode from masakari "
"host {}".format(host.uuid))
self.masakari_client.update_host(
host.uuid,
segment_id=segment.uuid,
**{'on_maintenance': False})
def test_instance_failover(self):
self.configure()
# Launch guest
vm_name = 'zaza_test_instance_failover'
try:
server = self.nova_client.servers.find(name=vm_name)
logging.info('Found existing guest')
except:
logging.info('Launching new guest')
self.launch_instance(
'bionic',
use_boot_volume=True,
vm_name=vm_name)
server = self.nova_client.servers.find(name=vm_name)
logging.info('Finding hosting hypervisor')
server = self.nova_client.servers.find(name=vm_name)
current_hypervisor = getattr(server, 'OS-EXT-SRV-ATTR:host')
logging.info('Simulate compute node shutdown')
server = self.nova_client.servers.find(name=vm_name)
guest_hypervisor = getattr(server, 'OS-EXT-SRV-ATTR:host')
hypervisor_machine_number = guest_hypervisor.split('-')[-1]
unit_name = [
u.entity_id
for u in zaza.model.get_units(application_name='nova-compute')
if u.data['machine-id'] == hypervisor_machine_number][0]
# Simulate shutdown
self.svc_control(
unit_name,
'stop',
['corosync', 'pacemaker', 'nova-compute'])
# Wait for instance move
self.wait_for_server_migration(vm_name, current_hypervisor)
# Bring things back
self.enable_the_things()

View File

@ -1,92 +1,35 @@
# Classic charm: ./tox.ini
# This file is managed centrally by release-tools and should not be modified
# within individual charm repos.
[tox]
envlist = pep8,py27,py35
envlist = pep8
skipsdist = True
[testenv]
setenv = VIRTUAL_ENV={envdir}
PYTHONHASHSEED=0
CHARM_DIR={envdir}
AMULET_SETUP_TIMEOUT=5400
whitelist_externals = juju
passenv = HOME TERM CS_API_* OS_* AMULET_*
deps = -r{toxinidir}/test-requirements.txt
install_command =
pip install {opts} {packages}
commands = ostestr {posargs}
whitelist_externals = juju
passenv = HOME TERM AMULET_* CS_API_*
[testenv:py27]
basepython = python2.7
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = /bin/true
[testenv:py35]
basepython = python3.5
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
[testenv:py36]
basepython = python3.6
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
[testenv:pep8]
basepython = python3
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = flake8 {posargs} hooks unit_tests tests actions lib
charm-proof
deps=charm-tools
commands = charm-proof
[testenv:func-noop]
basepython = python3
commands =
true
[testenv:func]
basepython = python3
commands =
functest-run-suite --keep-model
[testenv:func-smoke]
basepython = python3
commands =
functest-run-suite --keep-model --smoke
[testenv:venv]
basepython = python3
commands = {posargs}
[testenv:func27-noop]
# DRY RUN - For Debug
basepython = python2.7
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "gate-*" -n --no-destroy
[testenv:func27]
# Charm Functional Test
# Run all gate tests which are +x (expected to always pass)
basepython = python2.7
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "gate-*" --no-destroy
[testenv:func27-smoke]
# Charm Functional Test
# Run a specific test as an Amulet smoke test (expected to always pass)
basepython = python2.7
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
bundletester -vl DEBUG -r json -o func-results.json gate-basic-bionic-rocky --no-destroy
[testenv:func27-dfs]
# Charm Functional Test
# Run all deploy-from-source tests which are +x (may not always pass!)
basepython = python2.7
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "dfs-*" --no-destroy
[testenv:func27-dev]
# Charm Functional Test
# Run all development test targets which are +x (may not always pass!)
basepython = python2.7
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
bundletester -vl DEBUG -r json -o func-results.json --test-pattern "dev-*" --no-destroy
[flake8]
ignore = E402,E226
exclude = */charmhelpers