diff --git a/meta-arm-autonomy/documentation/xenguest-manager.md b/meta-arm-autonomy/documentation/xenguest-manager.md index 5a66e8a0..11df2a1d 100644 --- a/meta-arm-autonomy/documentation/xenguest-manager.md +++ b/meta-arm-autonomy/documentation/xenguest-manager.md @@ -23,7 +23,7 @@ Usage ----- xenguest-manager must be called like this: -`xenguest-manager OPERATION [OPTIONS]` +`xenguest-manager [-v(v)] OPERATION [OPTIONS]` The following operations are available: - create XENGUEST_IMAGE [GUESTNAME]: create a guest from a xenguest image file as guest GUESTNAME. If GUESTNAME is not given the image file name is used @@ -37,6 +37,9 @@ The following operations are available: - status [GUESTNAME]: print the current status of GUESTNAME. If GUESTNAME is not given, print the status of all guests. +Passing -v or -vv will increase the logging written to the logfile. +The terminal will always show only error messages, regardless of the logfile. + For a detailed help on available options please use: `xenguest-manager --help` @@ -65,6 +68,13 @@ The following parameters are available: name). This is set by default to "/usr/share/guests". +- XENGUEST_MANAGER_LOG_LEVEL: Set the default log level for xenguest manager. Must + be one of ERROR, INFO, VERBOSE (default: ERROR). The extra will be + written to /var/log/xenguest. + + If a verbosity argument (-v or -vv) is passed to xenguest-manager directly, it + will override the setting in xenguest-manager.conf + Init scripts ------------ @@ -80,7 +90,7 @@ directory on the target: Inside the directory, scripts will be executed in alphabetical order. -Since these scripts are sourced by xenguest-manager they can acccess functions +Since these scripts are sourced by xenguest-manager, they can acccess functions and variables from the parent file's scope, including: - ${guestname} : The name of the guest being created @@ -89,12 +99,31 @@ and variables from the parent file's scope, including: - ${guestcfgfile} : The name of the config file for the starting guest -- ${LOGFILE} : The file to append any logging to, e.g. - echo "Hello, World" >> ${LOGFILE} +- log() : Used to write a log to the logfile, default level INFO. + Takes an optional log level and a message body + e.g. log ERROR "blah" + + Options for log level: ERROR, INFO, VERBOSE, and FATAL, which + will call exit 1 immediately after logging the message + +- log_command() : Used to call a shell command and log that it has been + called, as well as capturing both stdout and stderr. + + By default the command output is dumped to the logfile as an error + if the command returns a status > 0, or as a verbose message if the + whole script is running in verbose mode. An optional log level can + be passed to alter the level the log should be if the command returns + a status >0, + e.g. log_command INFO "ls -lh ~" + + Options for log level: ERROR, INFO, and VERBOSE + +Attempting to call any other functions from xenguest_manager in an init script may +result in a fatal error, from which cleanup is not guarenteed. + Sourcing also allows the script to access params.cfg. - An example of how to create the directory and install an init shell script can be found in: recipes-extended/xenguest/xenguest-network.bb diff --git a/meta-arm-autonomy/recipes-extended/xenguest/files/logrotate-xenguest b/meta-arm-autonomy/recipes-extended/xenguest/files/logrotate-xenguest new file mode 100644 index 00000000..fefc3476 --- /dev/null +++ b/meta-arm-autonomy/recipes-extended/xenguest/files/logrotate-xenguest @@ -0,0 +1,6 @@ +/var/log/xenguest { + missingok + size 10k + copytruncate + rotate 2 +} diff --git a/meta-arm-autonomy/recipes-extended/xenguest/files/network-bridge.sh.in b/meta-arm-autonomy/recipes-extended/xenguest/files/network-bridge.sh.in index 967c2456..3f87b76e 100755 --- a/meta-arm-autonomy/recipes-extended/xenguest/files/network-bridge.sh.in +++ b/meta-arm-autonomy/recipes-extended/xenguest/files/network-bridge.sh.in @@ -11,11 +11,13 @@ BRIDGE_NAME="###BRIDGE_NAME###" case "${XENGUEST_NETWORK_TYPE:=}" in nat) echo "vif = ['script=vif-nat']" >> ${guestcfgfile} + log info "Network type is NAT" ;; bridge) echo "vif = ['script=vif-bridge,bridge=${BRIDGE_NAME}']" >> ${guestcfgfile} + log info "Network type is bridge: ${BRIDGE_NAME}" ;; *) - echo "${@}: XENGUEST_NETWORK_TYPE=$XENGUEST_NETWORK_TYPE invalid" + log error "XENGUEST_NETWORK_TYPE=$XENGUEST_NETWORK_TYPE invalid" ;; esac diff --git a/meta-arm-autonomy/recipes-extended/xenguest/files/xenguest-manager b/meta-arm-autonomy/recipes-extended/xenguest/files/xenguest-manager index 9acbca0d..b90cc73d 100755 --- a/meta-arm-autonomy/recipes-extended/xenguest/files/xenguest-manager +++ b/meta-arm-autonomy/recipes-extended/xenguest/files/xenguest-manager @@ -7,21 +7,179 @@ this="$0" XENGUEST_CONF_BASE="/etc/xenguest" LOGFILE="/var/log/xenguest" +# Valid values for log level +LOG_LEVEL_VALID="FATAL ERROR INFO VERBOSE" + +# Log levels being written to logfile +LOG_LEVEL_LIST="ERROR INFO VERBOSE" +# Affected by -v(v) param and conf file + +# Log levels being written to terminal +VERBOSE_LOG_LEVEL="ERROR" +# Constant + +# Highest Log Level: Default is ERROR only +LOG_LEVEL="ERROR" +# Used to update LOG_LEVEL_LIST + + +# This should only be called from either log() or log_command. +# It expectd $loglevel and $text to already be in scope +function log_to_file () +{ + if [[ ${LOG_LEVEL_LIST} = *${loglevel}* ]]; then + tstamp="$(date +"%d-%m-%Y %T")" + tag="[${loglevel}]" + + printf "%s %-9s %s\n" "$tstamp" "$tag" "$text" >> ${LOGFILE} + fi +} + +# Write a log to the logfile, and to the console +# Messages are written to the log with the date and a timestamp +function log () +{ + # Inputs: + # $1 - optional level to log at, one of ${LOG_LEVEL_VALID} + # Default: INFO + # $@ - log message body + + # get loglevel from parameter and capitalise + loglevel=${1^^} + + # If no loglevel is passed use INFO + if [[ ${LOG_LEVEL_VALID} = *${loglevel:-INVALID}* ]]; then + shift + else + loglevel="INFO" + fi + + # Kill script immediately after a fatal log + killscript=0 + if [ "FATAL" = ${loglevel} ]; then + killscript=1 + # Log at error level for the user + loglevel="ERROR" + fi + + text="$*" + log_to_file + + # Write to terminal if level is high enough + if [[ ${VERBOSE_LOG_LEVEL} = *${loglevel}* ]]; then + echo "${loglevel}: ${text}" + fi + + # if Log was fatal, kill the script + if [[ ${killscript} = 1 ]]; then + exit 1 + fi +} + +# Write a shell command to the log and execute it +# The stdout and stderr output of the command is captured in a variable, +# and written to the logfile in two cases: +# 1. The script is in verbose mode +# 2. The command returns a non-zero status AND +# The loglevel parameter (default: ERROR) is in $LOG_LEVEL_LIST +# +# This means by default a non-zero status results in a log tagged [ERROR], +# but if a command is expected to fail, the tag can be reduced for visual +# clarity +log_command () +{ + # Inputs: + # $1 - optional level to write errors at, one of ${LOG_LEVEL_VALID} + # Default: ERROR + # $@ - command to execute + + # get loglevel from parameter and capitalise + loglevel=${1^^} + + # If no level passed, log output on failure at ERROR + if [[ ${LOG_LEVEL_VALID} = *"${loglevel:-INVALID}"* ]]; then + shift + else + loglevel="ERROR" + fi + + # Commands cannot be logged at FATAL. + if [ "FATAL" = ${loglevel} ]; then + loglevel="ERROR" + fi + local command="$*" + local output="" + local status=0 + + # Capture stdout and sterr to write to logfile + output=$(eval "${command} 2>&1") + status=$? + # If command failed, or verbose mode, write log + if [[ ${status} -ne 0 ]] || [[ ${LOG_LEVEL_LIST} = *VERBOSE* ]]; then + + # if command didn't fail write it at verbose level + if [[ ${status} -eq 0 ]]; then + loglevel="VERBOSE" + fi + # otherwise write it at ${loglevel} from arguments + + local append_to="/dev/null" + # If we are writing ${loglevel} logs to file, use file as append_to + if [[ ${LOG_LEVEL_LIST} = *${loglevel}* ]]; then + append_to=${LOGFILE} + fi + + # Log that command was called + text="> ${command}" + log_to_file + + # Write command output to logfile or /dev/null, indent to match rest of logs + if [[ -n ${output} ]]; then + echo "${output}" | sed 's/^/ /' >> ${append_to} + fi + # Log exit status + text="< Exited with status ${status}" + log_to_file + fi + # Ensure return status is captured + return $status +} + +# Sources a shell script and logs it +log_source () +{ + local script=${1} + log verbose "> source ${script}" + + ( . ${script} ) + + status=$? + log verbose "< Exited with status ${status}" + + return $status +} + if [ ! -f ${XENGUEST_CONF_BASE}/xenguest-manager.conf ]; then - echo "Cannot find xenguest manager configuration" - exit 1 + log fatal "Cannot find xenguest manager configuration" fi # Following variables must be set in configuration: # XENGUEST_VOLUME_DEVICE: device to use for lvm # XENGUEST_VOLUME_NAME: lvm volume name to create on device +# Optionally set: +# XENGUEST_LOG_LEVEL: the loglevel for terminal and logfile source ${XENGUEST_CONF_BASE}/xenguest-manager.conf -PREF="xenguest:" +# Check that VERBOSE level from config file is valid +if [[ ${LOG_LEVEL_LIST} = *${XENGUEST_LOG_LEVEL}* ]]; then + LOG_LEVEL=${XENGUEST_LOG_LEVEL} +else + log error "Invalid log level '${XENGUEST_LOG_LEVEL}' found in xenguest-manager.conf" +fi function usage() { cat <&2 echo "error: invalid number '${1}'"; exit 1 + log fatal "invalid number '${1}'" fi } +# Public # check size and convert it to MB, e.g '1[G]' => '1000M' check_size() { - local disksize="${1}" [ -n "${disksize}" ] || disksize="invalid" @@ -106,11 +268,10 @@ check_size() { ;; esac - >&2 echo -e "Invalid size format '${1}'" \ - "\n\tSupported size format is e.g 1000M or 2[G]" - exit 1 + log fatal "Invalid size format '${1}'. Supported size format is e.g 1000M or 2[G]" } +# Private function xenguest_volume_init() { # Inputs: @@ -121,80 +282,79 @@ function xenguest_volume_init() # 0 - success # 1 - failure - check_private - local diskdevice local volumename diskdevice="${1}" volumename="${2}" + log info "Attempting to initialise xenguest volume '${volumename}'" + + check_private + if [ -z "${diskdevice}" -o ! -b "${diskdevice}" ]; then - echo "${PREF} Invalid volume device in configuration: ${diskdevice}" + log error "Invalid volume device in configuration: '${diskdevice}'" return 1 fi if [ -z "${volumename}" ]; then - echo "${PREF} Invalid volume name in configuration: ${volumename}" + log error "Invalid volume name in configuration: '${volumename}'" return 1 fi - pvs "${diskdevice}" > /dev/null 2>&1 + log_command verbose "pvs ${diskdevice}" if [ $? -ne 0 ]; then # Check if there is no filesystem in the block device - echo "lsblk -n -o FSTYPE ${diskdevice}" >> ${LOGFILE} 2>&1 - filesystem=$(lsblk -n -o FSTYPE "${diskdevice}" 2>> ${LOGFILE}) + log verbose "Checking for existing filesystem" + filesystem=$(lsblk -n -o FSTYPE ${diskdevice}) if [[ $? -eq 0 && -z "$filesystem" ]]; then - echo "${PREF} Initialize lvm on ${diskdevice}" - echo "pvcreate -f ${diskdevice}" >> ${LOGFILE} 2>&1 - pvcreate -f "${diskdevice}" >> ${LOGFILE} 2>&1 + log verbose "No filesystem found" + log info "Initializing lvm on ${diskdevice}" + log_command "pvcreate -f ${diskdevice}" if [ $? -ne 0 ]; then - echo "${PREF} Error: initialing lvm on " \ - "${diskdevice} failed." | tee -a ${LOGFILE} + log error "Initialing lvm on ${diskdevice} failed." return 1 fi else [ -z "$filesystem" ] || \ - echo "${PREF} Error: The ${diskdevice} is already " \ - "formatted as $filesystem." | tee -a ${LOGFILE} + log error "${diskdevice} is already formatted as $filesystem." return 1 fi fi - vgs "${volumename}" > /dev/null 2>&1 + log_command verbose "vgs ${volumename}" if [ $? -ne 0 ]; then - echo "${PREF} Create ${volumename} volume" - echo "vgcreate ${volumename} ${diskdevice}" \ - >> ${LOGFILE} 2>&1 - vgcreate "${volumename}" "${diskdevice}" \ - >> ${LOGFILE} 2>&1 + log info "Creating ${volumename} volume" + log_command "vgcreate ${volumename} ${diskdevice}" if [ $? -ne 0 ]; then - echo "${PREF} Error: creating ${volumename} volume " \ - "failed." | tee -a ${LOGFILE} + log error "Creating ${volumename} volume failed." return 1 fi fi + log info "xenguest volume '${volumename}' initialised successfully" + return 0 } +# Private # Detach a disk we attached to xen function xenguest_detach_disk() { + log verbose "Attempting to detach partition '${part}'" + check_private - echo "xl block-detach 0 \$\(xl block-list 0 | " \ - "grep \"domain/0\" | awk '{print \$1}'\)" \ - >> ${LOGFILE} 2>&1 - xl block-detach 0 $(xl block-list 0 | \ - grep "domain/0" | awk '{print $1}') \ - >> ${LOGFILE} 2>&1 + log_command "xl block-detach 0 \$(xl block-list 0 | grep \"domain/0\" | awk '{print \$1}')" if [ $? -ne 0 ]; then - echo "${PREF} Error detaching partition ${part}" + log error "Detaching partition '${part}' failed." return 1 fi + + log verbose "Partition '${part}' detached successfully" } +# Private function xenguest_disk_init() { # Inputs: @@ -206,79 +366,78 @@ function xenguest_disk_init() # 1 - failed at guest disk preparation # 2 - failed at guest disk creation - check_private - guestname="$1" guestfile="$2" + log info "Attempting to initialise disk for guest '${guestname}'" + + check_private + source ${XENGUEST_CONF_BASE}/guests/${guestname}/disk.cfg if [ -z "${DISK_DEVICE}" ]; then + log info "Using disk device and volume name from xenguest-manager.conf" # By default guest is using disk defined inside xenguest-manager.conf diskdevice="${XENGUEST_VOLUME_DEVICE}" volumename="${XENGUEST_VOLUME_NAME}" else + log info "Using disk device set in disk.cfg" # If guest configuration contains custom disk setting, # overwrite default one diskdevice="${DISK_DEVICE}" volumename="vg-xen-$(basename ${diskdevice})" fi + + log verbose "Disk Device = ${diskdevice}" + log verbose "Volume Name = ${volumename}" + devname="/dev/${volumename}/${guestname}" DISK_SIZE=$(check_size "${DISK_SIZE}") if [ -z "${DISK_SIZE}" ] || [ "${DISK_SIZE}" = "0M" ]; then - echo "${PREF} No disk for ${guestname}" + log info "No disk for ${guestname}" return fi - echo "${PREF} Create ${guestname} disk." - # Init our volume xenguest_volume_init "${diskdevice}" "${volumename}" if [ $? -ne 0 ]; then return 1 fi - echo "${PREF} Create hard drive for ${guestname}." \ - "This might take a while..." - + log info "Creating hard drive for guest '${guestname}'. This might take a while..." # Remove volume if it already exist - echo "lvs ${volumename}/${guestname}" >> ${LOGFILE} 2>&1 - lvs "${volumename}/${guestname}" >> ${LOGFILE} 2>&1 + log_command verbose "lvs ${volumename}/${guestname}" if [ $? -eq 0 ]; then - echo "lvremove -y ${devname}" >> ${LOGFILE} 2>&1 - lvremove -y "${devname}" >> ${LOGFILE} 2>&1 + log info "Removing existing volume ${devname}" + log_command "lvremove -y ${devname}" if [ $? -ne 0 ]; then - echo "${PREF} Error removing volume ${guestname}" + log error "Removing existing volume ${devname} failed." return 1 fi fi # Create volume - echo "lvcreate -y -L ${DISK_SIZE} -n ${guestname} ${volumename}" \ - >> ${LOGFILE} 2>&1 - lvcreate -y -L "${DISK_SIZE}" -n "${guestname}" "${volumename}" \ - >> ${LOGFILE} 2>&1 + log info "Creating volume '${volumename}/${guestname}'" + log_command "lvcreate -y -L ${DISK_SIZE} -n ${guestname} ${volumename}" if [ $? -ne 0 ]; then - echo "${PREF} Error creating volume ${guestname}" + log error "Creating volume '${volumename}/${guestname}' failed." return 1 fi # Add partition table - echo "parted -s ${devname} mklabel msdos" >> ${LOGFILE} 2>&1 - parted -s "${devname}" mklabel msdos >> ${LOGFILE} 2>&1 + log verbose "Creating partition table on ${devname}" + log_command "parted -s \"${devname}\" mklabel msdos" if [ $? -ne 0 ]; then - echo "${PREF} Error creating partition table on ${guestname}" + log error "Creating partition table on ${devname} failed." return 1 fi # Setup disk name in xen configuration - echo "xenguest-mkimage update ${XENGUEST_CONF_BASE}/guests/${guestname}" \ - "--xen-disk=${devname}" >> ${LOGFILE} 2>&1 - xenguest-mkimage update "${XENGUEST_CONF_BASE}/guests/${guestname}" \ - "--xen-disk=${devname}" >> ${LOGFILE} 2>&1 + log verbose "Setting disk name in xen configuration" + log_command "xenguest-mkimage update \"${XENGUEST_CONF_BASE}/guests/${guestname}\" --xen-disk=\"${devname}\"" if [ $? -ne 0 ]; then - echo "${PREF} Error setting disk in xen configuration" + log error "Setting disk name in xen configuration failed." return 1 fi @@ -328,12 +487,10 @@ function xenguest_disk_init() fi # Create partition - echo "parted -s ${devname} unit MB mkpart primary ${partstart}" \ - "${partend}" >> ${LOGFILE} 2>&1 - parted -s "${devname}" unit MB mkpart primary "${partstart}" \ - "${partend}" >> ${LOGFILE} 2>&1 + log verbose "Adding partition ${part}" + log_command "parted -s \"${devname}\" unit MB mkpart primary \"${partstart}\" \"${partend}\"" if [ $? -ne 0 ]; then - echo "${PREF} Error adding partition ${part}" + log error "Adding partition ${part} failed." return 1 fi @@ -341,8 +498,8 @@ function xenguest_disk_init() partstart="${partend}" # Sync to see the created partition - echo "sync" >> ${LOGFILE} 2>&1 - sync >> ${LOGFILE} 2>&1 + log verbose "Sync created partition" + log_command "sync" # Prepare format command if [ -n "${fstype}" ]; then @@ -354,8 +511,7 @@ function xenguest_disk_init() formatcmd="mkswap" ;; *) - echo "${PREF} partition ${part} of ${guestname}" \ - "fstype is invalid: ${fstype}" + log error "Partition ${part} of ${guestname} fstype is invalid '${fstype}'" return 1 ;; esac @@ -364,10 +520,10 @@ function xenguest_disk_init() fi # Attach disk to xen - echo "xl block-attach 0 phy:${devname} xvda w" >> ${LOGFILE} 2>&1 - xl block-attach 0 "phy:${devname}" xvda w >> ${LOGFILE} 2>&1 + log verbose "Attaching partition ${part}" + log_command "xl block-attach 0 \"phy:${devname}\" xvda w" if [ $? -ne 0 ]; then - echo "${PREF} Error attaching partition ${part}" + log error "Attaching partition ${part} failed." return 1 fi @@ -383,15 +539,17 @@ function xenguest_disk_init() done if [ ! -b /dev/xvda${part} ]; then - echo "${PREF} Partition ${part} creation error" + log error "Partition ${part} creation failed." return 2 fi + log verbose "/dev/xvda${part} created" + if [ -n "${formatcmd}" ]; then - echo "${formatcmd} /dev/xvda${part}" >> ${LOGFILE} 2>&1 - ${formatcmd} /dev/xvda${part} + log info "Creating filesystem for partition '${part}'" + log_command "${formatcmd} /dev/xvda${part}" if [ $? -ne 0 ]; then - echo "${PREF} Cannot create partition ${part} FS" + log error "Creating filesystem for partition '${part}' failed." return 2 fi fi @@ -411,17 +569,15 @@ function xenguest_disk_init() ;; *) # invalid/unknown compression type - echo "${PREF} Invalid file format in disk ${content}" + log error "Invalid file format in disk ${content}" return 2 ;; esac # dd into partition - echo "xenguest-mkimage extract-disk-file ${guestfile} " \ - "${content} | ${decompress} | dd of=/dev/xvda${part} " >> ${LOGFILE} 2>&1 - xenguest-mkimage extract-disk-file ${guestfile} ${content} \ - | ${decompress} | dd of=/dev/xvda${part} >> ${LOGFILE} 2>&1 + log verbose "Populating partition '${part}'" + log_command "xenguest-mkimage extract-disk-file ${guestfile} ${content} | ${decompress} | dd of=/dev/xvda${part} " if [ $? -ne 0 ]; then - echo "${PREF} Cannot populate partition ${part}" + log error "Populating partition '${part}' failed." return 2 fi ;; @@ -442,37 +598,33 @@ function xenguest_disk_init() ;; *) # invalid/unknown tar type - echo "${PREF} Invalid file format in disk ${content}" + log error "Invalid file format in disk ${content}" return 2 ;; esac # must mount the partition and extract mntdir=$(mktemp -d) - echo "mount /dev/xvda${part} ${mntdir}" >> ${LOGFILE} 2>&1 - mount /dev/xvda${part} ${mntdir} >> ${LOGFILE} 2>&1 + log verbose "Mounting partition '${part}'" + log_command "mount /dev/xvda${part} ${mntdir}" if [ $? -ne 0 ]; then - echo "${PREF} Cannot mount partition ${part}" + log error "Mounting partition '${part}' failed." rm -rf ${mntdir} return 2 fi # tar and unmount - echo "xenguest-mkimage extract-disk-file ${guestfile}" \ - "${content} | tar -C ${mntdir} -x${tararg}f - " \ - >> ${LOGFILE} 2>&1 - xenguest-mkimage extract-disk-file ${guestfile} ${content} \ - | tar -C ${mntdir} -x${tararg}f - >> ${LOGFILE} 2>&1 + log_command "xenguest-mkimage extract-disk-file ${guestfile} ${content} |" \ + "tar -C ${mntdir} -x${tararg}f - " if [ $? -ne 0 ]; then - echo "${PREF} Cannot populate partition ${part}" + log error "Cannot populate partition ${part}" umount ${mntdir} rm -rf ${mntdir} return 2 fi - echo "umount ${mntdir}" >> ${LOGFILE} 2>&1 - umount ${mntdir} >> ${LOGFILE} 2>&1 + log_command "umount ${mntdir}" if [ $? -ne 0 ]; then - echo "${PREF} Error unmounting ${part}" + log error "Unmounting ${part} failed." rm -rf ${mntdir} return 2 fi @@ -491,40 +643,42 @@ function xenguest_disk_init() fi done + log info "Initialised disk for guest '${guestname}' successfully" + } +# Private function xenguest_guest_create() { - check_private - guestfile="$1" - guestname="$2" - # extract xenguest tar # put xen config in etc ? # if disk config file: # disk init # add partititions - echo "${PREF} Create ${guestname} using ${guestfile}" - rm -rf ${XENGUEST_CONF_BASE}/guests/${guestname} - mkdir -p ${XENGUEST_CONF_BASE}/guests/${guestname} + guestfile="$1" + guestname="$2" - echo "xenguest-mkimage extract-config ${guestfile}" \ - "${XENGUEST_CONF_BASE}/guests/${guestname}" >> ${LOGFILE} 2>&1 - xenguest-mkimage extract-config ${guestfile} \ - ${XENGUEST_CONF_BASE}/guests/${guestname} >> ${LOGFILE} 2>&1 + log info "Attempting to create guest '${guestname}' using ${guestfile}" + + check_private + + log verbose "Cleaning up old directory" + log_command verbose "rm -rf ${XENGUEST_CONF_BASE}/guests/${guestname}" + log verbose "Creating directory for guest '${guestname}'" + log_command "mkdir -p ${XENGUEST_CONF_BASE}/guests/${guestname}" + + log verbose "Extracting guest image" + log_command "xenguest-mkimage extract-config ${guestfile} ${XENGUEST_CONF_BASE}/guests/${guestname}" if [ $? -ne 0 ]; then - echo "${PREF} Error extracting guest image" - exit 1 + log fatal "Extracting guest image failed." fi # Set guest name inside config - echo "xenguest-mkimage update ${XENGUEST_CONF_BASE}/guests/${guestname}" \ - "--xen-name=${guestname}" >> ${LOGFILE} 2>&1 - xenguest-mkimage update ${XENGUEST_CONF_BASE}/guests/${guestname} \ - --xen-name=${guestname} >> ${LOGFILE} 2>&1 + log verbose "Setting guest name" + log_command "xenguest-mkimage update ${XENGUEST_CONF_BASE}/guests/${guestname} --xen-name=${guestname}" if [ $? -ne 0 ]; then - echo "${PREF} Error setting guest name" + log error "Setting guest name failed." xenguest_guest_remove ${guestname} exit 1 fi @@ -532,7 +686,7 @@ function xenguest_guest_create() xenguest_disk_init ${guestname} ${guestfile} disk_init_status=$? if [ $disk_init_status -ne 0 ]; then - echo "${PREF} Error: ${guestname} disk creation failed." + log error "Disk creation for guest '${guestname}' failed." if [ $disk_init_status -eq 2 ]; then xenguest_detach_disk fi @@ -540,13 +694,18 @@ function xenguest_guest_create() exit 1 fi + log info "Guest '${guestname}' created successfully" + } +# Private function xenguest_guest_remove() { + guestname="$1" + log info "Attempting to remove guest '${guestname}'" + check_private - guestname="$1" source ${XENGUEST_CONF_BASE}/guests/${guestname}/disk.cfg if [ -z "${DISK_DEVICE}" ]; then # By default guest is using disk defined inside xenguest-manager.conf @@ -561,31 +720,35 @@ function xenguest_guest_remove() devname="/dev/${volumename}/${guestname}" # check if guest had a volume - echo "lvs ${volumename}/${guestname}" >> ${LOGFILE} 2>&1 - lvs ${volumename}/${guestname} >> ${LOGFILE} 2>&1 + log verbose "Checking if ${guestname} has a volume to remove" + log_command verbose "lvs ${volumename}/${guestname}" if [ $? -eq 0 ]; then # Remove guest volume - echo "${PREF} Removing ${guestname} volume. This might take a while..." - echo "lvremove -y ${devname}" >> ${LOGFILE} 2>&1 - lvremove -y "${devname}" >> ${LOGFILE} 2>&1 + log info "Removing volume ${devname}. This might take a while..." + log_command "lvremove -y \"${devname}\"" if [ $? -ne 0 ]; then - echo "${PREF} Error removing volume ${guestname}" + log error "Removing volume ${devname} failed." exit 1 fi fi # remove guest files - echo "${PREF} Removing ${guestname} configuration files." - rm -rf ${XENGUEST_CONF_BASE}/guests/${guestname} + log info "Removing configuration files for guest '${guestname}'." + log_command "rm -rf ${XENGUEST_CONF_BASE}/guests/${guestname}" + + log info "Removed guest '${guestname}' succesfully" } +# Private function xenguest_guest_start() { - check_private - guestname="${1}" guestdir=${XENGUEST_CONF_BASE}/guests/${guestname} + log info "Attempting to start guest '${guestname}'" + + check_private + guestcfgfile=$(mktemp -u "${guestname}.XXXXXX" --tmpdir="${guestdir}" --suffix=".cfg") # Get guest configuration @@ -609,100 +772,93 @@ function xenguest_guest_start() sort) $(find ${guestdir}/init.post -type f 2> /dev/null | sort)" # call pre init scripts + log verbose "Calling pre-init scripts" for f in ${init_pre}; do if [ -x "$f" ]; then - echo "( . $f )" >> ${LOGFILE} 2>&1 - ( . $f ) >> ${LOGFILE} 2>&1 + log_source $f if [ $? -ne 0 ]; then rm -f ${guestcfgfile} popd > /dev/null 2>&1 - echo "Error in init script $f" >> ${LOGFILE} 2>&1 - echo "${PREF} Error during pre init script $(basename $f) of ${guestname}" - echo "${PREF} Check the log: ${LOGFILE} for more information" - exit 1 + log fatal "Pre-init script $(basename $f) of ${guestname} returned non-zero status" fi else - echo "$f is not executable. Skipping." >> ${LOGFILE} + log fatal "$f is not executable. Exiting..." fi done # Create non started guest - echo "xl create -p ${guestcfgfile}" >> ${LOGFILE} 2>&1 - xl create -p ${guestcfgfile} >> ${LOGFILE} 2>&1 + log verbose "Initiating ${guestname}" + log_command "xl create -p ${guestcfgfile}" if [ $? -ne 0 ]; then rm -f ${guestcfgfile} popd > /dev/null 2>&1 - echo "${PREF} Error starting ${guestname}" - exit 1 + log fatal "Initiating ${guestname} failed." fi # call init scripts + log verbose "Calling init scripts" for f in ${init_d}; do if [ -x "$f" ]; then - echo "( . $f )" >> ${LOGFILE} 2>&1 - ( . $f ) >> ${LOGFILE} 2>&1 + log_source $f if [ $? -ne 0 ]; then rm -f ${guestcfgfile} - echo "xl destroy ${guestname}" >> ${LOGFILE} 2>&1 - xl destroy ${guestname} >> ${LOGFILE} 2>&1 + log_command "xl destroy ${guestname}" popd > /dev/null 2>&1 - echo "Error in init script $f" >> ${LOGFILE} 2>&1 - echo "${PREF} Error during init script $(basename $f) of ${guestname}" - echo "${PREF} Check the log: ${LOGFILE} for more information" - exit 1 + log fatal "Init script $(basename $f) of ${guestname} returned non-zero status" fi else - echo "$f is not executable. Skipping." >> ${LOGFILE} + log fatal "$f is not executable. Exiting..." fi done # Start guest - echo "xl unpause ${guestname}" >> ${LOGFILE} 2>&1 - xl unpause ${guestname} >> ${LOGFILE} 2>&1 + log info "Starting ${guestname}" + log_command "xl unpause ${guestname}" if [ $? -ne 0 ]; then rm -f ${guestcfgfile} popd > /dev/null 2>&1 - echo "${PREF} Error starting ${guestname}" - exit 1 + log fatal "Starting ${guestname} failed." fi # call post init scripts + log verbose "Calling post-init scripts" for f in ${init_post}; do if [ -x "$f" ]; then - echo "( . $f )" >> ${LOGFILE} 2>&1 - ( . $f ) >> ${LOGFILE} 2>&1 + log_source $f if [ $? -ne 0 ]; then rm -f ${guestcfgfile} - echo "xl destroy ${guestname}" >> ${LOGFILE} 2>&1 - xl destroy ${guestname} >> ${LOGFILE} 2>&1 + log_command "xl destroy ${guestname}" popd > /dev/null 2>&1 - echo "Error in init script $f" >> ${LOGFILE} 2>&1 - echo "${PREF} Error during post init script $(basename $f) of ${guestname}" - echo "${PREF} Check the log: ${LOGFILE} for more information" - exit 1 + log fatal "Post-init script $(basename $f) of ${guestname} returned non-zero status" fi else - echo "$f is not executable. Skipping." >> ${LOGFILE} + log fatal "$f is not executable. Exiting..." fi done rm -f ${guestcfgfile} popd > /dev/null 2>&1 + + log info "Guest '${guestname}' started successfully" } +# Private function xenguest_guest_stop() { + guestname="${1}" + log info "Attempting to stop guest '${guestname}'" + check_private - guestname="${1}" - echo "xl shutdown ${guestname}" >> ${LOGFILE} 2>&1 - xl shutdown ${guestname} >> ${LOGFILE} 2>&1 + log_command "xl shutdown ${guestname}" if [ $? -ne 0 ]; then - echo "${PREF} Error stopping ${guestname}" - exit 1 + log fatal "Stopping guest '${guestname}' failed." fi + + echo "Guest '${guestname}' may not have stopped yet. Use 'status' to check" } +# Private function check_guest_arg() { check_private @@ -710,51 +866,76 @@ function check_guest_arg() cmd="${1}" guestname="${2:-}" if [ -z "${guestname:-}" ]; then - echo "${PREF} Usage ${this} ${cmd} GUESTNAME" - exit 1 + log fatal "Usage ${this} ${cmd} GUESTNAME" fi } +# Public function check_guest_exist() { guestname="${1}" if [ ! -f ${XENGUEST_CONF_BASE}/guests/${guestname}/guest.cfg -o \ ! -f ${XENGUEST_CONF_BASE}/guests/${guestname}/params.cfg ]; then - echo "${PREF} Invalid guest name: ${guestname}" - exit 1 + log fatal "Invalid guest name '${guestname}'" fi + + log verbose "Guest '${guestname}' found: ${XENGUEST_CONF_BASE}/guests/${guestname}/" } +# Public function xl_list_contains() { guestname="${1}" # Select first column of xl list, and find guestname exactly using regex running=$(xl list | awk 'NR > 1 {print $1}' | grep "^${guestname}$" || echo) if [ "${running}" = "${guestname}" ]; then + log verbose "Guest '${guestname}' is running" return 0 fi + log verbose "Guest '${guestname}' is not running" + return 1 } +# Public function check_guest_running() { guestname="${1}" if ! xl_list_contains $guestname; then - echo "${PREF} Guest ${guestname} is not running" - exit 1 + log fatal "Cannot ${cmd} guest '${guestname}', already stopped" fi } +# Public function check_guest_not_running() { guestname="${1}" if xl_list_contains $guestname; then - echo "${PREF} Guest ${guestname} is running" - exit 1 + log fatal "Cannot ${cmd} guest '${guestname}', already started" fi } +## Entry Point ## + +# Check for verbose level arguments, and shift if found +case ${1:-help} in + -v|-V) + LOG_LEVEL="INFO" + shift + ;; + -vv|-VV) + LOG_LEVEL="VERBOSE" + shift + ;; +esac + +# Limit Verbose list to only those desired to be shown +LOG_LEVEL_LIST=${LOG_LEVEL_LIST//${LOG_LEVEL}*/${LOG_LEVEL}} + +log "" +log "Arguments: $*" + cmd="${1:-help}" arg1="${2:-}" arg2="${3:-}" @@ -764,30 +945,26 @@ case ${cmd} in usage exit 0 ;; - esac # Check if we have a valid Dom0 booted with Xen -ERROR_MSG=$(xl info 2>&1) +log_command "xl info" if [ $? -ne 0 ]; then - echo "ERROR: Xen environment is not valid!!!" | tee -a ${LOGFILE} - echo "ERROR: Check if Xen has booted and the kernel configuration." \ - | tee -a ${LOGFILE} - echo "ERROR: Output from 'xl info' command:" | tee -a ${LOGFILE} - echo "$ERROR_MSG" | tee -a ${LOGFILE} - exit 1 + log error "Xen environment is not valid!!!" + log error "Check if Xen has booted and the kernel configuration." + log fatal "More information in the logfile: ${LOGFILE}" fi case ${cmd} in check-xen) + log verbose "Valid Xen environment found" exit 0 ;; create) guestfile="${arg1}" guestname="${arg2}" if [ -z "${guestfile}" -o ! -f "${guestfile}" ]; then - echo "${PREF} Usage ${this} create XENGUEST_FILE [NAME]" - exit 1 + log fatal "Usage ${this} create XENGUEST_FILE [NAME]" fi if [ -z "${guestname}" ]; then guestname=$(basename ${guestfile} .xenguest) @@ -795,28 +972,25 @@ case ${cmd} in if [ -f ${XENGUEST_CONF_BASE}/guests/${guestname}/guest.cfg ]; then # Guest already exist - echo "${PREF} A guest ${guestname} already exist" - exit 1 + log fatal "Guest '${guestname}' already exists" fi xenguest_guest_create ${guestfile} ${guestname} - echo "${PREF} ${guestname} created." ;; remove) guestname="${arg1:-}" check_guest_arg ${cmd} ${guestname} check_guest_exist ${guestname} + log info "Guest '${guestname}' exists. Removing" # We need to stop the guest first if it is running if xl_list_contains $guestname; then - echo "xl destroy ${guestname}" >> ${LOGFILE} 2>&1 - xl destroy ${guestname} >> ${LOGFILE} 2>&1 + log info "Guest '${guestname}' is running. Killing..." + log_command "xl destroy ${guestname}" if [ $? -ne 0 ]; then - echo "${PREF} Error killing ${guestname}" - exit 1 + log fatal "Killing guest '${guestname}' failed." fi fi xenguest_guest_remove ${guestname} - echo "${PREF} ${guestname} removed." ;; start) guestname="${arg1:-}" @@ -837,17 +1011,17 @@ case ${cmd} in check_guest_arg ${cmd} ${guestname} check_guest_exist ${guestname} check_guest_running ${guestname} - echo "xl destroy ${guestname}" >> ${LOGFILE} 2>&1 - xl destroy ${guestname} >> ${LOGFILE} 2>&1 + log info "Attempting to kill guest '${guestname}'" + log_command "xl destroy ${guestname}" if [ $? -ne 0 ]; then - echo "${PREF} Error killing ${guestname}" - exit 1 + log fatal "Killing guest '${guestname}' failed." fi + log info "Guest '${guestname}' killed successfully" ;; list) if [ -d ${XENGUEST_CONF_BASE}/guests ]; then - for f in $(find ${XENGUEST_CONF_BASE}/guests -mindepth 1 \ - -maxdepth 1 -type d -exec basename {} \;); do + list=$(find ${XENGUEST_CONF_BASE}/guests -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) + for f in ${list}; do if [ -f ${XENGUEST_CONF_BASE}/guests/$f/guest.cfg ]; then echo "$f" fi @@ -856,15 +1030,15 @@ case ${cmd} in ;; status) - single_status() ( + single_status() { guestname="${1}" check_guest_exist ${guestname} if xl_list_contains $guestname; then - echo "${guestname}: Running" + echo "${guestname} Running" else - echo "${guestname}: Stopped" + echo "${guestname} Stopped" fi - ) + } guestname="${arg1}" if [ -n "${guestname}" ]; then @@ -879,8 +1053,7 @@ case ${cmd} in fi ;; *) - echo "${PREF} Invalid argument ${cmd}" - exit 1 + log fatal "Invalid argument: ${cmd}" ;; esac diff --git a/meta-arm-autonomy/recipes-extended/xenguest/files/xenguest-network-init-post.sh b/meta-arm-autonomy/recipes-extended/xenguest/files/xenguest-network-init-post.sh index aa43ce89..ce5e6de6 100755 --- a/meta-arm-autonomy/recipes-extended/xenguest/files/xenguest-network-init-post.sh +++ b/meta-arm-autonomy/recipes-extended/xenguest/files/xenguest-network-init-post.sh @@ -31,15 +31,14 @@ case "${XENGUEST_NETWORK_TYPE:-}" in release_lock "vif-nat-kea" exit 0 fi - echo "Waiting for ${vif_name} - network interface is not ready..."\ - " try #${try}" >> "${LOGFILE}" 2>&1 + log info "Waiting for ${vif_name} - network interface is not ready..." + log info "try #${try}" sleep 1 done - echo "ERROR: Failed to get ${vif_name} "\ - "network interface ready!" >> "${LOGFILE}" 2>&1 + log error "Failed to get ${vif_name}. network interface ready!" exit 1 ;; *) - echo "No action needed" >> "${LOGFILE}" 2>&1 + log verbose "No action needed" ;; esac diff --git a/meta-arm-autonomy/recipes-extended/xenguest/xenguest-manager.bb b/meta-arm-autonomy/recipes-extended/xenguest/xenguest-manager.bb index c55d8794..16a07005 100644 --- a/meta-arm-autonomy/recipes-extended/xenguest/xenguest-manager.bb +++ b/meta-arm-autonomy/recipes-extended/xenguest/xenguest-manager.bb @@ -9,6 +9,7 @@ LICENSE = "MIT" SRC_URI = " \ file://xenguest-manager \ file://xenguest-init \ + file://logrotate-xenguest \ " LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" @@ -19,6 +20,7 @@ S = "${WORKDIR}" XENGUEST_MANAGER_VOLUME_DEVICE ?= "/dev/sda2" XENGUEST_MANAGER_VOLUME_NAME ?= "vg-xen-$(basename ${XENGUEST_MANAGER_VOLUME_DEVICE})" XENGUEST_MANAGER_GUEST_DIR ?= "${datadir}/guests/" +XENGUEST_MANAGER_LOG_LEVEL ?= "ERROR" # We add an init script to create and start guests automatically # run start script after xen-tools and run stop script before xen-tools @@ -34,6 +36,8 @@ do_compile() { xenguest-manager.conf echo "XENGUEST_GUEST_DIR=\"${XENGUEST_MANAGER_GUEST_DIR}\"" >> \ xenguest-manager.conf + echo "XENGUEST_LOG_LEVEL=\"${XENGUEST_MANAGER_LOG_LEVEL}\"" >> \ + xenguest-manager.conf } do_install() { @@ -44,10 +48,13 @@ do_install() { install -d -m 755 ${D}${sysconfdir}/init.d install -m 755 xenguest-init ${D}${sysconfdir}/init.d/${INITSCRIPT_NAME} install -d -m 755 ${D}${XENGUEST_GUEST_DIR} + install -d -m 755 ${D}${sysconfdir}/logrotate.d + install -m 644 logrotate-xenguest ${D}${sysconfdir}/logrotate.d/xenguest } # Things that we need on the target -RDEPENDS_${PN} += "bash tar xenguest-mkimage lvm2 xen-tools parted e2fsprogs dosfstools" +RDEPENDS_${PN} += "bash tar xenguest-mkimage lvm2 xen-tools parted e2fsprogs \ + dosfstools logrotate" FILES_${PN} += "${bindir}/xenguest-manager \ ${sysconfdir}/xenguest"