summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--HACKING.rst1
-rwxr-xr-xbin/nova-novncproxy96
-rwxr-xr-xbin/nova-spicehtml5proxy93
-rw-r--r--doc/api_samples/os-consoles/get-spice-console-post-req.json5
-rw-r--r--doc/api_samples/os-consoles/get-spice-console-post-req.xml2
-rw-r--r--doc/api_samples/os-consoles/get-spice-console-post-resp.json6
-rw-r--r--doc/api_samples/os-consoles/get-spice-console-post-resp.xml5
-rw-r--r--doc/source/conf.py2
-rw-r--r--doc/source/man/nova-spicehtml5proxy.rst48
-rw-r--r--etc/nova/nova.conf.sample10
-rw-r--r--etc/nova/rootwrap.d/baremetal-compute-pxe.filters11
-rw-r--r--nova/api/openstack/compute/contrib/admin_actions.py2
-rw-r--r--nova/api/openstack/compute/contrib/consoles.py25
-rw-r--r--nova/api/openstack/compute/servers.py22
-rw-r--r--nova/api/openstack/wsgi.py4
-rw-r--r--nova/availability_zones.py2
-rw-r--r--nova/compute/api.py75
-rw-r--r--nova/compute/cells_api.py15
-rw-r--r--nova/compute/manager.py50
-rw-r--r--nova/compute/resource_tracker.py12
-rw-r--r--nova/compute/rpcapi.py8
-rw-r--r--nova/compute/utils.py9
-rw-r--r--nova/conductor/api.py20
-rw-r--r--nova/conductor/manager.py16
-rw-r--r--nova/conductor/rpcapi.py17
-rw-r--r--nova/console/websocketproxy.py89
-rw-r--r--nova/crypto.py43
-rw-r--r--nova/db/api.py18
-rw-r--r--nova/db/sqlalchemy/api.py112
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/150_add_host_to_instance_faults.py36
-rw-r--r--nova/db/sqlalchemy/models.py1
-rw-r--r--nova/exception.py10
-rw-r--r--nova/network/linux_net.py33
-rw-r--r--nova/network/manager.py23
-rw-r--r--nova/network/quantumv2/api.py80
-rw-r--r--nova/openstack/common/eventlet_backdoor.py2
-rw-r--r--nova/openstack/common/lockutils.py6
-rw-r--r--nova/openstack/common/log.py16
-rw-r--r--nova/scheduler/driver.py4
-rw-r--r--nova/scheduler/manager.py4
-rw-r--r--nova/service.py12
-rw-r--r--nova/servicegroup/api.py12
-rw-r--r--nova/servicegroup/drivers/db.py29
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_consoles.py92
-rw-r--r--nova/tests/api/openstack/compute/test_servers.py68
-rw-r--r--nova/tests/baremetal/test_pxe.py17
-rw-r--r--nova/tests/compute/test_compute.py204
-rw-r--r--nova/tests/compute/test_compute_utils.py3
-rw-r--r--nova/tests/compute/test_rpcapi.py5
-rw-r--r--nova/tests/conductor/test_conductor.py27
-rw-r--r--nova/tests/fake_policy.py1
-rw-r--r--nova/tests/fakelibvirt.py1
-rw-r--r--nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.json.tpl5
-rw-r--r--nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.xml.tpl4
-rw-r--r--nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.json.tpl6
-rw-r--r--nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.xml.tpl5
-rw-r--r--nova/tests/integrated/test_api_samples.py17
-rw-r--r--nova/tests/network/test_linux_net.py16
-rw-r--r--nova/tests/network/test_quantumv2.py115
-rw-r--r--nova/tests/scheduler/test_chance_scheduler.py6
-rw-r--r--nova/tests/scheduler/test_filter_scheduler.py16
-rw-r--r--nova/tests/scheduler/test_scheduler.py24
-rw-r--r--nova/tests/test_cinder.py14
-rw-r--r--nova/tests/test_crypto.py63
-rw-r--r--nova/tests/test_fakelibvirt.py1
-rw-r--r--nova/tests/test_libvirt.py49
-rw-r--r--nova/tests/test_utils.py21
-rw-r--r--nova/tests/test_virt_drivers.py9
-rw-r--r--nova/tests/test_xenapi.py25
-rw-r--r--nova/tests/xenapi/stubs.py14
-rw-r--r--nova/utils.py8
-rw-r--r--nova/virt/baremetal/net-dhcp.ubuntu.template3
-rw-r--r--nova/virt/baremetal/net-static.ubuntu.template3
-rw-r--r--nova/virt/baremetal/pxe.py28
-rw-r--r--nova/virt/baremetal/volume_driver.py2
-rw-r--r--nova/virt/driver.py4
-rw-r--r--nova/virt/fake.py6
-rw-r--r--nova/virt/hyperv/volumeops.py2
-rw-r--r--nova/virt/libvirt/driver.py36
-rw-r--r--nova/virt/libvirt/vif.py54
-rw-r--r--nova/virt/xenapi/agent.py9
-rw-r--r--nova/virt/xenapi/pool_states.py10
-rw-r--r--nova/volume/cinder.py4
-rwxr-xr-xrun_tests.sh2
-rw-r--r--setup.py1
-rwxr-xr-xtools/hacking.py88
-rw-r--r--tools/pip-requires1
-rw-r--r--tools/test-requires3
-rw-r--r--tox.ini4
89 files changed, 1712 insertions, 470 deletions
diff --git a/HACKING.rst b/HACKING.rst
index be894f072..35493e55b 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -9,6 +9,7 @@ Nova Style Commandments
General
-------
- Put two newlines between top-level code (funcs, classes, etc)
+- Use only UNIX style newlines ("\n"), not Windows style ("\r\n")
- Put one newline between methods in classes and anywhere else
- Long lines should be wrapped in parentheses
in preference to using a backslash for line continuation.
diff --git a/bin/nova-novncproxy b/bin/nova-novncproxy
index beee143f5..8562acc53 100755
--- a/bin/nova-novncproxy
+++ b/bin/nova-novncproxy
@@ -21,20 +21,12 @@ Websocket proxy that is compatible with OpenStack Nova
noVNC consoles. Leverages websockify.py by Joel Martin
'''
-import Cookie
import os
-import socket
import sys
-import websockify
-
from nova import config
-from nova.consoleauth import rpcapi as consoleauth_rpcapi
-from nova import context
+from nova.console import websocketproxy as ws
from nova.openstack.common import cfg
-from nova.openstack.common import log as logging
-from nova.openstack.common import rpc
-from nova import utils
opts = [
@@ -69,64 +61,6 @@ opts = [
CONF = cfg.CONF
CONF.register_cli_opts(opts)
-LOG = logging.getLogger(__name__)
-
-
-class NovaWebSocketProxy(websockify.WebSocketProxy):
- def __init__(self, *args, **kwargs):
- websockify.WebSocketProxy.__init__(self, unix_target=None,
- target_cfg=None,
- ssl_target=None, *args, **kwargs)
-
- def new_client(self):
- """
- Called after a new WebSocket connection has been established.
- """
- cookie = Cookie.SimpleCookie()
- cookie.load(self.headers.getheader('cookie'))
- token = cookie['token'].value
- ctxt = context.get_admin_context()
- rpcapi = consoleauth_rpcapi.ConsoleAuthAPI()
- connect_info = rpcapi.check_token(ctxt, token=token)
-
- if not connect_info:
- LOG.audit("Invalid Token: %s", token)
- raise Exception(_("Invalid Token"))
-
- host = connect_info['host']
- port = int(connect_info['port'])
-
- # Connect to the target
- self.msg("connecting to: %s:%s" % (host, port))
- LOG.audit("connecting to: %s:%s" % (host, port))
- tsock = self.socket(host, port, connect=True)
-
- # Handshake as necessary
- if connect_info.get('internal_access_path'):
- tsock.send("CONNECT %s HTTP/1.1\r\n\r\n" %
- connect_info['internal_access_path'])
- while True:
- data = tsock.recv(4096, socket.MSG_PEEK)
- if data.find("\r\n\r\n") != -1:
- if not data.split("\r\n")[0].find("200"):
- LOG.audit("Invalid Connection Info %s", token)
- raise Exception(_("Invalid Connection Info"))
- tsock.recv(len(data))
- break
-
- if self.verbose and not self.daemon:
- print(self.traffic_legend)
-
- # Start proxying
- try:
- self.do_proxy(tsock)
- except Exception:
- if tsock:
- tsock.shutdown(socket.SHUT_RDWR)
- tsock.close()
- self.vmsg("%s:%s: Target closed" % (host, port))
- LOG.audit("%s:%s: Target closed" % (host, port))
- raise
if __name__ == '__main__':
@@ -142,18 +76,18 @@ if __name__ == '__main__':
sys.exit(-1)
# Create and start the NovaWebSockets proxy
- server = NovaWebSocketProxy(listen_host=CONF.novncproxy_host,
- listen_port=CONF.novncproxy_port,
- source_is_ipv6=CONF.source_is_ipv6,
- verbose=CONF.verbose,
- cert=CONF.cert,
- key=CONF.key,
- ssl_only=CONF.ssl_only,
- daemon=CONF.daemon,
- record=CONF.record,
- web=CONF.web,
- target_host='ignore',
- target_port='ignore',
- wrap_mode='exit',
- wrap_cmd=None)
+ server = ws.NovaWebSocketProxy(listen_host=CONF.novncproxy_host,
+ listen_port=CONF.novncproxy_port,
+ source_is_ipv6=CONF.source_is_ipv6,
+ verbose=CONF.verbose,
+ cert=CONF.cert,
+ key=CONF.key,
+ ssl_only=CONF.ssl_only,
+ daemon=CONF.daemon,
+ record=CONF.record,
+ web=CONF.web,
+ target_host='ignore',
+ target_port='ignore',
+ wrap_mode='exit',
+ wrap_cmd=None)
server.start_server()
diff --git a/bin/nova-spicehtml5proxy b/bin/nova-spicehtml5proxy
new file mode 100755
index 000000000..b1882bbea
--- /dev/null
+++ b/bin/nova-spicehtml5proxy
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 OpenStack, LLC.
+# 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.
+
+'''
+Websocket proxy that is compatible with OpenStack Nova
+SPICE HTML5 consoles. Leverages websockify.py by Joel Martin
+'''
+
+import os
+import sys
+
+from nova import config
+from nova.console import websocketproxy as ws
+from nova.openstack.common import cfg
+
+
+opts = [
+ cfg.BoolOpt('record',
+ default=False,
+ help='Record sessions to FILE.[session_number]'),
+ cfg.BoolOpt('daemon',
+ default=False,
+ help='Become a daemon (background process)'),
+ cfg.BoolOpt('ssl_only',
+ default=False,
+ help='Disallow non-encrypted connections'),
+ cfg.BoolOpt('source_is_ipv6',
+ default=False,
+ help='Source is ipv6'),
+ cfg.StrOpt('cert',
+ default='self.pem',
+ help='SSL certificate file'),
+ cfg.StrOpt('key',
+ default=None,
+ help='SSL key file (if separate from cert)'),
+ cfg.StrOpt('web',
+ default='/usr/share/spice-html5',
+ help='Run webserver on same port. Serve files from DIR.'),
+ cfg.StrOpt('spicehtml5proxy_host',
+ default='0.0.0.0',
+ help='Host on which to listen for incoming requests'),
+ cfg.IntOpt('spicehtml5proxy_port',
+ default=6082,
+ help='Port on which to listen for incoming requests'),
+ ]
+
+CONF = cfg.CONF
+CONF.register_cli_opts(opts)
+
+
+if __name__ == '__main__':
+ if CONF.ssl_only and not os.path.exists(CONF.cert):
+ parser.error("SSL only and %s not found" % CONF.cert)
+
+ # Setup flags
+ config.parse_args(sys.argv)
+
+ # Check to see if spice html/js/css files are present
+ if not os.path.exists(CONF.web):
+ print "Can not find spice html/js/css files at %s." % CONF.web
+ sys.exit(-1)
+
+ # Create and start the NovaWebSockets proxy
+ server = ws.NovaWebSocketProxy(listen_host=CONF.spicehtml5proxy_host,
+ listen_port=CONF.spicehtml5proxy_port,
+ source_is_ipv6=CONF.source_is_ipv6,
+ verbose=CONF.verbose,
+ cert=CONF.cert,
+ key=CONF.key,
+ ssl_only=CONF.ssl_only,
+ daemon=CONF.daemon,
+ record=CONF.record,
+ web=CONF.web,
+ target_host='ignore',
+ target_port='ignore',
+ wrap_mode='exit',
+ wrap_cmd=None)
+ server.start_server()
diff --git a/doc/api_samples/os-consoles/get-spice-console-post-req.json b/doc/api_samples/os-consoles/get-spice-console-post-req.json
new file mode 100644
index 000000000..d04f7c7ae
--- /dev/null
+++ b/doc/api_samples/os-consoles/get-spice-console-post-req.json
@@ -0,0 +1,5 @@
+{
+ "os-getSPICEConsole": {
+ "type": "spice-html5"
+ }
+}
diff --git a/doc/api_samples/os-consoles/get-spice-console-post-req.xml b/doc/api_samples/os-consoles/get-spice-console-post-req.xml
new file mode 100644
index 000000000..59052abea
--- /dev/null
+++ b/doc/api_samples/os-consoles/get-spice-console-post-req.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<os-getSPICEConsole type="spice-html5" />
diff --git a/doc/api_samples/os-consoles/get-spice-console-post-resp.json b/doc/api_samples/os-consoles/get-spice-console-post-resp.json
new file mode 100644
index 000000000..f4999e1ba
--- /dev/null
+++ b/doc/api_samples/os-consoles/get-spice-console-post-resp.json
@@ -0,0 +1,6 @@
+{
+ "console": {
+ "type": "spice-html5",
+ "url": "http://example.com:6080/spice_auto.html?token=f9906a48-b71e-4f18-baca-c987da3ebdb3&title=dafa(75ecef58-3b8e-4659-ab3b-5501454188e9)"
+ }
+}
diff --git a/doc/api_samples/os-consoles/get-spice-console-post-resp.xml b/doc/api_samples/os-consoles/get-spice-console-post-resp.xml
new file mode 100644
index 000000000..acba8b1f0
--- /dev/null
+++ b/doc/api_samples/os-consoles/get-spice-console-post-resp.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<console>
+ <type>spice-html5</type>
+ <url>http://example.com:6080/spice_auto.html?token=f9906a48-b71e-4f18-baca-c987da3ebdb3</url>
+</console>
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 804080e79..0bdaeb08e 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -145,6 +145,8 @@ man_pages = [
[u'OpenStack'], 1),
('man/nova-novncproxy', 'nova-novncproxy', u'Cloud controller fabric',
[u'OpenStack'], 1),
+ ('man/nova-spicehtml5proxy', 'nova-spicehtml5proxy', u'Cloud controller fabric',
+ [u'OpenStack'], 1),
('man/nova-objectstore', 'nova-objectstore', u'Cloud controller fabric',
[u'OpenStack'], 1),
('man/nova-rootwrap', 'nova-rootwrap', u'Cloud controller fabric',
diff --git a/doc/source/man/nova-spicehtml5proxy.rst b/doc/source/man/nova-spicehtml5proxy.rst
new file mode 100644
index 000000000..4d0aaa202
--- /dev/null
+++ b/doc/source/man/nova-spicehtml5proxy.rst
@@ -0,0 +1,48 @@
+====================
+nova-spicehtml5proxy
+====================
+
+--------------------------------------------------------
+Websocket Proxy for OpenStack Nova SPICE HTML5 consoles.
+--------------------------------------------------------
+
+:Author: openstack@lists.launchpad.net
+:Date: 2012-09-27
+:Copyright: OpenStack LLC
+:Version: 2012.1
+:Manual section: 1
+:Manual group: cloud computing
+
+SYNOPSIS
+========
+
+ nova-spicehtml5proxy [options]
+
+DESCRIPTION
+===========
+
+Websocket proxy that is compatible with OpenStack Nova
+SPICE HTML5 consoles.
+
+OPTIONS
+=======
+
+ **General options**
+
+FILES
+========
+
+* /etc/nova/nova.conf
+* /etc/nova/policy.json
+* /etc/nova/rootwrap.conf
+* /etc/nova/rootwrap.d/
+
+SEE ALSO
+========
+
+* `OpenStack Nova <http://nova.openstack.org>`__
+
+BUGS
+====
+
+* Nova is sourced in Launchpad so you can view current bugs at `OpenStack Nova <http://nova.openstack.org>`__
diff --git a/etc/nova/nova.conf.sample b/etc/nova/nova.conf.sample
index 96118eb76..36a7b0d9c 100644
--- a/etc/nova/nova.conf.sample
+++ b/etc/nova/nova.conf.sample
@@ -1756,7 +1756,7 @@
# value)
#hyperv_attaching_volume_retry_count=10
-# The seconds to wait between an volume attachment attempt
+# The seconds to wait between a volume attachment attempt
# (integer value)
#hyperv_wait_between_attach_retry=5
@@ -2282,6 +2282,10 @@
# value)
#cinder_http_retries=3
+# Allow to perform insecure SSL (https) requests to cinder
+# (boolean value)
+#cinder_api_insecure=false
+
[conductor]
@@ -2476,7 +2480,7 @@
#
# Do not set this out of dev/test environments. If a node does
-# not have an fixed PXE IP address, volumes are exported with
+# not have a fixed PXE IP address, volumes are exported with
# globally opened ACL (boolean value)
#use_unsafe_iscsi=false
@@ -2546,4 +2550,4 @@
#keymap=en-us
-# Total option count: 519
+# Total option count: 520
diff --git a/etc/nova/rootwrap.d/baremetal-compute-pxe.filters b/etc/nova/rootwrap.d/baremetal-compute-pxe.filters
deleted file mode 100644
index 35fa61723..000000000
--- a/etc/nova/rootwrap.d/baremetal-compute-pxe.filters
+++ /dev/null
@@ -1,11 +0,0 @@
-# nova-rootwrap command filters for compute nodes
-# This file should be owned by (and only-writeable by) the root user
-
-[Filters]
-
-# nova/virt/baremetal/pxe.py: 'dnsmasq', ...
-dnsmasq: CommandFilter, /usr/sbin/dnsmasq, root
-
-# nova/virt/baremetal/pxe.py: 'kill', '-TERM', str(dnsmasq_pid)
-kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -15, -TERM
-
diff --git a/nova/api/openstack/compute/contrib/admin_actions.py b/nova/api/openstack/compute/contrib/admin_actions.py
index fa7836b37..1c053ea59 100644
--- a/nova/api/openstack/compute/contrib/admin_actions.py
+++ b/nova/api/openstack/compute/contrib/admin_actions.py
@@ -130,7 +130,7 @@ class AdminActionsController(wsgi.Controller):
@wsgi.action('resetNetwork')
def _reset_network(self, req, id, body):
- """Permit admins to reset networking on an server."""
+ """Permit admins to reset networking on a server."""
context = req.environ['nova.context']
authorize(context, 'resetNetwork')
try:
diff --git a/nova/api/openstack/compute/contrib/consoles.py b/nova/api/openstack/compute/contrib/consoles.py
index 4f88d033c..4895a9e7b 100644
--- a/nova/api/openstack/compute/contrib/consoles.py
+++ b/nova/api/openstack/compute/contrib/consoles.py
@@ -53,10 +53,33 @@ class ConsolesController(wsgi.Controller):
return {'console': {'type': console_type, 'url': output['url']}}
+ @wsgi.action('os-getSPICEConsole')
+ def get_spice_console(self, req, id, body):
+ """Get text console output."""
+ context = req.environ['nova.context']
+ authorize(context)
+
+ # If type is not supplied or unknown, get_spice_console below will cope
+ console_type = body['os-getSPICEConsole'].get('type')
+
+ try:
+ instance = self.compute_api.get(context, id)
+ output = self.compute_api.get_spice_console(context,
+ instance,
+ console_type)
+ except exception.InstanceNotFound as e:
+ raise webob.exc.HTTPNotFound(explanation=unicode(e))
+ except exception.InstanceNotReady as e:
+ raise webob.exc.HTTPConflict(explanation=unicode(e))
+
+ return {'console': {'type': console_type, 'url': output['url']}}
+
def get_actions(self):
"""Return the actions the extension adds, as required by contract."""
actions = [extensions.ActionExtension("servers", "os-getVNCConsole",
- self.get_vnc_console)]
+ self.get_vnc_console),
+ extensions.ActionExtension("servers", "os-getSPICEConsole",
+ self.get_spice_console)]
return actions
diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py
index f0fdb5a15..308530b8e 100644
--- a/nova/api/openstack/compute/servers.py
+++ b/nova/api/openstack/compute/servers.py
@@ -561,17 +561,28 @@ class Controller(wsgi.Controller):
req.cache_db_instance(instance)
return instance
- def _validate_server_name(self, value):
+ def _check_string_length(self, value, name, max_length=None):
if not isinstance(value, basestring):
- msg = _("Server name is not a string or unicode")
+ msg = _("%s is not a string or unicode") % name
raise exc.HTTPBadRequest(explanation=msg)
if not value.strip():
- msg = _("Server name is an empty string")
+ msg = _("%s is an empty string") % name
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ if max_length and len(value) > max_length:
+ msg = _("%(name)s can be at most %(max_length)s "
+ "characters.") % locals()
raise exc.HTTPBadRequest(explanation=msg)
- if not len(value) < 256:
- msg = _("Server name must be less than 256 characters.")
+ def _validate_server_name(self, value):
+ self._check_string_length(value, 'Server name', max_length=255)
+
+ def _validate_device_name(self, value):
+ self._check_string_length(value, 'Device name', max_length=255)
+
+ if ' ' in value:
+ msg = _("Device name cannot include spaces.")
raise exc.HTTPBadRequest(explanation=msg)
def _get_injected_files(self, personality):
@@ -809,6 +820,7 @@ class Controller(wsgi.Controller):
if self.ext_mgr.is_loaded('os-volumes'):
block_device_mapping = server_dict.get('block_device_mapping', [])
for bdm in block_device_mapping:
+ self._validate_device_name(bdm["device_name"])
if 'delete_on_termination' in bdm:
bdm['delete_on_termination'] = utils.bool_from_str(
bdm['delete_on_termination'])
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 519669134..733685b14 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -919,6 +919,10 @@ class Resource(wsgi.Application):
msg = _("Malformed request body")
return Fault(webob.exc.HTTPBadRequest(explanation=msg))
+ if body:
+ LOG.info(_("Action: '%(action)s', body: %(body)s") % locals())
+ LOG.debug(_("Calling method %s") % meth)
+
# Now, deserialize the request body...
try:
if content_type:
diff --git a/nova/availability_zones.py b/nova/availability_zones.py
index cb5cce591..7493ce5c5 100644
--- a/nova/availability_zones.py
+++ b/nova/availability_zones.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""utilities for multiple APIs."""
+"""Availability zone helper functions."""
from nova import db
from nova.openstack.common import cfg
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 8ba6b97aa..4b15a3e27 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -946,11 +946,10 @@ class API(base.Base):
if (old['vm_state'] != vm_states.SOFT_DELETED and
old['task_state'] not in (task_states.DELETING,
task_states.SOFT_DELETING)):
- reservations = QUOTAS.reserve(context,
- project_id=project_id,
- instances=-1,
- cores=-instance['vcpus'],
- ram=-instance['memory_mb'])
+ reservations = self._create_reservations(context,
+ old,
+ updated,
+ project_id)
if not host:
# Just update database, nothing else we can do
@@ -1026,6 +1025,45 @@ class API(base.Base):
reservations,
project_id=project_id)
+ def _create_reservations(self, context, old_instance, new_instance,
+ project_id):
+ instance_vcpus = old_instance['vcpus']
+ instance_memory_mb = old_instance['memory_mb']
+ # NOTE(wangpan): if the instance is resizing, and the resources
+ # are updated to new instance type, we should use
+ # the old instance type to create reservation.
+ # see https://bugs.launchpad.net/nova/+bug/1099729 for more details
+ if old_instance['task_state'] in (task_states.RESIZE_MIGRATED,
+ task_states.RESIZE_FINISH):
+ get_migration = self.db.migration_get_by_instance_and_status
+ try:
+ migration_ref = get_migration(context.elevated(),
+ old_instance['uuid'], 'post-migrating')
+ except exception.MigrationNotFoundByStatus:
+ migration_ref = None
+ if (migration_ref and
+ new_instance['instance_type_id'] ==
+ migration_ref['new_instance_type_id']):
+ old_inst_type_id = migration_ref['old_instance_type_id']
+ get_inst_type_by_id = instance_types.get_instance_type
+ try:
+ old_inst_type = get_inst_type_by_id(old_inst_type_id)
+ except exception.InstanceTypeNotFound:
+ LOG.warning(_("instance type %(old_inst_type_id)d "
+ "not found") % locals())
+ pass
+ else:
+ instance_vcpus = old_inst_type['vcpus']
+ instance_memory_mb = old_inst_type['memory_mb']
+ LOG.debug(_("going to delete a resizing instance"))
+
+ reservations = QUOTAS.reserve(context,
+ project_id=project_id,
+ instances=-1,
+ cores=-instance_vcpus,
+ ram=-instance_memory_mb)
+ return reservations
+
def _local_delete(self, context, instance, bdms):
LOG.warning(_("instance's host %s is down, deleting from "
"database") % instance['host'], instance=instance)
@@ -1182,8 +1220,10 @@ class API(base.Base):
# NOTE(ameade): we still need to support integer ids for ec2
if uuidutils.is_uuid_like(instance_id):
instance = self.db.instance_get_by_uuid(context, instance_id)
- else:
+ elif utils.is_int_like(instance_id):
instance = self.db.instance_get(context, instance_id)
+ else:
+ raise exception.InstanceNotFound(instance_id=instance_id)
check_policy(context, 'get', instance)
@@ -2031,6 +2071,29 @@ class API(base.Base):
return connect_info
@wrap_check_policy
+ def get_spice_console(self, context, instance, console_type):
+ """Get a url to an instance Console."""
+ if not instance['host']:
+ raise exception.InstanceNotReady(instance_id=instance['uuid'])
+
+ connect_info = self.compute_rpcapi.get_spice_console(context,
+ instance=instance, console_type=console_type)
+
+ self.consoleauth_rpcapi.authorize_console(context,
+ connect_info['token'], console_type, connect_info['host'],
+ connect_info['port'], connect_info['internal_access_path'])
+
+ return {'url': connect_info['access_url']}
+
+ def get_spice_connect_info(self, context, instance, console_type):
+ """Used in a child cell to get console info."""
+ if not instance['host']:
+ raise exception.InstanceNotReady(instance_id=instance['uuid'])
+ connect_info = self.compute_rpcapi.get_spice_console(context,
+ instance=instance, console_type=console_type)
+ return connect_info
+
+ @wrap_check_policy
def get_console_output(self, context, instance, tail_length=None):
"""Get console output for an instance."""
return self.compute_rpcapi.get_console_output(context,
diff --git a/nova/compute/cells_api.py b/nova/compute/cells_api.py
index d1d9a11d2..d5427a04b 100644
--- a/nova/compute/cells_api.py
+++ b/nova/compute/cells_api.py
@@ -439,6 +439,21 @@ class ComputeCellsAPI(compute_api.API):
connect_info['port'], connect_info['internal_access_path'])
return {'url': connect_info['access_url']}
+ @wrap_check_policy
+ @validate_cell
+ def get_spice_console(self, context, instance, console_type):
+ """Get a url to a SPICE Console."""
+ if not instance['host']:
+ raise exception.InstanceNotReady(instance_id=instance['uuid'])
+
+ connect_info = self._call_to_cells(context, instance,
+ 'get_spice_connect_info', console_type)
+
+ self.consoleauth_rpcapi.authorize_console(context,
+ connect_info['token'], console_type, connect_info['host'],
+ connect_info['port'], connect_info['internal_access_path'])
+ return {'url': connect_info['access_url']}
+
@validate_cell
def get_console_output(self, context, instance, *args, **kwargs):
"""Get console output for an an instance."""
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 3bf8e61ef..03c54a363 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -230,7 +230,7 @@ def wrap_instance_fault(function):
with excutils.save_and_reraise_exception():
compute_utils.add_instance_fault_from_exc(context,
- kwargs['instance']['uuid'], e, sys.exc_info())
+ kwargs['instance'], e, sys.exc_info())
return decorated_function
@@ -293,7 +293,7 @@ class ComputeVirtAPI(virtapi.VirtAPI):
class ComputeManager(manager.SchedulerDependentManager):
"""Manages the running instances from creation to destruction."""
- RPC_API_VERSION = '2.23'
+ RPC_API_VERSION = '2.24'
def __init__(self, compute_driver=None, *args, **kwargs):
"""Load configuration options and connect to the hypervisor."""
@@ -463,6 +463,11 @@ class ComputeManager(manager.SchedulerDependentManager):
except NotImplementedError:
LOG.warning(_('Hypervisor driver does not support '
'resume guests'), instance=instance)
+ except Exception:
+ # NOTE(vish): The instance failed to resume, so we set the
+ # instance to error and attempt to continue.
+ LOG.warning(_('Failed to resume instance'), instance=instance)
+ self._set_instance_error_state(context, instance['uuid'])
elif drv_state == power_state.RUNNING:
# VMwareAPI drivers will raise an exception
@@ -730,8 +735,8 @@ class ComputeManager(manager.SchedulerDependentManager):
instance_uuid = instance['uuid']
rescheduled = False
- compute_utils.add_instance_fault_from_exc(context, instance_uuid,
- exc_info[0], exc_info=exc_info)
+ compute_utils.add_instance_fault_from_exc(context, instance,
+ exc_info[1], exc_info=exc_info)
try:
self._deallocate_network(context, instance)
@@ -1464,7 +1469,7 @@ class ComputeManager(manager.SchedulerDependentManager):
LOG.error(_('Cannot reboot instance: %(exc)s'), locals(),
context=context, instance=instance)
compute_utils.add_instance_fault_from_exc(context,
- instance['uuid'], exc, sys.exc_info())
+ instance, exc, sys.exc_info())
# Fall through and reset task_state to None
current_power_state = self._get_power_state(context, instance)
@@ -1995,7 +2000,7 @@ class ComputeManager(manager.SchedulerDependentManager):
rescheduled = False
instance_uuid = instance['uuid']
- compute_utils.add_instance_fault_from_exc(context, instance_uuid,
+ compute_utils.add_instance_fault_from_exc(context, instance,
exc_info[0], exc_info=exc_info)
try:
@@ -2387,6 +2392,9 @@ class ComputeManager(manager.SchedulerDependentManager):
LOG.debug(_("Getting vnc console"), instance=instance)
token = str(uuid.uuid4())
+ if not CONF.vnc_enabled:
+ raise exception.ConsoleTypeInvalid(console_type=console_type)
+
if console_type == 'novnc':
# For essex, novncproxy_base_url must include the full path
# including the html file (like http://myhost/vnc_auto.html)
@@ -2404,6 +2412,33 @@ class ComputeManager(manager.SchedulerDependentManager):
return connect_info
+ @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
+ @wrap_instance_fault
+ def get_spice_console(self, context, console_type, instance):
+ """Return connection information for a spice console."""
+ context = context.elevated()
+ LOG.debug(_("Getting spice console"), instance=instance)
+ token = str(uuid.uuid4())
+
+ if not CONF.spice.enabled:
+ raise exception.ConsoleTypeInvalid(console_type=console_type)
+
+ if console_type == 'spice-html5':
+ # For essex, spicehtml5proxy_base_url must include the full path
+ # including the html file (like http://myhost/spice_auto.html)
+ access_url = '%s?token=%s' % (CONF.spice.html5proxy_base_url,
+ token)
+ else:
+ raise exception.ConsoleTypeInvalid(console_type=console_type)
+
+ # Retrieve connect info from driver, and then decorate with our
+ # access info token
+ connect_info = self.driver.get_spice_console(instance)
+ connect_info['token'] = token
+ connect_info['access_url'] = access_url
+
+ return connect_info
+
def _attach_volume_boot(self, context, instance, volume, mountpoint):
"""Attach a volume to an instance at boot time. So actual attach
is done by instance creation"""
@@ -2429,8 +2464,11 @@ class ComputeManager(manager.SchedulerDependentManager):
@lockutils.synchronized(instance['uuid'], 'nova-')
def do_reserve():
+ bdms = self.conductor_api.block_device_mapping_get_all_by_instance(
+ context, instance)
result = compute_utils.get_device_name_for_instance(context,
instance,
+ bdms,
device)
# NOTE(vish): create bdm here to avoid race condition
values = {'instance_uuid': instance['uuid'],
diff --git a/nova/compute/resource_tracker.py b/nova/compute/resource_tracker.py
index 055963873..be0360185 100644
--- a/nova/compute/resource_tracker.py
+++ b/nova/compute/resource_tracker.py
@@ -25,7 +25,6 @@ from nova.compute import task_states
from nova.compute import vm_states
from nova import conductor
from nova import context
-from nova import db
from nova import exception
from nova.openstack.common import cfg
from nova.openstack.common import importutils
@@ -304,8 +303,8 @@ class ResourceTracker(object):
def _create(self, context, values):
"""Create the compute node in the DB."""
# initialize load stats from existing instances:
- compute_node = db.compute_node_create(context, values)
- self.compute_node = dict(compute_node)
+ self.compute_node = self.conductor_api.compute_node_create(context,
+ values)
def _get_service(self, context):
try:
@@ -349,9 +348,10 @@ class ResourceTracker(object):
def _update(self, context, values, prune_stats=False):
"""Persist the compute node updates to the DB."""
- compute_node = db.compute_node_update(context,
- self.compute_node['id'], values, prune_stats)
- self.compute_node = dict(compute_node)
+ if "service" in self.compute_node:
+ del self.compute_node['service']
+ self.compute_node = self.conductor_api.compute_node_update(
+ context, self.compute_node, values, prune_stats)
def confirm_resize(self, context, migration, status='confirmed'):
"""Cleanup usage for a confirmed resize."""
diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py
index 3e7ed1cfd..525d1adc7 100644
--- a/nova/compute/rpcapi.py
+++ b/nova/compute/rpcapi.py
@@ -158,6 +158,7 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
2.22 - Add recreate, on_shared_storage and host arguments to
rebuild_instance()
2.23 - Remove network_info from reboot_instance
+ 2.24 - Added get_spice_console method
'''
#
@@ -295,6 +296,13 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
instance=instance_p, console_type=console_type),
topic=_compute_topic(self.topic, ctxt, None, instance))
+ def get_spice_console(self, ctxt, instance, console_type):
+ instance_p = jsonutils.to_primitive(instance)
+ return self.call(ctxt, self.make_msg('get_spice_console',
+ instance=instance_p, console_type=console_type),
+ topic=_compute_topic(self.topic, ctxt, None, instance),
+ version='2.24')
+
def host_maintenance_mode(self, ctxt, host_param, mode, host):
'''Set host maintenance mode
diff --git a/nova/compute/utils.py b/nova/compute/utils.py
index 0c475d082..2b1286e16 100644
--- a/nova/compute/utils.py
+++ b/nova/compute/utils.py
@@ -44,7 +44,7 @@ def metadata_to_dict(metadata):
return result
-def add_instance_fault_from_exc(context, instance_uuid, fault, exc_info=None):
+def add_instance_fault_from_exc(context, instance, fault, exc_info=None):
"""Adds the specified fault to the database."""
code = 500
@@ -62,15 +62,16 @@ def add_instance_fault_from_exc(context, instance_uuid, fault, exc_info=None):
details += '\n' + ''.join(traceback.format_tb(tb))
values = {
- 'instance_uuid': instance_uuid,
+ 'instance_uuid': instance['uuid'],
'code': code,
'message': unicode(message),
'details': unicode(details),
+ 'host': CONF.host
}
db.instance_fault_create(context, values)
-def get_device_name_for_instance(context, instance, device):
+def get_device_name_for_instance(context, instance, bdms, device):
"""Validates (or generates) a device name for instance.
If device is not set, it will generate a unique device appropriate
@@ -87,8 +88,6 @@ def get_device_name_for_instance(context, instance, device):
req_prefix, req_letters = block_device.match_device(device)
except (TypeError, AttributeError, ValueError):
raise exception.InvalidDevicePath(path=device)
- bdms = db.block_device_mapping_get_all_by_instance(context,
- instance['uuid'])
mappings = block_device.instance_block_mapping(instance, bdms)
try:
prefix = block_device.match_device(mappings['root'])[0]
diff --git a/nova/conductor/api.py b/nova/conductor/api.py
index c805f403c..138e72f70 100644
--- a/nova/conductor/api.py
+++ b/nova/conductor/api.py
@@ -278,6 +278,16 @@ class LocalAPI(object):
def service_destroy(self, context, service_id):
return self._manager.service_destroy(context, service_id)
+ def compute_node_create(self, context, values):
+ return self._manager.compute_node_create(context, values)
+
+ def compute_node_update(self, context, node, values, prune_stats=False):
+ return self._manager.compute_node_update(context, node, values,
+ prune_stats)
+
+ def service_update(self, context, service, values):
+ return self._manager.service_update(context, service, values)
+
class API(object):
"""Conductor API that does updates via RPC to the ConductorManager."""
@@ -534,3 +544,13 @@ class API(object):
def service_destroy(self, context, service_id):
return self.conductor_rpcapi.service_destroy(context, service_id)
+
+ def compute_node_create(self, context, values):
+ return self.conductor_rpcapi.compute_node_create(context, values)
+
+ def compute_node_update(self, context, node, values, prune_stats=False):
+ return self.conductor_rpcapi.compute_node_update(context, node,
+ values, prune_stats)
+
+ def service_update(self, context, service, values):
+ return self.conductor_rpcapi.service_update(context, service, values)
diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py
index 4a82989ec..0ff2e1400 100644
--- a/nova/conductor/manager.py
+++ b/nova/conductor/manager.py
@@ -43,7 +43,7 @@ datetime_fields = ['launched_at', 'terminated_at']
class ConductorManager(manager.SchedulerDependentManager):
"""Mission: TBD."""
- RPC_API_VERSION = '1.32'
+ RPC_API_VERSION = '1.34'
def __init__(self, *args, **kwargs):
super(ConductorManager, self).__init__(service_name='conductor',
@@ -301,3 +301,17 @@ class ConductorManager(manager.SchedulerDependentManager):
@rpc_common.client_exceptions(exception.ServiceNotFound)
def service_destroy(self, context, service_id):
self.db.service_destroy(context, service_id)
+
+ def compute_node_create(self, context, values):
+ result = self.db.compute_node_create(context, values)
+ return jsonutils.to_primitive(result)
+
+ def compute_node_update(self, context, node, values, prune_stats=False):
+ result = self.db.compute_node_update(context, node['id'], values,
+ prune_stats)
+ return jsonutils.to_primitive(result)
+
+ @rpc_common.client_exceptions(exception.ServiceNotFound)
+ def service_update(self, context, service, values):
+ svc = self.db.service_update(context, service['id'], values)
+ return jsonutils.to_primitive(svc)
diff --git a/nova/conductor/rpcapi.py b/nova/conductor/rpcapi.py
index e5f901289..6dc8aef04 100644
--- a/nova/conductor/rpcapi.py
+++ b/nova/conductor/rpcapi.py
@@ -65,6 +65,8 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy):
1.30 - Added migration_create
1.31 - Added migration_get_in_progress_by_host_and_node
1.32 - Added optional node to instance_get_all_by_host
+ 1.33 - Added compute_node_create and compute_node_update
+ 1.34 - Added service_update
"""
BASE_RPC_API_VERSION = '1.0'
@@ -305,3 +307,18 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy):
def service_destroy(self, context, service_id):
msg = self.make_msg('service_destroy', service_id=service_id)
return self.call(context, msg, version='1.29')
+
+ def compute_node_create(self, context, values):
+ msg = self.make_msg('compute_node_create', values=values)
+ return self.call(context, msg, version='1.33')
+
+ def compute_node_update(self, context, node, values, prune_stats=False):
+ node_p = jsonutils.to_primitive(node)
+ msg = self.make_msg('compute_node_update', node=node_p, values=values,
+ prune_stats=prune_stats)
+ return self.call(context, msg, version='1.33')
+
+ def service_update(self, context, service, values):
+ service_p = jsonutils.to_primitive(service)
+ msg = self.make_msg('service_update', service=service_p, values=values)
+ return self.call(context, msg, version='1.34')
diff --git a/nova/console/websocketproxy.py b/nova/console/websocketproxy.py
new file mode 100644
index 000000000..ce9243d46
--- /dev/null
+++ b/nova/console/websocketproxy.py
@@ -0,0 +1,89 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 OpenStack, LLC.
+# 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.
+
+'''
+Websocket proxy that is compatible with OpenStack Nova.
+Leverages websockify.py by Joel Martin
+'''
+
+import Cookie
+import socket
+
+import websockify
+
+from nova.consoleauth import rpcapi as consoleauth_rpcapi
+from nova import context
+from nova.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+class NovaWebSocketProxy(websockify.WebSocketProxy):
+ def __init__(self, *args, **kwargs):
+ websockify.WebSocketProxy.__init__(self, unix_target=None,
+ target_cfg=None,
+ ssl_target=None, *args, **kwargs)
+
+ def new_client(self):
+ """
+ Called after a new WebSocket connection has been established.
+ """
+ cookie = Cookie.SimpleCookie()
+ cookie.load(self.headers.getheader('cookie'))
+ token = cookie['token'].value
+ ctxt = context.get_admin_context()
+ rpcapi = consoleauth_rpcapi.ConsoleAuthAPI()
+ connect_info = rpcapi.check_token(ctxt, token=token)
+
+ if not connect_info:
+ LOG.audit("Invalid Token: %s", token)
+ raise Exception(_("Invalid Token"))
+
+ host = connect_info['host']
+ port = int(connect_info['port'])
+
+ # Connect to the target
+ self.msg("connecting to: %s:%s" % (host, port))
+ LOG.audit("connecting to: %s:%s" % (host, port))
+ tsock = self.socket(host, port, connect=True)
+
+ # Handshake as necessary
+ if connect_info.get('internal_access_path'):
+ tsock.send("CONNECT %s HTTP/1.1\r\n\r\n" %
+ connect_info['internal_access_path'])
+ while True:
+ data = tsock.recv(4096, socket.MSG_PEEK)
+ if data.find("\r\n\r\n") != -1:
+ if not data.split("\r\n")[0].find("200"):
+ LOG.audit("Invalid Connection Info %s", token)
+ raise Exception(_("Invalid Connection Info"))
+ tsock.recv(len(data))
+ break
+
+ if self.verbose and not self.daemon:
+ print(self.traffic_legend)
+
+ # Start proxying
+ try:
+ self.do_proxy(tsock)
+ except Exception:
+ if tsock:
+ tsock.shutdown(socket.SHUT_RDWR)
+ tsock.close()
+ self.vmsg("%s:%s: Target closed" % (host, port))
+ LOG.audit("%s:%s: Target closed" % (host, port))
+ raise
diff --git a/nova/crypto.py b/nova/crypto.py
index ff76a54d0..68d25e650 100644
--- a/nova/crypto.py
+++ b/nova/crypto.py
@@ -171,13 +171,44 @@ def decrypt_text(project_id, text):
raise exception.ProjectNotFound(project_id=project_id)
try:
dec, _err = utils.execute('openssl',
- 'rsautl',
- '-decrypt',
- '-inkey', '%s' % private_key,
- process_input=text)
+ 'rsautl',
+ '-decrypt',
+ '-inkey', '%s' % private_key,
+ process_input=text)
return dec
- except exception.ProcessExecutionError:
- raise exception.DecryptionFailure()
+ except exception.ProcessExecutionError as exc:
+ raise exception.DecryptionFailure(reason=exc.stderr)
+
+
+def ssh_encrypt_text(ssh_public_key, text):
+ """Encrypt text with an ssh public key.
+
+ Requires recent ssh-keygen binary in addition to openssl binary.
+ """
+ with utils.tempdir() as tmpdir:
+ sshkey = os.path.abspath(os.path.join(tmpdir, 'ssh.key'))
+ with open(sshkey, 'w') as f:
+ f.write(ssh_public_key)
+ sslkey = os.path.abspath(os.path.join(tmpdir, 'ssl.key'))
+ try:
+ # NOTE(vish): -P is to skip prompt on bad keys
+ out, _err = utils.execute('ssh-keygen',
+ '-P', '',
+ '-e',
+ '-f', sshkey,
+ '-m', 'PKCS8')
+ with open(sslkey, 'w') as f:
+ f.write(out)
+ enc, _err = utils.execute('openssl',
+ 'rsautl',
+ '-encrypt',
+ '-pubin',
+ '-inkey', sslkey,
+ '-keyform', 'PEM',
+ process_input=text)
+ return enc
+ except exception.ProcessExecutionError as exc:
+ raise exception.EncryptionFailure(reason=exc.stderr)
def revoke_cert(project_id, file_name):
diff --git a/nova/db/api.py b/nova/db/api.py
index ecfcfab15..d8a16c52d 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -497,6 +497,11 @@ def fixed_ip_get_by_address_detailed(context, address):
return IMPL.fixed_ip_get_by_address_detailed(context, address)
+def fixed_ip_get_by_floating_address(context, floating_address):
+ """Get a fixed ip by a floating address."""
+ return IMPL.fixed_ip_get_by_floating_address(context, floating_address)
+
+
def fixed_ip_get_by_instance(context, instance_uuid):
"""Get fixed ips by instance or raise if none exist."""
return IMPL.fixed_ip_get_by_instance(context, instance_uuid)
@@ -754,12 +759,13 @@ def instance_info_cache_update(context, instance_uuid, values,
:param values: = dict containing column values to update
"""
rv = IMPL.instance_info_cache_update(context, instance_uuid, values)
- try:
- cells_rpcapi.CellsAPI().instance_info_cache_update_at_top(context,
- rv)
- except Exception:
- LOG.exception(_("Failed to notify cells of instance info cache "
- "update"))
+ if update_cells:
+ try:
+ cells_rpcapi.CellsAPI().instance_info_cache_update_at_top(
+ context, rv)
+ except Exception:
+ LOG.exception(_("Failed to notify cells of instance info "
+ "cache update"))
return rv
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 038a47ca1..cc83ec4f5 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -253,6 +253,12 @@ def exact_filter(query, model, filters, legal_keys):
return query
+def convert_datetimes(values, *datetime_keys):
+ for key in values:
+ if key in datetime_keys and isinstance(values[key], basestring):
+ values[key] = timeutils.parse_strtime(values[key])
+ return values
+
###################
@@ -442,6 +448,7 @@ def service_update(context, service_id, values):
service_ref = service_get(context, service_id, session=session)
service_ref.update(values)
service_ref.save(session=session)
+ return service_ref
###################
@@ -497,6 +504,7 @@ def compute_node_create(context, values):
"""Creates a new ComputeNode and populates the capacity fields
with the most recent data."""
_prep_stats_dict(values)
+ convert_datetimes(values, 'created_at', 'deleted_at', 'updated_at')
compute_node_ref = models.ComputeNode()
compute_node_ref.update(values)
@@ -545,9 +553,10 @@ def compute_node_update(context, compute_id, values, prune_stats=False):
stats = values.pop('stats', {})
session = get_session()
- with session.begin(subtransactions=True):
+ with session.begin():
_update_stats(context, stats, compute_id, session, prune_stats)
compute_ref = _compute_node_get(context, compute_id, session=session)
+ convert_datetimes(values, 'created_at', 'deleted_at', 'updated_at')
compute_ref.update(values)
return compute_ref
@@ -1187,6 +1196,20 @@ def fixed_ip_get_by_address_detailed(context, address, session=None):
@require_context
+def fixed_ip_get_by_floating_address(context, floating_address):
+ subq = model_query(context, models.FloatingIp.fixed_ip_id,
+ read_deleted="no").\
+ filter_by(address=floating_address).\
+ limit(1).\
+ subquery()
+ return model_query(context, models.FixedIp, read_deleted="no").\
+ filter_by(id=subq.as_scalar()).\
+ first()
+
+ # NOTE(tr3buchet) please don't invent an exception here, empty list is fine
+
+
+@require_context
def fixed_ip_get_by_instance(context, instance_uuid):
if not uuidutils.is_uuid_like(instance_uuid):
raise exception.InvalidUUID(uuid=instance_uuid)
@@ -4691,49 +4714,44 @@ def _ec2_instance_get_query(context, session=None):
@require_admin_context
-def task_log_get(context, task_name, period_beginning,
- period_ending, host, state=None, session=None):
+def _task_log_get_query(context, task_name, period_beginning,
+ period_ending, host=None, state=None, session=None):
query = model_query(context, models.TaskLog, session=session).\
filter_by(task_name=task_name).\
filter_by(period_beginning=period_beginning).\
- filter_by(period_ending=period_ending).\
- filter_by(host=host)
+ filter_by(period_ending=period_ending)
+ if host is not None:
+ query = query.filter_by(host=host)
if state is not None:
query = query.filter_by(state=state)
+ return query
- return query.first()
+
+@require_admin_context
+def task_log_get(context, task_name, period_beginning, period_ending, host,
+ state=None):
+ return _task_log_get_query(task_name, period_beginning, period_ending,
+ host, state).first()
@require_admin_context
-def task_log_get_all(context, task_name, period_beginning,
- period_ending, host=None, state=None, session=None):
- query = model_query(context, models.TaskLog, session=session).\
- filter_by(task_name=task_name).\
- filter_by(period_beginning=period_beginning).\
- filter_by(period_ending=period_ending)
- if host is not None:
- query = query.filter_by(host=host)
- if state is not None:
- query = query.filter_by(state=state)
- return query.all()
+def task_log_get_all(context, task_name, period_beginning, period_ending,
+ host=None, state=None):
+ return _task_log_get_query(task_name, period_beginning, period_ending,
+ host, state).all()
@require_admin_context
-def task_log_begin_task(context, task_name,
- period_beginning,
- period_ending,
- host,
- task_items=None,
- message=None,
- session=None):
- session = session or get_session()
+def task_log_begin_task(context, task_name, period_beginning, period_ending,
+ host, task_items=None, message=None):
+ # NOTE(boris-42): This method has a race condition and will be rewritten
+ # after bp/db-unique-keys implementation.
+ session = get_session()
with session.begin():
- task = task_log_get(context, task_name,
- period_beginning,
- period_ending,
- host,
- session=session)
- if task:
+ task_ref = _task_log_get_query(context, task_name, period_beginning,
+ period_ending, host, session=session).\
+ first()
+ if task_ref:
#It's already run(ning)!
raise exception.TaskAlreadyRunning(task_name=task_name, host=host)
task = models.TaskLog()
@@ -4747,30 +4765,20 @@ def task_log_begin_task(context, task_name,
if task_items:
task.task_items = task_items
task.save(session=session)
- return task
@require_admin_context
-def task_log_end_task(context, task_name,
- period_beginning,
- period_ending,
- host,
- errors,
- message=None,
- session=None):
- session = session or get_session()
+def task_log_end_task(context, task_name, period_beginning, period_ending,
+ host, errors, message=None):
+ values = dict(state="DONE", errors=errors)
+ if message:
+ values["message"] = message
+
+ session = get_session()
with session.begin():
- task = task_log_get(context, task_name,
- period_beginning,
- period_ending,
- host,
- session=session)
- if not task:
+ rows = _task_log_get_query(context, task_name, period_beginning,
+ period_ending, host, session=session).\
+ update(values)
+ if rows == 0:
#It's not running!
raise exception.TaskNotRunning(task_name=task_name, host=host)
- task.state = "DONE"
- if message:
- task.message = message
- task.errors = errors
- task.save(session=session)
- return task
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/150_add_host_to_instance_faults.py b/nova/db/sqlalchemy/migrate_repo/versions/150_add_host_to_instance_faults.py
new file mode 100644
index 000000000..3fd87e1e1
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/150_add_host_to_instance_faults.py
@@ -0,0 +1,36 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack LLC.
+#
+# 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 sqlalchemy import Column, Index, MetaData, String, Table
+
+
+def upgrade(migrate_engine):
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ instance_faults = Table('instance_faults', meta, autoload=True)
+ host = Column('host', String(length=255))
+ instance_faults.create_column(host)
+ Index('instance_faults_host_idx', instance_faults.c.host).create(
+ migrate_engine)
+
+
+def downgrade(migrate_engine):
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ instance_faults = Table('instance_faults', meta, autoload=True)
+ instance_faults.drop_column('host')
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 56a4d944a..5050cb77e 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -992,6 +992,7 @@ class InstanceFault(BASE, NovaBase):
code = Column(Integer(), nullable=False)
message = Column(String(255))
details = Column(Text)
+ host = Column(String(255))
class InstanceAction(BASE, NovaBase):
diff --git a/nova/exception.py b/nova/exception.py
index dcd75bf4e..c15fc1e43 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -179,8 +179,12 @@ class DBDuplicateEntry(DBError):
super(DBDuplicateEntry, self).__init__(inner_exception)
+class EncryptionFailure(NovaException):
+ message = _("Failed to encrypt text: %(reason)s")
+
+
class DecryptionFailure(NovaException):
- message = _("Failed to decrypt text")
+ message = _("Failed to decrypt text: %(reason)s")
class VirtualInterfaceCreateException(NovaException):
@@ -526,6 +530,10 @@ class PortNotUsable(NovaException):
message = _("Port %(port_id)s not usable for instance %(instance)s.")
+class PortNotFree(NovaException):
+ message = _("No free port available for instance %(instance)s.")
+
+
class FixedIpNotFound(NotFound):
message = _("No fixed IP associated with id %(id)s.")
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index e6abde609..1be06f66a 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -371,19 +371,32 @@ class IptablesManager(object):
s += [('ip6tables', self.ipv6)]
for cmd, tables in s:
+ all_tables, _err = self.execute('%s-save' % (cmd,), '-c',
+ run_as_root=True,
+ attempts=5)
+ all_lines = all_tables.split('\n')
for table in tables:
- current_table, _err = self.execute('%s-save' % (cmd,), '-c',
- '-t', '%s' % (table,),
- run_as_root=True,
- attempts=5)
- current_lines = current_table.split('\n')
- new_filter = self._modify_rules(current_lines,
- tables[table])
- self.execute('%s-restore' % (cmd,), '-c', run_as_root=True,
- process_input='\n'.join(new_filter),
- attempts=5)
+ start, end = self._find_table(all_lines, table)
+ all_lines[start:end] = self._modify_rules(
+ all_lines[start:end], tables[table])
+ self.execute('%s-restore' % (cmd,), '-c', run_as_root=True,
+ process_input='\n'.join(all_lines),
+ attempts=5)
LOG.debug(_("IPTablesManager.apply completed with success"))
+ def _find_table(self, lines, table_name):
+ if len(lines) < 3:
+ # length only <2 when fake iptables
+ return (0, 0)
+ try:
+ start = lines.index('*%s' % table_name) - 1
+ except ValueError:
+ # Couldn't find table_name
+ # For Unit Tests
+ return (0, 0)
+ end = lines[start:].index('COMMIT') + start + 2
+ return (start, end)
+
def _modify_rules(self, current_lines, table, binary=None):
unwrapped_chains = table.unwrapped_chains
chains = table.chains
diff --git a/nova/network/manager.py b/nova/network/manager.py
index 7b69c7a36..e4a97f162 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -147,9 +147,6 @@ network_opts = [
cfg.BoolOpt('auto_assign_floating_ip',
default=False,
help='Autoassigning floating ip to VM'),
- cfg.StrOpt('network_host',
- default=socket.getfqdn(),
- help='Network host to use for ip allocation in flat modes'),
cfg.BoolOpt('fake_network',
default=False,
help='If passed, use fake network devices and addresses'),
@@ -482,7 +479,7 @@ class FloatingIP(object):
@wrap_check_policy
def deallocate_floating_ip(self, context, address,
affect_auto_assigned=False):
- """Returns an floating ip to the pool."""
+ """Returns a floating ip to the pool."""
floating_ip = self.db.floating_ip_get_by_address(context, address)
# handle auto_assigned
@@ -1926,21 +1923,11 @@ class NetworkManager(manager.SchedulerDependentManager):
def get_instance_id_by_floating_address(self, context, address):
"""Returns the instance id a floating ip's fixed ip is allocated to."""
- floating_ip = self.db.floating_ip_get_by_address(context, address)
- if floating_ip['fixed_ip_id'] is None:
+ fixed_ip = self.db.fixed_ip_get_by_floating_address(context, address)
+ if fixed_ip is None:
return None
-
- fixed_ip = self.db.fixed_ip_get(context, floating_ip['fixed_ip_id'])
-
- # NOTE(tr3buchet): this can be None
- # NOTE(mikal): we need to return the instance id here because its used
- # by ec2 (and possibly others)
- uuid = fixed_ip['instance_uuid']
- if not uuid:
- return uuid
-
- instance = self.db.instance_get_by_uuid(context, uuid)
- return instance['id']
+ else:
+ return fixed_ip['instance_uuid']
@wrap_check_policy
def get_network(self, context, network_uuid):
diff --git a/nova/network/quantumv2/api.py b/nova/network/quantumv2/api.py
index 0deb3a4bb..29e5e2f06 100644
--- a/nova/network/quantumv2/api.py
+++ b/nova/network/quantumv2/api.py
@@ -111,9 +111,19 @@ class API(base.Base):
:param macs: None or a set of MAC addresses that the instance
should use. macs is supplied by the hypervisor driver (contrast
with requested_networks which is user supplied).
- NB: QuantumV2 does not yet honour mac address limits.
+ NB: QuantumV2 currently assigns hypervisor supplied MAC addresses
+ to arbitrary networks, which requires openflow switches to
+ function correctly if more than one network is being used with
+ the bare metal hypervisor (which is the only one known to limit
+ MAC addresses).
"""
hypervisor_macs = kwargs.get('macs', None)
+ available_macs = None
+ if hypervisor_macs is not None:
+ # Make a copy we can mutate: records macs that have not been used
+ # to create a port on a network. If we find a mac with a
+ # pre-allocated port we also remove it from this set.
+ available_macs = set(hypervisor_macs)
quantum = quantumv2.get_client(context)
LOG.debug(_('allocate_for_instance() for %s'),
instance['display_name'])
@@ -133,6 +143,12 @@ class API(base.Base):
if port['mac_address'] not in hypervisor_macs:
raise exception.PortNotUsable(port_id=port_id,
instance=instance['display_name'])
+ else:
+ # Don't try to use this MAC if we need to create a
+ # port on the fly later. Identical MACs may be
+ # configured by users into multiple ports so we
+ # discard rather than popping.
+ available_macs.discard(port['mac_address'])
network_id = port['network_id']
ports[network_id] = port
elif fixed_ip:
@@ -141,7 +157,6 @@ class API(base.Base):
nets = self._get_available_networks(context, instance['project_id'],
net_ids)
-
touched_port_ids = []
created_port_ids = []
for network in nets:
@@ -161,6 +176,12 @@ class API(base.Base):
port_req_body['port']['network_id'] = network_id
port_req_body['port']['admin_state_up'] = True
port_req_body['port']['tenant_id'] = instance['project_id']
+ if available_macs is not None:
+ if not available_macs:
+ raise exception.PortNotFree(
+ instance=instance['display_name'])
+ mac_address = available_macs.pop()
+ port_req_body['port']['mac_address'] = mac_address
created_port_ids.append(
quantum.create_port(port_req_body)['port']['id'])
except Exception:
@@ -217,11 +238,62 @@ class API(base.Base):
def add_fixed_ip_to_instance(self, context, instance, network_id):
"""Add a fixed ip to the instance from specified network."""
- raise NotImplementedError()
+ search_opts = {'network_id': network_id}
+ data = quantumv2.get_client(context).list_subnets(**search_opts)
+ ipam_subnets = data.get('subnets', [])
+ if not ipam_subnets:
+ raise exception.NetworkNotFoundForInstance(
+ instance_id=instance['uuid'])
+
+ zone = 'compute:%s' % instance['availability_zone']
+ search_opts = {'device_id': instance['uuid'],
+ 'device_owner': zone,
+ 'network_id': network_id}
+ data = quantumv2.get_client(context).list_ports(**search_opts)
+ ports = data['ports']
+ for p in ports:
+ fixed_ips = p['fixed_ips']
+ for subnet in ipam_subnets:
+ fixed_ip = {'subnet_id': subnet['id']}
+ fixed_ips.append(fixed_ip)
+ port_req_body = {'port': {'fixed_ips': fixed_ips}}
+ try:
+ quantumv2.get_client(context).update_port(p['id'],
+ port_req_body)
+ except Exception as ex:
+ msg = _("Unable to update port %(portid)s with"
+ " failure: %(exception)s")
+ LOG.debug(msg, {'portid': p['id'], 'exception': ex})
+ return
+ raise exception.NetworkNotFoundForInstance(
+ instance_id=instance['uuid'])
def remove_fixed_ip_from_instance(self, context, instance, address):
"""Remove a fixed ip from the instance."""
- raise NotImplementedError()
+ zone = 'compute:%s' % instance['availability_zone']
+ search_opts = {'device_id': instance['uuid'],
+ 'device_owner': zone,
+ 'fixed_ips': 'ip_address=%s' % address}
+ data = quantumv2.get_client(context).list_ports(**search_opts)
+ ports = data['ports']
+ for p in ports:
+ fixed_ips = p['fixed_ips']
+ new_fixed_ips = []
+ for fixed_ip in fixed_ips:
+ if fixed_ip['ip_address'] != address:
+ new_fixed_ips.append(fixed_ip)
+ port_req_body = {'port': {'fixed_ips': new_fixed_ips}}
+ try:
+ quantumv2.get_client(context).update_port(p['id'],
+ port_req_body)
+ except Exception as ex:
+ msg = _("Unable to update port %(portid)s with"
+ " failure: %(exception)s")
+ LOG.debug(msg, {'portid': p['id'], 'exception': ex})
+ return
+
+ raise exception.FixedIpNotFoundForSpecificInstance(
+ instance_uuid=instance['uuid'], ip=address)
def validate_networks(self, context, requested_networks):
"""Validate that the tenant can use the requested networks."""
diff --git a/nova/openstack/common/eventlet_backdoor.py b/nova/openstack/common/eventlet_backdoor.py
index f18e84f6d..118385427 100644
--- a/nova/openstack/common/eventlet_backdoor.py
+++ b/nova/openstack/common/eventlet_backdoor.py
@@ -46,7 +46,7 @@ def _find_objects(t):
def _print_greenthreads():
- for i, gt in enumerate(find_objects(greenlet.greenlet)):
+ for i, gt in enumerate(_find_objects(greenlet.greenlet)):
print i, gt
traceback.print_stack(gt.gr_frame)
print
diff --git a/nova/openstack/common/lockutils.py b/nova/openstack/common/lockutils.py
index ba390dc69..6f80a1f67 100644
--- a/nova/openstack/common/lockutils.py
+++ b/nova/openstack/common/lockutils.py
@@ -28,6 +28,7 @@ from eventlet import semaphore
from nova.openstack.common import cfg
from nova.openstack.common import fileutils
+from nova.openstack.common.gettextutils import _
from nova.openstack.common import log as logging
@@ -219,6 +220,11 @@ def synchronized(name, lock_file_prefix, external=False, lock_path=None):
'method': f.__name__})
retval = f(*args, **kwargs)
finally:
+ LOG.debug(_('Released file lock "%(lock)s" at %(path)s'
+ ' for method "%(method)s"...'),
+ {'lock': name,
+ 'path': lock_file_path,
+ 'method': f.__name__})
# NOTE(vish): This removes the tempdir if we needed
# to create one. This is used to cleanup
# the locks left behind by unit tests.
diff --git a/nova/openstack/common/log.py b/nova/openstack/common/log.py
index 6e25bb597..5c6dbcf14 100644
--- a/nova/openstack/common/log.py
+++ b/nova/openstack/common/log.py
@@ -49,19 +49,19 @@ from nova.openstack.common import notifier
log_opts = [
cfg.StrOpt('logging_context_format_string',
- default='%(asctime)s.%(msecs)d %(levelname)s %(name)s '
+ default='%(asctime)s.%(msecs)03d %(levelname)s %(name)s '
'[%(request_id)s %(user)s %(tenant)s] %(instance)s'
'%(message)s',
help='format string to use for log messages with context'),
cfg.StrOpt('logging_default_format_string',
- default='%(asctime)s.%(msecs)d %(process)d %(levelname)s '
+ default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [-] %(instance)s%(message)s',
help='format string to use for log messages without context'),
cfg.StrOpt('logging_debug_format_suffix',
default='%(funcName)s %(pathname)s:%(lineno)d',
help='data to append to log format when level is DEBUG'),
cfg.StrOpt('logging_exception_prefix',
- default='%(asctime)s.%(msecs)d %(process)d TRACE %(name)s '
+ default='%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s '
'%(instance)s',
help='prefix each line of exception output with this format'),
cfg.ListOpt('default_log_levels',
@@ -259,7 +259,7 @@ class JSONFormatter(logging.Formatter):
class PublishErrorsHandler(logging.Handler):
def emit(self, record):
if ('nova.openstack.common.notifier.log_notifier' in
- CONF.notification_driver):
+ CONF.notification_driver):
return
notifier.api.notify(None, 'error.publisher',
'error_notification',
@@ -361,10 +361,12 @@ def _setup_logging_from_conf(product_name):
datefmt=datefmt))
handler.setFormatter(LegacyFormatter(datefmt=datefmt))
- if CONF.verbose or CONF.debug:
+ if CONF.debug:
log_root.setLevel(logging.DEBUG)
- else:
+ elif CONF.verbose:
log_root.setLevel(logging.INFO)
+ else:
+ log_root.setLevel(logging.WARNING)
level = logging.NOTSET
for pair in CONF.default_log_levels:
@@ -425,7 +427,7 @@ class LegacyFormatter(logging.Formatter):
self._fmt = CONF.logging_default_format_string
if (record.levelno == logging.DEBUG and
- CONF.logging_debug_format_suffix):
+ CONF.logging_debug_format_suffix):
self._fmt += " " + CONF.logging_debug_format_suffix
# Cache this on the record, Logger will respect our formated copy
diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py
index d1ae1cd6e..09de10388 100644
--- a/nova/scheduler/driver.py
+++ b/nova/scheduler/driver.py
@@ -56,8 +56,6 @@ CONF.register_opts(scheduler_driver_opts)
def handle_schedule_error(context, ex, instance_uuid, request_spec):
if not isinstance(ex, exception.NoValidHost):
LOG.exception(_("Exception during scheduler.run_instance"))
- compute_utils.add_instance_fault_from_exc(context,
- instance_uuid, ex, sys.exc_info())
state = vm_states.ERROR.upper()
LOG.warning(_('Setting instance to %(state)s state.'),
locals(), instance_uuid=instance_uuid)
@@ -68,6 +66,8 @@ def handle_schedule_error(context, ex, instance_uuid, request_spec):
'task_state': None})
notifications.send_update(context, old_ref, new_ref,
service="scheduler")
+ compute_utils.add_instance_fault_from_exc(context,
+ new_ref, ex, sys.exc_info())
properties = request_spec.get('instance_properties', {})
payload = dict(request_spec=request_spec,
diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py
index 84bdcddb5..23e64cd7c 100644
--- a/nova/scheduler/manager.py
+++ b/nova/scheduler/manager.py
@@ -180,8 +180,6 @@ class SchedulerManager(manager.Manager):
uuids = [properties.get('uuid')]
for instance_uuid in request_spec.get('instance_uuids') or uuids:
if instance_uuid:
- compute_utils.add_instance_fault_from_exc(context,
- instance_uuid, ex, sys.exc_info())
state = vm_state.upper()
LOG.warning(_('Setting instance to %(state)s state.'),
locals(), instance_uuid=instance_uuid)
@@ -191,6 +189,8 @@ class SchedulerManager(manager.Manager):
context, instance_uuid, updates)
notifications.send_update(context, old_ref, new_ref,
service="scheduler")
+ compute_utils.add_instance_fault_from_exc(context,
+ new_ref, ex, sys.exc_info())
payload = dict(request_spec=request_spec,
instance_properties=properties,
diff --git a/nova/service.py b/nova/service.py
index 0fde14baa..df8cf020f 100644
--- a/nova/service.py
+++ b/nova/service.py
@@ -411,7 +411,7 @@ class Service(object):
self.db_allowed = db_allowed
self.conductor_api = conductor.API(use_local=db_allowed)
self.conductor_api.wait_until_ready(context.get_admin_context())
- self.servicegroup_api = servicegroup.API()
+ self.servicegroup_api = servicegroup.API(db_allowed=db_allowed)
def start(self):
verstr = version.version_string_with_package()
@@ -421,12 +421,11 @@ class Service(object):
self.model_disconnected = False
ctxt = context.get_admin_context()
try:
- service_ref = self.conductor_api.service_get_by_args(ctxt,
- self.host,
- self.binary)
- self.service_id = service_ref['id']
+ self.service_ref = self.conductor_api.service_get_by_args(ctxt,
+ self.host, self.binary)
+ self.service_id = self.service_ref['id']
except exception.NotFound:
- self._create_service_ref(ctxt)
+ self.service_ref = self._create_service_ref(ctxt)
if self.backdoor_port is not None:
self.manager.backdoor_port = self.backdoor_port
@@ -479,6 +478,7 @@ class Service(object):
}
service = self.conductor_api.service_create(context, svc_values)
self.service_id = service['id']
+ return service
def __getattr__(self, key):
manager = self.__dict__.get('manager', None)
diff --git a/nova/servicegroup/api.py b/nova/servicegroup/api.py
index ebd0ee6ac..358b7dcbc 100644
--- a/nova/servicegroup/api.py
+++ b/nova/servicegroup/api.py
@@ -45,6 +45,15 @@ class API(object):
@lockutils.synchronized('nova.servicegroup.api.new', 'nova-')
def __new__(cls, *args, **kwargs):
+ '''Create an instance of the servicegroup API.
+
+ args and kwargs are passed down to the servicegroup driver when it gets
+ created. No args currently exist, though. Valid kwargs are:
+
+ db_allowed - Boolean. False if direct db access is not allowed and
+ alternative data access (conductor) should be used
+ instead.
+ '''
if not cls._driver:
LOG.debug(_('ServiceGroup driver defined as an instance of %s'),
@@ -55,7 +64,8 @@ class API(object):
except KeyError:
raise TypeError(_("unknown ServiceGroup driver name: %s")
% driver_name)
- cls._driver = importutils.import_object(driver_class)
+ cls._driver = importutils.import_object(driver_class,
+ *args, **kwargs)
utils.check_isinstance(cls._driver, ServiceGroupDriver)
# we don't have to check that cls._driver is not NONE,
# check_isinstance does it
diff --git a/nova/servicegroup/drivers/db.py b/nova/servicegroup/drivers/db.py
index 075db3ed8..686ee728b 100644
--- a/nova/servicegroup/drivers/db.py
+++ b/nova/servicegroup/drivers/db.py
@@ -14,8 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from nova import conductor
from nova import context
-from nova import db
from nova import exception
from nova.openstack.common import cfg
from nova.openstack.common import log as logging
@@ -32,6 +32,10 @@ LOG = logging.getLogger(__name__)
class DbDriver(api.ServiceGroupDriver):
+ def __init__(self, *args, **kwargs):
+ self.db_allowed = kwargs.get('db_allowed', True)
+ self.conductor_api = conductor.API(use_local=self.db_allowed)
+
def join(self, member_id, group_id, service=None):
"""Join the given service with it's group."""
@@ -53,6 +57,11 @@ class DbDriver(api.ServiceGroupDriver):
Check whether a service is up based on last heartbeat.
"""
last_heartbeat = service_ref['updated_at'] or service_ref['created_at']
+ if isinstance(last_heartbeat, basestring):
+ # NOTE(russellb) If this service_ref came in over rpc via
+ # conductor, then the timestamp will be a string and needs to be
+ # converted back to a datetime.
+ last_heartbeat = timeutils.parse_strtime(last_heartbeat)
# Timestamps in DB are UTC.
elapsed = utils.total_seconds(timeutils.utcnow() - last_heartbeat)
LOG.debug('DB_Driver.is_up last_heartbeat = %(lhb)s elapsed = %(el)s',
@@ -66,7 +75,8 @@ class DbDriver(api.ServiceGroupDriver):
LOG.debug(_('DB_Driver: get_all members of the %s group') % group_id)
rs = []
ctxt = context.get_admin_context()
- for service in db.service_get_all_by_topic(ctxt, group_id):
+ services = self.conductor_api.service_get_all_by_topic(ctxt, group_id)
+ for service in services:
if self.is_up(service):
rs.append(service['host'])
return rs
@@ -76,18 +86,11 @@ class DbDriver(api.ServiceGroupDriver):
ctxt = context.get_admin_context()
state_catalog = {}
try:
- try:
- service_ref = db.service_get(ctxt, service.service_id)
- except exception.NotFound:
- LOG.debug(_('The service database object disappeared, '
- 'Recreating it.'))
- service._create_service_ref(ctxt)
- service_ref = db.service_get(ctxt, service.service_id)
-
- state_catalog['report_count'] = service_ref['report_count'] + 1
+ report_count = service.service_ref['report_count'] + 1
+ state_catalog['report_count'] = report_count
- db.service_update(ctxt,
- service.service_id, state_catalog)
+ service.service_ref = self.conductor_api.service_update(ctxt,
+ service.service_ref, state_catalog)
# TODO(termie): make this pattern be more elegant.
if getattr(service, 'model_disconnected', False):
diff --git a/nova/tests/api/openstack/compute/contrib/test_consoles.py b/nova/tests/api/openstack/compute/contrib/test_consoles.py
index d251c6b75..cf044dfcd 100644
--- a/nova/tests/api/openstack/compute/contrib/test_consoles.py
+++ b/nova/tests/api/openstack/compute/contrib/test_consoles.py
@@ -26,19 +26,36 @@ def fake_get_vnc_console(self, _context, _instance, _console_type):
return {'url': 'http://fake'}
+def fake_get_spice_console(self, _context, _instance, _console_type):
+ return {'url': 'http://fake'}
+
+
def fake_get_vnc_console_invalid_type(self, _context,
_instance, _console_type):
raise exception.ConsoleTypeInvalid(console_type=_console_type)
+def fake_get_spice_console_invalid_type(self, _context,
+ _instance, _console_type):
+ raise exception.ConsoleTypeInvalid(console_type=_console_type)
+
+
def fake_get_vnc_console_not_ready(self, _context, instance, _console_type):
raise exception.InstanceNotReady(instance_id=instance["uuid"])
+def fake_get_spice_console_not_ready(self, _context, instance, _console_type):
+ raise exception.InstanceNotReady(instance_id=instance["uuid"])
+
+
def fake_get_vnc_console_not_found(self, _context, instance, _console_type):
raise exception.InstanceNotFound(instance_id=instance["uuid"])
+def fake_get_spice_console_not_found(self, _context, instance, _console_type):
+ raise exception.InstanceNotFound(instance_id=instance["uuid"])
+
+
def fake_get(self, context, instance_uuid):
return {'uuid': instance_uuid}
@@ -53,6 +70,8 @@ class ConsolesExtensionTest(test.TestCase):
super(ConsolesExtensionTest, self).setUp()
self.stubs.Set(compute_api.API, 'get_vnc_console',
fake_get_vnc_console)
+ self.stubs.Set(compute_api.API, 'get_spice_console',
+ fake_get_spice_console)
self.stubs.Set(compute_api.API, 'get', fake_get)
self.flags(
osapi_compute_extension=[
@@ -132,3 +151,76 @@ class ConsolesExtensionTest(test.TestCase):
res = req.get_response(self.app)
self.assertEqual(res.status_int, 400)
+
+ def test_get_spice_console(self):
+ body = {'os-getSPICEConsole': {'type': 'spice-html5'}}
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ output = jsonutils.loads(res.body)
+ self.assertEqual(res.status_int, 200)
+ self.assertEqual(output,
+ {u'console': {u'url': u'http://fake', u'type': u'spice-html5'}})
+
+ def test_get_spice_console_not_ready(self):
+ self.stubs.Set(compute_api.API, 'get_spice_console',
+ fake_get_spice_console_not_ready)
+ body = {'os-getSPICEConsole': {'type': 'spice-html5'}}
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ output = jsonutils.loads(res.body)
+ self.assertEqual(res.status_int, 409)
+
+ def test_get_spice_console_no_type(self):
+ self.stubs.Set(compute_api.API, 'get_spice_console',
+ fake_get_spice_console_invalid_type)
+ body = {'os-getSPICEConsole': {}}
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 400)
+
+ def test_get_spice_console_no_instance(self):
+ self.stubs.Set(compute_api.API, 'get', fake_get_not_found)
+ body = {'os-getSPICEConsole': {'type': 'spice-html5'}}
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 404)
+
+ def test_get_spice_console_no_instance_on_console_get(self):
+ self.stubs.Set(compute_api.API, 'get_spice_console',
+ fake_get_spice_console_not_found)
+ body = {'os-getSPICEConsole': {'type': 'spice-html5'}}
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 404)
+
+ def test_get_spice_console_invalid_type(self):
+ body = {'os-getSPICEConsole': {'type': 'invalid'}}
+ self.stubs.Set(compute_api.API, 'get_spice_console',
+ fake_get_spice_console_invalid_type)
+ req = webob.Request.blank('/v2/fake/servers/1/action')
+ req.method = "POST"
+ req.body = jsonutils.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(self.app)
+ self.assertEqual(res.status_int, 400)
diff --git a/nova/tests/api/openstack/compute/test_servers.py b/nova/tests/api/openstack/compute/test_servers.py
index b69268d2a..43746223a 100644
--- a/nova/tests/api/openstack/compute/test_servers.py
+++ b/nova/tests/api/openstack/compute/test_servers.py
@@ -2197,6 +2197,74 @@ class ServersControllerCreateTest(test.TestCase):
self.stubs.Set(compute_api.API, 'create', create)
self._test_create_extra(params)
+ def test_create_instance_with_device_name_not_string(self):
+ self.ext_mgr.extensions = {'os-volumes': 'fake'}
+ bdm = [{'delete_on_termination': 1,
+ 'device_name': 123,
+ 'volume_size': 1,
+ 'volume_id': '11111111-1111-1111-1111-111111111111'}]
+ params = {'block_device_mapping': bdm}
+ old_create = compute_api.API.create
+
+ def create(*args, **kwargs):
+ self.assertEqual(kwargs['block_device_mapping'], bdm)
+ return old_create(*args, **kwargs)
+
+ self.stubs.Set(compute_api.API, 'create', create)
+ self.assertRaises(webob.exc.HTTPBadRequest,
+ self._test_create_extra, params)
+
+ def test_create_instance_with_device_name_empty(self):
+ self.ext_mgr.extensions = {'os-volumes': 'fake'}
+ bdm = [{'delete_on_termination': 1,
+ 'device_name': '',
+ 'volume_size': 1,
+ 'volume_id': '11111111-1111-1111-1111-111111111111'}]
+ params = {'block_device_mapping': bdm}
+ old_create = compute_api.API.create
+
+ def create(*args, **kwargs):
+ self.assertEqual(kwargs['block_device_mapping'], bdm)
+ return old_create(*args, **kwargs)
+
+ self.stubs.Set(compute_api.API, 'create', create)
+ self.assertRaises(webob.exc.HTTPBadRequest,
+ self._test_create_extra, params)
+
+ def test_create_instance_with_device_name_too_long(self):
+ self.ext_mgr.extensions = {'os-volumes': 'fake'}
+ bdm = [{'delete_on_termination': 1,
+ 'device_name': 'a' * 256,
+ 'volume_size': 1,
+ 'volume_id': '11111111-1111-1111-1111-111111111111'}]
+ params = {'block_device_mapping': bdm}
+ old_create = compute_api.API.create
+
+ def create(*args, **kwargs):
+ self.assertEqual(kwargs['block_device_mapping'], bdm)
+ return old_create(*args, **kwargs)
+
+ self.stubs.Set(compute_api.API, 'create', create)
+ self.assertRaises(webob.exc.HTTPBadRequest,
+ self._test_create_extra, params)
+
+ def test_create_instance_with_space_in_device_name(self):
+ self.ext_mgr.extensions = {'os-volumes': 'fake'}
+ bdm = [{'delete_on_termination': 1,
+ 'device_name': 'vd a',
+ 'volume_size': 1,
+ 'volume_id': '11111111-1111-1111-1111-111111111111'}]
+ params = {'block_device_mapping': bdm}
+ old_create = compute_api.API.create
+
+ def create(*args, **kwargs):
+ self.assertEqual(kwargs['block_device_mapping'], bdm)
+ return old_create(*args, **kwargs)
+
+ self.stubs.Set(compute_api.API, 'create', create)
+ self.assertRaises(webob.exc.HTTPBadRequest,
+ self._test_create_extra, params)
+
def test_create_instance_with_bdm_delete_on_termination(self):
self.ext_mgr.extensions = {'os-volumes': 'fake'}
bdm = [{'device_name': 'foo1', 'delete_on_termination': 1},
diff --git a/nova/tests/baremetal/test_pxe.py b/nova/tests/baremetal/test_pxe.py
index 45c9ede43..ad4975849 100644
--- a/nova/tests/baremetal/test_pxe.py
+++ b/nova/tests/baremetal/test_pxe.py
@@ -147,12 +147,6 @@ class PXEClassMethodsTestCase(BareMetalPXETestCase):
config = pxe.build_network_config(net)
self.assertIn('eth0', config)
self.assertNotIn('eth1', config)
- self.assertIn('hwaddress ether fake', config)
- self.assertNotIn('hwaddress ether aa:bb:cc:dd', config)
-
- net[0][1]['mac'] = 'aa:bb:cc:dd'
- config = pxe.build_network_config(net)
- self.assertIn('hwaddress ether aa:bb:cc:dd', config)
net = utils.get_test_network_info(2)
config = pxe.build_network_config(net)
@@ -306,15 +300,6 @@ class PXEPrivateMethodsTestCase(BareMetalPXETestCase):
macs = self.driver._collect_mac_addresses(self.context, self.node)
self.assertEqual(macs, address_list)
- def test_generate_udev_rules(self):
- self._create_node()
- address_list = [nic['address'] for nic in self.nic_info]
- address_list.append(self.node_info['prov_mac_address'])
-
- rules = self.driver._generate_udev_rules(self.context, self.node)
- for address in address_list:
- self.assertIn('ATTR{address}=="%s"' % address, rules)
-
def test_cache_tftp_images(self):
self.instance['kernel_id'] = 'aaaa'
self.instance['ramdisk_id'] = 'bbbb'
@@ -357,8 +342,6 @@ class PXEPrivateMethodsTestCase(BareMetalPXETestCase):
# nova.virt.disk.api._inject_*_into_fs
self._create_node()
files = []
- files.append(('/etc/udev/rules.d/70-persistent-net.rules',
- self.driver._generate_udev_rules(self.context, self.node)))
self.instance['hostname'] = 'fake hostname'
files.append(('/etc/hostname', 'fake hostname'))
self.instance['key_data'] = 'fake ssh key'
diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py
index 0d9f67231..092fd940a 100644
--- a/nova/tests/compute/test_compute.py
+++ b/nova/tests/compute/test_compute.py
@@ -1335,6 +1335,9 @@ class ComputeTestCase(BaseTestCase):
def test_novnc_vnc_console(self):
# Make sure we can a vnc console for an instance.
+ self.flags(vnc_enabled=True)
+ self.flags(enabled=False, group='spice')
+
instance = jsonutils.to_primitive(self._create_fake_instance())
self.compute.run_instance(self.context, instance=instance)
@@ -1347,6 +1350,9 @@ class ComputeTestCase(BaseTestCase):
def test_xvpvnc_vnc_console(self):
# Make sure we can a vnc console for an instance.
+ self.flags(vnc_enabled=True)
+ self.flags(enabled=False, group='spice')
+
instance = jsonutils.to_primitive(self._create_fake_instance())
self.compute.run_instance(self.context, instance=instance)
@@ -1357,6 +1363,9 @@ class ComputeTestCase(BaseTestCase):
def test_invalid_vnc_console_type(self):
# Raise useful error if console type is an unrecognised string.
+ self.flags(vnc_enabled=True)
+ self.flags(enabled=False, group='spice')
+
instance = jsonutils.to_primitive(self._create_fake_instance())
self.compute.run_instance(self.context, instance=instance)
@@ -1367,6 +1376,9 @@ class ComputeTestCase(BaseTestCase):
def test_missing_vnc_console_type(self):
# Raise useful error is console type is None.
+ self.flags(vnc_enabled=True)
+ self.flags(enabled=False, group='spice')
+
instance = jsonutils.to_primitive(self._create_fake_instance())
self.compute.run_instance(self.context, instance=instance)
@@ -1375,6 +1387,47 @@ class ComputeTestCase(BaseTestCase):
self.context, None, instance=instance)
self.compute.terminate_instance(self.context, instance=instance)
+ def test_spicehtml5_spice_console(self):
+ # Make sure we can a spice console for an instance.
+ self.flags(vnc_enabled=False)
+ self.flags(enabled=True, group='spice')
+
+ instance = jsonutils.to_primitive(self._create_fake_instance())
+ self.compute.run_instance(self.context, instance=instance)
+
+ # Try with the full instance
+ console = self.compute.get_spice_console(self.context, 'spice-html5',
+ instance=instance)
+ self.assert_(console)
+
+ self.compute.terminate_instance(self.context, instance=instance)
+
+ def test_invalid_spice_console_type(self):
+ # Raise useful error if console type is an unrecognised string
+ self.flags(vnc_enabled=False)
+ self.flags(enabled=True, group='spice')
+
+ instance = jsonutils.to_primitive(self._create_fake_instance())
+ self.compute.run_instance(self.context, instance=instance)
+
+ self.assertRaises(exception.ConsoleTypeInvalid,
+ self.compute.get_spice_console,
+ self.context, 'invalid', instance=instance)
+ self.compute.terminate_instance(self.context, instance=instance)
+
+ def test_missing_spice_console_type(self):
+ # Raise useful error is console type is None
+ self.flags(vnc_enabled=False)
+ self.flags(enabled=True, group='spice')
+
+ instance = jsonutils.to_primitive(self._create_fake_instance())
+ self.compute.run_instance(self.context, instance=instance)
+
+ self.assertRaises(exception.ConsoleTypeInvalid,
+ self.compute.get_spice_console,
+ self.context, None, instance=instance)
+ self.compute.terminate_instance(self.context, instance=instance)
+
def test_diagnostics(self):
# Make sure we can get diagnostics for an instance.
expected_diagnostic = {'cpu0_time': 17300000000,
@@ -2645,8 +2698,8 @@ class ComputeTestCase(BaseTestCase):
self.assertEqual(task_states.POWERING_OFF, instances[0]['task_state'])
def test_add_instance_fault(self):
+ instance = self._create_fake_instance()
exc_info = None
- instance_uuid = str(uuid.uuid4())
def fake_db_fault_create(ctxt, values):
self.assertTrue(values['details'].startswith('test'))
@@ -2656,7 +2709,8 @@ class ComputeTestCase(BaseTestCase):
expected = {
'code': 500,
'message': 'NotImplementedError',
- 'instance_uuid': instance_uuid,
+ 'instance_uuid': instance['uuid'],
+ 'host': self.compute.host
}
self.assertEquals(expected, values)
@@ -2668,13 +2722,12 @@ class ComputeTestCase(BaseTestCase):
self.stubs.Set(nova.db, 'instance_fault_create', fake_db_fault_create)
ctxt = context.get_admin_context()
- compute_utils.add_instance_fault_from_exc(ctxt, instance_uuid,
- NotImplementedError('test'),
- exc_info)
+ compute_utils.add_instance_fault_from_exc(ctxt, instance,
+ NotImplementedError('test'), exc_info)
def test_add_instance_fault_with_remote_error(self):
+ instance = self._create_fake_instance()
exc_info = None
- instance_uuid = str(uuid.uuid4())
def fake_db_fault_create(ctxt, values):
self.assertTrue(values['details'].startswith('Remote error'))
@@ -2684,8 +2737,9 @@ class ComputeTestCase(BaseTestCase):
expected = {
'code': 500,
- 'instance_uuid': instance_uuid,
- 'message': 'My Test Message'
+ 'instance_uuid': instance['uuid'],
+ 'message': 'My Test Message',
+ 'host': self.compute.host
}
self.assertEquals(expected, values)
@@ -2697,13 +2751,12 @@ class ComputeTestCase(BaseTestCase):
self.stubs.Set(nova.db, 'instance_fault_create', fake_db_fault_create)
ctxt = context.get_admin_context()
- compute_utils.add_instance_fault_from_exc(ctxt, instance_uuid,
- exc,
- exc_info)
+ compute_utils.add_instance_fault_from_exc(ctxt, instance, exc,
+ exc_info)
def test_add_instance_fault_user_error(self):
+ instance = self._create_fake_instance()
exc_info = None
- instance_uuid = str(uuid.uuid4())
def fake_db_fault_create(ctxt, values):
@@ -2711,7 +2764,8 @@ class ComputeTestCase(BaseTestCase):
'code': 400,
'message': 'Invalid',
'details': 'fake details',
- 'instance_uuid': instance_uuid,
+ 'instance_uuid': instance['uuid'],
+ 'host': self.compute.host
}
self.assertEquals(expected, values)
@@ -2725,26 +2779,27 @@ class ComputeTestCase(BaseTestCase):
self.stubs.Set(nova.db, 'instance_fault_create', fake_db_fault_create)
ctxt = context.get_admin_context()
- compute_utils.add_instance_fault_from_exc(ctxt, instance_uuid,
- user_exc, exc_info)
+ compute_utils.add_instance_fault_from_exc(ctxt, instance, user_exc,
+ exc_info)
def test_add_instance_fault_no_exc_info(self):
- instance_uuid = str(uuid.uuid4())
+ instance = self._create_fake_instance()
def fake_db_fault_create(ctxt, values):
expected = {
'code': 500,
'message': 'NotImplementedError',
'details': 'test',
- 'instance_uuid': instance_uuid,
+ 'instance_uuid': instance['uuid'],
+ 'host': self.compute.host
}
self.assertEquals(expected, values)
self.stubs.Set(nova.db, 'instance_fault_create', fake_db_fault_create)
ctxt = context.get_admin_context()
- compute_utils.add_instance_fault_from_exc(ctxt, instance_uuid,
- NotImplementedError('test'))
+ compute_utils.add_instance_fault_from_exc(ctxt, instance,
+ NotImplementedError('test'))
def test_cleanup_running_deleted_instances(self):
admin_context = context.get_admin_context()
@@ -3251,6 +3306,35 @@ class ComputeTestCase(BaseTestCase):
self.mox.VerifyAll()
self.mox.UnsetStubs()
+ def test_init_instance_failed_resume_sets_error(self):
+ instance = {
+ 'uuid': 'fake-uuid',
+ 'info_cache': None,
+ 'power_state': power_state.RUNNING,
+ 'vm_state': vm_states.ACTIVE,
+ }
+ self.flags(resume_guests_state_on_host_boot=True)
+ self.mox.StubOutWithMock(self.compute, '_get_power_state')
+ self.mox.StubOutWithMock(self.compute.driver, 'plug_vifs')
+ self.mox.StubOutWithMock(self.compute.driver,
+ 'resume_state_on_host_boot')
+ self.mox.StubOutWithMock(self.compute,
+ '_get_instance_volume_block_device_info')
+ self.mox.StubOutWithMock(self.compute,
+ '_set_instance_error_state')
+ self.compute._get_power_state(mox.IgnoreArg(),
+ instance).AndReturn(power_state.SHUTDOWN)
+ self.compute.driver.plug_vifs(instance, mox.IgnoreArg())
+ self.compute._get_instance_volume_block_device_info(mox.IgnoreArg(),
+ instance['uuid']).AndReturn('fake-bdm')
+ self.compute.driver.resume_state_on_host_boot(mox.IgnoreArg(),
+ instance, mox.IgnoreArg(),
+ 'fake-bdm').AndRaise(test.TestingException)
+ self.compute._set_instance_error_state(mox.IgnoreArg(),
+ instance['uuid'])
+ self.mox.ReplayAll()
+ self.compute._init_instance('fake-context', instance)
+
def test_get_instances_on_driver(self):
fake_context = context.get_admin_context()
@@ -3903,6 +3987,38 @@ class ComputeAPITestCase(BaseTestCase):
db.instance_destroy(self.context, instance['uuid'])
+ def test_delete_in_resizing(self):
+ def fake_quotas_reserve(context, expire=None, project_id=None,
+ **deltas):
+ old_type = instance_types.get_instance_type_by_name('m1.tiny')
+ # ensure using old instance type to create reservations
+ self.assertEqual(deltas['cores'], -old_type['vcpus'])
+ self.assertEqual(deltas['ram'], -old_type['memory_mb'])
+
+ self.stubs.Set(QUOTAS, 'reserve', fake_quotas_reserve)
+
+ instance, instance_uuid = self._run_instance(params={
+ 'host': CONF.host})
+
+ # create a fake migration record (manager does this)
+ new_inst_type = instance_types.get_instance_type_by_name('m1.small')
+ db.migration_create(self.context.elevated(),
+ {'instance_uuid': instance['uuid'],
+ 'old_instance_type_id': instance['instance_type_id'],
+ 'new_instance_type_id': new_inst_type['id'],
+ 'status': 'post-migrating'})
+
+ # update instance type to resized one
+ db.instance_update(self.context, instance['uuid'],
+ {'instance_type_id': new_inst_type['id'],
+ 'vcpus': new_inst_type['vcpus'],
+ 'memory_mb': new_inst_type['memory_mb'],
+ 'task_state': task_states.RESIZE_FINISH})
+
+ self.compute_api.delete(self.context, instance)
+
+ db.instance_destroy(self.context, instance['uuid'])
+
def test_delete_in_resized(self):
instance, instance_uuid = self._run_instance(params={
'host': CONF.host})
@@ -5512,6 +5628,50 @@ class ComputeAPITestCase(BaseTestCase):
db.instance_destroy(self.context, instance['uuid'])
+ def test_spice_console(self):
+ # Make sure we can a spice console for an instance.
+
+ fake_instance = {'uuid': 'fake_uuid',
+ 'host': 'fake_compute_host'}
+ fake_console_type = "spice-html5"
+ fake_connect_info = {'token': 'fake_token',
+ 'console_type': fake_console_type,
+ 'host': 'fake_console_host',
+ 'port': 'fake_console_port',
+ 'internal_access_path': 'fake_access_path'}
+ fake_connect_info2 = copy.deepcopy(fake_connect_info)
+ fake_connect_info2['access_url'] = 'fake_console_url'
+
+ self.mox.StubOutWithMock(rpc, 'call')
+
+ rpc_msg1 = {'method': 'get_spice_console',
+ 'args': {'instance': fake_instance,
+ 'console_type': fake_console_type},
+ 'version': '2.24'}
+ rpc_msg2 = {'method': 'authorize_console',
+ 'args': fake_connect_info,
+ 'version': '1.0'}
+
+ rpc.call(self.context, 'compute.%s' % fake_instance['host'],
+ rpc_msg1, None).AndReturn(fake_connect_info2)
+ rpc.call(self.context, CONF.consoleauth_topic,
+ rpc_msg2, None).AndReturn(None)
+
+ self.mox.ReplayAll()
+
+ console = self.compute_api.get_spice_console(self.context,
+ fake_instance, fake_console_type)
+ self.assertEqual(console, {'url': 'fake_console_url'})
+
+ def test_get_spice_console_no_host(self):
+ instance = self._create_fake_instance(params={'host': ''})
+
+ self.assertRaises(exception.InstanceNotReady,
+ self.compute_api.get_spice_console,
+ self.context, instance, 'spice')
+
+ db.instance_destroy(self.context, instance['uuid'])
+
def test_get_backdoor_port(self):
# Test api call to get backdoor_port.
fake_backdoor_port = 59697
@@ -6520,7 +6680,7 @@ class ComputeRescheduleOrReraiseTestCase(BaseTestCase):
exc_info = sys.exc_info()
compute_utils.add_instance_fault_from_exc(self.context,
- instance_uuid, exc_info[0], exc_info=exc_info)
+ self.instance, exc_info[0], exc_info=exc_info)
self.compute._deallocate_network(self.context,
self.instance).AndRaise(InnerTestingException("Error"))
self.compute._log_original_error(exc_info, instance_uuid)
@@ -6570,7 +6730,7 @@ class ComputeRescheduleOrReraiseTestCase(BaseTestCase):
except Exception:
exc_info = sys.exc_info()
compute_utils.add_instance_fault_from_exc(self.context,
- instance_uuid, exc_info[0], exc_info=exc_info)
+ self.instance, exc_info[0], exc_info=exc_info)
self.compute._deallocate_network(self.context,
self.instance)
self.compute._reschedule(self.context, None, {}, instance_uuid,
@@ -6598,7 +6758,7 @@ class ComputeRescheduleOrReraiseTestCase(BaseTestCase):
exc_info = sys.exc_info()
compute_utils.add_instance_fault_from_exc(self.context,
- instance_uuid, exc_info[0], exc_info=exc_info)
+ self.instance, exc_info[0], exc_info=exc_info)
self.compute._deallocate_network(self.context,
self.instance)
self.compute._reschedule(self.context, None, {}, instance_uuid,
diff --git a/nova/tests/compute/test_compute_utils.py b/nova/tests/compute/test_compute_utils.py
index f29c68627..6e7227d4c 100644
--- a/nova/tests/compute/test_compute_utils.py
+++ b/nova/tests/compute/test_compute_utils.py
@@ -69,8 +69,11 @@ class ComputeValidateDeviceTestCase(test.TestCase):
lambda context, instance: self.data)
def _validate_device(self, device=None):
+ bdms = db.block_device_mapping_get_all_by_instance(
+ self.context, self.instance['uuid'])
return compute_utils.get_device_name_for_instance(self.context,
self.instance,
+ bdms,
device)
@staticmethod
diff --git a/nova/tests/compute/test_rpcapi.py b/nova/tests/compute/test_rpcapi.py
index 00b90ea65..b81e049bf 100644
--- a/nova/tests/compute/test_rpcapi.py
+++ b/nova/tests/compute/test_rpcapi.py
@@ -165,6 +165,11 @@ class ComputeRpcAPITestCase(test.TestCase):
self._test_compute_api('get_vnc_console', 'call',
instance=self.fake_instance, console_type='type')
+ def test_get_spice_console(self):
+ self._test_compute_api('get_spice_console', 'call',
+ instance=self.fake_instance, console_type='type',
+ version='2.24')
+
def test_host_maintenance_mode(self):
self._test_compute_api('host_maintenance_mode', 'call',
host_param='param', mode='mode', host='host')
diff --git a/nova/tests/conductor/test_conductor.py b/nova/tests/conductor/test_conductor.py
index 9f6940607..d010b454f 100644
--- a/nova/tests/conductor/test_conductor.py
+++ b/nova/tests/conductor/test_conductor.py
@@ -398,6 +398,25 @@ class _BaseTestCase(object):
result = self.conductor.ping(self.context, 'foo')
self.assertEqual(result, {'service': 'conductor', 'arg': 'foo'})
+ def test_compute_node_create(self):
+ self.mox.StubOutWithMock(db, 'compute_node_create')
+ db.compute_node_create(self.context, 'fake-values').AndReturn(
+ 'fake-result')
+ self.mox.ReplayAll()
+ result = self.conductor.compute_node_create(self.context,
+ 'fake-values')
+ self.assertEqual(result, 'fake-result')
+
+ def test_compute_node_update(self):
+ node = {'id': 'fake-id'}
+ self.mox.StubOutWithMock(db, 'compute_node_update')
+ db.compute_node_update(self.context, node['id'], 'fake-values',
+ False).AndReturn('fake-result')
+ self.mox.ReplayAll()
+ result = self.conductor.compute_node_update(self.context, node,
+ 'fake-values', False)
+ self.assertEqual(result, 'fake-result')
+
class ConductorTestCase(_BaseTestCase, test.TestCase):
"""Conductor Manager Tests."""
@@ -728,6 +747,14 @@ class ConductorAPITestCase(_BaseTestCase, test.TestCase):
def test_service_destroy(self):
self._test_stubbed('service_destroy', '', returns=False)
+ def test_service_update(self):
+ ctxt = self.context
+ self.mox.StubOutWithMock(db, 'service_update')
+ db.service_update(ctxt, '', {}).AndReturn('fake-result')
+ self.mox.ReplayAll()
+ result = self.conductor.service_update(self.context, {'id': ''}, {})
+ self.assertEqual(result, 'fake-result')
+
def test_instance_get_all_by_host(self):
self._test_stubbed('instance_get_all_by_host',
self.context.elevated(), 'host')
diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py
index 15890cdcd..acefa856c 100644
--- a/nova/tests/fake_policy.py
+++ b/nova/tests/fake_policy.py
@@ -42,6 +42,7 @@ policy_data = """
"compute:unlock": "",
"compute:get_vnc_console": "",
+ "compute:get_spice_console": "",
"compute:get_console_output": "",
"compute:associate_floating_ip": "",
diff --git a/nova/tests/fakelibvirt.py b/nova/tests/fakelibvirt.py
index 8d9561c7e..a573b7d1c 100644
--- a/nova/tests/fakelibvirt.py
+++ b/nova/tests/fakelibvirt.py
@@ -414,6 +414,7 @@ class Domain(object):
<input type='tablet' bus='usb'/>
<input type='mouse' bus='ps2'/>
<graphics type='vnc' port='-1' autoport='yes'/>
+ <graphics type='spice' port='-1' autoport='yes'/>
<video>
<model type='cirrus' vram='9216' heads='1'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02'
diff --git a/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.json.tpl b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.json.tpl
new file mode 100644
index 000000000..d04f7c7ae
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.json.tpl
@@ -0,0 +1,5 @@
+{
+ "os-getSPICEConsole": {
+ "type": "spice-html5"
+ }
+}
diff --git a/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.xml.tpl b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.xml.tpl
new file mode 100644
index 000000000..c8cd2df9f
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.xml.tpl
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<os-getSPICEConsole>
+ <type>spice-html5</type>
+</os-getSPICEConsole>
diff --git a/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.json.tpl
new file mode 100644
index 000000000..20e260e9e
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.json.tpl
@@ -0,0 +1,6 @@
+{
+ "console": {
+ "type": "spice-html5",
+ "url":"%(url)s"
+ }
+}
diff --git a/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.xml.tpl
new file mode 100644
index 000000000..77e35ae5b
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.xml.tpl
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<console>
+ <type>spice-html5</type>
+ <url>%(url)s</url>
+</console>
diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py
index 7c3157872..aa41a8259 100644
--- a/nova/tests/integrated/test_api_samples.py
+++ b/nova/tests/integrated/test_api_samples.py
@@ -2116,6 +2116,11 @@ class ConsolesSampleJsonTests(ServersSampleBase):
extension_name = ("nova.api.openstack.compute.contrib"
".consoles.Consoles")
+ def setUp(self):
+ super(ConsolesSampleJsonTests, self).setUp()
+ self.flags(vnc_enabled=True)
+ self.flags(enabled=True, group='spice')
+
def test_get_vnc_console(self):
uuid = self._post_server()
response = self._do_post('servers/%s/action' % uuid,
@@ -2128,6 +2133,18 @@ class ConsolesSampleJsonTests(ServersSampleBase):
return self._verify_response('get-vnc-console-post-resp',
subs, response)
+ def test_get_spice_console(self):
+ uuid = self._post_server()
+ response = self._do_post('servers/%s/action' % uuid,
+ 'get-spice-console-post-req',
+ {'action': 'os-getSPICEConsole'})
+ self.assertEqual(response.status, 200)
+ subs = self._get_regexes()
+ subs["url"] = \
+ "((https?):((//)|(\\\\))+([\w\d:#@%/;$()~_?\+-=\\\.&](#!)?)*)"
+ return self._verify_response('get-spice-console-post-resp',
+ subs, response)
+
class ConsolesSampleXmlTests(ConsolesSampleJsonTests):
ctype = 'xml'
diff --git a/nova/tests/network/test_linux_net.py b/nova/tests/network/test_linux_net.py
index c0770902d..8a7865b83 100644
--- a/nova/tests/network/test_linux_net.py
+++ b/nova/tests/network/test_linux_net.py
@@ -469,13 +469,9 @@ class LinuxNetworkTestCase(test.TestCase):
'--arp-ip-src', dhcp, '-j', 'DROP'),
('ebtables', '-I', 'OUTPUT', '-p', 'ARP', '-o', iface,
'--arp-ip-src', dhcp, '-j', 'DROP'),
- ('iptables-save', '-c', '-t', 'filter'),
+ ('iptables-save', '-c'),
('iptables-restore', '-c'),
- ('iptables-save', '-c', '-t', 'mangle'),
- ('iptables-restore', '-c'),
- ('iptables-save', '-c', '-t', 'nat'),
- ('iptables-restore', '-c'),
- ('ip6tables-save', '-c', '-t', 'filter'),
+ ('ip6tables-save', '-c'),
('ip6tables-restore', '-c'),
]
self.assertEqual(executes, expected)
@@ -508,13 +504,9 @@ class LinuxNetworkTestCase(test.TestCase):
'--arp-ip-dst', dhcp, '-j', 'DROP'),
('ebtables', '-D', 'OUTPUT', '-p', 'ARP', '-o', iface,
'--arp-ip-src', dhcp, '-j', 'DROP'),
- ('iptables-save', '-c', '-t', 'filter'),
- ('iptables-restore', '-c'),
- ('iptables-save', '-c', '-t', 'mangle'),
- ('iptables-restore', '-c'),
- ('iptables-save', '-c', '-t', 'nat'),
+ ('iptables-save', '-c'),
('iptables-restore', '-c'),
- ('ip6tables-save', '-c', '-t', 'filter'),
+ ('ip6tables-save', '-c'),
('ip6tables-restore', '-c'),
]
self.assertEqual(executes, expected)
diff --git a/nova/tests/network/test_quantumv2.py b/nova/tests/network/test_quantumv2.py
index 876bce90d..c9b2e43b3 100644
--- a/nova/tests/network/test_quantumv2.py
+++ b/nova/tests/network/test_quantumv2.py
@@ -349,6 +349,9 @@ class TestQuantumv2(test.TestCase):
nets = self.nets[net_idx - 1]
ports = {}
fixed_ips = {}
+ macs = kwargs.get('macs')
+ if macs:
+ macs = set(macs)
req_net_ids = []
if 'requested_networks' in kwargs:
for id, fixed_ip, port_id in kwargs['requested_networks']:
@@ -359,13 +362,15 @@ class TestQuantumv2(test.TestCase):
'mac_address': 'my_mac1'}})
ports['my_netid1'] = self.port_data1[0]
id = 'my_netid1'
+ if macs is not None:
+ macs.discard('my_mac1')
else:
fixed_ips[id] = fixed_ip
req_net_ids.append(id)
expected_network_order = req_net_ids
else:
expected_network_order = [n['id'] for n in nets]
- if kwargs.get('_break_list_networks'):
+ if kwargs.get('_break') == 'pre_list_networks':
self.mox.ReplayAll()
return api
search_ids = [net['id'] for net in nets if net['id'] in req_net_ids]
@@ -382,8 +387,10 @@ class TestQuantumv2(test.TestCase):
mox_list_network_params['id'] = mox.SameElementsAs(search_ids)
self.moxed_client.list_networks(
**mox_list_network_params).AndReturn({'networks': []})
-
for net_id in expected_network_order:
+ if kwargs.get('_break') == 'net_id2':
+ self.mox.ReplayAll()
+ return api
port_req_body = {
'port': {
'device_id': self.instance['uuid'],
@@ -406,10 +413,15 @@ class TestQuantumv2(test.TestCase):
port_req_body['port']['admin_state_up'] = True
port_req_body['port']['tenant_id'] = \
self.instance['project_id']
+ if macs:
+ port_req_body['port']['mac_address'] = macs.pop()
res_port = {'port': {'id': 'fake'}}
self.moxed_client.create_port(
MyComparator(port_req_body)).AndReturn(res_port)
+ if kwargs.get('_break') == 'pre_get_instance_nw_info':
+ self.mox.ReplayAll()
+ return api
api.get_instance_nw_info(mox.IgnoreArg(),
self.instance,
networks=nets).AndReturn(None)
@@ -433,16 +445,63 @@ class TestQuantumv2(test.TestCase):
self._allocate_for_instance(1, macs=None)
def test_allocate_for_instance_accepts_macs_kwargs_set(self):
- # The macs kwarg should be accepted, as a set.
+ # The macs kwarg should be accepted, as a set, the
+ # _allocate_for_instance helper checks that the mac is used to create a
+ # port.
self._allocate_for_instance(1, macs=set(['ab:cd:ef:01:23:45']))
+ def test_allocate_for_instance_not_enough_macs_via_ports(self):
+ # using a hypervisor MAC via a pre-created port will stop it being
+ # used to dynamically create a port on a network. We put the network
+ # first in requested_networks so that if the code were to not pre-check
+ # requested ports, it would incorrectly assign the mac and not fail.
+ requested_networks = [
+ (self.nets2[1]['id'], None, None),
+ (None, None, 'my_portid1')]
+ api = self._stub_allocate_for_instance(
+ net_idx=2, requested_networks=requested_networks,
+ macs=set(['my_mac1']),
+ _break='net_id2')
+ self.assertRaises(exception.PortNotFree,
+ api.allocate_for_instance, self.context,
+ self.instance, requested_networks=requested_networks,
+ macs=set(['my_mac1']))
+
+ def test_allocate_for_instance_not_enough_macs(self):
+ # If not enough MAC addresses are available to allocate to networks, an
+ # error should be raised.
+ # We could pass in macs=set(), but that wouldn't tell us that
+ # allocate_for_instance tracks used macs properly, so we pass in one
+ # mac, and ask for two networks.
+ requested_networks = [
+ (self.nets2[1]['id'], None, None),
+ (self.nets2[0]['id'], None, None)]
+ api = self._stub_allocate_for_instance(
+ net_idx=2, requested_networks=requested_networks,
+ macs=set(['my_mac2']),
+ _break='pre_get_instance_nw_info')
+ self.assertRaises(exception.PortNotFree,
+ api.allocate_for_instance, self.context,
+ self.instance, requested_networks=requested_networks,
+ macs=set(['my_mac2']))
+
+ def test_allocate_for_instance_two_macs_two_networks(self):
+ # If two MACs are available and two networks requested, two new ports
+ # get made and no exceptions raised.
+ requested_networks = [
+ (self.nets2[1]['id'], None, None),
+ (self.nets2[0]['id'], None, None)]
+ self._allocate_for_instance(
+ net_idx=2, requested_networks=requested_networks,
+ macs=set(['my_mac2', 'my_mac1']))
+
def test_allocate_for_instance_mac_conflicting_requested_port(self):
# specify only first and last network
requested_networks = [(None, None, 'my_portid1')]
api = self._stub_allocate_for_instance(
net_idx=1, requested_networks=requested_networks,
macs=set(['unknown:mac']),
- _break_list_networks=True)
+ _break='pre_list_networks')
self.assertRaises(exception.PortNotUsable,
api.allocate_for_instance, self.context,
self.instance, requested_networks=requested_networks,
@@ -943,6 +1002,54 @@ class TestQuantumv2(test.TestCase):
self.mox.ReplayAll()
api.disassociate_floating_ip(self.context, self.instance, address)
+ def test_add_fixed_ip_to_instance(self):
+ api = quantumapi.API()
+ network_id = 'my_netid1'
+ search_opts = {'network_id': network_id}
+ self.moxed_client.list_subnets(
+ **search_opts).AndReturn({'subnets': self.subnet_data1})
+
+ zone = 'compute:%s' % self.instance['availability_zone']
+ search_opts = {'device_id': self.instance['uuid'],
+ 'device_owner': 'compute:nova',
+ 'network_id': network_id}
+ self.moxed_client.list_ports(
+ **search_opts).AndReturn({'ports': self.port_data1})
+ port_req_body = {
+ 'port': {
+ 'fixed_ips': [{'subnet_id': 'my_subid1'}],
+ },
+ }
+ port = self.port_data1[0]
+ port['fixed_ips'] = [{'subnet_id': 'my_subid1'}]
+ self.moxed_client.update_port('my_portid1',
+ MyComparator(port_req_body)).AndReturn({'port': port})
+
+ self.mox.ReplayAll()
+ api.add_fixed_ip_to_instance(self.context, self.instance, network_id)
+
+ def test_remove_fixed_ip_from_instance(self):
+ api = quantumapi.API()
+ address = '10.0.0.3'
+ zone = 'compute:%s' % self.instance['availability_zone']
+ search_opts = {'device_id': self.instance['uuid'],
+ 'device_owner': zone,
+ 'fixed_ips': 'ip_address=%s' % address}
+ self.moxed_client.list_ports(
+ **search_opts).AndReturn({'ports': self.port_data1})
+ port_req_body = {
+ 'port': {
+ 'fixed_ips': [],
+ },
+ }
+ port = self.port_data1[0]
+ port['fixed_ips'] = []
+ self.moxed_client.update_port('my_portid1',
+ MyComparator(port_req_body)).AndReturn({'port': port})
+
+ self.mox.ReplayAll()
+ api.remove_fixed_ip_from_instance(self.context, self.instance, address)
+
class TestQuantumv2ModuleMethods(test.TestCase):
def test_ensure_requested_network_ordering_no_preference(self):
diff --git a/nova/tests/scheduler/test_chance_scheduler.py b/nova/tests/scheduler/test_chance_scheduler.py
index 26cde055b..76fba900d 100644
--- a/nova/tests/scheduler/test_chance_scheduler.py
+++ b/nova/tests/scheduler/test_chance_scheduler.py
@@ -130,11 +130,11 @@ class ChanceSchedulerTestCase(test_scheduler.SchedulerTestCase):
# instance 1
ctxt.elevated().AndReturn(ctxt_elevated)
self.driver.hosts_up(ctxt_elevated, 'compute').AndReturn([])
- compute_utils.add_instance_fault_from_exc(ctxt,
- uuid, mox.IsA(exception.NoValidHost), mox.IgnoreArg())
- db.instance_update_and_get_original(ctxt, uuid,
+ old_ref, new_ref = db.instance_update_and_get_original(ctxt, uuid,
{'vm_state': vm_states.ERROR,
'task_state': None}).AndReturn(({}, {}))
+ compute_utils.add_instance_fault_from_exc(ctxt,
+ new_ref, mox.IsA(exception.NoValidHost), mox.IgnoreArg())
self.mox.ReplayAll()
self.driver.schedule_run_instance(
diff --git a/nova/tests/scheduler/test_filter_scheduler.py b/nova/tests/scheduler/test_filter_scheduler.py
index 5d8e8236b..2bd2cb85b 100644
--- a/nova/tests/scheduler/test_filter_scheduler.py
+++ b/nova/tests/scheduler/test_filter_scheduler.py
@@ -58,11 +58,11 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
self.mox.StubOutWithMock(compute_utils, 'add_instance_fault_from_exc')
self.mox.StubOutWithMock(db, 'instance_update_and_get_original')
+ old_ref, new_ref = db.instance_update_and_get_original(fake_context,
+ uuid, {'vm_state': vm_states.ERROR, 'task_state':
+ None}).AndReturn(({}, {}))
compute_utils.add_instance_fault_from_exc(fake_context,
- uuid, mox.IsA(exception.NoValidHost), mox.IgnoreArg())
- db.instance_update_and_get_original(fake_context, uuid,
- {'vm_state': vm_states.ERROR,
- 'task_state': None}).AndReturn(({}, {}))
+ new_ref, mox.IsA(exception.NoValidHost), mox.IgnoreArg())
self.mox.ReplayAll()
sched.schedule_run_instance(
fake_context, request_spec, None, None, None, None, {})
@@ -88,11 +88,11 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
'instance_uuids': [uuid]}
self.mox.StubOutWithMock(compute_utils, 'add_instance_fault_from_exc')
self.mox.StubOutWithMock(db, 'instance_update_and_get_original')
+ old_ref, new_ref = db.instance_update_and_get_original(fake_context,
+ uuid, {'vm_state': vm_states.ERROR, 'task_state':
+ None}).AndReturn(({}, {}))
compute_utils.add_instance_fault_from_exc(fake_context,
- uuid, mox.IsA(exception.NoValidHost), mox.IgnoreArg())
- db.instance_update_and_get_original(fake_context, uuid,
- {'vm_state': vm_states.ERROR,
- 'task_state': None}).AndReturn(({}, {}))
+ new_ref, mox.IsA(exception.NoValidHost), mox.IgnoreArg())
self.mox.ReplayAll()
sched.schedule_run_instance(
fake_context, request_spec, None, None, None, None, {})
diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py
index dd5b0ae32..eb4c3864f 100644
--- a/nova/tests/scheduler/test_scheduler.py
+++ b/nova/tests/scheduler/test_scheduler.py
@@ -183,12 +183,12 @@ class SchedulerManagerTestCase(test.TestCase):
self.manager.driver.schedule_run_instance(self.context,
request_spec, None, None, None, None, {}).AndRaise(
exception.NoValidHost(reason=""))
- db.instance_update_and_get_original(self.context, fake_instance_uuid,
+ old, new_ref = db.instance_update_and_get_original(self.context,
+ fake_instance_uuid,
{"vm_state": vm_states.ERROR,
"task_state": None}).AndReturn((inst, inst))
- compute_utils.add_instance_fault_from_exc(self.context,
- fake_instance_uuid, mox.IsA(exception.NoValidHost),
- mox.IgnoreArg())
+ compute_utils.add_instance_fault_from_exc(self.context, new_ref,
+ mox.IsA(exception.NoValidHost), mox.IgnoreArg())
self.mox.ReplayAll()
self.manager.run_instance(self.context, request_spec,
@@ -217,12 +217,12 @@ class SchedulerManagerTestCase(test.TestCase):
}
self.manager.driver.schedule_prep_resize(**kwargs).AndRaise(
exception.NoValidHost(reason=""))
- db.instance_update_and_get_original(self.context, fake_instance_uuid,
+ old_ref, new_ref = db.instance_update_and_get_original(self.context,
+ fake_instance_uuid,
{"vm_state": vm_states.ACTIVE, "task_state": None}).AndReturn(
(inst, inst))
- compute_utils.add_instance_fault_from_exc(self.context,
- fake_instance_uuid, mox.IsA(exception.NoValidHost),
- mox.IgnoreArg())
+ compute_utils.add_instance_fault_from_exc(self.context, new_ref,
+ mox.IsA(exception.NoValidHost), mox.IgnoreArg())
self.mox.ReplayAll()
self.manager.prep_resize(**kwargs)
@@ -254,12 +254,12 @@ class SchedulerManagerTestCase(test.TestCase):
"vm_state": "",
"task_state": "",
}
- db.instance_update_and_get_original(self.context, fake_instance_uuid,
+ old_ref, new_ref = db.instance_update_and_get_original(self.context,
+ fake_instance_uuid,
{"vm_state": vm_states.ERROR,
"task_state": None}).AndReturn((inst, inst))
- compute_utils.add_instance_fault_from_exc(self.context,
- fake_instance_uuid, mox.IsA(test.TestingException),
- mox.IgnoreArg())
+ compute_utils.add_instance_fault_from_exc(self.context, new_ref,
+ mox.IsA(test.TestingException), mox.IgnoreArg())
self.mox.ReplayAll()
diff --git a/nova/tests/test_cinder.py b/nova/tests/test_cinder.py
index 29e2e978b..79b5ae66a 100644
--- a/nova/tests/test_cinder.py
+++ b/nova/tests/test_cinder.py
@@ -98,13 +98,14 @@ class FakeHTTPClient(cinder.cinder_client.client.HTTPClient):
class FakeCinderClient(cinder.cinder_client.Client):
def __init__(self, username, password, project_id=None, auth_url=None,
- retries=None):
+ insecure=False, retries=None):
super(FakeCinderClient, self).__init__(username, password,
project_id=project_id,
auth_url=auth_url,
+ insecure=insecure,
retries=retries)
self.client = FakeHTTPClient(username, password, project_id, auth_url,
- retries=retries)
+ insecure=insecure, retries=retries)
# keep a ref to the clients callstack for factory's assert_called
self.callstack = self.client.callstack = []
@@ -177,6 +178,15 @@ class CinderTestCase(test.TestCase):
self.assertTrue('volume_image_metadata' in volume)
self.assertEqual(volume['volume_image_metadata'], _image_metadata)
+ def test_cinder_api_insecure(self):
+ # The True/False negation is awkward, but better for the client
+ # to pass us insecure=True and we check verify_cert == False
+ self.flags(cinder_api_insecure=True)
+ volume = self.api.get(self.context, '1234')
+ self.assert_called('GET', '/volumes/1234')
+ self.assertEquals(
+ self.fake_client_factory.client.client.verify_cert, False)
+
def test_cinder_http_retries(self):
retries = 42
self.flags(cinder_http_retries=retries)
diff --git a/nova/tests/test_crypto.py b/nova/tests/test_crypto.py
index 83010cee2..25df336fb 100644
--- a/nova/tests/test_crypto.py
+++ b/nova/tests/test_crypto.py
@@ -149,3 +149,66 @@ class CertExceptionTests(test.TestCase):
self.assertRaises(exception.CryptoCRLFileNotFound,
crypto.fetch_crl, project_id='fake')
+
+
+class EncryptionTests(test.TestCase):
+ pubkey = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDArtgrfBu/g2o28o+H2ng/crv"
+ "zgES91i/NNPPFTOutXelrJ9QiPTPTm+B8yspLsXifmbsmXztNOlBQgQXs6usxb4"
+ "fnJKNUZ84Vkp5esbqK/L7eyRqwPvqo7btKBMoAMVX/kUyojMpxb7Ssh6M6Y8cpi"
+ "goi+MSDPD7+5yRJ9z4mH9h7MCY6Ejv8KTcNYmVHvRhsFUcVhWcIISlNWUGiG7rf"
+ "oki060F5myQN3AXcL8gHG5/Qb1RVkQFUKZ5geQ39/wSyYA1Q65QTba/5G2QNbl2"
+ "0eAIBTyKZhN6g88ak+yARa6BLLDkrlP7L4WctHQMLsuXHohQsUO9AcOlVMARgrg"
+ "uF test@test")
+ prikey = """-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAwK7YK3wbv4NqNvKPh9p4P3K784BEvdYvzTTzxUzrrV3payfU
+Ij0z05vgfMrKS7F4n5m7Jl87TTpQUIEF7OrrMW+H5ySjVGfOFZKeXrG6ivy+3ska
+sD76qO27SgTKADFV/5FMqIzKcW+0rIejOmPHKYoKIvjEgzw+/uckSfc+Jh/YezAm
+OhI7/Ck3DWJlR70YbBVHFYVnCCEpTVlBohu636JItOtBeZskDdwF3C/IBxuf0G9U
+VZEBVCmeYHkN/f8EsmANUOuUE22v+RtkDW5dtHgCAU8imYTeoPPGpPsgEWugSyw5
+K5T+y+FnLR0DC7Llx6IULFDvQHDpVTAEYK4LhQIDAQABAoIBAF9ibrrgHnBpItx+
+qVUMbriiGK8LUXxUmqdQTljeolDZi6KzPc2RVKWtpazBSvG7skX3+XCediHd+0JP
+DNri1HlNiA6B0aUIGjoNsf6YpwsE4YwyK9cR5k5YGX4j7se3pKX2jOdngxQyw1Mh
+dkmCeWZz4l67nbSFz32qeQlwrsB56THJjgHB7elDoGCXTX/9VJyjFlCbfxVCsIng
+inrNgT0uMSYMNpAjTNOjguJt/DtXpwzei5eVpsERe0TRRVH23ycS0fuq/ancYwI/
+MDr9KSB8r+OVGeVGj3popCxECxYLBxhqS1dAQyJjhQXKwajJdHFzidjXO09hLBBz
+FiutpYUCgYEA6OFikTrPlCMGMJjSj+R9woDAOPfvCDbVZWfNo8iupiECvei88W28
+RYFnvUQRjSC0pHe//mfUSmiEaE+SjkNCdnNR+vsq9q+htfrADm84jl1mfeWatg/g
+zuGz2hAcZnux3kQMI7ufOwZNNpM2bf5B4yKamvG8tZRRxSkkAL1NV48CgYEA08/Z
+Ty9g9XPKoLnUWStDh1zwG+c0q14l2giegxzaUAG5DOgOXbXcw0VQ++uOWD5ARELG
+g9wZcbBsXxJrRpUqx+GAlv2Y1bkgiPQS1JIyhsWEUtwfAC/G+uZhCX53aI3Pbsjh
+QmkPCSp5DuOuW2PybMaw+wVe+CaI/gwAWMYDAasCgYEA4Fzkvc7PVoU33XIeywr0
+LoQkrb4QyPUrOvt7H6SkvuFm5thn0KJMlRpLfAksb69m2l2U1+HooZd4mZawN+eN
+DNmlzgxWJDypq83dYwq8jkxmBj1DhMxfZnIE+L403nelseIVYAfPLOqxUTcbZXVk
+vRQFp+nmSXqQHUe5rAy1ivkCgYEAqLu7cclchCxqDv/6mc5NTVhMLu5QlvO5U6fq
+HqitgW7d69oxF5X499YQXZ+ZFdMBf19ypTiBTIAu1M3nh6LtIa4SsjXzus5vjKpj
+FdQhTBus/hU83Pkymk1MoDOPDEtsI+UDDdSDldmv9pyKGWPVi7H86vusXCLWnwsQ
+e6fCXWECgYEAqgpGvva5kJ1ISgNwnJbwiNw0sOT9BMOsdNZBElf0kJIIy6FMPvap
+6S1ziw+XWfdQ83VIUOCL5DrwmcYzLIogS0agmnx/monfDx0Nl9+OZRxy6+AI9vkK
+86A1+DXdo+IgX3grFK1l1gPhAZPRWJZ+anrEkyR4iLq6ZoPZ3BQn97U=
+-----END RSA PRIVATE KEY-----"""
+ text = "Some text! %$*"
+
+ def _ssh_decrypt_text(self, ssh_private_key, text):
+ with utils.tempdir() as tmpdir:
+ sshkey = os.path.abspath(os.path.join(tmpdir, 'ssh.key'))
+ with open(sshkey, 'w') as f:
+ f.write(ssh_private_key)
+ try:
+ dec, _err = utils.execute('openssl',
+ 'rsautl',
+ '-decrypt',
+ '-inkey', sshkey,
+ process_input=text)
+ return dec
+ except exception.ProcessExecutionError as exc:
+ raise exception.DecryptionFailure(reason=exc.stderr)
+
+ def test_ssh_encrypt_decrypt_text(self):
+ enc = crypto.ssh_encrypt_text(self.pubkey, self.text)
+ self.assertNotEqual(enc, self.text)
+ result = self._ssh_decrypt_text(self.prikey, enc)
+ self.assertEqual(result, self.text)
+
+ def test_ssh_encrypt_failure(self):
+ self.assertRaises(exception.EncryptionFailure,
+ crypto.ssh_encrypt_text, '', self.text)
diff --git a/nova/tests/test_fakelibvirt.py b/nova/tests/test_fakelibvirt.py
index fea666f36..32c85a95a 100644
--- a/nova/tests/test_fakelibvirt.py
+++ b/nova/tests/test_fakelibvirt.py
@@ -53,6 +53,7 @@ def get_vm_xml(name="testname", uuid=None, source_type='file',
</interface>
<input type='mouse' bus='ps2'/>
<graphics type='vnc' port='5901' autoport='yes' keymap='en-us'/>
+ <graphics type='spice' port='5901' autoport='yes' keymap='en-us'/>
</devices>
</domain>''' % {'name': name,
'uuid_tag': uuid_tag,
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index 0abf16801..c4816d202 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -2854,11 +2854,11 @@ class LibvirtConnTestCase(test.TestCase):
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
self.mox.StubOutWithMock(conn, "_wrapped_conn")
- self.mox.StubOutWithMock(conn._wrapped_conn, "getCapabilities")
+ self.mox.StubOutWithMock(conn._wrapped_conn, "getLibVersion")
self.mox.StubOutWithMock(libvirt.libvirtError, "get_error_code")
self.mox.StubOutWithMock(libvirt.libvirtError, "get_error_domain")
- conn._wrapped_conn.getCapabilities().AndRaise(
+ conn._wrapped_conn.getLibVersion().AndRaise(
libvirt.libvirtError("fake failure"))
libvirt.libvirtError.get_error_code().AndReturn(error)
@@ -3693,30 +3693,25 @@ class IptablesFirewallTestCase(test.TestCase):
fake.FakeVirtAPI(),
get_connection=lambda: self.fake_libvirt_connection)
- in_nat_rules = [
+ in_rules = [
'# Generated by iptables-save v1.4.10 on Sat Feb 19 00:03:19 2011',
'*nat',
':PREROUTING ACCEPT [1170:189210]',
':INPUT ACCEPT [844:71028]',
':OUTPUT ACCEPT [5149:405186]',
':POSTROUTING ACCEPT [5063:386098]',
- ]
-
- in_mangle_rules = [
- '# Generated by iptables-save v1.4.12 on Tue Dec 18 15:50:25 201;',
- '*mangle',
- ':PREROUTING ACCEPT [241:39722]',
- ':INPUT ACCEPT [230:39282]',
- ':FORWARD ACCEPT [0:0]',
- ':OUTPUT ACCEPT [266:26558]',
- ':POSTROUTING ACCEPT [267:26590]',
- '-A POSTROUTING -o virbr0 -p udp -m udp --dport 68 -j CHECKSUM '
- '--checksum-fill',
- 'COMMIT',
- '# Completed on Tue Dec 18 15:50:25 2012',
- ]
-
- in_filter_rules = [
+ '# Completed on Tue Dec 18 15:50:25 2012',
+ '# Generated by iptables-save v1.4.12 on Tue Dec 18 15:50:25 201;',
+ '*mangle',
+ ':PREROUTING ACCEPT [241:39722]',
+ ':INPUT ACCEPT [230:39282]',
+ ':FORWARD ACCEPT [0:0]',
+ ':OUTPUT ACCEPT [266:26558]',
+ ':POSTROUTING ACCEPT [267:26590]',
+ '-A POSTROUTING -o virbr0 -p udp -m udp --dport 68 -j CHECKSUM '
+ '--checksum-fill',
+ 'COMMIT',
+ '# Completed on Tue Dec 18 15:50:25 2012',
'# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010',
'*filter',
':INPUT ACCEPT [969615:281627771]',
@@ -3811,15 +3806,11 @@ class IptablesFirewallTestCase(test.TestCase):
# self.fw.add_instance(instance_ref)
def fake_iptables_execute(*cmd, **kwargs):
process_input = kwargs.get('process_input', None)
- if cmd == ('ip6tables-save', '-c', '-t', 'filter'):
+ if cmd == ('ip6tables-save', '-c'):
return '\n'.join(self.in6_filter_rules), None
- if cmd == ('iptables-save', '-c', '-t', 'filter'):
- return '\n'.join(self.in_filter_rules), None
- if cmd == ('iptables-save', '-c', '-t', 'nat'):
- return '\n'.join(self.in_nat_rules), None
- if cmd == ('iptables-save', '-c', '-t', 'mangle'):
- return '\n'.join(self.in_mangle_rules), None
- if cmd == ('iptables-restore', '-c',):
+ if cmd == ('iptables-save', '-c'):
+ return '\n'.join(self.in_rules), None
+ if cmd == ('iptables-restore', '-c'):
lines = process_input.split('\n')
if '*filter' in lines:
self.out_rules = lines
@@ -3843,7 +3834,7 @@ class IptablesFirewallTestCase(test.TestCase):
self.fw.apply_instance_filter(instance_ref, network_info)
in_rules = filter(lambda l: not l.startswith('#'),
- self.in_filter_rules)
+ self.in_rules)
for rule in in_rules:
if not 'nova' in rule:
self.assertTrue(rule in self.out_rules,
diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py
index 2c46b27bd..9eab72c5b 100644
--- a/nova/tests/test_utils.py
+++ b/nova/tests/test_utils.py
@@ -757,3 +757,24 @@ class LastBytesTestCase(test.TestCase):
content = '1234567890'
flo.write(content)
self.assertEqual((content, 0), utils.last_bytes(flo, 1000))
+
+
+class IntLikeTestCase(test.TestCase):
+
+ def test_is_int_like(self):
+ self.assertTrue(utils.is_int_like(1))
+ self.assertTrue(utils.is_int_like("1"))
+ self.assertTrue(utils.is_int_like("514"))
+ self.assertTrue(utils.is_int_like("0"))
+
+ self.assertFalse(utils.is_int_like(1.1))
+ self.assertFalse(utils.is_int_like("1.1"))
+ self.assertFalse(utils.is_int_like("1.1.1"))
+ self.assertFalse(utils.is_int_like(None))
+ self.assertFalse(utils.is_int_like("0."))
+ self.assertFalse(utils.is_int_like("aaaaaa"))
+ self.assertFalse(utils.is_int_like("...."))
+ self.assertFalse(utils.is_int_like("1g"))
+ self.assertFalse(
+ utils.is_int_like("0cc3346e-9fef-4445-abe6-5d2b2690ec64"))
+ self.assertFalse(utils.is_int_like("a1"))
diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py
index 199ae30b1..9747ecccd 100644
--- a/nova/tests/test_virt_drivers.py
+++ b/nova/tests/test_virt_drivers.py
@@ -446,6 +446,15 @@ class _VirtDriverTestCase(_FakeDriverBackendTestCase):
self.assertIn('port', vnc_console)
@catch_notimplementederror
+ def test_get_spice_console(self):
+ instance_ref, network_info = self._get_running_instance()
+ spice_console = self.connection.get_spice_console(instance_ref)
+ self.assertIn('internal_access_path', spice_console)
+ self.assertIn('host', spice_console)
+ self.assertIn('port', spice_console)
+ self.assertIn('tlsPort', spice_console)
+
+ @catch_notimplementederror
def test_get_console_pool_info(self):
instance_ref, network_info = self._get_running_instance()
console_pool = self.connection.get_console_pool_info(instance_ref)
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index 0b1c5d0e7..067e28a13 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -1822,16 +1822,31 @@ class XenAPIBWCountersTestCase(stubs.XenAPITestBase):
# Consider abstracting common code in a base class for firewall driver testing.
class XenAPIDom0IptablesFirewallTestCase(stubs.XenAPITestBase):
- _in_nat_rules = [
+ _in_rules = [
'# Generated by iptables-save v1.4.10 on Sat Feb 19 00:03:19 2011',
'*nat',
':PREROUTING ACCEPT [1170:189210]',
':INPUT ACCEPT [844:71028]',
':OUTPUT ACCEPT [5149:405186]',
':POSTROUTING ACCEPT [5063:386098]',
- ]
-
- _in_filter_rules = [
+ '# Completed on Mon Dec 6 11:54:13 2010',
+ '# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010',
+ '*mangle',
+ ':INPUT ACCEPT [969615:281627771]',
+ ':FORWARD ACCEPT [0:0]',
+ ':OUTPUT ACCEPT [915599:63811649]',
+ ':nova-block-ipv4 - [0:0]',
+ '[0:0] -A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ',
+ '[0:0] -A FORWARD -d 192.168.122.0/24 -o virbr0 -m state --state RELATED'
+ ',ESTABLISHED -j ACCEPT ',
+ '[0:0] -A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT ',
+ '[0:0] -A FORWARD -i virbr0 -o virbr0 -j ACCEPT ',
+ '[0:0] -A FORWARD -o virbr0 -j REJECT '
+ '--reject-with icmp-port-unreachable ',
+ '[0:0] -A FORWARD -i virbr0 -j REJECT '
+ '--reject-with icmp-port-unreachable ',
+ 'COMMIT',
+ '# Completed on Mon Dec 6 11:54:13 2010',
'# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010',
'*filter',
':INPUT ACCEPT [969615:281627771]',
@@ -1916,7 +1931,7 @@ class XenAPIDom0IptablesFirewallTestCase(stubs.XenAPITestBase):
def _validate_security_group(self):
in_rules = filter(lambda l: not l.startswith('#'),
- self._in_filter_rules)
+ self._in_rules)
for rule in in_rules:
if not 'nova' in rule:
self.assertTrue(rule in self._out_rules,
diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py
index 85c85b5e2..fa214b23e 100644
--- a/nova/tests/xenapi/stubs.py
+++ b/nova/tests/xenapi/stubs.py
@@ -208,12 +208,10 @@ class FakeSessionForFirewallTests(FakeSessionForVMTests):
def __init__(self, uri, test_case=None):
super(FakeSessionForFirewallTests, self).__init__(uri)
- if hasattr(test_case, '_in_filter_rules'):
- self._in_filter_rules = test_case._in_filter_rules
+ if hasattr(test_case, '_in_rules'):
+ self._in_rules = test_case._in_rules
if hasattr(test_case, '_in6_filter_rules'):
self._in6_filter_rules = test_case._in6_filter_rules
- if hasattr(test_case, '_in_nat_rules'):
- self._in_nat_rules = test_case._in_nat_rules
self._test_case = test_case
def host_call_plugin(self, _1, _2, plugin, method, args):
@@ -230,12 +228,10 @@ class FakeSessionForFirewallTests(FakeSessionForVMTests):
else:
output = ''
process_input = args.get('process_input', None)
- if cmd == ['ip6tables-save', '-c', '-t', 'filter']:
+ if cmd == ['ip6tables-save', '-c']:
output = '\n'.join(self._in6_filter_rules)
- if cmd == ['iptables-save', '-c', '-t', 'filter']:
- output = '\n'.join(self._in_filter_rules)
- if cmd == ['iptables-save', '-c', '-t', 'nat']:
- output = '\n'.join(self._in_nat_rules)
+ if cmd == ['iptables-save', '-c']:
+ output = '\n'.join(self._in_rules)
if cmd == ['iptables-restore', '-c', ]:
lines = process_input.split('\n')
if '*filter' in lines:
diff --git a/nova/utils.py b/nova/utils.py
index 115791b64..75cba0a7c 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -859,6 +859,14 @@ def bool_from_str(val):
val.lower() == 'y'
+def is_int_like(val):
+ """Check if a value looks like an int."""
+ try:
+ return str(int(val)) == str(val)
+ except Exception:
+ return False
+
+
def is_valid_boolstr(val):
"""Check if the provided string is a valid bool string or not."""
val = str(val).lower()
diff --git a/nova/virt/baremetal/net-dhcp.ubuntu.template b/nova/virt/baremetal/net-dhcp.ubuntu.template
index e8824a88d..34a9e8be7 100644
--- a/nova/virt/baremetal/net-dhcp.ubuntu.template
+++ b/nova/virt/baremetal/net-dhcp.ubuntu.template
@@ -10,9 +10,6 @@ iface lo inet loopback
#for $ifc in $interfaces
auto ${ifc.name}
iface ${ifc.name} inet dhcp
-#if $ifc.hwaddress
- hwaddress ether ${ifc.hwaddress}
-#end if
#if $use_ipv6
iface ${ifc.name} inet6 dhcp
diff --git a/nova/virt/baremetal/net-static.ubuntu.template b/nova/virt/baremetal/net-static.ubuntu.template
index f14f0ce8c..1fe5a1ab8 100644
--- a/nova/virt/baremetal/net-static.ubuntu.template
+++ b/nova/virt/baremetal/net-static.ubuntu.template
@@ -16,9 +16,6 @@ iface ${ifc.name} inet static
#if $ifc.dns
dns-nameservers ${ifc.dns}
#end if
-#if $ifc.hwaddress
- hwaddress ether ${ifc.hwaddress}
-#end if
#if $use_ipv6
iface ${ifc.name} inet6 static
diff --git a/nova/virt/baremetal/pxe.py b/nova/virt/baremetal/pxe.py
index b94ac9032..47bfc55af 100644
--- a/nova/virt/baremetal/pxe.py
+++ b/nova/virt/baremetal/pxe.py
@@ -121,7 +121,6 @@ def build_network_config(network_info):
gateway_v6 = mapping['gateway_v6']
interface = {
'name': 'eth%d' % id,
- 'hwaddress': mapping['mac'],
'address': mapping['ips'][0]['ip'],
'gateway': mapping['gateway'],
'netmask': mapping['ips'][0]['netmask'],
@@ -238,27 +237,12 @@ class PXE(base.NodeDriver):
super(PXE, self).__init__()
def _collect_mac_addresses(self, context, node):
- macs = []
- macs.append(db.bm_node_get(context, node['id'])['prov_mac_address'])
+ macs = set()
+ macs.add(db.bm_node_get(context, node['id'])['prov_mac_address'])
for nic in db.bm_interface_get_all_by_bm_node_id(context, node['id']):
if nic['address']:
- macs.append(nic['address'])
- macs.sort()
- return macs
-
- def _generate_udev_rules(self, context, node):
- # TODO(deva): fix assumption that device names begin with "eth"
- # and fix assumption of ordering
- macs = self._collect_mac_addresses(context, node)
- rules = ''
- for (i, mac) in enumerate(macs):
- rules += 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ' \
- 'ATTR{address}=="%(mac)s", ATTR{dev_id}=="0x0", ' \
- 'ATTR{type}=="1", KERNEL=="eth*", NAME="%(name)s"\n' \
- % {'mac': mac.lower(),
- 'name': 'eth%d' % i,
- }
- return rules
+ macs.add(nic['address'])
+ return sorted(macs)
def _cache_tftp_images(self, context, instance, image_info):
"""Fetch the necessary kernels and ramdisks for the instance."""
@@ -330,9 +314,6 @@ class PXE(base.NodeDriver):
injected_files = []
net_config = build_network_config(network_info)
- udev_rules = self._generate_udev_rules(context, node)
- injected_files.append(
- ('/etc/udev/rules.d/70-persistent-net.rules', udev_rules))
if instance['hostname']:
injected_files.append(('/etc/hostname', instance['hostname']))
@@ -385,7 +366,6 @@ class PXE(base.NodeDriver):
config
./pxelinux.cfg/
{mac} -> ../{uuid}/config
-
"""
image_info = get_tftp_image_info(instance)
(root_mb, swap_mb) = get_partition_sizes(instance)
diff --git a/nova/virt/baremetal/volume_driver.py b/nova/virt/baremetal/volume_driver.py
index 2e6f82b93..0a05dfedd 100644
--- a/nova/virt/baremetal/volume_driver.py
+++ b/nova/virt/baremetal/volume_driver.py
@@ -31,7 +31,7 @@ opts = [
cfg.BoolOpt('use_unsafe_iscsi',
default=False,
help='Do not set this out of dev/test environments. '
- 'If a node does not have an fixed PXE IP address, '
+ 'If a node does not have a fixed PXE IP address, '
'volumes are exported with globally opened ACL'),
cfg.StrOpt('iscsi_iqn_prefix',
default='iqn.2010-10.org.openstack.baremetal',
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index a8f779e66..aa0439e74 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -258,6 +258,10 @@ class ComputeDriver(object):
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
+ def get_spice_console(self, instance):
+ # TODO(Vek): Need to pass context in for access to auth_token
+ raise NotImplementedError()
+
def get_diagnostics(self, instance):
"""Return data about VM diagnostics."""
# TODO(Vek): Need to pass context in for access to auth_token
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 0a29a6d67..338d1dec1 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -271,6 +271,12 @@ class FakeDriver(driver.ComputeDriver):
'host': 'fakevncconsole.com',
'port': 6969}
+ def get_spice_console(self, instance):
+ return {'internal_access_path': 'FAKE',
+ 'host': 'fakespiceconsole.com',
+ 'port': 6969,
+ 'tlsPort': 6970}
+
def get_console_pool_info(self, console_type):
return {'address': '127.0.0.1',
'username': 'fakeuser',
diff --git a/nova/virt/hyperv/volumeops.py b/nova/virt/hyperv/volumeops.py
index 200236233..192d6834c 100644
--- a/nova/virt/hyperv/volumeops.py
+++ b/nova/virt/hyperv/volumeops.py
@@ -37,7 +37,7 @@ hyper_volumeops_opts = [
help='The number of times we retry on attaching volume '),
cfg.IntOpt('hyperv_wait_between_attach_retry',
default=5,
- help='The seconds to wait between an volume attachment attempt'),
+ help='The seconds to wait between a volume attachment attempt'),
cfg.BoolOpt('force_volumeutils_v1',
default=False,
help='Force volumeutils v1'),
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 557818a99..115c6cd02 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -277,6 +277,7 @@ class LibvirtDriver(driver.ComputeDriver):
self._host_state = None
self._initiator = None
self._wrapped_conn = None
+ self._caps = None
self.read_only = read_only
self.firewall_driver = firewall.load_driver(
DEFAULT_FIREWALL_DRIVER,
@@ -362,7 +363,7 @@ class LibvirtDriver(driver.ComputeDriver):
def _test_connection(self):
try:
- self._wrapped_conn.getCapabilities()
+ self._wrapped_conn.getLibVersion()
return True
except libvirt.libvirtError as e:
if (e.get_error_code() == libvirt.VIR_ERR_SYSTEM_ERROR and
@@ -1146,6 +1147,27 @@ class LibvirtDriver(driver.ComputeDriver):
return {'host': host, 'port': port, 'internal_access_path': None}
+ @exception.wrap_exception()
+ def get_spice_console(self, instance):
+ def get_spice_ports_for_instance(instance_name):
+ virt_dom = self._lookup_by_name(instance_name)
+ xml = virt_dom.XMLDesc(0)
+ # TODO(sleepsonthefloor): use etree instead of minidom
+ dom = minidom.parseString(xml)
+
+ for graphic in dom.getElementsByTagName('graphics'):
+ if graphic.getAttribute('type') == 'spice':
+ return (graphic.getAttribute('port'),
+ graphic.getAttribute('tlsPort'))
+
+ return (None, None)
+
+ ports = get_spice_ports_for_instance(instance['name'])
+ host = CONF.spice.server_proxyclient_address
+
+ return {'host': host, 'port': ports[0],
+ 'tlsPort': ports[1], 'internal_access_path': None}
+
@staticmethod
def _supports_direct_io(dirpath):
@@ -1422,11 +1444,11 @@ class LibvirtDriver(driver.ComputeDriver):
def get_host_capabilities(self):
"""Returns an instance of config.LibvirtConfigCaps representing
the capabilities of the host"""
- xmlstr = self._conn.getCapabilities()
-
- caps = vconfig.LibvirtConfigCaps()
- caps.parse_str(xmlstr)
- return caps
+ if not self._caps:
+ xmlstr = self._conn.getCapabilities()
+ self._caps = vconfig.LibvirtConfigCaps()
+ self._caps.parse_str(xmlstr)
+ return self._caps
def get_host_uuid(self):
"""Returns a UUID representing the host."""
@@ -1973,7 +1995,7 @@ class LibvirtDriver(driver.ComputeDriver):
def get_interfaces(self, xml):
"""
- Note that this function takes an domain xml.
+ Note that this function takes a domain xml.
Returns a list of all network interfaces for this instance.
"""
diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py
index 54de9da2d..83d43a6db 100644
--- a/nova/virt/libvirt/vif.py
+++ b/nova/virt/libvirt/vif.py
@@ -32,6 +32,11 @@ from nova.virt import netutils
LOG = logging.getLogger(__name__)
libvirt_vif_opts = [
+ # quantum_ovs_bridge is used, if Quantum provides Nova
+ # the 'vif_type' portbinding field
+ cfg.StrOpt('libvirt_ovs_bridge',
+ default='br-int',
+ help='Name of Integration Bridge used by Open vSwitch'),
cfg.BoolOpt('libvirt_use_virtio_for_bridges',
default=True,
help='Use virtio for bridge interfaces with KVM/QEMU'),
@@ -71,6 +76,9 @@ class LibvirtBaseVIFDriver(object):
class LibvirtBridgeDriver(LibvirtBaseVIFDriver):
"""VIF driver for Linux bridge."""
+ def get_bridge_name(self, network):
+ return network['bridge']
+
def get_config(self, instance, network, mapping):
"""Get VIF configurations for bridge type."""
@@ -82,7 +90,8 @@ class LibvirtBridgeDriver(LibvirtBaseVIFDriver):
mapping)
designer.set_vif_host_backend_bridge_config(
- conf, network['bridge'], self.get_vif_devname(mapping))
+ conf, self.get_bridge_name(network),
+ self.get_vif_devname(mapping))
name = "nova-instance-" + instance['name'] + "-" + mac_id
primary_addr = mapping['ips'][0]['ip']
@@ -112,18 +121,18 @@ class LibvirtBridgeDriver(LibvirtBaseVIFDriver):
iface = CONF.vlan_interface or network['bridge_interface']
LOG.debug(_('Ensuring vlan %(vlan)s and bridge %(bridge)s'),
{'vlan': network['vlan'],
- 'bridge': network['bridge']},
+ 'bridge': self.get_bridge_name(network)},
instance=instance)
linux_net.LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
network['vlan'],
- network['bridge'],
+ self.get_bridge_name(network),
iface)
else:
iface = CONF.flat_interface or network['bridge_interface']
- LOG.debug(_("Ensuring bridge %s"), network['bridge'],
- instance=instance)
+ LOG.debug(_("Ensuring bridge %s"),
+ self.get_bridge_name(network), instance=instance)
linux_net.LinuxBridgeInterfaceDriver.ensure_bridge(
- network['bridge'],
+ self.get_bridge_name(network),
iface)
def unplug(self, instance, vif):
@@ -138,6 +147,9 @@ class LibvirtOpenVswitchDriver(LibvirtBaseVIFDriver):
OVS virtual port XML (0.9.10 or earlier).
"""
+ def get_bridge_name(self, network):
+ return network.get('bridge') or CONF.libvirt_ovs_bridge
+
def get_config(self, instance, network, mapping):
dev = self.get_vif_devname(mapping)
@@ -183,7 +195,7 @@ class LibvirtOpenVswitchDriver(LibvirtBaseVIFDriver):
utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
- self.create_ovs_vif_port(network['bridge'],
+ self.create_ovs_vif_port(self.get_bridge_name(network),
dev, iface_id, mapping['mac'],
instance['uuid'])
@@ -191,7 +203,7 @@ class LibvirtOpenVswitchDriver(LibvirtBaseVIFDriver):
"""Unplug the VIF by deleting the port from the bridge."""
try:
network, mapping = vif
- self.delete_ovs_vif_port(network['bridge'],
+ self.delete_ovs_vif_port(self.get_bridge_name(network),
self.get_vif_devname(mapping))
except exception.ProcessExecutionError:
LOG.exception(_("Failed while unplugging vif"), instance=instance)
@@ -214,6 +226,9 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
return (("qvb%s" % iface_id)[:network_model.NIC_NAME_LEN],
("qvo%s" % iface_id)[:network_model.NIC_NAME_LEN])
+ def get_bridge_name(self, network):
+ return network.get('bridge') or CONF.libvirt_ovs_bridge
+
def get_config(self, instance, network, mapping):
br_name = self.get_br_name(mapping['vif_uuid'])
network['bridge'] = br_name
@@ -243,7 +258,7 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
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)
- self.create_ovs_vif_port(network['bridge'],
+ self.create_ovs_vif_port(self.get_bridge_name(network),
v2_name, iface_id, mapping['mac'],
instance['uuid'])
@@ -264,7 +279,7 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
run_as_root=True)
utils.execute('brctl', 'delbr', br_name, run_as_root=True)
- self.delete_ovs_vif_port(network['bridge'], v2_name)
+ self.delete_ovs_vif_port(self.get_bridge_name(network), v2_name)
except exception.ProcessExecutionError:
LOG.exception(_("Failed while unplugging vif"), instance=instance)
@@ -273,6 +288,9 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver):
"""VIF driver for Open vSwitch that uses integrated libvirt
OVS virtual port XML (introduced in libvirt 0.9.11)."""
+ def get_bridge_name(self, network):
+ return network.get('bridge') or CONF.libvirt_ovs_bridge
+
def get_config(self, instance, network, mapping):
"""Pass data required to create OVS virtual port element."""
conf = super(LibvirtOpenVswitchVirtualPortDriver,
@@ -281,7 +299,7 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver):
mapping)
designer.set_vif_host_backend_ovs_config(
- conf, network['bridge'], mapping['vif_uuid'],
+ conf, self.get_bridge_name(network), mapping['vif_uuid'],
self.get_vif_devname(mapping))
return conf
@@ -297,10 +315,15 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver):
class QuantumLinuxBridgeVIFDriver(LibvirtBaseVIFDriver):
"""VIF driver for Linux Bridge when running Quantum."""
+ def get_bridge_name(self, network):
+ def_bridge = ("brq" + network['id'])[:network_model.NIC_NAME_LEN]
+ return network.get('bridge') or def_bridge
+
def get_config(self, instance, network, mapping):
- linux_net.LinuxBridgeInterfaceDriver.ensure_bridge(network['bridge'],
- None,
- filtering=False)
+ linux_net.LinuxBridgeInterfaceDriver.ensure_bridge(
+ self.get_bridge_name(network),
+ None,
+ filtering=False)
conf = super(QuantumLinuxBridgeVIFDriver,
self).get_config(instance,
@@ -308,7 +331,8 @@ class QuantumLinuxBridgeVIFDriver(LibvirtBaseVIFDriver):
mapping)
designer.set_vif_host_backend_bridge_config(
- conf, network['bridge'], self.get_vif_devname(mapping))
+ conf, self.get_bridge_name(network),
+ self.get_vif_devname(mapping))
return conf
diff --git a/nova/virt/xenapi/agent.py b/nova/virt/xenapi/agent.py
index 61cfa9631..ef08edbc1 100644
--- a/nova/virt/xenapi/agent.py
+++ b/nova/virt/xenapi/agent.py
@@ -21,6 +21,9 @@ import os
import time
import uuid
+from nova.api.metadata import password
+from nova import context
+from nova import crypto
from nova.openstack.common import cfg
from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
@@ -207,6 +210,12 @@ class XenAPIBasedAgent(object):
LOG.error(msg, instance=self.instance)
raise Exception(msg)
+ sshkey = self.instance.get('key_data')
+ if sshkey:
+ enc = crypto.ssh_encrypt_text(sshkey, new_pass)
+ password.set_password(context.get_admin_context(),
+ self.instance['uuid'], base64.b64encode(enc))
+
return resp['message']
def inject_file(self, path, contents):
diff --git a/nova/virt/xenapi/pool_states.py b/nova/virt/xenapi/pool_states.py
index 5bf326117..138f84831 100644
--- a/nova/virt/xenapi/pool_states.py
+++ b/nova/virt/xenapi/pool_states.py
@@ -19,10 +19,10 @@
A pool may be 'created', in which case the admin has triggered its
creation, but the underlying hypervisor pool has not actually being set up
-yet. An pool may be 'changing', meaning that the underlying hypervisor
-pool is being setup. An pool may be 'active', in which case the underlying
-hypervisor pool is up and running. An pool may be 'dismissed' when it has
-no hosts and it has been deleted. An pool may be in 'error' in all other
+yet. A pool may be 'changing', meaning that the underlying hypervisor
+pool is being setup. A pool may be 'active', in which case the underlying
+hypervisor pool is up and running. A pool may be 'dismissed' when it has
+no hosts and it has been deleted. A pool may be in 'error' in all other
cases.
A 'created' pool becomes 'changing' during the first request of
adding a host. During a 'changing' status no other requests will be accepted;
@@ -34,7 +34,7 @@ All other operations (e.g. add/remove hosts) that succeed will keep the
pool in the 'active' state. If a number of continuous requests fail,
an 'active' pool goes into an 'error' state. To recover from such a state,
admin intervention is required. Currently an error state is irreversible,
-that is, in order to recover from it an pool must be deleted.
+that is, in order to recover from it a pool must be deleted.
"""
CREATED = 'created'
diff --git a/nova/volume/cinder.py b/nova/volume/cinder.py
index fccdedac8..3e1ccc66b 100644
--- a/nova/volume/cinder.py
+++ b/nova/volume/cinder.py
@@ -48,6 +48,9 @@ cinder_opts = [
cfg.IntOpt('cinder_http_retries',
default=3,
help='Number of cinderclient retries on failed http calls'),
+ cfg.BoolOpt('cinder_api_insecure',
+ default=False,
+ help='Allow to perform insecure SSL requests to cinder'),
]
CONF = cfg.CONF
@@ -88,6 +91,7 @@ def cinderclient(context):
context.auth_token,
project_id=context.project_id,
auth_url=url,
+ insecure=CONF.cinder_api_insecure,
retries=CONF.cinder_http_retries)
# noauth extracts user_id:project_id from auth_token
c.client.auth_token = context.auth_token or '%s:%s' % (context.user_id,
diff --git a/run_tests.sh b/run_tests.sh
index 3a579ca36..39176d78b 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -121,7 +121,7 @@ function run_pep8 {
srcfiles+=" setup.py"
# Until all these issues get fixed, ignore.
- ignore='--ignore=E12,E711,E721,E712'
+ ignore='--ignore=E12,E711,E721,E712,N403,N404'
# First run the hacking selftest, to make sure it's right
echo "Running hacking.py self test"
diff --git a/setup.py b/setup.py
index 12de5c4d6..78c9062c2 100644
--- a/setup.py
+++ b/setup.py
@@ -66,6 +66,7 @@ setuptools.setup(name='nova',
'bin/nova-objectstore',
'bin/nova-rootwrap',
'bin/nova-scheduler',
+ 'bin/nova-spicehtml5proxy',
'bin/nova-xvpvncproxy',
],
py_modules=[])
diff --git a/tools/hacking.py b/tools/hacking.py
index ed22956eb..56f6694bd 100755
--- a/tools/hacking.py
+++ b/tools/hacking.py
@@ -21,7 +21,6 @@
built on top of pep8.py
"""
-import fnmatch
import inspect
import logging
import os
@@ -46,16 +45,15 @@ logging.disable('LOG')
#N8xx git commit messages
IMPORT_EXCEPTIONS = ['sqlalchemy', 'migrate', 'nova.db.sqlalchemy.session']
-DOCSTRING_TRIPLE = ['"""', "'''"]
+START_DOCSTRING_TRIPLE = ['u"""', 'r"""', '"""', "u'''", "r'''", "'''"]
+END_DOCSTRING_TRIPLE = ['"""', "'''"]
VERBOSE_MISSING_IMPORT = os.getenv('HACKING_VERBOSE_MISSING_IMPORT', 'False')
# Monkey patch broken excluded filter in pep8
# See https://github.com/jcrocholl/pep8/pull/111
def excluded(self, filename):
- """
- Check if options.exclude contains a pattern that matches filename.
- """
+ """Check if options.exclude contains a pattern that matches filename."""
basename = os.path.basename(filename)
return any((pep8.filename_match(filename, self.options.exclude,
default=False),
@@ -120,7 +118,7 @@ def nova_todo_format(physical_line):
pos2 = physical_line.find('#') # make sure it's a comment
# TODO(sdague): should be smarter on this test
this_test = physical_line.find('N101: #TODO fail')
- if (pos != pos1 and pos2 >= 0 and pos2 < pos and this_test == -1):
+ if pos != pos1 and pos2 >= 0 and pos2 < pos and this_test == -1:
return pos, "N101: Use TODO(NAME)"
@@ -187,7 +185,8 @@ def nova_import_module_only(logical_line):
# TODO(sdague) actually get these tests working
def importModuleCheck(mod, parent=None, added=False):
- """
+ """Import Module helper function.
+
If can't find module on first try, recursively check for relative
imports
"""
@@ -258,8 +257,7 @@ def nova_import_module_only(logical_line):
def nova_import_alphabetical(logical_line, blank_lines, previous_logical,
indent_level, previous_indent_level):
- r"""
- Check for imports in alphabetical order.
+ r"""Check for imports in alphabetical order.
nova HACKING guide recommendation for imports:
imports in human alphabetical order
@@ -294,6 +292,11 @@ def nova_import_no_db_in_virt(logical_line, filename):
yield (0, "N307: nova.db import not allowed in nova/virt/*")
+def in_docstring_position(previous_logical):
+ return (previous_logical.startswith("def ") or
+ previous_logical.startswith("class "))
+
+
def nova_docstring_start_space(physical_line, previous_logical):
r"""Check for docstring not start with space.
@@ -311,11 +314,10 @@ def nova_docstring_start_space(physical_line, previous_logical):
# it's important that we determine this is actually a docstring,
# and not a doc block used somewhere after the first line of a
# function def
- if (previous_logical.startswith("def ") or
- previous_logical.startswith("class ")):
- pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE])
- if (pos != -1 and len(physical_line) > pos + 4):
- if (physical_line[pos + 3] == ' '):
+ if in_docstring_position(previous_logical):
+ pos = max([physical_line.find(i) for i in START_DOCSTRING_TRIPLE])
+ if pos != -1 and len(physical_line) > pos + 4:
+ if physical_line[pos + 3] == ' ':
return (pos, "N401: docstring should not start with"
" a space")
@@ -330,33 +332,63 @@ def nova_docstring_one_line(physical_line):
N402: '''This is not'''
N402: '''Bad punctuation,'''
"""
+ #TODO(jogo) make this apply to multi line docstrings as well
line = physical_line.lstrip()
if line.startswith('"') or line.startswith("'"):
- pos = max([line.find(i) for i in DOCSTRING_TRIPLE]) # start
- end = max([line[-4:-1] == i for i in DOCSTRING_TRIPLE]) # end
+ pos = max([line.find(i) for i in START_DOCSTRING_TRIPLE]) # start
+ end = max([line[-4:-1] == i for i in END_DOCSTRING_TRIPLE]) # end
- if (pos != -1 and end and len(line) > pos + 4):
- if (line[-5] not in ['.', '?', '!']):
+ if pos != -1 and end and len(line) > pos + 4:
+ if line[-5] not in ['.', '?', '!']:
return pos, "N402: one line docstring needs punctuation."
-def nova_docstring_multiline_end(physical_line):
+def nova_docstring_multiline_end(physical_line, previous_logical):
r"""Check multi line docstring end.
nova HACKING guide recommendation for docstring:
Docstring should end on a new line
- Okay: '''\nfoo\nbar\n'''
- # This test is not triggered, don't think it's right, removing
- # the colon prevents it from running
- N403 '''\nfoo\nbar\n ''' \n\n
+ Okay: '''foobar\nfoo\nbar\n'''
+ N403: def foo():\n'''foobar\nfoo\nbar\n d'''\n\n
"""
- # TODO(sdague) actually get these tests working
- pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) # start
- if (pos != -1 and len(physical_line) == pos):
- if (physical_line[pos + 3] == ' '):
- return (pos, "N403: multi line docstring end on new line")
+ if in_docstring_position(previous_logical):
+ pos = max(physical_line.find(i) for i in END_DOCSTRING_TRIPLE)
+ if pos != -1 and len(physical_line) == pos + 4:
+ if physical_line.strip() not in START_DOCSTRING_TRIPLE:
+ return (pos, "N403: multi line docstring end on new line")
+
+
+def nova_docstring_multiline_start(physical_line, previous_logical, tokens):
+ r"""Check multi line docstring start with summary.
+
+ nova HACKING guide recommendation for docstring:
+ Docstring should start with A multi line docstring has a one-line summary
+
+ Okay: '''foobar\nfoo\nbar\n'''
+ N404: def foo():\n'''\nfoo\nbar\n''' \n\n
+ """
+ if in_docstring_position(previous_logical):
+ pos = max([physical_line.find(i) for i in START_DOCSTRING_TRIPLE])
+ # start of docstring when len(tokens)==0
+ if len(tokens) == 0 and pos != -1 and len(physical_line) == pos + 4:
+ if physical_line.strip() in START_DOCSTRING_TRIPLE:
+ return (pos, "N404: multi line docstring "
+ "should start with a summary")
+
+
+def nova_no_cr(physical_line):
+ r"""Check that we only use newlines not cariage returns.
+
+ Okay: import os\nimport sys
+ # pep8 doesn't yet replace \r in strings, will work on an
+ # upstream fix
+ N901 import os\r\nimport sys
+ """
+ pos = physical_line.find('\r')
+ if pos != -1 and pos == (len(physical_line) - 2):
+ return (pos, "N901: Windows style line endings not allowed in code")
FORMAT_RE = re.compile("%(?:"
diff --git a/tools/pip-requires b/tools/pip-requires
index 1845ba7dd..231d5cfe5 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -25,3 +25,4 @@ python-quantumclient>=2.1
python-glanceclient>=0.5.0,<2
python-keystoneclient>=0.2.0
stevedore>=0.7
+websockify
diff --git a/tools/test-requires b/tools/test-requires
index 5f195d5c1..c691a6bca 100644
--- a/tools/test-requires
+++ b/tools/test-requires
@@ -12,4 +12,5 @@ pylint==0.25.2
python-subunit
sphinx>=1.1.2
testrepository>=0.0.13
-testtools>=0.9.22
+# testtools 0.9.25 is broken, change this when upstream is fixed (bug 1102400)
+testtools>=0.9.22,<=0.9.24
diff --git a/tox.ini b/tox.ini
index e3322e044..58468accb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -18,9 +18,9 @@ downloadcache = ~/cache/pip
deps=pep8==1.3.3
commands =
python tools/hacking.py --doctest
- python tools/hacking.py --ignore=E12,E711,E721,E712 --repeat --show-source \
+ python tools/hacking.py --ignore=E12,E711,E721,E712,N403,N404 --show-source \
--exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg .
- python tools/hacking.py --ignore=E12,E711,E721,E712 --repeat --show-source \
+ python tools/hacking.py --ignore=E12,E711,E721,E712,N403,N404 --show-source \
--filename=nova* bin
[testenv:pylint]