mirror of https://github.com/apache/cloudstack.git
411 lines
15 KiB
Python
411 lines
15 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you 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.
|
|
|
|
# Import Local Modules
|
|
from marvin.cloudstackTestCase import cloudstackTestCase
|
|
from marvin.cloudstackAPI import (
|
|
getDiagnosticsData,
|
|
stopSystemVm,
|
|
rebootSystemVm,
|
|
destroySystemVm,
|
|
updateConfiguration,
|
|
)
|
|
from marvin.lib.utils import (
|
|
cleanup_resources,
|
|
get_process_status,
|
|
get_host_credentials,
|
|
wait_until,
|
|
)
|
|
from marvin.lib.base import UserData, Network
|
|
from marvin.lib.common import (
|
|
get_zone,
|
|
list_hosts,
|
|
list_routers,
|
|
list_ssvms,
|
|
list_zones,
|
|
list_vlan_ipranges,
|
|
createEnabledNetworkOffering,
|
|
)
|
|
from marvin.codes import PASS
|
|
from nose.plugins.attrib import attr
|
|
import telnetlib
|
|
import logging
|
|
import base64
|
|
import os
|
|
import urllib
|
|
import zipfile
|
|
import uuid
|
|
import shutil
|
|
|
|
# Import System modules
|
|
import time
|
|
|
|
_multiprocess_shared_ = True
|
|
|
|
|
|
class TestSystemVMUserData(cloudstackTestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.testClient = super(TestSystemVMUserData, cls).getClsTestClient()
|
|
cls.api_client = cls.testClient.getApiClient()
|
|
|
|
# Fill services from the external config file
|
|
cls.testData = cls.testClient.getParsedTestDataConfig()
|
|
|
|
# Enable user data and set the script to be run on SSVM
|
|
cmd = updateConfiguration.updateConfigurationCmd()
|
|
cmd.name = "systemvm.userdata.enabled"
|
|
cmd.value = "true"
|
|
cls.api_client.updateConfiguration(cmd)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
# Disable user data
|
|
cmd = updateConfiguration.updateConfigurationCmd()
|
|
cmd.name = "systemvm.userdata.enabled"
|
|
cmd.value = "false"
|
|
cls.api_client.updateConfiguration(cmd)
|
|
|
|
def setUp(self):
|
|
test_case = super(TestSystemVMUserData, self)
|
|
self.apiclient = self.testClient.getApiClient()
|
|
self.hypervisor = self.testClient.getHypervisorInfo()
|
|
self.cleanup = []
|
|
self.config = test_case.getClsConfig()
|
|
self.services = self.testClient.getParsedTestDataConfig()
|
|
self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests())
|
|
|
|
self.logger = logging.getLogger("TestSystemVMUserData")
|
|
self.stream_handler = logging.StreamHandler()
|
|
self.logger.setLevel(logging.DEBUG)
|
|
self.logger.addHandler(self.stream_handler)
|
|
|
|
def tearDown(self):
|
|
if self.userdata_id:
|
|
UserData.delete(self.apiclient, self.userdata_id)
|
|
self.userdata_id = None
|
|
|
|
try:
|
|
cleanup_resources(self.apiclient, self.cleanup)
|
|
except Exception as e:
|
|
raise Exception("Warning: Exception during cleanup : %s" % e)
|
|
|
|
def waitForSystemVMAgent(self, vmname):
|
|
def checkRunningAgent():
|
|
list_host_response = list_hosts(self.apiclient, name=vmname)
|
|
if isinstance(list_host_response, list):
|
|
return list_host_response[0].state == "Up", None
|
|
return False, None
|
|
|
|
res, _ = wait_until(3, 300, checkRunningAgent)
|
|
if not res:
|
|
raise Exception("Failed to wait for SSVM agent to be Up")
|
|
|
|
def checkForRunningSystemVM(self, ssvm, ssvm_type=None):
|
|
if not ssvm:
|
|
return None
|
|
|
|
def checkRunningState():
|
|
if not ssvm_type:
|
|
response = list_ssvms(self.apiclient, id=ssvm.id)
|
|
else:
|
|
response = list_ssvms(
|
|
self.apiclient, zoneid=self.zone.id, systemvmtype=ssvm_type
|
|
)
|
|
|
|
if isinstance(response, list):
|
|
ssvm_response = response[0]
|
|
return ssvm_response.state == "Running", ssvm_response
|
|
return False, None
|
|
|
|
res, ssvm_response = wait_until(3, 300, checkRunningState)
|
|
if not res:
|
|
self.fail("Failed to reach systemvm state to Running")
|
|
return ssvm_response
|
|
|
|
def register_userdata(
|
|
self, userdata_name, global_setting_name, vm_type_display_name
|
|
):
|
|
"""Helper method to register userdata and configure the global setting
|
|
|
|
Args:
|
|
userdata_name: Name for the userdata entry
|
|
global_setting_name: Global setting name to update (e.g., 'secstorage.vm.userdata', 'console.proxy.vm.userdata', 'virtual.router.userdata')
|
|
vm_type_display_name: Display name for the VM type (e.g., 'SSVM', 'CPVM', 'VR')
|
|
|
|
Returns:
|
|
UserData object
|
|
"""
|
|
userdata_script = f"""#!/bin/bash
|
|
echo "User data script ran successfully on {vm_type_display_name}" > /tmp/userdata.txt
|
|
"""
|
|
b64_encoded_userdata = base64.b64encode(userdata_script.encode("utf-8")).decode(
|
|
"utf-8"
|
|
)
|
|
|
|
# Create a userdata entry
|
|
try:
|
|
userdata = UserData.register(
|
|
self.apiclient, name=userdata_name, userdata=b64_encoded_userdata
|
|
)
|
|
userdata_id = userdata["userdata"]["id"]
|
|
except Exception as e:
|
|
if "already exists" in str(e):
|
|
self.debug("Userdata already exists, getting it")
|
|
userdata = UserData.list(
|
|
self.apiclient, name=userdata_name, listall=True
|
|
)[0]
|
|
userdata_id = userdata.id
|
|
else:
|
|
self.fail("Failed to register userdata: %s" % e)
|
|
|
|
# Update global configuration to use this userdata
|
|
cmd = updateConfiguration.updateConfigurationCmd()
|
|
cmd.name = global_setting_name
|
|
cmd.value = userdata_id
|
|
self.apiclient.updateConfiguration(cmd)
|
|
self.debug(
|
|
"Updated global setting %s with userdata ID: %s"
|
|
% (global_setting_name, userdata.id)
|
|
)
|
|
|
|
return userdata_id
|
|
|
|
def download_and_verify_diagnostics_data(
|
|
self, target_id, vm_type_display_name, expected_content, retries=4
|
|
):
|
|
"""Helper method to download and verify diagnostics data
|
|
|
|
Args:
|
|
target_id: ID of the target VM/router
|
|
vm_type_display_name: Display name for log messages (e.g., 'SSVM', 'CPVM', 'VR')
|
|
expected_content: Expected content to verify in the userdata file
|
|
retries: Number of retries for getDiagnosticsData (default: 4)
|
|
"""
|
|
# Create a random temporary directory for this test
|
|
random_suffix = uuid.uuid4().hex[:8]
|
|
vm_type_prefix = vm_type_display_name.lower()
|
|
temp_dir = f"/tmp/{vm_type_prefix}-{random_suffix}"
|
|
os.makedirs(temp_dir, exist_ok=True)
|
|
|
|
# Download the file created by userdata script using the getDiagnosticsData command
|
|
cmd = getDiagnosticsData.getDiagnosticsDataCmd()
|
|
cmd.targetid = target_id
|
|
cmd.files = "/tmp/userdata.txt"
|
|
|
|
# getDiagnosticsData command takes some time to work after a VM is started
|
|
response = None
|
|
while retries > -1:
|
|
try:
|
|
response = self.apiclient.getDiagnosticsData(cmd)
|
|
break # Success, exit retry loop
|
|
except Exception as e:
|
|
if retries >= 0:
|
|
retries = retries - 1
|
|
self.debug(
|
|
"getDiagnosticsData failed (retries left: %d): %s"
|
|
% (retries + 1, e)
|
|
)
|
|
if retries > -1:
|
|
time.sleep(30)
|
|
continue
|
|
# If all retries exhausted, re-raise the exception
|
|
self.fail("Failed to get diagnostics data after retries: %s" % e)
|
|
|
|
# Download response.url file to temporary directory and extract it
|
|
zip_file_path = os.path.join(temp_dir, "userdata.zip")
|
|
extracted_file_path = os.path.join(temp_dir, "userdata.txt")
|
|
|
|
self.debug(
|
|
"Downloading userdata file from %s to %s"
|
|
% (vm_type_display_name, zip_file_path)
|
|
)
|
|
try:
|
|
urllib.request.urlretrieve(response.url, zip_file_path)
|
|
except Exception as e:
|
|
self.fail(
|
|
"Failed to download userdata file from %s: %s"
|
|
% (vm_type_display_name, e)
|
|
)
|
|
self.debug(
|
|
"Downloaded userdata file from %s: %s"
|
|
% (vm_type_display_name, zip_file_path)
|
|
)
|
|
|
|
try:
|
|
with zipfile.ZipFile(zip_file_path, "r") as zip_ref:
|
|
zip_ref.extractall(temp_dir)
|
|
except zipfile.BadZipFile as e:
|
|
self.fail("Downloaded userdata file is not a zip file: %s" % e)
|
|
self.debug("Extracted userdata file from zip: %s" % extracted_file_path)
|
|
|
|
# Verify the file contains the expected content
|
|
try:
|
|
with open(extracted_file_path, "r") as f:
|
|
content = f.read().strip()
|
|
self.debug("Userdata file content: %s" % content)
|
|
self.assertEqual(
|
|
expected_content in content,
|
|
True,
|
|
f"Check that userdata file contains expected content: '{expected_content}'",
|
|
)
|
|
except FileNotFoundError:
|
|
self.fail(
|
|
"Userdata file not found in extracted zip at %s" % extracted_file_path
|
|
)
|
|
except Exception as e:
|
|
self.fail("Failed to read userdata file: %s" % e)
|
|
finally:
|
|
# Clean up temporary directory
|
|
try:
|
|
if os.path.exists(temp_dir):
|
|
shutil.rmtree(temp_dir)
|
|
self.debug("Cleaned up temporary directory: %s" % temp_dir)
|
|
except Exception as e:
|
|
self.debug(
|
|
"Failed to clean up temporary directory %s: %s" % (temp_dir, e)
|
|
)
|
|
|
|
def test_userdata_on_systemvm(
|
|
self, systemvm_type, userdata_name, vm_type_display_name, global_setting_name
|
|
):
|
|
"""Helper method to test user data functionality on system VMs
|
|
|
|
Args:
|
|
systemvm_type: Type of system VM ('secondarystoragevm' or 'consoleproxy')
|
|
userdata_name: Name for the userdata entry
|
|
vm_type_display_name: Display name for log messages (e.g., 'SSVM' or 'CPVM')
|
|
global_setting_name: Global setting name for userdata (e.g., 'secstorage.vm.userdata' or 'console.proxy.vm.userdata')
|
|
"""
|
|
# 1) Register userdata and configure global setting
|
|
self.userdata_id = self.register_userdata(
|
|
userdata_name, global_setting_name, vm_type_display_name
|
|
)
|
|
|
|
# 2) Get and destroy the system VM to trigger recreation with userdata
|
|
list_ssvm_response = list_ssvms(
|
|
self.apiclient,
|
|
systemvmtype=systemvm_type,
|
|
state="Running",
|
|
zoneid=self.zone.id,
|
|
)
|
|
self.assertEqual(
|
|
isinstance(list_ssvm_response, list),
|
|
True,
|
|
"Check list response returns a valid list",
|
|
)
|
|
|
|
ssvm = list_ssvm_response[0]
|
|
self.debug("Destroying %s: %s" % (vm_type_display_name, ssvm.id))
|
|
cmd = destroySystemVm.destroySystemVmCmd()
|
|
cmd.id = ssvm.id
|
|
self.apiclient.destroySystemVm(cmd)
|
|
|
|
# 3) Wait for the system VM to be running again
|
|
ssvm_response = self.checkForRunningSystemVM(ssvm, systemvm_type)
|
|
self.debug(
|
|
"%s state after restart: %s" % (vm_type_display_name, ssvm_response.state)
|
|
)
|
|
self.assertEqual(
|
|
ssvm_response.state,
|
|
"Running",
|
|
"Check whether %s is running or not" % vm_type_display_name,
|
|
)
|
|
# Wait for the agent to be up
|
|
self.waitForSystemVMAgent(ssvm_response.name)
|
|
|
|
# 4) Download and verify the diagnostics data
|
|
expected_content = (
|
|
f"User data script ran successfully on {vm_type_display_name}"
|
|
)
|
|
self.download_and_verify_diagnostics_data(
|
|
ssvm_response.id, vm_type_display_name, expected_content
|
|
)
|
|
|
|
@attr(
|
|
tags=["advanced", "advancedns", "smoke", "basic", "sg"],
|
|
required_hardware="true",
|
|
)
|
|
def test_1_userdata_on_ssvm(self):
|
|
"""Test user data functionality on SSVM"""
|
|
self.test_userdata_on_systemvm(
|
|
systemvm_type="secondarystoragevm",
|
|
userdata_name="ssvm_userdata",
|
|
vm_type_display_name="SSVM",
|
|
global_setting_name="secstorage.vm.userdata",
|
|
)
|
|
|
|
@attr(
|
|
tags=["advanced", "advancedns", "smoke", "basic", "sg"],
|
|
required_hardware="true",
|
|
)
|
|
def test_2_userdata_on_cpvm(self):
|
|
"""Test user data functionality on CPVM"""
|
|
self.test_userdata_on_systemvm(
|
|
systemvm_type="consoleproxy",
|
|
userdata_name="cpvm_userdata",
|
|
vm_type_display_name="CPVM",
|
|
global_setting_name="console.proxy.vm.userdata",
|
|
)
|
|
|
|
@attr(
|
|
tags=["advanced", "advancedns", "smoke", "basic", "sg"],
|
|
required_hardware="true",
|
|
)
|
|
def test_3_userdata_on_vr(self):
|
|
"""Test user data functionality on VR"""
|
|
# 1) Register userdata and configure global setting
|
|
self.userdata_id = self.register_userdata("vr_userdata", "virtual.router.userdata", "VR")
|
|
|
|
# 2) Create an isolated network which will trigger VR creation with userdata
|
|
result = createEnabledNetworkOffering(
|
|
self.apiclient, self.testData["nw_off_isolated_persistent"]
|
|
)
|
|
assert result[0] == PASS, (
|
|
"Network offering creation/enabling failed due to %s" % result[2]
|
|
)
|
|
isolated_persistent_network_offering = result[1]
|
|
|
|
# Create an isolated network
|
|
self.network = Network.create(
|
|
self.apiclient,
|
|
self.testData["isolated_network"],
|
|
networkofferingid=isolated_persistent_network_offering.id,
|
|
zoneid=self.zone.id,
|
|
)
|
|
self.assertIsNotNone(self.network, "Network creation failed")
|
|
self.cleanup.append(self.network)
|
|
self.cleanup.append(isolated_persistent_network_offering)
|
|
|
|
# 3) Get the VR and verify it's running
|
|
routers = list_routers(
|
|
self.apiclient, networkid=self.network.id, state="Running"
|
|
)
|
|
self.assertEqual(
|
|
isinstance(routers, list),
|
|
True,
|
|
"Check list router response returns a valid list",
|
|
)
|
|
self.assertNotEqual(len(routers), 0, "Check list router response")
|
|
router = routers[0]
|
|
self.debug("Found VR: %s" % router.id)
|
|
|
|
# 4) Download and verify the diagnostics data
|
|
# VR doesn't need retries as it's freshly created
|
|
expected_content = "User data script ran successfully on VR"
|
|
self.download_and_verify_diagnostics_data(router.id, "VR", expected_content)
|