diff -Nru maas-0.1+bzr462+dfsg/contrib/maas_local_settings_sample.py maas-0.1+bzr482+dfsg/contrib/maas_local_settings_sample.py --- maas-0.1+bzr462+dfsg/contrib/maas_local_settings_sample.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/contrib/maas_local_settings_sample.py 2012-04-19 04:11:04.000000000 +0000 @@ -70,5 +70,4 @@ } # The location of the Provisioning API XML-RPC endpoint. -from getpass import getuser -PSERV_URL = "http://%s:password@localhost:5241/api" % getuser() +PSERV_URL = "http://maas:password@localhost:5241/api" diff -Nru maas-0.1+bzr462+dfsg/contrib/wsgi.py maas-0.1+bzr482+dfsg/contrib/wsgi.py --- maas-0.1+bzr462+dfsg/contrib/wsgi.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/contrib/wsgi.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """WSGI Application.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/debian/changelog maas-0.1+bzr482+dfsg/debian/changelog --- maas-0.1+bzr462+dfsg/debian/changelog 2012-04-12 20:46:28.000000000 +0000 +++ maas-0.1+bzr482+dfsg/debian/changelog 2012-04-19 04:20:52.000000000 +0000 @@ -1,3 +1,27 @@ +maas (0.1+bzr482+dfsg-0ubuntu1) precise-proposed; urgency=low + + * New upstream release (Fixes LP: #981103) + * debian/maas.postinst: + - Make sure rabbitmq and postgresql are started on upgrade (LP: #981282) + - Handle upgrades from any lower than 0.1+bzr462+dfsg-0ubuntu1 to + correctly re-generate passwords, and not have db sync/migrate issues + as config has changed upstream. + - Correctly set Passwords for PSERV, otherwise it won't set new passwords. + * Allow MAAS_DEFAULT_URL reconfiguration. (LP: #980970) + - debian/maas.config: Add reconfigure validation to correctly allow it, + and ask a question. + - debian/maas.postinst: Reconfigure DEFAULT_MAAS_URL as well as cobbler + server and next_server for PXE/Provisioning. + - debian/maas.templates: Add debconf question and update info. + * Do not lose MAAS_DEFAULT_URL settings on upgrade (LP: #984309) + * debian/maas.postinst: + - Set cobbler password in between quotes (LP: #984427) + - Do not change permissions to maas.log (LP: #980915) + * no longer use maas-cloudimg2ephemeral, but rather use premade images + at http://maas.ubuntu.com + + -- Andres Rodriguez Tue, 17 Apr 2012 23:44:46 -0700 + maas (0.1+bzr462+dfsg-0ubuntu1) precise; urgency=low * New upstream release (LP: #980240) @@ -38,7 +62,7 @@ * debian/maas.postinst: Update pserv.yaml and maas_local_settings.py to use password. - -- Andres Rodriguez Thu, 12 Apr 2012 16:46:22 -0400 + -- Andres Rodriguez Thu, 12 Apr 2012 16:37:53 -0400 maas (0.1+bzr415+dfsg-0ubuntu2) precise; urgency=low diff -Nru maas-0.1+bzr462+dfsg/debian/control maas-0.1+bzr482+dfsg/debian/control --- maas-0.1+bzr462+dfsg/debian/control 2012-04-12 20:38:52.000000000 +0000 +++ maas-0.1+bzr482+dfsg/debian/control 2012-04-19 04:19:51.000000000 +0000 @@ -11,7 +11,7 @@ Architecture: all Depends: apache2, avahi-daemon, - cobbler, + maas-provision (>= 2.2.2), dbconfig-common, distro-info, libapache2-mod-wsgi, diff -Nru maas-0.1+bzr462+dfsg/debian/maas.config maas-0.1+bzr482+dfsg/debian/maas.config --- maas-0.1+bzr462+dfsg/debian/maas.config 2012-04-12 20:38:52.000000000 +0000 +++ maas-0.1+bzr482+dfsg/debian/maas.config 2012-04-19 04:19:51.000000000 +0000 @@ -5,15 +5,15 @@ # creates question set_question() { - if ! db_fget "$1" seen; then - db_register dbconfig-common/dbconfig-install "$1" - db_subst "$1" ID "$1" - db_fget "$1" seen - fi - if [ "$RET" = false ]; then - db_set "$1" "$2" - db_fset "$1" seen true - fi + if ! db_fget "$1" seen; then + db_register dbconfig-common/dbconfig-install "$1" + db_subst "$1" ID "$1" + db_fget "$1" seen + fi + if [ "$RET" = false ]; then + db_set "$1" "$2" + db_fset "$1" seen true + fi } # source dbconfig-common shell library, and call the hook function @@ -30,4 +30,15 @@ set_question maas/dbconfig-install true set_question maas/pgsql/app-pass "" dbc_go maas $@ + +elif [ "$1" = "reconfigure" ] || [ -n "$DEBCONF_RECONFIGURE" ]; then + db_get maas/default-maas-url || true + if [ -n "$RET" ]; then + db_set maas/default-maas-url "$RET" + else + ipaddr=$(awk '$1 == "DEFAULT_MAAS_URL" { split($0,array,"/")} END{print array[3] }' /etc/maas/maas_local_settings.py) + db_set maas/default-maas-url "$ipaddr" + fi + db_input low maas/default-maas-url || true + db_go fi diff -Nru maas-0.1+bzr462+dfsg/debian/maas.install maas-0.1+bzr482+dfsg/debian/maas.install --- maas-0.1+bzr462+dfsg/debian/maas.install 2012-04-12 20:38:52.000000000 +0000 +++ maas-0.1+bzr482+dfsg/debian/maas.install 2012-04-19 04:19:51.000000000 +0000 @@ -25,7 +25,6 @@ man/maas-import-isos.8 usr/share/man/man8 scripts/maas-import-isos usr/sbin scripts/maas-import-ephemerals usr/sbin -scripts/maas-cloudimg2ephemeral usr/sbin debian/extras/maas usr/bin debian/extras/20-maas.conf etc/rsyslog.d debian/extras/maas_remote_syslog_compress etc/cron.d diff -Nru maas-0.1+bzr462+dfsg/debian/maas.postinst maas-0.1+bzr482+dfsg/debian/maas.postinst --- maas-0.1+bzr462+dfsg/debian/maas.postinst 2012-04-12 20:38:52.000000000 +0000 +++ maas-0.1+bzr482+dfsg/debian/maas.postinst 2012-04-19 04:19:51.000000000 +0000 @@ -21,6 +21,30 @@ fi } +restart_rabbitmq(){ + if [ -x /usr/sbin/invoke-rc.d ]; then + invoke-rc.d rabbitmq-server restart || true + else + /etc/init.d/rabbitmq-server restart || true + fi +} + +restart_postgresql(){ + if [ -x /usr/sbin/invoke-rc.d ]; then + invoke-rc.d --force postgresql restart || true + else + /etc/init.d/postgresql restart || true + fi +} + +restart_cobbler(){ + if [ -x /usr/sbin/invoke-rc.d ]; then + invoke-rc.d cobbler restart || true + else + /etc/init.d/cobbler restart || true + fi +} + add_user_group(){ local user="maas" local group="maas" @@ -46,19 +70,19 @@ htpasswd -D /etc/cobbler/users.digest "maas" || true printf "maas:Cobbler:$hash\n" >> /etc/cobbler/users.digest - if grep -qs "^\ \{1,\}password: [a-zA-Z0-9]\{1,\}$" /etc/maas/pserv.yaml; then - sed -i "s/^\ \{1,\}password: [a-zA-Z0-9]\{1,\}$/ password: "$cblr_pass"/" /etc/maas/pserv.yaml + if grep -qs "^\ \{1,\}password:.*$" /etc/maas/pserv.yaml; then + sed -i "s/^\ \{1,\}password:.*$/ password: \""$cblr_pass"\"/" /etc/maas/pserv.yaml fi } configure_maas_pserv_user() { local pserv_pass= pserv_pass="$(pwgen -s 20)" - if grep -qs "^password: \"test\"" /etc/maas/pserv.yaml; then - sed -i '/^password:/s/"test"/"'"${pserv_pass}"'"/' /etc/maas/pserv.yaml + if grep -qs "^password: \".*\"$" /etc/maas/pserv.yaml; then + sed -i '/^password:/s/".*"$/"'"${pserv_pass}"'"/' /etc/maas/pserv.yaml fi if grep -qs "^PSERV_URL\ =\ " /etc/maas/maas_local_settings.py; then - sed -i '/^PSERV_URL[ =]/s/:password@/:'"${pserv_pass}"'@/' /etc/maas/maas_local_settings.py + sed -i '/^PSERV_URL[ =]/s/maas:.*@/'"maas:${pserv_pass}"'@/' /etc/maas/maas_local_settings.py fi } @@ -103,6 +127,23 @@ ln -sf /var/lib/maas/ephemeral/tgt.conf /etc/tgt/conf.d/maas.conf } +configure_maas_default_url() { + local ipaddr="$1" + + if grep -qs "^DEFAULT_MAAS_URL\ \= \"[a-zA-Z0-9:/.]\{0,\}\"$" /etc/maas/maas_local_settings.py; then + sed -i "s/^DEFAULT_MAAS_URL\ \= \"[a-zA-Z0-9:/.]\{0,\}\"$/DEFAULT_MAAS_URL = \"http:\/\/"$ipaddr"\/\"/" \ + /etc/maas/maas_local_settings.py + fi + + # Replace for PXE + if grep -qs "^next_server:.*$" /etc/cobbler/settings; then + sed -i "s/^next_server:.*$/next_server: $ipaddr/" /etc/cobbler/settings + fi + if grep -qs "^server:.*$" /etc/cobbler/settings; then + sed -i "s/^server:.*$/server: $ipaddr/" /etc/cobbler/settings + fi +} + if [ "$1" = "configure" ] && [ -z "$2" ]; then ######################################################### ################ User/Group Creatiion ################## @@ -157,11 +198,9 @@ ipaddr=${ipaddr%%/*} # Set the IP address of the interface with default route if [ -n "$ipaddr" ]; then - if grep -qs "^DEFAULT_MAAS_URL\ \= \"[a-zA-Z0-9:/.]\{0,\}\"$" /etc/maas/maas_local_settings.py; then - sed -i "s/^DEFAULT_MAAS_URL\ \= \"[a-zA-Z0-9:/.]\{0,\}\"$/DEFAULT_MAAS_URL = \"http:\/\/"$ipaddr"\/\"/" \ - /etc/maas/maas_local_settings.py - fi + configure_maas_default_url "$ipaddr" db_subst maas/installation-note MAAS_URL "$ipaddr" + db_set maas/default-maas-url "$ipaddr" fi ######################################################### @@ -173,7 +212,6 @@ touch /var/log/maas/maas.log fi chown -R maas:maas /var/log/maas - chmod 620 /var/log/maas/maas.log chmod -R 775 /var/log/maas/oops # Create log directory base @@ -197,11 +235,7 @@ ######################################################### # Handle longpoll/rabbitmq publishing - if [ -x /usr/sbin/invoke-rc.d ]; then - invoke-rc.d rabbitmq-server restart || true - else - /etc/init.d/rabbitmq-server restart || true - fi + restart_rabbitmq configure_maas_txlongpoll_rabbitmq_user ######################################################### @@ -214,11 +248,7 @@ ######################################################### # Need to for postgresql start so it doesn't fail on the installer - if [ -x /usr/sbin/invoke-rc.d ]; then - invoke-rc.d --force postgresql restart || true - else - /etc/init.d/postgresql restart || true - fi + restart_postgresql # Create the database dbc_go maas $@ @@ -234,28 +264,62 @@ db_input high maas/installation-note || true db_go +elif [ "$1" = "reconfigure" ] || [ -n "$DEBCONF_RECONFIGURE" ]; then + # Set the IP address of the interface with default route + db_get maas/default-maas-url + ipaddr="$RET" + db_set maas/default-maas-url "$ipaddr" + if [ -n "$ipaddr" ]; then + configure_maas_default_url "$ipaddr" + fi + restart_cobbler + elif [ "$1" = "configure" ] && dpkg --compare-versions "$2" gt 0.1+bzr266+dfsg-0ubuntu1; then # If upgrading to any later package version, then upgrade db. if [ -x /usr/sbin/invoke-rc.d ]; then invoke-rc.d apache2 stop || true fi + # make sure postgresql is running + restart_postgresql + # If upgrading from any version lower than 0.1+bzr445+dfsg-0ubuntu1 # we need to update the user/group. if dpkg --compare-versions "$2" lt 0.1+bzr445+dfsg-0ubuntu1; then + # add user/group add_user_group + # set correct permissions chown -R maas:maas /var/lib/maas/ chown -R maas:maas /var/log/maas chown -R syslog:syslog /var/log/maas/rsyslog + fi + if dpkg --compare-versions "$2" lt 0.1+bzr459+dfsg-0ubuntu1; then + configure_maas_tgt + fi + # If upgrading from any version lower than 0.1+bzr462+dfsg-0ubuntu1 + # we need to regenerate the passwords and update configs. + if dpkg --compare-versions "$2" lt 0.1+bzr473+dfsg-0ubuntu1; then + # configure maas user for cobbler configure_maas_cobbler_user + # configure pserv user for cobbler configure_maas_pserv_user + # configure maas default url + db_get maas/default-maas-url + ipaddr="$RET" + # If nothing in the database, obtain the IP from cobbler settings + # which is the same as MAAS_DEFAULT_URL + if [ -z "$ipaddr" ]; then + ipaddr=$(awk '$1 == "server:" { print $2 }' /etc/cobbler/settings) + db_set maas/default-maas-url "$ipaddr" + fi + configure_maas_default_url "$ipaddr" + # make sure rabbitmq is running + restart_rabbitmq configure_maas_txlongpoll_rabbitmq_user + # handle database upgrade dbc_go maas $@ configure_maas_database "$dbc_dbpass" fi - if dpkg --compare-versions "$2" lt 0.1+bzr459+dfsg-0ubuntu1; then - configure_maas_tgt - fi maas_sync_migrate_db restart_apache2 diff -Nru maas-0.1+bzr462+dfsg/debian/maas.templates maas-0.1+bzr482+dfsg/debian/maas.templates --- maas-0.1+bzr462+dfsg/debian/maas.templates 2012-04-12 20:38:52.000000000 +0000 +++ maas-0.1+bzr482+dfsg/debian/maas.templates 2012-04-19 04:19:51.000000000 +0000 @@ -6,3 +6,17 @@ can access the MAAS Web interface here: . http://${MAAS_URL}/MAAS + . + If the automatically detected address above is not in the same + network as the MAAS clients, you need to reconfigure it: + . + sudo dpkg-reconfigure maas + +Template: maas/default-maas-url +Type: string +_Description: Ubuntu MAAS PXE/Provisioning network address: + The Ubuntu MAAS Server automatically detects the IP address + that is used for PXE and provisioning. However, it needs to be + in the same network as the clients. If the automatically + detected address is not in the same network as the clients, it + must be changed. diff -Nru maas-0.1+bzr462+dfsg/etc/maas/commissioning-user-data maas-0.1+bzr482+dfsg/etc/maas/commissioning-user-data --- maas-0.1+bzr462+dfsg/etc/maas/commissioning-user-data 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/etc/maas/commissioning-user-data 2012-04-19 04:11:04.000000000 +0000 @@ -73,7 +73,13 @@ exit 1 } +shutdown() { + echo "Finished, powering off." + /sbin/shutdown -P now +} + main() { + trap shutdown EXIT # the main function, actually execute stuff that is written below local script total=0 creds="" diff -Nru maas-0.1+bzr462+dfsg/etc/maas/import_ephemerals maas-0.1+bzr482+dfsg/etc/maas/import_ephemerals --- maas-0.1+bzr462+dfsg/etc/maas/import_ephemerals 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/etc/maas/import_ephemerals 2012-04-19 04:11:04.000000000 +0000 @@ -7,7 +7,7 @@ #EPH_KOPTS_ISCSI="ip=dhcp iscsi_target_name=@@iscsi_target@@ iscsi_target_ip=@@iscsi_target_ip@@ iscsi_target_port=3260" #EPH_KOPTS_ROOT="root=cloudimg-rootfs ro" #EPH_KOPTS_LOGGING="log_host=@@server_ip@@ log_port=514" -#EPH_UPDATE_CMD="maas-cloudimg2ephemeral" +#EPH_UPDATE_CMD="" #TARGET_NAME_PREFIX="iqn.2004-05.com.ubuntu:maas:" #DATA_DIR="/var/lib/maas/ephemeral" #RELEASES="precise" diff -Nru maas-0.1+bzr462+dfsg/scripts/maas-cloudimg2ephemeral maas-0.1+bzr482+dfsg/scripts/maas-cloudimg2ephemeral --- maas-0.1+bzr462+dfsg/scripts/maas-cloudimg2ephemeral 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/scripts/maas-cloudimg2ephemeral 1970-01-01 00:00:00.000000000 +0000 @@ -1,493 +0,0 @@ -#!/bin/bash -# -# maas-cloudimg2ephemeral - update a cloud image to make it sufficient -# for use as a maas ephemeral image -# -# Copyright (C) 2011-2012 Canonical -# -# Authors: -# Scott Moser -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, version 3 of the License. -# -# 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 Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -VERBOSITY=0 - -error() { echo "$@" 1>&2; } -errorp() { printf "$@" 1>&2; } -fail() { [ $# -eq 0 ] || error "$@"; exit 1; } -failp() { [ $# -eq 0 ] || errorp "$@"; exit 1; } - -Usage() { - cat <&2; [ $# -eq 0 ] || error "$@"; exit 1; } -cleanup() { - [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || { - unmount_under "${TEMP_D}" && - rm -Rf "${TEMP_D}" - } -} - -debug() { - local level=${1}; shift; - [ "${level}" -gt "${VERBOSITY}" ] && return - error "${@}" -} - -unmount_under() { - # unmount_under(dir) - # unmount all mounts under 'dir' - [ -f /proc/mounts ] || - { error "/proc/mounts not a file"; return 1; } - tac /proc/mounts | sh -c ' - under=$1 - while read s mp t opt a b ; do - [ "${mp#${under}}" != "${mp}" ] || continue; - umount $mp || - { echo "failed umount $mp, waiting, trying again" 1>&2; - sleep 10; - umount $mp || exit 1; } - done' -- "$1" -} - -loop_mount() { - # Create more loop nodes, if necessary - local mounts=$(grep -c /dev/loop /proc/mounts) || mounts=0 - local loops=$(ls /dev/loop* | wc -l) || loops=0 - if [ $mounts -ge $loops ]; then - mknod -m 660 /dev/loop$loops b 7 $loops && - chown root:disk /dev/loop$loops || - return 1 - fi - # Do the loop mount - mount -o loop "$1" "$2" -} - -mount_callback_umount() { - # mount_callback_umount(img_or_device, func, args) - # mount the image given, call function with args, - # umount the image, return function's exit value - local device="$1" cb="$2" mp="" opts="" ret=0 m="" - shift 2; - mp=$(mktemp -d "$TEMP_D/mp.XXXXXX") - if [ -b "$device" ]; then - mount $opts "$device" "$mp" || return 1 - else - loop_mount "$device" "$mp" || return 1 - fi - for m in "/proc" "/sys"; do - [ -d "$mp/$m" ] || continue - mount --bind "$m" "$mp/$m" || { - error "failed to mount $mp/$m"; - unmount_under "$mp"; - return 1; - } - done - "$cb" "$mp" "$@" - ret=$? - unmount_under "$mp" && rmdir "$mp" || - { error "WARN! failed to umount $device from $mp"; return 2; } - return $ret -} - -add_initramfs_hooks() { - local dir="$1" idir="" hook="" script="" - idir="$dir/etc/initramfs-tools" - mkdir -p "$idir/hooks" "$idir/scripts/init-bottom" || - return 1 - hook="$idir/hooks/overlay-ro" - cat > "$hook" <<"ENDEND" -#!/bin/sh -set -e - -PREREQS="" -case $1 in - prereqs) echo "${PREREQS}"; exit 0;; -esac - -. /usr/share/initramfs-tools/hook-functions - -## -manual_add_modules overlayfs -force_load overlayfs - -# vi: ts=4 noexpandtab -ENDEND - - [ $? -eq 0 ] || { error "failed to write $hook"; return 1; } - - script="$idir/scripts/init-bottom/root-ro" - cat > "$script" <<"ENDEND" -#!/bin/sh -# Copyright, 2012 Axel Heider -# -# Based on scrpts from -# Sebastian P. -# Nicholas A. Schembri State College PA USA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# 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 -# . -# -# -# Tested with Ubuntu 11.10 -# -# Notes: -# * no changes to the root fs are made by this script. -# * if /home/[user] is on the RO root fs, files are in ram and not saved. -# -# Install: -# put this file in /etc/initramfs-tools/scripts/init-bottom/root-ro -# chmod 0755 root-ro -# optional: clean up menu.lst, update-grub -# update-initramfs -u -# -# Disable read-only root fs -# * option 1: kernel boot parameter "disable-root-ro=true" -# * option 2: create file "/disable-root-ro" -# -# ROOT_RO_DRIVER variable controls which driver isused for the ro/rw layering -# Supported drivers are: overlayfs, aufs -# the kernel parameter "root-ro-driver=[driver]" can be used to initialize -# the variable ROOT_RO_DRIVER. If nothing is given, overlayfs is used. -# - -# no pre requirement -PREREQ="" - -prereqs() -{ - echo "${PREREQ}" -} - -case "$1" in - prereqs) - prereqs - exit 0 - ;; -esac - -. /scripts/functions - -MYTAG="root-ro" -DISABLE_MAGIC_FILE="/disable-root-ro" - -# parse kernel boot command line -ROOT_RO_DRIVER= -DISABLE_ROOT_RO= -for CMD_PARAM in $(cat /proc/cmdline); do - case ${CMD_PARAM} in - disable-root-ro=*) - DISABLE_ROOT_RO=${CMD_PARAM#disable-root-ro=} - ;; - root-ro-driver=*) - ROOT_RO_DRIVER=${CMD_PARAM#root-ro-driver=} - ;; - esac -done - -# check if read-only root fs is disabled -if [ ! -z "${DISABLE_ROOT_RO}" ]; then - log_warning_msg "${MYTAG}: disabled, found boot parameter disable-root-ro=${DISABLE_ROOT_RO}" - exit 0 -fi -if [ -e "${rootmnt}${DISABLE_MAGIC_FILE}" ]; then - log_warning_msg "${MYTAG}: disabled, found file ${rootmnt}${DISABLE_MAGIC_FILE}" - exit 0 -fi - -# generic settings -# ${ROOT} and ${rootmnt} are predefined by caller of this script. Note that -# the root fs ${rootmnt} it mounted readonly on the initrams, which fits nicely -# for our purposes. -ROOT_RW=/mnt/root-rw -ROOT_RO=/mnt/root-ro - -# check if ${ROOT_RO_DRIVER} is defined, otherwise set default -if [ -z "${ROOT_RO_DRIVER}" ]; then - ROOT_RO_DRIVER=overlayfs -fi -# settings based in ${ROOT_RO_DRIVER}, stop here if unsupported. -case ${ROOT_RO_DRIVER} in - overlayfs) - MOUNT_PARMS="-t overlayfs -o lowerdir=${ROOT_RO},upperdir=${ROOT_RW} overlayfs-root ${rootmnt}" - ;; - aufs) - MOUNT_PARMS="-t aufs -o dirs=${ROOT_RW}:${ROOT_RO}=ro aufs-root ${rootmnt}" - ;; - *) - panic "${MYTAG} ERROR: invalide ROOT_RO_DRIVER ${ROOT_RO_DRIVER}" - ;; -esac - - -# check if kernel module exists -modprobe -qb ${ROOT_RO_DRIVER} -if [ $? -ne 0 ]; then - log_failure_msg "${MYTAG} ERROR: missing kernel module ${ROOT_RO_DRIVER}" - exit 0 -fi - -# make the mount point on the init root fs ${ROOT_RW} -[ -d ${ROOT_RW} ] || mkdir -p ${ROOT_RW} -if [ $? -ne 0 ]; then - log_failure_msg "${MYTAG} ERROR: failed to create ${ROOT_RW}" - exit 0 -fi - -# make the mount point on the init root fs ${ROOT_RO} -[ -d ${ROOT_RO} ] || mkdir -p ${ROOT_RO} -if [ $? -ne 0 ]; then - log_failure_msg "${MYTAG} ERROR: failed to create ${ROOT_RO}" - exit 0 -fi - -# mount a tempfs using the device name tmpfs-root at ${ROOT_RW} -mount -t tmpfs tmpfs-root ${ROOT_RW} -if [ $? -ne 0 ]; then - log_failure_msg "${MYTAG} ERROR: failed to create tmpfs" - exit 0 -fi - - -# root is mounted on ${rootmnt}, move it to ${ROOT_RO}. -mount --move ${rootmnt} ${ROOT_RO} -if [ $? -ne 0 ]; then - log_failure_msg "${MYTAG} ERROR: failed to move root away from ${rootmnt} to ${ROOT_RO}" - exit 0 -fi - -# there is nothing left at ${rootmnt} now. So for any error we get we should -# either do recovery to restore ${rootmnt} for drop to a initramfs shell using -# "panic". Otherwise the boot process is very likely to fail with even more -# errors and leave the system in a wired state. - -# mount virtual fs ${rootmnt} with rw-fs ${ROOT_RW} on top or ro-fs ${ROOT_RO}. -mount ${MOUNT_PARMS} -if [ $? -ne 0 ]; then - log_failure_msg "${MYTAG} ERROR: failed to create new ro/rw layerd ${rootmnt}" - # do recovery and try resoring the mount for ${rootmnt} - mount --move ${ROOT_RO} ${rootmnt} - if [ $? -ne 0 ]; then - # thats badm, drpo to s shell to let the user try fixing this - panic "${MYTAG} RECOVERY ERROR: failed to move ${ROOT_RO} back to ${rootmnt}" - fi - exit 0 -fi - -# now the real root fs is on ${ROOT_RO} of the init file system, our layered -# root fs is set up at ${rootmnt}. So we can write anywhere in {rootmnt} and the -# changes will end up in ${ROOT_RW} while ${ROOT_RO} it not touched. However -# ${ROOT_RO} and ${ROOT_RW} are on the initramfs root fs, which will be removed -# an replaced by ${rootmnt}. Thus we must move ${ROOT_RO} and ${ROOT_RW} to the -# rootfs visible later, ie. ${rootmnt}${ROOT_RO} and ${rootmnt}${ROOT_RO}. -# Since the layered ro/rw is already up, these changes also end up on -# ${ROOT_RW} while ${ROOT_RO} is not touched. - -# move mount from ${ROOT_RO} to ${rootmnt}${ROOT_RO} -[ -d ${rootmnt}${ROOT_RO} ] || mkdir -p ${rootmnt}${ROOT_RO} -mount --move ${ROOT_RO} ${rootmnt}${ROOT_RO} -if [ $? -ne 0 ]; then - log_failure_msg "${MYTAG} ERROR: failed to move ${ROOT_RO} to ${rootmnt}${ROOT_RO}" - exit 0 -fi - -# move mount from ${ROOT_RW} to ${rootmnt}${ROOT_RW} -[ -d ${rootmnt}${ROOT_RW} ] || mkdir -p ${rootmnt}${ROOT_RW} -mount --move ${ROOT_RW} ${rootmnt}${ROOT_RW} -if [ $? -ne 0 ]; then - s "${MYTAG}: ERROR: failed to move ${ROOT_RW} to ${rootmnt}${ROOT_RW}" - exit 0 -fi - -# technically, everything is set up nicely now. Since ${rootmnt} had beend -# mounted read-only on the initfamfs already, ${rootmnt}${ROOT_RO} is it, too. -# Now we init process could run - but unfortunately, we may have to prepare -# some more things here. -# Basically, there are two ways to deal with the read-only root fs. If the -# system is made aware of this, things can be simplified a lot. -# If it is not, things need to be done to our best knowledge. -# -# So we assume here, the system does not really know about our read-only root fs. -# -# Let's deal with /etc/fstab first. It usually contains an entry for the root -# fs, which is no longer valid now. We have to remove it and add our new -# ${ROOT_RO} entry. -# Remember we are still on the initramfs root fs here, so we have to work on -# ${rootmnt}/etc/fstab. The original fstab is ${rootmnt}${ROOT_RO}/etc/fstab. -ROOT_TYPE=$(cat /proc/mounts | grep ${ROOT} | cut -d' ' -f3) -ROOT_OPTIONS=$(cat /proc/mounts | grep ${ROOT} | cut -d' ' -f4) -cat <${rootmnt}/etc/fstab -# -# This fstab is in RAM, the real one can be found at ${ROOT_RO}/etc/fstab -# The original entry for '/' and all swap files have been removed. The new -# entry for the read-only the real root fs follows. Write access can be -# enabled using: -# sudo mount -o remount,rw ${ROOT_RO} -# re-mounting it read-only is done using: -# sudo mount -o remount,ro ${ROOT_RO} -# - -${ROOT} ${ROOT_RO} ${ROOT_TYPE} ${ROOT_OPTIONS} 0 0 - -# -# remaining entries from the original ${ROOT_RO}/etc/fstab follow. -# -EOF -if [ $? -ne 0 ]; then - log_failure_msg "${MYTAG} ERROR: failed to modify /etc/fstab (step 1)" - #exit 0 -fi - -#remove root entry and swap from fstab -cat ${rootmnt}${ROOT_RO}/etc/fstab | grep -v ' / ' | grep -v swap >>${rootmnt}/etc/fstab -if [ $? -ne 0 ]; then - log_failure_msg "${MYTAG} ERROR: failed to modify etc/fstab (step 2)" - #exit 0 -fi - -# now we are done. Additinal steps may be necessary depending on the actualy -# distribution and/or its configuration. - -log_success_msg "${MYTAG} sucessfully set up ro/tmpfs-rw layered root fs using ${ROOT_RO_DRIVER}" - -exit 0 -ENDEND - [ $? -eq 0 ] || { error "failed to write $script"; return 1; } - chmod 755 "$hook" "$script" || - { error "failed to chmod $hook, $script"; return 1; } -} - -apply_updates() { - # apply_updates(dir, kernel_out, initramfs_out) - # update directory given, and pull out kernel and initramfs - # to given locations - local dir=$1 kernel_out=$2 initrd_out=$3 - if [ -f "$dir/etc/resolv.conf" ]; then - mv "$dir/etc/resolv.conf" "$dir/etc/resolv.conf.dist" || return 1 - fi - cp "/etc/resolv.conf" "$dir/etc/resolv.conf" || - return 1 - - cat > "$dir/usr/sbin/policy-rc.d" <<"EOF" -#!/bin/sh -while true; do - case "$1" in - -*) shift ;; - makedev) exit 0 ;; - x11-common) exit 0 ;; - *) exit 101 ;; - esac -done -EOF - [ $? -eq 0 ] && chmod 755 "$dir/usr/sbin/policy-rc.d" || - { error "failed to write policy-rc.d"; return 1; } - - add_initramfs_hooks "$dir" || return - - local prox="" apt_opts="" - out=$(apt-config shell prox Acquire::HTTP::Proxy) && - eval $out && [ -n "$prox" ] && - apt_opts="--option=Acquire::HTTP::Proxy=${prox}" - - apt_opts="${apt_opts} --option=Dpkg::Options::=--force-confold" - [ -n "${apt_opts}" ] && - debug 1 "using apt options ${apt_opts} for install" - - LC_ALL=C DEBIAN_FRONTEND=noninteractive \ - apt_opts="${apt_opts}" chroot "$dir" sh -c ' - mkdir -p /etc/iscsi && touch /etc/iscsi/iscsi.initramfs && - apt-get -q ${apt_opts} update && - apt-get remove "linux.*virtual" ${apt_opts} --assume-yes && - apt-get ${apt_opts} install -q -y linux-server open-iscsi || - exit - k="" - for i in /boot/vmlinuz-*; do - [ "${i%-virtual}" = "${i}" ] && k=${i}; done - ver=${k##*/vmlinuz-} - mkinitramfs -o /tmp/initrd.img $ver && - cp $k /tmp/kernel.img && chmod ugo+r /tmp/kernel.img' . VERBOSITY=0 -REMOTE_IMAGES_MIRROR="https://cloud-images.ubuntu.com" +REMOTE_IMAGES_MIRROR="https://maas.ubuntu.com/images" CONSOLE="ttyS0,9600n8" EPH_KOPTS_CONSOLE="console=$CONSOLE" EPH_KOPTS_ISCSI="ip=dhcp iscsi_target_name=@@iscsi_target@@ iscsi_target_ip=@@iscsi_target_ip@@ iscsi_target_port=3260" EPH_KOPTS_ROOT="root=LABEL=cloudimg-rootfs ro" EPH_KOPTS_LOGGING="log_host=@@server_ip@@ log_port=514" -EPH_UPDATE_CMD="maas-cloudimg2ephemeral" +EPH_UPDATE_CMD="" TARGET_NAME_PREFIX="iqn.2004-05.com.ubuntu:maas:" DATA_DIR="/var/lib/maas/ephemeral" CONFIG="/etc/maas/import_ephemerals" RELEASES="precise" ARCHES="amd64 i386" -BUILD_NAME="server" +BUILD_NAME="ephemeral" STREAM="released" KSDIR="/var/lib/cobbler/kickstarts" KICKSTART="$KSDIR/maas-commissioning.preseed" diff -Nru maas-0.1+bzr462+dfsg/scripts/maas-import-isos maas-0.1+bzr482+dfsg/scripts/maas-import-isos --- maas-0.1+bzr462+dfsg/scripts/maas-import-isos 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/scripts/maas-import-isos 2012-04-19 04:11:04.000000000 +0000 @@ -190,3 +190,6 @@ # Sync changes with cobbler daemon cobbler sync + +# Clear MAAS' cache to force the profile check. +maas clearcache --key=profile-check-done diff -Nru maas-0.1+bzr462+dfsg/setup.py maas-0.1+bzr482+dfsg/setup.py --- maas-0.1+bzr462+dfsg/setup.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/setup.py 2012-04-19 04:11:04.000000000 +0000 @@ -5,6 +5,7 @@ """Distutils installer for maas.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maas/demo.py maas-0.1+bzr482+dfsg/src/maas/demo.py --- maas-0.1+bzr462+dfsg/src/maas/demo.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maas/demo.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Django DEMO settings for maas project.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maas/development.py maas-0.1+bzr482+dfsg/src/maas/development.py --- maas-0.1+bzr462+dfsg/src/maas/development.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maas/development.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Django DEVELOPMENT settings for maas project.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maas/__init__.py maas-0.1+bzr482+dfsg/src/maas/__init__.py --- maas-0.1+bzr462+dfsg/src/maas/__init__.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maas/__init__.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """MAAS web.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maas/settings.py maas-0.1+bzr482+dfsg/src/maas/settings.py --- maas-0.1+bzr462+dfsg/src/maas/settings.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maas/settings.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Django settings for maas project.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -174,6 +175,12 @@ # Don't forget to use absolute paths, not relative paths. ) +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + } +} + # List of finder classes that know how to find static files in # various locations. STATICFILES_FINDERS = ( @@ -208,7 +215,12 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + # ErrorsMiddleware catches ExternalComponentException and redirects. + # Specialised error handling middleware (like APIErrorsMiddleware) + # should be placed after it. + 'maasserver.middleware.ErrorsMiddleware', 'maasserver.middleware.APIErrorsMiddleware', + 'maasserver.middleware.ExternalComponentsMiddleware', 'metadataserver.middleware.MetadataErrorsMiddleware', 'django.middleware.transaction.TransactionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', diff -Nru maas-0.1+bzr462+dfsg/src/maas/tests/test_maas.py maas-0.1+bzr482+dfsg/src/maas/tests/test_maas.py --- maas-0.1+bzr462+dfsg/src/maas/tests/test_maas.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maas/tests/test_maas.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test the maas package.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maas/urls.py maas-0.1+bzr482+dfsg/src/maas/urls.py --- maas-0.1+bzr462+dfsg/src/maas/urls.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maas/urls.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """URL configuration for the maas project.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/api_auth.py maas-0.1+bzr482+dfsg/src/maasserver/api_auth.py --- maas-0.1+bzr462+dfsg/src/maasserver/api_auth.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/api_auth.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """OAuth authentication for the various APIs.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/api.py maas-0.1+bzr482+dfsg/src/maasserver/api.py --- maas-0.1+bzr462+dfsg/src/maasserver/api.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/api.py 2012-04-19 04:11:04.000000000 +0000 @@ -47,6 +47,7 @@ """ from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/components.py maas-0.1+bzr482+dfsg/src/maasserver/components.py --- maas-0.1+bzr462+dfsg/src/maasserver/components.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/components.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """MAAS components management.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/context_processors.py maas-0.1+bzr482+dfsg/src/maasserver/context_processors.py --- maas-0.1+bzr462+dfsg/src/maasserver/context_processors.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/context_processors.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Context processors.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/exceptions.py maas-0.1+bzr482+dfsg/src/maasserver/exceptions.py --- maas-0.1+bzr462+dfsg/src/maasserver/exceptions.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/exceptions.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,12 +4,14 @@ """Exceptions.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) __metaclass__ = type __all__ = [ + "ExternalComponentException", "MAASException", "MAASAPIBadRequest", "MAASAPIException", @@ -43,6 +45,10 @@ api_error = httplib.INTERNAL_SERVER_ERROR +class ExternalComponentException(MAASAPIException): + """An external component failed.""" + + class MAASAPIBadRequest(MAASAPIException): api_error = httplib.BAD_REQUEST diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/fields.py maas-0.1+bzr482+dfsg/src/maasserver/fields.py --- maas-0.1+bzr462+dfsg/src/maasserver/fields.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/fields.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Custom model fields.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/forms.py maas-0.1+bzr482+dfsg/src/maasserver/forms.py --- maas-0.1+bzr462+dfsg/src/maasserver/forms.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/forms.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Forms.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/__init__.py maas-0.1+bzr482+dfsg/src/maasserver/__init__.py --- maas-0.1+bzr462+dfsg/src/maasserver/__init__.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/__init__.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """MAAS Server application.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/maasavahi.py maas-0.1+bzr482+dfsg/src/maasserver/maasavahi.py --- maas-0.1+bzr462+dfsg/src/maasserver/maasavahi.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/maasavahi.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Glue to publish MAAS over Avahi.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/management/commands/clearcache.py maas-0.1+bzr482+dfsg/src/maasserver/management/commands/clearcache.py --- maas-0.1+bzr462+dfsg/src/maasserver/management/commands/clearcache.py 1970-01-01 00:00:00.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/management/commands/clearcache.py 2012-04-19 04:11:04.000000000 +0000 @@ -0,0 +1,34 @@ +# Copyright 2012 Canonical Ltd. This software is licensed under the +# GNU Affero General Public License version 3 (see the file LICENSE). + +"""Django command: clear the cache.""" + +from __future__ import ( + absolute_import, + print_function, + unicode_literals, + ) + +__metaclass__ = type +__all__ = [ + 'Command', + ] + +from django.core.cache import cache +from optparse import make_option +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--key', dest='username', default=None, + help="Specify a specific key to delete."), + ) + help = "Clear the cache (the entire cache or only a specific key)." + + def handle(self, *args, **options): + key = options.get('key', None) + if key is None: + cache.clear() + else: + cache.delete(key) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/management/commands/createadmin.py maas-0.1+bzr482+dfsg/src/maasserver/management/commands/createadmin.py --- maas-0.1+bzr462+dfsg/src/maasserver/management/commands/createadmin.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/management/commands/createadmin.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Django command: create a superuser with an empty email.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/management/commands/deletedb.py maas-0.1+bzr482+dfsg/src/maasserver/management/commands/deletedb.py --- maas-0.1+bzr462+dfsg/src/maasserver/management/commands/deletedb.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/management/commands/deletedb.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Django command: stop and delete the local database cluster.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/management/commands/gc.py maas-0.1+bzr482+dfsg/src/maasserver/management/commands/gc.py --- maas-0.1+bzr462+dfsg/src/maasserver/management/commands/gc.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/management/commands/gc.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Custom django command: garabge-collect.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/management/commands/generate_api_doc.py maas-0.1+bzr482+dfsg/src/maasserver/management/commands/generate_api_doc.py --- maas-0.1+bzr462+dfsg/src/maasserver/management/commands/generate_api_doc.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/management/commands/generate_api_doc.py 2012-04-19 04:11:04.000000000 +0000 @@ -1,3 +1,19 @@ +# Copyright 2012 Canonical Ltd. This software is licensed under the +# GNU Affero General Public License version 3 (see the file LICENSE). + +"""Django command: generate the API documentation.""" + +from __future__ import ( + absolute_import, + print_function, + unicode_literals, + ) + +__metaclass__ = type +__all__ = [ + 'Command', + ] + from django.core.management.base import BaseCommand from maasserver.api import ( api_doc_title, diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/management/commands/query.py maas-0.1+bzr482+dfsg/src/maasserver/management/commands/query.py --- maas-0.1+bzr462+dfsg/src/maasserver/management/commands/query.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/management/commands/query.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Django command: access the development database directly in SQL.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/management/commands/runserver.py maas-0.1+bzr482+dfsg/src/maasserver/management/commands/runserver.py --- maas-0.1+bzr462+dfsg/src/maasserver/management/commands/runserver.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/management/commands/runserver.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Django command: run the server. Overrides the default implementation.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/messages.py maas-0.1+bzr482+dfsg/src/maasserver/messages.py --- maas-0.1+bzr462+dfsg/src/maasserver/messages.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/messages.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Messages.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/middleware.py maas-0.1+bzr482+dfsg/src/maasserver/middleware.py --- maas-0.1+bzr462+dfsg/src/maasserver/middleware.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/middleware.py 2012-04-19 04:11:04.000000000 +0000 @@ -2,6 +2,7 @@ # GNU Affero General Public License version 3 (see the file LICENSE). from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -12,6 +13,7 @@ __all__ = [ "AccessMiddleware", "APIErrorsMiddleware", + "ErrorsMiddleware", "ExceptionMiddleware", ] @@ -25,6 +27,8 @@ import re from django.conf import settings +from django.contrib import messages +from django.core.cache import cache from django.core.exceptions import ( PermissionDenied, ValidationError, @@ -37,7 +41,10 @@ HttpResponseRedirect, ) from django.utils.http import urlquote_plus -from maasserver.exceptions import MAASAPIException +from maasserver.exceptions import ( + ExternalComponentException, + MAASAPIException, + ) def get_relative_path(path): @@ -94,6 +101,47 @@ return None +PROFILES_CHECK_DONE_KEY = 'profile-check-done' + +# The profiles check done by check_profiles_cached is only done at most once +# every PROFILE_CHECK_DELAY seconds for efficiency. +PROFILE_CHECK_DELAY = 2 * 60 + + +def check_profiles_cached(): + """Check Cobbler's profiles. The check is actually done at most once every + PROFILE_CHECK_DELAY seconds for performance reasons. + """ + # Avoid circular imports. + from maasserver.provisioning import check_profiles + if not cache.get(PROFILES_CHECK_DONE_KEY, False): + # Mark the profile check as done beforehand as the actual check + # might raise an exception. + cache.set(PROFILES_CHECK_DONE_KEY, True, PROFILE_CHECK_DELAY) + check_profiles() + + +def clear_profiles_check_cache(): + """Force a profile check next time the MAAS server is accessed.""" + cache.delete(PROFILES_CHECK_DONE_KEY) + + +class ExternalComponentsMiddleware: + """This middleware performs checks for external components (right + now only Cobbler is checked) at regular intervals. + """ + def process_request(self, request): + # This middleware hijacks the request to perform checks. Any + # error raised during these checks should be caught to avoid + # disturbing the handling of the request. Proper error reporting + # should be handled in the check method itself. + try: + check_profiles_cached() + except Exception: + pass + return None + + class ExceptionMiddleware: """Convert exceptions into appropriate HttpResponse responses. @@ -163,6 +211,24 @@ path_regex = settings.API_URL_REGEXP +class ErrorsMiddleware: + """Handle ExternalComponentException exceptions in POST requests: add a + message with the error string and redirect to the same page (using GET). + """ + + def process_exception(self, request, exception): + should_process_exception = ( + request.method == 'POST' and + isinstance(exception, ExternalComponentException)) + if should_process_exception: + messages.error(request, unicode(exception)) + return HttpResponseRedirect(request.path) + else: + # Not an ExternalComponentException or not a POST request: do not + # handle it. + return None + + class ExceptionLoggerMiddleware: def process_exception(self, request, exception): diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/migrations/0001_initial.py maas-0.1+bzr482+dfsg/src/maasserver/migrations/0001_initial.py --- maas-0.1+bzr462+dfsg/src/maasserver/migrations/0001_initial.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/migrations/0001_initial.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Initial maasserver migration.""" from __future__ import ( + absolute_import, print_function, # This breaks South. #unicode_literals, diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/migrations/0002_macaddress_unique.py maas-0.1+bzr482+dfsg/src/maasserver/migrations/0002_macaddress_unique.py --- maas-0.1+bzr462+dfsg/src/maasserver/migrations/0002_macaddress_unique.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/migrations/0002_macaddress_unique.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Maasserver migration 0002_macaddress_unique.""" from __future__ import ( + absolute_import, print_function, # This breaks South. #unicode_literals, diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/migrations/0006_increase_filestorage_filename_length.py maas-0.1+bzr482+dfsg/src/maasserver/migrations/0006_increase_filestorage_filename_length.py --- maas-0.1+bzr462+dfsg/src/maasserver/migrations/0006_increase_filestorage_filename_length.py 1970-01-01 00:00:00.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/migrations/0006_increase_filestorage_filename_length.py 2012-04-19 04:11:04.000000000 +0000 @@ -0,0 +1,136 @@ +# flake8: noqa +# SKIP this file when reformatting. +# The rest of this file was generated by South. + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'FileStorage.filename' + db.alter_column('maasserver_filestorage', 'filename', self.gf('django.db.models.fields.CharField')(unique=True, max_length=200)) + + + def backwards(self, orm): + + # Changing field 'FileStorage.filename' + db.alter_column('maasserver_filestorage', 'filename', self.gf('django.db.models.fields.CharField')(max_length=100, unique=True)) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'maasserver.config': { + 'Meta': {'object_name': 'Config'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) + }, + 'maasserver.filestorage': { + 'Meta': {'object_name': 'FileStorage'}, + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}), + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'maasserver.macaddress': { + 'Meta': {'object_name': 'MACAddress'}, + 'created': ('django.db.models.fields.DateField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), + 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['maasserver.Node']"}), + 'updated': ('django.db.models.fields.DateTimeField', [], {}) + }, + 'maasserver.node': { + 'Meta': {'object_name': 'Node'}, + 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386'", 'max_length': '10'}), + 'created': ('django.db.models.fields.DateField', [], {}), + 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), + 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), + 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-1bda95d4-876c-11e1-b67e-002215205ce8'", 'unique': 'True', 'max_length': '41'}), + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {}) + }, + 'maasserver.sshkey': { + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, + 'created': ('django.db.models.fields.DateField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.TextField', [], {}), + 'updated': ('django.db.models.fields.DateTimeField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'maasserver.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'piston.consumer': { + 'Meta': {'object_name': 'Consumer'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"}) + }, + 'piston.token': { + 'Meta': {'object_name': 'Token'}, + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1334543418L'}), + 'token_type': ('django.db.models.fields.IntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) + } + } + + complete_apps = ['maasserver'] diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/models.py maas-0.1+bzr482+dfsg/src/maasserver/models.py --- maas-0.1+bzr462+dfsg/src/maasserver/models.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/models.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """MAAS model objects.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -386,7 +387,7 @@ if constraints.get('name'): available_nodes = available_nodes.filter( - system_id=constraints['name']) + hostname=constraints['name']) available_nodes = list(available_nodes[:1]) if len(available_nodes) == 0: @@ -1066,7 +1067,7 @@ # Unix filenames can be longer than this (e.g. 255 bytes), but leave # some extra room for the full path, as well as a versioning suffix. - filename = models.CharField(max_length=100, unique=True, editable=False) + filename = models.CharField(max_length=200, unique=True, editable=False) data = models.FileField( upload_to=upload_dir, storage=storage, max_length=255) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/provisioning.py maas-0.1+bzr482+dfsg/src/maasserver/provisioning.py --- maas-0.1+bzr462+dfsg/src/maasserver/provisioning.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/provisioning.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,18 +4,22 @@ """Interact with the Provisioning API.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) __metaclass__ = type __all__ = [ + 'check_profiles', 'get_provisioning_api_proxy', + 'get_all_profile_names', 'present_detailed_user_friendly_fault', 'ProvisioningProxy', ] from functools import partial +import itertools from logging import getLogger from textwrap import dedent from urllib import urlencode @@ -28,13 +32,15 @@ post_save, ) from django.dispatch import receiver +from django.utils.safestring import mark_safe from maasserver.components import ( COMPONENT, discard_persistent_error, register_persistent_error, ) -from maasserver.exceptions import MAASAPIException +from maasserver.exceptions import ExternalComponentException from maasserver.models import ( + ARCHITECTURE_CHOICES, Config, MACAddress, Node, @@ -142,7 +148,7 @@ if user_friendly_text is None: return None else: - return MAASAPIException(dedent( + return ExternalComponentException(dedent( user_friendly_text.lstrip('\n') % params)) @@ -181,6 +187,7 @@ 'add_node': [COMPONENT.PSERV, COMPONENT.COBBLER, COMPONENT.IMPORT_ISOS], 'modify_nodes': [COMPONENT.PSERV, COMPONENT.COBBLER], 'delete_nodes_by_name': [COMPONENT.PSERV, COMPONENT.COBBLER], + 'get_profiles_by_name': [COMPONENT.PSERV, COMPONENT.COBBLER], } # A mapping exception -> component. @@ -415,16 +422,57 @@ return conversions.get(architecture, architecture) -def select_profile_for_node(node): - """Select which profile a node should be configured for.""" - assert node.architecture, "Node's architecture is not known." - cobbler_arch = name_arch_in_cobbler_style(node.architecture) +def check_profiles(): + """Check that Cobbler has profiles defined for all the profiles used by + MAAS. If a profile is missing, display a persistent error with an invite + to run the maas-import-isos script. + """ + all_profiles = get_all_profile_names() + papi = get_provisioning_api_proxy() + existing_profiles = set(papi.get_profiles_by_name(all_profiles)) + missing_profiles = set(all_profiles) - existing_profiles + if len(missing_profiles) != 0: + # Some profiles are missing: display a persistent component + # error. + register_persistent_error( + COMPONENT.IMPORT_ISOS, + mark_safe( + """ + Some of the required system profiles are missing. + Run the maas-import-isos script to import Ubuntu isos and + create the related profiles: +
sudo maas-import-isos
+ """)) + + +def get_all_profile_names(): + """Return all the names of the profiles used by MAAS.""" + architectures = {arch[0] for arch in ARCHITECTURE_CHOICES} + commissioning = {True, False} + product = itertools.product(architectures, commissioning) + profiles = [ + get_profile_name(architecture, commissioning) + for architecture, commissioning in product] + return profiles + + +def get_profile_name(architecture, commissioning=False): + """Return the profile name for a given architecture and whether the node + is commissioning or not.""" + cobbler_arch = name_arch_in_cobbler_style(architecture) profile = "maas-%s-%s" % ("precise", cobbler_arch) - if node.status == NODE_STATUS.COMMISSIONING: + if commissioning: profile += "-commissioning" return profile +def select_profile_for_node(node): + """Select which profile a node should be configured for.""" + assert node.architecture, "Node's architecture is not known." + commissioning = node.status == NODE_STATUS.COMMISSIONING + return get_profile_name(node.architecture, commissioning) + + @receiver(post_save, sender=Node) def provision_post_save_Node(sender, instance, created, **kwargs): """Create or update nodes in the provisioning server.""" diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/rabbit.py maas-0.1+bzr482+dfsg/src/maasserver/rabbit.py --- maas-0.1+bzr462+dfsg/src/maasserver/rabbit.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/rabbit.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Rabbit messaging.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/static/css/base.css maas-0.1+bzr482+dfsg/src/maasserver/static/css/base.css --- maas-0.1+bzr462+dfsg/src/maasserver/static/css/base.css 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/static/css/base.css 2012-04-19 04:11:04.000000000 +0000 @@ -13,5 +13,9 @@ pre { background-color: #f2f2f2; padding: 5px 5px 5px 3px; - display: inline-block; + display: block; + overflow: auto; } +#flash-messages .warning pre { + background-color: #ECA918; +} diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/static/css/components/data_list.css maas-0.1+bzr482+dfsg/src/maasserver/static/css/components/data_list.css --- maas-0.1+bzr462+dfsg/src/maasserver/static/css/components/data_list.css 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/static/css/components/data_list.css 2012-04-19 04:11:04.000000000 +0000 @@ -9,3 +9,6 @@ .data-list span { font-size: 16px; } +.data-list .block { + word-wrap: break-word; + } diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/static/css/components/table_list.css maas-0.1+bzr482+dfsg/src/maasserver/static/css/components/table_list.css --- maas-0.1+bzr462+dfsg/src/maasserver/static/css/components/table_list.css 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/static/css/components/table_list.css 2012-04-19 04:11:04.000000000 +0000 @@ -40,5 +40,5 @@ padding: 2px 0; } ul.list .icon { - padding: 6px 0 0 0; + padding: 2px 0 0 0; } diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/static/css/components/yui_node_add.css maas-0.1+bzr482+dfsg/src/maasserver/static/css/components/yui_node_add.css --- maas-0.1+bzr462+dfsg/src/maasserver/static/css/components/yui_node_add.css 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/static/css/components/yui_node_add.css 2012-04-19 04:11:04.000000000 +0000 @@ -4,3 +4,6 @@ .yui3-node-add-widget .buttons { margin-top: 30px; } +.yui3-node-add-widget .add-link img.icon { + margin-right: 6px; + } diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/static/css/layout.css maas-0.1+bzr482+dfsg/src/maasserver/static/css/layout.css --- maas-0.1+bzr462+dfsg/src/maasserver/static/css/layout.css 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/static/css/layout.css 2012-04-19 04:11:04.000000000 +0000 @@ -189,6 +189,17 @@ #sidebar .button { width: 100px; } +#sidebar h4 { + margin: 30px 0 0 0; + } +#sidebar h4:first-child { + margin-top: 10px; + } +#sidebar .button, +#sidebar button, +#sidebar input[type="submit"] { + margin-top: 10px; + } /****************************************************************************** diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/static/css/modifiers.css maas-0.1+bzr482+dfsg/src/maasserver/static/css/modifiers.css --- maas-0.1+bzr462+dfsg/src/maasserver/static/css/modifiers.css 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/static/css/modifiers.css 2012-04-19 04:11:04.000000000 +0000 @@ -22,6 +22,9 @@ vertical-align: text-bottom; margin-right: 3px; } +a.icon:hover { + text-decoration: none; + } /* Spacing */ .space-top-small { margin-top: 10px; @@ -38,6 +41,9 @@ .space-right-small { margin-right: 10px; } +.space-left-small { + margin-left: 10px; + } .space-bottom-none { margin-bottom: 0; } @@ -45,8 +51,8 @@ margin-bottom: 5px; } .pad-top { - margin-top: 20px; + padding-top: 20px; } .pad-top-large { - margin-top: 40px; + padding-top: 40px; } Binary files /tmp/L56NtgH4uS/maas-0.1+bzr462+dfsg/src/maasserver/static/img/inline_add.png and /tmp/ugMSGV0c6Q/maas-0.1+bzr482+dfsg/src/maasserver/static/img/inline_add.png differ diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/static/js/node_add.js maas-0.1+bzr482+dfsg/src/maasserver/static/js/node_add.js --- maas-0.1+bzr462+dfsg/src/maasserver/static/js/node_add.js 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/static/js/node_add.js 2012-04-19 04:11:04.000000000 +0000 @@ -114,11 +114,16 @@ .set('href', '#') .set('text', "Cancel") .addClass('link-button'); + var macaddress_add_icon = Y.Node.create('') + .set('src', MAAS_config.uris.statics + 'img/inline_add.png') + .set('alt', "+") + .addClass('icon'); var macaddress_add_link = Y.Node.create('') .addClass('add-link') .addClass('add-mac-form') .set('href', '#') - .set('text', "Add additional MAC address"); + .set('text', "Add additional MAC address") + .prepend(macaddress_add_icon); var operation = Y.Node.create('') .set('type', 'hidden') .set('name', 'op') diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/static/js/node_views.js maas-0.1+bzr482+dfsg/src/maasserver/static/js/node_views.js --- maas-0.1+bzr462+dfsg/src/maasserver/static/js/node_views.js 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/static/js/node_views.js 2012-04-19 04:11:04.000000000 +0000 @@ -145,7 +145,13 @@ this.numberNode = Y.one(config.numberNode); this.descriptionNode = Y.one(config.descriptionNode); this.reservedNode = Y.one(config.reservedNode); + /* XXX: GavinPanella 2012-04-17 bug=984117: + * Hidden until we support reserved nodes. */ + this.reservedNode.hide(); this.retiredNode = Y.one(config.retiredNode); + /* XXX: GavinPanella 2012-04-17 bug=984116: + * Hidden until we support retired nodes. */ + this.retiredNode.hide(); this.deployed_nodes = 0; this.commissioned_nodes = 0; this.queued_nodes = 0; diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/static/js/prefs.js maas-0.1+bzr482+dfsg/src/maasserver/static/js/prefs.js --- maas-0.1+bzr462+dfsg/src/maasserver/static/js/prefs.js 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/static/js/prefs.js 2012-04-19 04:11:04.000000000 +0000 @@ -42,7 +42,7 @@ .set('id','create_token') .addClass('button') .addClass('right') - .set('text', "+ Add MAAS key"); + .set('text', "+ Generate MAAS key"); this.status_node = Y.Node.create('
') .set('id','create_error'); this.spinnerNode = Y.Node.create('') diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/static/js/tests/test_node_views.js maas-0.1+bzr482+dfsg/src/maasserver/static/js/tests/test_node_views.js --- maas-0.1+bzr462+dfsg/src/maasserver/static/js/tests/test_node_views.js 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/static/js/tests/test_node_views.js 2012-04-19 04:11:04.000000000 +0000 @@ -160,10 +160,16 @@ '3 nodes reserved for named deployment.', Y.one('#reserved-nodes').get('text'), 'The reserved text should be set'); + /* XXX: GavinPanella 2012-04-17 bug=984117: + * Hidden until we support reserved nodes. */ + Y.Assert.areEqual("none", view.reservedNode.getStyle("display")); Y.Assert.areEqual( '1 retired node not represented.', Y.one('#retired-nodes').get('text'), 'The retired text should be set'); + /* XXX: GavinPanella 2012-04-17 bug=984116: + * Hidden until we support retired nodes. */ + Y.Assert.areEqual("none", view.retiredNode.getStyle("display")); }, testUpdateNodeCreation: function() { diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/static/js/tests/test_prefs.js maas-0.1+bzr482+dfsg/src/maasserver/static/js/tests/test_prefs.js --- maas-0.1+bzr462+dfsg/src/maasserver/static/js/tests/test_prefs.js 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/static/js/tests/test_prefs.js 2012-04-19 04:11:04.000000000 +0000 @@ -40,7 +40,7 @@ var create_link = widget.get('srcNode').one('#create_token'); Y.Assert.isNotNull(create_link); Y.Assert.areEqual( - "+ Add MAAS key", create_link.get('text')); + "+ Generate MAAS key", create_link.get('text')); // The placeholder node for errors has been created. var status_node = widget.get('srcNode').one('#create_error'); Y.Assert.isNotNull(status_node); diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/base.html maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/base.html --- maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/base.html 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/base.html 2012-04-19 04:11:04.000000000 +0000 @@ -107,7 +107,8 @@ Ubuntu {% block footer-copyright %}

© 2012 Canonical Ltd. Ubuntu and Canonical are registered - trademarks of Canonical Ltd.

+ trademarks of Canonical Ltd. +
View Documentation

{% endblock %}
diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/index.html maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/index.html --- maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/index.html 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/index.html 2012-04-19 04:11:04.000000000 +0000 @@ -72,7 +72,7 @@

- Add node + + Add node
diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/node_list.html maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/node_list.html --- maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/node_list.html 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/node_list.html 2012-04-19 04:11:04.000000000 +0000 @@ -18,6 +18,11 @@ e.preventDefault(); Y.maas.node_add.showAddNodeWidget({targetNode: '#nodes'}); }); + // Reload the page when a new node gets added. + Y.maas.node_add.AddNodeDispatcher.on( + Y.maas.node_add.NODE_ADDED_EVENT, function(e) { + window.location.reload(); + }); }); }); // --> @@ -55,6 +60,6 @@ {% endfor %} {% endif%} - Add node + + Add node {% endblock %} diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/node_view.html maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/node_view.html --- maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/node_view.html 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/node_view.html 2012-04-19 04:11:04.000000000 +0000 @@ -7,41 +7,37 @@ {% block sidebar %} {% if can_edit %} -
-

Node details

- - Edit node - -
+

Node details

+ + Edit node + {% endif %} {% if form.action_buttons or can_delete %} -

Actions

- {% if can_delete %} - {% if node.owner %} - - Delete node - - {% else %} - - Delete node - - {% endif %} + {% endif %} + {% if can_delete %} + {% if node.owner %} + + Delete node + + {% else %} + + Delete node + {% endif %} - {% for action in form.action_buttons %} - {% if forloop.first %} -
- {% endif %} - - {% if forloop.last %}
{% endif %} - {% endfor %} -
+ {% endif %} + {% if form.action_buttons %} +
+ {% for action in form.action_buttons %} + + {% endfor %} +
{% endif %} {% endblock %} @@ -65,12 +61,16 @@ {{ node.display_status }} - {% if node.error %} + {% if error_text %}
  • Error output

    - - {{ node.error }} - + {{ error_text }} +
  • + {% endif %} + {% if status_text %} +
  • +

    Console output

    + {{ status_text }}
  • {% endif %} {% if node.owner %} @@ -81,4 +81,3 @@ {% endif %} {% endblock %} - diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/prefs_add_sshkey.html maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/prefs_add_sshkey.html --- maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/prefs_add_sshkey.html 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/prefs_add_sshkey.html 2012-04-19 04:11:04.000000000 +0000 @@ -4,13 +4,13 @@ {% block page-title %}Add SSH key{% endblock %} {% block content %} -
    +
      {% for field in form %} {% include "maasserver/form_field.html" %} {% endfor %}
    - -   Cancel + Cancel +
    {% endblock %} diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/prefs.html maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/prefs.html --- maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/prefs.html 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/prefs.html 2012-04-19 04:11:04.000000000 +0000 @@ -56,7 +56,7 @@ + Add SSH key + class="button right space-top">+ Add SSH key
    diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/settings_add_archive.html maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/settings_add_archive.html --- maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/settings_add_archive.html 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/settings_add_archive.html 2012-04-19 04:11:04.000000000 +0000 @@ -5,13 +5,13 @@ {% block page-title %}Add archive{% endblock %} {% block content %} -
    +
      {% for field in form %} {% include "maasserver/form_field.html" %} {% endfor %}
    - -   Cancel + + Cancel
    {% endblock %} diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/settings.html maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/settings.html --- maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/settings.html 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/settings.html 2012-04-19 04:11:04.000000000 +0000 @@ -40,14 +40,15 @@ + title="Edit user {{ user_item.username }}" + class="icon"> edit {% if user != user_item %} delete diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/user_view.html maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/user_view.html --- maas-0.1+bzr462+dfsg/src/maasserver/templates/maasserver/user_view.html 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/templates/maasserver/user_view.html 2012-04-19 04:11:04.000000000 +0000 @@ -6,18 +6,14 @@ {% block layout-modifiers %}sidebar{% endblock %} {% block sidebar %} -
    -

    User details

    - - Edit user - -
    -
    -

    Actions

    - - Delete user - -
    +

    User details

    + + Edit user + +

    Actions

    + + Delete user + {% endblock %} {% block content %} diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/templatetags/field_type.py maas-0.1+bzr482+dfsg/src/maasserver/templatetags/field_type.py --- maas-0.1+bzr462+dfsg/src/maasserver/templatetags/field_type.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/templatetags/field_type.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Field type template tag.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/testing/enum.py maas-0.1+bzr482+dfsg/src/maasserver/testing/enum.py --- maas-0.1+bzr462+dfsg/src/maasserver/testing/enum.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/testing/enum.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Enumeration helpers.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/testing/factory.py maas-0.1+bzr482+dfsg/src/maasserver/testing/factory.py --- maas-0.1+bzr462+dfsg/src/maasserver/testing/factory.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/testing/factory.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test object factories.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/testing/__init__.py maas-0.1+bzr482+dfsg/src/maasserver/testing/__init__.py --- maas-0.1+bzr462+dfsg/src/maasserver/testing/__init__.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/testing/__init__.py 2012-04-19 04:11:04.000000000 +0000 @@ -2,6 +2,7 @@ # GNU Affero General Public License version 3 (see the file LICENSE). from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/testing/models.py maas-0.1+bzr482+dfsg/src/maasserver/testing/models.py --- maas-0.1+bzr462+dfsg/src/maasserver/testing/models.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/testing/models.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test model for tests of testing module.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/testing/oauthclient.py maas-0.1+bzr482+dfsg/src/maasserver/testing/oauthclient.py --- maas-0.1+bzr462+dfsg/src/maasserver/testing/oauthclient.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/testing/oauthclient.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """OAuth client for API testing.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/testing/testcase.py maas-0.1+bzr482+dfsg/src/maasserver/testing/testcase.py --- maas-0.1+bzr462+dfsg/src/maasserver/testing/testcase.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/testing/testcase.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Custom test-case classes.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -16,6 +17,7 @@ 'TestModelTestCase', ] +from django.core.cache import cache from maasserver.testing import reset_fake_provisioning_api_proxy from maasserver.testing.factory import factory import maastesting.testcase @@ -25,6 +27,7 @@ def setUp(self): super(TestCase, self).setUp() + self.addCleanup(cache.clear) self.addCleanup(reset_fake_provisioning_api_proxy) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/testing/tests/test_enum.py maas-0.1+bzr482+dfsg/src/maasserver/testing/tests/test_enum.py --- maas-0.1+bzr462+dfsg/src/maasserver/testing/tests/test_enum.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/testing/tests/test_enum.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for enumeration helpers.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/testing/tests/test_factory.py maas-0.1+bzr482+dfsg/src/maasserver/testing/tests/test_factory.py --- maas-0.1+bzr462+dfsg/src/maasserver/testing/tests/test_factory.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/testing/tests/test_factory.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test the factory where appropriate. Don't overdo this.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/testing/tests/test_module.py maas-0.1+bzr482+dfsg/src/maasserver/testing/tests/test_module.py --- maas-0.1+bzr462+dfsg/src/maasserver/testing/tests/test_module.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/testing/tests/test_module.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for `maasserver.testing`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/models.py maas-0.1+bzr482+dfsg/src/maasserver/tests/models.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/models.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/models.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test related classes and functions for maas and its applications.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_api.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_api.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_api.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_api.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test maasserver API.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -1042,11 +1043,11 @@ desired_node = random.choice(available_nodes) response = self.client.post(self.get_uri('nodes/'), { 'op': 'acquire', - 'name': desired_node.system_id, + 'name': desired_node.hostname, }) self.assertEqual(httplib.OK, response.status_code) parsed_result = json.loads(response.content) - self.assertEqual(desired_node.system_id, parsed_result['system_id']) + self.assertEqual(desired_node.hostname, parsed_result['hostname']) def test_POST_acquire_would_rather_fail_than_disobey_constraint(self): # If "acquire" is passed a constraint, it won't return a node @@ -1076,13 +1077,13 @@ # If a name constraint is given, "acquire" attempts to allocate # a node of that name. node = factory.make_node(status=NODE_STATUS.READY, owner=None) - system_id = node.system_id response = self.client.post(self.get_uri('nodes/'), { 'op': 'acquire', - 'name': system_id, + 'name': node.hostname, }) self.assertEqual(httplib.OK, response.status_code) - self.assertEqual(system_id, json.loads(response.content)['system_id']) + self.assertEqual( + node.hostname, json.loads(response.content)['hostname']) def test_POST_acquire_constrains_by_name(self): # Negative test for name constraint. diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_auth.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_auth.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_auth.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_auth.py 2012-04-19 04:11:04.000000000 +0000 @@ -2,6 +2,7 @@ # GNU Affero General Public License version 3 (see the file LICENSE). from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_commands.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_commands.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_commands.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_commands.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test custom commands, as found in src/maasserver/management/commands.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -17,6 +18,7 @@ from django.conf import settings from django.contrib.auth.models import User +from django.core.cache import cache from django.core.management import call_command from maasserver.models import FileStorage from maasserver.testing.factory import factory @@ -101,3 +103,18 @@ self.assertTrue(users[0].check_password(password)) self.assertTrue(users[0].is_superuser) self.assertEqual(email, users[0].email) + + def test_clearcache_clears_entire_cache(self): + key = factory.getRandomString() + cache.set(key, factory.getRandomString()) + call_command('clearcache') + self.assertIsNone(cache.get(key, None)) + + def test_clearcache_clears_specific_key(self): + key = factory.getRandomString() + cache.set(key, factory.getRandomString()) + another_key = factory.getRandomString() + cache.set(another_key, factory.getRandomString()) + call_command('clearcache', key=key) + self.assertIsNone(cache.get(key, None)) + self.assertIsNotNone(cache.get(another_key, None)) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_components.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_components.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_components.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_components.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test maasserver components module.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_configuration.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_configuration.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_configuration.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_configuration.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests configuration.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_fields.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_fields.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_fields.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_fields.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test custom model fields.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_forms.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_forms.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_forms.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_forms.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test forms.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_js.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_js.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_js.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_js.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Run YUI3 unit tests with SST (http://testutils.org/sst/).""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_maasavahi.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_maasavahi.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_maasavahi.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_maasavahi.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for the Avahi export of MAAS.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_messages.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_messages.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_messages.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_messages.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test maasserver messages.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_middleware.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_middleware.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_middleware.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_middleware.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test maasserver middleware classes.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -16,31 +17,64 @@ import logging from tempfile import NamedTemporaryFile +from django.contrib.messages import constants +from django.core.cache import cache from django.core.exceptions import ( PermissionDenied, ValidationError, ) from django.test.client import RequestFactory +from maasserver import ( + components, + middleware as middleware_module, + provisioning, + ) from maasserver.exceptions import ( + ExternalComponentException, MAASAPIException, MAASAPINotFound, + MAASException, ) from maasserver.middleware import ( APIErrorsMiddleware, + check_profiles_cached, + clear_profiles_check_cache, + ErrorsMiddleware, ExceptionLoggerMiddleware, ExceptionMiddleware, + ExternalComponentsMiddleware, + PROFILES_CHECK_DONE_KEY, ) from maasserver.testing.factory import factory -from maasserver.testing.testcase import TestCase +from maasserver.testing.testcase import ( + LoggedInTestCase, + TestCase, + ) + + +class Messages: + """A class to record messages published by Django messaging + framework. + """ + + messages = [] + + def add(self, level, message, extras): + self.messages.append((level, message, extras)) -def fake_request(base_path): +def fake_request(path, method='GET'): """Create a fake request. - :param base_path: The base path to make the request to. + :param path: The path to make the request to. + :param method: The method to use for the reques + ('GET' or 'POST'). """ rf = RequestFactory() - return rf.get('%s/hello/' % base_path) + request = rf.get(path) + request.method = method + request._messages = Messages() + return request class ExceptionMiddlewareTest(TestCase): @@ -164,3 +198,109 @@ fake_request('/middleware/api/hello'), ValueError(error_text)) self.assertIn(error_text, open(logfile.name).read()) + + +class ExternalComponentsMiddlewareTest(TestCase): + + def patch_papi_get_profiles_by_name(self, method): + self.patch(components, '_PERSISTENT_ERRORS', {}) + papi = provisioning.get_provisioning_api_proxy() + self.patch(papi.proxy, 'get_profiles_by_name', method) + + def test_middleware_calls_check_profiles_cached(self): + calls = [] + self.patch( + middleware_module, "check_profiles_cached", + lambda: calls.append(1)) + middleware = ExternalComponentsMiddleware() + response = middleware.process_request(None) + self.assertIsNone(response) + self.assertEqual(1, len(calls)) + + def test_check_profiles_cached_sets_cache_key(self): + def return_all_profiles(profiles): + return profiles + self.patch_papi_get_profiles_by_name(return_all_profiles) + + check_profiles_cached() + self.assertTrue(cache.get(PROFILES_CHECK_DONE_KEY, False)) + + def test_check_profiles_cached_sets_cache_key_if_exception_raised(self): + # The cache key PROFILES_CHECK_DONE_KEY is set to True even if + # the call to papi.get_profiles_by_name raises an exception. + def raise_exception(profiles): + raise Exception() + self.patch_papi_get_profiles_by_name(raise_exception) + try: + check_profiles_cached() + except Exception: + pass + self.assertTrue(cache.get(PROFILES_CHECK_DONE_KEY, False)) + + def test_check_profiles_cached_does_nothing_if_cache_key_set(self): + # If the cache key PROFILES_CHECK_DONE_KE is set to True + # the call to check_profiles_cached is silent. + def raise_exception(profiles): + raise Exception() + cache.set(PROFILES_CHECK_DONE_KEY, True) + self.patch_papi_get_profiles_by_name(raise_exception) + check_profiles_cached() + # No exception, get_profiles_by_name has not been called. + + def test_clear_profiles_check_cache_deletes_PROFILES_CHECK_DONE_KEY(self): + cache.set(PROFILES_CHECK_DONE_KEY, factory.getRandomString()) + self.assertTrue(cache.get(PROFILES_CHECK_DONE_KEY, False)) + clear_profiles_check_cache() + self.assertFalse(cache.get(PROFILES_CHECK_DONE_KEY, False)) + + def test_middleware_returns_none_if_exception_raised(self): + def raise_exception(profiles): + raise Exception() + + self.patch_papi_get_profiles_by_name(raise_exception) + middleware = ExternalComponentsMiddleware() + request = fake_request(factory.getRandomString()) + response = middleware.process_request(request) + self.assertIsNone(response) + + def test_middleware_does_not_catch_keyboardinterrupt_exception(self): + def raise_exception(profiles): + raise KeyboardInterrupt() + + self.patch_papi_get_profiles_by_name(raise_exception) + middleware = ExternalComponentsMiddleware() + request = fake_request(factory.getRandomString()) + self.assertRaises( + KeyboardInterrupt, middleware.process_request, request) + + +class ErrorsMiddlewareTest(LoggedInTestCase): + + def test_error_middleware_ignores_GET_requests(self): + request = fake_request(factory.getRandomString(), 'GET') + exception = MAASException() + middleware = ErrorsMiddleware() + response = middleware.process_exception(request, exception) + self.assertIsNone(response) + + def test_error_middleware_ignores_non_ExternalComponentException(self): + request = fake_request(factory.getRandomString(), 'GET') + exception = ValueError() + middleware = ErrorsMiddleware() + response = middleware.process_exception(request, exception) + self.assertIsNone(response) + + def test_error_middleware_handles_ExternalComponentException(self): + url = factory.getRandomString() + request = fake_request(url, 'POST') + error_message = factory.getRandomString() + exception = ExternalComponentException(error_message) + middleware = ErrorsMiddleware() + response = middleware.process_exception(request, exception) + # The response is a redirect. + self.assertEqual( + (httplib.FOUND, response['Location']), + (response.status_code, url)) + # An error message has been published. + self.assertEqual( + [(constants.ERROR, error_message, '')], request._messages.messages) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_models.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_models.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_models.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_models.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test maasserver models.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -554,7 +555,7 @@ self.assertEqual( nodes[1], Node.objects.get_available_node_for_acquisition( - user, {'name': nodes[1].system_id})) + user, {'name': nodes[1].hostname})) def test_get_available_node_returns_None_if_name_is_unknown(self): user = factory.make_user() diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_provisioning.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_provisioning.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_provisioning.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_provisioning.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for `maasserver.provisioning`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -25,7 +26,10 @@ get_persistent_errors, register_persistent_error, ) -from maasserver.exceptions import MAASAPIException +from maasserver.exceptions import ( + ExternalComponentException, + MAASAPIException, + ) from maasserver.models import ( ARCHITECTURE, Config, @@ -35,11 +39,14 @@ NODE_STATUS_CHOICES, ) from maasserver.provisioning import ( + check_profiles, compose_cloud_init_preseed, compose_commissioning_preseed, compose_preseed, DETAILED_PRESENTATIONS, + get_all_profile_names, get_metadata_server_url, + get_profile_name, name_arch_in_cobbler_style, present_detailed_user_friendly_fault, present_user_friendly_fault, @@ -272,6 +279,21 @@ def test_name_arch_in_cobbler_returns_unicode(self): self.assertIsInstance(name_arch_in_cobbler_style(b'amd64'), unicode) + def test_get_profile_name_selects_Precise_and_right_arch(self): + architectures = map_enum(ARCHITECTURE).values() + self.assertItemsEqual( + [ + 'maas-precise-%s' % name_arch_in_cobbler_style(arch) + for arch in architectures], + [ + get_profile_name(arch) + for arch in architectures]) + + def test_get_profile_name_converts_architecture_name(self): + profile = get_profile_name(architecture='amd64') + self.assertNotIn('amd64', profile) + self.assertIn('x86_64', profile) + @inlineCallbacks def test_select_profile_for_node_ignores_previously_chosen_profile(self): node = factory.make_node(architecture='i386') @@ -280,23 +302,6 @@ self.assertEqual( 'maas-precise-i386', select_profile_for_node(node)) - def test_select_profile_for_node_selects_Precise_and_right_arch(self): - nodes = { - arch: self.make_node_without_saving(arch=arch) - for arch in map_enum(ARCHITECTURE).values()} - self.assertItemsEqual([ - 'maas-precise-%s' % name_arch_in_cobbler_style(arch) - for arch in nodes.keys()], - [ - select_profile_for_node(node) - for node in nodes.values()]) - - def test_select_profile_for_node_converts_architecture_name(self): - node = factory.make_node(architecture='amd64') - profile = select_profile_for_node(node) - self.assertNotIn('amd64', profile) - self.assertIn('x86_64', profile) - def test_select_profile_for_node_works_for_commissioning(self): # A special profile is chosen for nodes in the commissioning # state. @@ -316,6 +321,14 @@ pserv_node = self.papi.get_nodes_by_name([system_id])[system_id] self.assertEqual("maas-precise-i386", pserv_node["profile"]) + def test_get_all_profile_names(self): + expected_profiles = [] + for arch in map_enum(ARCHITECTURE).values(): + for commissioning in (False, True): + expected_profiles.append( + get_profile_name(arch, commissioning)) + self.assertItemsEqual(expected_profiles, get_all_profile_names()) + def test_provision_post_save_Node_checks_for_missing_profile(self): # If the required profile for a node is missing, MAAS reports # that the maas-import-isos script may need running. @@ -324,7 +337,7 @@ raise Fault(PSERV_FAULT.NO_SUCH_PROFILE, "Unknown profile.") self.patch(self.papi.proxy, 'add_node', raise_missing_profile) - with ExpectedException(MAASAPIException): + with ExpectedException(ExternalComponentException): node = factory.make_node(architecture='amd32k') provisioning.provision_post_save_Node( sender=Node, instance=node, created=True) @@ -335,7 +348,7 @@ raise Fault(PSERV_FAULT.NO_COBBLER, factory.getRandomString()) self.patch(self.papi.proxy, 'add_node', raise_fault) - with ExpectedException(MAASAPIException): + with ExpectedException(ExternalComponentException): node = factory.make_node(architecture='amd32k') provisioning.provision_post_save_Node( sender=Node, instance=node, created=True) @@ -416,7 +429,8 @@ self.patch(self.papi.proxy, 'add_node', raise_fault) - with ExpectedException(MAASAPIException, ".*provisioning server.*"): + with ExpectedException( + ExternalComponentException, ".*provisioning server.*"): self.papi.add_node('node', 'profile', 'power', '') def test_provisioning_errors_are_reported_helpfully(self): @@ -426,7 +440,7 @@ self.patch(self.papi.proxy, 'add_node', raise_provisioning_error) - with ExpectedException(MAASAPIException, ".*Cobbler.*"): + with ExpectedException(ExternalComponentException, ".*Cobbler.*"): self.papi.add_node('node', 'profile', 'power', '') def patch_and_call_papi_method(self, fault_code, papi_method='add_node'): @@ -459,6 +473,27 @@ errors = get_persistent_errors() self.assertEqual(1, len(errors)) + def patch_get_profiles_by_name(self, method): + self.patch(components, '_PERSISTENT_ERRORS', {}) + self.patch( + self.papi.proxy, 'get_profiles_by_name', method) + + def test_check_profiles_no_error_registered_if_all_profiles_found(self): + def return_all_profiles(profiles): + return profiles + self.patch_get_profiles_by_name(return_all_profiles) + check_profiles() + self.assertEqual([], get_persistent_errors()) + + def test_check_profiles_error_registered_if_not_all_profiles_found(self): + def return_some_profiles(profiles): + return profiles[1:] + self.patch_get_profiles_by_name(return_some_profiles) + + check_profiles() + errors = get_persistent_errors() + self.assertIn("
    sudo maas-import-isos
    ", errors[0]) + def test_failing_components_cleared_if_add_node_works(self): self.patch(components, '_PERSISTENT_ERRORS', {}) register_persistent_error(COMPONENT.PSERV, factory.getRandomString()) @@ -478,22 +513,30 @@ self.papi.modify_nodes({}) self.assertEqual([other_error], get_persistent_errors()) - def test_failing_components_cleared_if_modify_nodes_works(self): + def register_random_errors(self, failed_components): self.patch(components, '_PERSISTENT_ERRORS', {}) - register_persistent_error(COMPONENT.PSERV, factory.getRandomString()) - register_persistent_error(COMPONENT.COBBLER, factory.getRandomString()) + for component in failed_components: + register_persistent_error(component, factory.getRandomString()) + + def test_failing_components_cleared_if_modify_nodes_works(self): + self.register_random_errors((COMPONENT.PSERV, COMPONENT.COBBLER)) self.papi.modify_nodes({}) self.assertEqual([], get_persistent_errors()) def test_failing_components_cleared_if_delete_nodes_by_name_works(self): - self.patch(components, '_PERSISTENT_ERRORS', {}) - register_persistent_error(COMPONENT.PSERV, factory.getRandomString()) - register_persistent_error(COMPONENT.COBBLER, factory.getRandomString()) + self.register_random_errors((COMPONENT.PSERV, COMPONENT.COBBLER)) other_error = factory.getRandomString() register_persistent_error(factory.getRandomString(), other_error) self.papi.delete_nodes_by_name([]) self.assertEqual([other_error], get_persistent_errors()) + def test_failing_components_cleared_if_get_profiles_by_name_works(self): + self.register_random_errors((COMPONENT.PSERV, COMPONENT.COBBLER)) + other_error = factory.getRandomString() + register_persistent_error(factory.getRandomString(), other_error) + self.papi.get_profiles_by_name([]) + self.assertEqual([other_error], get_persistent_errors()) + class TestProvisioningWithFake(ProvisioningTests, ProvisioningFakeFactory, TestCase): diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_rabbit.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_rabbit.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_rabbit.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_rabbit.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Rabbit messaging tests.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_runserver.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_runserver.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_runserver.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_runserver.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for the "runserver" command module.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_views.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_views.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_views.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_views.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test maasserver API.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -15,6 +16,7 @@ import httplib import os import urllib2 +from urlparse import urlparse from xmlrpclib import Fault from django.conf import settings @@ -29,7 +31,10 @@ views, ) from maasserver.components import register_persistent_error -from maasserver.exceptions import NoRabbit +from maasserver.exceptions import ( + ExternalComponentException, + NoRabbit, + ) from maasserver.forms import NodeActionForm from maasserver.models import ( Config, @@ -57,6 +62,7 @@ from maasserver.views import ( get_longpoll_context, get_yui_location, + NodeEdit, proxy_to_longpoll, ) from maastesting.rabbit import uses_rabbit_fixture @@ -685,15 +691,30 @@ self.assertEqual(0, len(doc.cssselect('form#node_actions input'))) - def test_view_node_shows_error_if_set(self): + def test_view_node_shows_console_output_if_error_set(self): + # When node.error is set but the node's status does not indicate an + # error condition, the contents of node.error are displayed as console + # output. node = factory.make_node( - owner=self.logged_in_user, error=factory.getRandomString()) + owner=self.logged_in_user, error=factory.getRandomString(), + status=NODE_STATUS.READY) node_link = reverse('node-view', args=[node.system_id]) response = self.client.get(node_link) - doc = fromstring(response.content) - content_text = doc.cssselect('#content')[0].text_content() - self.assertIn("Error output", content_text) - self.assertIn(node.error, content_text) + console_output = fromstring(response.content).xpath( + '//h4[text()="Console output"]/following-sibling::span/text()') + self.assertEqual([node.error], console_output) + + def test_view_node_shows_error_output_if_error_set(self): + # When node.error is set and the node's status indicates an error + # condition, the contents of node.error are displayed as error output. + node = factory.make_node( + owner=self.logged_in_user, error=factory.getRandomString(), + status=NODE_STATUS.FAILED_TESTS) + node_link = reverse('node-view', args=[node.system_id]) + response = self.client.get(node_link) + error_output = fromstring(response.content).xpath( + '//h4[text()="Error output"]/following-sibling::span/text()') + self.assertEqual([node.error], error_output) def test_view_node_shows_no_error_if_no_error_set(self): node = factory.make_node(owner=self.logged_in_user) @@ -754,6 +775,43 @@ [message.message for message in response.context['messages']]) +class MAASExceptionHandledInView(LoggedInTestCase): + + def test_raised_MAASException_redirects(self): + # When a ExternalComponentException is raised in a POST request, the + # response is a redirect to the same page. + + # Patch NodeEdit to error on post. + def post(self, request, *args, **kwargs): + raise ExternalComponentException() + self.patch(NodeEdit, 'post', post) + node = factory.make_node(owner=self.logged_in_user) + node_edit_link = reverse('node-edit', args=[node.system_id]) + response = self.client.post(node_edit_link, {}) + redirect_url = urlparse(response['Location']).path + self.assertEqual( + (httplib.FOUND, redirect_url), + (response.status_code, node_edit_link)) + + def test_raised_ExternalComponentException_publishes_message(self): + # When a ExternalComponentException is raised in a POST request, a + # message is published with the error message. + error_message = factory.getRandomString() + + # Patch NodeEdit to error on post. + def post(self, request, *args, **kwargs): + raise ExternalComponentException(error_message) + self.patch(NodeEdit, 'post', post) + node = factory.make_node(owner=self.logged_in_user) + node_edit_link = reverse('node-edit', args=[node.system_id]) + self.client.post(node_edit_link, {}) + # Manually perform the redirect: i.e. get the same page. + response = self.client.get(node_edit_link, {}) + self.assertEqual( + [error_message], + [message.message for message in response.context['messages']]) + + class AdminNodeViewsTest(AdminLoggedInTestCase): def test_admin_can_edit_nodes(self): diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/tests/test_zeroconfservice.py maas-0.1+bzr482+dfsg/src/maasserver/tests/test_zeroconfservice.py --- maas-0.1+bzr462+dfsg/src/maasserver/tests/test_zeroconfservice.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/tests/test_zeroconfservice.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for `zeroconfservice`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/urls_api.py maas-0.1+bzr482+dfsg/src/maasserver/urls_api.py --- maas-0.1+bzr462+dfsg/src/maasserver/urls_api.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/urls_api.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """URL API routing configuration.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/urls.py maas-0.1+bzr482+dfsg/src/maasserver/urls.py --- maas-0.1+bzr462+dfsg/src/maasserver/urls.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/urls.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """URL routing configuration.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/views.py maas-0.1+bzr482+dfsg/src/maasserver/views.py --- maas-0.1+bzr462+dfsg/src/maasserver/views.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/views.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Views.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -31,7 +32,6 @@ import mimetypes import os import urllib2 -from django.utils.safestring import mark_safe from convoy.combo import ( combine_files, @@ -61,6 +61,7 @@ render_to_response, ) from django.template import RequestContext +from django.utils.safestring import mark_safe from django.views.generic import ( CreateView, DeleteView, @@ -147,6 +148,10 @@ NODE_PERMISSION.ADMIN, node) if node.status in (NODE_STATUS.COMMISSIONING, NODE_STATUS.READY): messages.info(self.request, NODE_BOOT_INFO) + context['error_text'] = ( + node.error if node.status == NODE_STATUS.FAILED_TESTS else None) + context['status_text'] = ( + node.error if node.status != NODE_STATUS.FAILED_TESTS else None) return context def get_success_url(self): diff -Nru maas-0.1+bzr462+dfsg/src/maasserver/zeroconfservice.py maas-0.1+bzr482+dfsg/src/maasserver/zeroconfservice.py --- maas-0.1+bzr462+dfsg/src/maasserver/zeroconfservice.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maasserver/zeroconfservice.py 2012-04-19 04:11:04.000000000 +0000 @@ -7,6 +7,7 @@ """Work with Zeroconf.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maastesting/factory.py maas-0.1+bzr482+dfsg/src/maastesting/factory.py --- maas-0.1+bzr462+dfsg/src/maastesting/factory.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maastesting/factory.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test object factories.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maastesting/management/commands/reconcile.py maas-0.1+bzr482+dfsg/src/maastesting/management/commands/reconcile.py --- maas-0.1+bzr462+dfsg/src/maastesting/management/commands/reconcile.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maastesting/management/commands/reconcile.py 2012-04-19 04:11:04.000000000 +0000 @@ -18,6 +18,7 @@ """ from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maastesting/management/commands/tests/test_reconcile.py maas-0.1+bzr482+dfsg/src/maastesting/management/commands/tests/test_reconcile.py --- maas-0.1+bzr462+dfsg/src/maastesting/management/commands/tests/test_reconcile.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maastesting/management/commands/tests/test_reconcile.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for `maastesting.management.commands.reconcile`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -11,10 +12,10 @@ __metaclass__ = type __all__ = [] -from maastesting.testcase import TestCase from maastesting.management.commands.reconcile import ( guess_architecture_from_profile, ) +from maastesting.testcase import TestCase class TestFunctions(TestCase): diff -Nru maas-0.1+bzr462+dfsg/src/maastesting/rabbit.py maas-0.1+bzr482+dfsg/src/maastesting/rabbit.py --- maas-0.1+bzr462+dfsg/src/maastesting/rabbit.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maastesting/rabbit.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Helpers for testing with RabbitMQ.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maastesting/runner.py maas-0.1+bzr482+dfsg/src/maastesting/runner.py --- maas-0.1+bzr462+dfsg/src/maastesting/runner.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maastesting/runner.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test runner for maas and its applications.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maastesting/testcase.py maas-0.1+bzr482+dfsg/src/maastesting/testcase.py --- maas-0.1+bzr462+dfsg/src/maastesting/testcase.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maastesting/testcase.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test related classes and functions for maas and its applications.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maastesting/tests/__init__.py maas-0.1+bzr482+dfsg/src/maastesting/tests/__init__.py --- maas-0.1+bzr462+dfsg/src/maastesting/tests/__init__.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maastesting/tests/__init__.py 2012-04-19 04:11:04.000000000 +0000 @@ -2,6 +2,7 @@ # GNU Affero General Public License version 3 (see the file LICENSE). from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maastesting/tests/test_factory.py maas-0.1+bzr482+dfsg/src/maastesting/tests/test_factory.py --- maas-0.1+bzr462+dfsg/src/maastesting/tests/test_factory.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maastesting/tests/test_factory.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test the factory where appropriate. Don't overdo this.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/maastesting/tests/test_rabbit.py maas-0.1+bzr482+dfsg/src/maastesting/tests/test_rabbit.py --- maas-0.1+bzr462+dfsg/src/maastesting/tests/test_rabbit.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/maastesting/tests/test_rabbit.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for `maastesting.rabbit`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/address.py maas-0.1+bzr482+dfsg/src/metadataserver/address.py --- maas-0.1+bzr462+dfsg/src/metadataserver/address.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/address.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Figure out server address for the maas_url setting.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/api.py maas-0.1+bzr482+dfsg/src/metadataserver/api.py --- maas-0.1+bzr462+dfsg/src/metadataserver/api.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/api.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Metadata API.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -176,6 +177,9 @@ return rc.ALL_OK node.status = target_status + # When moving to a terminal state, remove the allocation. + if target_status is not None: + node.owner = None node.error = request.POST.get('error', '') node.save() diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/fields.py maas-0.1+bzr482+dfsg/src/metadataserver/fields.py --- maas-0.1+bzr462+dfsg/src/metadataserver/fields.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/fields.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Custom field types for the metadata server.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/middleware.py maas-0.1+bzr482+dfsg/src/metadataserver/middleware.py --- maas-0.1+bzr462+dfsg/src/metadataserver/middleware.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/middleware.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Django "middlewares" for the metadata API.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/migrations/0001_initial.py maas-0.1+bzr482+dfsg/src/metadataserver/migrations/0001_initial.py --- maas-0.1+bzr462+dfsg/src/metadataserver/migrations/0001_initial.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/migrations/0001_initial.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Initial metadataserver migration.""" from __future__ import ( + absolute_import, print_function, # This breaks South. #unicode_literals, diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/models.py maas-0.1+bzr482+dfsg/src/metadataserver/models.py --- maas-0.1+bzr462+dfsg/src/metadataserver/models.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/models.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Model for the metadata server.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/nodeinituser.py maas-0.1+bzr482+dfsg/src/metadataserver/nodeinituser.py --- maas-0.1+bzr462+dfsg/src/metadataserver/nodeinituser.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/nodeinituser.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """User management for nodes' access to the metadata service.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/tests/__init__.py maas-0.1+bzr482+dfsg/src/metadataserver/tests/__init__.py --- maas-0.1+bzr462+dfsg/src/metadataserver/tests/__init__.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/tests/__init__.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for `metadataserver`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/tests/models.py maas-0.1+bzr482+dfsg/src/metadataserver/tests/models.py --- maas-0.1+bzr462+dfsg/src/metadataserver/tests/models.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/tests/models.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test model for testing BinaryField.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/tests/test_address.py maas-0.1+bzr482+dfsg/src/metadataserver/tests/test_address.py --- maas-0.1+bzr462+dfsg/src/metadataserver/tests/test_address.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/tests/test_address.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test server-address-guessing logic.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/tests/test_api.py maas-0.1+bzr482+dfsg/src/metadataserver/tests/test_api.py --- maas-0.1+bzr462+dfsg/src/metadataserver/tests/test_api.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/tests/test_api.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for the metadata API.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -313,6 +314,16 @@ self.assertEqual( NODE_STATUS.COMMISSIONING, reload_object(node).status) + def test_signaling_WORKING_keeps_owner(self): + user = factory.make_user() + node = factory.make_node(status=NODE_STATUS.COMMISSIONING) + node.owner = user + node.save() + client = self.make_node_client(node=node) + response = self.call_signal(client, status='WORKING') + self.assertEqual(httplib.OK, response.status_code) + self.assertEqual(user, reload_object(node).owner) + def test_signaling_commissioning_success_makes_node_Ready(self): node = factory.make_node(status=NODE_STATUS.COMMISSIONING) client = self.make_node_client(node=node) @@ -343,6 +354,15 @@ self.assertEqual(httplib.OK, response.status_code) self.assertEqual(NODE_STATUS.READY, reload_object(node).status) + def test_signaling_commissioning_success_clears_owner(self): + node = factory.make_node(status=NODE_STATUS.COMMISSIONING) + node.owner = factory.make_user() + node.save() + client = self.make_node_client(node=node) + response = self.call_signal(client, status='OK') + self.assertEqual(httplib.OK, response.status_code) + self.assertEqual(None, reload_object(node).owner) + def test_signaling_commissioning_failure_makes_node_Failed_Tests(self): node = factory.make_node(status=NODE_STATUS.COMMISSIONING) client = self.make_node_client(node=node) @@ -366,6 +386,15 @@ self.assertEqual(httplib.OK, response.status_code) self.assertEqual(error_text, reload_object(node).error) + def test_signaling_commissioning_failure_clears_owner(self): + node = factory.make_node(status=NODE_STATUS.COMMISSIONING) + node.owner = factory.make_user() + node.save() + client = self.make_node_client(node=node) + response = self.call_signal(client, status='FAILED') + self.assertEqual(httplib.OK, response.status_code) + self.assertEqual(None, reload_object(node).owner) + def test_signaling_no_error_clears_existing_error(self): node = factory.make_node( status=NODE_STATUS.COMMISSIONING, error=factory.getRandomString()) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/tests/test_fields.py maas-0.1+bzr482+dfsg/src/metadataserver/tests/test_fields.py --- maas-0.1+bzr462+dfsg/src/metadataserver/tests/test_fields.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/tests/test_fields.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Test custom field types.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/tests/test_models.py maas-0.1+bzr482+dfsg/src/metadataserver/tests/test_models.py --- maas-0.1+bzr462+dfsg/src/metadataserver/tests/test_models.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/tests/test_models.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Model tests for metadata server.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/tests/test_nodeinituser.py maas-0.1+bzr482+dfsg/src/metadataserver/tests/test_nodeinituser.py --- maas-0.1+bzr462+dfsg/src/metadataserver/tests/test_nodeinituser.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/tests/test_nodeinituser.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Model tests for metadata server.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/metadataserver/urls.py maas-0.1+bzr482+dfsg/src/metadataserver/urls.py --- maas-0.1+bzr462+dfsg/src/metadataserver/urls.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/metadataserver/urls.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Metadata API URLs.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/amqpclient.py maas-0.1+bzr482+dfsg/src/provisioningserver/amqpclient.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/amqpclient.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/amqpclient.py 2012-04-19 04:11:04.000000000 +0000 @@ -8,6 +8,7 @@ """ from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/api.py maas-0.1+bzr482+dfsg/src/provisioningserver/api.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/api.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/api.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Provisioning API for external use.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -170,6 +171,11 @@ super(ProvisioningAPI, self).__init__() self.session = session + def sync(self): + """Request Cobbler to sync and return when it's finished.""" + return self.session.call( + "sync", self.session.token_placeholder) + @inlineCallbacks def add_distro(self, name, initrd, kernel): assert isinstance(name, basestring) @@ -180,6 +186,7 @@ "initrd": initrd, "kernel": kernel, }) + yield self.sync() returnValue(distro.name) @inlineCallbacks @@ -188,6 +195,7 @@ assert isinstance(distro, basestring) profile = yield CobblerProfile.new( self.session, name, {"distro": distro}) + yield self.sync() returnValue(profile.name) @inlineCallbacks @@ -204,17 +212,20 @@ "power_type": power_type, } system = yield CobblerSystem.new(self.session, name, attributes) + yield self.sync() returnValue(system.name) @inlineCallbacks def modify_distros(self, deltas): for name, delta in deltas.items(): yield CobblerDistro(self.session, name).modify(delta) + yield self.sync() @inlineCallbacks def modify_profiles(self, deltas): for name, delta in deltas.items(): yield CobblerProfile(self.session, name).modify(delta) + yield self.sync() @inlineCallbacks def modify_nodes(self, deltas): @@ -231,6 +242,7 @@ for interface_modification in interface_modifications: yield system.modify(interface_modification) yield system.modify(delta) + yield self.sync() @inlineCallbacks def get_objects_by_name(self, object_type, names): @@ -278,6 +290,7 @@ assert all(isinstance(name, basestring) for name in names) for name in names: yield object_type(self.session, name).delete() + yield self.sync() @deferred def delete_distros_by_name(self, names): diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/cobblercatcher.py maas-0.1+bzr482+dfsg/src/provisioningserver/cobblercatcher.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/cobblercatcher.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/cobblercatcher.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Helping hands for dealing with Cobbler exceptions.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/cobblerclient.py maas-0.1+bzr482+dfsg/src/provisioningserver/cobblerclient.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/cobblerclient.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/cobblerclient.py 2012-04-19 04:11:04.000000000 +0000 @@ -2,6 +2,7 @@ # GNU Affero General Public License version 3 (see the file LICENSE). from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/enum.py maas-0.1+bzr482+dfsg/src/provisioningserver/enum.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/enum.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/enum.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Enumerations meaningful to the provisioning server.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/interfaces.py maas-0.1+bzr482+dfsg/src/provisioningserver/interfaces.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/interfaces.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/interfaces.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Provisioning API interfaces.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/plugin.py maas-0.1+bzr482+dfsg/src/provisioningserver/plugin.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/plugin.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/plugin.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Twisted Application Plugin code for the MAAS provisioning server""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -13,7 +14,6 @@ from getpass import getuser -from amqpclient import AMQFactory from formencode import Schema from formencode.validators import ( Int, @@ -21,6 +21,7 @@ String, URL, ) +from provisioningserver.amqpclient import AMQFactory from provisioningserver.cobblerclient import CobblerSession from provisioningserver.remote import ProvisioningAPI_XMLRPC from provisioningserver.services import ( diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/remote.py maas-0.1+bzr482+dfsg/src/provisioningserver/remote.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/remote.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/remote.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Provisioning API over XML-RPC.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/services.py maas-0.1+bzr482+dfsg/src/provisioningserver/services.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/services.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/services.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Additional services that compose the MAAS Provisioning Server.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/testing/amqpclient.py maas-0.1+bzr482+dfsg/src/provisioningserver/testing/amqpclient.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/testing/amqpclient.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/testing/amqpclient.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for `provisioningserver.amqpclient`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/testing/factory.py maas-0.1+bzr482+dfsg/src/provisioningserver/testing/factory.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/testing/factory.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/testing/factory.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Provisioning test-objects factory.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/testing/fakeapi.py maas-0.1+bzr482+dfsg/src/provisioningserver/testing/fakeapi.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/testing/fakeapi.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/testing/fakeapi.py 2012-04-19 04:11:04.000000000 +0000 @@ -12,6 +12,7 @@ """ from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/testing/fakecobbler.py maas-0.1+bzr482+dfsg/src/provisioningserver/testing/fakecobbler.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/testing/fakecobbler.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/testing/fakecobbler.py 2012-04-19 04:11:04.000000000 +0000 @@ -2,6 +2,7 @@ # GNU Affero General Public License version 3 (see the file LICENSE). from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/testing/realcobbler.py maas-0.1+bzr482+dfsg/src/provisioningserver/testing/realcobbler.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/testing/realcobbler.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/testing/realcobbler.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Set up test sessions against real Cobblers.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -16,6 +17,7 @@ from os import environ from textwrap import dedent from urlparse import urlparse +from unittest import skipIf from provisioningserver.cobblerclient import CobblerSession @@ -33,11 +35,17 @@ env_var = 'PSERV_TEST_COBBLER_URL' - help_text = dedent(""" + help_text_available = dedent("""\ Set %s to the URL for a Cobbler instance to test against, + e.g. http://username:password@example.com/cobbler_api. + WARNING: this will modify your Cobbler database. + """ % env_var) + + help_text_local = dedent("""\ + Set %s to the URL for a *local* Cobbler instance to test against, e.g. http://username:password@localhost/cobbler_api. WARNING: this will modify your Cobbler database. - """.lstrip('\n') % env_var) + """ % env_var) def __init__(self): self.url = environ.get(self.env_var) @@ -46,23 +54,52 @@ self.username = urlparts.username or 'cobbler' self.password = urlparts.password or '' + @property def is_available(self): - """Is a real Cobbler available for tests? + """Is a real Cobbler available for tests?""" + return self.url is not None - Use this to disable real-Cobbler tests if no real Cobbler is - available: annotate them with + @property + def skip_unless_available(self): + """Decorator to disable tests if no real Cobbler is available. + + Annotate tests like so:: + + @real_cobbler.skip_unless_available + def test_something_that_requires_a_real_cobbler(self): + ... - @testtools.skipIf( - not real_cobbler.is_available(), RealCobbler.help_text) """ - return self.url is not None + return skipIf(not self.is_available, self.help_text_available) + + @property + def is_local(self): + """Is a real Cobbler installed locally available for tests?""" + if self.is_available: + hostname = urlparse(self.url).hostname + return hostname == "localhost" or hostname.startswith("127.") + else: + return False + + @property + def skip_unless_local(self): + """Decorator to disable tests if no real *local* Cobbler is available. + + Annotate tests like so:: + + @real_cobbler.skip_unless_local + def test_something_that_requires_a_real_local_cobbler(self): + ... + + """ + return skipIf(not self.is_local, self.help_text_local) def get_session(self): """Obtain a session on the real Cobbler. Returns None if no real Cobbler is available. """ - if self.is_available(): + if self.is_available: return CobblerSession(self.url, self.username, self.password) else: return None diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/tests/__init__.py maas-0.1+bzr482+dfsg/src/provisioningserver/tests/__init__.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/tests/__init__.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/tests/__init__.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for `provisioningserver`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_amqaclient.py maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_amqaclient.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_amqaclient.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_amqaclient.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for C{AMQFactory}.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_api.py maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_api.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_api.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_api.py 2012-04-19 04:11:04.000000000 +0000 @@ -7,6 +7,7 @@ """ from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -19,7 +20,7 @@ abstractmethod, ) from base64 import b64decode -from unittest import skipIf +from contextlib import contextmanager from maasserver.testing.enum import map_enum from maastesting.factory import factory @@ -40,6 +41,11 @@ from provisioningserver.testing.realcobbler import RealCobbler from testtools import TestCase from testtools.deferredruntest import AsynchronousDeferredRunTest +from testtools.matchers import ( + FileExists, + Not, + ) +from testtools.monkey import patch from twisted.internet.defer import inlineCallbacks from zope.interface.verify import verifyObject @@ -607,6 +613,84 @@ self.assertEqual( preseed_data, b64decode(attrs['ks_meta']['MAAS_PRESEED'])) + @contextmanager + def expected_sync(self, papi, times=1): + """Context where # calls to `papi.sync` must match `times`.""" + sync_calls = [] + orig_sync = papi.sync + fake_sync = lambda: orig_sync().addCallback(sync_calls.append) + unpatch = patch(papi, "sync", fake_sync) + try: + yield + finally: + unpatch() + self.assertEqual(times, len(sync_calls)) + + @inlineCallbacks + def test_add_distro_syncs(self): + # add_distro ensures that Cobbler syncs. + papi = self.get_provisioning_api() + with self.expected_sync(papi): + yield self.add_distro(papi) + + @inlineCallbacks + def test_add_profile_syncs(self): + # add_profile ensures that Cobbler syncs. + papi = self.get_provisioning_api() + distro_name = yield self.add_distro(papi) + with self.expected_sync(papi): + yield self.add_profile(papi, distro_name=distro_name) + + @inlineCallbacks + def test_add_node_syncs(self): + # add_node ensures that Cobbler syncs. + papi = self.get_provisioning_api() + profile_name = yield self.add_profile(papi) + with self.expected_sync(papi): + yield self.add_node(papi, profile_name=profile_name) + + @inlineCallbacks + def test_modify_distros_syncs(self): + # modify_distros ensures that Cobbler syncs. + papi = self.get_provisioning_api() + with self.expected_sync(papi): + yield papi.modify_distros({}) + + @inlineCallbacks + def test_modify_profiles_syncs(self): + # modify_profiles ensures that Cobbler syncs. + papi = self.get_provisioning_api() + with self.expected_sync(papi): + yield papi.modify_profiles({}) + + @inlineCallbacks + def test_modify_nodes_syncs(self): + # modify_nodes ensures that Cobbler syncs. + papi = self.get_provisioning_api() + with self.expected_sync(papi): + yield papi.modify_nodes({}) + + @inlineCallbacks + def test_delete_distros_by_name_syncs(self): + # delete_distros_by_name ensures that Cobbler syncs. + papi = self.get_provisioning_api() + with self.expected_sync(papi): + yield papi.delete_distros_by_name([]) + + @inlineCallbacks + def test_delete_profiles_by_name_syncs(self): + # delete_profiles_by_name ensures that Cobbler syncs. + papi = self.get_provisioning_api() + with self.expected_sync(papi): + yield papi.delete_profiles_by_name([]) + + @inlineCallbacks + def test_delete_nodes_by_name_syncs(self): + # delete_nodes_by_name ensures that Cobbler syncs. + papi = self.get_provisioning_api() + with self.expected_sync(papi): + yield papi.delete_nodes_by_name([]) + class TestFakeProvisioningAPI(ProvisioningAPITests, TestCase): """Test :class:`FakeAsynchronousProvisioningAPI`. @@ -631,6 +715,22 @@ """Return a real ProvisioningAPI, but using a fake Cobbler session.""" return ProvisioningAPI(make_fake_cobbler_session()) + def test_sync(self): + """`ProvisioningAPI.sync` issues an authenticated ``sync`` call. + + It is not exported - i.e. it is not part of :class:`IProvisioningAPI` + - but is used heavily by other methods in `IProvisioningAPI`. + """ + papi = self.get_provisioning_api() + calls = [] + self.patch( + papi.session, "call", + lambda *args: calls.append(args)) + papi.sync() + self.assertEqual( + [("sync", papi.session.token_placeholder)], + calls) + class TestProvisioningAPIWithRealCobbler(ProvisioningAPITests, ProvisioningAPITestsWithCobbler, @@ -640,12 +740,34 @@ The URL for the Cobbler instance must be provided in the `PSERV_TEST_COBBLER_URL` environment variable. - Includes by inheritance all the tests in :class:`ProvisioningAPITests`. + Includes by inheritance all the tests in :class:`ProvisioningAPITests` and + :class:`ProvisioningAPITestsWithCobbler`. """ real_cobbler = RealCobbler() - @skipIf(not real_cobbler.is_available(), RealCobbler.help_text) + @real_cobbler.skip_unless_available def get_provisioning_api(self): """Return a connected :class:`ProvisioningAPI`.""" return ProvisioningAPI(self.real_cobbler.get_session()) + + @real_cobbler.skip_unless_local + @inlineCallbacks + def test_sync_after_modify(self): + # When MAAS modifies the MAC addresses of a node it triggers a sync of + # Cobbler. This is to ensure that netboot files are up-to-date, or + # removed as appropriate. + papi = self.get_provisioning_api() + node_name = yield self.add_node(papi) + mac_address = factory.getRandomMACAddress() + yield papi.modify_nodes( + {node_name: {"mac_addresses": [mac_address]}}) + # The PXE file corresponding to the node's MAC address is present. + pxe_filename = "/var/lib/tftpboot/pxelinux.cfg/01-%s" % ( + mac_address.replace(":", "-"),) + self.assertThat(pxe_filename, FileExists()) + # Remove the MAC address again. + yield papi.modify_nodes( + {node_name: {"mac_addresses": []}}) + # The PXE file has been removed too. + self.assertThat(pxe_filename, Not(FileExists())) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_cobblercatcher.py maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_cobblercatcher.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_cobblercatcher.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_cobblercatcher.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for conversion of Cobbler exceptions to `ProvisioningError`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) @@ -15,7 +16,6 @@ ABCMeta, abstractmethod, ) -from unittest import skipIf from xmlrpclib import Fault from maastesting.factory import factory @@ -180,7 +180,7 @@ real_cobbler = RealCobbler() - @skipIf(not real_cobbler.is_available(), RealCobbler.help_text) + @real_cobbler.skip_unless_available @deferred def get_cobbler_session(self): return self.real_cobbler.get_session() diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_cobblerclient.py maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_cobblerclient.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_cobblerclient.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_cobblerclient.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for `provisioningserver.cobblerclient`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_cobblersession.py maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_cobblersession.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_cobblersession.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_cobblersession.py 2012-04-19 04:11:04.000000000 +0000 @@ -2,6 +2,7 @@ # GNU Affero General Public License version 3 (see the file LICENSE). from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_fakecobbler.py maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_fakecobbler.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_fakecobbler.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_fakecobbler.py 2012-04-19 04:11:04.000000000 +0000 @@ -2,6 +2,7 @@ # GNU Affero General Public License version 3 (see the file LICENSE). from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_plugin.py maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_plugin.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_plugin.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_plugin.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for the psmaas TAP.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_remote.py maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_remote.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_remote.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_remote.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for `provisioningserver.remote`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_services.py maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_services.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/tests/test_services.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/tests/test_services.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Tests for `provisioningserver.services`.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/src/provisioningserver/utils.py maas-0.1+bzr482+dfsg/src/provisioningserver/utils.py --- maas-0.1+bzr462+dfsg/src/provisioningserver/utils.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/src/provisioningserver/utils.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Utilities for the provisioning server.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/templates/module.py maas-0.1+bzr482+dfsg/templates/module.py --- maas-0.1+bzr462+dfsg/templates/module.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/templates/module.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """...""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/templates/script.py maas-0.1+bzr482+dfsg/templates/script.py --- maas-0.1+bzr462+dfsg/templates/script.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/templates/script.py 2012-04-19 04:11:04.000000000 +0000 @@ -5,6 +5,7 @@ """...""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/templates/test_module.py maas-0.1+bzr482+dfsg/templates/test_module.py --- maas-0.1+bzr462+dfsg/templates/test_module.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/templates/test_module.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """...""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/twisted/plugins/maasps.py maas-0.1+bzr482+dfsg/twisted/plugins/maasps.py --- maas-0.1+bzr462+dfsg/twisted/plugins/maasps.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/twisted/plugins/maasps.py 2012-04-19 04:11:04.000000000 +0000 @@ -4,6 +4,7 @@ """Twisted Application Plugin for the MAAS provisioning server.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/utilities/rewrite-future-imports maas-0.1+bzr482+dfsg/utilities/rewrite-future-imports --- maas-0.1+bzr462+dfsg/utilities/rewrite-future-imports 1970-01-01 00:00:00.000000000 +0000 +++ maas-0.1+bzr482+dfsg/utilities/rewrite-future-imports 2012-04-19 04:11:04.000000000 +0000 @@ -0,0 +1,42 @@ +#!/usr/bin/env python2.7 +# Copyright 2012 Canonical Ltd. This software is licensed under the +# GNU Affero General Public License version 3 (see the file LICENSE). + +"""Ensure that __future__ import lines are populated correctly.""" + +from __future__ import ( + absolute_import, + print_function, + unicode_literals, + ) + +__metaclass__ = type + +import re +import sys + + +re_futures = re.compile( + r"^(from __future__ import) [(](.*?)[)]", re.DOTALL | re.MULTILINE) + + +mandatory_future_imports = frozenset( + ("absolute_import", "print_function", "unicode_literals")) + + +def replace(match): + imports = set().union( + (name.strip() for name in match.group(2).split(",")), + mandatory_future_imports) + imports.discard("") + imports = "".join(" %s,\n" % name for name in sorted(imports)) + return "%s (\n%s )" % (match.group(1), imports) + + +if __name__ == '__main__': + for filename in sys.argv[1:]: + with open(filename, "rb") as fd: + source = fd.read() + source = re_futures.sub(replace, source) + with open(filename, "wb") as fd: + fd.write(source) diff -Nru maas-0.1+bzr462+dfsg/vdenv/api-list.py maas-0.1+bzr482+dfsg/vdenv/api-list.py --- maas-0.1+bzr462+dfsg/vdenv/api-list.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/vdenv/api-list.py 2012-04-19 04:11:04.000000000 +0000 @@ -5,6 +5,7 @@ """Print information from the Cobbler server.""" from __future__ import ( + absolute_import, print_function, unicode_literals, ) diff -Nru maas-0.1+bzr462+dfsg/vdenv/setup.py maas-0.1+bzr482+dfsg/vdenv/setup.py --- maas-0.1+bzr462+dfsg/vdenv/setup.py 2012-04-12 20:11:10.000000000 +0000 +++ maas-0.1+bzr482+dfsg/vdenv/setup.py 2012-04-19 04:11:04.000000000 +0000 @@ -5,6 +5,7 @@ """Setup a Virtual Data-center Environment.""" from __future__ import ( + absolute_import, print_function, unicode_literals, )