From 6603a7f323ae6a9c55f09244bb0d3de95d9c4c45 Mon Sep 17 00:00:00 2001 From: James Page Date: Tue, 23 Jul 2019 13:56:57 +0100 Subject: [PATCH] Retire master branch of nova-lxd Drop content and replace with retirement notice. Change-Id: I2de2eff7694d60597a6413a0a64124fbbede69bb --- .coveragerc | 7 - .gitignore | 58 - .mailmap | 3 - .stestr.conf | 3 - .zuul.yaml | 30 - CONTRIBUTING.rst | 91 - HACKING.rst | 4 - LICENSE | 175 -- MANIFEST.in | 6 - README.md | 42 - README.rst | 10 + babel.cfg | 1 - contrib/ci/post_test_hook.sh | 25 - contrib/ci/pre_test_hook.sh | 28 - .../glance_metadefs/compute-lxd-flavor.json | 26 - contrib/tempest/README.1st | 1 - contrib/tempest/run_tempest_lxd.sh | 33 - devstack/local.conf.sample | 29 - devstack/override-defaults | 2 - devstack/plugin.sh | 202 --- devstack/settings | 6 - devstack/tempest-dsvm-lxd-rc | 93 - doc/source/conf.py | 76 - doc/source/contributing.rst | 4 - doc/source/exclusive_machine.rst | 125 -- doc/source/index.rst | 25 - doc/source/support_matrix/support-matrix.ini | 701 -------- doc/source/support_matrix/support-matrix.rst | 16 - doc/source/usage.rst | 7 - doc/source/vif_wiring.rst | 59 - etc/nova/rootwrap.d/lxd.filters | 11 - lower-constraints.txt | 112 -- nova/__init__.py | 1 - nova/tests/unit/virt/lxd/__init__.py | 0 nova/tests/unit/virt/lxd/fake_api.py | 397 ----- nova/tests/unit/virt/lxd/stubs.py | 114 -- nova/tests/unit/virt/lxd/test_common.py | 87 - nova/tests/unit/virt/lxd/test_driver.py | 1581 ----------------- nova/tests/unit/virt/lxd/test_flavor.py | 493 ----- nova/tests/unit/virt/lxd/test_migrate.py | 92 - nova/tests/unit/virt/lxd/test_session.py | 109 -- nova/tests/unit/virt/lxd/test_storage.py | 259 --- nova/tests/unit/virt/lxd/test_vif.py | 331 ---- nova/virt/__init__.py | 1 - nova/virt/lxd/__init__.py | 3 - nova/virt/lxd/common.py | 48 - nova/virt/lxd/driver.py | 1373 -------------- nova/virt/lxd/flavor.py | 234 --- nova/virt/lxd/session.py | 183 -- nova/virt/lxd/storage.py | 138 -- nova/virt/lxd/vif.py | 335 ---- nova_lxd_tempest_plugin/README | 3 - nova_lxd_tempest_plugin/__init__.py | 0 nova_lxd_tempest_plugin/plugin.py | 33 - nova_lxd_tempest_plugin/tests/__init__.py | 0 nova_lxd_tempest_plugin/tests/api/__init__.py | 0 .../tests/api/compute/__init__.py | 0 .../tests/api/compute/servers/__init__.py | 0 .../api/compute/servers/test_create_server.py | 152 -- .../tests/api/compute/servers/test_servers.py | 119 -- .../tests/api/compute/volumes/__init__.py | 0 .../api/compute/volumes/test_attach_volume.py | 105 -- .../tests/scenario/__init__.py | 0 .../tests/scenario/manager.py | 697 -------- .../tests/scenario/test_server_basic_ops.py | 127 -- .../tests/scenario/test_volume_ops.py | 112 -- openstack-common.conf | 6 - requirements.txt | 18 - run_tests.sh | 252 --- setup.cfg | 35 - setup.py | 36 - test-requirements.txt | 19 - tools/abandon_old_reviews.sh | 81 - tools/clean-vlans | 24 - tools/colorizer.py | 326 ---- tools/config/README | 20 - tools/config/analyze_opts.py | 83 - tools/config/check_uptodate.sh | 25 - tools/config/generate_sample.sh | 119 -- tools/config/oslo.config.generator.rc | 2 - tools/db/schema_diff.py | 286 --- tools/enable-pre-commit-hook.sh | 42 - tools/install_venv.py | 73 - tools/install_venv_common.py | 172 -- tools/nova-manage.bash_completion | 37 - tools/pretty_tox.sh | 6 - tools/regression_tester.py | 109 -- tools/with_venv.sh | 7 - tox.ini | 80 - 89 files changed, 10 insertions(+), 10986 deletions(-) delete mode 100644 .coveragerc delete mode 100644 .gitignore delete mode 100644 .mailmap delete mode 100644 .stestr.conf delete mode 100644 .zuul.yaml delete mode 100644 CONTRIBUTING.rst delete mode 100644 HACKING.rst delete mode 100644 LICENSE delete mode 100644 MANIFEST.in delete mode 100644 README.md create mode 100644 README.rst delete mode 100644 babel.cfg delete mode 100755 contrib/ci/post_test_hook.sh delete mode 100755 contrib/ci/pre_test_hook.sh delete mode 100644 contrib/glance_metadefs/compute-lxd-flavor.json delete mode 100644 contrib/tempest/README.1st delete mode 100644 contrib/tempest/run_tempest_lxd.sh delete mode 100644 devstack/local.conf.sample delete mode 100644 devstack/override-defaults delete mode 100755 devstack/plugin.sh delete mode 100644 devstack/settings delete mode 100644 devstack/tempest-dsvm-lxd-rc delete mode 100755 doc/source/conf.py delete mode 100644 doc/source/contributing.rst delete mode 100644 doc/source/exclusive_machine.rst delete mode 100644 doc/source/index.rst delete mode 100644 doc/source/support_matrix/support-matrix.ini delete mode 100644 doc/source/support_matrix/support-matrix.rst delete mode 100644 doc/source/usage.rst delete mode 100644 doc/source/vif_wiring.rst delete mode 100644 etc/nova/rootwrap.d/lxd.filters delete mode 100644 lower-constraints.txt delete mode 100644 nova/__init__.py delete mode 100644 nova/tests/unit/virt/lxd/__init__.py delete mode 100644 nova/tests/unit/virt/lxd/fake_api.py delete mode 100644 nova/tests/unit/virt/lxd/stubs.py delete mode 100644 nova/tests/unit/virt/lxd/test_common.py delete mode 100644 nova/tests/unit/virt/lxd/test_driver.py delete mode 100644 nova/tests/unit/virt/lxd/test_flavor.py delete mode 100644 nova/tests/unit/virt/lxd/test_migrate.py delete mode 100644 nova/tests/unit/virt/lxd/test_session.py delete mode 100644 nova/tests/unit/virt/lxd/test_storage.py delete mode 100644 nova/tests/unit/virt/lxd/test_vif.py delete mode 100644 nova/virt/__init__.py delete mode 100644 nova/virt/lxd/__init__.py delete mode 100644 nova/virt/lxd/common.py delete mode 100644 nova/virt/lxd/driver.py delete mode 100644 nova/virt/lxd/flavor.py delete mode 100644 nova/virt/lxd/session.py delete mode 100644 nova/virt/lxd/storage.py delete mode 100644 nova/virt/lxd/vif.py delete mode 100644 nova_lxd_tempest_plugin/README delete mode 100644 nova_lxd_tempest_plugin/__init__.py delete mode 100644 nova_lxd_tempest_plugin/plugin.py delete mode 100644 nova_lxd_tempest_plugin/tests/__init__.py delete mode 100644 nova_lxd_tempest_plugin/tests/api/__init__.py delete mode 100644 nova_lxd_tempest_plugin/tests/api/compute/__init__.py delete mode 100644 nova_lxd_tempest_plugin/tests/api/compute/servers/__init__.py delete mode 100644 nova_lxd_tempest_plugin/tests/api/compute/servers/test_create_server.py delete mode 100644 nova_lxd_tempest_plugin/tests/api/compute/servers/test_servers.py delete mode 100644 nova_lxd_tempest_plugin/tests/api/compute/volumes/__init__.py delete mode 100644 nova_lxd_tempest_plugin/tests/api/compute/volumes/test_attach_volume.py delete mode 100644 nova_lxd_tempest_plugin/tests/scenario/__init__.py delete mode 100644 nova_lxd_tempest_plugin/tests/scenario/manager.py delete mode 100644 nova_lxd_tempest_plugin/tests/scenario/test_server_basic_ops.py delete mode 100644 nova_lxd_tempest_plugin/tests/scenario/test_volume_ops.py delete mode 100644 openstack-common.conf delete mode 100644 requirements.txt delete mode 100755 run_tests.sh delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 test-requirements.txt delete mode 100755 tools/abandon_old_reviews.sh delete mode 100755 tools/clean-vlans delete mode 100755 tools/colorizer.py delete mode 100644 tools/config/README delete mode 100755 tools/config/analyze_opts.py delete mode 100755 tools/config/check_uptodate.sh delete mode 100755 tools/config/generate_sample.sh delete mode 100644 tools/config/oslo.config.generator.rc delete mode 100755 tools/db/schema_diff.py delete mode 100755 tools/enable-pre-commit-hook.sh delete mode 100644 tools/install_venv.py delete mode 100644 tools/install_venv_common.py delete mode 100644 tools/nova-manage.bash_completion delete mode 100755 tools/pretty_tox.sh delete mode 100755 tools/regression_tester.py delete mode 100755 tools/with_venv.sh delete mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index d8e98bbb..00000000 --- a/.coveragerc +++ /dev/null @@ -1,7 +0,0 @@ -[run] -branch = True -source = nova.virt.lxd -omit = nova/tests/* - -[report] -ignore_errors = True diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8a9fa55f..00000000 --- a/.gitignore +++ /dev/null @@ -1,58 +0,0 @@ -*.py[cod] -*.idea - -# C extensions -*.so - -# Packages -*.egg -*.eggs -*.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 -.stestr -.venv -.stestr - -# 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 -.*sw? - -cover diff --git a/.mailmap b/.mailmap deleted file mode 100644 index cc92f17b..00000000 --- a/.mailmap +++ /dev/null @@ -1,3 +0,0 @@ -# Format is: -# -# \ No newline at end of file diff --git a/.stestr.conf b/.stestr.conf deleted file mode 100644 index 69f05bc0..00000000 --- a/.stestr.conf +++ /dev/null @@ -1,3 +0,0 @@ -[DEFAULT] -test_path=./nova/tests/unit/virt/lxd -top_dir=./nova/tests/unit/virt/lxd/ diff --git a/.zuul.yaml b/.zuul.yaml deleted file mode 100644 index 4fd2c8c5..00000000 --- a/.zuul.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# 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 job will execute 'tox -e func_lxd' from the OSA -# repo specified in 'osa_test_repo'. -- job: - name: openstack-ansible-nova-lxd - parent: openstack-ansible-cross-repo-functional - voting: false - required-projects: - - name: openstack/openstack-ansible-os_nova - vars: - tox_env: lxd - osa_test_repo: openstack/openstack-ansible-os_nova - -- project: - templates: - - openstack-lower-constraints-jobs - check: - jobs: - - openstack-ansible-nova-lxd diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index e97449bd..00000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,91 +0,0 @@ -Crash course in lxd setup -========================= - -nova-lxd absolutely requires lxd, though its installation and configuration -is out of scope here. If you're running Ubuntu, here is the easy path -to a running lxd. - -.. code-block: bash - - add-apt-repository ppa:ubuntu-lxc/lxd-git-master && sudo apt-get update - apt-get -y install lxd - usermod -G lxd ${your_username|stack} - service lxd start - -If you're currently logged in as the user you just added to lxd, you'll -need to log out and log back in again. - - -Using nova-lxd with devstack -============================ - -nova-lxd includes a plugin for use in devstack. If you'd like to run -devstack with nova-lxd, you'll want to add the following to `local.conf`: - -.. code-block: bash - - enable_plugin nova-lxd https://git.openstack.org/openstack/nova-lxd - -In this case, nova-lxd will run HEAD from master. You may want to point -this at your own fork. A final argument to `enable_plugin` can be used -to specify a git revision. - -Configuration and installation of devstack is beyond the scope -of this document. Here's an example `local.conf` file that will -run the very minimum you`ll need for devstack. - -.. code-block: bash - - [[local|localrc]] - ADMIN_PASSWORD=password - DATABASE_PASSWORD=$ADMIN_PASSWORD - RABBIT_PASSWORD=$ADMIN_PASSWORD - SERVICE_PASSWORD=$ADMIN_PASSWORD - SERVICE_TOKEN=$ADMIN_PASSWORD - - disable_service cinder c-sch c-api c-vol - disable_service n-net n-novnc - disable_service horizon - disable_service ironic ir-api ir-cond - - enable_service q-svc q-agt q-dhcp q-13 q-meta - - # Optional, to enable tempest configuration as part of devstack - enable_service tempest - - enable_plugin nova-lxd https://git.openstack.org/openstack/nova-lxd - - # More often than not, stack.sh explodes trying to configure IPv6 support, - # so let's just disable it for now. - IP_VERSION=4 - -Once devstack is running, you'll want to add the lxd image to glance. You can -do this (as an admin) with: - -.. code-block: bash - - wget http://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-root.tar.xz - glance image-create --name lxd --container-format bare --disk-format raw \ - --visibility=public < trusty-server-cloudimg-amd64-root.tar.xz - -To run the tempest tests, you can use: - -.. code-block: bash - - /opt/stack/tempest/run_tempest.sh -N tempest.api.compute - - -Errata -====== - -Patches should be submitted to Openstack Gerrit via `git-review`. - -Bugs should be filed on Launchpad: - - https://bugs.launchpad.net/nova-lxd - -If you would like to contribute to the development of OpenStack, -you must follow the steps in this page: - - https://docs.openstack.org/infra/manual/developers.html - diff --git a/HACKING.rst b/HACKING.rst deleted file mode 100644 index 80699e3e..00000000 --- a/HACKING.rst +++ /dev/null @@ -1,4 +0,0 @@ -nova-lxd Style Commandments -=============================================== - -Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 67db8588..00000000 --- a/LICENSE +++ /dev/null @@ -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. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 90f8a7ae..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include AUTHORS -include ChangeLog -exclude .gitignore -exclude .gitreview - -global-exclude *.pyc \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 4792c5ab..00000000 --- a/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# nova-lxd [![Build Status](https://travis-ci.org/lxc/nova-lxd.svg?branch=master)](https://travis-ci.org/lxc/nova-lxd) - -An OpenStack Compute driver for managing containers using LXD. - -## nova-lxd on Devstack - -For development purposes, nova-lxd provides a devstack plugin. To use it, just include the -following in your devstack `local.conf`: - -``` -[[local|localrc]] -enable_plugin nova-lxd https://git.openstack.org/openstack/nova-lxd - -# You should enable the following if you use lxd 3.0. -# In addition, this setting requires zfs >= 0.7.0. -#LXD_BACKEND_DRIVER=zfs -``` - -Change git repositories as needed (it's probably not very useful to point to the main -nova-lxd repo). If you have a local tree you'd like to use, you can symlink your tree to -`/opt/stack/nova-lxd` and do your development from there. - -The devstack default images come cirros LXD, you can still download -Ubuntu. Once your stack is up and you've configured authentication -against your devstack, do the following:: - -``` -wget http://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64-root.tar.xz -glance image-create --name bionic-amd64 --disk-format raw --container-format bare --file bionic-server-cloudimg-amd64-root.tar.xz -``` - -# Support and discussions - -We use the LXC mailing-lists for developer and user discussions, you can -find and subscribe to those at: https://lists.linuxcontainers.org - -If you prefer live discussions, some of us also hang out in -[#lxcontainers](http://webchat.freenode.net/?channels=#lxcontainers) on irc.freenode.net. - -## Bug reports - -Bug reports can be filed at https://bugs.launchpad.net/nova-lxd diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..86e34d67 --- /dev/null +++ b/README.rst @@ -0,0 +1,10 @@ +This project is no longer maintained. + +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". + +For any further questions, please email +openstack-discuss@lists.openstack.org or join #openstack-dev on +Freenode. diff --git a/babel.cfg b/babel.cfg deleted file mode 100644 index efceab81..00000000 --- a/babel.cfg +++ /dev/null @@ -1 +0,0 @@ -[python: **.py] diff --git a/contrib/ci/post_test_hook.sh b/contrib/ci/post_test_hook.sh deleted file mode 100755 index ebf2de7a..00000000 --- a/contrib/ci/post_test_hook.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -xe -# -# 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 function in devstack gate. - -source $BASE/new/devstack/functions - -INSTALLDIR=${INSTALLDIR:-/opt/stack} -source $INSTALLDIR/devstack/functions-common -LOGDIR=/opt/stack/logs - -# Collect logs from the containers -sudo mkdir -p $LOGDIR/containers/ -sudo cp -rp /var/log/lxd/* $LOGDIR/containers diff --git a/contrib/ci/pre_test_hook.sh b/contrib/ci/pre_test_hook.sh deleted file mode 100755 index 9b05230a..00000000 --- a/contrib/ci/pre_test_hook.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -xe -# -# 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. -# First argument ($1) expects boolean as value where: -# 'False' means share driver will not handle share servers -# 'True' means it will handle share servers. - -# Import devstack function 'trueorfalse' - -source $BASE/new/devstack/functions - -# Note, due to Bug#1822182 we have to set this to default for the disk backend -# otherwise rescue tests will not work. -DEVSTACK_LOCAL_CONFIG+=$'\n'"LXD_BACKEND_DRIVER=default" - -export DEVSTACK_LOCAL_CONFIG diff --git a/contrib/glance_metadefs/compute-lxd-flavor.json b/contrib/glance_metadefs/compute-lxd-flavor.json deleted file mode 100644 index 61716841..00000000 --- a/contrib/glance_metadefs/compute-lxd-flavor.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "namespace": "OS::Nova::LXDFlavor", - "display_name": "LXD properties", - "description": "You can pass several options to the LXD container hypervisor that will affect the container's capabilities.", - "visibility": "public", - "protected": false, - "resource_type_associations": [ - { - "name": "OS::Nova::Flavor" - } - ], - "properties": { - "lxd:nested_allowed": { - "title": "Allow nested containers", - "description": "Allow or disallow creation of nested containers. If True, you can install and run LXD inside the VM itself and provision another level of containers.", - "type": "string", - "default": false - }, - "lxd:privileged_allowed": { - "title": "Create privileged container", - "description": "Containers created as Privileged have elevated powers on the compute host. You should not set this option on containers that you don't fully trust.", - "type": "string", - "default": false - } - } -} diff --git a/contrib/tempest/README.1st b/contrib/tempest/README.1st deleted file mode 100644 index f526dffe..00000000 --- a/contrib/tempest/README.1st +++ /dev/null @@ -1 +0,0 @@ -Run run_tempest_lxd.sh to run tempest.api.compute tests to run against nova-lxd diff --git a/contrib/tempest/run_tempest_lxd.sh b/contrib/tempest/run_tempest_lxd.sh deleted file mode 100644 index 43227361..00000000 --- a/contrib/tempest/run_tempest_lxd.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# Construct a regex t ouse when limiting scope of tempest -# to avoid features unsupported by nova-lxd - -# Note that several tests are disabled by the use of tempest -# feature toggels in devstack for an LXD config -# so this regex is not entiriely representative of -# what's excluded - -# Wen adding entries to the ignored_tests, add a comment explaining -# why since this list should not grow - -# Temporarily skip the image tests since they give false positivies -# for nova-lxd - -ignored_tests="|^tempest.api.compute.images" - -# Regressions -ignored_tests="$ignored_tests|.*AttachInterfacesTestJSON.test_create_list_show_delete_interfaces" - -# backups are not supported -ignored_tests="$ignored_tests|.*ServerActionsTestJSON.test_create_backup" - -# failed verfication tests -ignored_tests="$ignored_tests|.*ServersWithSpecificFlavorTestJSON.test_verify_created_server_ephemeral_disk" -ignored_tests="$ignored_tests|.*AttachVolumeShelveTestJSON.test_attach_detach_volume" -ignored_tests="$ignored_tests|.*AttachVolumeTestJSON.test_attach_detach_volume" - -regex="(?!.*\\[.*\\bslow\\b.*\\]$ignored_tests)(^tempest\\.api.\\compute)"; - -ostestr --serial --regex $regex run - diff --git a/devstack/local.conf.sample b/devstack/local.conf.sample deleted file mode 100644 index e55f4ac3..00000000 --- a/devstack/local.conf.sample +++ /dev/null @@ -1,29 +0,0 @@ -[[local|localrc]] - -# Set the HOST_IP and FLAT_INTERFACE if automatique detection is -# unreliable -#HOST_IP= -#FLAT_INTERFACE= - -DATABASE_PASSWORD=password -RABBIT_PASSWORD=password -SERVICE_PASSWORD=password -SERVICE_TOKEN=password -ADMIN_PASSWORD=password - -# run the services you want to use -ENABLED_SERVICES=rabbit,mysql,key -ENABLED_SERVICES+=,g-api,g-reg -ENABLED_SERVICES+=,n-cpu,n-api,n-crt,n-obj,n-cond,n-sch,n-novnc,n-cauth,placement-api,placement-client -ENABLED_SERVICES+=,neutron,q-svc,q-agt,q-dhcp,q-meta,q-l3 -ENABLED_SERVICES+=,cinder,c-sch,c-api,c-vol -ENABLED_SERVICES+=,horizon - -# disabled services -disable_service n-net - -# enable nova-lxd -enable_plugin nova-lxd https://git.openstack.org/openstack/nova-lxd -# You should enable the following if you use lxd 3.0. -# In addition, this setting requires zfs >= 0.7.0. -#LXD_BACKEND_DRIVER=zfs diff --git a/devstack/override-defaults b/devstack/override-defaults deleted file mode 100644 index e8ded4cb..00000000 --- a/devstack/override-defaults +++ /dev/null @@ -1,2 +0,0 @@ -# Plug-in overrides -VIRT_DRIVER=lxd diff --git a/devstack/plugin.sh b/devstack/plugin.sh deleted file mode 100755 index 11cd1881..00000000 --- a/devstack/plugin.sh +++ /dev/null @@ -1,202 +0,0 @@ -#!/bin/bash - -# Save trace setting -MY_XTRACE=$(set +o | grep xtrace) -set +o xtrace - -# Defaults -# -------- - -# Set up base directories -NOVA_DIR=${NOVA_DIR:-$DEST/nova} -NOVA_CONF_DIR=${NOVA_CONF_DIR:-/etc/nova} -NOVA_CONF=${NOVA_CONF:-NOVA_CONF_DIR/nova.conf} - -# Configure LXD storage backends -# Note Bug:1822182 - ZFS backend is broken for Rescue's so don't use it! -LXD_BACKEND_DRIVER=${LXD_BACKEND_DRIVER:-default} -LXD_DISK_IMAGE=${DATA_DIR}/lxd.img -LXD_LOOPBACK_DISK_SIZE=${LXD_LOOPBACK_DISK_SIZE:-8G} -LXD_POOL_NAME=${LXD_POOL_NAME:-default} - -# nova-lxd directories -NOVA_COMPUTE_LXD_DIR=${NOVA_COMPUTE_LXD_DIR:-${DEST}/nova-lxd} -NOVA_COMPUTE_LXD_PLUGIN_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})) - -# glance directories -GLANCE_CONF_DIR=${GLANCE_CONF_DIR:-/etc/glance} -GLANCE_API_CONF=$GLANCE_CONF_DIR/glance-api.conf - -function pre_install_nova-lxd() { - # Install OS packages if necessary with "install_package ...". - echo_summary "Installing LXD" - if is_ubuntu; then - if [ "$DISTRO" == "trusty" ]; then - sudo add-apt-repository -y ppa:ubuntu-lxc/lxd-stable - fi - - is_package_installed lxd || install_package lxd - - add_user_to_group $STACK_USER $LXD_GROUP - - needs_restart=false - is_package_installed apparmor || \ - install_package apparmor && needs_restart=true - is_package_installed apparmor-profiles-extra || \ - install_package apparmor-profiles-extra && needs_restart=true - is_package_installed apparmor-utils || \ - install_package apparmor-utils && needs_restart=true - if $needs_restart; then - restart_service lxd - fi - fi -} - -function install_nova-lxd() { - # Install the service. - setup_develop $NOVA_COMPUTE_LXD_DIR -} - -function configure_nova-lxd() { - # Configure the service. - iniset $NOVA_CONF DEFAULT compute_driver lxd.LXDDriver - iniset $NOVA_CONF DEFAULT force_config_drive False - iniset $NOVA_CONF lxd pool $LXD_POOL_NAME - - if is_service_enabled glance; then - iniset $GLANCE_API_CONF DEFAULT disk_formats "ami,ari,aki,vhd,raw,iso,qcow2,root-tar" - iniset $GLANCE_API_CONF DEFAULT container_formats "ami,ari,aki,bare,ovf,tgz" - fi - - # Install the rootwrap - sudo install -o root -g root -m 644 $NOVA_COMPUTE_LXD_DIR/etc/nova/rootwrap.d/*.filters $NOVA_CONF_DIR/rootwrap.d -} - -function init_nova-lxd() { - # Initialize and start the service. - - mkdir -p $TOP_DIR/files - - # Download and install the cirros lxc image - CIRROS_IMAGE_FILE=cirros-${CIRROS_VERSION}-${CIRROS_ARCH}-lxc.tar.gz - if [ ! -f $TOP_DIR/files/$CIRROS_IMAGE_FILE ]; then - wget --progress=dot:giga \ - -c http://download.cirros-cloud.net/${CIRROS_VERSION}/${CIRROS_IMAGE_FILE} \ - -O $TOP_DIR/files/${CIRROS_IMAGE_FILE} - fi - openstack --os-cloud=devstack-admin \ - --os-region-name="$REGION_NAME" image create "cirros-${CIRROS_VERSION}-${CIRROS_ARCH}-lxd" \ - --public --container-format bare \ - --disk-format raw < $TOP_DIR/files/cirros-${CIRROS_VERSION}-${CIRROS_ARCH}-lxc.tar.gz - - if is_service_enabled cinder; then - # Enable user namespace for ext4, this has only been tested on xenial+ - echo Y | sudo tee /sys/module/ext4/parameters/userns_mounts - fi -} - -function test_config_nova-lxd() { - # Configure tempest or other tests as required - if is_service_enabled tempest; then - TEMPEST_CONFIG=${TEMPEST_CONFIG:-$TEMPEST_DIR/etc/tempest.conf} - TEMPEST_IMAGE=`openstack image list | grep cirros-${CIRROS_VERSION}-${CIRROS_ARCH}-lxd | awk {'print $2'}` - TEMPEST_IMAGE_ALT=$TEMPEST_IMAGE - iniset $TEMPEST_CONFIG image disk_formats "ami,ari,aki,vhd,raw,iso,root-tar" - iniset $TEMPEST_CONFIG compute volume_device_name sdb - # TODO(jamespage): Review and update - iniset $TEMPEST_CONFIG compute-feature-enabled shelve False - iniset $TEMPEST_CONFIG compute-feature-enabled resize False - iniset $TEMPEST_CONFIG compute-feature-enabled config_drive False - iniset $TEMPEST_CONFIG compute-feature-enabled attach_encrypted_volume False - iniset $TEMPEST_CONFIG compute-feature-enabled vnc_console False - iniset $TEMPEST_CONFIG compute image_ref $TEMPEST_IMAGE - iniset $TEMPEST_CONFIG compute image_ref_alt $TEMPEST_IMAGE_ALT - iniset $TEMPEST_CONFIG scenario img_file cirros-${CIRROS_VERSION}-${CIRROS_ARCH}-lxc.tar.gz - fi -} - -function configure_lxd_block() { - echo_summary "Configure LXD storage backend." - if is_ubuntu; then - if [ "$LXD_BACKEND_DRIVER" == "default" ]; then - if [ "$LXD_POOL_NAME" == "default" ]; then - echo_summary " . Configuring '${LXD_POOL_NAME}' dir backend for bionic lxd" - sudo lxd init --auto --storage-backend dir - else - echo_summary " . LXD_POOL_NAME != default, considering lxd already initialized" - fi - elif [ "$LXD_BACKEND_DRIVER" == "zfs" ]; then - pool=`lxc profile device get default root pool 2>> /dev/null || :` - if [ "$pool" != "$LXD_POOL_NAME" ]; then - echo_summary " . Configuring ZFS backend" - truncate -s $LXD_LOOPBACK_DISK_SIZE $LXD_DISK_IMAGE - # TODO(sahid): switch to use snap - sudo apt-get install -y zfsutils-linux - lxd_dev=`sudo losetup --show -f ${LXD_DISK_IMAGE}` - sudo lxd init --auto --storage-backend zfs --storage-pool $LXD_POOL_NAME \ - --storage-create-device $lxd_dev - else - echo_summary " . ZFS backend already configured" - fi - fi - fi -} - -function shutdown_nova-lxd() { - # Shut the service down. - : -} - -function cleanup_nova-lxd() { - # Cleanup the service. - if [ "$LXD_BACKEND_DRIVER" == "zfs" ]; then - pool=`lxc profile device get default root pool 2>> /dev/null || :` - if [ "$pool" == "$LXD_POOL_NAME" ]; then - sudo lxc profile device remove default root - sudo lxc storage delete $LXD_POOL_NAME - fi - fi -} - -if is_service_enabled nova-lxd; then - - if [[ "$1" == "stack" && "$2" == "pre-install" ]]; then - # Set up system services - echo_summary "Configuring system services nova-lxd" - pre_install_nova-lxd - configure_lxd_block - - elif [[ "$1" == "stack" && "$2" == "install" ]]; then - # Perform installation of service source - echo_summary "Installing nova-lxd" - install_nova-lxd - - elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then - # Configure after the other layer 1 and 2 services have been configured - echo_summary "Configuring nova-lxd" - configure_nova-lxd - - elif [[ "$1" == "stack" && "$2" == "extra" ]]; then - # Initialize and start the nova-lxd service - echo_summary "Initializing nova-lxd" - init_nova-lxd - - elif [[ "$1" == "stack" && "$2" == "test-config" ]]; then - # Configure any testing configuration - echo_summary "Test configuration - nova-lxd" - test_config_nova-lxd - fi - - if [[ "$1" == "unstack" ]]; then - # Shut down nova-lxd services - # no-op - shutdown_nova-lxd - fi - - if [[ "$1" == "clean" ]]; then - # Remove state and transient data - # Remember clean.sh first calls unstack.sh - # no-op - cleanup_nova-lxd - fi -fi diff --git a/devstack/settings b/devstack/settings deleted file mode 100644 index 32bd08e8..00000000 --- a/devstack/settings +++ /dev/null @@ -1,6 +0,0 @@ -# Add nova-lxd to enabled services -enable_service nova-lxd - -# LXD install/upgrade settings -INSTALL_LXD=${INSTALL_LXD:-False} -LXD_GROUP=${LXD_GROUP:-lxd} diff --git a/devstack/tempest-dsvm-lxd-rc b/devstack/tempest-dsvm-lxd-rc deleted file mode 100644 index 643e3189..00000000 --- a/devstack/tempest-dsvm-lxd-rc +++ /dev/null @@ -1,93 +0,0 @@ -# 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 in the OpenStack CI *tempest-dsvm-lxd job. -# It's used to configure which tempest tests actually get run. You can find -# the CI job configuration here: -# -# http://git.openstack.org/cgit/openstack-infra/project-config/tree/jenkins/jobs/devstack-gate.yaml -# - -# Construct a regex to use when limiting scope of tempest -# to avoid features unsupported by Nova's LXD support. - -# Note that several tests are disabled by the use of tempest -# feature toggles in devstack/lib/tempest for an lxd config, -# so this regex is not entirely representative of what's excluded. - -# When adding entries to the regex, add a comment explaining why -# since this list should not grow. - -r="^(?!.*" -r="$r(?:.*\[.*\bslow\b.*\])" - -# (zulcss) nova-lxd does not support booting ami/aki images -r="$r|(?:tempest\.scenario\.test_minimum_basic\.TestMinimumBasicScenario\.test_minimum_basic_scenario)" - -# XXX: zulcss (18 Oct 2016) nova-lxd does not support booting from ebs volumes -r="$r|(?:tempest\.scenario\.test_volume_boot_pattern.*)" -r="$r|(?:tempest\.api\.compute\.servers\.test_create_server\.ServersTestBootFromVolume)" -# XXX: zulcss (18 Oct 2016) tempest test only passes when there is more than 10 lines in the -# console output, and cirros LXD consoles have only a single line of output -r="$r|(?:tempest\.api\.compute\.servers\.test_server_actions\.ServerActionsTestJSON\.test_get_console_output_with_unlimited_size)" -# tempest.api.compute.servers.test_server_actions.ServerActionsTestJSON.test_get_console_output_with_unlimited_size -# also tempest get console fails for the following two for length of output reasons -r="$r|(?:tempest\.api\.compute\.servers\.test_server_actions\.ServerActionsTestJSON\.test_get_console_output)" -# tempest.api.compute.servers.test_server_actions.ServerActionsTestJSON.test_get_console_output -r="$r|(?:tempest\.api\.compute\.servers\.test_server_actions\.ServerActionsTestJSON\.test_get_console_output_server_id_in_shutoff_status)" -# tempest.api.compute.servers.test_server_actions.ServerActionsTestJSON.test_get_console_output_server_id_in_shutoff_status - -# XXX: jamespage (09 June 2017) veth pair nics not detected/configured by tempest -# https://review.openstack.org/#/c/472641/ -# XXX: jamespage (09 June 2017) instance not accessible via floating IP. -r="$r|(?:tempest\.scenario\.test_network_v6\.TestGettingAddress\.test_dualnet_multi_prefix_dhcpv6_stateless)" -r="$r|(?:tempest\.scenario\.test_network_v6\.TestGettingAddress\.test_dualnet_multi_prefix_slaac)" -#tempest.scenario.test_network_v6.TestGettingAddress.test_dualnet_multi_prefix_dhcpv6_stateless -#tempest.scenario.test_network_v6.TestGettingAddress.test_dualnet_multi_prefix_slaac - -# XXX: zulcss (18 Oct 2016) Could not connect to instance -#r="$r|(?:tempest\.scenario\.test_network_advanced_server_ops\.TestNetworkAdvancedServerOps\.test_server_connectivity_suspend_resume)" - -# XXX: jamespage (08 June 2017): test failures with a mismatch in the number of disks reported -r="$r|(?:tempest\.api\.compute\.admin\.test_create_server\.ServersWithSpecificFlavorTestJSON\.test_verify_created_server_ephemeral_disk)" -#tempest.api.compute.admin.test_create_server.ServersWithSpecificFlavorTestJSON.test_verify_created_server_ephemeral_disk - -# XXX: jamespage (08 June 2017): nova-lxd driver does not support device tagging -r="$r|(?:tempest\.api\.compute\.servers\.test_device_tagging.*)" -#tempest.api.compute.servers.test_device_tagging.DeviceTaggingTestV2_42.test_device_tagging -#tempest.api.compute.servers.test_device_tagging.DeviceTaggingTestV2_42.test_device_tagging - -# XXX: jamespage (08 June 2017): mismatching output on LXD instance use-case -#tempest.api.compute.volumes.test_attach_volume.AttachVolumeTestJSON.test_attach_detach_volume -#tempest.api.compute.volumes.test_attach_volume.AttachVolumeShelveTestJSON.test_attach_detach_volume -r="$r|(?:tempest\.api\.compute\.volumes\.test_attach_volume\.AttachVolumeTestJSON\.test_attach_detach_volume)" -r="$r|(?:tempest\.api\.compute\.volumes\.test_attach_volume\.AttachVolumeShelveTestJSON\.test_attach_detach_volume)" -#testtools.matchers._impl.MismatchError: u'NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT\nsda 8:0 0 1073741824 0 disk \nsdb 8:16 0 1073741824 0 disk \nvda 253:0 0 85899345920 0 disk \nvdb 253:16 0 42949672960 0 disk ' matches Contains('\nsdb ') - -# XXX: jamespage (26 June 2017): disable diagnostic checks until driver implements them -# https://bugs.launchpad.net/nova-lxd/+bug/1700516 -r="$r|(?:.*test_get_server_diagnostics.*)" -#test_get_server_diagnostics - -# XXX: ajkavanagh (2018-07-23): disable test_show_update_rebuild_list_server as nova-lxd doesn't have the -# 'supports_trusted_certs' capability, and the test uses it. -# BUG: https://bugs.launchpad.net/nova-lxd/+bug/1783080 -r="$r|(?:.*ServerShowV263Test.test_show_update_rebuild_list_server.*)" - -r="$r).*$" - -export DEVSTACK_GATE_TEMPEST_REGEX="$r" - -# set the concurrency to 1 for devstack-gate -# See: https://bugs.launchpad.net/nova-lxd/+bug/1790943 -#export TEMPEST_CONCURRENCY=1 diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100755 index ed595db4..00000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,76 +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_feature_classification.support_matrix', - #'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-lxd' -copyright = u'2015, Canonical Ltd' - -# 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} diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst deleted file mode 100644 index ed77c126..00000000 --- a/doc/source/contributing.rst +++ /dev/null @@ -1,4 +0,0 @@ -============ -Contributing -============ -.. include:: ../../CONTRIBUTING.rst \ No newline at end of file diff --git a/doc/source/exclusive_machine.rst b/doc/source/exclusive_machine.rst deleted file mode 100644 index 7798dc7c..00000000 --- a/doc/source/exclusive_machine.rst +++ /dev/null @@ -1,125 +0,0 @@ -Nova-LXD Exclusive Machine -========================== - -As LXD is a system container format, it is possible to provision "bare metal" -machines with nova-lxd without exposing the kernel and firmware to the tenant. -This is done by means of host aggregates and flavor assignment. The instance -will fill the entirety of the host, and no other instances will be assigned -to it. - -This document describes the method used to achieve this exclusive machine -scheduling. It is meant to serve as an example; the names of flavors and -aggregates may be named as desired. - - -Prerequisites -------------- - -Exclusive machine scheduling requires two scheduler filters to be enabled in -`scheduler_default_filters` in `nova.conf`, namely -`AggregateInstanceExtraSpecsFilter` and `AggregateNumInstancesFilter`. - -If juju was used to install and manage the openstack environment, the following -command will enable these filters:: - - juju set nova-cloud-controller scheduler-default-filters="AggregateInstanceExtraSpecsFilter,AggregateNumInstancesFilter,RetryFilter,AvailabilityZoneFilter,CoreFilter,RamFilter,ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,ServerGroupAntiAffinityFilter,ServerGroupAffinityFilter" - - -Host Aggregate --------------- - -Each host designed to be exclusively available to a single instance must be -added to a special host aggregate. - -In this example, the following is a nova host listing:: - - user@openstack$ nova host-list - +------------+-----------+----------+ - | host_name | service | zone | - +------------+-----------+----------+ - | machine-9 | cert | internal | - | machine-9 | scheduler | internal | - | machine-9 | conductor | internal | - | machine-12 | compute | nova | - | machine-11 | compute | nova | - | machine-10 | compute | nova | - +------------+-----------+----------+ - -Create the host aggregate itself. In this example, the aggregate is called -"exclusive-machines":: - - user@openstack$ nova aggregate-create exclusive-machines - +----+--------------------+-------------------+-------+----------+ - | 1 | exclusive-machines | - | | | - +----+--------------------+-------------------+-------+----------+ - -Two metadata properties are then set on the host aggregate itself:: - - user@openstack$ nova aggregate-set-metadata 1 aggregate_instance_extra_specs:exclusive=true - Metadata has been successfully updated for aggregate 1. - +----+--------------------+-------------------+-------+-------------------------------------------------+ - | Id | Name | Availability Zone | Hosts | Metadata | - +----+--------------------+-------------------+-------+-------------------------------------------------+ - | 1 | exclusive-machines | - | | 'aggregate_instance_extra_specs:exclusive=true' | - +----+--------------------+-------------------+-------+-------------------------------------------------+ - user@openstack$ nova aggregate-set-metadata 1 max_instances_per_host=1 - Metadata has been successfully updated for aggregate 1. - +----+--------------------+-------------------+-------+-----------------------------------------------------------------------------+ - | Id | Name | Availability Zone | Hosts | Metadata | - +----+--------------------+-------------------+-------+-----------------------------------------------------------------------------+ - | 1 | exclusive-machines | - | | 'aggregate_instance_extra_specs:exclusive=true', 'max_instances_per_host=1' | - +----+--------------------+-------------------+-------+----------------------------------------------------------------------------- - -The first aggregate metadata property is the link between the flavor (still to -be created) and the compute hosts (still to be added to the aggregate). The -second metadata property ensures that nova doesn't ever try to add another -instance to this one in (e.g. if nova is configured to overcommit resources). - -Now the hosts must be added to the aggregate. Once they are added to the -host aggregate, they will not be available for other flavors. This will be -important in resource sizing efforts. To add the hosts:: - - user@openstack$ nova aggregate-add-host exclusive-machines machine-10 - Host juju-serverstack-machine-10 has been successfully added for aggregate 1 - +----+--------------------+-------------------+--------------+-----------------------------------------------------------------------------+ - | Id | Name | Availability Zone | Hosts | Metadata | - +----+--------------------+-------------------+--------------+-----------------------------------------------------------------------------+ - | 1 | exclusive-machines | - | 'machine-10' | 'aggregate_instance_extra_specs:exclusive=true', 'max_instances_per_host=1' | - +----+--------------------+-------------------+--------------+-----------------------------------------------------------------------------+ - -Exclusive machine flavors -------------------------- - -When planning for exclusive machine flavors, there is still a small amount -of various resources that will be needed for nova compute and lxd itself. -In general, it's a safe bet that this can be quantified in 100MB of RAM, -though specific hosts may need to be configured more closely to their -use cases. - -In this example, `machine-10` has 4096MB of total memory, 2 CPUS, and 500GB -of disk space. The flavor that is created will have a quantity of 3996MB of -RAM, 2 CPUS, and 500GB of disk.:: - - user@openstack$ nova flavor-create --is-public true e1.medium 100 3996 500 2 - +-----+-----------+-----------+------+-----------+------+-------+-------------+-----------+ - | ID | Name | Memory_MB | Disk | Ephemeral | Swap | VCPUs | RXTX_Factor | Is_Public | - +-----+-----------+-----------+------+-----------+------+-------+-------------+-----------+ - | 100 | e1.medium | 3996 | 500 | 0 | | 2 | 1.0 | True | - +-----+-----------+-----------+------+-----------+------+-------+-------------+-----------+ - -The `e1.medium` flavor must now have some metadata set to link it with the -`exclusive-machines` host aggregate.:: - - user@openstack$ nova flavor-key 100 set exclusive=true - - -Booting an exclusive instance ------------------------------ - -Once the host aggregate and flavor have been created, exclusive machines -can be provisioned by using the flavor `e1.medium`:: - - user@openstack$ nova boot --flavor 100 --image $IMAGE exclusive - -The `exclusive` instance, once provisioned, will fill the entire host -machine. diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index fcb1eeca..00000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. nova-lxd 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-lxd's documentation! -======================================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - usage - contributing - exclusive_machine - vif_wiring - support_matrix/support-matrix - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/doc/source/support_matrix/support-matrix.ini b/doc/source/support_matrix/support-matrix.ini deleted file mode 100644 index 16554a88..00000000 --- a/doc/source/support_matrix/support-matrix.ini +++ /dev/null @@ -1,701 +0,0 @@ - -# Driver definition -[driver.nova-lxd] -title=Nova-LXD - -# Functions: -[operation.attach-volume] -title=Attach block volume to instance -status=optional -notes=The attach volume operation provides a means to hotplug - additional block storage to a running instance. This allows - storage capabilities to be expanded without interruption of - service. In a cloud model it would be more typical to just - spin up a new instance with large storage, so the ability to - hotplug extra storage is for those cases where the instance - is considered to be more of a pet than cattle. Therefore - this operation is not considered to be mandatory to support. -cli=nova volume-attach -driver.nova-lxd=complete - -[operation.attach-tagged-volume] -title=Attach tagged block device to instance -status=optional -notes=Attach a block device with a tag to an existing server instance. See - "Device tags" for more information. -cli=nova volume-attach [--tag ] -driver.nova-lxd=unknown - -[operation.detach-volume] -title=Detach block volume from instance -status=optional -notes=See notes for attach volume operation. -cli=nova volume-detach -driver.nova-lxd=missing - -[operation.extend-volume] -title=Extend block volume attached to instance -status=optional -notes=The extend volume operation provides a means to extend - the size of an attached volume. This allows volume size - to be expanded without interruption of service. - In a cloud model it would be more typical to just - spin up a new instance with large storage, so the ability to - extend the size of an attached volume is for those cases - where the instance is considered to be more of a pet than cattle. - Therefore this operation is not considered to be mandatory to support. -cli=cinder extend -driver.nova-lxd=unknown - -[operation.attach-interface] -title=Attach virtual network interface to instance -status=optional -notes=The attach interface operation provides a means to hotplug - additional interfaces to a running instance. Hotplug support - varies between guest OSes and some guests require a reboot for - new interfaces to be detected. This operation allows interface - capabilities to be expanded without interruption of service. - In a cloud model it would be more typical to just spin up a - new instance with more interfaces. -cli=nova interface-attach -driver.nova-lxd=complete - -[operation.attach-tagged-interface] -title=Attach tagged virtual network interface to instance -status=optional -notes=Attach a virtual network interface with a tag to an existing - server instance. See "Device tags" for more information. -cli=nova interface-attach [--tag ] -driver.nova-lxd=unknown - -[operation.detach-interface] -title=Detach virtual network interface from instance -status=optional -notes=See notes for attach-interface operation. -cli=nova interface-detach -driver.nova-lxd=complete - -[operation.maintenance-mode] -title=Set the host in a maintenance mode -status=optional -notes=This operation allows a host to be placed into maintenance - mode, automatically triggering migration of any running - instances to an alternative host and preventing new - instances from being launched. This is not considered - to be a mandatory operation to support. - The driver methods to implement are "host_maintenance_mode" and - "set_host_enabled". -cli=nova host-update -driver.nova-lxd=unknown - -[operation.evacuate] -title=Evacuate instances from a host -status=optional -notes=A possible failure scenario in a cloud environment is the outage - of one of the compute nodes. In such a case the instances of the down - host can be evacuated to another host. It is assumed that the old host - is unlikely ever to be powered back on, otherwise the evacuation - attempt will be rejected. When the instances get moved to the new - host, their volumes get re-attached and the locally stored data is - dropped. That happens in the same way as a rebuild. - This is not considered to be a mandatory operation to support. -cli=nova evacuate ;nova host-evacuate -driver.nova-lxd=complete - -[operation.rebuild] -title=Rebuild instance -status=optional -notes=A possible use case is additional attributes need to be set - to the instance, nova will purge all existing data from the system - and remakes the VM with given information such as 'metadata' and - 'personalities'. Though this is not considered to be a mandatory - operation to support. -cli=nova rebuild -driver.nova-lxd=complete - -[operation.get-guest-info] -title=Guest instance status -status=mandatory -notes=Provides realtime information about the power state of the guest - instance. Since the power state is used by the compute manager for - tracking changes in guests, this operation is considered mandatory to - support. -cli= -driver.nova-lxd=unknown - -[operation.get-host-uptime] -title=Guest host uptime -status=optional -notes=Returns the result of host uptime since power on, - it's used to report hypervisor status. -cli= -driver.nova-lxd=unknown - -[operation.get-host-ip] -title=Guest host ip -status=optional -notes=Returns the ip of this host, it's used when doing - resize and migration. -cli= -driver.nova-lxd=unknown - -[operation.live-migrate] -title=Live migrate instance across hosts -status=optional -notes=Live migration provides a way to move an instance off one - compute host, to another compute host. Administrators may use - this to evacuate instances from a host that needs to undergo - maintenance tasks, though of course this may not help if the - host is already suffering a failure. In general instances are - considered cattle rather than pets, so it is expected that an - instance is liable to be killed if host maintenance is required. - It is technically challenging for some hypervisors to provide - support for the live migration operation, particularly those - built on the container based virtualization. Therefore this - operation is not considered mandatory to support. -cli=nova live-migration ;nova host-evacuate-live -driver.nova-lxd=complete - -[operation.force-live-migration-to-complete] -title=Force live migration to complete -status=optional -notes=Live migration provides a way to move a running instance to another - compute host. But it can sometimes fail to complete if an instance has - a high rate of memory or disk page access. - This operation provides the user with an option to assist the progress - of the live migration. The mechanism used to complete the live - migration depends on the underlying virtualization subsystem - capabilities. If libvirt/qemu is used and the post-copy feature is - available and enabled then the force complete operation will cause - a switch to post-copy mode. Otherwise the instance will be suspended - until the migration is completed or aborted. -cli=nova live-migration-force-complete -driver.nova-lxd=unknown - -[operation.launch] -title=Launch instance -status=mandatory -notes=Importing pre-existing running virtual machines on a host is - considered out of scope of the cloud paradigm. Therefore this - operation is mandatory to support in drivers. -cli= -driver.nova-lxd=unknown - -[operation.pause] -title=Stop instance CPUs (pause) -status=optional -notes=Stopping an instances CPUs can be thought of as roughly - equivalent to suspend-to-RAM. The instance is still present - in memory, but execution has stopped. The problem, however, - is that there is no mechanism to inform the guest OS that - this takes place, so upon unpausing, its clocks will no - longer report correct time. For this reason hypervisor vendors - generally discourage use of this feature and some do not even - implement it. Therefore this operation is considered optional - to support in drivers. -cli=nova pause -driver.nova-lxd=complete - -[operation.reboot] -title=Reboot instance -status=optional -notes=It is reasonable for a guest OS administrator to trigger a - graceful reboot from inside the instance. A host initiated - graceful reboot requires guest co-operation and a non-graceful - reboot can be achieved by a combination of stop+start. Therefore - this operation is considered optional. -cli=nova reboot -driver.nova-lxd=complete - -[operation.rescue] -title=Rescue instance -status=optional -notes=The rescue operation starts an instance in a special - configuration whereby it is booted from an special root - disk image. The goal is to allow an administrator to - recover the state of a broken virtual machine. In general - the cloud model considers instances to be cattle, so if - an instance breaks the general expectation is that it be - thrown away and a new instance created. Therefore this - operation is considered optional to support in drivers. -cli=nova rescue -driver.nova-lxd=complete - -[operation.resize] -title=Resize instance -status=optional -notes=The resize operation allows the user to change a running - instance to match the size of a different flavor from the one - it was initially launched with. There are many different - flavor attributes that potentially need to be updated. In - general it is technically challenging for a hypervisor to - support the alteration of all relevant config settings for a - running instance. Therefore this operation is considered - optional to support in drivers. -cli=nova resize -driver.nova-lxd=missing - -[operation.resume] -title=Restore instance -status=optional -notes=See notes for the suspend operation -cli=nova resume -driver.nova-lxd=complete - -[operation.set-admin-password] -title=Set instance admin password -status=optional -notes=Provides a mechanism to (re)set the password of the administrator - account inside the instance operating system. This requires that the - hypervisor has a way to communicate with the running guest operating - system. Given the wide range of operating systems in existence it is - unreasonable to expect this to be practical in the general case. The - configdrive and metadata service both provide a mechanism for setting - the administrator password at initial boot time. In the case where this - operation were not available, the administrator would simply have to - login to the guest and change the password in the normal manner, so - this is just a convenient optimization. Therefore this operation is - not considered mandatory for drivers to support. -cli=nova set-password -driver.nova-lxd=unknown - -[operation.snapshot] -title=Save snapshot of instance disk -status=optional -notes=The snapshot operation allows the current state of the - instance root disk to be saved and uploaded back into the - glance image repository. The instance can later be booted - again using this saved image. This is in effect making - the ephemeral instance root disk into a semi-persistent - storage, in so much as it is preserved even though the guest - is no longer running. In general though, the expectation is - that the root disks are ephemeral so the ability to take a - snapshot cannot be assumed. Therefore this operation is not - considered mandatory to support. -cli=nova image-create -driver.nova-lxd=complete - -[operation.suspend] -title=Suspend instance -status=optional -notes=Suspending an instance can be thought of as roughly - equivalent to suspend-to-disk. The instance no longer - consumes any RAM or CPUs, with its live running state - having been preserved in a file on disk. It can later - be restored, at which point it should continue execution - where it left off. As with stopping instance CPUs, it suffers from the fact - that the guest OS will typically be left with a clock that - is no longer telling correct time. For container based - virtualization solutions, this operation is particularly - technically challenging to implement and is an area of - active research. This operation tends to make more sense - when thinking of instances as pets, rather than cattle, - since with cattle it would be simpler to just terminate - the instance instead of suspending. Therefore this operation - is considered optional to support. -cli=nova suspend -driver.nova-lxd=complete - -[operation.swap-volume] -title=Swap block volumes -status=optional -notes=The swap volume operation is a mechanism for changing a running - instance so that its attached volume(s) are backed by different - storage in the host. An alternative to this would be to simply - terminate the existing instance and spawn a new instance with the - new storage. In other words this operation is primarily targeted towards - the pet use case rather than cattle, however, it is required for volume - migration to work in the volume service. This is considered optional to - support. -cli=nova volume-update -driver.nova-lxd=missing - -[operation.terminate] -title=Shutdown instance -status=mandatory -notes=The ability to terminate a virtual machine is required in - order for a cloud user to stop utilizing resources and thus - avoid indefinitely ongoing billing. Therefore this operation - is mandatory to support in drivers. -cli=nova delete -driver.nova-lxd=complete - -[operation.trigger-crash-dump] -title=Trigger crash dump -status=optional -notes=The trigger crash dump operation is a mechanism for triggering - a crash dump in an instance. The feature is typically implemented by - injecting an NMI (Non-maskable Interrupt) into the instance. It provides - a means to dump the production memory image as a dump file which is useful - for users. Therefore this operation is considered optional to support. -cli=nova trigger-crash-dump -driver.nova-lxd=unknown - -[operation.unpause] -title=Resume instance CPUs (unpause) -status=optional -notes=See notes for the "Stop instance CPUs" operation -cli=nova unpause -driver.nova-lxd=unknown - -[operation.guest.disk.autoconfig] -title=[Guest]Auto configure disk -status=optional -notes=Partition and resize FS to match the size specified by - flavors.root_gb, As this is hypervisor specific feature. - Therefore this operation is considered optional to support. -cli= -driver.nova-lxd=complete - -[operation.guest.disk.rate-limit] -title=[Guest]Instance disk I/O limits -status=optional -notes=The ability to set rate limits on virtual disks allows for - greater performance isolation between instances running on the - same host storage. It is valid to delegate scheduling of I/O - operations to the hypervisor with its default settings, instead - of doing fine grained tuning. Therefore this is not considered - to be an mandatory configuration to support. -cli=nova limits -driver.nova-lxd=unknown - -[operation.guest.setup.configdrive] -title=[Guest]Config drive support -status=choice(guest.setup) -notes=The config drive provides an information channel into - the guest operating system, to enable configuration of the - administrator password, file injection, registration of - SSH keys, etc. Since cloud images typically ship with all - login methods locked, a mechanism to set the administrator - password or keys is required to get login access. Alternatives - include the metadata service and disk injection. At least one - of the guest setup mechanisms is required to be supported by - drivers, in order to enable login access. -cli= -driver.nova-lxd=complete - -[operation.guest.setup.inject.file] -title=[Guest]Inject files into disk image -status=optional -notes=This allows for the end user to provide data for multiple - files to be injected into the root filesystem before an instance - is booted. This requires that the compute node understand the - format of the filesystem and any partitioning scheme it might - use on the block device. This is a non-trivial problem considering - the vast number of filesystems in existence. The problem of injecting - files to a guest OS is better solved by obtaining via the metadata - service or config drive. Therefore this operation is considered - optional to support. -cli= -driver.nova-lxd=unknown - -[operation.guest.setup.inject.networking] -title=[Guest]Inject guest networking config -status=optional -notes=This allows for static networking configuration (IP - address, netmask, gateway and routes) to be injected directly - into the root filesystem before an instance is booted. This - requires that the compute node understand how networking is - configured in the guest OS which is a non-trivial problem - considering the vast number of operating system types. The - problem of configuring networking is better solved by DHCP - or by obtaining static config via - config drive. Therefore this operation is considered optional - to support. -cli= -driver.nova-lxd=unknown - -[operation.console.rdp] -title=[Console]Remote desktop over RDP -status=choice(console) -notes=This allows the administrator to interact with the graphical - console of the guest OS via RDP. This provides a way to see boot - up messages and login to the instance when networking configuration - has failed, thus preventing a network based login. Some operating - systems may prefer to emit messages via the serial console for - easier consumption. Therefore support for this operation is not - mandatory, however, a driver is required to support at least one - of the listed console access operations. -cli=nova get-rdp-console -driver.nova-lxd=missing - -[operation.console.serial.log] -title=[Console]View serial console logs -status=choice(console) -notes=This allows the administrator to query the logs of data - emitted by the guest OS on its virtualized serial port. For - UNIX guests this typically includes all boot up messages and - so is useful for diagnosing problems when an instance fails - to successfully boot. Not all guest operating systems will be - able to emit boot information on a serial console, others may - only support graphical consoles. Therefore support for this - operation is not mandatory, however, a driver is required to - support at least one of the listed console access operations. -cli=nova console-log -driver.nova-lxd=complete - -[operation.console.serial.interactive] -title=[Console]Remote interactive serial console -status=choice(console) -notes=This allows the administrator to interact with the serial - console of the guest OS. This provides a way to see boot - up messages and login to the instance when networking configuration - has failed, thus preventing a network based login. Not all guest - operating systems will be able to emit boot information on a serial - console, others may only support graphical consoles. Therefore support - for this operation is not mandatory, however, a driver is required to - support at least one of the listed console access operations. - This feature was introduced in the Juno release with blueprint - https://blueprints.launchpad.net/nova/+spec/serial-ports -cli=nova get-serial-console -driver.nova-lxd=unknown - -[operation.console.spice] -title=[Console]Remote desktop over SPICE -status=choice(console) -notes=This allows the administrator to interact with the graphical - console of the guest OS via SPICE. This provides a way to see boot - up messages and login to the instance when networking configuration - has failed, thus preventing a network based login. Some operating - systems may prefer to emit messages via the serial console for - easier consumption. Therefore support for this operation is not - mandatory, however, a driver is required to support at least one - of the listed console access operations. -cli=nova get-spice-console -driver.nova-lxd=missing - -[operation.console.vnc] -title=[Console]Remote desktop over VNC -status=choice(console) -notes=This allows the administrator to interact with the graphical - console of the guest OS via VNC. This provides a way to see boot - up messages and login to the instance when networking configuration - has failed, thus preventing a network based login. Some operating - systems may prefer to emit messages via the serial console for - easier consumption. Therefore support for this operation is not - mandatory, however, a driver is required to support at least one - of the listed console access operations. -cli=nova get-vnc-console -driver.nova-lxd=missing - -[operation.storage.block] -title=[Storage]Block storage support -status=optional -notes=Block storage provides instances with direct attached - virtual disks that can be used for persistent storage of data. - As an alternative to direct attached disks, an instance may - choose to use network based persistent storage. OpenStack provides - object storage via the Swift service, or a traditional filesystem - such as NFS may be used. Some types of instances may - not require persistent storage at all, being simple transaction - processing systems reading requests & sending results to and from - the network. Therefore support for this configuration is not - considered mandatory for drivers to support. -cli= -driver.nova-lxd=partial -driver-notes.nova-lxd=Booting instances from block storages is not supported. - -[operation.storage.block.backend.fibrechannel] -title=[Storage]Block storage over fibre channel -status=optional -notes=To maximise performance of the block storage, it may be desirable - to directly access fibre channel LUNs from the underlying storage - technology on the compute hosts. Since this is just a performance - optimization of the I/O path it is not considered mandatory to support. -cli= -driver.nova-lxd=unknown - -[operation.storage.block.backend.iscsi] -title=[Storage]Block storage over iSCSI -status=condition(storage.block==complete) -notes=If the driver wishes to support block storage, it is common to - provide an iSCSI based backend to access the storage from cinder. - This isolates the compute layer for knowledge of the specific storage - technology used by Cinder, albeit at a potential performance cost due - to the longer I/O path involved. If the driver chooses to support - block storage, then this is considered mandatory to support, otherwise - it is considered optional. -cli= -driver.nova-lxd=complete - -[operation.storage.block.backend.iscsi.auth.chap] -title=[Storage]CHAP authentication for iSCSI -status=optional -notes=If accessing the cinder iSCSI service over an untrusted LAN it - is desirable to be able to enable authentication for the iSCSI - protocol. CHAP is the commonly used authentication protocol for - iSCSI. This is not considered mandatory to support. (?) -cli= -driver.nova-lxd=unknown - -[operation.storage.block.ceph] -title=[Storage]Block storage over RBD(Ceph) -status=condition(storage.block==complete) -notes=Ceph is an open source software storage platform based upon RADOS. - Instances can access to the ceph storage cluster by ceph's RBD(RADOS - Block Device). -cli= -driver.nova-lxd=complete - -[operation.storage.image] -title=[Storage]Image storage support -status=mandatory -notes=This refers to the ability to boot an instance from an image - stored in the glance image repository. Without this feature it - would not be possible to bootstrap from a clean environment, since - there would be no way to get block volumes populated and reliance - on external PXE servers is out of scope. Therefore this is considered - a mandatory storage feature to support. -cli=nova boot --image -driver.nova-lxd=complete - -[operation.networking.firewallrules] -title=[Networking]Network firewall rules -status=optional -notes=Unclear how this is different from security groups -cli= -driver.nova-lxd=complete - -[operation.networking.routing] -title=Network routing -status=optional -notes=Unclear what this refers to -cli= -driver.nova-lxd=complete - -[operation.networking.securitygroups] -title=[Networking]Network security groups -status=optional -notes=The security groups feature provides a way to define rules - to isolate the network traffic of different instances running - on a compute host. This would prevent actions such as MAC and - IP address spoofing, or the ability to setup rogue DHCP servers. - In a private cloud environment this may be considered to be a - superfluous requirement. Therefore this is considered to be an - optional configuration to support. -cli= -driver.nova-lxd=complete - -[operation.networking.topology.flat] -title=[Networking]Flat networking -status=choice(networking.topology) -notes=Provide network connectivity to guests using a - flat topology across all compute nodes. At least one - of the networking configurations is mandatory to - support in the drivers. -cli= -driver.nova-lxd=complete - -[operation.networking.topology.vlan] -title=[Networking]VLAN networking -status=choice(networking.topology) -notes=Provide network connectivity to guests using VLANs to define the - topology. At least one of the networking configurations is mandatory - to support in the drivers. -cli= -driver.nova-lxd=complete - -[operation.networking.topology.vxlan] -title=[Networking]VXLAN networking -status=choice(networking.topology) -notes=Provide network connectivity to guests using VXLANs to define the - topology. At least one of the networking configurations is mandatory - to support in the drivers. -cli= -driver.nova-lxd=complete - -[operation.uefi-boot] -title=uefi boot -status=optional -notes=This allows users to boot a guest with uefi firmware. -cli= -driver.nova-lxd=unknown - -[operation.device-tags] -title=Device tags -status=optional -notes=This allows users to set tags on virtual devices when creating a - server instance. Device tags are used to identify virtual device - metadata, as exposed in the metadata API and on the config drive. - For example, a network interface tagged with "nic1" will appear in - the metadata along with its bus (ex: PCI), bus address - (ex: 0000:00:02.0), MAC address, and tag (nic1). If multiple networks - are defined, the order in which they appear in the guest operating - system will not necessarily reflect the order in which they are given - in the server boot request. Guests should therefore not depend on - device order to deduce any information about their network devices. - Instead, device role tags should be used. Device tags can be - applied to virtual network interfaces and block devices. -cli=nova boot -driver.nova-lxd=unknown - -[operation.quiesce] -title=quiesce -status=optional -notes=Quiesce the specified instance to prepare for snapshots. - For libvirt, guest filesystems will be frozen through qemu - agent. -cli= -driver.nova-lxd=unknown - -[operation.unquiesce] -title=unquiesce -status=optional -notes=See notes for the quiesce operation -cli= -driver.nova-lxd=unknown - -[operation.multiattach-volume] -title=Attach block volume to multiple instances -status=optional -notes=The multiattach volume operation is an extension to - the attach volume operation. It allows to attach a - single volume to multiple instances. This operation is - not considered to be mandatory to support. - Note that for the libvirt driver, this is only supported - if qemu<2.10 or libvirt>=3.10. -cli=nova volume-attach -driver.nova-lxd=unknown - -[operation.encrypted-volume] -title=Attach encrypted block volume to server -status=optional -notes=This is the same as the attach volume operation - except with an encrypted block device. Encrypted - volumes are controlled via admin-configured volume - types in the block storage service. Since attach - volume is optional this feature is also optional for - compute drivers to support. -cli=nova volume-attach -driver.nova-lxd=unknown - -[operation.trusted-certs] -title=Validate image with trusted certificates -status=optional -notes=Since trusted image certification validation is configurable - by the cloud deployer it is considered optional. However, it is - a virt-agnostic feature so there is no good reason that all virt - drivers cannot support the feature since it is mostly just plumbing - user requests through the virt driver when downloading images. -cli=nova boot --trusted-image-certificate-id ... -driver.nova-lxd=unknown - -[operation.file-backed-memory] -title=File backed memory -status=optional -notes=The file backed memory feature in Openstack allows a Nova node to serve - guest memory from a file backing store. This mechanism uses the libvirt - file memory source, causing guest instance memory to be allocated as files - within the libvirt memory backing directory. This is only supported if - qemu>2.6 and libivrt>4.0.0 -cli= -driver.nova-lxd=unknown - -[operation.report-cpu-traits] -title=Report CPU traits -status=optional -notes=The report CPU traits feature in OpenStack allows a Nova node to report - its CPU traits according to CPU mode configuration. This gives users the ability - to boot instances based on desired CPU traits. -cli= -driver.nova-lxd=unknown diff --git a/doc/source/support_matrix/support-matrix.rst b/doc/source/support_matrix/support-matrix.rst deleted file mode 100644 index 5227fc77..00000000 --- a/doc/source/support_matrix/support-matrix.rst +++ /dev/null @@ -1,16 +0,0 @@ -=============================== -Nova-lxd Feature Support Matrix -=============================== - -The following support matrix reflects the nova-lxd that is currently available -or is available at the time of release. - -.. Note:: - - Notes for each operation of this matrix were basically quoted from - `support-matrix of Nova `_. - - -.. _driver_support_matrix: - -.. support_matrix:: support-matrix.ini diff --git a/doc/source/usage.rst b/doc/source/usage.rst deleted file mode 100644 index f817f636..00000000 --- a/doc/source/usage.rst +++ /dev/null @@ -1,7 +0,0 @@ -======== -Usage -======== - -To use nova-lxd in a project:: - - import nova.virt.lxd diff --git a/doc/source/vif_wiring.rst b/doc/source/vif_wiring.rst deleted file mode 100644 index 3a783fd6..00000000 --- a/doc/source/vif_wiring.rst +++ /dev/null @@ -1,59 +0,0 @@ -Nova-LXD VIF Design Notes -========================= - -VIF plugging workflow ---------------------- - -Nova-LXD makes use of the os-vif interface plugging library to wire LXD -instances into underlying Neutron networking; however there are some -subtle differences between the Nova-Libvirt driver and the Nova-LXD driver -in terms of how the last mile wiring is done to the instances. - -In the Nova-Libvirt driver, Libvirt is used to start the instance in a -paused state, which creates the required tap device and any required wiring -to bridges created in previous os-vif plugging events. - -The concept of 'start-and-pause' does not exist in LXD, so the driver -creates a veth pair instead, allowing the last mile wiring to be created -in advance of the actual LXD container being created. - -This allows Neutron to complete the underlying VIF plugging at which -point it will notify Nova and the Nova-LXD driver will create the LXD -container and wire the pre-created veth pair into its profile. - -tap/tin veth pairs ------------------- - -The veth pair created to wire the LXD instance into the underlying Neutron -networking uses the tap and tin prefixes; the tap named device is present -on the host OS, allowing iptables based firewall rules to be applied as -they are for other virt drivers, and the tin named device is passed to -LXD as part of the container profile. LXD will rename this device -internally within the container to an ethNN style name. - -The LXD profile devices for network interfaces are created as 'physical' -rather than 'bridged' network devices as the driver handles creation of -the veth pair, rather than LXD (as would happen with a bridged device). - -LXD profile interface naming ----------------------------- - -The name of the interfaces in each containers LXD profile maps to the -devname provided by Neutron as part of VIF plugging - this will typically -be of the format tapXXXXXXX. This allows for easier identification of -the interface during detachment events later in instance lifecycle. - -Prior versions of the nova-lxd driver did not take this approach; interface -naming was not consistent depending on when the interface was attached. The -legacy code used to detach interfaces based on MAC address is used as a -fallback in the event that the new style device name is not found, supporting -upgraders from previous versions of the driver. - -Supported Interface Types -------------------------- - -The Nova-LXD driver has been validated with: - - - OpenvSwitch (ovs) hybrid bridge ports. - - OpenvSwitch (ovs) standard ports. - - Linuxbridge (bridge) ports diff --git a/etc/nova/rootwrap.d/lxd.filters b/etc/nova/rootwrap.d/lxd.filters deleted file mode 100644 index 441c671d..00000000 --- a/etc/nova/rootwrap.d/lxd.filters +++ /dev/null @@ -1,11 +0,0 @@ -# nova-rootwrap filters for compute nodes running nova-lxd -# This file should be owned by (and only-writable by) the root user - -[Filters] -zfs: CommandFilter, zfs, root -zpool: CommandFilter, zpool, root -btrfs: CommandFilter, btrfs, root -chown: CommandFilter, chown, root -chmod: CommandFilter, chmod, root -mount: CommandFilter, mount, root -umount: CommandFilter, umount, root diff --git a/lower-constraints.txt b/lower-constraints.txt deleted file mode 100644 index 5bebaebe..00000000 --- a/lower-constraints.txt +++ /dev/null @@ -1,112 +0,0 @@ -alabaster==0.7.10 -amqp==2.2.2 -appdirs==1.4.3 -asn1crypto==0.24.0 -Babel==2.5.3 -cachetools==2.0.1 -certifi==2018.1.18 -cffi==1.11.5 -chardet==3.0.4 -cliff==2.11.0 -cmd2==0.8.1 -contextlib2==0.5.5 -coverage==4.5.1 -cryptography==2.1.4 -ddt==1.1.2 -debtcollector==1.19.0 -docutils==0.14 -enum-compat==0.0.2 -eventlet==0.20.0 -extras==1.0.0 -fasteners==0.14.1 -fixtures==3.0.0 -flake8==2.5.5 -future==0.16.0 -futurist==1.6.0 -greenlet==0.4.13 -hacking==0.12.0 -idna==2.6 -imagesize==1.0.0 -iso8601==0.1.12 -Jinja2==2.10 -keystoneauth1==3.4.0 -kombu==4.1.0 -linecache2==1.0.0 -MarkupSafe==1.0 -mccabe==0.2.1 -mock==2.0.0 -monotonic==1.4 -mox3==0.25.0 -msgpack==0.5.6 -netaddr==0.7.19 -netifaces==0.10.6 -nose==1.3.7 -nosexcover==1.0.11 -os-brick==2.3.0 -os-client-config==1.29.0 -os-testr==1.0.0 -os-vif==1.9.0 -os-win==4.0.0 -oslo.concurrency==3.26.0 -oslo.config==5.2.0 -oslo.context==2.20.0 -oslo.i18n==3.20.0 -oslo.log==3.37.0 -oslo.messaging==5.36.0 -oslo.middleware==3.35.0 -oslo.privsep==1.28.0 -oslo.serialization==2.25.0 -oslo.service==1.30.0 -oslo.utils==3.36.0 -oslo.versionedobjects==1.32.0 -oslosphinx==4.18.0 -oslotest==3.3.0 -Paste==2.0.3 -PasteDeploy==1.5.2 -pbr==3.1.1 -pep8==1.5.7 -pika==0.10.0 -pika-pool==0.1.3 -prettytable==0.7.2 -pycparser==2.18 -pyflakes==0.8.1 -Pygments==2.2.0 -pyinotify==0.9.6 -pylxd==2.2.6 -pyparsing==2.2.0 -pyperclip==1.6.0 -pyroute2==0.4.21 -python-dateutil==2.7.0 -python-mimeparse==1.6.0 -python-subunit==1.2.0 -pytz==2018.3 -PyYAML==3.12 -repoze.lru==0.7 -requests==2.18.4 -requests-toolbelt==0.8.0 -requests-unixsocket==0.1.5 -requestsexceptions==1.4.0 -retrying==1.3.3 -rfc3986==1.1.0 -Routes==2.4.1 -six==1.11.0 -snowballstemmer==1.2.1 -Sphinx==1.6.5 -sphinx-feature-classification==0.1.0 -sphinxcontrib-websupport==1.0.1 -statsd==3.2.2 -stestr==1.0.0 -stevedore==1.28.0 -tenacity==4.9.0 -testrepository==0.0.20 -testscenarios==0.5.0 -testtools==2.3.0 -traceback2==1.4.0 -unittest2==1.1.0 -urllib3==1.22 -vine==1.1.4 -voluptuous==0.11.1 -WebOb==1.7.4 -wrapt==1.10.11 -ws4py==0.5.1 -wsgi-intercept==1.6.0 diff --git a/nova/__init__.py b/nova/__init__.py deleted file mode 100644 index de40ea7c..00000000 --- a/nova/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__) diff --git a/nova/tests/unit/virt/lxd/__init__.py b/nova/tests/unit/virt/lxd/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nova/tests/unit/virt/lxd/fake_api.py b/nova/tests/unit/virt/lxd/fake_api.py deleted file mode 100644 index 3ead0dd2..00000000 --- a/nova/tests/unit/virt/lxd/fake_api.py +++ /dev/null @@ -1,397 +0,0 @@ -# Copyright (c) 2015 Canonical Ltd -# -# 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. - - -def fake_standard_return(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": {} - } - - -def fake_host(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": { - "api_compat": 1, - "auth": "trusted", - "config": {}, - "environment": { - "backing_fs": "ext4", - "driver": "lxc", - "kernel_version": "3.19.0-22-generic", - "lxc_version": "1.1.2", - "lxd_version": "0.12" - } - } - } - - -def fake_image_list_empty(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": [] - } - - -def fake_image_list(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": ['/1.0/images/trusty'] - } - - -def fake_image_info(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": { - "aliases": [ - { - "target": "ubuntu", - "description": "ubuntu" - } - ], - "architecture": 2, - "fingerprint": "04aac4257341478b49c25d22cea8a6ce" - "0489dc6c42d835367945e7596368a37f", - "filename": "", - "properties": {}, - "public": 0, - "size": 67043148, - "created_at": 0, - "expires_at": 0, - "uploaded_at": 1435669853 - } - } - - -def fake_alias(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": { - "target": "ubuntu", - "description": "ubuntu" - } - } - - -def fake_alias_list(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": [ - "/1.0/images/aliases/ubuntu" - ] - } - - -def fake_container_list(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": [ - "/1.0/containers/trusty-1" - ] - } - - -def fake_container_state(status): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": { - "status_code": status - } - } - - -def fake_container_log(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": { - "log": "fake log" - } - } - - -def fake_container_migrate(): - return { - "type": "async", - "status": "Operation created", - "status_code": 100, - "metadata": { - "id": "dbd9f22c-6da5-4066-8fca-c02f09f76738", - "class": "websocket", - "created_at": "2016-02-07T09:20:53.127321875-05:00", - "updated_at": "2016-02-07T09:20:53.127321875-05:00", - "status": "Running", - "status_code": 103, - "resources": { - "containers": [ - "/1.0/containers/instance-00000010" - ] - }, - "metadata": { - "control": "fake_control", - "fs": "fake_fs" - }, - "may_cancel": 'false', - "err": "" - }, - "operation": "/1.0/operations/dbd9f22c-6da5-4066-8fca-c02f09f76738" - } - - -def fake_snapshots_list(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": [ - "/1.0/containers/trusty-1/snapshots/first" - ] - } - - -def fake_certificate_list(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": [ - "/1.0/certificates/ABCDEF01" - ] - } - - -def fake_certificate(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": { - "type": "client", - "certificate": "ABCDEF01" - } - } - - -def fake_profile_list(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": [ - "/1.0/profiles/fake-profile" - ] - } - - -def fake_profile(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": { - "name": "fake-profile", - "config": { - "resources.memory": "2GB", - "network.0.bridge": "lxcbr0" - } - } - } - - -def fake_operation_list(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": [ - "/1.0/operations/1234" - ] - } - - -def fake_operation(): - return { - "type": "async", - "status": "OK", - "status_code": 100, - "operation": "/1.0/operation/1234", - "metadata": { - "created_at": "2015-06-09T19:07:24.379615253-06:00", - "updated_at": "2015-06-09T19:07:23.379615253-06:00", - "status": "Running", - "status_code": 103, - "resources": { - "containers": ["/1.0/containers/1"] - }, - "metadata": {}, - "may_cancel": True - } - } - - -def fake_operation_info_ok(): - return { - "type": "async", - "status": "OK", - "status_code": 200, - "operation": "/1.0/operation/1234", - "metadata": { - "created_at": "2015-06-09T19:07:24.379615253-06:00", - "updated_at": "2015-06-09T19:07:23.379615253-06:00", - "status": "Completed", - "status_code": 200, - "resources": { - "containers": ["/1.0/containers/1"] - }, - "metadata": {}, - "may_cancel": True - } - } - - -def fake_operation_info_failed(): - return { - "type": "async", - "status": "OK", - "status_code": 200, - "operation": "/1.0/operation/1234", - "metadata": { - "created_at": "2015-06-09T19:07:24.379615253-06:00", - "updated_at": "2015-06-09T19:07:23.379615253-06:00", - "status": "Failure", - "status_code": 400, - "resources": { - "containers": ["/1.0/containers/1"] - }, - "metadata": "Invalid container name", - "may_cancel": True - } - } - - -def fake_network_list(): - return { - "type": "sync", - "status": "Success", - "status_code": 200, - "metadata": [ - "/1.0/networks/lxcbr0" - ] - } - - -def fake_network(): - return { - "type": "async", - "status": "OK", - "status_code": 100, - "operation": "/1.0/operation/1234", - "metadata": { - "name": "lxcbr0", - "type": "bridge", - "members": ["/1.0/containers/trusty-1"] - } - } - - -def fake_container_config(): - return { - 'name': "my-container", - 'profiles': ["default"], - 'architecture': 2, - 'config': {"limits.cpus": "3"}, - 'expanded_config': {"limits.cpus": "3"}, - 'devices': { - 'rootfs': { - 'type': "disk", - 'path': "/", - 'source': "UUID=8f7fdf5e-dc60-4524-b9fe-634f82ac2fb6" - } - }, - 'expanded_devices': { - 'rootfs': { - 'type': "disk", - 'path': "/", - 'source': "UUID=8f7fdf5e-dc60-4524-b9fe-634f82ac2fb6"} - }, - "eth0": { - "type": "nic", - "parent": "lxcbr0", - "hwaddr": "00:16:3e:f4:e7:1c", - "name": "eth0", - "nictype": "bridged", - } - } - - -def fake_container_info(): - return { - 'name': "my-container", - 'profiles': ["default"], - 'architecture': 2, - 'config': {"limits.cpus": "3"}, - 'expanded_config': {"limits.cpus": "3"}, - 'devices': { - 'rootfs': { - 'type': "disk", - 'path': "/", - 'source': "UUID=8f7fdf5e-dc60-4524-b9fe-634f82ac2fb6" - } - }, - 'expanded_devices': { - 'rootfs': { - 'type': "disk", - 'path': "/", - 'source': "UUID=8f7fdf5e-dc60-4524-b9fe-634f82ac2fb6"} - }, - "eth0": { - "type": "nic", - "parent": "lxcbr0", - "hwaddr": "00:16:3e:f4:e7:1c", - "name": "eth0", - "nictype": "bridged", - }, - 'status': { - 'status': "Running", - 'status_code': 103, - 'ips': [{'interface': "eth0", - 'protocol': "INET6", - 'address': "2001:470:b368:1020:1::2", - 'host_veth': "vethGMDIY9"}, - {'interface': "eth0", - 'protocol': "INET", - 'address': "172.16.15.30", - 'host_veth': "vethGMDIY9"}]}, - } diff --git a/nova/tests/unit/virt/lxd/stubs.py b/nova/tests/unit/virt/lxd/stubs.py deleted file mode 100644 index 124f1438..00000000 --- a/nova/tests/unit/virt/lxd/stubs.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright (c) 2015 Canonical Ltd -# -# 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 ddt -import mock -import uuid - -from nova import context -from nova.tests.unit import fake_instance -from pylxd.deprecated import api - - -class MockConf(mock.Mock): - - def __init__(self, lxd_args=(), lxd_kwargs={}, *args, **kwargs): - default = { - 'config_drive_format': None, - 'instances_path': '/fake/instances/path', - 'image_cache_subdirectory_name': '/fake/image/cache', - 'vif_plugging_timeout': 10, - 'my_ip': '1.2.3.4', - 'vlan_interface': 'vlanif', - 'flat_interface': 'flatif', - } - - default.update(kwargs) - super(MockConf, self).__init__(*args, **default) - - lxd_default = { - 'root_dir': '/fake/lxd/root', - 'timeout': 20, - 'retry_interval': 2, - } - lxd_default.update(lxd_kwargs) - self.lxd = mock.Mock(lxd_args, **lxd_default) - - -class MockInstance(mock.Mock): - - def __init__(self, name='fake-uuid', uuid='fake-uuid', - image_ref='mock_image', ephemeral_gb=0, memory_mb=-1, - vcpus=0, *args, **kwargs): - super(MockInstance, self).__init__( - uuid=uuid, - image_ref=image_ref, - ephemeral_gb=ephemeral_gb, - *args, **kwargs) - self.uuid = uuid - self.name = name - self.flavor = mock.Mock(memory_mb=memory_mb, vcpus=vcpus) - - -def lxd_mock(*args, **kwargs): - mock_api = mock.Mock(spec=api.API) - default = { - 'profile_list.return_value': ['fake_profile'], - 'container_list.return_value': ['mock-instance-1', 'mock-instance-2'], - 'host_ping.return_value': True, - } - default.update(kwargs) - mock_api.configure_mock(**default) - return mock_api - - -def annotated_data(*args): - class List(list): - pass - - class Dict(dict): - pass - - new_args = [] - - for arg in args: - if isinstance(arg, (list, tuple)): - new_arg = List(arg) - new_arg.__name__ = arg[0] - elif isinstance(arg, dict): - new_arg = Dict(arg) - new_arg.__name__ = arg['tag'] - else: - raise TypeError('annotate_data can only handle dicts, ' - 'lists and tuples') - new_args.append(new_arg) - - return lambda func: ddt.data(*new_args)(ddt.unpack(func)) - - -def _fake_instance(): - ctxt = context.get_admin_context() - _instance_values = { - 'display_name': 'fake_display_name', - 'name': 'fake_name', - 'uuid': uuid.uuid1(), - 'image_ref': 'fake_image', - 'vcpus': 1, - 'memory_mb': 512, - 'root_gb': 10, - 'host': 'fake_host', - 'expected_attrs': ['system_metadata'], - } - return fake_instance.fake_instance_obj( - ctxt, **_instance_values) diff --git a/nova/tests/unit/virt/lxd/test_common.py b/nova/tests/unit/virt/lxd/test_common.py deleted file mode 100644 index fc7c527c..00000000 --- a/nova/tests/unit/virt/lxd/test_common.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2016 Canonical Ltd -# 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 context -from nova import test -from nova.tests.unit import fake_instance - -from nova.virt.lxd import common - - -class InstanceAttributesTest(test.NoDBTestCase): - """Tests for InstanceAttributes.""" - - def setUp(self): - super(InstanceAttributesTest, self).setUp() - - self.CONF_patcher = mock.patch('nova.virt.lxd.driver.nova.conf.CONF') - self.CONF = self.CONF_patcher.start() - self.CONF.instances_path = '/i' - self.CONF.lxd.root_dir = '/c' - - def tearDown(self): - super(InstanceAttributesTest, self).tearDown() - self.CONF_patcher.stop() - - def test_is_snap_lxd(self): - with mock.patch('os.path.isfile') as isfile: - isfile.return_value = False - self.assertFalse(common.is_snap_lxd()) - isfile.return_value = True - self.assertTrue(common.is_snap_lxd()) - - @mock.patch.object(common, 'is_snap_lxd') - def test_instance_dir(self, is_snap_lxd): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - is_snap_lxd.return_value = False - - attributes = common.InstanceAttributes(instance) - - self.assertEqual( - '/i/instance-00000001', attributes.instance_dir) - - @mock.patch.object(common, 'is_snap_lxd') - def test_console_path(self, is_snap_lxd): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - is_snap_lxd.return_value = False - - attributes = common.InstanceAttributes(instance) - self.assertEqual( - '/var/log/lxd/instance-00000001/console.log', - attributes.console_path) - - is_snap_lxd.return_value = True - attributes = common.InstanceAttributes(instance) - self.assertEqual( - '/var/snap/lxd/common/lxd/logs/instance-00000001/console.log', - attributes.console_path) - - @mock.patch.object(common, 'is_snap_lxd') - def test_storage_path(self, is_snap_lxd): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - is_snap_lxd.return_value = False - - attributes = common.InstanceAttributes(instance) - - self.assertEqual( - '/i/instance-00000001/storage', - attributes.storage_path) diff --git a/nova/tests/unit/virt/lxd/test_driver.py b/nova/tests/unit/virt/lxd/test_driver.py deleted file mode 100644 index 670030f1..00000000 --- a/nova/tests/unit/virt/lxd/test_driver.py +++ /dev/null @@ -1,1581 +0,0 @@ -# Copyright 2016 Canonical Ltd -# 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 collections -import base64 -from contextlib import closing - -import eventlet -from oslo_config import cfg -from oslo_serialization import jsonutils -import mock -from nova import context -from nova import exception -from nova import utils -from nova import test -from nova.compute import manager -from nova.compute import power_state -from nova.compute import vm_states -from nova.network import model as network_model -from nova.tests.unit import fake_instance -from pylxd import exceptions as lxdcore_exceptions -import six - -from nova.virt.lxd import common -from nova.virt.lxd import driver - -MockResponse = collections.namedtuple('Response', ['status_code']) - -MockContainer = collections.namedtuple('Container', ['name']) -MockContainerState = collections.namedtuple( - 'ContainerState', ['status', 'memory', 'status_code']) - -_VIF = { - 'devname': 'lol0', 'type': 'bridge', 'id': '0123456789abcdef', - 'address': 'ca:fe:de:ad:be:ef'} - - -def fake_connection_info(volume, location, iqn, auth=False, transport=None): - dev_name = 'ip-%s-iscsi-%s-lun-1' % (location, iqn) - if transport is not None: - dev_name = 'pci-0000:00:00.0-' + dev_name - dev_path = '/dev/disk/by-path/%s' % (dev_name) - ret = { - 'driver_volume_type': 'iscsi', - 'data': { - 'volume_id': volume['id'], - 'target_portal': location, - 'target_iqn': iqn, - 'target_lun': 1, - 'device_path': dev_path, - 'qos_specs': { - 'total_bytes_sec': '102400', - 'read_iops_sec': '200', - } - } - } - if auth: - ret['data']['auth_method'] = 'CHAP' - ret['data']['auth_username'] = 'foo' - ret['data']['auth_password'] = 'bar' - return ret - - -class GetPowerStateTest(test.NoDBTestCase): - """Tests for nova.virt.lxd.driver.LXDDriver.""" - - def test_running(self): - state = driver._get_power_state(100) - self.assertEqual(power_state.RUNNING, state) - - def test_shutdown(self): - state = driver._get_power_state(102) - self.assertEqual(power_state.SHUTDOWN, state) - - def test_nostate(self): - state = driver._get_power_state(105) - self.assertEqual(power_state.NOSTATE, state) - - def test_crashed(self): - state = driver._get_power_state(108) - self.assertEqual(power_state.CRASHED, state) - - def test_suspended(self): - state = driver._get_power_state(109) - self.assertEqual(power_state.SUSPENDED, state) - - def test_unknown(self): - self.assertRaises(ValueError, driver._get_power_state, 69) - - -class LXDDriverTest(test.NoDBTestCase): - """Tests for nova.virt.lxd.driver.LXDDriver.""" - - def setUp(self): - super(LXDDriverTest, self).setUp() - - self.Client_patcher = mock.patch('nova.virt.lxd.driver.pylxd.Client') - self.Client = self.Client_patcher.start() - - self.client = mock.Mock() - self.client.host_info = { - 'environment': { - 'storage': 'zfs', - } - } - self.Client.return_value = self.client - - self.patchers = [] - - CONF_patcher = mock.patch('nova.virt.lxd.driver.CONF') - self.patchers.append(CONF_patcher) - self.CONF = CONF_patcher.start() - self.CONF.instances_path = '/path/to/instances' - self.CONF.my_ip = '0.0.0.0' - self.CONF.config_drive_format = 'iso9660' - - # XXX: rockstar (03 Nov 2016) - This should be removed once - # everything is where it should live. - CONF2_patcher = mock.patch('nova.virt.lxd.driver.nova.conf.CONF') - self.patchers.append(CONF2_patcher) - self.CONF2 = CONF2_patcher.start() - self.CONF2.lxd.root_dir = '/lxd' - self.CONF2.lxd.pool = None - self.CONF2.instances_path = '/i' - - # LXDDriver._after_reboot reads from the database and syncs container - # state. These tests can't read from the database. - after_reboot_patcher = mock.patch( - 'nova.virt.lxd.driver.LXDDriver._after_reboot') - self.patchers.append(after_reboot_patcher) - self.after_reboot = after_reboot_patcher.start() - - bdige_patcher = mock.patch( - 'nova.virt.lxd.driver.driver.block_device_info_get_ephemerals') - self.patchers.append(bdige_patcher) - self.block_device_info_get_ephemerals = bdige_patcher.start() - self.block_device_info_get_ephemerals.return_value = [] - - vif_driver_patcher = mock.patch( - 'nova.virt.lxd.driver.lxd_vif.LXDGenericVifDriver') - self.patchers.append(vif_driver_patcher) - self.LXDGenericVifDriver = vif_driver_patcher.start() - self.vif_driver = mock.Mock() - self.LXDGenericVifDriver.return_value = self.vif_driver - - vif_gc_patcher = mock.patch('nova.virt.lxd.driver.lxd_vif.get_config') - self.patchers.append(vif_gc_patcher) - self.get_config = vif_gc_patcher.start() - self.get_config.return_value = { - 'mac_address': '00:11:22:33:44:55', 'bridge': 'qbr0123456789a', - } - - # NOTE: mock out fileutils to ensure that unit tests don't try - # to manipulate the filesystem (breaks in package builds). - driver.fileutils = mock.Mock() - - def tearDown(self): - super(LXDDriverTest, self).tearDown() - self.Client_patcher.stop() - for patcher in self.patchers: - patcher.stop() - - def test_init_host(self): - """init_host initializes the pylxd Client.""" - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - self.Client.assert_called_once_with() - self.assertEqual(self.client, lxd_driver.client) - - def test_init_host_fail(self): - def side_effect(): - raise lxdcore_exceptions.ClientConnectionFailed() - self.Client.side_effect = side_effect - self.Client.return_value = None - - lxd_driver = driver.LXDDriver(None) - - self.assertRaises(exception.HostNotFound, lxd_driver.init_host, None) - - def test_get_info(self): - container = mock.Mock() - container.state.return_value = MockContainerState( - 'Running', {'usage': 4000, 'usage_peak': 4500}, 100) - self.client.containers.get.return_value = container - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - info = lxd_driver.get_info(instance) - - self.assertEqual(power_state.RUNNING, info.state) - - def test_list_instances(self): - self.client.containers.all.return_value = [ - MockContainer('mock-instance-1'), - MockContainer('mock-instance-2'), - ] - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - instances = lxd_driver.list_instances() - - self.assertEqual(['mock-instance-1', 'mock-instance-2'], instances) - - @mock.patch('nova.virt.lxd.driver.IMAGE_API') - @mock.patch('nova.virt.lxd.driver.lockutils.lock') - def test_spawn_unified_image(self, lock, IMAGE_API=None): - def image_get(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(404)) - self.client.images.get_by_alias.side_effect = image_get - self.client.images.exists.return_value = False - image = {'name': mock.Mock(), 'disk_format': 'raw'} - IMAGE_API.get.return_value = image - - def download_unified(*args, **kwargs): - # unified image with metadata - # structure is gzipped tarball, content: - # / - # metadata.yaml - # rootfs/ - unified_tgz = 'H4sIALpegVkAA+3SQQ7CIBCFYY7CCXRAppwHo66sTVpYeHsh0a'\ - 'Ru1A2Lxv/bDGQmYZLHeM7plHLa3dN4NX1INQyhVRdV1vXFuIML'\ - '4lVVopF28cZKp33elCWn2VpTjuWWy4e5L/2NmqcpX5Z91zdawD'\ - 'HqT/kHrf/E+Xo0Vrtu9fTn+QMAAAAAAAAAAAAAAADYrgfk/3zn'\ - 'ACgAAA==' - with closing(open(kwargs['dest_path'], 'wb+')) as img: - img.write(base64.b64decode(unified_tgz)) - IMAGE_API.download = download_unified - self.test_spawn() - - @mock.patch('nova.virt.configdrive.required_by') - def test_spawn(self, configdrive, neutron_failure=None): - def container_get(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(404)) - self.client.containers.get.side_effect = container_get - configdrive.return_value = False - container = mock.Mock() - self.client.containers.create.return_value = container - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - image_meta = mock.Mock() - injected_files = mock.Mock() - admin_password = mock.Mock() - allocations = mock.Mock() - network_info = [_VIF] - block_device_info = mock.Mock() - virtapi = manager.ComputeVirtAPI(mock.MagicMock()) - - lxd_driver = driver.LXDDriver(virtapi) - lxd_driver.init_host(None) - # XXX: rockstar (6 Jul 2016) - There are a number of XXX comments - # related to these calls in spawn. They require some work before we - # can take out these mocks and follow the real codepaths. - lxd_driver.firewall_driver = mock.Mock() - - lxd_driver.spawn( - ctx, instance, image_meta, injected_files, admin_password, - allocations, network_info, block_device_info) - - self.vif_driver.plug.assert_called_once_with( - instance, network_info[0]) - fd = lxd_driver.firewall_driver - fd.setup_basic_filtering.assert_called_once_with( - instance, network_info) - fd.apply_instance_filter.assert_called_once_with( - instance, network_info) - container.start.assert_called_once_with(wait=True) - - def test_spawn_already_exists(self): - """InstanceExists is raised if the container already exists.""" - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - image_meta = mock.Mock() - injected_files = mock.Mock() - admin_password = mock.Mock() - allocations = mock.Mock() - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - self.assertRaises( - exception.InstanceExists, - - lxd_driver.spawn, - ctx, instance, image_meta, injected_files, admin_password, - allocations, None, None) - - @mock.patch('nova.virt.configdrive.required_by') - def test_spawn_with_configdrive(self, configdrive): - def container_get(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(404)) - - self.client.containers.get.side_effect = container_get - configdrive.return_value = True - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - image_meta = mock.Mock() - injected_files = mock.Mock() - admin_password = mock.Mock() - allocations = mock.Mock() - network_info = [_VIF] - block_device_info = mock.Mock() - virtapi = manager.ComputeVirtAPI(mock.MagicMock()) - - lxd_driver = driver.LXDDriver(virtapi) - lxd_driver.init_host(None) - # XXX: rockstar (6 Jul 2016) - There are a number of XXX comments - # related to these calls in spawn. They require some work before we - # can take out these mocks and follow the real codepaths. - lxd_driver.firewall_driver = mock.Mock() - lxd_driver._add_configdrive = mock.Mock() - - lxd_driver.spawn( - ctx, instance, image_meta, injected_files, admin_password, - allocations, network_info, block_device_info) - - self.vif_driver.plug.assert_called_once_with( - instance, network_info[0]) - fd = lxd_driver.firewall_driver - fd.setup_basic_filtering.assert_called_once_with( - instance, network_info) - fd.apply_instance_filter.assert_called_once_with( - instance, network_info) - configdrive.assert_called_once_with(instance) - lxd_driver.client.profiles.get.assert_called_once_with(instance.name) - - @mock.patch('nova.virt.configdrive.required_by') - def test_spawn_profile_fail(self, configdrive, neutron_failure=None): - """Cleanup is called when profile creation fails.""" - def container_get(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(404)) - - def profile_create(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(500)) - self.client.containers.get.side_effect = container_get - self.client.profiles.create.side_effect = profile_create - configdrive.return_value = False - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - image_meta = mock.Mock() - injected_files = mock.Mock() - admin_password = mock.Mock() - allocations = mock.Mock() - network_info = [_VIF] - block_device_info = mock.Mock() - virtapi = manager.ComputeVirtAPI(mock.MagicMock()) - - lxd_driver = driver.LXDDriver(virtapi) - lxd_driver.init_host(None) - lxd_driver.cleanup = mock.Mock() - - self.assertRaises( - lxdcore_exceptions.LXDAPIException, - lxd_driver.spawn, - ctx, instance, image_meta, injected_files, admin_password, - allocations, network_info, block_device_info) - lxd_driver.cleanup.assert_called_once_with( - ctx, instance, network_info, block_device_info) - - @mock.patch('nova.virt.configdrive.required_by') - def test_spawn_container_fail(self, configdrive, neutron_failure=None): - """Cleanup is called when container creation fails.""" - def container_get(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(404)) - - def container_create(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(500)) - self.client.containers.get.side_effect = container_get - self.client.containers.create.side_effect = container_create - configdrive.return_value = False - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - image_meta = mock.Mock() - injected_files = mock.Mock() - admin_password = mock.Mock() - allocations = mock.Mock() - network_info = [_VIF] - block_device_info = mock.Mock() - virtapi = manager.ComputeVirtAPI(mock.MagicMock()) - - lxd_driver = driver.LXDDriver(virtapi) - lxd_driver.init_host(None) - lxd_driver.cleanup = mock.Mock() - - self.assertRaises( - lxdcore_exceptions.LXDAPIException, - lxd_driver.spawn, - ctx, instance, image_meta, injected_files, admin_password, - allocations, network_info, block_device_info) - lxd_driver.cleanup.assert_called_once_with( - ctx, instance, network_info, block_device_info) - - @mock.patch('nova.virt.configdrive.required_by', return_value=False) - def test_spawn_container_cleanup_fail(self, configdrive): - """Cleanup is called but also fail when container creation fails.""" - self.client.containers.get.side_effect = ( - lxdcore_exceptions.LXDAPIException(MockResponse(404))) - container = mock.Mock() - self.client.containers.create.return_value = container - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - image_meta = mock.Mock() - injected_files = mock.Mock() - admin_password = mock.Mock() - allocations = mock.Mock() - network_info = [_VIF] - block_device_info = mock.Mock() - virtapi = manager.ComputeVirtAPI(mock.MagicMock()) - - lxd_driver = driver.LXDDriver(virtapi) - lxd_driver.init_host(None) - - container.start.side_effect = ( - lxdcore_exceptions.LXDAPIException(MockResponse(500))) - lxd_driver.cleanup = mock.Mock() - lxd_driver.cleanup.side_effect = Exception("a bad thing") - - self.assertRaises( - lxdcore_exceptions.LXDAPIException, - lxd_driver.spawn, - ctx, instance, image_meta, injected_files, admin_password, - allocations, network_info, block_device_info) - lxd_driver.cleanup.assert_called_once_with( - ctx, instance, network_info, block_device_info) - - def test_spawn_container_start_fail(self, neutron_failure=None): - def container_get(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(404)) - - def side_effect(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(200)) - - self.client.containers.get.side_effect = container_get - container = mock.Mock() - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - image_meta = mock.Mock() - injected_files = mock.Mock() - admin_password = mock.Mock() - allocations = mock.Mock() - network_info = [_VIF] - block_device_info = mock.Mock() - virtapi = manager.ComputeVirtAPI(mock.MagicMock()) - - lxd_driver = driver.LXDDriver(virtapi) - lxd_driver.init_host(None) - lxd_driver.cleanup = mock.Mock() - lxd_driver.client.containers.create = mock.Mock( - side_effect=side_effect) - container.start.side_effect = side_effect - - self.assertRaises( - lxdcore_exceptions.LXDAPIException, - lxd_driver.spawn, - ctx, instance, image_meta, injected_files, admin_password, - allocations, network_info, block_device_info) - lxd_driver.cleanup.assert_called_once_with( - ctx, instance, network_info, block_device_info) - - def _test_spawn_instance_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 - drv = driver.LXDDriver(virtapi) - - instance_href = fake_instance.fake_instance_obj( - context.get_admin_context(), name='test', memory_mb=0) - - @mock.patch.object(drv, 'plug_vifs') - @mock.patch('nova.virt.configdrive.required_by') - def test_spawn(configdrive, plug_vifs): - def container_get(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(404)) - self.client.containers.get.side_effect = container_get - configdrive.return_value = False - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - image_meta = mock.Mock() - injected_files = mock.Mock() - admin_password = mock.Mock() - allocations = mock.Mock() - network_info = [_VIF] - block_device_info = mock.Mock() - - drv.init_host(None) - drv.spawn( - ctx, instance, image_meta, injected_files, admin_password, - allocations, network_info, block_device_info) - - test_spawn() - - if cfg.CONF.vif_plugging_timeout and utils.is_neutron(): - 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_spawn_instance_with_network_events(self, is_neutron): - self.flags(vif_plugging_timeout=0) - self._test_spawn_instance_with_network_events() - - @mock.patch('nova.utils.is_neutron', return_value=True) - def test_spawn_instance_with_events_neutron_failed_nonfatal_timeout( - self, is_neutron): - self.flags(vif_plugging_timeout=0) - self.flags(vif_plugging_is_fatal=False) - self._test_spawn_instance_with_network_events( - neutron_failure='timeout') - - @mock.patch('nova.virt.lxd.driver.lockutils.lock') - def test_destroy(self, lock): - mock_container = mock.Mock() - mock_container.status = 'Running' - self.client.containers.get.return_value = mock_container - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - network_info = [_VIF] - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - lxd_driver.cleanup = mock.Mock() # There is a separate cleanup test - - lxd_driver.destroy(ctx, instance, network_info) - - lxd_driver.cleanup.assert_called_once_with( - ctx, instance, network_info, None) - lxd_driver.client.containers.get.assert_called_once_with(instance.name) - mock_container.stop.assert_called_once_with(wait=True) - mock_container.delete.assert_called_once_with(wait=True) - - @mock.patch('nova.virt.lxd.driver.lockutils.lock') - def test_destroy_when_in_rescue(self, lock): - mock_stopped_container = mock.Mock() - mock_stopped_container.status = 'Stopped' - mock_rescued_container = mock.Mock() - mock_rescued_container.status = 'Running' - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - network_info = [_VIF] - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - lxd_driver.cleanup = mock.Mock() - - # set the vm_state on the fake instance to RESCUED - instance.vm_state = vm_states.RESCUED - - # set up the containers.get to return the stopped container and then - # the rescued container - self.client.containers.get.side_effect = [ - mock_stopped_container, mock_rescued_container] - - lxd_driver.destroy(ctx, instance, network_info) - - lxd_driver.cleanup.assert_called_once_with( - ctx, instance, network_info, None) - lxd_driver.client.containers.get.assert_has_calls([ - mock.call(instance.name), - mock.call('{}-rescue'.format(instance.name))]) - mock_stopped_container.stop.assert_not_called() - mock_stopped_container.delete.assert_called_once_with(wait=True) - mock_rescued_container.stop.assert_called_once_with(wait=True) - mock_rescued_container.delete.assert_called_once_with(wait=True) - - @mock.patch('nova.virt.lxd.driver.lockutils.lock') - def test_destroy_without_instance(self, lock): - def side_effect(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(404)) - self.client.containers.get.side_effect = side_effect - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - network_info = [_VIF] - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - lxd_driver.cleanup = mock.Mock() # There is a separate cleanup test - - lxd_driver.destroy(ctx, instance, network_info) - lxd_driver.cleanup.assert_called_once_with( - ctx, instance, network_info, None) - - @mock.patch('nova.virt.lxd.driver.network') - @mock.patch('os.path.exists', mock.Mock(return_value=True)) - @mock.patch('pwd.getpwuid') - @mock.patch('shutil.rmtree') - @mock.patch.object(driver.utils, 'execute') - def test_cleanup(self, execute, rmtree, getpwuid, _): - mock_profile = mock.Mock() - self.client.profiles.get.return_value = mock_profile - pwuid = mock.Mock() - pwuid.pw_name = 'user' - getpwuid.return_value = pwuid - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - network_info = [_VIF] - instance_dir = common.InstanceAttributes(instance).instance_dir - block_device_info = mock.Mock() - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - lxd_driver.firewall_driver = mock.Mock() - - lxd_driver.cleanup(ctx, instance, network_info, block_device_info) - - self.vif_driver.unplug.assert_called_once_with( - instance, network_info[0]) - lxd_driver.firewall_driver.unfilter_instance.assert_called_once_with( - instance, network_info) - execute.assert_called_once_with( - 'chown', '-R', 'user:user', instance_dir, run_as_root=True) - rmtree.assert_called_once_with(instance_dir) - mock_profile.delete.assert_called_once_with() - - def test_reboot(self): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.reboot(ctx, instance, None, None) - - self.client.containers.get.assert_called_once_with(instance.name) - - @mock.patch('nova.virt.lxd.driver.network') - @mock.patch('pwd.getpwuid', mock.Mock(return_value=mock.Mock(pw_uid=1234))) - @mock.patch('os.getuid', mock.Mock()) - @mock.patch('os.path.exists', mock.Mock(return_value=True)) - @mock.patch('six.moves.builtins.open') - @mock.patch.object(driver.utils, 'execute') - def test_get_console_output(self, execute, _open, _): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - expected_calls = [ - mock.call( - 'chown', '1234:1234', '/var/log/lxd/{}/console.log'.format( - instance.name), - run_as_root=True), - mock.call( - 'chmod', '755', '/lxd/containers/{}'.format( - instance.name), - run_as_root=True), - ] - _open.return_value.__enter__.return_value = six.BytesIO(b'output') - - lxd_driver = driver.LXDDriver(None) - - contents = lxd_driver.get_console_output(context, instance) - - self.assertEqual(b'output', contents) - self.assertEqual(expected_calls, execute.call_args_list) - - def test_get_host_ip_addr(self): - lxd_driver = driver.LXDDriver(None) - - result = lxd_driver.get_host_ip_addr() - - self.assertEqual('0.0.0.0', result) - - def test_attach_interface(self): - expected = { - 'hwaddr': '00:11:22:33:44:55', - 'parent': 'tin0123456789a', - 'nictype': 'physical', - 'type': 'nic', - } - - profile = mock.Mock() - profile.devices = { - 'eth0': { - 'name': 'eth0', - 'nictype': 'bridged', - 'parent': 'lxdbr0', - 'type': 'nic' - }, - 'root': { - 'path': '/', - 'type': 'disk' - }, - } - self.client.profiles.get.return_value = profile - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - image_meta = None - vif = { - 'id': '0123456789abcdef', - 'type': network_model.VIF_TYPE_OVS, - 'address': '00:11:22:33:44:55', - 'network': { - 'bridge': 'fakebr'}, - 'devname': 'tap0123456789a'} - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - lxd_driver.firewall_driver = mock.Mock() - - lxd_driver.attach_interface(ctx, instance, image_meta, vif) - - self.assertTrue('tap0123456789a' in profile.devices) - self.assertEqual(expected, profile.devices['tap0123456789a']) - profile.save.assert_called_once_with(wait=True) - - def test_detach_interface_legacy(self): - profile = mock.Mock() - profile.devices = { - 'eth0': { - 'nictype': 'bridged', - 'parent': 'lxdbr0', - 'hwaddr': '00:11:22:33:44:55', - 'type': 'nic' - }, - 'root': { - 'path': '/', - 'type': 'disk' - }, - } - self.client.profiles.get.return_value = profile - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - vif = { - 'id': '0123456789abcdef', - 'type': network_model.VIF_TYPE_OVS, - 'address': '00:11:22:33:44:55', - 'network': { - 'bridge': 'fakebr'}} - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.detach_interface(ctx, instance, vif) - - self.vif_driver.unplug.assert_called_once_with( - instance, vif) - self.assertEqual(['root'], sorted(profile.devices.keys())) - profile.save.assert_called_once_with(wait=True) - - def test_detach_interface(self): - profile = mock.Mock() - profile.devices = { - 'tap0123456789a': { - 'nictype': 'physical', - 'parent': 'tin0123456789a', - 'hwaddr': '00:11:22:33:44:55', - 'type': 'nic' - }, - 'root': { - 'path': '/', - 'type': 'disk' - }, - } - self.client.profiles.get.return_value = profile - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - vif = { - 'id': '0123456789abcdef', - 'type': network_model.VIF_TYPE_OVS, - 'address': '00:11:22:33:44:55', - 'network': { - 'bridge': 'fakebr'}} - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.detach_interface(ctx, instance, vif) - - self.vif_driver.unplug.assert_called_once_with( - instance, vif) - self.assertEqual(['root'], sorted(profile.devices.keys())) - profile.save.assert_called_once_with(wait=True) - - def test_detach_interface_not_found(self): - self.client.profiles.get.side_effect = lxdcore_exceptions.NotFound( - "404") - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - vif = { - 'id': '0123456789abcdef', - 'type': network_model.VIF_TYPE_OVS, - 'address': '00:11:22:33:44:55', - 'network': { - 'bridge': 'fakebr'}} - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.detach_interface(ctx, instance, vif) - - self.vif_driver.unplug.assert_called_once_with( - instance, vif) - - def test_migrate_disk_and_power_off(self): - container = mock.Mock() - self.client.containers.get.return_value = container - profile = mock.Mock() - self.client.profiles.get.return_value = profile - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - dest = '0.0.0.0' - flavor = mock.Mock() - network_info = [] - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.migrate_disk_and_power_off( - ctx, instance, dest, flavor, network_info) - - profile.save.assert_called_once_with() - container.stop.assert_called_once_with(wait=True) - - def test_migrate_disk_and_power_off_different_host(self): - """Migrating to a different host only shuts down the container.""" - container = mock.Mock() - self.client.containers.get.return_value = container - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - dest = '0.0.0.1' - flavor = mock.Mock() - network_info = [] - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.migrate_disk_and_power_off( - ctx, instance, dest, flavor, network_info) - - self.assertEqual(0, self.client.profiles.get.call_count) - container.stop.assert_called_once_with(wait=True) - - @mock.patch('nova.virt.lxd.driver.network') - @mock.patch('os.major') - @mock.patch('os.minor') - @mock.patch('os.stat') - @mock.patch('os.path.realpath') - def test_attach_volume(self, realpath, stat, minor, major, _): - profile = mock.Mock() - self.client.profiles.get.return_value = profile - realpath.return_value = '/dev/sdc' - stat.return_value.st_rdev = 2080 - minor.return_value = 32 - major.return_value = 8 - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - connection_info = fake_connection_info( - {'id': 1, 'name': 'volume-00000001'}, - '10.0.2.15:3260', 'iqn.2010-10.org.openstack:volume-00000001', - auth=True) - mountpoint = '/dev/sdd' - - driver.brick_get_connector = mock.MagicMock() - driver.brick_get_connector_properties = mock.MagicMock() - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - # driver.brick_get_connector = mock.MagicMock() - # lxd_driver.storage_driver.connect_volume = mock.MagicMock() - lxd_driver.attach_volume( - ctx, connection_info, instance, mountpoint, None, None, None) - - lxd_driver.client.profiles.get.assert_called_once_with(instance.name) - # driver.brick_get_connector.connect_volume.assert_called_once_with( - # connection_info['data']) - profile.save.assert_called_once_with() - - def test_detach_volume(self): - profile = mock.Mock() - profile.devices = { - 'eth0': { - 'name': 'eth0', - 'nictype': 'bridged', - 'parent': 'lxdbr0', - 'type': 'nic' - }, - 'root': { - 'path': '/', - 'type': 'disk' - }, - 1: { - 'path': '/dev/sdc', - 'type': 'unix-block' - }, - } - - expected = { - 'eth0': { - 'name': 'eth0', - 'nictype': 'bridged', - 'parent': 'lxdbr0', - 'type': 'nic' - }, - 'root': { - 'path': '/', - 'type': 'disk' - }, - } - - self.client.profiles.get.return_value = profile - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - connection_info = fake_connection_info( - {'id': 1, 'name': 'volume-00000001'}, - '10.0.2.15:3260', 'iqn.2010-10.org.openstack:volume-00000001', - auth=True) - mountpoint = mock.Mock() - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - driver.brick_get_connector = mock.MagicMock() - driver.brick_get_connector_properties = mock.MagicMock() - lxd_driver.detach_volume(ctx, connection_info, instance, - mountpoint, None) - - lxd_driver.client.profiles.get.assert_called_once_with(instance.name) - - self.assertEqual(expected, profile.devices) - profile.save.assert_called_once_with() - - def test_pause(self): - container = mock.Mock() - self.client.containers.get.return_value = container - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.pause(instance) - - self.client.containers.get.assert_called_once_with(instance.name) - container.freeze.assert_called_once_with(wait=True) - - def test_unpause(self): - container = mock.Mock() - self.client.containers.get.return_value = container - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.unpause(instance) - - self.client.containers.get.assert_called_once_with(instance.name) - container.unfreeze.assert_called_once_with(wait=True) - - def test_suspend(self): - container = mock.Mock() - self.client.containers.get.return_value = container - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.suspend(ctx, instance) - - self.client.containers.get.assert_called_once_with(instance.name) - container.freeze.assert_called_once_with(wait=True) - - def test_resume(self): - container = mock.Mock() - self.client.containers.get.return_value = container - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.resume(ctx, instance, None, None) - - self.client.containers.get.assert_called_once_with(instance.name) - container.unfreeze.assert_called_once_with(wait=True) - - def test_resume_state_on_host_boot(self): - container = mock.Mock() - state = mock.Mock() - state.memory = dict({'usage': 0, 'usage_peak': 0}) - state.status_code = 102 - container.state.return_value = state - self.client.containers.get.return_value = container - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.resume_state_on_host_boot(ctx, instance, None, None) - container.start.assert_called_once_with(wait=True) - - def test_rescue(self): - profile = mock.Mock() - profile.devices = { - 'root': { - 'type': 'disk', - 'path': '/', - 'size': '1GB' - } - } - container = mock.Mock() - self.client.profiles.get.return_value = profile - self.client.containers.get.return_value = container - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - profile.name = instance.name - network_info = [_VIF] - image_meta = mock.Mock() - rescue_password = mock.Mock() - rescue = '%s-rescue' % instance.name - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.rescue( - ctx, instance, network_info, image_meta, rescue_password) - - lxd_driver.client.containers.get.assert_called_once_with(instance.name) - container.rename.assert_called_once_with(rescue, wait=True) - lxd_driver.client.profiles.get.assert_called_once_with(instance.name) - lxd_driver.client.containers.create.assert_called_once_with( - {'name': instance.name, 'profiles': [profile.name], - 'source': {'type': 'image', 'alias': None}, - }, wait=True) - - self.assertTrue('rescue' in profile.devices) - - def test_unrescue(self): - container = mock.Mock() - container.status = 'Running' - self.client.containers.get.return_value = container - profile = mock.Mock() - profile.devices = { - 'root': { - 'type': 'disk', - 'path': '/', - 'size': '1GB' - }, - 'rescue': { - 'source': '/path', - 'path': '/mnt', - 'type': 'disk' - } - } - self.client.profiles.get.return_value = profile - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - network_info = [_VIF] - rescue = '%s-rescue' % instance.name - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.unrescue(instance, network_info) - - container.stop.assert_called_once_with(wait=True) - container.delete.assert_called_once_with(wait=True) - lxd_driver.client.profiles.get.assert_called_once_with(instance.name) - profile.save.assert_called_once_with() - lxd_driver.client.containers.get.assert_called_with(rescue) - container.rename.assert_called_once_with(instance.name, wait=True) - container.start.assert_called_once_with(wait=True) - self.assertTrue('rescue' not in profile.devices) - - def test_power_off(self): - container = mock.Mock() - container.status = 'Running' - self.client.containers.get.return_value = container - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - lxd_driver.power_off(instance) - - self.client.containers.get.assert_called_once_with(instance.name) - container.stop.assert_called_once_with(wait=True) - - def test_power_on(self): - container = mock.Mock() - container.status = 'Stopped' - self.client.containers.get.return_value = container - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - lxd_driver.power_on(ctx, instance, None) - - self.client.containers.get.assert_called_once_with(instance.name) - container.start.assert_called_once_with(wait=True) - - @mock.patch('socket.gethostname', mock.Mock(return_value='fake_hostname')) - @mock.patch('os.statvfs', return_value=mock.Mock( - f_blocks=131072000, f_bsize=8192, f_bavail=65536000)) - @mock.patch('nova.virt.lxd.driver.open') - @mock.patch.object(driver.utils, 'execute') - def test_get_available_resource(self, execute, open, statvfs): - expected = { - 'cpu_info': { - "features": "fake flag goes here", - "model": "Fake CPU", - "topology": {"sockets": "10", "threads": "4", "cores": "5"}, - "arch": "x86_64", "vendor": "FakeVendor" - }, - 'hypervisor_hostname': 'fake_hostname', - 'hypervisor_type': 'lxd', - 'hypervisor_version': '011', - 'local_gb': 1000, - 'local_gb_used': 500, - 'memory_mb': 10000, - 'memory_mb_used': 8000, - 'numa_topology': None, - 'supported_instances': [ - ('i686', 'lxd', 'exe'), - ('x86_64', 'lxd', 'exe'), - ('i686', 'lxc', 'exe'), - ('x86_64', 'lxc', 'exe')], - 'vcpus': 200, - 'vcpus_used': 0} - - execute.return_value = ( - 'Model name: Fake CPU\n' - 'Vendor ID: FakeVendor\n' - 'Socket(s): 10\n' - 'Core(s) per socket: 5\n' - 'Thread(s) per core: 4\n\n', - None) - meminfo = mock.MagicMock() - meminfo.__enter__.return_value = six.moves.cStringIO( - 'MemTotal: 10240000 kB\n' - 'MemFree: 2000000 kB\n' - 'Buffers: 24000 kB\n' - 'Cached: 24000 kB\n') - - open.side_effect = [ - six.moves.cStringIO('flags: fake flag goes here\n' - 'processor: 2\n' - '\n'), - meminfo, - ] - lxd_config = { - 'environment': { - 'storage': 'dir', - }, - 'config': {} - } - lxd_driver = driver.LXDDriver(None) - lxd_driver.client = mock.MagicMock() - lxd_driver.client.host_info = lxd_config - value = lxd_driver.get_available_resource(None) - # This is funky, but json strings make for fragile tests. - value['cpu_info'] = jsonutils.loads(value['cpu_info']) - - self.assertEqual(expected, value) - - @mock.patch.object(driver.utils, 'execute') - def test__get_zpool_info(self, execute): - # first test with a zpool; should make 3 calls to execute - execute.side_effect = [ - ('1\n', None), - ('2\n', None), - ('3\n', None) - ] - expected = { - 'total': 1, - 'used': 2, - 'available': 3, - } - self.assertEqual(expected, driver._get_zpool_info('lxd')) - - # then test with a zfs dataset; should just be 2 calls - execute.reset_mock() - execute.side_effect = [ - ('10\n', None), - ('20\n', None), - ] - expected = { - 'total': 30, - 'used': 10, - 'available': 20, - } - self.assertEqual(expected, driver._get_zpool_info('lxd/dataset')) - - @mock.patch('socket.gethostname', mock.Mock(return_value='fake_hostname')) - @mock.patch('nova.virt.lxd.driver.open') - @mock.patch.object(driver.utils, 'execute') - def test_get_available_resource_zfs(self, execute, open): - expected = { - 'cpu_info': { - "features": "fake flag goes here", - "model": "Fake CPU", - "topology": {"sockets": "10", "threads": "4", "cores": "5"}, - "arch": "x86_64", "vendor": "FakeVendor" - }, - 'hypervisor_hostname': 'fake_hostname', - 'hypervisor_type': 'lxd', - 'hypervisor_version': '011', - 'local_gb': 2222, - 'local_gb_used': 200, - 'memory_mb': 10000, - 'memory_mb_used': 8000, - 'numa_topology': None, - 'supported_instances': [ - ('i686', 'lxd', 'exe'), - ('x86_64', 'lxd', 'exe'), - ('i686', 'lxc', 'exe'), - ('x86_64', 'lxc', 'exe')], - 'vcpus': 200, - 'vcpus_used': 0} - - execute.side_effect = [ - ('Model name: Fake CPU\n' - 'Vendor ID: FakeVendor\n' - 'Socket(s): 10\n' - 'Core(s) per socket: 5\n' - 'Thread(s) per core: 4\n\n', - None), - ('2385940232273\n', None), # 2.17T - ('215177861529\n', None), # 200.4G - ('1979120929996\n', None) # 1.8T - ] - - meminfo = mock.MagicMock() - meminfo.__enter__.return_value = six.moves.cStringIO( - 'MemTotal: 10240000 kB\n' - 'MemFree: 2000000 kB\n' - 'Buffers: 24000 kB\n' - 'Cached: 24000 kB\n') - - open.side_effect = [ - six.moves.cStringIO('flags: fake flag goes here\n' - 'processor: 2\n' - '\n'), - meminfo, - ] - lxd_config = { - 'environment': { - 'storage': 'zfs', - }, - 'config': { - 'storage.zfs_pool_name': 'lxd', - } - } - lxd_driver = driver.LXDDriver(None) - lxd_driver.client = mock.MagicMock() - lxd_driver.client.host_info = lxd_config - value = lxd_driver.get_available_resource(None) - # This is funky, but json strings make for fragile tests. - value['cpu_info'] = jsonutils.loads(value['cpu_info']) - - self.assertEqual(expected, value) - - def test_refresh_instance_security_rules(self): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - firewall = mock.Mock() - - lxd_driver = driver.LXDDriver(None) - lxd_driver.firewall_driver = firewall - lxd_driver.refresh_instance_security_rules(instance) - - firewall.refresh_instance_security_rules.assert_called_once_with( - instance) - - def test_ensure_filtering_rules_for_instance(self): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - firewall = mock.Mock() - network_info = object() - - lxd_driver = driver.LXDDriver(None) - lxd_driver.firewall_driver = firewall - lxd_driver.ensure_filtering_rules_for_instance(instance, network_info) - - firewall.ensure_filtering_rules_for_instance.assert_called_once_with( - instance, network_info) - - def test_filter_defer_apply_on(self): - firewall = mock.Mock() - - lxd_driver = driver.LXDDriver(None) - lxd_driver.firewall_driver = firewall - lxd_driver.filter_defer_apply_on() - - firewall.filter_defer_apply_on.assert_called_once_with() - - def test_filter_defer_apply_off(self): - firewall = mock.Mock() - - lxd_driver = driver.LXDDriver(None) - lxd_driver.firewall_driver = firewall - lxd_driver.filter_defer_apply_off() - - firewall.filter_defer_apply_off.assert_called_once_with() - - def test_unfilter_instance(self): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - firewall = mock.Mock() - network_info = object() - - lxd_driver = driver.LXDDriver(None) - lxd_driver.firewall_driver = firewall - lxd_driver.unfilter_instance(instance, network_info) - - firewall.unfilter_instance.assert_called_once_with( - instance, network_info) - - @mock.patch.object(driver.utils, 'execute') - def test_get_host_uptime(self, execute): - expected = '00:00:00 up 0 days, 0:00 , 0 users, load average: 0' - execute.return_value = (expected, 'stderr') - - lxd_driver = driver.LXDDriver(None) - result = lxd_driver.get_host_uptime() - - self.assertEqual(expected, result) - - @mock.patch('nova.virt.lxd.driver.psutil.cpu_times') - @mock.patch('nova.virt.lxd.driver.open') - @mock.patch.object(driver.utils, 'execute') - def test_get_host_cpu_stats(self, execute, open, cpu_times): - cpu_times.return_value = [ - '1', 'b', '2', '3', '4' - ] - execute.return_value = ( - 'Model name: Fake CPU\n' - 'Vendor ID: FakeVendor\n' - 'Socket(s): 10\n' - 'Core(s) per socket: 5\n' - 'Thread(s) per core: 4\n\n', - None) - open.return_value = six.moves.cStringIO( - 'flags: fake flag goes here\n' - 'processor: 2\n\n') - - expected = { - 'user': 1, 'iowait': 4, 'frequency': 0, 'kernel': 2, 'idle': 3} - - lxd_driver = driver.LXDDriver(None) - result = lxd_driver.get_host_cpu_stats() - - self.assertEqual(expected, result) - - def test_get_volume_connector(self): - expected = { - 'host': 'fakehost', - 'initiator': 'fake', - 'ip': self.CONF.my_block_storage_ip - } - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - - lxd_driver = driver.LXDDriver(None) - result = lxd_driver.get_volume_connector(instance) - - self.assertEqual(expected, result) - - @mock.patch('nova.virt.lxd.driver.socket.gethostname') - def test_get_available_nodes(self, gethostname): - gethostname.return_value = 'nova-lxd' - - expected = ['nova-lxd'] - - lxd_driver = driver.LXDDriver(None) - result = lxd_driver.get_available_nodes() - - self.assertEqual(expected, result) - - @mock.patch('nova.virt.lxd.driver.IMAGE_API') - @mock.patch('nova.virt.lxd.driver.lockutils.lock') - def test_snapshot(self, lock, IMAGE_API): - update_task_state_expected = [ - mock.call(task_state='image_pending_upload'), - mock.call( - expected_state='image_pending_upload', - task_state='image_uploading'), - ] - - container = mock.Mock() - self.client.containers.get.return_value = container - image = mock.Mock() - container.publish.return_value = image - data = mock.Mock() - image.export.return_value = data - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - image_id = mock.Mock() - update_task_state = mock.Mock() - snapshot = {'name': mock.Mock()} - IMAGE_API.get.return_value = snapshot - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.snapshot(ctx, instance, image_id, update_task_state) - - self.assertEqual( - update_task_state_expected, update_task_state.call_args_list) - IMAGE_API.get.assert_called_once_with(ctx, image_id) - IMAGE_API.update.assert_called_once_with( - ctx, image_id, { - 'name': snapshot['name'], - 'disk_format': 'raw', - 'container_format': 'bare'}, - data) - - def test_finish_revert_migration(self): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - network_info = [] - - container = mock.Mock() - self.client.containers.get.return_value = container - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.finish_revert_migration(ctx, instance, network_info) - - container.start.assert_called_once_with(wait=True) - - def test_check_can_live_migrate_destination(self): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - src_compute_info = mock.Mock() - dst_compute_info = mock.Mock() - - def container_get(*args, **kwargs): - raise lxdcore_exceptions.LXDAPIException(MockResponse(404)) - self.client.containers.get.side_effect = container_get - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - retval = lxd_driver.check_can_live_migrate_destination( - ctx, instance, src_compute_info, dst_compute_info) - - self.assertIsInstance(retval, driver.LXDLiveMigrateData) - - def test_confirm_migration(self): - migration = mock.Mock() - instance = fake_instance.fake_instance_obj( - context.get_admin_context, name='test', memory_mb=0) - network_info = [] - profile = mock.Mock() - container = mock.Mock() - self.client.profiles.get.return_value = profile - self.client.containers.get.return_value = container - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.confirm_migration(migration, instance, network_info) - - profile.delete.assert_called_once_with() - container.delete.assert_called_once_with(wait=True) - - def test_post_live_migration(self): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - container = mock.Mock() - self.client.containers.get.return_value = container - - lxd_driver = driver.LXDDriver(None) - lxd_driver.init_host(None) - - lxd_driver.post_live_migration(context, instance, None) - - container.delete.assert_called_once_with(wait=True) - - def test_post_live_migration_at_source(self): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - network_info = [] - profile = mock.Mock() - self.client.profiles.get.return_value = profile - - lxd_driver = driver.LXDDriver(None) - lxd_driver.cleanup = mock.Mock() - lxd_driver.init_host(None) - - lxd_driver.post_live_migration_at_source( - ctx, instance, network_info) - - profile.delete.assert_called_once_with() - lxd_driver.cleanup.assert_called_once_with(ctx, instance, network_info) diff --git a/nova/tests/unit/virt/lxd/test_flavor.py b/nova/tests/unit/virt/lxd/test_flavor.py deleted file mode 100644 index 59a683b5..00000000 --- a/nova/tests/unit/virt/lxd/test_flavor.py +++ /dev/null @@ -1,493 +0,0 @@ -# Copyright 2016 Canonical Ltd -# 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 context -from nova import exception -from nova import test -from nova.network import model as network_model -from nova.tests.unit import fake_instance - -from nova.virt.lxd import flavor - - -class ToProfileTest(test.NoDBTestCase): - """Tests for nova.virt.lxd.flavor.to_profile.""" - - def setUp(self): - super(ToProfileTest, self).setUp() - self.client = mock.Mock() - self.client.host_info = { - 'api_extensions': [], - 'environment': { - 'storage': 'zfs' - } - } - - self.patchers = [] - CONF_patcher = mock.patch('nova.virt.lxd.driver.nova.conf.CONF') - self.patchers.append(CONF_patcher) - self.CONF = CONF_patcher.start() - self.CONF.instances_path = '/i' - self.CONF.lxd.root_dir = '' - - CONF_patcher = mock.patch('nova.virt.lxd.flavor.CONF') - self.patchers.append(CONF_patcher) - self.CONF2 = CONF_patcher.start() - self.CONF2.lxd.pool = None - self.CONF2.lxd.root_dir = '' - - def tearDown(self): - super(ToProfileTest, self).tearDown() - for patcher in self.patchers: - patcher.stop() - - def test_to_profile(self): - """A profile configuration is requested of the LXD client.""" - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - network_info = [] - block_info = [] - - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)), - } - expected_devices = { - 'root': { - 'path': '/', - 'size': '0GB', - 'type': 'disk' - }, - } - - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) - - def test_to_profile_lvm(self): - """A profile configuration is requested of the LXD client.""" - self.client.host_info['environment']['storage'] = 'lvm' - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - network_info = [] - block_info = [] - - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)), - } - expected_devices = { - 'root': { - 'path': '/', - 'type': 'disk' - }, - } - - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) - - def test_storage_pools(self): - self.client.host_info['api_extensions'].append('storage') - self.CONF2.lxd.pool = 'test_pool' - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - network_info = [] - block_info = [] - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)) - } - expected_devices = { - 'root': { - 'path': '/', - 'type': 'disk', - 'pool': 'test_pool', - }, - } - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) - - def test_to_profile_security(self): - self.client.host_info['api_extensions'].append('id_map') - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - instance.flavor.extra_specs = { - 'lxd:nested_allowed': True, - 'lxd:privileged_allowed': True, - } - network_info = [] - block_info = [] - - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)), - 'security.nesting': 'True', - 'security.privileged': 'True', - } - expected_devices = { - 'root': { - 'path': '/', - 'size': '0GB', - 'type': 'disk' - }, - } - - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) - - def test_to_profile_idmap(self): - self.client.host_info['api_extensions'].append('id_map') - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - instance.flavor.extra_specs = { - 'lxd:isolated': True, - } - network_info = [] - block_info = [] - - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'security.idmap.isolated': 'True', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)), - } - expected_devices = { - 'root': { - 'path': '/', - 'size': '0GB', - 'type': 'disk' - }, - } - - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) - - def test_to_profile_idmap_unsupported(self): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - instance.flavor.extra_specs = { - 'lxd:isolated': True, - } - network_info = [] - block_info = [] - - self.assertRaises( - exception.NovaException, - flavor.to_profile, self.client, instance, network_info, block_info) - - def test_to_profile_quota_extra_specs_bytes(self): - """A profile configuration is requested of the LXD client.""" - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - instance.flavor.extra_specs = { - 'quota:disk_read_bytes_sec': '3000000', - 'quota:disk_write_bytes_sec': '4000000', - } - network_info = [] - block_info = [] - - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)), - } - expected_devices = { - 'root': { - 'limits.read': '2MB', - 'limits.write': '3MB', - 'path': '/', - 'size': '0GB', - 'type': 'disk' - }, - } - - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) - - def test_to_profile_quota_extra_specs_iops(self): - """A profile configuration is requested of the LXD client.""" - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - instance.flavor.extra_specs = { - 'quota:disk_read_iops_sec': '300', - 'quota:disk_write_iops_sec': '400', - } - network_info = [] - block_info = [] - - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)), - } - expected_devices = { - 'root': { - 'limits.read': '300iops', - 'limits.write': '400iops', - 'path': '/', - 'size': '0GB', - 'type': 'disk' - }, - } - - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) - - def test_to_profile_quota_extra_specs_max_bytes(self): - """A profile configuration is requested of the LXD client.""" - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - instance.flavor.extra_specs = { - 'quota:disk_total_bytes_sec': '6000000', - } - network_info = [] - block_info = [] - - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)), - } - expected_devices = { - 'root': { - 'limits.max': '5MB', - 'path': '/', - 'size': '0GB', - 'type': 'disk' - }, - } - - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) - - def test_to_profile_quota_extra_specs_max_iops(self): - """A profile configuration is requested of the LXD client.""" - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - instance.flavor.extra_specs = { - 'quota:disk_total_iops_sec': '500', - } - network_info = [] - block_info = [] - - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)), - } - expected_devices = { - 'root': { - 'limits.max': '500iops', - 'path': '/', - 'size': '0GB', - 'type': 'disk' - }, - } - - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) - - @mock.patch('nova.virt.lxd.vif._is_no_op_firewall', return_value=False) - def test_to_profile_network_config_average(self, _is_no_op_firewall): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - instance.flavor.extra_specs = { - 'quota:vif_inbound_average': '1000000', - 'quota:vif_outbound_average': '2000000', - } - network_info = [{ - 'id': '0123456789abcdef', - 'type': network_model.VIF_TYPE_OVS, - 'address': '00:11:22:33:44:55', - 'network': { - 'bridge': 'fakebr'}, - 'devname': 'tap0123456789a'}] - block_info = [] - - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)), - } - expected_devices = { - 'tap0123456789a': { - 'hwaddr': '00:11:22:33:44:55', - 'nictype': 'physical', - 'parent': 'tin0123456789a', - 'type': 'nic', - 'limits.egress': '16000Mbit', - 'limits.ingress': '8000Mbit', - }, - 'root': { - 'path': '/', - 'size': '0GB', - 'type': 'disk' - }, - } - - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) - - @mock.patch('nova.virt.lxd.vif._is_no_op_firewall', return_value=False) - def test_to_profile_network_config_peak(self, _is_no_op_firewall): - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - instance.flavor.extra_specs = { - 'quota:vif_inbound_peak': '3000000', - 'quota:vif_outbound_peak': '4000000', - } - network_info = [{ - 'id': '0123456789abcdef', - 'type': network_model.VIF_TYPE_OVS, - 'address': '00:11:22:33:44:55', - 'network': { - 'bridge': 'fakebr'}, - 'devname': 'tap0123456789a'}] - block_info = [] - - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)), - } - expected_devices = { - 'tap0123456789a': { - 'hwaddr': '00:11:22:33:44:55', - 'nictype': 'physical', - 'parent': 'tin0123456789a', - 'type': 'nic', - 'limits.egress': '32000Mbit', - 'limits.ingress': '24000Mbit', - }, - 'root': { - 'path': '/', - 'size': '0GB', - 'type': 'disk' - }, - } - - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) - - @mock.patch('nova.virt.lxd.flavor.driver.block_device_info_get_ephemerals') - def test_to_profile_ephemeral_storage(self, get_ephemerals): - """A profile configuration is requested of the LXD client.""" - get_ephemerals.return_value = [ - {'virtual_name': 'ephemeral1'}, - ] - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - network_info = [] - block_info = [] - - expected_config = { - 'environment.product_name': 'OpenStack Nova', - 'limits.cpu': '1', - 'limits.memory': '0MB', - 'raw.lxc': ( - 'lxc.console.logfile=/var/log/lxd/{}/console.log\n'.format( - instance.name)), - } - expected_devices = { - 'root': { - 'path': '/', - 'size': '0GB', - 'type': 'disk' - }, - 'ephemeral1': { - 'type': 'disk', - 'path': '/mnt', - 'source': '/i/{}/storage/ephemeral1'.format(instance.name), - }, - } - - flavor.to_profile(self.client, instance, network_info, block_info) - - self.client.profiles.create.assert_called_once_with( - instance.name, expected_config, expected_devices) diff --git a/nova/tests/unit/virt/lxd/test_migrate.py b/nova/tests/unit/virt/lxd/test_migrate.py deleted file mode 100644 index 5d334be2..00000000 --- a/nova/tests/unit/virt/lxd/test_migrate.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2015 Canonical Ltd -# 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 nova.conf -from nova import exception -from nova import test -from pylxd.deprecated import exceptions as lxd_exceptions - -from nova.virt.lxd import driver - -CONF = nova.conf.CONF - - -class LXDTestLiveMigrate(test.NoDBTestCase): - - def setUp(self): - super(LXDTestLiveMigrate, self).setUp() - - self.driver = driver.LXDDriver(None) - self.context = 'fake_context' - self.driver.session = mock.MagicMock() - self.driver.config = mock.MagicMock() - self.driver.operations = mock.MagicMock() - - @mock.patch.object(driver.LXDDriver, '_migrate') - def test_live_migration(self, mock_migrate): - """Verify that the correct live migration calls - are made. - """ - self.flags(my_ip='fakeip') - mock_post_method = mock.MagicMock() - self.driver.live_migration( - mock.sentinel.context, mock.sentinel.instance, - mock.sentinel.dest, mock_post_method, - mock.sentinel.recover_method, mock.sentinel.block_migration, - mock.sentinel.migrate_data) - mock_migrate.assert_called_once_with(mock.sentinel.dest, - mock.sentinel.instance) - mock_post_method.assert_called_once_with( - mock.sentinel.context, mock.sentinel.instance, mock.sentinel.dest, - mock.sentinel.block_migration) - - @mock.patch.object(driver.LXDDriver, '_migrate') - def test_live_migration_failed(self, mock_migrate): - """Verify that an exception is raised when live-migration - fails. - """ - self.flags(my_ip='fakeip') - mock_migrate.side_effect = \ - lxd_exceptions.APIError(500, 'Fake') - self.assertRaises( - lxd_exceptions.APIError, - self.driver.live_migration, mock.sentinel.context, - mock.sentinel.instance, mock.sentinel.dest, - mock.sentinel.recover_method, mock.sentinel.block_migration, - mock.sentinel.migrate_data) - - def test_live_migration_not_allowed(self): - """Verify an exception is raised when live migration is not allowed.""" - self.flags(allow_live_migration=False, - group='lxd') - self.assertRaises(exception.MigrationPreCheckError, - self.driver.check_can_live_migrate_source, - mock.sentinel.context, mock.sentinel.instance, - mock.sentinel.dest_check_data, - mock.sentinel.block_device_info) - - def test_live_migration_allowed(self): - """Verify live-migration is allowed when the allow_lvie_migrate - flag is True. - """ - self.flags(allow_live_migration=True, - group='lxd') - self.assertEqual(mock.sentinel.dest_check_data, - self.driver.check_can_live_migrate_source( - mock.sentinel.context, mock.sentinel.instance, - mock.sentinel.dest_check_data, - mock.sentinel.block_device_info)) diff --git a/nova/tests/unit/virt/lxd/test_session.py b/nova/tests/unit/virt/lxd/test_session.py deleted file mode 100644 index ac389140..00000000 --- a/nova/tests/unit/virt/lxd/test_session.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright 2015 Canonical Ltd -# 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. - -""" -Unit tests for ContinerMixin class - -The following tests the ContainerMixin class -for nova-lxd. -""" - -import ddt -import mock - -from nova import exception -from nova import test -from pylxd.deprecated import exceptions as lxd_exceptions - -from nova.virt.lxd import session -import fake_api -import stubs - - -@ddt.ddt -class SessionContainerTest(test.NoDBTestCase): - - def setUp(self): - super(SessionContainerTest, self).setUp() - - """This is so we can mock out pylxd API calls.""" - self.ml = stubs.lxd_mock() - lxd_patcher = mock.patch('pylxd.deprecated.api.API', - mock.Mock(return_value=self.ml)) - lxd_patcher.start() - self.addCleanup(lxd_patcher.stop) - - self.session = session.LXDAPISession() - - @stubs.annotated_data( - ('1', (200, fake_api.fake_operation_info_ok())) - ) - def test_container_init(self, tag, side_effect): - """ - conatainer_init creates a container based on given config - for a container. Check to see if we are returning the right - pylxd calls for the LXD API. - """ - config = mock.Mock() - instance = stubs._fake_instance() - self.ml.container_init.return_value = side_effect - self.ml.operation_info.return_value = \ - (200, fake_api.fake_container_state(200)) - self.assertIsNone(self.session.container_init(config, instance)) - calls = [mock.call.container_init(config), - mock.call.wait_container_operation( - '/1.0/operation/1234', 200, -1), - mock.call.operation_info('/1.0/operation/1234')] - self.assertEqual(calls, self.ml.method_calls) - - @stubs.annotated_data( - ('api_fail', lxd_exceptions.APIError(500, 'Fake'), - exception.NovaException), - ) - def test_container_init_fail(self, tag, side_effect, expected): - """ - continer_init create as container on a given LXD host. Make - sure that we reaise an exception.NovaException if there is - an APIError from the LXD API. - """ - config = mock.Mock() - instance = stubs._fake_instance() - self.ml.container_init.side_effect = side_effect - self.assertRaises(expected, - self.session.container_init, config, - instance) - - -@ddt.ddt -class SessionEventTest(test.NoDBTestCase): - - def setUp(self): - super(SessionEventTest, self).setUp() - - self.ml = stubs.lxd_mock() - lxd_patcher = mock.patch('pylxd.deprecated.api.API', - mock.Mock(return_value=self.ml)) - lxd_patcher.start() - self.addCleanup(lxd_patcher.stop) - - self.session = session.LXDAPISession() - - def test_container_wait(self): - instance = stubs._fake_instance() - operation_id = mock.Mock() - self.ml.wait_container_operation.return_value = True - self.assertIsNone(self.session.operation_wait(operation_id, instance)) - self.ml.wait_container_operation.assert_called_with(operation_id, - 200, -1) diff --git a/nova/tests/unit/virt/lxd/test_storage.py b/nova/tests/unit/virt/lxd/test_storage.py deleted file mode 100644 index 44e581a4..00000000 --- a/nova/tests/unit/virt/lxd/test_storage.py +++ /dev/null @@ -1,259 +0,0 @@ -# Copyright 2016 Canonical Ltd -# 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 context -from nova import test -from nova.tests.unit import fake_instance - -from nova.virt.lxd import storage - - -class TestAttachEphemeral(test.NoDBTestCase): - """Tests for nova.virt.lxd.storage.attach_ephemeral.""" - - def setUp(self): - super(TestAttachEphemeral, self).setUp() - - self.patchers = [] - - CONF_patcher = mock.patch('nova.virt.lxd.common.conf.CONF') - self.patchers.append(CONF_patcher) - self.CONF = CONF_patcher.start() - self.CONF.instances_path = '/i' - self.CONF.lxd.root_dir = '/var/lib/lxd' - - def tearDown(self): - super(TestAttachEphemeral, self).tearDown() - for patcher in self.patchers: - patcher.stop() - - @mock.patch.object(storage.utils, 'execute') - @mock.patch( - 'nova.virt.lxd.storage.driver.block_device_info_get_ephemerals') - def test_add_ephemerals_with_zfs( - self, block_device_info_get_ephemerals, execute): - ctx = context.get_admin_context() - block_device_info_get_ephemerals.return_value = [ - {'virtual_name': 'ephemerals0'}] - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - block_device_info = mock.Mock() - lxd_config = {'environment': {'storage': 'zfs'}, - 'config': {'storage.zfs_pool_name': 'zfs'}} - - container = mock.Mock() - container.config = { - 'volatile.last_state.idmap': '[{"Isuid":true,"Isgid":false,' - '"Hostid":165536,"Nsid":0,' - '"Maprange":65536}]' - } - client = mock.Mock() - client.containers.get.return_value = container - - storage.attach_ephemeral( - client, block_device_info, lxd_config, instance) - - block_device_info_get_ephemerals.assert_called_once_with( - block_device_info) - - expected_calls = [ - mock.call( - 'zfs', 'create', '-o', - 'mountpoint=/i/instance-00000001/storage/ephemerals0', '-o', - 'quota=0G', 'zfs/instance-00000001-ephemeral', - run_as_root=True), - mock.call( - 'chown', '165536', '/i/instance-00000001/storage/ephemerals0', - run_as_root=True) - ] - - self.assertEqual(expected_calls, execute.call_args_list) - - @mock.patch.object(storage.utils, 'execute') - @mock.patch( - 'nova.virt.lxd.storage.driver.block_device_info_get_ephemerals') - def test_add_ephemerals_with_btrfs( - self, block_device_info_get_ephemerals, execute): - ctx = context.get_admin_context() - block_device_info_get_ephemerals.return_value = [ - {'virtual_name': 'ephemerals0'}] - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - instance.ephemeral_gb = 1 - block_device_info = mock.Mock() - lxd_config = {'environment': {'storage': 'btrfs'}} - profile = mock.Mock() - profile.devices = { - 'root': { - 'path': '/', - 'type': 'disk', - 'size': '1G' - }, - 'ephemerals0': { - 'optional': 'True', - 'path': '/mnt', - 'source': '/path/fake_path', - 'type': 'disk' - - } - } - client = mock.Mock() - client.profiles.get.return_value = profile - - container = mock.Mock() - container.config = { - 'volatile.last_state.idmap': '[{"Isuid":true,"Isgid":false,' - '"Hostid":165536,"Nsid":0,' - '"Maprange":65536}]' - } - client.containers.get.return_value = container - - storage.attach_ephemeral( - client, block_device_info, lxd_config, instance) - - block_device_info_get_ephemerals.assert_called_once_with( - block_device_info) - profile.save.assert_called_once_with() - - expected_calls = [ - mock.call( - 'btrfs', 'subvolume', 'create', - '/var/lib/lxd/containers/instance-00000001/ephemerals0', - run_as_root=True), - mock.call( - 'btrfs', 'qgroup', 'limit', '1g', - '/var/lib/lxd/containers/instance-00000001/ephemerals0', - run_as_root=True), - mock.call( - 'chown', '165536', - '/var/lib/lxd/containers/instance-00000001/ephemerals0', - run_as_root=True) - ] - self.assertEqual(expected_calls, execute.call_args_list) - self.assertEqual( - profile.devices['ephemerals0']['source'], - '/var/lib/lxd/containers/instance-00000001/ephemerals0') - - @mock.patch.object(storage.utils, 'execute') - @mock.patch( - 'nova.virt.lxd.storage.driver.block_device_info_get_ephemerals') - def test_ephemeral_with_lvm( - self, block_device_info_get_ephemerals, execute): - ctx = context.get_admin_context() - block_device_info_get_ephemerals.return_value = [ - {'virtual_name': 'ephemerals0'}] - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - block_device_info = mock.Mock() - lxd_config = {'environment': {'storage': 'lvm'}, - 'config': {'storage.lvm_vg_name': 'lxd'}} - - storage.fileutils = mock.Mock() - - container = mock.Mock() - container.config = { - 'volatile.last_state.idmap': '[{"Isuid":true,"Isgid":false,' - '"Hostid":165536,"Nsid":0,' - '"Maprange":65536}]' - } - client = mock.Mock() - client.containers.get.return_value = container - - storage.attach_ephemeral( - client, block_device_info, lxd_config, instance) - - block_device_info_get_ephemerals.assert_called_once_with( - block_device_info) - - expected_calls = [ - mock.call( - 'lvcreate', '-L', '0G', '-n', 'instance-00000001-ephemerals0', - 'lxd', attempts=3, run_as_root=True), - mock.call( - 'mkfs', '-t', 'ext4', '/dev/lxd/instance-00000001-ephemerals0', - run_as_root=True), - mock.call( - 'mount', '-t', 'ext4', - '/dev/lxd/instance-00000001-ephemerals0', - '/i/instance-00000001/storage/ephemerals0', - run_as_root=True), - mock.call( - 'chown', '165536', '/i/instance-00000001/storage/ephemerals0', - run_as_root=True)] - self.assertEqual(expected_calls, execute.call_args_list) - - -class TestDetachEphemeral(test.NoDBTestCase): - """Tests for nova.virt.lxd.storage.detach_ephemeral.""" - - @mock.patch.object(storage.utils, 'execute') - @mock.patch( - 'nova.virt.lxd.storage.driver.block_device_info_get_ephemerals') - def test_remove_ephemeral_with_zfs( - self, block_device_info_get_ephemerals, execute): - block_device_info_get_ephemerals.return_value = [ - {'virtual_name': 'ephemerals0'}] - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - block_device_info = mock.Mock() - lxd_config = {'environment': {'storage': 'zfs'}, - 'config': {'storage.zfs_pool_name': 'zfs'}} - - client = mock.Mock() - storage.detach_ephemeral( - client, block_device_info, lxd_config, instance) - - block_device_info_get_ephemerals.assert_called_once_with( - block_device_info) - - expected_calls = [ - mock.call('zfs', 'destroy', 'zfs/instance-00000001-ephemeral', - run_as_root=True) - ] - self.assertEqual(expected_calls, execute.call_args_list) - - @mock.patch.object(storage.utils, 'execute') - @mock.patch( - 'nova.virt.lxd.storage.driver.block_device_info_get_ephemerals') - def test_remove_ephemeral_with_lvm( - self, block_device_info_get_ephemerals, execute): - block_device_info_get_ephemerals.return_value = [ - {'virtual_name': 'ephemerals0'}] - - ctx = context.get_admin_context() - instance = fake_instance.fake_instance_obj( - ctx, name='test', memory_mb=0) - block_device_info = mock.Mock() - lxd_config = {'environment': {'storage': 'lvm'}, - 'config': {'storage.lvm_vg_name': 'lxd'}} - - client = mock.Mock() - storage.detach_ephemeral( - client, block_device_info, lxd_config, instance) - - block_device_info_get_ephemerals.assert_called_once_with( - block_device_info) - - expected_calls = [ - mock.call( - 'umount', '/dev/lxd/instance-00000001-ephemerals0', - run_as_root=True), - mock.call('lvremove', '-f', - '/dev/lxd/instance-00000001-ephemerals0', - run_as_root=True) - ] - self.assertEqual(expected_calls, execute.call_args_list) diff --git a/nova/tests/unit/virt/lxd/test_vif.py b/nova/tests/unit/virt/lxd/test_vif.py deleted file mode 100644 index 8554c0d7..00000000 --- a/nova/tests/unit/virt/lxd/test_vif.py +++ /dev/null @@ -1,331 +0,0 @@ -# Copyright 2016 Canonical Ltd -# 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 context -from nova import exception -from nova.network import model as network_model -from nova import test -from nova.tests.unit import fake_instance -from nova.virt.lxd import vif - -GATEWAY = network_model.IP(address='101.168.1.1', type='gateway') -DNS_BRIDGE = network_model.IP(address='8.8.8.8', type=None) -SUBNET = network_model.Subnet( - cidr='101.168.1.0/24', dns=[DNS_BRIDGE], gateway=GATEWAY, - routes=None, dhcp_server='191.168.1.1') -NETWORK = network_model.Network( - id='ab7b876b-2c1c-4bb2-afa1-f9f4b6a28053', bridge='br0', label=None, - subnets=[SUBNET], bridge_interface=None, vlan=99, mtu=1000) -OVS_VIF = network_model.VIF( - id='da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', address='ca:fe:de:ad:be:ef', - network=NETWORK, type=network_model.VIF_TYPE_OVS, - devname='tapda5cc4bf-f1', - ovs_interfaceid='7b6812a6-b044-4596-b3c5-43a8ec431638', - details={network_model.VIF_DETAILS_OVS_HYBRID_PLUG: False}) -OVS_HYBRID_VIF = network_model.VIF( - id='da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', address='ca:fe:de:ad:be:ef', - network=NETWORK, type=network_model.VIF_TYPE_OVS, - devname='tapda5cc4bf-f1', - ovs_interfaceid='7b6812a6-b044-4596-b3c5-43a8ec431638', - details={network_model.VIF_DETAILS_OVS_HYBRID_PLUG: True}) -TAP_VIF = network_model.VIF( - id='da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', address='ca:fe:de:ad:be:ee', - network=NETWORK, type=network_model.VIF_TYPE_TAP, - devname='tapda5cc4bf-f1', - details={'mac_address': 'aa:bb:cc:dd:ee:ff'}) -LB_VIF = network_model.VIF( - id='da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', address='ca:fe:de:ad:be:ed', - network=NETWORK, type=network_model.VIF_TYPE_BRIDGE, - devname='tapda5cc4bf-f1') - -INSTANCE = fake_instance.fake_instance_obj( - context.get_admin_context(), name='test') - - -class GetVifDevnameTest(test.NoDBTestCase): - """Tests for get_vif_devname.""" - - def test_get_vif_devname_devname_exists(self): - an_vif = { - 'id': 'da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', - 'devname': 'oth1', - } - - devname = vif.get_vif_devname(an_vif) - - self.assertEqual('oth1', devname) - - def test_get_vif_devname_devname_nonexistent(self): - an_vif = { - 'id': 'da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', - } - - devname = vif.get_vif_devname(an_vif) - - self.assertEqual('nicda5cc4bf-f1', devname) - - -class GetConfigTest(test.NoDBTestCase): - """Tests for get_config.""" - - def setUp(self): - super(GetConfigTest, self).setUp() - self.CONF_patcher = mock.patch('nova.virt.lxd.vif.CONF') - self.CONF = self.CONF_patcher.start() - self.CONF.firewall_driver = 'nova.virt.firewall.NoopFirewallDriver' - - def tearDown(self): - super(GetConfigTest, self).tearDown() - self.CONF_patcher.stop() - - def test_get_config_bad_vif_type(self): - """Unsupported vif types raise an exception.""" - an_vif = network_model.VIF( - id='da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', - address='ca:fe:de:ad:be:ef', - network=NETWORK, type='invalid', - devname='tapda5cc4bf-f1', - ovs_interfaceid='7b6812a6-b044-4596-b3c5-43a8ec431638') - - self.assertRaises( - exception.NovaException, vif.get_config, an_vif) - - def test_get_config_bridge(self): - expected = {'bridge': 'br0', 'mac_address': 'ca:fe:de:ad:be:ef'} - an_vif = network_model.VIF( - id='da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', - address='ca:fe:de:ad:be:ef', - network=NETWORK, type='bridge', - devname='tapda5cc4bf-f1', - ovs_interfaceid='7b6812a6-b044-4596-b3c5-43a8ec431638') - - config = vif.get_config(an_vif) - - self.assertEqual(expected, config) - - def test_get_config_ovs_bridge(self): - expected = { - 'bridge': 'br0', 'mac_address': 'ca:fe:de:ad:be:ef'} - an_vif = network_model.VIF( - id='da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', - address='ca:fe:de:ad:be:ef', - network=NETWORK, type='ovs', - devname='tapda5cc4bf-f1', - ovs_interfaceid='7b6812a6-b044-4596-b3c5-43a8ec431638') - - config = vif.get_config(an_vif) - - self.assertEqual(expected, config) - - def test_get_config_ovs_hybrid(self): - self.CONF.firewall_driver = 'AnFirewallDriver' - - expected = { - 'bridge': 'qbrda5cc4bf-f1', 'mac_address': 'ca:fe:de:ad:be:ef'} - an_vif = network_model.VIF( - id='da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', - address='ca:fe:de:ad:be:ef', - network=NETWORK, type='ovs', - devname='tapda5cc4bf-f1', - ovs_interfaceid='7b6812a6-b044-4596-b3c5-43a8ec431638') - - config = vif.get_config(an_vif) - - self.assertEqual(expected, config) - - def test_get_config_tap(self): - expected = {'mac_address': 'ca:fe:de:ad:be:ef'} - an_vif = network_model.VIF( - id='da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', - address='ca:fe:de:ad:be:ef', - network=NETWORK, type='tap', - devname='tapda5cc4bf-f1', - ovs_interfaceid='7b6812a6-b044-4596-b3c5-43a8ec431638') - - config = vif.get_config(an_vif) - - self.assertEqual(expected, config) - - -class LXDGenericVifDriverTest(test.NoDBTestCase): - """Tests for LXDGenericVifDriver.""" - - def setUp(self): - super(LXDGenericVifDriverTest, self).setUp() - self.vif_driver = vif.LXDGenericVifDriver() - - @mock.patch.object(vif, '_post_plug_wiring') - @mock.patch('nova.virt.lxd.vif.os_vif') - def test_plug_ovs(self, os_vif, _post_plug_wiring): - self.vif_driver.plug(INSTANCE, OVS_VIF) - - self.assertEqual( - 'tapda5cc4bf-f1', os_vif.plug.call_args[0][0].vif_name) - self.assertEqual( - 'instance-00000001', os_vif.plug.call_args[0][1].name) - _post_plug_wiring.assert_called_with(INSTANCE, OVS_VIF) - - @mock.patch.object(vif, '_post_unplug_wiring') - @mock.patch('nova.virt.lxd.vif.os_vif') - def test_unplug_ovs(self, os_vif, _post_unplug_wiring): - self.vif_driver.unplug(INSTANCE, OVS_VIF) - - self.assertEqual( - 'tapda5cc4bf-f1', os_vif.unplug.call_args[0][0].vif_name) - self.assertEqual( - 'instance-00000001', os_vif.unplug.call_args[0][1].name) - _post_unplug_wiring.assert_called_with(INSTANCE, OVS_VIF) - - @mock.patch.object(vif, '_post_plug_wiring') - @mock.patch.object(vif, '_create_veth_pair') - @mock.patch('nova.virt.lxd.vif.os_vif') - def test_plug_tap(self, os_vif, _create_veth_pair, _post_plug_wiring): - self.vif_driver.plug(INSTANCE, TAP_VIF) - os_vif.plug.assert_not_called() - _create_veth_pair.assert_called_with('tapda5cc4bf-f1', - 'tinda5cc4bf-f1', - 1000) - _post_plug_wiring.assert_called_with(INSTANCE, TAP_VIF) - - @mock.patch.object(vif, '_post_unplug_wiring') - @mock.patch('nova.virt.lxd.vif.linux_net') - @mock.patch('nova.virt.lxd.vif.os_vif') - def test_unplug_tap(self, os_vif, linux_net, _post_unplug_wiring): - self.vif_driver.unplug(INSTANCE, TAP_VIF) - os_vif.plug.assert_not_called() - linux_net.delete_net_dev.assert_called_with('tapda5cc4bf-f1') - _post_unplug_wiring.assert_called_with(INSTANCE, TAP_VIF) - - -class PostPlugTest(test.NoDBTestCase): - """Tests for post plug operations""" - - def setUp(self): - super(PostPlugTest, self).setUp() - - @mock.patch('nova.virt.lxd.vif._create_veth_pair') - @mock.patch('nova.virt.lxd.vif._add_bridge_port') - @mock.patch('nova.virt.lxd.vif.linux_net') - def test_post_plug_ovs_hybrid(self, - linux_net, - add_bridge_port, - create_veth_pair): - linux_net.device_exists.return_value = False - - vif._post_plug_wiring(INSTANCE, OVS_HYBRID_VIF) - - linux_net.device_exists.assert_called_with('tapda5cc4bf-f1') - create_veth_pair.assert_called_with('tapda5cc4bf-f1', - 'tinda5cc4bf-f1', - 1000) - add_bridge_port.assert_called_with('qbrda5cc4bf-f1', - 'tapda5cc4bf-f1') - - @mock.patch('nova.virt.lxd.vif._create_veth_pair') - @mock.patch('nova.virt.lxd.vif._add_bridge_port') - @mock.patch.object(vif, '_create_ovs_vif_port') - @mock.patch('nova.virt.lxd.vif.linux_net') - def test_post_plug_ovs(self, - linux_net, - create_ovs_vif_port, - add_bridge_port, - create_veth_pair): - - linux_net.device_exists.return_value = False - - vif._post_plug_wiring(INSTANCE, OVS_VIF) - - linux_net.device_exists.assert_called_with('tapda5cc4bf-f1') - create_veth_pair.assert_called_with('tapda5cc4bf-f1', - 'tinda5cc4bf-f1', - 1000) - add_bridge_port.assert_not_called() - create_ovs_vif_port.assert_called_with( - 'br0', - 'tapda5cc4bf-f1', - 'da5cc4bf-f16c-4807-a0b6-911c7c67c3f8', - 'ca:fe:de:ad:be:ef', - INSTANCE.uuid, - 1000 - ) - - @mock.patch('nova.virt.lxd.vif._create_veth_pair') - @mock.patch('nova.virt.lxd.vif._add_bridge_port') - @mock.patch('nova.virt.lxd.vif.linux_net') - def test_post_plug_bridge(self, - linux_net, - add_bridge_port, - create_veth_pair): - linux_net.device_exists.return_value = False - - vif._post_plug_wiring(INSTANCE, LB_VIF) - - linux_net.device_exists.assert_called_with('tapda5cc4bf-f1') - create_veth_pair.assert_called_with('tapda5cc4bf-f1', - 'tinda5cc4bf-f1', - 1000) - add_bridge_port.assert_called_with('br0', - 'tapda5cc4bf-f1') - - @mock.patch('nova.virt.lxd.vif._create_veth_pair') - @mock.patch('nova.virt.lxd.vif._add_bridge_port') - @mock.patch('nova.virt.lxd.vif.linux_net') - def test_post_plug_tap(self, - linux_net, - add_bridge_port, - create_veth_pair): - linux_net.device_exists.return_value = False - - vif._post_plug_wiring(INSTANCE, TAP_VIF) - - linux_net.device_exists.assert_not_called() - - -class PostUnplugTest(test.NoDBTestCase): - """Tests for post unplug operations""" - - @mock.patch('nova.virt.lxd.vif.linux_net') - def test_post_unplug_ovs_hybrid(self, linux_net): - vif._post_unplug_wiring(INSTANCE, OVS_HYBRID_VIF) - linux_net.delete_net_dev.assert_called_with('tapda5cc4bf-f1') - - @mock.patch.object(vif, '_delete_ovs_vif_port') - def test_post_unplug_ovs(self, delete_ovs_vif_port): - vif._post_unplug_wiring(INSTANCE, OVS_VIF) - delete_ovs_vif_port.assert_called_with('br0', - 'tapda5cc4bf-f1', - True) - - @mock.patch('nova.virt.lxd.vif.linux_net') - def test_post_unplug_bridge(self, linux_net): - vif._post_unplug_wiring(INSTANCE, LB_VIF) - linux_net.delete_net_dev.assert_called_with('tapda5cc4bf-f1') - - -class MiscHelpersTest(test.NoDBTestCase): - """Misc tests for vif module""" - - def test_is_ovs_vif_port(self): - self.assertTrue(vif._is_ovs_vif_port(OVS_VIF)) - self.assertFalse(vif._is_ovs_vif_port(OVS_HYBRID_VIF)) - self.assertFalse(vif._is_ovs_vif_port(TAP_VIF)) - - @mock.patch.object(vif, 'utils') - def test_add_bridge_port(self, utils): - vif._add_bridge_port('br-int', 'tapXYZ') - utils.execute.assert_called_with('brctl', 'addif', - 'br-int', 'tapXYZ', - run_as_root=True) diff --git a/nova/virt/__init__.py b/nova/virt/__init__.py deleted file mode 100644 index de40ea7c..00000000 --- a/nova/virt/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__) diff --git a/nova/virt/lxd/__init__.py b/nova/virt/lxd/__init__.py deleted file mode 100644 index 4c104079..00000000 --- a/nova/virt/lxd/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from nova.virt.lxd import driver - -LXDDriver = driver.LXDDriver diff --git a/nova/virt/lxd/common.py b/nova/virt/lxd/common.py deleted file mode 100644 index cd003938..00000000 --- a/nova/virt/lxd/common.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2015 Canonical Ltd -# 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 collections -import os - -from nova import conf - - -_InstanceAttributes = collections.namedtuple('InstanceAttributes', [ - 'instance_dir', 'console_path', 'storage_path', 'container_path']) - - -def InstanceAttributes(instance): - """An instance adapter for nova-lxd specific attributes.""" - if is_snap_lxd(): - prefix = '/var/snap/lxd/common/lxd/logs' - else: - prefix = '/var/log/lxd' - instance_dir = os.path.join(conf.CONF.instances_path, instance.name) - console_path = os.path.join(prefix, instance.name, 'console.log') - storage_path = os.path.join(instance_dir, 'storage') - container_path = os.path.join( - conf.CONF.lxd.root_dir, 'containers', instance.name) - return _InstanceAttributes( - instance_dir, console_path, storage_path, container_path) - - -def is_snap_lxd(): - """Determine whether it's a snap installed lxd or a package installed lxd - - This is easily done by checking if the bin file is /snap/bin/lxc - - :returns: True if snap installed, otherwise False - :rtype: bool - """ - return os.path.isfile('/snap/bin/lxc') diff --git a/nova/virt/lxd/driver.py b/nova/virt/lxd/driver.py deleted file mode 100644 index 67c65dfb..00000000 --- a/nova/virt/lxd/driver.py +++ /dev/null @@ -1,1373 +0,0 @@ -# Copyright 2015 Canonical Ltd -# 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 __future__ import absolute_import - -import errno -import io -import os -import platform -import pwd -import shutil -import socket -import tarfile -import tempfile -import hashlib - -import eventlet -import nova.conf -import nova.context -from contextlib import closing - -from nova import exception -from nova import i18n -from nova import image -from nova import network -from nova.network import model as network_model -from nova import objects -from nova.virt import driver -from os_brick.initiator import connector -from oslo_concurrency import processutils -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import fileutils -import pylxd -from pylxd import exceptions as lxd_exceptions - -from nova.virt.lxd import vif as lxd_vif -from nova.virt.lxd import common -from nova.virt.lxd import flavor -from nova.virt.lxd import storage - -from nova.api.metadata import base as instance_metadata -from nova.objects import fields as obj_fields -from nova.objects import migrate_data -from nova.virt import configdrive -from nova.compute import power_state -from nova.compute import vm_states -from nova.virt import hardware -from oslo_utils import units -from oslo_serialization import jsonutils -from nova import utils -import psutil -from oslo_concurrency import lockutils -from nova.compute import task_states -from oslo_utils import excutils -from nova.virt import firewall - -_ = i18n._ - -lxd_opts = [ - cfg.StrOpt('root_dir', - default='/var/lib/lxd/', - help='Default LXD directory'), - cfg.StrOpt('pool', - default=None, - help='LXD Storage pool to use with LXD >= 2.9'), - cfg.IntOpt('timeout', - default=-1, - help='Default LXD timeout'), - cfg.BoolOpt('allow_live_migration', - default=False, - help='Determine wheter to allow live migration'), -] - -CONF = cfg.CONF -CONF.register_opts(lxd_opts, 'lxd') -LOG = logging.getLogger(__name__) -IMAGE_API = image.API() - -MAX_CONSOLE_BYTES = 100 * units.Ki -NOVA_CONF = nova.conf.CONF - -ACCEPTABLE_IMAGE_FORMATS = {'raw', 'root-tar', 'squashfs'} -BASE_DIR = os.path.join( - CONF.instances_path, CONF.image_cache_subdirectory_name) - - -def _last_bytes(file_like_object, num): - """Return num bytes from the end of the file, and remaning byte count. - - :param file_like_object: The file to read - :param num: The number of bytes to return - - :returns: (data, remaining) - """ - - try: - file_like_object.seek(-num, os.SEEK_END) - except IOError as e: - # seek() fails with EINVAL when trying to go before the start of - # the file. It means that num is larger than the file size, so - # just go to the start. - if e.errno == errno.EINVAL: - file_like_object.seek(0, os.SEEK_SET) - else: - raise - - remaining = file_like_object.tell() - return (file_like_object.read(), remaining) - - -def _neutron_failed_callback(event_name, instance): - LOG.error("Neutron Reported failure on event " - "{event} for instance {uuid}" - .format(event=event_name, uuid=instance.name), - instance=instance) - if CONF.vif_plugging_is_fatal: - raise exception.VirtualInterfaceCreateException() - - -def _get_cpu_info(): - """Get cpu information. - - This method executes lscpu and then parses the output, - returning a dictionary of information. - """ - cpuinfo = {} - out, err = utils.execute('lscpu') - if err: - msg = _("Unable to parse lscpu output.") - raise exception.NovaException(msg) - - cpu = [line.strip('\n') for line in out.splitlines()] - for line in cpu: - if line.strip(): - name, value = line.split(':', 1) - name = name.strip().lower() - cpuinfo[name] = value.strip() - - f = open('/proc/cpuinfo', 'r') - features = [line.strip('\n') for line in f.readlines()] - for line in features: - if line.strip(): - if line.startswith('flags'): - name, value = line.split(':', 1) - name = name.strip().lower() - cpuinfo[name] = value.strip() - - return cpuinfo - - -def _get_ram_usage(): - """Get memory info.""" - with open('/proc/meminfo') as fp: - m = fp.read().split() - idx1 = m.index('MemTotal:') - idx2 = m.index('MemFree:') - idx3 = m.index('Buffers:') - idx4 = m.index('Cached:') - - total = int(m[idx1 + 1]) - avail = int(m[idx2 + 1]) + int(m[idx3 + 1]) + int(m[idx4 + 1]) - - return { - 'total': total * 1024, - 'used': (total - avail) * 1024 - } - - -def _get_fs_info(path): - """Get free/used/total disk space.""" - hddinfo = os.statvfs(path) - total = hddinfo.f_blocks * hddinfo.f_bsize - available = hddinfo.f_bavail * hddinfo.f_bsize - used = total - available - return {'total': total, - 'available': available, - 'used': used} - - -def _get_zpool_info(pool_or_dataset): - """Get the free/used/total diskspace in a zfs pool or dataset. - A dataset is distinguished by having a '/' in the string. - - :param pool_or_dataset: The string name of the pool or dataset - :type pool_or_dataset: str - :returns: dictionary with keys 'total', 'available', 'used' - :rtype: Dict[str, int] - :raises: :class:`exception.NovaException` - :raises: :class:`oslo.concurrency.PorcessExecutionError` - :raises: :class:`OSError` - """ - def _get_zfs_attribute(cmd, attribute): - value, err = utils.execute(cmd, 'list', - '-o', attribute, - '-H', - '-p', - pool_or_dataset, - run_as_root=True) - if err: - msg = _("Unable to parse zfs output.") - raise exception.NovaException(msg) - value = int(value.strip()) - return value - - if '/' in pool_or_dataset: - # it's a dataset: - # for zfs datasets we only have 'available' and 'used' and so need to - # construct the total from available and used. - used = _get_zfs_attribute('zfs', 'used') - available = _get_zfs_attribute('zfs', 'available') - total = available + used - else: - # otherwise it's a zpool - total = _get_zfs_attribute('zpool', 'size') - used = _get_zfs_attribute('zpool', 'alloc') - available = _get_zfs_attribute('zpool', 'free') - return {'total': total, - 'available': available, - 'used': used} - - -def _get_power_state(lxd_state): - """Take a lxd state code and translate it to nova power state.""" - state_map = [ - (power_state.RUNNING, {100, 101, 103, 200}), - (power_state.SHUTDOWN, {102, 104, 107}), - (power_state.NOSTATE, {105, 106, 401}), - (power_state.CRASHED, {108, 400}), - (power_state.SUSPENDED, {109, 110, 111}), - ] - for nova_state, lxd_states in state_map: - if lxd_state in lxd_states: - return nova_state - raise ValueError('Unknown LXD power state: {}'.format(lxd_state)) - - -def _sync_glance_image_to_lxd(client, context, image_ref): - """Sync an image from glance to LXD image store. - - The image from glance can't go directly into the LXD image store, - as LXD needs some extra metadata connected to it. - - The image is stored in the LXD image store with an alias to - the image_ref. This way, it will only copy over once. - """ - lock_path = os.path.join(CONF.instances_path, 'locks') - with lockutils.lock( - lock_path, external=True, - lock_file_prefix='lxd-image-{}'.format(image_ref)): - - # NOTE(jamespage): Re-query by image_ref to ensure - # that another process did not - # sneak infront of this one and create - # the same image already. - try: - client.images.get_by_alias(image_ref) - return - except lxd_exceptions.LXDAPIException as e: - if e.response.status_code != 404: - raise - - try: - ifd, image_file = tempfile.mkstemp() - mfd, manifest_file = tempfile.mkstemp() - - image = IMAGE_API.get(context, image_ref) - if image.get('disk_format') not in ACCEPTABLE_IMAGE_FORMATS: - raise exception.ImageUnacceptable( - image_id=image_ref, reason=_("Bad image format")) - IMAGE_API.download(context, image_ref, dest_path=image_file) - - # It is possible that LXD already have the same image - # but NOT aliased as result of previous publish/export operation - # (snapshot from openstack). - # In that case attempt to add it again - # (implicitly via instance launch from affected image) will produce - # LXD error - "Image with same fingerprint already exists". - # Error does not have unique identifier to handle it we calculate - # fingerprint of image as LXD do it and check if LXD already have - # image with such fingerprint. - # If any we will add alias to this image and will not re-import it - def add_alias(): - - def lxdimage_fingerprint(): - def sha256_file(): - sha256 = hashlib.sha256() - with closing(open(image_file, 'rb')) as f: - for block in iter(lambda: f.read(65536), b''): - sha256.update(block) - return sha256.hexdigest() - - return sha256_file() - - fingerprint = lxdimage_fingerprint() - if client.images.exists(fingerprint): - LOG.info("Image with fingerprint {fingerprint} already " - "exists but not accessible by alias {alias}, " - "add alias" - .format(fingerprint=fingerprint, alias=image_ref)) - lxdimage = client.images.get(fingerprint) - lxdimage.add_alias(image_ref, '') - return True - - return False - - if add_alias(): - return - - # up2date LXD publish/export operations produce images which - # already contains /rootfs and metdata.yaml in exported file. - # We should not pass metdata explicitly in that case as imported - # image will be unusable bacause LXD will think that it containts - # rootfs and will not extract embedded /rootfs properly. - # Try to detect if image content already has metadata and not pass - # explicit metadata in that case - def imagefile_has_metadata(image_file): - try: - with closing(tarfile.TarFile.open( - name=image_file, mode='r:*')) as tf: - try: - tf.getmember('metadata.yaml') - return True - except KeyError: - pass - except tarfile.ReadError: - pass - return False - - if imagefile_has_metadata(image_file): - LOG.info("Image {alias} already has metadata, " - "skipping metadata injection..." - .format(alias=image_ref)) - with open(image_file, 'rb') as image: - image = client.images.create(image, wait=True) - else: - metadata = { - 'architecture': image.get( - 'hw_architecture', - obj_fields.Architecture.from_host()), - 'creation_date': int(os.stat(image_file).st_ctime)} - metadata_yaml = jsonutils.dumps( - metadata, sort_keys=True, indent=4, - separators=(',', ': '), - ensure_ascii=False).encode('utf-8') + b"\n" - - tarball = tarfile.open(manifest_file, "w:gz") - tarinfo = tarfile.TarInfo(name='metadata.yaml') - tarinfo.size = len(metadata_yaml) - tarball.addfile(tarinfo, io.BytesIO(metadata_yaml)) - tarball.close() - - with open(manifest_file, 'rb') as manifest: - with open(image_file, 'rb') as image: - image = client.images.create( - image, metadata=manifest, - wait=True) - - image.add_alias(image_ref, '') - - finally: - os.close(ifd) - os.close(mfd) - os.unlink(image_file) - os.unlink(manifest_file) - - -def brick_get_connector_properties(multipath=False, enforce_multipath=False): - """Wrapper to automatically set root_helper in brick calls. - :param multipath: A boolean indicating whether the connector can - support multipath. - :param enforce_multipath: If True, it raises exception when multipath=True - is specified but multipathd is not running. - If False, it falls back to multipath=False - when multipathd is not running. - """ - - root_helper = utils.get_root_helper() - return connector.get_connector_properties(root_helper, - CONF.my_ip, - multipath, - enforce_multipath) - - -def brick_get_connector(protocol, driver=None, - use_multipath=False, - device_scan_attempts=3, - *args, **kwargs): - """Wrapper to get a brick connector object. - This automatically populates the required protocol as well - as the root_helper needed to execute commands. - """ - - root_helper = utils.get_root_helper() - if protocol.upper() == "RBD": - kwargs['do_local_attach'] = True - return connector.InitiatorConnector.factory( - protocol, root_helper, - driver=driver, - use_multipath=use_multipath, - device_scan_attempts=device_scan_attempts, - *args, **kwargs) - - -class LXDLiveMigrateData(migrate_data.LiveMigrateData): - """LiveMigrateData for LXD.""" - - VERSION = '1.0' - fields = {} - - -class LXDDriver(driver.ComputeDriver): - """A LXD driver for nova. - - LXD is a system container hypervisor. LXDDriver provides LXD - functionality to nova. For more information about LXD, see - http://www.ubuntu.com/cloud/lxd - """ - - capabilities = { - "has_imagecache": False, - "supports_recreate": False, - "supports_migrate_to_same_host": False, - "supports_attach_interface": True, - "supports_multiattach": False, - } - - def __init__(self, virtapi): - super(LXDDriver, self).__init__(virtapi) - - self.client = None # Initialized by init_host - self.host = NOVA_CONF.host - self.network_api = network.API() - self.vif_driver = lxd_vif.LXDGenericVifDriver() - self.firewall_driver = firewall.load_driver( - default='nova.virt.firewall.NoopFirewallDriver') - - def init_host(self, host): - """Initialize the driver on the host. - - The pylxd Client is initialized. This initialization may raise - an exception if the LXD instance cannot be found. - - The `host` argument is ignored here, as the LXD instance is - assumed to be on the same system as the compute worker - running this code. This is by (current) design. - - See `nova.virt.driver.ComputeDriver.init_host` for more - information. - """ - try: - self.client = pylxd.Client() - except lxd_exceptions.ClientConnectionFailed as e: - msg = _("Unable to connect to LXD daemon: {}").format(e) - raise exception.HostNotFound(msg) - self._after_reboot() - - def cleanup_host(self, host): - """Clean up the host. - - `nova.virt.ComputeDriver` defines this method. It is overridden - here to be explicit that there is nothing to be done, as - `init_host` does not create any resources that would need to be - cleaned up. - - See `nova.virt.driver.ComputeDriver.cleanup_host` for more - information. - """ - - def get_info(self, instance, use_cache=True): - """Return an InstanceInfo object for the instance.""" - try: - container = self.client.containers.get(instance.name) - except lxd_exceptions.NotFound: - raise exception.InstanceNotFound(instance_id=instance.uuid) - - state = container.state() - return hardware.InstanceInfo( - state=_get_power_state(state.status_code)) - - def list_instances(self): - """Return a list of all instance names.""" - return [c.name for c in self.client.containers.all()] - - def spawn(self, context, instance, image_meta, injected_files, - admin_password, allocations, network_info=None, - block_device_info=None): - """Create a new lxd container as a nova instance. - - Creating a new container requires a number of steps. First, the - image is fetched from glance, if needed. Next, the network is - connected. A profile is created in LXD, and then the container - is created and started. - - See `nova.virt.driver.ComputeDriver.spawn` for more - information. - """ - try: - self.client.containers.get(instance.name) - raise exception.InstanceExists(name=instance.name) - except lxd_exceptions.LXDAPIException as e: - if e.response.status_code != 404: - raise # Re-raise the exception if it wasn't NotFound - - instance_dir = common.InstanceAttributes(instance).instance_dir - if not os.path.exists(instance_dir): - fileutils.ensure_tree(instance_dir) - - # Check to see if LXD already has a copy of the image. If not, - # fetch it. - try: - self.client.images.get_by_alias(instance.image_ref) - except lxd_exceptions.LXDAPIException as e: - if e.response.status_code != 404: - raise - _sync_glance_image_to_lxd( - self.client, context, instance.image_ref) - - # Plug in the network - if network_info: - timeout = CONF.vif_plugging_timeout - if (utils.is_neutron() and timeout): - events = [('network-vif-plugged', vif['id']) - for vif in network_info if not vif.get( - 'active', True)] - else: - events = [] - - try: - with self.virtapi.wait_for_instance_event( - instance, events, deadline=timeout, - error_callback=_neutron_failed_callback): - self.plug_vifs(instance, network_info) - except eventlet.timeout.Timeout: - LOG.warn("Timeout waiting for vif plugging callback for " - "instance {uuid}" - .format(uuid=instance['name'])) - if CONF.vif_plugging_is_fatal: - self.destroy( - context, instance, network_info, block_device_info) - raise exception.InstanceDeployFailure( - 'Timeout waiting for vif plugging', - instance_id=instance['name']) - - # Create the profile - try: - profile = flavor.to_profile( - self.client, instance, network_info, block_device_info) - except lxd_exceptions.LXDAPIException as e: - with excutils.save_and_reraise_exception(): - self.cleanup( - context, instance, network_info, block_device_info) - - # Create the container - container_config = { - 'name': instance.name, - 'profiles': [profile.name], - 'source': { - 'type': 'image', - 'alias': instance.image_ref, - }, - } - try: - container = self.client.containers.create( - container_config, wait=True) - except lxd_exceptions.LXDAPIException as e: - with excutils.save_and_reraise_exception(): - self.cleanup( - context, instance, network_info, block_device_info) - - lxd_config = self.client.host_info - storage.attach_ephemeral( - self.client, block_device_info, lxd_config, instance) - if configdrive.required_by(instance): - configdrive_path = self._add_configdrive( - context, instance, - injected_files, admin_password, - network_info) - - profile = self.client.profiles.get(instance.name) - config_drive = { - 'configdrive': { - 'path': '/config-drive', - 'source': configdrive_path, - 'type': 'disk', - 'readonly': 'True', - } - } - profile.devices.update(config_drive) - profile.save() - - try: - self.firewall_driver.setup_basic_filtering( - instance, network_info) - self.firewall_driver.instance_filter( - instance, network_info) - - container.start(wait=True) - - self.firewall_driver.apply_instance_filter( - instance, network_info) - except lxd_exceptions.LXDAPIException: - with excutils.save_and_reraise_exception(): - try: - self.cleanup( - context, instance, network_info, block_device_info) - except Exception as e: - LOG.warn('The cleanup process failed with: %s. This ' - 'error may or not may be relevant', e) - - def destroy(self, context, instance, network_info, block_device_info=None, - destroy_disks=True, migrate_data=None): - """Destroy a running instance. - - Since the profile and the instance are created on `spawn`, it is - safe to delete them together. - - See `nova.virt.driver.ComputeDriver.destroy` for more - information. - """ - lock_path = os.path.join(CONF.instances_path, 'locks') - - with lockutils.lock( - lock_path, external=True, - lock_file_prefix='lxd-container-{}'.format(instance.name)): - # TODO(sahid): Each time we get a container we should - # protect it by using a mutex. - try: - container = self.client.containers.get(instance.name) - if container.status != 'Stopped': - container.stop(wait=True) - container.delete(wait=True) - if (instance.vm_state == vm_states.RESCUED): - rescued_container = self.client.containers.get( - '{}-rescue'.format(instance.name)) - if rescued_container.status != 'Stopped': - rescued_container.stop(wait=True) - rescued_container.delete(wait=True) - except lxd_exceptions.LXDAPIException as e: - if e.response.status_code == 404: - LOG.warning("Failed to delete instance. " - "Container does not exist for {instance}." - .format(instance=instance.name)) - else: - raise - finally: - self.cleanup( - context, instance, network_info, block_device_info) - - def cleanup(self, context, instance, network_info, block_device_info=None, - destroy_disks=True, migrate_data=None, destroy_vifs=True): - """Clean up the filesystem around the container. - - See `nova.virt.driver.ComputeDriver.cleanup` for more - information. - """ - if destroy_vifs: - self.unplug_vifs(instance, network_info) - self.firewall_driver.unfilter_instance(instance, network_info) - - lxd_config = self.client.host_info - storage.detach_ephemeral(self.client, - block_device_info, - lxd_config, - instance) - - name = pwd.getpwuid(os.getuid()).pw_name - - container_dir = common.InstanceAttributes(instance).instance_dir - if os.path.exists(container_dir): - utils.execute( - 'chown', '-R', '{}:{}'.format(name, name), - container_dir, run_as_root=True) - shutil.rmtree(container_dir) - - try: - self.client.profiles.get(instance.name).delete() - except lxd_exceptions.LXDAPIException as e: - if e.response.status_code == 404: - LOG.warning("Failed to delete instance. " - "Profile does not exist for {instance}." - .format(instance=instance.name)) - else: - raise - - def reboot(self, context, instance, network_info, reboot_type, - block_device_info=None, bad_volumes_callback=None): - """Reboot the container. - - Nova *should* not execute this on a stopped container, but - the documentation specifically says that if it is called, the - container should always return to a 'Running' state. - - See `nova.virt.driver.ComputeDriver.cleanup` for more - information. - """ - container = self.client.containers.get(instance.name) - container.restart(force=True, wait=True) - - def get_console_output(self, context, instance): - """Get the output of the container console. - - See `nova.virt.driver.ComputeDriver.get_console_output` for more - information. - """ - instance_attrs = common.InstanceAttributes(instance) - console_path = instance_attrs.console_path - if not os.path.exists(console_path): - return '' - uid = pwd.getpwuid(os.getuid()).pw_uid - utils.execute( - 'chown', '%s:%s' % (uid, uid), console_path, run_as_root=True) - utils.execute( - 'chmod', '755', instance_attrs.container_path, run_as_root=True) - with open(console_path, 'rb') as f: - log_data, _ = _last_bytes(f, MAX_CONSOLE_BYTES) - return log_data - - def get_host_ip_addr(self): - return CONF.my_ip - - def attach_volume(self, context, connection_info, instance, mountpoint, - disk_bus=None, device_type=None, encryption=None): - """Attach block device to a nova instance. - - Attaching a block device to a container requires a couple of steps. - First os_brick connects the cinder volume to the host. Next, - the block device is added to the containers profile. Next, the - apparmor profile for the container is updated to allow mounting - 'ext4' block devices. Finally, the profile is saved. - - The block device must be formatted as ext4 in order to mount - the block device inside the container. - - See `nova.virt.driver.ComputeDriver.attach_volume' for - more information/ - """ - profile = self.client.profiles.get(instance.name) - protocol = connection_info['driver_volume_type'] - storage_driver = brick_get_connector(protocol) - device_info = storage_driver.connect_volume( - connection_info['data']) - disk = os.stat(os.path.realpath(device_info['path'])) - vol_id = connection_info['data']['volume_id'] - - disk_device = { - vol_id: { - 'path': mountpoint, - 'major': '%s' % os.major(disk.st_rdev), - 'minor': '%s' % os.minor(disk.st_rdev), - 'type': 'unix-block' - } - } - - profile.devices.update(disk_device) - # XXX zulcss (10 Jul 2016) - fused is currently not supported. - profile.config.update({'raw.apparmor': 'mount fstype=ext4,'}) - profile.save() - - def detach_volume(self, context, connection_info, instance, mountpoint, - encryption=None): - """Detach block device from a nova instance. - - First the volume id is deleted from the profile, and the - profile is saved. The os-brick disconnects the volume - from the host. - - See `nova.virt.driver.Computedriver.detach_volume` for - more information. - """ - profile = self.client.profiles.get(instance.name) - vol_id = connection_info['data']['volume_id'] - if vol_id in profile.devices: - del profile.devices[vol_id] - profile.save() - - protocol = connection_info['driver_volume_type'] - storage_driver = brick_get_connector(protocol) - storage_driver.disconnect_volume(connection_info['data'], None) - - def attach_interface(self, context, instance, image_meta, vif): - self.vif_driver.plug(instance, vif) - self.firewall_driver.setup_basic_filtering(instance, vif) - - profile = self.client.profiles.get(instance.name) - - net_device = lxd_vif.get_vif_devname(vif) - config_update = { - net_device: { - 'nictype': 'physical', - 'hwaddr': vif['address'], - 'parent': lxd_vif.get_vif_internal_devname(vif), - 'type': 'nic', - } - } - - profile.devices.update(config_update) - profile.save(wait=True) - - def detach_interface(self, context, instance, vif): - try: - profile = self.client.profiles.get(instance.name) - devname = lxd_vif.get_vif_devname(vif) - - # NOTE(jamespage): Attempt to remove device using - # new style tap naming - if devname in profile.devices: - del profile.devices[devname] - profile.save(wait=True) - else: - # NOTE(jamespage): For upgrades, scan devices - # and attempt to identify - # using mac address as the - # device will *not* have a - # consistent name - for key, val in profile.devices.items(): - if val.get('hwaddr') == vif['address']: - del profile.devices[key] - profile.save(wait=True) - break - except lxd_exceptions.NotFound: - # This method is called when an instance get destroyed. It - # could happen that Nova to receive an event - # "vif-delete-event" after the instance is destroyed which - # result the lxd profile not exist. - LOG.debug("lxd profile for instance {instance} does not exist. " - "The instance probably got destroyed before this method " - "got called.".format(instance=instance.name)) - - self.vif_driver.unplug(instance, vif) - - def migrate_disk_and_power_off( - self, context, instance, dest, _flavor, network_info, - block_device_info=None, timeout=0, retry_interval=0): - - if CONF.my_ip == dest: - # Make sure that the profile for the container is up-to-date to - # the actual state of the container. - flavor.to_profile( - self.client, instance, network_info, block_device_info, - update=True) - container = self.client.containers.get(instance.name) - container.stop(wait=True) - return '' - - def snapshot(self, context, instance, image_id, update_task_state): - lock_path = str(os.path.join(CONF.instances_path, 'locks')) - - with lockutils.lock( - lock_path, external=True, - lock_file_prefix='lxd-container-{}'.format(instance.name)): - - update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) - - container = self.client.containers.get(instance.name) - if container.status != 'Stopped': - container.stop(wait=True) - image = container.publish(wait=True) - container.start(wait=True) - - update_task_state( - task_state=task_states.IMAGE_UPLOADING, - expected_state=task_states.IMAGE_PENDING_UPLOAD) - - snapshot = IMAGE_API.get(context, image_id) - data = image.export() - image_meta = {'name': snapshot['name'], - 'disk_format': 'raw', - 'container_format': 'bare'} - IMAGE_API.update(context, image_id, image_meta, data) - - def pause(self, instance): - """Pause container. - - See `nova.virt.driver.ComputeDriver.pause` for more - information. - """ - container = self.client.containers.get(instance.name) - container.freeze(wait=True) - - def unpause(self, instance): - """Unpause container. - - See `nova.virt.driver.ComputeDriver.unpause` for more - information. - """ - container = self.client.containers.get(instance.name) - container.unfreeze(wait=True) - - def suspend(self, context, instance): - """Suspend container. - - See `nova.virt.driver.ComputeDriver.suspend` for more - information. - """ - self.pause(instance) - - def resume(self, context, instance, network_info, block_device_info=None): - """Resume container. - - See `nova.virt.driver.ComputeDriver.resume` for more - information. - """ - self.unpause(instance) - - def resume_state_on_host_boot(self, context, instance, network_info, - block_device_info=None): - """resume guest state when a host is booted.""" - try: - state = self.get_info(instance).state - ignored_states = (power_state.RUNNING, - power_state.SUSPENDED, - power_state.NOSTATE, - power_state.PAUSED) - - if state in ignored_states: - return - - self.power_on(context, instance, network_info, block_device_info) - except (exception.InternalError, exception.InstanceNotFound): - pass - - def rescue(self, context, instance, network_info, image_meta, - rescue_password): - """Rescue a LXD container. - - From the perspective of nova, rescuing a instance requires a number of - steps. First, the failed container is stopped, and then this method is - called. - - So the original container is already stopped, and thus, next, - '-rescue', is appended to the failed container's name, this is done so - the container can be unrescued. The container's profile is updated with - the rootfs of the failed container. Finally, a new container is created - and started. - - See 'nova.virt.driver.ComputeDriver.rescue` for more - information. - """ - rescue = '%s-rescue' % instance.name - - container = self.client.containers.get(instance.name) - container_rootfs = os.path.join( - nova.conf.CONF.lxd.root_dir, 'containers', instance.name, 'rootfs') - container.rename(rescue, wait=True) - - profile = self.client.profiles.get(instance.name) - - rescue_dir = { - 'rescue': { - 'source': container_rootfs, - 'path': '/mnt', - 'type': 'disk', - } - } - profile.devices.update(rescue_dir) - profile.save() - - container_config = { - 'name': instance.name, - 'profiles': [profile.name], - 'source': { - 'type': 'image', - 'alias': instance.image_ref, - } - } - container = self.client.containers.create( - container_config, wait=True) - container.start(wait=True) - - def unrescue(self, instance, network_info): - """Unrescue an instance. - - Unrescue a container that has previously been rescued. - First the rescue containerisremoved. Next the rootfs - of the defective container is removed from the profile. - Finally the container is renamed and started. - - See 'nova.virt.drvier.ComputeDriver.unrescue` for more - information. - """ - rescue = '%s-rescue' % instance.name - - container = self.client.containers.get(instance.name) - if container.status != 'Stopped': - container.stop(wait=True) - container.delete(wait=True) - - profile = self.client.profiles.get(instance.name) - del profile.devices['rescue'] - profile.save() - - container = self.client.containers.get(rescue) - container.rename(instance.name, wait=True) - container.start(wait=True) - - def power_off(self, instance, timeout=0, retry_interval=0): - """Power off an instance - - See 'nova.virt.drvier.ComputeDriver.power_off` for more - information. - """ - container = self.client.containers.get(instance.name) - if container.status != 'Stopped': - container.stop(wait=True) - - def power_on(self, context, instance, network_info, - block_device_info=None): - """Power on an instance - - See 'nova.virt.drvier.ComputeDriver.power_on` for more - information. - """ - container = self.client.containers.get(instance.name) - if container.status != 'Running': - container.start(wait=True) - - def get_available_resource(self, nodename): - """Aggregate all available system resources. - - See 'nova.virt.drvier.ComputeDriver.get_available_resource` - for more information. - """ - cpuinfo = _get_cpu_info() - - cpu_info = { - 'arch': platform.uname()[5], - 'features': cpuinfo.get('flags', 'unknown'), - 'model': cpuinfo.get('model name', 'unknown'), - 'topology': { - 'sockets': cpuinfo['socket(s)'], - 'cores': cpuinfo['core(s) per socket'], - 'threads': cpuinfo['thread(s) per core'], - }, - 'vendor': cpuinfo.get('vendor id', 'unknown'), - } - - cpu_topology = cpu_info['topology'] - vcpus = (int(cpu_topology['cores']) * - int(cpu_topology['sockets']) * - int(cpu_topology['threads'])) - - local_memory_info = _get_ram_usage() - - lxd_config = self.client.host_info - - # NOTE(jamespage): ZFS storage report is very LXD 2.0.x - # centric and will need to be updated - # to support LXD storage pools - storage_driver = lxd_config['environment']['storage'] - if storage_driver == 'zfs': - # NOTE(ajkavanagh) - BUG/1782329 - this is temporary until storage - # pools is implemented. LXD 3 removed the storage.zfs_pool_name - # key from the config. So, if it fails, we need to grab the - # configured storage pool and use that as the name instead. - try: - pool_name = lxd_config['config']['storage.zfs_pool_name'] - except KeyError: - pool_name = CONF.lxd.pool - local_disk_info = _get_zpool_info(pool_name) - else: - local_disk_info = _get_fs_info(CONF.lxd.root_dir) - - data = { - 'vcpus': vcpus, - 'memory_mb': local_memory_info['total'] // units.Mi, - 'memory_mb_used': local_memory_info['used'] // units.Mi, - 'local_gb': local_disk_info['total'] // units.Gi, - 'local_gb_used': local_disk_info['used'] // units.Gi, - 'vcpus_used': 0, - 'hypervisor_type': 'lxd', - 'hypervisor_version': '011', - 'cpu_info': jsonutils.dumps(cpu_info), - 'hypervisor_hostname': socket.gethostname(), - 'supported_instances': [ - (obj_fields.Architecture.I686, obj_fields.HVType.LXD, - obj_fields.VMMode.EXE), - (obj_fields.Architecture.X86_64, obj_fields.HVType.LXD, - obj_fields.VMMode.EXE), - (obj_fields.Architecture.I686, obj_fields.HVType.LXC, - obj_fields.VMMode.EXE), - (obj_fields.Architecture.X86_64, obj_fields.HVType.LXC, - obj_fields.VMMode.EXE), - ], - 'numa_topology': None, - } - - return data - - def refresh_instance_security_rules(self, instance): - return self.firewall_driver.refresh_instance_security_rules( - instance) - - def ensure_filtering_rules_for_instance(self, instance, network_info): - return self.firewall_driver.ensure_filtering_rules_for_instance( - instance, network_info) - - def filter_defer_apply_on(self): - return self.firewall_driver.filter_defer_apply_on() - - def filter_defer_apply_off(self): - return self.firewall_driver.filter_defer_apply_off() - - def unfilter_instance(self, instance, network_info): - return self.firewall_driver.unfilter_instance( - instance, network_info) - - def get_host_uptime(self): - out, err = utils.execute('env', 'LANG=C', 'uptime') - return out - - def plug_vifs(self, instance, network_info): - for vif in network_info: - self.vif_driver.plug(instance, vif) - - def unplug_vifs(self, instance, network_info): - for vif in network_info: - self.vif_driver.unplug(instance, vif) - - def get_host_cpu_stats(self): - return { - 'kernel': int(psutil.cpu_times()[2]), - 'idle': int(psutil.cpu_times()[3]), - 'user': int(psutil.cpu_times()[0]), - 'iowait': int(psutil.cpu_times()[4]), - 'frequency': _get_cpu_info().get('cpu mhz', 0) - } - - def get_volume_connector(self, instance): - return {'ip': CONF.my_block_storage_ip, - 'initiator': 'fake', - 'host': 'fakehost'} - - def get_available_nodes(self, refresh=False): - hostname = socket.gethostname() - return [hostname] - - # XXX: rockstar (5 July 2016) - The methods and code below this line - # have not been through the cleanup process. We know the cleanup process - # is complete when there is no more code below this comment, and the - # comment can be removed. - - # - # ComputeDriver implementation methods - # - def finish_migration(self, context, migration, instance, disk_info, - network_info, image_meta, resize_instance, - block_device_info=None, power_on=True): - # Ensure that the instance directory exists - instance_dir = common.InstanceAttributes(instance).instance_dir - if not os.path.exists(instance_dir): - fileutils.ensure_tree(instance_dir) - - # Step 1 - Setup the profile on the dest host - flavor.to_profile(self.client, - instance, network_info, block_device_info) - - # Step 2 - Open a websocket on the srct and and - # generate the container config - self._migrate(migration['source_compute'], instance) - - # Step 3 - Start the network and container - self.plug_vifs(instance, network_info) - self.client.container.get(instance.name).start(wait=True) - - def confirm_migration(self, migration, instance, network_info): - self.unplug_vifs(instance, network_info) - - self.client.profiles.get(instance.name).delete() - self.client.containers.get(instance.name).delete(wait=True) - - def finish_revert_migration(self, context, instance, network_info, - block_device_info=None, power_on=True): - self.client.containers.get(instance.name).start(wait=True) - - def pre_live_migration(self, context, instance, block_device_info, - network_info, disk_info, migrate_data=None): - for vif in network_info: - self.vif_driver.plug(instance, vif) - 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) - - flavor.to_profile(self.client, - instance, network_info, block_device_info) - - def live_migration(self, context, instance, dest, - post_method, recover_method, block_migration=False, - migrate_data=None): - self._migrate(dest, instance) - post_method(context, instance, dest, block_migration) - - def post_live_migration(self, context, instance, block_device_info, - migrate_data=None): - self.client.containers.get(instance.name).delete(wait=True) - - def post_live_migration_at_source(self, context, instance, network_info): - self.client.profiles.get(instance.name).delete() - self.cleanup(context, instance, network_info) - - def check_can_live_migrate_destination( - self, context, instance, src_compute_info, dst_compute_info, - block_migration=False, disk_over_commit=False): - try: - self.client.containers.get(instance.name) - raise exception.InstanceExists(name=instance.name) - except lxd_exceptions.LXDAPIException as e: - if e.response.status_code != 404: - raise - return LXDLiveMigrateData() - - def cleanup_live_migration_destination_check( - self, context, dest_check_data): - return - - def check_can_live_migrate_source(self, context, instance, - dest_check_data, block_device_info=None): - if not CONF.lxd.allow_live_migration: - msg = _("Live migration is not enabled.") - LOG.error(msg, instance=instance) - raise exception.MigrationPreCheckError(reason=msg) - return dest_check_data - - # - # LXDDriver "private" implementation methods - # - # XXX: rockstar (21 Nov 2016) - The methods and code below this line - # have not been through the cleanup process. We know the cleanup process - # is complete when there is no more code below this comment, and the - # comment can be removed. - def _add_configdrive(self, context, instance, - injected_files, admin_password, network_info): - """Create configdrive for the instance.""" - if CONF.config_drive_format != 'iso9660': - raise exception.ConfigDriveUnsupportedFormat( - format=CONF.config_drive_format) - - container = self.client.containers.get(instance.name) - storage_id = 0 - """ - Determine UID shift used for container uid mapping - Sample JSON config from LXD - { - "volatile.apply_template": "create", - ... - "volatile.last_state.idmap": "[ - { - \"Isuid\":true, - \"Isgid\":false, - \"Hostid\":100000, - \"Nsid\":0, - \"Maprange\":65536 - }, - { - \"Isuid\":false, - \"Isgid\":true, - \"Hostid\":100000, - \"Nsid\":0, - \"Maprange\":65536 - }] ", - "volatile.tap5fd6808a-7b.name": "eth0" - } - """ - container_id_map = jsonutils.loads( - container.config['volatile.last_state.idmap']) - uid_map = list(filter(lambda id_map: id_map.get("Isuid"), - container_id_map)) - if uid_map: - storage_id = uid_map[0].get("Hostid", 0) - else: - # privileged containers does not have uid/gid mapping - # LXD API return nothing - pass - - extra_md = {} - if admin_password: - extra_md['admin_pass'] = admin_password - - inst_md = instance_metadata.InstanceMetadata( - instance, content=injected_files, extra_md=extra_md, - network_info=network_info, request_context=context) - - iso_path = os.path.join( - common.InstanceAttributes(instance).instance_dir, - 'configdrive.iso') - - with configdrive.ConfigDriveBuilder(instance_md=inst_md) as cdb: - try: - cdb.make_drive(iso_path) - except processutils.ProcessExecutionError as e: - with excutils.save_and_reraise_exception(): - LOG.error("Creating config drive failed with error: {}" - .format(e), instance=instance) - - configdrive_dir = os.path.join( - nova.conf.CONF.instances_path, instance.name, 'configdrive') - if not os.path.exists(configdrive_dir): - fileutils.ensure_tree(configdrive_dir) - - with utils.tempdir() as tmpdir: - mounted = False - try: - _, err = utils.execute('mount', - '-o', - 'loop,uid=%d,gid=%d' % (os.getuid(), - os.getgid()), - iso_path, tmpdir, - run_as_root=True) - mounted = True - - # Copy and adjust the files from the ISO so that we - # dont have the ISO mounted during the life cycle of the - # instance and the directory can be removed once the instance - # is terminated - for ent in os.listdir(tmpdir): - shutil.copytree(os.path.join(tmpdir, ent), - os.path.join(configdrive_dir, ent)) - - utils.execute('chmod', '-R', '775', configdrive_dir, - run_as_root=True) - utils.execute('chown', '-R', - '%s:%s' % (storage_id, storage_id), - configdrive_dir, run_as_root=True) - finally: - if mounted: - utils.execute('umount', tmpdir, run_as_root=True) - - return configdrive_dir - - def _after_reboot(self): - """Perform sync operation after host reboot.""" - context = nova.context.get_admin_context() - instances = objects.InstanceList.get_by_host( - context, self.host, expected_attrs=['info_cache', 'metadata']) - - for instance in instances: - if (instance.vm_state != vm_states.STOPPED): - continue - try: - network_info = self.network_api.get_instance_nw_info( - context, instance) - except exception.InstanceNotFound: - network_info = network_model.NetworkInfo() - - self.plug_vifs(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 _migrate(self, source_host, instance): - """Migrate an instance from source.""" - source_client = pylxd.Client( - endpoint='https://{}'.format(source_host), verify=False) - container = source_client.containers.get(instance.name) - data = container.generate_migration_data() - - self.containers.create(data, wait=True) diff --git a/nova/virt/lxd/flavor.py b/nova/virt/lxd/flavor.py deleted file mode 100644 index 21026a45..00000000 --- a/nova/virt/lxd/flavor.py +++ /dev/null @@ -1,234 +0,0 @@ -# Copyright 2016 Canonical Ltd -# 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 nova import exception -from nova import i18n -from nova.virt import driver -from oslo_config import cfg -from oslo_utils import units - -from nova.virt.lxd import common -from nova.virt.lxd import vif - -_ = i18n._ -CONF = cfg.CONF - - -def _base_config(instance, _): - instance_attributes = common.InstanceAttributes(instance) - return { - 'environment.product_name': 'OpenStack Nova', - 'raw.lxc': 'lxc.console.logfile={}\n'.format( - instance_attributes.console_path), - } - - -def _nesting(instance, _): - if instance.flavor.extra_specs.get('lxd:nested_allowed'): - return {'security.nesting': 'True'} - - -def _security(instance, _): - if instance.flavor.extra_specs.get('lxd:privileged_allowed'): - return {'security.privileged': 'True'} - - -def _memory(instance, _): - mem = instance.memory_mb - if mem >= 0: - return {'limits.memory': '{}MB'.format(mem)} - - -def _cpu(instance, _): - vcpus = instance.flavor.vcpus - if vcpus >= 0: - return {'limits.cpu': str(vcpus)} - - -def _isolated(instance, client): - lxd_isolated = instance.flavor.extra_specs.get('lxd:isolated') - if lxd_isolated: - extensions = client.host_info.get('api_extensions', []) - if 'id_map' in extensions: - return {'security.idmap.isolated': 'True'} - else: - msg = _("Host does not support isolated instances") - raise exception.NovaException(msg) - - -_CONFIG_FILTER_MAP = [ - _base_config, - _nesting, - _security, - _memory, - _cpu, - _isolated, -] - - -def _root(instance, client, *_): - """Configure the root disk.""" - device = {'type': 'disk', 'path': '/'} - - # we don't do quotas if the CONF.lxd.pool is set and is dir or lvm, or if - # the environment['storage'] is dir or lvm. - if CONF.lxd.pool: - extensions = client.host_info.get('api_extensions', []) - if 'storage' in extensions: - device['pool'] = CONF.lxd.pool - storage_type = client.storage_pools.get(CONF.lxd.pool).driver - else: - msg = _("Host does not have storage pool support") - raise exception.NovaException(msg) - else: - storage_type = client.host_info['environment']['storage'] - - if storage_type in ['btrfs', 'zfs']: - device['size'] = '{}GB'.format(instance.root_gb) - - specs = instance.flavor.extra_specs - - # Bytes and iops are not separate config options in a container - # profile - we let Bytes take priority over iops if both are set. - # Align all limits to MiB/s, which should be a sensible middle road. - if specs.get('quota:disk_read_iops_sec'): - device['limits.read'] = '{}iops'.format( - specs['quota:disk_read_iops_sec']) - if specs.get('quota:disk_write_iops_sec'): - device['limits.write'] = '{}iops'.format( - specs['quota:disk_write_iops_sec']) - - if specs.get('quota:disk_read_bytes_sec'): - device['limits.read'] = '{}MB'.format( - int(specs['quota:disk_read_bytes_sec']) // units.Mi) - if specs.get('quota:disk_write_bytes_sec'): - device['limits.write'] = '{}MB'.format( - int(specs['quota:disk_write_bytes_sec']) // units.Mi) - - minor_quota_defined = ('limits.write' in device or - 'limits.read' in device) - if specs.get('quota:disk_total_iops_sec') and not minor_quota_defined: - device['limits.max'] = '{}iops'.format( - specs['quota:disk_total_iops_sec']) - if specs.get('quota:disk_total_bytes_sec') and not minor_quota_defined: - device['limits.max'] = '{}MB'.format( - int(specs['quota:disk_total_bytes_sec']) // units.Mi) - - return {'root': device} - - -def _ephemeral_storage(instance, client, __, block_info): - instance_attributes = common.InstanceAttributes(instance) - ephemeral_storage = driver.block_device_info_get_ephemerals(block_info) - if ephemeral_storage: - devices = {} - for ephemeral in ephemeral_storage: - ephemeral_src = os.path.join( - instance_attributes.storage_path, - ephemeral['virtual_name']) - device = { - 'path': '/mnt', - 'source': ephemeral_src, - 'type': 'disk', - } - if CONF.lxd.pool: - extensions = client.host_info.get('api_extensions', []) - if 'storage' in extensions: - device['pool'] = CONF.lxd.pool - else: - msg = _("Host does not have storage pool support") - raise exception.NovaException(msg) - devices[ephemeral['virtual_name']] = device - return devices - - -def _network(instance, _, network_info, __): - if not network_info: - return - - devices = {} - for vifaddr in network_info: - cfg = vif.get_config(vifaddr) - devname = vif.get_vif_devname(vifaddr) - key = devname - devices[key] = { - 'nictype': 'physical', - 'hwaddr': str(cfg['mac_address']), - 'parent': vif.get_vif_internal_devname(vifaddr), - 'type': 'nic' - } - - specs = instance.flavor.extra_specs - # Since LXD does not implement average NIC IO and number of burst - # bytes, we take the max(vif_*_average, vif_*_peak) to set the peak - # network IO and simply ignore the burst bytes. - # Align values to MBit/s (8 * powers of 1000 in this case), having - # in mind that the values are recieved in Kilobytes/s. - vif_inbound_limit = max( - int(specs.get('quota:vif_inbound_average', 0)), - int(specs.get('quota:vif_inbound_peak', 0)), - ) - if vif_inbound_limit: - devices[key]['limits.ingress'] = '{}Mbit'.format( - vif_inbound_limit * units.k * 8 // units.M) - - vif_outbound_limit = max( - int(specs.get('quota:vif_outbound_average', 0)), - int(specs.get('quota:vif_outbound_peak', 0)), - ) - if vif_outbound_limit: - devices[key]['limits.egress'] = '{}Mbit'.format( - vif_outbound_limit * units.k * 8 // units.M) - return devices - - -_DEVICE_FILTER_MAP = [ - _root, - _ephemeral_storage, - _network, -] - - -def to_profile(client, instance, network_info, block_info, update=False): - """Convert a nova flavor to a lxd profile. - - Every instance container created via nova-lxd has a profile by the - same name. The profile is sync'd with the configuration of the container. - When the instance container is deleted, so is the profile. - """ - - name = instance.name - - config = {} - for f in _CONFIG_FILTER_MAP: - new = f(instance, client) - if new: - config.update(new) - - devices = {} - for f in _DEVICE_FILTER_MAP: - new = f(instance, client, network_info, block_info) - if new: - devices.update(new) - - if update is True: - profile = client.profiles.get(name) - profile.devices = devices - profile.config = config - profile.save() - return profile - else: - return client.profiles.create(name, config, devices) diff --git a/nova/virt/lxd/session.py b/nova/virt/lxd/session.py deleted file mode 100644 index 1524858d..00000000 --- a/nova/virt/lxd/session.py +++ /dev/null @@ -1,183 +0,0 @@ -# Copyright 2015 Canonical Ltd -# 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 nova.conf -from nova import context as nova_context -from nova import exception -from nova import i18n -from nova import rpc - -from oslo_log import log as logging -from oslo_utils import excutils - -from pylxd.deprecated import api -from pylxd.deprecated import exceptions as lxd_exceptions - -_ = i18n._ - -CONF = nova.conf.CONF -LOG = logging.getLogger(__name__) - - -class LXDAPISession(object): - """The session to invoke the LXD API session.""" - - def get_session(self, host=None): - """Returns a connection to the LXD hypervisor - - This method should be used to create a connection - to the LXD hypervisor via the pylxd API call. - - :param host: host is the LXD daemon to connect to - :return: pylxd object - """ - try: - if host: - return api.API(host=host) - else: - return api.API() - except Exception as ex: - # notify the compute host that the connection failed - # via an rpc call - LOG.exception("Connection to LXD failed") - payload = dict(ip=CONF.host, - method='_connect', - reason=ex) - rpc.get_notifier('compute').error(nova_context.get_admin_context, - 'compute.nova_lxd.error', - payload) - raise exception.HypervisorUnavailable(host=CONF.host) - - # - # Container related API methods - # - def container_init(self, config, instance, host=None): - """Create a LXD container - - :param config: LXD container config as a dict - :param instance: nova instance object - :param host: perform initialization on perfered host - """ - try: - LOG.info("Creating container {instance} with {image}" - .format(instance=instance.name, - image=instance.image_ref), - instance=instance) - - client = self.get_session(host=host) - (state, data) = client.container_init(config) - operation = data.get('operation') - self.operation_wait(operation, instance, host=host) - status, data = self.operation_info(operation, instance, host=host) - data = data.get('metadata') - if not data['status_code'] == 200: - msg = data.get('err') or data['metadata'] - raise exception.NovaException(msg) - - LOG.info("Successfully created container {instance} with {image}" - .format(instance=instance.name, - image=instance.image_ref), - instance=instance) - except lxd_exceptions.APIError as ex: - msg = (_("Failed to communicate with LXD API {instance}: {reason}") - .format(instance=instance.name, reason=ex)) - raise exception.NovaException(msg) - except Exception as ex: - with excutils.save_and_reraise_exception(): - LOG.error("Failed to create container {instance}: {reason}" - .format(instance=instance.name, reason=ex), - instance=instance) - - # - # Operation methods - # - - def operation_wait(self, operation_id, instance, host=None): - """Waits for an operation to return 200 (Success) - - :param operation_id: The operation to wait for. - :param instance: nova instace object - """ - LOG.debug("wait_for_container for instance", instance=instance) - try: - client = self.get_session(host=host) - if not client.wait_container_operation(operation_id, 200, -1): - msg = _("Container creation timed out") - raise exception.NovaException(msg) - except lxd_exceptions.APIError as ex: - msg = _("Failed to communicate with LXD API {instance}: " - "{reason}").format(instance=instance.image_ref, - reason=ex) - LOG.error(msg, instance=instance) - raise exception.NovaException(msg) - except Exception as e: - with excutils.save_and_reraise_exception(): - LOG.error("Error from LXD during operation wait " - "{instance}: {reason}" - .format(instance=instance.image_ref, reason=e)) - - def operation_info(self, operation_id, instance, host=None): - LOG.debug("operation_info called for instance", instance=instance) - try: - client = self.get_session(host=host) - return client.operation_info(operation_id) - except lxd_exceptions.APIError as ex: - msg = _("Failed to communicate with LXD API {instance}:" - " {reason}").format(instance=instance.image_ref, - reason=ex) - LOG.error(msg, instance=instance) - raise exception.NovaException(msg) - except Exception as e: - with excutils.save_and_reraise_exception(): - LOG.error("Error from LXD during operation_info " - "{instance}: {reason}" - .format(instance=instance.image_ref, reason=e)) - - # - # Migrate methods - # - def container_migrate(self, instance_name, host, instance): - """Initialize a container migration for LXD - - :param instance_name: container name - :param host: host to move container from - :param instance: nova instance object - :return: dictionary of the container keys - - """ - LOG.debug("container_migrate called for instance", instance=instance) - try: - LOG.info("Migrating instance {instance} with {image}" - .format(instance=instance_name, - image=instance.image_ref)) - - client = self.get_session() - (state, data) = client.container_migrate(instance_name) - - LOG.info("Successfully initialized migration for instance " - "{instance} with {image}" - .format(instance=instance.name, - image=instance.image_ref)) - return (state, data) - except lxd_exceptions.APIError as ex: - msg = _("Failed to communicate with LXD API {instance}:" - " {reason}").format(instance=instance.name, - reason=ex) - raise exception.NovaException(msg) - except Exception as ex: - with excutils.save_and_reraise_exception(): - LOG.error("Failed to migrate container {instance}: {reason}" - .format(instance=instance.name, reason=ex)) diff --git a/nova/virt/lxd/storage.py b/nova/virt/lxd/storage.py deleted file mode 100644 index 921044f6..00000000 --- a/nova/virt/lxd/storage.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright 2016 Canonical Ltd -# 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_config import cfg -from oslo_utils import fileutils -from nova import exception -from nova import i18n -from nova import utils -from nova.virt import driver - -from nova.virt.lxd import common - -_ = i18n._ -CONF = cfg.CONF - - -def attach_ephemeral(client, block_device_info, lxd_config, instance): - """Attach ephemeral storage to an instance.""" - ephemeral_storage = driver.block_device_info_get_ephemerals( - block_device_info) - if ephemeral_storage: - storage_driver = lxd_config['environment']['storage'] - - container = client.containers.get(instance.name) - container_id_map = container.config[ - 'volatile.last_state.idmap'].split(',') - storage_id = container_id_map[2].split(':')[1] - - instance_attrs = common.InstanceAttributes(instance) - for ephemeral in ephemeral_storage: - storage_dir = os.path.join( - instance_attrs.storage_path, ephemeral['virtual_name']) - if storage_driver == 'zfs': - # NOTE(ajkavanagh) - BUG/1782329 - this is temporary until - # storage pools is implemented. LXD 3 removed the - # storage.zfs_pool_name key from the config. So, if it fails, - # we need to grab the configured storage pool and use that as - # the name instead. - try: - zfs_pool = lxd_config['config']['storage.zfs_pool_name'] - except KeyError: - zfs_pool = CONF.lxd.pool - - utils.execute( - 'zfs', 'create', - '-o', 'mountpoint=%s' % storage_dir, - '-o', 'quota=%sG' % instance.ephemeral_gb, - '%s/%s-ephemeral' % (zfs_pool, instance.name), - run_as_root=True) - elif storage_driver == 'btrfs': - # We re-use the same btrfs subvolumes that LXD uses, - # so the ephemeral storage path is updated in the profile - # before the container starts. - storage_dir = os.path.join( - instance_attrs.container_path, ephemeral['virtual_name']) - profile = client.profiles.get(instance.name) - storage_name = ephemeral['virtual_name'] - profile.devices[storage_name]['source'] = storage_dir - profile.save() - - utils.execute( - 'btrfs', 'subvolume', 'create', storage_dir, - run_as_root=True) - utils.execute( - 'btrfs', 'qgroup', 'limit', - '%sg' % instance.ephemeral_gb, storage_dir, - run_as_root=True) - elif storage_driver == 'lvm': - fileutils.ensure_tree(storage_dir) - - lvm_pool = lxd_config['config']['storage.lvm_vg_name'] - lvm_volume = '%s-%s' % (instance.name, - ephemeral['virtual_name']) - lvm_path = '/dev/%s/%s' % (lvm_pool, lvm_volume) - - cmd = ( - 'lvcreate', '-L', '%sG' % instance.ephemeral_gb, - '-n', lvm_volume, lvm_pool) - utils.execute(*cmd, run_as_root=True, attempts=3) - - utils.execute('mkfs', '-t', 'ext4', - lvm_path, run_as_root=True) - cmd = ('mount', '-t', 'ext4', lvm_path, storage_dir) - utils.execute(*cmd, run_as_root=True) - else: - reason = _("Unsupport LXD storage detected. Supported" - " storage drivers are zfs and btrfs.") - raise exception.NovaException(reason) - - utils.execute( - 'chown', storage_id, - storage_dir, run_as_root=True) - - -def detach_ephemeral(client, block_device_info, lxd_config, instance): - """Detach ephemeral device from the instance.""" - ephemeral_storage = driver.block_device_info_get_ephemerals( - block_device_info) - if ephemeral_storage: - storage_driver = lxd_config['environment']['storage'] - - for ephemeral in ephemeral_storage: - if storage_driver == 'zfs': - # NOTE(ajkavanagh) - BUG/1782329 - this is temporary until - # storage pools is implemented. LXD 3 removed the - # storage.zfs_pool_name key from the config. So, if it fails, - # we need to grab the configured storage pool and use that as - # the name instead. - try: - zfs_pool = lxd_config['config']['storage.zfs_pool_name'] - except KeyError: - zfs_pool = CONF.lxd.pool - - utils.execute( - 'zfs', 'destroy', - '%s/%s-ephemeral' % (zfs_pool, instance.name), - run_as_root=True) - if storage_driver == 'lvm': - lvm_pool = lxd_config['config']['storage.lvm_vg_name'] - - lvm_path = '/dev/%s/%s-%s' % ( - lvm_pool, instance.name, ephemeral['virtual_name']) - - utils.execute('umount', lvm_path, run_as_root=True) - utils.execute('lvremove', '-f', lvm_path, run_as_root=True) diff --git a/nova/virt/lxd/vif.py b/nova/virt/lxd/vif.py deleted file mode 100644 index 1cd7a715..00000000 --- a/nova/virt/lxd/vif.py +++ /dev/null @@ -1,335 +0,0 @@ -# Copyright (c) 2015 Canonical Ltd -# -# 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 conf -from nova import exception -from nova import utils -from nova.network import model as network_model -from nova.network import os_vif_util -from nova.privsep import linux_net - -import os_vif - - -CONF = conf.CONF - -LOG = logging.getLogger(__name__) - - -def get_vif_devname(vif): - """Get device name for a given vif.""" - if 'devname' in vif: - return vif['devname'] - return ("nic" + vif['id'])[:network_model.NIC_NAME_LEN] - - -def get_vif_internal_devname(vif): - """Get the internal device name for a given vif.""" - return get_vif_devname(vif).replace('tap', 'tin') - - -def _create_veth_pair(dev1_name, dev2_name, mtu=None): - """Create a pair of veth devices with the specified names, - deleting any previous devices with those names. - """ - for dev in [dev1_name, dev2_name]: - linux_net.delete_net_dev(dev) - - utils.execute('ip', 'link', 'add', dev1_name, 'type', 'veth', 'peer', - 'name', dev2_name, run_as_root=True) - - for dev in [dev1_name, dev2_name]: - utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True) - linux_net.set_device_mtu(dev, mtu) - - -def _add_bridge_port(bridge, dev): - utils.execute('brctl', 'addif', bridge, dev, run_as_root=True) - - -def _is_no_op_firewall(): - return CONF.firewall_driver == "nova.virt.firewall.NoopFirewallDriver" - - -def _is_ovs_vif_port(vif): - return vif['type'] == 'ovs' and not vif.is_hybrid_plug_enabled() - - -def _get_bridge_config(vif): - return { - 'bridge': vif['network']['bridge'], - 'mac_address': vif['address']} - - -def _get_ovs_config(vif): - if not _is_no_op_firewall() or vif.is_hybrid_plug_enabled(): - return { - 'bridge': ('qbr{}'.format(vif['id']))[:network_model.NIC_NAME_LEN], - 'mac_address': vif['address']} - else: - return { - 'bridge': vif['network']['bridge'], - 'mac_address': vif['address']} - - -def _get_tap_config(vif): - return {'mac_address': vif['address']} - - -def _ovs_vsctl(args): - full_args = ['ovs-vsctl', '--timeout=%s' % CONF.ovs_vsctl_timeout] + args - try: - return utils.execute(*full_args, run_as_root=True) - except Exception as e: - LOG.error("Unable to execute %(cmd)s. Exception: %(exception)s", - {'cmd': full_args, 'exception': e}) - raise exception.OvsConfigurationFailure(inner_exception=e) - - -def _create_ovs_vif_cmd(bridge, dev, iface_id, mac, - instance_id, interface_type=None): - cmd = ['--', '--if-exists', 'del-port', dev, '--', - 'add-port', bridge, dev, - '--', 'set', 'Interface', dev, - 'external-ids:iface-id=%s' % iface_id, - 'external-ids:iface-status=active', - 'external-ids:attached-mac=%s' % mac, - 'external-ids:vm-uuid=%s' % instance_id] - if interface_type: - cmd += ['type=%s' % interface_type] - return cmd - - -def _create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id, - mtu=None, interface_type=None): - _ovs_vsctl(_create_ovs_vif_cmd(bridge, dev, iface_id, - mac, instance_id, - interface_type)) - linux_net.set_device_mtu(dev, mtu) - - -def _delete_ovs_vif_port(bridge, dev, delete_dev=True): - _ovs_vsctl(['--', '--if-exists', 'del-port', bridge, dev]) - if delete_dev: - linux_net.delete_net_dev(dev) - - -CONFIG_GENERATORS = { - 'bridge': _get_bridge_config, - 'ovs': _get_ovs_config, - 'tap': _get_tap_config, -} - - -def get_config(vif): - """Get LXD specific config for a vif.""" - vif_type = vif['type'] - - try: - return CONFIG_GENERATORS[vif_type](vif) - except KeyError: - raise exception.NovaException( - 'Unsupported vif type: {}'.format(vif_type)) - - -# VIF_TYPE_OVS = 'ovs' -# VIF_TYPE_BRIDGE = 'bridge' -def _post_plug_wiring_veth_and_bridge(instance, vif): - """Wire/plug the virtual interface for the instance into the bridge that - lxd is using. - - :param instance: the instance to plug into the bridge - :type instance: ??? - :param vif: the virtual interface to plug into the bridge - :type vif: :class:`nova.network.model.VIF` - """ - config = get_config(vif) - network = vif.get('network') - mtu = network.get_meta('mtu') if network else None - v1_name = get_vif_devname(vif) - v2_name = get_vif_internal_devname(vif) - if not linux_net.device_exists(v1_name): - _create_veth_pair(v1_name, v2_name, mtu) - if _is_ovs_vif_port(vif): - # NOTE(jamespage): wire tap device directly to ovs bridge - _create_ovs_vif_port(vif['network']['bridge'], - v1_name, - vif['id'], - vif['address'], - instance.uuid, - mtu) - else: - # NOTE(jamespage): wire tap device linux bridge - _add_bridge_port(config['bridge'], v1_name) - else: - linux_net.set_device_mtu(v1_name, mtu) - - -POST_PLUG_WIRING = { - 'bridge': _post_plug_wiring_veth_and_bridge, - 'ovs': _post_plug_wiring_veth_and_bridge, -} - - -def _post_plug_wiring(instance, vif): - """Perform nova-lxd specific post os-vif plug processing - - Perform any post os-vif plug wiring required to network - the instance LXD container with the underlying Neutron - network infrastructure - - :param instance: the instance to plug into the bridge - :type instance: ??? - :param vif: the virtual interface to plug into the bridge - :type vif: :class:`nova.network.model.VIF` - """ - - LOG.debug("Performing post plug wiring for VIF {}".format(vif), - instance=instance) - vif_type = vif['type'] - - try: - POST_PLUG_WIRING[vif_type](instance, vif) - LOG.debug("Post plug wiring step for VIF {} done".format(vif), - instance=instance) - except KeyError: - LOG.debug("No post plug wiring step " - "for vif type: {}".format(vif_type), - instance=instance) - - -# VIF_TYPE_OVS = 'ovs' -# VIF_TYPE_BRIDGE = 'bridge' -def _post_unplug_wiring_delete_veth(instance, vif): - """Wire/plug the virtual interface for the instance into the bridge that - lxd is using. - - :param instance: the instance to plug into the bridge - :type instance: ??? - :param vif: the virtual interface to plug into the bridge - :type vif: :class:`nova.network.model.VIF` - """ - v1_name = get_vif_devname(vif) - try: - if _is_ovs_vif_port(vif): - _delete_ovs_vif_port(vif['network']['bridge'], - v1_name, True) - else: - linux_net.delete_net_dev(v1_name) - except processutils.ProcessExecutionError: - LOG.exception("Failed to delete veth for vif {}".foramt(vif), - instance=instance) - - -POST_UNPLUG_WIRING = { - 'bridge': _post_unplug_wiring_delete_veth, - 'ovs': _post_unplug_wiring_delete_veth, -} - - -def _post_unplug_wiring(instance, vif): - """Perform nova-lxd specific post os-vif unplug processing - - Perform any post os-vif unplug wiring required to remove - network interfaces assocaited with a lxd container. - - :param instance: the instance to plug into the bridge - :type instance: :class:`nova.db.sqlalchemy.models.Instance` - :param vif: the virtual interface to plug into the bridge - :type vif: :class:`nova.network.model.VIF` - """ - - LOG.debug("Performing post unplug wiring for VIF {}".format(vif), - instance=instance) - vif_type = vif['type'] - - try: - POST_UNPLUG_WIRING[vif_type](instance, vif) - LOG.debug("Post unplug wiring for VIF {} done".format(vif), - instance=instance) - except KeyError: - LOG.debug("No post unplug wiring step " - "for vif type: {}".format(vif_type), - instance=instance) - - -class LXDGenericVifDriver(object): - """Generic VIF driver for LXD networking.""" - - def __init__(self): - os_vif.initialize() - - def plug(self, instance, vif): - vif_type = vif['type'] - instance_info = os_vif_util.nova_to_osvif_instance(instance) - - # Try os-vif codepath first - vif_obj = os_vif_util.nova_to_osvif_vif(vif) - if vif_obj is not None: - os_vif.plug(vif_obj, instance_info) - else: - # Legacy non-os-vif codepath - func = getattr(self, 'plug_%s' % vif_type, None) - if not func: - raise exception.InternalError( - "Unexpected vif_type=%s" % vif_type - ) - func(instance, vif) - - _post_plug_wiring(instance, vif) - - def unplug(self, instance, vif): - vif_type = vif['type'] - instance_info = os_vif_util.nova_to_osvif_instance(instance) - - # Try os-vif codepath first - vif_obj = os_vif_util.nova_to_osvif_vif(vif) - if vif_obj is not None: - os_vif.unplug(vif_obj, instance_info) - else: - # Legacy non-os-vif codepath - func = getattr(self, 'unplug_%s' % vif_type, None) - if not func: - raise exception.InternalError( - "Unexpected vif_type=%s" % vif_type - ) - func(instance, vif) - - _post_unplug_wiring(instance, vif) - - def plug_tap(self, instance, vif): - """Plug a VIF_TYPE_TAP virtual interface.""" - v1_name = get_vif_devname(vif) - v2_name = get_vif_internal_devname(vif) - network = vif.get('network') - mtu = network.get_meta('mtu') if network else None - # NOTE(jamespage): For nova-lxd this is really a veth pair - # so that a) security rules get applied on the host - # and b) that the container can still be wired. - if not linux_net.device_exists(v1_name): - _create_veth_pair(v1_name, v2_name, mtu) - else: - linux_net.set_device_mtu(v1_name, mtu) - - def unplug_tap(self, instance, vif): - """Unplug a VIF_TYPE_TAP virtual interface.""" - dev = get_vif_devname(vif) - try: - linux_net.delete_net_dev(dev) - except processutils.ProcessExecutionError: - LOG.exception("Failed while unplugging vif for instance", - instance=instance) diff --git a/nova_lxd_tempest_plugin/README b/nova_lxd_tempest_plugin/README deleted file mode 100644 index c00f2d5c..00000000 --- a/nova_lxd_tempest_plugin/README +++ /dev/null @@ -1,3 +0,0 @@ -To run tempest specific tests for nova-lxd run the following command: - - tox -e all-plugin -- nova_lxd diff --git a/nova_lxd_tempest_plugin/__init__.py b/nova_lxd_tempest_plugin/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nova_lxd_tempest_plugin/plugin.py b/nova_lxd_tempest_plugin/plugin.py deleted file mode 100644 index 8cfb5e15..00000000 --- a/nova_lxd_tempest_plugin/plugin.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2016 Canonical Ltd -# 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 tempest.test_discover import plugins - - -class MyPlugin(plugins.TempestPlugin): - def load_tests(self): - base_path = os.path.split(os.path.dirname( - os.path.abspath(__file__)))[0] - test_dir = "nova_lxd_tempest_plugin/tests" - full_test_dir = os.path.join(base_path, test_dir) - return full_test_dir, base_path - - def register_opts(self, conf): - pass - - def get_opt_lists(self): - pass diff --git a/nova_lxd_tempest_plugin/tests/__init__.py b/nova_lxd_tempest_plugin/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nova_lxd_tempest_plugin/tests/api/__init__.py b/nova_lxd_tempest_plugin/tests/api/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nova_lxd_tempest_plugin/tests/api/compute/__init__.py b/nova_lxd_tempest_plugin/tests/api/compute/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nova_lxd_tempest_plugin/tests/api/compute/servers/__init__.py b/nova_lxd_tempest_plugin/tests/api/compute/servers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nova_lxd_tempest_plugin/tests/api/compute/servers/test_create_server.py b/nova_lxd_tempest_plugin/tests/api/compute/servers/test_create_server.py deleted file mode 100644 index 5506ca88..00000000 --- a/nova_lxd_tempest_plugin/tests/api/compute/servers/test_create_server.py +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright 2016 Canonical Ltd -# 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 pylxd import client - -from tempest.api.compute import base -from tempest import config -from tempest.lib.common.utils import data_utils - -CONF = config.CONF - - -class LXDServersTestJSON(base.BaseV2ComputeAdminTest): - disk_config = 'AUTO' - - @classmethod - def setup_credentials(cls): - cls.prepare_instance_network() - super(LXDServersTestJSON, cls).setup_credentials() - - @classmethod - def setup_clients(cls): - super(LXDServersTestJSON, cls).setup_clients() - cls.lxd = client.Client() - cls.client = cls.os_admin.servers_client - cls.flavors_client = cls.os_admin.flavors_client - - @classmethod - def resource_setup(cls): - cls.set_validation_resources() - super(LXDServersTestJSON, cls).resource_setup() - cls.meta = {'hello': 'world'} - cls.accessIPv4 = '1.1.1.1' - cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2' - cls.name = data_utils.rand_name(cls.__name__ + '-server') - cls.password = data_utils.rand_password() - disk_config = cls.disk_config - cls.server_initial = cls.create_test_server( - validatable=True, - wait_until='ACTIVE', - name=cls.name, - metadata=cls.meta, - accessIPv4=cls.accessIPv4, - accessIPv6=cls.accessIPv6, - disk_config=disk_config, - adminPass=cls.password) - cls.server = ( - cls.client.show_server(cls.server_initial['id'])['server']) - - def test_profile_configuration(self): - # Verify that the profile was created - profile = self.lxd.profiles.get( - self.server['OS-EXT-SRV-ATTR:instance_name']) - - self.assertEqual( - self.server['OS-EXT-SRV-ATTR:instance_name'], profile.name) - - self.assertIn('raw.lxc', profile.config) - self.assertIn('boot.autostart', profile.config) - self.assertIn('limits.cpu', profile.config) - self.assertIn('limits.memory', profile.config) - - self.assertIn('root', profile.devices) - - def test_verify_created_server_vcpus(self): - # Verify that the number of vcpus reported by the instance matches - # the amount stated by the flavor - flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] - - profile = self.lxd.profiles.get( - self.server['OS-EXT-SRV-ATTR:instance_name']) - self.assertEqual( - '%s' % flavor['vcpus'], profile.config['limits.cpu']) - - def test_verify_created_server_memory(self): - # Verify that the memory reported by the instance matches - # the amount stated by the flavor - flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] - - profile = self.lxd.profiles.get( - self.server['OS-EXT-SRV-ATTR:instance_name']) - self.assertEqual( - '%sMB' % flavor['ram'], profile.config['limits.memory']) - - def test_verify_server_root_size(self): - flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] - - profile = self.lxd.profiles.get( - self.server['OS-EXT-SRV-ATTR:instance_name']) - self.assertEqual( - '%sGB' % flavor['disk'], profile.devices['root']['size']) - - def test_verify_console_log(self): - # Verify that the console log for the container exists - profile = self.lxd.profiles.get( - self.server['OS-EXT-SRV-ATTR:instance_name']) - self.assertIn('lxc.console.logfile', profile.config['raw.lxc']) - - def test_verify_network_configuration(self): - # Verify network is configured for the instance - profile = self.lxd.profiles.get( - self.server['OS-EXT-SRV-ATTR:instance_name']) - for device in profile.devices: - if 'root' not in device: - network_device = device - self.assertEqual('nic', profile.devices[network_device]['type']) - self.assertEqual('bridged', profile.devices[network_device]['nictype']) - self.assertEqual( - network_device, profile.devices[network_device]['parent']) - - def test_container_configuration_valid(self): - # Verify container configuration is correct - profile = self.lxd.profiles.get( - self.server['OS-EXT-SRV-ATTR:instance_name']) - container = self.lxd.containers.get( - self.server['OS-EXT-SRV-ATTR:instance_name']) - flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor'] - - self.assertEqual(profile.name, container.profiles[0]) - self.assertIn('raw.lxc', container.expanded_config) - self.assertEqual( - '%s' % flavor['vcpus'], container.expanded_config['limits.cpu']) - self.assertEqual( - '%sMB' % flavor['ram'], container.expanded_config['limits.memory']) - - self.assertEqual( - '%sGB' % flavor['disk'], - container.expanded_devices['root']['size']) - - for device in profile.devices: - if 'root' not in device: - network_device = device - self.assertIn(network_device, container.expanded_devices) - self.assertEqual( - 'nic', container.expanded_devices[network_device]['type']) - self.assertEqual( - 'bridged', container.expanded_devices[network_device]['nictype']) - self.assertEqual( - network_device, - container.expanded_devices[network_device]['parent']) diff --git a/nova_lxd_tempest_plugin/tests/api/compute/servers/test_servers.py b/nova_lxd_tempest_plugin/tests/api/compute/servers/test_servers.py deleted file mode 100644 index ba48a629..00000000 --- a/nova_lxd_tempest_plugin/tests/api/compute/servers/test_servers.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright 2016 Canonical Ltd -# 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 pylxd import client - -from tempest.api.compute import base -from tempest import config -from tempest.lib.common.utils import data_utils -from tempest.lib.common.utils.linux import remote_client - -CONF = config.CONF - - -class LXDServersWithSpecificFlavorTestJSON(base.BaseV2ComputeAdminTest): - disk_config = 'AUTO' - - @classmethod - def setup_credentials(cls): - cls.prepare_instance_network() - super(LXDServersWithSpecificFlavorTestJSON, cls).setup_credentials() - - @classmethod - def setup_clients(cls): - super(LXDServersWithSpecificFlavorTestJSON, cls).setup_clients() - cls.flavor_client = cls.os_admin.flavors_client - cls.client = cls.os_admin.servers_client - - @classmethod - def resource_setup(cls): - cls.set_validation_resources() - - super(LXDServersWithSpecificFlavorTestJSON, cls).resource_setup() - - def test_verify_created_server_ephemeral_disk(self): - # Verify that the ephemeral disk is created when creating server - flavor_base = self.flavors_client.show_flavor( - self.flavor_ref)['flavor'] - - def create_flavor_with_extra_specs(): - flavor_with_eph_disk_name = data_utils.rand_name('eph_flavor') - flavor_with_eph_disk_id = data_utils.rand_int_id(start=1000) - - ram = flavor_base['ram'] - vcpus = flavor_base['vcpus'] - disk = flavor_base['disk'] - - # Create a flavor with extra specs - flavor = (self.flavor_client. - create_flavor(name=flavor_with_eph_disk_name, - ram=ram, vcpus=vcpus, disk=disk, - id=flavor_with_eph_disk_id, - ephemeral=1))['flavor'] - self.addCleanup(flavor_clean_up, flavor['id']) - - return flavor['id'] - - def create_flavor_without_extra_specs(): - flavor_no_eph_disk_name = data_utils.rand_name('no_eph_flavor') - flavor_no_eph_disk_id = data_utils.rand_int_id(start=1000) - - ram = flavor_base['ram'] - vcpus = flavor_base['vcpus'] - disk = flavor_base['disk'] - - # Create a flavor without extra specs - flavor = (self.flavor_client. - create_flavor(name=flavor_no_eph_disk_name, - ram=ram, vcpus=vcpus, disk=disk, - id=flavor_no_eph_disk_id))['flavor'] - self.addCleanup(flavor_clean_up, flavor['id']) - - return flavor['id'] - - def flavor_clean_up(flavor_id): - self.flavor_client.delete_flavor(flavor_id) - self.flavor_client.wait_for_resource_deletion(flavor_id) - - flavor_with_eph_disk_id = create_flavor_with_extra_specs() - - admin_pass = self.image_ssh_password - - server_with_eph_disk = self.create_test_server( - validatable=True, - wait_until='ACTIVE', - adminPass=admin_pass, - flavor=flavor_with_eph_disk_id) - - server_with_eph_disk = self.client.show_server( - server_with_eph_disk['id'])['server'] - - linux_client = remote_client.RemoteClient( - self.get_server_ip(server_with_eph_disk), - self.ssh_user, - admin_pass, - self.validation_resources['keypair']['private_key'], - server=server_with_eph_disk, - servers_client=self.client) - cmd = 'sudo touch /mnt/tempest.txt' - linux_client.exec_command(cmd) - - lxd = client.Client() - profile = lxd.profiles.get(server_with_eph_disk[ - 'OS-EXT-SRV-ATTR:instance_name']) - tempfile = '%s/tempest.txt' % profile.devices['ephemeral0']['source'] - self.assertTrue(os.path.exists(tempfile)) diff --git a/nova_lxd_tempest_plugin/tests/api/compute/volumes/__init__.py b/nova_lxd_tempest_plugin/tests/api/compute/volumes/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nova_lxd_tempest_plugin/tests/api/compute/volumes/test_attach_volume.py b/nova_lxd_tempest_plugin/tests/api/compute/volumes/test_attach_volume.py deleted file mode 100644 index 72730081..00000000 --- a/nova_lxd_tempest_plugin/tests/api/compute/volumes/test_attach_volume.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# Copyright 2016 Canonical Ltd -# 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 pylxd import client - -from tempest.api.compute import base -from tempest.common import waiters -from tempest import config -from tempest.lib.common.utils import data_utils - -CONF = config.CONF - - -class LXDVolumeTests(base.BaseV2ComputeAdminTest): - disk_config = 'AUTO' - - def __init__(self, *args, **kwargs): - super(LXDVolumeTests, self).__init__(*args, **kwargs) - self.attachment = None - - @classmethod - def setup_credentials(cls): - cls.prepare_instance_network() - super(LXDVolumeTests, cls).setup_credentials() - - @classmethod - def setup_clients(cls): - super(LXDVolumeTests, cls).setup_clients() - cls.lxd = client.Client() - cls.client = cls.os_admin.servers_client - cls.flavors_client = cls.os_admin.flavors_client - - @classmethod - def resource_setup(cls): - cls.set_validation_resources() - super(LXDVolumeTests, cls).resource_setup() - cls.meta = {'hello': 'world'} - cls.accessIPv4 = '1.1.1.1' - cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2' - cls.name = data_utils.rand_name(cls.__name__ + '-server') - cls.password = data_utils.rand_password() - disk_config = cls.disk_config - cls.server_initial = cls.create_test_server( - validatable=True, - wait_until='ACTIVE', - name=cls.name, - metadata=cls.meta, - accessIPv4=cls.accessIPv4, - accessIPv6=cls.accessIPv6, - disk_config=disk_config, - adminPass=cls.password) - cls.server = ( - cls.client.show_server(cls.server_initial['id'])['server']) - cls.device = CONF.compute.volume_device_name - - def _detach(self, server_id, volume_id): - if self.attachment: - self.servers_client.detach_volume(server_id, volume_id) - waiters.wait_for_volume_status(self.volumes_client, - volume_id, 'available') - - def _create_and_attach_volume(self, server): - # Create a volume and wait for it to become ready - vol_name = data_utils.rand_name(self.__class__.__name__ + '-volume') - volume = self.volumes_client.create_volume( - size=CONF.volume.volume_size, display_name=vol_name)['volume'] - self.addCleanup(self.delete_volume, volume['id']) - waiters.wait_for_volume_status(self.volumes_client, - volume['id'], 'available') - - # Attach the volume to the server - self.attachment = self.servers_client.attach_volume( - server['id'], - volumeId=volume['id'], - device='/dev/%s' % self.device)['volumeAttachment'] - waiters.wait_for_volume_status(self.volumes_client, - volume['id'], 'in-use') - - self.addCleanup(self._detach, server['id'], volume['id']) - return volume - - def test_create_server_and_attach_volume(self): - # Verify that LXD profile has the correct configuration - # for volumes - volume = self._create_and_attach_volume(self.server) - - profile = self.lxd.profiles.get( - self.server['OS-EXT-SRV-ATTR:instance_name']) - - self.assertIn(volume['id'], [device for device in profile.devices]) - self.assertEqual( - '/dev/%s' % self.device, profile.devices[volume['id']]['path']) diff --git a/nova_lxd_tempest_plugin/tests/scenario/__init__.py b/nova_lxd_tempest_plugin/tests/scenario/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nova_lxd_tempest_plugin/tests/scenario/manager.py b/nova_lxd_tempest_plugin/tests/scenario/manager.py deleted file mode 100644 index 20b3944e..00000000 --- a/nova_lxd_tempest_plugin/tests/scenario/manager.py +++ /dev/null @@ -1,697 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# Copyright 2013 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 subprocess - -from oslo_log import log -from oslo_serialization import jsonutils as json - -from tempest.common import compute -from tempest.common import image as common_image -from tempest.common.utils.linux import remote_client -from tempest.common.utils import net_utils -from tempest.common import waiters -from tempest import config -from tempest import exceptions -from tempest.lib.common.utils import data_utils -from tempest.lib.common.utils import test_utils -from tempest.lib import exceptions as lib_exc -import tempest.test - -CONF = config.CONF - -LOG = log.getLogger(__name__) - - -class ScenarioTest(tempest.test.BaseTestCase): - """Base class for scenario tests. Uses tempest own clients. """ - - credentials = ['primary'] - - @classmethod - def setup_clients(cls): - super(ScenarioTest, cls).setup_clients() - # Clients (in alphabetical order) - cls.flavors_client = cls.os_primary.flavors_client - cls.compute_floating_ips_client = ( - cls.os_primary.compute_floating_ips_client) - if CONF.service_available.glance: - # Check if glance v1 is available to determine which client to use. - if CONF.image_feature_enabled.api_v1: - cls.image_client = cls.os_primary.image_client - elif CONF.image_feature_enabled.api_v2: - cls.image_client = cls.os_primary.image_client_v2 - else: - raise lib_exc.InvalidConfiguration( - 'Either api_v1 or api_v2 must be True in ' - '[image-feature-enabled].') - # Compute image client - cls.compute_images_client = cls.os_primary.compute_images_client - cls.keypairs_client = cls.os_primary.keypairs_client - # Nova security groups client - cls.compute_security_groups_client = ( - cls.os_primary.compute_security_groups_client) - cls.compute_security_group_rules_client = ( - cls.os_primary.compute_security_group_rules_client) - cls.servers_client = cls.os_primary.servers_client - cls.interface_client = cls.os_primary.interfaces_client - # Neutron network client - cls.networks_client = cls.os_primary.networks_client - cls.ports_client = cls.os_primary.ports_client - cls.routers_client = cls.os_primary.routers_client - cls.subnets_client = cls.os_primary.subnets_client - cls.floating_ips_client = cls.os_primary.floating_ips_client - cls.security_groups_client = cls.os_primary.security_groups_client - cls.security_group_rules_client = ( - cls.os_primary.security_group_rules_client) - - if (CONF.volume_feature_enabled.api_v2 or - CONF.volume_feature_enabled.api_v3): - cls.volumes_client = cls.os_primary.volumes_client_latest - cls.snapshots_client = cls.os_primary.snapshots_client_latest - - # ## Test functions library - # - # The create_[resource] functions only return body and discard the - # resp part which is not used in scenario tests - - def _create_port(self, network_id, client=None, namestart='port-quotatest', - **kwargs): - if not client: - client = self.ports_client - name = data_utils.rand_name(namestart) - result = client.create_port( - name=name, - network_id=network_id, - **kwargs) - self.assertIsNotNone(result, 'Unable to allocate port') - port = result['port'] - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - client.delete_port, port['id']) - return port - - def create_keypair(self, client=None): - if not client: - client = self.keypairs_client - name = data_utils.rand_name(self.__class__.__name__) - # We don't need to create a keypair by pubkey in scenario - body = client.create_keypair(name=name) - self.addCleanup(client.delete_keypair, name) - return body['keypair'] - - def create_server(self, name=None, image_id=None, flavor=None, - validatable=False, wait_until='ACTIVE', - clients=None, **kwargs): - """Wrapper utility that returns a test server. - - This wrapper utility calls the common create test server and - returns a test server. The purpose of this wrapper is to minimize - the impact on the code of the tests already using this - function. - """ - - # NOTE(jlanoux): As a first step, ssh checks in the scenario - # tests need to be run regardless of the run_validation and - # validatable parameters and thus until the ssh validation job - # becomes voting in CI. The test resources management and IP - # association are taken care of in the scenario tests. - # Therefore, the validatable parameter is set to false in all - # those tests. In this way create_server just return a standard - # server and the scenario tests always perform ssh checks. - - # Needed for the cross_tenant_traffic test: - if clients is None: - clients = self.os_primary - - if name is None: - name = data_utils.rand_name(self.__class__.__name__ + "-server") - - vnic_type = CONF.network.port_vnic_type - - # If vnic_type is configured create port for - # every network - if vnic_type: - ports = [] - - create_port_body = {'binding:vnic_type': vnic_type, - 'namestart': 'port-smoke'} - if kwargs: - # Convert security group names to security group ids - # to pass to create_port - if 'security_groups' in kwargs: - security_groups = \ - clients.security_groups_client.list_security_groups( - ).get('security_groups') - sec_dict = dict([(s['name'], s['id']) - for s in security_groups]) - - sec_groups_names = [s['name'] for s in kwargs.pop( - 'security_groups')] - security_groups_ids = [sec_dict[s] - for s in sec_groups_names] - - if security_groups_ids: - create_port_body[ - 'security_groups'] = security_groups_ids - networks = kwargs.pop('networks', []) - else: - networks = [] - - # If there are no networks passed to us we look up - # for the project's private networks and create a port. - # The same behaviour as we would expect when passing - # the call to the clients with no networks - if not networks: - networks = clients.networks_client.list_networks( - **{'router:external': False, 'fields': 'id'})['networks'] - - # It's net['uuid'] if networks come from kwargs - # and net['id'] if they come from - # clients.networks_client.list_networks - for net in networks: - net_id = net.get('uuid', net.get('id')) - if 'port' not in net: - port = self._create_port(network_id=net_id, - client=clients.ports_client, - **create_port_body) - ports.append({'port': port['id']}) - else: - ports.append({'port': net['port']}) - if ports: - kwargs['networks'] = ports - self.ports = ports - - tenant_network = self.get_tenant_network() - - body, servers = compute.create_test_server( - clients, - tenant_network=tenant_network, - wait_until=wait_until, - name=name, flavor=flavor, - image_id=image_id, **kwargs) - - self.addCleanup(waiters.wait_for_server_termination, - clients.servers_client, body['id']) - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - clients.servers_client.delete_server, body['id']) - server = clients.servers_client.show_server(body['id'])['server'] - return server - - def create_volume(self, size=None, name=None, snapshot_id=None, - imageRef=None, volume_type=None): - if size is None: - size = CONF.volume.volume_size - if imageRef: - image = self.compute_images_client.show_image(imageRef)['image'] - min_disk = image.get('minDisk') - size = max(size, min_disk) - if name is None: - name = data_utils.rand_name(self.__class__.__name__ + "-volume") - kwargs = {'display_name': name, - 'snapshot_id': snapshot_id, - 'imageRef': imageRef, - 'volume_type': volume_type, - 'size': size} - volume = self.volumes_client.create_volume(**kwargs)['volume'] - - self.addCleanup(self.volumes_client.wait_for_resource_deletion, - volume['id']) - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - self.volumes_client.delete_volume, volume['id']) - - # NOTE(e0ne): Cinder API v2 uses name instead of display_name - if 'display_name' in volume: - self.assertEqual(name, volume['display_name']) - else: - self.assertEqual(name, volume['name']) - waiters.wait_for_volume_resource_status(self.volumes_client, - volume['id'], 'available') - # The volume retrieved on creation has a non-up-to-date status. - # Retrieval after it becomes active ensures correct details. - volume = self.volumes_client.show_volume(volume['id'])['volume'] - return volume - - def create_volume_type(self, client=None, name=None, backend_name=None): - if not client: - client = self.admin_volume_types_client - if not name: - class_name = self.__class__.__name__ - name = data_utils.rand_name(class_name + '-volume-type') - randomized_name = data_utils.rand_name('scenario-type-' + name) - - LOG.debug("Creating a volume type: {name} on backend {backend}" - .format(name=randomized_name, backend=backend_name)) - extra_specs = {} - if backend_name: - extra_specs = {"volume_backend_name": backend_name} - - body = client.create_volume_type(name=randomized_name, - extra_specs=extra_specs) - volume_type = body['volume_type'] - self.assertIn('id', volume_type) - self.addCleanup(client.delete_volume_type, volume_type['id']) - return volume_type - - def _create_loginable_secgroup_rule(self, secgroup_id=None): - _client = self.compute_security_groups_client - _client_rules = self.compute_security_group_rules_client - if secgroup_id is None: - sgs = _client.list_security_groups()['security_groups'] - for sg in sgs: - if sg['name'] == 'default': - secgroup_id = sg['id'] - - # These rules are intended to permit inbound ssh and icmp - # traffic from all sources, so no group_id is provided. - # Setting a group_id would only permit traffic from ports - # belonging to the same security group. - rulesets = [ - { - # ssh - 'ip_protocol': 'tcp', - 'from_port': 22, - 'to_port': 22, - 'cidr': '0.0.0.0/0', - }, - { - # ping - 'ip_protocol': 'icmp', - 'from_port': -1, - 'to_port': -1, - 'cidr': '0.0.0.0/0', - } - ] - rules = list() - for ruleset in rulesets: - sg_rule = _client_rules.create_security_group_rule( - parent_group_id=secgroup_id, **ruleset)['security_group_rule'] - rules.append(sg_rule) - return rules - - def _create_security_group(self): - # Create security group - sg_name = data_utils.rand_name(self.__class__.__name__) - sg_desc = sg_name + " description" - secgroup = self.compute_security_groups_client.create_security_group( - name=sg_name, description=sg_desc)['security_group'] - self.assertEqual(secgroup['name'], sg_name) - self.assertEqual(secgroup['description'], sg_desc) - self.addCleanup( - test_utils.call_and_ignore_notfound_exc, - self.compute_security_groups_client.delete_security_group, - secgroup['id']) - - # Add rules to the security group - self._create_loginable_secgroup_rule(secgroup['id']) - - return secgroup - - def get_remote_client(self, ip_address, username=None, private_key=None): - """Get a SSH client to a remote server - - @param ip_address the server floating or fixed IP address to use - for ssh validation - @param username name of the Linux account on the remote server - @param private_key the SSH private key to use - @return a RemoteClient object - """ - - if username is None: - username = CONF.validation.image_ssh_user - # Set this with 'keypair' or others to log in with keypair or - # username/password. - if CONF.validation.auth_method == 'keypair': - password = None - if private_key is None: - private_key = self.keypair['private_key'] - else: - password = CONF.validation.image_ssh_password - private_key = None - linux_client = remote_client.RemoteClient(ip_address, username, - pkey=private_key, - password=password) - try: - linux_client.validate_authentication() - except Exception as e: - message = ("Initializing SSH connection to {ip} failed. " - "Error: {error}" - .format(ip=ip_address, - error=e)) - caller = test_utils.find_test_caller() - if caller: - message = '(%s) %s' % (caller, message) - LOG.exception(message) - self._log_console_output() - raise - - return linux_client - - def _image_create(self, name, fmt, path, - disk_format=None, properties=None): - if properties is None: - properties = {} - name = data_utils.rand_name('%s-' % name) - params = { - 'name': name, - 'container_format': fmt, - 'disk_format': disk_format or fmt, - } - if CONF.image_feature_enabled.api_v1: - params['is_public'] = 'False' - params['properties'] = properties - params = {'headers': common_image.image_meta_to_headers(**params)} - else: - params['visibility'] = 'private' - # Additional properties are flattened out in the v2 API. - params.update(properties) - body = self.image_client.create_image(**params) - image = body['image'] if 'image' in body else body - self.addCleanup(self.image_client.delete_image, image['id']) - self.assertEqual("queued", image['status']) - with open(path, 'rb') as image_file: - if CONF.image_feature_enabled.api_v1: - self.image_client.update_image(image['id'], data=image_file) - else: - self.image_client.store_image_file(image['id'], image_file) - return image['id'] - - def glance_image_create(self): - img_path = CONF.scenario.img_dir + "/" + CONF.scenario.img_file - aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file - ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file - ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file - img_container_format = CONF.scenario.img_container_format - img_disk_format = CONF.scenario.img_disk_format - img_properties = CONF.scenario.img_properties - LOG.debug("paths: img: {img}, container_format: {cf}, " - "disk_format: {df}, properties: {props}, ami: {ami}, " - "ari: {ari}, aki: {aki}" - .format(img=img_path, - cf=img_container_format, - df=img_disk_format, - props=img_properties, - ami=ami_img_path, - ari=ari_img_path, - aki=aki_img_path)) - try: - image = self._image_create('scenario-img', - img_container_format, - img_path, - disk_format=img_disk_format, - properties=img_properties) - except IOError: - LOG.debug("A qcow2 image was not found. Try to get a uec image.") - kernel = self._image_create('scenario-aki', 'aki', aki_img_path) - ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path) - properties = {'kernel_id': kernel, 'ramdisk_id': ramdisk} - image = self._image_create('scenario-ami', 'ami', - path=ami_img_path, - properties=properties) - LOG.debug("image: {}".format(image)) - - return image - - def _log_console_output(self, servers=None): - if not CONF.compute_feature_enabled.console_output: - LOG.debug("Console output not supported, cannot log") - return - if not servers: - servers = self.servers_client.list_servers() - servers = servers['servers'] - for server in servers: - try: - console_output = self.servers_client.get_console_output( - server['id'])['output'] - LOG.debug("Console output for {}\nbody=\n{}" - .format(server['id'], console_output)) - except lib_exc.NotFound: - LOG.debug("Server {} disappeared(deleted) while looking " - "for the console log".format(server['id'])) - - def _log_net_info(self, exc): - # network debug is called as part of ssh init - if not isinstance(exc, lib_exc.SSHTimeout): - LOG.debug("Network information on a devstack host") - - def create_server_snapshot(self, server, name=None): - # Glance client - _image_client = self.image_client - # Compute client - _images_client = self.compute_images_client - if name is None: - name = data_utils.rand_name(self.__class__.__name__ + 'snapshot') - LOG.debug("Creating a snapshot image for server: {}" - .format(server['name'])) - image = _images_client.create_image(server['id'], name=name) - image_id = image.response['location'].split('images/')[1] - waiters.wait_for_image_status(_image_client, image_id, 'active') - - self.addCleanup(_image_client.wait_for_resource_deletion, - image_id) - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - _image_client.delete_image, image_id) - - if CONF.image_feature_enabled.api_v1: - # In glance v1 the additional properties are stored in the headers. - resp = _image_client.check_image(image_id) - snapshot_image = common_image.get_image_meta_from_headers(resp) - image_props = snapshot_image.get('properties', {}) - else: - # In glance v2 the additional properties are flattened. - snapshot_image = _image_client.show_image(image_id) - image_props = snapshot_image - - bdm = image_props.get('block_device_mapping') - if bdm: - bdm = json.loads(bdm) - if bdm and 'snapshot_id' in bdm[0]: - snapshot_id = bdm[0]['snapshot_id'] - self.addCleanup( - self.snapshots_client.wait_for_resource_deletion, - snapshot_id) - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - self.snapshots_client.delete_snapshot, - snapshot_id) - waiters.wait_for_volume_resource_status(self.snapshots_client, - snapshot_id, - 'available') - image_name = snapshot_image['name'] - self.assertEqual(name, image_name) - LOG.debug("Created snapshot image {} for server {}" - .format(image_name, server['name'])) - return snapshot_image - - def nova_volume_attach(self, server, volume_to_attach): - volume = self.servers_client.attach_volume( - server['id'], volumeId=volume_to_attach['id'], device='/dev/%s' - % CONF.compute.volume_device_name)['volumeAttachment'] - self.assertEqual(volume_to_attach['id'], volume['id']) - waiters.wait_for_volume_resource_status(self.volumes_client, - volume['id'], 'in-use') - - # Return the updated volume after the attachment - return self.volumes_client.show_volume(volume['id'])['volume'] - - def nova_volume_detach(self, server, volume): - self.servers_client.detach_volume(server['id'], volume['id']) - waiters.wait_for_volume_resource_status(self.volumes_client, - volume['id'], 'available') - - volume = self.volumes_client.show_volume(volume['id'])['volume'] - self.assertEqual('available', volume['status']) - - def rebuild_server(self, server_id, image=None, - preserve_ephemeral=False, wait=True, - rebuild_kwargs=None): - if image is None: - image = CONF.compute.image_ref - - rebuild_kwargs = rebuild_kwargs or {} - - LOG.debug("Rebuilding server (id: {_id}, image: {image}, " - "preserve eph: {ephemeral})" - .format(_id=server_id, - image=image, - ephemeral=preserve_ephemeral)) - self.servers_client.rebuild_server( - server_id=server_id, image_ref=image, - preserve_ephemeral=preserve_ephemeral, - **rebuild_kwargs) - if wait: - waiters.wait_for_server_status(self.servers_client, - server_id, 'ACTIVE') - - def ping_ip_address(self, ip_address, should_succeed=True, - ping_timeout=None, mtu=None): - timeout = ping_timeout or CONF.validation.ping_timeout - cmd = ['ping', '-c1', '-w1'] - - if mtu: - cmd += [ - # don't fragment - '-M', 'do', - # ping receives just the size of ICMP payload - '-s', str(net_utils.get_ping_payload_size(mtu, 4)) - ] - cmd.append(ip_address) - - def ping(): - proc = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc.communicate() - - return (proc.returncode == 0) == should_succeed - - caller = test_utils.find_test_caller() - LOG.debug("{caller} begins to ping {ip} in {timeout} sec and the" - " expected result is {should_succeed}" - .format(caller=caller, - ip=ip_address, - timeout=timeout, - should_succeed=('reachable' if should_succeed - else 'unreachable'))) - result = test_utils.call_until_true(ping, timeout, 1) - LOG.debug("{caller} finishes ping {ip} in {timeout} sec and the " - "ping result is {result}" - .format(caller=caller, - ip=ip_address, - timeout=timeout, - result='expected' if result else 'unexpected')) - return result - - def check_vm_connectivity(self, ip_address, - username=None, - private_key=None, - should_connect=True, - mtu=None): - """Check server connectivity - - :param ip_address: server to test against - :param username: server's ssh username - :param private_key: server's ssh private key to be used - :param should_connect: True/False indicates positive/negative test - positive - attempt ping and ssh - negative - attempt ping and fail if succeed - :param mtu: network MTU to use for connectivity validation - - :raises: AssertError if the result of the connectivity check does - not match the value of the should_connect param - """ - if should_connect: - msg = "Timed out waiting for %s to become reachable" % ip_address - else: - msg = "ip address %s is reachable" % ip_address - self.assertTrue(self.ping_ip_address(ip_address, - should_succeed=should_connect, - mtu=mtu), - msg=msg) - if should_connect: - # no need to check ssh for negative connectivity - self.get_remote_client(ip_address, username, private_key) - - def check_public_network_connectivity(self, ip_address, username, - private_key, should_connect=True, - msg=None, servers=None, mtu=None): - # The target login is assumed to have been configured for - # key-based authentication by cloud-init. - LOG.debug("checking network connections to IP {} with user: {}" - .format(ip_address, username)) - try: - self.check_vm_connectivity(ip_address, - username, - private_key, - should_connect=should_connect, - mtu=mtu) - except Exception: - ex_msg = 'Public network connectivity check failed' - if msg: - ex_msg += ": " + msg - LOG.exception(ex_msg) - self._log_console_output(servers) - raise - - def create_floating_ip(self, thing, pool_name=None): - """Create a floating IP and associates to a server on Nova""" - - if not pool_name: - pool_name = CONF.network.floating_network_name - floating_ip = (self.compute_floating_ips_client. - create_floating_ip(pool=pool_name)['floating_ip']) - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - self.compute_floating_ips_client.delete_floating_ip, - floating_ip['id']) - self.compute_floating_ips_client.associate_floating_ip_to_server( - floating_ip['ip'], thing['id']) - return floating_ip - - def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt', - private_key=None): - ssh_client = self.get_remote_client(ip_address, - private_key=private_key) - if dev_name is not None: - ssh_client.make_fs(dev_name) - ssh_client.mount(dev_name, mount_path) - cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path - ssh_client.exec_command(cmd_timestamp) - timestamp = ssh_client.exec_command('sudo cat %s/timestamp' - % mount_path) - if dev_name is not None: - ssh_client.umount(mount_path) - return timestamp - - def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt', - private_key=None): - ssh_client = self.get_remote_client(ip_address, - private_key=private_key) - if dev_name is not None: - ssh_client.mount(dev_name, mount_path) - timestamp = ssh_client.exec_command('sudo cat %s/timestamp' - % mount_path) - if dev_name is not None: - ssh_client.umount(mount_path) - return timestamp - - def get_server_ip(self, server): - """Get the server fixed or floating IP. - - Based on the configuration we're in, return a correct ip - address for validating that a guest is up. - """ - if CONF.validation.connect_method == 'floating': - # The tests calling this method don't have a floating IP - # and can't make use of the validation resources. So the - # method is creating the floating IP there. - return self.create_floating_ip(server)['ip'] - elif CONF.validation.connect_method == 'fixed': - # Determine the network name to look for based on config or creds - # provider network resources. - if CONF.validation.network_for_ssh: - addresses = server['addresses'][ - CONF.validation.network_for_ssh] - else: - creds_provider = self._get_credentials_provider() - net_creds = creds_provider.get_primary_creds() - network = getattr(net_creds, 'network', None) - addresses = (server['addresses'][network['name']] - if network else []) - for address in addresses: - if (address['version'] == CONF.validation.ip_version_for_ssh - and address['OS-EXT-IPS:type'] == 'fixed'): - return address['addr'] - raise exceptions.ServerUnreachable(server_id=server['id']) - else: - raise lib_exc.InvalidConfiguration() diff --git a/nova_lxd_tempest_plugin/tests/scenario/test_server_basic_ops.py b/nova_lxd_tempest_plugin/tests/scenario/test_server_basic_ops.py deleted file mode 100644 index 601a3ccb..00000000 --- a/nova_lxd_tempest_plugin/tests/scenario/test_server_basic_ops.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright 2106 Canonical Ltd -# Copyright 2012 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 tempest.common import utils -from tempest import config -from tempest import exceptions -from tempest.lib.common.utils import test_utils -from tempest.lib import decorators - -from nova_lxd_tempest_plugin.tests.scenario import manager -from oslo_serialization import jsonutils - -CONF = config.CONF - - -class TestServerBasicOps(manager.ScenarioTest): - - """The test suite for server basic operations - - This smoke test case follows this basic set of operations: - * Create a keypair for use in launching an instance - * Create a security group to control network access in instance - * Add simple permissive rules to the security group - * Launch an instance - * Perform ssh to instance - * Verify metadata service - * Verify metadata on config_drive - * Terminate the instance - """ - - def setUp(self): - super(TestServerBasicOps, self).setUp() - self.image_ref = CONF.compute.image_ref - self.flavor_ref = CONF.compute.flavor_ref - self.run_ssh = CONF.validation.run_validation - self.ssh_user = CONF.validation.image_ssh_user - - def verify_ssh(self, keypair): - if self.run_ssh: - # Obtain a floating IP - self.fip = self.create_floating_ip(self.instance)['ip'] - # Check ssh - self.ssh_client = self.get_remote_client( - ip_address=self.fip, - username=self.ssh_user, - private_key=keypair['private_key']) - - def verify_metadata(self): - if self.run_ssh and CONF.compute_feature_enabled.metadata_service: - # Verify metadata service - md_url = 'http://169.254.169.254/latest/meta-data/public-ipv4' - - def exec_cmd_and_verify_output(): - cmd = 'curl ' + md_url - result = self.ssh_client.exec_command(cmd) - if result: - msg = ('Failed while verifying metadata on server. Result ' - 'of command "%s" is NOT "%s".' % (cmd, self.fip)) - self.assertEqual(self.fip, result, msg) - return 'Verification is successful!' - - if not test_utils.call_until_true(exec_cmd_and_verify_output, - CONF.compute.build_timeout, - CONF.compute.build_interval): - raise exceptions.TimeoutException('Timed out while waiting to ' - 'verify metadata on server. ' - '%s is empty.' % md_url) - - def verify_metadata_on_config_drive(self): - if self.run_ssh and CONF.compute_feature_enabled.config_drive: - # Verify metadata on config_drive - cmd_md = \ - 'cat /var/lib/cloud/data/openstack/latest/meta_data.json' - result = self.ssh_client.exec_command(cmd_md) - result = jsonutils.loads(result) - self.assertIn('meta', result) - msg = ('Failed while verifying metadata on config_drive on server.' - ' Result of command "%s" is NOT "%s".' % (cmd_md, self.md)) - self.assertEqual(self.md, result['meta'], msg) - - def verify_networkdata_on_config_drive(self): - if self.run_ssh and CONF.compute_feature_enabled.config_drive: - # Verify network data on config_drive - cmd_md = \ - 'cat /var/lib/cloud/data/openstack/latest/network_data.json' - result = self.ssh_client.exec_command(cmd_md) - result = jsonutils.loads(result) - self.assertIn('services', result) - self.assertIn('links', result) - self.assertIn('networks', result) - # TODO(clarkb) construct network_data from known network - # instance info and do direct comparison. - - @decorators.idempotent_id('7fff3fb3-91d8-4fd0-bd7d-0204f1f180ba') - @decorators.attr(type='smoke') - @utils.services('compute', 'network') - def test_server_basic_ops(self): - keypair = self.create_keypair() - self.security_group = self._create_security_group() - security_groups = [{'name': self.security_group['name']}] - self.md = {'meta1': 'data1', 'meta2': 'data2', 'metaN': 'dataN'} - self.instance = self.create_server( - image_id=self.image_ref, - flavor=self.flavor_ref, - key_name=keypair['name'], - security_groups=security_groups, - config_drive=CONF.compute_feature_enabled.config_drive, - metadata=self.md, - wait_until='ACTIVE') - self.verify_ssh(keypair) - self.verify_metadata() - self.verify_metadata_on_config_drive() - self.verify_networkdata_on_config_drive() - self.servers_client.delete_server(self.instance['id']) diff --git a/nova_lxd_tempest_plugin/tests/scenario/test_volume_ops.py b/nova_lxd_tempest_plugin/tests/scenario/test_volume_ops.py deleted file mode 100644 index cf5e731f..00000000 --- a/nova_lxd_tempest_plugin/tests/scenario/test_volume_ops.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright 2013 NEC Corporation -# Copyright 2016 Canonical Ltd -# 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_log import log as logging - -from tempest import config -from tempest import exceptions -from tempest.lib.common.utils import test_utils - -from nova_lxd_tempest_plugin.tests.scenario import manager - -CONF = config.CONF -LOG = logging.getLogger(__name__) - - -class LXDVolumeScenario(manager.ScenarioTest): - """The test suite for attaching volume to an instance - - The following is the scenario outline: - 1. Boot an instance "instance" - 2. Create a volume "volume1" - 3. Attach volume1 to instance - 4. Create a filesystem on volume1 - 5. Mount volume1 - 6. Create a file which timestamp is written in volume1 - 7. Check for file on instnace1 - 7. Unmount volume1 - 8. Detach volume1 from instance1 - """ - - def setUp(self): - super(LXDVolumeScenario, self).setUp() - self.image_ref = CONF.compute.image_ref - self.flavor_ref = CONF.compute.flavor_ref - self.run_ssh = CONF.validation.run_validation - self.ssh_user = CONF.validation.image_ssh_user - - @classmethod - def skip_checks(cls): - super(LXDVolumeScenario, cls).skip_checks() - - def _wait_for_volume_available_on_the_system(self, ip_address, - private_key): - ssh = self.get_remote_client(ip_address, private_key=private_key) - - def _func(): - part = ssh.get_partitions() - LOG.debug("Partitions: {}".format(part)) - return CONF.compute.volume_device_name in part - - if not test_utils.call_until_true(_func, - CONF.compute.build_timeout, - CONF.compute.build_interval): - raise exceptions.TimeoutException - - def test_volume_attach(self): - keypair = self.create_keypair() - self.security_group = self._create_security_group() - security_groups = [{'name': self.security_group['name']}] - self.md = {'meta1': 'data1', 'meta2': 'data2', 'metaN': 'dataN'} - server = self.create_server( - image_id=self.image_ref, - flavor=self.flavor_ref, - key_name=keypair['name'], - security_groups=security_groups, - config_drive=CONF.compute_feature_enabled.config_drive, - metadata=self.md, - wait_until='ACTIVE') - - volume = self.create_volume() - - # create and add floating IP to server1 - ip_for_server = self.get_server_ip(server) - - self.nova_volume_attach(server, volume) - self._wait_for_volume_available_on_the_system(ip_for_server, - keypair['private_key']) - - ssh_client = self.get_remote_client( - ip_address=ip_for_server, - username=self.ssh_user, - private_key=keypair['private_key']) - - ssh_client.exec_command( - 'sudo /sbin/mke2fs -t ext4 /dev/%s' - % CONF.compute.volume_device_name) - ssh_client.exec_command( - 'sudo /bin/mount -t ext4 /dev/%s /mnt' - % CONF.compute.volume_device_name) - ssh_client.exec_command( - 'sudo sh -c "date > /mnt/timestamp; sync"') - timestamp = ssh_client.exec_command( - 'test -f /mnt/timestamp && echo ok') - ssh_client.exec_command( - 'sudo /bin/umount /mnt') - - self.nova_volume_detach(server, volume) - self.assertEqual(u'ok\n', timestamp) diff --git a/openstack-common.conf b/openstack-common.conf deleted file mode 100644 index 3ad58021..00000000 --- a/openstack-common.conf +++ /dev/null @@ -1,6 +0,0 @@ -[DEFAULT] - -# The list of modules to copy from oslo-incubator.git - -# The base module to hold the copy of openstack.common -base=nova-lxd diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 180fbf83..00000000 --- a/requirements.txt +++ /dev/null @@ -1,18 +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!=2.1.0,>=3.1.1 # Apache-2.0 -os-brick>=2.3.0 # Apache-2.0 -os-vif!=1.8.0,>=1.9.0 # Apache-2.0 -oslo.config>=5.2.0 # Apache-2.0 -oslo.concurrency>=3.26.0 # Apache-2.0 -oslo.utils>=3.36.0 # Apache-2.0 -oslo.i18n>=3.20.0 # Apache-2.0 -oslo.log>=3.37.0 # Apache-2.0 -pylxd>=2.2.6 # Apache-2.0 - -# XXX: rockstar (17 Feb 2016) - oslo_config imports -# debtcollector, which imports this, but doesn't -# require it in dependencies. -#wrapt>=1.7.0 # BSD License diff --git a/run_tests.sh b/run_tests.sh deleted file mode 100755 index 4f553585..00000000 --- a/run_tests.sh +++ /dev/null @@ -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 Location of the virtualenv directory" - echo " Default: \$(pwd)" - echo " --virtual-env-name Name of the virtualenv directory" - echo " Default: .venv" - echo " --tools-path Location of the tools directory" - echo " Default: \$(pwd)" - echo " --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_lxd/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 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index e2444664..00000000 --- a/setup.cfg +++ /dev/null @@ -1,35 +0,0 @@ -[metadata] -name = nova-lxd -summary = native lxd driver for openstack -description-file = - README.md -author = OpenStack -author-email = openstack-discuss@lists.openstack.org -home-page = https://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 - -[files] -packages = - nova/virt/lxd - nova/tests - nova_lxd_tempest_plugin - -[entry_points] -tempest.test_plugins = - nova-lxd-tempest-plugin = nova_lxd_tempest_plugin.plugin:MyPlugin - -[build_sphinx] -source-dir = doc/source -build-dir = doc/build -all_files = 1 - -[upload_sphinx] -upload-dir = doc/build/html diff --git a/setup.py b/setup.py deleted file mode 100644 index 6f3da25e..00000000 --- a/setup.py +++ /dev/null @@ -1,36 +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 os -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 - -this_directory = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(this_directory, 'README.md'), 'rb') as f: - long_description = f.read().decode('utf-8') - -setuptools.setup( - long_description=long_description, - long_description_content_type='text/markdown', - setup_requires=['pbr>=2.0.0'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 909fd418..00000000 --- a/test-requirements.txt +++ /dev/null @@ -1,19 +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.13.0,<0.14,>=0.12.0 # Apache-2.0 - -coverage!=4.4,>=4.5.1 # Apache-2.0 -ddt>=1.1.2 # MIT -python-subunit>=1.2.0 # Apache-2.0/BSD -sphinx!=1.6.6,!=1.6.7,>=1.6.5 # BSD -sphinx-feature-classification>=0.1.0 # Apache 2.0 -oslosphinx>=4.18.0 # Apache-2.0 -oslotest>=3.3.0 # Apache-2.0 -testrepository>=0.0.20 # Apache-2.0/BSD -testscenarios>=0.5.0 # Apache-2.0/BSD -testtools>=2.3.0 # MIT -stestr>=1.0.0 # Apache-2.0 -nosexcover>=1.0.11 # BSD -wsgi-intercept>=1.6.0 # MIT License diff --git a/tools/abandon_old_reviews.sh b/tools/abandon_old_reviews.sh deleted file mode 100755 index 21e1bcd1..00000000 --- a/tools/abandon_old_reviews.sh +++ /dev/null @@ -1,81 +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. -# -# -# -# before you run this modify your .ssh/config to create a -# review.openstack.org entry: -# -# Host review.openstack.org -# User -# Port 29418 -# - -# Note: due to gerrit bug somewhere, this double posts messages. :( - -# first purge the all reviews that are more than 4w old and blocked by a core -2 - -set -o errexit - -function abandon_review { - local gitid=$1 - shift - local msg=$@ - echo "Abandoning $gitid" - ssh review.openstack.org gerrit review $gitid --abandon --message \"$msg\" -} - - -blocked_reviews=$(ssh review.openstack.org "gerrit query --current-patch-set --format json project:openstack/nova status:open age:4w label:Code-Review<=-2" | jq .currentPatchSet.revision | grep -v null | sed 's/"//g') - -blocked_msg=$(cat < 4 weeks without comment and currently blocked by a -core reviewer with a -2. We are abandoning this for now. - -Feel free to reactivate the review by pressing the restore button and -contacting the reviewer with the -2 on this review to ensure you -address their concerns. - -EOF -) - -# For testing, put in a git rev of something you own and uncomment -# blocked_reviews="b6c4218ae4d75b86c33fa3d37c27bc23b46b6f0f" - -for review in $blocked_reviews; do - # echo ssh review.openstack.org gerrit review $review --abandon --message \"$msg\" - echo "Blocked review $review" - abandon_review $review $blocked_msg -done - -# then purge all the reviews that are > 4w with no changes and Jenkins has -1ed - -failing_reviews=$(ssh review.openstack.org "gerrit query --current-patch-set --format json project:openstack/nova status:open age:4w NOT label:Verified>=1,jenkins" | jq .currentPatchSet.revision | grep -v null | sed 's/"//g') - -failing_msg=$(cat < 4 weeks without comment, and failed Jenkins the last -time it was checked. We are abandoning this for now. - -Feel free to reactivate the review by pressing the restore button and -leaving a 'recheck' comment to get fresh test results. - -EOF -) - -for review in $failing_reviews; do - echo "Failing review $review" - abandon_review $review $failing_msg -done diff --git a/tools/clean-vlans b/tools/clean-vlans deleted file mode 100755 index 8a0ebfc8..00000000 --- a/tools/clean-vlans +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -# 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. - -export LC_ALL=C - -sudo ifconfig -a | grep br | grep -v bridge | cut -f1 -d" " | xargs -n1 -ifoo ifconfig foo down -sudo ifconfig -a | grep br | grep -v bridge | cut -f1 -d" " | xargs -n1 -ifoo brctl delbr foo -sudo ifconfig -a | grep vlan | cut -f1 -d" " | xargs -n1 -ifoo ifconfig foo down -sudo ifconfig -a | grep vlan | cut -f1 -d" " | xargs -n1 -ifoo ip link del foo diff --git a/tools/colorizer.py b/tools/colorizer.py deleted file mode 100755 index 5f97e197..00000000 --- a/tools/colorizer.py +++ /dev/null @@ -1,326 +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 sys -import unittest - -import subunit -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) diff --git a/tools/config/README b/tools/config/README deleted file mode 100644 index 0d5bd574..00000000 --- a/tools/config/README +++ /dev/null @@ -1,20 +0,0 @@ -This generate_sample.sh tool is used to generate etc/nova/nova.conf.sample - -Run it from the top-level working directory i.e. - - $> ./tools/config/generate_sample.sh -b ./ -p nova -o etc/nova - -Watch out for warnings about modules like libvirt, qpid and zmq not -being found - these warnings are significant because they result -in options not appearing in the generated config file. - - -The analyze_opts.py tool is used to find options which appear in -/etc/nova/nova.conf but not in etc/nova/nova.conf.sample -This helps identify options in the nova.conf file which are not used by nova. -The tool also identifies any options which are set to the default value. - -Run it from the top-level working directory i.e. - - $> ./tools/config/analyze_opts.py - diff --git a/tools/config/analyze_opts.py b/tools/config/analyze_opts.py deleted file mode 100755 index 8661bf96..00000000 --- a/tools/config/analyze_opts.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2012, Cloudscaling -# 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. -''' -find_unused_options.py - -Compare the nova.conf file with the nova.conf.sample file to find any unused -options or default values in nova.conf -''' - -from __future__ import print_function - -import argparse -import os -import sys - -from oslo.config import iniparser - -sys.path.append(os.getcwd()) - - -class PropertyCollecter(iniparser.BaseParser): - - def __init__(self): - super(PropertyCollecter, self).__init__() - self.key_value_pairs = {} - - def assignment(self, key, value): - self.key_value_pairs[key] = value - - def new_section(self, section): - pass - - @classmethod - def collect_properties(cls, lineiter, sample_format=False): - def clean_sample(f): - for line in f: - if line.startswith("#") and not line.startswith("# "): - line = line[1:] - yield line - pc = cls() - if sample_format: - lineiter = clean_sample(lineiter) - pc.parse(lineiter) - return pc.key_value_pairs - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='''Compare the nova.conf - file with the nova.conf.sample file to find any unused options or - default values in nova.conf''') - - parser.add_argument('-c', action='store', - default='/etc/nova/nova.conf', - help='path to nova.conf' - ' (defaults to /etc/nova/nova.conf)') - parser.add_argument('-s', default='./etc/nova/nova.conf.sample', - help='path to nova.conf.sample' - ' (defaults to ./etc/nova/nova.conf.sample') - options = parser.parse_args() - - conf_file_options = PropertyCollecter.collect_properties(open(options.c)) - sample_conf_file_options = PropertyCollecter.collect_properties( - open(options.s), sample_format=True) - - for k, v in sorted(conf_file_options.items()): - if k not in sample_conf_file_options: - print("Unused:", k) - for k, v in sorted(conf_file_options.items()): - if k in sample_conf_file_options and v == sample_conf_file_options[k]: - print("Default valued:", k) diff --git a/tools/config/check_uptodate.sh b/tools/config/check_uptodate.sh deleted file mode 100755 index 48add478..00000000 --- a/tools/config/check_uptodate.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -PROJECT_NAME=${PROJECT_NAME:-nova} -CFGFILE_NAME=${PROJECT_NAME}.conf.sample - -if [ -e etc/${PROJECT_NAME}/${CFGFILE_NAME} ]; then - CFGFILE=etc/${PROJECT_NAME}/${CFGFILE_NAME} -elif [ -e etc/${CFGFILE_NAME} ]; then - CFGFILE=etc/${CFGFILE_NAME} -else - echo "${0##*/}: can not find config file" - exit 1 -fi - -TEMPDIR=`mktemp -d /tmp/${PROJECT_NAME}.XXXXXX` -trap "rm -rf $TEMPDIR" EXIT - -tools/config/generate_sample.sh -b ./ -p ${PROJECT_NAME} -o ${TEMPDIR} - -if ! diff -u ${TEMPDIR}/${CFGFILE_NAME} ${CFGFILE} -then - echo "${0##*/}: ${PROJECT_NAME}.conf.sample is not up to date." - echo "${0##*/}: Please run ${0%%${0##*/}}generate_sample.sh." - exit 1 -fi diff --git a/tools/config/generate_sample.sh b/tools/config/generate_sample.sh deleted file mode 100755 index 94d6f3ec..00000000 --- a/tools/config/generate_sample.sh +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env bash - -print_hint() { - echo "Try \`${0##*/} --help' for more information." >&2 -} - -PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:m:l:o: \ - --long help,base-dir:,package-name:,output-dir:,module:,library: -- "$@") - -if [ $? != 0 ] ; then print_hint ; exit 1 ; fi - -eval set -- "$PARSED_OPTIONS" - -while true; do - case "$1" in - -h|--help) - echo "${0##*/} [options]" - echo "" - echo "options:" - echo "-h, --help show brief help" - echo "-b, --base-dir=DIR project base directory" - echo "-p, --package-name=NAME project package name" - echo "-o, --output-dir=DIR file output directory" - echo "-m, --module=MOD extra python module to interrogate for options" - echo "-l, --library=LIB extra library that registers options for discovery" - exit 0 - ;; - -b|--base-dir) - shift - BASEDIR=`echo $1 | sed -e 's/\/*$//g'` - shift - ;; - -p|--package-name) - shift - PACKAGENAME=`echo $1` - shift - ;; - -o|--output-dir) - shift - OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'` - shift - ;; - -m|--module) - shift - MODULES="$MODULES -m $1" - shift - ;; - -l|--library) - shift - LIBRARIES="$LIBRARIES -l $1" - shift - ;; - --) - break - ;; - esac -done - -BASEDIR=${BASEDIR:-`pwd`} -if ! [ -d $BASEDIR ] -then - echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1 -elif [[ $BASEDIR != /* ]] -then - BASEDIR=$(cd "$BASEDIR" && pwd) -fi - -PACKAGENAME=${PACKAGENAME:-${BASEDIR##*/}} -TARGETDIR=$BASEDIR/$PACKAGENAME -if ! [ -d $TARGETDIR ] -then - echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1 -fi - -OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc} -# NOTE(bnemec): Some projects put their sample config in etc/, -# some in etc/$PACKAGENAME/ -if [ -d $OUTPUTDIR/$PACKAGENAME ] -then - OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME -elif ! [ -d $OUTPUTDIR ] -then - echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2 - exit 1 -fi - -BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'` -find $TARGETDIR -type f -name "*.pyc" -delete -FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" \ - -exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u) - -RC_FILE="`dirname $0`/oslo.config.generator.rc" -if test -r "$RC_FILE" -then - source "$RC_FILE" -fi - -for mod in ${NOVA_CONFIG_GENERATOR_EXTRA_MODULES}; do - MODULES="$MODULES -m $mod" -done - -for lib in ${NOVA_CONFIG_GENERATOR_EXTRA_LIBRARIES}; do - LIBRARIES="$LIBRARIES -l $lib" -done - -export EVENTLET_NO_GREENDNS=yes - -OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs) -[ "$OS_VARS" ] && eval "unset \$OS_VARS" -DEFAULT_MODULEPATH=nova.openstack.common.config.generator -MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH} -OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample -python -m $MODULEPATH $MODULES $LIBRARIES $FILES > $OUTPUTFILE - -# Hook to allow projects to append custom config file snippets -CONCAT_FILES=$(ls $BASEDIR/tools/config/*.conf.sample 2>/dev/null) -for CONCAT_FILE in $CONCAT_FILES; do - cat $CONCAT_FILE >> $OUTPUTFILE -done diff --git a/tools/config/oslo.config.generator.rc b/tools/config/oslo.config.generator.rc deleted file mode 100644 index afcad616..00000000 --- a/tools/config/oslo.config.generator.rc +++ /dev/null @@ -1,2 +0,0 @@ -NOVA_CONFIG_GENERATOR_EXTRA_LIBRARIES="oslo.messaging oslo.db oslo.concurrency" -NOVA_CONFIG_GENERATOR_EXTRA_MODULES=keystonemiddleware.auth_token diff --git a/tools/db/schema_diff.py b/tools/db/schema_diff.py deleted file mode 100755 index dbeded87..00000000 --- a/tools/db/schema_diff.py +++ /dev/null @@ -1,286 +0,0 @@ -#!/usr/bin/env python -# Copyright 2012 OpenStack Foundation -# -# 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. - -""" -Utility for diff'ing two versions of the DB schema. - -Each release cycle the plan is to compact all of the migrations from that -release into a single file. This is a manual and, unfortunately, error-prone -process. To ensure that the schema doesn't change, this tool can be used to -diff the compacted DB schema to the original, uncompacted form. - -The database is specified by providing a SQLAlchemy connection URL WITHOUT the -database-name portion (that will be filled in automatically with a temporary -database name). - -The schema versions are specified by providing a git ref (a branch name or -commit hash) and a SQLAlchemy-Migrate version number: - -Run like: - - MYSQL: - - ./tools/db/schema_diff.py mysql://root@localhost \ - master:latest my_branch:82 - - POSTGRESQL: - - ./tools/db/schema_diff.py postgresql://localhost \ - master:latest my_branch:82 -""" - -from __future__ import print_function - -import datetime -import glob -from nova import i18n -import os -import subprocess -import sys - -_ = i18n._ - -# Dump - - -def dump_db(db_driver, db_name, db_url, migration_version, dump_filename): - if not db_url.endswith('/'): - db_url += '/' - - db_url += db_name - - db_driver.create(db_name) - try: - _migrate(db_url, migration_version) - db_driver.dump(db_name, dump_filename) - finally: - db_driver.drop(db_name) - - -# Diff - - -def diff_files(filename1, filename2): - pipeline = ["diff -U 3 {filename1} {filename2}" - .format(filename1=filename1, filename2=filename2)] - - # Use colordiff if available - if subprocess.call(['which', 'colordiff']) == 0: - pipeline.append('colordiff') - - pipeline.append('less -R') - - cmd = ' | '.join(pipeline) - subprocess.check_call(cmd, shell=True) - - -# Database - - -class Mysql(object): - - def create(self, name): - subprocess.check_call(['mysqladmin', '-u', 'root', 'create', name]) - - def drop(self, name): - subprocess.check_call(['mysqladmin', '-f', '-u', 'root', 'drop', name]) - - def dump(self, name, dump_filename): - subprocess.check_call( - "mysqldump -u root {name} > {dump_filename}" - .format(name=name, dump_filename=dump_filename), - shell=True) - - -class Postgresql(object): - - def create(self, name): - subprocess.check_call(['createdb', name]) - - def drop(self, name): - subprocess.check_call(['dropdb', name]) - - def dump(self, name, dump_filename): - subprocess.check_call( - "pg_dump {name} > {dump_filename}" - .format(name=name, dump_filename=dump_filename), - shell=True) - - -def _get_db_driver_class(db_url): - try: - return globals()[db_url.split('://')[0].capitalize()] - except KeyError: - raise Exception(_("database {] not supported").format(db_url)) - - -# Migrate - - -MIGRATE_REPO = os.path.join(os.getcwd(), "nova/db/sqlalchemy/migrate_repo") - - -def _migrate(db_url, migration_version): - earliest_version = _migrate_get_earliest_version() - - # NOTE(sirp): sqlalchemy-migrate currently cannot handle the skipping of - # migration numbers. - _migrate_cmd( - db_url, 'version_control', str(earliest_version - 1)) - - upgrade_cmd = ['upgrade'] - if migration_version != 'latest': - upgrade_cmd.append(str(migration_version)) - - _migrate_cmd(db_url, *upgrade_cmd) - - -def _migrate_cmd(db_url, *cmd): - manage_py = os.path.join(MIGRATE_REPO, 'manage.py') - - args = ['python', manage_py] - args += cmd - args += ['--repository=%s' % MIGRATE_REPO, - '--url=%s' % db_url] - - subprocess.check_call(args) - - -def _migrate_get_earliest_version(): - versions_glob = os.path.join(MIGRATE_REPO, 'versions', '???_*.py') - - versions = [] - for path in glob.iglob(versions_glob): - filename = os.path.basename(path) - prefix = filename.split('_', 1)[0] - try: - version = int(prefix) - except ValueError: - pass - versions.append(version) - - versions.sort() - return versions[0] - - -# Git - - -def git_current_branch_name(): - ref_name = git_symbolic_ref('HEAD', quiet=True) - current_branch_name = ref_name.replace('refs/heads/', '') - return current_branch_name - - -def git_symbolic_ref(ref, quiet=False): - args = ['git', 'symbolic-ref', ref] - if quiet: - args.append('-q') - proc = subprocess.Popen(args, stdout=subprocess.PIPE) - stdout, stderr = proc.communicate() - return stdout.strip() - - -def git_checkout(branch_name): - subprocess.check_call(['git', 'checkout', branch_name]) - - -def git_has_uncommited_changes(): - return subprocess.call(['git', 'diff', '--quiet', '--exit-code']) == 1 - - -# Command - - -def die(msg): - print("ERROR: %s" % msg, file=sys.stderr) - sys.exit(1) - - -def usage(msg=None): - if msg: - print("ERROR: %s" % msg, file=sys.stderr) - - prog = "schema_diff.py" - args = ["", "", - ""] - - print("usage: %s %s" % (prog, ' '.join(args)), file=sys.stderr) - sys.exit(1) - - -def parse_options(): - try: - db_url = sys.argv[1] - except IndexError: - usage("must specify DB connection url") - - try: - orig_branch, orig_version = sys.argv[2].split(':') - except IndexError: - usage('original branch and version required (e.g. master:82)') - - try: - new_branch, new_version = sys.argv[3].split(':') - except IndexError: - usage('new branch and version required (e.g. master:82)') - - return db_url, orig_branch, orig_version, new_branch, new_version - - -def main(): - timestamp = datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S") - - ORIG_DB = 'orig_db_%s' % timestamp - NEW_DB = 'new_db_%s' % timestamp - - ORIG_DUMP = ORIG_DB + ".dump" - NEW_DUMP = NEW_DB + ".dump" - - options = parse_options() - db_url, orig_branch, orig_version, new_branch, new_version = options - - # Since we're going to be switching branches, ensure user doesn't have any - # uncommited changes - if git_has_uncommited_changes(): - die("You have uncommited changes. Please commit them before running " - "this command.") - - db_driver = _get_db_driver_class(db_url)() - - users_branch = git_current_branch_name() - git_checkout(orig_branch) - - try: - # Dump Original Schema - dump_db(db_driver, ORIG_DB, db_url, orig_version, ORIG_DUMP) - - # Dump New Schema - git_checkout(new_branch) - dump_db(db_driver, NEW_DB, db_url, new_version, NEW_DUMP) - - diff_files(ORIG_DUMP, NEW_DUMP) - finally: - git_checkout(users_branch) - - if os.path.exists(ORIG_DUMP): - os.unlink(ORIG_DUMP) - - if os.path.exists(NEW_DUMP): - os.unlink(NEW_DUMP) - - -if __name__ == "__main__": - main() diff --git a/tools/enable-pre-commit-hook.sh b/tools/enable-pre-commit-hook.sh deleted file mode 100755 index be1ed27d..00000000 --- a/tools/enable-pre-commit-hook.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh - -# Copyright 2011 OpenStack Foundation -# -# 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. - -PRE_COMMIT_SCRIPT=.git/hooks/pre-commit - -make_hook() { - echo "exec ./run_tests.sh -N -p" >> $PRE_COMMIT_SCRIPT - chmod +x $PRE_COMMIT_SCRIPT - - if [ -w $PRE_COMMIT_SCRIPT -a -x $PRE_COMMIT_SCRIPT ]; then - echo "pre-commit hook was created successfully" - else - echo "unable to create pre-commit hook" - fi -} - -# NOTE(jk0): Make sure we are in nova's root directory before adding the hook. -if [ ! -d ".git" ]; then - echo "unable to find .git; moving up a directory" - cd .. - if [ -d ".git" ]; then - make_hook - else - echo "still unable to find .git; hook not created" - fi -else - make_hook -fi - diff --git a/tools/install_venv.py b/tools/install_venv.py deleted file mode 100644 index e3c7fd42..00000000 --- a/tools/install_venv.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2010 OpenStack Foundation -# Copyright 2013 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. - -from __future__ import print_function - -import os -import sys - -import install_venv_common as install_venv - - -def print_help(venv, root): - help = """ - Nova development environment setup is complete. - - Nova development uses virtualenv to track and manage Python dependencies - while in development and testing. - - To activate the Nova virtualenv for the extent of your current shell - session you can run: - - $ source %s/bin/activate - - Or, if you prefer, you can run commands in the virtualenv on a case by case - basis by running: - - $ %s/tools/with_venv.sh - - Also, make test will automatically use the virtualenv. - """ - print(help % (venv, root)) - - -def main(argv): - root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) - - if os.environ.get('tools_path'): - root = os.environ['tools_path'] - venv = os.path.join(root, '.venv') - if os.environ.get('venv'): - venv = os.environ['venv'] - - pip_requires = os.path.join(root, 'requirements.txt') - test_requires = os.path.join(root, 'test-requirements.txt') - py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) - project = 'Nova' - install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, - py_version, project) - options = install.parse_args(argv) - install.check_python_version() - install.check_dependencies() - install.create_virtualenv(no_site_packages=options.no_site_packages) - install.install_dependencies() - print_help(venv, root) - -if __name__ == '__main__': - main(sys.argv) diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py deleted file mode 100644 index e279159a..00000000 --- a/tools/install_venv_common.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# Copyright 2013 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. - -"""Provides methods needed by installation script for OpenStack development -virtual environments. - -Since this script is used to bootstrap a virtualenv from the system's Python -environment, it should be kept strictly compatible with Python 2.6. - -Synced in from openstack-common -""" - -from __future__ import print_function - -import optparse -import os -import subprocess -import sys - - -class InstallVenv(object): - - def __init__(self, root, venv, requirements, - test_requirements, py_version, - project): - self.root = root - self.venv = venv - self.requirements = requirements - self.test_requirements = test_requirements - self.py_version = py_version - self.project = project - - def die(self, message, *args): - print(message % args, file=sys.stderr) - sys.exit(1) - - def check_python_version(self): - if sys.version_info < (2, 6): - self.die("Need Python Version >= 2.6") - - def run_command_with_code(self, cmd, redirect_output=True, - check_exit_code=True): - """Runs a command in an out-of-process shell. - - Returns the output of that command. Working directory is self.root. - """ - if redirect_output: - stdout = subprocess.PIPE - else: - stdout = None - - proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) - output = proc.communicate()[0] - if check_exit_code and proc.returncode != 0: - self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) - return (output, proc.returncode) - - def run_command(self, cmd, redirect_output=True, check_exit_code=True): - return self.run_command_with_code(cmd, redirect_output, - check_exit_code)[0] - - def get_distro(self): - if (os.path.exists('/etc/fedora-release') or - os.path.exists('/etc/redhat-release')): - return Fedora( - self.root, self.venv, self.requirements, - self.test_requirements, self.py_version, self.project) - else: - return Distro( - self.root, self.venv, self.requirements, - self.test_requirements, self.py_version, self.project) - - def check_dependencies(self): - self.get_distro().install_virtualenv() - - def create_virtualenv(self, no_site_packages=True): - """Creates the virtual environment and installs PIP. - - Creates the virtual environment and installs PIP only into the - virtual environment. - """ - if not os.path.isdir(self.venv): - print('Creating venv...', end=' ') - if no_site_packages: - self.run_command(['virtualenv', '-q', '--no-site-packages', - self.venv]) - else: - self.run_command(['virtualenv', '-q', self.venv]) - print('done.') - else: - print("venv already exists...") - pass - - def pip_install(self, *args): - self.run_command(['tools/with_venv.sh', - 'pip', 'install', '--upgrade'] + list(args), - redirect_output=False) - - def install_dependencies(self): - print('Installing dependencies with pip (this can take a while)...') - - # First things first, make sure our venv has the latest pip and - # setuptools and pbr - self.pip_install('pip>=1.4') - self.pip_install('setuptools') - self.pip_install('pbr') - - self.pip_install('-r', self.requirements, '-r', self.test_requirements) - - def parse_args(self, argv): - """Parses command-line arguments.""" - parser = optparse.OptionParser() - parser.add_option('-n', '--no-site-packages', - action='store_true', - help="Do not inherit packages from global Python " - "install.") - return parser.parse_args(argv[1:])[0] - - -class Distro(InstallVenv): - - def check_cmd(self, cmd): - return bool(self.run_command(['which', cmd], - check_exit_code=False).strip()) - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if self.check_cmd('easy_install'): - print('Installing virtualenv via easy_install...', end=' ') - if self.run_command(['easy_install', 'virtualenv']): - print('Succeeded') - return - else: - print('Failed') - - self.die('ERROR: virtualenv not found.\n\n%s development' - ' requires virtualenv, please install it using your' - ' favorite package management tool' % self.project) - - -class Fedora(Distro): - """This covers all Fedora-based distributions. - - Includes: Fedora, RHEL, CentOS, Scientific Linux - """ - - def check_pkg(self, pkg): - return self.run_command_with_code(['rpm', '-q', pkg], - check_exit_code=False)[1] == 0 - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if not self.check_pkg('python-virtualenv'): - self.die("Please install 'python-virtualenv'.") - - super(Fedora, self).install_virtualenv() diff --git a/tools/nova-manage.bash_completion b/tools/nova-manage.bash_completion deleted file mode 100644 index 053d4195..00000000 --- a/tools/nova-manage.bash_completion +++ /dev/null @@ -1,37 +0,0 @@ -# bash completion for openstack nova-manage - -_nova_manage_opts="" # lazy init -_nova_manage_opts_exp="" # lazy init - -# dict hack for bash 3 -_set_nova_manage_subopts () { - eval _nova_manage_subopts_"$1"='$2' -} -_get_nova_manage_subopts () { - eval echo '${_nova_manage_subopts_'"$1"'#_nova_manage_subopts_}' -} - -_nova_manage() -{ - local cur prev subopts - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - - if [ "x$_nova_manage_opts" == "x" ] ; then - _nova_manage_opts="`nova-manage bash-completion 2>/dev/null`" - _nova_manage_opts_exp="`echo $_nova_manage_opts | sed -e "s/\s/|/g"`" - fi - - if [[ " `echo $_nova_manage_opts` " =~ " $prev " ]] ; then - if [ "x$(_get_nova_manage_subopts "$prev")" == "x" ] ; then - subopts="`nova-manage bash-completion $prev 2>/dev/null`" - _set_nova_manage_subopts "$prev" "$subopts" - fi - COMPREPLY=($(compgen -W "$(_get_nova_manage_subopts "$prev")" -- ${cur})) - elif [[ ! " ${COMP_WORDS[@]} " =~ " "($_nova_manage_opts_exp)" " ]] ; then - COMPREPLY=($(compgen -W "${_nova_manage_opts}" -- ${cur})) - fi - return 0 -} -complete -F _nova_manage nova-manage diff --git a/tools/pretty_tox.sh b/tools/pretty_tox.sh deleted file mode 100755 index ac760458..00000000 --- a/tools/pretty_tox.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -o pipefail - -TESTRARGS=$1 -python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | subunit-trace -f diff --git a/tools/regression_tester.py b/tools/regression_tester.py deleted file mode 100755 index 366da6ef..00000000 --- a/tools/regression_tester.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2013 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. - - -"""Tool for checking if patch contains a regression test. - -By default runs against current patch but can be set to use any gerrit review -as specified by change number (uses 'git review -d'). - -Idea: take tests from patch to check, and run against code from previous patch. -If new tests pass, then no regression test, if new tests fails against old code -then either -* new tests depend on new code and cannot confirm regression test is valid - (false positive) -* new tests detects the bug being fixed (detect valid regression test) -Due to the risk of false positives, the results from this need some human -interpretation. -""" - -from __future__ import print_function - -import optparse -import string -import subprocess -import sys - - -def run(cmd, fail_ok=False): - print("running: %s" % cmd) - obj = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - shell=True) - obj.wait() - if obj.returncode != 0 and not fail_ok: - print("The above command terminated with an error.") - sys.exit(obj.returncode) - return obj.stdout.read() - - -def main(): - usage = """ - Tool for checking if a patch includes a regression test. - - Usage: %prog [options]""" - parser = optparse.OptionParser(usage) - parser.add_option("-r", "--review", dest="review", - help="gerrit review number to test") - (options, args) = parser.parse_args() - if options.review: - original_branch = run("git rev-parse --abbrev-ref HEAD") - run("git review -d %s" % options.review) - else: - print("no gerrit review number specified, running on latest commit" - "on current branch.") - - test_works = False - - # run new tests with old code - run("git checkout HEAD^ nova") - run("git checkout HEAD nova/tests") - - # identify which tests have changed - tests = run("git whatchanged --format=oneline -1 | grep \"nova/tests\" " - "| cut -f2").split() - test_list = [] - for test in tests: - test_list.append(string.replace(test[0:-3], '/', '.')) - - if test_list == []: - test_works = False - expect_failure = "" - else: - # run new tests, expect them to fail - expect_failure = run(("tox -epy27 %s 2>&1" % string.join(test_list)), - fail_ok=True) - if "FAILED (id=" in expect_failure: - test_works = True - - # cleanup - run("git checkout HEAD nova") - if options.review: - new_branch = run("git status | head -1 | cut -d ' ' -f 4") - run("git checkout %s" % original_branch) - run("git branch -D %s" % new_branch) - - print(expect_failure) - print("") - print("*******************************") - if test_works: - print("FOUND a regression test") - else: - print("NO regression test") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/tools/with_venv.sh b/tools/with_venv.sh deleted file mode 100755 index 94e05c12..00000000 --- a/tools/with_venv.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -tools_path=${tools_path:-$(dirname $0)} -venv_path=${venv_path:-${tools_path}} -venv_dir=${venv_name:-/../.venv} -TOOLS=${tools_path} -VENV=${venv:-${venv_path}/${venv_dir}} -source ${VENV}/bin/activate && "$@" diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 8bfc113a..00000000 --- a/tox.ini +++ /dev/null @@ -1,80 +0,0 @@ -[tox] -minversion = 2.0 -envlist = py{3,27},pep8 -skipsdist = True - -[testenv] -usedevelop = True -install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} -setenv = - VIRTUAL_ENV={envdir} - EVENTLET_NO_GREENDNS=yes - PYTHONDONTWRITEBYTECODE=1 - LANGUAGE=en_US - LC_ALL=en_US.utf-8 -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - -egit+https://github.com/openstack/nova.git#egg=nova -whitelist_externals = - bash - find - rm - env -commands = - find . -type f -name "*.pyc" -delete - rm -Rf .testrepository/times.dbm -passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY OS_DEBUG GENERATE_HASHES - -[testenv:py27] -commands = - {[testenv]commands} - /bin/cp -r {toxinidir}/nova/virt/lxd/ {toxinidir}/.tox/py27/src/nova/nova/virt/ - stestr run {posargs} - -[testenv:py3] -basepython = python3 -commands = - {[testenv]commands} - /bin/cp -r {toxinidir}/nova/virt/lxd/ {toxinidir}/.tox/py3/src/nova/nova/virt/ - stestr run {posargs} - -[testenv:pep8] -basepython = python3 -deps = {[testenv]deps} -commands = - flake8 {toxinidir}/nova - flake8 {toxinidir}/nova_lxd_tempest_plugin - -[testenv:venv] -basepython = python3 -commands = {posargs} - -[testenv:cover] -basepython = python3 -# Also do not run test_coverage_ext tests while gathering coverage as those -# tests conflict with coverage. -commands = - coverage erase - find . -type f -name "*.pyc" -delete - python setup.py testr --coverage --testr-args='{posargs}' - coverage report - -[testenv:docs] -basepython = python3 -commands = python setup.py build_sphinx - -[flake8] -# H803 skipped on purpose per list discussion. -# E123, E125 skipped as they are invalid PEP-8. - -show-source = True -ignore = E123,E125,H803,H904,H405,H404,H305,H306,H307 -builtins = _ -exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools/colorizer.py - -[testenv:lower-constraints] -basepython = python3 -deps = - -c{toxinidir}/lower-constraints.txt - -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt