Merge "Add CirrOS based HTTP server stack"
This commit is contained in:
commit
521a28b4dc
@ -39,6 +39,7 @@ CirrosSameHostServerStackFixture = _cirros.CirrosSameHostServerStackFixture
|
||||
RebootCirrosServerOperation = _cirros.RebootCirrosServerOperation
|
||||
EvacuableCirrosImageFixture = _cirros.EvacuableCirrosImageFixture
|
||||
EvacuableServerStackFixture = _cirros.EvacuableServerStackFixture
|
||||
CirrosHttpServerStackFixture = _cirros.CirrosHttpServerStackFixture
|
||||
|
||||
RedHatFlavorStackFixture = _redhat.RedHatFlavorStackFixture
|
||||
RhelImageFixture = _redhat.RhelImageFixture
|
||||
|
@ -93,3 +93,23 @@ class EvacuableServerStackFixture(CirrosServerStackFixture):
|
||||
class CirrosExternalServerStackFixture(CirrosServerStackFixture,
|
||||
_nova.ExternalServerStackFixture):
|
||||
pass
|
||||
|
||||
|
||||
class CirrosHttpServerStackFixture(CirrosPeerServerStackFixture,
|
||||
_nova.HttpServerStackFixture):
|
||||
|
||||
@property
|
||||
def user_data(self):
|
||||
# Launch a webserver on port 80 that replies the server name to the
|
||||
# client
|
||||
# This webserver relies on the nc command which may fail if multiple
|
||||
# clients connect at the same time. For concurrency testing,
|
||||
# OctaviaCentosServerStackFixture is more suited to handle multiple
|
||||
# requests.
|
||||
reply = ("HTTP/1.1 200 OK\r\n"
|
||||
"Content-Length:8\r\n"
|
||||
"\r\n"
|
||||
"$(hostname)")
|
||||
command = f'nc -lk -p {self.http_server_port} -e echo -e "{reply}"'
|
||||
return ("#!/bin/sh\n"
|
||||
f"{command}")
|
||||
|
@ -15,21 +15,25 @@
|
||||
# under the License.
|
||||
from __future__ import absolute_import
|
||||
|
||||
import abc
|
||||
import os
|
||||
import typing # noqa
|
||||
import typing
|
||||
|
||||
import netaddr
|
||||
import six
|
||||
from oslo_log import log
|
||||
|
||||
import tobiko
|
||||
from tobiko import config
|
||||
from tobiko.openstack import glance
|
||||
from tobiko.openstack import heat
|
||||
from tobiko.openstack import neutron
|
||||
from tobiko.openstack import nova
|
||||
from tobiko.openstack.stacks import _hot
|
||||
from tobiko.openstack.stacks import _neutron
|
||||
from tobiko.shell import ssh
|
||||
from tobiko.shell import curl
|
||||
from tobiko.shell import sh
|
||||
from tobiko.shell import ssh
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
@ -81,7 +85,7 @@ class FlavorStackFixture(heat.HeatStackFixture):
|
||||
|
||||
|
||||
@neutron.skip_if_missing_networking_extensions('port-security')
|
||||
class ServerStackFixture(heat.HeatStackFixture):
|
||||
class ServerStackFixture(heat.HeatStackFixture, abc.ABC):
|
||||
|
||||
#: Heat template file
|
||||
template = _hot.heat_template_file('nova/server.yaml')
|
||||
@ -92,8 +96,10 @@ class ServerStackFixture(heat.HeatStackFixture):
|
||||
#: stack with the internal where the server port is created
|
||||
network_stack = tobiko.required_setup_fixture(_neutron.NetworkStackFixture)
|
||||
|
||||
#: Glance image used to create a Nova server instance
|
||||
image_fixture = None
|
||||
@property
|
||||
def image_fixture(self) -> glance.GlanceImageFixture:
|
||||
"""Glance image used to create a Nova server instance"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_stack(self, stack_id=None):
|
||||
if self._outputs:
|
||||
@ -101,28 +107,30 @@ class ServerStackFixture(heat.HeatStackFixture):
|
||||
super(ServerStackFixture, self).delete_stack(stack_id=stack_id)
|
||||
|
||||
@property
|
||||
def image(self):
|
||||
def image(self) -> str:
|
||||
return self.image_fixture.image_id
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
def username(self) -> str:
|
||||
"""username used to login to a Nova server instance"""
|
||||
return self.image_fixture.username
|
||||
return self.image_fixture.username or 'root'
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
def password(self) -> typing.Optional[str]:
|
||||
"""password used to login to a Nova server instance"""
|
||||
return self.image_fixture.password
|
||||
|
||||
@property
|
||||
def connection_timeout(self):
|
||||
def connection_timeout(self) -> tobiko.Seconds:
|
||||
return self.image_fixture.connection_timeout
|
||||
|
||||
# Stack used to create flavor for Nova server instance
|
||||
flavor_stack = None
|
||||
@property
|
||||
def flavor_stack(self) -> FlavorStackFixture:
|
||||
"""stack used to create flavor for Nova server instance"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def flavor(self):
|
||||
def flavor(self) -> str:
|
||||
"""Flavor for Nova server instance"""
|
||||
return self.flavor_stack.flavor_id
|
||||
|
||||
@ -130,23 +138,23 @@ class ServerStackFixture(heat.HeatStackFixture):
|
||||
port_security_enabled = False
|
||||
|
||||
#: Security groups to be associated to network ports
|
||||
security_groups = [] # type: typing.List[str]
|
||||
security_groups: typing.List[str] = []
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
def key_name(self) -> str:
|
||||
return self.key_pair_stack.key_name
|
||||
|
||||
@property
|
||||
def network(self):
|
||||
def network(self) -> str:
|
||||
return self.network_stack.network_id
|
||||
|
||||
#: Floating IP network where the Neutron floating IP are created
|
||||
@property
|
||||
def floating_network(self):
|
||||
def floating_network(self) -> str:
|
||||
return self.network_stack.floating_network
|
||||
|
||||
@property
|
||||
def has_floating_ip(self):
|
||||
def has_floating_ip(self) -> bool:
|
||||
return bool(self.floating_network)
|
||||
|
||||
@property
|
||||
@ -157,17 +165,42 @@ class ServerStackFixture(heat.HeatStackFixture):
|
||||
connection_timeout=self.connection_timeout)
|
||||
|
||||
@property
|
||||
def ssh_command(self):
|
||||
def ssh_command(self) -> sh.ShellCommand:
|
||||
return ssh.ssh_command(host=self.ip_address,
|
||||
username=self.username)
|
||||
|
||||
@property
|
||||
def ip_address(self):
|
||||
def ip_address(self) -> str:
|
||||
if self.has_floating_ip:
|
||||
return self.floating_ip_address
|
||||
else:
|
||||
return self.outputs.fixed_ips[0]['ip_address']
|
||||
|
||||
def list_fixed_ips(self, ip_version: typing.Optional[int] = None) -> \
|
||||
tobiko.Selection[netaddr.IPAddress]:
|
||||
fixed_ips: tobiko.Selection[netaddr.IPAddress] = tobiko.Selection(
|
||||
netaddr.IPAddress(fixed_ip['ip_address'])
|
||||
for fixed_ip in self.fixed_ips)
|
||||
if ip_version is not None:
|
||||
fixed_ips.with_attributes(version=ip_version)
|
||||
return fixed_ips
|
||||
|
||||
def find_fixed_ip(self, ip_version: typing.Optional[int] = None,
|
||||
unique=False) -> netaddr.IPAddress:
|
||||
fixed_ips = self.list_fixed_ips(ip_version=ip_version)
|
||||
if unique:
|
||||
return fixed_ips.unique
|
||||
else:
|
||||
return fixed_ips.first
|
||||
|
||||
@property
|
||||
def fixed_ipv4(self):
|
||||
return self.find_fixed_ip(ip_version=4)
|
||||
|
||||
@property
|
||||
def fixed_ipv6(self):
|
||||
return self.find_fixed_ip(ip_version=6)
|
||||
|
||||
#: Schedule on different host that this Nova server instance ID
|
||||
different_host = None
|
||||
|
||||
@ -305,7 +338,8 @@ class ServerStackFixture(heat.HeatStackFixture):
|
||||
"method not implemented")
|
||||
|
||||
|
||||
class ExternalServerStackFixture(ServerStackFixture):
|
||||
class ExternalServerStackFixture(ServerStackFixture, abc.ABC):
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
#: stack with the network where the server port is created
|
||||
network_stack = tobiko.required_setup_fixture(
|
||||
@ -321,17 +355,19 @@ class ExternalServerStackFixture(ServerStackFixture):
|
||||
return self.network_stack.network_id
|
||||
|
||||
|
||||
class PeerServerStackFixture(ServerStackFixture):
|
||||
class PeerServerStackFixture(ServerStackFixture, abc.ABC):
|
||||
"""Server witch networking access requires passing by another Nova server
|
||||
"""
|
||||
|
||||
has_floating_ip = False
|
||||
|
||||
#: Peer server used to reach this one
|
||||
peer_stack = None
|
||||
@property
|
||||
def peer_stack(self) -> ServerStackFixture:
|
||||
"""Peer server used to reach this one"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def ssh_client(self):
|
||||
def ssh_client(self) -> ssh.SSHClientFixture:
|
||||
return ssh.ssh_client(host=self.ip_address,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
@ -339,7 +375,7 @@ class PeerServerStackFixture(ServerStackFixture):
|
||||
proxy_jump=self.peer_stack.ssh_client)
|
||||
|
||||
@property
|
||||
def ssh_command(self):
|
||||
def ssh_command(self) -> sh.ShellCommand:
|
||||
proxy_command = self.peer_stack.ssh_command + [
|
||||
'nc', self.ip_address, '22']
|
||||
return ssh.ssh_command(host=self.ip_address,
|
||||
@ -347,19 +383,20 @@ class PeerServerStackFixture(ServerStackFixture):
|
||||
proxy_command=proxy_command)
|
||||
|
||||
@property
|
||||
def network(self):
|
||||
def network(self) -> str:
|
||||
return self.peer_stack.network
|
||||
|
||||
|
||||
@nova.skip_if_missing_hypervisors(count=2, state='up', status='enabled')
|
||||
class DifferentHostServerStackFixture(PeerServerStackFixture):
|
||||
class DifferentHostServerStackFixture(PeerServerStackFixture, abc.ABC):
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
@property
|
||||
def different_host(self):
|
||||
return [self.peer_stack.server_id]
|
||||
|
||||
|
||||
class SameHostServerStackFixture(PeerServerStackFixture):
|
||||
class SameHostServerStackFixture(PeerServerStackFixture, abc.ABC):
|
||||
|
||||
@property
|
||||
def same_host(self):
|
||||
@ -373,6 +410,43 @@ def as_str(text):
|
||||
return text.decode()
|
||||
|
||||
|
||||
class HttpServerStackFixture(PeerServerStackFixture, abc.ABC):
|
||||
|
||||
http_server_port = 80
|
||||
|
||||
http_request_scheme = 'http'
|
||||
http_request_path = ''
|
||||
|
||||
def send_http_request(
|
||||
self,
|
||||
hostname: typing.Union[str, netaddr.IPAddress, None] = None,
|
||||
ip_version: typing.Optional[int] = None,
|
||||
port: typing.Optional[int] = None,
|
||||
path: typing.Optional[str] = None,
|
||||
retry_count: typing.Optional[int] = None,
|
||||
retry_timeout: tobiko.Seconds = None,
|
||||
retry_interval: tobiko.Seconds = None,
|
||||
ssh_client: typing.Optional[ssh.SSHClientFixture] = None,
|
||||
**curl_parameters) -> str:
|
||||
if hostname is None:
|
||||
hostname = self.find_fixed_ip(ip_version=ip_version)
|
||||
if port is None:
|
||||
port = self.http_server_port
|
||||
if path is None:
|
||||
path = self.http_request_path
|
||||
if ssh_client is None:
|
||||
ssh_client = self.peer_stack.ssh_client
|
||||
return curl.execute_curl(scheme='http',
|
||||
hostname=hostname,
|
||||
port=port,
|
||||
path=path,
|
||||
retry_count=retry_count,
|
||||
retry_timeout=retry_timeout,
|
||||
retry_interval=retry_interval,
|
||||
ssh_client=ssh_client,
|
||||
**curl_parameters)
|
||||
|
||||
|
||||
class ServerGroupStackFixture(heat.HeatStackFixture):
|
||||
template = _hot.heat_template_file('nova/server_group.yaml')
|
||||
|
||||
|
@ -25,6 +25,7 @@ from tobiko.openstack import keystone
|
||||
from tobiko.openstack import neutron
|
||||
from tobiko.openstack import nova
|
||||
from tobiko.openstack import stacks
|
||||
from tobiko.shell import curl
|
||||
from tobiko.shell import ping
|
||||
from tobiko.shell import sh
|
||||
|
||||
@ -38,11 +39,28 @@ class CirrosServerStackTest(testtools.TestCase):
|
||||
|
||||
nameservers_filenames: typing.Optional[typing.Sequence[str]] = None
|
||||
|
||||
def test_ping(self):
|
||||
def test_ping_floating_ip(self):
|
||||
"""Test connectivity to floating IP address"""
|
||||
ping.ping_until_received(
|
||||
self.stack.floating_ip_address).assert_replied()
|
||||
|
||||
def test_ping_fixed_ipv4(self):
|
||||
ping.ping_until_received(
|
||||
self.get_fixed_ip(ip_version=4),
|
||||
ssh_client=self.stack.ssh_client).assert_replied()
|
||||
|
||||
def test_ping_fixed_ipv6(self):
|
||||
ping.ping_until_received(
|
||||
self.get_fixed_ip(ip_version=6),
|
||||
ssh_client=self.stack.ssh_client).assert_replied()
|
||||
|
||||
def get_fixed_ip(self, ip_version: int):
|
||||
try:
|
||||
return self.stack.find_fixed_ip(ip_version=ip_version)
|
||||
except tobiko.ObjectNotFound:
|
||||
self.skipTest(f"Server {self.stack.server_id} has any "
|
||||
f"IPv{ip_version} address.")
|
||||
|
||||
def test_ssh_connect(self):
|
||||
"""Test SSH connectivity via Paramiko SSHClient"""
|
||||
self.stack.ssh_client.connect()
|
||||
@ -135,3 +153,56 @@ class EvacuablesServerStackTest(CirrosServerStackTest):
|
||||
def test_image_tags(self):
|
||||
image = self.stack.image_fixture.get_image()
|
||||
self.assertEqual(['evacuable'], image.tags)
|
||||
|
||||
|
||||
class CirrosPeerServerStackTest(CirrosServerStackTest):
|
||||
|
||||
#: Stack of resources with an HTTP server
|
||||
stack = tobiko.required_setup_fixture(stacks.CirrosPeerServerStackFixture)
|
||||
|
||||
def test_ping_floating_ip(self):
|
||||
self.skipTest(f"Server '{self.stack.server_id}' has any floating IP")
|
||||
|
||||
def test_ping_fixed_ipv4(self):
|
||||
ping.ping_until_received(
|
||||
self.get_fixed_ip(ip_version=4),
|
||||
ssh_client=self.stack.peer_stack.ssh_client).assert_replied()
|
||||
|
||||
def test_ping_fixed_ipv6(self):
|
||||
ping.ping_until_received(
|
||||
self.get_fixed_ip(ip_version=6),
|
||||
ssh_client=self.stack.peer_stack.ssh_client).assert_replied()
|
||||
|
||||
|
||||
class HttpServerStackTest(CirrosPeerServerStackTest):
|
||||
|
||||
#: Stack of resources with an HTTP server
|
||||
stack = tobiko.required_setup_fixture(stacks.CirrosHttpServerStackFixture)
|
||||
|
||||
def test_server_port_ipv4(self):
|
||||
self._test_server_port(ip_version=4)
|
||||
|
||||
def test_server_port_ipv6(self):
|
||||
self._test_server_port(ip_version=6)
|
||||
|
||||
def _test_server_port(self, ip_version: int):
|
||||
scheme = self.stack.http_request_scheme
|
||||
ip_address = self.get_fixed_ip(ip_version=ip_version)
|
||||
port = self.stack.http_server_port
|
||||
ssh_client = self.stack.peer_stack.ssh_client
|
||||
reply = curl.execute_curl(scheme=scheme,
|
||||
hostname=ip_address,
|
||||
port=port,
|
||||
ssh_client=ssh_client,
|
||||
connect_timeout=5.,
|
||||
retry_count=10,
|
||||
retry_timeout=60.)
|
||||
self.assertEqual(self.stack.server_name, reply)
|
||||
|
||||
def test_send_http_request_ipv4(self):
|
||||
reply = self.stack.send_http_request(ip_version=4)
|
||||
self.assertEqual(self.stack.server_name, reply)
|
||||
|
||||
def test_send_http_request_ipv6(self):
|
||||
reply = self.stack.send_http_request(ip_version=6)
|
||||
self.assertEqual(self.stack.server_name, reply)
|
||||
|
Loading…
x
Reference in New Issue
Block a user