Merge "Add scenario test for fip port_details"
diff --git a/.zuul.yaml b/.zuul.yaml
index 25714a2..cc1b61f 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -117,10 +117,11 @@
devstack_localrc:
PHYSICAL_NETWORK: default
DOWNLOAD_DEFAULT_IMAGES: false
- IMAGE_URLS: "http://cloud-images.ubuntu.com/releases/16.04/release-20170113/ubuntu-16.04-server-cloudimg-amd64-disk1.img,"
+ IMAGE_URLS: "http://cloud-images.ubuntu.com/releases/16.04/release-20180622/ubuntu-16.04-server-cloudimg-amd64-disk1.img,"
DEFAULT_INSTANCE_TYPE: ds512M
DEFAULT_INSTANCE_USER: ubuntu
BUILD_TIMEOUT: 784
+ LIBVIRT_TYPE: kvm
devstack_services:
cinder: true
diff --git a/neutron_tempest_plugin/api/test_networks.py b/neutron_tempest_plugin/api/test_networks.py
index 7e9943d..c4b3596 100644
--- a/neutron_tempest_plugin/api/test_networks.py
+++ b/neutron_tempest_plugin/api/test_networks.py
@@ -209,6 +209,7 @@
def test_list_no_pagination_limit_0(self):
self._test_list_no_pagination_limit_0()
+ @decorators.skip_because(bug="1749820")
@decorators.idempotent_id('3574ec9b-a8b8-43e3-9c11-98f5875df6a9')
def test_list_validation_filters(self):
self._test_list_validation_filters()
diff --git a/neutron_tempest_plugin/api/test_subnetpools.py b/neutron_tempest_plugin/api/test_subnetpools.py
index ec3753a..8adbc4c 100644
--- a/neutron_tempest_plugin/api/test_subnetpools.py
+++ b/neutron_tempest_plugin/api/test_subnetpools.py
@@ -414,6 +414,7 @@
def test_list_no_pagination_limit_0(self):
self._test_list_no_pagination_limit_0()
+ @decorators.skip_because(bug="1749820")
@decorators.idempotent_id('27feb3f8-40f4-4e50-8cd2-7d0096a98682')
def test_list_validation_filters(self):
self._test_list_validation_filters()
diff --git a/neutron_tempest_plugin/api/test_subnets.py b/neutron_tempest_plugin/api/test_subnets.py
index fb2f4d6..b7a1b21 100644
--- a/neutron_tempest_plugin/api/test_subnets.py
+++ b/neutron_tempest_plugin/api/test_subnets.py
@@ -64,6 +64,7 @@
def test_list_no_pagination_limit_0(self):
self._test_list_no_pagination_limit_0()
+ @decorators.skip_because(bug="1749820")
@decorators.idempotent_id('c0f9280b-9d81-4728-a967-6be22659d4c8')
def test_list_validation_filters(self):
self._test_list_validation_filters()
diff --git a/neutron_tempest_plugin/common/ssh.py b/neutron_tempest_plugin/common/ssh.py
index b919b65..99f731c 100644
--- a/neutron_tempest_plugin/common/ssh.py
+++ b/neutron_tempest_plugin/common/ssh.py
@@ -12,13 +12,103 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os
+
+from oslo_log import log
from tempest.lib.common import ssh
from neutron_tempest_plugin import config
+CONF = config.CONF
+LOG = log.getLogger(__name__)
+
+
class Client(ssh.Client):
- def __init__(self, *args, **kwargs):
- if 'timeout' not in kwargs:
- kwargs['timeout'] = config.CONF.validation.ssh_timeout
- super(Client, self).__init__(*args, **kwargs)
+
+ timeout = CONF.validation.ssh_timeout
+
+ proxy_jump_host = CONF.neutron_plugin_options.ssh_proxy_jump_host
+ proxy_jump_username = CONF.neutron_plugin_options.ssh_proxy_jump_username
+ proxy_jump_password = CONF.neutron_plugin_options.ssh_proxy_jump_password
+ proxy_jump_keyfile = CONF.neutron_plugin_options.ssh_proxy_jump_keyfile
+ proxy_jump_port = CONF.neutron_plugin_options.ssh_proxy_jump_port
+
+ def __init__(self, host, username, password=None, timeout=None, pkey=None,
+ channel_timeout=10, look_for_keys=False, key_filename=None,
+ port=22, proxy_client=None):
+
+ timeout = timeout or self.timeout
+
+ if self.proxy_jump_host:
+ # Perform all SSH connections passing through configured SSH server
+ proxy_client = proxy_client or self.create_proxy_client(
+ timeout=timeout, channel_timeout=channel_timeout)
+
+ super(Client, self).__init__(
+ host=host, username=username, password=password, timeout=timeout,
+ pkey=pkey, channel_timeout=channel_timeout,
+ look_for_keys=look_for_keys, key_filename=key_filename, port=port,
+ proxy_client=proxy_client)
+
+ @classmethod
+ def create_proxy_client(cls, look_for_keys=True, **kwargs):
+ host = cls.proxy_jump_host
+ if not host:
+ # proxy_jump_host string cannot be empty or None
+ raise ValueError(
+ "'proxy_jump_host' configuration option is empty.")
+
+ # Let accept an empty string as a synonymous of default value on below
+ # options
+ password = cls.proxy_jump_password or None
+ key_file = cls.proxy_jump_keyfile or None
+ username = cls.proxy_jump_username
+
+ # Port must be a positive integer
+ port = cls.proxy_jump_port
+ if port <= 0 or port > 65535:
+ raise ValueError(
+ "Invalid value for 'proxy_jump_port' configuration option: "
+ "{!r}".format(port))
+
+ login = "{username}@{host}:{port}".format(username=username, host=host,
+ port=port)
+
+ if key_file:
+ # expand ~ character with user HOME directory
+ key_file = os.path.expanduser(key_file)
+ if os.path.isfile(key_file):
+ LOG.debug("Going to create SSH connection to %r using key "
+ "file: %s", login, key_file)
+
+ else:
+ # This message could help the user to identify a
+ # mis-configuration in tempest.conf
+ raise ValueError(
+ "Cannot find file specified as 'proxy_jump_keyfile' "
+ "option: {!r}".format(key_file))
+
+ elif password:
+ LOG.debug("Going to create SSH connection to %r using password.",
+ login)
+
+ elif look_for_keys:
+ # This message could help the user to identify a mis-configuration
+ # in tempest.conf
+ LOG.info("Both 'proxy_jump_password' and 'proxy_jump_keyfile' "
+ "options are empty. Going to create SSH connection to %r "
+ "looking for key file location into %r directory.",
+ login, os.path.expanduser('~/.ssh'))
+ else:
+ # An user that forces look_for_keys=False should really know what
+ # he really wants
+ LOG.warning("No authentication method provided to create an SSH "
+ "connection to %r. If it fails, then please "
+ "set 'proxy_jump_keyfile' to provide a valid SSH key "
+ "file.", login)
+
+ return ssh.Client(
+ host=host, username=username, password=password,
+ look_for_keys=look_for_keys, key_filename=key_file,
+ port=port, proxy_client=None, **kwargs)
diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py
index fc07e81..e15748d 100644
--- a/neutron_tempest_plugin/config.py
+++ b/neutron_tempest_plugin/config.py
@@ -56,7 +56,25 @@
'"provider:network_type":<TYPE> - string '
'"mtu":<MTU> - integer '
'"cidr"<SUBNET/MASK> - string '
- '"provider:segmentation_id":<VLAN_ID> - integer')
+ '"provider:segmentation_id":<VLAN_ID> - integer'),
+
+ # Option for feature to connect via SSH to VMs using an intermediate SSH
+ # server
+ cfg.StrOpt('ssh_proxy_jump_host',
+ default=None,
+ help='Proxy jump host used to connect via SSH to VMs..'),
+ cfg.StrOpt('ssh_proxy_jump_username',
+ default='root',
+ help='User name used to connect to "ssh_proxy_jump_host".'),
+ cfg.StrOpt('ssh_proxy_jump_password',
+ default=None,
+ help='Password used to connect to "ssh_proxy_jump_host".'),
+ cfg.StrOpt('ssh_proxy_jump_keyfile',
+ default=None,
+ help='Keyfile used to connect to "ssh_proxy_jump_host".'),
+ cfg.IntOpt('ssh_proxy_jump_port',
+ default=22,
+ help='Port used to connect to "ssh_proxy_jump_host".'),
]
# TODO(amuller): Redo configuration options registration as part of the planned
diff --git a/neutron_tempest_plugin/scenario/test_floatingip.py b/neutron_tempest_plugin/scenario/test_floatingip.py
index ae9ac11..504af12 100644
--- a/neutron_tempest_plugin/scenario/test_floatingip.py
+++ b/neutron_tempest_plugin/scenario/test_floatingip.py
@@ -289,7 +289,8 @@
class FloatingIPQosTest(FloatingIpTestCasesMixin,
- test_qos.QoSTest):
+ test_qos.QoSTestMixin,
+ base.BaseTempestTestCase):
same_network = True
diff --git a/neutron_tempest_plugin/scenario/test_migration.py b/neutron_tempest_plugin/scenario/test_migration.py
index 5e081f1..f4b918c 100644
--- a/neutron_tempest_plugin/scenario/test_migration.py
+++ b/neutron_tempest_plugin/scenario/test_migration.py
@@ -67,6 +67,19 @@
device_owner),
timeout=300, sleep=5)
+ def _wait_until_router_ports_down(self, router_id):
+
+ def _is_port_down(port_id):
+ port = self.os_admin.network_client.show_port(port_id).get('port')
+ return port['status'] == const.DOWN
+
+ ports = self.os_admin.network_client.list_ports(
+ device_id=router_id).get('ports')
+ for port in ports:
+ common_utils.wait_until_true(
+ functools.partial(_is_port_down, port['id']),
+ timeout=300, sleep=5)
+
def _is_port_active(self, router_id, device_owner):
ports = self.os_admin.network_client.list_ports(
device_id=router_id,
@@ -120,6 +133,8 @@
self.os_admin.network_client.update_router(
router_id=router['id'], admin_state_up=False)
+ self._wait_until_router_ports_down(router['id'])
+
self.os_admin.network_client.update_router(
router_id=router['id'], distributed=after_dvr, ha=after_ha)
self._check_update(router, after_dvr, after_ha)
diff --git a/neutron_tempest_plugin/scenario/test_mtu.py b/neutron_tempest_plugin/scenario/test_mtu.py
index 0e3afe9..dbfde9b 100644
--- a/neutron_tempest_plugin/scenario/test_mtu.py
+++ b/neutron_tempest_plugin/scenario/test_mtu.py
@@ -19,6 +19,7 @@
from tempest.common import waiters
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+import testtools
from neutron_tempest_plugin.common import ssh
from neutron_tempest_plugin import config
@@ -118,6 +119,9 @@
self.keypair['private_key'])
return server_ssh_client1, fip1, server_ssh_client2, fip2
+ @testtools.skipUnless(
+ CONF.neutron_plugin_options.image_is_advanced,
+ "Advanced image is required to run this test.")
@decorators.idempotent_id('3d73ec1a-2ec6-45a9-b0f8-04a273d9d344')
def test_connectivity_min_max_mtu(self):
server_ssh_client, _, _, fip2 = self._create_setup()
@@ -207,6 +211,9 @@
self.keypair['private_key'])
return server_ssh_client1, fip1, server_ssh_client2, fip2
+ @testtools.skipUnless(
+ CONF.neutron_plugin_options.image_is_advanced,
+ "Advanced image is required to run this test.")
@decorators.idempotent_id('bc470200-d8f4-4f07-b294-1b4cbaaa35b9')
def test_connectivity_min_max_mtu(self):
server_ssh_client, _, _, fip2 = self._create_setup()
diff --git a/neutron_tempest_plugin/scenario/test_qos.py b/neutron_tempest_plugin/scenario/test_qos.py
index 0611160..702bbaa 100644
--- a/neutron_tempest_plugin/scenario/test_qos.py
+++ b/neutron_tempest_plugin/scenario/test_qos.py
@@ -66,7 +66,7 @@
port=port)
-class QoSTest(base.BaseTempestTestCase):
+class QoSTestMixin(object):
credentials = ['primary', 'admin']
force_tenant_isolation = False
@@ -81,22 +81,16 @@
NC_PORT = 1234
- @classmethod
- @tutils.requires_ext(extension="qos", service="network")
- @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
- def resource_setup(cls):
- super(QoSTest, cls).resource_setup()
-
def _create_file_for_bw_tests(self, ssh_client):
cmd = ("(dd if=/dev/zero bs=%(bs)d count=%(count)d of=%(file_path)s) "
- % {'bs': QoSTest.BS, 'count': QoSTest.COUNT,
- 'file_path': QoSTest.FILE_PATH})
+ % {'bs': QoSTestMixin.BS, 'count': QoSTestMixin.COUNT,
+ 'file_path': QoSTestMixin.FILE_PATH})
ssh_client.exec_command(cmd)
- cmd = "stat -c %%s %s" % QoSTest.FILE_PATH
+ cmd = "stat -c %%s %s" % QoSTestMixin.FILE_PATH
filesize = ssh_client.exec_command(cmd)
- if int(filesize.strip()) != QoSTest.FILE_SIZE:
+ if int(filesize.strip()) != QoSTestMixin.FILE_SIZE:
raise sc_exceptions.FileCreationFailedException(
- file=QoSTest.FILE_PATH)
+ file=QoSTestMixin.FILE_PATH)
def _check_bw(self, ssh_client, host, port):
cmd = "killall -q nc"
@@ -105,15 +99,15 @@
except exceptions.SSHExecCommandFailed:
pass
cmd = ("(nc -ll -p %(port)d < %(file_path)s > /dev/null &)" % {
- 'port': port, 'file_path': QoSTest.FILE_PATH})
+ 'port': port, 'file_path': QoSTestMixin.FILE_PATH})
ssh_client.exec_command(cmd)
start_time = time.time()
client_socket = _connect_socket(host, port)
total_bytes_read = 0
- while total_bytes_read < QoSTest.FILE_SIZE:
- data = client_socket.recv(QoSTest.BUFFER_SIZE)
+ while total_bytes_read < QoSTestMixin.FILE_SIZE:
+ data = client_socket.recv(QoSTestMixin.BUFFER_SIZE)
total_bytes_read += len(data)
time_elapsed = time.time() - start_time
@@ -126,7 +120,7 @@
'total_bytes_read': total_bytes_read,
'bytes_per_second': bytes_per_second})
- return bytes_per_second <= QoSTest.LIMIT_BYTES_SEC
+ return bytes_per_second <= QoSTestMixin.LIMIT_BYTES_SEC
def _create_ssh_client(self):
return ssh.Client(self.fip['floating_ip_address'],
@@ -153,6 +147,14 @@
shared=True)
return policy['policy']['id']
+
+class QoSTest(QoSTestMixin, base.BaseTempestTestCase):
+ @classmethod
+ @tutils.requires_ext(extension="qos", service="network")
+ @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
+ def resource_setup(cls):
+ super(QoSTest, cls).resource_setup()
+
@decorators.idempotent_id('1f7ed39b-428f-410a-bd2b-db9f465680df')
def test_qos(self):
"""This is a basic test that check that a QoS policy with
diff --git a/requirements.txt b/requirements.txt
index 2ecce4e..5660c68 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,7 @@
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
-neutron-lib>=1.13.0 # Apache-2.0
+neutron-lib>=1.18.0 # Apache-2.0
oslo.config>=5.2.0 # Apache-2.0
ipaddress>=1.0.17;python_version<'3.3' # PSF
netaddr>=0.7.18 # BSD