summaryrefslogtreecommitdiff
diff options
-rwxr-xr-xbin/boot_mode_test35
-rwxr-xr-xbin/disk_cpu_load21
-rwxr-xr-xbin/disk_stress_ng43
-rwxr-xr-xbin/dmitest39
-rwxr-xr-xbin/efi-pxeboot149
-rwxr-xr-xbin/fwts_test82
-rwxr-xr-xbin/maas-version-check33
-rwxr-xr-xbin/memory_stress_ng7
-rwxr-xr-xbin/network27
-rwxr-xr-xbin/virtualization127
-rw-r--r--jobs/miscellanea.txt.in40
-rwxr-xr-xmanage.py2
12 files changed, 467 insertions, 138 deletions
diff --git a/bin/boot_mode_test b/bin/boot_mode_test
index d4c2956..330bb2b 100755
--- a/bin/boot_mode_test
+++ b/bin/boot_mode_test
@@ -24,6 +24,35 @@ import os
import sys
import logging
from argparse import ArgumentParser
+from platform import linux_distribution
+
+
+def version_check(check):
+ """Check the installed Ubuntu version to see if it is older than 16.04
+ (Xenial) and run the requested check
+
+ :returns:
+ 0 if if the installed version is older than 16.04 regardless of return
+ code from the requested check
+
+ return code from requested check if the installed version is 16.04 or
+ newer.
+ """
+ installed_version = int(linux_distribution()[1].split()[0])
+ if installed_version < 16:
+ logging.info("This system appears to be older than 16.04 LTS so this "
+ "will not block a certification in progress.")
+ if check == 'efi':
+ efi_boot_check()
+ else:
+ secure_boot_check()
+
+ return 0
+ else:
+ if check == 'efi':
+ return efi_boot_check()
+ else:
+ return secure_boot_check()
def efi_boot_check():
@@ -86,11 +115,7 @@ def main():
FORMAT = '%(levelname)s: %(message)s'
logging.basicConfig(level=logging.INFO, format=FORMAT)
- if args.check == 'efi':
- return efi_boot_check()
- else:
- return secure_boot_check()
-
+ return version_check(args.check)
if __name__ == '__main__':
sys.exit(main())
diff --git a/bin/disk_cpu_load b/bin/disk_cpu_load
index 5e0d0eb..93b1569 100755
--- a/bin/disk_cpu_load
+++ b/bin/disk_cpu_load
@@ -24,13 +24,14 @@
#
# Usage:
# disk_cpu_load [ --max-load <load> ] [ --xfer <mebibytes> ]
-# [ <device-filename> ]
+# [ --verbose ] [ <device-filename> ]
#
# Parameters:
# --max-load <load> -- The maximum acceptable CPU load, as a percentage.
# Defaults to 30.
# --xfer <mebibytes> -- The amount of data to read from the disk, in
# mebibytes. Defaults to 4096 (4 GiB).
+# --verbose -- If present, produce more verbose output
# <device-filename> -- This is the WHOLE-DISK device filename (with or
# without "/dev/"), e.g. "sda" or "/dev/sda". The
# script finds a filesystem on that device, mounts
@@ -44,6 +45,7 @@ set -e
get_params() {
disk_device="/dev/sda"
short_device="sda"
+ verbose=0
max_load=30
xfer=4096
while [ $# -gt 0 ] ; do
@@ -54,6 +56,8 @@ get_params() {
--xfer) xfer="$2"
shift
;;
+ --verbose) verbose=1
+ ;;
*) disk_device="/dev/$1"
disk_device=`echo $disk_device | sed "s/\/dev\/\/dev/\/dev/g"`
short_device=$(echo $disk_device | sed "s/\/dev//g")
@@ -109,8 +113,15 @@ compute_cpu_load() {
let diff_total=${end_total}-${start_total}
let diff_used=$diff_total-$diff_idle
+ if [ "$verbose" == "1" ] ; then
+ echo "Start CPU time = $start_total"
+ echo "End CPU time = $end_total"
+ echo "CPU time used = $diff_used"
+ echo "Total elapsed time = $diff_total"
+ fi
+
if [ "$diff_total" != "0" ] ; then
- let cpu_load=($diff_used*100)/$diff_total
+ cpu_load=$(echo "($diff_used*100)/$diff_total" | bc)
else
cpu_load=0
fi
@@ -127,7 +138,13 @@ echo "Testing CPU load when reading $xfer MiB from $disk_device"
echo "Maximum acceptable CPU load is $max_load"
blockdev --flushbufs $disk_device
start_load="$(grep "cpu " /proc/stat | tr -s " " | cut -d " " -f 2-)"
+if [ "$verbose" == "1" ] ; then
+ echo "Beginning disk read...."
+fi
dd if="$disk_device" of=/dev/null bs=1048576 count="$xfer" &> /dev/null
+if [ "$verbose" == "1" ] ; then
+ echo "Disk read complete!"
+fi
end_load="$(grep "cpu " /proc/stat | tr -s " " | cut -d " " -f 2-)"
compute_cpu_load "$start_load" "$end_load"
echo "Detected disk read CPU load is $cpu_load"
diff --git a/bin/disk_stress_ng b/bin/disk_stress_ng
index 8445488..802d116 100755
--- a/bin/disk_stress_ng
+++ b/bin/disk_stress_ng
@@ -63,11 +63,40 @@ get_params() {
} # get_params()
-# Find the largest partition that holds a supported filesystem on $disk_device.
+# Find the largest logical volume in an LVM partition.
# Output:
# $largest_part -- Device filename of largest qualifying partition
# $largest_size -- Size of largest qualifying partition
# $largest_fs -- Filesystem (ext4, etc.) used on largest qualifying partition
+# Note: Above variables are initialized in find_largest_partition(), which
+# calls this function.
+# Caveat: If LVM is used, there can be no guarantee that a specific disk
+# device is actually being tested. Thus, an LVM configuration should span
+# just one disk device. LVM may be used on one disk, but subsequent disks
+# should use "raw" partitions.
+find_largest_lv() {
+ local partonly=$(echo $partition | cut -f 3 -d "/")
+ for syslv in $(ls -d /sys/block/dm-*/slaves/$partonly) ; do
+ lv=$(echo "$syslv" | cut -f 4 -d "/")
+ size=$(cat /sys/block/$lv/size)
+ sector_size=$(cat /sys/block/$lv/queue/hw_sector_size)
+ let size=$size*$sector_size
+ local blkid_info=$(blkid -s TYPE /dev/$lv | grep -E ext2\|ext3\|ext4\|xfs\|jfs\|btrfs)
+ if [ "$size" -gt "$largest_size" ] && [ -n "$blkid_info" ] ; then
+ local blkid_info=$(blkid -s TYPE /dev/$lv)
+ largest_size=$size
+ largest_part="/dev/$lv"
+ largest_fs=$(blkid -s TYPE "/dev/$lv" | cut -d "=" -f 2)
+ fi
+ done
+} # find_largest_lv()
+
+
+# Find the largest partition that holds a supported filesystem on $disk_device.
+# Output:
+# $largest_part -- Device filename of largest qualifying partition or logical volume
+# $largest_size -- Size of largest qualifying partition or logical volume
+# $largest_fs -- Filesystem (ext4, etc.) used on largest qualifying partition or logicl volume
# $unsupported_fs -- Empty or contains name of unsupported filesystem found on disk
find_largest_partition() {
largest_part=""
@@ -76,11 +105,15 @@ find_largest_partition() {
unsupported_fs=""
for partition in $(echo "$partitions" | cut -d " " -f 1) ; do
part_size=$(echo "$partitions" | grep "$partition " | cut -d " " -f 2)
- local blkid_info=$(blkid -s TYPE /dev/$partition | grep -E ext2\|ext3\|ext4\|xfs\|jfs\|btrfs)
+ local blkid_info=$(blkid -s TYPE /dev/$partition | grep -E ext2\|ext3\|ext4\|xfs\|jfs\|btrfs\|LVM2_member)
if [ "$part_size" -gt "$largest_size" ] && [ -n "$blkid_info" ] ; then
- largest_size=$part_size
- largest_part="/dev/$partition"
- largest_fs=$(blkid -s TYPE "/dev/$partition" | cut -d "=" -f 2)
+ if [[ "$blkid_info" =~ .*LVM2_member.* ]] ; then
+ find_largest_lv
+ else
+ largest_size=$part_size
+ largest_part="/dev/$partition"
+ largest_fs=$(blkid -s TYPE "/dev/$partition" | cut -d "=" -f 2)
+ fi
fi
local blkid_info=$(blkid -s TYPE /dev/$partition | grep -E ntfs\|vfat\|hfs)
if [ -n "$blkid_info" ] ; then
diff --git a/bin/dmitest b/bin/dmitest
index 27cfc8e..9be70be 100755
--- a/bin/dmitest
+++ b/bin/dmitest
@@ -28,6 +28,8 @@
:param --dmifile:
Input filename; optional. If specified, file is used instead of
dmidecode output.
+:param --show_dmi:
+ Print the DMI data used. For debugging purposes if errors are encountered.
:param --test_versions:
Include chassis, system, and base boad version numbers among tests.
:param --test_serials:
@@ -228,26 +230,45 @@ def main():
choices=['server', 'desktop', 'cpu-check'])
parser.add_argument('--dmifile',
help="File to use in lieu of dmidecode.")
+ parser.add_argument('--show_dmi', action="store_true",
+ help="Print DMI Data used for debugging purposes.")
parser.add_argument('--test_versions', action="store_true",
help="Set to check version information")
parser.add_argument('--test_serials', action="store_true",
help="Set to check serial number information")
args = parser.parse_args()
+ bad_data = False
# Command to retrieve DMI information
COMMAND = "dmidecode"
+ if args.dmifile:
+ COMMAND = ['cat', args.dmifile]
+ print("Reading " + args.dmifile + " as DMI data")
try:
- if args.dmifile:
- print("Reading " + args.dmifile + " as DMI data")
- stream = subprocess.check_output(['cat', args.dmifile],
- universal_newlines=True).splitlines()
- else:
- stream = subprocess.check_output(COMMAND,
- universal_newlines=True).splitlines()
+ dmi_out = subprocess.check_output(COMMAND).splitlines()
except subprocess.CalledProcessError as err:
print("Error running {}: {}".format(COMMAND, err))
return 1
+ # Convert the bytes output separately, line by line, because it's possible
+ # that someone put non-encodable characters in DMI, which cases a
+ # UnicodeDecodeError that is non-helpful. LP: 1655155
+ stream = []
+ for line in dmi_out:
+ try:
+ stream.append(line.decode('utf-8'))
+ except UnicodeDecodeError as ude:
+ print("DATA ERROR: {}".format(ude))
+ print("\tLINE NUMBER {}: {}".format(dmi_out.index(line) + 1, line))
+ stream.append("ERROR: BAD DATA FOUND HERE")
+ bad_data = True
+
+ if args.show_dmi:
+ print("===== DMI Data Used: =====")
+ for line in stream:
+ print(line)
+ print("===== DMI Output Complete =====")
+
retval = 0
if args.test_type == 'server' or args.test_type == 'desktop':
retval += standard_tests(args, stream)
@@ -272,6 +293,10 @@ def main():
else:
print("\nPassed all tests")
+ if bad_data:
+ print("\nBad Characters discovered in DMI output. Rerun with "
+ "the --show_dmi option to see more")
+
return retval
diff --git a/bin/efi-pxeboot b/bin/efi-pxeboot
new file mode 100755
index 0000000..724fefb
--- /dev/null
+++ b/bin/efi-pxeboot
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+
+"""
+Script to test that the system PXE-booted, if run in EFI mode
+
+Copyright (c) 2016 Canonical Ltd.
+
+Authors
+ Rod Smith <rod.smith@canonical.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 3,
+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, see <http://www.gnu.org/licenses/>.
+
+The purpose of this script is to identify whether an EFI-based
+system booted from the network (test passes) or from a local disk
+(test fails).
+
+Usage:
+ efi-pxeboot
+"""
+
+import os
+import shlex
+import shutil
+import sys
+
+from subprocess import Popen, PIPE
+
+
+def discover_data():
+ """Extract boot entry and boot order information.
+
+ :returns:
+ boot_entries, boot_order, boot_current
+ """
+ command = "efibootmgr -v"
+ bootinfo_bytes = (Popen(shlex.split(command), stdout=PIPE)
+ .communicate()[0])
+ bootinfo = (bootinfo_bytes.decode(encoding="utf-8", errors="ignore")
+ .splitlines())
+ boot_entries = {}
+ boot_order = []
+ if len(bootinfo) > 1:
+ for s in bootinfo:
+ if "BootOrder" in s:
+ try:
+ boot_order = s.split(":")[1].replace(" ", "").split(",")
+ except IndexError:
+ pass
+ elif "BootCurrent" in s:
+ try:
+ boot_current = s.split(":")[1].strip()
+ except IndexError:
+ pass
+ else:
+ # On Boot#### lines, #### is characters 4-8....
+ hex_value = s[4:8]
+ # ....and the description starts at character 10
+ name = s[10:]
+ try:
+ # In normal efibootmgr output, only Boot#### entries
+ # have characters 4-8 that can be interpreted as
+ # hex values, so this will harmlessly error out on all
+ # but Boot#### entries....
+ int(hex_value, 16)
+ boot_entries[hex_value] = name
+ except ValueError:
+ pass
+ return boot_entries, boot_order, boot_current
+
+
+def is_pxe_booted(boot_entries, boot_order, boot_current):
+ retval = 0
+ desc = boot_entries[boot_current]
+ print("The current boot item is {}".format(boot_current))
+ print("The first BootOrder item is {}".format(boot_order[0]))
+ print("The description of Boot{} is '{}'".format(boot_current, desc))
+ if boot_current != boot_order[0]:
+ # If the BootCurrent entry isn't the same as the first of the
+ # BootOrder entries, then something is causing the first boot entry
+ # to fail or be bypassed. This could be a Secure Boot failure, manual
+ # intervention, a bad boot entry, etc. This is not necessarily a
+ # problem, but warn of it anyhow....
+ desc2 = boot_entries[boot_order[0]]
+ print("The description of Boot{} is '{}'".format(boot_order[0], desc2))
+ print("WARNING: The system is booted using Boot{}, but the first".
+ format(boot_current))
+ print("boot item is Boot{}!".format(boot_order[0]))
+ if "Network" in desc or "PXE" in desc or "NIC" in desc \
+ or "Ethernet" in desc or "IP4" in desc or "IP6" in desc:
+ # These strings are present in network-boot descriptions.
+ print("The system seems to have PXE-booted; all OK.")
+ elif "ubuntu" in desc or "grub" in desc or "shim" in desc or "rEFInd" \
+ or "refind_" in desc:
+ # This string indicates a boot directly from the normal Ubuntu GRUB
+ # or rEFInd installation on the hard disk.
+ print("The system seems to have booted directly from the hard disk!")
+ retval = 1
+ elif "SATA" in desc or "Sata" in desc or "Hard Drive" in desc:
+ # These strings indicate booting with a "generic" disk entry (one
+ # that uses the fallback filename, EFI/BOOT/bootx64.efi or similar).
+ print("The system seems to have booted from a disk with the fallback "
+ "boot loader!")
+ retval = 1
+ else:
+ # Probably a rare description. Call it an error so we can flag it and
+ # improve this script.
+ print("Unable to identify boot path.")
+ retval = 1
+ return retval
+
+
+def main():
+ """Check to see if the system PXE-booted."""
+
+ if shutil.which("efibootmgr") is None:
+ print("The efibootmgr utility is not installed; exiting!")
+ return(4)
+ if not os.geteuid() == 0:
+ print("This program must be run as root (or via sudo); exiting!")
+ return(4)
+
+ retval = 0
+ boot_entries, boot_order, boot_current = discover_data()
+ if boot_entries == {}:
+ print("No EFI boot entries are available. This may indicate a "
+ "firmware problem.")
+ retval = 2
+ if boot_order == []:
+ print("The EFI BootOrder variable is not available. This may "
+ "indicate a firmware")
+ print("problem.")
+ retval = 3
+ if (retval == 0):
+ retval = is_pxe_booted(boot_entries, boot_order, boot_current)
+ return(retval)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/bin/fwts_test b/bin/fwts_test
index 0e5ffc9..c8ff1f7 100755
--- a/bin/fwts_test
+++ b/bin/fwts_test
@@ -18,60 +18,35 @@ INTERACTIVE_TESTS = ['ac_adapter',
'power_button',
'brightness',
'lid']
+# Tests recommended by the Hardware Enablement Team (HWE)
# These are performed on QA certification runs
QA_TESTS = ['acpitests',
- 'acpidump',
- 'acpitables',
- 'apicinstance',
+ 'apicedge',
'aspm',
- 'bios32',
+ 'cpufreq',
'dmicheck',
- 'ebda',
- 'mpcheck',
+ 'esrt',
+ 'klog',
+ 'maxfreq',
'msr',
+ 'mtrr',
'nx',
- 'version']
-# These are advanced tests that shouldn't affect certification status
-NON_CERT_TESTS = ['bios_info',
- 'cmosdump',
- 'cpufreq',
- 'crs',
- 'crsdump',
- 'csm',
- 'ebdadump',
- 'fan',
- 'gpedump',
- 'hda_audio',
- 'maxfreq',
- 'maxreadreq',
- 'memmapdump',
- 'microcode',
- 'mpdump',
- 'os2gap',
- 'osilinux',
- 'pciirq',
- 'plddump',
- 'pnp',
- 'prsdump',
- 'romdump',
- 'securebootcert',
- 'syntaxcheck',
- 'uefidump',
- 'uefirtmisc',
- 'uefirttime',
- 'uefirtvariable',
- 'uefivarinfo',
- 'wakealarm'
- ]
+ 'oops',
+ 'uefibootpath',
+ 'uefirtmisc',
+ 'uefirttime',
+ 'uefirtvariable',
+ 'version',
+ 'virt']
# The following tests will record logs in a separate file for the HWE team
-HWE_TESTS = ['mtrr',
+HWE_TESTS = ['version',
+ 'mtrr',
'virt',
'apicedge',
'klog',
- 'oops',
- 'uefibootpath']
-CERT_TESTS = sorted(QA_TESTS + HWE_TESTS)
-TESTS = sorted(QA_TESTS + NON_CERT_TESTS + HWE_TESTS)
+ 'oops']
+# By default, we launch all the tests
+TESTS = sorted(list(set(QA_TESTS + HWE_TESTS)))
def get_sleep_times(start_marker, end_marker, sleep_time, resume_time):
@@ -210,9 +185,6 @@ def main():
group.add_argument('-t', '--test',
action='append',
help='Name of the test to run.')
- group.add_argument('-a', '--all',
- action='store_true',
- help='Run ALL FWTS automated tests (assumes -w and -c)')
group.add_argument('-s', '--sleep',
nargs=REMAINDER,
action='store',
@@ -236,12 +208,6 @@ def main():
group.add_argument('--list',
action='store_true',
help='List all tests in fwts.')
- group.add_argument('--list-cert',
- action='store_true',
- help='List all certification tests in fwts.')
- group.add_argument('--list-advanced',
- action='store_true',
- help='List all advanced tests in fwts.')
group.add_argument('--list-hwe',
action='store_true',
help='List all HWE concerned tests in fwts')
@@ -282,12 +248,6 @@ def main():
elif args.list:
print('\n'.join(TESTS))
return 0
- elif args.list_cert:
- print('\n'.join(CERT_TESTS))
- return 0
- elif args.list_advanced:
- print('\n'.join(NON_CERT_TESTS))
- return 0
elif args.list_hwe:
print('\n'.join(HWE_TESTS))
return 0
@@ -296,8 +256,6 @@ def main():
return 0
elif args.test:
tests.extend(args.test)
- elif args.all:
- tests.extend(TESTS)
elif args.hwe:
tests.extend(HWE_TESTS)
elif args.qa:
@@ -335,7 +293,7 @@ def main():
args.resume_time = 3
tests.extend(args.sleep)
else:
- tests.extend(CERT_TESTS)
+ tests.extend(TESTS)
# run the tests we want
if args.sleep:
diff --git a/bin/maas-version-check b/bin/maas-version-check
new file mode 100755
index 0000000..412717d
--- /dev/null
+++ b/bin/maas-version-check
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# Copyright (C) 2012-2015 Canonical Ltd.
+
+# Authors
+# Jeff Lane <jeff@ubuntu.com>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3,
+# 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, see <http://www.gnu.org/licenses/>.
+
+MAAS_FILE="/etc/installed-by-maas"
+MIN_VERSION="2.0"
+
+# Is the file there?
+if [ -s $MAAS_FILE ]; then
+ maas_version=`cat $MAAS_FILE`
+ echo $maas_version
+else
+ echo "ERROR: This system does not appear to have been installed by MAAS"
+ exit 1
+fi
+
+#is the version appropriate
+exit `dpkg --compare-versions $maas_version "ge" $MIN_VERSION`
diff --git a/bin/memory_stress_ng b/bin/memory_stress_ng
index ab65e86..e619772 100755
--- a/bin/memory_stress_ng
+++ b/bin/memory_stress_ng
@@ -114,6 +114,8 @@ echo "Variable run time is $variable_time seconds per stressor"
had_error=0
+numa_nodes=$(numactl --hardware | grep available | head -n 1 | cut -f 2 -d " ")
+
# NOTE: Specify stressors in two arrays rather than rely on stress-ng's
# --class memory,vm option for two reasons:
# 1. We want to run some stressors (those that exhaust all memory)
@@ -130,6 +132,9 @@ crt_stressors=("bsearch" "context" "hsearch" "lsearch" "matrix" \
"memcpy" "null" "pipe" "qsort" "stack" "str" "stream" \
"tsearch" "vm-rw" "wcs" "zero" "mlock" "mmapfork" "mmapmany" \
"mremap" "shm-sysv" "vm-splice")
+if [ "$numa_nodes" -gt 1 ]; then
+ crt_stressors+=("numa")
+fi
crt_runtime=$((${#crt_stressors[@]}*$base_time))
# Variable-run-time stressors -- run them longer on systems with more RAM....
@@ -152,7 +157,7 @@ echo "*******************************************************************"
if [ $had_error = "0" ] ; then
echo "** stress-ng memory test passed!"
else
- echo "** stress-ng memory test failed; most recent error was $return_code"
+ echo "** stress-ng memory test failed; most recent error was $result"
fi
echo "*******************************************************************"
exit $result
diff --git a/bin/network b/bin/network
index 7ba8ef0..ae9cd38 100755
--- a/bin/network
+++ b/bin/network
@@ -280,29 +280,26 @@ class Interface(socket.socket):
@property
def max_speed(self):
- # Parse ethtool data for max speed since /sys/class/net/DEV/speed only
- # reports link speed.
-
- # Search for things that look like 100baseSX,
- # 40000baseNX, 10000baseT
+ # Parse mii-tool data for max speed
+ # search for numbers in the line starting with 'capabilities'
+ # return largest number as max_speed
try:
- ethinfo = check_output(['ethtool', self.interface],
- universal_newlines=True,
- stderr=STDOUT).split(' ')
+ info = check_output(['mii-tool', '-v', self.interface],
+ universal_newlines=True,
+ stderr=STDOUT).split('\n')
except FileNotFoundError:
- logging.warning('ethtool not found! Unable to get max speed')
+ logging.warning('mii-tool not found! Unable to get max speed')
ethinfo = None
except CalledProcessError as e:
- logging.error('ethtool returned an error!')
+ logging.error('mii-tool returned an error!')
logging.error(e.output)
ethinfo = None
finally:
- expression = '(\\d+)(base)([A-Z]+)|(\d+)(Mb/s)'
- regex = re.compile(expression)
+ regex = re.compile(r'(\d+)(base)([A-Z]+)')
speeds = [0]
- if ethinfo:
- for i in ethinfo:
- hit = regex.search(i)
+ for line in filter(lambda l: 'capabilities' in l, info):
+ for s in line.split(' '):
+ hit = regex.search(s)
if hit:
speeds.append(int(re.sub("\D", "", hit.group(0))))
return max(speeds)
diff --git a/bin/virtualization b/bin/virtualization
index fe59656..0ee74e9 100755
--- a/bin/virtualization
+++ b/bin/virtualization
@@ -8,6 +8,7 @@ Copyright (C) 2013, 2014 Canonical Ltd.
Authors
Jeff Marcom <jeff.marcom@canonical.com>
Daniel Manrique <roadmr@ubuntu.com>
+ Jeff Lane <jeff@ubuntu.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3,
@@ -30,6 +31,7 @@ import os
import re
import logging
import lsb_release
+import requests
import shlex
import signal
from subprocess import (
@@ -110,7 +112,7 @@ QEMU_ARCH_CONFIG = {
'qemu_extra_args': [
'-enable-kvm',
'-machine', 'pseries,usb=off',
- '-cpu', 'POWER8',
+ '-cpu', 'host',
],
},
's390x': {
@@ -214,48 +216,108 @@ class KVMTest(object):
# Gives us the stuff needed to build the URL to download the image
return self.download_image(image_path)
- def construct_cloud_filename(self):
+ def construct_cloud_url(self, image_url=None):
"""
Build a URL for official Ubuntu images hosted either at
cloud-images.ubuntu.com or on a maas server hosting a mirror of
cloud-images.ubuntu.com
"""
- if self.qemu_config['cloudimg_type'] == CLOUD_IMAGE_TYPE_TAR:
- cloud_iso = "%s-server-cloudimg-%s.tar.gz" % (
- self.release, self.qemu_config['cloudimg_arch'])
- elif self.qemu_config['cloudimg_type'] == CLOUD_IMAGE_TYPE_DISK:
- cloud_iso = "%s-server-cloudimg-%s-disk1.img" % (
- self.release, self.qemu_config['cloudimg_arch'])
- else:
- logging.error("Unknown cloud image type")
- sys.exit(1)
- return cloud_iso
+ def _construct_filename(alt_pattern=None):
+ if self.qemu_config['cloudimg_type'] == CLOUD_IMAGE_TYPE_TAR:
+ cloud_iso = "%s-server-cloudimg-%s.tar.gz" % (
+ self.release, self.qemu_config['cloudimg_arch'])
+ elif alt_pattern is "modern":
+ # LP 1635345 - yakkety and beyond have a new naming scheme
+ cloud_iso = "%s-server-cloudimg-%s.img" % (
+ self.release, self.qemu_config['cloudimg_arch'])
+ elif self.qemu_config['cloudimg_type'] == CLOUD_IMAGE_TYPE_DISK:
+ cloud_iso = "%s-server-cloudimg-%s-disk1.img" % (
+ self.release, self.qemu_config['cloudimg_arch'])
+ else:
+ logging.error("Unknown cloud image type")
+ sys.exit(1)
+
+ return cloud_iso
+
+ def _construct_url(initial_url, cloud_iso):
+ return "/".join((initial_url, cloud_iso))
+
+ def _test_cloud_url(url):
+ # test our URL to make sure it's reachable
+ ret = requests.head(url)
+ if ret.status_code is not 200:
+ return False
+ else:
+ return True
- def download_image(self, image_url=None):
- """
- Downloads Cloud image for same release as host machine
- """
if image_url is None:
# If we have not specified a URL to get our images from, default
# to ubuntu.com
- cloud_url = "http://cloud-images.ubuntu.com"
- cloud_iso = self.construct_cloud_filename()
- full_url = "/".join((
- cloud_url, self.release, "current", cloud_iso))
+ cloud_iso = _construct_filename()
+ initial_url = "/".join(("http://cloud-images.ubuntu.com",
+ self.release, "current"))
+ full_url = _construct_url(initial_url, cloud_iso)
+ # Test our URL and rebuild with alternate name
+ if not _test_cloud_url(full_url):
+ logging.warn("Cloud Image URL not valid: %s" % full_url)
+ logging.warn(" * This means we could not reach the remote "
+ "file. Retrying with a different filename "
+ "schema.")
+ cloud_iso = _construct_filename("modern")
+ full_url = _construct_url(initial_url, cloud_iso)
+ # retest one more time then exit if it still fails
+ if not _test_cloud_url(full_url):
+ logging.error("Cloud URL is not valid: %s" %
+ full_url)
+ logging.error(" * It appears that there is a problem "
+ "finding the expected file. Check the URL "
+ "noted above.")
+ sys.exit(1)
else:
url = urlparse(image_url)
- if url.path.endswith('/') or url.path == '':
- # If we have a relative URL (MAAS server mirror)
- cloud_url = image_url
- cloud_iso = self.construct_cloud_filename()
- full_url = "/".join((
- cloud_url, cloud_iso))
+ if (
+ url.path.endswith('/') or
+ url.path == '' or
+ not url.path.endswith(".img")
+ ):
+ # If we have a relative URL (local copies of official images)
+ # http://192.168.0.1/ or http://192.168.0.1/images/
+ cloud_iso = _construct_filename()
+ full_url = _construct_url(image_url.rstrip("/"), cloud_iso)
+ if not _test_cloud_url(full_url):
+ logging.warn("Cloud Image URL not valid: %s" % full_url)
+ logging.warn(" * This means we could not reach the remote "
+ "file. Retrying with a different filename "
+ "schema.")
+ cloud_iso = _construct_filename("modern")
+ full_url = _construct_url(image_url.rstrip("/"), cloud_iso)
+ if not _test_cloud_url(full_url):
+ logging.error("Cloud URL is not valid: %s" %
+ full_url)
+ logging.error(" * It appears that there is a problem "
+ "finding the expected file. Check the "
+ "URL noted above.")
+ sys.exit(1)
else:
# Assume anything else is an absolute URL to a remote server
- cloud_iso = url.path.split('/')[-1]
- cloud_url = "{}://{}".format(url.scheme, url.netloc)
- full_url = image_url
- logging.debug("Downloading {}, from {}".format(cloud_iso, cloud_url))
+ if not _test_cloud_url(image_url):
+ logging.error("Cloud Image URL invalid: %s" % image_url)
+ logging.error(" * Check the URL and ensure it is correct")
+ sys.exit(1)
+ else:
+ full_url = image_url
+
+ return full_url
+
+ def download_image(self, image_url=None):
+ """
+ Downloads Cloud image for same release as host machine
+ """
+ if image_url is None:
+ full_url = self.construct_cloud_url()
+ else:
+ full_url = self.construct_cloud_url(image_url)
+ logging.debug("Acquiring cloud image from: {}".format(full_url))
# Attempt download
try:
@@ -394,7 +456,7 @@ final_message: CERTIFICATION BOOT COMPLETE
# Download cloud image
self.image = self.download_image()
else:
- logging.debug('Cloud image location specified: %s.' %
+ logging.debug('Cloud image location specified: %s' %
self.image)
self.image = self.url_to_path(self.image)
@@ -507,6 +569,9 @@ def main():
except AttributeError:
pass # avoids exception when trying to run without specifying 'kvm'
+ # silence normal output from requests module
+ logging.getLogger("requests").setLevel(logging.WARNING)
+
# to check if not len(sys.argv) > 1
if len(vars(args)) == 0:
parser.print_help()
diff --git a/jobs/miscellanea.txt.in b/jobs/miscellanea.txt.in
index b5544c2..295ea95 100644
--- a/jobs/miscellanea.txt.in
+++ b/jobs/miscellanea.txt.in
@@ -72,6 +72,20 @@ environ: PLAINBOX_SESSION_SHARE
command:
fwts_test -l $PLAINBOX_SESSION_SHARE/fwts_results.log
+plugin: shell
+category_id: 2013.com.canonical.plainbox::miscellanea
+id: miscellanea/fwupdate
+estimated_duration: 1.0
+depends: miscellanea/efi_boot_mode
+requires:
+ cpuinfo.platform in ("i386", "x86_64", "aarch64", "armhf")
+ package.name == 'fwupdate'
+_description:
+ Determine if EFI firmware supports update from OS.
+_summary:
+ Check for firmware update support.
+command: fwupdate -s
+
plugin: attachment
category_id: 2013.com.canonical.plainbox::miscellanea
id: miscellanea/fwts_results.log
@@ -132,6 +146,20 @@ command: boot_mode_test secureboot
plugin: shell
category_id: 2013.com.canonical.plainbox::miscellanea
+estimated_duration: 0.5
+user: root
+id: miscellanea/efi_pxeboot
+requires:
+ cpuinfo.platform in ("i386", "x86_64", "aarch64")
+depends: miscellanea/efi_boot_mode
+_summary: Test that system booted from the network
+_description:
+ Test to verify that the system booted from the network.
+ Works only on EFI-based systems.
+command: efi-pxeboot
+
+plugin: shell
+category_id: 2013.com.canonical.plainbox::miscellanea
id: miscellanea/bmc_info
requires:
package.name == 'ipmitool'
@@ -163,7 +191,7 @@ id: miscellanea/dmitest_server
requires: package.name == 'dmidecode'
estimated_duration: 0.5
user: root
-command: dmitest --test_versions --test_serials server
+command: dmitest --test_versions server
_description:
Sanity check of DMI system identification data (for servers)
_summary:
@@ -275,15 +303,9 @@ plugin: shell
category_id: 2013.com.canonical.plainbox::miscellanea
estimated_duration: 0.1
id: miscellanea/get_maas_version
-command:
- if [ -s /etc/installed-by-maas ]; then
- cat /etc/installed-by-maas
- else
- echo "MAAS VERSION NOT FOUND" >&2
- false
- fi
+command: maas-version-check
_description: If system was installed via MAAS from a cert server, the MAAS version used should be contained in /etc/installed-by-maas
-_summary: Attach MAAS version used to deploy the SUT
+_summary: Verify MAAS version used to deploy the SUT
plugin: shell
category_id: 2013.com.canonical.plainbox::miscellanea
diff --git a/manage.py b/manage.py
index fb48698..9818362 100755
--- a/manage.py
+++ b/manage.py
@@ -30,7 +30,7 @@ class InstallPyModules(InstallCommand):
setup(
name='plainbox-provider-checkbox',
namespace='2013.com.canonical.certification',
- version="0.33.0",
+ version="0.34.0rc1",
description=N_("Checkbox provider"),
gettext_domain='plainbox-provider-checkbox',
strict=False, deprecated=False,