Frist commit
Signed-off-by: Chuck Short <chuck.short@canonical.com>
This commit is contained in:
commit
ce54272ad4
|
@ -0,0 +1,16 @@
|
|||
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
|
||||
|
||||
Once those steps have been completed, changes to OpenStack
|
||||
should be submitted for review via the Gerrit tool, following
|
||||
the workflow documented at:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
Bugs should be filed on Launchpad, not GitHub:
|
||||
|
||||
https://bugs.launchpad.net/nova-compute-lxd
|
|
@ -0,0 +1,4 @@
|
|||
nova-compute-lxd Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
|
@ -0,0 +1,175 @@
|
|||
|
||||
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.
|
|
@ -0,0 +1,6 @@
|
|||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
|
@ -0,0 +1,15 @@
|
|||
===============================
|
||||
nova-compute-lxd
|
||||
===============================
|
||||
|
||||
OpenStack Boilerplate contains all the boilerplate you need to create an OpenStack package.
|
||||
|
||||
* Free software: Apache license
|
||||
* Documentation: http://docs.openstack.org/developer/nova-compute-lxd
|
||||
* Source: http://git.openstack.org/cgit/openstack/nova-compute-lxd
|
||||
* Bugs: http://bugs.launchpad.net/nova-compute-lxd
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* TODO
|
|
@ -0,0 +1,10 @@
|
|||
contrib/devstack/ directory contains the files necessary to integrate Granite driver with devstack.
|
||||
|
||||
To install:
|
||||
|
||||
git clone https://github.com/zulcss/nova-compute-lxd /opt/stack/nova-compute-lxd
|
||||
git clone https://github.com/openstack-dev/devstack /opt/stack/devstack
|
||||
|
||||
cd /opt/stack/nova-compute-lxd
|
||||
contrib/devstack/prepare-devstack.sh
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# lxd.sh - Devstack extras script to install lxd
|
||||
|
||||
if [[ $VIRT_DRIVER == "lxd" ]] ; then
|
||||
if [[ $1 == "source" ]] ; then
|
||||
# Keep track of the current directory
|
||||
SCRIPT_DIR=$(cd $(dirname "$0") && pwd)
|
||||
TOP_DIR=$SCRIPT_DIR
|
||||
|
||||
echo $SCRIPT_DIR $TOP_DIR
|
||||
|
||||
# Import common functions
|
||||
source $TOP_DIR/functions
|
||||
|
||||
# Load local configuration
|
||||
source $TOP_DIR/stackrc
|
||||
|
||||
FILES=$TOP_DIR/files
|
||||
|
||||
# Get our defaults
|
||||
source $TOP_DIR/lib/nova_plugins/hypervisor-lxd
|
||||
source $TOP_DIR/lib/lxd
|
||||
|
||||
elif [[ $2 == "install" ]] ; then
|
||||
echo_summary "Configuring LXD"
|
||||
|
||||
configure_lxd
|
||||
install_lxd
|
||||
fi
|
||||
fi
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -xe
|
||||
|
||||
env
|
||||
|
||||
NOVAGRANITEDIR=$(readlink -f $(dirname $0)/../..)
|
||||
INSTALLDIR=${INSTALLDIR:-/opt/stack}
|
||||
|
||||
cp $NOVAGRANITEDIR/contrib/devstack/extras.d/70-lxd.sh $INSTALLDIR/devstack/extras.d
|
||||
cp $NOVAGRANITEDIR/contrib/devstack/lib/nova_plugins/hypervisor-lxd $INSTALLDIR/devstack/lib/nova_plugins/
|
||||
cp $NOVAGRANITEDIR/contrib/devstack/lib/lxd $INSTALLDIR/devstack/lib/lxd
|
||||
cat - <<-EOF >> $INSTALLDIR/devstack/localrc
|
||||
VIRT_DRIVER=lxd
|
||||
EOF
|
|
@ -0,0 +1,75 @@
|
|||
# -*- 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-compute-lxd'
|
||||
copyright = u'2013, OpenStack Foundation'
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
# html_theme_path = ["."]
|
||||
# html_theme = '_theme'
|
||||
# html_static_path = ['static']
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
#intersphinx_mapping = {'http://docs.python.org/': None}
|
|
@ -0,0 +1,4 @@
|
|||
============
|
||||
Contributing
|
||||
============
|
||||
.. include:: ../../CONTRIBUTING.rst
|
|
@ -0,0 +1,24 @@
|
|||
.. nova-compute-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-compute-lxd's documentation!
|
||||
========================================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
readme
|
||||
installation
|
||||
usage
|
||||
contributing
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
|
@ -0,0 +1,12 @@
|
|||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
At the command line::
|
||||
|
||||
$ pip install nova-compute-lxd
|
||||
|
||||
Or, if you have virtualenvwrapper installed::
|
||||
|
||||
$ mkvirtualenv nova-compute-lxd
|
||||
$ pip install nova-compute-lxd
|
|
@ -0,0 +1 @@
|
|||
.. include:: ../../README.rst
|
|
@ -0,0 +1,7 @@
|
|||
========
|
||||
Usage
|
||||
========
|
||||
|
||||
To use nova-compute-lxd in a project::
|
||||
|
||||
import nova-compute-lxd
|
|
@ -0,0 +1,5 @@
|
|||
# nova-rootwrap filters for compute nodes running flex
|
||||
# This file should be owned by (and only-writable by) the root user
|
||||
|
||||
[Filters]
|
||||
cgm: CommandFilter, cgm, root
|
|
@ -0,0 +1 @@
|
|||
__import__('pkg_resources').declare_namespace(__name__)
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/python
|
||||
"""
|
||||
Manage edits to lxc-usernet(5) style file (/etc/lxc/lxc-usernet).
|
||||
File is
|
||||
* comment lines (#)
|
||||
* <username> <type> <bridge> <count>
|
||||
|
||||
# USERNAME TYPE BRIDGE COUNT
|
||||
ubuntu veth br100 128
|
||||
"""
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
|
||||
ETC_LXC_USERNET = "/etc/lxc/lxc-usernet"
|
||||
|
||||
|
||||
class UserNetLine(object):
|
||||
def __init__(self, line):
|
||||
self.error = None
|
||||
cpos = line.find("#")
|
||||
if cpos < 0:
|
||||
payload = line.strip()
|
||||
comment = None
|
||||
else:
|
||||
payload = line[:cpos].strip()
|
||||
comment = line[cpos:]
|
||||
|
||||
if payload:
|
||||
try:
|
||||
user, ntype, brname, count = split(payload)
|
||||
except ValueError:
|
||||
# don't understand this line.
|
||||
user, ntype, brname, count = None
|
||||
self.error = line
|
||||
else:
|
||||
user, ntype, brname, count = None
|
||||
|
||||
self.user = user
|
||||
self.ntype = ntype
|
||||
self.bridge = brname
|
||||
self.comment = comment
|
||||
|
||||
def __str__(self):
|
||||
if self.error is not None:
|
||||
return self.error
|
||||
comment = ""
|
||||
if self.comment is not None:
|
||||
if self.user:
|
||||
comm = " " + self.comment
|
||||
else:
|
||||
comm = self.comment
|
||||
return ' '.join(self.user, self.ntype, self.bridge,
|
||||
self.count + comm)
|
||||
|
||||
|
||||
def update_usernet(user, bridge, op, if_type="veth",
|
||||
count=1, require=True, fname=ETC_LXC_USERNET):
|
||||
|
||||
ops = ("set", "inc", "dec")
|
||||
if op not in ops:
|
||||
raise TypeError("op = '%s'. must be one of %s",
|
||||
(op, ','.join(ops)))
|
||||
|
||||
minfo = "user=%s, bridge=%s, if_type=%s" % (user, bridge, if_type)
|
||||
with lockutils.lock(str(fname)):
|
||||
lines = load_usernet(fname)
|
||||
|
||||
found = []
|
||||
for i, l in enumerate(lines):
|
||||
if (user != l.user or bridge != l.bridge or l.ntype != if_type):
|
||||
continue
|
||||
found.append(i)
|
||||
|
||||
if found:
|
||||
# update the last one (others deleted on write)
|
||||
line = lines(found[-1])
|
||||
if op == "inc":
|
||||
line.count = int(count) + int(line.count)
|
||||
elif op == "dec":
|
||||
line.count = int(count) - int(line.count)
|
||||
elif op == "set":
|
||||
line.count = count
|
||||
|
||||
if require and not found and op != "set":
|
||||
raise ValueError("EntryNotFound: %s" % minfo)
|
||||
elif op == "set":
|
||||
mline = UserNetLine("")
|
||||
mline.user = user
|
||||
mline.ntype = if_type
|
||||
mline.bridge = bridge
|
||||
mline.count = int(count)
|
||||
|
||||
tf = None
|
||||
try:
|
||||
tf = tempfile.NamedTemporaryFile(dir=os.path.dirname(fname),
|
||||
delete=False)
|
||||
for i, l in enumerate(lines):
|
||||
if i in found[:-1]:
|
||||
continue
|
||||
else:
|
||||
tf.write(l + "\n")
|
||||
tf.close()
|
||||
os.rename(tf.name, fname)
|
||||
except Exception as e:
|
||||
if tf is not None:
|
||||
os.unlink(tf.name)
|
||||
raise e
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from . import driver
|
||||
|
||||
LXDDriver = driver.LXDDriver
|
|
@ -0,0 +1,76 @@
|
|||
import json
|
||||
import requests
|
||||
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.i18n import _LW
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, host, cert, key):
|
||||
self.host = host
|
||||
self.cert = cert
|
||||
self.key = key
|
||||
|
||||
def _url(self, path):
|
||||
return 'https://{0}{1}'.format(self.host, path)
|
||||
|
||||
def _get(self, path):
|
||||
return requests.get(self._url(path),
|
||||
cert=(self.cert, self.key), verify=False)
|
||||
|
||||
def _put(self, path, data):
|
||||
return requests.put(self._url(path), data=json.dumps(data),
|
||||
cert=(self.cert, self.key), verify=False)
|
||||
|
||||
def defined(self, name):
|
||||
container_exists = False
|
||||
response = self._get('/1.0/containers/%s/state' % name)
|
||||
if response:
|
||||
container_exists = True
|
||||
return container_exists
|
||||
|
||||
def running(self, name):
|
||||
container_running = False
|
||||
if self.defined(name):
|
||||
response = self._get('/1.0/containers/%s/state' % name)
|
||||
if response:
|
||||
content = json.loads(response.text)
|
||||
if content['metadata']['state'] != 'STOPPED':
|
||||
container_running = True
|
||||
return container_running
|
||||
|
||||
def state(self, name):
|
||||
if self.defined(name):
|
||||
response = elf._get('/1.0/containers/%s/state' % name)
|
||||
if response:
|
||||
content = json.loads(response.text)
|
||||
return content['metadata']['state']
|
||||
|
||||
def start(self, name):
|
||||
container_start = False
|
||||
if self.defined(name):
|
||||
params = {'action':'start'}
|
||||
response = self._put('/1.0/containers/%s/state' % name, params)
|
||||
if response.status_code == 200:
|
||||
container_start = True
|
||||
return container_start
|
||||
|
||||
def stop(self, name):
|
||||
container_stop = False
|
||||
if self.defined(name):
|
||||
params = {'action':'start'}
|
||||
response = self._put('/1.0/containers/%s/state' % name, params)
|
||||
if response.status_code == 200:
|
||||
container_stop = True
|
||||
return container_stop
|
||||
|
||||
|
||||
def list(self):
|
||||
containers = []
|
||||
response = self._get('/1.0/list')
|
||||
if response:
|
||||
content = json.loads(response.text)
|
||||
for i in content['metadata']:
|
||||
containers.append(i)
|
||||
return containers
|
|
@ -0,0 +1,108 @@
|
|||
import os
|
||||
|
||||
from oslo.config import cfg
|
||||
from nova.i18n import _LW
|
||||
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import fileutils
|
||||
from nova import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
class LXDConfigObject(object):
|
||||
def __init__(self, **kwargs):
|
||||
super(LXDConfigObject, self).__init__()
|
||||
|
||||
def set_config(self):
|
||||
pass
|
||||
|
||||
class LXDConfigTemplate(LXDConfigObject):
|
||||
def __init__(self, instance, image_meta):
|
||||
super(LXDConfigTemplate, self).__init__()
|
||||
self.instance = instance
|
||||
self.image_meta = image_meta
|
||||
|
||||
def set_config(self):
|
||||
templates = []
|
||||
if (self.image_meta and
|
||||
self.image_meta.get('properties', {}).get('template')):
|
||||
lxc_template = self.image_meta['properties'].get('template')
|
||||
else:
|
||||
lxc_template = CONF.lxd.lxd_default_template
|
||||
|
||||
path = os.listdir(CONF.lxd.lxd_template_dir)
|
||||
for line in path:
|
||||
templates.append(line.replace('lxc-', ''))
|
||||
|
||||
if lxc_template in templates:
|
||||
config_file = os.path.join(CONF.lxd.lxd_root_dir,
|
||||
self.instance, 'config')
|
||||
|
||||
f = open(config_file, 'w')
|
||||
f.write('lxc.include = %s/%s.common.conf\n' % (CONF.lxd.lxd_config_dir,
|
||||
lxc_template))
|
||||
f.write('lxc.include = %s/%s.userns.conf\n' % (CONF.lxd.lxd_config_dir,
|
||||
lxc_template))
|
||||
|
||||
|
||||
class LXDConfigSetName(LXDConfigObject):
|
||||
def __init__(self, container, instance):
|
||||
super(LXDConfigSetName, self).__init__()
|
||||
self.container = container
|
||||
self.instance = instance
|
||||
|
||||
def set_config(self):
|
||||
self.container.append_config_item('lxc.utsname',
|
||||
self.instance)
|
||||
|
||||
class LXDConfigSetRoot(LXDConfigObject):
|
||||
def __init__(self, container, instance):
|
||||
super(LXDConfigSetRoot, self).__init__()
|
||||
self.container = container
|
||||
self.instance = instance
|
||||
self.container_rootfs = os.path.join(CONF.lxd.lxd_root_dir,
|
||||
self.instance,
|
||||
'rootfs')
|
||||
|
||||
def set_config(self):
|
||||
self.container.append_config_item('lxc.rootfs',
|
||||
self.container_rootfs)
|
||||
|
||||
class LXDConfigSetLog(LXDConfigObject):
|
||||
def __init__(self, container, instance):
|
||||
super(LXDConfigSetLog, self).__init__()
|
||||
self.container = container
|
||||
self.instance = instance
|
||||
|
||||
def set_config(self):
|
||||
container_logfile = os.path.join(CONF.lxd.lxd_root_dir,
|
||||
self.instance,
|
||||
'logfile')
|
||||
self.container.append_config_item('lxc.logfile',
|
||||
container_logfile)
|
||||
|
||||
class LXDConfigConsole(LXDConfigObject):
|
||||
def __init__(self, container, instance):
|
||||
super(LXDConfigConsole, self).__init__()
|
||||
self.container = container
|
||||
self.instance = instance
|
||||
|
||||
def set_config(self):
|
||||
console_log = os.path.join(CONF.lxd.lxd_root_dir,
|
||||
self.instance,
|
||||
'console.log')
|
||||
self.container.append_config_item('lxc.console.logfile',
|
||||
console_log)
|
||||
utils.execute('touch', console_log)
|
||||
|
||||
|
||||
class LXDUserConfig(LXDConfigObject):
|
||||
def __init__(self, container, idmap):
|
||||
super(LXDUserConfig, self).__init__()
|
||||
self.container = container
|
||||
self.idmap = idmap
|
||||
|
||||
def set_config(self):
|
||||
for ent in self.idmap.lxc_conf_lines():
|
||||
self.container.append_config_item(*ent)
|
|
@ -0,0 +1,198 @@
|
|||
import os
|
||||
import pwd
|
||||
|
||||
import lxc
|
||||
import tarfile
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import importutils
|
||||
|
||||
from nova.i18n import _, _LW, _LE, _LI
|
||||
from nova.openstack.common import fileutils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import utils
|
||||
from nova.virt import images
|
||||
from nova import exception
|
||||
|
||||
from . import config
|
||||
from . import vif
|
||||
|
||||
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__)
|
||||
|
||||
def get_container_rootfs(instance):
|
||||
return os.path.join(CONF.lxd_root_dir, instance, 'rootfs')
|
||||
|
||||
class Container(object):
|
||||
def __init__(self, client, virtapi):
|
||||
self.client = client
|
||||
self.virtapi = virtapi
|
||||
|
||||
self.container = lxc.Container(instance['uuid'])
|
||||
self.container.set_config_path(CONF.lxd.lxd_root_dir)
|
||||
self.idmap = LXCUserIdMap()
|
||||
|
||||
self.base_dir = os.path.join(CONF.instances_path,
|
||||
CONF.image_cache_subdirectory_name)
|
||||
|
||||
def start_container(self, context, instance, image_meta, network_info, block_device_info, flavor):
|
||||
LOG.info(_LI('Starting new instance'), instance=instance)
|
||||
|
||||
instance_name = instance['uuid']
|
||||
try:
|
||||
''' Create the instance directories '''
|
||||
self._create_container(instance_name)
|
||||
|
||||
''' Fetch the image from glance '''
|
||||
self._fetch_image(context, instance)
|
||||
|
||||
''' Start the contianer '''
|
||||
self._start_container(instance, network_info, image_meta)
|
||||
except Exception:
|
||||
LOG.error(_LE('Failed to spawn instance'), instance=instance)
|
||||
|
||||
def _create_container(self, instance):
|
||||
if not os.path.exists(get_container_dir(instance)):
|
||||
fileutils.ensure_tree(get_container_dir(instance))
|
||||
if not os.path.exists(self.base_dir):
|
||||
fileutils.ensure_tree(self.base_dir)
|
||||
|
||||
def _fetch_image(self, context, instance):
|
||||
(user, group) = self.idmap.get_user()
|
||||
image = os.path.join(self.base_dir, '%s.tar.gz' % instnce['image_ref'])
|
||||
if not os.path.exists(image):
|
||||
images.fetch_to_raw(context, instance['image_ref'], base,
|
||||
instance['user_id'], instance['project_id'])
|
||||
if not tarfile.is_tarfile(image):
|
||||
raise exception.NovaException(_('Not an valid image'))
|
||||
|
||||
utils.execute('tar', '--directory', get_container_dir(instance['uuid']),
|
||||
'--anchored', '--numeric-owner', '-xpzf', image,
|
||||
run_as_root=True, check_exit_code=[0, 2])
|
||||
utils.execute('chown', '-R', '%s:%s' % (user, group),
|
||||
get_container_dir(instance['uuid']), run_as_root=True)
|
||||
|
||||
def _start_container(self, instance, network_info, image_meta):
|
||||
timeout = CONF.vif_plugging_timeout
|
||||
# check to see if neutron is ready before
|
||||
# doing anything else
|
||||
if (not self.client.running(instance['uuid']) and
|
||||
utils.is_neutron() and timeout):
|
||||
events = self._get_neutron_events(network_info)
|
||||
else:
|
||||
events = {}
|
||||
|
||||
try:
|
||||
with self.virtapi.wait_for_instance_event(
|
||||
instance, events, deadline=timeout,
|
||||
error_callback=self._neutron_failed_callback):
|
||||
self._write_config(instance, network_info, image_meta)
|
||||
self._start_network(instance, network_info)
|
||||
self.client.start(instance['uuid'])
|
||||
except exception.VirtualInterfaceCreateException:
|
||||
LOG.info(_LW('Failed'))
|
||||
|
||||
def _write_config(self, instance, network_info, image_meta):
|
||||
template = config.LXDConfigTemplate(instance['uuid'], image_meta)
|
||||
template.set_config()
|
||||
|
||||
self.container.load_config()
|
||||
|
||||
name = config.LXDConfigSetName(self.container, instance['uuid'])
|
||||
name.set_config()
|
||||
|
||||
rootfs = config.LXDConfigSetRoot(self.container, instance['uuid'])
|
||||
rootfs.set_config()
|
||||
|
||||
logpath = config.LXDConfigSetLog(self.container, instance['uuid'])
|
||||
logpath.set_config()
|
||||
|
||||
console_log = config.LXDConfigConsole(self.container, instance['uuid'])
|
||||
console_log.set_config()
|
||||
|
||||
idmap = config.LXDUserConfig(self.container, self.idmap)
|
||||
idmap.set_config()
|
||||
|
||||
self.container.save_config()
|
||||
|
||||
def _start_network(self, instance, network_info):
|
||||
for vif in network_info:
|
||||
self.vif_driver.plug(instance, vif)
|
||||
|
||||
def _teardown_network(self, instance, network_info):
|
||||
for vif in network_info:
|
||||
self.vif_driver.unplug(instancece, vif)
|
||||
|
||||
def _get_neutron_events(self, network_info):
|
||||
return [('network-vif-plugged', vif['id'])
|
||||
for vif in network_info if vif.get('active', True) is False]
|
||||
|
||||
def _neutron_failed_callback(self, event_name, instance):
|
||||
LOG.error(_LE('Neutron Reported failure on event '
|
||||
'%(event)s for instance %(uuid)s'),
|
||||
{'event': event_name, 'uuid': instance.uuid})
|
||||
if CONF.vif_plugging_is_fatal:
|
||||
raise exception.VirtualInterfaceCreateException()
|
||||
|
||||
|
||||
class LXCIdMap(object):
|
||||
|
||||
def __init__(self, ustart, unum, gstart, gnum):
|
||||
self.ustart = int(ustart)
|
||||
self.unum = int(unum)
|
||||
self.gstart = int(gstart)
|
||||
self.gnum = int(gnum)
|
||||
|
||||
def usernsexec_margs(self, with_read=None):
|
||||
if with_read:
|
||||
if with_read == "user":
|
||||
with_read = os.getuid()
|
||||
unum = self.unum - 1
|
||||
rflag = ['-m', 'u:%s:%s:1' % (self.ustart + self.unum, with_read)]
|
||||
print(
|
||||
"================ rflag: %s ==================" %
|
||||
(str(rflag)))
|
||||
else:
|
||||
unum = self.unum
|
||||
rflag = []
|
||||
|
||||
return ['-m', 'u:0:%s:%s' % (self.ustart, unum),
|
||||
'-m', 'g:0:%s:%s' % (self.gstart, self.gnum)] + rflag
|
||||
|
||||
def lxc_conf_lines(self):
|
||||
return (('lxc.id_map', 'u 0 %s %s' % (self.ustart, self.unum)),
|
||||
('lxc.id_map', 'g 0 %s %s' % (self.gstart, self.gnum)))
|
||||
|
||||
def get_user(self):
|
||||
return (self.ustart, self.gstart)
|
||||
|
||||
|
||||
class LXCUserIdMap(LXCIdMap):
|
||||
|
||||
def __init__(self, user=None, group=None, subuid_f="/etc/subuid",
|
||||
subgid_f="/etc/subgid"):
|
||||
if user is None:
|
||||
user = pwd.getpwuid(os.getuid())[0]
|
||||
if group is None:
|
||||
group = grp.getgrgid(os.getgid()).gr_name
|
||||
|
||||
def parse_sfile(fname, name):
|
||||
line = None
|
||||
with open(fname, "r") as fp:
|
||||
for cline in fp:
|
||||
if cline.startswith(name + ":"):
|
||||
line = cline
|
||||
break
|
||||
if line is None:
|
||||
raise ValueError("%s not found in %s" % (name, fname))
|
||||
toks = line.split(":")
|
||||
return (toks[1], toks[2])
|
||||
|
||||
ustart, unum = parse_sfile(subuid_f, user)
|
||||
gstart, gnum = parse_sfile(subgid_f, group)
|
||||
|
||||
self.user = user
|
||||
self.group = group
|
||||
super(LXCUserIdMap, self).__init__(ustart, unum, gstart, gnum)
|
|
@ -0,0 +1,316 @@
|
|||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2010 Citrix Systems, Inc.
|
||||
# Copyright (c) 2014 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.
|
||||
|
||||
"""
|
||||
Nova LXD Driver
|
||||
|
||||
"""
|
||||
|
||||
import socket
|
||||
import contextlib
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.serialization import jsonutils
|
||||
|
||||
from nova.compute import arch
|
||||
from nova.compute import hv_type
|
||||
from nova.compute import power_state
|
||||
from nova.compute import task_states
|
||||
from nova.compute import vm_mode
|
||||
from nova.console import type as ctype
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova.i18n import _LW
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import utils
|
||||
from nova.virt import diagnostics
|
||||
from nova.virt import driver
|
||||
from nova.virt import hardware
|
||||
from nova.virt import virtapi
|
||||
|
||||
from . import client
|
||||
from . import container
|
||||
|
||||
lxd_opts = [
|
||||
cfg.StrOpt('lxd_client_cert',
|
||||
default='/etc/lxd/client.crt',
|
||||
help='LXD client certificate'),
|
||||
cfg.StrOpt('lxd_client_key',
|
||||
default='/etc/lxd/client.key',
|
||||
help='LXD client key'),
|
||||
cfg.StrOpt('lxd_client_host',
|
||||
default='10.5.0.10:8443',
|
||||
help='LXD API Server'),
|
||||
cfg.StrOpt('lxd_root_dir',
|
||||
default='/var/lib/lxd/lxc',
|
||||
help='Default LXD directory'),
|
||||
cfg.StrOpt('lxd_default_template',
|
||||
default='ubuntu-cloud',
|
||||
help='Default LXC template'),
|
||||
cfg.StrOpt('lxd_template_dir',
|
||||
default='/usr/share/lxc/templates',
|
||||
help='Default template directory'),
|
||||
cfg.StrOpt('lxd_config_dir',
|
||||
default='/usr/share/lxc/config',
|
||||
help='Default lxc config dir')
|
||||
]
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(lxd_opts, 'lxd')
|
||||
CONF.import_opt('host', 'nova.netconf')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
class LXDDriver(driver.ComputeDriver):
|
||||
capabilities = {
|
||||
"has_imagecache": False,
|
||||
"supports_recreate": False,
|
||||
}
|
||||
|
||||
|
||||
"""LXD hypervisor driver."""
|
||||
|
||||
def __init__(self, virtapi, read_only=False):
|
||||
super(LXDDriver, self).__init__(virtapi)
|
||||
|
||||
self.client = client.Client(CONF.lxd.lxd_client_host,
|
||||
CONF.lxd.lxd_client_cert,
|
||||
CONF.lxd.lxd_client_key)
|
||||
self.container = container.Container(self.client,
|
||||
self.virtapi)
|
||||
|
||||
|
||||
def init_host(self, host):
|
||||
return
|
||||
|
||||
def list_instances(self):
|
||||
return self.client.list()
|
||||
|
||||
def list_instance_uuids(self):
|
||||
return self.client.list()
|
||||
|
||||
def spawn(self, context, instance, image_meta, injected_files,
|
||||
admin_password, network_info=None, block_device_info=None,
|
||||
flavor=None):
|
||||
self.container.start_container(context, instance, image_meta,
|
||||
injected_files, admin_password, network_info,
|
||||
block_device_info, flavor)
|
||||
|
||||
def snapshot(self, context, instance, name, update_task_state):
|
||||
raise NotImplemented()
|
||||
|
||||
def reboot(self, context, instance, network_info, reboot_type,
|
||||
block_device_info=None, bad_volumes_callback=None):
|
||||
self.client.reboot(instance['uuid'])
|
||||
|
||||
def rescue(self, context, instance, network_info, image_meta,
|
||||
rescue_password):
|
||||
raise NotImplemented()
|
||||
|
||||
def unrescue(self, instance, network_info):
|
||||
raise NotImplemented()
|
||||
|
||||
def poll_rebooting_instances(self, timeout, instances):
|
||||
pass
|
||||
|
||||
def migrate_disk_and_power_off(self, context, instance, dest,
|
||||
flavor, network_info,
|
||||
block_device_info=None,
|
||||
timeout=0, retry_interval=0):
|
||||
pass
|
||||
|
||||
def finish_revert_migration(self, context, instance, network_info,
|
||||
block_device_info=None, power_on=True):
|
||||
pass
|
||||
|
||||
def post_live_migration_at_destination(self, context, instance,
|
||||
network_info,
|
||||
block_migration=False,
|
||||
block_device_info=None):
|
||||
pass
|
||||
|
||||
def power_off(self, instance, shutdown_timeout=0, shutdown_attempts=0):
|
||||
self.client.stop(instance['uuid'])
|
||||
|
||||
def power_on(self, context, instance, network_info, block_device_info):
|
||||
self.client.start(instance['uuid])
|
||||
|
||||
def soft_delete(self, instance):
|
||||
pass
|
||||
|
||||
def restore(self, instance):
|
||||
raise NotImplemented()
|
||||
|
||||
def pause(self, instance):
|
||||
raise NotImplemented()
|
||||
|
||||
def unpause(self, instance):
|
||||
raise NotImplemented()
|
||||
|
||||
def suspend(self, instance):
|
||||
raise NotImplemented()
|
||||
|
||||
def resume(self, context, instance, network_info, block_device_info=None):
|
||||
raise NotImplemented()
|
||||
|
||||
def destroy(self, context, instance, network_info, block_device_info=None,
|
||||
destroy_disks=True, migrate_data=None):
|
||||
pass
|
||||
|
||||
def cleanup(self, context, instance, network_info, block_device_info=None,
|
||||
destroy_disks=True, migrate_data=None, destroy_vifs=True):
|
||||
pass
|
||||
|
||||
def attach_volume(self, context, connection_info, instance, mountpoint,
|
||||
disk_bus=None, device_type=None, encryption=None):
|
||||
"""Attach the disk to the instance at mountpoint using info."""
|
||||
raise NotImplemented()
|
||||
|
||||
def detach_volume(self, connection_info, instance, mountpoint,
|
||||
encryption=None):
|
||||
"""Detach the disk attached to the instance."""
|
||||
raise NotImplemented()
|
||||
|
||||
def swap_volume(self, old_connection_info, new_connection_info,
|
||||
instance, mountpoint, resize_to):
|
||||
"""Replace the disk attached to the instance."""
|
||||
raise NotImplemented()
|
||||
|
||||
def attach_interface(self, instance, image_meta, vif):
|
||||
raise NotImplemented()
|
||||
|
||||
def detach_interface(self, instance, vif):
|
||||
raise NotImplemented()
|
||||
|
||||
def get_info(self, instance):
|
||||
if self.client.running(instance['uuid']):
|
||||
pstate = power_state.RUNNING
|
||||
else:
|
||||
pstate = power_state.SHUTDOWN
|
||||
return hardware.InstanceInfo(state=pstate,
|
||||
max_mem_kb=0,
|
||||
mem_kb=0,
|
||||
num_cpu=2,
|
||||
cpu_time_ns=0)
|
||||
|
||||
def get_console_output(self, context, instance):
|
||||
return 'FAKE CONSOLE OUTPUT\nANOTHER\nLAST LINE'
|
||||
|
||||
def get_vnc_console(self, context, instance):
|
||||
return True
|
||||
|
||||
def get_spice_console(self, context, instance):
|
||||
return True
|
||||
|
||||
def get_rdp_console(self, context, instance):
|
||||
return True
|
||||
|
||||
def get_serial_console(self, context, instance):
|
||||
return True
|
||||
def get_console_pool_info(self, console_type):
|
||||
return True
|
||||
|
||||
def refresh_security_group_rules(self, security_group_id):
|
||||
return True
|
||||
|
||||
def refresh_security_group_members(self, security_group_id):
|
||||
return True
|
||||
|
||||
def refresh_instance_security_rules(self, instance):
|
||||
return True
|
||||
|
||||
def refresh_provider_fw_rules(self):
|
||||
pass
|
||||
|
||||
def get_available_resource(self, nodename):
|
||||
"""Updates compute manager resource info on ComputeNode table.
|
||||
|
||||
Since we don't have a real hypervisor, pretend we have lots of
|
||||
disk and ram.
|
||||
"""
|
||||
data = {}
|
||||
disk = host_utils.get_fs_info(CONF.instances_path)
|
||||
memory = host_utils.get_memory_mb_usage()
|
||||
|
||||
data["supported_instances"] = jsonutils.dumps([
|
||||
('i686', 'lxd', 'lxd'),
|
||||
('x86_64', 'lxd', 'lxd')])
|
||||
data["vcpus"] = psutil.cpu_count()
|
||||
data["memory_mb"] = memory['total'] / units.Mi
|
||||
data["local_gb"] = disk['total'] / units.Gi
|
||||
data["vcpus_used"] = 1
|
||||
data["memory_mb_used"] = memory['used'] / units.Mi
|
||||
data["local_gb_used"] = disk['used'] / units.Gi
|
||||
data["hypervisor_type"] = "lxd"
|
||||
data["hypervisor_version"] = "1.0"
|
||||
data["hypervisor_hostname"] = nodename
|
||||
data["cpu_info"] = "?"
|
||||
data["disk_available_least"] = disk['free'] / units.Gi
|
||||
data['numa_topology'] = None
|
||||
|
||||
return data
|
||||
|
||||
def ensure_filtering_rules_for_instance(self, instance_ref, network_info):
|
||||
return
|
||||
|
||||
def get_instance_disk_info(self, instance, block_device_info=None):
|
||||
return
|
||||
|
||||
def live_migration(self, context, instance_ref, dest,
|
||||
post_method, recover_method, block_migration=False,
|
||||
migrate_data=None):
|
||||
post_method(context, instance_ref, dest, block_migration,
|
||||
migrate_data)
|
||||
return
|
||||
|
||||
def check_can_live_migrate_destination_cleanup(self, ctxt,
|
||||
dest_check_data):
|
||||
return
|
||||
|
||||
def check_can_live_migrate_destination(self, ctxt, instance_ref,
|
||||
src_compute_info, dst_compute_info,
|
||||
block_migration=False,
|
||||
disk_over_commit=False):
|
||||
return {}
|
||||
|
||||
def check_can_live_migrate_source(self, ctxt, instance_ref,
|
||||
dest_check_data, block_device_info=None):
|
||||
return
|
||||
|
||||
def finish_migration(self, context, migration, instance, disk_info,
|
||||
network_info, image_meta, resize_instance,
|
||||
block_device_info=None, power_on=True):
|
||||
return
|
||||
|
||||
def confirm_migration(self, migration, instance, network_info):
|
||||
return
|
||||
|
||||
def pre_live_migration(self, context, instance_ref, block_device_info,
|
||||
network_info, disk, migrate_data=None):
|
||||
return
|
||||
|
||||
def unfilter_instance(self, instance_ref, network_info):
|
||||
return
|
||||
|
||||
|
||||
def get_available_nodes(self, refresh=False):
|
||||
hostname = socket.gethostname()
|
||||
return [hostname]
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import os
|
||||
|
||||
def get_fs_info(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_frsize * hddinfo.f_blocks
|
||||
free = hddinfo.f_frsize * hddinfo.f_bavail
|
||||
used = hddinfo.f_frsize * (hddinfo.f_blocks - hddinfo.f_bfree)
|
||||
return {'total': total,
|
||||
'free': free,
|
||||
'used': used}
|
||||
|
||||
def get_memory_mb_usage():
|
||||
"""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
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
# Copyright (c) 2014 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.
|
||||
|
||||
"""
|
||||
Manage edits to lxc-usernet(5) style file (/etc/lxc/lxc-usernet).
|
||||
File is
|
||||
* comment lines (#)
|
||||
* <username> <type> <bridge> <count>
|
||||
|
||||
example:
|
||||
# USERNAME TYPE BRIDGE COUNT
|
||||
ubuntu veth br100 128
|
||||
"""
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import tempfile
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
|
||||
ETC_LXC_USERNET = "/etc/lxc/lxc-usernet"
|
||||
|
||||
|
||||
class UserNetLine(object):
|
||||
|
||||
def __init__(self, line):
|
||||
self.error = None
|
||||
line = line.rstrip("\n")
|
||||
cpos = line.find("#")
|
||||
user = None
|
||||
ntype = None
|
||||
brname = None
|
||||
count = None
|
||||
if cpos < 0:
|
||||
payload = line.strip()
|
||||
comment = None
|
||||
else:
|
||||
payload = line[:cpos].strip()
|
||||
comment = line[cpos:]
|
||||
|
||||
if payload:
|
||||
try:
|
||||
user, ntype, brname, count = payload.split()
|
||||
except ValueError:
|
||||
# don't understand this line.
|
||||
self.error = line
|
||||
else:
|
||||
comment = line
|
||||
|
||||
self.user = user
|
||||
self.bridge = brname
|
||||
self.ntype = ntype
|
||||
self.count = count
|
||||
self.comment = comment
|
||||
|
||||
def __repr__(self):
|
||||
return(self.__str__())
|
||||
|
||||
def __str__(self):
|
||||
if self.error is not None:
|
||||
return self.error
|
||||
|
||||
if self.user:
|
||||
comm = ""
|
||||
if self.comment:
|
||||
comm = " " + self.comment
|
||||
return ("%s %s %s %s" % (self.user, self.ntype, self.bridge,
|
||||
str(self.count) + comm))
|
||||
return self.comment
|
||||
|
||||
|
||||
def load_usernet(fname):
|
||||
lines = []
|
||||
with open(fname, "r") as fp:
|
||||
for line in fp:
|
||||
lines.append(UserNetLine(line))
|
||||
return lines
|
||||
|
||||
|
||||
def write_usernet(fname, lines, drop=None):
|
||||
if drop:
|
||||
nlines = []
|
||||
for i, l in enumerate(lines):
|
||||
if i not in drop:
|
||||
nlines.append(l)
|
||||
lines = nlines
|
||||
tf = None
|
||||
try:
|
||||
tf = tempfile.NamedTemporaryFile(dir=os.path.dirname(fname),
|
||||
delete=False)
|
||||
for l in lines:
|
||||
tf.write(str(l) + "\n")
|
||||
tf.close()
|
||||
|
||||
if os.path.isfile(fname):
|
||||
statl = os.stat(fname)
|
||||
os.chmod(tf.name, statl.st_mode)
|
||||
os.chown(tf.name, statl.st_uid, statl.st_gid)
|
||||
else:
|
||||
os.chmod(tf.name, 0o644)
|
||||
|
||||
os.rename(tf.name, fname)
|
||||
finally:
|
||||
if tf is not None and os.path.isfile(tf.name):
|
||||
os.unlink(tf.name)
|
||||
|
||||
|
||||
def update_usernet(user, bridge, op, count=1, ntype="veth",
|
||||
strict=False, fname=ETC_LXC_USERNET):
|
||||
|
||||
ops = ("set", "inc", "dec")
|
||||
if op not in ops:
|
||||
raise TypeError("op = '%s'. must be one of %s",
|
||||
(op, ','.join(ops)))
|
||||
|
||||
minfo = "user=%s, bridge=%s, ntype=%s" % (user, bridge, ntype)
|
||||
lines = load_usernet(fname)
|
||||
|
||||
found = []
|
||||
for i, l in enumerate(lines):
|
||||
if (user != l.user or bridge != l.bridge or l.ntype != ntype):
|
||||
continue
|
||||
found.append(i)
|
||||
|
||||
if strict and not found and op != "set":
|
||||
raise ValueError("EntryNotFound: %s" % minfo)
|
||||
|
||||
if not found:
|
||||
if op == "dec":
|
||||
# decrement non-existing, assume zero
|
||||
return
|
||||
newline = UserNetLine("")
|
||||
newline.user = user
|
||||
newline.ntype = ntype
|
||||
newline.bridge = bridge
|
||||
newline.count = int(count)
|
||||
lines.append(newline)
|
||||
else:
|
||||
# update the last one (others deleted on write)
|
||||
line = lines[found[-1]]
|
||||
if op == "inc":
|
||||
line.count = int(line.count) + int(count)
|
||||
elif op == "dec":
|
||||
line.count = int(line.count) - int(count)
|
||||
elif op == "set":
|
||||
if len(found) == 1 and line.count == count and count != 0:
|
||||
return
|
||||
line.count = count
|
||||
if line.count == 0:
|
||||
# set or dec to '0'. add this line to found, for delete
|
||||
found.append(found[-1])
|
||||
|
||||
write_usernet(fname, lines, found[:-1])
|
||||
|
||||
|
||||
def lfilter(fname, user=None, bridge=None, count=None, ntype="veth"):
|
||||
ret = []
|
||||
for f in load_usernet(fname):
|
||||
if user is not None and f.user != user:
|
||||
continue
|
||||
if bridge is not None and f.bridge != bridge:
|
||||
continue
|
||||
if count is not None and str(f.count) != str(count):
|
||||
continue
|
||||
if ntype is not None and f.ntype != ntype:
|
||||
continue
|
||||
ret.append(f)
|
||||
return ret
|
||||
|
||||
|
||||
def manage_main():
|
||||
fname = ETC_LXC_USERNET
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--type", "-t", help="nic type (default: 'veth')",
|
||||
default="veth", dest="ntype")
|
||||
parser.add_argument(
|
||||
'operation',
|
||||
choices=(
|
||||
"set",
|
||||
"inc",
|
||||
"dec",
|
||||
"del",
|
||||
"get"))
|
||||
parser.add_argument('user', help="username")
|
||||
parser.add_argument('bridge', help="bridge")
|
||||
parser.add_argument('count', nargs="?", help="number to operate with.",
|
||||
default=None, const=int)
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.operation == "del":
|
||||
args.operation = "set"
|
||||
args.count = 0
|
||||
elif args.operation in ("set", "inc", "dec") and args.count is None:
|
||||
args.count = 1
|
||||
|
||||
if args.operation == "get":
|
||||
if args.bridge == "*":
|
||||
args.bridge = None
|
||||
matching = lfilter(fname, user=args.user, bridge=args.bridge,
|
||||
count=args.count, ntype=args.ntype)
|
||||
for l in matching:
|
||||
print(str(l))
|
||||
return 0
|
||||
|
||||
with lockutils.lock(str(fname)):
|
||||
update_usernet(user=args.user, bridge=args.bridge, op=args.operation,
|
||||
count=args.count, ntype=args.ntype, fname=fname)
|
|
@ -0,0 +1,148 @@
|
|||
import getpass
|
||||
|
||||
import os
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova.i18n import _LW
|
||||
from nova import exception
|
||||
from nova import utils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.network import linux_net
|
||||
from nova.network import model as network_model
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def write_lxc_usernet(instance, bridge, user=None, count=1):
|
||||
if user is None:
|
||||
user = getpass.getuser()
|
||||
utils.execute('lxc-usernet-manage', 'set', user, bridge, str(count),
|
||||
run_as_root=True, check_exit_code=[0])
|
||||
|
||||
def write_lxc_config(instance, vif):
|
||||
config_file = os.path.join(CONF.lxd.lxd_root_dir,
|
||||
instance['uuid'],
|
||||
'config')
|
||||
with open(config_file, 'a+') as f:
|
||||
f.write('lxc.network.type = veth\n')
|
||||
f.write('lxc.network.hwaddr = %s\n' % vif['address'])
|
||||
if vif['type'] == 'ovs':
|
||||
bridge = 'qbr%s' % vif['id'][:11]
|
||||
else:
|
||||
bridge = vif['network']['bridge']
|
||||
|
||||
f.write('lxc.network.link = %s\n' % bridge)
|
||||
|
||||
|
||||
class LXDGenericDriver(object):
|
||||
def _get_vif_driver(self, vif):
|
||||
vif_type = vif['type']
|
||||
if vif_type is None:
|
||||
raise exception.NovaException(
|
||||
_("vif_type parameter must be present "
|
||||
"for this vif_driver implementation"))
|
||||
elif vif_type == network_model.VIF_TYPE_OVS:
|
||||
return LXDOpenVswitchDriver()
|
||||
else:
|
||||
return LXDNetworkBridgeDriver()
|
||||
|
||||
def plug(self, instance, vif):
|
||||
vif_driver = self._get_vif_driver(vif)
|
||||
vif_driver.plug(instance, vif)
|
||||
|
||||
def unplug(self, instance, vif):
|
||||
vif_driver = self._get_vif_driver(vif)
|
||||
vif_driver.unplug(instance, vif)
|
||||
|
||||
class LXDOpenVswitchDriver(object):
|
||||
def plug(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['uuid'])
|
||||
|
||||
write_lxc_config(instance, vif)
|
||||
write_lxc_usernet(instance, br_name)
|
||||
|
||||
def unplug(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(_("Failed while unplugging vif"),
|
||||
instance=instance)
|
||||
|
||||
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])
|
||||
|
||||
class LXDNetworkBridgeDriver(object):
|
||||
def plug(self, contianer, 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': vif['network']['bridge']},
|
||||
instance=instance)
|
||||
linux_net.LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
|
||||
network.get_meta('vlan'),
|
||||
vif['network']['bridge'],
|
||||
iface)
|
||||
else:
|
||||
iface = CONF.flat_interface or \
|
||||
network.get_meta('bridge_interface')
|
||||
LOG.debug("Ensuring bridge %s",
|
||||
vif['network']['bridge'], instance=instance)
|
||||
linux_net.LinuxBridgeInterfaceDriver.ensure_bridge(
|
||||
vif['network']['bridge'],
|
||||
iface)
|
||||
write_lxc_config(instance, vif)
|
||||
|
||||
def unplug(self, container, intsance, vif):
|
||||
pass
|
|
@ -0,0 +1,6 @@
|
|||
[DEFAULT]
|
||||
|
||||
# The list of modules to copy from oslo-incubator.git
|
||||
|
||||
# The base module to hold the copy of openstack.common
|
||||
base=nova-compute-lxd
|
|
@ -0,0 +1,10 @@
|
|||
# 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>=0.6,!=0.7,<1.0
|
||||
Babel>=1.3
|
||||
requests
|
||||
oslo.concurrency
|
||||
oslo.utils
|
||||
oslo.i18n
|
|
@ -0,0 +1,53 @@
|
|||
[metadata]
|
||||
name = nclxd
|
||||
summary = native lxd driver for openstack
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = http://www.openstack.org/
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 2.6
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.3
|
||||
Programming Language :: Python :: 3.4
|
||||
|
||||
[files]
|
||||
packages =
|
||||
nclxd
|
||||
namespace_packages =
|
||||
nclxd
|
||||
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
lxc-usernet-manage = nclxd.nova.virt.lxd.lxc_usernet:manage_main
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[upload_sphinx]
|
||||
upload-dir = doc/build/html
|
||||
|
||||
[compile_catalog]
|
||||
directory = nclxd/locale
|
||||
domain = nclxd
|
||||
|
||||
[update_catalog]
|
||||
domain = nclxd
|
||||
output_dir = nclxd/locale
|
||||
input_file = nclxd/locale/nclxd.pot
|
||||
|
||||
[extract_messages]
|
||||
keywords = _ gettext ngettext l_ lazy_gettext
|
||||
mapping_file = babel.cfg
|
||||
output_file = nclxd/locale/nclxd.pot
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python
|
||||
# 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
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
pbr=True)
|
|
@ -0,0 +1,15 @@
|
|||
# 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.9.2,<0.10
|
||||
|
||||
coverage>=3.6
|
||||
discover
|
||||
python-subunit
|
||||
sphinx>=1.1.2
|
||||
oslosphinx
|
||||
oslotest>=1.1.0.0a1
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=0.9.34
|
|
@ -0,0 +1,34 @@
|
|||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = py33,py34,py26,py27,pypy,pep8
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install -U {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||
|
||||
[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
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
|
Loading…
Reference in New Issue