Tobiko does not customize images during run time
Before this patch, tobiko could customize provided guest images, such as ubuntu images. This has been problematic recently, especially since tobiko is executed on pods, because there have been issues to run virt-customize with tobiko. With this patch, tobiko tests expect an already customized guest image included in its tobiko.conf. If the tobiko ansible roles are used, by default they download an ubuntu image and customize it, before executing tobiko tests. Some parameter have been deprecated from tobiko.conf (python level), such as `interface_name` and `customized_image_provided`. Note: `interface_name` is still a valid ansible parameter because ansible roles still customize ubuntu images. Change-Id: Icde5fa5951dde79fec94a1c47366cc018785b1ed
This commit is contained in:
parent
22503c28a2
commit
b2103cbec9
doc/source/_static
releasenotes/notes
roles
tobiko/openstack
@ -38,9 +38,6 @@
|
||||
# Default cirros password (string value)
|
||||
#password = <None>
|
||||
|
||||
# Default cirros interface name (string value)
|
||||
#interface_name = <None>
|
||||
|
||||
# Default cirros SSH connection timeout (seconds) (floating point value)
|
||||
#connection_timeout = <None>
|
||||
|
||||
@ -537,9 +534,6 @@
|
||||
# Default ubuntu password (string value)
|
||||
#password = <None>
|
||||
|
||||
# Default ubuntu interface name (string value)
|
||||
#interface_name = <None>
|
||||
|
||||
# Default ubuntu SSH connection timeout (seconds) (floating point value)
|
||||
#connection_timeout = <None>
|
||||
|
||||
|
@ -0,0 +1,15 @@
|
||||
---
|
||||
deprecations:
|
||||
- |
|
||||
CustomizedGlanceImageFixture class is not supported anymore.
|
||||
This means the Ubuntu image provided via tobiko.conf, which could be customized during test execution in previous tobiko versions,
|
||||
has to include all the expected customizations.
|
||||
When using the tobiko ansible roles, the tobiko-download-images roles performes the required customizations by default.
|
||||
The URL to download an image without any customizations and the command that are executed during the opendev jobs can be found here:
|
||||
https://opendev.org/x/tobiko/src/commit/eb83ebe860dfb4206b346a822675c01f8ba82ccf/roles/tobiko-common/defaults/main.yaml#L62
|
||||
|
||||
- |
|
||||
Due to the deprecation of the CustomizedGlanceImageFixture class, some configuration parameters have been deprecated at image level.
|
||||
|
||||
* `interface_name` is not supported anymore because tobiko does not need to create a VLAN over that interface (the customized image already includes it).
|
||||
* `customized_image_provided` boolean is not supported anymore because the provided images were previously customized.
|
@ -56,6 +56,7 @@ test_log_file: '{{ test_report_dir | realpath }}/tobiko.log'
|
||||
# Local where test cases results are being collected to
|
||||
test_collect_dir: '{{ test_src_dir | realpath }}/{{ test_report_name }}'
|
||||
|
||||
|
||||
# --- download-images options -------------------------------------------------
|
||||
download_images_dir: "{{ ansible_user_dir }}/.downloaded-images"
|
||||
download_images:
|
||||
@ -72,5 +73,10 @@ download_images:
|
||||
--run-command 'systemctl enable iperf3-server@5201'
|
||||
--run-command 'echo "8021q" >> /etc/modules'
|
||||
--run-command
|
||||
'echo "network:\n version: 2\n vlans:\n vlan101:\n dhcp4: true\n dhcp4-overrides:\n use-routes: false\n dhcp6: true\n dhcp6-overrides:\n use-routes: false\n id: 101\n link: {{ ubuntu_nic_name | default('ens3') }}\n"
|
||||
'echo "network:\n version: 2\n vlans:\n vlan101:\n dhcp4: true\n dhcp4-overrides:\n use-routes: false\n dhcp6: true\n dhcp6-overrides:\n use-routes: false\n id: 101\n link: {{ ubuntu_nic_name }}\n"
|
||||
> /etc/netplan/75-tobiko-vlan.yaml'
|
||||
|
||||
# NIC name expected on Ubuntu VM instances.
|
||||
# Depending on the openstack version, it has been observed the Ubuntu VM
|
||||
# instances are created with an interface named 'ens3' or 'enp3s0'
|
||||
ubuntu_nic_name: 'ens3'
|
||||
|
@ -25,17 +25,7 @@
|
||||
- section: "{{ dict_value.type }}"
|
||||
option: image_url
|
||||
value: "file://{{ download_images_dir }}/{{ file_name }}"
|
||||
{% if dict_value.customized | default(False) | bool or dict_value.customize_command_pattern is defined %}
|
||||
- section: "{{ dict_value.type }}"
|
||||
option: customized_image_provided
|
||||
value: True
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if ubuntu_nic_name is defined %}
|
||||
- section: ubuntu
|
||||
option: interface_name
|
||||
value: "{{ ubuntu_nic_name }}"
|
||||
{% endif %}
|
||||
|
||||
vars:
|
||||
sections: "{{ test_default_conf | combine(test_conf, recursive=True) }}"
|
||||
|
@ -31,7 +31,6 @@ delete_image = _client.delete_image
|
||||
GlanceImageFixture = _image.GlanceImageFixture
|
||||
HasImageMixin = _image.HasImageMixin
|
||||
UrlGlanceImageFixture = _image.UrlGlanceImageFixture
|
||||
CustomizedGlanceImageFixture = _image.CustomizedGlanceImageFixture
|
||||
|
||||
open_image_file = _io.open_image_file
|
||||
|
||||
|
@ -18,8 +18,8 @@ import io
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
import typing # noqa
|
||||
from abc import ABC, abstractmethod
|
||||
import typing
|
||||
from abc import ABC
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from oslo_log import log
|
||||
@ -30,7 +30,6 @@ from tobiko.config import get_bool_env
|
||||
from tobiko.openstack.glance import _client
|
||||
from tobiko.openstack.glance import _io
|
||||
from tobiko.openstack import keystone
|
||||
from tobiko.shell import sh
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -347,11 +346,7 @@ class UrlGlanceImageFixture(UploadGlanceImageFixture, ABC):
|
||||
# else, download the image
|
||||
return self.get_image_from_url(real_image_file)
|
||||
|
||||
def customize_image_file(self, base_file: str) -> str:
|
||||
return base_file
|
||||
|
||||
def get_image_from_file(self, image_file: str):
|
||||
image_file = self.customize_image_file(base_file=image_file)
|
||||
image_size = os.path.getsize(image_file)
|
||||
LOG.debug('Uploading image %r data from file %r (%d bytes)',
|
||||
self.image_name, image_file, image_size)
|
||||
@ -418,114 +413,6 @@ class UrlGlanceImageFixture(UploadGlanceImageFixture, ABC):
|
||||
os.rename(temp_file, image_file)
|
||||
|
||||
|
||||
class CustomizedGlanceImageFixture(UrlGlanceImageFixture, ABC):
|
||||
|
||||
@property
|
||||
def firstboot_commands(self) -> typing.List[str]:
|
||||
return []
|
||||
|
||||
@property
|
||||
def install_packages(self) -> typing.List[str]:
|
||||
return []
|
||||
|
||||
@property
|
||||
def run_commands(self) -> typing.List[str]:
|
||||
return []
|
||||
|
||||
@property
|
||||
def write_files(self) -> typing.Dict[str, str]:
|
||||
return {}
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def customization_required(self) -> bool:
|
||||
pass
|
||||
|
||||
username: str = ''
|
||||
password: str = ''
|
||||
|
||||
def _get_customized_suffix(self) -> str:
|
||||
return 'customized'
|
||||
|
||||
def customize_image_file(self, base_file: str) -> str:
|
||||
def workaround_passt(full_command, exc):
|
||||
which_passt = sh.execute(
|
||||
'which passt', expect_exit_status=None).stdout.rstrip()
|
||||
if which_passt == '':
|
||||
raise exc
|
||||
|
||||
cmd = f'mv {which_passt} {which_passt}.bak'
|
||||
cmd_cleanup = f'mv {which_passt}.bak {which_passt}'
|
||||
tobiko.add_cleanup(sh.execute, cmd_cleanup,
|
||||
expect_exit_status=None, sudo=True)
|
||||
LOG.exception("Executing virt-customize without passt")
|
||||
sh.execute(cmd, sudo=True)
|
||||
sh.execute(full_command)
|
||||
|
||||
# if the image does not have to be customized, then do nothing
|
||||
if not self.customization_required:
|
||||
return base_file
|
||||
|
||||
customized_file = f'{base_file}-{self._get_customized_suffix()}'
|
||||
if os.path.isfile(customized_file):
|
||||
if (os.stat(base_file).st_mtime_ns <
|
||||
os.stat(customized_file).st_mtime_ns):
|
||||
LOG.debug(f"Image file is up to date '{customized_file}'")
|
||||
return customized_file
|
||||
else:
|
||||
LOG.debug(f"Remove obsolete image file '{customized_file}'")
|
||||
os.remove(customized_file)
|
||||
work_file = sh.execute('mktemp').stdout.strip()
|
||||
try:
|
||||
LOG.debug(f"Copy base image file: '{base_file}' to '{work_file}'")
|
||||
sh.put_file(base_file, work_file)
|
||||
|
||||
options = self.get_virt_customize_options()
|
||||
if options:
|
||||
command = sh.shell_command(['LIBGUESTFS_BACKEND=direct',
|
||||
'virt-customize',
|
||||
'-a',
|
||||
work_file])
|
||||
try:
|
||||
sh.execute(command + options)
|
||||
except sh.ShellCommandFailed as exc:
|
||||
workaround_passt(command + options, exc)
|
||||
|
||||
sh.get_file(work_file, customized_file)
|
||||
return customized_file
|
||||
finally:
|
||||
sh.execute(['rm', '-f', work_file])
|
||||
|
||||
def get_virt_customize_options(self) -> sh.ShellCommand:
|
||||
options = sh.ShellCommand()
|
||||
|
||||
firstboot_commands = self.firstboot_commands
|
||||
if firstboot_commands:
|
||||
for cmd in firstboot_commands:
|
||||
options += ['--firstboot-command', cmd]
|
||||
|
||||
install_packages = self.install_packages
|
||||
if install_packages:
|
||||
options += ['--install', ','.join(install_packages)]
|
||||
|
||||
username = self.username
|
||||
password = self.password
|
||||
if username and password:
|
||||
options += f'--password "{username}:password:{password}"'
|
||||
|
||||
run_commands = self.run_commands
|
||||
if run_commands:
|
||||
for cmd in run_commands:
|
||||
options += ['--run-command', cmd]
|
||||
|
||||
write_files = self.write_files
|
||||
if write_files:
|
||||
for filename, content in write_files.items():
|
||||
options += ['--write', f'{filename}:{content}']
|
||||
|
||||
return options
|
||||
|
||||
|
||||
class InvalidGlanceImageStatus(tobiko.TobikoException):
|
||||
message = ("Invalid image {image_name!r} (id {image_id!r}) status: "
|
||||
"{actual_status!r} not in {expected_status!r}")
|
||||
|
@ -58,9 +58,6 @@ def get_images_options():
|
||||
help="Default " + name + " username"),
|
||||
cfg.StrOpt('password',
|
||||
help="Default " + name + " password"),
|
||||
cfg.StrOpt('interface_name',
|
||||
default=None if name != 'ubuntu' else 'ens3',
|
||||
help="Default " + name + " interface name"),
|
||||
cfg.FloatOpt('connection_timeout',
|
||||
default=None,
|
||||
help=("Default " + name +
|
||||
@ -70,10 +67,6 @@ def get_images_options():
|
||||
help=("Allow to disable SSH auth algorithms"
|
||||
"in order to SSH to old servers like"
|
||||
"CirrOS ones")),
|
||||
cfg.BoolOpt('customized_image_provided',
|
||||
default=False,
|
||||
help=("Whether the provided image (URL or file) is"
|
||||
"already customized or not"))
|
||||
]
|
||||
)]
|
||||
|
||||
|
@ -13,8 +13,6 @@
|
||||
# under the License.
|
||||
from __future__ import absolute_import
|
||||
|
||||
import typing
|
||||
|
||||
import tobiko
|
||||
from tobiko import config
|
||||
from tobiko.openstack import glance
|
||||
@ -41,7 +39,7 @@ DefaultInstance=5201
|
||||
"""
|
||||
|
||||
|
||||
class UbuntuImageFixture(glance.CustomizedGlanceImageFixture):
|
||||
class UbuntuImageFixture(glance.UrlGlanceImageFixture):
|
||||
"""Ubuntu server image running an HTTP server
|
||||
|
||||
The server has additional installed packages compared to
|
||||
@ -68,30 +66,6 @@ class UbuntuImageFixture(glance.CustomizedGlanceImageFixture):
|
||||
disabled_algorithms = CONF.tobiko.ubuntu.disabled_algorithms
|
||||
is_reachable_timeout = CONF.tobiko.nova.ubuntu_is_reachable_timeout
|
||||
|
||||
def __init__(self,
|
||||
ethernet_devide: str = None,
|
||||
**kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._ethernet_device = ethernet_devide
|
||||
|
||||
@property
|
||||
def customization_required(self) -> bool:
|
||||
return not CONF.tobiko.ubuntu.customized_image_provided
|
||||
|
||||
@property
|
||||
def firstboot_commands(self) -> typing.List[str]:
|
||||
return super().firstboot_commands + [
|
||||
'sh -c "hostname > /var/www/html/id"']
|
||||
|
||||
@property
|
||||
def install_packages(self) -> typing.List[str]:
|
||||
return super().install_packages + ['iperf3',
|
||||
'iputils-ping',
|
||||
'nano',
|
||||
'ncat',
|
||||
'nginx',
|
||||
'vlan']
|
||||
|
||||
# port of running HTTP server
|
||||
http_port = 80
|
||||
|
||||
@ -102,22 +76,6 @@ class UbuntuImageFixture(glance.CustomizedGlanceImageFixture):
|
||||
def iperf3_service_name(self) -> str:
|
||||
return f"iperf3-server@{self.iperf3_port}.service"
|
||||
|
||||
@property
|
||||
def run_commands(self) -> typing.List[str]:
|
||||
run_commands = super().run_commands
|
||||
run_commands.append(
|
||||
f'echo "{IPERF3_SERVICE_FILE}" '
|
||||
'> /etc/systemd/system/iperf3-server@.service')
|
||||
run_commands.append(
|
||||
f"systemctl enable iperf3-server@{self.iperf3_port}")
|
||||
run_commands.append('echo "8021q" >> /etc/modules')
|
||||
return run_commands
|
||||
|
||||
ethernet_device = CONF.tobiko.ubuntu.interface_name
|
||||
|
||||
def _get_customized_suffix(self) -> str:
|
||||
return f'{super()._get_customized_suffix()}-{self.ethernet_device}'
|
||||
|
||||
@property
|
||||
def vlan_id(self) -> int:
|
||||
return tobiko.tobiko_config().neutron.vlan_id
|
||||
@ -126,35 +84,6 @@ class UbuntuImageFixture(glance.CustomizedGlanceImageFixture):
|
||||
def vlan_device(self) -> str:
|
||||
return f'vlan{self.vlan_id}'
|
||||
|
||||
@property
|
||||
def vlan_config(self) -> typing.Dict[str, typing.Any]:
|
||||
return {
|
||||
'network': {
|
||||
'version': 2,
|
||||
'vlans': {
|
||||
self.vlan_device: {
|
||||
'link': self.ethernet_device,
|
||||
'dhcp4': True,
|
||||
'dhcp4-overrides': {
|
||||
'use-routes': False
|
||||
},
|
||||
'dhcp6': True,
|
||||
'dhcp6-overrides': {
|
||||
'use-routes': False
|
||||
},
|
||||
'id': self.vlan_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@property
|
||||
def write_files(self) -> typing.Dict[str, str]:
|
||||
write_files = super().write_files
|
||||
write_files['/etc/netplan/75-tobiko-vlan.yaml'] = tobiko.dump_yaml(
|
||||
self.vlan_config)
|
||||
return write_files
|
||||
|
||||
|
||||
class UbuntuFlavorStackFixture(_nova.FlavorStackFixture):
|
||||
ram = 256
|
||||
|
Loading…
x
Reference in New Issue
Block a user