diff --git a/config.yml b/config.yml index cf7497bce..e46893d22 100644 --- a/config.yml +++ b/config.yml @@ -34,6 +34,9 @@ node-scripts-location: 'scripts/vagrant' # URL to download the Windows image win-image-url: '' opensuse-image-url: https://download.opensuse.org/repositories/Cloud:/Images:/Leap_15.3/images/openSUSE-Leap-15.3.x86_64-NoCloud.qcow2 +# Jammy by default +# Jammy is needed for OpenStack integration tests as Focal is not supported by OpenStack microstack +ubuntu-image-url: https://cloud-images.ubuntu.com/releases/jammy/release/ubuntu-22.04-server-cloudimg-amd64-disk-kvm.img # URL to download all images image-cache-url: '' diff --git a/harvester_e2e_tests/conftest.py b/harvester_e2e_tests/conftest.py index c78ff334b..f5f5ffaf6 100644 --- a/harvester_e2e_tests/conftest.py +++ b/harvester_e2e_tests/conftest.py @@ -127,6 +127,12 @@ def pytest_addoption(parser): default=config_data.get('opensuse-image-url'), help=('OpenSUSE image URL') ) + parser.addoption( + '--ubuntu-image-url', + action='store', + default=config_data.get('ubuntu-image-url'), + help=('Ubuntu image URL') + ) parser.addoption( '--terraform-scripts-location', action='store', diff --git a/harvester_e2e_tests/fixtures/images.py b/harvester_e2e_tests/fixtures/images.py index 9ebee3e9a..9fbfc9ebf 100644 --- a/harvester_e2e_tests/fixtures/images.py +++ b/harvester_e2e_tests/fixtures/images.py @@ -9,7 +9,7 @@ # TODO: remove it after update CI's config.yml DEFAULT_OPENSUSE_IMAGE_URL = ("https://download.opensuse.org/repositories/Cloud:/Images:" "/Leap_15.3/images/openSUSE-Leap-15.3.x86_64-NoCloud.qcow2") - +DEFAULT_JAMMY_IMAGE_URL = "https://cloud-images.ubuntu.com/releases/jammy/release/ubuntu-22.04-server-cloudimg-amd64-disk-kvm.img" @pytest.fixture(scope="session") def image_opensuse(request, api_client): @@ -23,6 +23,19 @@ def image_opensuse(request, api_client): return ImageInfo(url, ssh_user='opensuse') +# needed for openstack, as openstack impl only works with jammy +@pytest.fixture(scope="session") +def image_jammy(request, api_client): + image_server = request.config.getoption('--image-cache-url') + url = urlparse(request.config.getoption('--ubuntu-image-url') or DEFAULT_JAMMY_IMAGE_URL) + + if image_server: + *_, image_name = url.path.rsplit('/', 1) + url = urlparse(urljoin(f"{image_server}/", image_name)) + + return ImageInfo(url, ssh_user='ubuntu') + + class ImageInfo: def __init__(self, url_result, ssh_user=None): self.url_result = url_result diff --git a/harvester_e2e_tests/fixtures/vm_imports.py b/harvester_e2e_tests/fixtures/vm_imports.py new file mode 100644 index 000000000..68660bc27 --- /dev/null +++ b/harvester_e2e_tests/fixtures/vm_imports.py @@ -0,0 +1,59 @@ +# Copyright (c) 2023 SUSE LLC +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 3 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. +# +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com +from harvester_e2e_tests import utils +import pytest +import os + +from datetime import datetime, timedelta +from time import sleep + + +@pytest.fixture(scope='module') +def user_data_openstack(): + # set to root user password to 'linux' to test password login in + # addition to SSH login + yaml_data = """#cloud-config +chpasswd: + list: | + root:linux + expire: false +ssh_pwauth: true +users: + - name: root +package_update: true +packages: + - qemu-guest-agent +snap: + commands: + - snap install microstack --devmode --beta + - snap alias microstack.openstack openstack + - snap set microstack config.credentials.keystone-password=testtesttest +runcmd: + - - systemctl + - enable + - '--now' + - qemu-ga + - echo fs.inotify.max_queued_events=1048576 | tee -a /etc/sysctl.conf + - echo fs.inotify.max_user_instances=1048576 | tee -a /etc/sysctl.conf + - echo fs.inotify.max_user_watches=1048576 | tee -a /etc/sysctl.conf + - echo vm.max_map_count=262144 | tee -a /etc/sysctl.conf + - echo vm.swappiness=1 | tee -a /etc/sysctl.conf + - sysctl -p + - microstack init --auto --control --setup-loop-based-cinder-lvm-backend --loop-device-file-size 50 + - snap restart microstack.cinder-{uwsgi,scheduler,volume} +""" + return yaml_data.replace('\n', '\\n') diff --git a/harvester_e2e_tests/integration/test_vm_imports.py b/harvester_e2e_tests/integration/test_vm_imports.py new file mode 100644 index 000000000..63cb4fb64 --- /dev/null +++ b/harvester_e2e_tests/integration/test_vm_imports.py @@ -0,0 +1,191 @@ +# Copyright (c) 2021 SUSE LLC +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 3 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. +# +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com + +import pytest +from harvester_e2e_tests import utils +from datetime import datetime, timedelta +from pathlib import Path +from tempfile import NamedTemporaryFile +from time import sleep + +import json +import re +import pytest +import yaml +from paramiko.ssh_exception import ChannelException + +pytest_plugins = [ + 'harvester_e2e_tests.fixtures.api_client', + 'harvester_e2e_tests.fixtures.vm_imports', + 'harvester_e2e_tests.fixtures.images', + 'harvester_e2e_tests.fixtures.virtualmachines' +] + + +@pytest.fixture(scope="session") +def virtctl(api_client): + code, ctx = api_client.vms.download_virtctl() + + with NamedTemporaryFile("wb") as f: + f.write(ctx) + f.seek(0) + yield Path(f.name) + + +@pytest.fixture(scope="session") +def kubeconfig_file(api_client): + kubeconfig = api_client.generate_kubeconfig() + with NamedTemporaryFile("w") as f: + f.write(kubeconfig) + f.seek(0) + yield Path(f.name) + +@pytest.fixture(scope='module') +def vlan_network_openstack(request, api_client): + vlan_nic = request.config.getoption('--vlan-nic') + vlan_id = request.config.getoption('--vlan-id') + assert -1 != vlan_id, "OpenStack integration test needs VLAN" + + api_client.clusternetworks.create(vlan_nic) + api_client.clusternetworks.create_config(vlan_nic, vlan_nic, vlan_nic) + + network_name = f'os-{vlan_id}' + code, data = api_client.networks.get(network_name) + if code != 200: + code, data = api_client.networks.create(network_name, vlan_id, cluster_network=vlan_nic) + assert 201 == code, ( + f"Failed to create network-attachment-definition {network_name} \ + with error {code}, {data}" + ) + + data['id'] = data['metadata']['name'] + yield data + + api_client.networks.delete(network_name) + + +@pytest.fixture(scope="module") +def image_ubuntu_jammy(api_client, image_jammy, unique_name, wait_timeout): + unique_image_id = f'image-{unique_name}' + code, data = api_client.images.create_by_url( + unique_image_id, image_jammy.url, display_name=f"{unique_name}-{image_jammy.name}" + ) + + assert 201 == code, (code, data) + + endtime = datetime.now() + timedelta(seconds=wait_timeout) + while endtime > datetime.now(): + code, data = api_client.images.get(unique_image_id) + if 100 == data.get('status', {}).get('progress', 0): + break + sleep(3) + else: + raise AssertionError( + "Failed to create Image with error:\n" + f"Status({code}): {data}" + ) + + yield dict(id=f"{data['metadata']['namespace']}/{unique_image_id}", + user=image_jammy.ssh_user) + + code, data = api_client.images.delete(unique_image_id) + + +@pytest.fixture(scope="module") +def unique_vm_name(unique_name): + return f"vm-{unique_name}" + +@pytest.fixture(scope="module") +def openstack_vm(api_client, image_ubuntu_jammy, unique_vm_name, wait_timeout, user_data_openstack, vlan_network_openstack): + """ + tbd... + """ + cpu, mem = 6, 12 + vm = api_client.vms.Spec(cpu, mem, description="OpenStack VM", guest_agent=False) + vm.add_image("disk-0", image_ubuntu_jammy['id']) + vm.add_network('nic-0', vlan_network_openstack['id']) + vm.user_data = user_data_openstack + + code, data = api_client.vms.create(unique_vm_name, vm) + + assert 201 == code, (code, data) + + endtime = datetime.now() + timedelta(seconds=wait_timeout) + while endtime > datetime.now(): + code, data = api_client.vms.get_status(unique_vm_name) + if 200 == code and "Running" == data.get('status', {}).get('phase'): + break + sleep(3) + else: + raise AssertionError( + f"Failed to create OpenStack VM({cpu} core, {mem} RAM) with errors:\n" + f"Status: {data.get('status')}\n" + f"API Status({code}): {data}" + ) + + yield unique_vm_name + + api_client.vms.delete(unique_vm_name) + endtime = datetime.now() + timedelta(seconds=wait_timeout) + while endtime > datetime.now(): + code, data = api_client.vms.get_status(unique_vm_name) + if 404 == code: + break + sleep(3) + + +@pytest.mark.p0 +class TestOpenStackSetup: + + @pytest.mark.dependency(name="openstack_openrc") + def test_openstack_openrc(self, api_client, openstack_vm, wait_timeout, vm_shell): + """ + Test... + """ + # make sure the VM instance is successfully created + code, data = api_client.vms.get(openstack_vm) + assert 200 == code + assert "Running" == data['status']['printableStatus'] + #vm_shell = vm_shell.login(data['status']['interfaces'][0]['ipAddress'], user="root", password="linux") + pass + # find some way to get clouds.yaml file https://192.168.9.90/project/api_access/clouds.yaml/ + # This is a clouds.yaml file, which can be used by OpenStack tools as a source + # of configuration on how to connect to a cloud. If this is your only cloud, + # just put this file in ~/.config/openstack/clouds.yaml and tools like + # python-openstackclient will just work with no further config. (You will need + # to add your password to the auth section) + # If you have more than one cloud account, add the cloud entry to the clouds + # section of your existing file and you can refer to them by name with + # OS_CLOUD=openstack or --os-cloud=openstack + # clouds: + # openstack: + # auth: + # auth_url: https://192.168.9.90:5000/v3/ + # username: "admin" + # project_id: 63c9628764e54f9a913af16c472bb7e8 + # project_name: "admin" + # user_domain_name: "Default" + # region_name: "microstack" + # interface: "public" + # identity_api_version: 3 +# ubuntu@node4:~$ openstack project list --user admin -f json +# [ +# { +# "ID": "63c9628764e54f9a913af16c472bb7e8", +# "Name": "admin" +# } +# ]