nova-lxd-13.0.0/0000775000175000017500000000000012701775064013552 5ustar chuckchuck00000000000000nova-lxd-13.0.0/specs/0000775000175000017500000000000012701775064014667 5ustar chuckchuck00000000000000nova-lxd-13.0.0/specs/todo.txt0000664000175000017500000001045112701774405016374 0ustar chuckchuck00000000000000nova-lxd todo list Taken from http://docs.openstack.org/developer/nova/support-matrix.html Feature Status Kilo Liberty Attach block optional X not started volume to instance ------------------------------------------------------ Detach block optional X not started volume from instance ------------------------------------------------------ Evacuate optional X complete instances from host -------------------------------------------------------- Guest instance mandatory started started status -------------------------------------------------------- Gust host optional started started status -------------------------------------------------------- Live migrate optional X not started instance across hosts --------------------------------------------------------- Launch mandatory complete complete instance -------------------------------------------------------- Stop instance optional complete complete CPUs -------------------------------------------------------- Reboot optional complete complete instance -------------------------------------------------------- Rescue optional X complete instance -------------------------------------------------------- Resize optional X not started instance -------------------------------------------------------- Restore optional X complete instance -------------------------------------------------------- Service optional X not started control (??) -------------------------------------------------------- Set instance optional X not started admin password -------------------------------------------------------- Save snapshot optional X complete of instance disk -------------------------------------------------------- Swap block optional X not applicable volumes ----------------------------------------------------------- Shutdown mandatory complete complete instance ----------------------------------------------------------- Resume optional X not applicable insance CPUs ---------------------------------------------------------- Config drive choice X complete support ---------------------------------------------------------- inject files optional X not started into disk image --------------------------------------------------------- inject guest optional X not started networking config --------------------------------------------------------- Remote choice X not applicable desktop over RDP ---------------------------------------------------------- View serial choice complete complete console logs ---------------------------------------------------------- Remote choice X not applicable desktp over SPICE ----------------------------------------------------------- Remote choice X not applicable desktop over VNC ---------------------------------------------------------- Block storage optional X not started support --------------------------------------------------------- Block storage optional X not started over iSCSI --------------------------------------------------------- CHAP optional X not started authenication for iSCIS --------------------------------------------------------- Image storage mandatory complete complete support --------------------------------------------------------- Network optional X complete firewall rules --------------------------------------------------------- Network optional complete complete routing --------------------------------------------------------- Network optional X complete security groups --------------------------------------------------------- Flat choice complete complete networking -------------------------------------------------------- VLAN choice complete complete networking nova-lxd-13.0.0/HACKING.rst0000664000175000017500000000024012701774405015342 0ustar chuckchuck00000000000000nova-lxd Style Commandments =============================================== Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ nova-lxd-13.0.0/tools/0000775000175000017500000000000012701775064014712 5ustar chuckchuck00000000000000nova-lxd-13.0.0/tools/config/0000775000175000017500000000000012701775064016157 5ustar chuckchuck00000000000000nova-lxd-13.0.0/tools/config/check_uptodate.sh0000775000175000017500000000125412701774405021500 0ustar chuckchuck00000000000000#!/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 nova-lxd-13.0.0/tools/config/oslo.config.generator.rc0000664000175000017500000000022212701774405022704 0ustar chuckchuck00000000000000NOVA_CONFIG_GENERATOR_EXTRA_LIBRARIES="oslo.messaging oslo.db oslo.concurrency" NOVA_CONFIG_GENERATOR_EXTRA_MODULES=keystonemiddleware.auth_token nova-lxd-13.0.0/tools/config/generate_sample.sh0000775000175000017500000000651512701774405021656 0ustar chuckchuck00000000000000#!/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 nova-lxd-13.0.0/tools/config/analyze_opts.py0000775000175000017500000000536012701774405021246 0ustar chuckchuck00000000000000#!/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) nova-lxd-13.0.0/tools/config/README0000664000175000017500000000134712701774405017042 0ustar chuckchuck00000000000000This 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 nova-lxd-13.0.0/tools/regression_tester.py0000775000175000017500000000672012701774405021040 0ustar chuckchuck00000000000000#!/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() nova-lxd-13.0.0/tools/clean-vlans0000775000175000017500000000214012701774405017036 0ustar chuckchuck00000000000000#!/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 nova-lxd-13.0.0/tools/abandon_old_reviews.sh0000775000175000017500000000513712701774405021261 0ustar chuckchuck00000000000000#!/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 nova-lxd-13.0.0/tools/colorizer.py0000775000175000017500000002701112701774405017276 0ustar chuckchuck00000000000000#!/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) nova-lxd-13.0.0/tools/enable-pre-commit-hook.sh0000775000175000017500000000232512701774405021507 0ustar chuckchuck00000000000000#!/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 nova-lxd-13.0.0/tools/pretty_tox.sh0000775000175000017500000000021212701774405017463 0ustar chuckchuck00000000000000#!/usr/bin/env bash set -o pipefail TESTRARGS=$1 python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | subunit-trace -f nova-lxd-13.0.0/tools/install_venv.py0000664000175000017500000000455412701774405017776 0ustar chuckchuck00000000000000# 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) nova-lxd-13.0.0/tools/with_venv.sh0000775000175000017500000000033212701774405017256 0ustar chuckchuck00000000000000#!/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 && "$@" nova-lxd-13.0.0/tools/nova-manage.bash_completion0000664000175000017500000000214012701774405022166 0ustar chuckchuck00000000000000# 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 nova-lxd-13.0.0/tools/db/0000775000175000017500000000000012701775064015277 5ustar chuckchuck00000000000000nova-lxd-13.0.0/tools/db/schema_diff.py0000775000175000017500000001643712701774405020115 0ustar chuckchuck00000000000000#!/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)s %(filename2)s' % {'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)s > %(dump_filename)s' % {'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)s > %(dump_filename)s' % {'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 %s not supported") % 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() nova-lxd-13.0.0/tools/install_venv_common.py0000664000175000017500000001350712701774405021344 0ustar chuckchuck00000000000000# 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() nova-lxd-13.0.0/PKG-INFO0000664000175000017500000000303212701775064014645 0ustar chuckchuck00000000000000Metadata-Version: 1.1 Name: nova-lxd Version: 13.0.0 Summary: native lxd driver for openstack Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: # 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. ## Bug reports Bug reports can be filed at https://github.com/lxc/nova-lxd/issues/new ## 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. Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 nova-lxd-13.0.0/AUTHORS0000664000175000017500000000100512701775064014616 0ustar chuckchuck00000000000000Chuck Short Chuck Short Gyorgy Szombathelyi James Page Masaki Matsushita Michał Sawicz Paul Hummer Paul Hummer Paul Hummer Peter Slovak Ryan Harper Stéphane Graber zulcss nova-lxd-13.0.0/ChangeLog0000664000175000017500000003756412701775063015342 0ustar chuckchuck00000000000000CHANGES ======= 13.0.0 ------ * Tag 13.0.0 * Query the LXD API for memory usage * Fix issue #20 * Fix migration with configdrive * Fix spelling typos * Remove old LXD console configuration * Fix launching instance with configdrive * fix pep8 * Fix logic error * Fix migration with configdrive * Revert "Wire in live migration" * Revert "Fix pep8" * Fix pep8 * Wire in live migration * Fix unit tests * Simplify block detection * Adjust function name * Check for LXD block devices * Fix pep8 * Remove extra debug messages * Bump version * Use certificate when copying hosts * Fix the imports for pylxd.exceptions to pylxd.deprecated.exceptions * Add a broken dependency issue * Bump version to 13.0.0b2 * Fix pep8 * Fix disk limits * Update travis * Update requirements * Fix typo * Fix pep8 * Add more unit tests * Add unit tests for profiles * Fix failing pep8 and unit tests * Fix various typos found * Refactor migration support * Fix typos * Add finish_revert_migration * Make functions non-private * Make sure profiles exist * Remove unimplemented methods * Cleanup log messages * Always connect to the unix socket * Implement resize and contianer resource limits * Rename container migration * Fix PEP * Change session to package rather than a module * Consolidate utils into session * Add session mixin consolidation * Consolidate profile session code * Unify the operations handling * Consolidate container session stuff * Consolidate image session handling * No * Whitespace.. * Add docs for setting everything up * Update unit tests and fix pep8 * Update unit tests * Fix bridge creation with LinuxBridge * Fix concurrency issues when running snapshots * Fix attach interface * Fix typos * Make snapshots less racey and update for newer LXD * Fix config drive * Re-enable console-log * Simplify and consolidate rescue/unrescue * Fix image uploading * Fix unplug bridge with nova-network * Fix coverage * Revert "Fix image_meta properties for architecture" * Fix image_meta properties for architecture * revert .testr.conf change * Fix coverage usage * Fix pep8 issues * Fix pep8 issues * Drop JSON encoding for supported_instances * Remove files * Fix unit tests and pep8 * More doc strings * Fix pep8 issues * Remove import alias * Fix pep8 errors * Add more doc strings * Fix typos * Update note about config_migration * Make it more pythonic * Add more doc strings * Fix spelling typos * Be more pythonic * Refactor image creation * Fix typos, unit tests and pep8 * Set timeout to -1 * Fix typos uncovered by tempest * Update unit tests and fix pep8 errors * Update unit tests * Remove return from methods * Fix unit tests * Fix pep8 errors * Refactor container output * Refactor container info * Refactor cleanup * Refactor unrescue * Remove dead code * Refactor container local copy * Refactor rescue * Refactor resume * Refactor suspend * Refactor pause, power_on, power_off * Refactor power off * Refactor container destroy * Refactor container reboot * Fix up typos * Re-add config drive support * Refactor spawn method * Start the network early * Rename container_ops and test_container_ops * Fix unit tests and pep8 errors * Remove the usage of the default profile * Refactor LXD container creation * Rename container_config and test_container_config * Bump version 0.19 ---- * Bump to v0.19 * Fix grammar * Fix pep8 * Fix typos * Improve error message for ephemeral devices * Fix almost empty line * Fix typo * Fix container_migrate tests in py3 * Update travis * Check for container operation in iamge_upload * More pep8 fixes * Fix pep8 and py27 errors * Add H404 to tox.ini * Ignore H405 * Re-add container_migration tests * Update doc strings * Update docstring in tests * Check for container operation in iamge_upload * Fix typo * Cache travis pip installation * Bump travis to test on 3.4 * WTF. Case got changed on the bool * Fix tests to run on python3 * Don't write pyc files when running tox * Fix a bug in the envlist for tox * Fix relative import to be a full path import * Re-add LXC hv_types * Use LXD as hypervisor type * Use socket.gethostname() * Remove container_utils and ontainer_client * Refactor utils used by LXD functions * Refactor snapstop LXD functions * Refactor migration LXD functions * Refactor container LXD functions * Remove container_client and container_utils * Fix strings * Use hostname fqdn * Fix arch on non-x86_64 arches * Fix pep8 and py27 errors * More typo fixes * Fix typos * Improve debug output * Split out fetch image * Add doc strings * Remove more dead code * Add image_upload to session * Refactor container_wait * Consolidate create_alias * Add image_ref to the fake instance * Use alias_defined * Add docstring for tests * Refactor image_defined * Fix fall out from renames * Rename test_container_image.py and container_image.py * Remove dead code * Fix pep8 * Wait for image to upload * Transition from nova-compute-lxd to nova-lxd * Update readme * Update requirements * Fix alias creation * Split out LXDContainerDirectories * Update devstack config * Update default profile * Update test_vif_api * Update test_driver_api * Update test_container_utils * Update test_container_ops * Update test_container_migration * Update test_container_image * Update test_container_config * Update entry point * Update driver * Update container_snapshot * Update container_ops * Update container_migrate * nclxd -> nova_lxd transition * Update container_client * Remove contrib directory * Update todo list * Update coverage * Update package defaults * nclxd becomes nova_lxd * Pass the full operation url * Fix typo * Switch devstack installation to devstack-plugin * PEP8 * implemented plug_vifs() and unplug_vifs() driver methods, fixed missing import of nova.exception * Fix pep8 * Remove cpu limits * Fix pep8 errors * Fix container migration and resize * Update spec * Use snapshot id when creating images * Revert "Refactor network creation" * Revert "Fix pep8" * Fix pep8 * Refactor network creation * Bump to 0.18 * Bump to 0.18 * Fix container rescue/unrescue with lvm * Update get_container_dir * Transition to use instance.name * Handle container initialization failure * Re-add timeout * Fix unit tests * Add container migration unit tests * Fix vif creation * Fix unit tests * Spelling fixes * Fixup for rename of README.rst to README.md * Fix pep8 * Fix setup.cfg * Add unit tests * Fix contianer destroy tests * Stop the container stop before destroying * Specify the correct host * Remove wait_for_state * Fix exceptions and LOG info * Ensure vifs are unplugged during cleanup * Updates to README * More pep8 fixes * Fix image_properties handling * fix pep8 * Update image property * Fix pep8 * Add negative test * Fix failing unit test * Generate LXD manifest from image_meta * Always use unix socket * fix pep8 * Remove python3.4 checks * LXD test image fixes * Remove LXDTestContainerUtils * Fix LXDTestNetworkDriver * Fix LXDContainerOps * Update travis config * Fix LXDTestDriver * Fix LXDTestDriverNoops tests * Fix flake8 errors * Add travis * Update readme * Ensure image upload activities are guarded against concurrency * Drop update for glance image metadata * Use unix socket if hosts matches up * Remove unused variables * Fix test_container_config * Create a fake instance * Split out mock functions * Version bump * Use os-testr * Adjust image properties * Update setup.cfg 0.17 ---- * Container check * small fixes * Fix typo * Remove host check * Update tests for container_config * Add cold migration support * Destroy check * Refactor container creation and operations * Capture back traces * Spawn a container into a seperate thread * Refactor container rescue/unrescue * fix snapshot name * fix another typo * wire up container snapshot * dont sent task_state directly * operation-show -> operation-info * Fix container_stop * Rescue * fix typo * Query snapshot status * Update rescue continer - part 1 * Update continer - part 1 * Pep8 fixes * Fix typos * fix bad merge * Update attach interface * Fix typos * remove debug info * Simplify vif plugging * Improve debugging messages * Dont upload image if exists * Just do a single init * Update snapshot support * Fix various typos * Ensure that we are on the right host * Fix debug info * Re-add suspend/resume * Fix unknown power_state * Fix container pause/unpause * Reset container migration * Dont stop container before deleting it * Update requirements * Check status_code for power_state * Reset migration * Optimize image creation and startup * Optimize image creation usage * Re-add bridge support * Refactor network creation * Release 0.16 * Add new features: * Refactor init_host * Fix up instance.uuid migration * Revert "Rename instance.name to instance.uuid." * Rename instance.name to instance.uuid * Fix pep8 and flake8 tests * Update test-requirements * Update requirements * devstack: Fix the README some more * Fix devstack integration * Cover more cases for cpu stats * Add failing snapshot test * Add failing wait tests * Drop container_utils tests * Cover container_update fail * Cover container_init fail * Simplify test_rescue_defined * Cover the 404 case of container_defined * Add bridge type vif tests * Add OVS unplug tests * Add LXDTestOVSDriver * Add uuid to MockInstance * Add test_node_is_available * Add test_get_available_nodes * Add test_get_host_uptime * Don't use iteritems * Fix firewall calls * Add test_firewall_calls * Don't handle PyLXDException in container_ops * Fix test_detach_interface * Add test_attach_interface * Fix PEP8 issues * Fix raise * Add get_available_resource tests * Refactor simple cases * Add power_off tests * Move rescue tests to driver suite * Add pause test * Add simple / noop method tests * Add test_snapshot * Don't catch all exceptions * Add detach interface tests * Fix NotImplementedError raises * Add more NotImplemented names * Add test_get_host_ip_addr * Add more NotImplemented tests * Move console test to driver suite * Add reboot tests * Add cleanup test * This line was never reached * Fix typo * Add destroy tests * Move test_spawn_ to driver suite * Add not implemented suite * Add test_estimate_instance_overhead * Add test_instance_exists * Move test_get_info to driver suite * Move list_instances test to driver suite * Move init tests to driver suite * Add LXDDriver config test * Use fake virtapi * Drop network_info from tests * Adapt tests to new paths * Check for host before creating profile * Add container_info * Add container_config * Removed unused variable * Update root_dir * wire up get_host_cpu_stats * Dropped reset_network * Deprecate inject_file and inject_network_info * Drop the redundant 'lxd' prefix in config options * Fix py3 compatibility * Move container_ops to oslo_utils, too * Fix PEP8 issues * More fixes * More fileutils fixes * Switch to fileutils * Update requirements * Add attach/detach interface * Rename container_info to container_state * Fix container_defined * Add test_init_host_new_profile * Fix PEP8 * Add test_get_console_output * Add test_get_info * Add test_container_unrescue * Add rescue tests * Fix oslo_utils import * Fix log message * Simplify container_running * Fix fallback events type * Add test_start_instance * Add test_create_instance * Add failing create_instance tests * Add test_spawn_new * Fix invocation of InstanceExists and raises * Add first container_ops tests * Create default profile if it doesnt exist * Fix bad merge * tag 0.13 * Update requirements * Add security groups support * Add security groups support * Update requirements * Refactor image tests to mock CONF, not utils * Refactor config tests to mock CONF, not utils * Add test_fetch_image_new * Add test_fetch_image_new_alias_failed * Add test_fetch_image_new_upload_failed * pylxd.API.image_defined never raises 404 * Fix LXDContainerImage.fetch_image * Add test_fetch_image_new_defined * Add tests for existing image * Move MockInstance to nclxd.tests * Add test_configure_container_configdrive tests * Add test_configure_container_rescuedisk * Add test_configure_network_devices * Add test_configure_container_config * Add first container_config tests * Fix .coveragerc * Update requirements * Re-add missing import * Add requirement of pylxd since its in pypi * Fixes for python3 * Use absolute imports * Reorder imports * Fix typo in fix_container_configdrive name * Fix PEP8 errors * Fix container utils tests * Fix run_tests.sh path for --debug * tag 0.12 * Fix container cleanup * image synching * Add snapshot support * More updates * remove cruft * cleanup * rescue/unrescue part 2 * rescue part 1 * rough in rescue/unrescue * Fix container config * Configure container devices * Rename function * Fix add-config for container configuration * Remove profile * create default profile * More cleanups * remove cruft * Add configdrive support * refactor container config * Split out profile and container configuration * Refactor container-configuration * Prep for rescue/unrescue * Add suspend/resume/pause/unpause * Add power on and power off * Add more unit tests * Add container reboot * final code refactor * Revert "nclxd ng" * Revert "Add get-available-nodes" * Revert "Remove cleanup_host" * Revert "Add .idea directory" * Revert "Wire up list_instances and list_instances_uuid." * Revert "wire up hosts" * wire up hosts * Wire up list_instances and list_instances_uuid * Add .idea directory * Remove cleanup_host * Add get-available-nodes * nclxd ng * Correct test cases * Remove unused file * Add firewall support * Fix up typos * Liberty fixes * Fix typo * devstack fixes * Update devstack support * Add Scott's image conversion script * Open for liberty * fix git repository * Add todo list stable/kilo ----------- * Various bug fixes * Updates for LXD 0.7 * Removed debug info * Update filters * Create the container at one shot * More fixes from lxd testing * Fixes from lxd testing * More fixes from lxd testing * Miscalenous vivid fixes for 0.5 * fix destroy * Vivid overlay * Add lxd load testing fixes * remove firewall * Add fixups from load testing * Work backwards for known units * Correct the dict * Correct hard disk calculation * converter remove verbose flag in tar creation * converter fix tar parameter quoting * converter remove extra tar parameter * Add support for security groups * Re-do lxd-image-converter * Update to version 0.5 * Add utiltity to convert tarballs * pep8 clean ups * Removed unused import * Adjust changes for nclxd * 0.3 updates * Update * Sync with 0.4 * Re-do image creation * Use pylxd instead * Switch to using pylxd * 0.2 changes * Debug * use instance img_href * Create alias * Get fingerprint of the image * typo fix * Fetch image from glance * Update clients * Start of surgery * Re-do how we do container list * Loose logging * Loose logging * Use oslo.log * Speed up fixes * Fix console and other stuff * Clean up * Add version * Remove debug * Add debug * Fix lxc-usernet * Fix typo * Fix template * fix typo * Fix formatting * fix typo * default user * Config fix ups * Revert "Add default user" * More fixes * Fix typo * Add default user * Configure lxd a bit harder * Fix lxd * LXD changes * Fix LXC configuration file of console and logfile * Fix typo * Fix unix sockets * Use unix socket * Add filters * Add missing file * Add missing btrfs * More typos * KeyError * Specify the correct directory * Makre sure we are using ints * Fix containre stats * Add statistics for container * Remove ppa and fix up networking a bit more * Fix console log * Stop the container before destorying it * Force Stop * More whoopsies * Whoops * Fix typos * More config changes * Fix container console * Add missing images * More updates * Fix typo * Fix network again * Fix typo * Fix white space * More networking changes * More fixes * Updates * More fixes * Startover with tests * Fix requirements * Update deps * Update requirements * More test fixes * Add missing tools directory * Add more unit tests * Add run_tests.sh * Improve tests * Fix pep8 * Add missing tox.ini * M00re fixes * more fixes * More fixes * Fix container reboot * Copyright headers and cleanup * Add missing files * More fixes * Start adding tests * Update filters * Misc fixes * Add missing files * Add missing files * Frist commit nova-lxd-13.0.0/requirements.txt0000664000175000017500000000112112701774405017027 0ustar chuckchuck00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=1.6 # Apache-2.0 Babel>=1.3 # BSD requests!=2.9.0,>=2.8.1 # Apache-2.0 oslo.config>=3.4.0 # Apache-2.0 oslo.concurrency>=2.3.0 # Apache-2.0 oslo.utils>=3.4.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.log>=1.14.0 # Apache-2.0 # XXX: rockstar (17 Feb 2016) - oslo_config imports # debtcollector, which imports this, but doesn't # require it in dependencies. wrapt==1.10.6 nova-lxd-13.0.0/MANIFEST.in0000664000175000017500000000013512701774405015305 0ustar chuckchuck00000000000000include AUTHORS include ChangeLog exclude .gitignore exclude .gitreview global-exclude *.pycnova-lxd-13.0.0/doc/0000775000175000017500000000000012701775064014317 5ustar chuckchuck00000000000000nova-lxd-13.0.0/doc/source/0000775000175000017500000000000012701775064015617 5ustar chuckchuck00000000000000nova-lxd-13.0.0/doc/source/conf.py0000775000175000017500000000462112701774405017122 0ustar chuckchuck00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', #'sphinx.ext.intersphinx', 'oslosphinx' ] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'nova-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} nova-lxd-13.0.0/doc/source/index.rst0000664000175000017500000000076212701774405017463 0ustar chuckchuck00000000000000.. 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 readme installation usage contributing Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` nova-lxd-13.0.0/doc/source/contributing.rst0000664000175000017500000000011212701774405021050 0ustar chuckchuck00000000000000============ Contributing ============ .. include:: ../../CONTRIBUTING.rstnova-lxd-13.0.0/doc/source/usage.rst0000664000175000017500000000013312701774405017450 0ustar chuckchuck00000000000000======== Usage ======== To use nova-lxd in a project:: import nova_lxd.nova.virt.lxd nova-lxd-13.0.0/doc/source/readme.rst0000664000175000017500000000003512701774405017602 0ustar chuckchuck00000000000000.. include:: ../../README.rstnova-lxd-13.0.0/.mailmap0000664000175000017500000000013012701774405015163 0ustar chuckchuck00000000000000# Format is: # # nova-lxd-13.0.0/babel.cfg0000664000175000017500000000002012701774405015266 0ustar chuckchuck00000000000000[python: **.py] nova-lxd-13.0.0/openstack-common.conf0000664000175000017500000000020712701774405017673 0ustar chuckchuck00000000000000[DEFAULT] # The list of modules to copy from oslo-incubator.git # The base module to hold the copy of openstack.common base=nova-lxd nova-lxd-13.0.0/devstack/0000775000175000017500000000000012701775064015356 5ustar chuckchuck00000000000000nova-lxd-13.0.0/devstack/settings0000664000175000017500000000023012701774405017132 0ustar chuckchuck00000000000000# Add nova-lxd to enabled services enable_service nova-lxd # LXD install/upgrade settings INTSALL_LXD=${INSTALL_LXD:-False} LXD_GROUP=${LXD_GROUP:lxd} nova-lxd-13.0.0/devstack/nova-lxd-functions.sh0000664000175000017500000000150612701774405021450 0ustar chuckchuck00000000000000#!/bin/bash # devstack/powervm-functions.sh # Functions to control the installation and configuration of the PowerVM # compute services GITREPO["pylxd"]=${PYLXD_REPO:-https://github.com/lxc/pylxd} GITBRANCH["pylxd"]=${PYLXD_BRANCH:-master} GITDIR["pylxd"]=$DEST/pylxd function install_pylxd { # Install the latest pylxd from git echo_summary "Installing pylxd" git_clone_by_name pylxd setup_dev_lib "pylxd" echo_summary "Pylxd install complete" } function cleanup_pylxd { echo_summary "Cleaning pylxd" rm -rf ${GITDIR["pylxd"]} } function install_lxd { echo_summary "Checing LXD installation" if is_ubuntu; then if ! ( is_package_installed lxd ); then install_package lxd fi add_user_to_group $STACK_USER $LXD_GROUP fi echo_summary "Installing LXD" } nova-lxd-13.0.0/devstack/plugin.sh0000775000175000017500000000416312701774405017215 0ustar chuckchuck00000000000000#!/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} # nova-powervm directories NOVA_COMPUTE_LXD_DIR=${NOVA_POWERVM_DIR:-${DEST}/nova-lxd} NOVA_COMPUTE_LXD_PLUGIN_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})) source $NOVA_COMPUTE_LXD_PLUGIN_DIR/nova-lxd-functions.sh function pre_install_nova-lxd() { # Install OS packages if necessary with "install_package ...". install_lxd install_pylxd } 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 nova_lxd.nova.virt.lxd.LXDDriver } function init_nova-lxd() { # Initialize and start the service. : } function shutdown_nova-lxd() { # Shut the service down. : } function cleanup_nova-lxd() { # Cleanup the service. : } 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 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 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 nova-lxd-13.0.0/devstack/override-defaults0000664000175000017500000000004512701774405020722 0ustar chuckchuck00000000000000# Plug-in overrides VIRT_DRIVER=lxd nova-lxd-13.0.0/devstack/README.rst0000664000175000017500000000052012701774405017040 0ustar chuckchuck00000000000000==================== Enabling in Devstack =================== 1. Download DevStack: $ git clone https://git.openstack.org/openstack-dev/devstack /opt/stack/devstack 2. Modify DevStack's local.conf to pull in this project by adding: [[local|localrc]] enable_plugin nova-lxd https://github.com/lxc/nova-lxd 3. run stack.sh nova-lxd-13.0.0/setup.cfg0000664000175000017500000000247212701775064015400 0ustar chuckchuck00000000000000[metadata] name = nova-lxd summary = native lxd driver for openstack description-file = README.md version = 13.0.0 author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 2.6 Programming Language :: Python :: 3 Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 [files] packages = nova_lxd namespace_packages = nova_lxd [entry_points] console_scripts = lxc-image-converter = nova_lxd.cmd.converter:main [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [compile_catalog] directory = nova_lxd/locale domain = nova_lxd [update_catalog] domain = nova_lxd output_dir = nova_lxd/locale input_file = nova_lxd/locale/nova_lxd.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = nova_lxd/locale/nova_lxd.pot [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 nova-lxd-13.0.0/etc/0000775000175000017500000000000012701775064014325 5ustar chuckchuck00000000000000nova-lxd-13.0.0/etc/lxd/0000775000175000017500000000000012701775064015114 5ustar chuckchuck00000000000000nova-lxd-13.0.0/etc/lxd/lxd.template0000664000175000017500000000076412701774405017445 0ustar chuckchuck00000000000000# Distribution configuration lxc.include = {{lxd_common_config}} lxc.include = {{lxd_userns_config}} lxc.arch = x86_64 # Container specific configuration lxc.id_map = u 0 {{lxd_user}} {{lxd_uoffset}} lxc.id_map = g 0 {{lxd_group}} {{lxd_goffset}} lxc.rootfs = {{lxd_rootfs}} lxc.utsname = {{lxd_name}} lxc.logfile = {{lxd_logfile}} lxc.console.logfile = {{lxd_console_file}} # Container network lxc.network.type = veth lxc.network.hwaddr = {{lxd_mac_addr}} lxc.network.link = {{lxd_network_link}} nova-lxd-13.0.0/etc/nova/0000775000175000017500000000000012701775064015270 5ustar chuckchuck00000000000000nova-lxd-13.0.0/etc/nova/rootwrap.d/0000775000175000017500000000000012701775064017367 5ustar chuckchuck00000000000000nova-lxd-13.0.0/etc/nova/rootwrap.d/lxd.filters0000664000175000017500000000024412701774405021546 0ustar chuckchuck00000000000000# nova-rootwrap filters for compute nodes running flex # This file should be owned by (and only-writable by) the root user [Filters] tar: CommandFilter, tar, root nova-lxd-13.0.0/setup.py0000664000175000017500000000200412701774405015256 0ustar chuckchuck00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=1.8'], pbr=True) nova-lxd-13.0.0/test-requirements.txt0000664000175000017500000000125312701774405020012 0ustar chuckchuck00000000000000# 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.10,>=0.9.2 coverage>=3.6 # Apache-2.0 discover # BSD ddt>=1.0.1 # MIT python-subunit>=0.0.18 # Apache-2.0/BSD sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT os-testr>=0.4.1 # Apache-2.0 nosexcover # BSD -e git+https://github.com/lxc/pylxd#egg=pylxd -e git+https://github.com/openstack/nova#egg=nova nova-lxd-13.0.0/README.md0000664000175000017500000000111712701774405015027 0ustar chuckchuck00000000000000# 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. ## Bug reports Bug reports can be filed at https://github.com/lxc/nova-lxd/issues/new ## 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. nova-lxd-13.0.0/nova_lxd/0000775000175000017500000000000012701775064015364 5ustar chuckchuck00000000000000nova-lxd-13.0.0/nova_lxd/tests/0000775000175000017500000000000012701775064016526 5ustar chuckchuck00000000000000nova-lxd-13.0.0/nova_lxd/tests/session/0000775000175000017500000000000012701775064020211 5ustar chuckchuck00000000000000nova-lxd-13.0.0/nova_lxd/tests/session/test_profile.py0000664000175000017500000000534712701774405023271 0ustar chuckchuck00000000000000# 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 ddt import mock from nova import exception from nova import test from pylxd.deprecated import exceptions as lxd_exceptions from nova_lxd.nova.virt.lxd import session from nova_lxd.tests import fake_api from nova_lxd.tests import stubs @ddt.ddt class SessionProfileTest(test.NoDBTestCase): def setUp(self): super(SessionProfileTest, self).setUp() """This is so we can mock out pylxd API calls.""" self.ml = stubs.lxd_mock() lxd_patcher = mock.patch('pylxd.api.API', mock.Mock(return_value=self.ml)) lxd_patcher.start() self.addCleanup(lxd_patcher.stop) self.session = session.LXDAPISession() @stubs.annotated_data( ('empty', [], []), ('valid', ['test'], ['test']), ) def test_profile_list(self, tag, side_effect, expected): self.ml.profile_list.return_value = side_effect self.assertEqual(expected, self.session.profile_list()) def test_profile_list_fail(self): self.ml.profile_list.side_effect = ( lxd_exceptions.APIError('Fake', 500)) self.assertRaises( exception.NovaException, self.session.profile_list) def test_profile_create(self): instance = stubs._fake_instance() config = mock.Mock() self.ml.profile_defined.return_value = True self.ml.profile_create.return_value = \ (200, fake_api.fake_standard_return()) self.assertEqual((200, fake_api.fake_standard_return()), self.session.profile_create(config, instance)) calls = [mock.call.profile_list(), mock.call.profile_create(config)] self.assertEqual(calls, self.ml.method_calls) def test_profile_delete(self): instance = stubs._fake_instance() self.ml.profile_defined.return_value = True self.ml.profile_delete.return_value = \ (200, fake_api.fake_standard_return()) self.assertEqual(None, self.session.profile_delete(instance)) nova-lxd-13.0.0/nova_lxd/tests/session/test_event.py0000664000175000017500000000304212701774405022740 0ustar chuckchuck00000000000000# 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 ddt import mock from nova import test from nova_lxd.nova.virt.lxd import session from nova_lxd.tests import stubs @ddt.ddt class SessionEventTest(test.NoDBTestCase): def setUp(self): super(SessionEventTest, self).setUp() self.ml = stubs.lxd_mock() lxd_patcher = mock.patch('pylxd.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.assertEqual(None, self.session.operation_wait(operation_id, instance)) self.ml.wait_container_operation.assert_called_with(operation_id, 200, -1) nova-lxd-13.0.0/nova_lxd/tests/session/test_container.py0000664000175000017500000005364412701774405023616 0ustar chuckchuck00000000000000# 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.compute import power_state from nova import exception from nova import test from pylxd.deprecated import exceptions as lxd_exceptions from nova_lxd.nova.virt.lxd import session from nova_lxd.tests import fake_api from nova_lxd.tests 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.api.API', mock.Mock(return_value=self.ml)) lxd_patcher.start() self.addCleanup(lxd_patcher.stop) self.session = session.LXDAPISession() @stubs.annotated_data( ('empty', [], []), ('valid', ['test'], ['test']), ) def test_container_list(self, tag, side_effect, expected): """ container_list returns a list of LXD containers found on an LXD host. """ self.ml.container_list.return_value = side_effect self.assertEqual(expected, self.session.container_list()) def test_container_list_fail(self): """ container_list returns an exception.NovaException, if pylxd raises an APIError. """ self.ml.container_list.side_effect = ( lxd_exceptions.APIError('Fake', 500)) self.assertRaises( exception.NovaException, self.session.container_list) def test_container_update(self): """ container_update updates the LXD container configuration, so verify that the correct pylxd calls are made. """ config = mock.Mock() instance = stubs._fake_instance() self.ml.container_update.return_value = \ (200, fake_api.fake_container_config()) self.assertEqual((200, fake_api.fake_container_config()), self.session.container_update(config, instance)) calls = [ mock.call.container_defined(instance.name), mock.call.container_update(instance.name, config)] self.assertEqual(calls, self.ml.method_calls) @stubs.annotated_data( ('api_fail', True, lxd_exceptions.APIError('Fake', 500), exception.NovaException), ('missing_container', False, None, exception.InstanceNotFound) ) def test_container_update_fail(self, tag, container_defined, side_effect, expected): """ container_update will fail if the container is not found, or the LXD raises an API error. Verify that the exceptions are raised in both scenarios. """ config = mock.Mock() instance = stubs._fake_instance() if container_defined: self.ml.container_defined.return_value = container_defined self.ml.container_update.side_effect = ( lxd_exceptions.APIError('Fake', 500)) self.assertRaises( expected, self.session.container_update, config, instance) if not container_defined: self.ml.container_defined.return_value = container_defined self.assertRaises( expected, self.session.container_update, config, instance) @stubs.annotated_data( ('running', True), ('idle', False), ('api_failure', lxd_exceptions.APIError('Fake', '500')), ) def test_container_running(self, tag, side_effect): """ container_running determines if the container is running or not. Verify that we are returning True if the container is running. False if its not, raise an exception if there is an API error. """ instance = stubs._fake_instance() if side_effect: self.ml.container_running.return_value = side_effect self.assertTrue(self.session.container_running(instance)) if not side_effect: self.ml.container_running.return_value = side_effect self.assertFalse(self.session.container_running(instance)) if tag == 'api_failure': self.ml.container_running.side_effect = side_effect self.assertRaises( exception.NovaException, self.session.container_running, instance ) def test_container_state(self): """ container_state translates LXD container status into what nova understands. Verify that status_code sends a power_state.RUNNING and a 108 sends a power_state.CRASHED. """ calls = [] self.assertEqual(calls, self.ml.method_calls) @stubs.annotated_data( ('api_fail', True, lxd_exceptions.APIError('Fake', 500), {'state': power_state.NOSTATE, 'mem': 0, 'max_mem': 0}) ) def test_container_state_fail(self, tag, container_defined, side_effect, expected): """ container_state translates LXD container status into what nova understands. If the API sends an APIError then raise an power_state.NOSTATE, same if the the container goes missing. """ instance = stubs._fake_instance() if container_defined: self.ml.container_defined.return_value = container_defined self.ml.container_state.side_effect = ( lxd_exceptions.APIError('Fake', 500)) self.assertEqual( expected, self.session.container_state(instance)) if not container_defined: self.ml.container_defined.return_value = container_defined self.assertEqual( expected, self.session.container_state(instance)) def test_container_config(self): """ container_config returns a dictionary representation of the LXD container. Verify that the funciton returns a container_config """ instance = stubs._fake_instance() self.ml.get_container_config.return_value = \ (200, fake_api.fake_container_config()) self.assertEqual( (200, fake_api.fake_container_config()), self.session.container_config(instance)) @stubs.annotated_data( ('api_fail', True, lxd_exceptions.APIError('Fake', 500), exception.NovaException), ) def test_container_config_fail(self, tag, container_defined, side_effect, expected): """ container_config returns a dictionary represeation of the LXD container. Verify that the function raises an exception.NovaException when there is a APIError. """ instance = stubs._fake_instance() if container_defined: self.ml.container_defined.return_value = container_defined self.ml.get_container_config.side_effect = side_effect self.assertRaises( expected, self.session.container_config, instance) def test_container_info(self): """ container_info returns a dictonary represenation of useful information about a container, (ip address, pid, etc). Verify that the function returns the approiate dictionary representation for the LXD API. """ instance = stubs._fake_instance() self.ml.container_info.return_value = \ (200, fake_api.fake_container_info()) self.assertEqual( (200, fake_api.fake_container_info()), self.session.container_info(instance)) def test_container_info_fail(self): """ container_info returns a dictionary reprsentation of userful information about a container (ip address, pid, etc). Verify that the container_info returns an exception.NovaException when there is an APIError. """ instance = stubs._fake_instance() self.ml.container_info.side_effect = ( lxd_exceptions.APIError('Fake', 500)) self.assertRaises( exception.NovaException, self.session.container_info, instance) @stubs.annotated_data( ('exists', True), ('missing', False), ) def test_container_defined(self, tag, side_effect): """ container_defined returns True if the container exists on an LXD host, False otherwise, verify the apporiate return value is returned. """ instance = stubs._fake_instance() self.ml.container_defined.return_value = side_effect if side_effect: self.assertTrue(self.session.container_defined( instance.name, instance)) if not side_effect: self.assertFalse(self.session.container_defined( instance.name, instance)) @stubs.annotated_data( ('1', True, (200, fake_api.fake_operation_info_ok())) ) def test_container_start(self, tag, defined, side_effect=None): """ containser_start starts a container on a given LXD host. Verify that the correct pyLXD calls are made. """ instance = stubs._fake_instance() self.ml.container_defined.return_value = defined self.ml.container_start.return_value = side_effect self.assertEqual(None, self.session.container_start(instance.name, instance)) calls = [mock.call.container_defined(instance.name), mock.call.container_start(instance.name, -1), mock.call.wait_container_operation( '/1.0/operation/1234', 200, -1)] self.assertEqual(calls, self.ml.method_calls) @stubs.annotated_data( ('container_missing', False, exception.InstanceNotFound), ('api_error', True, exception.NovaException, lxd_exceptions.APIError('Fake', 500)), ) def test_container_start_fail(self, tag, container_defined, expected, side_effect=None): """ container_start starts a container on a given LXD host. Veify that an exception.InstanceNotFound when the container is not found on an LXD host. Raises an exception.NovaException when there is an APIError. """ instance = stubs._fake_instance() if container_defined: self.ml.container_defined.return_value = container_defined self.ml.container_start.side_effect = side_effect self.assertRaises(expected, self.session.container_start, instance.name, instance) if not container_defined: self.ml.container_defined.return_value = container_defined self.assertRaises(expected, self.session.container_start, instance.name, instance) @stubs.annotated_data( ('1', (200, fake_api.fake_operation_info_ok())) ) def test_container_stop(self, tag, side_effect): """ container_stop stops a container on a given LXD ost. Verifty that that the apprioated pylxd calls are made to the LXD api. """ instance = stubs._fake_instance() self.ml.container_stop.return_value = side_effect self.assertEqual(None, self.session.container_stop(instance.name, instance)) calls = [mock.call.container_defined(instance.name), mock.call.container_stop(instance.name, -1), mock.call.wait_container_operation( '/1.0/operation/1234', 200, -1)] self.assertEqual(calls, self.ml.method_calls) @stubs.annotated_data( ('api_fail', lxd_exceptions.APIError('Fake', 500), exception.NovaException) ) def test_container_stop_fail(self, tag, side_effect, expected): """ contianer_stop stops a container on a given LXD host. Verifty that we raise an exception.NovaException when there is an APIError. """ instance = stubs._fake_instance() self.ml.container_stop.side_effect = side_effect self.assertRaises(expected, self.session.container_stop, instance.name, instance) @stubs.annotated_data( ('1,', (200, fake_api.fake_operation_info_ok())) ) def test_continer_reboot(self, tag, side_effect): """" container_reboot reboots a container on a given LXD host. Verify that the right pylxd calls are made to the LXD host. """ instance = stubs._fake_instance() self.ml.container_reboot.return_value = side_effect self.assertEqual(None, self.session.container_reboot(instance)) calls = [mock.call.container_defined(instance.name), mock.call.container_reboot(instance.name, -1), mock.call.wait_container_operation( '/1.0/operation/1234', 200, -1)] self.assertEqual(calls, self.ml.method_calls) @stubs.annotated_data( ('api_fail', lxd_exceptions.APIError('Fake', 500), exception.NovaException) ) def test_container_reboot_fail(self, tag, side_effect, expected): """ container_reboot reboots a container on a given LXD host. Check that an exception.NovaException is raised when there is an LXD API error. """ instance = stubs._fake_instance() self.ml.container_reboot.side_effect = side_effect self.assertRaises(expected, self.session.container_reboot, instance) @stubs.annotated_data( ('exists', True, (200, fake_api.fake_operation_info_ok())), ('missing', False, (200, fake_api.fake_operation_info_ok())) ) def test_container_destroy(self, tag, container_defined, side_effect): """ container_destroy delete a container from the LXD Host. Check that the approiate pylxd calls are made. """ instance = stubs._fake_instance() if container_defined: self.ml.container_defined.return_value = container_defined self.ml.container_stop.return_value = side_effect self.ml.container_destroy.return_value = side_effect self.assertEqual(None, self.session.container_destroy(instance.name, instance)) calls = [mock.call.container_defined(instance.name), mock.call.container_defined(instance.name), mock.call.container_stop(instance.name, -1), mock.call.wait_container_operation( '/1.0/operation/1234', 200, -1), mock.call.container_destroy(instance.name), mock.call.wait_container_operation( '/1.0/operation/1234', 200, -1)] self.assertEqual(calls, self.ml.method_calls) if not container_defined: self.ml.container_defined.return_value = container_defined self.assertEqual(None, self.session.container_destroy(instance.name, instance)) calls = [mock.call.container_defined(instance.name)] self.assertEqual(calls, self.ml.method_calls) @stubs.annotated_data( ('fail_to_stop', True, 'fail_stop', lxd_exceptions.APIError('Fake', '500'), exception.NovaException), ('fail_to_destroy', True, 'fail_destroy', lxd_exceptions.APIError('Fake', '500'), exception.NovaException) ) def test_container_destroy_fail(self, tag, container_defined, test_type, side_effect, expected): """ container_destroy deletes a container on the LXD host. Check whether an exeption.NovaException is raised when there is an APIError or when the container fails to stop. """ instance = stubs._fake_instance() self.ml.cotnainer_defined.return_value = container_defined if test_type == 'fail_stop': self.ml.container_stop.side_effect = side_effect self.assertRaises(expected, self.session.container_destroy, instance.name, instance) if test_type == 'fail_destroy': self.ml.container_defined.return_value = container_defined self.ml.container_stop.return_value = \ (200, fake_api.fake_operation_info_ok()) self.ml.container_destroy.side_effect = side_effect self.assertRaises(expected, self.session.container_destroy, instance.name, instance) @stubs.annotated_data( ('1', (200, fake_api.fake_operation_info_ok())) ) def fake_container_pause(self, tag, side_effect): """ container_pause pauses a container on a given LXD host. Verify that the appropiate pylxd API calls are made. """ instance = stubs._fake_instance() self.ml.container_suspend.return_value = side_effect self.assertEqual(None, self.session.container_pause(instance.name, instance)) calls = [ mock.call.container_susepnd(instance.name, -1), mock.call.wait_container_operation( '/1.0/operation/1234', 200, -1)] self.assertEqual(calls, self.ml.method_calls) @stubs.annotated_data( ('api_fail', lxd_exceptions.APIError(500, 'Fake'), exception.NovaException) ) def test_container_pause_fail(self, tag, side_effect, expected): """ container_pause pauses a contianer on a LXD host. Verify that an exception.NovaException is raised when there is an APIError. """ instance = stubs._fake_instance() instance = stubs._fake_instance() self.ml.container_suspend.side_effect = side_effect self.assertRaises(expected, self.session.container_pause, instance.name, instance) @stubs.annotated_data( ('1', (200, fake_api.fake_operation_info_ok())) ) def test_container_unpause(self, tag, side_effect): """ container_unpase unpauses a continer on a LXD host. Check that the right pylxd calls are being sent to the LXD API server. """ instance = stubs._fake_instance() self.ml.container_resume.return_value = side_effect self.assertEqual(None, self.session.container_unpause(instance.name, instance)) calls = [ mock.call.container_defined(instance.name), mock.call.container_resume(instance.name, -1), mock.call.wait_container_operation( '/1.0/operation/1234', 200, -1)] self.assertEqual(calls, self.ml.method_calls) @stubs.annotated_data( ('api_fail', lxd_exceptions.APIError(500, 'Fake'), exception.NovaException) ) def test_container_unpause_fail(self, tag, side_effect, expected): """ container_unpause resumes a previously suespended container. Validate that an exception.NovaException is raised when a APIError is sent by the API. """ instance = stubs._fake_instance() self.ml.container_resume.side_effect = side_effect self.assertRaises(expected, self.session.container_unpause, instance.name, instance) @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.assertEqual(None, 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) nova-lxd-13.0.0/nova_lxd/tests/session/test_image.py0000664000175000017500000000352412701774405022706 0ustar chuckchuck00000000000000# 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 ddt import mock from nova import test from nova_lxd.nova.virt.lxd import session from nova_lxd.tests import stubs @ddt.ddt class SessionImageTest(test.NoDBTestCase): def setUp(self): super(SessionImageTest, self).setUp() self.ml = stubs.lxd_mock() lxd_patcher = mock.patch('pylxd.api.API', mock.Mock(return_value=self.ml)) lxd_patcher.start() self.addCleanup(lxd_patcher.stop) self.session = session.LXDAPISession() def test_image_defined(self): """Test the image is defined in the LXD hypervisor.""" instance = stubs._fake_instance() self.ml.alias_defined.return_value = True self.assertTrue(self.session.image_defined(instance)) calls = [mock.call.alias_defined(instance.image_ref)] self.assertEqual(calls, self.ml.method_calls) def test_alias_create(self): """Test the alias is created.""" instance = stubs._fake_instance() alias = mock.Mock() self.ml.alias_create.return_value = True self.assertTrue(self.session.create_alias(alias, instance)) calls = [mock.call.alias_create(alias)] self.assertEqual(calls, self.ml.method_calls) nova-lxd-13.0.0/nova_lxd/tests/session/test_migrate.py0000664000175000017500000000226612701774405023256 0ustar chuckchuck00000000000000# 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 ddt import mock from nova import test from nova_lxd.nova.virt.lxd import session from nova_lxd.tests import stubs @ddt.ddt class SessionMigrateTest(test.NoDBTestCase): def setUp(self): super(SessionMigrateTest, self).setUp() """This is so we can mock out pylxd API calls.""" self.ml = stubs.lxd_mock() lxd_patcher = mock.patch('pylxd.api.API', mock.Mock(return_value=self.ml)) lxd_patcher.start() self.addCleanup(lxd_patcher.stop) self.session = session.LXDAPISession() nova-lxd-13.0.0/nova_lxd/tests/session/test_snapshot.py0000664000175000017500000000555612701774405023472 0ustar chuckchuck00000000000000# 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 ddt import mock from nova import exception from nova import test from pylxd.deprecated import exceptions as lxd_exceptions from nova_lxd.nova.virt.lxd import session from nova_lxd.tests import fake_api from nova_lxd.tests import stubs @ddt.ddt class SessionSnapshotTest(test.NoDBTestCase): def setUp(self): super(SessionSnapshotTest, self).setUp() """This is so we can mock out pylxd API calls.""" self.ml = stubs.lxd_mock() lxd_patcher = mock.patch('pylxd.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_snapshot(self, tag, side_effect): snapshot = mock.Mock() instance = stubs._fake_instance() self.ml.container_snapshot_create.return_value = side_effect self.assertEqual(None, self.session.container_snapshot(snapshot, instance)) calls = [ mock.call.container_snapshot_create(instance.name, snapshot), mock.call.wait_container_operation( '/1.0/operation/1234', 200, -1)] self.assertEqual(calls, self.ml.method_calls) @stubs.annotated_data( ('api_fail', lxd_exceptions.APIError(500, 'Fake'), exception.NovaException) ) def test_container_snapshot_fail(self, tag, side_effect, expected): snapshot = mock.Mock() instance = stubs._fake_instance() self.ml.container_snapshot_create.side_effect = side_effect self.assertRaises(expected, self.session.container_snapshot, instance.name, snapshot) @stubs.annotated_data( (1, (200, fake_api.fake_operation_info_ok())) ) def test_container_publish(self, tag, side_effect): image = mock.Mock() instance = stubs._fake_instance() self.ml.image_export.return_value = True self.assertTrue( self.session.container_publish(image, instance)) calls = [ mock.call.container_publish(image)] self.assertEqual(calls, self.ml.method_calls) nova-lxd-13.0.0/nova_lxd/tests/session/__init__.py0000664000175000017500000000000012701774405022306 0ustar chuckchuck00000000000000nova-lxd-13.0.0/nova_lxd/tests/test_config.py0000664000175000017500000001340312701774405021403 0ustar chuckchuck00000000000000# 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 ddt import mock from nova import test from nova.tests.unit import fake_network from nova_lxd.nova.virt.lxd import config from nova_lxd.nova.virt.lxd import session from nova_lxd.nova.virt.lxd import utils as container_dir from nova_lxd.tests import stubs @ddt.ddt @mock.patch.object(config, 'CONF', stubs.MockConf()) @mock.patch.object(container_dir, 'CONF', stubs.MockConf()) class LXDTestContainerConfig(test.NoDBTestCase): """LXD Container configuration unit tests.""" def setUp(self): super(LXDTestContainerConfig, self).setUp() self.config = config.LXDContainerConfig() @stubs.annotated_data( ('test_name', 'name', 'instance-00000001'), ('test_source', 'source', {'type': 'image', 'alias': 'fake_image'}), ('test_devices', 'devices', {}) ) def test_create_container(self, tag, key, expected): """Tests the create_container methond on LXDContainerConfig. Inspect that the correct dictionary is returned for a given instance. """ instance = stubs._fake_instance() container_config = self.config.create_container(instance) self.assertEqual(container_config[key], expected) @stubs.annotated_data( ('test_memmoy', 'limits.memory', '512MB') ) def test_create_config(self, tag, key, expected): instance = stubs._fake_instance() instance_name = 'fake_instance' config = self.config.create_config(instance_name, instance) self.assertEqual(config[key], expected) def test_create_network(self): instance = stubs._fake_instance() instance_name = 'fake_instance' network_info = fake_network.fake_get_instance_nw_info(self) config = self.config.create_network(instance_name, instance, network_info) self.assertEqual({'fake_br1': {'hwaddr': 'DE:AD:BE:EF:00:01', 'nictype': 'bridged', 'parent': 'fake_br1', 'type': 'nic'}}, config) @mock.patch('os.path.exists', mock.Mock(return_value=True)) def test_create_disk_path(self): instance = stubs._fake_instance() config = self.config.configure_disk_path('/fake/src_path', '/fake/dest_path', 'fake_disk', instance) self.assertEqual({'fake_disk': {'path': '/fake/dest_path', 'source': '/fake/src_path', 'type': 'disk', 'optional': 'True'}}, config) def test_config_instance_options(self): instance = stubs._fake_instance() config = {} container_config = self.config.config_instance_options(config, instance) self.assertEqual({'boot.autostart': 'True'}, container_config) def test_create_container_source(self): instance = stubs._fake_instance() config = self.config.get_container_source(instance) self.assertEqual(config, {'type': 'image', 'alias': 'fake_image'}) @mock.patch.object(session.LXDAPISession, 'get_host_config', mock.Mock(return_value={'storage': 'btrfs'})) def test_container_root_btrfs(self): instance = stubs._fake_instance() config = self.config.configure_container_root(instance) self.assertEqual({'root': {'path': '/', 'type': 'disk', 'size': '10GB'}}, config) @mock.patch.object(session.LXDAPISession, 'get_host_config', mock.Mock(return_value={'storage': 'zfs'})) def test_container_root_zfs(self): instance = stubs._fake_instance() config = self.config.configure_container_root(instance) self.assertEqual({'root': {'path': '/', 'type': 'disk', 'size': '10GB'}}, config) @mock.patch.object(session.LXDAPISession, 'get_host_config', mock.Mock(return_value={'storage': 'lvm'})) def test_container_root_lvm(self): instance = stubs._fake_instance() config = self.config.configure_container_root(instance) self.assertEqual({'root': {'path': '/', 'type': 'disk'}}, config) def test_container_nested_container(self): instance = stubs._fake_instance() instance.flavor.extra_specs = {'lxd_nested_allowed': True} config = self.config.config_instance_options({}, instance) self.assertEqual({'security.nesting': 'True', 'boot.autostart': 'True'}, config) def test_container_privileged_container(self): instance = stubs._fake_instance() instance.flavor.extra_specs = {'lxd_privileged_allowed': True} config = self.config.config_instance_options({}, instance) self.assertEqual({'security.privileged': 'True', 'boot.autostart': 'True'}, config) nova-lxd-13.0.0/nova_lxd/tests/stubs.py0000664000175000017500000000633512701774405020245 0ustar chuckchuck00000000000000# 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 from nova import context from nova.tests.unit import fake_instance 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): 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) return mock.Mock(*args, **default) 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': 'fake_uuid', '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) nova-lxd-13.0.0/nova_lxd/tests/fake_api.py0000664000175000017500000002311212701774405020634 0ustar chuckchuck00000000000000# 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"}]}, } nova-lxd-13.0.0/nova_lxd/tests/test_operations.py0000664000175000017500000002353012701774405022323 0ustar chuckchuck00000000000000# 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 ddt import mock from nova import test from nova.virt import fake from nova_lxd.nova.virt.lxd import config from nova_lxd.nova.virt.lxd import image from nova_lxd.nova.virt.lxd import operations as container_ops from nova_lxd.nova.virt.lxd import session from nova_lxd.tests import stubs @ddt.ddt @mock.patch.object(container_ops, 'CONF', stubs.MockConf()) class LXDTestContainerOps(test.NoDBTestCase): """LXD Container operations unit tests.""" def setUp(self): super(LXDTestContainerOps, self).setUp() self.ml = stubs.lxd_mock() lxd_patcher = mock.patch('pylxd.api.API', mock.Mock(return_value=self.ml)) lxd_patcher.start() self.addCleanup(lxd_patcher.stop) self.operations = ( container_ops.LXDContainerOperations(fake.FakeVirtAPI())) self.mv = mock.MagicMock() vif_patcher = mock.patch.object(self.operations, 'vif_driver', self.mv) vif_patcher.start() self.addCleanup(vif_patcher.stop) def test_spawn_container(self): """Test spawn method. Ensure that the right calls are made when creating a container. """ context = mock.Mock() instance = stubs._fake_instance() image_meta = mock.Mock() injected_files = mock.Mock() admin_password = mock.Mock() network_info = mock.Mock() block_device_info = mock.Mock() with test.nested( mock.patch.object(session.LXDAPISession, 'container_defined'), mock.patch.object(container_ops.LXDContainerOperations, '_fetch_image'), mock.patch.object(container_ops.LXDContainerOperations, '_setup_network'), mock.patch.object(container_ops.LXDContainerOperations, '_setup_profile'), mock.patch.object(container_ops.LXDContainerOperations, '_add_configdrive'), mock.patch.object(container_ops.LXDContainerOperations, '_setup_container') ) as ( mock_container_defined, mock_fetch_image, mock_setup_network, mock_setup_profile, mock_add_configdrive, mock_setup_container ): mock_container_defined.return_value = False self.assertEqual(None, self.operations.spawn(context, instance, image_meta, injected_files, admin_password, network_info, block_device_info)) def test_reboot_container(self): """Test the reboot method. Ensure that the proper calls are made when rebooting a continer. """ instance = stubs._fake_instance() context = mock.Mock() with test.nested( mock.patch.object(session.LXDAPISession, 'container_reboot') ) as (container_reboot): self.assertEqual(None, self.operations.reboot(context, instance, {}, None, None, None)) self.assertTrue(container_reboot) def test_destroy_container(self): """Test the destroy conainer method. Ensure that the correct calls are made when removing the contianer. """ context = mock.Mock() instance = stubs._fake_instance() network_info = mock.Mock() with test.nested( mock.patch.object(session.LXDAPISession, 'profile_delete'), mock.patch.object(session.LXDAPISession, 'container_destroy'), mock.patch.object(container_ops.LXDContainerOperations, 'cleanup'), ) as ( mock_profile_delete, mock_container_destroy, mock_cleanup ): self.assertEqual(None, self.operations.destroy(context, instance, network_info)) self.assertTrue(mock_profile_delete) self.assertTrue(mock_container_destroy) def test_power_off(self): """Test the power_off method. Ensure that the proper calls are made when the container is powered off. """ instance = stubs._fake_instance() with test.nested( mock.patch.object(session.LXDAPISession, 'container_stop') ) as (mock_container_stop): self.assertEqual(None, self.operations.power_off(instance)) self.assertTrue(mock_container_stop) def test_power_on(self): """test the power_on method. Ensure that the proper calls are made when the container is powered on. """ instance = stubs._fake_instance() network_info = mock.Mock() context = mock.Mock() block_device_info = mock.Mock() with test.nested( mock.patch.object(session.LXDAPISession, 'container_start') ) as (mock_container_start): self.assertEqual(None, self.operations.power_on(context, instance, network_info, block_device_info)) self.assertTrue(mock_container_start) def test_pause_container(self): """Test the pause container method. Ensure that that the proper calls are made when pausing the container. """ instance = stubs._fake_instance() with test.nested( mock.patch.object(session.LXDAPISession, 'container_pause') ) as (mock_container_pause): self.assertEqual(None, self.operations.pause(instance)) self.assertTrue(mock_container_pause) def test_unpause_container(self): """Test the unapuse continaer. Ensure that the proper calls are made when unpausing a container. """ instance = stubs._fake_instance() with test.nested( mock.patch.object(session.LXDAPISession, 'container_unpause') ) as (mock_container_unpause): self.assertEqual(None, self.operations.unpause(instance)) self.assertTrue(mock_container_unpause) def test_container_suspend(self): instance = stubs._fake_instance() context = mock.Mock() with test.nested( mock.patch.object(session.LXDAPISession, 'container_pause') ) as (mock_container_suspend): self.assertEqual(None, self.operations.suspend(context, instance)) self.assertTrue(mock_container_suspend) def test_container_resume(self): instance = stubs._fake_instance() context = mock.Mock() network_info = mock.Mock() with test.nested( mock.patch.object(session.LXDAPISession, 'container_unpause') ) as (mock_container_resume): self.assertEqual(None, self.operations.resume(context, instance, network_info)) self.assertTrue(mock_container_resume) @mock.patch.object(image.LXDContainerImage, 'setup_image') def test_fetch_image(self, mock_fetch_image): instance = stubs._fake_instance() context = mock.Mock() self.operations._fetch_image(context, instance, {}) mock_fetch_image.assert_called_once_with(context, instance, {}) @mock.patch.object(container_ops.LXDContainerOperations, 'plug_vifs') def test_setup_network(self, mock_plug_vifs): instance = stubs._fake_instance() self.operations._setup_network(instance.name, [], instance) mock_plug_vifs.assert_called_once_with([], instance) @mock.patch.object(session.LXDAPISession, 'profile_create') @mock.patch.object(config.LXDContainerConfig, 'create_profile') def test_setup_profile(self, mock_profile_create, mock_create_profile): instance = stubs._fake_instance() network_info = mock.Mock() container_profile = mock.Mock() self.operations._setup_profile(instance.name, instance, network_info) mock_profile_create.assert_has_calls( [mock.call(instance, network_info)]) container_profile = mock_profile_create.return_value mock_create_profile.assert_has_calls( [mock.call(container_profile, instance)]) @mock.patch.object(config.LXDContainerConfig, 'create_container') @mock.patch.object(session.LXDAPISession, 'container_init') @mock.patch.object(session.LXDAPISession, 'container_start') def test_setup_container(self, mock_create_container, mock_container_init, mock_container_start): instance = stubs._fake_instance() self.assertEqual(None, self.operations._setup_container(instance.name, instance)) nova-lxd-13.0.0/nova_lxd/tests/test_image.py0000664000175000017500000000641612701774405021226 0ustar chuckchuck00000000000000# 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 io import json from nova import exception from nova import test import os import tarfile import ddt import fixtures import mock from oslo_concurrency import lockutils from oslo_config import fixture as config_fixture from nova_lxd.nova.virt.lxd import image from nova_lxd.nova.virt.lxd import session from nova_lxd.tests import stubs @ddt.ddt class LXDTestContainerImage(test.NoDBTestCase): @mock.patch.object(session, 'CONF', stubs.MockConf()) def setUp(self): super(LXDTestContainerImage, self).setUp() self.tempdir = self.useFixture(fixtures.TempDir()).path self.fixture = self.useFixture(config_fixture.Config(lockutils.CONF)) self.fixture.config(lock_path=self.tempdir, group='oslo_concurrency') self.fixture.config(disable_process_locking=True, group='oslo_concurrency') self.image = image.LXDContainerImage() @stubs.annotated_data( ('valid_image', True, {'disk_format': 'raw'}, None), ('qcow2_image', False, {'disk_format': 'qcow2'}, exception.ImageUnacceptable), ('iso_image', False, {'disk_format': 'iso'}, exception.ImageUnacceptable), ('image_unacceptable', False, {'disk_format': ''}, exception.ImageUnacceptable), ('bad_meta', False, {}, exception.ImageUnacceptable), ) def test_image(self, tag, sucess, image_data, expected): context = mock.Mock instance = stubs._fake_instance() with mock.patch.object(image.IMAGE_API, 'get', return_value=image_data): if sucess: self.assertEqual(expected, self.image._verify_image(context, instance)) else: self.assertRaises(expected, self.image._verify_image, context, instance) @mock.patch.object(image.IMAGE_API, 'download') def test_fetch_image(self, mock_download): context = mock.Mock() instance = stubs._fake_instance() self.assertEqual(None, self.image._fetch_image(context, instance)) @mock.patch.object(os, 'stat') @mock.patch.object(json, 'dumps') @mock.patch.object(tarfile, 'open') @mock.patch.object(io, 'BytesIO') @mock.patch.object(image.IMAGE_API, 'get') def test_get_lxd_manifest(self, mock_stat, mock_json, mock_tarfile, mock_io, mock_image): instance = stubs._fake_instance() image_meta = mock.Mock() self.assertEqual(None, self.image._get_lxd_manifest(instance, image_meta)) nova-lxd-13.0.0/nova_lxd/tests/test_migrate.py0000664000175000017500000000677512701774405021604 0ustar chuckchuck00000000000000# 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 from nova import test from nova.virt import fake from oslo_config import cfg from nova_lxd.nova.virt.lxd import config from nova_lxd.nova.virt.lxd import migrate from nova_lxd.nova.virt.lxd import operations from nova_lxd.nova.virt.lxd import session from nova_lxd.tests import stubs CONF = cfg.CONF CONF.import_opt('my_ip', 'nova.netconf') class LXDTestContainerMigrate(test.NoDBTestCase): def setUp(self): super(LXDTestContainerMigrate, self).setUp() self.migrate = migrate.LXDContainerMigrate( fake.FakeVirtAPI()) def test_migrate_disk_power_off_resize(self): self.flags(my_ip='fakeip') instance = stubs._fake_instance() network_info = mock.Mock() flavor = mock.Mock() context = mock.Mock() dest = 'fakeip' with test.nested( mock.patch.object(session.LXDAPISession, 'container_defined'), mock.patch.object(config.LXDContainerConfig, 'create_profile'), mock.patch.object(session.LXDAPISession, 'profile_update') ) as ( mock_container_defined, mock_create_profile, mock_profile_update ): self.assertEqual('', self.migrate.migrate_disk_and_power_off( context, instance, dest, flavor, network_info)) mock_container_defined.assert_called_once_with(instance.name, instance) mock_create_profile.assert_called_once_with(instance, network_info) def test_confirm_migration(self): migration = mock.Mock() instance = stubs._fake_instance() network_info = mock.Mock() with test.nested( mock.patch.object(session.LXDAPISession, 'container_defined'), mock.patch.object(session.LXDAPISession, 'profile_delete'), mock.patch.object(session.LXDAPISession, 'container_destroy'), mock.patch.object(operations.LXDContainerOperations, 'unplug_vifs'), ) as ( mock_container_defined, mock_profile_delete, mock_container_destroy, mock_unplug_vifs): self.assertEqual(None, self.migrate.confirm_migration(migration, instance, network_info)) mock_container_defined.assert_called_once_with(instance.name, instance) mock_profile_delete.assert_called_once_with(instance) mock_unplug_vifs.assert_called_once_with(instance, network_info) nova-lxd-13.0.0/nova_lxd/tests/test_vif_api.py0000664000175000017500000001402612701774405021555 0ustar chuckchuck00000000000000# 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 copy import ddt import mock from oslo_concurrency import processutils from nova import exception from nova.network import model as network_model from nova import test from nova_lxd.nova.virt.lxd import vif from nova_lxd.tests import stubs @ddt.ddt class LXDTestNetworkDriver(test.NoDBTestCase): vif_data = { 'id': '0123456789abcdef', 'type': network_model.VIF_TYPE_OVS, 'address': '00:11:22:33:44:55', 'network': { 'bridge': 'fakebr'}} def setUp(self): super(LXDTestNetworkDriver, self).setUp() self.vif_driver = vif.LXDGenericDriver() mn = mock.Mock() net_patcher = mock.patch.object(vif, 'linux_net', mn) net_patcher.start() self.addCleanup(net_patcher.stop) me = mock.Mock() net_patcher = mock.patch.object(vif.utils, 'execute', me) net_patcher.start() self.addCleanup(net_patcher.stop) self.mgr = mock.Mock() self.mgr.attach_mock(mn, 'net') self.mgr.attach_mock(me, 'ex') def test_nonetype(self): instance = stubs.MockInstance() vif_data = {'type': None} self.assertRaises( exception.NovaException, self.vif_driver.plug, instance, vif_data) def test_get_config_ovs(self): instance = stubs._fake_instance() vif_data = copy.deepcopy(self.vif_data) vif_type = self.vif_driver.get_config(instance, vif_data) self.assertEqual(vif_type, {'bridge': 'qbr0123456789a', 'mac_address': '00:11:22:33:44:55'}) def test_get_config_bridge(self): instance = stubs._fake_instance() vif_data = copy.deepcopy(self.vif_data) vif_type = self.vif_driver.get_config(instance, vif_data) self.assertEqual(vif_type, {'bridge': 'qbr0123456789a', 'mac_address': '00:11:22:33:44:55'}) @stubs.annotated_data( ('id', {}, [True, True]), ('ovs-id', {'ovs_interfaceid': '123456789abcdef0'}, [True, True]), ('no-bridge', {}, [False, True]), ('no-v2', {}, [True, False]), ('no-bridge-or-v2', {}, [False, False]), ) def test_plug(self, tag, vif_data, exists): instance = stubs.MockInstance() vif_data = copy.deepcopy(self.vif_data) vif_data.update(vif_data) self.mgr.net.device_exists.side_effect = exists self.assertEqual( None, self.vif_driver.plug(instance, vif_data)) calls = [ mock.call.net.device_exists('qbr0123456789a'), mock.call.net.device_exists('qvo0123456789a') ] if not exists[0]: calls[1:1] = [ mock.call.ex( 'brctl', 'addbr', 'qbr0123456789a', run_as_root=True), mock.call.ex( 'brctl', 'setfd', 'qbr0123456789a', 0, run_as_root=True), mock.call.ex('brctl', 'stp', 'qbr0123456789a', 'off', run_as_root=True), mock.call.ex('tee', '/sys/class/net/qbr0123456789a/' 'bridge/multicast_snooping', process_input='0', run_as_root=True, check_exit_code=[0, 1]), ] if not exists[1]: calls.extend([ mock.call.net._create_veth_pair('qvb0123456789a', 'qvo0123456789a'), mock.call.ex('ip', 'link', 'set', 'qbr0123456789a', 'up', run_as_root=True), mock.call.ex('brctl', 'addif', 'qbr0123456789a', 'qvb0123456789a', run_as_root=True)]) calls.append(mock.call.net.create_ovs_vif_port( 'fakebr', 'qvo0123456789a', '0123456789abcdef', '00:11:22:33:44:55', 'fake-uuid')) self.assertEqual(calls, self.mgr.method_calls) def test_unplug_fail(self): instance = stubs.MockInstance() vif_data = copy.deepcopy(self.vif_data) self.mgr.net.device_exists.side_effect = ( processutils.ProcessExecutionError) self.assertEqual( None, self.vif_driver.unplug(instance, vif_data)) @stubs.annotated_data( ('id', {}, [True, True]), ('ovs-id', {'ovs_interfaceid': '123456789abcdef0'}, [True, True]), ('no-bridge', {}, [False, True]), ('no-v2', {}, [True, False]), ('no-bridge-or-v2', {}, [False, False]), ) def test_unplug(self, tag, vif_data, exists): instance = stubs.MockInstance() vif = copy.deepcopy(self.vif_data) self.mgr.net.device_exists.side_effect = exists self.assertEqual( None, self.vif_driver.unplug(instance, vif)) calls = [mock.call.net.device_exists('qbr0123456789a')] if exists[0]: calls[1:1] = [ mock.call.ex('brctl', 'delif', 'qbr0123456789a', 'qvb0123456789a', run_as_root=True), mock.call.ex('ip', 'link', 'set', 'qbr0123456789a', 'down', run_as_root=True), mock.call.ex('brctl', 'delbr', 'qbr0123456789a', run_as_root=True), mock.call.net.delete_ovs_vif_port('fakebr', 'qvo0123456789a') ] self.assertEqual(calls, self.mgr.method_calls) nova-lxd-13.0.0/nova_lxd/tests/test_driver_api.py0000664000175000017500000004507612701774405022275 0ustar chuckchuck00000000000000# 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 inspect import json import os import platform from pylxd.deprecated import exceptions as lxd_exceptions import ddt import mock from oslo_config import cfg import six from nova.compute import arch from nova.compute import hv_type from nova.compute import power_state from nova.compute import vm_mode from nova import exception from nova import test from nova.virt import fake from nova.virt import hardware from nova_lxd.nova.virt.lxd import driver from nova_lxd.nova.virt.lxd import host from nova_lxd.nova.virt.lxd import operations as container_ops from nova_lxd.nova.virt.lxd import session from nova_lxd.nova.virt.lxd import utils as container_dir from nova_lxd.tests import stubs class LXDTestConfig(test.NoDBTestCase): def test_config(self): self.assertIsInstance(driver.CONF.lxd, cfg.ConfigOpts.GroupAttr) self.assertEqual(os.path.abspath('/var/lib/lxd'), os.path.abspath(driver.CONF.lxd.root_dir)) self.assertEqual(-1, driver.CONF.lxd.timeout) @ddt.ddt @mock.patch.object(container_ops, 'CONF', stubs.MockConf()) @mock.patch.object(container_dir, 'CONF', stubs.MockConf()) @mock.patch.object(driver, 'CONF', stubs.MockConf()) @mock.patch.object(host, 'CONF', stubs.MockConf()) class LXDTestDriver(test.NoDBTestCase): @mock.patch.object(driver, 'CONF', stubs.MockConf()) def setUp(self): super(LXDTestDriver, self).setUp() self.ml = stubs.lxd_mock() lxd_patcher = mock.patch('pylxd.api.API', mock.Mock(return_value=self.ml)) lxd_patcher.start() self.addCleanup(lxd_patcher.stop) self.connection = driver.LXDDriver(fake.FakeVirtAPI()) def test_capabilities(self): self.assertFalse(self.connection.capabilities['has_imagecache']) self.assertFalse(self.connection.capabilities['supports_recreate']) self.assertFalse( self.connection.capabilities['supports_migrate_to_same_host']) def test_init_host(self): self.assertEqual( True, self.connection.init_host(None) ) def test_init_host_new_profile(self): self.ml.profile_list.return_value = [] self.assertEqual( True, self.connection.init_host(None) ) @stubs.annotated_data( ('no_ping', {'host_ping.return_value': False}), ('ping_fail', {'host_ping.side_effect': (lxd_exceptions. APIError('Fake', 500))}), ) def test_init_host_fail(self, tag, config): self.ml.configure_mock(**config) self.assertRaises( exception.HostNotFound, self.connection.init_host, None ) @stubs.annotated_data( ('running', {'state': 200, 'mem': 0, 'max_mem': 0}, power_state.RUNNING), ('shutdown', {'state': 102, 'mem': 0, 'max_mem': 0}, power_state.SHUTDOWN), ('crashed', {'state': 108, 'mem': 0, 'max_mem': 0}, power_state.CRASHED), ('suspend', {'state': 109, 'mem': 0, 'max_mem': 0}, power_state.SUSPENDED), ('no_state', {'state': 401, 'mem': 0, 'max_mem': 0}, power_state.NOSTATE), ) def test_get_info(self, tag, side_effect, expected): instance = stubs._fake_instance() with mock.patch.object(session.LXDAPISession, "container_state", ) as state: state.return_value = side_effect info = self.connection.get_info(instance) self.assertEqual(dir(hardware.InstanceInfo(state=expected, num_cpu=2)), dir(info)) @stubs.annotated_data( (True, 'mock-instance-1'), (False, 'fake-instance'), ) def test_instance_exists(self, expected, name): self.assertEqual( expected, self.connection.instance_exists(stubs.MockInstance(name=name))) def test_estimate_instance_overhead(self): self.assertEqual( {'memory_mb': 0}, self.connection.estimate_instance_overhead(mock.Mock())) def test_list_instances(self): self.assertEqual(['mock-instance-1', 'mock-instance-2'], self.connection.list_instances()) def test_list_instances_fail(self): self.ml.container_list.side_effect = ( lxd_exceptions.APIError('Fake', 500)) self.assertRaises( exception.NovaException, self.connection.list_instances ) @stubs.annotated_data( ('exists', [True], exception.InstanceExists), ('fail', lxd_exceptions.APIError('Fake', 500), exception.NovaException) ) def test_spawn_defined(self, tag, side_effect, expected): instance = stubs.MockInstance() self.ml.container_defined.side_effect = side_effect self.assertRaises( expected, self.connection.spawn, {}, instance, {}, [], 'secret') self.ml.container_defined.called_once_with('mock_instance') @stubs.annotated_data( ('undefined', False), ('404', lxd_exceptions.APIError('Not found', 404)), ) @mock.patch('oslo_concurrency.lockutils.lock') def test_spawn_new(self, tag, side_effect, mc): context = mock.Mock() instance = stubs.MockInstance() image_meta = mock.Mock() injected_files = mock.Mock() network_info = mock.Mock() block_device_info = mock.Mock() self.ml.container_defined.side_effect = [side_effect] with test.nested( mock.patch.object(self.connection.container_ops, 'spawn'), ) as ( create_container ): self.connection.spawn(context, instance, image_meta, injected_files, None, network_info, block_device_info) self.assertTrue(create_container) def test_destroy_fail(self): instance = stubs._fake_instance() context = mock.Mock() network_info = mock.Mock() self.ml.container_destroy.side_effect = ( lxd_exceptions.APIError('Fake', 500)) with test.nested( mock.patch.object(session.LXDAPISession, 'container_destroy'), mock.patch.object(session.LXDAPISession, 'container_stop'), mock.patch.object(self.connection, 'cleanup'), mock.patch.object(container_ops.LXDContainerOperations, 'unplug_vifs'), ) as ( container_destroy, container_stop, cleanup, unplug_vifs ): self.connection.destroy(context, instance, network_info) def test_destroy(self): instance = stubs._fake_instance() context = mock.Mock() network_info = mock.Mock() with test.nested( mock.patch.object(session.LXDAPISession, 'container_stop'), mock.patch.object(session.LXDAPISession, 'container_destroy'), mock.patch.object(self.connection, 'cleanup'), mock.patch.object(container_ops.LXDContainerOperations, 'unplug_vifs'), ) as ( container_stop, container_destroy, cleanup, unplug_vifs ): self.connection.destroy(context, instance, network_info) self.assertTrue(container_stop) self.assertTrue(container_destroy) self.assertTrue(cleanup) unplug_vifs.assert_called_with(instance, network_info) @mock.patch('os.path.exists', mock.Mock(return_value=True)) @mock.patch('shutil.rmtree') def test_cleanup(self, mr): instance = stubs.MockInstance() self.assertEqual( None, self.connection.cleanup({}, instance, [], [], None, None, None)) mr.assert_called_once_with( '/fake/instances/path/fake-uuid') @mock.patch('six.moves.builtins.open') @mock.patch.object(container_ops.utils, 'execute') @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)) def test_get_console_output(self, me, mo): instance = stubs.MockInstance() mo.return_value.__enter__.return_value = six.BytesIO(b'fake contents') self.assertEqual(b'fake contents', self.connection.get_console_output({}, instance)) calls = [ mock.call('chown', '1234:1234', '/fake/lxd/root/containers/fake-uuid/console.log', run_as_root=True), mock.call('chmod', '755', '/fake/lxd/root/containers/fake-uuid', run_as_root=True) ] self.assertEqual(calls, me.call_args_list) @mock.patch.object(host.compute_utils, 'get_machine_ips') @stubs.annotated_data( ('found', ['1.2.3.4']), ('not-found', ['4.3.2.1']), ) def test_get_host_ip_addr(self, tag, return_value, mi): mi.return_value = return_value self.assertEqual('1.2.3.4', self.connection.get_host_ip_addr()) @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('six.moves.builtins.open') @mock.patch.object(container_ops.utils, 'execute') def test_get_available_resource(self, me, mo, ms): me.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') mo.side_effect = [ six.moves.cStringIO('flags: fake flag goes here\n' 'processor: 2\n' '\n'), meminfo, ] value = self.connection.get_available_resource(None) value['cpu_info'] = json.loads(value['cpu_info']) value['supported_instances'] = [[arch.I686, hv_type.LXD, vm_mode.EXE], [arch.X86_64, hv_type.LXD, vm_mode.EXE], [arch.I686, hv_type.LXC, vm_mode.EXE], [arch.X86_64, hv_type.LXC, vm_mode.EXE]] expected = {'cpu_info': {u'arch': platform.uname()[5], u'features': u'fake flag goes here', u'model': u'Fake CPU', u'topology': {u'cores': u'5', u'sockets': u'10', u'threads': u'4'}, u'vendor': u'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': [[arch.I686, hv_type.LXD, vm_mode.EXE], [arch.X86_64, hv_type.LXD, vm_mode.EXE], [arch.I686, hv_type.LXC, vm_mode.EXE], [arch.X86_64, hv_type.LXC, vm_mode.EXE]], 'vcpus': 200, 'vcpus_used': 0} self.assertEqual(expected, value) me.assert_called_once_with('lscpu') self.assertEqual([mock.call('/proc/cpuinfo', 'r'), mock.call('/proc/meminfo')], mo.call_args_list) ms.assert_called_once_with('/fake/lxd/root') def test_container_reboot(self): instance = stubs._fake_instance() context = mock.Mock() network_info = mock.Mock() reboot_type = 'SOFT' with test.nested( mock.patch.object(self.connection.container_ops, 'reboot') ) as ( reboot ): self.connection.reboot(context, instance, network_info, reboot_type) self.assertTrue(reboot) def test_container_power_off(self): instance = stubs._fake_instance() with test.nested( mock.patch.object(self.connection.container_ops, 'power_off') ) as ( power_off ): self.connection.power_off(instance) self.assertTrue(power_off) def test_container_power_on(self): context = mock.Mock() instance = stubs._fake_instance() network_info = mock.Mock() with test.nested( mock.patch.object(self.connection.container_ops, 'power_on') ) as ( power_on ): self.connection.power_on(context, instance, network_info) self.assertTrue(power_on) @stubs.annotated_data( ('refresh_security_group_rules', (mock.Mock(),)), ('refresh_security_group_members', (mock.Mock(),)), ('refresh_provider_fw_rules',), ('refresh_instance_security_rules', (mock.Mock(),)), ('ensure_filtering_rules_for_instance', (mock.Mock(), mock.Mock())), ('filter_defer_apply_on',), ('filter_defer_apply_off',), ('unfilter_instance', (mock.Mock(), mock.Mock())), ) def test_firewall_calls(self, name, args=()): with mock.patch.object(self.connection.container_firewall, 'firewall_driver') as mf: driver_method = getattr(self.connection, name) firewall_method = getattr(mf, name) self.assertEqual( firewall_method.return_value, driver_method(*args)) firewall_method.assert_called_once_with(*args) @mock.patch.object(host.utils, 'execute') def test_get_host_uptime(self, me): me.return_value = ('out', 'err') self.assertEqual('out', self.connection.get_host_uptime()) @mock.patch('socket.gethostname', mock.Mock(return_value='mock_hostname')) def test_get_available_nodes(self): self.assertEqual( ['mock_hostname'], self.connection.get_available_nodes()) @mock.patch('socket.gethostname', mock.Mock(return_value='mock_hostname')) @stubs.annotated_data( ('mock_hostname', True), ('wrong_hostname', False), ) def test_node_is_available(self, nodename, available): self.assertEqual(available, self.connection.node_is_available(nodename)) @ddt.ddt class LXDTestDriverNoops(test.NoDBTestCase): def setUp(self): super(LXDTestDriverNoops, self).setUp() self.connection = driver.LXDDriver(fake.FakeVirtAPI()) @ddt.data( 'list_instance_uuids', 'get_diagnostics', 'get_instance_diagnostics', 'get_all_bw_counters', 'get_all_volume_usage', 'attach_volume', 'detach_volume', 'soft_delete', 'post_live_migration_at_source', 'check_instance_shared_storage_local', 'check_instance_shared_storage_remote', 'check_can_live_migrate_destination', 'check_can_live_migrate_destination_cleanup', 'check_can_live_migrate_source', 'get_instance_disk_info', 'poll_rebooting_instances', 'host_power_action', 'host_maintenance_mode', 'set_host_enabled', 'block_stats', 'add_to_aggregate', 'remove_from_aggregate', 'undo_aggregate_operation', 'volume_snapshot_create', 'volume_snapshot_delete', 'quiesce', 'unquiesce', ) def test_notimplemented(self, method): call = getattr(self.connection, method) argspec = inspect.getargspec(call) self.assertRaises( NotImplementedError, call, *([None] * (len(argspec.args) - 1))) @ddt.data( 'post_interrupted_snapshot_cleanup', 'check_instance_shared_storage_cleanup', 'manage_image_cache', ) def test_pass(self, method): call = getattr(self.connection, method) argspec = inspect.getargspec(call) self.assertEqual( None, call(*([None] * (len(argspec.args) - 1)))) @stubs.annotated_data( ('deallocate_networks_on_reschedule', False), ('macs_for_instance', None), ('get_per_instance_usage', {}), ('instance_on_disk', False), ) def test_return(self, method, expected): call = getattr(self.connection, method) argspec = inspect.getargspec(call) self.assertEqual( expected, call(*([None] * (len(argspec.args) - 1)))) nova-lxd-13.0.0/nova_lxd/tests/__init__.py0000664000175000017500000000000012701774405020623 0ustar chuckchuck00000000000000nova-lxd-13.0.0/nova_lxd/nova/0000775000175000017500000000000012701775064016327 5ustar chuckchuck00000000000000nova-lxd-13.0.0/nova_lxd/nova/virt/0000775000175000017500000000000012701775064017313 5ustar chuckchuck00000000000000nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/0000775000175000017500000000000012701775064020102 5ustar chuckchuck00000000000000nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/migrate.py0000664000175000017500000001547112701774405022112 0ustar chuckchuck00000000000000# 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 import utils from nova.virt import configdrive from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import fileutils from nova_lxd.nova.virt.lxd import config from nova_lxd.nova.virt.lxd import operations from nova_lxd.nova.virt.lxd import utils as container_dir from nova_lxd.nova.virt.lxd import session _ = i18n._ _LE = i18n._LE _LI = i18n._LI CONF = cfg.CONF CONF.import_opt('my_ip', 'nova.netconf') LOG = logging.getLogger(__name__) class LXDContainerMigrate(object): def __init__(self, virtapi): self.virtapi = virtapi self.config = config.LXDContainerConfig() self.container_dir = container_dir.LXDContainerDirectories() self.session = session.LXDAPISession() self.operations = \ operations.LXDContainerOperations( self.virtapi) def migrate_disk_and_power_off(self, context, instance, dest, flavor, network_info, block_device_info=None, timeout=0, retry_interval=0): LOG.debug("migrate_disk_and_power_off called", instance=instance) same_host = False if CONF.my_ip == dest: same_host = True LOG.debug('Migration target is the source host') else: LOG.debug('Migration target host: %s' % dest) if not self.session.container_defined(instance.name, instance): msg = _('Instance is not found.') raise exception.NovaException(msg) try: if same_host: container_profile = self.config.create_profile(instance, network_info) self.session.profile_update(container_profile, instance) else: self.session.container_stop(instance.name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('failed to resize container ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) # disk_info is not used return "" def confirm_migration(self, migration, instance, network_info): LOG.debug("confirm_migration called", instance=instance) if not self.session.container_defined(instance.name, instance): msg = _('Failed to find container %(instance)s') % \ {'instance': instance.name} raise exception.NovaException(msg) try: self.session.profile_delete(instance) self.session.container_destroy(instance.name, instance) self.operations.unplug_vifs(instance, network_info) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Confirm migration failed for %(instance)s: ' '%(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def finish_migration(self, context, migration, instance, disk_info, network_info, image_meta, resize_instance=False, block_device_info=None, power_on=True): LOG.debug("finish_migration called", instance=instance) if self.session.container_defined(instance.name, instance): return try: # Ensure that the instance directory exists instance_dir = \ self.container_dir.get_instance_dir(instance.name) if not os.path.exists(instance_dir): fileutils.ensure_tree(instance_dir) if configdrive.required_by(instance): configdrive_dir = \ self.container_dir.get_container_configdrive( instance.name) fileutils.ensure_tree(configdrive_dir) # Step 1 - Setup the profile on the dest host container_profile = self.config.create_profile(instance, network_info) self.session.profile_create(container_profile, instance) # Step 2 - Open a websocket on the srct and and # generate the container config src_host = self._get_hostname( migration['source_compute'], instance) (state, data) = (self.session.container_migrate(instance.name, src_host, instance)) container_config = self.config.create_container(instance) container_config['source'] = \ self.config.get_container_migrate( data, migration, src_host, instance) self.session.container_init(container_config, instance) # Step 3 - Start the network and contianer self.operations.plug_vifs(instance, network_info) self.session.container_start(instance.name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Migration failed for %(instance)s: ' '%(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def finish_revert_migration(self, context, instance, network_info, block_device_info=None, power_on=True): LOG.debug('finish_revert_migration called for instance', instance=instance) if self.session.container_defined(instance.name, instance): self.session.container_start(instance.name, instance) def _get_hostname(self, host, instance): LOG.debug('_get_hostname called for instance', instance=instance) out, err = utils.execute('env', 'LANG=C', 'dnsdomainname') if out != '': return '%s.%s' % (host, out.rstrip('\n')) else: return host nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/config.py0000664000175000017500000003314412701774405021724 0ustar chuckchuck00000000000000# Copyright 2011 Justin Santa Barbara # 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 socket from nova import exception from nova import i18n from nova.virt import configdrive from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils from nova_lxd.nova.virt.lxd import session from nova_lxd.nova.virt.lxd import utils as container_dir from nova_lxd.nova.virt.lxd import vif _ = i18n._ _LE = i18n._LE _LI = i18n._LI CONF = cfg.CONF CONF.import_opt('my_ip', 'nova.netconf') LOG = logging.getLogger(__name__) class LXDContainerConfig(object): """LXD configuration methods.""" def __init__(self): self.container_dir = container_dir.LXDContainerDirectories() self.session = session.LXDAPISession() self.vif_driver = vif.LXDGenericDriver() def create_container(self, instance): """Create a LXD container dictionary so that we can use it to initialize a container :param instance: nova instance object """ LOG.debug('create_container called for instance', instance=instance) instance_name = instance.name try: # Fetch the container configuration from the current nova # instance object container_config = { 'name': instance_name, 'profiles': [str(instance.name)], 'source': self.get_container_source(instance), 'devices': {} } # if a configdrive is required, setup the mount point for # the container if configdrive.required_by(instance): configdrive_dir = \ self.container_dir.get_container_configdrive( instance.name) config = self.configure_disk_path(configdrive_dir, 'var/lib/cloud/data', 'configdrive', instance) container_config['devices'].update(config) if container_config is None: msg = _('Failed to get container configuration for %s') \ % instance_name raise exception.NovaException(msg) return container_config except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error('Failed to get container configuration' ' %(instance)s: %(ex)s', {'instance': instance_name, 'ex': ex}, instance=instance) def create_profile(self, instance, network_info): """Create a LXD container profile configuration :param instance: nova instance object :param network_info: nova network configuration object :return: LXD container profile dictionary """ LOG.debug('create_container_profile called for instance', instance=instance) instance_name = instance.name try: config = {} config['name'] = str(instance_name) config['config'] = self.create_config(instance_name, instance) # Restrict the size of the "/" disk config['devices'] = self.configure_container_root(instance) if network_info: config['devices'].update(self.create_network(instance_name, instance, network_info)) return config except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to create profile %(instance)s: %(ex)s'), {'instance': instance_name, 'ex': ex}, instance=instance) def create_config(self, instance_name, instance): """Create the LXD container resources :param instance_name: instance name :param instance: nova instance object :return: LXD resources dictionary """ LOG.debug('create_config called for instance', instance=instance) try: config = {} # Update continaer options config.update(self.config_instance_options(config, instance)) # Set the instance memory limit mem = instance.memory_mb if mem >= 0: config['limits.memory'] = '%sMB' % mem # Set the instance vcpu limit vcpus = instance.flavor.vcpus if vcpus >= 0: config['limits.cpu'] = str(vcpus) # Configure the console for the instance config['raw.lxc'] = 'lxc.console.logfile=%s\n' \ % self.container_dir.get_console_path(instance_name) return config except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to set container resources %(instance)s: ' '%(ex)s'), {'instance': instance_name, 'ex': ex}, instance=instance) def config_instance_options(self, config, instance): LOG.debug('config_instance_options called for instance', instance=instance) # Set the container to autostart when the host reboots config['boot.autostart'] = 'True' # Determine if we require a nested container flavor = instance.flavor lxd_nested_allowed = flavor.extra_specs.get( 'lxd_nested_allowed', False) if lxd_nested_allowed: config['security.nesting'] = 'True' # Determine if we require a privileged container lxd_privileged_allowed = flavor.extra_specs.get( 'lxd_privileged_allowed', False) if lxd_privileged_allowed: config['security.privileged'] = 'True' return config def configure_container_root(self, instance): LOG.debug('configure_container_root called for instance', instance=instance) try: config = {} lxd_config = self.session.get_host_config(instance) if str(lxd_config['storage']) in ['btrfs', 'zfs']: config['root'] = {'path': '/', 'type': 'disk', 'size': '%sGB' % str(instance.root_gb)} else: config['root'] = {'path': '/', 'type': 'disk'} return config except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to configure disk for ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def create_network(self, instance_name, instance, network_info): """Create the LXD container network on the host :param instance_name: nova instance name :param instance: nova instance object :param network_info: instance network configuration object :return:network configuration dictionary """ LOG.debug('create_network called for instance', instance=instance) try: network_devices = {} if not network_info: return for vifaddr in network_info: cfg = self.vif_driver.get_config(instance, vifaddr) network_devices[str(cfg['bridge'])] = \ {'nictype': 'bridged', 'hwaddr': str(cfg['mac_address']), 'parent': str(cfg['bridge']), 'type': 'nic'} return network_devices except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Fail to configure network for %(instance)s: %(ex)s'), {'instance': instance_name, 'ex': ex}, instance=instance) def get_container_source(self, instance): """Set the LXD container image for the instance. :param instance: nova instance object :return: the container source """ LOG.debug('get_container_source called for instance', instance=instance) try: container_source = {'type': 'image', 'alias': str(instance.image_ref)} if container_source is None: msg = _('Failed to determine container source for %s') \ % instance.name raise exception.NovaException(msg) return container_source except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to configure container source ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def get_container_migrate(self, container_migrate, migration, host, instance): LOG.debug('get_container_migrate called for instance', instance=instance) try: # Generate the container config host = socket.gethostbyname(host) container_metadata = container_migrate['metadata'] container_control = container_metadata['metadata']['control'] container_fs = container_metadata['metadata']['fs'] container_url = 'https://%s:8443%s' \ % (host, container_migrate.get('operation')) container_migrate = { 'base_image': '', 'mode': 'pull', 'certificate': str(self.session.host_certificate(instance, host)), 'operation': str(container_url), 'secrets': { 'control': str(container_control), 'fs': str(container_fs) }, 'type': 'migration' } return container_migrate except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to configure migation source ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def configure_disk_path(self, src_path, dest_path, vfs_type, instance): """Configure the host mount point for the LXD container :param src_path: source path on the house :param dest_path: destination path on the LXD container :param vfs_type: dictionary identifier :param instance: nova instance object :return: container disk paths """ LOG.debug('configure_disk_path called for instance', instance=instance) try: config = {} config[vfs_type] = {'path': dest_path, 'source': src_path, 'type': 'disk', 'optional': 'True'} return config except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to configure disk for ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def create_container_net_device(self, instance, vif): """Translate nova network object into a LXD interface :param instance: nova instance object :param vif: network instaance object """ LOG.debug('create_container_net_device called for instance', insance=instance) try: network_config = self.vif_driver.get_config(instance, vif) config = {} config[self.get_network_device(instance)] = { 'nictype': 'bridged', 'hwaddr': str(vif['address']), 'parent': str(network_config['bridge']), 'type': 'nic'} return config except Exception as ex: LOG.error(_LE('Failed to configure network for ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def get_network_device(self, instance): """Try to detect which network interfaces are available in a contianer :param instance: nova instance object """ LOG.debug('get_network_device called for instance', instance=instance) data = self.session.container_info(instance) lines = open('/proc/%s/net/dev' % data['init']).readlines() interfaces = [] for line in lines[2:]: if line.find(':') < 0: continue face, _ = line.split(':') if 'eth' in face: interfaces.append(face.strip()) if len(interfaces) == 1: return 'eth1' else: return 'eth%s' % int(len(interfaces) - 1) nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/constants.py0000664000175000017500000000217412701774405022472 0ustar chuckchuck00000000000000# 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 nova.compute import power_state LXD_POWER_STATES = { 100: power_state.RUNNING, 101: power_state.RUNNING, 102: power_state.SHUTDOWN, 103: power_state.RUNNING, 104: power_state.SHUTDOWN, 105: power_state.NOSTATE, 106: power_state.NOSTATE, 107: power_state.SHUTDOWN, 108: power_state.CRASHED, 109: power_state.SUSPENDED, 110: power_state.SUSPENDED, 111: power_state.SUSPENDED, 200: power_state.RUNNING, 400: power_state.CRASHED, 401: power_state.NOSTATE } nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/driver.py0000664000175000017500000003436712701774405021762 0ustar chuckchuck00000000000000# Copyright 2011 Justin Santa Barbara # 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 from nova import exception from nova import i18n from nova.virt import driver import socket from oslo_config import cfg from oslo_log import log as logging from nova_lxd.nova.virt.lxd import container_firewall from nova_lxd.nova.virt.lxd import container_snapshot from nova_lxd.nova.virt.lxd import host from nova_lxd.nova.virt.lxd import migrate from nova_lxd.nova.virt.lxd import operations as container_ops from nova_lxd.nova.virt.lxd import vif as lxd_vif _ = i18n._ lxd_opts = [ cfg.StrOpt('root_dir', default='/var/lib/lxd/', help='Default LXD directory'), cfg.IntOpt('timeout', default=-1, help='Default LXD timeout'), cfg.IntOpt('retry_interval', default=2, help='How often to retry in seconds when a' 'request does conflict'), ] CONF = cfg.CONF CONF.register_opts(lxd_opts, 'lxd') LOG = logging.getLogger(__name__) class LXDDriver(driver.ComputeDriver): """LXD Lightervisor.""" capabilities = { "has_imagecache": False, "supports_recreate": False, "supports_migrate_to_same_host": False, } def __init__(self, virtapi): self.virtapi = virtapi self.vif_driver = lxd_vif.LXDGenericDriver() self.container_ops = container_ops.LXDContainerOperations(virtapi) self.container_snapshot = container_snapshot.LXDSnapshot() self.container_firewall = container_firewall.LXDContainerFirewall() self.container_migrate = migrate.LXDContainerMigrate(virtapi) self.host = host.LXDHost() def init_host(self, host): return self.host.init_host(host) def get_info(self, instance): return self.container_ops.get_info(instance) def instance_exists(self, instance): try: return instance.name in self.list_instance_uuids() except NotImplementedError: return instance.name in self.list_instances() def plug_vifs(self, instance, network_info): """Plug VIFs into networks.""" for vif in network_info: self.vif_driver.plug(instance, vif) def unplug_vifs(self, instance, network_info): """Unplug VIFs from networks.""" for vif in network_info: try: self.vif_driver.unplug(instance, vif) except exception.NovaException: pass def estimate_instance_overhead(self, instance_info): return {'memory_mb': 0} def list_instances(self): return self.container_ops.list_instances() def list_instance_uuids(self): raise NotImplementedError() def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info=None, block_device_info=None): self.container_ops.spawn(context, instance, image_meta, injected_files, admin_password, network_info, block_device_info) def destroy(self, context, instance, network_info, block_device_info=None, destroy_disks=True, migrate_data=None): self.container_ops.destroy(context, instance, network_info, block_device_info, destroy_disks, migrate_data) def cleanup(self, context, instance, network_info, block_device_info=None, destroy_disks=True, migrate_data=None, destroy_vifs=True): self.container_ops.cleanup(context, instance, network_info, block_device_info, destroy_disks, migrate_data, destroy_vifs) def reboot(self, context, instance, network_info, reboot_type, block_device_info=None, bad_volumes_callback=None): self.container_ops.reboot(context, instance, network_info, reboot_type, block_device_info, bad_volumes_callback) def get_console_output(self, context, instance): return self.container_ops.get_console_output(context, instance) def get_diagnostics(self, instance): raise NotImplementedError() def get_instance_diagnostics(self, instance): raise NotImplementedError() def get_all_bw_counters(self, instances): raise NotImplementedError() def get_all_volume_usage(self, context, compute_host_bdms): raise NotImplementedError() def get_host_ip_addr(self): return self.host.get_host_ip_addr() def attach_volume(self, context, connection_info, instance, mountpoint, disk_bus=None, device_type=None, encryption=None): raise NotImplementedError() def detach_volume(self, connection_info, instance, mountpoint, encryption=None): raise NotImplementedError() def attach_interface(self, instance, image_meta, vif): return self.container_ops.container_attach_interface(instance, image_meta, vif) def detach_interface(self, instance, vif): return self.container_ops.container_detach_interface(instance, vif) def migrate_disk_and_power_off(self, context, instance, dest, flavor, network_info, block_device_info=None, timeout=0, retry_interval=0): return self.container_migrate.migrate_disk_and_power_off( context, instance, dest, flavor, network_info, block_device_info, timeout, retry_interval) def snapshot(self, context, instance, image_id, update_task_state): return self.container_snapshot.snapshot(context, instance, image_id, update_task_state) def post_interrupted_snapshot_cleanup(self, context, instance): pass def finish_migration(self, context, migration, instance, disk_info, network_info, image_meta, resize_instance, block_device_info=None, power_on=True): return self.container_migrate.finish_migration( context, migration, instance, disk_info, network_info, image_meta, resize_instance, block_device_info, power_on) def confirm_migration(self, migration, instance, network_info): return self.container_migrate.confirm_migration(migration, instance, network_info) def finish_revert_migration(self, context, instance, network_info, block_device_info=None, power_on=True): return self.container_migrate.finish_revert_migration( context, instance, network_info, block_device_info, power_on) def pause(self, instance): self.container_ops.pause(instance) def unpause(self, instance): self.container_ops.unpause(instance) def suspend(self, context, instance): self.container_ops.suspend(context, instance) def resume(self, context, instance, network_info, block_device_info=None): self.container_ops.resume(context, instance, network_info, block_device_info) def rescue(self, context, instance, network_info, image_meta, rescue_password): self.container_ops.rescue(context, instance, network_info, image_meta, rescue_password) def unrescue(self, instance, network_info): return self.container_ops.unrescue(instance, network_info) def power_off(self, instance, timeout=0, retry_interval=0): self.container_ops.power_off(instance, timeout=0, retry_interval=0) def power_on(self, context, instance, network_info, block_device_info=None): self.container_ops.power_on(context, instance, network_info, block_device_info) def soft_delete(self, instance): raise NotImplementedError() def get_available_resource(self, nodename): return self.host.get_available_resource(nodename) def pre_live_migration(self, context, instance, block_device_info, network_info, disk_info, migrate_data=None): raise NotImplementedError() def live_migration(self, context, instance, dest, post_method, recover_method, block_migration=False, migrate_data=None): raise NotImplementedError() def post_live_migration(self, context, instance, block_device_info, migrate_data=None): raise NotImplementedError() def post_live_migration_at_destination(self, context, instance, network_info, block_migration=False, block_device_info=None): raise NotImplementedError() def check_instance_shared_storage_local(self, context, instance): raise NotImplementedError() def check_instance_shared_storage_remote(self, context, data): raise NotImplementedError() def check_instance_shared_storage_cleanup(self, context, data): pass def check_can_live_migrate_destination(self, context, instance, src_compute_info, dst_compute_info, block_migration=False, disk_over_commit=False): raise NotImplementedError() def check_can_live_migrate_destination_cleanup(self, context, dest_check_data): raise NotImplementedError() def check_can_live_migrate_source(self, context, instance, dest_check_data, block_device_info=None): raise NotImplementedError() def get_instance_disk_info(self, instance, block_device_info=None): raise NotImplementedError() def refresh_security_group_rules(self, security_group_id): return (self.container_firewall .refresh_security_group_rules(security_group_id)) def refresh_security_group_members(self, security_group_id): return (self.container_firewall .refresh_security_group_members(security_group_id)) def refresh_provider_fw_rules(self): return self.container_firewall.refresh_provider_fw_rules() def refresh_instance_security_rules(self, instance): return (self.container_firewall .refresh_instance_security_rules(instance)) def ensure_filtering_rules_for_instance(self, instance, network_info): return (self.container_firewall .ensure_filtering_rules_for_instance(instance, network_info)) def filter_defer_apply_on(self): return self.container_firewall.filter_defer_apply_on() def filter_defer_apply_off(self): return self.container_firewall.filter_defer_apply_off() def unfilter_instance(self, instance, network_info): return self.container_firewall.unfilter_instance(instance, network_info) def poll_rebooting_instances(self, timeout, instances): raise NotImplementedError() def host_power_action(self, action): raise NotImplementedError() def host_maintenance_mode(self, host, mode): raise NotImplementedError() def set_host_enabled(self, enabled): raise NotImplementedError() def get_host_uptime(self): return self.host.get_host_uptime() def get_host_cpu_stats(self): return self.host.get_host_cpu_stats() def block_stats(self, instance, disk_id): raise NotImplementedError() def deallocate_networks_on_reschedule(self, instance): """Does the driver want networks deallocated on reschedule?""" return False def macs_for_instance(self, instance): return None def manage_image_cache(self, context, all_instances): pass def add_to_aggregate(self, context, aggregate, host, **kwargs): raise NotImplementedError() def remove_from_aggregate(self, context, aggregate, host, **kwargs): raise NotImplementedError() def undo_aggregate_operation(self, context, op, aggregate, host, set_error=True): raise NotImplementedError() 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] def node_is_available(self, nodename): if nodename in self.get_available_nodes(): return True # Refresh and check again. return nodename in self.get_available_nodes(refresh=True) def get_per_instance_usage(self): return {} def instance_on_disk(self, instance): return False def volume_snapshot_create(self, context, instance, volume_id, create_info): raise NotImplementedError() def volume_snapshot_delete(self, context, instance, volume_id, snapshot_id, delete_info): raise NotImplementedError() def quiesce(self, context, instance, image_meta): raise NotImplementedError() def unquiesce(self, context, instance, image_meta): raise NotImplementedError() nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/vif.py0000664000175000017500000002055412701774405021244 0ustar chuckchuck00000000000000# 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_config import cfg from oslo_log import log as logging from nova import exception from nova import i18n from nova.network import linux_net from nova.network import model as network_model from nova import utils _ = i18n._ _LE = i18n._LE CONF = cfg.CONF LOG = logging.getLogger(__name__) class LXDGenericDriver(object): def get_vif_devname(self, vif): if 'devname' in vif: return vif['devname'] return ("nic" + vif['id'])[:network_model.NIC_NAME_LEN] def get_vif_devname_with_prefix(self, vif, prefix): devname = self.get_vif_devname(vif) return prefix + devname[3:] def get_bridge_name(self, vif): return vif['network']['bridge'] def get_ovs_interfaceid(self, vif): return vif.get('ovs_interfaceid') or vif['id'] def get_br_name(self, iface_id): return ("qbr" + iface_id)[:network_model.NIC_NAME_LEN] def get_veth_pair_names(self, iface_id): return (("qvb%s" % iface_id)[:network_model.NIC_NAME_LEN], ("qvo%s" % iface_id)[:network_model.NIC_NAME_LEN]) def get_firewall_required(self, vif): if CONF.firewall_driver != "nova.virt.firewall.NoopFirewallDriver": return True return False def get_config(self, instance, vif): vif_type = vif['type'] LOG.debug('vif_type=%(vif_type)s instance=%(instance)s ' 'vif=%(vif)s', {'vif_type': vif_type, 'instance': instance, 'vif': vif}) if vif_type is None: raise exception.NovaException( _("vif_type parameter must be present " "for this vif_driver implementation")) func = getattr(self, 'get_config_%s' % vif_type, None) if not func: raise exception.NovaException( _("Unexpected vif_type=%s") % vif_type) return func(instance, vif) def get_config_bridge(self, instance, vif): conf = {'bridge': self.get_bridge_name(vif), 'mac_address': vif['address']} return conf def get_config_ovs_hybrid(self, instance, vif): conf = {'bridge': self.get_br_name(vif['id']), 'mac_address': vif['address']} return conf def get_config_ovs_bridge(self, instance, vif): conf = {'bridge': self.get_bridge_name(vif), 'mac_address': vif['address']} return conf def get_config_ovs(self, instance, vif): if self.get_firewall_required(vif) or vif.is_hybrid_plug_enabled(): return self.get_config_ovs_hybrid(instance, vif) else: return self.get_config_ovs_bridge(instance, vif) def plug(self, instance, vif): vif_type = vif['type'] LOG.debug('vif_type=%(vif_type)s instance=%(instance)s ' 'vif=%(vif)s', {'vif_type': vif_type, 'instance': instance, 'vif': vif}) if vif_type is None: raise exception.NovaException( _("vif_type parameter must be present " "for this vif_driver implementation")) func = getattr(self, 'plug_%s' % vif_type, None) if not func: raise exception.NovaException( _("Unexpected vif_type=%s") % vif_type) return func(instance, vif) def plug_bridge(self, instance, vif): network = vif['network'] if (not network.get_meta('multi_host', False) and network.get_meta('should_create_bridge', False)): if network.get_meta('should_create_vlan', False): iface = (CONF.vlan_interface or network.get_meta('bridge_interface')) LOG.debug('Ensuring vlan %(vlan)s and bridge %(bridge)s', {'vlan': network.get_meta('vlan'), 'bridge': self.get_bridge_name(vif)}, instance=instance) linux_net.LinuxBridgeInterfaceDriver.ensure_vlan_bridge( network.get_meta('vlan'), self.get_bridge_name(vif), iface) else: iface = (CONF.flat_interface or network.get_meta('bridge_interface')) LOG.debug("Ensuring bridge %s", self.get_bridge_name(vif), instance=instance) linux_net.LinuxBridgeInterfaceDriver.ensure_bridge( self.get_bridge_name(vif), iface) def plug_ovs(self, instance, vif): if self.get_firewall_required(vif) or vif.is_hybrid_plug_enabled(): self.plug_ovs_hybrid(instance, vif) else: self.plug_ovs_bridge(instance, vif) def plug_ovs_bridge(self, instance, vif): pass def plug_ovs_hybrid(self, instance, vif): iface_id = self.get_ovs_interfaceid(vif) br_name = self.get_br_name(vif['id']) v1_name, v2_name = self.get_veth_pair_names(vif['id']) if not linux_net.device_exists(br_name): utils.execute('brctl', 'addbr', br_name, run_as_root=True) utils.execute('brctl', 'setfd', br_name, 0, run_as_root=True) utils.execute('brctl', 'stp', br_name, 'off', run_as_root=True) utils.execute('tee', ('/sys/class/net/%s/bridge/multicast_snooping' % br_name), process_input='0', run_as_root=True, check_exit_code=[0, 1]) if not linux_net.device_exists(v2_name): linux_net._create_veth_pair(v1_name, v2_name) utils.execute('ip', 'link', 'set', br_name, 'up', run_as_root=True) utils.execute('brctl', 'addif', br_name, v1_name, run_as_root=True) linux_net.create_ovs_vif_port(self.get_bridge_name(vif), v2_name, iface_id, vif['address'], instance.name) def unplug(self, instance, vif): vif_type = vif['type'] LOG.debug('vif_type=%(vif_type)s instance=%(instance)s ' 'vif=%(vif)s', {'vif_type': vif_type, 'instance': instance, 'vif': vif}) if vif_type is None: raise exception.NovaException( _("vif_type parameter must be present " "for this vif_driver implementation")) func = getattr(self, 'unplug_%s' % vif_type, None) if not func: raise exception.NovaException( _("Unexpected vif_type=%s") % vif_type) return func(instance, vif) def unplug_ovs(self, instance, vif): if self.get_firewall_required(vif) or vif.is_hybrid_plug_enabled(): self.unplug_ovs_hybrid(instance, vif) else: self.unplug_ovs_bridge(instance, vif) def unplug_ovs_hybrid(self, instance, vif): try: br_name = self.get_br_name(vif['id']) v1_name, v2_name = self.get_veth_pair_names(vif['id']) if linux_net.device_exists(br_name): utils.execute('brctl', 'delif', br_name, v1_name, run_as_root=True) utils.execute('ip', 'link', 'set', br_name, 'down', run_as_root=True) utils.execute('brctl', 'delbr', br_name, run_as_root=True) linux_net.delete_ovs_vif_port(self.get_bridge_name(vif), v2_name) except processutils.ProcessExecutionError: LOG.exception(_LE("Failed while unplugging vif"), instance=instance) def unplug_ovs_bridge(self, instance, vif): pass def unplug_bridge(self, instance, vif): pass nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/image.py0000664000175000017500000002632712701774405021546 0ustar chuckchuck00000000000000# 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 hashlib import io import json from nova.compute import arch from nova import exception from nova import i18n from nova import image from nova import utils import os import shutil import tarfile import tempfile import uuid from oslo_concurrency import lockutils from oslo_concurrency import processutils from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import fileutils from nova_lxd.nova.virt.lxd import session from nova_lxd.nova.virt.lxd import utils as container_dir _ = i18n._ _LE = i18n._LE CONF = cfg.CONF LOG = logging.getLogger(__name__) IMAGE_API = image.API() class LXDContainerImage(object): """Upload an image from glance to the local LXD image store.""" def __init__(self): self.client = session.LXDAPISession() self.container_dir = container_dir.LXDContainerDirectories() self.lock_path = str(os.path.join(CONF.instances_path, 'locks')) self.container_image = None self.container_manifest = None def setup_image(self, context, instance, image_meta): """Download an image from glance and upload it to LXD :param context: context object :param instance: The nova instance :param image_meta: Image dict returned by nova.image.glance """ LOG.debug('setup_image called for instance', instance=instance) self.container_image = \ self.container_dir.get_container_rootfs_image(image_meta) self.container_manifest = \ self.container_dir.get_container_manifest_image(image_meta) with lockutils.lock(self.lock_path, lock_file_prefix=('lxd-image-%s' % instance.image_ref), external=True): if self.client.image_defined(instance): return base_dir = self.container_dir.get_base_dir() if not os.path.exists(base_dir): fileutils.ensure_tree(base_dir) try: # Inspect image for the correct format self._verify_image(context, instance) # Fetch the image from glance self._fetch_image(context, instance) # Generate the LXD manifest for the image self._get_lxd_manifest(instance, image_meta) # Upload the image to the local LXD image store self._image_upload(instance) # Setup the LXD alias for the image self._setup_alias(instance) # Remove image and manifest when done. self._cleanup_image(instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to upload %(image)s to LXD: ' '%(reason)s'), {'image': instance.image_ref, 'reason': ex}, instance=instance) self._cleanup_image(instance) def _verify_image(self, context, instance): """Inspect image to verify the correct disk format. Inspect and verify and the image that will downloaded from glance is the correct image. The image must be in a raw disk format in order for the LXD daemon to import it into the local image store. :param context: nova security context ;param instance: nova instance object """ LOG.debug('_verify_image called for instance', instance=instance) try: # grab the disk format of the image img_meta = IMAGE_API.get(context, instance.image_ref) disk_format = img_meta.get('disk_format') if not disk_format: reason = _('Bad image format') raise exception.ImageUnacceptable(image_id=instance.image_ref, reason=reason) if disk_format != 'raw': reason = _('nova-lxd does not support images in %s format. ' 'You should upload an image in raw format.') % \ disk_format raise exception.ImageUnacceptable(image_id=instance.image_ref, reason=reason) except Exception as ex: reason = _('Bad Image format: %(ex)s') \ % {'ex': ex} raise exception.ImageUnacceptable(image_id=instance.image_ref, reason=reason) def _fetch_image(self, context, instance): """Fetch an image from glance :param context: nova security object :param instance: the nova instance object """ LOG.debug('_fetch_image called for instance', instance=instance) with fileutils.remove_path_on_error(self.container_image): IMAGE_API.download(context, instance.image_ref, dest_path=self.container_image) def _get_lxd_manifest(self, instance, image_meta): """Creates the LXD manifest, needed for split images :param instance: nova instance :param image_meta: image metadata dictionary """ LOG.debug('_get_lxd_manifest called for instance', instance=instance) metadata_yaml = None try: # Create a basic LXD manifest from the image properties image_arch = image_meta.properties.get('hw_architecture') if image_arch is None: image_arch = arch.from_host() metadata = { 'architecture': image_arch, 'creation_date': int(os.stat(self.container_image).st_ctime) } metadata_yaml = (json.dumps(metadata, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False).encode('utf-8') + b"\n") except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to generate manifest for %(image)s: ' '%(reason)s'), {'image': instance.name, 'ex': ex}, instance=instance) try: # Compress the manifest using tar target_tarball = tarfile.open(self.container_manifest, "w:") metadata_file = tarfile.TarInfo() metadata_file.size = len(metadata_yaml) metadata_file.name = "metadata.yaml" target_tarball.addfile(metadata_file, io.BytesIO(metadata_yaml)) target_tarball.close() except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to generate manifest tarball for' ' %(image)s: %(reason)s'), {'image': instance.name, 'ex': ex}, instance=instance) try: # Compress the manifest further using xz with fileutils.remove_path_on_error(self.container_manifest): utils.execute('xz', '-9', self.container_manifest, check_exit_code=[0, 1]) except processutils.ProcessExecutionError as ex: with excutils.save_and_reraise_exception: LOG.error(_LE('Failed to compress manifest for %(image)s:' ' %(ex)s'), {'image': instance.image_ref, 'ex': ex}, instance=instance) def _image_upload(self, instance): """Upload an image to the LXD image store We create the LXD manifest on the fly since glance does not understand how to talk to Glance. :param instance: nova instance """ LOG.debug('image_upload called for instance', instance=instance) headers = {} boundary = str(uuid.uuid1()) # Create the binary blob to upload the file to LXD tmpdir = tempfile.mkdtemp() upload_path = os.path.join(tmpdir, "upload") body = open(upload_path, 'wb+') for name, path in [("metadata", (self.container_manifest + '.xz')), ("rootfs", self.container_image)]: filename = os.path.basename(path) body.write(bytearray("--%s\r\n" % boundary, "utf-8")) body.write(bytearray("Content-Disposition: form-data; " "name=%s; filename=%s\r\n" % (name, filename), "utf-8")) body.write("Content-Type: application/octet-stream\r\n") body.write("\r\n") with open(path, "rb") as fd: shutil.copyfileobj(fd, body) body.write("\r\n") body.write(bytearray("--%s--\r\n" % boundary, "utf-8")) body.write('\r\n') body.close() headers['Content-Type'] = "multipart/form-data; boundary=%s" \ % boundary # Upload the file to LXD and then remove the tmpdir. self.client.image_upload(data=open(upload_path, 'rb'), headers=headers, instance=instance) shutil.rmtree(tmpdir) def _setup_alias(self, instance): """Creates the LXD alias for the image :param instance: nova instance """ LOG.debug('_setup_alias called for instance', instance=instance) try: with open((self.container_manifest + '.xz'), 'rb') as meta_fd: with open(self.container_image, "rb") as rootfs_fd: fingerprint = hashlib.sha256(meta_fd.read() + rootfs_fd.read()).hexdigest() alias_config = { 'name': instance.image_ref, 'target': fingerprint } self.client.create_alias(alias_config, instance) except Exception as ex: with excutils.save_and_reraise_exception: LOG.error(_LE('Failed to setup alias for %(image)s:' ' %(ex)s'), {'image': instance.image_ref, 'ex': ex}, instance=instance) def _cleanup_image(self, instance): """Cleanup the remaning bits of the glance/lxd interaction :params image_meta: image_meta dictionary """ LOG.debug('_cleanup_image called for instance', instance=instance) if os.path.exists(self.container_image): os.unlink(self.container_image) if os.path.exists(self.container_manifest): os.unlink(self.container_manifest) nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/host.py0000664000175000017500000001537112701774405021436 0ustar chuckchuck00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright (c) 2010 Citrix Systems, Inc. # Copyright 2011 Justin Santa Barbara # 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 nova.compute import arch from nova.compute import hv_type from nova.compute import utils as compute_utils from nova.compute import vm_mode from nova import exception from nova import i18n from nova import utils import os import platform from pylxd.deprecated import api from pylxd.deprecated import exceptions as lxd_exceptions import socket from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import units import psutil _ = i18n._ _LW = i18n._LW CONF = cfg.CONF LOG = logging.getLogger(__name__) class LXDHost(object): def __init__(self): self.lxd = api.API() def get_available_resource(self, nodename): LOG.debug('In get_available_resource') local_cpu_info = self._get_cpuinfo() cpu_topology = local_cpu_info['topology'] vcpus = (int(cpu_topology['cores']) * int(cpu_topology['sockets']) * int(cpu_topology['threads'])) local_memory_info = self._get_memory_mb_usage() local_disk_info = self._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(local_cpu_info), 'hypervisor_hostname': socket.gethostname(), 'supported_instances': [(arch.I686, hv_type.LXD, vm_mode.EXE), (arch.X86_64, hv_type.LXD, vm_mode.EXE), (arch.I686, hv_type.LXC, vm_mode.EXE), (arch.X86_64, hv_type.LXC, vm_mode.EXE)], 'numa_topology': None, } return data def get_host_ip_addr(self): ips = compute_utils.get_machine_ips() if CONF.my_ip not in ips: LOG.warn(_LW('my_ip address (%(my_ip)s) was not found on ' 'any of the interfaces: %(ifaces)s'), {'my_ip': CONF.my_ip, 'ifaces': ", ".join(ips)}) return CONF.my_ip def get_host_uptime(self): out, err = utils.execute('env', 'LANG=C', 'uptime') return out def _get_fs_info(self, path): """Get free/used/total space info for a filesystem :param path: Any dirent on the filesystem :returns: A dict containing :free: How much space is free (in bytes) :used: How much space is used (in bytes) :total: How big the filesytem is (in bytes) """ 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_memory_mb_usage(self): """Get the used memory size(MB) of the host. :returns: the total usage of memory(MB) """ 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_cpuinfo(self): cpuinfo = self._get_cpu_info() cpu_info = dict() cpu_info['arch'] = platform.uname()[5] cpu_info['model'] = cpuinfo.get('model name', 'unknown') cpu_info['vendor'] = cpuinfo.get('vendor id', 'unknown') topology = dict() topology['sockets'] = cpuinfo.get('socket(s)', 1) topology['cores'] = cpuinfo.get('core(s) per socket', 1) topology['threads'] = cpuinfo.get('thread(s) per core', 1) cpu_info['topology'] = topology cpu_info['features'] = cpuinfo.get('flags', 'unknown') return cpu_info def _get_cpu_info(self): '''Parse the output of lscpu.''' 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_hypersivor_version(self): version = self.lxd.get_lxd_version() return '.'.join(str(v) for v in version) def get_host_cpu_stats(self): cpuinfo = self._get_cpu_info() 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': cpuinfo.get('cpu mhz', 0) } def init_host(self, host): LOG.debug('Host check') try: if not self.lxd.host_ping(): msg = _('Unable to connect to LXD daemon') raise exception.HostNotFound(msg) return True except lxd_exceptions.APIError as ex: msg = _('Unable to connect to LXD daemon: %s') % ex raise exception.HostNotFound(msg) nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/operations.py0000664000175000017500000007006712701774405022647 0ustar chuckchuck00000000000000# Copyright 2011 Justin Santa Barbara # 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 nova.api.metadata import base as instance_metadata from nova.virt import configdrive from nova.virt import hardware import os import pwd import shutil from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import fileutils from oslo_utils import units from nova import exception from nova import i18n from nova import utils from nova.compute import power_state from nova_lxd.nova.virt.lxd import config as container_config from nova_lxd.nova.virt.lxd import container_firewall from nova_lxd.nova.virt.lxd import image from nova_lxd.nova.virt.lxd import session from nova_lxd.nova.virt.lxd import utils as container_dir from nova_lxd.nova.virt.lxd import vif _ = i18n._ _LE = i18n._LE _LW = i18n._LW _LI = i18n._LI CONF = cfg.CONF CONF.import_opt('vif_plugging_timeout', 'nova.virt.driver') CONF.import_opt('vif_plugging_is_fatal', 'nova.virt.driver') LOG = logging.getLogger(__name__) MAX_CONSOLE_BYTES = 100 * units.Ki class LXDContainerOperations(object): """LXD container operations.""" def __init__(self, virtapi): self.virtapi = virtapi self.config = container_config.LXDContainerConfig() self.container_dir = container_dir.LXDContainerDirectories() self.image = image.LXDContainerImage() self.firewall_driver = container_firewall.LXDContainerFirewall() self.session = session.LXDAPISession() self.vif_driver = vif.LXDGenericDriver() self.instance_dir = None def list_instances(self): return self.session.container_list() def spawn(self, context, instance, image_meta, injected_files, admin_password=None, network_info=None, block_device_info=None): """Start the LXD container Once this successfully completes, the instance should be running (power_state.RUNNING). If this fails, any partial instance should be completely cleaned up, and the virtualization platform should be in the state that it was before this call began. :param context: security context :param instance: nova.objects.instance.Instance This function should use the data there to guide the creation of the new instance. :param image_meta: image object returned by nova.image.glance that defines the image from which to boot this instance :param injected_files: User files to inject into instance. :param admin_password: Administrator password to set in instance. :param network_info: :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` :param block_device_info: Information about block devices to be attached to the instance """ msg = ('Spawning container ' 'network_info=%(network_info)s ' 'image_meta=%(image_meta)s ' 'instance=%(instance)s ' 'block_device_info=%(block_device_info)s' % {'network_info': network_info, 'instance': instance, 'image_meta': image_meta, 'block_device_info': block_device_info}) LOG.debug(msg, instance=instance) instance_name = instance.name if self.session.container_defined(instance_name, instance): raise exception.InstanceExists(name=instance.name) try: # Ensure that the instance directory exists self.instance_dir = \ self.container_dir.get_instance_dir(instance_name) if not os.path.exists(self.instance_dir): fileutils.ensure_tree(self.instance_dir) # Step 1 - Fetch the image from glance self._fetch_image(context, instance, image_meta) # Step 2 - Setup the container network self._setup_network(instance_name, instance, network_info) # Step 3 - Create the container profile self._setup_profile(instance_name, instance, network_info) # Step 4 - Create a config drive (optional) if configdrive.required_by(instance): self._add_configdrive(instance, injected_files) # Step 5 - Configure and start the container self._setup_container(instance_name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Faild to start container ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) self.destroy(context, instance, network_info) def _fetch_image(self, context, instance, image_meta): """Fetch the LXD image from glance :param context: nova security context :param instance: nova instance object :param image_meta: nova image opbject """ LOG.debug('_fetch_image called for instance', instance=instance) try: # Download the image from glance and upload the image # to the local LXD image store. self.image.setup_image(context, instance, image_meta) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Upload image failed for %(instance)s ' 'for %(image)s: %(e)s'), {'instance': instance.name, 'image': instance.image_ref, 'ex': ex}, instance=instance) def _setup_network(self, instance_name, instance, network_info): """Setup the network when creating the lXD container :param instance_name: nova instance name :param instance: nova instance object :param network_info: instance network configuration """ LOG.debug('_setup_network called for instance', instance=instance) try: self.plug_vifs(instance, network_info) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to create container network for ' '%(instance)s: %(ex)s'), {'instance': instance_name, 'ex': ex}, instance=instance) def _setup_profile(self, instance_name, instance, network_info): """Create an LXD container profile for the nova intsance :param instance_name: nova instance name :param instance: nova instance object :param network_info: nova instance netowkr configuration """ LOG.debug('_setup_profile called for instance', instance=instance) try: # Setup the container profile based on the nova # instance object and network objects container_profile = self.config.create_profile(instance, network_info) self.session.profile_create(container_profile, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to create a profile for' ' %(instance)s: %(ex)s'), {'instance': instance_name, 'ex': ex}, instance=instance) def _setup_container(self, instance_name, instance): """Create and start the LXD container. :param instance_name: nova instjace name :param instance: nova instance object """ LOG.debug('_setup_container called for instance', instance=instance) try: # Create the container container_config = \ self.config.create_container(instance) self.session.container_init( container_config, instance) # Start the container self.session.container_start(instance_name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Container creation failed for ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def _add_configdrive(self, instance, injected_files): """Configure the config drive for the container :param instance: nova instance object :param injected_files: instance injected files """ LOG.debug('add_configdrive called for instance', instance=instance) extra_md = {} inst_md = instance_metadata.InstanceMetadata(instance, content=injected_files, extra_md=extra_md) # Create the ISO image so we can inject the contents of the ISO # into the container iso_path = os.path.join(self.instance_dir, 'configdirve.iso') with configdrive.ConfigDriveBuilder(instance_md=inst_md) as cdb: try: cdb.make_drive(iso_path) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Creating config drive failed with error: ' '%s'), e, instance=instance) # Copy the metadata info from the ISO into the container configdrive_dir = \ self.container_dir.get_container_configdrive(instance.name) 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) finally: if mounted: utils.execute('umount', tmpdir, run_as_root=True) def reboot(self, context, instance, network_info, reboot_type, block_device_info=None, bad_volumes_callback=None): """Reboot a instance on a LXD host :param instance: nova.objects.instance.Instance :param network_info: :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` :param reboot_type: Either a HARD or SOFT reboot :param block_device_info: Info pertaining to attached volumes :param bad_volumes_callback: Function to handle any bad volumes encountered """ LOG.debug('reboot called for instance', instance=instance) try: self.session.container_reboot(instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Container reboot failed for ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def plug_vifs(self, instance, network_info): """Setup the container network on the host :param instance: nova instance object :param network_info: instance network configuration """ LOG.debug('plug_vifs called for instance', instance=instance) try: for viface in network_info: self.vif_driver.plug(instance, viface) self.start_firewall(instance, network_info) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to configure container network' ' for %(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def unplug_vifs(self, instance, network_info): """Unconfigure the LXD container network :param instance: nova intance object :param network_info: instance network confiugration """ try: for viface in network_info: self.vif_driver.unplug(instance, viface) self.stop_firewall(instance, network_info) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to remove container network' ' for %(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def destroy(self, context, instance, network_info, block_device_info=None, destroy_disks=True, migrate_data=None): """Destroy the instance on the LXD host :param context: security context :param instance: Instance object as returned by DB layer. :param network_info: :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` :param block_device_info: Information about block devices that should be detached from the instance. :param destroy_disks: Indicates if disks should be destroyed :param migrate_data: implementation specific params """ LOG.debug('destroy called for instance', instance=instance) try: self.session.profile_delete(instance) self.session.container_destroy(instance.name, instance) self.cleanup(context, instance, network_info, block_device_info) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to remove container' ' for %(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def power_off(self, instance, timeout=0, retry_interval=0): """Power off an instance :param instance: nova.objects.instance.Instance :param timeout: time to wait for GuestOS to shutdown :param retry_interval: How often to signal guest while waiting for it to shutdown """ LOG.debug('power_off called for instance', instance=instance) try: self.session.container_stop(instance.name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to power_off container' ' for %(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def power_on(self, context, instance, network_info, block_device_info=None): """Power on instance :param instance: nova.objects.instance.Instance """ LOG.debug('power_on called for instance', instance=instance) try: self.session.container_start(instance.name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Container power off for ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def pause(self, instance): """Pause an instance :param nova.objects.instance.Instance instance: The instance which should be paused. """ LOG.debug('pause called for instance', instance=instance) try: self.session.container_pause(instance.name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to pause container' ' for %(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def unpause(self, instance): """Unpause an instance :param nova.objects.instance.Instance instance: The instance which should be paused. """ LOG.debug('unpause called for instance', instance=instance) try: self.session.container_unpause(instance.name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to unpause container' ' for %(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def suspend(self, context, instance): """Suspend an instance :param context: nova security context :param nova.objects.instance.Instance instance: The instance which should be paused. """ LOG.debug('suspend called for instance', instance=instance) try: self.session.container_pause(instance.name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Container suspend failed for ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def resume(self, context, instance, network_info, block_device_info=None): """Resume an instance on an LXD host :param nova.context.RequestContext context: The context for the resume. :param nova.objects.instance.Instance instance: The suspended instance to resume. :param nova.network.model.NetworkInfo network_info: Necessary network information for the resume. :param dict block_device_info: Instance volume block device info. """ LOG.debug('resume called for instance', instance=instance) try: self.session.container_unpause(instance.name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to resume container' ' for %(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def rescue(self, context, instance, network_info, image_meta, rescue_password): """Rescue an instance :param instance: nova.objects.instance.Instance """ LOG.debug('rescue called for instance', instance=instance) try: if not self.session.container_defined(instance.name, instance): msg = _('Unable to find instance') raise exception.NovaException(msg) # Step 1 - Stop the old container self.session.container_stop(instance.name, instance) # Step 2 - Rename the broken contianer to be rescued self.session.container_move(instance.name, {'name': '%s-backup' % instance.name}, instance) # Step 3 - Re use the old instance object and confiugre # the disk mount point and create a new container. container_config = self.config.create_container(instance) rescue_dir = self.container_dir.get_container_rescue( instance.name + '-backup') config = self.config.configure_disk_path(rescue_dir, 'mnt', 'rescue', instance) container_config['devices'].update(config) self.session.container_init(container_config, instance) # Step 4 - Start the rescue instance self.session.container_start(instance.name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Container rescue failed for ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def unrescue(self, instance, network_info): """Unrescue a LXD host :param instance: nova instance object :param network_info: nova network configuration """ LOG.debug('unrescue called for instance', instance=instance) try: if not self.session.container_defined(instance.name, instance): msg = _('Unable to find instance') raise exception.NovaException(msg) # Step 1 - Destory the rescue instance. self.session.container_destroy(instance.name, instance) # Step 2 - Rename the backup container that # the user was working on. self.session.container_move(instance.name + '-backup', {'name': instance.name}, instance) # Step 3 - Start the old contianer self.session.container_start(instance.name, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Container unrescue failed for ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def cleanup(self, context, instance, network_info, block_device_info=None, destroy_disks=True, migrate_data=None, destroy_vifs=True): """Cleanup a contianer after its been deleted. :param context: security context :param instance: Instance object as returned by DB layer. :param network_info: :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` :param block_device_info: Information about block devices that should be detached from the instance. :param destroy_disks: Indicates if disks should be destroyed :param migrate_data: implementation specific params """ LOG.debug('cleanup called for instance', instance=instance) try: if destroy_vifs: self.unplug_vifs(instance, network_info) container_dir = self.container_dir.get_instance_dir(instance.name) if os.path.exists(container_dir): shutil.rmtree(container_dir) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Container cleanup failed for ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def get_info(self, instance): """Get the current status of an instance, by name (not ID!) :param instance: nova.objects.instance.Instance object Returns a InstanceInfo object """ LOG.debug('get_info called for instance', instance=instance) try: if not self.session.container_defined(instance.name, instance): return hardware.InstanceInfo(state=power_state.NOSTATE) container_state = self.session.container_state(instance) return hardware.InstanceInfo(state=container_state['state'], max_mem_kb=container_state['max_mem'], mem_kb=container_state['mem'], num_cpu=instance.flavor.vcpus, cpu_time_ns=0) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to get container info' ' for %(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def get_console_output(self, context, instance): """Get console output for an instance :param context: security context :param instance: nova.objects.instance.Instance """ LOG.debug('get_console_output called for instance', instance=instance) try: console_log = self.container_dir.get_console_path(instance.name) if not os.path.exists(console_log): return "" uid = pwd.getpwuid(os.getuid()).pw_uid utils.execute('chown', '%s:%s' % (uid, uid), console_log, run_as_root=True) utils.execute('chmod', '755', os.path.join( self.container_dir.get_container_dir( instance.name), instance.name), run_as_root=True) with open(console_log, 'rb') as fp: log_data, remaning = utils.last_bytes(fp, MAX_CONSOLE_BYTES) return log_data except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to get container output' ' for %(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def container_attach_interface(self, instance, image_meta, vif): LOG.debug('container_attach_interface called for instance', instance=instance) try: self.vif_driver.plug(instance, vif) self.firewall_driver.setup_basic_filtering(instance, vif) container_config = self.config.create_container(instance) container_network = self.config.create_container_net_device( instance, vif) container_config['devices'].update(container_network) self.session.container_update(container_config, instance) except Exception as ex: with excutils.save_and_reraise_exception(): self.vif_driver.unplug(instance, vif) LOG.error(_LE('Failed to configure network' ' for %(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def container_detach_interface(self, instance, vif): LOG.debug('container_defatch_interface called for instance', instance=instance) try: self.vif_driver.unplug(instance, vif) except exception.NovaException: pass def start_firewall(self, instance, network_info): self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) self.firewall_driver.apply_instance_filter(instance, network_info) def stop_firewall(self, instance, network_info): self.firewall_driver.unfilter_instance(instance, network_info) nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/container_snapshot.py0000664000175000017500000001447712701774405024370 0ustar chuckchuck00000000000000# Copyright 2011 Justin Santa Barbara # 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 oslo_config import cfg from nova.compute import task_states from nova import exception from nova import i18n from nova import image import os from oslo_concurrency import lockutils from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils from nova_lxd.nova.virt.lxd import session _ = i18n._ _LE = i18n._LE CONF = cfg.CONF LOG = logging.getLogger(__name__) IMAGE_API = image.API() class LXDSnapshot(object): def __init__(self): self.session = session.LXDAPISession() self.lock_path = str(os.path.join(CONF.instances_path, 'locks')) def snapshot(self, context, instance, image_id, update_task_state): """Create a LXD snapshot of the instance Steps involved in creating an LXD Snapshot: 1. Ensure the container exists 2. Stop the LXD container: LXD requires a container to be stopped in or 3. Publish the container: Run the API equivalent to 'lxd publish container --alias ' to create a snapshot and upload it to the local LXD image store. 4. Create an alias for the image: Create an alias so that nova-lxd can re-use the image that was created. 5. Upload the image to glance so that it can bed on other compute hosts. :param context: nova security context :param instance: nova instance object :param image_id: glance image id """ LOG.debug('snapshot called for instance', instance=instance) try: if not self.session.container_defined(instance.name, instance): raise exception.InstanceNotFound(instance_id=instance.name) with lockutils.lock(self.lock_path, lock_file_prefix=('lxd-snapshot-%s' % instance.name), external=True): update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) # We have to stop the container before we can publish the # image to the local store self.session.container_stop(instance.name, instance) fingerprint = self._save_lxd_image(instance, image_id) self.session.container_start(instance.name, instance) update_task_state(task_state=task_states.IMAGE_UPLOADING, expected_state=task_states.IMAGE_PENDING_UPLOAD) # noqa self._save_glance_image(context, instance, image_id, fingerprint) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to create snapshot for %(instance)s: ' '%(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def _save_lxd_image(self, instance, image_id): """Creates an LXD image from the LXD continaer """ LOG.debug('_save_lxd_image called for instance', instance=instance) fingerprint = None try: # Publish the snapshot to the local LXD image store container_snapshot = { "properties": {}, "public": False, "source": { "name": instance.name, "type": "container" } } (state, data) = self.session.container_publish(container_snapshot, instance) event_id = data.get('operation') self.session.wait_for_snapshot(event_id, instance) # Image has been create but the fingerprint is buried deep # in the metadata when the snapshot is complete (state, data) = self.session.operation_info(event_id, instance) fingerprint = data['metadata']['metadata']['fingerprint'] except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to publish snapshot for %(instance)s: ' '%(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) try: # Set the alias for the LXD image alias_config = { 'name': image_id, 'target': fingerprint } self.session.create_alias(alias_config, instance) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to create alias for %(instance)s: ' '%(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) return fingerprint def _save_glance_image(self, context, instance, image_id, fingerprint): LOG.debug('_save_glance_image called for instance', instance=instance) try: snapshot = IMAGE_API.get(context, image_id) data = self.session.container_export(fingerprint, instance) image_meta = {'name': snapshot['name'], 'disk_format': 'raw'} IMAGE_API.update(context, image_id, image_meta, data) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to upload image to glance for ' '%(instance)s: %(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/session.py0000664000175000017500000012410112701774405022134 0ustar chuckchuck00000000000000# 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 nova import context as nova_context from nova import exception from nova import i18n from nova import rpc from nova import utils from nova.compute import power_state from oslo_concurrency import processutils from oslo_config import cfg from oslo_log import log as logging from oslo_service import loopingcall from oslo_utils import excutils from pylxd.deprecated import api from pylxd.deprecated import exceptions as lxd_exceptions import six from nova_lxd.nova.virt.lxd import constants _ = i18n._ _LE = i18n._LE _LI = i18n._LI CONF = cfg.CONF CONF.import_opt('host', 'nova.netconf') LOG = logging.getLogger(__name__) def mount_filesystem(self, dev_path, dir_path): try: _out, err = utils.execute('mount', '-t', 'ext4', dev_path, dir_path, run_as_root=True) except processutils.ProcessExecutionError as e: err = six.text_type(e) return err def umount_filesystem(self, dir_path): try: _out, err = utils.execute('umount', dir_path, run_as_root=True) except processutils.ProcessExecutionError as e: err = six.text_type(e) return err class LXDAPISession(object): """The session to invoke the LXD API session.""" def __init__(self): super(LXDAPISession, self).__init__() 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(_LE('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_list(self): """List of containers running on a given host Returns a list of running containers """ LOG.debug('container_list called') try: client = self.get_session() return client.container_list() except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API: %(reason)s') \ % {'reason': ex} LOG.error(msg) raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during container_list: ' '%(reason)s') % {'reason': ex}) def container_update(self, config, instance): """Update the LXD configuration of a given container :param config: LXD configuration dictionary :param instance: nova instance object :return: an update LXD configuration dictionary """ LOG.debug('container_update called fo instance', instance=instance) try: client = self.get_session() if not self.container_defined(instance.name, instance): msg = _('Instance is not found: %s') % instance.name raise exception.InstanceNotFound(msg) return client.container_update(instance.name, config) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during container_update' '%(instance)s: %(reason)s'), {'instance': instance.name, 'reason': e}, instance=instance) def container_running(self, instance): """Determine if the container is running :param instance: nova instance object :return: True if container is running otherwise false """ LOG.debug('container_running for instance', instance=instance) try: client = self.get_session() return client.container_running(instance.name) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during container_running' '%(instance)s: %(reason)s'), {'instance': instance.name, 'reason': e}, instance=instance) def container_state(self, instance): """Determine container_state and translate state :param instance: nova instance object :return: nova power_state """ LOG.debug('container_state called for instance', instance=instance) try: mem = 0 max_mem = 0 client = self.get_session() if not self.container_defined(instance.name, instance): return (state, data) = client.container_state(instance.name) state = constants.LXD_POWER_STATES[data['metadata']['status_code']] container_state = self.container_info(instance) mem = int(container_state['memory']['usage']) >> 10 max_mem = int(container_state['memory']['usage_peak']) >> 10 except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} LOG.error(msg) state = power_state.NOSTATE except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during container_state' '%(instance)s: %(reason)s'), {'instance': instance.name, 'reason': e}, instance=instance) state = power_state.NOSTATE return {'state': state, 'mem': mem, 'max_mem': max_mem} def container_config(self, instance): """Fetches the configuration of a given LXD container :param instance: nova instance object :return: dictionary represenation of a LXD container """ LOG.debug('container_config called for instance', instance=instance) try: if not self.container_defined(instance.name, instance): msg = _('Instance is not found %s') % instance.name raise exception.InstanceNotFound(msg) client = self.get_session() return client.get_container_config(instance.name) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during container_config' '%(instance)s: %(reason)s'), {'instance': instance.name, 'reason': e}, instance=instance) def container_info(self, instance): """Returns basic information about a LXD container :param instance: nova instance object :return: LXD container information """ LOG.debug('container_info called for instance', instance=instance) try: if not self.container_defined(instance.name, instance): msg = _('Instance is not found %s') % instance.name raise exception.InstanceNotFound(msg) client = self.get_session() return client.container_info(instance.name) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during container_info' '%(instance)s: %(reason)s'), {'instance': instance.name, 'reason': e}, instance=instance) def container_defined(self, instance_name, instance): """Determine if the container exists :param instance_name: container anme :param instance: nova instance opbject :return: True if exists otherwise False """ LOG.debug('container_defined for instance', instance=instance) try: client = self.get_session() return client.container_defined(instance_name) except lxd_exceptions.APIError as ex: if ex.status_code == 404: return False else: msg = _('Failed to get container status: %s') % ex raise exception.NovaException(msg) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during container_defined' '%(instance)s: %(reason)s'), {'instance': instance.name, 'reason': e}, instance=instance) def container_start(self, instance_name, instance): """Start an LXD container :param instance_name: name of container :param instance: nova instance object """ LOG.debug('container_start called for instance', instance=instance) try: LOG.info(_LI('Starting instance %(instance)s with ' '%(image)s'), {'instance': instance.name, 'image': instance.image_ref}) # Start the container client = self.get_session() # (chuck): Something wicked could happen between # container if not self.container_defined(instance_name, instance): msg = _('Instance is not found %s ') % instance.name raise exception.InstanceNotFound(msg) (state, data) = client.container_start(instance_name, CONF.lxd.timeout) self.operation_wait(data.get('operation'), instance) LOG.info(_LI('Successfully started instance %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to start container %(instance)s: %(reason)s'), {'instance': instance_name, 'reason': ex}, instance=instance) def container_stop(self, instance_name, instance): """Stops an LXD container :param instance_name: instance name :param instance: nova instance object """ LOG.debug('container_stop called for instance', instance=instance) try: if not self.container_defined(instance_name, instance): msg = _('Instance is not found %s') % instance.name raise exception.InstanceNotFound(msg) LOG.info(_LI('Stopping instance %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) # Stop the container client = self.get_session() (state, data) = client.container_stop(instance_name, CONF.lxd.timeout) self.operation_wait(data.get('operation'), instance) LOG.info(_LI('Successfully stopped instance %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to stop container %(instance)s: ' '%(reason)s'), {'instance': instance_name, 'reason': ex}) def container_reboot(self, instance): """Reboot a LXD container :param instance: nova instance object """ LOG.debug('container_reboot called for instance', instance=instance) try: if not self.container_defined(instance.name, instance): msg = _('Instance is not found %s') % instance.name raise exception.InstanceNotFound(msg) LOG.info(_LI('Rebooting instance %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) # Container reboot client = self.get_session() (state, data) = client.container_reboot(instance.name, CONF.lxd.timeout) self.operation_wait(data.get('operation'), instance) LOG.info(_LI('Successfully rebooted instance %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to reboot container %(instance)s: ' '%(reason)s'), {'instance': instance.name, 'reason': ex}, instance=instance) def container_destroy(self, instance_name, instance): """Destroy a LXD container :param instance_name: container name :param instance: nova instance object """ LOG.debug('container_destroy for instance', instance=instance) try: if not self.container_defined(instance_name, instance): return LOG.info(_LI('Destroying instance %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) # Destroying container self.container_stop(instance_name, instance) client = self.get_session() (state, data) = client.container_destroy(instance_name) self.operation_wait(data.get('operation'), instance) LOG.info(_LI('Successfully destroyed instance %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to destroy container %(instance)s: ' '%(reason)s'), {'instance': instance_name, 'reason': ex}) def container_pause(self, instance_name, instance): """Pause a LXD container :param instance_name: container name :param instance: nova instance object """ LOG.debug('container_paused called for instance', instance=instance) try: if not self.container_defined(instance_name, instance): msg = _('Instance is not found %s') % instance_name raise exception.InstanceNotFound(msg) LOG.info(_LI('Pausing instance %(instance)s with' ' %(image)s'), {'instance': instance_name, 'image': instance.image_ref}) client = self.get_session() (state, data) = client.container_suspend(instance_name, CONF.lxd.timeout) self.operation_wait(data.get('operation'), instance) LOG.info(_LI('Successfully paused instance %(instance)s with' ' %(image)s'), {'instance': instance_name, 'image': instance.image_ref}) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance_name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to pause container %(instance)s: ' '%(reason)s'), {'instance': instance_name, 'reason': ex}, instance=instance) def container_unpause(self, instance_name, instance): """Unpause a LXD container :param instance_name: container name :param instance: nova instance object """ LOG.debug('container_unpause called for instance', instance=instance) try: if not self.container_defined(instance_name, instance): msg = _('Instance is not found %s') % instance_name raise exception.InstanceNotFound(msg) LOG.info(_LI('Unpausing instance %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) client = self.get_session() (state, data) = client.container_resume(instance_name, CONF.lxd.timeout) self.operation_wait(data.get('operation'), instance) LOG.info(_LI('Successfully unpaused instance %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to unpause container %(instance)s: ' '%(reason)s'), {'instance': instance_name, 'reason': ex}) def container_init(self, config, instance): """Create a LXD container :param config: LXD container config as a dict :param instance: nova instance object """ LOG.debug('container_init called for instance', instance=instance) try: LOG.info(_LI('Creating container %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) client = self.get_session() (state, data) = client.container_init(config) operation = data.get('operation') self.operation_wait(operation, instance) status, data = self.operation_info(operation, instance) data = data.get('metadata') if not data['status_code'] == 200: raise exception.NovaException(data['metadata']) LOG.info(_LI('Successfully created container %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to create container %(instance)s: %(reason)s'), {'instance': instance.name, 'reason': ex}, instance=instance) # # Image related API methods. # def image_defined(self, instance): """Checks existence of an image on the local LXD image store :param instance: The nova instance Returns True if supplied image exists on the host, False otherwise """ LOG.debug('image_defined called for instance', instance=instance) try: client = self.get_session() return client.alias_defined(instance.image_ref) except lxd_exceptions.APIError as ex: if ex.status_code == 404: return False else: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.image_ref, 'reason': ex} LOG.error(msg) raise exception.NovaException(msg) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during image_defined ' '%(instance)s: %(reason)s'), {'instance': instance.image_ref, 'reason': e}, instance=instance) def create_alias(self, alias, instance): """Creates an alias for a given image :param alias: The alias to be crerated :param instance: The nove instance :return: true if alias is created, false otherwise """ LOG.debug('create_alias called for instance', instance=instance) try: client = self.get_session() return client.alias_create(alias) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.image_ref, 'reason': ex} LOG.error(msg) raise exception.NovaException(msg) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during create alias' '%(instance)s: %(reason)s'), {'instance': instance.image_ref, 'reason': e}, instance=instance) def image_upload(self, data, headers, instance): """Upload an image to the local LXD image store :param data: image data :param headers: image headers :param instance: The nova instance """ LOG.debug('upload_image called for instance', instance=instance) try: client = self.get_session() (state, data) = client.image_upload(data=data, headers=headers) # XXX - zulcss (Dec 8, 2015) - Work around for older # versions of LXD. if 'operation' in data: self.operation_wait(data.get('operation'), instance) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' '%(reason)s') % {'instance': instance.image_ref, 'reason': ex} LOG.error(msg) raise exception.NovaException(msg) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during image upload' '%(instance)s: %(reason)s'), {'instance': instance.image_ref, 'reason': e}, instance=instance) # # Operation methods # def operation_wait(self, operation_id, instance): """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_contianer for instance', instance=instance) try: client = self.get_session() 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)s:' '%(reason)s') % {'instance': instance.image_ref, 'reason': ex} LOG.error(msg) raise exception.NovaException(msg) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during operation wait' '%(instance)s: %(reason)s'), {'instance': instance.image_ref, 'reason': e}, instance=instance) def operation_info(self, operation_id, instance): LOG.debug('operation_info called for instance', instance=instance) try: client = self.get_session() return client.operation_info(operation_id) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.image_ref, 'reason': ex} LOG.error(msg) raise exception.NovaException(msg) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during operation_info ' '%(instance)s: %(reason)s'), {'instance': instance.image_ref, 'reason': e}, instance=instance) # # Profile methods # def profile_list(self): LOG.debug('profile_list called for instance') try: client = self.get_session() return client.profile_list() except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API: %(reason)s') \ % {'reason': ex} LOG.error(msg) raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Error from LXD during profile_list: ' '%(reason)s') % {'reason': ex}) def profile_defined(self, instance_name, instance): """Validate if the profile is available on the LXD host :param instance: nova instance object """ LOG.debug('profile_defined called for instance', instance=instance) try: found = False if instance_name in self.profile_list(): found = True return found except lxd_exceptions.APIError as ex: if ex.status_code == 404: return False else: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to determine profile %(instance)s:' ' %(reason)s'), {'instance': instance.name, 'reason': ex}) def profile_create(self, config, instance): """Create an LXD container profile :param config: profile dictionary :param instance: nova instance object """ LOG.debug('profile_create called for instance', instance=instance) try: if self.profile_defined(instance.name, instance): msg = _('Profile already exists %(instnce)s') % \ {'instance': instance.name} raise exception.NovaException(msg) client = self.get_session() return client.profile_create(config) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to create profile %(instance)s: %(reason)s'), {'instance': instance.name, 'reason': ex}) def profile_update(self, config, instance): """Update an LXD container profile :param config: LXD profile dictironary :param instance: nova instance object """ LOG.debug('profile_udpate called for instance', instance=instance) try: if not self.profile_defined(instance.name, instance): msg = _('Profile not found %(instance)s') % \ {'instance': instance.name} raise exception.NovaException(msg) client = self.get_session() return client.profile_update(instance.name, config) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to update profile %(instance)s: ' '%(reason)s'), {'instance': instance.name, 'reason': ex}) def profile_delete(self, instance): """Delete a LXD container profile. :param instance: nova instance object """ LOG.debug('profile_delete called for instance', instance=instance) try: if not self.profile_defined(instance.name, instance): return client = self.get_session() return client.profile_delete(instance.name) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to delete profile %(instance)s: %(reason)s'), {'instance': instance.name, 'reason': ex}) # # Host Methods # def host_certificate(self, instance, host): LOG.debug('host_certificate called for instance', instance=instance) try: client = self.get_session(host) return client.get_host_certificate() except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'ex': ex} LOG.error(msg) def get_host_config(self, instance): LOG.debug('host_config called for instance', instance=instance) try: client = self.get_session() return client.host_config()['environment'] except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'ex': ex} LOG.error(msg) # # 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(_LI('Migrating instance %(instance)s with ' '%(image)s'), {'instance': instance_name, 'image': instance.image_ref}) client = self.get_session(host) (state, data) = client.container_migrate(instance_name) LOG.info(_LI('Successfully initialized migration for instance ' '%(instance)s with %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) return (state, data) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to migrate container %(instance)s: %(' 'reason)s'), {'instance': instance.name, 'reason': ex}, instance=instance) # # Snapshot methods # def container_move(self, old_name, config, instance): """Move a container from one host to another :param old_name: Old container name :param config: Old container config :param instance: nova instance object :return: """ LOG.debug('container_move called for instance', instance=instance) try: LOG.info(_LI('Moving container %(instance)s with ' '%(image)s'), {'instance': instance.name, 'image': instance.image_ref}) # Container move client = self.get_session() (state, data) = client.container_local_move(old_name, config) self.operation_wait(data.get('operation'), instance) LOG.info(_LI('Successfully moved container %(instance)s with ' '%(image)s'), {'instance': instance.name, 'image': instance.image_ref}) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to move container %(instance)s: ' '%(reason)s'), {'instance': instance.name, 'reason': ex}, instance=instance) def container_snapshot(self, snapshot, instance): """Snapshot a LXD container :param snapshot: snapshot config dictionary :param instance: nova instance object """ LOG.debug('container_snapshot called for instance', instance=instance) try: LOG.info(_LI('Snapshotting container %(instance)s with ' '%(image)s'), {'instance': instance.name, 'image': instance.image_ref}) # Container snapshot client = self.get_session() (state, data) = client.container_snapshot_create( instance.name, snapshot) self.operation_wait(data.get('operation'), instance) LOG.info(_LI('Successfully snapshotted container %(instance)s with' ' %(image)s'), {'instance': instance.name, 'image': instance.image_ref}) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to snapshot container %(instance)s: ' '%(reason)s'), {'instance': instance.name, 'reason': ex}, instance=instance) def container_publish(self, image, instance): """Publish a container to the local LXD image store :param image: LXD fingerprint :param instance: nova instance object :return: True if published, False otherwise """ LOG.debug('container_publish called for instance', instance=instance) try: client = self.get_session() return client.container_publish(image) except lxd_exceptions.APIError as ex: msg = _('Failed to communicate with LXD API %(instance)s:' ' %(reason)s') % {'instance': instance.name, 'reason': ex} raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to publish container %(instance)s: ' '%(reason)s'), {'instance': instance.name, 'reason': ex}, instance=instance) def container_export(self, image, instance): """ Export an image from the local LXD image store into an file. :param image: image dictionary :param instance: nova instance object """ LOG.debug('container_export called for instance', instance=instance) try: client = self.get_session() return client.image_export(image) except lxd_exceptions.APIError as ex: msg = _('Failed to export image: %s') % ex raise exception.NovaException(msg) except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error( _LE('Failed to export container %(instance)s: ' '%(reason)s'), {'instance': instance.name, 'reason': ex}, instance=instance) def wait_for_snapshot(self, event_id, instance): """Poll snapshot operation for the snapshot to be ready. :param event_id: operation id :param instance: nova instance object """ LOG.debug('wait_for_snapshot called for instance', instance=instance) timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_snapshot, event_id, instance) try: timer.start(interval=2).wait() except Exception as ex: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to create snapshot for %(instance)s: ' '%(ex)s'), {'instance': instance.name, 'ex': ex}, instance=instance) def _wait_for_snapshot(self, event_id, instance): """Check the status code of the opeation id. :param event_id: operation id :param instance: nova instance object """ client = self.get_session() (state, data) = client.operation_info(event_id) status_code = data['metadata']['status_code'] if status_code == 200: raise loopingcall.LoopingCallDone() elif status_code == 400: msg = _('Snapshot failed') raise exception.NovaException(msg) nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/__init__.py0000664000175000017500000000011012701774405022201 0ustar chuckchuck00000000000000from nova_lxd.nova.virt.lxd import driver LXDDriver = driver.LXDDriver nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/utils.py0000664000175000017500000000623412701774405021617 0ustar chuckchuck00000000000000# 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 os from oslo_config import cfg CONF = cfg.CONF class LXDContainerDirectories(object): def __init__(self): self.base_dir = os.path.join(CONF.instances_path, CONF.image_cache_subdirectory_name) def get_base_dir(self): return self.base_dir def get_instance_dir(self, instance): return os.path.join(CONF.instances_path, instance) def get_container_rootfs_image(self, image_meta): return os.path.join(self.base_dir, '%s-rootfs.tar.gz' % image_meta.id) def get_container_manifest_image(self, image_meta): return os.path.join(self.base_dir, '%s-manifest.tar' % image_meta.id) def get_container_metadata(self, image_meta): return os.path.join(self.base_dir, '%s-lxd.tar.xz' % image_meta.id) def get_container_rootfsImg(self, image_meta): return os.path.join(self.base_dir, '%s-root.tar.gz' % image_meta.id) def get_container_configdrive(self, instance): return os.path.join(CONF.instances_path, instance, 'configdrive') def get_console_path(self, instance): return os.path.join(CONF.lxd.root_dir, 'containers', instance, 'console.log') def get_container_dir(self, instance): return os.path.join(CONF.lxd.root_dir, 'containers') def get_container_rootfs(self, instance): return os.path.join(CONF.lxd.root_dir, 'containers', instance, 'rootfs') def get_container_rescue(self, instance): if self.is_lvm(instance): return os.path.join(CONF.lxd.root_dir, 'containers', instance) else: return os.path.join(CONF.lxd.root_dir, 'containers', instance, 'rootfs') def get_container_lvm(self, instance): return '%s/%s.lv' % (self.get_container_dir(instance), instance) def is_lvm(self, instance): try: if os.path.exists(os.readlink( self.get_container_lvm(instance))): return True except Exception: return False nova-lxd-13.0.0/nova_lxd/nova/virt/lxd/container_firewall.py0000664000175000017500000000515612701774405024330 0ustar chuckchuck00000000000000# 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 nova.virt import firewall from oslo_config import cfg from oslo_log import log as logging CONF = cfg.CONF LOG = logging.getLogger(__name__) class LXDContainerFirewall(object): def __init__(self): self.firewall_driver = firewall.load_driver( default='nova.virt.firewall.NoopFirewallDriver') def refresh_security_group_rules(self, security_group_id): return (self.firewall_driver .refresh_security_group_rules(security_group_id)) def refresh_security_group_members(self, security_group_id): return (self.firewall_driver .refresh_security_group_members(security_group_id)) def refresh_provider_fw_rules(self): return self.firewall_driver.refresh_provider_fw_rules() 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 setup_basic_filtering(self, instance, network_info): return self.firewall_driver.setup_basic_filtering(instance, network_info) def prepare_instance_filter(self, instance, network_info): return self.firewall_driver.prepare_instance_filter(instance, network_info) def apply_instance_filter(self, instance, network_info): return self.firewall_driver.apply_instance_filter(instance, network_info) nova-lxd-13.0.0/nova_lxd/nova/virt/__init__.py0000664000175000017500000000000012701774405021410 0ustar chuckchuck00000000000000nova-lxd-13.0.0/nova_lxd/nova/__init__.py0000664000175000017500000000007012701774405020433 0ustar chuckchuck00000000000000__import__('pkg_resources').declare_namespace(__name__) nova-lxd-13.0.0/nova_lxd/__init__.py0000664000175000017500000000015712701774405017476 0ustar chuckchuck00000000000000__import__('pkg_resources').declare_namespace(__name__) import os os.environ['EVENTLET_NO_GREENDNS'] = 'yes' nova-lxd-13.0.0/.testr.conf0000664000175000017500000000047612701774405015645 0ustar chuckchuck00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--listnova-lxd-13.0.0/tox.ini0000664000175000017500000000224012701774405015061 0ustar chuckchuck00000000000000[tox] minversion = 1.6 # Python 3 tests MUST run first, due to this bug: # https://bugs.launchpad.net/testrepository/+bug/1229445 envlist = py34,py27,pep8 skipsdist = True [testenv] usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} EVENTLET_NO_GREENDNS=yes PYTHONDONTWRITEBYTECODE=1 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = ostestr {posargs} [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} [testenv:cover] # Also do not run test_coverage_ext tests while gathering coverage as those # tests conflict with coverage. commands = coverage erase python setup.py testr --coverage --coverage-package-name=nova_lxd \ --testr-args='{posargs}' coverage combine coverage html --include='nova_lxd/*' -d covhtml -i [testenv:docs] 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 nova-lxd-13.0.0/LICENSE0000664000175000017500000002363612701774405014567 0ustar chuckchuck00000000000000 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. nova-lxd-13.0.0/nova_lxd.egg-info/0000775000175000017500000000000012701775064017056 5ustar chuckchuck00000000000000nova-lxd-13.0.0/nova_lxd.egg-info/top_level.txt0000664000175000017500000000001112701775064021600 0ustar chuckchuck00000000000000nova_lxd nova-lxd-13.0.0/nova_lxd.egg-info/SOURCES.txt0000664000175000017500000000441012701775064020741 0ustar chuckchuck00000000000000.coveragerc .mailmap .testr.conf .travis.yml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE MANIFEST.in README.md babel.cfg openstack-common.conf requirements.txt run_tests.sh setup.cfg setup.py test-requirements.txt tox.ini devstack/README.rst devstack/nova-lxd-functions.sh devstack/override-defaults devstack/plugin.sh devstack/settings doc/source/conf.py doc/source/contributing.rst doc/source/index.rst doc/source/readme.rst doc/source/usage.rst etc/lxd/lxd.template etc/nova/rootwrap.d/lxd.filters nova_lxd/__init__.py nova_lxd.egg-info/PKG-INFO nova_lxd.egg-info/SOURCES.txt nova_lxd.egg-info/dependency_links.txt nova_lxd.egg-info/entry_points.txt nova_lxd.egg-info/namespace_packages.txt nova_lxd.egg-info/not-zip-safe nova_lxd.egg-info/pbr.json nova_lxd.egg-info/requires.txt nova_lxd.egg-info/top_level.txt nova_lxd/nova/__init__.py nova_lxd/nova/virt/__init__.py nova_lxd/nova/virt/lxd/__init__.py nova_lxd/nova/virt/lxd/config.py nova_lxd/nova/virt/lxd/constants.py nova_lxd/nova/virt/lxd/container_firewall.py nova_lxd/nova/virt/lxd/container_snapshot.py nova_lxd/nova/virt/lxd/driver.py nova_lxd/nova/virt/lxd/host.py nova_lxd/nova/virt/lxd/image.py nova_lxd/nova/virt/lxd/migrate.py nova_lxd/nova/virt/lxd/operations.py nova_lxd/nova/virt/lxd/session.py nova_lxd/nova/virt/lxd/utils.py nova_lxd/nova/virt/lxd/vif.py nova_lxd/tests/__init__.py nova_lxd/tests/fake_api.py nova_lxd/tests/stubs.py nova_lxd/tests/test_config.py nova_lxd/tests/test_driver_api.py nova_lxd/tests/test_image.py nova_lxd/tests/test_migrate.py nova_lxd/tests/test_operations.py nova_lxd/tests/test_vif_api.py nova_lxd/tests/session/__init__.py nova_lxd/tests/session/test_container.py nova_lxd/tests/session/test_event.py nova_lxd/tests/session/test_image.py nova_lxd/tests/session/test_migrate.py nova_lxd/tests/session/test_profile.py nova_lxd/tests/session/test_snapshot.py specs/todo.txt tools/abandon_old_reviews.sh tools/clean-vlans tools/colorizer.py tools/enable-pre-commit-hook.sh tools/install_venv.py tools/install_venv_common.py tools/nova-manage.bash_completion tools/pretty_tox.sh tools/regression_tester.py tools/with_venv.sh tools/config/README tools/config/analyze_opts.py tools/config/check_uptodate.sh tools/config/generate_sample.sh tools/config/oslo.config.generator.rc tools/db/schema_diff.pynova-lxd-13.0.0/nova_lxd.egg-info/PKG-INFO0000664000175000017500000000303212701775064020151 0ustar chuckchuck00000000000000Metadata-Version: 1.1 Name: nova-lxd Version: 13.0.0 Summary: native lxd driver for openstack Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: # 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. ## Bug reports Bug reports can be filed at https://github.com/lxc/nova-lxd/issues/new ## 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. Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 nova-lxd-13.0.0/nova_lxd.egg-info/namespace_packages.txt0000664000175000017500000000001112701775064023401 0ustar chuckchuck00000000000000nova_lxd nova-lxd-13.0.0/nova_lxd.egg-info/requires.txt0000664000175000017500000000023112701775064021452 0ustar chuckchuck00000000000000pbr>=1.6 Babel>=1.3 requests!=2.9.0,>=2.8.1 oslo.config>=3.4.0 oslo.concurrency>=2.3.0 oslo.utils>=3.4.0 oslo.i18n>=2.1.0 oslo.log>=1.14.0 wrapt==1.10.6 nova-lxd-13.0.0/nova_lxd.egg-info/entry_points.txt0000664000175000017500000000010512701775064022350 0ustar chuckchuck00000000000000[console_scripts] lxc-image-converter = nova_lxd.cmd.converter:main nova-lxd-13.0.0/nova_lxd.egg-info/not-zip-safe0000664000175000017500000000000112701774772021311 0ustar chuckchuck00000000000000 nova-lxd-13.0.0/nova_lxd.egg-info/dependency_links.txt0000664000175000017500000000000112701775064023124 0ustar chuckchuck00000000000000 nova-lxd-13.0.0/nova_lxd.egg-info/pbr.json0000664000175000017500000000005612701775064020535 0ustar chuckchuck00000000000000{"is_release": true, "git_version": "ff2792f"}nova-lxd-13.0.0/CONTRIBUTING.rst0000664000175000017500000000473012701774405016215 0ustar chuckchuck00000000000000Crash 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. 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`: enable_plugin nova-lxd https://github.com/lxc/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. [[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://github.com/lxc/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: 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: /opt/stack/tempest/run_tempest.sh -N tempest.api.compute Errata ====== Pull requests should be submitted through GitHub. Bugs should be filed on GitHub: https://github.com/lxc/nova-lxd If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html nova-lxd-13.0.0/run_tests.sh0000775000175000017500000001741012701774405016140 0ustar chuckchuck00000000000000#!/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 nova-lxd-13.0.0/.coveragerc0000664000175000017500000000015312701774405015670 0ustar chuckchuck00000000000000[run] branch = True source = nova_lxd omit = nova_lxd/tests/* [report] ignore_errors = True precision = 2 nova-lxd-13.0.0/.travis.yml0000664000175000017500000000037412701774405015665 0ustar chuckchuck00000000000000language: python python: - "3.4" - "2.7" install: pip install -U -r requirements.txt -r test-requirements.txt before_script: flake8 --ignore=E123,E125,H904,H405,H404,H305,H306,H307 script: ostestr cache: directories: - $HOME/.cache/pip