From dfa5ce93d5f7caa49950d4565bab12fe2dc56ffb Mon Sep 17 00:00:00 2001 From: Lingxian Kong Date: Wed, 12 Jun 2019 20:35:29 +1200 Subject: [PATCH] Improve devmode=flase when building the image During debugging, the following changes are also included: - Support to specify an image ID to run the integration test. - Fix the reboot function bug. - Remove the unsuccessful restart test. How to run integration test with dev_mode=false: ADMIN_PASSWORD=password \ SERVICE_PASSWORD=password \ DEV_MODE=false \ /opt/stack/trove/integration/scripts/trovestack gate-tests mysql mysql Change-Id: I31d4ee579a554f4c98f9facb9fd4b7779665a3dd --- devstack/plugin.sh | 2 +- doc/source/admin/building_guest_images.rst | 26 +- doc/source/admin/guest_cloud_init.rst | 74 ----- doc/source/admin/index.rst | 1 - doc/source/admin/trovestack.rst | 21 +- .../files/elements/guest-agent/README.rst | 4 +- .../guest-agent/extra-data.d/11-ssh-key | 28 -- .../elements/guest-agent/install.d/50-user | 22 ++ .../75-guest-agent-install | 33 ++- .../guest-agent.service | 5 +- .../guest-agent/package-installs.yaml | 12 +- .../files/elements/guest-agent/pkg-map | 2 +- .../guest-agent/post-install.d/12-ssh-key | 39 --- .../guest-agent/post-install.d/99-clean-apt | 2 - .../files/elements/no-resolvconf/README.rst | 7 +- .../pre-install.d/20-apparmor-mysql-local | 1 + integration/scripts/functions_qemu | 8 +- integration/scripts/trovestack | 35 ++- integration/scripts/trovestack.rc | 3 + integration/tests/integration/int_tests.py | 1 - .../integration/tests/api/instances_quotas.py | 47 ---- run_tests.py | 1 - trove/common/notification.py | 9 + trove/extensions/mgmt/instances/service.py | 9 +- trove/instance/service.py | 6 +- trove/taskmanager/models.py | 28 +- trove/tests/api/backups.py | 4 +- trove/tests/api/flavors.py | 265 ------------------ trove/tests/api/instances.py | 138 ++------- trove/tests/api/instances_actions.py | 162 ++--------- trove/tests/api/instances_mysql_down.py | 1 + trove/tests/int_tests.py | 4 - .../runners/instance_create_runners.py | 4 +- trove/tests/scenario/runners/test_runners.py | 4 +- trove/tests/unittests/mgmt/test_datastores.py | 2 +- .../unittests/taskmanager/test_models.py | 10 +- 36 files changed, 204 insertions(+), 816 deletions(-) delete mode 100644 doc/source/admin/guest_cloud_init.rst delete mode 100755 integration/scripts/files/elements/guest-agent/extra-data.d/11-ssh-key create mode 100755 integration/scripts/files/elements/guest-agent/install.d/50-user delete mode 100755 integration/scripts/files/elements/guest-agent/post-install.d/12-ssh-key delete mode 100644 integration/tests/integration/tests/api/instances_quotas.py delete mode 100644 trove/tests/api/flavors.py diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 09c2121eed..a54d948068 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -404,7 +404,7 @@ function create_mgmt_subnet_v4 { local name=$3 local ip_range=$4 - subnet_id=$(openstack subnet create --project ${project_id} --ip-version 4 --subnet-range ${ip_range} --gateway none --network ${net_id} $name -c id -f value) + subnet_id=$(openstack subnet create --project ${project_id} --ip-version 4 --subnet-range ${ip_range} --gateway none --dns-nameserver 8.8.8.8 --network ${net_id} $name -c id -f value) die_if_not_set $LINENO subnet_id "Failed to create private IPv4 subnet for network: ${net_id}, project: ${project_id}" echo $subnet_id } diff --git a/doc/source/admin/building_guest_images.rst b/doc/source/admin/building_guest_images.rst index 389325ccbe..5b01747013 100644 --- a/doc/source/admin/building_guest_images.rst +++ b/doc/source/admin/building_guest_images.rst @@ -55,9 +55,9 @@ Operating System and Database A Trove Guest Instance contains at least a functioning Operating System and the database software that the instance wishes to provide (as a Service). For example, if your chosen operating system is Ubuntu -and you wish to deliver MySQL version 5.5, then your guest instance is +and you wish to deliver MySQL version 5.7, then your guest instance is a Nova instance running the Ubuntu operating system and will have -MySQL version 5.5 installed on it. +MySQL version 5.7 installed on it. ----------------- Trove Guest Agent @@ -77,7 +77,7 @@ Guest Agent API is the common API used by Trove to communicate with any guest database, and the Guest Agent is the implementation of that API for the specific database. -The Trove Guest Agent runs on the Trove Guest Instance. +The Trove Guest Agent runs inside the Trove Guest Instance. ------------------------------------------ Injected Configuration for the Guest Agent @@ -87,11 +87,11 @@ When TaskManager launches the guest VM it injects the specific settings for the guest into the VM, into the file /etc/trove/conf.d/guest_info.conf. The file is injected one of three ways. -If use_nova_server_config_drive=True, it is injected via ConfigDrive. Otherwise -it is passed to the nova create call as the 'files' parameter and will be -injected based on the configuration of Nova; the Nova default is to discard the -files. If the settings in guest_info.conf are not present on the guest -Guest Agent will fail to start up. +If ``use_nova_server_config_drive=True``, it is injected via ConfigDrive. +Otherwise it is passed to the nova create call as the 'files' parameter and +will be injected based on the configuration of Nova; the Nova default is to +discard the files. If the settings in guest_info.conf are not present on the +guest Guest Agent will fail to start up. ------------------------------ Persistent Storage, Networking @@ -99,9 +99,13 @@ Persistent Storage, Networking The database stores data on persistent storage on Cinder (if configured, see trove.conf and the volume_support parameter) or -ephemeral storage on the Nova instance. The database is accessible -over the network and the Guest Instance is configured for network -access by client applications. +ephemeral storage on the Nova instance. The database service is accessible +over the tenant network provided when creating the database instance. + +The cloud administrator is able to config a management +networks(``CONF.management_networks``) that is invisible to the cloud tenants, +database instance can talk to the control plane services(e.g. the message +queue) via that network. Building Guest Images using DIB =============================== diff --git a/doc/source/admin/guest_cloud_init.rst b/doc/source/admin/guest_cloud_init.rst deleted file mode 100644 index 183743fe28..0000000000 --- a/doc/source/admin/guest_cloud_init.rst +++ /dev/null @@ -1,74 +0,0 @@ -.. _guest_cloud_init: - -.. role:: bash(code) - :language: bash - -=========================== -Guest Images via Cloud-Init -=========================== - -.. If section numbers are desired, unindent this - .. sectnum:: - -.. If a TOC is desired, unindent this - .. contents:: - -Overview -======== - -While creating an image is the preferred method for providing a base -for the Guest Instance, there may be cases where creating an image -is impractical. In those cases a Guest instance can be based on -an available Cloud Image and configured at boot via cloud-init. - -Currently the most tested Guest image is Ubunutu 14.04 (trusty). - -Setting up the Image -==================== - -* Visit the `Ubuntu Cloud Archive `_ and download ``ubuntu-14.04-server-cloudimg-amd64-disk1.img``. - -* Upload that image to glance, and note the glance ID for the image. - -* Cloud-Init files go into the directory set by the ``cloudinit_location`` - configuration parameter, usually ``/etc/trove/cloudinit``. Files in - that directory are of the format ``[datastore].cloudinit``, for - example ``mysql.cloudinit``. - -* Create a cloud-init file for your datastore and put it into place. - For this example, it is assumed you are using Ubuntu 16.04, with - the MySQL database and a Trove Agent from the Pike release. You - would put this into ``/etc/trove/cloudinit/mysql.cloudinit``. - -.. code-block:: console - - #cloud-config - # For Ubuntu-16.04 cloudimage - apt_sources: - - source: "cloud-archive:pike" - packages: - - trove-guestagent - - mysql-server-5.7 - write_files: - - path: /etc/sudoers.d/trove - content: | - Defaults:trove !requiretty - trove ALL=(ALL) NOPASSWD:ALL - runcmd: - - stop trove-guestagent - - cat /etc/trove/trove-guestagent.conf /etc/trove/conf.d/guest_info.conf >/etc/trove/trove.conf - - start trove-guestagent - - -* If you need to debug guests failing to launch simply append - the cloud-init to add a user to allow you to login and - debug the instance. - -* When using ``trove-manage datastore_version_update`` to - define your datastore simply use the Glance ID you have for - the Trusty Cloud image. - -When trove launches the Guest Instance, the cloud-init will install -the Pike Trove Guest Agent and MySQL database, and then adjust -the configuration files and launch the Guest Agent. - diff --git a/doc/source/admin/index.rst b/doc/source/admin/index.rst index 0c3be72366..14054207e2 100644 --- a/doc/source/admin/index.rst +++ b/doc/source/admin/index.rst @@ -8,6 +8,5 @@ basics building_guest_images database_module_usage - guest_cloud_init secure_oslo_messaging trovestack diff --git a/doc/source/admin/trovestack.rst b/doc/source/admin/trovestack.rst index 3096450daa..824a60e2a1 100644 --- a/doc/source/admin/trovestack.rst +++ b/doc/source/admin/trovestack.rst @@ -34,23 +34,21 @@ The trove guest agent image could be created by running the following command: .. code-block:: console - $ CONTROLLER_IP=10.0.17.132 \ - ./trovestack build-image \ + $ ./trovestack build-image \ ${datastore_type} \ ${guest_os} \ ${guest_os_release} \ ${dev_mode} * Currently, only ``guest_os=ubuntu`` and ``guest_os_release=xenial`` are fully - tested. + tested and supported. -* ``dev_mode=true`` is mainly for testing purpose for trove developers. When - ``dev_mode=true``, ``CONTROLLER_IP`` could be ignored. You need to build the - image on the trove controller service host, because the host and the guest VM - need to ssh into each other without password. In this mode, when the trove - guest agent code is changed, the image doesn't need to be rebuilt which is - convenient for debugging. Trove guest agent will ssh into the host and - download trove code when the service is initialized. +* ``dev_mode=true`` is mainly for testing purpose for trove developers and it's + necessary to build the image on the trove controller host, because the host + and the guest VM need to ssh into each other without password. In this mode, + when the trove guest agent code is changed, the image doesn't need to be + rebuilt which is convenient for debugging. Trove guest agent will ssh into + the host and download trove code during the service initialization. * if ``dev_mode=false``, the trove code for guest agent is injected into the image at the building time. Now ``dev_mode=false`` is still in experimental @@ -62,7 +60,8 @@ The trove guest agent image could be created by running the following command: also need to create a Nova keypair and set ``nova_keypair`` option in Trove config file in order to ssh into the guest agent. -For example, build a MySQL image for Ubuntu Xenial operating system: +For example, in order to build a MySQL image for Ubuntu Xenial operating +system: .. code-block:: console diff --git a/integration/scripts/files/elements/guest-agent/README.rst b/integration/scripts/files/elements/guest-agent/README.rst index e1cc7f0e45..a5ff0e74ec 100644 --- a/integration/scripts/files/elements/guest-agent/README.rst +++ b/integration/scripts/files/elements/guest-agent/README.rst @@ -1,6 +1,4 @@ Element to install an Trove guest agent. -Note: this requires a system base image modified to include OpenStack +Note: this requires a system base image modified to include Trove source code repositories - -the ubuntu-guest element could be removed. diff --git a/integration/scripts/files/elements/guest-agent/extra-data.d/11-ssh-key b/integration/scripts/files/elements/guest-agent/extra-data.d/11-ssh-key deleted file mode 100755 index fceae3f775..0000000000 --- a/integration/scripts/files/elements/guest-agent/extra-data.d/11-ssh-key +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -set -e -set -o xtrace - -# CONTEXT: HOST prior to IMAGE BUILD as SCRIPT USER -# PURPOSE: creates the SSH key on the host if it doesn't exist. Then this copies the keys over to a staging area where -# they will be duplicated in the guest VM. -# This process allows the host to log into the guest but more importantly the guest phones home to get the trove -# source - -source $_LIB/die - -HOST_USERNAME=${HOST_USERNAME:-"ubuntu"} -SSH_DIR=${SSH_DIR:-"/home/${HOST_USERNAME}/.ssh"} - -[ -n "${TMP_HOOKS_PATH}" ] || die "Temp hook path not set" - -# copy files over the "staging" area for the guest image (they'll later be put in the correct location by the guest user -# not these keys should not be overridden otherwise a) you won't be able to ssh in and b) the guest won't be able to -# rsync the files -if [ -f ${SSH_DIR}/id_rsa ]; then - dd if=${SSH_DIR}/authorized_keys of=${TMP_HOOKS_PATH}/ssh-authorized-keys - dd if=${SSH_DIR}/id_rsa of=${TMP_HOOKS_PATH}/ssh-id_rsa - dd if=${SSH_DIR}/id_rsa.pub of=${TMP_HOOKS_PATH}/ssh-id_rsa.pub -else - die "SSH Authorized Keys file must exist along with pub and private key" -fi diff --git a/integration/scripts/files/elements/guest-agent/install.d/50-user b/integration/scripts/files/elements/guest-agent/install.d/50-user new file mode 100755 index 0000000000..8a2b145fe5 --- /dev/null +++ b/integration/scripts/files/elements/guest-agent/install.d/50-user @@ -0,0 +1,22 @@ +#!/bin/bash + +# PURPOSE: Add the guest image user that will own the trove agent source if the +# user does not already exist + +if [ ${DIB_DEBUG_TRACE:-1} -gt 0 ]; then + set -x +fi +set -e +set -o pipefail + +GUEST_USERNAME=${GUEST_USERNAME:-"ubuntu"} + +if ! id -u ${GUEST_USERNAME} >/dev/null 2>&1; then + echo "Adding ${GUEST_USERNAME} user" + useradd -G sudo -m ${GUEST_USERNAME} -s /bin/bash + chown ${GUEST_USERNAME}:${GUEST_USERNAME} /home/${GUEST_USERNAME} + passwd ${GUEST_USERNAME} <<_EOF_ +${GUEST_USERNAME} +${GUEST_USERNAME} +_EOF_ +fi diff --git a/integration/scripts/files/elements/guest-agent/install.d/guest-agent-source-install/75-guest-agent-install b/integration/scripts/files/elements/guest-agent/install.d/guest-agent-source-install/75-guest-agent-install index 377a2006a1..87a11958e2 100755 --- a/integration/scripts/files/elements/guest-agent/install.d/guest-agent-source-install/75-guest-agent-install +++ b/integration/scripts/files/elements/guest-agent/install.d/guest-agent-source-install/75-guest-agent-install @@ -8,28 +8,35 @@ set -o pipefail SCRIPTDIR=$(dirname $0) GUEST_VENV=/opt/guest-agent-venv +GUEST_USERNAME=${GUEST_USERNAME:-"ubuntu"} -# Create a virtual environment to contain the guest agent -${DIB_PYTHON} -m virtualenv $GUEST_VENV -$GUEST_VENV/bin/pip install pip --upgrade -$GUEST_VENV/bin/pip install -U -c /opt/upper-constraints.txt /opt/guest-agent +# Create a virtual environment for guest agent +${DIB_PYTHON} -m virtualenv ${GUEST_VENV} +${GUEST_VENV}/bin/pip install pip --upgrade +${GUEST_VENV}/bin/pip install -U -c /opt/upper-constraints.txt /opt/guest-agent +chown -R ${GUEST_USERNAME}:root ${GUEST_VENV} -# Link the trove-guestagent out to /usr/local/bin where the startup scripts look -ln -s $GUEST_VENV/bin/trove-guestagent /usr/local/bin/guest-agent || true +# Link the trove-guestagent out to /usr/local/bin where the startup scripts look for +ln -s ${GUEST_VENV}/bin/trove-guestagent /usr/local/bin/guest-agent || true -mkdir -p /var/lib/trove /etc/trove/certs /var/log/trove +for folder in "/var/lib/trove" "/etc/trove" "/etc/trove/certs" "/etc/trove/conf.d" "/var/log/trove"; do + mkdir -p ${folder} + chown -R ${GUEST_USERNAME}:root ${folder} +done -install -D -g root -o root -m 0644 ${SCRIPTDIR}/guest-agent.logrotate /etc/logrotate.d/guest-agent +install -D -g root -o ${GUEST_USERNAME} -m 0644 ${SCRIPTDIR}/guest-agent.logrotate /etc/logrotate.d/guest-agent case "$DIB_INIT_SYSTEM" in - upstart) - install -D -g root -o root -m 0644 ${SCRIPTDIR}/guest-agent.conf /etc/init/guest-agent.conf - ;; systemd) - install -D -g root -o root -m 0644 ${SCRIPTDIR}/guest-agent.service /usr/lib/systemd/system/guest-agent.service + mkdir -p /usr/lib/systemd/system + touch /usr/lib/systemd/system/guest-agent.service + sed "s/GUEST_USERNAME/${GUEST_USERNAME}/g" ${SCRIPTDIR}/guest-agent.service > /usr/lib/systemd/system/guest-agent.service + ;; + upstart) + install -D -g root -o ${GUEST_USERNAME} -m 0644 ${SCRIPTDIR}/guest-agent.conf /etc/init/guest-agent.conf ;; sysv) - install -D -g root -o root -m 0644 ${SCRIPTDIR}/guest-agent.init /etc/init.d/guest-agent.init + install -D -g root -o ${GUEST_USERNAME} -m 0644 ${SCRIPTDIR}/guest-agent.init /etc/init.d/guest-agent.init ;; *) echo "Unsupported init system" diff --git a/integration/scripts/files/elements/guest-agent/install.d/guest-agent-source-install/guest-agent.service b/integration/scripts/files/elements/guest-agent/install.d/guest-agent-source-install/guest-agent.service index 047553f42a..788bebc92f 100644 --- a/integration/scripts/files/elements/guest-agent/install.d/guest-agent-source-install/guest-agent.service +++ b/integration/scripts/files/elements/guest-agent/install.d/guest-agent-source-install/guest-agent.service @@ -4,11 +4,12 @@ After=network.target syslog.service Wants=syslog.service [Service] +User=GUEST_USERNAME +Group=GUEST_USERNAME +ExecStartPre=/bin/bash -c "sudo chown -R GUEST_USERNAME:root /etc/trove/conf.d" ExecStart=/usr/local/bin/guest-agent --config-dir=/etc/trove/conf.d KillMode=mixed Restart=always -ExecStartPost=/bin/sh -c "echo $MAINPID > /var/run/guest-agent.pid" -PIDFile=/var/run/guest-agent.pid [Install] WantedBy=multi-user.target diff --git a/integration/scripts/files/elements/guest-agent/package-installs.yaml b/integration/scripts/files/elements/guest-agent/package-installs.yaml index b8baae1ef9..ff8701048b 100644 --- a/integration/scripts/files/elements/guest-agent/package-installs.yaml +++ b/integration/scripts/files/elements/guest-agent/package-installs.yaml @@ -9,15 +9,6 @@ libssl-dev: python-dev: installtype: source -python-sqlalchemy: -python-lxml: -python-eventlet: -python-webob: -python-httplib2: -python-iso8601: -python-pexpect: -python-mysqldb: -python-migrate: acl: acpid: apparmor: @@ -46,13 +37,14 @@ lsof: net-tools: netbase: netcat-openbsd: +network-scripts: open-vm-tools: + arch: i386, amd64 openssh-client: openssh-server: pollinate: psmisc: rsyslog: -screen: socat: tcpdump: ubuntu-cloudimage-keyring: diff --git a/integration/scripts/files/elements/guest-agent/pkg-map b/integration/scripts/files/elements/guest-agent/pkg-map index ad53fb6480..69c07900f8 100644 --- a/integration/scripts/files/elements/guest-agent/pkg-map +++ b/integration/scripts/files/elements/guest-agent/pkg-map @@ -10,7 +10,7 @@ "cloud-guest-utils": "", "apparmor": "", "dmeventd": "", - "isc-dhcp-client": "", + "isc-dhcp-client": "dhcp-client", "uuid-runtime": "", "ubuntu-cloudimage-keyring": "", "vim-tiny": "", diff --git a/integration/scripts/files/elements/guest-agent/post-install.d/12-ssh-key b/integration/scripts/files/elements/guest-agent/post-install.d/12-ssh-key deleted file mode 100755 index f0f64e6f3b..0000000000 --- a/integration/scripts/files/elements/guest-agent/post-install.d/12-ssh-key +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# CONTEXT: GUEST during CONSTRUCTION as ROOT -# PURPOSE: take "staged" ssh keys (see extra-data.d/62-ssh-key) and put them in the GUEST_USERS home directory - -set -e -set -o xtrace - -SSH_DIR="/home/${GUEST_USERNAME}/.ssh" -TMP_HOOKS_DIR="/tmp/in_target.d" - -if ! id -u ${GUEST_USERNAME} >/dev/null 2>&1; then - echo "Adding ${GUEST_USERNAME} user" - useradd -G sudo -m ${GUEST_USERNAME} -s /bin/bash - chown ${GUEST_USERNAME}:${GUEST_USERNAME} /home/${GUEST_USERNAME} - passwd ${GUEST_USERNAME} <<_EOF_ -${GUEST_USERNAME} -${GUEST_USERNAME} -_EOF_ -fi - -if [ -f "${TMP_HOOKS_DIR}/ssh-authorized-keys" ]; then - if [ ! -d ${SSH_DIR} ]; then - # this method worked more reliable in vmware fusion over doing sudo -Hiu ${GUEST_USERNAME} - mkdir ${SSH_DIR} - chown ${GUEST_USERNAME}:${GUEST_USERNAME} ${SSH_DIR} - fi - - sudo -Hiu ${GUEST_USERNAME} dd of=${SSH_DIR}/authorized_keys conv=notrunc if=${TMP_HOOKS_DIR}/ssh-authorized-keys - if [ ! -f "${SSH_DIR}/id_rsa" ]; then - sudo -Hiu ${GUEST_USERNAME} dd of=${SSH_DIR}/id_rsa if=${TMP_HOOKS_DIR}/ssh-id_rsa - # perms have to be right on this file for ssh to work - sudo -Hiu ${GUEST_USERNAME} chmod 600 ${SSH_DIR}/id_rsa - sudo -Hiu ${GUEST_USERNAME} dd of=${SSH_DIR}/id_rsa.pub if=${TMP_HOOKS_DIR}/ssh-id_rsa.pub - fi -else - echo "SSH Keys were not staged by host" - exit -1 -fi diff --git a/integration/scripts/files/elements/guest-agent/post-install.d/99-clean-apt b/integration/scripts/files/elements/guest-agent/post-install.d/99-clean-apt index cc348c5cb9..227c508db1 100755 --- a/integration/scripts/files/elements/guest-agent/post-install.d/99-clean-apt +++ b/integration/scripts/files/elements/guest-agent/post-install.d/99-clean-apt @@ -7,5 +7,3 @@ set -e set -o xtrace apt-get clean - - diff --git a/integration/scripts/files/elements/no-resolvconf/README.rst b/integration/scripts/files/elements/no-resolvconf/README.rst index d08b658e9d..8a3dfc7d4f 100644 --- a/integration/scripts/files/elements/no-resolvconf/README.rst +++ b/integration/scripts/files/elements/no-resolvconf/README.rst @@ -1,11 +1,8 @@ This element clears out /etc/resolv.conf and prevents dhclient from populating it with data from DHCP. This means that DNS resolution will not work from the -amphora. This is OK because all outbound connections from the guest will +guest. This is OK because all outbound connections from the guest will be based using raw IP addresses. In addition we remove dns from the nsswitch.conf hosts setting. -This has the real benefit of speeding up host boot and configutation times. -This is especially helpful when running tempest tests in a devstack environment -where DNS resolution from the guest usually doesn't work anyway. This means -that the guest never waits for DNS timeouts to occur. +This means that the guest never waits for DNS timeouts to occur. diff --git a/integration/scripts/files/elements/ubuntu-mysql/pre-install.d/20-apparmor-mysql-local b/integration/scripts/files/elements/ubuntu-mysql/pre-install.d/20-apparmor-mysql-local index a3e1dc7c7d..90bd85b10c 100755 --- a/integration/scripts/files/elements/ubuntu-mysql/pre-install.d/20-apparmor-mysql-local +++ b/integration/scripts/files/elements/ubuntu-mysql/pre-install.d/20-apparmor-mysql-local @@ -5,6 +5,7 @@ set -e #CONTEXT: chroot on host #PURPOSE: Allows mysqld to create temporary files when restoring backups +mkdir -p /etc/apparmor.d/local/ cat <>/etc/apparmor.d/local/usr.sbin.mysqld /tmp/ rw, /tmp/** rwk, diff --git a/integration/scripts/functions_qemu b/integration/scripts/functions_qemu index 67426bd62f..cf8392480e 100644 --- a/integration/scripts/functions_qemu +++ b/integration/scripts/functions_qemu @@ -21,6 +21,8 @@ function build_vm() { GUEST_CACHEDIR=${GUEST_CACHEDIR:-"$HOME/.cache/image-create"} GUEST_WORKING_DIR=${GUEST_WORKING_DIR:-"$HOME/images"} + export GUEST_USERNAME=${guest_username} + # In dev mode, the trove guest agent needs to download trove code from # trove-taskmanager host during service initialization. if [[ "${dev_mode}" == "true" ]]; then @@ -34,7 +36,6 @@ function build_vm() { export HOST_SCP_USERNAME=$(whoami) export HOST_USERNAME=${HOST_SCP_USERNAME} export SSH_DIR=${SSH_DIR:-"$HOME/.ssh"} - export GUEST_USERNAME=${guest_username} manage_ssh_keys fi @@ -68,7 +69,6 @@ function build_vm() { if [[ "${dev_mode}" == "false" ]]; then elementes="$elementes pip-and-virtualenv" elementes="$elementes pip-cache" - elementes="$elementes no-resolvconf" elementes="$elementes guest-agent" else elementes="$elementes ${guest_os}-guest" @@ -101,7 +101,7 @@ function build_guest_image() { dev_mode=${4:-"true"} guest_username=${5:-"ubuntu"} - exclaim "Building a ${datastore_type} image of trove guest agent for ${guest_os} ${guest_release}." + exclaim "Building a ${datastore_type} image of trove guest agent for ${guest_os} ${guest_release}, dev_mode=${dev_mode}" VALID_SERVICES='mysql percona mariadb redis cassandra couchbase mongodb postgresql couchdb vertica db2 pxc' if ! [[ " $VALID_SERVICES " =~ " $datastore_type " ]]; then @@ -109,7 +109,7 @@ function build_guest_image() { exit 1 fi - image_name=${guest_os}_${datastore_type} + image_name=${guest_os}-${datastore_type} image_folder=$HOME/images mkdir -p $image_folder image_path=${image_folder}/${image_name} diff --git a/integration/scripts/trovestack b/integration/scripts/trovestack index 1ac4d7bf69..f629cab420 100755 --- a/integration/scripts/trovestack +++ b/integration/scripts/trovestack @@ -850,19 +850,27 @@ function cmd_build_and_upload_image() { exit 1 fi - glance_imageid=$(openstack $CLOUD_ADMIN_ARG image list | grep "$datastore_type" | get_field 1) - echo "IMAGEID: $glance_imageid" - if [[ -z $glance_imageid ]]; then - build_guest_image ${datastore_type} ${guest_os} ${guest_release} ${dev_mode} ${guest_username} + image_var="${datastore_type^^}_IMAGE_ID" + glance_imageid=`eval echo '$'"$image_var"` - image_folder=$HOME/images - qcow_image=`find $image_folder -name '*.qcow2'` - image_url="file://$qcow_image" - glance_imageid=`get_glance_id upload_image $image_url` - [[ -z "$glance_imageid" ]] && echo "Glance upload failed!" && exit 1 - echo "IMAGE ID: $glance_imageid" + if [[ -z $glance_imageid ]]; then + # Find the first image id with the name contains datastore_type. + glance_imageid=$(openstack $CLOUD_ADMIN_ARG image list | grep "$datastore_type" | awk 'NR==1 {print}' | awk '{print $2}') + + if [[ -z $glance_imageid ]]; then + build_guest_image ${datastore_type} ${guest_os} ${guest_release} ${dev_mode} ${guest_username} + + image_folder=$HOME/images + qcow_image=`find $image_folder -name '*.qcow2'` + image_url="file://$qcow_image" + glance_imageid=`get_glance_id upload_image $image_url` + [[ -z "$glance_imageid" ]] && echo "Glance upload failed!" && exit 1 + echo "IMAGE ID: $glance_imageid" + fi fi + echo "IMAGEID: $glance_imageid" + exclaim "Updating Datastores" cmd_set_datastore "${glance_imageid}" "${datastore_type}" "${restart_trove}" } @@ -1257,7 +1265,12 @@ function cmd_kick_start() { exclaim "Running kick-start for $DATASTORE_TYPE (restart trove: $RESTART_TROVE)" dump_env cmd_test_init "${DATASTORE_TYPE}" - cmd_build_and_upload_image "${DATASTORE_TYPE}" "${RESTART_TROVE}" + + export GUEST_OS=${GUEST_OS:-"ubuntu"} + export GUEST_OS_RELEASE=${GUEST_OS_RELEASE:-"xenial"} + export GUEST_OS_USERNAME=${GUEST_OS_USERNAME:-"ubuntu"} + export DEV_MOEE=${DEV_MODE:-"true"} + cmd_build_and_upload_image "${DATASTORE_TYPE}" "${RESTART_TROVE}" "${GUEST_OS}" "${GUEST_OS_RELEASE}" "${DEV_MOEE}" "${GUEST_OS_USERNAME}" } function cmd_gate_tests() { diff --git a/integration/scripts/trovestack.rc b/integration/scripts/trovestack.rc index 165c27a500..b97b381398 100644 --- a/integration/scripts/trovestack.rc +++ b/integration/scripts/trovestack.rc @@ -108,3 +108,6 @@ SWIFT_DISK_IMAGE=${SWIFT_DATA_DIR}/drives/images/swift.img #export TROVE_RESIZE_TIME_OUT=3600 #export TROVE_USAGE_TIMEOUT=1500 #export TROVE_STATE_CHANGE_WAIT_TIME=180 + +# Image +MYSQL_IMAGE_ID=${MYSQL_IMAGE_ID:-""} diff --git a/integration/tests/integration/int_tests.py b/integration/tests/integration/int_tests.py index de5417ef73..d988b72324 100644 --- a/integration/tests/integration/int_tests.py +++ b/integration/tests/integration/int_tests.py @@ -125,7 +125,6 @@ def import_tests(): if not ADD_DOMAINS: from tests.api import delete_all from tests.api import instances_pagination - from tests.api import instances_quotas from tests.api import instances_states from tests.dns import dns from tests import initialize diff --git a/integration/tests/integration/tests/api/instances_quotas.py b/integration/tests/integration/tests/api/instances_quotas.py deleted file mode 100644 index 3a1c2de637..0000000000 --- a/integration/tests/integration/tests/api/instances_quotas.py +++ /dev/null @@ -1,47 +0,0 @@ -from proboscis import before_class -from proboscis import test -from proboscis.asserts import assert_raises - -from troveclient.compat import exceptions -from trove.tests.config import CONFIG -from trove.tests.util import create_client - - -@test(groups=['dbaas.api.instances.quotas']) -class InstanceQuotas(object): - - created_instances = [] - - @before_class - def setup(self): - self.client = create_client(is_admin=False) - - @test - def test_too_many_instances(self): - self.created_instances = [] - if 'trove_max_instances_per_user' in CONFIG.values: - too_many = CONFIG.values['trove_max_instances_per_user'] - already_there = len(self.client.instances.list()) - flavor = 1 - for i in range(too_many - already_there): - response = self.client.instances.create('too_many_%d' % i, - flavor, - {'size': 1}) - self.created_instances.append(response) - # This one better fail, because we just reached our quota. - assert_raises(exceptions.OverLimit, - self.client.instances.create, - "too_many", flavor, - {'size': 1}) - - @test(runs_after=[test_too_many_instances]) - def delete_excessive_entries(self): - # Delete all the instances called too_many*. - for id in self.created_instances: - while True: - try: - self.client.instances.delete(id) - except exceptions.UnprocessableEntity: - continue - except exceptions.NotFound: - break diff --git a/run_tests.py b/run_tests.py index 95c2f3b8d1..bc1006dca2 100644 --- a/run_tests.py +++ b/run_tests.py @@ -206,7 +206,6 @@ def import_tests(): from trove.tests.api import configurations # noqa from trove.tests.api import databases # noqa from trove.tests.api import datastores # noqa - from trove.tests.api import flavors # noqa from trove.tests.api import header # noqa from trove.tests.api import instances as rd_instances # noqa from trove.tests.api import instances_actions as rd_actions # noqa diff --git a/trove/common/notification.py b/trove/common/notification.py index 70d2071f4b..0ceddf83f0 100644 --- a/trove/common/notification.py +++ b/trove/common/notification.py @@ -447,6 +447,15 @@ class DBaaSInstanceCreate(DBaaSAPINotification): return ['instance_id'] +class DBaaSInstanceReboot(DBaaSAPINotification): + + def event_type(self): + return 'instance_reboot' + + def required_start_traits(self): + return ['instance_id'] + + class DBaaSInstanceRestart(DBaaSAPINotification): def event_type(self): diff --git a/trove/extensions/mgmt/instances/service.py b/trove/extensions/mgmt/instances/service.py index 914dc9a634..7c4f95dcef 100644 --- a/trove/extensions/mgmt/instances/service.py +++ b/trove/extensions/mgmt/instances/service.py @@ -131,7 +131,14 @@ class MgmtInstanceController(InstanceController): def _action_reboot(self, context, instance, req, body): LOG.debug("Rebooting instance %s.", instance.id) - instance.reboot() + + context.notification = notification.DBaaSInstanceReboot( + context, + request=req + ) + with StartNotification(context, instance_id=instance.id): + instance.reboot() + return wsgi.Result(None, 202) def _action_migrate(self, context, instance, req, body): diff --git a/trove/instance/service.py b/trove/instance/service.py index 1e558f5f1a..d8007dd89b 100644 --- a/trove/instance/service.py +++ b/trove/instance/service.py @@ -36,13 +36,11 @@ from trove.instance import models, views from trove.module import models as module_models from trove.module import views as module_views - CONF = cfg.CONF LOG = logging.getLogger(__name__) class InstanceController(wsgi.Controller): - """Controller for instance functionality.""" schemas = apischema.instance.copy() @@ -86,7 +84,7 @@ class InstanceController(wsgi.Controller): 'restart': self._action_restart, 'resize': self._action_resize, 'promote_to_replica_source': - self._action_promote_to_replica_source, + self._action_promote_to_replica_source, 'eject_replica_source': self._action_eject_replica_source, 'reset_status': self._action_reset_status, } @@ -478,7 +476,7 @@ class InstanceController(wsgi.Controller): LOG.debug("Default config for instance %(instance_id)s is %(config)s", {'instance_id': id, 'config': config}) return wsgi.Result(views.DefaultConfigurationView( - config).data(), 200) + config).data(), 200) def guest_log_list(self, req, tenant_id, id): """Return all information about all logs for an instance.""" diff --git a/trove/taskmanager/models.py b/trove/taskmanager/models.py index 3b855e3524..f4c8b8bc87 100755 --- a/trove/taskmanager/models.py +++ b/trove/taskmanager/models.py @@ -1259,7 +1259,6 @@ class BuiltInstanceTasks(BuiltInstance, NotifyMixin, ConfigurationMixin): def reboot(self): try: - # Issue a guest stop db call to shutdown the db if running LOG.debug("Stopping datastore on instance %s.", self.id) try: self.guest.stop_db() @@ -1268,15 +1267,27 @@ class BuiltInstanceTasks(BuiltInstance, NotifyMixin, ConfigurationMixin): # Also we check guest state before issuing reboot LOG.debug(str(e)) - self._refresh_datastore_status() - if not (self.datastore_status_matches( - rd_instance.ServiceStatuses.SHUTDOWN) or + # Wait for the mysql stopped. + def _datastore_is_offline(): + self._refresh_datastore_status() + return ( self.datastore_status_matches( - rd_instance.ServiceStatuses.CRASHED)): - # We will bail if db did not get stopped or is blocked - LOG.error("Cannot reboot instance. DB status is %s.", + rd_instance.ServiceStatuses.SHUTDOWN) or + self.datastore_status_matches( + rd_instance.ServiceStatuses.CRASHED) + ) + + try: + utils.poll_until( + _datastore_is_offline, + sleep_time=3, + time_out=CONF.reboot_time_out + ) + except exception.PollTimeOut: + LOG.error("Cannot reboot instance, DB status is %s", self.datastore_status.status) return + LOG.debug("The guest service status is %s.", self.datastore_status.status) @@ -1291,7 +1302,7 @@ class BuiltInstanceTasks(BuiltInstance, NotifyMixin, ConfigurationMixin): utils.poll_until( update_server_info, - sleep_time=2, + sleep_time=3, time_out=reboot_time_out) # Set the status to PAUSED. The guest agent will reset the status @@ -1302,7 +1313,6 @@ class BuiltInstanceTasks(BuiltInstance, NotifyMixin, ConfigurationMixin): LOG.error("Failed to reboot instance %(id)s: %(e)s", {'id': self.id, 'e': str(e)}) finally: - LOG.debug("Rebooting FINALLY %s", self.id) self.reset_task_status() def restart(self): diff --git a/trove/tests/api/backups.py b/trove/tests/api/backups.py index dd0e9cb4ef..8504d2fd19 100644 --- a/trove/tests/api/backups.py +++ b/trove/tests/api/backups.py @@ -52,7 +52,7 @@ backup_count_prior_to_create = 0 backup_count_for_instance_prior_to_create = 0 -@test(depends_on_groups=[instances_actions.GROUP_STOP_MYSQL], +@test(depends_on_groups=[instances_actions.GROUP_RESIZE], groups=[BACKUP_GROUP, tests.INSTANCES], enabled=CONFIG.swift_enabled) class CreateBackups(object): @@ -379,7 +379,7 @@ class DeleteRestoreInstance(object): assert_raises(exceptions.NotFound, instance_info.dbaas.instances.get, instance_id) - @test(runs_after=[VerifyRestore.test_database_restored_incremental]) + @test(depends_on=[VerifyRestore.test_database_restored_incremental]) def test_delete_restored_instance_incremental(self): try: self._delete(incremental_restore_instance_id) diff --git a/trove/tests/api/flavors.py b/trove/tests/api/flavors.py deleted file mode 100644 index c5f3423410..0000000000 --- a/trove/tests/api/flavors.py +++ /dev/null @@ -1,265 +0,0 @@ -# Copyright (c) 2011 OpenStack Foundation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import os - -from nose.tools import assert_equal -from nose.tools import assert_false -from nose.tools import assert_true -from proboscis.asserts import assert_raises -from proboscis import before_class -from proboscis.decorators import time_out -from proboscis import test -from trove.common.utils import poll_until -from trove import tests -from trove.tests.api.instances import TIMEOUT_INSTANCE_CREATE -from trove.tests.config import CONFIG -from trove.tests.util.check import AttrCheck -from trove.tests.util import create_dbaas_client -from trove.tests.util import create_nova_client -from trove.tests.util import test_config -from trove.tests.util.users import Requirements -from troveclient.compat import exceptions -from troveclient.v1.flavors import Flavor - -GROUP = "dbaas.api.flavors" -GROUP_DS = "dbaas.api.datastores" -FAKE_MODE = test_config.values['fake_mode'] - -servers_flavors = None -dbaas_flavors = None -user = None - - -def assert_attributes_equal(name, os_flavor, dbaas_flavor): - """Given an attribute name and two objects, - ensures the attribute is equal. - """ - assert_true(hasattr(os_flavor, name), - "open stack flavor did not have attribute %s" % name) - assert_true(hasattr(dbaas_flavor, name), - "dbaas flavor did not have attribute %s" % name) - expected = getattr(os_flavor, name) - actual = getattr(dbaas_flavor, name) - assert_equal(expected, actual, - 'DBaas flavor differs from Open Stack on attribute ' + name) - - -def assert_flavors_roughly_equivalent(os_flavor, dbaas_flavor): - assert_attributes_equal('name', os_flavor, dbaas_flavor) - assert_attributes_equal('ram', os_flavor, dbaas_flavor) - - -def assert_link_list_is_equal(flavor): - assert_true(hasattr(flavor, 'links')) - assert_true(flavor.links) - - if flavor.id: - flavor_id = str(flavor.id) - else: - flavor_id = flavor.str_id - - for link in flavor.links: - href = link['href'] - - if "self" in link['rel']: - expected_href = os.path.join(test_config.dbaas_url, "flavors", - str(flavor.id)) - url = test_config.dbaas_url.replace('http:', 'https:', 1) - msg = ("REL HREF %s doesn't start with %s" % - (href, test_config.dbaas_url)) - assert_true(href.startswith(url), msg) - url = os.path.join("flavors", flavor_id) - msg = "REL HREF %s doesn't end in '%s'" % (href, url) - assert_true(href.endswith(url), msg) - elif "bookmark" in link['rel']: - base_url = test_config.version_url.replace('http:', 'https:', 1) - expected_href = os.path.join(base_url, "flavors", flavor_id) - msg = 'bookmark "href" must be %s, not %s' % (expected_href, href) - assert_equal(href, expected_href, msg) - else: - assert_false(True, "Unexpected rel - %s" % link['rel']) - - -@test(groups=[tests.DBAAS_API, GROUP, GROUP_DS, tests.PRE_INSTANCES], - depends_on_groups=["services.initialize"]) -class Flavors(object): - @before_class - def setUp(self): - rd_user = test_config.users.find_user( - Requirements(is_admin=False, services=["trove"])) - self.rd_client = create_dbaas_client(rd_user) - - if test_config.nova_client is not None: - nova_user = test_config.users.find_user( - Requirements(services=["nova"])) - self.nova_client = create_nova_client(nova_user) - - def get_expected_flavors(self): - # If we have access to the client, great! Let's use that as the flavors - # returned by Trove should be identical. - if test_config.nova_client is not None: - return self.nova_client.flavors.list() - # If we don't have access to the client the flavors need to be spelled - # out in the config file. - flavors = [Flavor(Flavors, flavor_dict, loaded=True) - for flavor_dict in test_config.flavors] - return flavors - - @test - def confirm_flavors_lists_nearly_identical(self): - os_flavors = self.get_expected_flavors() - dbaas_flavors = self.rd_client.flavors.list() - - print("Open Stack Flavors:") - print(os_flavors) - print("DBaaS Flavors:") - print(dbaas_flavors) - # Length of both flavors list should be identical. - assert_equal(len(os_flavors), len(dbaas_flavors)) - for os_flavor in os_flavors: - found_index = None - for index, dbaas_flavor in enumerate(dbaas_flavors): - if os_flavor.name == dbaas_flavor.name: - msg = ("Flavor ID '%s' appears in elements #%s and #%d." % - (dbaas_flavor.id, str(found_index), index)) - assert_true(found_index is None, msg) - assert_flavors_roughly_equivalent(os_flavor, dbaas_flavor) - found_index = index - msg = "Some flavors from OS list were missing in DBAAS list." - assert_false(found_index is None, msg) - for flavor in dbaas_flavors: - assert_link_list_is_equal(flavor) - - @test - def test_flavor_list_attrs(self): - allowed_attrs = ['id', 'name', 'ram', 'vcpus', 'disk', 'links', - 'ephemeral', 'local_storage', 'str_id'] - flavors = self.rd_client.flavors.list() - attrcheck = AttrCheck() - for flavor in flavors: - flavor_dict = flavor._info - attrcheck.contains_allowed_attrs( - flavor_dict, allowed_attrs, - msg="Flavors list") - attrcheck.links(flavor_dict['links']) - - @test - def test_flavor_get_attrs(self): - allowed_attrs = ['id', 'name', 'ram', 'vcpus', 'disk', 'links', - 'ephemeral', 'local_storage', 'str_id'] - flavor = self.rd_client.flavors.get(1) - attrcheck = AttrCheck() - flavor_dict = flavor._info - attrcheck.contains_allowed_attrs( - flavor_dict, allowed_attrs, - msg="Flavor Get 1") - attrcheck.links(flavor_dict['links']) - - @test - def test_flavor_not_found(self): - assert_raises(exceptions.NotFound, - self.rd_client.flavors.get, "foo") - - @test - def test_flavor_list_datastore_version_associated_flavors(self): - datastore = self.rd_client.datastores.get( - test_config.dbaas_datastore) - dbaas_flavors = (self.rd_client.flavors. - list_datastore_version_associated_flavors( - datastore=test_config.dbaas_datastore, - version_id=datastore.default_version)) - os_flavors = self.get_expected_flavors() - assert_equal(len(dbaas_flavors), len(os_flavors)) - # verify flavor lists are identical - for os_flavor in os_flavors: - found_index = None - for index, dbaas_flavor in enumerate(dbaas_flavors): - if os_flavor.name == dbaas_flavor.name: - msg = ("Flavor ID '%s' appears in elements #%s and #%d." % - (dbaas_flavor.id, str(found_index), index)) - assert_true(found_index is None, msg) - assert_flavors_roughly_equivalent(os_flavor, dbaas_flavor) - found_index = index - msg = "Some flavors from OS list were missing in DBAAS list." - assert_false(found_index is None, msg) - for flavor in dbaas_flavors: - assert_link_list_is_equal(flavor) - - -@test(runs_after=[Flavors], - groups=[tests.DBAAS_API, GROUP, GROUP_DS], - depends_on_groups=["services.initialize"], - enabled=FAKE_MODE) -class DatastoreFlavorAssociation(object): - @before_class - def setUp(self): - rd_user = test_config.users.find_user( - Requirements(is_admin=False, services=["trove"])) - self.rd_client = create_dbaas_client(rd_user) - - self.datastore = self.rd_client.datastores.get( - test_config.dbaas_datastore) - self.name1 = "test_instance1" - self.name2 = "test_instance2" - self.volume = {'size': 2} - self.instance_id = None - self.nics = None - shared_network = CONFIG.get('shared_network', None) - if shared_network: - self.nics = [{'net-id': shared_network}] - - @test - @time_out(TIMEOUT_INSTANCE_CREATE) - def test_create_instance_with_valid_flavor_association(self): - # all the nova flavors are associated with the default datastore - result = self.rd_client.instances.create( - name=self.name1, flavor_id='1', volume=self.volume, - datastore=self.datastore.id, - nics=self.nics) - self.instance_id = result.id - assert_equal(200, self.rd_client.last_http_code) - - def result_is_active(): - instance = self.rd_client.instances.get(self.instance_id) - if instance.status == "ACTIVE": - return True - else: - # If its not ACTIVE, anything but BUILD must be - # an error. - assert_equal("BUILD", instance.status) - return False - - poll_until(result_is_active) - self.rd_client.instances.delete(self.instance_id) - - @test(runs_after=[test_create_instance_with_valid_flavor_association]) - def test_create_instance_with_invalid_flavor_association(self): - dbaas_flavors = (self.rd_client.flavors. - list_datastore_version_associated_flavors( - datastore=test_config.dbaas_datastore, - version_id=self.datastore.default_version)) - self.flavor_not_associated = None - os_flavors = Flavors().get_expected_flavors() - for os_flavor in os_flavors: - if os_flavor not in dbaas_flavors: - self.flavor_not_associated = os_flavor.id - break - if self.flavor_not_associated is not None: - assert_raises(exceptions.BadRequest, - self.rd_client.instances.create, self.name2, - flavor_not_associated, self.volume, - datastore=self.datastore.id, - nics=self.nics) diff --git a/trove/tests/api/instances.py b/trove/tests/api/instances.py index e8ef046845..e48ce6683e 100644 --- a/trove/tests/api/instances.py +++ b/trove/tests/api/instances.py @@ -16,11 +16,9 @@ import netaddr import os import time -from time import sleep import unittest import uuid -from proboscis import after_class from proboscis.asserts import assert_equal from proboscis.asserts import assert_false from proboscis.asserts import assert_is_not_none @@ -35,7 +33,6 @@ from proboscis import test from troveclient.compat import exceptions from trove.common import cfg -from trove.common import exception as rd_exceptions from trove.common.utils import poll_until from trove.datastore import models as datastore_models from trove import tests @@ -116,15 +113,17 @@ class InstanceTestInfo(object): 'eph.rd-tiny') else: flavor_name = CONFIG.values.get('instance_flavor_name', 'm1.tiny') + flavors = self.dbaas.find_flavors_by_name(flavor_name) assert_equal(len(flavors), 1, "Number of flavors with name '%s' " "found was '%d'." % (flavor_name, len(flavors))) + flavor = flavors[0] - assert_true(flavor is not None, "Flavor '%s' not found!" % flavor_name) flavor_href = self.dbaas.find_flavor_self_href(flavor) assert_true(flavor_href is not None, "Flavor href '%s' not found!" % flavor_name) + return flavor, flavor_href def get_address(self, mgmt=False): @@ -255,17 +254,10 @@ def test_delete_instance_not_found(): groups=[GROUP, GROUP_QUOTAS], runs_after_groups=[tests.PRE_INSTANCES]) class CreateInstanceQuotaTest(unittest.TestCase): - - def setUp(self): - import copy - - self.test_info = copy.deepcopy(instance_info) - self.test_info.dbaas_datastore = CONFIG.dbaas_datastore - def tearDown(self): quota_dict = {'instances': CONFIG.trove_max_instances_per_tenant, 'volumes': CONFIG.trove_max_volumes_per_tenant} - dbaas_admin.quota.update(self.test_info.user.tenant_id, + dbaas_admin.quota.update(instance_info.user.tenant_id, quota_dict) def test_instance_size_too_big(self): @@ -273,52 +265,48 @@ class CreateInstanceQuotaTest(unittest.TestCase): VOLUME_SUPPORT): too_big = CONFIG.trove_max_accepted_volume_size - self.test_info.volume = {'size': too_big + 1} - self.test_info.name = "way_too_large" assert_raises(exceptions.OverLimit, dbaas.instances.create, - self.test_info.name, - self.test_info.dbaas_flavor_href, - self.test_info.volume, + "volume_size_too_large", + instance_info.dbaas_flavor_href, + {'size': too_big + 1}, nics=instance_info.nics) def test_update_quota_invalid_resource_should_fail(self): quota_dict = {'invalid_resource': 100} assert_raises(exceptions.NotFound, dbaas_admin.quota.update, - self.test_info.user.tenant_id, quota_dict) + instance_info.user.tenant_id, quota_dict) def test_update_quota_volume_should_fail_volume_not_supported(self): if VOLUME_SUPPORT: raise SkipTest("Volume support needs to be disabled") quota_dict = {'volumes': 100} assert_raises(exceptions.NotFound, dbaas_admin.quota.update, - self.test_info.user.tenant_id, quota_dict) + instance_info.user.tenant_id, quota_dict) def test_create_too_many_instances(self): instance_quota = 0 quota_dict = {'instances': instance_quota} - new_quotas = dbaas_admin.quota.update(self.test_info.user.tenant_id, + new_quotas = dbaas_admin.quota.update(instance_info.user.tenant_id, quota_dict) - set_quota = dbaas_admin.quota.show(self.test_info.user.tenant_id) + set_quota = dbaas_admin.quota.show(instance_info.user.tenant_id) verify_quota = {q.resource: q.limit for q in set_quota} assert_equal(new_quotas['instances'], quota_dict['instances']) assert_equal(0, verify_quota['instances']) - self.test_info.volume = None + volume = None if VOLUME_SUPPORT: assert_equal(CONFIG.trove_max_volumes_per_tenant, verify_quota['volumes']) - self.test_info.volume = {'size': - CONFIG.get('trove_volume_size', 1)} + volume = {'size': CONFIG.get('trove_volume_size', 1)} - self.test_info.name = "too_many_instances" assert_raises(exceptions.OverLimit, dbaas.instances.create, - self.test_info.name, - self.test_info.dbaas_flavor_href, - self.test_info.volume, + "too_many_instances", + instance_info.dbaas_flavor_href, + volume, nics=instance_info.nics) assert_equal(413, dbaas.last_http_code) @@ -328,17 +316,15 @@ class CreateInstanceQuotaTest(unittest.TestCase): raise SkipTest("Volume support not enabled") volume_quota = 3 quota_dict = {'volumes': volume_quota} - self.test_info.volume = {'size': volume_quota + 1} - new_quotas = dbaas_admin.quota.update(self.test_info.user.tenant_id, + new_quotas = dbaas_admin.quota.update(instance_info.user.tenant_id, quota_dict) assert_equal(volume_quota, new_quotas['volumes']) - self.test_info.name = "too_large_volume" assert_raises(exceptions.OverLimit, dbaas.instances.create, - self.test_info.name, - self.test_info.dbaas_flavor_href, - self.test_info.volume, + "too_large_volume", + instance_info.dbaas_flavor_href, + {'size': volume_quota + 1}, nics=instance_info.nics) assert_equal(413, dbaas.last_http_code) @@ -474,6 +460,7 @@ class CreateInstanceFail(object): databases = [] flavor_name = CONFIG.values.get('instance_flavor_name', 'm1.tiny') flavors = dbaas.find_flavors_by_name(flavor_name) + assert_raises(exceptions.BadRequest, dbaas.instances.create, instance_name, flavors[0].id, None, databases, nics=instance_info.nics) @@ -1508,86 +1495,3 @@ class CheckInstance(AttrCheck): slave, allowed_attrs, msg="Replica links not found") self.links(slave['links']) - - -@test(groups=[GROUP]) -class BadInstanceStatusBug(object): - - @before_class() - def setUp(self): - self.instances = [] - reqs = Requirements(is_admin=True) - self.user = CONFIG.users.find_user( - reqs, black_list=[]) - self.client = create_dbaas_client(self.user) - self.mgmt = self.client.management - - @test - def test_instance_status_after_double_migrate(self): - """ - This test is to verify that instance status returned is more - informative than 'Status is {}'. There are several ways to - replicate this error. A double migration is just one of them but - since this is a known way to recreate that error we will use it - here to be sure that the error is fixed. The actual code lives - in trove/instance/models.py in _validate_can_perform_action() - """ - # TODO(imsplitbit): test other instances where this issue could be - # replicated. Resizing a resized instance awaiting confirmation - # can be used as another case. This all boils back to the same - # piece of code so I'm not sure if it's relevant or not but could - # be done. - size = None - if VOLUME_SUPPORT: - size = {'size': 5} - - result = self.client.instances.create('testbox', - instance_info.dbaas_flavor_href, - size, - nics=instance_info.nics) - id = result.id - self.instances.append(id) - - def verify_instance_is_active(): - result = self.client.instances.get(id) - print(result.status) - return result.status == 'ACTIVE' - - def attempt_migrate(): - print('attempting migration') - try: - self.mgmt.migrate(id) - except exceptions.UnprocessableEntity: - return False - return True - - # Timing necessary to make the error occur - poll_until(verify_instance_is_active, time_out=120, sleep_time=1) - - try: - poll_until(attempt_migrate, time_out=10, sleep_time=1) - except rd_exceptions.PollTimeOut: - fail('Initial migration timed out') - - try: - self.mgmt.migrate(id) - except exceptions.UnprocessableEntity as err: - assert('status was {}' not in err.message) - else: - # If we are trying to test what status is returned when an - # instance is in a confirm_resize state and another - # migration is attempted then we also need to - # assert that an exception is raised when running migrate. - # If one is not then we aren't able to test what the - # returned status is in the exception message. - fail('UnprocessableEntity was not thrown') - - @after_class(always_run=True) - def tearDown(self): - while len(self.instances) > 0: - for id in self.instances: - try: - self.client.instances.delete(id) - self.instances.remove(id) - except exceptions.UnprocessableEntity: - sleep(1.0) diff --git a/trove/tests/api/instances_actions.py b/trove/tests/api/instances_actions.py index 1ed36e900d..1bcb40cf97 100644 --- a/trove/tests/api/instances_actions.py +++ b/trove/tests/api/instances_actions.py @@ -45,6 +45,7 @@ GROUP_REBOOT = "dbaas.api.instances.actions.reboot" GROUP_RESTART = "dbaas.api.instances.actions.restart" GROUP_RESIZE = "dbaas.api.instances.actions.resize" GROUP_STOP_MYSQL = "dbaas.api.instances.actions.stop" +GROUP_UPDATE_GUEST = "dbaas.api.instances.actions.update_guest" MYSQL_USERNAME = "test_user" MYSQL_PASSWORD = "abcde" # stored in test conf @@ -104,7 +105,6 @@ def get_resize_timeout(): TIME_OUT_TIME = get_resize_timeout() -USER_WAS_DELETED = False class ActionTestBase(object): @@ -223,23 +223,25 @@ class RebootTestBase(ActionTestBase): def call_reboot(self): raise NotImplementedError() - def wait_for_broken_connection(self): - """Wait until our connection breaks.""" - if not USE_IP: - return - if not hasattr(self, "connection"): - return - poll_until(self.connection.is_connected, - lambda connected: not connected, - time_out=TIME_OUT_TIME) - def wait_for_successful_restart(self): - """Wait until status becomes running.""" - def is_finished_rebooting(): + """Wait until status becomes running. + + Reboot is an async operation, make sure the instance is rebooting + before active. + """ + def _is_rebooting(): instance = self.instance if instance.status == "REBOOT": + return True + return False + + poll_until(_is_rebooting, time_out=TIME_OUT_TIME) + + def is_finished_rebooting(): + instance = self.instance + asserts.assert_not_equal(instance.status, "ERROR") + if instance.status != "ACTIVE": return False - asserts.assert_equal("ACTIVE", instance.status) return True poll_until(is_finished_rebooting, time_out=TIME_OUT_TIME) @@ -253,45 +255,10 @@ class RebootTestBase(ActionTestBase): def successful_restart(self): """Restart MySQL via the REST API successfully.""" - self.fix_mysql() self.call_reboot() - self.wait_for_broken_connection() self.wait_for_successful_restart() self.assert_mysql_proc_is_different() - def mess_up_mysql(self): - """Ruin MySQL's ability to restart.""" - server = create_server_connection(self.instance_id, - self.instance_mgmt_address) - cmd_template = "sudo cp /dev/null /var/lib/mysql/data/ib_logfile%d" - instance_info.dbaas_admin.management.stop(self.instance_id) - - for index in range(2): - cmd = cmd_template % index - try: - server.execute(cmd) - except Exception as e: - asserts.fail("Failed to execute command %s, error: %s" % - (cmd, str(e))) - - def fix_mysql(self): - """Fix MySQL's ability to restart.""" - if not FAKE_MODE: - server = create_server_connection(self.instance_id, - self.instance_mgmt_address) - cmd_template = "sudo rm /var/lib/mysql/data/ib_logfile%d" - # We want to stop mysql so that upstart does not keep trying to - # respawn it and block the guest agent from accessing the logs. - instance_info.dbaas_admin.management.stop(self.instance_id) - - for index in range(2): - cmd = cmd_template % index - try: - server.execute(cmd) - except Exception as e: - asserts.fail("Failed to execute command %s, error: %s" % - (cmd, str(e))) - def wait_for_failure_status(self): """Wait until status becomes running.""" def is_finished_rebooting(): @@ -306,19 +273,6 @@ class RebootTestBase(ActionTestBase): poll_until(is_finished_rebooting, time_out=TIME_OUT_TIME) - def unsuccessful_restart(self): - """Restart MySQL via the REST when it should fail, assert it does.""" - assert not FAKE_MODE - self.mess_up_mysql() - self.call_reboot() - self.wait_for_broken_connection() - self.wait_for_failure_status() - - def restart_normally(self): - """Fix iblogs and reboot normally.""" - self.fix_mysql() - self.test_successful_restart() - @test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP, GROUP_RESTART], depends_on_groups=[GROUP_START], depends_on=[create_user]) @@ -338,22 +292,14 @@ class RestartTests(RebootTestBase): """Make sure MySQL is accessible before restarting.""" self.ensure_mysql_is_running() - @test(depends_on=[test_ensure_mysql_is_running], enabled=not FAKE_MODE) - def test_unsuccessful_restart(self): - """Restart MySQL via the REST when it should fail, assert it does.""" - if FAKE_MODE: - raise SkipTest("Cannot run this in fake mode.") - self.unsuccessful_restart() - - @test(depends_on=[test_set_up], - runs_after=[test_ensure_mysql_is_running, test_unsuccessful_restart]) + @test(depends_on=[test_ensure_mysql_is_running]) def test_successful_restart(self): """Restart MySQL via the REST API successfully.""" self.successful_restart() @test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP, GROUP_STOP_MYSQL], - depends_on_groups=[GROUP_START], depends_on=[create_user]) + depends_on_groups=[GROUP_RESTART], depends_on=[create_user]) class StopTests(RebootTestBase): """Tests which involve stopping MySQL.""" @@ -373,11 +319,10 @@ class StopTests(RebootTestBase): def test_stop_mysql(self): """Stops MySQL.""" instance_info.dbaas_admin.management.stop(self.instance_id) - self.wait_for_broken_connection() self.wait_for_failure_status() @test(depends_on=[test_stop_mysql]) - def test_instance_get_shows_volume_info_while_mysql_is_down(self): + def test_volume_info_while_mysql_is_down(self): """ Confirms the get call behaves appropriately while an instance is down. @@ -392,15 +337,14 @@ class StopTests(RebootTestBase): check.true(isinstance(instance.volume.get('size', None), int)) check.true(isinstance(instance.volume.get('used', None), float)) - @test(depends_on=[test_set_up], - runs_after=[test_instance_get_shows_volume_info_while_mysql_is_down]) + @test(depends_on=[test_volume_info_while_mysql_is_down]) def test_successful_restart_when_in_shutdown_state(self): """Restart MySQL via the REST API successfully when MySQL is down.""" self.successful_restart() @test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP, GROUP_REBOOT], - depends_on_groups=[GROUP_START], depends_on=[RestartTests, create_user]) + depends_on_groups=[GROUP_STOP_MYSQL]) class RebootTests(RebootTestBase): """Tests restarting instance.""" @@ -418,14 +362,7 @@ class RebootTests(RebootTestBase): """Make sure MySQL is accessible before restarting.""" self.ensure_mysql_is_running() - @test(depends_on=[test_ensure_mysql_is_running]) - def test_unsuccessful_restart(self): - """Restart MySQL via the REST when it should fail, assert it does.""" - if FAKE_MODE: - raise SkipTest("Cannot run this in fake mode.") - self.unsuccessful_restart() - - @after_class(depends_on=[test_set_up]) + @after_class(depends_on=[test_ensure_mysql_is_running]) def test_successful_restart(self): """Restart MySQL via the REST API successfully.""" if FAKE_MODE: @@ -434,8 +371,7 @@ class RebootTests(RebootTestBase): @test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP, GROUP_RESIZE], - depends_on_groups=[GROUP_START], depends_on=[create_user], - runs_after=[RebootTests]) + depends_on_groups=[GROUP_REBOOT]) class ResizeInstanceTest(ActionTestBase): """ @@ -466,7 +402,6 @@ class ResizeInstanceTest(ActionTestBase): self.connection.connect() asserts.assert_true(self.connection.is_connected(), "Should be able to connect before resize.") - self.user_was_deleted = False @test def test_instance_resize_same_size_should_fail(self): @@ -484,8 +419,6 @@ class ResizeInstanceTest(ActionTestBase): poll_until(is_active, time_out=TIME_OUT_TIME) asserts.assert_equal(self.instance.status, 'ACTIVE') - self.get_flavor_href( - flavor_id=self.expected_old_flavor_id) asserts.assert_raises(HTTPNotImplemented, self.dbaas.instances.resize_instance, self.instance_id, flavors[0].id) @@ -517,11 +450,6 @@ class ResizeInstanceTest(ActionTestBase): flavor = flavors[0] self.old_dbaas_flavor = instance_info.dbaas_flavor instance_info.dbaas_flavor = flavor - asserts.assert_true(flavor is not None, - "Flavor '%s' not found!" % flavor_name) - flavor_href = self.dbaas.find_flavor_self_href(flavor) - asserts.assert_true(flavor_href is not None, - "Flavor href '%s' not found!" % flavor_name) self.expected_new_flavor_id = flavor.id @test(depends_on=[test_instance_resize_same_size_should_fail]) @@ -579,45 +507,6 @@ class ResizeInstanceTest(ActionTestBase): expected = self.get_flavor_href(flavor_id=self.expected_new_flavor_id) asserts.assert_equal(actual, expected) - @test(depends_on=[test_instance_has_new_flavor_after_resize]) - @time_out(TIME_OUT_TIME) - def test_resize_down(self): - expected_dbaas_flavor = self.expected_dbaas_flavor - - def is_active(): - return self.instance.status == 'ACTIVE' - poll_until(is_active, time_out=TIME_OUT_TIME) - asserts.assert_equal(self.instance.status, 'ACTIVE') - - old_flavor_href = self.get_flavor_href( - flavor_id=self.expected_old_flavor_id) - - self.dbaas.instances.resize_instance(self.instance_id, old_flavor_href) - asserts.assert_equal(202, self.dbaas.last_http_code) - self.old_dbaas_flavor = instance_info.dbaas_flavor - instance_info.dbaas_flavor = expected_dbaas_flavor - self.wait_for_resize() - asserts.assert_equal(str(self.instance.flavor['id']), - str(self.expected_old_flavor_id)) - - @test(depends_on=[test_resize_down], - groups=["dbaas.usage"]) - def test_resize_instance_down_usage_event_sent(self): - expected = self._build_expected_msg() - expected['old_instance_size'] = self.old_dbaas_flavor.ram - instance_info.consumer.check_message(instance_info.id, - 'trove.instance.modify_flavor', - **expected) - - -@test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP, - GROUP + ".resize.instance"], - depends_on_groups=[GROUP_START], depends_on=[create_user], - runs_after=[RebootTests, ResizeInstanceTest]) -def resize_should_not_delete_users(): - if USER_WAS_DELETED: - asserts.fail("Somehow, the resize made the test user disappear.") - @test(depends_on=[ResizeInstanceTest], groups=[GROUP, tests.INSTANCES, INSTANCE_GROUP, GROUP_RESIZE], @@ -708,9 +597,8 @@ class ResizeInstanceVolume(ActionTestBase): UPDATE_GUEST_CONF = CONFIG.values.get("guest-update-test", None) -@test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP, GROUP + ".update_guest"], - depends_on=[create_user], - depends_on_groups=[GROUP_START]) +@test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP, GROUP_UPDATE_GUEST], + depends_on_groups=[GROUP_RESIZE]) class UpdateGuest(object): def get_version(self): diff --git a/trove/tests/api/instances_mysql_down.py b/trove/tests/api/instances_mysql_down.py index 18e08fdeba..7b160527a1 100644 --- a/trove/tests/api/instances_mysql_down.py +++ b/trove/tests/api/instances_mysql_down.py @@ -52,6 +52,7 @@ class TestBase(object): 'm1.tiny') flavor2_name = test_config.values.get( 'instance_bigger_flavor_name', 'm1.small') + flavors = self.client.find_flavors_by_name(flavor_name) self.flavor_id = flavors[0].id self.name = "TEST_" + str(uuid.uuid4()) diff --git a/trove/tests/int_tests.py b/trove/tests/int_tests.py index a3c19ef9c9..eea923155e 100644 --- a/trove/tests/int_tests.py +++ b/trove/tests/int_tests.py @@ -18,7 +18,6 @@ from trove.tests.api import backups from trove.tests.api import configurations from trove.tests.api import databases from trove.tests.api import datastores -from trove.tests.api import flavors from trove.tests.api import instances from trove.tests.api import instances_actions from trove.tests.api.mgmt import accounts @@ -86,7 +85,6 @@ def register(group_names, *test_groups, **kwargs): depends_on_groups=build_group(*test_groups)) black_box_groups = [ - flavors.GROUP, users.GROUP, user_access.GROUP, databases.GROUP, @@ -114,7 +112,6 @@ proboscis.register(groups=["blackbox", "mysql"], simple_black_box_groups = [ GROUP_SERVICES_INITIALIZE, - flavors.GROUP, versions.GROUP, instances.GROUP_START_SIMPLE, admin_required.GROUP, @@ -141,7 +138,6 @@ proboscis.register(groups=["blackbox_mgmt"], # Base groups for all other groups base_groups = [ GROUP_SERVICES_INITIALIZE, - flavors.GROUP, versions.GROUP, GROUP_SETUP ] diff --git a/trove/tests/scenario/runners/instance_create_runners.py b/trove/tests/scenario/runners/instance_create_runners.py index 3e9e6484fa..371b0a88c9 100644 --- a/trove/tests/scenario/runners/instance_create_runners.py +++ b/trove/tests/scenario/runners/instance_create_runners.py @@ -199,8 +199,8 @@ class InstanceCreateRunner(TestRunner): self.assert_equal(instance_info.name, instance._info['name'], "Unexpected instance name") - self.assert_equal(flavor.id, - int(instance._info['flavor']['id']), + self.assert_equal(str(flavor.id), + str(instance._info['flavor']['id']), "Unexpected instance flavor") self.assert_equal(instance_info.dbaas_datastore, instance._info['datastore']['type'], diff --git a/trove/tests/scenario/runners/test_runners.py b/trove/tests/scenario/runners/test_runners.py index be10dac468..221c7a60fe 100644 --- a/trove/tests/scenario/runners/test_runners.py +++ b/trove/tests/scenario/runners/test_runners.py @@ -802,10 +802,8 @@ class TestRunner(object): self.assert_equal( 1, len(flavors), "Unexpected number of flavors with name '%s' found." % flavor_name) - flavor = flavors[0] - self.assert_is_not_none(flavor, "Flavor '%s' not found." % flavor_name) - return flavor + return flavors[0] def get_instance_flavor(self, fault_num=None): name_format = 'instance%s%s_flavor_name' diff --git a/trove/tests/unittests/mgmt/test_datastores.py b/trove/tests/unittests/mgmt/test_datastores.py index 94cf2e0b7e..d1721f398a 100644 --- a/trove/tests/unittests/mgmt/test_datastores.py +++ b/trove/tests/unittests/mgmt/test_datastores.py @@ -52,7 +52,7 @@ class TestDatastoreVersion(trove_testtools.TestCase): def test_version_create(self, mock_glance_client): body = {"version": { "datastore_name": "test_ds", - "name": "test_vr", + "name": "test_version", "datastore_manager": "mysql", "image": "image-id", "packages": "test-pkg", diff --git a/trove/tests/unittests/taskmanager/test_models.py b/trove/tests/unittests/taskmanager/test_models.py index 74fb9253ef..e91ef38392 100644 --- a/trove/tests/unittests/taskmanager/test_models.py +++ b/trove/tests/unittests/taskmanager/test_models.py @@ -309,6 +309,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): new_callable=PropertyMock, return_value='fake-hostname') def test_servers_create_block_device_mapping_v2(self, mock_hostname): + self.freshinstancetasks._prepare_userdata = Mock(return_value=None) mock_nova_client = self.freshinstancetasks.nova_client = Mock() mock_servers_create = mock_nova_client.servers.create self.freshinstancetasks._create_server('fake-flavor', 'fake-image', @@ -867,26 +868,23 @@ class BuiltInstanceTasksTest(trove_testtools.TestCase): @patch.object(utils, 'poll_until') def test_reboot(self, mock_poll): - self.instance_task.datastore_status_matches = Mock(return_value=True) - self.instance_task._refresh_datastore_status = Mock() self.instance_task.server.reboot = Mock() self.instance_task.set_datastore_status_to_paused = Mock() self.instance_task.reboot() self.instance_task._guest.stop_db.assert_any_call() - self.instance_task._refresh_datastore_status.assert_any_call() self.instance_task.server.reboot.assert_any_call() self.instance_task.set_datastore_status_to_paused.assert_any_call() @patch.object(utils, 'poll_until') @patch('trove.taskmanager.models.LOG') def test_reboot_datastore_not_ready(self, mock_logging, mock_poll): - self.instance_task.datastore_status_matches = Mock(return_value=False) - self.instance_task._refresh_datastore_status = Mock() + mock_poll.side_effect = PollTimeOut self.instance_task.server.reboot = Mock() self.instance_task.set_datastore_status_to_paused = Mock() + self.instance_task.reboot() + self.instance_task._guest.stop_db.assert_any_call() - self.instance_task._refresh_datastore_status.assert_any_call() assert not self.instance_task.server.reboot.called assert not self.instance_task.set_datastore_status_to_paused.called