Step #2 - Retiring Nova-docker : Remove Project Content
Depends-On: I7adcc29cac151ec55f6cc322a880189e0e827db1 Change-Id: I0f46c700c8c6a6495ebb64e9976218bc2fb83b65
This commit is contained in:
parent
034a4842fc
commit
64426d814b
|
@ -1,7 +0,0 @@
|
|||
[run]
|
||||
branch = True
|
||||
source = novadocker
|
||||
omit = novadocker/tests/*,novadocker/openstack/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
|
@ -1,53 +0,0 @@
|
|||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
./lib
|
||||
lib64
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
nosetests.xml
|
||||
.testrepository
|
||||
subunit.log
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# Complexity
|
||||
output/*.html
|
||||
output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
||||
# Editors
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
|
@ -1,4 +0,0 @@
|
|||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/nova-docker.git
|
3
.mailmap
3
.mailmap
|
@ -1,3 +0,0 @@
|
|||
# Format is:
|
||||
# <preferred e-mail> <other e-mail 1>
|
||||
# <preferred e-mail> <other e-mail 2>
|
|
@ -1,7 +0,0 @@
|
|||
[DEFAULT]
|
||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
||||
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
|
||||
test_id_option=--load-list $IDFILE
|
||||
test_list_option=--list
|
|
@ -1,21 +0,0 @@
|
|||
=============
|
||||
Contributing
|
||||
=============
|
||||
|
||||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps in the "If you're a developer, start here"
|
||||
section of this page:
|
||||
|
||||
http://wiki.openstack.org/HowToContribute
|
||||
|
||||
Once those steps have been completed, changes to OpenStack
|
||||
should be submitted for review via the Gerrit tool, following
|
||||
the workflow documented at:
|
||||
|
||||
http://wiki.openstack.org/GerritWorkflow
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
Bugs should be filed on Launchpad, not GitHub:
|
||||
|
||||
https://bugs.launchpad.net/nova-docker
|
|
@ -1,4 +0,0 @@
|
|||
nova-docker Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
175
LICENSE
175
LICENSE
|
@ -1,175 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
147
README.rst
147
README.rst
|
@ -1,140 +1,13 @@
|
|||
===============================
|
||||
nova-docker
|
||||
===============================
|
||||
This project is no longer maintained.
|
||||
|
||||
Docker driver for OpenStack Nova.
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
repository before it reached its end of life, please check out the
|
||||
previous commit with "git checkout HEAD^1".
|
||||
|
||||
Free software: Apache license
|
||||
For an alternative project, please see OpenStack Zun at
|
||||
http://git.openstack.org/cgit/openstack/zun/tree/README.rst
|
||||
|
||||
----------------------------
|
||||
Installation & Configuration
|
||||
----------------------------
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
1. Install the python modules.
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For example::
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
Note: There are better and cleaner ways of managing Python modules, such as
|
||||
using distribution packages or 'pip'. The setup.py file and Debian's stdeb,
|
||||
for instance, may be used to create Debian/Ubuntu packages.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
2. Enable the driver in Nova's configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In nova.conf::
|
||||
|
||||
compute_driver=novadocker.virt.docker.DockerDriver
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
3. Optionally tune site-specific settings.
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In nova.conf::
|
||||
|
||||
[docker]
|
||||
# Commented out. Uncomment these if you'd like to customize:
|
||||
## vif_driver=novadocker.virt.docker.vifs.DockerGenericVIFDriver
|
||||
## snapshots_directory=/var/tmp/my-snapshot-tempdir
|
||||
|
||||
--------------------------
|
||||
Uploading Images to Glance
|
||||
--------------------------
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
1. Enable the driver in Glance's configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In glance-api.conf::
|
||||
|
||||
container_formats=ami,ari,aki,bare,ovf,ova,docker
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
2. Save docker images to Glance
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Images may now be saved directly to Glance::
|
||||
|
||||
$ docker pull busybox
|
||||
$ docker save busybox | openstack image create busybox --public --container-format docker --disk-format raw
|
||||
|
||||
**Note:** At present, only administrators should be allowed to manage images.
|
||||
With devstack you can make yourself administrator by sourcing openrc as::
|
||||
|
||||
source openrc admin
|
||||
|
||||
Once done you can go back to a user::
|
||||
|
||||
source openrc demo
|
||||
|
||||
The name of the image in Glance should be explicitly set to the same name
|
||||
as the image as it is known to Docker. In the example above, an image has
|
||||
been tagged in Docker as 'busybox'. Matching this is the '--name busybox'
|
||||
argument to *glance image-create*. If these names do not align, the image
|
||||
will not be bootable.
|
||||
|
||||
**Note:** There should be a long lived process running in the docker image,
|
||||
otherwise the instance will not be able to spawn successfully. This can be
|
||||
done by using CMD or ENTRYPOINT in DockerFile:
|
||||
|
||||
https://docs.docker.com/reference/builder/
|
||||
|
||||
or specifing the command through glance image property 'os_command_line' for
|
||||
test::
|
||||
|
||||
glance image-update --property os_command_line='/usr/sbin/sshd -D' $IMAGE_ID
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
3. Generate a keypair
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can optionally create a keypair to use in your docker images::
|
||||
|
||||
nova keypair-add mykey > mykey.pem
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
4. Start a container
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Start a new container. This uses the key created above::
|
||||
|
||||
nova boot --flavor m1.small --image busybox --key-name mykey test1
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
5. ssh into container
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can check the IP address of the container by using::
|
||||
|
||||
nova list
|
||||
|
||||
And then ssh into it::
|
||||
|
||||
ssh -i ../devstack/mykey.pem cirros@<IP ADDRESS>
|
||||
|
||||
-----
|
||||
Notes
|
||||
-----
|
||||
|
||||
* Earlier releases of this driver required the deployment of a private docker
|
||||
registry. This is no longer required. Images are now saved and loaded from
|
||||
Glance.
|
||||
|
||||
* Images loaded from Glance may do bad things. Only allow administrators to
|
||||
add images. Users may create snapshots of their containers, generating images
|
||||
in Glance -- these images are managed and thus safe.
|
||||
|
||||
----------
|
||||
Contact Us
|
||||
----------
|
||||
Join us in #nova-docker on Freenode IRC
|
||||
|
||||
--------
|
||||
Features
|
||||
--------
|
||||
|
||||
* TODO
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
|
@ -1,16 +0,0 @@
|
|||
The contrib/devstack/ directory contains the files necessary to integrate Docker Nova driver with devstack.
|
||||
|
||||
To install::
|
||||
|
||||
$ git clone https://git.openstack.org/openstack/nova-docker /opt/stack/nova-docker
|
||||
$ git clone https://git.openstack.org/openstack-dev/devstack /opt/stack/devstack
|
||||
|
||||
# Note : only needed until we can make use of configure_nova_hypervisor_rootwrap
|
||||
$ git clone https://git.openstack.org/openstack/nova /opt/stack/nova
|
||||
|
||||
$ cd /opt/stack/nova-docker
|
||||
$ ./contrib/devstack/prepare_devstack.sh
|
||||
|
||||
Run devstack as normal::
|
||||
|
||||
$ ./stack.sh
|
|
@ -1,32 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# 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.
|
||||
|
||||
# This script is executed inside gate_hook.sh
|
||||
|
||||
INSTALLDIR=${INSTALLDIR:-/opt/stack}
|
||||
source $INSTALLDIR/devstack/functions-common
|
||||
LOGDIR=/opt/stack/logs
|
||||
|
||||
if is_ubuntu; then
|
||||
# Find and collect docker daemon logs
|
||||
sudo find /var/log/ -name "docker*" -print -exec sudo cp {} $LOGDIR \;
|
||||
elif is_fedora; then
|
||||
# fetch the docker logs from the journal
|
||||
sudo journalctl _SYSTEMD_UNIT=docker.service | sudo tee -a $LOGDIR/docker.service.log > /dev/null
|
||||
sudo journalctl _SYSTEMD_UNIT=docker.socket | sudo tee -a $LOGDIR/docker.socket.log > /dev/null
|
||||
fi
|
||||
|
||||
# Copy logs from the containers
|
||||
sudo mkdir -p $LOGDIR/containers/
|
||||
sudo find /var/lib/docker/containers/ -name "*.log" -print -exec sudo cp {} $LOGDIR/containers/ \;
|
|
@ -1,43 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -xe
|
||||
|
||||
SCRIPTDIR=$(readlink -f $(dirname $0))
|
||||
|
||||
# TODO : This should be removed once PATH contains sbin
|
||||
# https://review.openstack.org/#/c/91655/
|
||||
export PATH=$PATH:/usr/local/sbin:/usr/sbin
|
||||
sudo useradd -U -s /bin/bash -d /opt/stack/new -m stack || true
|
||||
sudo useradd -U -s /bin/bash -m tempest || true
|
||||
|
||||
export INSTALLDIR=$BASE/new
|
||||
bash -xe $SCRIPTDIR/prepare_devstack.sh
|
||||
|
||||
export DEVSTACK_GATE_VIRT_DRIVER=docker
|
||||
export KEEP_LOCALRC=1
|
||||
|
||||
export DEVSTACK_GATE_TEMPEST_REGEX='^(?!.*?(boto|volume|resize|suspend|rescue|cinder|migrate|object_storage)).*'
|
||||
|
||||
export DEVSTACK_GATE_TEMPEST=1
|
||||
export DEVSTACK_GATE_TEMPEST_FULL=0
|
||||
export DEVSTACK_GATE_TROVE=0
|
||||
|
||||
source $INSTALLDIR/devstack-gate/functions.sh
|
||||
source $INSTALLDIR/devstack/functions-common
|
||||
if is_ubuntu; then
|
||||
apt_get update
|
||||
install_package --force-yes linux-image-extra-`uname -r`
|
||||
fi
|
||||
|
||||
trap exit_trap EXIT
|
||||
function exit_trap {
|
||||
local r=$?
|
||||
if [[ "$r" -eq "0" ]]; then
|
||||
echo "All tests run successfully"
|
||||
else
|
||||
echo "ERROR! some tests failed, please see detailed output"
|
||||
fi
|
||||
echo "Collecting docker-specific logs"
|
||||
bash -x $SCRIPTDIR/copy_logs_hook.sh
|
||||
}
|
||||
|
||||
$INSTALLDIR/devstack-gate/devstack-vm-gate.sh
|
|
@ -1,129 +0,0 @@
|
|||
# lib/nova_plugins/hypervisor-docker
|
||||
# Configure the Docker hypervisor
|
||||
|
||||
# Enable with:
|
||||
#
|
||||
# VIRT_DRIVER=docker
|
||||
|
||||
# Dependencies:
|
||||
#
|
||||
# - ``functions`` file
|
||||
# - ``nova`` and ``glance`` configurations
|
||||
|
||||
# install_nova_hypervisor - install any external requirements
|
||||
# configure_nova_hypervisor - make configuration changes, including those to other services
|
||||
# start_nova_hypervisor - start any external services
|
||||
# stop_nova_hypervisor - stop any external services
|
||||
# cleanup_nova_hypervisor - remove transient data and cache
|
||||
|
||||
# Save trace setting
|
||||
MY_XTRACE=$(set +o | grep xtrace)
|
||||
set -o xtrace
|
||||
|
||||
|
||||
# Defaults
|
||||
# --------
|
||||
|
||||
# Set up default repos
|
||||
NOVA_DOCKER_REPO=${NOVA_DOCKER_REPO:-${GIT_BASE}/openstack/nova-docker.git}
|
||||
NOVA_DOCKER_BRANCH=${NOVA_DOCKER_BRANCH:-master}
|
||||
|
||||
# Set up default directories
|
||||
DOCKER_DIR=$DEST/nova-docker
|
||||
|
||||
|
||||
DOCKER_UNIX_SOCKET=/var/run/docker.sock
|
||||
DOCKER_PID_FILE=/var/run/docker.pid
|
||||
DOCKER_REGISTRY_PORT=${DOCKER_REGISTRY_PORT:-5042}
|
||||
|
||||
DOCKER_IMAGE_NAME=${DOCKER_IMAGE_NAME:-cirros}
|
||||
DOCKER_REGISTRY_IMAGE=${DOCKER_REGISTRY_IMAGE:-registry}
|
||||
DOCKER_REGISTRY_IMAGE_NAME=registry
|
||||
|
||||
DOCKER_APT_REPO=${DOCKER_APT_REPO:-https://apt.dockerproject.org/repo}
|
||||
|
||||
|
||||
# Entry Points
|
||||
# ------------
|
||||
|
||||
# clean_nova_hypervisor - Clean up an installation
|
||||
function cleanup_nova_hypervisor {
|
||||
stop_service docker
|
||||
|
||||
# Clean out work area
|
||||
sudo rm -rf /var/lib/docker
|
||||
|
||||
# remove docker bits from nova
|
||||
rm $NOVA_DIR/nova/virt/docker
|
||||
rm $NOVA_DIR/etc/nova/rootwrap.d/docker.filters
|
||||
}
|
||||
|
||||
# configure_nova_hypervisor - Set config files, create data dirs, etc
|
||||
function configure_nova_hypervisor {
|
||||
iniset $NOVA_CONF DEFAULT compute_driver novadocker.virt.docker.driver.DockerDriver
|
||||
iniset $GLANCE_API_CONF DEFAULT container_formats ami,ari,aki,bare,ovf,docker
|
||||
}
|
||||
|
||||
# is_docker_running - Return 0 (true) if Docker is running, otherwise 1
|
||||
function is_docker_running {
|
||||
local docker_pid
|
||||
if [ -f "$DOCKER_PID_FILE" ]; then
|
||||
docker_pid=$(cat "$DOCKER_PID_FILE")
|
||||
fi
|
||||
if [[ -z "$docker_pid" ]] || ! ps -p "$docker_pid" | grep [d]ocker; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# install_nova_hypervisor() - Install external components
|
||||
function install_nova_hypervisor {
|
||||
setup_develop $DOCKER_DIR
|
||||
# TODO : move into configure_nova_hypervisor_rootwrap if
|
||||
# https://review.openstack.org/#/c/84839/ is merged
|
||||
}
|
||||
|
||||
# start_nova_hypervisor - Start any required external services
|
||||
function start_nova_hypervisor {
|
||||
# Make sure Docker is installed
|
||||
if ! ( is_package_installed docker || is_package_installed docker-io || is_package_installed docker-engine ) ; then
|
||||
die $LINENO "Docker is not installed. Please run tools/docker/install_docker.sh"
|
||||
fi
|
||||
if ! (is_docker_running); then
|
||||
die $LINENO "Docker not running"
|
||||
fi
|
||||
|
||||
# Gather some information
|
||||
docker version
|
||||
docker -D info
|
||||
|
||||
# push the rootwrap
|
||||
sudo cp $DOCKER_DIR/etc/nova/rootwrap.d/docker.filters /etc/nova/rootwrap.d/docker.filters
|
||||
|
||||
echo "Checking default image for docker..."
|
||||
if ! (glance image-show "$DEFAULT_IMAGE_NAME"); then
|
||||
echo "Pulling $DOCKER_IMAGE_NAME..."
|
||||
|
||||
# Dance around the image that got renamed
|
||||
docker pull ewindisch/$DOCKER_IMAGE_NAME
|
||||
docker tag ewindisch/$DOCKER_IMAGE_NAME:latest $DOCKER_IMAGE_NAME
|
||||
|
||||
echo "Saving $DOCKER_IMAGE_NAME to glance..."
|
||||
TOKEN=$(openstack token issue -c id -f value)
|
||||
docker save $DOCKER_IMAGE_NAME | openstack --os-token $TOKEN --os-url $GLANCE_SERVICE_PROTOCOL://$GLANCE_HOSTPORT image create $DEFAULT_IMAGE_NAME --public --container-format docker --disk-format raw
|
||||
fi
|
||||
}
|
||||
|
||||
# stop_nova_hypervisor - Stop any external services
|
||||
function stop_nova_hypervisor {
|
||||
# Stop the docker registry container
|
||||
docker stop $DOCKER_REGISTRY_IMAGE_NAME
|
||||
docker rm $DOCKER_REGISTRY_IMAGE_NAME
|
||||
}
|
||||
|
||||
# Restore xtrace
|
||||
$MY_XTRACE
|
||||
|
||||
# Local variables:
|
||||
# mode: shell-script
|
||||
# End:
|
|
@ -1,15 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# 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.
|
||||
|
||||
# This script is executed inside post_test_hook function in devstack gate.
|
|
@ -1,15 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# 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.
|
||||
|
||||
# This script is executed inside pre_test_hook function in devstack gate.
|
|
@ -1,14 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -xe
|
||||
|
||||
env
|
||||
|
||||
NOVADOCKERDIR=$(readlink -f $(dirname $0)/../..)
|
||||
INSTALLDIR=${INSTALLDIR:-/opt/stack}
|
||||
|
||||
cp $NOVADOCKERDIR/contrib/devstack/lib/nova_plugins/hypervisor-docker $INSTALLDIR/devstack/lib/nova_plugins/
|
||||
|
||||
cat - <<-EOF >> $INSTALLDIR/devstack/localrc
|
||||
enable_plugin nova-docker https://git.openstack.org/openstack/nova-docker
|
||||
GITDIR['nova-docker']=$INSTALLDIR/nova-docker
|
||||
EOF
|
|
@ -1,2 +0,0 @@
|
|||
[[local|localrc]]
|
||||
enable_plugin docker-service https://github.com/dims/devstack-plugin-docker
|
|
@ -1,78 +0,0 @@
|
|||
#!/bin/bash
|
||||
# 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.
|
||||
|
||||
# Save trace setting
|
||||
XTRACE=$(set +o | grep xtrace)
|
||||
set -o xtrace
|
||||
|
||||
DOCKER_UNIX_SOCKET=/var/run/docker.sock
|
||||
|
||||
# main loop
|
||||
if [[ "$1" == "stack" && "$2" == "install" ]]; then
|
||||
echo_summary "Running stack install"
|
||||
elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
|
||||
echo_summary "Running stack post-config"
|
||||
wget http://get.docker.com -O install_docker.sh
|
||||
sudo chmod 777 install_docker.sh
|
||||
sudo bash -x install_docker.sh
|
||||
sudo rm install_docker.sh
|
||||
|
||||
if is_fedora; then
|
||||
install_package socat dnsmasq
|
||||
fi
|
||||
|
||||
# CentOS/RedHat distros don't start the services just after the package
|
||||
# is installed if it is not explicitily set. So the script fails on
|
||||
# them in this killall because there is nothing to kill.
|
||||
sudo killall docker || true
|
||||
|
||||
# Enable debug level logging
|
||||
if [ -f "/etc/default/docker" ]; then
|
||||
sudo cat /etc/default/docker
|
||||
sudo sed -i 's/^.*DOCKER_OPTS=.*$/DOCKER_OPTS=\"--debug --storage-opt dm.override_udev_sync_check=true\"/' /etc/default/docker
|
||||
sudo cat /etc/default/docker
|
||||
fi
|
||||
if [ -f "/etc/sysconfig/docker" ]; then
|
||||
sudo cat /etc/sysconfig/docker
|
||||
sudo sed -i 's/^.*OPTIONS=.*$/OPTIONS=--debug --selinux-enabled/' /etc/sysconfig/docker
|
||||
sudo cat /etc/sysconfig/docker
|
||||
fi
|
||||
if [ -f "/usr/lib/systemd/system/docker.service" ]; then
|
||||
sudo cat /usr/lib/systemd/system/docker.service
|
||||
sudo sed -i 's/docker daemon/docker daemon --debug/' /usr/lib/systemd/system/docker.service
|
||||
sudo cat /usr/lib/systemd/system/docker.service
|
||||
sudo systemctl daemon-reload
|
||||
fi
|
||||
|
||||
# Start the daemon - restart just in case the package ever auto-starts...
|
||||
restart_service docker
|
||||
|
||||
echo "Waiting for docker daemon to start..."
|
||||
DOCKER_GROUP=$(groups | cut -d' ' -f1)
|
||||
CONFIGURE_CMD="while ! /bin/echo -e 'GET /version HTTP/1.0\n\n' | socat - unix-connect:$DOCKER_UNIX_SOCKET 2>/dev/null | grep -q '200 OK'; do
|
||||
# Set the right group on docker unix socket before retrying
|
||||
sudo chgrp $DOCKER_GROUP $DOCKER_UNIX_SOCKET
|
||||
sudo chmod g+rw $DOCKER_UNIX_SOCKET
|
||||
sleep 1
|
||||
done"
|
||||
if ! timeout $SERVICE_TIMEOUT sh -c "$CONFIGURE_CMD"; then
|
||||
die $LINENO "docker did not start"
|
||||
fi
|
||||
fi
|
||||
if [[ "$1" == "unstack" ]]; then
|
||||
echo_summary "Running unstack"
|
||||
stop_service docker
|
||||
fi
|
||||
|
||||
# Restore xtrace
|
||||
$XTRACE
|
|
@ -1,4 +0,0 @@
|
|||
export VIRT_DRIVER=docker
|
||||
export DEFAULT_IMAGE_NAME=cirros
|
||||
export NON_STANDARD_REQS=1
|
||||
export IMAGE_URLS=" "
|
|
@ -1,75 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# 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
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
# 'sphinx.ext.intersphinx',
|
||||
'oslosphinx'
|
||||
]
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'nova-docker'
|
||||
copyright = u'2013, OpenStack Foundation'
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
# html_theme_path = ["."]
|
||||
# html_theme = '_theme'
|
||||
# html_static_path = ['static']
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
# intersphinx_mapping = {'http://docs.python.org/': None}
|
|
@ -1 +0,0 @@
|
|||
.. include:: ../../CONTRIBUTING.rst
|
|
@ -1,24 +0,0 @@
|
|||
.. nova-docker documentation master file, created by
|
||||
sphinx-quickstart on Tue Jul 9 22:26:36 2013.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to nova-docker's documentation!
|
||||
========================================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
readme
|
||||
installation
|
||||
usage
|
||||
contributing
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
|
@ -1,12 +0,0 @@
|
|||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
At the command line::
|
||||
|
||||
$ pip install nova-docker
|
||||
|
||||
Or, if you have virtualenvwrapper installed::
|
||||
|
||||
$ mkvirtualenv nova-docker
|
||||
$ pip install nova-docker
|
|
@ -1 +0,0 @@
|
|||
.. include:: ../../README.rst
|
|
@ -1,7 +0,0 @@
|
|||
========
|
||||
Usage
|
||||
========
|
||||
|
||||
To use nova-docker in a project::
|
||||
|
||||
import novadocker
|
|
@ -1,6 +0,0 @@
|
|||
# nova-rootwrap command filters for setting up network in the docker driver
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
[Filters]
|
||||
# nova/virt/docker/driver.py: 'ln', '-sf', '/var/run/netns/.*'
|
||||
ln: CommandFilter, /bin/ln, root
|
|
@ -1,19 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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 pbr.version
|
||||
|
||||
|
||||
__version__ = pbr.version.VersionInfo(
|
||||
'nova-docker').version_string()
|
|
@ -1,46 +0,0 @@
|
|||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""oslo.i18n integration module.
|
||||
|
||||
See http://docs.openstack.org/developer/oslo.i18n/usage.html .
|
||||
|
||||
"""
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
DOMAIN = 'nova-docker'
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# Translators for log levels.
|
||||
#
|
||||
# The abbreviated names are meant to reflect the usual use of a short
|
||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||
# the level.
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
||||
|
||||
|
||||
def translate(value, user_locale):
|
||||
return oslo_i18n.translate(value, user_locale)
|
||||
|
||||
|
||||
def get_available_languages():
|
||||
return oslo_i18n.get_available_languages(DOMAIN)
|
|
@ -1,153 +0,0 @@
|
|||
# Translations template for nova-docker.
|
||||
# Copyright (C) 2015 ORGANIZATION
|
||||
# This file is distributed under the same license as the nova-docker
|
||||
# project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: nova-docker 0.1.1.dev112\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2015-09-03 15:09-0700\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.0\n"
|
||||
|
||||
#: novadocker/virt/docker/driver.py:121
|
||||
msgid ""
|
||||
"Docker daemon is not running or is not reachable (check the rights on "
|
||||
"/var/run/docker.sock)"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:231
|
||||
msgid "Cannot find any PID under container \"{0}\""
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:307
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hostname has changed from %(old)s to %(new)s. A restart is required to "
|
||||
"take effect."
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:363
|
||||
msgid "Image container format not supported ({0})"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:385
|
||||
#, python-format
|
||||
msgid "Cannot load repository file from shared directory: %s"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:404
|
||||
#, python-format
|
||||
msgid "Cannot load repository file: %s"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:406
|
||||
msgid "Cannot load repository file: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:444 novadocker/virt/docker/driver.py:604
|
||||
#, python-format
|
||||
msgid "Cannot setup network: %s"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:446 novadocker/virt/docker/driver.py:606
|
||||
msgid "Cannot setup network: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:481
|
||||
msgid "Cannot create container"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:531
|
||||
#, python-format
|
||||
msgid "Cannot stop container: %s"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:573
|
||||
msgid "Cannot destroy the container network during reboot {0}"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:586
|
||||
msgid "Cannot setup network on reboot: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:628
|
||||
#, python-format
|
||||
msgid "Error pause container: %s"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:630
|
||||
msgid "Cannot pause container: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:644
|
||||
#, python-format
|
||||
msgid "Error unpause container: %s"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:646
|
||||
msgid "Cannot unpause container: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:702
|
||||
#, python-format
|
||||
msgid "Error saving image: %s"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/driver.py:704
|
||||
msgid "Error saving image: {0}"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/network.py:36
|
||||
#, python-format
|
||||
msgid "Cannot remove network namespace, netns id: %s"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/network.py:46
|
||||
msgid "Cannot find fixed ip"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/network.py:53
|
||||
msgid "Cannot find gateway"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/opencontrail.py:70 novadocker/virt/docker/vifs.py:106
|
||||
#: novadocker/virt/docker/vifs.py:131 novadocker/virt/docker/vifs.py:160
|
||||
#: novadocker/virt/docker/vifs.py:308
|
||||
msgid "Failed to setup the network, rolling back"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/opencontrail.py:128
|
||||
msgid "Failed to attach the network, rolling back"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/opencontrail.py:155
|
||||
msgid "Failed to attach vif"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/opencontrail.py:172
|
||||
msgid "Delete port failed"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/vifs.py:55 novadocker/virt/docker/vifs.py:321
|
||||
msgid "vif_type parameter must be present for this vif_driver implementation"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/vifs.py:71 novadocker/virt/docker/vifs.py:337
|
||||
#, python-format
|
||||
msgid "Unexpected vif_type=%s"
|
||||
msgstr ""
|
||||
|
||||
#: novadocker/virt/docker/vifs.py:355 novadocker/virt/docker/vifs.py:363
|
||||
#: novadocker/virt/docker/vifs.py:374 novadocker/virt/docker/vifs.py:397
|
||||
msgid "Failed while unplugging vif"
|
||||
msgstr ""
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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.
|
||||
|
||||
# TODO(mikal): move eventlet imports to nova.__init__ once we move to PBR
|
||||
import os
|
||||
import sys
|
||||
|
||||
# NOTE(mikal): All of this is because if dnspython is present in your
|
||||
# environment then eventlet monkeypatches socket.getaddrinfo() with an
|
||||
# implementation which doesn't work for IPv6. What we're checking here is
|
||||
# that the magic environment variable was set when the import happened.
|
||||
if ('eventlet' in sys.modules and
|
||||
os.environ.get('EVENTLET_NO_GREENDNS', '').lower() != 'yes'):
|
||||
raise ImportError('eventlet imported before nova/cmd/__init__ '
|
||||
'(env var set to %s)'
|
||||
% os.environ.get('EVENTLET_NO_GREENDNS'))
|
||||
|
||||
os.environ['EVENTLET_NO_GREENDNS'] = 'yes'
|
||||
|
||||
import eventlet
|
||||
|
||||
eventlet.monkey_patch(os=False)
|
|
@ -1,235 +0,0 @@
|
|||
# Copyright (c) 2013 dotCloud, Inc.
|
||||
# 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 inspect
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from oslo_utils import timeutils
|
||||
from six import moves
|
||||
|
||||
from novadocker.virt.docker import client as docker_client
|
||||
|
||||
|
||||
class MockClient(object):
|
||||
def __init__(self, endpoint=None):
|
||||
self._containers = {}
|
||||
self.name = None
|
||||
|
||||
# Fake repository
|
||||
self._repository = {'image_with_cmd':
|
||||
{'ContainerConfig':
|
||||
{'Cmd': 'echo Test'}},
|
||||
'image_without_cmd':
|
||||
{'ContainerConfig':
|
||||
{'Cmd': None}}}
|
||||
self._images = {'snap-1':
|
||||
{'ContainerConfig':
|
||||
{'Cmd': None}}}
|
||||
self._image_data = {'snap-1': 'dummy'}
|
||||
self._setup_decorators()
|
||||
|
||||
def _setup_decorators(self):
|
||||
for name, member in inspect.getmembers(self, inspect.ismethod):
|
||||
if not name.startswith('_'):
|
||||
setattr(self, name, docker_client.filter_data(member))
|
||||
|
||||
def _fake_id(self):
|
||||
return uuid.uuid4().hex + uuid.uuid4().hex
|
||||
|
||||
def _image_name(self, image_name):
|
||||
"""Split full image name to host and image name."""
|
||||
if '/' in image_name:
|
||||
host, image_name = image_name.split('/', 1)
|
||||
return image_name
|
||||
|
||||
def _is_image_exists(self, image_name):
|
||||
"""Check whether Images is listed in self._repository."""
|
||||
image_name = self._image_name(image_name)
|
||||
if image_name in self._repository:
|
||||
return image_name in self._images
|
||||
return True
|
||||
|
||||
def _is_daemon_running(self):
|
||||
return True
|
||||
|
||||
def containers(self, all=True, filters=None):
|
||||
containers = []
|
||||
for container_id in self._containers.iterkeys():
|
||||
containers.append({
|
||||
'Status': 'Exit 0',
|
||||
'Created': int(time.time()),
|
||||
'Image': 'ubuntu:12.04',
|
||||
'Ports': '',
|
||||
'Command': 'bash ',
|
||||
'Id': container_id
|
||||
})
|
||||
if filters and filters.get('name'):
|
||||
if (self._containers[container_id]['Config']['name'] ==
|
||||
filters.get('name')):
|
||||
return [{'Id': container_id}]
|
||||
return containers
|
||||
|
||||
def create_container(self, image_name, **args):
|
||||
self.name = args['name']
|
||||
data = {
|
||||
'Hostname': args['hostname'],
|
||||
'User': '',
|
||||
'MemorySwap': 0,
|
||||
'AttachStdin': False,
|
||||
'AttachStdout': False,
|
||||
'AttachStderr': False,
|
||||
'PortSpecs': None,
|
||||
'Tty': True,
|
||||
'OpenStdin': True,
|
||||
'StdinOnce': False,
|
||||
'Env': None,
|
||||
'Cmd': [],
|
||||
'Dns': None,
|
||||
'Image': image_name,
|
||||
'Volumes': {},
|
||||
'VolumesFrom': '',
|
||||
'CpuShares': args['cpu_shares'],
|
||||
'NetworkDisabled': args['network_disabled']
|
||||
}
|
||||
data.update(args)
|
||||
if not self._is_image_exists(data['Image']):
|
||||
return None
|
||||
container_id = self._fake_id()
|
||||
self._containers[container_id] = {
|
||||
'Id': container_id,
|
||||
'running': False,
|
||||
'Config': data
|
||||
}
|
||||
return container_id
|
||||
|
||||
def start(self, container_id, binds=None, dns=None, privileged=False):
|
||||
if container_id not in self._containers:
|
||||
return False
|
||||
self._containers[container_id]['running'] = True
|
||||
return True
|
||||
|
||||
def inspect_image(self, image_name):
|
||||
if not self._is_image_exists(image_name):
|
||||
return None
|
||||
|
||||
image_name = self._image_name(image_name)
|
||||
if image_name in self._images:
|
||||
return self._images[image_name]
|
||||
return {'ContainerConfig': {'Cmd': None}}
|
||||
|
||||
def inspect_container(self, container_id):
|
||||
if container_id not in self._containers:
|
||||
return
|
||||
container = self._containers[container_id]
|
||||
info = {
|
||||
'Args': [],
|
||||
'Config': container['Config'],
|
||||
'Created': str(timeutils.utcnow()),
|
||||
'Id': container_id,
|
||||
'Image': self._fake_id(),
|
||||
'NetworkSettings': {
|
||||
'Bridge': '',
|
||||
'Gateway': '',
|
||||
'IPAddress': '',
|
||||
'IPPrefixLen': 0,
|
||||
'PortMapping': None
|
||||
},
|
||||
'Path': 'bash',
|
||||
'ResolvConfPath': '/etc/resolv.conf',
|
||||
'State': {
|
||||
'ExitCode': 0,
|
||||
'Ghost': False,
|
||||
'Pid': 0,
|
||||
'Running': container['running'],
|
||||
'StartedAt': str(timeutils.utcnow())
|
||||
},
|
||||
'SysInitPath': '/tmp/docker',
|
||||
'Volumes': {},
|
||||
}
|
||||
return info
|
||||
|
||||
def stop(self, container_id, timeout=None):
|
||||
if container_id not in self._containers:
|
||||
return False
|
||||
self._containers[container_id]['running'] = False
|
||||
return True
|
||||
|
||||
def kill(self, container_id):
|
||||
if container_id not in self._containers:
|
||||
return False
|
||||
self._containers[container_id]['running'] = False
|
||||
return True
|
||||
|
||||
def remove_container(self, container_id, force=False):
|
||||
if container_id not in self._containers:
|
||||
return False
|
||||
|
||||
# Docker doesn't allow to destroy a running container.
|
||||
if self._containers[container_id]['running']:
|
||||
return False
|
||||
|
||||
del self._containers[container_id]
|
||||
return True
|
||||
|
||||
def unpause(self, container_id):
|
||||
if container_id not in self._containers:
|
||||
return False
|
||||
|
||||
self._containers[container_id]['paused'] = False
|
||||
return True
|
||||
|
||||
def pause(self, container_id):
|
||||
if container_id not in self._containers:
|
||||
return False
|
||||
|
||||
self._containers[container_id]['paused'] = True
|
||||
return True
|
||||
|
||||
def commit(self, container_id, repository=None, tag=None):
|
||||
if container_id not in self._containers:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_container_logs(self, container_id):
|
||||
if container_id not in self._containers:
|
||||
return False
|
||||
return '\n'.join([
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ',
|
||||
'Vivamus ornare mi sit amet orci feugiat, nec luctus magna ',
|
||||
'vehicula. Quisque diam nisl, dictum vitae pretium id, ',
|
||||
'consequat eget sapien. Ut vehicula tortor non ipsum ',
|
||||
'consectetur, at tincidunt elit posuere. In ut ligula leo. ',
|
||||
'Donec eleifend accumsan mi, in accumsan metus. Nullam nec ',
|
||||
'nulla eu risus vehicula porttitor. Sed purus ligula, ',
|
||||
'placerat nec metus a, imperdiet viverra turpis. Praesent ',
|
||||
'dapibus ornare massa. Nam ut hendrerit nunc. Interdum et ',
|
||||
'malesuada fames ac ante ipsum primis in faucibus. ',
|
||||
'Fusce nec pellentesque nisl.'])
|
||||
|
||||
def get_image(self, name):
|
||||
if (name not in self._images or
|
||||
name not in self._image_data):
|
||||
raise Exception("Image not found - %s" % name)
|
||||
return moves.StringIO(self._image_data[name])
|
||||
|
||||
def load_image(self, name, data):
|
||||
self._image_data[name] = data
|
||||
|
||||
def load_repository_file(self, name, path):
|
||||
pass
|
||||
|
||||
def ping(self):
|
||||
return True
|
|
@ -1,583 +0,0 @@
|
|||
# Copyright (c) 2013 dotCloud, Inc.
|
||||
# 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 contextlib
|
||||
import os
|
||||
import socket
|
||||
|
||||
import eventlet
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as config_fixture
|
||||
from oslo_utils import units
|
||||
|
||||
from nova.compute import manager
|
||||
from nova.compute import task_states
|
||||
from nova import context
|
||||
from nova import exception
|
||||
from nova import test
|
||||
import nova.tests.unit.image.fake
|
||||
from nova.tests.unit import matchers
|
||||
from nova.tests.unit import utils
|
||||
from nova.tests.unit.virt import test_virt_drivers
|
||||
from novadocker.tests.virt.docker import mock_client
|
||||
import novadocker.virt.docker
|
||||
from novadocker.virt.docker import driver as docker_driver
|
||||
from novadocker.virt.docker import hostinfo
|
||||
from novadocker.virt.docker import network
|
||||
|
||||
|
||||
class DockerDriverTestCase(test_virt_drivers._VirtDriverTestCase,
|
||||
test.TestCase):
|
||||
|
||||
driver_module = 'novadocker.virt.docker.DockerDriver'
|
||||
|
||||
def setUp(self):
|
||||
super(DockerDriverTestCase, self).setUp()
|
||||
|
||||
self.mock_client = mock_client.MockClient()
|
||||
self.stubs.Set(novadocker.virt.docker.driver.DockerDriver, 'docker',
|
||||
self.mock_client)
|
||||
|
||||
def fake_plug_vifs(self, instance, network_info):
|
||||
return
|
||||
|
||||
self.stubs.Set(novadocker.virt.docker.driver.DockerDriver,
|
||||
'plug_vifs',
|
||||
fake_plug_vifs)
|
||||
|
||||
def fake_attach_vifs(self, instance, network_info):
|
||||
return
|
||||
|
||||
self.stubs.Set(novadocker.virt.docker.driver.DockerDriver,
|
||||
'_attach_vifs',
|
||||
fake_attach_vifs)
|
||||
|
||||
# Note: using mock.object.path on class throws
|
||||
# errors in test_virt_drivers
|
||||
def fake_teardown_network(container_id):
|
||||
return
|
||||
|
||||
self.stubs.Set(network, 'teardown_network', fake_teardown_network)
|
||||
self.context = context.RequestContext('fake_user', 'fake_project')
|
||||
|
||||
self.connection.init_host(None)
|
||||
self.fixture = self.useFixture(
|
||||
config_fixture.Config(novadocker.virt.docker.driver.CONF))
|
||||
|
||||
def test_live_migration(self):
|
||||
self.skipTest('Live migration is not implemented.')
|
||||
|
||||
def test_driver_capabilities(self):
|
||||
self.assertFalse(self.connection.capabilities['has_imagecache'])
|
||||
self.assertFalse(self.connection.capabilities['supports_recreate'])
|
||||
|
||||
# NOTE(bcwaldon): This exists only because _get_running_instance on the
|
||||
# base class will not let us set a custom disk/container_format.
|
||||
def _get_running_instance(self, obj=True):
|
||||
instance_ref = utils.get_test_instance(obj=obj)
|
||||
network_info = utils.get_test_network_info()
|
||||
network_info[0]['network']['subnets'][0]['meta']['dhcp_server'] = \
|
||||
'1.1.1.1'
|
||||
image_info = utils.get_test_image_object(None, instance_ref)
|
||||
image_info.disk_format = 'raw'
|
||||
image_info.container_format = 'docker'
|
||||
self.connection.spawn(self.ctxt, instance_ref, image_info,
|
||||
[], 'herp', network_info=network_info)
|
||||
return instance_ref, network_info
|
||||
|
||||
@mock.patch.object(hostinfo, 'get_total_vcpus', return_value=1)
|
||||
@mock.patch.object(hostinfo, 'get_vcpus_used', return_value=0)
|
||||
def test_get_host_stats(self, mock_total, mock_used):
|
||||
memory = {
|
||||
'total': 4 * units.Mi,
|
||||
'used': 1 * units.Mi
|
||||
}
|
||||
self.mox.StubOutWithMock(socket, 'gethostname')
|
||||
self.mox.StubOutWithMock(hostinfo, 'get_memory_usage')
|
||||
socket.gethostname().AndReturn('foo')
|
||||
hostinfo.get_memory_usage().AndReturn(memory)
|
||||
socket.gethostname().AndReturn('bar')
|
||||
hostinfo.get_memory_usage().AndReturn(memory)
|
||||
self.mox.ReplayAll()
|
||||
self.assertEqual('foo',
|
||||
self.connection.get_host_stats()['host_hostname'])
|
||||
self.assertEqual('foo',
|
||||
self.connection.get_host_stats()['host_hostname'])
|
||||
|
||||
def test_get_available_resource(self):
|
||||
memory = {
|
||||
'total': 4 * units.Mi,
|
||||
'used': 1 * units.Mi
|
||||
}
|
||||
disk = {
|
||||
'total': 50 * units.Gi,
|
||||
'available': 25 * units.Gi,
|
||||
'used': 25 * units.Gi
|
||||
}
|
||||
# create the mocks
|
||||
with contextlib.nested(
|
||||
mock.patch.object(hostinfo, 'get_total_vcpus',
|
||||
return_value=1),
|
||||
mock.patch.object(hostinfo, 'get_vcpus_used',
|
||||
return_value=0),
|
||||
mock.patch.object(hostinfo, 'get_memory_usage',
|
||||
return_value=memory),
|
||||
mock.patch.object(hostinfo, 'get_disk_usage',
|
||||
return_value=disk)
|
||||
) as (
|
||||
get_total_vcpus,
|
||||
get_vcpus_used,
|
||||
get_memory_usage,
|
||||
get_disk_usage
|
||||
):
|
||||
# run the code
|
||||
stats = self.connection.get_available_resource(nodename='test')
|
||||
# make our assertions
|
||||
get_memory_usage.assert_called_once_with()
|
||||
get_disk_usage.assert_called_once_with()
|
||||
expected_stats = {
|
||||
'vcpus': 1,
|
||||
'vcpus_used': 0,
|
||||
'memory_mb': 4,
|
||||
'memory_mb_used': 1,
|
||||
'local_gb': 50L,
|
||||
'local_gb_used': 25L,
|
||||
'disk_available_least': 25L,
|
||||
'hypervisor_type': 'docker',
|
||||
'hypervisor_version': 1000,
|
||||
'hypervisor_hostname': 'test',
|
||||
'cpu_info': '?',
|
||||
'numa_topology': None,
|
||||
'supported_instances': [("i686", "docker", "exe"),
|
||||
("x86_64", "docker", "exe")]
|
||||
}
|
||||
self.assertEqual(expected_stats, stats)
|
||||
|
||||
def _test_start_container_with_network_events(self, neutron_failure=None):
|
||||
generated_events = []
|
||||
|
||||
def wait_timeout():
|
||||
event = mock.MagicMock()
|
||||
if neutron_failure == 'timeout':
|
||||
raise eventlet.timeout.Timeout()
|
||||
elif neutron_failure == 'error':
|
||||
event.status = 'failed'
|
||||
else:
|
||||
event.status = 'completed'
|
||||
return event
|
||||
|
||||
def fake_prepare(instance, event_name):
|
||||
m = mock.MagicMock()
|
||||
m.instance = instance
|
||||
m.event_name = event_name
|
||||
m.wait.side_effect = wait_timeout
|
||||
generated_events.append(m)
|
||||
return m
|
||||
|
||||
virtapi = manager.ComputeVirtAPI(mock.MagicMock())
|
||||
prepare = virtapi._compute.instance_events.prepare_for_instance_event
|
||||
prepare.side_effect = fake_prepare
|
||||
drvr = novadocker.virt.docker.driver.DockerDriver(virtapi)
|
||||
|
||||
instance_href = utils.get_test_instance()
|
||||
container_id = self.connection._find_container_by_uuid(
|
||||
instance_href['uuid']).get('id')
|
||||
|
||||
vifs = [{'id': 'vif1', 'active': False},
|
||||
{'id': 'vif2', 'active': False}]
|
||||
|
||||
@mock.patch.object(drvr, '_extract_dns_entries')
|
||||
@mock.patch.object(drvr, 'plug_vifs')
|
||||
@mock.patch.object(drvr, '_attach_vifs')
|
||||
@mock.patch.object(self.mock_client, 'start')
|
||||
@mock.patch.object(self.mock_client, 'kill')
|
||||
@mock.patch.object(self.mock_client, 'remove_container')
|
||||
def test_start(remove_container, kill, start, attach_vif, plug_vifs,
|
||||
extract_dns_entries):
|
||||
drvr._start_container(container_id, instance_href,
|
||||
vifs)
|
||||
plug_vifs.assert_called_with(instance_href, vifs)
|
||||
attach_vif.assert_called_with(instance_href, vifs)
|
||||
|
||||
if neutron_failure and cfg.CONF.vif_plugging_is_fatal:
|
||||
kill.assert_called_once_with(container_id)
|
||||
remove_container.assert_called_once_with(container_id,
|
||||
Force=True)
|
||||
test_start()
|
||||
|
||||
if nova.utils.is_neutron() and cfg.CONF.vif_plugging_timeout:
|
||||
prepare.assert_has_calls([
|
||||
mock.call(instance_href, 'network-vif-plugged-vif1'),
|
||||
mock.call(instance_href, 'network-vif-plugged-vif2')])
|
||||
for event in generated_events:
|
||||
if neutron_failure and generated_events.index(event) != 0:
|
||||
self.assertEqual(0, event.call_count)
|
||||
else:
|
||||
self.assertEqual(0, prepare.call_count)
|
||||
|
||||
@mock.patch('nova.utils.is_neutron', return_value=True)
|
||||
def test_start_container_with_network_events(self, is_neutron):
|
||||
self._test_start_container_with_network_events()
|
||||
|
||||
@mock.patch('nova.utils.is_neutron', return_value=True)
|
||||
def test_start_container_with_network_events_nowait(self, is_neutron):
|
||||
self.flags(vif_plugging_timeout=0)
|
||||
self._test_start_container_with_network_events()
|
||||
|
||||
@mock.patch('nova.utils.is_neutron', return_value=True)
|
||||
def test_start_container_with_network_events_failed_timeout_non_fatal(
|
||||
self, is_neutron):
|
||||
self.flags(vif_plugging_is_fatal=False)
|
||||
self._test_start_container_with_network_events(
|
||||
neutron_failure='timeout')
|
||||
|
||||
@mock.patch('nova.utils.is_neutron', return_value=True)
|
||||
def test_start_container_with_network_events_failed_timeout_fatal(
|
||||
self, is_neutron):
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self._test_start_container_with_network_events,
|
||||
neutron_failure='timeout')
|
||||
|
||||
@mock.patch('nova.utils.is_neutron', return_value=True)
|
||||
def test_start_container_with_network_events_failed_error_non_fatal(
|
||||
self, is_neutron):
|
||||
self.flags(vif_plugging_is_fatal=False)
|
||||
self._test_start_container_with_network_events(
|
||||
neutron_failure='error')
|
||||
|
||||
@mock.patch('nova.utils.is_neutron', return_value=True)
|
||||
def test_start_container_with_network_events_failed_error_fatal(
|
||||
self, is_neutron):
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self._test_start_container_with_network_events,
|
||||
neutron_failure='error')
|
||||
|
||||
@mock.patch('nova.utils.is_neutron', return_value=False)
|
||||
def test_start_container_with_network_events_non_neutron(self,
|
||||
is_neutron):
|
||||
self._test_start_container_with_network_events()
|
||||
|
||||
def test_create_container(self, image_info=None, instance_href=None,
|
||||
network_info=None):
|
||||
if instance_href is None:
|
||||
instance_href = utils.get_test_instance()
|
||||
if image_info is None:
|
||||
image_info = utils.get_test_image_object(None, instance_href)
|
||||
image_info.disk_format = 'raw'
|
||||
image_info.container_format = 'docker'
|
||||
self.connection.spawn(self.context, instance_href, image_info,
|
||||
'fake_files', 'fake_password',
|
||||
network_info=network_info)
|
||||
self._assert_cpu_shares(instance_href)
|
||||
self.assertEqual(self.mock_client.name, "nova-{0}".format(
|
||||
instance_href['uuid']))
|
||||
|
||||
def test_create_container_empty_cmd(self, image_info=None,
|
||||
instance_href=None,
|
||||
network_info=None):
|
||||
if instance_href is None:
|
||||
instance_href = utils.get_test_instance()
|
||||
if image_info is None:
|
||||
image_info = utils.get_test_image_object(None, instance_href)
|
||||
image_info.disk_format = 'raw'
|
||||
image_info.container_format = 'docker'
|
||||
with mock.patch.object(self.mock_client, 'create_container') as mc:
|
||||
self.connection.spawn(self.context, instance_href, image_info,
|
||||
'fake_files', 'fake_password',
|
||||
network_info=network_info)
|
||||
self.assertIsNone(mc.call_args[1].get('command'))
|
||||
|
||||
@mock.patch.object(novadocker.virt.docker.driver.DockerDriver,
|
||||
'_inject_key', return_value='/tmp/.ssh')
|
||||
def test_create_container_inject_key(self, mock_inject_key):
|
||||
self.fixture.config(inject_key=True, group='docker')
|
||||
instance_href = utils.get_test_instance()
|
||||
instance_href.key_data = 'my_key'
|
||||
image_info = utils.get_test_image_object(None, instance_href)
|
||||
image_info.disk_format = 'raw'
|
||||
image_info.container_format = 'docker'
|
||||
with mock.patch.object(self.mock_client, 'create_container'):
|
||||
with mock.patch.object(self.mock_client, 'start') as ms:
|
||||
self.connection.spawn(self.context, instance_href, image_info,
|
||||
'fake_files', 'fake_password',
|
||||
network_info=None)
|
||||
command = ms.call_args[1]
|
||||
expected = {'binds': {'/tmp/.ssh':
|
||||
{'bind': '/root/.ssh', 'ro': True}},
|
||||
'dns': None,
|
||||
'privileged': False}
|
||||
self.assertEqual(expected, command)
|
||||
|
||||
def test_create_container_glance_cmd(self, image_info=None,
|
||||
instance_href=None,
|
||||
network_info=None):
|
||||
if instance_href is None:
|
||||
instance_href = utils.get_test_instance()
|
||||
if image_info is None:
|
||||
image_info = utils.get_test_image_object(None, instance_href)
|
||||
image_info.disk_format = 'raw'
|
||||
image_info.container_format = 'docker'
|
||||
image_info.properties.os_command_line = 'uname'
|
||||
with mock.patch.object(self.mock_client, 'create_container') as mc:
|
||||
self.connection.spawn(self.context, instance_href, image_info,
|
||||
'fake_files', 'fake_password',
|
||||
network_info=network_info)
|
||||
command = mc.call_args[1]['command']
|
||||
self.assertEqual('uname', command)
|
||||
|
||||
def test_create_container_vcpus_2(self, image_info=None):
|
||||
flavor = utils.get_test_flavor(options={
|
||||
'name': 'vcpu_2',
|
||||
'flavorid': 'vcpu_2',
|
||||
'vcpus': 2
|
||||
})
|
||||
instance_href = utils.get_test_instance(flavor=flavor)
|
||||
if image_info is None:
|
||||
image_info = utils.get_test_image_object(None, instance_href)
|
||||
image_info.disk_format = 'raw'
|
||||
image_info.container_format = 'docker'
|
||||
self.connection.spawn(self.context, instance_href, image_info,
|
||||
'fake_files', 'fake_password')
|
||||
self._assert_cpu_shares(instance_href, vcpus=2)
|
||||
self.assertEqual(self.mock_client.name, "nova-{0}".format(
|
||||
instance_href['uuid']))
|
||||
|
||||
def _assert_cpu_shares(self, instance_href, vcpus=4):
|
||||
container_id = self.connection._find_container_by_uuid(
|
||||
instance_href['uuid']).get('id')
|
||||
container_info = self.connection.docker.inspect_container(container_id)
|
||||
self.assertEqual(vcpus * 1024, container_info['Config']['CpuShares'])
|
||||
|
||||
@mock.patch('novadocker.virt.docker.driver.DockerDriver.plug_vifs',
|
||||
side_effect=Exception)
|
||||
def test_create_container_net_setup_fails(self, mock_plug_vifs):
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.test_create_container,
|
||||
network_info=utils.get_test_network_info())
|
||||
self.assertEqual(0, len(self.mock_client.containers()))
|
||||
|
||||
def test_create_container_wrong_image(self):
|
||||
instance_href = utils.get_test_instance()
|
||||
image_info = utils.get_test_image_object(None, instance_href)
|
||||
image_info.disk_format = 'raw'
|
||||
image_info.container_format = 'invalid_format'
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
self.test_create_container,
|
||||
image_info, instance_href)
|
||||
|
||||
@mock.patch.object(novadocker.virt.docker.driver.DockerDriver,
|
||||
'cleanup')
|
||||
@mock.patch.object(novadocker.virt.docker.driver.DockerDriver,
|
||||
'_find_container_by_uuid',
|
||||
return_value={'id': 'fake_id'})
|
||||
def test_destroy_container(self, byuuid_mock, cleanup_mock):
|
||||
instance = utils.get_test_instance()
|
||||
self.connection.destroy(self.context, instance, 'fake_networkinfo')
|
||||
cleanup_mock.assert_called_with(self.context, instance,
|
||||
'fake_networkinfo', None, True)
|
||||
|
||||
@mock.patch.object(network, 'teardown_network')
|
||||
@mock.patch.object(novadocker.virt.docker.driver.DockerDriver,
|
||||
'unplug_vifs')
|
||||
@mock.patch.object(novadocker.virt.docker.driver.DockerDriver,
|
||||
'_find_container_by_uuid',
|
||||
return_value={'id': 'fake_id'})
|
||||
def test_cleanup_container(self, byuuid_mock, unplug_mock, teardown_mock):
|
||||
instance = utils.get_test_instance()
|
||||
self.connection.cleanup(self.context, instance, 'fake_networkinfo')
|
||||
byuuid_mock.assert_called_with(instance['uuid'])
|
||||
teardown_mock.assert_called_with('fake_id')
|
||||
|
||||
@mock.patch.object(novadocker.virt.docker.driver.DockerDriver,
|
||||
'unplug_vifs')
|
||||
@mock.patch.object(novadocker.virt.docker.driver.DockerDriver,
|
||||
'_find_container_by_uuid',
|
||||
return_value={})
|
||||
def test_cleanup_container_notfound(self, byuuid_mock, unplug_mock):
|
||||
instance = utils.get_test_instance()
|
||||
self.connection.cleanup(self.context, instance, 'fake_networkinfo')
|
||||
byuuid_mock.assert_called_with(instance['uuid'])
|
||||
unplug_mock.assert_called_once_with(instance, 'fake_networkinfo')
|
||||
|
||||
def test_soft_delete_restore_container(self):
|
||||
instance_href = utils.get_test_instance()
|
||||
image_info = utils.get_test_image_object(None, instance_href)
|
||||
image_info.disk_format = 'raw'
|
||||
image_info.container_format = 'docker'
|
||||
|
||||
self.connection.spawn(self.context, instance_href, image_info,
|
||||
'fake_files', 'fake_password')
|
||||
container_id = self.connection._find_container_by_uuid(
|
||||
instance_href['uuid']).get('id')
|
||||
|
||||
self.connection.soft_delete(instance_href)
|
||||
info = self.connection.docker.inspect_container(container_id)
|
||||
self.assertFalse(info['State']['Running'])
|
||||
|
||||
self.connection.restore(instance_href)
|
||||
info = self.connection.docker.inspect_container(container_id)
|
||||
self.assertTrue(info['State']['Running'])
|
||||
|
||||
def test_get_memory_limit_from_sys_meta_in_object(self):
|
||||
instance = utils.get_test_instance(obj=True)
|
||||
limit = self.connection._get_memory_limit_bytes(instance)
|
||||
self.assertEqual(512 * units.Mi, limit)
|
||||
|
||||
def test_get_memory_limit_from_sys_meta_in_db_instance(self):
|
||||
instance = utils.get_test_instance(obj=False)
|
||||
limit = self.connection._get_memory_limit_bytes(instance)
|
||||
self.assertEqual(2048 * units.Mi, limit)
|
||||
|
||||
def test_list_instances(self):
|
||||
instance_href = utils.get_test_instance()
|
||||
image_info = utils.get_test_image_object(None, instance_href)
|
||||
image_info.disk_format = 'raw'
|
||||
image_info.container_format = 'docker'
|
||||
self.connection.spawn(self.context, instance_href, image_info,
|
||||
'fake_files', 'fake_password')
|
||||
|
||||
instances = self.connection.list_instances()
|
||||
self.assertIn(instance_href.name, instances)
|
||||
|
||||
def test_list_instances_none(self):
|
||||
instances = self.connection.list_instances()
|
||||
self.assertIsInstance(instances, list)
|
||||
self.assertFalse(instances)
|
||||
|
||||
def test_list_instances_no_inspect_race(self):
|
||||
"""Assures containers that cannot be inspected are not listed."""
|
||||
instance_href = utils.get_test_instance()
|
||||
image_info = utils.get_test_image_object(None, instance_href)
|
||||
image_info.disk_format = 'raw'
|
||||
image_info.container_format = 'docker'
|
||||
self.connection.spawn(self.context, instance_href, image_info,
|
||||
'fake_files', 'fake_password')
|
||||
|
||||
with mock.patch.object(self.mock_client, 'inspect_container',
|
||||
return_value={}):
|
||||
instances = self.connection.list_instances()
|
||||
self.assertFalse(instances)
|
||||
|
||||
def test_find_container_pid(self):
|
||||
driver = novadocker.virt.docker.driver.DockerDriver(None)
|
||||
with mock.patch.object(driver.docker,
|
||||
"inspect_container") as inspect_container:
|
||||
inspect_container.return_value = {'State': {'Pid': '12345'}}
|
||||
pid = driver._find_container_pid("fake_container_id")
|
||||
self.assertEqual(pid, '12345')
|
||||
|
||||
def test_get_console_output_with_no_container(self):
|
||||
driver = novadocker.virt.docker.driver.DockerDriver(None)
|
||||
with mock.patch.object(driver,
|
||||
"_get_container_id") as get_container_id:
|
||||
get_container_id.return_value = None
|
||||
logs = driver.get_console_output(None, None)
|
||||
self.assertEqual('', logs)
|
||||
|
||||
@mock.patch.object(novadocker.virt.docker.driver.DockerDriver,
|
||||
'_find_container_by_uuid',
|
||||
return_value={'id': 'fake_id'})
|
||||
def test_snapshot(self, byuuid_mock):
|
||||
# Use mix-case to test that mixed-case image names succeed.
|
||||
snapshot_name = 'tEsT-SnAp'
|
||||
|
||||
expected_calls = [
|
||||
{'args': (),
|
||||
'kwargs':
|
||||
{'task_state': task_states.IMAGE_PENDING_UPLOAD}},
|
||||
{'args': (),
|
||||
'kwargs':
|
||||
{'task_state': task_states.IMAGE_UPLOADING,
|
||||
'expected_state': task_states.IMAGE_PENDING_UPLOAD}}]
|
||||
func_call_matcher = matchers.FunctionCallMatcher(expected_calls)
|
||||
|
||||
instance_ref = utils.get_test_instance()
|
||||
properties = {'instance_id': instance_ref['id'],
|
||||
'user_id': str(self.context.user_id)}
|
||||
sent_meta = {'name': snapshot_name, 'is_public': False,
|
||||
'status': 'creating', 'properties': properties}
|
||||
|
||||
# Because the docker driver doesn't push directly into Glance, we
|
||||
# cannot check that the images are correctly configured in the
|
||||
# fake image service, but we can ensuring naming and other
|
||||
# conventions are accurate.
|
||||
image_service = nova.tests.unit.image.fake.FakeImageService()
|
||||
recv_meta = image_service.create(context, sent_meta)
|
||||
|
||||
with mock.patch.object(self.mock_client, 'load_image'):
|
||||
with mock.patch.object(self.mock_client, 'get_image'):
|
||||
self.connection.snapshot(self.context, instance_ref,
|
||||
recv_meta['id'],
|
||||
func_call_matcher.call)
|
||||
|
||||
snapshot = image_service.show(context, recv_meta['id'])
|
||||
self.assertEqual(snapshot['properties']['image_state'],
|
||||
'available')
|
||||
self.assertEqual(snapshot['status'], 'active')
|
||||
self.assertEqual(snapshot['disk_format'], 'raw')
|
||||
self.assertEqual(snapshot['container_format'], 'docker')
|
||||
self.assertEqual(snapshot['name'], snapshot_name)
|
||||
|
||||
def test_get_image_name(self):
|
||||
instance_ref = utils.get_test_instance()
|
||||
image_info = utils.get_test_image_object(None, instance_ref)
|
||||
image_info.container_format = 'docker'
|
||||
image_info.name = 'MiXeDcAsE-image'
|
||||
repo = self.connection._get_image_name(self.context,
|
||||
instance_ref, image_info)
|
||||
|
||||
# image_name = repo.split("/")[1]
|
||||
self.assertEqual(image_info.name, repo)
|
||||
|
||||
def test_get_host_uptime_returns_exec_result(self):
|
||||
result = '4294967296'
|
||||
with mock.patch('nova.utils.execute',
|
||||
return_value=(result, None)):
|
||||
uptime = self.connection.get_host_uptime()
|
||||
self.assertEqual(result, uptime)
|
||||
|
||||
def test_get_dns_entries(self):
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
network_info = utils.get_test_network_info()
|
||||
self.assertEqual(['192.168.122.1', '192.168.122.2'],
|
||||
driver._extract_dns_entries(network_info))
|
||||
|
||||
@mock.patch.object(os.path, 'exists', return_value=True)
|
||||
def test_pull_missing_image_with_shared_dir(self, mock_os):
|
||||
self.fixture.config(shared_directory='/fake_dir', group='docker')
|
||||
instance_ref = utils.get_test_instance()
|
||||
image_info = utils.get_test_image_object(None, instance_ref)
|
||||
image_info.name = 'fake_name'
|
||||
image_info.id = 'fake_id'
|
||||
with mock.patch.object(self.mock_client, 'load_repository_file') as f:
|
||||
with mock.patch.object(self.mock_client, 'inspect_image') as i:
|
||||
i.return_value = 'fake_image'
|
||||
image = self.connection._pull_missing_image(self.context,
|
||||
image_info,
|
||||
instance_ref)
|
||||
f.assert_called_once_with('fake_name', '/fake_dir/fake_id')
|
||||
i.assert_called_once_with('fake_name')
|
||||
self.assertEqual('fake_image', image)
|
||||
|
||||
def test_find_container_by_uuid(self):
|
||||
instance_href = utils.get_test_instance()
|
||||
image_info = utils.get_test_image_object(None, instance_href)
|
||||
image_info.disk_format = 'raw'
|
||||
image_info.container_format = 'docker'
|
||||
self.connection.spawn(self.context, instance_href, image_info,
|
||||
'fake_files', 'fake_password')
|
||||
info = self.connection._find_container_by_uuid(instance_href['uuid'])
|
||||
self.assertEqual(instance_href['name'], info['Config'].get('Hostname'))
|
|
@ -1,111 +0,0 @@
|
|||
# Copyright 2014 IBM Corp.
|
||||
# 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 mock
|
||||
|
||||
from nova.network import model as network_model
|
||||
from nova import test
|
||||
from nova.virt import firewall
|
||||
from novadocker.virt.docker import driver
|
||||
|
||||
|
||||
class DockerFirewallDriverTestCase(test.TestCase):
|
||||
|
||||
REQUIRES_LOCKING = True
|
||||
gateway_bridge_4 = network_model.IP(address='10.11.12.1', type='gateway')
|
||||
dns_bridge_4 = network_model.IP(address='8.8.8.8', type=None)
|
||||
ips_bridge_4 = [network_model.IP(address='101.168.1.9', type='fixed',
|
||||
version=4)]
|
||||
|
||||
subnet_bridge_4 = network_model.Subnet(cidr='10.11.1.0/24',
|
||||
dns=[dns_bridge_4],
|
||||
gateway=gateway_bridge_4,
|
||||
ips=ips_bridge_4,
|
||||
routes=None)
|
||||
|
||||
network_bridge = network_model.Network(id='network-id-xxx-yyy-zzz',
|
||||
bridge='br100',
|
||||
label=None,
|
||||
subnets=[subnet_bridge_4],
|
||||
bridge_interface='eth0')
|
||||
|
||||
vif_bridge = network_model.VIF(id='920be2f4-2b98-411e-890a-69bcabb2a5a0',
|
||||
address='00:11:22:33:44:55',
|
||||
network=network_bridge,
|
||||
type=network_model.VIF_TYPE_BRIDGE)
|
||||
|
||||
def setUp(self):
|
||||
super(DockerFirewallDriverTestCase, self).setUp()
|
||||
self.driver = driver.DockerDriver(None)
|
||||
|
||||
@mock.patch.object(firewall.NoopFirewallDriver, 'prepare_instance_filter',
|
||||
create=True)
|
||||
@mock.patch.object(firewall.NoopFirewallDriver, 'setup_basic_filtering',
|
||||
create=True)
|
||||
@mock.patch.object(firewall.NoopFirewallDriver, 'apply_instance_filter',
|
||||
create=True)
|
||||
def test_start_firewall(self, mock_aif, mock_sbf, mock_pif):
|
||||
fake_inst = 'fake-inst'
|
||||
fake_net_info = mock.ANY
|
||||
self.driver._start_firewall(fake_inst, fake_net_info)
|
||||
|
||||
mock_aif.assert_called_once_with(fake_inst, fake_net_info)
|
||||
mock_sbf.assert_called_once_with(fake_inst, fake_net_info)
|
||||
mock_pif.assert_called_once_with(fake_inst, fake_net_info)
|
||||
|
||||
@mock.patch.object(firewall.NoopFirewallDriver, 'unfilter_instance',
|
||||
create=True)
|
||||
def test_stop_firewall(self, mock_ui):
|
||||
fake_inst = 'fake-inst'
|
||||
fake_net_info = mock.ANY
|
||||
self.driver._stop_firewall(fake_inst, fake_net_info)
|
||||
mock_ui.assert_called_once_with(fake_inst, fake_net_info)
|
||||
|
||||
@mock.patch.object(firewall.NoopFirewallDriver, 'prepare_instance_filter',
|
||||
create=True)
|
||||
@mock.patch.object(firewall.NoopFirewallDriver, 'setup_basic_filtering',
|
||||
create=True)
|
||||
@mock.patch.object(firewall.NoopFirewallDriver, 'apply_instance_filter',
|
||||
create=True)
|
||||
def test_plug_vifs_bridge(self, mock_aif, mock_sbf, mock_pif):
|
||||
fake_net_info = [self.vif_bridge]
|
||||
with mock.patch('nova.utils.execute'):
|
||||
d = driver.DockerDriver(object)
|
||||
fake_inst = {'name': 'fake_instance'}
|
||||
d.plug_vifs(fake_inst, fake_net_info)
|
||||
mock_aif.assert_called_once_with(fake_inst, fake_net_info)
|
||||
mock_sbf.assert_called_once_with(fake_inst, fake_net_info)
|
||||
mock_pif.assert_called_once_with(fake_inst, fake_net_info)
|
||||
|
||||
@mock.patch.object(firewall.NoopFirewallDriver, 'unfilter_instance',
|
||||
create=True)
|
||||
def test_unplug_vifs_ovs(self, mock_ui):
|
||||
iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
|
||||
fake_net_info = [
|
||||
{'network': {'bridge': 'br-int',
|
||||
'subnets': [{'gateway': {'address': '10.11.12.1'},
|
||||
'cidr': '10.11.12.0/24',
|
||||
'ips': [{'address': '10.11.12.3',
|
||||
'type': 'fixed', 'version': 4}]
|
||||
}]},
|
||||
'devname': 'tap920be2f4-2b',
|
||||
'address': '00:11:22:33:44:55',
|
||||
'id': iface_id,
|
||||
'type': network_model.VIF_TYPE_OVS}]
|
||||
with mock.patch('nova.utils.execute'):
|
||||
d = driver.DockerDriver(object)
|
||||
fake_inst = {'name': 'fake_instance', 'uuid': 'instance_uuid'}
|
||||
d.unplug_vifs(fake_inst, fake_net_info)
|
||||
mock_ui.assert_called_once_with(fake_inst, fake_net_info)
|
|
@ -1,87 +0,0 @@
|
|||
# Copyright (c) 2013 dotCloud, Inc.
|
||||
# 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 mock
|
||||
import multiprocessing
|
||||
|
||||
from nova import test
|
||||
from novadocker.virt.docker import hostinfo
|
||||
import psutil
|
||||
|
||||
|
||||
class HostInfoTestCase(test.NoDBTestCase):
|
||||
|
||||
_FAKE_DISK_INFO = {'total_size': 100000,
|
||||
'free_size': 50000,
|
||||
'used_size': 50000}
|
||||
|
||||
def setUp(self):
|
||||
super(HostInfoTestCase, self).setUp()
|
||||
self.stubs.Set(hostinfo, 'statvfs', self.statvfs)
|
||||
|
||||
def statvfs(self):
|
||||
diskinfo = psutil.namedtuple('usage', ('total', 'free', 'used'))
|
||||
return diskinfo(self._FAKE_DISK_INFO['total_size'],
|
||||
self._FAKE_DISK_INFO['free_size'],
|
||||
self._FAKE_DISK_INFO['used_size'])
|
||||
|
||||
def test_get_disk_usage(self):
|
||||
disk_usage = hostinfo.get_disk_usage()
|
||||
self.assertEqual(disk_usage['total'],
|
||||
self._FAKE_DISK_INFO['total_size'])
|
||||
self.assertEqual(disk_usage['available'],
|
||||
self._FAKE_DISK_INFO['free_size'])
|
||||
self.assertEqual(disk_usage['used'],
|
||||
self._FAKE_DISK_INFO['used_size'])
|
||||
|
||||
@mock.patch.object(multiprocessing, 'cpu_count')
|
||||
def test_get_total_vcpus(self, mock_cpu_count):
|
||||
mock_cpu_count.return_value = 1
|
||||
|
||||
cpu_count = hostinfo.get_total_vcpus()
|
||||
|
||||
self.assertEqual(mock_cpu_count.return_value, cpu_count)
|
||||
|
||||
def test_get_memory_usage(self):
|
||||
fake_total_memory = 4096
|
||||
fake_used_memory = 2048
|
||||
|
||||
with mock.patch.object(psutil,
|
||||
'virtual_memory') as mock_virtual_memory:
|
||||
mock_virtual_memory.return_value.total = fake_total_memory
|
||||
mock_virtual_memory.return_value.used = fake_used_memory
|
||||
|
||||
usage = hostinfo.get_memory_usage()
|
||||
|
||||
self.assertEqual(fake_total_memory, usage['total'])
|
||||
self.assertEqual(fake_used_memory, usage['used'])
|
||||
|
||||
@mock.patch('novadocker.virt.docker.hostinfo.get_mounts')
|
||||
def test_find_cgroup_devices_path_centos(self, mock):
|
||||
mock.return_value = [
|
||||
'none /sys/fs/cgroup cgroup rw,relatime,perf_event,'
|
||||
'blkio,net_cls,freezer,devices,memory,cpuacct,cpu,'
|
||||
'cpuset 0 0']
|
||||
path = hostinfo.get_cgroup_devices_path()
|
||||
self.assertEqual('/sys/fs/cgroup', path)
|
||||
|
||||
@mock.patch('novadocker.virt.docker.hostinfo.get_mounts')
|
||||
def test_find_cgroup_devices_path_ubuntu(self, mock):
|
||||
mock.return_value = [
|
||||
'cgroup /cgroup tmpfs rw,relatime,mode=755 0 0',
|
||||
'cgroup /cgroup/devices cgroup rw,relatime,devices,' +
|
||||
'clone_children 0 0']
|
||||
path = hostinfo.get_cgroup_devices_path()
|
||||
self.assertEqual('/cgroup/devices', path)
|
|
@ -1,98 +0,0 @@
|
|||
# Copyright 2014 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 uuid
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova import utils
|
||||
|
||||
from nova.tests.unit import utils as test_utils
|
||||
|
||||
from novadocker.virt.docker import network
|
||||
|
||||
import mock
|
||||
|
||||
|
||||
class NetworkTestCase(test.NoDBTestCase):
|
||||
@mock.patch.object(network, 'os')
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_teardown_delete_network(self, utils_mock, mock_os):
|
||||
id = "second-id"
|
||||
utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None)
|
||||
network.teardown_network(id)
|
||||
utils_mock.assert_called_with('ip', 'netns', 'delete', id,
|
||||
run_as_root=True)
|
||||
|
||||
@mock.patch.object(network, 'os')
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_teardown_network_not_in_list(self, utils_mock, mock_os):
|
||||
utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None)
|
||||
network.teardown_network("not-in-list")
|
||||
utils_mock.assert_called_with('ip', '-o', 'netns', 'list')
|
||||
|
||||
@mock.patch.object(network, 'os')
|
||||
@mock.patch.object(utils, 'execute')
|
||||
def test_teardown_network_hyperv(self, utils_mock, mock_os):
|
||||
mock_os.name = 'nt'
|
||||
network.teardown_network("fake_id")
|
||||
self.assertFalse(utils_mock.execute.called)
|
||||
|
||||
@mock.patch.object(network, 'os')
|
||||
@mock.patch.object(network, 'LOG')
|
||||
@mock.patch.object(utils, 'execute',
|
||||
side_effect=processutils.ProcessExecutionError)
|
||||
def test_teardown_network_fails(self, utils_mock, log_mock, mock_os):
|
||||
# Call fails but method should not fail.
|
||||
# Error will be caught and logged.
|
||||
utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None)
|
||||
id = "third-id"
|
||||
network.teardown_network(id)
|
||||
log_mock.warning.assert_called_with(mock.ANY, id)
|
||||
|
||||
def test_find_gateway(self):
|
||||
instance = {'uuid': uuid.uuid4()}
|
||||
network_info = test_utils.get_test_network_info()
|
||||
first_net = network_info[0]['network']
|
||||
first_net['subnets'][0]['gateway']['address'] = '10.0.0.1'
|
||||
self.assertEqual('10.0.0.1', network.find_gateway(instance, first_net))
|
||||
|
||||
def test_cannot_find_gateway(self):
|
||||
instance = {'uuid': uuid.uuid4()}
|
||||
network_info = test_utils.get_test_network_info()
|
||||
first_net = network_info[0]['network']
|
||||
first_net['subnets'] = []
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
network.find_gateway, instance, first_net)
|
||||
|
||||
def test_find_fixed_ip(self):
|
||||
instance = {'uuid': uuid.uuid4()}
|
||||
network_info = test_utils.get_test_network_info()
|
||||
first_net = network_info[0]['network']
|
||||
first_net['subnets'][0]['cidr'] = '10.0.0.0/24'
|
||||
first_net['subnets'][0]['ips'][0]['type'] = 'fixed'
|
||||
first_net['subnets'][0]['ips'][0]['address'] = '10.0.1.13'
|
||||
self.assertEqual('10.0.1.13/24', network.find_fixed_ip(instance,
|
||||
first_net))
|
||||
|
||||
def test_cannot_find_fixed_ip(self):
|
||||
instance = {'uuid': uuid.uuid4()}
|
||||
network_info = test_utils.get_test_network_info()
|
||||
first_net = network_info[0]['network']
|
||||
first_net['subnets'] = []
|
||||
self.assertRaises(exception.InstanceDeployFailure,
|
||||
network.find_fixed_ip, instance, first_net)
|
|
@ -1,165 +0,0 @@
|
|||
# Copyright (C) 2014 Juniper Networks, Inc
|
||||
# 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 mock
|
||||
import sys
|
||||
|
||||
|
||||
class MockContrailVRouterApi(mock.MagicMock):
|
||||
def __init__(self, *a, **kw):
|
||||
super(MockContrailVRouterApi, self).__init__()
|
||||
self._args = a
|
||||
self._dict = kw
|
||||
self._vifs = {}
|
||||
|
||||
def add_port(self, vm_uuid_str, vif_uuid_str, interface_name,
|
||||
mac_address, **kwargs):
|
||||
if vm_uuid_str not in self._vifs:
|
||||
self._vifs[vif_uuid_str] = dict(vm=vm_uuid_str,
|
||||
intf=interface_name,
|
||||
mac=mac_address)
|
||||
self._vifs[vm_uuid_str].update(kwargs)
|
||||
return self._vifs[vif_uuid_str]
|
||||
|
||||
def delete_port(self, vif_uuid_str):
|
||||
if vif_uuid_str in self._vifs:
|
||||
del self._vifs[vif_uuid_str]
|
||||
|
||||
|
||||
mock_pkg = mock.MagicMock(name='mock_contrail_vrouter_api')
|
||||
mock_mod = mock.MagicMock(name='mock_vrouter_api')
|
||||
mock_cls = mock.MagicMock(name='MockOpenContrailVIFDriver',
|
||||
side_effect=MockContrailVRouterApi)
|
||||
mock_mod.OpenContrailVIFDriver = mock_cls
|
||||
mock_pkg.vrouter_api = mock_mod
|
||||
|
||||
sys.modules['contrail_vrouter_api'] = mock_pkg
|
||||
sys.modules['contrail_vrouter_api.vrouter_api'] = mock_mod
|
||||
|
||||
from nova.network import model as network_model
|
||||
from nova import test
|
||||
from novadocker.virt.docker import driver as docker_driver
|
||||
|
||||
|
||||
class DockerOpenContrailVIFDriverTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(DockerOpenContrailVIFDriverTestCase, self).setUp()
|
||||
docker_driver.CONF.set_override(
|
||||
'vif_driver',
|
||||
'novadocker.virt.docker.opencontrail.OpenContrailVIFDriver',
|
||||
group='docker')
|
||||
|
||||
def test_plug_vrouter(self):
|
||||
vid = '920be1f4-2b98-411e-890a-69bcabb2a5a0'
|
||||
address = '10.1.2.1'
|
||||
calls = [
|
||||
mock.call('ip', 'link', 'add', 'veth%s' % vid[:8],
|
||||
'type', 'veth', 'peer', 'name',
|
||||
'ns%s' % vid[:8], run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'ns%s' % vid[:8],
|
||||
'address', address, run_as_root=True),
|
||||
]
|
||||
network_info = [network_model.VIF(id=vid, address=address)]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.plug_vifs({'name': 'fake_instance'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
def test_plug_two_vrouters(self):
|
||||
vid1 = '921be2f4-2b98-411e-890a-69bcabb2a5a1'
|
||||
address1 = '10.1.2.2'
|
||||
vid2 = '922be3f4-2b98-411e-890a-69bcabb2a5a2'
|
||||
address2 = '10.1.2.3'
|
||||
calls = [
|
||||
mock.call('ip', 'link', 'add', 'veth%s' % vid1[:8],
|
||||
'type', 'veth', 'peer', 'name',
|
||||
'ns%s' % vid1[:8], run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'ns%s' % vid1[:8],
|
||||
'address', address1, run_as_root=True),
|
||||
mock.call('ip', 'link', 'add', 'veth%s' % vid2[:8],
|
||||
'type', 'veth', 'peer', 'name',
|
||||
'ns%s' % vid2[:8], run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'ns%s' % vid2[:8],
|
||||
'address', address2, run_as_root=True),
|
||||
]
|
||||
network_info = [network_model.VIF(id=vid1, address=address1),
|
||||
network_model.VIF(id=vid2, address=address2)]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.plug_vifs({'name': 'fake_instance'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
@mock.patch.object(docker_driver.DockerDriver,
|
||||
'_find_container_by_uuid',
|
||||
return_value={'id': 'my_vm'})
|
||||
@mock.patch.object(docker_driver.DockerDriver,
|
||||
'_find_container_pid',
|
||||
return_value=7890)
|
||||
def test_attach_vrouter(self, mock_find_by_uuid, mock_find_pid):
|
||||
vid = '920be1f5-2b98-411e-890a-69bcabb2a5a0'
|
||||
if_remote_name = 'ns%s' % vid[:8]
|
||||
if_local_name = 'veth%s' % vid[:8]
|
||||
address = '10.1.2.1'
|
||||
gateway = '1.1.1.254'
|
||||
fixed_ip = '1.1.1.42/24'
|
||||
calls = [
|
||||
mock.call('mkdir', '-p', '/var/run/netns', run_as_root=True),
|
||||
mock.call('ln', '-sf', '/proc/7890/ns/net',
|
||||
'/var/run/netns/my_vm', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'my_vm', 'ip', 'link',
|
||||
'set', 'lo', 'up', run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', if_remote_name, 'netns', 'my_vm',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', if_local_name, 'up',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'my_vm', 'ip', 'link',
|
||||
'set', if_remote_name, 'address', address,
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'my_vm', 'ifconfig',
|
||||
if_remote_name, fixed_ip, run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'my_vm', 'ip',
|
||||
'route', 'replace', 'default', 'via', gateway,
|
||||
'dev', if_remote_name, run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'my_vm', 'ip', 'link',
|
||||
'set', if_remote_name, 'up', run_as_root=True)
|
||||
]
|
||||
network_info = [network_model.VIF(id=vid, address=address,
|
||||
network=network_model.Network(
|
||||
id='virtual-network-1',
|
||||
subnets=[network_model.Subnet(
|
||||
cidr='1.1.1.0/24',
|
||||
gateway=network_model.IP(
|
||||
address=gateway,
|
||||
type='gateway'),
|
||||
ips=[network_model.IP(
|
||||
address='1.1.1.42',
|
||||
type='fixed',
|
||||
version=4)]
|
||||
)]
|
||||
))]
|
||||
instance = dict(name='fake_instance', display_name='fake_vm',
|
||||
hostname='fake_vm', host='linux',
|
||||
project_id='e2d2ddc6-4e0f-4cd4-b846-3bad53093ec6',
|
||||
uuid='d4b817fb-7885-4649-bad7-89302dde12e1')
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver._attach_vifs(instance, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
def test_unplug_vrouter(self):
|
||||
vid = '920be1f4-2b98-411e-890a-69bcabb2a5a0'
|
||||
address = '10.1.2.1'
|
||||
network_info = [network_model.VIF(id=vid, address=address)]
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.unplug_vifs({'name': 'fake_instance'}, network_info)
|
|
@ -1,488 +0,0 @@
|
|||
# Copyright (c) 2013 VMware, Inc.
|
||||
# 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 mock
|
||||
|
||||
from nova.network import model as network_model
|
||||
from nova import test
|
||||
from novadocker.virt.docker import driver as docker_driver
|
||||
from novadocker.virt.docker import vifs
|
||||
|
||||
|
||||
class DockerGenericVIFDriverTestCase(test.TestCase):
|
||||
|
||||
REQUIRES_LOCKING = True
|
||||
gateway_bridge_4 = network_model.IP(address='10.11.12.1', type='gateway')
|
||||
dns_bridge_4 = network_model.IP(address='8.8.8.8', type=None)
|
||||
ips_bridge_4 = [network_model.IP(address='101.168.1.9', type='fixed',
|
||||
version=4)]
|
||||
|
||||
subnet_bridge_4 = network_model.Subnet(cidr='10.11.1.0/24',
|
||||
dns=[dns_bridge_4],
|
||||
gateway=gateway_bridge_4,
|
||||
ips=ips_bridge_4,
|
||||
routes=None)
|
||||
|
||||
network_bridge = network_model.Network(id='network-id-xxx-yyy-zzz',
|
||||
bridge='br100',
|
||||
label=None,
|
||||
subnets=[subnet_bridge_4],
|
||||
bridge_interface='eth0')
|
||||
|
||||
vif_bridge = network_model.VIF(id='920be2f4-2b98-411e-890a-69bcabb2a5a0',
|
||||
address='00:11:22:33:44:55',
|
||||
network=network_bridge,
|
||||
type=network_model.VIF_TYPE_BRIDGE)
|
||||
|
||||
network_iovisor = network_model.Network(id='network-id-xxx-yyy-zzz',
|
||||
bridge='br100',
|
||||
label='net1',
|
||||
subnets=[subnet_bridge_4],
|
||||
bridge_interface='eth0')
|
||||
|
||||
vif_iovisor = network_model.VIF(id='920be2f4-2b98-411e-890a-69bcabb2a5a0',
|
||||
address='00:11:22:33:44:55',
|
||||
network=network_iovisor,
|
||||
type=network_model.VIF_TYPE_IOVISOR)
|
||||
|
||||
def setUp(self):
|
||||
super(DockerGenericVIFDriverTestCase, self).setUp()
|
||||
|
||||
def fake_fe_random_mac(self):
|
||||
return 'fe:16:3e:ff:ff:ff'
|
||||
|
||||
self.stubs.Set(vifs.DockerGenericVIFDriver,
|
||||
'_fe_random_mac',
|
||||
fake_fe_random_mac)
|
||||
|
||||
def test_plug_vifs_bridge(self):
|
||||
calls = [
|
||||
mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b',
|
||||
'type', 'veth', 'peer', 'name', 'ns920be2f4-2b',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'address',
|
||||
'fe:16:3e:ff:ff:ff', run_as_root=True),
|
||||
mock.call('brctl', 'addif', 'br100', 'tap920be2f4-2b',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
|
||||
run_as_root=True)
|
||||
]
|
||||
network_info = [self.vif_bridge]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.plug_vifs({'name': 'fake_instance'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
def test_plug_vifs_bridge_two_interfaces(self):
|
||||
calls = [
|
||||
# interface 1
|
||||
mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b',
|
||||
'type', 'veth', 'peer', 'name', 'ns920be2f4-2b',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'address',
|
||||
'fe:16:3e:ff:ff:ff', run_as_root=True),
|
||||
mock.call('brctl', 'addif', 'br100', 'tap920be2f4-2b',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
|
||||
run_as_root=True),
|
||||
# interface 2
|
||||
mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b',
|
||||
'type', 'veth', 'peer', 'name', 'ns920be2f4-2b',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'address',
|
||||
'fe:16:3e:ff:ff:ff', run_as_root=True),
|
||||
mock.call('brctl', 'addif', 'br100', 'tap920be2f4-2b',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
|
||||
run_as_root=True),
|
||||
]
|
||||
network_info = [self.vif_bridge, self.vif_bridge]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.plug_vifs({'name': 'fake_instance'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
def test_plug_vifs_ovs(self):
|
||||
iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
|
||||
calls = [
|
||||
mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b',
|
||||
'type', 'veth', 'peer', 'name', 'ns920be2f4-2b',
|
||||
run_as_root=True),
|
||||
|
||||
mock.call('ovs-vsctl', '--timeout=120', '--', '--if-exists',
|
||||
'del-port', 'tap920be2f4-2b', '--', 'add-port',
|
||||
'br-int', 'tap920be2f4-2b',
|
||||
'--', 'set', 'Interface', 'tap920be2f4-2b',
|
||||
'external-ids:iface-id=%s' % iface_id,
|
||||
'external-ids:iface-status=active',
|
||||
'external-ids:attached-mac=00:11:22:33:44:55',
|
||||
'external-ids:vm-uuid=instance_uuid',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
|
||||
run_as_root=True),
|
||||
]
|
||||
network_info = [
|
||||
{'network': {'bridge': 'br-int',
|
||||
'subnets': [{'gateway': {'address': '10.11.12.1'},
|
||||
'cidr': '10.11.12.0/24',
|
||||
'ips': [{'address': '10.11.12.3',
|
||||
'type': 'fixed', 'version': 4}]
|
||||
}]},
|
||||
'address': '00:11:22:33:44:55',
|
||||
'id': iface_id,
|
||||
'type': network_model.VIF_TYPE_OVS}]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.plug_vifs({'name': 'fake_instance',
|
||||
'uuid': 'instance_uuid'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
def test_plug_vifs_ovs_hybrid(self):
|
||||
iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
|
||||
calls = [
|
||||
mock.call('brctl', 'addbr', 'qbr920be2f4-2b', run_as_root=True),
|
||||
|
||||
mock.call('brctl', 'setfd', 'qbr920be2f4-2b', 0, run_as_root=True),
|
||||
mock.call('brctl', 'stp', 'qbr920be2f4-2b', 'off',
|
||||
run_as_root=True),
|
||||
mock.call('tee', "/sys/class/net/qbr920be2f4-2b/bridge/multicast_"
|
||||
"snooping", run_as_root=True, process_input='0',
|
||||
check_exit_code=[0, 1]),
|
||||
mock.call('ip', 'link', 'add', 'qvb920be2f4-2b', 'type',
|
||||
'veth', 'peer', 'name', 'qvo920be2f4-2b',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'qvb920be2f4-2b', 'up',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'qvb920be2f4-2b', 'promisc', 'on',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'qvo920be2f4-2b', 'up',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'qvo920be2f4-2b', 'promisc', 'on',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'qbr920be2f4-2b', 'up',
|
||||
run_as_root=True),
|
||||
mock.call('brctl', 'addif', 'qbr920be2f4-2b', 'qvb920be2f4-2b',
|
||||
run_as_root=True),
|
||||
mock.call('ovs-vsctl', '--timeout=120', '--', '--if-exists',
|
||||
'del-port', 'qvo920be2f4-2b', '--', 'add-port',
|
||||
'br-int', 'qvo920be2f4-2b', '--', 'set', 'Interface',
|
||||
'qvo920be2f4-2b', "external-ids:iface-id=920be2f4-2b98-"
|
||||
"411e-890a-69bcabb2a5a0",
|
||||
'external-ids:iface-status=active',
|
||||
'external-ids:attached-mac=00:11:22:33:44:55',
|
||||
'external-ids:vm-uuid=instance_uuid', run_as_root=True),
|
||||
mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b', 'type',
|
||||
'veth', 'peer', 'name', 'ns920be2f4-2b',
|
||||
run_as_root=True),
|
||||
mock.call('brctl', 'addif', 'qbr920be2f4-2b', 'tap920be2f4-2b',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
|
||||
run_as_root=True),
|
||||
]
|
||||
network_info = [
|
||||
{'network': {'bridge': 'br-int',
|
||||
'subnets': [{'gateway': {'address': '10.11.12.1'},
|
||||
'cidr': '10.11.12.0/24',
|
||||
'ips': [{'address': '10.11.12.3',
|
||||
'type': 'fixed', 'version': 4}]
|
||||
}]},
|
||||
'address': '00:11:22:33:44:55',
|
||||
'id': iface_id,
|
||||
'details': {'port_filter': True, 'ovs_hybrid_plug': True},
|
||||
'type': network_model.VIF_TYPE_OVS}]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.plug_vifs({'name': 'fake_instance',
|
||||
'uuid': 'instance_uuid'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
def test_unplug_vifs_ovs(self):
|
||||
iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
|
||||
calls = [
|
||||
mock.call('ovs-vsctl', '--timeout=120', '--', '--if-exists',
|
||||
'del-port', 'br-int', 'tap920be2f4-2b',
|
||||
run_as_root=True)
|
||||
]
|
||||
network_info = [
|
||||
{'network': {'bridge': 'br-int',
|
||||
'subnets': [{'gateway': {'address': '10.11.12.1'},
|
||||
'cidr': '10.11.12.0/24',
|
||||
'ips': [{'address': '10.11.12.3',
|
||||
'type': 'fixed', 'version': 4}]
|
||||
}]},
|
||||
'devname': 'tap920be2f4-2b',
|
||||
'address': '00:11:22:33:44:55',
|
||||
'id': iface_id,
|
||||
'type': network_model.VIF_TYPE_OVS}]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.unplug_vifs({'name': 'fake_instance',
|
||||
'uuid': 'instance_uuid'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
def test_plug_vifs_midonet(self):
|
||||
iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
|
||||
host_side_veth = 'tap920be2f4-2b'
|
||||
calls = [
|
||||
mock.call('ip', 'link', 'add', 'name', host_side_veth,
|
||||
'type', 'veth', 'peer', 'name', 'ns920be2f4-2b',
|
||||
run_as_root=True),
|
||||
|
||||
mock.call('ip', 'link', 'set', host_side_veth, 'up',
|
||||
run_as_root=True),
|
||||
|
||||
mock.call('mm-ctl', '--bind-port', iface_id, host_side_veth,
|
||||
run_as_root=True),
|
||||
]
|
||||
|
||||
network_info = [{'id': iface_id,
|
||||
'type': network_model.VIF_TYPE_MIDONET}]
|
||||
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.plug_vifs({'name': 'fake_instance',
|
||||
'uuid': 'instance_uuid'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
def test_unplug_vifs_midonet(self):
|
||||
iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
|
||||
calls = [
|
||||
mock.call('mm-ctl', '--unbind-port', iface_id, run_as_root=True)
|
||||
]
|
||||
network_info = [{'id': iface_id,
|
||||
'type': network_model.VIF_TYPE_MIDONET}]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.unplug_vifs({'name': 'fake_instance',
|
||||
'uuid': 'instance_uuid'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
def test_plug_vifs_iovisor(self):
|
||||
iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
|
||||
tenant_id = 'tenant1'
|
||||
calls = [
|
||||
mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b', 'type',
|
||||
'veth', 'peer', 'name', 'ns920be2f4-2b',
|
||||
run_as_root=True),
|
||||
|
||||
mock.call('ifc_ctl', 'gateway', 'add_port', 'tap920be2f4-2b',
|
||||
run_as_root=True),
|
||||
|
||||
mock.call('ifc_ctl', 'gateway', 'ifup', 'tap920be2f4-2b',
|
||||
'access_vm', "net1_" + iface_id, '00:11:22:33:44:55',
|
||||
'pgtag2=network-id-xxx-yyy-zzz',
|
||||
'pgtag1=%s' % tenant_id, run_as_root=True),
|
||||
|
||||
mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
|
||||
run_as_root=True),
|
||||
]
|
||||
|
||||
network_info = [self.vif_iovisor]
|
||||
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.plug_vifs({'name': 'fake_instance',
|
||||
'uuid': 'instance_uuid',
|
||||
'project_id': tenant_id}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
def test_unplug_vifs_iovisor(self):
|
||||
iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
|
||||
tenant_id = 'tenant1'
|
||||
calls = [
|
||||
mock.call('ifc_ctl', 'gateway', 'ifdown', 'tap920be2f4-2b',
|
||||
'access_vm', 'net1_' + iface_id, '00:11:22:33:44:55',
|
||||
run_as_root=True),
|
||||
mock.call('ifc_ctl', 'gateway', 'del_port', 'tap920be2f4-2b',
|
||||
run_as_root=True)
|
||||
]
|
||||
network_info = [self.vif_iovisor]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.unplug_vifs({'name': 'fake_instance',
|
||||
'uuid': 'instance_uuid',
|
||||
'project_id': tenant_id}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
def test_plug_vifs_windows(self):
|
||||
network_info = [{'type': 'hyperv'}]
|
||||
fake_instance = mock.sentinel.instance
|
||||
with mock.patch.object(vifs.DockerGenericVIFDriver,
|
||||
'plug_windows') as mock_plug_windows:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.plug_vifs(fake_instance, network_info)
|
||||
mock_plug_windows.assert_called_once_with(
|
||||
fake_instance, {'type': 'hyperv'})
|
||||
|
||||
def test_unplug_vifs_windows(self):
|
||||
network_info = [{'type': 'hyperv'}]
|
||||
fake_instance = mock.sentinel.instance
|
||||
with mock.patch.object(vifs.DockerGenericVIFDriver,
|
||||
'unplug_windows') as mock_unplug_windows:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.unplug_vifs(fake_instance, network_info)
|
||||
mock_unplug_windows.assert_called_once_with(
|
||||
fake_instance, {'type': 'hyperv'})
|
||||
|
||||
def test_unplug_vifs_ovs_hybrid(self):
|
||||
iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
|
||||
calls = [
|
||||
mock.call('ovs-vsctl', '--timeout=120', '--', '--if-exists',
|
||||
'del-port', 'br-int', 'qvo920be2f4-2b',
|
||||
run_as_root=True)
|
||||
]
|
||||
network_info = [
|
||||
{'network': {'bridge': 'br-int',
|
||||
'subnets': [{'gateway': {'address': '10.11.12.1'},
|
||||
'cidr': '10.11.12.0/24',
|
||||
'ips': [{'address': '10.11.12.3',
|
||||
'type': 'fixed', 'version': 4}]
|
||||
}]},
|
||||
'devname': 'tap920be2f4-2b',
|
||||
'address': '00:11:22:33:44:55',
|
||||
'id': iface_id,
|
||||
'details': {'port_filter': True, 'ovs_hybrid_plug': True},
|
||||
'type': network_model.VIF_TYPE_OVS}]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver.unplug_vifs({'name': 'fake_instance',
|
||||
'uuid': 'instance_uuid'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
@mock.patch.object(docker_driver.DockerDriver,
|
||||
'_find_container_by_uuid',
|
||||
return_value={'id': 'fake_id'})
|
||||
@mock.patch.object(docker_driver.DockerDriver,
|
||||
'_find_container_pid',
|
||||
return_value=1234)
|
||||
def test_attach_vifs(self, mock_find_by_uuid, mock_find_pid):
|
||||
calls = [
|
||||
mock.call('ln', '-sf', '/proc/1234/ns/net',
|
||||
'/var/run/netns/fake_id', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'link',
|
||||
'set', 'lo', 'up', run_as_root=True),
|
||||
mock.call('ip', 'link', 'set', 'ns920be2f4-2b', 'netns',
|
||||
'fake_id', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'link',
|
||||
'set', 'ns920be2f4-2b', 'address', '00:11:22:33:44:55',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id',
|
||||
'ip', 'addr', 'add', '10.11.12.3/24', 'dev',
|
||||
'ns920be2f4-2b', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id',
|
||||
'ip', 'link', 'set',
|
||||
'ns920be2f4-2b', 'up', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'route',
|
||||
'replace', 'default', 'via', '10.11.12.1', 'dev',
|
||||
'ns920be2f4-2b', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id', 'ethtool', '--offload',
|
||||
'ns920be2f4-2b', 'tso', 'off', run_as_root=True)
|
||||
]
|
||||
network_info = [
|
||||
{'network': {'bridge': 'br100',
|
||||
'subnets': [{'gateway': {'address': '10.11.12.1'},
|
||||
'cidr': '10.11.12.0/24',
|
||||
'ips': [{'address': '10.11.12.3',
|
||||
'type': 'fixed', 'version': 4}]
|
||||
}]},
|
||||
'address': '00:11:22:33:44:55',
|
||||
'id': '920be2f4-2b98-411e-890a-69bcabb2a5a0',
|
||||
'type': network_model.VIF_TYPE_BRIDGE}]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver._attach_vifs({'uuid': 'fake_uuid'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
@mock.patch.object(docker_driver.DockerDriver,
|
||||
'_find_container_by_uuid',
|
||||
return_value={'id': 'fake_id'})
|
||||
@mock.patch.object(docker_driver.DockerDriver,
|
||||
'_find_container_pid',
|
||||
return_value=1234)
|
||||
def test_attach_vifs_two_interfaces(self, mock_find_by_uuid,
|
||||
mock_find_pid):
|
||||
calls = [
|
||||
mock.call('ln', '-sf', '/proc/1234/ns/net',
|
||||
'/var/run/netns/fake_id', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'link',
|
||||
'set', 'lo', 'up', run_as_root=True),
|
||||
# interface 1
|
||||
mock.call('ip', 'link', 'set', 'ns920be2f4-2b', 'netns', 'fake_id',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'link',
|
||||
'set', 'ns920be2f4-2b', 'address', '00:11:22:33:44:55',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id',
|
||||
'ip', 'addr', 'add', '10.11.12.3/24', 'dev',
|
||||
'ns920be2f4-2b', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id',
|
||||
'ip', 'link', 'set',
|
||||
'ns920be2f4-2b', 'up', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'route',
|
||||
'replace', 'default', 'via', '10.11.12.1', 'dev',
|
||||
'ns920be2f4-2b', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id', 'ethtool', '--offload',
|
||||
'ns920be2f4-2b', 'tso', 'off', run_as_root=True),
|
||||
# interface 2
|
||||
mock.call('ip', 'link', 'set', 'ns920be2f4-2b', 'netns', 'fake_id',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'link',
|
||||
'set', 'ns920be2f4-2b', 'address', '00:11:22:33:44:66',
|
||||
run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id',
|
||||
'ip', 'addr', 'add', '10.13.12.3/24', 'dev',
|
||||
'ns920be2f4-2b', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id',
|
||||
'ip', 'link', 'set',
|
||||
'ns920be2f4-2b', 'up', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'route',
|
||||
'replace', 'default', 'via', '10.13.12.1', 'dev',
|
||||
'ns920be2f4-2b', run_as_root=True),
|
||||
mock.call('ip', 'netns', 'exec', 'fake_id', 'ethtool', '--offload',
|
||||
'ns920be2f4-2b', 'tso', 'off', run_as_root=True)
|
||||
]
|
||||
network_info = [
|
||||
{'network': {'bridge': 'br100',
|
||||
'subnets': [{'gateway': {'address': '10.11.12.1'},
|
||||
'cidr': '10.11.12.0/24',
|
||||
'ips': [{'address': '10.11.12.3',
|
||||
'type': 'fixed', 'version': 4}],
|
||||
}]},
|
||||
'address': '00:11:22:33:44:55',
|
||||
'type': network_model.VIF_TYPE_BRIDGE,
|
||||
'id': '920be2f4-2b98-411e-890a-69bcabb2a5a0'},
|
||||
{'network': {'bridge': 'br100',
|
||||
'subnets': [{'gateway': {'address': '10.13.12.1'},
|
||||
'cidr': '10.13.12.0/24',
|
||||
'ips': [{'address': '10.13.12.3',
|
||||
'type': 'fixed', 'version': 4}]
|
||||
}]},
|
||||
'address': '00:11:22:33:44:66',
|
||||
'type': network_model.VIF_TYPE_BRIDGE,
|
||||
'id': '920be2f4-2b98-411e-890a-69bcabb2a5a0'}]
|
||||
with mock.patch('nova.utils.execute') as ex:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver._attach_vifs({'uuid': 'fake_uuid'}, network_info)
|
||||
ex.assert_has_calls(calls)
|
||||
|
||||
@mock.patch.object(docker_driver, 'os')
|
||||
def test_attach_vifs_hyperv(self, mock_os):
|
||||
mock_os.name = 'nt'
|
||||
with mock.patch.object(docker_driver.DockerDriver,
|
||||
'_get_container_id') as mock_get_container_id:
|
||||
driver = docker_driver.DockerDriver(object)
|
||||
driver._attach_vifs(mock.sentinel.instance,
|
||||
mock.sentinel.network_info)
|
||||
self.assertFalse(mock_get_container_id.called)
|
|
@ -1,54 +0,0 @@
|
|||
# Copyright 2014 Docker, Inc
|
||||
# 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 mock
|
||||
|
||||
from nova import test
|
||||
from novadocker.virt import hostutils
|
||||
|
||||
|
||||
class HostUtilsTestCase(test.NoDBTestCase):
|
||||
def _test_sys_uptime(self, is_nt_os=False):
|
||||
expect_uptime = ("fake_time up 0:00:00, 0 users, "
|
||||
"load average: 0, 0, 0")
|
||||
fake_tick_count = 0
|
||||
fake_time = 'fake_time'
|
||||
|
||||
with mock.patch.multiple(hostutils, os=mock.DEFAULT, time=mock.DEFAULT,
|
||||
ctypes=mock.DEFAULT, utils=mock.DEFAULT,
|
||||
create=True) as lib_mocks:
|
||||
|
||||
lib_mocks['os'].name = 'nt' if is_nt_os else ''
|
||||
lib_mocks['time'].strftime.return_value = fake_time
|
||||
lib_mocks['utils'].execute.return_value = (expect_uptime, None)
|
||||
tick_count = lib_mocks['ctypes'].windll.kernel32.GetTickCount64
|
||||
tick_count.return_value = fake_tick_count
|
||||
|
||||
uptime = hostutils.sys_uptime()
|
||||
|
||||
if is_nt_os:
|
||||
tick_count.assert_called_once_with()
|
||||
lib_mocks['time'].strftime.assert_called_once_with("%H:%M:%S")
|
||||
else:
|
||||
lib_mocks['utils'].execute.assert_called_once_with(
|
||||
'env', 'LANG=C', 'uptime')
|
||||
|
||||
self.assertEqual(expect_uptime, uptime)
|
||||
|
||||
def test_sys_uptime(self):
|
||||
self._test_sys_uptime()
|
||||
|
||||
def test_nt_sys_uptime(self):
|
||||
self._test_sys_uptime(is_nt_os=True)
|
|
@ -1,22 +0,0 @@
|
|||
# Copyright (c) 2013 dotCloud, Inc.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
:mod:`docker` -- Nova support for Docker Hypervisor to run Linux containers
|
||||
===========================================================================
|
||||
"""
|
||||
from novadocker.virt.docker import driver
|
||||
|
||||
DockerDriver = driver.DockerDriver
|
|
@ -1,99 +0,0 @@
|
|||
# Copyright (c) 2013 dotCloud, Inc.
|
||||
# Copyright 2014 IBM Corp.
|
||||
# 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 functools
|
||||
import inspect
|
||||
|
||||
from oslo_config import cfg
|
||||
import six
|
||||
|
||||
from docker import client
|
||||
from docker import tls
|
||||
|
||||
CONF = cfg.CONF
|
||||
DEFAULT_TIMEOUT_SECONDS = 120
|
||||
DEFAULT_DOCKER_API_VERSION = '1.19'
|
||||
|
||||
|
||||
def filter_data(f):
|
||||
"""Decorator that post-processes data returned by Docker.
|
||||
|
||||
This will avoid any surprises with different versions of Docker.
|
||||
"""
|
||||
@functools.wraps(f, assigned=[])
|
||||
def wrapper(*args, **kwds):
|
||||
out = f(*args, **kwds)
|
||||
|
||||
def _filter(obj):
|
||||
if isinstance(obj, list):
|
||||
new_list = []
|
||||
for o in obj:
|
||||
new_list.append(_filter(o))
|
||||
obj = new_list
|
||||
if isinstance(obj, dict):
|
||||
for k, v in obj.items():
|
||||
if isinstance(k, six.string_types):
|
||||
obj[k.lower()] = _filter(v)
|
||||
return obj
|
||||
return _filter(out)
|
||||
return wrapper
|
||||
|
||||
|
||||
class DockerHTTPClient(client.Client):
|
||||
def __init__(self, url='unix://var/run/docker.sock'):
|
||||
if (CONF.docker.cert_file or
|
||||
CONF.docker.key_file):
|
||||
client_cert = (CONF.docker.cert_file, CONF.docker.key_file)
|
||||
else:
|
||||
client_cert = None
|
||||
if (CONF.docker.ca_file or
|
||||
CONF.docker.api_insecure or
|
||||
client_cert):
|
||||
ssl_config = tls.TLSConfig(
|
||||
client_cert=client_cert,
|
||||
ca_cert=CONF.docker.ca_file,
|
||||
verify=CONF.docker.api_insecure)
|
||||
else:
|
||||
ssl_config = False
|
||||
super(DockerHTTPClient, self).__init__(
|
||||
base_url=url,
|
||||
version=DEFAULT_DOCKER_API_VERSION,
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS,
|
||||
tls=ssl_config
|
||||
)
|
||||
self._setup_decorators()
|
||||
|
||||
def _setup_decorators(self):
|
||||
for name, member in inspect.getmembers(self, inspect.ismethod):
|
||||
if not name.startswith('_'):
|
||||
setattr(self, name, filter_data(member))
|
||||
|
||||
def pause(self, container_id):
|
||||
url = self._url("/containers/{0}/pause".format(container_id))
|
||||
res = self._post(url)
|
||||
return res.status_code == 204
|
||||
|
||||
def unpause(self, container_id):
|
||||
url = self._url("/containers/{0}/unpause".format(container_id))
|
||||
res = self._post(url)
|
||||
return res.status_code == 204
|
||||
|
||||
def load_repository_file(self, name, path):
|
||||
with open(path, 'rb') as fh:
|
||||
self.load_image(fh)
|
||||
|
||||
def get_container_logs(self, container_id):
|
||||
return self.attach(container_id, 1, 1, 0, 1)
|
|
@ -1,806 +0,0 @@
|
|||
# Copyright (c) 2013 dotCloud, Inc.
|
||||
# Copyright 2014 IBM Corp.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
A Docker Hypervisor which allows running Linux Containers instead of VMs.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from docker import errors
|
||||
from docker import utils as docker_utils
|
||||
import eventlet
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import fileutils
|
||||
from oslo_utils import importutils
|
||||
from oslo_utils import units
|
||||
from oslo_utils import versionutils
|
||||
|
||||
from nova.compute import arch
|
||||
from nova.compute import flavors
|
||||
from nova.compute import hv_type
|
||||
from nova.compute import power_state
|
||||
from nova.compute import task_states
|
||||
from nova.compute import vm_mode
|
||||
from nova import exception
|
||||
from nova.image import glance
|
||||
from nova import objects
|
||||
from nova import utils
|
||||
from nova import utils as nova_utils
|
||||
from nova.virt import driver
|
||||
from nova.virt import firewall
|
||||
from nova.virt import hardware
|
||||
from nova.virt import images
|
||||
from novadocker.i18n import _, _LI, _LE, _LW
|
||||
from novadocker.virt.docker import client as docker_client
|
||||
from novadocker.virt.docker import hostinfo
|
||||
from novadocker.virt.docker import network
|
||||
from novadocker.virt import hostutils
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('my_ip', 'nova.conf.netconf')
|
||||
CONF.import_opt('instances_path', 'nova.compute.manager')
|
||||
|
||||
docker_opts = [
|
||||
cfg.StrOpt('root_directory',
|
||||
default='/var/lib/docker',
|
||||
help='Path to use as the root of the Docker runtime.'),
|
||||
cfg.StrOpt('host_url',
|
||||
default='unix:///var/run/docker.sock',
|
||||
help='tcp://host:port to bind/connect to or '
|
||||
'unix://path/to/socket to use'),
|
||||
cfg.BoolOpt('api_insecure',
|
||||
default=False,
|
||||
help='If set, ignore any SSL validation issues'),
|
||||
cfg.StrOpt('ca_file',
|
||||
help='Location of CA certificates file for '
|
||||
'securing docker api requests (tlscacert).'),
|
||||
cfg.StrOpt('cert_file',
|
||||
help='Location of TLS certificate file for '
|
||||
'securing docker api requests (tlscert).'),
|
||||
cfg.StrOpt('key_file',
|
||||
help='Location of TLS private key file for '
|
||||
'securing docker api requests (tlskey).'),
|
||||
cfg.StrOpt('vif_driver',
|
||||
default='novadocker.virt.docker.vifs.DockerGenericVIFDriver'),
|
||||
cfg.StrOpt('snapshots_directory',
|
||||
default='$instances_path/snapshots',
|
||||
help='Location where docker driver will temporarily store '
|
||||
'snapshots.'),
|
||||
cfg.BoolOpt('inject_key',
|
||||
default=False,
|
||||
help='Inject the ssh public key at boot time'),
|
||||
cfg.StrOpt('shared_directory',
|
||||
default=None,
|
||||
help='Shared directory where glance images located. If '
|
||||
'specified, docker will try to load the image from '
|
||||
'the shared directory by image ID.'),
|
||||
cfg.BoolOpt('privileged',
|
||||
default=False,
|
||||
help='Set true can own all root privileges in a container.'),
|
||||
]
|
||||
|
||||
CONF.register_opts(docker_opts, 'docker')
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DockerDriver(driver.ComputeDriver):
|
||||
"""Docker hypervisor driver."""
|
||||
|
||||
def __init__(self, virtapi):
|
||||
super(DockerDriver, self).__init__(virtapi)
|
||||
self._docker = None
|
||||
vif_class = importutils.import_class(CONF.docker.vif_driver)
|
||||
self.vif_driver = vif_class()
|
||||
self.firewall_driver = firewall.load_driver(
|
||||
default='nova.virt.firewall.NoopFirewallDriver')
|
||||
# NOTE(zhangguoqing): For passing the nova unit tests
|
||||
self.active_migrations = {}
|
||||
|
||||
@property
|
||||
def docker(self):
|
||||
if self._docker is None:
|
||||
self._docker = docker_client.DockerHTTPClient(CONF.docker.host_url)
|
||||
return self._docker
|
||||
|
||||
def init_host(self, host):
|
||||
if self._is_daemon_running() is False:
|
||||
raise exception.NovaException(
|
||||
_('Docker daemon is not running or is not reachable'
|
||||
' (check the rights on /var/run/docker.sock)'))
|
||||
|
||||
def _is_daemon_running(self):
|
||||
return self.docker.ping()
|
||||
|
||||
def _start_firewall(self, instance, network_info):
|
||||
self.firewall_driver.setup_basic_filtering(instance, network_info)
|
||||
self.firewall_driver.prepare_instance_filter(instance, network_info)
|
||||
self.firewall_driver.apply_instance_filter(instance, network_info)
|
||||
|
||||
def _stop_firewall(self, instance, network_info):
|
||||
self.firewall_driver.unfilter_instance(instance, network_info)
|
||||
|
||||
def refresh_security_group_rules(self, security_group_id):
|
||||
"""Refresh security group rules from data store.
|
||||
|
||||
Invoked when security group rules are updated.
|
||||
|
||||
:param security_group_id: The security group id.
|
||||
|
||||
"""
|
||||
self.firewall_driver.refresh_security_group_rules(security_group_id)
|
||||
|
||||
def refresh_security_group_members(self, security_group_id):
|
||||
"""Refresh security group members from data store.
|
||||
|
||||
Invoked when instances are added/removed to a security group.
|
||||
|
||||
:param security_group_id: The security group id.
|
||||
|
||||
"""
|
||||
self.firewall_driver.refresh_security_group_members(security_group_id)
|
||||
|
||||
def refresh_provider_fw_rules(self):
|
||||
"""Triggers a firewall update based on database changes."""
|
||||
self.firewall_driver.refresh_provider_fw_rules()
|
||||
|
||||
def refresh_instance_security_rules(self, instance):
|
||||
"""Refresh security group rules from data store.
|
||||
|
||||
Gets called when an instance gets added to or removed from
|
||||
the security group the instance is a member of or if the
|
||||
group gains or loses a rule.
|
||||
|
||||
:param instance: The instance object.
|
||||
|
||||
"""
|
||||
self.firewall_driver.refresh_instance_security_rules(instance)
|
||||
|
||||
def ensure_filtering_rules_for_instance(self, instance, network_info):
|
||||
"""Set up filtering rules.
|
||||
|
||||
:param instance: The instance object.
|
||||
:param network_info: Instance network information.
|
||||
|
||||
"""
|
||||
self.firewall_driver.setup_basic_filtering(instance, network_info)
|
||||
self.firewall_driver.prepare_instance_filter(instance, network_info)
|
||||
|
||||
def unfilter_instance(self, instance, network_info):
|
||||
"""Stop filtering instance.
|
||||
|
||||
:param instance: The instance object.
|
||||
:param network_info: Instance network information.
|
||||
|
||||
"""
|
||||
self.firewall_driver.unfilter_instance(instance, network_info)
|
||||
|
||||
def list_instances(self, inspect=False):
|
||||
res = []
|
||||
for container in self.docker.containers(all=True):
|
||||
info = self.docker.inspect_container(container['id'])
|
||||
if not info:
|
||||
continue
|
||||
if inspect:
|
||||
res.append(info)
|
||||
else:
|
||||
res.append(info['Config'].get('Hostname'))
|
||||
return res
|
||||
|
||||
def attach_interface(self, instance, image_meta, vif):
|
||||
"""Attach an interface to the container."""
|
||||
self.vif_driver.plug(instance, vif)
|
||||
container_id = self._find_container_by_uuid(instance['uuid']).get('id')
|
||||
self.vif_driver.attach(instance, vif, container_id)
|
||||
|
||||
def detach_interface(self, instance, vif):
|
||||
"""Detach an interface from the container."""
|
||||
self.vif_driver.unplug(instance, vif)
|
||||
|
||||
def plug_vifs(self, instance, network_info):
|
||||
"""Plug VIFs into networks."""
|
||||
for vif in network_info:
|
||||
self.vif_driver.plug(instance, vif)
|
||||
self._start_firewall(instance, network_info)
|
||||
|
||||
def _attach_vifs(self, instance, network_info):
|
||||
"""Plug VIFs into container."""
|
||||
if not network_info:
|
||||
return
|
||||
|
||||
if os.name == 'nt':
|
||||
return
|
||||
|
||||
container_id = self._get_container_id(instance)
|
||||
if not container_id:
|
||||
return
|
||||
netns_path = '/var/run/netns'
|
||||
if not os.path.exists(netns_path):
|
||||
utils.execute(
|
||||
'mkdir', '-p', netns_path, run_as_root=True)
|
||||
nspid = self._find_container_pid(container_id)
|
||||
if not nspid:
|
||||
msg = _('Cannot find any PID under container "{0}"')
|
||||
raise RuntimeError(msg.format(container_id))
|
||||
netns_path = os.path.join(netns_path, container_id)
|
||||
utils.execute(
|
||||
'ln', '-sf', '/proc/{0}/ns/net'.format(nspid),
|
||||
'/var/run/netns/{0}'.format(container_id),
|
||||
run_as_root=True)
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ip', 'link',
|
||||
'set', 'lo', 'up', run_as_root=True)
|
||||
|
||||
for vif in network_info:
|
||||
self.vif_driver.attach(instance, vif, container_id)
|
||||
|
||||
def unplug_vifs(self, instance, network_info):
|
||||
"""Unplug VIFs from networks."""
|
||||
for vif in network_info:
|
||||
self.vif_driver.unplug(instance, vif)
|
||||
self._stop_firewall(instance, network_info)
|
||||
|
||||
def _encode_utf8(self, value):
|
||||
return unicode(value).encode('utf-8')
|
||||
|
||||
def _find_container_by_uuid(self, uuid):
|
||||
try:
|
||||
name = "nova-" + uuid
|
||||
containers = self.docker.containers(all=True,
|
||||
filters={'name': name})
|
||||
if containers:
|
||||
# NOTE(dims): We expect only one item in the containers list
|
||||
return self.docker.inspect_container(containers[0]['id'])
|
||||
except errors.APIError as e:
|
||||
if e.response.status_code != 404:
|
||||
raise
|
||||
return {}
|
||||
|
||||
def _get_container_id(self, instance):
|
||||
return self._find_container_by_uuid(instance['uuid']).get('id')
|
||||
|
||||
def get_info(self, instance):
|
||||
container = self._find_container_by_uuid(instance['uuid'])
|
||||
if not container:
|
||||
raise exception.InstanceNotFound(instance_id=instance['name'])
|
||||
running = container['State'].get('Running')
|
||||
mem = container['Config'].get('Memory', 0)
|
||||
|
||||
# NOTE(ewindisch): cgroups/lxc defaults to 1024 multiplier.
|
||||
# see: _get_cpu_shares for further explaination
|
||||
num_cpu = container['Config'].get('CpuShares', 0) / 1024
|
||||
|
||||
# FIXME(ewindisch): Improve use of statistics:
|
||||
# For 'mem', we should expose memory.stat.rss, and
|
||||
# for cpu_time we should expose cpuacct.stat.system,
|
||||
# but these aren't yet exposed by Docker.
|
||||
#
|
||||
# Also see:
|
||||
# docker/docs/sources/articles/runmetrics.md
|
||||
info = hardware.InstanceInfo(
|
||||
max_mem_kb=mem,
|
||||
mem_kb=mem,
|
||||
num_cpu=num_cpu,
|
||||
cpu_time_ns=0,
|
||||
state=(power_state.RUNNING if running
|
||||
else power_state.SHUTDOWN)
|
||||
)
|
||||
return info
|
||||
|
||||
def get_host_stats(self, refresh=False):
|
||||
hostname = socket.gethostname()
|
||||
stats = self.get_available_resource(hostname)
|
||||
stats['host_hostname'] = stats['hypervisor_hostname']
|
||||
stats['host_name_label'] = stats['hypervisor_hostname']
|
||||
return stats
|
||||
|
||||
def get_available_nodes(self, refresh=False):
|
||||
hostname = socket.gethostname()
|
||||
return [hostname]
|
||||
|
||||
def get_available_resource(self, nodename):
|
||||
if not hasattr(self, '_nodename'):
|
||||
self._nodename = nodename
|
||||
if nodename != self._nodename:
|
||||
LOG.error(_('Hostname has changed from %(old)s to %(new)s. '
|
||||
'A restart is required to take effect.'
|
||||
), {'old': self._nodename,
|
||||
'new': nodename})
|
||||
|
||||
memory = hostinfo.get_memory_usage()
|
||||
disk = hostinfo.get_disk_usage()
|
||||
stats = {
|
||||
'vcpus': hostinfo.get_total_vcpus(),
|
||||
'vcpus_used': hostinfo.get_vcpus_used(self.list_instances(True)),
|
||||
'memory_mb': memory['total'] / units.Mi,
|
||||
'memory_mb_used': memory['used'] / units.Mi,
|
||||
'local_gb': disk['total'] / units.Gi,
|
||||
'local_gb_used': disk['used'] / units.Gi,
|
||||
'disk_available_least': disk['available'] / units.Gi,
|
||||
'hypervisor_type': 'docker',
|
||||
'hypervisor_version': versionutils.convert_version_to_int('1.0'),
|
||||
'hypervisor_hostname': self._nodename,
|
||||
'cpu_info': '?',
|
||||
'numa_topology': None,
|
||||
'supported_instances': [
|
||||
(arch.I686, hv_type.DOCKER, vm_mode.EXE),
|
||||
(arch.X86_64, hv_type.DOCKER, vm_mode.EXE)
|
||||
]
|
||||
}
|
||||
return stats
|
||||
|
||||
def _find_container_pid(self, container_id):
|
||||
n = 0
|
||||
while True:
|
||||
# NOTE(samalba): We wait for the process to be spawned inside the
|
||||
# container in order to get the the "container pid". This is
|
||||
# usually really fast. To avoid race conditions on a slow
|
||||
# machine, we allow 10 seconds as a hard limit.
|
||||
if n > 20:
|
||||
return
|
||||
info = self.docker.inspect_container(container_id)
|
||||
if info:
|
||||
pid = info['State']['Pid']
|
||||
# Pid is equal to zero if it isn't assigned yet
|
||||
if pid:
|
||||
return pid
|
||||
time.sleep(0.5)
|
||||
n += 1
|
||||
|
||||
def _get_memory_limit_bytes(self, instance):
|
||||
if isinstance(instance, objects.Instance):
|
||||
return instance.get_flavor().memory_mb * units.Mi
|
||||
else:
|
||||
system_meta = utils.instance_sys_meta(instance)
|
||||
return int(system_meta.get(
|
||||
'instance_type_memory_mb', 0)) * units.Mi
|
||||
|
||||
def _get_image_name(self, context, instance, image):
|
||||
fmt = image.container_format
|
||||
if fmt != 'docker':
|
||||
msg = _('Image container format not supported ({0})')
|
||||
raise exception.InstanceDeployFailure(msg.format(fmt),
|
||||
instance_id=instance['name'])
|
||||
return image.name
|
||||
|
||||
def _pull_missing_image(self, context, image_meta, instance):
|
||||
msg = 'Image name "%s" does not exist, fetching it...'
|
||||
LOG.debug(msg, image_meta.name)
|
||||
|
||||
shared_directory = CONF.docker.shared_directory
|
||||
if (shared_directory and
|
||||
os.path.exists(os.path.join(shared_directory,
|
||||
image_meta.id))):
|
||||
LOG.debug('Found %s in shared_directory', image_meta.id)
|
||||
try:
|
||||
LOG.debug('Loading repository file into docker %s',
|
||||
self._encode_utf8(image_meta.name))
|
||||
self.docker.load_repository_file(
|
||||
self._encode_utf8(image_meta.name),
|
||||
os.path.join(shared_directory, image_meta.id))
|
||||
return self.docker.inspect_image(
|
||||
self._encode_utf8(image_meta.name))
|
||||
except Exception as e:
|
||||
# If failed to load image from shared_directory, continue
|
||||
# to download the image from glance then load.
|
||||
LOG.warning(_('Cannot load repository file from shared '
|
||||
'directory: %s'),
|
||||
e, instance=instance, exc_info=True)
|
||||
|
||||
# TODO(imain): It would be nice to do this with file like object
|
||||
# passing but that seems a bit complex right now.
|
||||
snapshot_directory = CONF.docker.snapshots_directory
|
||||
fileutils.ensure_tree(snapshot_directory)
|
||||
with utils.tempdir(dir=snapshot_directory) as tmpdir:
|
||||
try:
|
||||
out_path = os.path.join(tmpdir, uuid.uuid4().hex)
|
||||
|
||||
LOG.debug('Fetching image with id %s from glance',
|
||||
image_meta.id)
|
||||
images.fetch(context, image_meta.id, out_path,
|
||||
instance['user_id'], instance['project_id'])
|
||||
LOG.debug('Loading repository file into docker %s',
|
||||
self._encode_utf8(image_meta.name))
|
||||
self.docker.load_repository_file(
|
||||
self._encode_utf8(image_meta.name),
|
||||
out_path
|
||||
)
|
||||
return self.docker.inspect_image(
|
||||
self._encode_utf8(image_meta.name))
|
||||
except Exception as e:
|
||||
LOG.warning(_('Cannot load repository file: %s'),
|
||||
e, instance=instance, exc_info=True)
|
||||
msg = _('Cannot load repository file: {0}')
|
||||
raise exception.NovaException(msg.format(e),
|
||||
instance_id=image_meta.name)
|
||||
|
||||
def _extract_dns_entries(self, network_info):
|
||||
dns = []
|
||||
if network_info:
|
||||
for net in network_info:
|
||||
subnets = net['network'].get('subnets', [])
|
||||
for subnet in subnets:
|
||||
dns_entries = subnet.get('dns', [])
|
||||
for dns_entry in dns_entries:
|
||||
if 'address' in dns_entry:
|
||||
dns.append(dns_entry['address'])
|
||||
return dns if dns else None
|
||||
|
||||
def _get_key_binds(self, container_id, instance):
|
||||
binds = None
|
||||
# Handles the key injection.
|
||||
if CONF.docker.inject_key and instance.get('key_data'):
|
||||
key = str(instance['key_data'])
|
||||
mount_origin = self._inject_key(container_id, key)
|
||||
binds = {mount_origin: {'bind': '/root/.ssh', 'ro': True}}
|
||||
return binds
|
||||
|
||||
def _neutron_failed_callback(self, event_name, instance):
|
||||
LOG.error(_LE('Neutron Reported failure on event '
|
||||
'%(event)s for instance %(uuid)s'),
|
||||
{'event': event_name, 'uuid': instance.uuid},
|
||||
instance=instance)
|
||||
if CONF.vif_plugging_is_fatal:
|
||||
raise exception.VirtualInterfaceCreateException()
|
||||
|
||||
def _get_neutron_events(self, network_info):
|
||||
# NOTE(danms): We need to collect any VIFs that are currently
|
||||
# down that we expect a down->up event for. Anything that is
|
||||
# already up will not undergo that transition, and for
|
||||
# anything that might be stale (cache-wise) assume it's
|
||||
# already up so we don't block on it.
|
||||
return [('network-vif-plugged', vif['id'])
|
||||
for vif in network_info if vif.get('active', True) is False]
|
||||
|
||||
def _start_container(self, container_id, instance, network_info=None):
|
||||
binds = self._get_key_binds(container_id, instance)
|
||||
dns = self._extract_dns_entries(network_info)
|
||||
self.docker.start(container_id, binds=binds, dns=dns,
|
||||
privileged=CONF.docker.privileged)
|
||||
|
||||
if not network_info:
|
||||
return
|
||||
timeout = CONF.vif_plugging_timeout
|
||||
if (utils.is_neutron() and timeout):
|
||||
events = self._get_neutron_events(network_info)
|
||||
else:
|
||||
events = []
|
||||
|
||||
try:
|
||||
with self.virtapi.wait_for_instance_event(
|
||||
instance, events, deadline=timeout,
|
||||
error_callback=self._neutron_failed_callback):
|
||||
self.plug_vifs(instance, network_info)
|
||||
self._attach_vifs(instance, network_info)
|
||||
except eventlet.timeout.Timeout:
|
||||
LOG.warn(_LW('Timeout waiting for vif plugging callback for '
|
||||
'instance %(uuid)s'), {'uuid': instance['name']})
|
||||
if CONF.vif_plugging_is_fatal:
|
||||
self.docker.kill(container_id)
|
||||
self.docker.remove_container(container_id, force=True)
|
||||
raise exception.InstanceDeployFailure(
|
||||
'Timeout waiting for vif plugging',
|
||||
instance_id=instance['name'])
|
||||
except (Exception) as e:
|
||||
LOG.warning(_('Cannot setup network: %s'),
|
||||
e, instance=instance, exc_info=True)
|
||||
msg = _('Cannot setup network: {0}')
|
||||
self.docker.kill(container_id)
|
||||
self.docker.remove_container(container_id, force=True)
|
||||
raise exception.InstanceDeployFailure(msg.format(e),
|
||||
instance_id=instance['name'])
|
||||
|
||||
def spawn(self, context, instance, image_meta, injected_files,
|
||||
admin_password, network_info=None, block_device_info=None,
|
||||
flavor=None):
|
||||
image_name = self._get_image_name(context, instance, image_meta)
|
||||
args = {
|
||||
'hostname': instance['name'],
|
||||
'mem_limit': self._get_memory_limit_bytes(instance),
|
||||
'cpu_shares': self._get_cpu_shares(instance),
|
||||
'network_disabled': True,
|
||||
}
|
||||
|
||||
try:
|
||||
image = self.docker.inspect_image(self._encode_utf8(image_name))
|
||||
except errors.APIError:
|
||||
image = None
|
||||
|
||||
if not image:
|
||||
image = self._pull_missing_image(context, image_meta, instance)
|
||||
# Glance command-line overrides any set in the Docker image
|
||||
if (image_meta is not None and
|
||||
image_meta.properties.get("os_command_line") is not None):
|
||||
args['command'] = image_meta.properties.get("os_command_line")
|
||||
|
||||
if 'metadata' in instance:
|
||||
args['environment'] = nova_utils.instance_meta(instance)
|
||||
|
||||
container_id = self._create_container(instance, image_name, args)
|
||||
if not container_id:
|
||||
raise exception.InstanceDeployFailure(
|
||||
_('Cannot create container'),
|
||||
instance_id=instance['name'])
|
||||
|
||||
self._start_container(container_id, instance, network_info)
|
||||
|
||||
def _inject_key(self, id, key):
|
||||
if isinstance(id, dict):
|
||||
id = id.get('id')
|
||||
sshdir = os.path.join(CONF.instances_path, id, '.ssh')
|
||||
key_data = ''.join([
|
||||
'\n',
|
||||
'# The following ssh key was injected by Nova',
|
||||
'\n',
|
||||
key.strip(),
|
||||
'\n',
|
||||
])
|
||||
fileutils.ensure_tree(sshdir)
|
||||
keys_file = os.path.join(sshdir, 'authorized_keys')
|
||||
with open(keys_file, 'a') as f:
|
||||
f.write(key_data)
|
||||
os.chmod(sshdir, 0o700)
|
||||
os.chmod(keys_file, 0o600)
|
||||
return sshdir
|
||||
|
||||
def _cleanup_key(self, instance, id):
|
||||
if isinstance(id, dict):
|
||||
id = id.get('id')
|
||||
dir = os.path.join(CONF.instances_path, id)
|
||||
if os.path.exists(dir):
|
||||
LOG.info(_LI('Deleting instance files %s'), dir,
|
||||
instance=instance)
|
||||
try:
|
||||
shutil.rmtree(dir)
|
||||
except OSError as e:
|
||||
LOG.error(_LE('Failed to cleanup directory %(target)s: '
|
||||
'%(e)s'), {'target': dir, 'e': e},
|
||||
instance=instance)
|
||||
|
||||
def restore(self, instance):
|
||||
container_id = self._get_container_id(instance)
|
||||
if not container_id:
|
||||
return
|
||||
|
||||
self._start_container(container_id, instance)
|
||||
|
||||
def _stop(self, container_id, instance, timeout=5):
|
||||
try:
|
||||
self.docker.stop(container_id, max(timeout, 5))
|
||||
except errors.APIError as e:
|
||||
if 'Unpause the container before stopping' not in e.explanation:
|
||||
LOG.warning(_('Cannot stop container: %s'),
|
||||
e, instance=instance, exc_info=True)
|
||||
raise
|
||||
self.docker.unpause(container_id)
|
||||
self.docker.stop(container_id, timeout)
|
||||
|
||||
def soft_delete(self, instance):
|
||||
container_id = self._get_container_id(instance)
|
||||
if not container_id:
|
||||
return
|
||||
self._stop(container_id, instance)
|
||||
|
||||
def destroy(self, context, instance, network_info, block_device_info=None,
|
||||
destroy_disks=True, migrate_data=None):
|
||||
self.soft_delete(instance)
|
||||
self.cleanup(context, instance, network_info,
|
||||
block_device_info, destroy_disks)
|
||||
|
||||
def cleanup(self, context, instance, network_info, block_device_info=None,
|
||||
destroy_disks=True, migrate_data=None, destroy_vifs=True):
|
||||
"""Cleanup after instance being destroyed by Hypervisor."""
|
||||
container_id = self._get_container_id(instance)
|
||||
if not container_id:
|
||||
self.unplug_vifs(instance, network_info)
|
||||
return
|
||||
self.docker.remove_container(container_id, force=True)
|
||||
network.teardown_network(container_id)
|
||||
self.unplug_vifs(instance, network_info)
|
||||
if CONF.docker.inject_key:
|
||||
self._cleanup_key(instance, container_id)
|
||||
|
||||
def reboot(self, context, instance, network_info, reboot_type,
|
||||
block_device_info=None, bad_volumes_callback=None):
|
||||
container_id = self._get_container_id(instance)
|
||||
if not container_id:
|
||||
return
|
||||
self._stop(container_id, instance)
|
||||
try:
|
||||
network.teardown_network(container_id)
|
||||
if network_info:
|
||||
self.unplug_vifs(instance, network_info)
|
||||
except Exception as e:
|
||||
LOG.warning(_('Cannot destroy the container network'
|
||||
' during reboot {0}').format(e),
|
||||
exc_info=True)
|
||||
return
|
||||
|
||||
binds = self._get_key_binds(container_id, instance)
|
||||
dns = self._extract_dns_entries(network_info)
|
||||
self.docker.start(container_id, binds=binds, dns=dns)
|
||||
try:
|
||||
if network_info:
|
||||
self.plug_vifs(instance, network_info)
|
||||
self._attach_vifs(instance, network_info)
|
||||
except Exception as e:
|
||||
LOG.warning(_('Cannot setup network on reboot: {0}'), e,
|
||||
exc_info=True)
|
||||
return
|
||||
|
||||
def power_on(self, context, instance, network_info,
|
||||
block_device_info=None):
|
||||
container_id = self._get_container_id(instance)
|
||||
if not container_id:
|
||||
return
|
||||
binds = self._get_key_binds(container_id, instance)
|
||||
dns = self._extract_dns_entries(network_info)
|
||||
self.docker.start(container_id, binds=binds, dns=dns)
|
||||
if not network_info:
|
||||
return
|
||||
try:
|
||||
self.plug_vifs(instance, network_info)
|
||||
self._attach_vifs(instance, network_info)
|
||||
except Exception as e:
|
||||
LOG.debug(_('Cannot setup network: %s'),
|
||||
e, instance=instance, exc_info=True)
|
||||
msg = _('Cannot setup network: {0}')
|
||||
self.docker.kill(container_id)
|
||||
self.docker.remove_container(container_id, force=True)
|
||||
raise exception.InstanceDeployFailure(msg.format(e),
|
||||
instance_id=instance['name'])
|
||||
|
||||
def power_off(self, instance, timeout=0, retry_interval=0):
|
||||
container_id = self._get_container_id(instance)
|
||||
if not container_id:
|
||||
return
|
||||
self._stop(container_id, instance, timeout)
|
||||
|
||||
def pause(self, instance):
|
||||
"""Pause the specified instance.
|
||||
|
||||
:param instance: nova.objects.instance.Instance
|
||||
"""
|
||||
try:
|
||||
cont_id = self._get_container_id(instance)
|
||||
if not self.docker.pause(cont_id):
|
||||
raise exception.NovaException
|
||||
except Exception as e:
|
||||
LOG.debug(_('Error pause container: %s'),
|
||||
e, instance=instance, exc_info=True)
|
||||
msg = _('Cannot pause container: {0}')
|
||||
raise exception.NovaException(msg.format(e),
|
||||
instance_id=instance['name'])
|
||||
|
||||
def unpause(self, instance):
|
||||
"""Unpause paused VM instance.
|
||||
|
||||
:param instance: nova.objects.instance.Instance
|
||||
"""
|
||||
try:
|
||||
cont_id = self._get_container_id(instance)
|
||||
if not self.docker.unpause(cont_id):
|
||||
raise exception.NovaException
|
||||
except Exception as e:
|
||||
LOG.debug(_('Error unpause container: %s'),
|
||||
e, instance=instance, exc_info=True)
|
||||
msg = _('Cannot unpause container: {0}')
|
||||
raise exception.NovaException(msg.format(e),
|
||||
instance_id=instance['name'])
|
||||
|
||||
def get_console_output(self, context, instance):
|
||||
container_id = self._get_container_id(instance)
|
||||
if not container_id:
|
||||
return ''
|
||||
return self.docker.get_container_logs(container_id)
|
||||
|
||||
def snapshot(self, context, instance, image_href, update_task_state):
|
||||
container_id = self._get_container_id(instance)
|
||||
if not container_id:
|
||||
raise exception.InstanceNotRunning(instance_id=instance['uuid'])
|
||||
|
||||
update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)
|
||||
(image_service, image_id) = glance.get_remote_image_service(
|
||||
context, image_href)
|
||||
image = image_service.show(context, image_id)
|
||||
if ':' not in image['name']:
|
||||
commit_name = self._encode_utf8(image['name'])
|
||||
tag = 'latest'
|
||||
else:
|
||||
parts = self._encode_utf8(image['name']).rsplit(':', 1)
|
||||
commit_name = parts[0]
|
||||
tag = parts[1]
|
||||
|
||||
self.docker.commit(container_id, repository=commit_name, tag=tag)
|
||||
|
||||
update_task_state(task_state=task_states.IMAGE_UPLOADING,
|
||||
expected_state=task_states.IMAGE_PENDING_UPLOAD)
|
||||
|
||||
metadata = {
|
||||
'is_public': False,
|
||||
'status': 'active',
|
||||
'disk_format': 'raw',
|
||||
'container_format': 'docker',
|
||||
'name': image['name'],
|
||||
'properties': {
|
||||
'image_location': 'snapshot',
|
||||
'image_state': 'available',
|
||||
'status': 'available',
|
||||
'owner_id': instance['project_id'],
|
||||
'ramdisk_id': instance['ramdisk_id']
|
||||
}
|
||||
}
|
||||
if instance['os_type']:
|
||||
metadata['properties']['os_type'] = instance['os_type']
|
||||
|
||||
try:
|
||||
raw = self.docker.get_image(commit_name)
|
||||
# Patch the seek/tell as urllib3 throws UnsupportedOperation
|
||||
raw.seek = lambda x=None, y=None: None
|
||||
raw.tell = lambda: None
|
||||
image_service.update(context, image_href, metadata, raw)
|
||||
except Exception as e:
|
||||
LOG.debug(_('Error saving image: %s'),
|
||||
e, instance=instance, exc_info=True)
|
||||
msg = _('Error saving image: {0}')
|
||||
raise exception.NovaException(msg.format(e),
|
||||
instance_id=instance['name'])
|
||||
|
||||
def _get_cpu_shares(self, instance):
|
||||
"""Get allocated CPUs from configured flavor.
|
||||
|
||||
Docker/lxc supports relative CPU allocation.
|
||||
|
||||
cgroups specifies following:
|
||||
/sys/fs/cgroup/lxc/cpu.shares = 1024
|
||||
/sys/fs/cgroup/cpu.shares = 1024
|
||||
|
||||
For that reason we use 1024 as multiplier.
|
||||
This multiplier allows to divide the CPU
|
||||
resources fair with containers started by
|
||||
the user (e.g. docker registry) which has
|
||||
the default CpuShares value of zero.
|
||||
"""
|
||||
if isinstance(instance, objects.Instance):
|
||||
flavor = instance.get_flavor()
|
||||
else:
|
||||
flavor = flavors.extract_flavor(instance)
|
||||
return int(flavor['vcpus']) * 1024
|
||||
|
||||
def _create_container(self, instance, image_name, args):
|
||||
name = "nova-" + instance['uuid']
|
||||
hostname = args.pop('hostname', None)
|
||||
cpu_shares = args.pop('cpu_shares', None)
|
||||
network_disabled = args.pop('network_disabled', False)
|
||||
environment = args.pop('environment', None)
|
||||
command = args.pop('command', None)
|
||||
host_config = docker_utils.create_host_config(**args)
|
||||
return self.docker.create_container(image_name,
|
||||
name=self._encode_utf8(name),
|
||||
hostname=hostname,
|
||||
cpu_shares=cpu_shares,
|
||||
network_disabled=network_disabled,
|
||||
environment=environment,
|
||||
command=command,
|
||||
host_config=host_config)
|
||||
|
||||
def get_host_uptime(self):
|
||||
return hostutils.sys_uptime()
|
|
@ -1,74 +0,0 @@
|
|||
# Copyright (c) 2013 dotCloud, Inc.
|
||||
# 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 multiprocessing
|
||||
import os
|
||||
|
||||
from oslo_config import cfg
|
||||
import psutil
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def statvfs():
|
||||
docker_path = CONF.docker.root_directory
|
||||
if not os.path.exists(docker_path):
|
||||
docker_path = '/'
|
||||
return psutil.disk_usage(docker_path)
|
||||
|
||||
|
||||
def get_disk_usage():
|
||||
# This is the location where Docker stores its containers. It's currently
|
||||
# hardcoded in Docker so it's not configurable yet.
|
||||
st = statvfs()
|
||||
return {
|
||||
'total': st.total,
|
||||
'available': st.free,
|
||||
'used': st.used
|
||||
}
|
||||
|
||||
|
||||
def get_total_vcpus():
|
||||
return multiprocessing.cpu_count()
|
||||
|
||||
|
||||
def get_vcpus_used(containers):
|
||||
total_vcpus_used = 0
|
||||
for container in containers:
|
||||
if isinstance(container, dict):
|
||||
total_vcpus_used += container.get('Config', {}).get(
|
||||
'CpuShares', 0) / 1024
|
||||
|
||||
return total_vcpus_used
|
||||
|
||||
|
||||
def get_memory_usage():
|
||||
vmem = psutil.virtual_memory()
|
||||
return {
|
||||
'total': vmem.total,
|
||||
'used': vmem.used
|
||||
}
|
||||
|
||||
|
||||
def get_mounts():
|
||||
with open('/proc/mounts') as f:
|
||||
return f.readlines()
|
||||
|
||||
|
||||
def get_cgroup_devices_path():
|
||||
for ln in get_mounts():
|
||||
fields = ln.split(' ')
|
||||
if fields[2] == 'cgroup' and 'devices' in fields[3].split(','):
|
||||
return fields[1]
|
|
@ -1,65 +0,0 @@
|
|||
# Copyright 2014 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 oslo_concurrency import processutils
|
||||
from oslo_log import log
|
||||
|
||||
from nova import exception
|
||||
from nova import utils
|
||||
|
||||
from novadocker.i18n import _
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def teardown_network(container_id):
|
||||
if os.name == 'nt':
|
||||
return
|
||||
|
||||
try:
|
||||
output, err = utils.execute('ip', '-o', 'netns', 'list')
|
||||
for line in output.split('\n'):
|
||||
if container_id == line.strip():
|
||||
utils.execute('ip', 'netns', 'delete', container_id,
|
||||
run_as_root=True)
|
||||
break
|
||||
except processutils.ProcessExecutionError:
|
||||
LOG.warning(_('Cannot remove network namespace, netns id: %s'),
|
||||
container_id)
|
||||
|
||||
|
||||
def find_fixed_ip(instance, network_info):
|
||||
for subnet in network_info['subnets']:
|
||||
netmask = subnet['cidr'].split('/')[1]
|
||||
for ip in subnet['ips']:
|
||||
if ip['type'] == 'fixed' and ip['address']:
|
||||
return ip['address'] + "/" + netmask
|
||||
raise exception.InstanceDeployFailure(_('Cannot find fixed ip'),
|
||||
instance_id=instance['uuid'])
|
||||
|
||||
|
||||
def find_gateway(instance, network_info):
|
||||
for subnet in network_info['subnets']:
|
||||
return subnet['gateway']['address']
|
||||
raise exception.InstanceDeployFailure(_('Cannot find gateway'),
|
||||
instance_id=instance['uuid'])
|
||||
|
||||
|
||||
# NOTE(arosen) - this method should be removed after it's moved into the
|
||||
# linux_net code in nova.
|
||||
def get_ovs_interfaceid(vif):
|
||||
return vif.get('ovs_interfaceid') or vif['id']
|
|
@ -1,172 +0,0 @@
|
|||
# Copyright (C) 2014 Juniper Networks, Inc
|
||||
# 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 eventlet
|
||||
|
||||
from contrail_vrouter_api.vrouter_api import ContrailVRouterApi
|
||||
|
||||
from nova.network import linux_net
|
||||
from nova import utils
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import loopingcall
|
||||
|
||||
from novadocker.i18n import _
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OpenContrailVIFDriver(object):
|
||||
def __init__(self):
|
||||
self._vrouter_semaphore = eventlet.semaphore.Semaphore()
|
||||
self._vrouter_client = ContrailVRouterApi(
|
||||
doconnect=True, semaphore=self._vrouter_semaphore)
|
||||
timer = loopingcall.FixedIntervalLoopingCall(self._keep_alive)
|
||||
timer.start(interval=2)
|
||||
|
||||
def _keep_alive(self):
|
||||
self._vrouter_client.periodic_connection_check()
|
||||
|
||||
def plug(self, instance, vif):
|
||||
vif_type = vif['type']
|
||||
|
||||
LOG.debug('Plug vif_type=%(vif_type)s instance=%(instance)s '
|
||||
'vif=%(vif)s',
|
||||
{'vif_type': vif_type, 'instance': instance,
|
||||
'vif': vif})
|
||||
|
||||
if_local_name = 'veth%s' % vif['id'][:8]
|
||||
if_remote_name = 'ns%s' % vif['id'][:8]
|
||||
|
||||
# Device already exists so return.
|
||||
if linux_net.device_exists(if_local_name):
|
||||
return
|
||||
undo_mgr = utils.UndoManager()
|
||||
|
||||
try:
|
||||
utils.execute('ip', 'link', 'add', if_local_name, 'type', 'veth',
|
||||
'peer', 'name', if_remote_name, run_as_root=True)
|
||||
undo_mgr.undo_with(lambda: utils.execute(
|
||||
'ip', 'link', 'delete', if_local_name, run_as_root=True))
|
||||
|
||||
utils.execute('ip', 'link', 'set', if_remote_name, 'address',
|
||||
vif['address'], run_as_root=True)
|
||||
|
||||
except Exception:
|
||||
LOG.exception("Failed to configure network")
|
||||
msg = _('Failed to setup the network, rolling back')
|
||||
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
|
||||
|
||||
def attach(self, instance, vif, container_id):
|
||||
vif_type = vif['type']
|
||||
|
||||
LOG.debug('Attach vif_type=%(vif_type)s instance=%(instance)s '
|
||||
'vif=%(vif)s',
|
||||
{'vif_type': vif_type, 'instance': instance,
|
||||
'vif': vif})
|
||||
|
||||
if_local_name = 'veth%s' % vif['id'][:8]
|
||||
if_remote_name = 'ns%s' % vif['id'][:8]
|
||||
|
||||
undo_mgr = utils.UndoManager()
|
||||
undo_mgr.undo_with(lambda: utils.execute(
|
||||
'ip', 'link', 'delete', if_local_name, run_as_root=True))
|
||||
ipv4_address = '0.0.0.0'
|
||||
ipv6_address = None
|
||||
if 'subnets' in vif['network']:
|
||||
subnets = vif['network']['subnets']
|
||||
for subnet in subnets:
|
||||
ips = subnet['ips'][0]
|
||||
if (ips['version'] == 4):
|
||||
if ips['address'] is not None:
|
||||
ipv4_address = ips['address']
|
||||
ipv4_netmask = subnet['cidr'].split('/')[1]
|
||||
ipv4_gateway = subnet['gateway']['address']
|
||||
if (ips['version'] == 6):
|
||||
if ips['address'] is not None:
|
||||
ipv6_address = ips['address']
|
||||
ipv6_netmask = subnet['cidr'].split('/')[1]
|
||||
ipv6_gateway = subnet['gateway']['address']
|
||||
params = {
|
||||
'ip_address': ipv4_address,
|
||||
'vn_id': vif['network']['id'],
|
||||
'display_name': instance['display_name'],
|
||||
'hostname': instance['hostname'],
|
||||
'host': instance['host'],
|
||||
'vm_project_id': instance['project_id'],
|
||||
'port_type': 'NovaVMPort',
|
||||
'ip6_address': ipv6_address,
|
||||
}
|
||||
|
||||
try:
|
||||
utils.execute('ip', 'link', 'set', if_remote_name, 'netns',
|
||||
container_id, run_as_root=True)
|
||||
|
||||
result = self._vrouter_client.add_port(
|
||||
instance['uuid'], vif['id'],
|
||||
if_local_name, vif['address'], **params)
|
||||
if not result:
|
||||
# follow the exception path
|
||||
raise RuntimeError('add_port returned %s' % str(result))
|
||||
utils.execute('ip', 'link', 'set', if_local_name, 'up',
|
||||
run_as_root=True)
|
||||
except Exception:
|
||||
LOG.exception("Failed to attach the network")
|
||||
msg = _('Failed to attach the network, rolling back')
|
||||
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
|
||||
|
||||
try:
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ip', 'link',
|
||||
'set', if_remote_name, 'address', vif['address'],
|
||||
run_as_root=True)
|
||||
if ipv6_address:
|
||||
ip = ipv6_address + "/" + ipv6_netmask
|
||||
gateway = ipv6_gateway
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ifconfig',
|
||||
if_remote_name, 'inet6', 'add', ip,
|
||||
run_as_root=True)
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ip', '-6',
|
||||
'route', 'replace', 'default', 'via', gateway,
|
||||
'dev', if_remote_name, run_as_root=True)
|
||||
if ipv4_address != '0.0.0.0':
|
||||
ip = ipv4_address + "/" + ipv4_netmask
|
||||
gateway = ipv4_gateway
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ifconfig',
|
||||
if_remote_name, ip, run_as_root=True)
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ip',
|
||||
'route', 'replace', 'default', 'via', gateway,
|
||||
'dev', if_remote_name, run_as_root=True)
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ip', 'link',
|
||||
'set', if_remote_name, 'up', run_as_root=True)
|
||||
except Exception:
|
||||
LOG.exception(_("Failed to attach vif"), instance=instance)
|
||||
|
||||
def unplug(self, instance, vif):
|
||||
vif_type = vif['type']
|
||||
if_local_name = 'veth%s' % vif['id'][:8]
|
||||
|
||||
LOG.debug('Unplug vif_type=%(vif_type)s instance=%(instance)s '
|
||||
'vif=%(vif)s',
|
||||
{'vif_type': vif_type, 'instance': instance,
|
||||
'vif': vif})
|
||||
|
||||
try:
|
||||
self._vrouter_client.delete_port(vif['id'])
|
||||
if linux_net.device_exists(if_local_name):
|
||||
utils.execute('ip', 'link', 'delete', if_local_name,
|
||||
run_as_root=True)
|
||||
except Exception:
|
||||
LOG.exception(_("Delete port failed"), instance=instance)
|
|
@ -1,491 +0,0 @@
|
|||
# Copyright (C) 2013 VMware, Inc
|
||||
# Copyright 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.
|
||||
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_log import log as logging
|
||||
|
||||
from nova import exception
|
||||
from nova.network import linux_net
|
||||
from nova.network import manager
|
||||
from nova.network import model as network_model
|
||||
from nova import utils
|
||||
from novadocker.i18n import _
|
||||
from novadocker.virt.docker import network
|
||||
from oslo_config import cfg
|
||||
import random
|
||||
|
||||
# We need config opts from manager, but pep8 complains, this silences it.
|
||||
assert manager
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('my_ip', 'nova.conf.netconf')
|
||||
CONF.import_opt('vlan_interface', 'nova.manager')
|
||||
CONF.import_opt('flat_interface', 'nova.manager')
|
||||
CONF.import_opt('network_device_mtu', 'nova.objects.network')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DockerGenericVIFDriver(object):
|
||||
|
||||
def plug(self, instance, vif):
|
||||
vif_type = vif['type']
|
||||
|
||||
LOG.debug('plug vif_type=%(vif_type)s instance=%(instance)s '
|
||||
'vif=%(vif)s',
|
||||
{'vif_type': vif_type, 'instance': instance,
|
||||
'vif': vif})
|
||||
|
||||
if vif_type is None:
|
||||
raise exception.NovaException(
|
||||
_("vif_type parameter must be present "
|
||||
"for this vif_driver implementation"))
|
||||
|
||||
if vif_type == network_model.VIF_TYPE_BRIDGE:
|
||||
self.plug_bridge(instance, vif)
|
||||
elif vif_type == network_model.VIF_TYPE_OVS:
|
||||
if self.ovs_hybrid_required(vif):
|
||||
self.plug_ovs_hybrid(instance, vif)
|
||||
else:
|
||||
self.plug_ovs(instance, vif)
|
||||
elif vif_type == network_model.VIF_TYPE_MIDONET:
|
||||
self.plug_midonet(instance, vif)
|
||||
elif vif_type == network_model.VIF_TYPE_IOVISOR:
|
||||
self.plug_iovisor(instance, vif)
|
||||
elif vif_type == 'hyperv':
|
||||
self.plug_windows(instance, vif)
|
||||
else:
|
||||
raise exception.NovaException(
|
||||
_("Unexpected vif_type=%s") % vif_type)
|
||||
|
||||
def plug_windows(self, instance, vif):
|
||||
pass
|
||||
|
||||
def plug_iovisor(self, instance, vif):
|
||||
"""Plug docker vif into IOvisor
|
||||
|
||||
Creates a port on IOvisor and onboards the interface
|
||||
"""
|
||||
if_local_name = 'tap%s' % vif['id'][:11]
|
||||
if_remote_name = 'ns%s' % vif['id'][:11]
|
||||
|
||||
iface_id = vif['id']
|
||||
net_id = vif['network']['id']
|
||||
tenant_id = instance['project_id']
|
||||
|
||||
# Device already exists so return.
|
||||
if linux_net.device_exists(if_local_name):
|
||||
return
|
||||
undo_mgr = utils.UndoManager()
|
||||
|
||||
try:
|
||||
utils.execute('ip', 'link', 'add', 'name', if_local_name, 'type',
|
||||
'veth', 'peer', 'name', if_remote_name,
|
||||
run_as_root=True)
|
||||
utils.execute('ifc_ctl', 'gateway', 'add_port', if_local_name,
|
||||
run_as_root=True)
|
||||
utils.execute('ifc_ctl', 'gateway', 'ifup', if_local_name,
|
||||
'access_vm',
|
||||
vif['network']['label'] + "_" + iface_id,
|
||||
vif['address'], 'pgtag2=%s' % net_id,
|
||||
'pgtag1=%s' % tenant_id, run_as_root=True)
|
||||
utils.execute('ip', 'link', 'set', if_local_name, 'up',
|
||||
run_as_root=True)
|
||||
|
||||
except Exception:
|
||||
LOG.exception("Failed to configure network on IOvisor")
|
||||
msg = _('Failed to setup the network, rolling back')
|
||||
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
|
||||
|
||||
def plug_ovs(self, instance, vif):
|
||||
if_local_name = 'tap%s' % vif['id'][:11]
|
||||
if_remote_name = 'ns%s' % vif['id'][:11]
|
||||
bridge = vif['network']['bridge']
|
||||
|
||||
# Device already exists so return.
|
||||
if linux_net.device_exists(if_local_name):
|
||||
return
|
||||
undo_mgr = utils.UndoManager()
|
||||
|
||||
try:
|
||||
utils.execute('ip', 'link', 'add', 'name', if_local_name, 'type',
|
||||
'veth', 'peer', 'name', if_remote_name,
|
||||
run_as_root=True)
|
||||
linux_net.create_ovs_vif_port(bridge, if_local_name,
|
||||
network.get_ovs_interfaceid(vif),
|
||||
vif['address'],
|
||||
instance['uuid'])
|
||||
utils.execute('ip', 'link', 'set', if_local_name, 'up',
|
||||
run_as_root=True)
|
||||
except Exception:
|
||||
LOG.exception("Failed to configure network")
|
||||
msg = _('Failed to setup the network, rolling back')
|
||||
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
|
||||
|
||||
def plug_midonet(self, instance, vif):
|
||||
"""Plug into MidoNet's network port
|
||||
|
||||
This accomplishes binding of the vif to a MidoNet virtual port
|
||||
"""
|
||||
if_local_name = 'tap%s' % vif['id'][:11]
|
||||
if_remote_name = 'ns%s' % vif['id'][:11]
|
||||
port_id = network.get_ovs_interfaceid(vif)
|
||||
|
||||
# Device already exists so return.
|
||||
if linux_net.device_exists(if_local_name):
|
||||
return
|
||||
|
||||
undo_mgr = utils.UndoManager()
|
||||
try:
|
||||
utils.execute('ip', 'link', 'add', 'name', if_local_name, 'type',
|
||||
'veth', 'peer', 'name', if_remote_name,
|
||||
run_as_root=True)
|
||||
undo_mgr.undo_with(lambda: utils.execute(
|
||||
'ip', 'link', 'delete', if_local_name, run_as_root=True))
|
||||
utils.execute('ip', 'link', 'set', if_local_name, 'up',
|
||||
run_as_root=True)
|
||||
utils.execute('mm-ctl', '--bind-port', port_id, if_local_name,
|
||||
run_as_root=True)
|
||||
except Exception:
|
||||
LOG.exception("Failed to configure network")
|
||||
msg = _('Failed to setup the network, rolling back')
|
||||
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
|
||||
|
||||
def plug_ovs_hybrid(self, instance, vif):
|
||||
"""Plug using hybrid strategy
|
||||
|
||||
Create a per-VIF linux bridge, then link that bridge to the OVS
|
||||
integration bridge via a veth device, setting up the other end
|
||||
of the veth device just like a normal OVS port. Then boot the
|
||||
VIF on the linux bridge. and connect the tap port to linux bridge
|
||||
"""
|
||||
|
||||
if_local_name = 'tap%s' % vif['id'][:11]
|
||||
if_remote_name = 'ns%s' % vif['id'][:11]
|
||||
iface_id = self.get_ovs_interfaceid(vif)
|
||||
br_name = self.get_br_name(vif['id'])
|
||||
v1_name, v2_name = self.get_veth_pair_names(vif['id'])
|
||||
|
||||
# Device already exists so return.
|
||||
if linux_net.device_exists(if_local_name):
|
||||
return
|
||||
undo_mgr = utils.UndoManager()
|
||||
|
||||
try:
|
||||
if not linux_net.device_exists(br_name):
|
||||
utils.execute('brctl', 'addbr', br_name, run_as_root=True)
|
||||
# Incase of failure undo the Steps
|
||||
# Deleting/Undoing the interface will delete all
|
||||
# associated resources
|
||||
undo_mgr.undo_with(lambda: utils.execute(
|
||||
'brctl', 'delbr', br_name, run_as_root=True))
|
||||
# LOG.exception('Throw Test exception with bridgename %s'
|
||||
# % br_name)
|
||||
|
||||
utils.execute('brctl', 'setfd', br_name, 0, run_as_root=True)
|
||||
utils.execute('brctl', 'stp', br_name, 'off', run_as_root=True)
|
||||
utils.execute('tee',
|
||||
('/sys/class/net/%s/bridge/multicast_snooping' %
|
||||
br_name),
|
||||
process_input='0',
|
||||
run_as_root=True,
|
||||
check_exit_code=[0, 1])
|
||||
|
||||
if not linux_net.device_exists(v2_name):
|
||||
linux_net._create_veth_pair(v1_name, v2_name)
|
||||
undo_mgr.undo_with(lambda: utils.execute(
|
||||
'ip', 'link', 'delete', v1_name, run_as_root=True))
|
||||
|
||||
utils.execute('ip', 'link', 'set', br_name, 'up',
|
||||
run_as_root=True)
|
||||
undo_mgr.undo_with(lambda: utils.execute('ip', 'link', 'set',
|
||||
br_name, 'down',
|
||||
run_as_root=True))
|
||||
|
||||
# Deleting/Undoing the interface will delete all
|
||||
# associated resources (remove from the bridge, its
|
||||
# pair, etc...)
|
||||
utils.execute('brctl', 'addif', br_name, v1_name,
|
||||
run_as_root=True)
|
||||
|
||||
linux_net.create_ovs_vif_port(self.get_bridge_name(vif),
|
||||
v2_name,
|
||||
iface_id, vif['address'],
|
||||
instance['uuid'])
|
||||
undo_mgr.undo_with(
|
||||
lambda: utils.execute('ovs-vsctl', 'del-port',
|
||||
self.get_bridge_name(vif),
|
||||
v2_name, run_as_root=True))
|
||||
|
||||
utils.execute('ip', 'link', 'add', 'name', if_local_name, 'type',
|
||||
'veth', 'peer', 'name', if_remote_name,
|
||||
run_as_root=True)
|
||||
undo_mgr.undo_with(
|
||||
lambda: utils.execute('ip', 'link', 'delete', if_local_name,
|
||||
run_as_root=True))
|
||||
|
||||
# Deleting/Undoing the interface will delete all
|
||||
# associated resources (remove from the bridge, its pair, etc...)
|
||||
utils.execute('brctl', 'addif', br_name, if_local_name,
|
||||
run_as_root=True)
|
||||
utils.execute('ip', 'link', 'set', if_local_name, 'up',
|
||||
run_as_root=True)
|
||||
except Exception:
|
||||
msg = "Failed to configure Network." \
|
||||
" Rolling back the network interfaces %s %s %s %s " % (
|
||||
br_name, if_local_name, v1_name, v2_name)
|
||||
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
|
||||
|
||||
# We are creating our own mac's now because the linux bridge interface
|
||||
# takes on the lowest mac that is assigned to it. By using FE range
|
||||
# mac's we prevent the interruption and possible loss of networking
|
||||
# from changing mac addresses.
|
||||
def _fe_random_mac(self):
|
||||
mac = [0xfe, 0xed,
|
||||
random.randint(0x00, 0xff),
|
||||
random.randint(0x00, 0xff),
|
||||
random.randint(0x00, 0xff),
|
||||
random.randint(0x00, 0xff)]
|
||||
return ':'.join(map(lambda x: "%02x" % x, mac))
|
||||
|
||||
def plug_bridge(self, instance, vif):
|
||||
if_local_name = 'tap%s' % vif['id'][:11]
|
||||
if_remote_name = 'ns%s' % vif['id'][:11]
|
||||
bridge = vif['network']['bridge']
|
||||
gateway = network.find_gateway(instance, vif['network'])
|
||||
|
||||
net = vif['network']
|
||||
if net.get_meta('should_create_vlan', False):
|
||||
vlan = net.get_meta('vlan'),
|
||||
iface = (CONF.vlan_interface or
|
||||
vif['network'].get_meta('bridge_interface'))
|
||||
linux_net.LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
|
||||
vlan,
|
||||
bridge,
|
||||
iface,
|
||||
net_attrs=vif,
|
||||
mtu=vif.get('mtu'))
|
||||
iface = 'vlan%s' % vlan
|
||||
else:
|
||||
iface = (CONF.flat_interface or
|
||||
vif['network'].get_meta('bridge_interface'))
|
||||
LOG.debug('Ensuring bridge for %s - %s' % (iface, bridge))
|
||||
linux_net.LinuxBridgeInterfaceDriver.ensure_bridge(
|
||||
bridge,
|
||||
iface,
|
||||
net_attrs=vif,
|
||||
gateway=gateway)
|
||||
|
||||
# Device already exists so return.
|
||||
if linux_net.device_exists(if_local_name):
|
||||
return
|
||||
undo_mgr = utils.UndoManager()
|
||||
|
||||
try:
|
||||
utils.execute('ip', 'link', 'add', 'name', if_local_name, 'type',
|
||||
'veth', 'peer', 'name', if_remote_name,
|
||||
run_as_root=True)
|
||||
undo_mgr.undo_with(lambda: utils.execute(
|
||||
'ip', 'link', 'delete', if_local_name, run_as_root=True))
|
||||
# NOTE(samalba): Deleting the interface will delete all
|
||||
# associated resources (remove from the bridge, its pair, etc...)
|
||||
utils.execute('ip', 'link', 'set', if_local_name, 'address',
|
||||
self._fe_random_mac(), run_as_root=True)
|
||||
utils.execute('brctl', 'addif', bridge, if_local_name,
|
||||
run_as_root=True)
|
||||
utils.execute('ip', 'link', 'set', if_local_name, 'up',
|
||||
run_as_root=True)
|
||||
except Exception:
|
||||
LOG.exception("Failed to configure network")
|
||||
msg = _('Failed to setup the network, rolling back')
|
||||
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
|
||||
|
||||
def unplug(self, instance, vif):
|
||||
vif_type = vif['type']
|
||||
|
||||
LOG.debug('vif_type=%(vif_type)s instance=%(instance)s '
|
||||
'vif=%(vif)s',
|
||||
{'vif_type': vif_type, 'instance': instance,
|
||||
'vif': vif})
|
||||
|
||||
if vif_type is None:
|
||||
raise exception.NovaException(
|
||||
_("vif_type parameter must be present "
|
||||
"for this vif_driver implementation"))
|
||||
|
||||
if vif_type == network_model.VIF_TYPE_BRIDGE:
|
||||
self.unplug_bridge(instance, vif)
|
||||
elif vif_type == network_model.VIF_TYPE_OVS:
|
||||
if self.ovs_hybrid_required(vif):
|
||||
self.unplug_ovs_hybrid(instance, vif)
|
||||
else:
|
||||
self.unplug_ovs(instance, vif)
|
||||
elif vif_type == network_model.VIF_TYPE_MIDONET:
|
||||
self.unplug_midonet(instance, vif)
|
||||
elif vif_type == network_model.VIF_TYPE_IOVISOR:
|
||||
self.unplug_iovisor(instance, vif)
|
||||
elif vif_type == 'hyperv':
|
||||
self.unplug_windows(instance, vif)
|
||||
else:
|
||||
raise exception.NovaException(
|
||||
_("Unexpected vif_type=%s") % vif_type)
|
||||
|
||||
def unplug_windows(self, instance, vif):
|
||||
pass
|
||||
|
||||
def unplug_iovisor(self, instance, vif):
|
||||
"""Unplug vif from IOvisor
|
||||
|
||||
Offboard an interface and deletes port from IOvisor
|
||||
"""
|
||||
if_local_name = 'tap%s' % vif['id'][:11]
|
||||
iface_id = vif['id']
|
||||
try:
|
||||
utils.execute('ifc_ctl', 'gateway', 'ifdown',
|
||||
if_local_name, 'access_vm',
|
||||
vif['network']['label'] + "_" + iface_id,
|
||||
vif['address'], run_as_root=True)
|
||||
utils.execute('ifc_ctl', 'gateway', 'del_port', if_local_name,
|
||||
run_as_root=True)
|
||||
linux_net.delete_net_dev(if_local_name)
|
||||
except processutils.ProcessExecutionError:
|
||||
LOG.exception(_("Failed while unplugging vif"), instance=instance)
|
||||
|
||||
def unplug_ovs(self, instance, vif):
|
||||
"""Unplug the VIF by deleting the port from the bridge."""
|
||||
try:
|
||||
linux_net.delete_ovs_vif_port(vif['network']['bridge'],
|
||||
vif['devname'])
|
||||
except processutils.ProcessExecutionError:
|
||||
LOG.exception(_("Failed while unplugging vif"), instance=instance)
|
||||
|
||||
def unplug_midonet(self, instance, vif):
|
||||
"""Unplug into MidoNet's network port
|
||||
|
||||
This accomplishes unbinding of the vif from its MidoNet virtual port
|
||||
"""
|
||||
try:
|
||||
utils.execute('mm-ctl', '--unbind-port',
|
||||
network.get_ovs_interfaceid(vif), run_as_root=True)
|
||||
except processutils.ProcessExecutionError:
|
||||
LOG.exception(_("Failed while unplugging vif"), instance=instance)
|
||||
|
||||
def unplug_ovs_hybrid(self, instance, vif):
|
||||
"""UnPlug using hybrid strategy
|
||||
|
||||
Unhook port from OVS, unhook port from bridge, delete
|
||||
bridge, and delete both veth devices.
|
||||
"""
|
||||
try:
|
||||
br_name = self.get_br_name(vif['id'])
|
||||
v1_name, v2_name = self.get_veth_pair_names(vif['id'])
|
||||
|
||||
if linux_net.device_exists(br_name):
|
||||
utils.execute('brctl', 'delif', br_name, v1_name,
|
||||
run_as_root=True)
|
||||
utils.execute('ip', 'link', 'set', br_name, 'down',
|
||||
run_as_root=True)
|
||||
utils.execute('brctl', 'delbr', br_name,
|
||||
run_as_root=True)
|
||||
|
||||
linux_net.delete_ovs_vif_port(self.get_bridge_name(vif),
|
||||
v2_name)
|
||||
except processutils.ProcessExecutionError:
|
||||
LOG.exception(_("Failed while unplugging vif"), instance=instance)
|
||||
|
||||
def unplug_bridge(self, instance, vif):
|
||||
# NOTE(arosen): nothing has to be done in the linuxbridge case
|
||||
# as when the veth is deleted it automatically is removed from
|
||||
# the bridge.
|
||||
pass
|
||||
|
||||
def attach(self, instance, vif, container_id):
|
||||
vif_type = vif['type']
|
||||
if_remote_name = 'ns%s' % vif['id'][:11]
|
||||
gateway = network.find_gateway(instance, vif['network'])
|
||||
ip = network.find_fixed_ip(instance, vif['network'])
|
||||
|
||||
LOG.debug('attach vif_type=%(vif_type)s instance=%(instance)s '
|
||||
'vif=%(vif)s',
|
||||
{'vif_type': vif_type, 'instance': instance,
|
||||
'vif': vif})
|
||||
|
||||
try:
|
||||
utils.execute('ip', 'link', 'set', if_remote_name, 'netns',
|
||||
container_id, run_as_root=True)
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ip', 'link',
|
||||
'set', if_remote_name, 'address', vif['address'],
|
||||
run_as_root=True)
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ip', 'addr',
|
||||
'add', ip, 'dev', if_remote_name, run_as_root=True)
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ip', 'link',
|
||||
'set', if_remote_name, 'up', run_as_root=True)
|
||||
|
||||
# Setup MTU on if_remote_name is required if it is a non
|
||||
# default value
|
||||
mtu = CONF.network_device_mtu
|
||||
if vif.get('mtu') is not None:
|
||||
mtu = vif.get('mtu')
|
||||
if mtu is not None:
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ip',
|
||||
'link', 'set', if_remote_name, 'mtu', mtu,
|
||||
run_as_root=True)
|
||||
|
||||
if gateway is not None:
|
||||
utils.execute('ip', 'netns', 'exec', container_id,
|
||||
'ip', 'route', 'replace', 'default', 'via',
|
||||
gateway, 'dev', if_remote_name, run_as_root=True)
|
||||
|
||||
# Disable TSO, for now no config option
|
||||
utils.execute('ip', 'netns', 'exec', container_id, 'ethtool',
|
||||
'--offload', if_remote_name, 'tso', 'off',
|
||||
run_as_root=True)
|
||||
|
||||
except Exception:
|
||||
LOG.exception("Failed to attach vif")
|
||||
|
||||
def get_bridge_name(self, vif):
|
||||
return vif['network']['bridge']
|
||||
|
||||
def get_ovs_interfaceid(self, vif):
|
||||
return vif.get('ovs_interfaceid') or vif['id']
|
||||
|
||||
def get_br_name(self, iface_id):
|
||||
return ("qbr" + iface_id)[:network_model.NIC_NAME_LEN]
|
||||
|
||||
def get_veth_pair_names(self, iface_id):
|
||||
return (("qvb%s" % iface_id)[:network_model.NIC_NAME_LEN],
|
||||
("qvo%s" % iface_id)[:network_model.NIC_NAME_LEN])
|
||||
|
||||
def ovs_hybrid_required(self, vif):
|
||||
ovs_hybrid_required = self.get_firewall_required(vif) or \
|
||||
self.get_hybrid_plug_enabled(vif)
|
||||
return ovs_hybrid_required
|
||||
|
||||
def get_firewall_required(self, vif):
|
||||
if vif.get('details'):
|
||||
enabled = vif['details'].get('port_filter', False)
|
||||
if enabled:
|
||||
return False
|
||||
if CONF.firewall_driver != "nova.virt.firewall.NoopFirewallDriver":
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_hybrid_plug_enabled(self, vif):
|
||||
if vif.get('details'):
|
||||
return vif['details'].get('ovs_hybrid_plug', False)
|
||||
return False
|
|
@ -1,35 +0,0 @@
|
|||
# Copyright (c) 2014 Docker, Inc.
|
||||
# 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 ctypes
|
||||
import datetime
|
||||
import os
|
||||
import time
|
||||
|
||||
from nova import utils
|
||||
|
||||
|
||||
def sys_uptime():
|
||||
"""Returns the host uptime."""
|
||||
|
||||
if os.name == 'nt':
|
||||
tick_count64 = ctypes.windll.kernel32.GetTickCount64()
|
||||
return ("%s up %s, 0 users, load average: 0, 0, 0" %
|
||||
(str(time.strftime("%H:%M:%S")),
|
||||
str(datetime.timedelta(milliseconds=long(tick_count64)))))
|
||||
else:
|
||||
out, err = utils.execute('env', 'LANG=C', 'uptime')
|
||||
return out
|
|
@ -1,12 +0,0 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
pbr>=1.6 # Apache-2.0
|
||||
Babel!=2.3.0,!=2.3.1,!=2.3.2,!=2.3.3,>=1.3 # BSD
|
||||
six>=1.9.0 # MIT
|
||||
oslo.serialization>=1.10.0 # Apache-2.0
|
||||
oslo.utils>=3.5.0 # Apache-2.0
|
||||
oslo.config>=3.9.0 # Apache-2.0
|
||||
oslo.concurrency>=3.5.0 # Apache-2.0
|
||||
oslo.i18n>=2.1.0 # Apache-2.0
|
||||
docker-py<1.8.0,>=1.6.0 # Apache-2.0
|
252
run_tests.sh
252
run_tests.sh
|
@ -1,252 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
function usage {
|
||||
echo "Usage: $0 [OPTION]..."
|
||||
echo "Run Nova's test suite(s)"
|
||||
echo ""
|
||||
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
|
||||
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
|
||||
echo " -s, --no-site-packages Isolate the virtualenv from the global Python environment"
|
||||
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
|
||||
echo " -u, --update Update the virtual environment with any newer package versions"
|
||||
echo " -p, --pep8 Just run PEP8 and HACKING compliance check"
|
||||
echo " -8, --pep8-only-changed Just run PEP8 and HACKING compliance check on files changed since HEAD~1"
|
||||
echo " -P, --no-pep8 Don't run static code checks"
|
||||
echo " -c, --coverage Generate coverage report"
|
||||
echo " -d, --debug Run tests with testtools instead of testr. This allows you to use the debugger."
|
||||
echo " -h, --help Print this usage message"
|
||||
echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list"
|
||||
echo " --virtual-env-path <path> Location of the virtualenv directory"
|
||||
echo " Default: \$(pwd)"
|
||||
echo " --virtual-env-name <name> Name of the virtualenv directory"
|
||||
echo " Default: .venv"
|
||||
echo " --tools-path <dir> Location of the tools directory"
|
||||
echo " Default: \$(pwd)"
|
||||
echo " --concurrency <concurrency> How many processes to use when running the tests. A value of 0 autodetects concurrency from your CPU count"
|
||||
echo " Default: 0"
|
||||
echo ""
|
||||
echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
|
||||
echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
|
||||
echo " prefer to run tests NOT in a virtual environment, simply pass the -N option."
|
||||
exit
|
||||
}
|
||||
|
||||
function process_options {
|
||||
i=1
|
||||
while [ $i -le $# ]; do
|
||||
case "${!i}" in
|
||||
-h|--help) usage;;
|
||||
-V|--virtual-env) always_venv=1; never_venv=0;;
|
||||
-N|--no-virtual-env) always_venv=0; never_venv=1;;
|
||||
-s|--no-site-packages) no_site_packages=1;;
|
||||
-f|--force) force=1;;
|
||||
-u|--update) update=1;;
|
||||
-p|--pep8) just_pep8=1;;
|
||||
-8|--pep8-only-changed) just_pep8_changed=1;;
|
||||
-P|--no-pep8) no_pep8=1;;
|
||||
-c|--coverage) coverage=1;;
|
||||
-d|--debug) debug=1;;
|
||||
--virtual-env-path)
|
||||
(( i++ ))
|
||||
venv_path=${!i}
|
||||
;;
|
||||
--virtual-env-name)
|
||||
(( i++ ))
|
||||
venv_dir=${!i}
|
||||
;;
|
||||
--tools-path)
|
||||
(( i++ ))
|
||||
tools_path=${!i}
|
||||
;;
|
||||
--concurrency)
|
||||
(( i++ ))
|
||||
concurrency=${!i}
|
||||
;;
|
||||
-*) testropts="$testropts ${!i}";;
|
||||
*) testrargs="$testrargs ${!i}"
|
||||
esac
|
||||
(( i++ ))
|
||||
done
|
||||
}
|
||||
|
||||
tool_path=${tools_path:-$(pwd)}
|
||||
venv_path=${venv_path:-$(pwd)}
|
||||
venv_dir=${venv_name:-.venv}
|
||||
with_venv=tools/with_venv.sh
|
||||
always_venv=0
|
||||
never_venv=0
|
||||
force=0
|
||||
no_site_packages=0
|
||||
installvenvopts=
|
||||
testrargs=
|
||||
testropts=
|
||||
wrapper=""
|
||||
just_pep8=0
|
||||
just_pep8_changed=0
|
||||
no_pep8=0
|
||||
coverage=0
|
||||
debug=0
|
||||
update=0
|
||||
concurrency=0
|
||||
|
||||
LANG=en_US.UTF-8
|
||||
LANGUAGE=en_US:en
|
||||
LC_ALL=C
|
||||
|
||||
process_options $@
|
||||
# Make our paths available to other scripts we call
|
||||
export venv_path
|
||||
export venv_dir
|
||||
export venv_name
|
||||
export tools_dir
|
||||
export venv=${venv_path}/${venv_dir}
|
||||
|
||||
if [ $no_site_packages -eq 1 ]; then
|
||||
installvenvopts="--no-site-packages"
|
||||
fi
|
||||
|
||||
function run_tests {
|
||||
# Cleanup *pyc
|
||||
${wrapper} find . -type f -name "*.pyc" -delete
|
||||
|
||||
if [ $debug -eq 1 ]; then
|
||||
if [ "$testropts" = "" ] && [ "$testrargs" = "" ]; then
|
||||
# Default to running all tests if specific test is not
|
||||
# provided.
|
||||
testrargs="discover ./nova/tests"
|
||||
fi
|
||||
${wrapper} python -m testtools.run $testropts $testrargs
|
||||
|
||||
# Short circuit because all of the testr and coverage stuff
|
||||
# below does not make sense when running testtools.run for
|
||||
# debugging purposes.
|
||||
return $?
|
||||
fi
|
||||
|
||||
if [ $coverage -eq 1 ]; then
|
||||
TESTRTESTS="$TESTRTESTS --coverage"
|
||||
else
|
||||
TESTRTESTS="$TESTRTESTS"
|
||||
fi
|
||||
|
||||
# Just run the test suites in current environment
|
||||
set +e
|
||||
testrargs=`echo "$testrargs" | sed -e's/^\s*\(.*\)\s*$/\1/'`
|
||||
TESTRTESTS="$TESTRTESTS --testr-args='--subunit --concurrency $concurrency $testropts $testrargs'"
|
||||
if [ setup.cfg -nt nova.egg-info/entry_points.txt ]
|
||||
then
|
||||
${wrapper} python setup.py egg_info
|
||||
fi
|
||||
echo "Running \`${wrapper} $TESTRTESTS\`"
|
||||
if ${wrapper} which subunit-2to1 2>&1 > /dev/null
|
||||
then
|
||||
# subunit-2to1 is present, testr subunit stream should be in version 2
|
||||
# format. Convert to version one before colorizing.
|
||||
bash -c "${wrapper} $TESTRTESTS | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py"
|
||||
else
|
||||
bash -c "${wrapper} $TESTRTESTS | ${wrapper} tools/colorizer.py"
|
||||
fi
|
||||
RESULT=$?
|
||||
set -e
|
||||
|
||||
copy_subunit_log
|
||||
|
||||
if [ $coverage -eq 1 ]; then
|
||||
echo "Generating coverage report in covhtml/"
|
||||
# Don't compute coverage for common code, which is tested elsewhere
|
||||
${wrapper} coverage combine
|
||||
${wrapper} coverage html --include='nova/*' --omit='nova/openstack/common/*' -d covhtml -i
|
||||
fi
|
||||
|
||||
return $RESULT
|
||||
}
|
||||
|
||||
function copy_subunit_log {
|
||||
LOGNAME=`cat .testrepository/next-stream`
|
||||
LOGNAME=$(($LOGNAME - 1))
|
||||
LOGNAME=".testrepository/${LOGNAME}"
|
||||
cp $LOGNAME subunit.log
|
||||
}
|
||||
|
||||
function warn_on_flake8_without_venv {
|
||||
if [ $never_venv -eq 1 ]; then
|
||||
echo "**WARNING**:"
|
||||
echo "Running flake8 without virtual env may miss OpenStack HACKING detection"
|
||||
fi
|
||||
}
|
||||
|
||||
function run_pep8 {
|
||||
echo "Running flake8 ..."
|
||||
warn_on_flake8_without_venv
|
||||
bash -c "${wrapper} flake8"
|
||||
}
|
||||
|
||||
|
||||
TESTRTESTS="python setup.py testr"
|
||||
|
||||
if [ $never_venv -eq 0 ]
|
||||
then
|
||||
# Remove the virtual environment if --force used
|
||||
if [ $force -eq 1 ]; then
|
||||
echo "Cleaning virtualenv..."
|
||||
rm -rf ${venv}
|
||||
fi
|
||||
if [ $update -eq 1 ]; then
|
||||
echo "Updating virtualenv..."
|
||||
python tools/install_venv.py $installvenvopts
|
||||
fi
|
||||
if [ -e ${venv} ]; then
|
||||
wrapper="${with_venv}"
|
||||
else
|
||||
if [ $always_venv -eq 1 ]; then
|
||||
# Automatically install the virtualenv
|
||||
python tools/install_venv.py $installvenvopts
|
||||
wrapper="${with_venv}"
|
||||
else
|
||||
echo -e "No virtual environment found...create one? (Y/n) \c"
|
||||
read use_ve
|
||||
if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
|
||||
# Install the virtualenv and run the test suite in it
|
||||
python tools/install_venv.py $installvenvopts
|
||||
wrapper=${with_venv}
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Delete old coverage data from previous runs
|
||||
if [ $coverage -eq 1 ]; then
|
||||
${wrapper} coverage erase
|
||||
fi
|
||||
|
||||
if [ $just_pep8 -eq 1 ]; then
|
||||
run_pep8
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ $just_pep8_changed -eq 1 ]; then
|
||||
# NOTE(gilliard) We want use flake8 to check the entirety of every file that has
|
||||
# a change in it. Unfortunately the --filenames argument to flake8 only accepts
|
||||
# file *names* and there are no files named (eg) "nova/compute/manager.py". The
|
||||
# --diff argument behaves surprisingly as well, because although you feed it a
|
||||
# diff, it actually checks the file on disk anyway.
|
||||
files=$(git diff --name-only HEAD~1 | tr '\n' ' ')
|
||||
echo "Running flake8 on ${files}"
|
||||
warn_on_flake8_without_venv
|
||||
bash -c "diff -u --from-file /dev/null ${files} | ${wrapper} flake8 --diff"
|
||||
exit
|
||||
fi
|
||||
|
||||
run_tests
|
||||
|
||||
# NOTE(sirp): we only want to run pep8 when we're running the full-test suite,
|
||||
# not when we're running tests individually. To handle this, we need to
|
||||
# distinguish between options (testropts), which begin with a '-', and
|
||||
# arguments (testrargs).
|
||||
if [ -z "$testrargs" ]; then
|
||||
if [ $no_pep8 -eq 0 ]; then
|
||||
run_pep8
|
||||
fi
|
||||
fi
|
45
setup.cfg
45
setup.cfg
|
@ -1,45 +0,0 @@
|
|||
[metadata]
|
||||
name = nova-docker
|
||||
summary = Docker driver for OpenStack Nova.
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = http://www.openstack.org/
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.3
|
||||
|
||||
[files]
|
||||
packages =
|
||||
novadocker
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[upload_sphinx]
|
||||
upload-dir = doc/build/html
|
||||
|
||||
[compile_catalog]
|
||||
directory = novadocker/locale
|
||||
domain = nova-docker
|
||||
|
||||
[update_catalog]
|
||||
domain = nova-docker
|
||||
output_dir = novadocker/locale
|
||||
input_file = novadocker/locale/nova-docker.pot
|
||||
|
||||
[extract_messages]
|
||||
keywords = _ gettext ngettext l_ lazy_gettext
|
||||
mapping_file = babel.cfg
|
||||
output_file = novadocker/locale/nova-docker.pot
|
29
setup.py
29
setup.py
|
@ -1,29 +0,0 @@
|
|||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr>=1.8'],
|
||||
pbr=True)
|
|
@ -1,17 +0,0 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
hacking<0.11,>=0.10.0
|
||||
|
||||
coverage>=3.6 # Apache-2.0
|
||||
discover # BSD
|
||||
fixtures<2.0,>=1.3.1 # Apache-2.0/BSD
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
|
||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
mock>=1.2 # BSD
|
||||
mox3>=0.7.0 # Apache-2.0
|
|
@ -1,333 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright (c) 2013, Nebula, Inc.
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
#
|
||||
# Colorizer Code is borrowed from Twisted:
|
||||
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
"""Display a subunit stream through a colorized unittest test runner."""
|
||||
|
||||
import heapq
|
||||
import subunit
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import testtools
|
||||
|
||||
|
||||
class _AnsiColorizer(object):
|
||||
"""
|
||||
A colorizer is an object that loosely wraps around a stream, allowing
|
||||
callers to write text to the stream in a particular color.
|
||||
|
||||
Colorizer classes must implement C{supported()} and C{write(text, color)}.
|
||||
"""
|
||||
_colors = dict(black=30, red=31, green=32, yellow=33,
|
||||
blue=34, magenta=35, cyan=36, white=37)
|
||||
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
|
||||
def supported(cls, stream=sys.stdout):
|
||||
"""
|
||||
A class method that returns True if the current platform supports
|
||||
coloring terminal output using this method. Returns False otherwise.
|
||||
"""
|
||||
if not stream.isatty():
|
||||
return False # auto color only on TTYs
|
||||
try:
|
||||
import curses
|
||||
except ImportError:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
try:
|
||||
return curses.tigetnum("colors") > 2
|
||||
except curses.error:
|
||||
curses.setupterm()
|
||||
return curses.tigetnum("colors") > 2
|
||||
except Exception:
|
||||
# guess false in case of error
|
||||
return False
|
||||
supported = classmethod(supported)
|
||||
|
||||
def write(self, text, color):
|
||||
"""
|
||||
Write the given text to the stream in the given color.
|
||||
|
||||
@param text: Text to be written to the stream.
|
||||
|
||||
@param color: A string label for a color. e.g. 'red', 'white'.
|
||||
"""
|
||||
color = self._colors[color]
|
||||
self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
|
||||
|
||||
|
||||
class _Win32Colorizer(object):
|
||||
"""
|
||||
See _AnsiColorizer docstring.
|
||||
"""
|
||||
def __init__(self, stream):
|
||||
import win32console
|
||||
red, green, blue, bold = (win32console.FOREGROUND_RED,
|
||||
win32console.FOREGROUND_GREEN,
|
||||
win32console.FOREGROUND_BLUE,
|
||||
win32console.FOREGROUND_INTENSITY)
|
||||
self.stream = stream
|
||||
self.screenBuffer = win32console.GetStdHandle(
|
||||
win32console.STD_OUT_HANDLE)
|
||||
self._colors = {
|
||||
'normal': red | green | blue,
|
||||
'red': red | bold,
|
||||
'green': green | bold,
|
||||
'blue': blue | bold,
|
||||
'yellow': red | green | bold,
|
||||
'magenta': red | blue | bold,
|
||||
'cyan': green | blue | bold,
|
||||
'white': red | green | blue | bold
|
||||
}
|
||||
|
||||
def supported(cls, stream=sys.stdout):
|
||||
try:
|
||||
import win32console
|
||||
screenBuffer = win32console.GetStdHandle(
|
||||
win32console.STD_OUT_HANDLE)
|
||||
except ImportError:
|
||||
return False
|
||||
import pywintypes
|
||||
try:
|
||||
screenBuffer.SetConsoleTextAttribute(
|
||||
win32console.FOREGROUND_RED |
|
||||
win32console.FOREGROUND_GREEN |
|
||||
win32console.FOREGROUND_BLUE)
|
||||
except pywintypes.error:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
supported = classmethod(supported)
|
||||
|
||||
def write(self, text, color):
|
||||
color = self._colors[color]
|
||||
self.screenBuffer.SetConsoleTextAttribute(color)
|
||||
self.stream.write(text)
|
||||
self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
|
||||
|
||||
|
||||
class _NullColorizer(object):
|
||||
"""
|
||||
See _AnsiColorizer docstring.
|
||||
"""
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
|
||||
def supported(cls, stream=sys.stdout):
|
||||
return True
|
||||
supported = classmethod(supported)
|
||||
|
||||
def write(self, text, color):
|
||||
self.stream.write(text)
|
||||
|
||||
|
||||
def get_elapsed_time_color(elapsed_time):
|
||||
if elapsed_time > 1.0:
|
||||
return 'red'
|
||||
elif elapsed_time > 0.25:
|
||||
return 'yellow'
|
||||
else:
|
||||
return 'green'
|
||||
|
||||
|
||||
class NovaTestResult(testtools.TestResult):
|
||||
def __init__(self, stream, descriptions, verbosity):
|
||||
super(NovaTestResult, self).__init__()
|
||||
self.stream = stream
|
||||
self.showAll = verbosity > 1
|
||||
self.num_slow_tests = 10
|
||||
self.slow_tests = [] # this is a fixed-sized heap
|
||||
self.colorizer = None
|
||||
# NOTE(vish): reset stdout for the terminal check
|
||||
stdout = sys.stdout
|
||||
sys.stdout = sys.__stdout__
|
||||
for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
|
||||
if colorizer.supported():
|
||||
self.colorizer = colorizer(self.stream)
|
||||
break
|
||||
sys.stdout = stdout
|
||||
self.start_time = None
|
||||
self.last_time = {}
|
||||
self.results = {}
|
||||
self.last_written = None
|
||||
|
||||
def _writeElapsedTime(self, elapsed):
|
||||
color = get_elapsed_time_color(elapsed)
|
||||
self.colorizer.write(" %.2f" % elapsed, color)
|
||||
|
||||
def _addResult(self, test, *args):
|
||||
try:
|
||||
name = test.id()
|
||||
except AttributeError:
|
||||
name = 'Unknown.unknown'
|
||||
test_class, test_name = name.rsplit('.', 1)
|
||||
|
||||
elapsed = (self._now() - self.start_time).total_seconds()
|
||||
item = (elapsed, test_class, test_name)
|
||||
if len(self.slow_tests) >= self.num_slow_tests:
|
||||
heapq.heappushpop(self.slow_tests, item)
|
||||
else:
|
||||
heapq.heappush(self.slow_tests, item)
|
||||
|
||||
self.results.setdefault(test_class, [])
|
||||
self.results[test_class].append((test_name, elapsed) + args)
|
||||
self.last_time[test_class] = self._now()
|
||||
self.writeTests()
|
||||
|
||||
def _writeResult(self, test_name, elapsed, long_result, color,
|
||||
short_result, success):
|
||||
if self.showAll:
|
||||
self.stream.write(' %s' % str(test_name).ljust(66))
|
||||
self.colorizer.write(long_result, color)
|
||||
if success:
|
||||
self._writeElapsedTime(elapsed)
|
||||
self.stream.writeln()
|
||||
else:
|
||||
self.colorizer.write(short_result, color)
|
||||
|
||||
def addSuccess(self, test):
|
||||
super(NovaTestResult, self).addSuccess(test)
|
||||
self._addResult(test, 'OK', 'green', '.', True)
|
||||
|
||||
def addFailure(self, test, err):
|
||||
if test.id() == 'process-returncode':
|
||||
return
|
||||
super(NovaTestResult, self).addFailure(test, err)
|
||||
self._addResult(test, 'FAIL', 'red', 'F', False)
|
||||
|
||||
def addError(self, test, err):
|
||||
super(NovaTestResult, self).addFailure(test, err)
|
||||
self._addResult(test, 'ERROR', 'red', 'E', False)
|
||||
|
||||
def addSkip(self, test, reason=None, details=None):
|
||||
super(NovaTestResult, self).addSkip(test, reason, details)
|
||||
self._addResult(test, 'SKIP', 'blue', 'S', True)
|
||||
|
||||
def startTest(self, test):
|
||||
self.start_time = self._now()
|
||||
super(NovaTestResult, self).startTest(test)
|
||||
|
||||
def writeTestCase(self, cls):
|
||||
if not self.results.get(cls):
|
||||
return
|
||||
if cls != self.last_written:
|
||||
self.colorizer.write(cls, 'white')
|
||||
self.stream.writeln()
|
||||
for result in self.results[cls]:
|
||||
self._writeResult(*result)
|
||||
del self.results[cls]
|
||||
self.stream.flush()
|
||||
self.last_written = cls
|
||||
|
||||
def writeTests(self):
|
||||
time = self.last_time.get(self.last_written, self._now())
|
||||
if not self.last_written or (self._now() - time).total_seconds() > 2.0:
|
||||
diff = 3.0
|
||||
while diff > 2.0:
|
||||
classes = self.results.keys()
|
||||
oldest = min(classes, key=lambda x: self.last_time[x])
|
||||
diff = (self._now() - self.last_time[oldest]).total_seconds()
|
||||
self.writeTestCase(oldest)
|
||||
else:
|
||||
self.writeTestCase(self.last_written)
|
||||
|
||||
def done(self):
|
||||
self.stopTestRun()
|
||||
|
||||
def stopTestRun(self):
|
||||
for cls in list(self.results.iterkeys()):
|
||||
self.writeTestCase(cls)
|
||||
self.stream.writeln()
|
||||
self.writeSlowTests()
|
||||
|
||||
def writeSlowTests(self):
|
||||
# Pare out 'fast' tests
|
||||
slow_tests = [item for item in self.slow_tests
|
||||
if get_elapsed_time_color(item[0]) != 'green']
|
||||
if slow_tests:
|
||||
slow_total_time = sum(item[0] for item in slow_tests)
|
||||
slow = ("Slowest %i tests took %.2f secs:"
|
||||
% (len(slow_tests), slow_total_time))
|
||||
self.colorizer.write(slow, 'yellow')
|
||||
self.stream.writeln()
|
||||
last_cls = None
|
||||
# sort by name
|
||||
for elapsed, cls, name in sorted(slow_tests,
|
||||
key=lambda x: x[1] + x[2]):
|
||||
if cls != last_cls:
|
||||
self.colorizer.write(cls, 'white')
|
||||
self.stream.writeln()
|
||||
last_cls = cls
|
||||
self.stream.write(' %s' % str(name).ljust(68))
|
||||
self._writeElapsedTime(elapsed)
|
||||
self.stream.writeln()
|
||||
|
||||
def printErrors(self):
|
||||
if self.showAll:
|
||||
self.stream.writeln()
|
||||
self.printErrorList('ERROR', self.errors)
|
||||
self.printErrorList('FAIL', self.failures)
|
||||
|
||||
def printErrorList(self, flavor, errors):
|
||||
for test, err in errors:
|
||||
self.colorizer.write("=" * 70, 'red')
|
||||
self.stream.writeln()
|
||||
self.colorizer.write(flavor, 'red')
|
||||
self.stream.writeln(": %s" % test.id())
|
||||
self.colorizer.write("-" * 70, 'red')
|
||||
self.stream.writeln()
|
||||
self.stream.writeln("%s" % err)
|
||||
|
||||
|
||||
test = subunit.ProtocolTestCase(sys.stdin, passthrough=None)
|
||||
|
||||
if sys.version_info[0:2] <= (2, 6):
|
||||
runner = unittest.TextTestRunner(verbosity=2)
|
||||
else:
|
||||
runner = unittest.TextTestRunner(verbosity=2, resultclass=NovaTestResult)
|
||||
|
||||
if runner.run(test).wasSuccessful():
|
||||
exit_code = 0
|
||||
else:
|
||||
exit_code = 1
|
||||
sys.exit(exit_code)
|
39
tox.ini
39
tox.ini
|
@ -1,39 +0,0 @@
|
|||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = py27,py34,pypy,pep8
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install -U {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
-egit+https://github.com/openstack/nova#egg=nova
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:docs]
|
||||
commands = python setup.py build_sphinx
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||
|
||||
[flake8]
|
||||
# E712 is ignored on purpose, since it is normal to use 'column == true'
|
||||
# in sqlalchemy.
|
||||
# H803 skipped on purpose per list discussion.
|
||||
# E125 is deliberately excluded. See https://github.com/jcrocholl/pep8/issues/126
|
||||
# The rest of the ignores are TODOs
|
||||
|
||||
ignore = E125,E712,F811,H305,H307,H803
|
||||
exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools
|
||||
|
||||
[hacking]
|
||||
import_exceptions = novadocker.i18n
|
Loading…
Reference in New Issue