summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rwxr-xr-xbin/nova-api9
-rwxr-xr-xbin/nova-api-ec22
-rwxr-xr-xbin/nova-baremetal-deploy-helper5
-rwxr-xr-xbin/nova-dhcpbridge5
-rwxr-xr-xbin/nova-manage9
-rwxr-xr-xbin/nova-novncproxy6
-rwxr-xr-xbin/nova-spicehtml5proxy6
-rw-r--r--contrib/boto_v6/ec2/connection.py4
-rw-r--r--contrib/boto_v6/ec2/instance.py18
-rw-r--r--doc/api_samples/all_extensions/extensions-get-resp.json8
-rw-r--r--doc/api_samples/all_extensions/extensions-get-resp.xml3
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json5
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml4
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json8
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml2
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.json14
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml12
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json16
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml15
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json25
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml23
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json5
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml4
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json23
-rw-r--r--doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml21
-rw-r--r--doc/api_samples/os-flavor-manage/flavor-create-post-req.json2
-rw-r--r--doc/api_samples/os-floating-ip-pools/floatingippools-list-resp.json10
-rw-r--r--doc/api_samples/os-floating-ip-pools/floatingippools-list-resp.xml5
-rw-r--r--doc/source/devref/aggregates.rst2
-rw-r--r--etc/nova/api-paste.ini20
-rw-r--r--etc/nova/nova.conf.sample145
-rw-r--r--etc/nova/policy.json3
-rw-r--r--etc/nova/rootwrap.d/compute.filters2
-rw-r--r--nova/api/ec2/cloud.py29
-rw-r--r--nova/api/ec2/ec2utils.py3
-rw-r--r--nova/api/openstack/compute/contrib/availability_zone.py155
-rw-r--r--nova/api/openstack/compute/contrib/baremetal_nodes.py210
-rw-r--r--nova/api/openstack/compute/contrib/coverage_ext.py4
-rw-r--r--nova/api/openstack/compute/server_metadata.py9
-rw-r--r--nova/api/openstack/compute/servers.py7
-rw-r--r--nova/api/openstack/wsgi.py2
-rw-r--r--nova/availability_zones.py26
-rw-r--r--nova/compute/api.py31
-rw-r--r--nova/compute/manager.py32
-rw-r--r--nova/compute/utils.py10
-rw-r--r--nova/conductor/api.py10
-rw-r--r--nova/conductor/manager.py14
-rw-r--r--nova/conductor/rpcapi.py8
-rw-r--r--nova/crypto.py11
-rw-r--r--nova/db/api.py20
-rw-r--r--nova/db/sqlalchemy/api.py145
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/151_change_task_log_column_type.py52
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/152_change_type_of_deleted_column.py226
-rw-r--r--nova/db/sqlalchemy/models.py99
-rw-r--r--nova/db/sqlalchemy/session.py14
-rw-r--r--nova/network/api.py2
-rw-r--r--nova/network/linux_net.py62
-rw-r--r--nova/network/manager.py18
-rw-r--r--nova/network/model.py7
-rw-r--r--nova/network/quantumv2/api.py3
-rw-r--r--nova/network/rpcapi.py6
-rw-r--r--nova/notifications.py8
-rw-r--r--nova/openstack/common/cfg.py76
-rw-r--r--nova/openstack/common/iniparser.py2
-rw-r--r--nova/openstack/common/log.py84
-rw-r--r--nova/scheduler/driver.py2
-rw-r--r--nova/scheduler/filter_scheduler.py12
-rw-r--r--nova/service.py20
-rw-r--r--nova/servicegroup/__init__.py4
-rw-r--r--nova/servicegroup/api.py4
-rw-r--r--nova/tests/api/ec2/test_cinder_cloud.py5
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_admin_actions_with_cells.py10
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_availability_zone.py247
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py197
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_coverage_ext.py6
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_floating_ips.py18
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_services.py86
-rw-r--r--nova/tests/api/openstack/compute/test_limits.py2
-rw-r--r--nova/tests/api/openstack/compute/test_server_metadata.py62
-rw-r--r--nova/tests/api/openstack/compute/test_servers.py55
-rw-r--r--nova/tests/baremetal/test_nova_baremetal_deploy_helper.py256
-rw-r--r--nova/tests/baremetal/test_pxe.py29
-rw-r--r--nova/tests/compute/test_compute.py112
-rw-r--r--nova/tests/compute/test_compute_utils.py14
-rw-r--r--nova/tests/conductor/test_conductor.py15
-rw-r--r--nova/tests/conf_fixture.py4
-rw-r--r--nova/tests/fake_policy.py3
-rw-r--r--nova/tests/fakelibvirt.py6
-rw-r--r--nova/tests/image/test_s3.py4
-rw-r--r--nova/tests/integrated/api_samples/README.rst18
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl8
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl3
-rw-r--r--nova/tests/integrated/api_samples/os-availability-zone/availability-zone-details-resp.json.tpl48
-rw-r--r--nova/tests/integrated/api_samples/os-availability-zone/availability-zone-details-resp.xml.tpl44
-rw-r--r--nova/tests/integrated/api_samples/os-availability-zone/availability-zone-get-resp.json.tpl18
-rw-r--r--nova/tests/integrated/api_samples/os-availability-zone/availability-zone-get-resp.xml.tpl12
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json.tpl5
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml.tpl4
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json.tpl8
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml.tpl7
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.json.tpl14
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml.tpl12
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json.tpl16
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml.tpl15
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json.tpl21
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml.tpl19
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json.tpl5
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml.tpl4
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json.tpl21
-rw-r--r--nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml.tpl17
-rw-r--r--nova/tests/integrated/api_samples/os-floating-ip-pools/floatingippools-list-resp.json.tpl10
-rw-r--r--nova/tests/integrated/api_samples/os-floating-ip-pools/floatingippools-list-resp.xml.tpl4
-rw-r--r--nova/tests/integrated/test_api_samples.py138
-rw-r--r--nova/tests/integrated/test_multiprocess_api.py2
-rw-r--r--nova/tests/network/test_manager.py21
-rw-r--r--nova/tests/network/test_rpcapi.py5
-rw-r--r--nova/tests/scheduler/test_host_filters.py5
-rw-r--r--nova/tests/ssl_cert/ca.crt35
-rw-r--r--nova/tests/ssl_cert/certificate.crt30
-rw-r--r--nova/tests/ssl_cert/privatekey.key51
-rw-r--r--nova/tests/test_api.py6
-rw-r--r--nova/tests/test_availability_zones.py114
-rw-r--r--nova/tests/test_db_api.py44
-rw-r--r--nova/tests/test_driver.py60
-rw-r--r--nova/tests/test_imagebackend.py2
-rw-r--r--nova/tests/test_imagecache.py4
-rw-r--r--nova/tests/test_libvirt.py24
-rw-r--r--nova/tests/test_libvirt_vif.py87
-rw-r--r--nova/tests/test_metadata.py36
-rw-r--r--nova/tests/test_migrations.py102
-rw-r--r--nova/tests/test_periodic_tasks.py18
-rw-r--r--nova/tests/test_pipelib.py8
-rw-r--r--nova/tests/test_utils.py11
-rw-r--r--nova/tests/test_virt_drivers.py6
-rw-r--r--nova/tests/test_wsgi.py118
-rw-r--r--nova/tests/virt/xenapi/test_volumeops.py16
-rw-r--r--nova/utils.py15
-rw-r--r--nova/virt/baremetal/__init__.py4
-rw-r--r--nova/virt/baremetal/db/api.py13
-rw-r--r--nova/virt/baremetal/db/migration.py3
-rw-r--r--nova/virt/baremetal/db/sqlalchemy/api.py71
-rw-r--r--nova/virt/baremetal/db/sqlalchemy/migration.py15
-rw-r--r--nova/virt/baremetal/fake.py4
-rw-r--r--nova/virt/baremetal/ipmi.py16
-rw-r--r--nova/virt/baremetal/pxe.py3
-rw-r--r--nova/virt/disk/api.py10
-rw-r--r--nova/virt/driver.py14
-rw-r--r--nova/virt/fake.py6
-rw-r--r--nova/virt/firewall.py2
-rw-r--r--nova/virt/hyperv/driver.py2
-rw-r--r--nova/virt/hyperv/vif.py8
-rw-r--r--nova/virt/hyperv/volumeops.py2
-rw-r--r--nova/virt/images.py2
-rw-r--r--nova/virt/libvirt/__init__.py4
-rw-r--r--nova/virt/libvirt/driver.py99
-rw-r--r--nova/virt/libvirt/firewall.py4
-rw-r--r--nova/virt/libvirt/imagebackend.py2
-rw-r--r--nova/virt/libvirt/imagecache.py2
-rw-r--r--nova/virt/libvirt/vif.py77
-rw-r--r--nova/virt/powervm/__init__.py4
-rw-r--r--nova/virt/powervm/operator.py40
-rw-r--r--nova/virt/vmwareapi/__init__.py4
-rw-r--r--nova/virt/vmwareapi/driver.py26
-rw-r--r--nova/virt/vmwareapi/fake.py25
-rw-r--r--nova/virt/vmwareapi/io_util.py9
-rw-r--r--nova/virt/vmwareapi/network_util.py2
-rw-r--r--nova/virt/vmwareapi/vm_util.py201
-rw-r--r--nova/virt/vmwareapi/vmops.py18
-rw-r--r--nova/virt/vmwareapi/volume_util.py178
-rw-r--r--nova/virt/vmwareapi/volumeops.py183
-rw-r--r--nova/virt/xenapi/__init__.py4
-rw-r--r--nova/virt/xenapi/driver.py5
-rw-r--r--nova/virt/xenapi/vm_utils.py13
-rw-r--r--nova/virt/xenapi/volumeops.py2
-rw-r--r--nova/volume/cinder.py4
-rw-r--r--nova/wsgi.py98
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/migration2
-rwxr-xr-xrun_tests.sh30
-rw-r--r--smoketests/base.py6
-rw-r--r--tools/conf/extract_opts.py35
-rwxr-xr-xtools/hacking.py62
-rwxr-xr-xtools/lintstack.sh15
-rw-r--r--tools/test-requires3
-rwxr-xr-xtools/xenserver/cleanup_sm_locks.py123
-rwxr-xr-xtools/xenserver/vm_vdi_cleaner.py1
-rw-r--r--tox.ini2
187 files changed, 4716 insertions, 1034 deletions
diff --git a/.gitignore b/.gitignore
index efb88c781..6028b8a44 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,3 +37,5 @@ nosetests.xml
nova/tests/cover/*
nova/vcsversion.py
tools/conf/nova.conf*
+tools/lintstack.head.py
+tools/pylint_exceptions
diff --git a/bin/nova-api b/bin/nova-api
index 8457ea43d..d957f3e58 100755
--- a/bin/nova-api
+++ b/bin/nova-api
@@ -44,13 +44,20 @@ from nova import utils
CONF = cfg.CONF
CONF.import_opt('enabled_apis', 'nova.service')
+CONF.import_opt('enabled_ssl_apis', 'nova.service')
if __name__ == '__main__':
config.parse_args(sys.argv)
logging.setup("nova")
utils.monkey_patch()
+
launcher = service.ProcessLauncher()
for api in CONF.enabled_apis:
- server = service.WSGIService(api)
+ should_use_ssl = api in CONF.enabled_ssl_apis
+ if api == 'ec2':
+ server = service.WSGIService(api, use_ssl=should_use_ssl,
+ max_url_len=16384)
+ else:
+ server = service.WSGIService(api, use_ssl=should_use_ssl)
launcher.launch_server(server, workers=server.workers or 1)
launcher.wait()
diff --git a/bin/nova-api-ec2 b/bin/nova-api-ec2
index c7b08845d..d1b3d45ea 100755
--- a/bin/nova-api-ec2
+++ b/bin/nova-api-ec2
@@ -41,6 +41,6 @@ if __name__ == '__main__':
config.parse_args(sys.argv)
logging.setup("nova")
utils.monkey_patch()
- server = service.WSGIService('ec2')
+ server = service.WSGIService('ec2', max_url_len=16384)
service.serve(server, workers=server.workers)
service.wait()
diff --git a/bin/nova-baremetal-deploy-helper b/bin/nova-baremetal-deploy-helper
index f8a487d37..894a42003 100755
--- a/bin/nova-baremetal-deploy-helper
+++ b/bin/nova-baremetal-deploy-helper
@@ -18,7 +18,10 @@
"""Starter script for Bare-Metal Deployment Service."""
import eventlet
-eventlet.monkey_patch()
+
+# Do not monkey_patch in unittest
+if __name__ == '__main__':
+ eventlet.monkey_patch()
import os
import sys
diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge
index ee7bf2da9..0438ee6ff 100755
--- a/bin/nova-dhcpbridge
+++ b/bin/nova-dhcpbridge
@@ -96,10 +96,15 @@ def init_leases(network_id):
def add_action_parsers(subparsers):
parser = subparsers.add_parser('init')
+ # NOTE(cfb): dnsmasq always passes mac, and ip. hostname
+ # is passed if known. We don't care about
+ # hostname, but argparse will complain if we
+ # do not accept it.
for action in ['add', 'del', 'old']:
parser = subparsers.add_parser(action)
parser.add_argument('mac')
parser.add_argument('ip')
+ parser.add_argument('hostname', nargs='?', default='')
parser.set_defaults(func=globals()[action + '_lease'])
diff --git a/bin/nova-manage b/bin/nova-manage
index 4f3d889ea..90d191eca 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -1128,8 +1128,13 @@ def add_command_parsers(subparsers):
action_kwargs = []
for args, kwargs in getattr(action_fn, 'args', []):
- action_kwargs.append(kwargs['dest'])
- kwargs['dest'] = 'action_kwarg_' + kwargs['dest']
+ if kwargs['dest'].startswith('action_kwarg_'):
+ action_kwargs.append(
+ kwargs['dest'][len('action_kwarg_'):])
+ else:
+ action_kwargs.append(kwargs['dest'])
+ kwargs['dest'] = 'action_kwarg_' + kwargs['dest']
+
parser.add_argument(*args, **kwargs)
parser.set_defaults(action_fn=action_fn)
diff --git a/bin/nova-novncproxy b/bin/nova-novncproxy
index 8562acc53..657f97b48 100755
--- a/bin/nova-novncproxy
+++ b/bin/nova-novncproxy
@@ -25,7 +25,7 @@ import os
import sys
from nova import config
-from nova.console import websocketproxy as ws
+from nova.console import websocketproxy
from nova.openstack.common import cfg
@@ -61,6 +61,7 @@ opts = [
CONF = cfg.CONF
CONF.register_cli_opts(opts)
+CONF.import_opt('debug', 'nova.openstack.common.log')
if __name__ == '__main__':
@@ -76,7 +77,8 @@ if __name__ == '__main__':
sys.exit(-1)
# Create and start the NovaWebSockets proxy
- server = ws.NovaWebSocketProxy(listen_host=CONF.novncproxy_host,
+ server = websocketproxy.NovaWebSocketProxy(
+ listen_host=CONF.novncproxy_host,
listen_port=CONF.novncproxy_port,
source_is_ipv6=CONF.source_is_ipv6,
verbose=CONF.verbose,
diff --git a/bin/nova-spicehtml5proxy b/bin/nova-spicehtml5proxy
index b1882bbea..17f8cb7c2 100755
--- a/bin/nova-spicehtml5proxy
+++ b/bin/nova-spicehtml5proxy
@@ -25,7 +25,7 @@ import os
import sys
from nova import config
-from nova.console import websocketproxy as ws
+from nova.console import websocketproxy
from nova.openstack.common import cfg
@@ -61,6 +61,7 @@ opts = [
CONF = cfg.CONF
CONF.register_cli_opts(opts)
+CONF.import_opt('debug', 'nova.openstack.common.log')
if __name__ == '__main__':
@@ -76,7 +77,8 @@ if __name__ == '__main__':
sys.exit(-1)
# Create and start the NovaWebSockets proxy
- server = ws.NovaWebSocketProxy(listen_host=CONF.spicehtml5proxy_host,
+ server = websocketproxy.NovaWebSocketProxy(
+ listen_host=CONF.spicehtml5proxy_host,
listen_port=CONF.spicehtml5proxy_port,
source_is_ipv6=CONF.source_is_ipv6,
verbose=CONF.verbose,
diff --git a/contrib/boto_v6/ec2/connection.py b/contrib/boto_v6/ec2/connection.py
index 940608ffd..4cec65ad8 100644
--- a/contrib/boto_v6/ec2/connection.py
+++ b/contrib/boto_v6/ec2/connection.py
@@ -6,7 +6,7 @@ Created on 2010/12/20
import base64
import boto
import boto.ec2
-from boto.ec2.securitygroup import SecurityGroup
+import boto.ec2.securitygroup as securitygroup
from boto_v6.ec2.instance import ReservationV6
@@ -114,7 +114,7 @@ class EC2ConnectionV6(boto.ec2.EC2Connection):
if security_groups:
l = []
for group in security_groups:
- if isinstance(group, SecurityGroup):
+ if isinstance(group, securitygroup.SecurityGroup):
l.append(group.name)
else:
l.append(group)
diff --git a/contrib/boto_v6/ec2/instance.py b/contrib/boto_v6/ec2/instance.py
index 74adccc00..6f088c67e 100644
--- a/contrib/boto_v6/ec2/instance.py
+++ b/contrib/boto_v6/ec2/instance.py
@@ -3,31 +3,29 @@ Created on 2010/12/20
@author: Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
'''
-from boto.ec2.instance import Group
-from boto.ec2.instance import Instance
-from boto.ec2.instance import Reservation
-from boto.resultset import ResultSet
+from boto.ec2 import instance
+from boto import resultset
-class ReservationV6(Reservation):
+class ReservationV6(instance.Reservation):
def startElement(self, name, attrs, connection):
if name == 'instancesSet':
- self.instances = ResultSet([('item', InstanceV6)])
+ self.instances = resultset.ResultSet([('item', InstanceV6)])
return self.instances
elif name == 'groupSet':
- self.groups = ResultSet([('item', Group)])
+ self.groups = resultset.ResultSet([('item', instance.Group)])
return self.groups
else:
return None
-class InstanceV6(Instance):
+class InstanceV6(instance.Instance):
def __init__(self, connection=None):
- Instance.__init__(self, connection)
+ instance.Instance.__init__(self, connection)
self.dns_name_v6 = None
def endElement(self, name, value, connection):
- Instance.endElement(self, name, value, connection)
+ instance.Instance.endElement(self, name, value, connection)
if name == 'dnsNameV6':
self.dns_name_v6 = value
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json
index bd002c080..604ad6763 100644
--- a/doc/api_samples/all_extensions/extensions-get-resp.json
+++ b/doc/api_samples/all_extensions/extensions-get-resp.json
@@ -89,6 +89,14 @@
"updated": "2012-08-09T00:00:00+00:00"
},
{
+ "alias": "os-baremetal-nodes",
+ "description": "Admin-only bare-metal node administration.",
+ "links": [],
+ "name": "BareMetalNodes",
+ "namespace": "http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2",
+ "updated": "2013-01-04T00:00:00+00:00"
+ },
+ {
"alias": "os-cells",
"description": "Enables cells-related functionality such as adding neighbor cells,\n listing neighbor cells, and getting the capabilities of the local cell.\n ",
"links": [],
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml
index ebb1c4302..d7f483745 100644
--- a/doc/api_samples/all_extensions/extensions-get-resp.xml
+++ b/doc/api_samples/all_extensions/extensions-get-resp.xml
@@ -37,6 +37,9 @@
<extension alias="os-availability-zone" updated="2012-08-09T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1" name="AvailabilityZone">
<description>Add availability_zone to the Create Server v1.1 API.</description>
</extension>
+ <extension alias="os-baremetal-nodes" updated="2013-01-04T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2" name="BareMetalNodes">
+ <description>Admin-only bare-metal node administration.</description>
+ </extension>
<extension alias="os-cells" updated="2011-09-21T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/cells/api/v1.1" name="Cells">
<description>Enables cells-related functionality such as adding child cells,
listing child cells, getting the capabilities of the local cell,
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json
new file mode 100644
index 000000000..2e795e483
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json
@@ -0,0 +1,5 @@
+{
+ "add_interface": {
+ "address": "aa:aa:aa:aa:aa:aa"
+ }
+} \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml
new file mode 100644
index 000000000..63ca9c21e
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<add_interface
+ address="aa:aa:aa:aa:aa:aa"
+/> \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json
new file mode 100644
index 000000000..d0b9cc3fb
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json
@@ -0,0 +1,8 @@
+{
+ "interface": {
+ "address": "aa:aa:aa:aa:aa:aa",
+ "datapath_id": null,
+ "id": 1,
+ "port_no": null
+ }
+} \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml
new file mode 100644
index 000000000..1da1dd284
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<interface datapath_id="None" id="1" port_no="None" address="aa:aa:aa:aa:aa:aa"/> \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.json
new file mode 100644
index 000000000..d8b9eb452
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.json
@@ -0,0 +1,14 @@
+{
+ "node": {
+ "service_host": "host",
+ "cpus": 8,
+ "memory_mb": 8192,
+ "local_gb": 128,
+ "pm_address": "10.1.2.3",
+ "pm_user": "pm_user",
+ "pm_password": "pm_pass",
+ "prov_mac_address": "12:34:56:78:90:ab",
+ "prov_vlan_id": 1234,
+ "terminal_port": 8000
+ }
+} \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml
new file mode 100644
index 000000000..85c863a97
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node
+ service_host="host"
+ cpus="8"
+ memory_mb="8192"
+ local_gb="128"
+ pm_address="10.1.2.3"
+ pm_user="pm_user"
+ prov_mac_address="12:34:56:78:90:ab"
+ prov_vlan_id="1234"
+ terminal_port="8000"
+/> \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json
new file mode 100644
index 000000000..b62a9e663
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json
@@ -0,0 +1,16 @@
+{
+ "node": {
+ "cpus": 8,
+ "id": 1,
+ "instance_uuid": null,
+ "interfaces": [],
+ "local_gb": 128,
+ "memory_mb": 8192,
+ "pm_address": "10.1.2.3",
+ "pm_user": "pm_user",
+ "prov_mac_address": "12:34:56:78:90:ab",
+ "prov_vlan_id": 1234,
+ "service_host": "host",
+ "terminal_port": 8000
+ }
+} \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml
new file mode 100644
index 000000000..9b8421f0f
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml
@@ -0,0 +1,15 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<node
+ instance_uuid="None"
+ pm_address="10.1.2.3"
+ cpus="8"
+ prov_vlan_id="1234"
+ memory_mb="8192"
+ prov_mac_address="12:34:56:78:90:ab"
+ service_host="host"
+ local_gb="128"
+ id="1"
+ pm_user="pm_user"
+ terminal_port="8000">
+ <interfaces/>
+</node> \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json
new file mode 100644
index 000000000..d43d580ed
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json
@@ -0,0 +1,25 @@
+{
+ "nodes": [
+ {
+ "cpus": 8,
+ "id": 1,
+ "instance_uuid": null,
+ "interfaces": [
+ {
+ "address": "aa:aa:aa:aa:aa:aa",
+ "datapath_id": null,
+ "id": 1,
+ "port_no": null
+ }
+ ],
+ "local_gb": 128,
+ "memory_mb": 8192,
+ "pm_address": "10.1.2.3",
+ "pm_user": "pm_user",
+ "prov_mac_address": "12:34:56:78:90:ab",
+ "prov_vlan_id": 1234,
+ "service_host": "host",
+ "terminal_port": 8000
+ }
+ ]
+} \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml
new file mode 100644
index 000000000..7cd1b5d8a
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml
@@ -0,0 +1,23 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<nodes>
+ <node
+ instance_uuid="None"
+ pm_address="10.1.2.3"
+ cpus="8"
+ prov_vlan_id="1234"
+ memory_mb="8192"
+ prov_mac_address="12:34:56:78:90:ab"
+ service_host="host"
+ local_gb="128"
+ id="1"
+ pm_user="pm_user"
+ terminal_port="8000">
+ <interfaces>
+ <interface
+ datapath_id="None"
+ id="1"
+ port_no="None"
+ address="aa:aa:aa:aa:aa:aa"/>
+ </interfaces>
+ </node>
+</nodes> \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json
new file mode 100644
index 000000000..0ce85577d
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json
@@ -0,0 +1,5 @@
+{
+ "remove_interface": {
+ "address": "aa:aa:aa:aa:aa:aa"
+ }
+} \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml
new file mode 100644
index 000000000..6457b059b
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<remove_interface
+ address="aa:aa:aa:aa:aa:aa"
+/> \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json
new file mode 100644
index 000000000..d42365752
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json
@@ -0,0 +1,23 @@
+{
+ "node": {
+ "cpus": 8,
+ "id": 1,
+ "instance_uuid": null,
+ "interfaces": [
+ {
+ "address": "aa:aa:aa:aa:aa:aa",
+ "datapath_id": null,
+ "id": 1,
+ "port_no": null
+ }
+ ],
+ "local_gb": 128,
+ "memory_mb": 8192,
+ "pm_address": "10.1.2.3",
+ "pm_user": "pm_user",
+ "prov_mac_address": "12:34:56:78:90:ab",
+ "prov_vlan_id": 1234,
+ "service_host": "host",
+ "terminal_port": 8000
+ }
+} \ No newline at end of file
diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml
new file mode 100644
index 000000000..6d5f9719f
--- /dev/null
+++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml
@@ -0,0 +1,21 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<node
+ instance_uuid="None"
+ pm_address="10.1.2.3"
+ cpus="8"
+ prov_vlan_id="1234"
+ memory_mb="8192"
+ prov_mac_address="12:34:56:78:90:ab"
+ service_host="host"
+ local_gb="128"
+ id="1"
+ pm_user="pm_user"
+ terminal_port="8000">
+ <interfaces>
+ <interface
+ datapath_id="None"
+ id="1"
+ port_no="None"
+ address="aa:aa:aa:aa:aa:aa"/>
+ </interfaces>
+</node> \ No newline at end of file
diff --git a/doc/api_samples/os-flavor-manage/flavor-create-post-req.json b/doc/api_samples/os-flavor-manage/flavor-create-post-req.json
index 8a3830f09..0c5914a01 100644
--- a/doc/api_samples/os-flavor-manage/flavor-create-post-req.json
+++ b/doc/api_samples/os-flavor-manage/flavor-create-post-req.json
@@ -4,6 +4,6 @@
"ram": 1024,
"vcpus": 2,
"disk": 10,
- "id": "10",
+ "id": "10"
}
}
diff --git a/doc/api_samples/os-floating-ip-pools/floatingippools-list-resp.json b/doc/api_samples/os-floating-ip-pools/floatingippools-list-resp.json
new file mode 100644
index 000000000..7b6482987
--- /dev/null
+++ b/doc/api_samples/os-floating-ip-pools/floatingippools-list-resp.json
@@ -0,0 +1,10 @@
+{
+ "floating_ip_pools": [
+ {
+ "name": "pool1"
+ },
+ {
+ "name": "pool2"
+ }
+ ]
+} \ No newline at end of file
diff --git a/doc/api_samples/os-floating-ip-pools/floatingippools-list-resp.xml b/doc/api_samples/os-floating-ip-pools/floatingippools-list-resp.xml
new file mode 100644
index 000000000..ca09fbf95
--- /dev/null
+++ b/doc/api_samples/os-floating-ip-pools/floatingippools-list-resp.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<floating_ip_pools>
+ <floating_ip_pool name="pool1"/>
+ <floating_ip_pool name="pool2"/>
+</floating_ip_pools> \ No newline at end of file
diff --git a/doc/source/devref/aggregates.rst b/doc/source/devref/aggregates.rst
index 56d398717..ecc6329ba 100644
--- a/doc/source/devref/aggregates.rst
+++ b/doc/source/devref/aggregates.rst
@@ -65,7 +65,7 @@ Usage
* aggregate-add-host <id> <host> Add the host to the specified aggregate.
* aggregate-remove-host <id> <host> Remove the specified host from the specfied aggregate.
* aggregate-set-metadata <id> <key=value> [<key=value> ...] Update the metadata associated with the aggregate.
- * aggregate-update <id> <name> [<availability_zone>] Update the aggregate's name and optionally availablity zone.
+ * aggregate-update <id> <name> [<availability_zone>] Update the aggregate's name and optionally availability zone.
* host-list List all hosts by service
* host-update --maintenance [enable | disable] Put/resume host into/from maintenance.
diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini
index 85603fe59..08d59c521 100644
--- a/etc/nova/api-paste.ini
+++ b/etc/nova/api-paste.ini
@@ -62,23 +62,12 @@ use = call:nova.api.openstack.urlmap:urlmap_factory
/v1.1: openstack_compute_api_v2
/v2: openstack_compute_api_v2
-[composite:osapi_volume]
-use = call:nova.api.openstack.urlmap:urlmap_factory
-/: osvolumeversions
-/v1: openstack_volume_api_v1
-
[composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory
noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2
keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
-[composite:openstack_volume_api_v1]
-use = call:nova.api.auth:pipeline_factory
-noauth = faultwrap sizelimit noauth ratelimit osapi_volume_app_v1
-keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_volume_app_v1
-keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_volume_app_v1
-
[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
@@ -97,18 +86,9 @@ paste.app_factory = nova.api.openstack.compute:APIRouter.factory
[pipeline:oscomputeversions]
pipeline = faultwrap oscomputeversionapp
-[app:osapi_volume_app_v1]
-paste.app_factory = nova.api.openstack.volume:APIRouter.factory
-
[app:oscomputeversionapp]
paste.app_factory = nova.api.openstack.compute.versions:Versions.factory
-[pipeline:osvolumeversions]
-pipeline = faultwrap osvolumeversionapp
-
-[app:osvolumeversionapp]
-paste.app_factory = nova.api.openstack.volume.versions:Versions.factory
-
##########
# Shared #
##########
diff --git a/etc/nova/nova.conf.sample b/etc/nova/nova.conf.sample
index 36a7b0d9c..a5f945618 100644
--- a/etc/nova/nova.conf.sample
+++ b/etc/nova/nova.conf.sample
@@ -1,47 +1,6 @@
[DEFAULT]
#
-# Options defined in nova.openstack.common.cfg:CommonConfigOpts
-#
-
-# Print debugging output (boolean value)
-#debug=false
-
-# Print more verbose output (boolean value)
-#verbose=false
-
-# If this option is specified, the logging configuration file
-# specified is used and overrides any other logging options
-# specified. Please see the Python logging module
-# documentation for details on logging configuration files.
-# (string value)
-#log_config=<None>
-
-# A logging.Formatter log message format string which may use
-# any of the available logging.LogRecord attributes. Default:
-# %(default)s (string value)
-#log_format=%(asctime)s %(levelname)8s [%(name)s] %(message)s
-
-# Format string for %%(asctime)s in log records. Default:
-# %(default)s (string value)
-#log_date_format=%Y-%m-%d %H:%M:%S
-
-# (Optional) Name of log file to output to. If not set,
-# logging will go to stdout. (string value)
-#log_file=<None>
-
-# (Optional) The directory to keep log files in (will be
-# prepended to --log-file) (string value)
-#log_dir=<None>
-
-# Use syslog for logging. (boolean value)
-#use_syslog=false
-
-# syslog facility to receive log lines (string value)
-#syslog_log_facility=LOG_USER
-
-
-#
# Options defined in nova.availability_zones
#
@@ -486,6 +445,22 @@
#
+# Options defined in nova.api.openstack.compute.contrib.os_tenant_networks
+#
+
+# Enables or disables quotaing of tenant networks (boolean
+# value)
+#enable_network_quota=false
+
+# Control for checking for default networks (string value)
+#use_quantum_default_nets=False
+
+# Default tenant id when creating quantum networks (string
+# value)
+#quantum_default_tenant_id=default
+
+
+#
# Options defined in nova.api.openstack.compute.extensions
#
@@ -1123,10 +1098,6 @@
# Autoassigning floating ip to VM (boolean value)
#auto_assign_floating_ip=false
-# Network host to use for ip allocation in flat modes (string
-# value)
-#network_host=nova
-
# If passed, use fake network devices and addresses (boolean
# value)
#fake_network=false
@@ -1207,6 +1178,10 @@
# (string value)
#quantum_auth_strategy=keystone
+# Name of Integration Bridge used by Open vSwitch (string
+# value)
+#quantum_ovs_bridge=br-int
+
#
# Options defined in nova.network.rpcapi
@@ -1253,6 +1228,14 @@
# Options defined in nova.openstack.common.log
#
+# Print debugging output (set logging level to DEBUG instead
+# of default WARNING level). (boolean value)
+#debug=false
+
+# Print more verbose output (set logging level to INFO instead
+# of default WARNING level). (boolean value)
+#verbose=false
+
# Log output to standard error (boolean value)
#use_stderr=true
@@ -1262,11 +1245,11 @@
# format string to use for log messages with context (string
# value)
-#logging_context_format_string=%(asctime)s %(levelname)s %(name)s [%(request_id)s %(user_id)s %(project_id)s] %(instance)s%(message)s
+#logging_context_format_string=%(asctime)s.%(msecs)03d %(levelname)s %(name)s [%(request_id)s %(user)s %(tenant)s] %(instance)s%(message)s
# format string to use for log messages without context
# (string value)
-#logging_default_format_string=%(asctime)s %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
+#logging_default_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
# data to append to log format when level is DEBUG (string
# value)
@@ -1274,7 +1257,7 @@
# prefix each line of exception output with this format
# (string value)
-#logging_exception_prefix=%(asctime)s %(process)d TRACE %(name)s %(instance)s
+#logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s
# list of logger=LEVEL pairs (list value)
#default_log_levels=amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO,eventlet.wsgi.server=WARN
@@ -1293,6 +1276,36 @@
# it like this (string value)
#instance_uuid_format="[instance: %(uuid)s] "
+# If this option is specified, the logging configuration file
+# specified is used and overrides any other logging options
+# specified. Please see the Python logging module
+# documentation for details on logging configuration files.
+# (string value)
+#log_config=<None>
+
+# A logging.Formatter log message format string which may use
+# any of the available logging.LogRecord attributes. Default:
+# %(default)s (string value)
+#log_format=%(asctime)s %(levelname)8s [%(name)s] %(message)s
+
+# Format string for %%(asctime)s in log records. Default:
+# %(default)s (string value)
+#log_date_format=%Y-%m-%d %H:%M:%S
+
+# (Optional) Name of log file to output to. If not set,
+# logging will go to stdout. (string value)
+#log_file=<None>
+
+# (Optional) The directory to keep log files in (will be
+# prepended to --log-file) (string value)
+#log_dir=<None>
+
+# Use syslog for logging. (boolean value)
+#use_syslog=false
+
+# syslog facility to receive log lines (string value)
+#syslog_log_facility=LOG_USER
+
#
# Options defined in nova.openstack.common.notifier.api
@@ -1724,13 +1737,18 @@
#
-# Options defined in nova.virt.hyperv.vmops
+# Options defined in nova.virt.hyperv.vif
#
-# Default vSwitch Name, if none provided first external is
-# used (string value)
+# External virtual switch Name, if not provided, the first
+# external virtual switch is used (string value)
#vswitch_name=<None>
+
+#
+# Options defined in nova.virt.hyperv.vmops
+#
+
# Required for live migration among hosts with different CPU
# features (boolean value)
#limit_cpu_features=false
@@ -1985,26 +2003,26 @@
# Options defined in nova.virt.vmwareapi.driver
#
-# URL for connection to VMWare ESX host.Required if
-# compute_driver is vmwareapi.VMWareESXDriver. (string value)
+# URL for connection to VMware ESX host.Required if
+# compute_driver is vmwareapi.VMwareESXDriver. (string value)
#vmwareapi_host_ip=<None>
-# Username for connection to VMWare ESX host. Used only if
-# compute_driver is vmwareapi.VMWareESXDriver. (string value)
+# Username for connection to VMware ESX host. Used only if
+# compute_driver is vmwareapi.VMwareESXDriver. (string value)
#vmwareapi_host_username=<None>
-# Password for connection to VMWare ESX host. Used only if
-# compute_driver is vmwareapi.VMWareESXDriver. (string value)
+# Password for connection to VMware ESX host. Used only if
+# compute_driver is vmwareapi.VMwareESXDriver. (string value)
#vmwareapi_host_password=<None>
# The interval used for polling of remote tasks. Used only if
-# compute_driver is vmwareapi.VMWareESXDriver. (floating point
+# compute_driver is vmwareapi.VMwareESXDriver. (floating point
# value)
#vmwareapi_task_poll_interval=5.0
# The number of times we retry on failures, e.g., socket
# error, etc. Used only if compute_driver is
-# vmwareapi.VMWareESXDriver. (integer value)
+# vmwareapi.VMwareESXDriver. (integer value)
#vmwareapi_api_retry_count=10
@@ -2278,12 +2296,15 @@
# (string value)
#cinder_endpoint_template=<None>
+# region name of this node (string value)
+#os_region_name=<None>
+
# Number of cinderclient retries on failed http calls (integer
# value)
#cinder_http_retries=3
-# Allow to perform insecure SSL (https) requests to cinder
-# (boolean value)
+# Allow to perform insecure SSL requests to cinder (boolean
+# value)
#cinder_api_insecure=false
@@ -2550,4 +2571,4 @@
#keymap=en-us
-# Total option count: 520
+# Total option count: 525
diff --git a/etc/nova/policy.json b/etc/nova/policy.json
index fd1f9c2e0..d94ec7192 100644
--- a/etc/nova/policy.json
+++ b/etc/nova/policy.json
@@ -29,6 +29,7 @@
"compute_extension:admin_actions:migrate": "rule:admin_api",
"compute_extension:aggregates": "rule:admin_api",
"compute_extension:agents": "rule:admin_api",
+ "compute_extension:baremetal_nodes": "rule:admin_api",
"compute_extension:cells": "rule:admin_api",
"compute_extension:certificates": "",
"compute_extension:cloudpipe": "rule:admin_api",
@@ -83,6 +84,8 @@
"compute_extension:virtual_storage_arrays": "",
"compute_extension:volumes": "",
"compute_extension:volumetypes": "",
+ "compute_extension:availability_zone:list": "",
+ "compute_extension:availability_zone:detail": "rule:admin_api",
"volume:create": "",
diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters
index f344a1b1c..e1113a9e7 100644
--- a/etc/nova/rootwrap.d/compute.filters
+++ b/etc/nova/rootwrap.d/compute.filters
@@ -99,9 +99,11 @@ pygrub: CommandFilter, /usr/bin/pygrub, root
fdisk: CommandFilter, /sbin/fdisk, root
# nova/virt/xenapi/vm_utils.py: e2fsck, -f, -p, partition_path
+# nova/virt/disk/api.py: e2fsck, -f, -p, image
e2fsck: CommandFilter, /sbin/e2fsck, root
# nova/virt/xenapi/vm_utils.py: resize2fs, partition_path
+# nova/virt/disk/api.py: resize2fs, image
resize2fs: CommandFilter, /sbin/resize2fs, root
# nova/network/linux_net.py: 'ip[6]tables-save' % (cmd, '-t', ...
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 31f486b81..48b0f632f 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -250,32 +250,10 @@ class CloudController(object):
else:
return self._describe_availability_zones(context, **kwargs)
- def _get_zones(self, context):
- """Return available and unavailable zones."""
- enabled_services = db.service_get_all(context, False)
- disabled_services = db.service_get_all(context, True)
- enabled_services = availability_zones.set_availability_zones(context,
- enabled_services)
- disabled_services = availability_zones.set_availability_zones(context,
- disabled_services)
-
- available_zones = []
- for zone in [service['availability_zone'] for service
- in enabled_services]:
- if not zone in available_zones:
- available_zones.append(zone)
-
- not_available_zones = []
- zones = [service['available_zones'] for service in disabled_services
- if service['available_zones'] not in available_zones]
- for zone in zones:
- if zone not in not_available_zones:
- not_available_zones.append(zone)
- return (available_zones, not_available_zones)
-
def _describe_availability_zones(self, context, **kwargs):
ctxt = context.elevated()
- available_zones, not_available_zones = self._get_zones(ctxt)
+ available_zones, not_available_zones = \
+ availability_zones.get_availability_zones(ctxt)
result = []
for zone in available_zones:
@@ -291,7 +269,8 @@ class CloudController(object):
def _describe_availability_zones_verbose(self, context, **kwargs):
ctxt = context.elevated()
- available_zones, not_available_zones = self._get_zones(ctxt)
+ available_zones, not_available_zones = \
+ availability_zones.get_availability_zones(ctxt)
# Available services
enabled_services = db.service_get_all(context, False)
diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py
index 1ce880de4..bc47b3e0d 100644
--- a/nova/api/ec2/ec2utils.py
+++ b/nova/api/ec2/ec2utils.py
@@ -117,7 +117,8 @@ def get_ip_info_for_instance(context, instance):
def get_availability_zone_by_host(services, host):
if len(services) > 0:
- return availability_zones.get_host_availability_zone(context, host)
+ return availability_zones.get_host_availability_zone(
+ context.get_admin_context(), host)
return 'unknown zone'
diff --git a/nova/api/openstack/compute/contrib/availability_zone.py b/nova/api/openstack/compute/contrib/availability_zone.py
index 2955b68eb..6cde5ca64 100644
--- a/nova/api/openstack/compute/contrib/availability_zone.py
+++ b/nova/api/openstack/compute/contrib/availability_zone.py
@@ -14,14 +14,165 @@
# License for the specific language governing permissions and limitations
# under the License
+from nova.api.openstack import common
from nova.api.openstack import extensions
+from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
+from nova import availability_zones
+from nova import db
+from nova.openstack.common import cfg
+from nova.openstack.common import log as logging
+from nova import servicegroup
+
+
+LOG = logging.getLogger(__name__)
+CONF = cfg.CONF
+
+authorize_list = extensions.extension_authorizer('compute',
+ 'availability_zone:list')
+authorize_detail = extensions.extension_authorizer('compute',
+ 'availability_zone:detail')
+
+
+def make_availability_zone(elem):
+ elem.set('name', 'zoneName')
+
+ zoneStateElem = xmlutil.SubTemplateElement(elem, 'zoneState',
+ selector='zoneState')
+ zoneStateElem.set('available')
+
+ hostsElem = xmlutil.SubTemplateElement(elem, 'hosts', selector='hosts')
+ hostElem = xmlutil.SubTemplateElement(hostsElem, 'host',
+ selector=xmlutil.get_items)
+ hostElem.set('name', 0)
+
+ svcsElem = xmlutil.SubTemplateElement(hostElem, 'services', selector=1)
+ svcElem = xmlutil.SubTemplateElement(svcsElem, 'service',
+ selector=xmlutil.get_items)
+ svcElem.set('name', 0)
+
+ svcStateElem = xmlutil.SubTemplateElement(svcElem, 'serviceState',
+ selector=1)
+ svcStateElem.set('available')
+ svcStateElem.set('active')
+ svcStateElem.set('updated_at')
+
+ # Attach metadata node
+ elem.append(common.MetadataTemplate())
+
+
+class AvailabilityZonesTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('availabilityZones')
+ zoneElem = xmlutil.SubTemplateElement(root, 'availabilityZone',
+ selector='availabilityZoneInfo')
+ make_availability_zone(zoneElem)
+ return xmlutil.MasterTemplate(root, 1, nsmap={
+ Availability_zone.alias: Availability_zone.namespace})
+
+
+class AvailabilityZoneController(wsgi.Controller):
+ """The Availability Zone API controller for the OpenStack API."""
+
+ def __init__(self):
+ super(AvailabilityZoneController, self).__init__()
+ self.servicegroup_api = servicegroup.API()
+
+ def _describe_availability_zones(self, context, **kwargs):
+ ctxt = context.elevated()
+ available_zones, not_available_zones = \
+ availability_zones.get_availability_zones(ctxt)
+
+ result = []
+ for zone in available_zones:
+ # Hide internal_service_availability_zone
+ if zone == CONF.internal_service_availability_zone:
+ continue
+ result.append({'zoneName': zone,
+ 'zoneState': {'available': True},
+ "hosts": None})
+ for zone in not_available_zones:
+ result.append({'zoneName': zone,
+ 'zoneState': {'available': False},
+ "hosts": None})
+ return {'availabilityZoneInfo': result}
+
+ def _describe_availability_zones_verbose(self, context, **kwargs):
+ ctxt = context.elevated()
+ available_zones, not_available_zones = \
+ availability_zones.get_availability_zones(ctxt)
+
+ # Available services
+ enabled_services = db.service_get_all(context, False)
+ enabled_services = availability_zones.set_availability_zones(context,
+ enabled_services)
+ zone_hosts = {}
+ host_services = {}
+ for service in enabled_services:
+ zone_hosts.setdefault(service['availability_zone'], [])
+ if not service['host'] in zone_hosts[service['availability_zone']]:
+ zone_hosts[service['availability_zone']].append(
+ service['host'])
+
+ host_services.setdefault(service['availability_zone'] +
+ service['host'], [])
+ host_services[service['availability_zone'] + service['host']].\
+ append(service)
+
+ result = []
+ for zone in available_zones:
+ hosts = {}
+ for host in zone_hosts[zone]:
+ hosts[host] = {}
+ for service in host_services[zone + host]:
+ alive = self.servicegroup_api.service_is_up(service)
+ hosts[host][service['binary']] = {'available': alive,
+ 'active': True != service['disabled'],
+ 'updated_at': service['updated_at']}
+ result.append({'zoneName': zone,
+ 'zoneState': {'available': True},
+ "hosts": hosts})
+
+ for zone in not_available_zones:
+ result.append({'zoneName': zone,
+ 'zoneState': {'available': False},
+ "hosts": None})
+ return {'availabilityZoneInfo': result}
+
+ @wsgi.serializers(xml=AvailabilityZonesTemplate)
+ def index(self, req):
+ """Returns a summary list of availability zone."""
+ context = req.environ['nova.context']
+ authorize_list(context)
+
+ return self._describe_availability_zones(context)
+
+ @wsgi.serializers(xml=AvailabilityZonesTemplate)
+ def detail(self, req):
+ """Returns a detailed list of availability zone."""
+ context = req.environ['nova.context']
+ authorize_detail(context)
+
+ return self._describe_availability_zones_verbose(context)
class Availability_zone(extensions.ExtensionDescriptor):
- """Add availability_zone to the Create Server v1.1 API."""
+ """1. Add availability_zone to the Create Server v1.1 API.
+ 2. Add availability zones describing.
+ """
name = "AvailabilityZone"
alias = "os-availability-zone"
namespace = ("http://docs.openstack.org/compute/ext/"
"availabilityzone/api/v1.1")
- updated = "2012-08-09T00:00:00+00:00"
+ updated = "2012-12-21T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+
+ res = extensions.ResourceExtension('os-availability-zone',
+ AvailabilityZoneController(),
+ collection_actions={'detail': 'GET'})
+ resources.append(res)
+
+ return resources
diff --git a/nova/api/openstack/compute/contrib/baremetal_nodes.py b/nova/api/openstack/compute/contrib/baremetal_nodes.py
new file mode 100644
index 000000000..38d66d2ae
--- /dev/null
+++ b/nova/api/openstack/compute/contrib/baremetal_nodes.py
@@ -0,0 +1,210 @@
+# Copyright (c) 2013 NTT DOCOMO, INC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""The bare-metal admin extension."""
+
+import webob
+
+from nova.api.openstack import extensions
+from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
+from nova import exception
+from nova.openstack.common import log as logging
+from nova.virt.baremetal import db
+
+LOG = logging.getLogger(__name__)
+authorize = extensions.extension_authorizer('compute', 'baremetal_nodes')
+
+node_fields = ['id', 'cpus', 'local_gb', 'memory_mb', 'pm_address',
+ 'pm_user', 'prov_mac_address', 'prov_vlan_id',
+ 'service_host', 'terminal_port', 'instance_uuid',
+ ]
+
+interface_fields = ['id', 'address', 'datapath_id', 'port_no']
+
+
+def _node_dict(node_ref):
+ d = {}
+ for f in node_fields:
+ d[f] = node_ref.get(f)
+ return d
+
+
+def _interface_dict(interface_ref):
+ d = {}
+ for f in interface_fields:
+ d[f] = interface_ref.get(f)
+ return d
+
+
+def _make_node_elem(elem):
+ for f in node_fields:
+ elem.set(f)
+
+
+def _make_interface_elem(elem):
+ for f in interface_fields:
+ elem.set(f)
+
+
+class NodeTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ node_elem = xmlutil.TemplateElement('node', selector='node')
+ _make_node_elem(node_elem)
+ ifs_elem = xmlutil.TemplateElement('interfaces')
+ if_elem = xmlutil.SubTemplateElement(ifs_elem, 'interface',
+ selector='interfaces')
+ _make_interface_elem(if_elem)
+ node_elem.append(ifs_elem)
+ return xmlutil.MasterTemplate(node_elem, 1)
+
+
+class NodesTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('nodes')
+ node_elem = xmlutil.SubTemplateElement(root, 'node', selector='nodes')
+ _make_node_elem(node_elem)
+ ifs_elem = xmlutil.TemplateElement('interfaces')
+ if_elem = xmlutil.SubTemplateElement(ifs_elem, 'interface',
+ selector='interfaces')
+ _make_interface_elem(if_elem)
+ node_elem.append(ifs_elem)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class InterfaceTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('interface', selector='interface')
+ _make_interface_elem(root)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class BareMetalNodeController(wsgi.Controller):
+ """The Bare-Metal Node API controller for the OpenStack API."""
+
+ @wsgi.serializers(xml=NodesTemplate)
+ def index(self, req):
+ context = req.environ['nova.context']
+ authorize(context)
+ nodes_from_db = db.bm_node_get_all(context)
+ nodes = []
+ for node_from_db in nodes_from_db:
+ try:
+ ifs = db.bm_interface_get_all_by_bm_node_id(
+ context, node_from_db['id'])
+ except exception.InstanceNotFound:
+ ifs = []
+ node = _node_dict(node_from_db)
+ node['interfaces'] = [_interface_dict(i) for i in ifs]
+ nodes.append(node)
+ return {'nodes': nodes}
+
+ @wsgi.serializers(xml=NodeTemplate)
+ def show(self, req, id):
+ context = req.environ['nova.context']
+ authorize(context)
+ try:
+ node = db.bm_node_get(context, id)
+ except exception.InstanceNotFound:
+ raise webob.exc.HTTPNotFound
+ try:
+ ifs = db.bm_interface_get_all_by_bm_node_id(context, id)
+ except exception.InstanceNotFound:
+ ifs = []
+ node = _node_dict(node)
+ node['interfaces'] = [_interface_dict(i) for i in ifs]
+ return {'node': node}
+
+ @wsgi.serializers(xml=NodeTemplate)
+ def create(self, req, body):
+ context = req.environ['nova.context']
+ authorize(context)
+ node = db.bm_node_create(context, body['node'])
+ node = _node_dict(node)
+ node['interfaces'] = []
+ return {'node': node}
+
+ def delete(self, req, id):
+ context = req.environ['nova.context']
+ authorize(context)
+ try:
+ db.bm_node_destroy(context, id)
+ except exception.InstanceNotFound:
+ raise webob.exc.HTTPNotFound
+ return webob.Response(status_int=202)
+
+ def _check_node_exists(self, context, node_id):
+ try:
+ db.bm_node_get(context, node_id)
+ except exception.InstanceNotFound:
+ raise webob.exc.HTTPNotFound
+
+ @wsgi.serializers(xml=InterfaceTemplate)
+ @wsgi.action('add_interface')
+ def _add_interface(self, req, id, body):
+ context = req.environ['nova.context']
+ authorize(context)
+ self._check_node_exists(context, id)
+ body = body['add_interface']
+ address = body['address']
+ datapath_id = body.get('datapath_id')
+ port_no = body.get('port_no')
+ if_id = db.bm_interface_create(context,
+ bm_node_id=id,
+ address=address,
+ datapath_id=datapath_id,
+ port_no=port_no)
+ if_ref = db.bm_interface_get(context, if_id)
+ return {'interface': _interface_dict(if_ref)}
+
+ @wsgi.response(202)
+ @wsgi.action('remove_interface')
+ def _remove_interface(self, req, id, body):
+ context = req.environ['nova.context']
+ authorize(context)
+ self._check_node_exists(context, id)
+ body = body['remove_interface']
+ print "body(%s)" % body
+ if_id = body.get('id')
+ address = body.get('address')
+ if not if_id and not address:
+ raise webob.exc.HTTPBadRequest(
+ explanation=_("Must specify id or address"))
+ ifs = db.bm_interface_get_all_by_bm_node_id(context, id)
+ for i in ifs:
+ if if_id and if_id != i['id']:
+ continue
+ if address and address != i['address']:
+ continue
+ db.bm_interface_destroy(context, i['id'])
+ return webob.Response(status_int=202)
+ raise webob.exc.HTTPNotFound
+
+
+class Baremetal_nodes(extensions.ExtensionDescriptor):
+ """Admin-only bare-metal node administration."""
+
+ name = "BareMetalNodes"
+ alias = "os-baremetal-nodes"
+ namespace = "http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2"
+ updated = "2013-01-04T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+ res = extensions.ResourceExtension('os-baremetal-nodes',
+ BareMetalNodeController(),
+ member_actions={"action": "POST", })
+ resources.append(res)
+ return resources
diff --git a/nova/api/openstack/compute/contrib/coverage_ext.py b/nova/api/openstack/compute/contrib/coverage_ext.py
index 4b7d4e57f..bc4d0f0f9 100644
--- a/nova/api/openstack/compute/contrib/coverage_ext.py
+++ b/nova/api/openstack/compute/contrib/coverage_ext.py
@@ -23,7 +23,7 @@ import sys
import telnetlib
import tempfile
-from coverage import coverage
+import coverage
from webob import exc
from nova.api.openstack import extensions
@@ -47,7 +47,7 @@ class CoverageController(object):
def __init__(self):
self.data_path = tempfile.mkdtemp(prefix='nova-coverage_')
data_out = os.path.join(self.data_path, '.nova-coverage')
- self.coverInst = coverage(data_file=data_out)
+ self.coverInst = coverage.coverage(data_file=data_out)
self.compute_api = compute_api.API()
self.network_api = network_api.API()
self.conductor_api = conductor_api.API()
diff --git a/nova/api/openstack/compute/server_metadata.py b/nova/api/openstack/compute/server_metadata.py
index 023a054d0..0de5d536f 100644
--- a/nova/api/openstack/compute/server_metadata.py
+++ b/nova/api/openstack/compute/server_metadata.py
@@ -136,6 +136,10 @@ class Controller(object):
raise exc.HTTPRequestEntityTooLarge(explanation=unicode(error),
headers={'Retry-After': 0})
+ except exception.InstanceInvalidState as state_error:
+ common.raise_http_conflict_for_instance_invalid_state(state_error,
+ 'update metadata')
+
@wsgi.serializers(xml=common.MetaItemTemplate)
def show(self, req, server_id, id):
"""Return a single metadata item."""
@@ -162,10 +166,15 @@ class Controller(object):
try:
server = self.compute_api.get(context, server_id)
self.compute_api.delete_instance_metadata(context, server, id)
+
except exception.InstanceNotFound:
msg = _('Server does not exist')
raise exc.HTTPNotFound(explanation=msg)
+ except exception.InstanceInvalidState as state_error:
+ common.raise_http_conflict_for_instance_invalid_state(state_error,
+ 'delete metadata')
+
def create_resource():
return wsgi.Resource(Controller())
diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py
index 308530b8e..93a07ec3f 100644
--- a/nova/api/openstack/compute/servers.py
+++ b/nova/api/openstack/compute/servers.py
@@ -540,8 +540,9 @@ class Controller(wsgi.Controller):
msg = _('marker [%s] not found') % marker
raise exc.HTTPBadRequest(explanation=msg)
except exception.FlavorNotFound as e:
- msg = _("Flavor could not be found")
- raise exc.HTTPUnprocessableEntity(explanation=msg)
+ log_msg = _("Flavor '%s' could not be found ")
+ LOG.debug(log_msg, search_opts['flavor'])
+ instance_list = []
if is_detail:
self._add_instance_faults(context, instance_list)
@@ -905,6 +906,8 @@ class Controller(wsgi.Controller):
raise exc.HTTPBadRequest(explanation=unicode(error))
except exception.InvalidMetadataSize as error:
raise exc.HTTPRequestEntityTooLarge(explanation=unicode(error))
+ except exception.InvalidRequest as error:
+ raise exc.HTTPBadRequest(explanation=unicode(error))
except exception.ImageNotFound as error:
msg = _("Can not find requested image")
raise exc.HTTPBadRequest(explanation=msg)
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 733685b14..8b593d742 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -406,6 +406,8 @@ class XMLDictSerializer(DictSerializer):
if k in attrs:
result.setAttribute(k, str(v))
else:
+ if k == "deleted":
+ v = str(bool(v))
node = self._to_xml_node(doc, metadata, k, v)
result.appendChild(node)
else:
diff --git a/nova/availability_zones.py b/nova/availability_zones.py
index 7493ce5c5..09cbd98b8 100644
--- a/nova/availability_zones.py
+++ b/nova/availability_zones.py
@@ -46,7 +46,7 @@ def set_availability_zones(context, services):
az = CONF.internal_service_availability_zone
if service['topic'] == "compute":
if metadata.get(service['host']):
- az = str(metadata[service['host']])[5:-2]
+ az = u','.join(list(metadata[service['host']]))
else:
az = CONF.default_availability_zone
service['availability_zone'] = az
@@ -55,8 +55,30 @@ def set_availability_zones(context, services):
def get_host_availability_zone(context, host):
metadata = db.aggregate_metadata_get_by_host(
- context.get_admin_context(), host, key='availability_zone')
+ context, host, key='availability_zone')
if 'availability_zone' in metadata:
return list(metadata['availability_zone'])[0]
else:
return CONF.default_availability_zone
+
+
+def get_availability_zones(context):
+ """Return available and unavailable zones."""
+ enabled_services = db.service_get_all(context, False)
+ disabled_services = db.service_get_all(context, True)
+ enabled_services = set_availability_zones(context, enabled_services)
+ disabled_services = set_availability_zones(context, disabled_services)
+
+ available_zones = []
+ for zone in [service['availability_zone'] for service
+ in enabled_services]:
+ if not zone in available_zones:
+ available_zones.append(zone)
+
+ not_available_zones = []
+ zones = [service['available_zones'] for service in disabled_services
+ if service['available_zones'] not in available_zones]
+ for zone in zones:
+ if zone not in not_available_zones:
+ not_available_zones.append(zone)
+ return (available_zones, not_available_zones)
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 4b15a3e27..06ce2e07e 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -433,7 +433,11 @@ class API(base.Base):
max_count = min_count
block_device_mapping = block_device_mapping or []
-
+ if min_count > 1 or max_count > 1:
+ if any(map(lambda bdm: 'volume_id' in bdm, block_device_mapping)):
+ msg = _('Cannot attach one or more volumes to multiple'
+ ' instances')
+ raise exception.InvalidRequest(msg)
if instance_type['disabled']:
raise exception.InstanceTypeNotFound(
instance_type_id=instance_type['id'])
@@ -1353,9 +1357,6 @@ class API(base.Base):
None if rotation shouldn't be used (as in the case of snapshots)
:param extra_properties: dict of extra image properties to include
"""
- instance = self.update(context, instance,
- task_state=task_states.IMAGE_BACKUP,
- expected_task_state=None)
if image_id:
# The image entry has already been created, so just pull the
# metadata.
@@ -1364,13 +1365,19 @@ class API(base.Base):
image_meta = self._create_image(context, instance, name,
'backup', backup_type=backup_type,
rotation=rotation, extra_properties=extra_properties)
+
+ instance = self.update(context, instance,
+ task_state=task_states.IMAGE_BACKUP,
+ expected_task_state=None)
+
self.compute_rpcapi.snapshot_instance(context, instance=instance,
image_id=image_meta['id'], image_type='backup',
backup_type=backup_type, rotation=rotation)
return image_meta
@wrap_check_policy
- @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED])
+ @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED,
+ vm_states.PAUSED, vm_states.SUSPENDED])
def snapshot(self, context, instance, name, extra_properties=None,
image_id=None):
"""Snapshot the given instance.
@@ -1381,9 +1388,6 @@ class API(base.Base):
:returns: A dict containing image metadata
"""
- instance = self.update(context, instance,
- task_state=task_states.IMAGE_SNAPSHOT,
- expected_task_state=None)
if image_id:
# The image entry has already been created, so just pull the
# metadata.
@@ -1391,6 +1395,11 @@ class API(base.Base):
else:
image_meta = self._create_image(context, instance, name,
'snapshot', extra_properties=extra_properties)
+
+ instance = self.update(context, instance,
+ task_state=task_states.IMAGE_SNAPSHOT,
+ expected_task_state=None)
+
self.compute_rpcapi.snapshot_instance(context, instance=instance,
image_id=image_meta['id'], image_type='snapshot')
return image_meta
@@ -2198,6 +2207,9 @@ class API(base.Base):
@wrap_check_policy
@check_instance_lock
+ @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED,
+ vm_states.SUSPENDED, vm_states.STOPPED],
+ task_state=None)
def delete_instance_metadata(self, context, instance, key):
"""Delete the given metadata item from an instance."""
self.db.instance_metadata_delete(context, instance['uuid'], key)
@@ -2209,6 +2221,9 @@ class API(base.Base):
@wrap_check_policy
@check_instance_lock
+ @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED,
+ vm_states.SUSPENDED, vm_states.STOPPED],
+ task_state=None)
def update_instance_metadata(self, context, instance,
metadata, delete=False):
"""Updates or creates instance metadata.
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 03c54a363..275611cdf 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -172,7 +172,6 @@ CONF.import_opt('allow_resize_to_same_host', 'nova.compute.api')
CONF.import_opt('console_topic', 'nova.console.rpcapi')
CONF.import_opt('host', 'nova.netconf')
CONF.import_opt('my_ip', 'nova.netconf')
-CONF.import_opt('network_manager', 'nova.service')
QUOTAS = quota.QUOTAS
@@ -301,8 +300,6 @@ class ComputeManager(manager.SchedulerDependentManager):
self.driver = driver.load_compute_driver(self.virtapi, compute_driver)
self.network_api = network.API()
self.volume_api = volume.API()
- self.network_manager = importutils.import_object(
- CONF.network_manager, host=kwargs.get('host', None))
self._last_host_check = 0
self._last_bw_usage_poll = 0
self._last_vol_usage_poll = 0
@@ -1135,8 +1132,7 @@ class ComputeManager(manager.SchedulerDependentManager):
vm_state=vm_states.DELETED,
task_state=None,
terminated_at=timeutils.utcnow())
- system_meta = compute_utils.metadata_to_dict(
- instance['system_metadata'])
+ system_meta = utils.metadata_to_dict(instance['system_metadata'])
self.conductor_api.instance_destroy(context, instance)
# ensure block device mappings are not leaked
@@ -1678,8 +1674,7 @@ class ComputeManager(manager.SchedulerDependentManager):
def _get_rescue_image_ref(self, context, instance):
"""Determine what image should be used to boot the rescue VM."""
- system_meta = compute_utils.metadata_to_dict(
- instance['system_metadata'])
+ system_meta = utils.metadata_to_dict(instance['system_metadata'])
rescue_image_ref = system_meta.get('image_base_image_ref')
@@ -2566,7 +2561,7 @@ class ComputeManager(manager.SchedulerDependentManager):
mp)
except Exception: # pylint: disable=W0702
with excutils.save_and_reraise_exception():
- msg = _("Faild to detach volume %(volume_id)s from %(mp)s")
+ msg = _("Failed to detach volume %(volume_id)s from %(mp)s")
LOG.exception(msg % locals(), context=context,
instance=instance)
volume = self.volume_api.get(context, volume_id)
@@ -2872,9 +2867,12 @@ class ComputeManager(manager.SchedulerDependentManager):
self.network_api.migrate_instance_finish(context, instance, migration)
network_info = self._get_instance_nw_info(context, instance)
+ block_device_info = self._get_instance_volume_block_device_info(
+ context, instance)
+
self.driver.post_live_migration_at_destination(context, instance,
self._legacy_nw_info(network_info),
- block_migration)
+ block_migration, block_device_info)
# Restore instance state
current_power_state = self._get_power_state(context, instance)
instance = self._instance_update(context, instance['uuid'],
@@ -3382,10 +3380,8 @@ class ComputeManager(manager.SchedulerDependentManager):
LOG.exception(_("error during stop() in "
"sync_power_state."),
instance=db_instance)
- elif vm_power_state in (power_state.PAUSED,
- power_state.SUSPENDED):
- LOG.warn(_("Instance is paused or suspended "
- "unexpectedly. Calling "
+ elif vm_power_state == power_state.SUSPENDED:
+ LOG.warn(_("Instance is suspended unexpectedly. Calling "
"the stop API."), instance=db_instance)
try:
self.compute_api.stop(context, db_instance)
@@ -3393,6 +3389,16 @@ class ComputeManager(manager.SchedulerDependentManager):
LOG.exception(_("error during stop() in "
"sync_power_state."),
instance=db_instance)
+ elif vm_power_state == power_state.PAUSED:
+ # Note(maoy): a VM may get into the paused state not only
+ # because the user request via API calls, but also
+ # due to (temporary) external instrumentations.
+ # Before the virt layer can reliably report the reason,
+ # we simply ignore the state discrepancy. In many cases,
+ # the VM state will go back to running after the external
+ # instrumentation is done. See bug 1097806 for details.
+ LOG.warn(_("Instance is paused unexpectedly. Ignore."),
+ instance=db_instance)
elif vm_state == vm_states.STOPPED:
if vm_power_state not in (power_state.NOSTATE,
power_state.SHUTDOWN,
diff --git a/nova/compute/utils.py b/nova/compute/utils.py
index 2b1286e16..1874e886f 100644
--- a/nova/compute/utils.py
+++ b/nova/compute/utils.py
@@ -37,13 +37,6 @@ CONF.import_opt('host', 'nova.netconf')
LOG = log.getLogger(__name__)
-def metadata_to_dict(metadata):
- result = {}
- for item in metadata:
- result[item['key']] = item['value']
- return result
-
-
def add_instance_fault_from_exc(context, instance, fault, exc_info=None):
"""Adds the specified fault to the database."""
@@ -159,7 +152,8 @@ def notify_usage_exists(context, instance_ref, current_period=False,
ignore_missing_network_data)
if system_metadata is None:
- system_metadata = metadata_to_dict(instance_ref['system_metadata'])
+ system_metadata = utils.metadata_to_dict(
+ instance_ref['system_metadata'])
# add image metadata to the notification:
image_meta = notifications.image_meta(system_metadata)
diff --git a/nova/conductor/api.py b/nova/conductor/api.py
index 138e72f70..d05c94877 100644
--- a/nova/conductor/api.py
+++ b/nova/conductor/api.py
@@ -117,6 +117,11 @@ class LocalAPI(object):
return self._manager.instance_get_active_by_window(
context, begin, end, project_id, host)
+ def instance_get_active_by_window_joined(self, context, begin, end=None,
+ project_id=None, host=None):
+ return self._manager.instance_get_active_by_window_joined(
+ context, begin, end, project_id, host)
+
def instance_info_cache_update(self, context, instance, values):
return self._manager.instance_info_cache_update(context,
instance,
@@ -369,6 +374,11 @@ class API(object):
return self.conductor_rpcapi.instance_get_active_by_window(
context, begin, end, project_id, host)
+ def instance_get_active_by_window_joined(self, context, begin, end=None,
+ project_id=None, host=None):
+ return self.conductor_rpcapi.instance_get_active_by_window_joined(
+ context, begin, end, project_id, host)
+
def instance_info_cache_update(self, context, instance, values):
return self.conductor_rpcapi.instance_info_cache_update(context,
instance, values)
diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py
index 0ff2e1400..87b143912 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.34'
+ RPC_API_VERSION = '1.35'
def __init__(self, *args, **kwargs):
super(ConductorManager, self).__init__(service_name='conductor',
@@ -234,10 +234,14 @@ class ConductorManager(manager.SchedulerDependentManager):
def instance_get_active_by_window(self, context, begin, end=None,
project_id=None, host=None):
- result = self.db.instance_get_active_by_window_joined(context,
- begin, end,
- project_id,
- host)
+ result = self.db.instance_get_active_by_window(context, begin, end,
+ project_id, host)
+ return jsonutils.to_primitive(result)
+
+ def instance_get_active_by_window_joined(self, context, begin, end=None,
+ project_id=None, host=None):
+ result = self.db.instance_get_active_by_window_joined(
+ context, begin, end, project_id, host)
return jsonutils.to_primitive(result)
def instance_destroy(self, context, instance):
diff --git a/nova/conductor/rpcapi.py b/nova/conductor/rpcapi.py
index 6dc8aef04..1699c85ed 100644
--- a/nova/conductor/rpcapi.py
+++ b/nova/conductor/rpcapi.py
@@ -67,6 +67,7 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy):
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
+ 1.35 - Added instance_get_active_by_window_joined
"""
BASE_RPC_API_VERSION = '1.0'
@@ -241,6 +242,13 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy):
host=host)
return self.call(context, msg, version='1.15')
+ def instance_get_active_by_window_joined(self, context, begin, end=None,
+ project_id=None, host=None):
+ msg = self.make_msg('instance_get_active_by_window_joined',
+ begin=begin, end=end, project_id=project_id,
+ host=host)
+ return self.call(context, msg, version='1.35')
+
def instance_destroy(self, context, instance):
instance_p = jsonutils.to_primitive(instance)
msg = self.make_msg('instance_destroy', instance=instance_p)
diff --git a/nova/crypto.py b/nova/crypto.py
index 68d25e650..5c48c60b6 100644
--- a/nova/crypto.py
+++ b/nova/crypto.py
@@ -135,13 +135,14 @@ def generate_fingerprint(public_key):
raise exception.InvalidKeypair()
-def generate_key_pair(bits=1024):
- # what is the magic 65537?
-
+def generate_key_pair(bits=None):
with utils.tempdir() as tmpdir:
keyfile = os.path.join(tmpdir, 'temp')
- utils.execute('ssh-keygen', '-q', '-b', bits, '-N', '',
- '-t', 'rsa', '-f', keyfile, '-C', 'Generated by Nova')
+ args = ['ssh-keygen', '-q', '-N', '', '-t', 'rsa',
+ '-f', keyfile, '-C', 'Generated by Nova']
+ if bits is not None:
+ args.extend(['-b', bits])
+ utils.execute(*args)
fingerprint = _generate_fingerprint('%s.pub' % (keyfile))
if not os.path.exists(keyfile):
raise exception.FileNotFound(keyfile)
diff --git a/nova/db/api.py b/nova/db/api.py
index d8a16c52d..6af81e5f9 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -1705,16 +1705,14 @@ def task_log_end_task(context, task_name,
period_ending,
host,
errors,
- message=None,
- session=None):
+ message=None):
"""Mark a task as complete for a given host/time period."""
return IMPL.task_log_end_task(context, task_name,
period_beginning,
period_ending,
host,
errors,
- message,
- session)
+ message)
def task_log_begin_task(context, task_name,
@@ -1722,25 +1720,23 @@ def task_log_begin_task(context, task_name,
period_ending,
host,
task_items=None,
- message=None,
- session=None):
+ message=None):
"""Mark a task as started for a given host/time period."""
return IMPL.task_log_begin_task(context, task_name,
period_beginning,
period_ending,
host,
task_items,
- message,
- session)
+ message)
def task_log_get_all(context, task_name, period_beginning,
- period_ending, host=None, state=None, session=None):
+ period_ending, host=None, state=None):
return IMPL.task_log_get_all(context, task_name, period_beginning,
- period_ending, host, state, session)
+ period_ending, host, state)
def task_log_get(context, task_name, period_beginning,
- period_ending, host, state=None, session=None):
+ period_ending, host, state=None):
return IMPL.task_log_get(context, task_name, period_beginning,
- period_ending, host, state, session)
+ period_ending, host, state)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 5317487cd..a6f585eef 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -35,7 +35,7 @@ from sqlalchemy.sql.expression import desc
from sqlalchemy.sql import func
from nova import block_device
-from nova.common.sqlalchemyutils import paginate_query
+from nova.common import sqlalchemyutils
from nova.compute import task_states
from nova.compute import vm_states
from nova import db
@@ -172,27 +172,43 @@ def model_query(context, model, *args, **kwargs):
:param project_only: if present and context is user-type, then restrict
query to match the context's project_id. If set to 'allow_none',
restriction includes project_id = None.
+ :param base_model: Where model_query is passed a "model" parameter which is
+ not a subclass of NovaBase, we should pass an extra base_model
+ parameter that is a subclass of NovaBase and corresponds to the
+ model parameter.
"""
session = kwargs.get('session') or get_session()
read_deleted = kwargs.get('read_deleted') or context.read_deleted
project_only = kwargs.get('project_only', False)
+ def issubclassof_nova_base(obj):
+ return isinstance(obj, type) and issubclass(obj, models.NovaBase)
+
+ base_model = model
+ if not issubclassof_nova_base(base_model):
+ base_model = kwargs.get('base_model', None)
+ if not issubclassof_nova_base(base_model):
+ raise Exception(_("model or base_model parameter should be "
+ "subclass of NovaBase"))
+
query = session.query(model, *args)
+ default_deleted_value = base_model.__mapper__.c.deleted.default.arg
if read_deleted == 'no':
- query = query.filter_by(deleted=False)
+ query = query.filter(base_model.deleted == default_deleted_value)
elif read_deleted == 'yes':
pass # omit the filter to include deleted and active
elif read_deleted == 'only':
- query = query.filter_by(deleted=True)
+ query = query.filter(base_model.deleted != default_deleted_value)
else:
- raise Exception(
- _("Unrecognized read_deleted value '%s'") % read_deleted)
+ raise Exception(_("Unrecognized read_deleted value '%s'")
+ % read_deleted)
if is_user_context(context) and project_only:
if project_only == 'allow_none':
- query = query.filter(or_(model.project_id == context.project_id,
- model.project_id == None))
+ query = query.\
+ filter(or_(base_model.project_id == context.project_id,
+ base_model.project_id == None))
else:
query = query.filter_by(project_id=context.project_id)
@@ -408,7 +424,8 @@ def service_get_all_compute_sorted(context):
label = 'instance_cores'
subq = model_query(context, models.Instance.host,
func.sum(models.Instance.vcpus).label(label),
- session=session, read_deleted="no").\
+ base_model=models.Instance, session=session,
+ read_deleted="no").\
group_by(models.Instance.host).\
subquery()
return _service_get_all_topic_subquery(context,
@@ -540,7 +557,7 @@ def _update_stats(context, new_stats, compute_id, session, prune_stats=False):
# prune un-touched old stats:
for stat in statmap.values():
session.add(stat)
- stat.update({'deleted': True})
+ stat.soft_delete(session=session)
# add new and updated stats
for stat in stats:
@@ -563,10 +580,9 @@ def compute_node_update(context, compute_id, values, prune_stats=False):
def compute_node_get_by_host(context, host):
"""Get all capacity entries for the given host."""
- result = model_query(context, models.ComputeNode).\
+ result = model_query(context, models.ComputeNode, read_deleted="no").\
join('service').\
filter(models.Service.host == host).\
- filter_by(deleted=False).\
first()
return result
@@ -586,6 +602,7 @@ def compute_node_statistics(context):
func.sum(models.ComputeNode.current_workload),
func.sum(models.ComputeNode.running_vms),
func.sum(models.ComputeNode.disk_available_least),
+ base_model=models.ComputeNode,
read_deleted="no").first()
# Build a dict of the info--making no assumptions about result
@@ -660,7 +677,8 @@ def floating_ip_get(context, id):
@require_context
def floating_ip_get_pools(context):
pools = []
- for result in model_query(context, models.FloatingIp.pool).distinct():
+ for result in model_query(context, models.FloatingIp.pool,
+ base_model=models.FloatingIp).distinct():
pools.append({'name': result[0]})
return pools
@@ -1094,30 +1112,31 @@ def fixed_ip_disassociate_all_by_timeout(context, host, time):
# host; i.e. the network host or the instance
# host matches. Two queries necessary because
# join with update doesn't work.
- host_filter = or_(and_(models.Instance.host == host,
- models.Network.multi_host == True),
- models.Network.host == host)
- result = session.query(models.FixedIp.id).\
- filter(models.FixedIp.deleted == False).\
- filter(models.FixedIp.allocated == False).\
- filter(models.FixedIp.updated_at < time).\
- join((models.Network,
- models.Network.id == models.FixedIp.network_id)).\
- join((models.Instance,
- models.Instance.uuid ==
- models.FixedIp.instance_uuid)).\
- filter(host_filter).\
- all()
- fixed_ip_ids = [fip[0] for fip in result]
- if not fixed_ip_ids:
- return 0
- result = model_query(context, models.FixedIp, session=session).\
- filter(models.FixedIp.id.in_(fixed_ip_ids)).\
- update({'instance_uuid': None,
- 'leased': False,
- 'updated_at': timeutils.utcnow()},
- synchronize_session='fetch')
- return result
+ with session.begin():
+ host_filter = or_(and_(models.Instance.host == host,
+ models.Network.multi_host == True),
+ models.Network.host == host)
+ result = model_query(context, models.FixedIp.id,
+ base_model=models.FixedIp, read_deleted="no",
+ session=session).\
+ filter(models.FixedIp.allocated == False).\
+ filter(models.FixedIp.updated_at < time).\
+ join((models.Network,
+ models.Network.id == models.FixedIp.network_id)).\
+ join((models.Instance,
+ models.Instance.uuid == models.FixedIp.instance_uuid)).\
+ filter(host_filter).\
+ all()
+ fixed_ip_ids = [fip[0] for fip in result]
+ if not fixed_ip_ids:
+ return 0
+ result = model_query(context, models.FixedIp, session=session).\
+ filter(models.FixedIp.id.in_(fixed_ip_ids)).\
+ update({'instance_uuid': None,
+ 'leased': False,
+ 'updated_at': timeutils.utcnow()},
+ synchronize_session='fetch')
+ return result
@require_context
@@ -1468,7 +1487,7 @@ def instance_data_get_for_project(context, project_id, session=None):
func.count(models.Instance.id),
func.sum(models.Instance.vcpus),
func.sum(models.Instance.memory_mb),
- read_deleted="no",
+ base_model=models.Instance,
session=session).\
filter_by(project_id=project_id).\
first()
@@ -1593,12 +1612,12 @@ def instance_get_all_by_filters(context, filters, sort_key, sort_dir,
# Instances can be soft or hard deleted and the query needs to
# include or exclude both
if filters.pop('deleted'):
- deleted = or_(models.Instance.deleted == True,
+ deleted = or_(models.Instance.deleted == models.Instance.id,
models.Instance.vm_state == vm_states.SOFT_DELETED)
query_prefix = query_prefix.filter(deleted)
else:
query_prefix = query_prefix.\
- filter_by(deleted=False).\
+ filter_by(deleted=0).\
filter(models.Instance.vm_state != vm_states.SOFT_DELETED)
if not context.is_admin:
@@ -1626,7 +1645,8 @@ def instance_get_all_by_filters(context, filters, sort_key, sort_dir,
marker = _instance_get_by_uuid(context, marker, session=session)
except exception.InstanceNotFound:
raise exception.MarkerNotFound(marker)
- query_prefix = paginate_query(query_prefix, models.Instance, limit,
+ query_prefix = sqlalchemyutils.paginate_query(query_prefix,
+ models.Instance, limit,
[sort_key, 'created_at', 'id'],
marker=marker,
sort_dir=sort_dir)
@@ -1695,6 +1715,7 @@ def instance_get_active_by_window_joined(context, begin, end=None,
options(joinedload('security_groups')).\
options(joinedload('metadata')).\
options(joinedload('instance_type')).\
+ options(joinedload('system_metadata')).\
filter(or_(models.Instance.terminated_at == None,
models.Instance.terminated_at > begin))
if end:
@@ -2122,19 +2143,21 @@ def network_create_safe(context, values):
def network_delete_safe(context, network_id):
session = get_session()
with session.begin():
- result = session.query(models.FixedIp).\
+ result = model_query(context, models.FixedIp, session=session,
+ read_deleted="no").\
filter_by(network_id=network_id).\
- filter_by(deleted=False).\
filter_by(allocated=True).\
count()
if result != 0:
raise exception.NetworkInUse(network_id=network_id)
network_ref = network_get(context, network_id=network_id,
session=session)
- session.query(models.FixedIp).\
+
+ model_query(context, models.FixedIp, session=session,
+ read_deleted="no").\
filter_by(network_id=network_id).\
- filter_by(deleted=False).\
soft_delete()
+
session.delete(network_ref)
@@ -2213,9 +2236,9 @@ def network_get_associated_fixed_ips(context, network_id, host=None):
# without regenerating the whole list
vif_and = and_(models.VirtualInterface.id ==
models.FixedIp.virtual_interface_id,
- models.VirtualInterface.deleted == False)
+ models.VirtualInterface.deleted == 0)
inst_and = and_(models.Instance.uuid == models.FixedIp.instance_uuid,
- models.Instance.deleted == False)
+ models.Instance.deleted == 0)
session = get_session()
query = session.query(models.FixedIp.address,
models.FixedIp.instance_uuid,
@@ -2225,7 +2248,7 @@ def network_get_associated_fixed_ips(context, network_id, host=None):
models.Instance.hostname,
models.Instance.updated_at,
models.Instance.created_at).\
- filter(models.FixedIp.deleted == False).\
+ filter(models.FixedIp.deleted == 0).\
filter(models.FixedIp.network_id == network_id).\
filter(models.FixedIp.allocated == True).\
join((models.VirtualInterface, vif_and)).\
@@ -2326,6 +2349,7 @@ def network_get_all_by_host(context, host):
fixed_host_filter = or_(models.FixedIp.host == host,
models.Instance.host == host)
fixed_ip_query = model_query(context, models.FixedIp.network_id,
+ base_model=models.FixedIp,
session=session).\
outerjoin((models.VirtualInterface,
models.VirtualInterface.id ==
@@ -3138,13 +3162,14 @@ def security_group_in_use(context, group_id):
with session.begin():
# Are there any instances that haven't been deleted
# that include this group?
- inst_assoc = session.query(models.SecurityGroupInstanceAssociation).\
- filter_by(security_group_id=group_id).\
- filter_by(deleted=False).\
- all()
+ inst_assoc = model_query(context,
+ models.SecurityGroupInstanceAssociation,
+ read_deleted="no", session=session).\
+ filter_by(security_group_id=group_id).\
+ all()
for ia in inst_assoc:
- num_instances = session.query(models.Instance).\
- filter_by(deleted=False).\
+ num_instances = model_query(context, models.Instance,
+ session=session, read_deleted="no").\
filter_by(uuid=ia.instance_uuid).\
count()
if num_instances:
@@ -3595,7 +3620,7 @@ def instance_type_get_all(context, inactive=False, filters=None):
if filters['is_public'] and context.project_id is not None:
the_filter.extend([
models.InstanceTypes.projects.any(
- project_id=context.project_id, deleted=False)
+ project_id=context.project_id, deleted=0)
])
if len(the_filter) > 1:
query = query.filter(or_(*the_filter))
@@ -4037,7 +4062,8 @@ def _instance_type_extra_specs_get_query(context, flavor_id,
session=None):
# Two queries necessary because join with update doesn't work.
t = model_query(context, models.InstanceTypes.id,
- session=session, read_deleted="no").\
+ base_model=models.InstanceTypes, session=session,
+ read_deleted="no").\
filter(models.InstanceTypes.flavorid == flavor_id).\
subquery()
return model_query(context, models.InstanceTypeExtraSpecs,
@@ -4091,6 +4117,7 @@ def instance_type_extra_specs_update_or_create(context, flavor_id, specs):
session = get_session()
with session.begin():
instance_type_id = model_query(context, models.InstanceTypes.id,
+ base_model=models.InstanceTypes,
session=session, read_deleted="no").\
filter(models.InstanceTypes.flavorid == flavor_id).\
first()
@@ -4745,15 +4772,15 @@ def _task_log_get_query(context, task_name, period_beginning,
@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()
+ return _task_log_get_query(context, 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):
- return _task_log_get_query(task_name, period_beginning, period_ending,
- host, state).all()
+ return _task_log_get_query(context, task_name, period_beginning,
+ period_ending, host, state).all()
@require_admin_context
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/151_change_task_log_column_type.py b/nova/db/sqlalchemy/migrate_repo/versions/151_change_task_log_column_type.py
new file mode 100644
index 000000000..44c3aa41f
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/151_change_task_log_column_type.py
@@ -0,0 +1,52 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (C) 2013 Wenhao Xu <xuwenhao2008@gmail.com>.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from sqlalchemy import MetaData, String, Table, DateTime
+from sqlalchemy.dialects import postgresql
+
+
+def upgrade(migrate_engine):
+ """Convert period_beginning and period_ending to DateTime."""
+ meta = MetaData()
+ meta.bind = migrate_engine
+ dialect = migrate_engine.url.get_dialect()
+
+ if dialect is postgresql.dialect:
+ # We need to handle postresql specially.
+ # Can't use migrate's alter() because it does not support
+ # explicit casting
+ for column in ('period_beginning', 'period_ending'):
+ migrate_engine.execute(
+ "ALTER TABLE task_log "
+ "ALTER COLUMN %s TYPE TIMESTAMP WITHOUT TIME ZONE "
+ "USING %s::TIMESTAMP WITHOUT TIME ZONE"
+ % (column, column))
+ else:
+ migrations = Table('task_log', meta, autoload=True)
+ migrations.c.period_beginning.alter(DateTime)
+ migrations.c.period_ending.alter(DateTime)
+
+
+def downgrade(migrate_engine):
+ """Convert columns back to String(255)."""
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ # don't need to handle postgresql here.
+ migrations = Table('task_log', meta, autoload=True)
+ migrations.c.period_beginning.alter(String(255))
+ migrations.c.period_ending.alter(String(255))
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/152_change_type_of_deleted_column.py b/nova/db/sqlalchemy/migrate_repo/versions/152_change_type_of_deleted_column.py
new file mode 100644
index 000000000..d4bd991f7
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/152_change_type_of_deleted_column.py
@@ -0,0 +1,226 @@
+from sqlalchemy import CheckConstraint
+from sqlalchemy.engine import reflection
+from sqlalchemy.ext.compiler import compiles
+from sqlalchemy import MetaData, Table, Column, Index
+from sqlalchemy import select
+from sqlalchemy.sql.expression import UpdateBase
+from sqlalchemy.sql import literal_column
+from sqlalchemy import String, Integer, Boolean
+from sqlalchemy.types import NullType, BigInteger
+
+
+all_tables = ['services', 'compute_nodes', 'compute_node_stats',
+ 'certificates', 'instances', 'instance_info_caches',
+ 'instance_types', 'volumes', 'quotas', 'quota_classes',
+ 'quota_usages', 'reservations', 'snapshots',
+ 'block_device_mapping', 'iscsi_targets',
+ 'security_group_instance_association', 'security_groups',
+ 'security_group_rules', 'provider_fw_rules', 'key_pairs',
+ 'migrations', 'networks', 'virtual_interfaces', 'fixed_ips',
+ 'floating_ips', 'console_pools', 'consoles',
+ 'instance_metadata', 'instance_system_metadata',
+ 'instance_type_projects', 'instance_type_extra_specs',
+ 'aggregate_hosts', 'aggregate_metadata', 'aggregates',
+ 'agent_builds', 's3_images',
+ 'instance_faults',
+ 'bw_usage_cache', 'volume_id_mappings', 'snapshot_id_mappings',
+ 'instance_id_mappings', 'volume_usage_cache', 'task_log',
+ 'instance_actions', 'instance_actions_events']
+# note(boris-42): We can't do migration for the dns_domains table because it
+# doesn't have `id` column.
+
+
+class InsertFromSelect(UpdateBase):
+ def __init__(self, table, select):
+ self.table = table
+ self.select = select
+
+
+@compiles(InsertFromSelect)
+def visit_insert_from_select(element, compiler, **kw):
+ return "INSERT INTO %s %s" % (
+ compiler.process(element.table, asfrom=True),
+ compiler.process(element.select))
+
+
+def get_default_deleted_value(table):
+ if isinstance(table.c.id.type, Integer):
+ return 0
+ # NOTE(boris-42): There is only one other type that is used as id (String)
+ return ""
+
+
+def upgrade_enterprise_dbs(migrate_engine):
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ for table_name in all_tables:
+ table = Table(table_name, meta, autoload=True)
+
+ new_deleted = Column('new_deleted', table.c.id.type,
+ default=get_default_deleted_value(table))
+ new_deleted.create(table, populate_default=True)
+
+ table.update().\
+ where(table.c.deleted == True).\
+ values(new_deleted=table.c.id).\
+ execute()
+ table.c.deleted.drop()
+ table.c.new_deleted.alter(name="deleted")
+
+
+def upgrade(migrate_engine):
+ if migrate_engine.name != "sqlite":
+ return upgrade_enterprise_dbs(migrate_engine)
+
+ # NOTE(boris-42): sqlaclhemy-migrate can't drop column with check
+ # constraints in sqlite DB and our `deleted` column has
+ # 2 check constraints. So there is only one way to remove
+ # these constraints:
+ # 1) Create new table with the same columns, constraints
+ # and indexes. (except deleted column).
+ # 2) Copy all data from old to new table.
+ # 3) Drop old table.
+ # 4) Rename new table to old table name.
+ insp = reflection.Inspector.from_engine(migrate_engine)
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ for table_name in all_tables:
+ table = Table(table_name, meta, autoload=True)
+ default_deleted_value = get_default_deleted_value(table)
+
+ columns = []
+ for column in table.columns:
+ column_copy = None
+ if column.name != "deleted":
+ # NOTE(boris-42): BigInteger is not supported by sqlite, so
+ # after copy it will have NullType, other
+ # types that are used in Nova are supported by
+ # sqlite.
+ if isinstance(column.type, NullType):
+ column_copy = Column(column.name, BigInteger(), default=0)
+ else:
+ column_copy = column.copy()
+ else:
+ column_copy = Column('deleted', table.c.id.type,
+ default=default_deleted_value)
+ columns.append(column_copy)
+
+ def is_deleted_column_constraint(constraint):
+ # NOTE(boris-42): There is no other way to check is CheckConstraint
+ # associated with deleted column.
+ if not isinstance(constraint, CheckConstraint):
+ return False
+ sqltext = str(constraint.sqltext)
+ return (sqltext.endswith("deleted in (0, 1)") or
+ sqltext.endswith("deleted IN (:deleted_1, :deleted_2)"))
+
+ constraints = []
+ for constraint in table.constraints:
+ if not is_deleted_column_constraint(constraint):
+ constraints.append(constraint.copy())
+
+ new_table = Table(table_name + "__tmp__", meta,
+ *(columns + constraints))
+ new_table.create()
+
+ indexes = []
+ for index in insp.get_indexes(table_name):
+ column_names = [new_table.c[c] for c in index['column_names']]
+ indexes.append(Index(index["name"],
+ *column_names,
+ unique=index["unique"]))
+
+ ins = InsertFromSelect(new_table, table.select())
+ migrate_engine.execute(ins)
+
+ table.drop()
+ [index.create(migrate_engine) for index in indexes]
+
+ new_table.rename(table_name)
+ new_table.update().\
+ where(new_table.c.deleted == True).\
+ values(deleted=new_table.c.id).\
+ execute()
+
+ # NOTE(boris-42): Fix value of deleted column: False -> "" or 0.
+ new_table.update().\
+ where(new_table.c.deleted == False).\
+ values(deleted=default_deleted_value).\
+ execute()
+
+
+def downgrade_enterprise_dbs(migrate_engine):
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ for table_name in all_tables:
+ table = Table(table_name, meta, autoload=True)
+
+ old_deleted = Column('old_deleted', Boolean, default=False)
+ old_deleted.create(table, populate_default=False)
+
+ table.update().\
+ where(table.c.deleted == table.c.id).\
+ values(old_deleted=True).\
+ execute()
+
+ table.c.deleted.drop()
+ table.c.old_deleted.alter(name="deleted")
+
+
+def downgrade(migrate_engine):
+ if migrate_engine.name != "sqlite":
+ return downgrade_enterprise_dbs(migrate_engine)
+
+ insp = reflection.Inspector.from_engine(migrate_engine)
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ for table_name in all_tables:
+ table = Table(table_name, meta, autoload=True)
+
+ columns = []
+ for column in table.columns:
+ column_copy = None
+ if column.name != "deleted":
+ if isinstance(column.type, NullType):
+ column_copy = Column(column.name, BigInteger(), default=0)
+ else:
+ column_copy = column.copy()
+ else:
+ column_copy = Column('deleted', Boolean, default=0)
+ columns.append(column_copy)
+
+ constraints = [constraint.copy() for constraint in table.constraints]
+
+ new_table = Table(table_name + "__tmp__", meta,
+ *(columns + constraints))
+ new_table.create()
+
+ indexes = []
+ for index in insp.get_indexes(table_name):
+ column_names = [new_table.c[c] for c in index['column_names']]
+ indexes.append(Index(index["name"],
+ *column_names,
+ unique=index["unique"]))
+
+ c_select = []
+ for c in table.c:
+ if c.name != "deleted":
+ c_select.append(c)
+ else:
+ c_select.append(table.c.deleted == table.c.id)
+
+ ins = InsertFromSelect(new_table, select(c_select))
+ migrate_engine.execute(ins)
+
+ table.drop()
+ [index.create(migrate_engine) for index in indexes]
+
+ new_table.rename(table_name)
+ new_table.update().\
+ where(new_table.c.deleted == new_table.c.id).\
+ values(deleted=True).\
+ execute()
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 5050cb77e..05452f2ad 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -27,7 +27,7 @@ from sqlalchemy import ForeignKey, DateTime, Boolean, Text, Float
from sqlalchemy.orm import relationship, backref, object_mapper
from nova.db.sqlalchemy.session import get_session
-from nova.db.sqlalchemy.types import IPAddress
+from nova.db.sqlalchemy import types
from nova.openstack.common import cfg
from nova.openstack.common import timeutils
@@ -42,7 +42,7 @@ class NovaBase(object):
created_at = Column(DateTime, default=timeutils.utcnow)
updated_at = Column(DateTime, onupdate=timeutils.utcnow)
deleted_at = Column(DateTime)
- deleted = Column(Boolean, default=False)
+ deleted = Column(Integer, default=0)
metadata = None
def save(self, session=None):
@@ -63,7 +63,7 @@ class NovaBase(object):
def soft_delete(self, session=None):
"""Mark this object as deleted."""
- self.deleted = True
+ self.deleted = self.id
self.deleted_at = timeutils.utcnow()
self.save(session=session)
@@ -129,7 +129,7 @@ class ComputeNode(BASE, NovaBase):
foreign_keys=service_id,
primaryjoin='and_('
'ComputeNode.service_id == Service.id,'
- 'ComputeNode.deleted == False)')
+ 'ComputeNode.deleted == 0)')
vcpus = Column(Integer)
memory_mb = Column(Integer)
@@ -173,7 +173,7 @@ class ComputeNodeStat(BASE, NovaBase):
compute_node_id = Column(Integer, ForeignKey('compute_nodes.id'))
primary_join = ('and_(ComputeNodeStat.compute_node_id == '
- 'ComputeNode.id, ComputeNodeStat.deleted == False)')
+ 'ComputeNode.id, ComputeNodeStat.deleted == 0)')
stats = relationship("ComputeNode", backref="stats",
primaryjoin=primary_join)
@@ -221,7 +221,7 @@ class Instance(BASE, NovaBase):
return base_name
def _extra_keys(self):
- return ['name']
+ return ['name', 'system_metadata']
user_id = Column(String(255))
project_id = Column(String(255))
@@ -291,8 +291,8 @@ class Instance(BASE, NovaBase):
# User editable field meant to represent what ip should be used
# to connect to the instance
- access_ip_v4 = Column(IPAddress())
- access_ip_v6 = Column(IPAddress())
+ access_ip_v4 = Column(types.IPAddress())
+ access_ip_v6 = Column(types.IPAddress())
auto_disk_config = Column(Boolean())
progress = Column(Integer)
@@ -358,6 +358,7 @@ class Volume(BASE, NovaBase):
"""Represents a block storage device that can be attached to a VM."""
__tablename__ = 'volumes'
id = Column(String(36), primary_key=True)
+ deleted = Column(String(36), default="")
@property
def name(self):
@@ -465,13 +466,14 @@ class Reservation(BASE, NovaBase):
"QuotaUsage",
foreign_keys=usage_id,
primaryjoin='and_(Reservation.usage_id == QuotaUsage.id,'
- 'QuotaUsage.deleted == False)')
+ 'QuotaUsage.deleted == 0)')
class Snapshot(BASE, NovaBase):
"""Represents a block storage device that can be attached to a VM."""
__tablename__ = 'snapshots'
id = Column(String(36), primary_key=True)
+ deleted = Column(String(36), default="")
@property
def name(self):
@@ -507,7 +509,7 @@ class BlockDeviceMapping(BASE, NovaBase):
'instance_uuid=='
'Instance.uuid,'
'BlockDeviceMapping.deleted=='
- 'False)')
+ '0)')
device_name = Column(String(255), nullable=False)
# default=False for compatibility of the existing code.
@@ -542,7 +544,7 @@ class IscsiTarget(BASE, NovaBase):
backref=backref('iscsi_target', uselist=False),
foreign_keys=volume_id,
primaryjoin='and_(IscsiTarget.volume_id==Volume.id,'
- 'IscsiTarget.deleted==False)')
+ 'IscsiTarget.deleted==0)')
class SecurityGroupInstanceAssociation(BASE, NovaBase):
@@ -567,14 +569,14 @@ class SecurityGroup(BASE, NovaBase):
primaryjoin='and_('
'SecurityGroup.id == '
'SecurityGroupInstanceAssociation.security_group_id,'
- 'SecurityGroupInstanceAssociation.deleted == False,'
- 'SecurityGroup.deleted == False)',
+ 'SecurityGroupInstanceAssociation.deleted == 0,'
+ 'SecurityGroup.deleted == 0)',
secondaryjoin='and_('
'SecurityGroupInstanceAssociation.instance_uuid == Instance.uuid,'
# (anthony) the condition below shouldn't be necessary now that the
# association is being marked as deleted. However, removing this
# may cause existing deployments to choke, so I'm leaving it
- 'Instance.deleted == False)',
+ 'Instance.deleted == 0)',
backref='security_groups')
@@ -588,12 +590,12 @@ class SecurityGroupIngressRule(BASE, NovaBase):
foreign_keys=parent_group_id,
primaryjoin='and_('
'SecurityGroupIngressRule.parent_group_id == SecurityGroup.id,'
- 'SecurityGroupIngressRule.deleted == False)')
+ 'SecurityGroupIngressRule.deleted == 0)')
protocol = Column(String(5)) # "tcp", "udp", or "icmp"
from_port = Column(Integer)
to_port = Column(Integer)
- cidr = Column(IPAddress())
+ cidr = Column(types.IPAddress())
# Note: This is not the parent SecurityGroup. It's SecurityGroup we're
# granting access for.
@@ -602,7 +604,7 @@ class SecurityGroupIngressRule(BASE, NovaBase):
foreign_keys=group_id,
primaryjoin='and_('
'SecurityGroupIngressRule.group_id == SecurityGroup.id,'
- 'SecurityGroupIngressRule.deleted == False)')
+ 'SecurityGroupIngressRule.deleted == 0)')
class ProviderFirewallRule(BASE, NovaBase):
@@ -613,7 +615,7 @@ class ProviderFirewallRule(BASE, NovaBase):
protocol = Column(String(5)) # "tcp", "udp", or "icmp"
from_port = Column(Integer)
to_port = Column(Integer)
- cidr = Column(IPAddress())
+ cidr = Column(types.IPAddress())
class KeyPair(BASE, NovaBase):
@@ -651,7 +653,7 @@ class Migration(BASE, NovaBase):
instance = relationship("Instance", foreign_keys=instance_uuid,
primaryjoin='and_(Migration.instance_uuid == '
'Instance.uuid, Instance.deleted == '
- 'False)')
+ '0)')
class Network(BASE, NovaBase):
@@ -663,25 +665,25 @@ class Network(BASE, NovaBase):
label = Column(String(255))
injected = Column(Boolean, default=False)
- cidr = Column(IPAddress(), unique=True)
- cidr_v6 = Column(IPAddress(), unique=True)
+ cidr = Column(types.IPAddress(), unique=True)
+ cidr_v6 = Column(types.IPAddress(), unique=True)
multi_host = Column(Boolean, default=False)
- gateway_v6 = Column(IPAddress())
- netmask_v6 = Column(IPAddress())
- netmask = Column(IPAddress())
+ gateway_v6 = Column(types.IPAddress())
+ netmask_v6 = Column(types.IPAddress())
+ netmask = Column(types.IPAddress())
bridge = Column(String(255))
bridge_interface = Column(String(255))
- gateway = Column(IPAddress())
- broadcast = Column(IPAddress())
- dns1 = Column(IPAddress())
- dns2 = Column(IPAddress())
+ gateway = Column(types.IPAddress())
+ broadcast = Column(types.IPAddress())
+ dns1 = Column(types.IPAddress())
+ dns2 = Column(types.IPAddress())
vlan = Column(Integer)
- vpn_public_address = Column(IPAddress())
+ vpn_public_address = Column(types.IPAddress())
vpn_public_port = Column(Integer)
- vpn_private_address = Column(IPAddress())
- dhcp_start = Column(IPAddress())
+ vpn_private_address = Column(types.IPAddress())
+ dhcp_start = Column(types.IPAddress())
rxtx_base = Column(Integer)
@@ -706,7 +708,7 @@ class FixedIp(BASE, NovaBase):
"""Represents a fixed ip for an instance."""
__tablename__ = 'fixed_ips'
id = Column(Integer, primary_key=True)
- address = Column(IPAddress())
+ address = Column(types.IPAddress())
network_id = Column(Integer, nullable=True)
virtual_interface_id = Column(Integer, nullable=True)
instance_uuid = Column(String(36), nullable=True)
@@ -723,7 +725,7 @@ class FloatingIp(BASE, NovaBase):
"""Represents a floating ip that dynamically forwards to a fixed ip."""
__tablename__ = 'floating_ips'
id = Column(Integer, primary_key=True)
- address = Column(IPAddress())
+ address = Column(types.IPAddress())
fixed_ip_id = Column(Integer, nullable=True)
project_id = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
@@ -735,6 +737,7 @@ class FloatingIp(BASE, NovaBase):
class DNSDomain(BASE, NovaBase):
"""Represents a DNS domain with availability zone or project info."""
__tablename__ = 'dns_domains'
+ deleted = Column(Boolean, default=False)
domain = Column(String(512), primary_key=True)
scope = Column(String(255))
availability_zone = Column(String(255))
@@ -745,7 +748,7 @@ class ConsolePool(BASE, NovaBase):
"""Represents pool of consoles on the same physical node."""
__tablename__ = 'console_pools'
id = Column(Integer, primary_key=True)
- address = Column(IPAddress())
+ address = Column(types.IPAddress())
username = Column(String(255))
password = Column(String(255))
console_type = Column(String(255))
@@ -779,7 +782,7 @@ class InstanceMetadata(BASE, NovaBase):
primaryjoin='and_('
'InstanceMetadata.instance_uuid == '
'Instance.uuid,'
- 'InstanceMetadata.deleted == False)')
+ 'InstanceMetadata.deleted == 0)')
class InstanceSystemMetadata(BASE, NovaBase):
@@ -793,7 +796,7 @@ class InstanceSystemMetadata(BASE, NovaBase):
nullable=False)
primary_join = ('and_(InstanceSystemMetadata.instance_uuid == '
- 'Instance.uuid, InstanceSystemMetadata.deleted == False)')
+ 'Instance.uuid, InstanceSystemMetadata.deleted == 0)')
instance = relationship(Instance, backref="system_metadata",
foreign_keys=instance_uuid,
primaryjoin=primary_join)
@@ -811,7 +814,7 @@ class InstanceTypeProjects(BASE, NovaBase):
foreign_keys=instance_type_id,
primaryjoin='and_('
'InstanceTypeProjects.instance_type_id == InstanceTypes.id,'
- 'InstanceTypeProjects.deleted == False)')
+ 'InstanceTypeProjects.deleted == 0)')
class InstanceTypeExtraSpecs(BASE, NovaBase):
@@ -826,7 +829,7 @@ class InstanceTypeExtraSpecs(BASE, NovaBase):
foreign_keys=instance_type_id,
primaryjoin='and_('
'InstanceTypeExtraSpecs.instance_type_id == InstanceTypes.id,'
- 'InstanceTypeExtraSpecs.deleted == False)')
+ 'InstanceTypeExtraSpecs.deleted == 0)')
class Cell(BASE, NovaBase):
@@ -880,24 +883,24 @@ class Aggregate(BASE, NovaBase):
secondary="aggregate_hosts",
primaryjoin='and_('
'Aggregate.id == AggregateHost.aggregate_id,'
- 'AggregateHost.deleted == False,'
- 'Aggregate.deleted == False)',
+ 'AggregateHost.deleted == 0,'
+ 'Aggregate.deleted == 0)',
secondaryjoin='and_('
'AggregateHost.aggregate_id == Aggregate.id, '
- 'AggregateHost.deleted == False,'
- 'Aggregate.deleted == False)',
+ 'AggregateHost.deleted == 0,'
+ 'Aggregate.deleted == 0)',
backref='aggregates')
_metadata = relationship(AggregateMetadata,
secondary="aggregate_metadata",
primaryjoin='and_('
'Aggregate.id == AggregateMetadata.aggregate_id,'
- 'AggregateMetadata.deleted == False,'
- 'Aggregate.deleted == False)',
+ 'AggregateMetadata.deleted == 0,'
+ 'Aggregate.deleted == 0)',
secondaryjoin='and_('
'AggregateMetadata.aggregate_id == Aggregate.id, '
- 'AggregateMetadata.deleted == False,'
- 'Aggregate.deleted == False)',
+ 'AggregateMetadata.deleted == 0,'
+ 'Aggregate.deleted == 0)',
backref='aggregates')
def _extra_keys(self):
@@ -1038,8 +1041,8 @@ class TaskLog(BASE, NovaBase):
task_name = Column(String(255), nullable=False)
state = Column(String(255), nullable=False)
host = Column(String(255))
- period_beginning = Column(String(255), default=timeutils.utcnow)
- period_ending = Column(String(255), default=timeutils.utcnow)
+ period_beginning = Column(DateTime, default=timeutils.utcnow)
+ period_ending = Column(DateTime, default=timeutils.utcnow)
message = Column(String(255), nullable=False)
task_items = Column(Integer(), default=0)
errors = Column(Integer(), default=0)
diff --git a/nova/db/sqlalchemy/session.py b/nova/db/sqlalchemy/session.py
index 9c896ae97..eb5d8016f 100644
--- a/nova/db/sqlalchemy/session.py
+++ b/nova/db/sqlalchemy/session.py
@@ -236,9 +236,7 @@ import sqlalchemy.orm
from sqlalchemy.pool import NullPool, StaticPool
from sqlalchemy.sql.expression import literal_column
-from nova.exception import DBDuplicateEntry
-from nova.exception import DBError
-from nova.exception import InvalidUnicodeParameter
+import nova.exception
from nova.openstack.common import cfg
import nova.openstack.common.log as logging
from nova.openstack.common import timeutils
@@ -362,7 +360,7 @@ def raise_if_duplicate_entry_error(integrity_error, engine_name):
columns = columns.strip().split(", ")
else:
columns = get_columns_from_uniq_cons_or_name(columns)
- raise DBDuplicateEntry(columns, integrity_error)
+ raise nova.exception.DBDuplicateEntry(columns, integrity_error)
def wrap_db_error(f):
@@ -370,7 +368,7 @@ def wrap_db_error(f):
try:
return f(*args, **kwargs)
except UnicodeEncodeError:
- raise InvalidUnicodeParameter()
+ raise nova.exception.InvalidUnicodeParameter()
# note(boris-42): We should catch unique constraint violation and
# wrap it by our own DBDuplicateEntry exception. Unique constraint
# violation is wrapped by IntegrityError.
@@ -381,10 +379,10 @@ def wrap_db_error(f):
# means we should get names of columns, which values violate
# unique constraint, from error message.
raise_if_duplicate_entry_error(e, get_engine().name)
- raise DBError(e)
+ raise nova.exception.DBError(e)
except Exception, e:
LOG.exception(_('DB exception wrapped.'))
- raise DBError(e)
+ raise nova.exception.DBError(e)
_wrap.func_name = f.func_name
return _wrap
@@ -536,7 +534,7 @@ def create_engine(sql_connection):
class Query(sqlalchemy.orm.query.Query):
"""Subclass of sqlalchemy.query with soft_delete() method."""
def soft_delete(self, synchronize_session='evaluate'):
- return self.update({'deleted': True,
+ return self.update({'deleted': literal_column('id'),
'updated_at': literal_column('updated_at'),
'deleted_at': timeutils.utcnow()},
synchronize_session=synchronize_session)
diff --git a/nova/network/api.py b/nova/network/api.py
index 976be93ed..5e3762e89 100644
--- a/nova/network/api.py
+++ b/nova/network/api.py
@@ -111,7 +111,7 @@ class API(base.Base):
return self.network_rpcapi.get_floating_ip(context, id)
def get_floating_ip_pools(self, context):
- return self.network_rpcapi.get_floating_pools(context)
+ return self.network_rpcapi.get_floating_ip_pools(context)
def get_floating_ip_by_address(self, context, address):
return self.network_rpcapi.get_floating_ip_by_address(context, address)
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 1be06f66a..a9b44e94a 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -1124,6 +1124,40 @@ def _create_veth_pair(dev1_name, dev2_name):
run_as_root=True)
+def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id):
+ utils.execute('ovs-vsctl', '--', '--may-exist', 'add-port',
+ bridge, dev,
+ '--', 'set', 'Interface', dev,
+ 'external-ids:iface-id=%s' % iface_id,
+ 'external-ids:iface-status=active',
+ 'external-ids:attached-mac=%s' % mac,
+ 'external-ids:vm-uuid=%s' % instance_id,
+ run_as_root=True)
+
+
+def delete_ovs_vif_port(bridge, dev):
+ utils.execute('ovs-vsctl', 'del-port', bridge, dev,
+ run_as_root=True)
+ utils.execute('ip', 'link', 'delete', dev,
+ run_as_root=True)
+
+
+def create_tap_dev(dev, mac_address=None):
+ if not device_exists(dev):
+ try:
+ # First, try with 'ip'
+ utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap',
+ run_as_root=True, check_exit_code=[0, 2, 254])
+ except exception.ProcessExecutionError:
+ # Second option: tunctl
+ utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
+ if mac_address:
+ utils.execute('ip', 'link', 'set', dev, 'address', mac_address,
+ run_as_root=True, check_exit_code=[0, 2, 254])
+ utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True,
+ check_exit_code=[0, 2, 254])
+
+
# Similar to compute virt layers, the Linux network node
# code uses a flexible driver model to support different ways
# of creating ethernet interfaces and attaching them to the network.
@@ -1163,7 +1197,7 @@ class LinuxNetInterfaceDriver(object):
raise NotImplementedError()
def unplug(self, network):
- """Destory Linux device, return device name."""
+ """Destroy Linux device, return device name."""
raise NotImplementedError()
def get_dev(self, network):
@@ -1403,7 +1437,7 @@ def remove_ebtables_rules(rules):
def isolate_dhcp_address(interface, address):
- # block arp traffic to address accross the interface
+ # block arp traffic to address across the interface
rules = []
rules.append('INPUT -p ARP -i %s --arp-ip-dst %s -j DROP'
% (interface, address))
@@ -1419,7 +1453,7 @@ def isolate_dhcp_address(interface, address):
ipv4_filter.add_rule('FORWARD',
'-m physdev --physdev-out %s -d 255.255.255.255 '
'-p udp --dport 67 -j DROP' % interface, top=True)
- # block ip traffic to address accross the interface
+ # block ip traffic to address across the interface
ipv4_filter.add_rule('FORWARD',
'-m physdev --physdev-in %s -d %s -j DROP'
% (interface, address), top=True)
@@ -1429,7 +1463,7 @@ def isolate_dhcp_address(interface, address):
def remove_isolate_dhcp_address(interface, address):
- # block arp traffic to address accross the interface
+ # block arp traffic to address across the interface
rules = []
rules.append('INPUT -p ARP -i %s --arp-ip-dst %s -j DROP'
% (interface, address))
@@ -1445,7 +1479,7 @@ def remove_isolate_dhcp_address(interface, address):
ipv4_filter.remove_rule('FORWARD',
'-m physdev --physdev-out %s -d 255.255.255.255 '
'-p udp --dport 67 -j DROP' % interface, top=True)
- # block ip traffic to address accross the interface
+ # block ip traffic to address across the interface
ipv4_filter.remove_rule('FORWARD',
'-m physdev --physdev-in %s -d %s -j DROP'
% (interface, address), top=True)
@@ -1535,7 +1569,7 @@ class QuantumLinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
iptables_manager.ipv4['filter'].add_rule('FORWARD',
'--out-interface %s -j ACCEPT' % bridge)
- QuantumLinuxBridgeInterfaceDriver.create_tap_dev(dev, mac_address)
+ create_tap_dev(dev, mac_address)
if not device_exists(bridge):
LOG.debug(_("Starting bridge %s "), bridge)
@@ -1570,22 +1604,6 @@ class QuantumLinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
LOG.debug(_("Unplugged gateway interface '%s'"), dev)
return dev
- @classmethod
- def create_tap_dev(_self, dev, mac_address=None):
- if not device_exists(dev):
- try:
- # First, try with 'ip'
- utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap',
- run_as_root=True, check_exit_code=[0, 2, 254])
- except exception.ProcessExecutionError:
- # Second option: tunctl
- utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
- if mac_address:
- utils.execute('ip', 'link', 'set', dev, 'address', mac_address,
- run_as_root=True, check_exit_code=[0, 2, 254])
- utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True,
- check_exit_code=[0, 2, 254])
-
def get_dev(self, network):
dev = self.GATEWAY_INTERFACE_PREFIX + str(network['uuid'][0:11])
return dev
diff --git a/nova/network/manager.py b/nova/network/manager.py
index e4a97f162..96f17a855 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -678,7 +678,7 @@ class FloatingIP(object):
# actually remove the ip address on the host. We are
# safe from races on this host due to the decorator,
# but another host might grab the ip right away. We
- # don't worry about this case because the miniscule
+ # don't worry about this case because the minuscule
# window where the ip is on both hosts shouldn't cause
# any problems.
fixed_address = self.db.floating_ip_disassociate(context, address)
@@ -708,6 +708,13 @@ class FloatingIP(object):
@wrap_check_policy
def get_floating_pools(self, context):
"""Returns list of floating pools."""
+ # NOTE(maurosr) This method should be removed in future, replaced by
+ # get_floating_ip_pools. See bug #1091668
+ return self.get_floating_ip_pools(context)
+
+ @wrap_check_policy
+ def get_floating_ip_pools(self, context):
+ """Returns list of floating ip pools."""
pools = self.db.floating_ip_get_pools(context)
return [dict(pool.iteritems()) for pool in pools]
@@ -922,7 +929,7 @@ class NetworkManager(manager.SchedulerDependentManager):
The one at a time part is to flatten the layout to help scale
"""
- RPC_API_VERSION = '1.6'
+ RPC_API_VERSION = '1.7'
# If True, this manager requires VIF to create a bridge.
SHOULD_CREATE_BRIDGE = False
@@ -2065,6 +2072,13 @@ class FlatManager(NetworkManager):
@wrap_check_policy
def get_floating_pools(self, context):
"""Returns list of floating pools."""
+ # NOTE(maurosr) This method should be removed in future, replaced by
+ # get_floating_ip_pools. See bug #1091668
+ return {}
+
+ @wrap_check_policy
+ def get_floating_ip_pools(self, context):
+ """Returns list of floating ip pools."""
return {}
@wrap_check_policy
diff --git a/nova/network/model.py b/nova/network/model.py
index e4fe0d54c..9accb883e 100644
--- a/nova/network/model.py
+++ b/nova/network/model.py
@@ -207,7 +207,7 @@ class Network(Model):
class VIF(Model):
"""Represents a Virtual Interface in Nova."""
def __init__(self, id=None, address=None, network=None, type=None,
- devname=None, **kwargs):
+ devname=None, ovs_interfaceid=None, **kwargs):
super(VIF, self).__init__()
self['id'] = id
@@ -216,6 +216,8 @@ class VIF(Model):
self['type'] = type
self['devname'] = devname
+ self['ovs_interfaceid'] = ovs_interfaceid
+
self._set_meta(kwargs)
def __eq__(self, other):
@@ -250,7 +252,7 @@ class VIF(Model):
'meta': {...}}]
"""
if self['network']:
- # remove unecessary fields on fixed_ips
+ # remove unnecessary fields on fixed_ips
ips = [IP(**ensure_string_keys(ip)) for ip in self.fixed_ips()]
for ip in ips:
# remove floating ips from IP, since this is a flat structure
@@ -381,6 +383,7 @@ class NetworkInfo(list):
'vif_type': vif['type'],
'vif_devname': vif.get('devname'),
'vif_uuid': vif['id'],
+ 'ovs_interfaceid': vif.get('ovs_interfaceid'),
'rxtx_cap': vif.get_meta('rxtx_cap', 0),
'dns': [get_ip(ip) for ip in subnet_v4['dns']],
'ips': [fixed_ip_dict(ip, subnet)
diff --git a/nova/network/quantumv2/api.py b/nova/network/quantumv2/api.py
index 29e5e2f06..704ed5cef 100644
--- a/nova/network/quantumv2/api.py
+++ b/nova/network/quantumv2/api.py
@@ -661,11 +661,13 @@ class API(base.Base):
if fixed_ip.is_in_subnet(subnet)]
bridge = None
+ ovs_interfaceid = None
vif_type = port.get('binding:vif_type')
# TODO(berrange) Quantum should pass the bridge name
# in another binding metadata field
if vif_type == network_model.VIF_TYPE_OVS:
bridge = CONF.quantum_ovs_bridge
+ ovs_interfaceid = port['id']
elif vif_type == network_model.VIF_TYPE_BRIDGE:
bridge = "brq" + port['network_id']
@@ -688,6 +690,7 @@ class API(base.Base):
address=port['mac_address'],
network=network,
type=port.get('binding:vif_type'),
+ ovs_interfaceid=ovs_interfaceid,
devname=devname))
return nw_info
diff --git a/nova/network/rpcapi.py b/nova/network/rpcapi.py
index 2f52add57..a7bffe17a 100644
--- a/nova/network/rpcapi.py
+++ b/nova/network/rpcapi.py
@@ -45,6 +45,7 @@ class NetworkAPI(rpc_proxy.RpcProxy):
1.4 - Add get_backdoor_port()
1.5 - Adds associate
1.6 - Adds instance_uuid to _{dis,}associate_floating_ip
+ 1.7 - Adds method get_floating_ip_pools to replace get_floating_pools
'''
#
@@ -94,8 +95,9 @@ class NetworkAPI(rpc_proxy.RpcProxy):
def get_floating_ip(self, ctxt, id):
return self.call(ctxt, self.make_msg('get_floating_ip', id=id))
- def get_floating_pools(self, ctxt):
- return self.call(ctxt, self.make_msg('get_floating_pools'))
+ def get_floating_ip_pools(self, ctxt):
+ return self.call(ctxt, self.make_msg('get_floating_ip_pools'),
+ version="1.7")
def get_floating_ip_by_address(self, ctxt, address):
return self.call(ctxt, self.make_msg('get_floating_ip_by_address',
diff --git a/nova/notifications.py b/nova/notifications.py
index f399ac55d..65428d03f 100644
--- a/nova/notifications.py
+++ b/nova/notifications.py
@@ -283,12 +283,8 @@ def info_from_instance(context, instance_ref, network_info,
instance_type_name = instance_ref.get('instance_type', {}).get('name', '')
if system_metadata is None:
- try:
- system_metadata = db.instance_system_metadata_get(
- context, instance_ref['uuid'])
-
- except exception.NotFound:
- system_metadata = {}
+ system_metadata = utils.metadata_to_dict(
+ instance_ref['system_metadata'])
instance_info = dict(
# Owner properties
diff --git a/nova/openstack/common/cfg.py b/nova/openstack/common/cfg.py
index ad1f2a8a6..534a610c0 100644
--- a/nova/openstack/common/cfg.py
+++ b/nova/openstack/common/cfg.py
@@ -217,7 +217,7 @@ log files::
...
]
-This module also contains a global instance of the CommonConfigOpts class
+This module also contains a global instance of the ConfigOpts class
in order to support a common usage pattern in OpenStack::
from nova.openstack.common import cfg
@@ -236,10 +236,11 @@ in order to support a common usage pattern in OpenStack::
Positional command line arguments are supported via a 'positional' Opt
constructor argument::
- >>> CONF.register_cli_opt(MultiStrOpt('bar', positional=True))
+ >>> conf = ConfigOpts()
+ >>> conf.register_cli_opt(MultiStrOpt('bar', positional=True))
True
- >>> CONF(['a', 'b'])
- >>> CONF.bar
+ >>> conf(['a', 'b'])
+ >>> conf.bar
['a', 'b']
It is also possible to use argparse "sub-parsers" to parse additional
@@ -249,10 +250,11 @@ command line arguments using the SubCommandOpt class:
... list_action = subparsers.add_parser('list')
... list_action.add_argument('id')
...
- >>> CONF.register_cli_opt(SubCommandOpt('action', handler=add_parsers))
+ >>> conf = ConfigOpts()
+ >>> conf.register_cli_opt(SubCommandOpt('action', handler=add_parsers))
True
- >>> CONF(['list', '10'])
- >>> CONF.action.name, CONF.action.id
+ >>> conf(args=['list', '10'])
+ >>> conf.action.name, conf.action.id
('list', '10')
"""
@@ -1726,62 +1728,4 @@ class ConfigOpts(collections.Mapping):
return value
-class CommonConfigOpts(ConfigOpts):
-
- DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
- DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
-
- common_cli_opts = [
- BoolOpt('debug',
- short='d',
- default=False,
- help='Print debugging output'),
- BoolOpt('verbose',
- short='v',
- default=False,
- help='Print more verbose output'),
- ]
-
- logging_cli_opts = [
- StrOpt('log-config',
- metavar='PATH',
- help='If this option is specified, the logging configuration '
- 'file specified is used and overrides any other logging '
- 'options specified. Please see the Python logging module '
- 'documentation for details on logging configuration '
- 'files.'),
- StrOpt('log-format',
- default=DEFAULT_LOG_FORMAT,
- metavar='FORMAT',
- help='A logging.Formatter log message format string which may '
- 'use any of the available logging.LogRecord attributes. '
- 'Default: %(default)s'),
- StrOpt('log-date-format',
- default=DEFAULT_LOG_DATE_FORMAT,
- metavar='DATE_FORMAT',
- help='Format string for %%(asctime)s in log records. '
- 'Default: %(default)s'),
- StrOpt('log-file',
- metavar='PATH',
- deprecated_name='logfile',
- help='(Optional) Name of log file to output to. '
- 'If not set, logging will go to stdout.'),
- StrOpt('log-dir',
- deprecated_name='logdir',
- help='(Optional) The directory to keep log files in '
- '(will be prepended to --log-file)'),
- BoolOpt('use-syslog',
- default=False,
- help='Use syslog for logging.'),
- StrOpt('syslog-log-facility',
- default='LOG_USER',
- help='syslog facility to receive log lines')
- ]
-
- def __init__(self):
- super(CommonConfigOpts, self).__init__()
- self.register_cli_opts(self.common_cli_opts)
- self.register_cli_opts(self.logging_cli_opts)
-
-
-CONF = CommonConfigOpts()
+CONF = ConfigOpts()
diff --git a/nova/openstack/common/iniparser.py b/nova/openstack/common/iniparser.py
index 241284449..9bf399f0c 100644
--- a/nova/openstack/common/iniparser.py
+++ b/nova/openstack/common/iniparser.py
@@ -54,7 +54,7 @@ class BaseParser(object):
value = value.strip()
if ((value and value[0] == value[-1]) and
- (value[0] == "\"" or value[0] == "'")):
+ (value[0] == "\"" or value[0] == "'")):
value = value[1:-1]
return key.strip(), [value]
diff --git a/nova/openstack/common/log.py b/nova/openstack/common/log.py
index 5c6dbcf14..32513bb32 100644
--- a/nova/openstack/common/log.py
+++ b/nova/openstack/common/log.py
@@ -47,6 +47,67 @@ from nova.openstack.common import local
from nova.openstack.common import notifier
+_DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
+_DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+common_cli_opts = [
+ cfg.BoolOpt('debug',
+ short='d',
+ default=False,
+ help='Print debugging output (set logging level to '
+ 'DEBUG instead of default WARNING level).'),
+ cfg.BoolOpt('verbose',
+ short='v',
+ default=False,
+ help='Print more verbose output (set logging level to '
+ 'INFO instead of default WARNING level).'),
+]
+
+logging_cli_opts = [
+ cfg.StrOpt('log-config',
+ metavar='PATH',
+ help='If this option is specified, the logging configuration '
+ 'file specified is used and overrides any other logging '
+ 'options specified. Please see the Python logging module '
+ 'documentation for details on logging configuration '
+ 'files.'),
+ cfg.StrOpt('log-format',
+ default=_DEFAULT_LOG_FORMAT,
+ metavar='FORMAT',
+ help='A logging.Formatter log message format string which may '
+ 'use any of the available logging.LogRecord attributes. '
+ 'Default: %(default)s'),
+ cfg.StrOpt('log-date-format',
+ default=_DEFAULT_LOG_DATE_FORMAT,
+ metavar='DATE_FORMAT',
+ help='Format string for %%(asctime)s in log records. '
+ 'Default: %(default)s'),
+ cfg.StrOpt('log-file',
+ metavar='PATH',
+ deprecated_name='logfile',
+ help='(Optional) Name of log file to output to. '
+ 'If not set, logging will go to stdout.'),
+ cfg.StrOpt('log-dir',
+ deprecated_name='logdir',
+ help='(Optional) The directory to keep log files in '
+ '(will be prepended to --log-file)'),
+ cfg.BoolOpt('use-syslog',
+ default=False,
+ help='Use syslog for logging.'),
+ cfg.StrOpt('syslog-log-facility',
+ default='LOG_USER',
+ help='syslog facility to receive log lines')
+]
+
+generic_log_opts = [
+ cfg.BoolOpt('use_stderr',
+ default=True,
+ help='Log output to standard error'),
+ cfg.StrOpt('logfile_mode',
+ default='0644',
+ help='Default file mode used when creating log files'),
+]
+
log_opts = [
cfg.StrOpt('logging_context_format_string',
default='%(asctime)s.%(msecs)03d %(levelname)s %(name)s '
@@ -94,24 +155,9 @@ log_opts = [
'format it like this'),
]
-
-generic_log_opts = [
- cfg.StrOpt('logdir',
- default=None,
- help='Log output to a per-service log file in named directory'),
- cfg.StrOpt('logfile',
- default=None,
- help='Log output to a named file'),
- cfg.BoolOpt('use_stderr',
- default=True,
- help='Log output to standard error'),
- cfg.StrOpt('logfile_mode',
- default='0644',
- help='Default file mode used when creating log files'),
-]
-
-
CONF = cfg.CONF
+CONF.register_cli_opts(common_cli_opts)
+CONF.register_cli_opts(logging_cli_opts)
CONF.register_opts(generic_log_opts)
CONF.register_opts(log_opts)
@@ -149,8 +195,8 @@ def _get_binary_name():
def _get_log_file_path(binary=None):
- logfile = CONF.log_file or CONF.logfile
- logdir = CONF.log_dir or CONF.logdir
+ logfile = CONF.log_file
+ logdir = CONF.log_dir
if logfile and not logdir:
return logfile
diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py
index 09de10388..16714a5ff 100644
--- a/nova/scheduler/driver.py
+++ b/nova/scheduler/driver.py
@@ -23,7 +23,6 @@ Scheduler base class that all Schedulers should inherit from
import sys
-from nova.compute import api as compute_api
from nova.compute import power_state
from nova.compute import rpcapi as compute_rpcapi
from nova.compute import utils as compute_utils
@@ -115,7 +114,6 @@ class Scheduler(object):
def __init__(self):
self.host_manager = importutils.import_object(
CONF.scheduler_host_manager)
- self.compute_api = compute_api.API()
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
self.servicegroup_api = servicegroup.API()
diff --git a/nova/scheduler/filter_scheduler.py b/nova/scheduler/filter_scheduler.py
index 07a3f578a..9384e1495 100644
--- a/nova/scheduler/filter_scheduler.py
+++ b/nova/scheduler/filter_scheduler.py
@@ -47,15 +47,15 @@ class FilterScheduler(driver.Scheduler):
Returns a list of the instances created.
"""
- instance_uuids = request_spec.get('instance_uuids')
- num_instances = len(instance_uuids)
- LOG.debug(_("Attempting to build %(num_instances)d instance(s)") %
- locals())
-
payload = dict(request_spec=request_spec)
notifier.notify(context, notifier.publisher_id("scheduler"),
'scheduler.run_instance.start', notifier.INFO, payload)
+ instance_uuids = request_spec.pop('instance_uuids')
+ num_instances = len(instance_uuids)
+ LOG.debug(_("Attempting to build %(num_instances)d instance(s)") %
+ locals())
+
weighed_hosts = self._schedule(context, request_spec,
filter_properties, instance_uuids)
@@ -124,6 +124,8 @@ class FilterScheduler(driver.Scheduler):
filter_properties, requested_networks, injected_files,
admin_password, is_first_time, instance_uuid=None):
"""Create the requested resource in this Zone."""
+ # NOTE(vish): add our current instance back into the request spec
+ request_spec['instance_uuids'] = [instance_uuid]
payload = dict(request_spec=request_spec,
weighted_host=weighed_host.to_dict(),
instance_id=instance_uuid)
diff --git a/nova/service.py b/nova/service.py
index df8cf020f..c250673f4 100644
--- a/nova/service.py
+++ b/nova/service.py
@@ -61,6 +61,9 @@ service_opts = [
cfg.ListOpt('enabled_apis',
default=['ec2', 'osapi_compute', 'metadata'],
help='a list of APIs to enable by default'),
+ cfg.ListOpt('enabled_ssl_apis',
+ default=[],
+ help='a list of APIs with enabled SSL'),
cfg.StrOpt('ec2_listen',
default="0.0.0.0",
help='IP address for EC2 API to listen'),
@@ -399,6 +402,14 @@ class Service(object):
self.binary = binary
self.topic = topic
self.manager_class_name = manager
+ # NOTE(russellb) We want to make sure to create the servicegroup API
+ # instance early, before creating other things such as the manager,
+ # that will also create a servicegroup API instance. Internally, the
+ # servicegroup only allocates a single instance of the driver API and
+ # we want to make sure that our value of db_allowed is there when it
+ # gets created. For that to happen, this has to be the first instance
+ # of the servicegroup API.
+ self.servicegroup_api = servicegroup.API(db_allowed=db_allowed)
manager_class = importutils.import_class(self.manager_class_name)
self.manager = manager_class(host=self.host, *args, **kwargs)
self.report_interval = report_interval
@@ -408,10 +419,8 @@ class Service(object):
self.saved_args, self.saved_kwargs = args, kwargs
self.timers = []
self.backdoor_port = None
- 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(db_allowed=db_allowed)
def start(self):
verstr = version.version_string_with_package()
@@ -565,7 +574,7 @@ class Service(object):
class WSGIService(object):
"""Provides ability to launch API from a 'paste' configuration."""
- def __init__(self, name, loader=None):
+ def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
"""Initialize, but do not start the WSGI server.
:param name: The name of the WSGI server given to the loader.
@@ -580,10 +589,13 @@ class WSGIService(object):
self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
self.port = getattr(CONF, '%s_listen_port' % name, 0)
self.workers = getattr(CONF, '%s_workers' % name, None)
+ self.use_ssl = use_ssl
self.server = wsgi.Server(name,
self.app,
host=self.host,
- port=self.port)
+ port=self.port,
+ use_ssl=self.use_ssl,
+ max_url_len=max_url_len)
# Pull back actual port used
self.port = self.server.port
self.backdoor_port = None
diff --git a/nova/servicegroup/__init__.py b/nova/servicegroup/__init__.py
index 318d30ff7..a804c62fa 100644
--- a/nova/servicegroup/__init__.py
+++ b/nova/servicegroup/__init__.py
@@ -19,4 +19,6 @@ The membership service for Nova. Different implementations can be plugged
according to the Nova configuration.
"""
-from nova.servicegroup.api import API
+from nova.servicegroup import api
+
+API = api.API
diff --git a/nova/servicegroup/api.py b/nova/servicegroup/api.py
index 358b7dcbc..0fb30cdf5 100644
--- a/nova/servicegroup/api.py
+++ b/nova/servicegroup/api.py
@@ -23,7 +23,7 @@ from nova.openstack.common import lockutils
from nova.openstack.common import log as logging
from nova import utils
-from random import choice
+import random
LOG = logging.getLogger(__name__)
@@ -144,4 +144,4 @@ class ServiceGroupDriver(object):
length = len(members)
if length == 0:
return None
- return choice(members)
+ return random.choice(members)
diff --git a/nova/tests/api/ec2/test_cinder_cloud.py b/nova/tests/api/ec2/test_cinder_cloud.py
index d403ba1f0..5e5723a08 100644
--- a/nova/tests/api/ec2/test_cinder_cloud.py
+++ b/nova/tests/api/ec2/test_cinder_cloud.py
@@ -18,9 +18,10 @@
# under the License.
import copy
-import tempfile
import uuid
+import fixtures
+
from nova.api.ec2 import cloud
from nova.api.ec2 import ec2utils
from nova.compute import api as compute_api
@@ -86,7 +87,7 @@ def get_instances_with_cached_ips(orig_func, *args, **kwargs):
class CinderCloudTestCase(test.TestCase):
def setUp(self):
super(CinderCloudTestCase, self).setUp()
- vol_tmpdir = tempfile.mkdtemp()
+ vol_tmpdir = self.useFixture(fixtures.TempDir()).path
self.flags(compute_driver='nova.virt.fake.FakeDriver',
volume_api_class='nova.tests.fake_volume.API')
diff --git a/nova/tests/api/openstack/compute/contrib/test_admin_actions_with_cells.py b/nova/tests/api/openstack/compute/contrib/test_admin_actions_with_cells.py
index b8f4e6398..4e577e1f5 100644
--- a/nova/tests/api/openstack/compute/contrib/test_admin_actions_with_cells.py
+++ b/nova/tests/api/openstack/compute/contrib/test_admin_actions_with_cells.py
@@ -54,10 +54,10 @@ class CellsAdminAPITestCase(test.TestCase):
def fake_cast_to_cells(context, instance, method, *args, **kwargs):
"""
- Makes sure that the cells recieve the cast to update
+ Makes sure that the cells receive the cast to update
the cell state
"""
- self.cells_recieved_kwargs.update(kwargs)
+ self.cells_received_kwargs.update(kwargs)
self.admin_api = admin_actions.AdminActionsController()
self.admin_api.compute_api = compute_cells_api.ComputeCellsAPI()
@@ -76,14 +76,14 @@ class CellsAdminAPITestCase(test.TestCase):
self.uuid = uuidutils.generate_uuid()
url = '/fake/servers/%s/action' % self.uuid
self.request = fakes.HTTPRequest.blank(url)
- self.cells_recieved_kwargs = {}
+ self.cells_received_kwargs = {}
def test_reset_active(self):
body = {"os-resetState": {"state": "error"}}
result = self.admin_api._reset_state(self.request, 'inst_id', body)
self.assertEqual(result.status_int, 202)
- # Make sure the cells recieved the update
- self.assertEqual(self.cells_recieved_kwargs,
+ # Make sure the cells received the update
+ self.assertEqual(self.cells_received_kwargs,
dict(vm_state=vm_states.ERROR,
task_state=None))
diff --git a/nova/tests/api/openstack/compute/contrib/test_availability_zone.py b/nova/tests/api/openstack/compute/contrib/test_availability_zone.py
new file mode 100644
index 000000000..fb9a36ba9
--- /dev/null
+++ b/nova/tests/api/openstack/compute/contrib/test_availability_zone.py
@@ -0,0 +1,247 @@
+# Copyright 2012 IBM
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+from lxml import etree
+import webob
+
+from nova.api.openstack.compute.contrib import availability_zone
+from nova import availability_zones
+from nova import context
+from nova import db
+from nova.openstack.common import jsonutils
+from nova import servicegroup
+from nova import test
+from nova.tests.api.openstack import fakes
+
+
+def fake_service_get_all(context, disabled=None):
+ def __fake_service(binary, availability_zone,
+ created_at, updated_at, host, disabled):
+ return {'binary': binary,
+ 'availability_zone': availability_zone,
+ 'available_zones': availability_zone,
+ 'created_at': created_at,
+ 'updated_at': updated_at,
+ 'host': host,
+ 'disabled': disabled}
+
+ if disabled:
+ return [__fake_service("nova-compute", "zone-2",
+ datetime.datetime(2012, 11, 14, 9, 53, 25, 0),
+ datetime.datetime(2012, 12, 26, 14, 45, 25, 0),
+ "fake_host-1", True),
+ __fake_service("nova-scheduler", "internal",
+ datetime.datetime(2012, 11, 14, 9, 57, 3, 0),
+ datetime.datetime(2012, 12, 26, 14, 45, 25, 0),
+ "fake_host-1", True),
+ __fake_service("nova-network", "internal",
+ datetime.datetime(2012, 11, 16, 7, 25, 46, 0),
+ datetime.datetime(2012, 12, 26, 14, 45, 24, 0),
+ "fake_host-2", True)]
+ else:
+ return [__fake_service("nova-compute", "zone-1",
+ datetime.datetime(2012, 11, 14, 9, 53, 25, 0),
+ datetime.datetime(2012, 12, 26, 14, 45, 25, 0),
+ "fake_host-1", False),
+ __fake_service("nova-sched", "internal",
+ datetime.datetime(2012, 11, 14, 9, 57, 03, 0),
+ datetime.datetime(2012, 12, 26, 14, 45, 25, 0),
+ "fake_host-1", False),
+ __fake_service("nova-network", "internal",
+ datetime.datetime(2012, 11, 16, 7, 25, 46, 0),
+ datetime.datetime(2012, 12, 26, 14, 45, 24, 0),
+ "fake_host-2", False)]
+
+
+def fake_service_is_up(self, service):
+ return service['binary'] != u"nova-network"
+
+
+def fake_set_availability_zones(context, services):
+ return services
+
+
+class AvailabilityZoneApiTest(test.TestCase):
+ def setUp(self):
+ super(AvailabilityZoneApiTest, self).setUp()
+ self.stubs.Set(db, 'service_get_all', fake_service_get_all)
+ self.stubs.Set(availability_zones, 'set_availability_zones',
+ fake_set_availability_zones)
+ self.stubs.Set(servicegroup.API, 'service_is_up', fake_service_is_up)
+
+ def test_availability_zone_index(self):
+ req = webob.Request.blank('/v2/fake/os-availability-zone')
+ resp = req.get_response(fakes.wsgi_app())
+ self.assertEqual(resp.status_int, 200)
+ resp_dict = jsonutils.loads(resp.body)
+
+ self.assertTrue('availabilityZoneInfo' in resp_dict)
+ zones = resp_dict['availabilityZoneInfo']
+ self.assertEqual(len(zones), 2)
+ self.assertEqual(zones[0]['zoneName'], u'zone-1')
+ self.assertTrue(zones[0]['zoneState']['available'])
+ self.assertIsNone(zones[0]['hosts'])
+ self.assertEqual(zones[1]['zoneName'], u'zone-2')
+ self.assertFalse(zones[1]['zoneState']['available'])
+ self.assertIsNone(zones[1]['hosts'])
+
+ def test_availability_zone_detail(self):
+ def _formatZone(zone_dict):
+ result = []
+
+ # Zone tree view item
+ result.append({'zoneName': zone_dict['zoneName'],
+ 'zoneState': u'available'
+ if zone_dict['zoneState']['available'] else
+ u'not available'})
+
+ if zone_dict['hosts'] is not None:
+ for (host, services) in zone_dict['hosts'].items():
+ # Host tree view item
+ result.append({'zoneName': u'|- %s' % host,
+ 'zoneState': u''})
+ for (svc, state) in services.items():
+ # Service tree view item
+ result.append({'zoneName': u'| |- %s' % svc,
+ 'zoneState': u'%s %s %s' % (
+ 'enabled' if state['active'] else
+ 'disabled',
+ ':-)' if state['available'] else
+ 'XXX',
+ jsonutils.to_primitive(
+ state['updated_at']))})
+ return result
+
+ def _assertZone(zone, name, status):
+ self.assertEqual(zone['zoneName'], name)
+ self.assertEqual(zone['zoneState'], status)
+
+ availabilityZone = availability_zone.AvailabilityZoneController()
+
+ req = webob.Request.blank('/v2/fake/os-availability-zone/detail')
+ req.method = 'GET'
+ req.environ['nova.context'] = context.get_admin_context()
+ resp_dict = availabilityZone.detail(req)
+
+ self.assertTrue('availabilityZoneInfo' in resp_dict)
+ zones = resp_dict['availabilityZoneInfo']
+ self.assertEqual(len(zones), 3)
+
+ ''' availabilityZoneInfo field content in response body:
+ [{'zoneName': 'zone-1',
+ 'zoneState': {'available': True},
+ 'hosts': {'fake_host-1': {
+ 'nova-compute': {'active': True, 'available': True,
+ 'updated_at': datetime(2012, 12, 26, 14, 45, 25)}}}},
+ {'zoneName': 'internal',
+ 'zoneState': {'available': True},
+ 'hosts': {'fake_host-1': {
+ 'nova-sched': {'active': True, 'available': True,
+ 'updated_at': datetime(2012, 12, 26, 14, 45, 25)}},
+ 'fake_host-2': {
+ 'nova-network': {'active': True, 'available': False,
+ 'updated_at': datetime(2012, 12, 26, 14, 45, 24)}}}},
+ {'zoneName': 'zone-2',
+ 'zoneState': {'available': False},
+ 'hosts': None}]
+ '''
+
+ l0 = [u'zone-1', u'available']
+ l1 = [u'|- fake_host-1', u'']
+ l2 = [u'| |- nova-compute', u'enabled :-) 2012-12-26T14:45:25.000000']
+ l3 = [u'internal', u'available']
+ l4 = [u'|- fake_host-1', u'']
+ l5 = [u'| |- nova-sched', u'enabled :-) 2012-12-26T14:45:25.000000']
+ l6 = [u'|- fake_host-2', u'']
+ l7 = [u'| |- nova-network', u'enabled XXX 2012-12-26T14:45:24.000000']
+ l8 = [u'zone-2', u'not available']
+
+ z0 = _formatZone(zones[0])
+ z1 = _formatZone(zones[1])
+ z2 = _formatZone(zones[2])
+
+ self.assertEqual(len(z0), 3)
+ self.assertEqual(len(z1), 5)
+ self.assertEqual(len(z2), 1)
+
+ _assertZone(z0[0], l0[0], l0[1])
+ _assertZone(z0[1], l1[0], l1[1])
+ _assertZone(z0[2], l2[0], l2[1])
+ _assertZone(z1[0], l3[0], l3[1])
+ _assertZone(z1[1], l4[0], l4[1])
+ _assertZone(z1[2], l5[0], l5[1])
+ _assertZone(z1[3], l6[0], l6[1])
+ _assertZone(z1[4], l7[0], l7[1])
+ _assertZone(z2[0], l8[0], l8[1])
+
+
+class AvailabilityZoneSerializerTest(test.TestCase):
+ def test_availability_zone_index_detail_serializer(self):
+ def _verify_zone(zone_dict, tree):
+ self.assertEqual(tree.tag, 'availabilityZone')
+ self.assertEqual(zone_dict['zoneName'], tree.get('name'))
+ self.assertEqual(str(zone_dict['zoneState']['available']),
+ tree[0].get('available'))
+
+ for _idx, host_child in enumerate(tree[1]):
+ self.assertTrue(host_child.get('name') in zone_dict['hosts'])
+ svcs = zone_dict['hosts'][host_child.get('name')]
+ for _idx, svc_child in enumerate(host_child[0]):
+ self.assertTrue(svc_child.get('name') in svcs)
+ svc = svcs[svc_child.get('name')]
+ self.assertEqual(len(svc_child), 1)
+
+ self.assertEqual(str(svc['available']),
+ svc_child[0].get('available'))
+ self.assertEqual(str(svc['active']),
+ svc_child[0].get('active'))
+ self.assertEqual(str(svc['updated_at']),
+ svc_child[0].get('updated_at'))
+
+ serializer = availability_zone.AvailabilityZonesTemplate()
+ raw_availability_zones = \
+ [{'zoneName': 'zone-1',
+ 'zoneState': {'available': True},
+ 'hosts': {'fake_host-1': {
+ 'nova-compute': {'active': True, 'available': True,
+ 'updated_at':
+ datetime.datetime(
+ 2012, 12, 26, 14, 45, 25)}}}},
+ {'zoneName': 'internal',
+ 'zoneState': {'available': True},
+ 'hosts': {'fake_host-1': {
+ 'nova-sched': {'active': True, 'available': True,
+ 'updated_at':
+ datetime.datetime(
+ 2012, 12, 26, 14, 45, 25)}},
+ 'fake_host-2': {
+ 'nova-network': {'active': True,
+ 'available': False,
+ 'updated_at':
+ datetime.datetime(
+ 2012, 12, 26, 14, 45, 24)}}}},
+ {'zoneName': 'zone-2',
+ 'zoneState': {'available': False},
+ 'hosts': None}]
+
+ text = serializer.serialize(
+ dict(availabilityZoneInfo=raw_availability_zones))
+ tree = etree.fromstring(text)
+
+ self.assertEqual('availabilityZones', tree.tag)
+ self.assertEqual(len(raw_availability_zones), len(tree))
+ for idx, child in enumerate(tree):
+ _verify_zone(raw_availability_zones[idx], child)
diff --git a/nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py b/nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py
new file mode 100644
index 000000000..381d452a7
--- /dev/null
+++ b/nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py
@@ -0,0 +1,197 @@
+# Copyright (c) 2013 NTT DOCOMO, INC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from webob import exc
+
+from nova.api.openstack.compute.contrib import baremetal_nodes
+from nova import context
+from nova import exception
+from nova import test
+from nova.virt.baremetal import db
+
+
+class FakeRequest(object):
+
+ def __init__(self, context):
+ self.environ = {"nova.context": context}
+
+
+class BareMetalNodesTest(test.TestCase):
+
+ def setUp(self):
+ super(BareMetalNodesTest, self).setUp()
+
+ self.context = context.get_admin_context()
+ self.controller = baremetal_nodes.BareMetalNodeController()
+ self.request = FakeRequest(self.context)
+
+ def test_create(self):
+ node = {
+ 'service_host': "host",
+ 'cpus': 8,
+ 'memory_mb': 8192,
+ 'local_gb': 128,
+ 'pm_address': "10.1.2.3",
+ 'pm_user': "pm_user",
+ 'pm_password': "pm_pass",
+ 'prov_mac_address': "12:34:56:78:90:ab",
+ 'prov_vlan_id': 1234,
+ 'terminal_port': 8000,
+ 'interfaces': [],
+ }
+ response = node.copy()
+ response['id'] = 100
+ del response['pm_password']
+ response['instance_uuid'] = None
+ self.mox.StubOutWithMock(db, 'bm_node_create')
+ db.bm_node_create(self.context, node).AndReturn(response)
+ self.mox.ReplayAll()
+ res_dict = self.controller.create(self.request, {'node': node})
+ self.assertEqual({'node': response}, res_dict)
+
+ def test_delete(self):
+ self.mox.StubOutWithMock(db, 'bm_node_destroy')
+ db.bm_node_destroy(self.context, 1)
+ self.mox.ReplayAll()
+ self.controller.delete(self.request, 1)
+
+ def test_index(self):
+ nodes = [{'id': 1},
+ {'id': 2},
+ ]
+ interfaces = [{'id': 1, 'address': '11:11:11:11:11:11'},
+ {'id': 2, 'address': '22:22:22:22:22:22'},
+ ]
+ self.mox.StubOutWithMock(db, 'bm_node_get_all')
+ self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id')
+ db.bm_node_get_all(self.context).AndReturn(nodes)
+ db.bm_interface_get_all_by_bm_node_id(self.context, 1).\
+ AndRaise(exception.InstanceNotFound(instance_id=1))
+ db.bm_interface_get_all_by_bm_node_id(self.context, 2).\
+ AndReturn(interfaces)
+ self.mox.ReplayAll()
+ res_dict = self.controller.index(self.request)
+ self.assertEqual(2, len(res_dict['nodes']))
+ self.assertEqual([], res_dict['nodes'][0]['interfaces'])
+ self.assertEqual(2, len(res_dict['nodes'][1]['interfaces']))
+
+ def test_show(self):
+ node_id = 1
+ node = {'id': node_id}
+ interfaces = [{'id': 1, 'address': '11:11:11:11:11:11'},
+ {'id': 2, 'address': '22:22:22:22:22:22'},
+ ]
+ self.mox.StubOutWithMock(db, 'bm_node_get')
+ self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id')
+ db.bm_node_get(self.context, node_id).AndReturn(node)
+ db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\
+ AndReturn(interfaces)
+ self.mox.ReplayAll()
+ res_dict = self.controller.show(self.request, node_id)
+ self.assertEqual(node_id, res_dict['node']['id'])
+ self.assertEqual(2, len(res_dict['node']['interfaces']))
+
+ def test_add_interface(self):
+ node_id = 1
+ address = '11:22:33:44:55:66'
+ body = {'add_interface': {'address': address}}
+ self.mox.StubOutWithMock(db, 'bm_node_get')
+ self.mox.StubOutWithMock(db, 'bm_interface_create')
+ self.mox.StubOutWithMock(db, 'bm_interface_get')
+ db.bm_node_get(self.context, node_id)
+ db.bm_interface_create(self.context,
+ bm_node_id=node_id,
+ address=address,
+ datapath_id=None,
+ port_no=None).\
+ AndReturn(12345)
+ db.bm_interface_get(self.context, 12345).\
+ AndReturn({'id': 12345, 'address': address})
+ self.mox.ReplayAll()
+ res_dict = self.controller._add_interface(self.request, node_id, body)
+ self.assertEqual(12345, res_dict['interface']['id'])
+ self.assertEqual(address, res_dict['interface']['address'])
+
+ def test_remove_interface(self):
+ node_id = 1
+ interfaces = [{'id': 1},
+ {'id': 2},
+ {'id': 3},
+ ]
+ body = {'remove_interface': {'id': 2}}
+ self.mox.StubOutWithMock(db, 'bm_node_get')
+ self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id')
+ self.mox.StubOutWithMock(db, 'bm_interface_destroy')
+ db.bm_node_get(self.context, node_id)
+ db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\
+ AndReturn(interfaces)
+ db.bm_interface_destroy(self.context, 2)
+ self.mox.ReplayAll()
+ self.controller._remove_interface(self.request, node_id, body)
+
+ def test_remove_interface_by_address(self):
+ node_id = 1
+ interfaces = [{'id': 1, 'address': '11:11:11:11:11:11'},
+ {'id': 2, 'address': '22:22:22:22:22:22'},
+ {'id': 3, 'address': '33:33:33:33:33:33'},
+ ]
+ self.mox.StubOutWithMock(db, 'bm_node_get')
+ self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id')
+ self.mox.StubOutWithMock(db, 'bm_interface_destroy')
+ db.bm_node_get(self.context, node_id)
+ db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\
+ AndReturn(interfaces)
+ db.bm_interface_destroy(self.context, 2)
+ self.mox.ReplayAll()
+ body = {'remove_interface': {'address': '22:22:22:22:22:22'}}
+ self.controller._remove_interface(self.request, node_id, body)
+
+ def test_remove_interface_no_id_no_address(self):
+ node_id = 1
+ self.mox.StubOutWithMock(db, 'bm_node_get')
+ db.bm_node_get(self.context, node_id)
+ self.mox.ReplayAll()
+ body = {'remove_interface': {}}
+ self.assertRaises(exc.HTTPBadRequest,
+ self.controller._remove_interface,
+ self.request,
+ node_id,
+ body)
+
+ def test_add_interface_node_not_found(self):
+ node_id = 1
+ self.mox.StubOutWithMock(db, 'bm_node_get')
+ db.bm_node_get(self.context, node_id).\
+ AndRaise(exception.InstanceNotFound(instance_id=node_id))
+ self.mox.ReplayAll()
+ body = {'add_interface': {'address': '11:11:11:11:11:11'}}
+ self.assertRaises(exc.HTTPNotFound,
+ self.controller._add_interface,
+ self.request,
+ node_id,
+ body)
+
+ def test_remove_interface_node_not_found(self):
+ node_id = 1
+ self.mox.StubOutWithMock(db, 'bm_node_get')
+ db.bm_node_get(self.context, node_id).\
+ AndRaise(exception.InstanceNotFound(instance_id=node_id))
+ self.mox.ReplayAll()
+ body = {'remove_interface': {'address': '11:11:11:11:11:11'}}
+ self.assertRaises(exc.HTTPNotFound,
+ self.controller._remove_interface,
+ self.request,
+ node_id,
+ body)
diff --git a/nova/tests/api/openstack/compute/contrib/test_coverage_ext.py b/nova/tests/api/openstack/compute/contrib/test_coverage_ext.py
index 39a883049..66a8a8f82 100644
--- a/nova/tests/api/openstack/compute/contrib/test_coverage_ext.py
+++ b/nova/tests/api/openstack/compute/contrib/test_coverage_ext.py
@@ -16,7 +16,7 @@
import telnetlib
-from coverage import coverage
+import coverage
import webob
from nova.api.openstack.compute.contrib import coverage_ext
@@ -48,8 +48,8 @@ class CoverageExtensionTest(test.TestCase):
super(CoverageExtensionTest, self).setUp()
self.stubs.Set(telnetlib.Telnet, 'write', fake_telnet)
self.stubs.Set(telnetlib.Telnet, 'expect', fake_telnet)
- self.stubs.Set(coverage, 'report', fake_report)
- self.stubs.Set(coverage, 'xml_report', fake_xml_report)
+ self.stubs.Set(coverage.coverage, 'report', fake_report)
+ self.stubs.Set(coverage.coverage, 'xml_report', fake_xml_report)
self.admin_context = context.RequestContext('fakeadmin_0',
'fake',
is_admin=True)
diff --git a/nova/tests/api/openstack/compute/contrib/test_floating_ips.py b/nova/tests/api/openstack/compute/contrib/test_floating_ips.py
index a72430fd9..efc9b36cc 100644
--- a/nova/tests/api/openstack/compute/contrib/test_floating_ips.py
+++ b/nova/tests/api/openstack/compute/contrib/test_floating_ips.py
@@ -380,16 +380,16 @@ class FloatingIpTest(test.TestCase):
floating_ips = ["10.10.10.10", "10.10.10.11"]
if floating_address not in floating_ips:
raise exception.FloatingIpNotFoundForAddress(
- address=flaoting_address)
+ address=floating_address)
- self.stubs.Set(network.api.API, "associate_floating_ip",
- fake_network_api_associate)
+ self.stubs.Set(network.api.API, "associate_floating_ip",
+ fake_network_api_associate)
- body = dict(addFloatingIp=dict(address='1.1.1.1'))
- req = fakes.HTTPRequest.blank('/v2/fake/servers/test_inst/action')
- self.assertRaises(webob.exc.HTTPNotFound,
- self.manager._add_floating_ip,
- req, 'test_inst', body)
+ body = dict(addFloatingIp=dict(address='1.1.1.1'))
+ req = fakes.HTTPRequest.blank('/v2/fake/servers/test_inst/action')
+ self.assertRaises(webob.exc.HTTPNotFound,
+ self.manager._add_floating_ip,
+ req, 'test_inst', body)
def test_floating_ip_disassociate_non_existent_ip(self):
def network_api_get_floating_ip_by_address(self, context,
@@ -400,7 +400,7 @@ class FloatingIpTest(test.TestCase):
address=floating_address)
self.stubs.Set(network.api.API, "get_floating_ip_by_address",
- network_api_get_floating_ip_by_address)
+ network_api_get_floating_ip_by_address)
body = dict(removeFloatingIp=dict(address='1.1.1.1'))
req = fakes.HTTPRequest.blank('/v2/fake/servers/test_inst/action')
diff --git a/nova/tests/api/openstack/compute/contrib/test_services.py b/nova/tests/api/openstack/compute/contrib/test_services.py
index 1bd47b67a..aba1b92c1 100644
--- a/nova/tests/api/openstack/compute/contrib/test_services.py
+++ b/nova/tests/api/openstack/compute/contrib/test_services.py
@@ -14,7 +14,8 @@
# under the License.
-from datetime import datetime
+import datetime
+
from nova.api.openstack.compute.contrib import services
from nova import context
from nova import db
@@ -24,35 +25,36 @@ from nova import test
from nova.tests.api.openstack import fakes
-fake_services_list = [{'binary': 'nova-scheduler',
- 'host': 'host1',
- 'id': 1,
- 'disabled': True,
- 'topic': 'scheduler',
- 'updated_at': datetime(2012, 10, 29, 13, 42, 2),
- 'created_at': datetime(2012, 9, 18, 2, 46, 27)},
- {'binary': 'nova-compute',
- 'host': 'host1',
- 'id': 2,
- 'disabled': True,
- 'topic': 'compute',
- 'updated_at': datetime(2012, 10, 29, 13, 42, 5),
- 'created_at': datetime(2012, 9, 18, 2, 46, 27)},
- {'binary': 'nova-scheduler',
- 'host': 'host2',
- 'id': 3,
- 'disabled': False,
- 'topic': 'scheduler',
- 'updated_at': datetime(2012, 9, 19, 6, 55, 34),
- 'created_at': datetime(2012, 9, 18, 2, 46, 28)},
- {'binary': 'nova-compute',
- 'host': 'host2',
- 'id': 4,
- 'disabled': True,
- 'topic': 'compute',
- 'updated_at': datetime(2012, 9, 18, 8, 3, 38),
- 'created_at': datetime(2012, 9, 18, 2, 46, 28)},
- ]
+fake_services_list = [
+ {'binary': 'nova-scheduler',
+ 'host': 'host1',
+ 'id': 1,
+ 'disabled': True,
+ 'topic': 'scheduler',
+ 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2),
+ 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 27)},
+ {'binary': 'nova-compute',
+ 'host': 'host1',
+ 'id': 2,
+ 'disabled': True,
+ 'topic': 'compute',
+ 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5),
+ 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 27)},
+ {'binary': 'nova-scheduler',
+ 'host': 'host2',
+ 'id': 3,
+ 'disabled': False,
+ 'topic': 'scheduler',
+ 'updated_at': datetime.datetime(2012, 9, 19, 6, 55, 34),
+ 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 28)},
+ {'binary': 'nova-compute',
+ 'host': 'host2',
+ 'id': 4,
+ 'disabled': True,
+ 'topic': 'compute',
+ 'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38),
+ 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 28)},
+ ]
class FakeRequest(object):
@@ -60,7 +62,7 @@ class FakeRequest(object):
GET = {}
-class FakeRequestWithSevice(object):
+class FakeRequestWithService(object):
environ = {"nova.context": context.get_admin_context()}
GET = {"service": "nova-compute"}
@@ -103,7 +105,7 @@ def fake_service_update(context, service_id, values):
def fake_utcnow():
- return datetime(2012, 10, 29, 13, 42, 11)
+ return datetime.datetime(2012, 10, 29, 13, 42, 11)
class ServicesTest(test.TestCase):
@@ -130,19 +132,19 @@ class ServicesTest(test.TestCase):
response = {'services': [{'binary': 'nova-scheduler',
'host': 'host1', 'zone': 'internal',
'status': 'disabled', 'state': 'up',
- 'updated_at': datetime(2012, 10, 29, 13, 42, 2)},
+ 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2)},
{'binary': 'nova-compute',
'host': 'host1', 'zone': 'nova',
'status': 'disabled', 'state': 'up',
- 'updated_at': datetime(2012, 10, 29, 13, 42, 5)},
+ 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)},
{'binary': 'nova-scheduler', 'host': 'host2',
'zone': 'internal',
'status': 'enabled', 'state': 'down',
- 'updated_at': datetime(2012, 9, 19, 6, 55, 34)},
+ 'updated_at': datetime.datetime(2012, 9, 19, 6, 55, 34)},
{'binary': 'nova-compute', 'host': 'host2',
'zone': 'nova',
'status': 'disabled', 'state': 'down',
- 'updated_at': datetime(2012, 9, 18, 8, 3, 38)}]}
+ 'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38)}]}
self.assertEqual(res_dict, response)
def test_services_list_with_host(self):
@@ -152,25 +154,25 @@ class ServicesTest(test.TestCase):
response = {'services': [{'binary': 'nova-scheduler', 'host': 'host1',
'zone': 'internal',
'status': 'disabled', 'state': 'up',
- 'updated_at': datetime(2012, 10, 29, 13, 42, 2)},
+ 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2)},
{'binary': 'nova-compute', 'host': 'host1',
'zone': 'nova',
'status': 'disabled', 'state': 'up',
- 'updated_at': datetime(2012, 10, 29, 13, 42, 5)}]}
+ 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}]}
self.assertEqual(res_dict, response)
def test_services_list_with_service(self):
- req = FakeRequestWithSevice()
+ req = FakeRequestWithService()
res_dict = self.controller.index(req)
response = {'services': [{'binary': 'nova-compute', 'host': 'host1',
'zone': 'nova',
'status': 'disabled', 'state': 'up',
- 'updated_at': datetime(2012, 10, 29, 13, 42, 5)},
+ 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)},
{'binary': 'nova-compute', 'host': 'host2',
'zone': 'nova',
'status': 'disabled', 'state': 'down',
- 'updated_at': datetime(2012, 9, 18, 8, 3, 38)}]}
+ 'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38)}]}
self.assertEqual(res_dict, response)
def test_services_list_with_host_service(self):
@@ -180,7 +182,7 @@ class ServicesTest(test.TestCase):
response = {'services': [{'binary': 'nova-compute', 'host': 'host1',
'zone': 'nova',
'status': 'disabled', 'state': 'up',
- 'updated_at': datetime(2012, 10, 29, 13, 42, 5)}]}
+ 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}]}
self.assertEqual(res_dict, response)
def test_services_enable(self):
diff --git a/nova/tests/api/openstack/compute/test_limits.py b/nova/tests/api/openstack/compute/test_limits.py
index f0f2f02d5..375355a70 100644
--- a/nova/tests/api/openstack/compute/test_limits.py
+++ b/nova/tests/api/openstack/compute/test_limits.py
@@ -618,7 +618,7 @@ class WsgiLimiterTest(BaseLimitTestSuite):
self.app = limits.WsgiLimiter(TEST_LIMITS)
def _request_data(self, verb, path):
- """Get data decribing a limit request verb/path."""
+ """Get data describing a limit request verb/path."""
return jsonutils.dumps({"verb": verb, "path": path})
def _request(self, verb, url, username=None):
diff --git a/nova/tests/api/openstack/compute/test_server_metadata.py b/nova/tests/api/openstack/compute/test_server_metadata.py
index 1e992c2a3..71fa9f3f3 100644
--- a/nova/tests/api/openstack/compute/test_server_metadata.py
+++ b/nova/tests/api/openstack/compute/test_server_metadata.py
@@ -21,6 +21,7 @@ import webob
from nova.api.openstack.compute import server_metadata
from nova.compute import rpcapi as compute_rpcapi
+from nova.compute import vm_states
import nova.db
from nova import exception
from nova.openstack.common import cfg
@@ -75,14 +76,16 @@ def return_server(context, server_id):
return {'id': server_id,
'uuid': '0cc3346e-9fef-4445-abe6-5d2b2690ec64',
'name': 'fake',
- 'locked': False}
+ 'locked': False,
+ 'vm_state': vm_states.ACTIVE}
def return_server_by_uuid(context, server_uuid):
return {'id': 1,
'uuid': '0cc3346e-9fef-4445-abe6-5d2b2690ec64',
'name': 'fake',
- 'locked': False}
+ 'locked': False,
+ 'vm_state': vm_states.ACTIVE}
def return_server_nonexistent(context, server_id):
@@ -93,10 +96,9 @@ def fake_change_instance_metadata(self, context, instance, diff):
pass
-class ServerMetaDataTest(test.TestCase):
-
+class BaseTest(test.TestCase):
def setUp(self):
- super(ServerMetaDataTest, self).setUp()
+ super(BaseTest, self).setUp()
fakes.stub_out_key_pair_funcs(self.stubs)
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(nova.db, 'instance_get_by_uuid',
@@ -112,6 +114,9 @@ class ServerMetaDataTest(test.TestCase):
self.uuid = str(uuid.uuid4())
self.url = '/v1.1/fake/servers/%s/metadata' % self.uuid
+
+class ServerMetaDataTest(BaseTest):
+
def test_index(self):
req = fakes.HTTPRequest.blank(self.url)
res_dict = self.controller.index(req, self.uuid)
@@ -510,3 +515,50 @@ class ServerMetaDataTest(test.TestCase):
req.body = jsonutils.dumps(data)
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.update_all, req, self.uuid, data)
+
+
+class BadStateServerMetaDataTest(BaseTest):
+
+ def setUp(self):
+ super(BadStateServerMetaDataTest, self).setUp()
+ self.stubs.Set(nova.db, 'instance_get', self._return_server_in_build)
+ self.stubs.Set(nova.db, 'instance_get_by_uuid',
+ self._return_server_in_build_by_uuid)
+ self.stubs.Set(nova.db, 'instance_metadata_delete',
+ delete_server_metadata)
+
+ def test_invalid_state_on_delete(self):
+ req = fakes.HTTPRequest.blank(self.url + '/key2')
+ req.method = 'DELETE'
+ self.assertRaises(webob.exc.HTTPConflict, self.controller.delete,
+ req, self.uuid, 'key2')
+
+ def test_invalid_state_on_update_metadata(self):
+ self.stubs.Set(nova.db, 'instance_metadata_update',
+ return_create_instance_metadata)
+ req = fakes.HTTPRequest.blank(self.url)
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ expected = {
+ 'metadata': {
+ 'key1': 'updatedvalue',
+ 'key29': 'newkey',
+ }
+ }
+ req.body = jsonutils.dumps(expected)
+ self.assertRaises(webob.exc.HTTPConflict, self.controller.update_all,
+ req, self.uuid, expected)
+
+ def _return_server_in_build(self, context, server_id):
+ return {'id': server_id,
+ 'uuid': '0cc3346e-9fef-4445-abe6-5d2b2690ec64',
+ 'name': 'fake',
+ 'locked': False,
+ 'vm_state': vm_states.BUILDING}
+
+ def _return_server_in_build_by_uuid(self, context, server_uuid):
+ return {'id': 1,
+ 'uuid': '0cc3346e-9fef-4445-abe6-5d2b2690ec64',
+ 'name': 'fake',
+ 'locked': False,
+ 'vm_state': vm_states.BUILDING}
diff --git a/nova/tests/api/openstack/compute/test_servers.py b/nova/tests/api/openstack/compute/test_servers.py
index 43746223a..5456c23af 100644
--- a/nova/tests/api/openstack/compute/test_servers.py
+++ b/nova/tests/api/openstack/compute/test_servers.py
@@ -835,6 +835,12 @@ class ServersControllerTest(test.TestCase):
self.assertEqual(len(servers), 1)
self.assertEqual(servers[0]['id'], server_uuid)
+ def test_get_servers_with_bad_flavor(self):
+ req = fakes.HTTPRequest.blank('/v2/fake/servers?flavor=abcde')
+ servers = self.controller.index(req)['servers']
+
+ self.assertEqual(len(servers), 0)
+
def test_get_servers_allows_status(self):
server_uuid = str(uuid.uuid4())
@@ -2002,6 +2008,55 @@ class ServersControllerCreateTest(test.TestCase):
self.assertNotEqual(reservation_id, None)
self.assertTrue(len(reservation_id) > 1)
+ def test_create_multiple_instances_with_multiple_volume_bdm(self):
+ """
+ Test that a BadRequest is raised if multiple instances
+ are requested with a list of block device mappings for volumes.
+ """
+ self.ext_mgr.extensions = {'os-multiple-create': 'fake'}
+ min_count = 2
+ bdm = [{'device_name': 'foo1', 'volume_id': 'vol-xxxx'},
+ {'device_name': 'foo2', 'volume_id': 'vol-yyyy'}
+ ]
+ params = {
+ 'block_device_mapping': bdm,
+ 'min_count': min_count
+ }
+ old_create = compute_api.API.create
+
+ def create(*args, **kwargs):
+ self.assertEqual(kwargs['min_count'], 2)
+ self.assertEqual(len(kwargs['block_device_mapping']), 2)
+ return old_create(*args, **kwargs)
+
+ self.stubs.Set(compute_api.API, 'create', create)
+ self.assertRaises(webob.exc.HTTPBadRequest,
+ self._test_create_extra, params, no_image=True)
+
+ def test_create_multiple_instances_with_single_volume_bdm(self):
+ """
+ Test that a BadRequest is raised if multiple instances
+ are requested to boot from a single volume.
+ """
+ self.ext_mgr.extensions = {'os-multiple-create': 'fake'}
+ min_count = 2
+ bdm = [{'device_name': 'foo1', 'volume_id': 'vol-xxxx'}]
+ params = {
+ 'block_device_mapping': bdm,
+ 'min_count': min_count
+ }
+ old_create = compute_api.API.create
+
+ def create(*args, **kwargs):
+ self.assertEqual(kwargs['min_count'], 2)
+ self.assertEqual(kwargs['block_device_mapping']['volume_id'],
+ 'vol-xxxx')
+ return old_create(*args, **kwargs)
+
+ self.stubs.Set(compute_api.API, 'create', create)
+ self.assertRaises(webob.exc.HTTPBadRequest,
+ self._test_create_extra, params, no_image=True)
+
def test_create_instance_image_ref_is_bookmark(self):
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
image_href = 'http://localhost/fake/images/%s' % image_uuid
diff --git a/nova/tests/baremetal/test_nova_baremetal_deploy_helper.py b/nova/tests/baremetal/test_nova_baremetal_deploy_helper.py
new file mode 100644
index 000000000..56c3f953e
--- /dev/null
+++ b/nova/tests/baremetal/test_nova_baremetal_deploy_helper.py
@@ -0,0 +1,256 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 NTT DOCOMO, INC.
+# Copyright 2011 OpenStack LLC
+# Copyright 2011 Ilya Alekseyev
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import imp
+import os
+import sys
+import tempfile
+import time
+
+from nova import test
+
+from nova.tests.baremetal.db import base as bm_db_base
+
+
+TOPDIR = os.path.normpath(os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ os.pardir,
+ os.pardir,
+ os.pardir))
+BMDH_PATH = os.path.join(TOPDIR, 'bin', 'nova-baremetal-deploy-helper')
+
+sys.dont_write_bytecode = True
+bmdh = imp.load_source('bmdh', BMDH_PATH)
+sys.dont_write_bytecode = False
+
+_PXECONF_DEPLOY = """
+default deploy
+
+label deploy
+kernel deploy_kernel
+append initrd=deploy_ramdisk
+ipappend 3
+
+label boot
+kernel kernel
+append initrd=ramdisk root=${ROOT}
+"""
+
+_PXECONF_BOOT = """
+default boot
+
+label deploy
+kernel deploy_kernel
+append initrd=deploy_ramdisk
+ipappend 3
+
+label boot
+kernel kernel
+append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef
+"""
+
+
+class WorkerTestCase(bm_db_base.BMDBTestCase):
+ def setUp(self):
+ super(WorkerTestCase, self).setUp()
+ self.worker = bmdh.Worker()
+ # Make tearDown() fast
+ self.worker.queue_timeout = 0.1
+ self.worker.start()
+
+ def tearDown(self):
+ if self.worker.isAlive():
+ self.worker.stop = True
+ self.worker.join(timeout=1)
+ super(WorkerTestCase, self).tearDown()
+
+ def wait_queue_empty(self, timeout):
+ for _ in xrange(int(timeout / 0.1)):
+ if bmdh.QUEUE.empty():
+ break
+ time.sleep(0.1)
+
+ def test_run_calls_deploy(self):
+ """Check all queued requests are passed to deploy()."""
+ history = []
+
+ def fake_deploy(**params):
+ history.append(params)
+
+ self.stubs.Set(bmdh, 'deploy', fake_deploy)
+ params_list = [{'fake1': ''}, {'fake2': ''}, {'fake3': ''}]
+ for (dep_id, params) in enumerate(params_list):
+ bmdh.QUEUE.put((dep_id, params))
+ self.wait_queue_empty(1)
+ self.assertEqual(params_list, history)
+
+ def test_run_with_failing_deploy(self):
+ """Check a worker keeps on running even if deploy() raises
+ an exception.
+ """
+ history = []
+
+ def fake_deploy(**params):
+ history.append(params)
+ # always fail
+ raise Exception('test')
+
+ self.stubs.Set(bmdh, 'deploy', fake_deploy)
+ params_list = [{'fake1': ''}, {'fake2': ''}, {'fake3': ''}]
+ for (dep_id, params) in enumerate(params_list):
+ bmdh.QUEUE.put((dep_id, params))
+ self.wait_queue_empty(1)
+ self.assertEqual(params_list, history)
+
+
+class PhysicalWorkTestCase(test.TestCase):
+ def setUp(self):
+ super(PhysicalWorkTestCase, self).setUp()
+
+ def noop(*args, **kwargs):
+ pass
+
+ self.stubs.Set(time, 'sleep', noop)
+
+ def test_deploy(self):
+ """Check loosely all functions are called with right args."""
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ pxe_config_path = '/tmp/abc/pxeconfig'
+ root_mb = 128
+ swap_mb = 64
+
+ dev = '/dev/fake'
+ root_part = '/dev/fake-part1'
+ swap_part = '/dev/fake-part2'
+ root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
+
+ self.mox.StubOutWithMock(bmdh, 'get_dev')
+ self.mox.StubOutWithMock(bmdh, 'get_image_mb')
+ self.mox.StubOutWithMock(bmdh, 'discovery')
+ self.mox.StubOutWithMock(bmdh, 'login_iscsi')
+ self.mox.StubOutWithMock(bmdh, 'logout_iscsi')
+ self.mox.StubOutWithMock(bmdh, 'make_partitions')
+ self.mox.StubOutWithMock(bmdh, 'is_block_device')
+ self.mox.StubOutWithMock(bmdh, 'dd')
+ self.mox.StubOutWithMock(bmdh, 'mkswap')
+ self.mox.StubOutWithMock(bmdh, 'block_uuid')
+ self.mox.StubOutWithMock(bmdh, 'switch_pxe_config')
+ self.mox.StubOutWithMock(bmdh, 'notify')
+
+ bmdh.get_dev(address, port, iqn, lun).AndReturn(dev)
+ bmdh.get_image_mb(image_path).AndReturn(1) # < root_mb
+ bmdh.discovery(address, port)
+ bmdh.login_iscsi(address, port, iqn)
+ bmdh.is_block_device(dev).AndReturn(True)
+ bmdh.make_partitions(dev, root_mb, swap_mb)
+ bmdh.is_block_device(root_part).AndReturn(True)
+ bmdh.is_block_device(swap_part).AndReturn(True)
+ bmdh.dd(image_path, root_part)
+ bmdh.mkswap(swap_part)
+ bmdh.block_uuid(root_part).AndReturn(root_uuid)
+ bmdh.logout_iscsi(address, port, iqn)
+ bmdh.switch_pxe_config(pxe_config_path, root_uuid)
+ bmdh.notify(address, 10000)
+ self.mox.ReplayAll()
+
+ bmdh.deploy(address, port, iqn, lun, image_path, pxe_config_path,
+ root_mb, swap_mb)
+
+ def test_always_logout_iscsi(self):
+ """logout_iscsi() must be called once login_iscsi() is called."""
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ pxe_config_path = '/tmp/abc/pxeconfig'
+ root_mb = 128
+ swap_mb = 64
+
+ dev = '/dev/fake'
+
+ self.mox.StubOutWithMock(bmdh, 'get_dev')
+ self.mox.StubOutWithMock(bmdh, 'get_image_mb')
+ self.mox.StubOutWithMock(bmdh, 'discovery')
+ self.mox.StubOutWithMock(bmdh, 'login_iscsi')
+ self.mox.StubOutWithMock(bmdh, 'logout_iscsi')
+ self.mox.StubOutWithMock(bmdh, 'work_on_disk')
+
+ class TestException(Exception):
+ pass
+
+ bmdh.get_dev(address, port, iqn, lun).AndReturn(dev)
+ bmdh.get_image_mb(image_path).AndReturn(1) # < root_mb
+ bmdh.discovery(address, port)
+ bmdh.login_iscsi(address, port, iqn)
+ bmdh.work_on_disk(dev, root_mb, swap_mb, image_path).\
+ AndRaise(TestException)
+ bmdh.logout_iscsi(address, port, iqn)
+ self.mox.ReplayAll()
+
+ self.assertRaises(TestException,
+ bmdh.deploy,
+ address, port, iqn, lun, image_path,
+ pxe_config_path, root_mb, swap_mb)
+
+
+class SwitchPxeConfigTestCase(test.TestCase):
+ def setUp(self):
+ super(SwitchPxeConfigTestCase, self).setUp()
+ (fd, self.fname) = tempfile.mkstemp()
+ os.write(fd, _PXECONF_DEPLOY)
+ os.close(fd)
+
+ def tearDown(self):
+ os.unlink(self.fname)
+ super(SwitchPxeConfigTestCase, self).tearDown()
+
+ def test_switch_pxe_config(self):
+ bmdh.switch_pxe_config(self.fname,
+ '12345678-1234-1234-1234-1234567890abcdef')
+ with open(self.fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(pxeconf, _PXECONF_BOOT)
+
+
+class OtherFunctionTestCase(test.TestCase):
+ def test_get_dev(self):
+ expected = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9'
+ actual = bmdh.get_dev('1.2.3.4', 5678, 'iqn.fake', 9)
+ self.assertEqual(expected, actual)
+
+ def test_get_image_mb(self):
+ mb = 1024 * 1024
+ size = None
+
+ def fake_getsize(path):
+ return size
+
+ self.stubs.Set(os.path, 'getsize', fake_getsize)
+ size = 0
+ self.assertEqual(bmdh.get_image_mb('x'), 0)
+ size = 1
+ self.assertEqual(bmdh.get_image_mb('x'), 1)
+ size = mb
+ self.assertEqual(bmdh.get_image_mb('x'), 1)
+ size = mb + 1
+ self.assertEqual(bmdh.get_image_mb('x'), 2)
diff --git a/nova/tests/baremetal/test_pxe.py b/nova/tests/baremetal/test_pxe.py
index 73ef8caa3..7e44581e8 100644
--- a/nova/tests/baremetal/test_pxe.py
+++ b/nova/tests/baremetal/test_pxe.py
@@ -22,10 +22,7 @@
import os
import mox
-from testtools.matchers import Contains
-from testtools.matchers import MatchesAll
-from testtools.matchers import Not
-from testtools.matchers import StartsWith
+from testtools import matchers
from nova import exception
from nova.openstack.common import cfg
@@ -120,26 +117,26 @@ class PXEClassMethodsTestCase(BareMetalPXETestCase):
'ari_path': 'ggg',
}
config = pxe.build_pxe_config(**args)
- self.assertThat(config, StartsWith('default deploy'))
+ self.assertThat(config, matchers.StartsWith('default deploy'))
# deploy bits are in the deploy section
start = config.index('label deploy')
end = config.index('label boot')
- self.assertThat(config[start:end], MatchesAll(
- Contains('kernel ddd'),
- Contains('initrd=eee'),
- Contains('deployment_id=aaa'),
- Contains('deployment_key=bbb'),
- Contains('iscsi_target_iqn=ccc'),
- Not(Contains('kernel fff')),
+ self.assertThat(config[start:end], matchers.MatchesAll(
+ matchers.Contains('kernel ddd'),
+ matchers.Contains('initrd=eee'),
+ matchers.Contains('deployment_id=aaa'),
+ matchers.Contains('deployment_key=bbb'),
+ matchers.Contains('iscsi_target_iqn=ccc'),
+ matchers.Not(matchers.Contains('kernel fff')),
))
# boot bits are in the boot section
start = config.index('label boot')
- self.assertThat(config[start:], MatchesAll(
- Contains('kernel fff'),
- Contains('initrd=ggg'),
- Not(Contains('kernel ddd')),
+ self.assertThat(config[start:], matchers.MatchesAll(
+ matchers.Contains('kernel fff'),
+ matchers.Contains('initrd=ggg'),
+ matchers.Not(matchers.Contains('kernel ddd')),
))
def test_build_network_config(self):
diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py
index 092fd940a..dc381d800 100644
--- a/nova/tests/compute/test_compute.py
+++ b/nova/tests/compute/test_compute.py
@@ -58,7 +58,7 @@ import nova.policy
from nova import quota
from nova import test
from nova.tests.compute import fake_resource_tracker
-from nova.tests.db.fakes import FakeModel
+from nova.tests.db import fakes as db_fakes
from nova.tests import fake_network
from nova.tests.image import fake as fake_image
from nova.tests import matchers
@@ -2631,8 +2631,6 @@ class ComputeTestCase(BaseTestCase):
'setup_networks_on_host')
self.mox.StubOutWithMock(self.compute.network_api,
'migrate_instance_finish')
- self.mox.StubOutWithMock(self.compute.driver,
- 'post_live_migration_at_destination')
self.mox.StubOutWithMock(self.compute, '_get_power_state')
self.mox.StubOutWithMock(self.compute, '_instance_update')
@@ -2650,10 +2648,12 @@ class ComputeTestCase(BaseTestCase):
self.compute.network_api.migrate_instance_finish(admin_ctxt,
instance, migration)
fake_net_info = []
+ fake_block_dev_info = {'foo': 'bar'}
self.compute.driver.post_live_migration_at_destination(admin_ctxt,
- instance,
- fake_net_info,
- False)
+ instance,
+ fake_net_info,
+ False,
+ fake_block_dev_info)
self.compute._get_power_state(admin_ctxt, instance).AndReturn(
'fake_power_state')
@@ -2961,7 +2961,7 @@ class ComputeTestCase(BaseTestCase):
call_info['expected_instance'] = instances[0]
self.compute._heal_instance_info_cache(ctxt)
self.assertEqual(call_info['get_all_by_host'], 2)
- # Stays the same, beacuse the instance came from the DB
+ # Stays the same, because the instance came from the DB
self.assertEqual(call_info['get_by_uuid'], 3)
self.assertEqual(call_info['get_nw_info'], 4)
@@ -4688,6 +4688,60 @@ class ComputeAPITestCase(BaseTestCase):
self.assertEqual(properties['d'], 'd')
self.assertFalse('spam' in properties)
+ def _do_test_snapshot_image_service_fails(self, method, image_id):
+ # Ensure task_state remains at None if image service fails.
+ def fake_fails(*args, **kwargs):
+ raise test.TestingException()
+
+ restore = getattr(fake_image._FakeImageService, method)
+ self.stubs.Set(fake_image._FakeImageService, method, fake_fails)
+
+ instance = self._create_fake_instance()
+ self.assertRaises(test.TestingException,
+ self.compute_api.snapshot,
+ self.context,
+ instance,
+ 'no_image_snapshot',
+ image_id=image_id)
+
+ self.stubs.Set(fake_image._FakeImageService, method, restore)
+ db_instance = db.instance_get_all(self.context)[0]
+ self.assertIsNone(db_instance['task_state'])
+
+ def test_snapshot_image_creation_fails(self):
+ self._do_test_snapshot_image_service_fails('create', None)
+
+ def test_snapshot_image_show_fails(self):
+ self._do_test_snapshot_image_service_fails('show', 'image')
+
+ def _do_test_backup_image_service_fails(self, method, image_id):
+ # Ensure task_state remains at None if image service fails.
+ def fake_fails(*args, **kwargs):
+ raise test.TestingException()
+
+ restore = getattr(fake_image._FakeImageService, method)
+ self.stubs.Set(fake_image._FakeImageService, method, fake_fails)
+
+ instance = self._create_fake_instance()
+ self.assertRaises(test.TestingException,
+ self.compute_api.backup,
+ self.context,
+ instance,
+ 'no_image_backup',
+ 'DAILY',
+ 0,
+ image_id=image_id)
+
+ self.stubs.Set(fake_image._FakeImageService, method, restore)
+ db_instance = db.instance_get_all(self.context)[0]
+ self.assertIsNone(db_instance['task_state'])
+
+ def test_backup_image_creation_fails(self):
+ self._do_test_backup_image_service_fails('create', None)
+
+ def test_backup_image_show_fails(self):
+ self._do_test_backup_image_service_fails('show', 'image')
+
def test_backup(self):
# Can't backup an instance which is already being backed up.
instance = self._create_fake_instance()
@@ -5255,14 +5309,14 @@ class ComputeAPITestCase(BaseTestCase):
self.assertTrue(instance3['uuid'] in instance_uuids)
self.assertTrue(instance4['uuid'] in instance_uuids)
- # multiple criterias as a dict
+ # multiple criteria as a dict
instances = self.compute_api.get_all(c,
search_opts={'metadata': {'key3': 'value3',
'key4': 'value4'}})
self.assertEqual(len(instances), 1)
self.assertEqual(instances[0]['uuid'], instance4['uuid'])
- # multiple criterias as a list
+ # multiple criteria as a list
instances = self.compute_api.get_all(c,
search_opts={'metadata': [{'key4': 'value4'},
{'key3': 'value3'}]})
@@ -5287,7 +5341,7 @@ class ComputeAPITestCase(BaseTestCase):
_context = context.get_admin_context()
instance = self._create_fake_instance({'metadata': {'key1': 'value1'}})
- instance = dict(instance)
+ instance = dict(instance.iteritems())
metadata = self.compute_api.get_instance_metadata(_context, instance)
self.assertEqual(metadata, {'key1': 'value1'})
@@ -5334,6 +5388,24 @@ class ComputeAPITestCase(BaseTestCase):
db.instance_destroy(_context, instance['uuid'])
+ def test_disallow_metadata_changes_during_building(self):
+ def fake_change_instance_metadata(inst, ctxt, diff, instance=None,
+ instance_uuid=None):
+ pass
+ self.stubs.Set(compute_rpcapi.ComputeAPI, 'change_instance_metadata',
+ fake_change_instance_metadata)
+
+ instance = self._create_fake_instance({'vm_state': vm_states.BUILDING})
+ instance = dict(instance)
+
+ self.assertRaises(exception.InstanceInvalidState,
+ self.compute_api.delete_instance_metadata, self.context,
+ instance, "key")
+
+ self.assertRaises(exception.InstanceInvalidState,
+ self.compute_api.update_instance_metadata, self.context,
+ instance, "key")
+
def test_get_instance_faults(self):
# Get an instances latest fault.
instance = self._create_fake_instance()
@@ -5861,11 +5933,11 @@ class ComputeAPITestCase(BaseTestCase):
instance = self._create_fake_instance()
def rule_get(*args, **kwargs):
- mock_rule = FakeModel({'parent_group_id': 1})
+ mock_rule = db_fakes.FakeModel({'parent_group_id': 1})
return [mock_rule]
def group_get(*args, **kwargs):
- mock_group = FakeModel({'instances': [instance]})
+ mock_group = db_fakes.FakeModel({'instances': [instance]})
return mock_group
self.stubs.Set(
@@ -5890,11 +5962,11 @@ class ComputeAPITestCase(BaseTestCase):
instance = self._create_fake_instance()
def rule_get(*args, **kwargs):
- mock_rule = FakeModel({'parent_group_id': 1})
+ mock_rule = db_fakes.FakeModel({'parent_group_id': 1})
return [mock_rule]
def group_get(*args, **kwargs):
- mock_group = FakeModel({'instances': [instance]})
+ mock_group = db_fakes.FakeModel({'instances': [instance]})
return mock_group
self.stubs.Set(
@@ -5917,11 +5989,11 @@ class ComputeAPITestCase(BaseTestCase):
def test_secgroup_refresh_none(self):
def rule_get(*args, **kwargs):
- mock_rule = FakeModel({'parent_group_id': 1})
+ mock_rule = db_fakes.FakeModel({'parent_group_id': 1})
return [mock_rule]
def group_get(*args, **kwargs):
- mock_group = FakeModel({'instances': []})
+ mock_group = db_fakes.FakeModel({'instances': []})
return mock_group
self.stubs.Set(
@@ -5939,7 +6011,7 @@ class ComputeAPITestCase(BaseTestCase):
instance = self._create_fake_instance()
def group_get(*args, **kwargs):
- mock_group = FakeModel({'instances': [instance]})
+ mock_group = db_fakes.FakeModel({'instances': [instance]})
return mock_group
self.stubs.Set(self.compute_api.db, 'security_group_get', group_get)
@@ -5960,7 +6032,7 @@ class ComputeAPITestCase(BaseTestCase):
instance = self._create_fake_instance()
def group_get(*args, **kwargs):
- mock_group = FakeModel({'instances': [instance]})
+ mock_group = db_fakes.FakeModel({'instances': [instance]})
return mock_group
self.stubs.Set(self.compute_api.db, 'security_group_get', group_get)
@@ -5979,7 +6051,7 @@ class ComputeAPITestCase(BaseTestCase):
def test_secrule_refresh_none(self):
def group_get(*args, **kwargs):
- mock_group = FakeModel({'instances': []})
+ mock_group = db_fakes.FakeModel({'instances': []})
return mock_group
self.stubs.Set(self.compute_api.db, 'security_group_get', group_get)
@@ -6430,7 +6502,7 @@ class DisabledInstanceTypesTestCase(BaseTestCase):
"""
Some instance-types are marked 'disabled' which means that they will not
show up in customer-facing listings. We do, however, want those
- instance-types to be availble for emergency migrations and for rebuilding
+ instance-types to be available for emergency migrations and for rebuilding
of existing instances.
One legitimate use of the 'disabled' field would be when phasing out a
diff --git a/nova/tests/compute/test_compute_utils.py b/nova/tests/compute/test_compute_utils.py
index 6e7227d4c..4372039e0 100644
--- a/nova/tests/compute/test_compute_utils.py
+++ b/nova/tests/compute/test_compute_utils.py
@@ -359,6 +359,9 @@ class UsageInfoTestCase(test.TestCase):
extra_usage_info = {'image_name': 'fake_name'}
db.instance_system_metadata_update(self.context, instance['uuid'],
sys_metadata, False)
+ # NOTE(russellb) Make sure our instance has the latest system_metadata
+ # in it.
+ instance = db.instance_get(self.context, instance_id)
compute_utils.notify_about_instance_usage(self.context, instance,
'create.start', extra_usage_info=extra_usage_info)
self.assertEquals(len(test_notifier.NOTIFICATIONS), 1)
@@ -382,14 +385,3 @@ class UsageInfoTestCase(test.TestCase):
image_ref_url = "%s/images/1" % glance.generate_glance_url()
self.assertEquals(payload['image_ref_url'], image_ref_url)
self.compute.terminate_instance(self.context, instance)
-
-
-class MetadataToDictTestCase(test.TestCase):
- def test_metadata_to_dict(self):
- self.assertEqual(compute_utils.metadata_to_dict(
- [{'key': 'foo1', 'value': 'bar'},
- {'key': 'foo2', 'value': 'baz'}]),
- {'foo1': 'bar', 'foo2': 'baz'})
-
- def test_metadata_to_dict_empty(self):
- self.assertEqual(compute_utils.metadata_to_dict([]), {})
diff --git a/nova/tests/conductor/test_conductor.py b/nova/tests/conductor/test_conductor.py
index d010b454f..30d176bbd 100644
--- a/nova/tests/conductor/test_conductor.py
+++ b/nova/tests/conductor/test_conductor.py
@@ -335,14 +335,23 @@ class _BaseTestCase(object):
def test_instance_get_active_by_window(self):
self.mox.StubOutWithMock(db, 'instance_get_active_by_window_joined')
- db.instance_get_active_by_window_joined(self.context, 'fake-begin',
- 'fake-end', 'fake-proj',
- 'fake-host')
+ db.instance_get_active_by_window(self.context, 'fake-begin',
+ 'fake-end', 'fake-proj',
+ 'fake-host')
self.mox.ReplayAll()
self.conductor.instance_get_active_by_window(self.context,
'fake-begin', 'fake-end',
'fake-proj', 'fake-host')
+ def test_instance_get_active_by_window_joined(self):
+ self.mox.StubOutWithMock(db, 'instance_get_active_by_window_joined')
+ db.instance_get_active_by_window_joined(self.context, 'fake-begin',
+ 'fake-end', 'fake-proj',
+ 'fake-host')
+ self.mox.ReplayAll()
+ self.conductor.instance_get_active_by_window_joined(
+ self.context, 'fake-begin', 'fake-end', 'fake-proj', 'fake-host')
+
def test_instance_destroy(self):
self.mox.StubOutWithMock(db, 'instance_destroy')
db.instance_destroy(self.context, 'fake-uuid')
diff --git a/nova/tests/conf_fixture.py b/nova/tests/conf_fixture.py
index 9155a3f68..2f4d0ebb1 100644
--- a/nova/tests/conf_fixture.py
+++ b/nova/tests/conf_fixture.py
@@ -22,7 +22,7 @@ from nova import config
from nova import ipv6
from nova.openstack.common import cfg
from nova import paths
-from nova.tests.utils import cleanup_dns_managers
+from nova.tests import utils
CONF = cfg.CONF
CONF.import_opt('use_ipv6', 'nova.netconf')
@@ -70,5 +70,5 @@ class ConfFixture(fixtures.Fixture):
self.conf.set_default('vlan_interface', 'eth0')
config.parse_args([], default_config_files=[])
self.addCleanup(self.conf.reset)
- self.addCleanup(cleanup_dns_managers)
+ self.addCleanup(utils.cleanup_dns_managers)
self.addCleanup(ipv6.api.reset_backend)
diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py
index acefa856c..d8780744d 100644
--- a/nova/tests/fake_policy.py
+++ b/nova/tests/fake_policy.py
@@ -105,6 +105,7 @@ policy_data = """
"compute_extension:admin_actions:migrate": "",
"compute_extension:aggregates": "",
"compute_extension:agents": "",
+ "compute_extension:baremetal_nodes": "",
"compute_extension:cells": "",
"compute_extension:certificates": "",
"compute_extension:cloudpipe": "",
@@ -158,6 +159,8 @@ policy_data = """
"compute_extension:volumes": "",
"compute_extension:volumetypes": "",
"compute_extension:zones": "",
+ "compute_extension:availability_zone:list": "",
+ "compute_extension:availability_zone:detail": "is_admin:True",
"volume:create": "",
diff --git a/nova/tests/fakelibvirt.py b/nova/tests/fakelibvirt.py
index a573b7d1c..259d192cb 100644
--- a/nova/tests/fakelibvirt.py
+++ b/nova/tests/fakelibvirt.py
@@ -179,6 +179,7 @@ class Domain(object):
self._def = self._parse_definition(xml)
self._has_saved_state = False
self._snapshots = {}
+ self._id = self._connection._id_counter
def _parse_definition(self, xml):
try:
@@ -299,6 +300,9 @@ class Domain(object):
self._state = VIR_DOMAIN_SHUTOFF
self._connection._mark_not_running(self)
+ def ID(self):
+ return self._id
+
def name(self):
return self._def['name']
@@ -517,6 +521,8 @@ class Connection(object):
if dom._transient:
self._undefine(dom)
+ dom._id = -1
+
for (k, v) in self._running_vms.iteritems():
if v == dom:
del self._running_vms[k]
diff --git a/nova/tests/image/test_s3.py b/nova/tests/image/test_s3.py
index 4f8790cc7..0afe397a2 100644
--- a/nova/tests/image/test_s3.py
+++ b/nova/tests/image/test_s3.py
@@ -129,7 +129,7 @@ class TestS3ImageService(test.TestCase):
'snapshot_id': 'snap-12345678',
'delete_on_termination': True},
{'device_name': '/dev/sda2',
- 'virutal_name': 'ephemeral0'},
+ 'virtual_name': 'ephemeral0'},
{'device_name': '/dev/sdb0',
'no_device': True}]}}
_manifest, image, image_uuid = self.image_service._s3_parse_manifest(
@@ -156,7 +156,7 @@ class TestS3ImageService(test.TestCase):
'snapshot_id': 'snap-12345678',
'delete_on_termination': True},
{'device_name': '/dev/sda2',
- 'virutal_name': 'ephemeral0'},
+ 'virtual_name': 'ephemeral0'},
{'device_name': '/dev/sdb0',
'no_device': True}]
self.assertEqual(block_device_mapping, expected_bdm)
diff --git a/nova/tests/integrated/api_samples/README.rst b/nova/tests/integrated/api_samples/README.rst
index 065df1d32..b2ad71d4c 100644
--- a/nova/tests/integrated/api_samples/README.rst
+++ b/nova/tests/integrated/api_samples/README.rst
@@ -1,11 +1,21 @@
Api Samples
===========
-Samples in this directory are automatically generated from the api samples
-integration tests. To regenerate the samples, simply set GENERATE_SAMPLES
-in the environment before running the tests. For example:
+This part of the tree contains templates for API samples. The
+documentation in doc/api_samples is completely autogenerated from the
+tests in this directory.
+
+To add a new api sample, add tests for the common passing and failing
+cases in this directory for your extension, and modify test_samples.py
+for your tests. There should be both JSON and XML tests included.
+
+Then run the following command:
GENERATE_SAMPLES=True tox -epy27 nova.tests.integrated
+Which will create the files on doc/api_samples.
+
If new tests are added or the .tpl files are changed due to bug fixes, the
-samples should be regenerated so they are in sync with the templates.
+samples must be regenerated so they are in sync with the templates, as
+there is an additional test which reloads the documentation and
+ensures that it's in sync.
diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
index fe0613646..be2fabec4 100644
--- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl
@@ -89,6 +89,14 @@
"updated": "%(timestamp)s"
},
{
+ "alias": "os-baremetal-nodes",
+ "description": "%(text)s",
+ "links": [],
+ "name": "BareMetalNodes",
+ "namespace": "http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2",
+ "updated": "%(timestamp)s"
+ },
+ {
"alias": "os-cells",
"description": "%(text)s",
"links": [],
diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
index 2051d891a..ae2e9ff9e 100644
--- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl
@@ -33,6 +33,9 @@
<extension alias="os-agents" name="Agents" namespace="http://docs.openstack.org/compute/ext/agents/api/v2" updated="%(timestamp)s">
<description>%(text)s</description>
</extension>
+ <extension alias="os-baremetal-nodes" name="BareMetalNodes" namespace="http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2" updated="%(timestamp)s">
+ <description>%(text)s</description>
+ </extension>
<extension alias="os-cells" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/cells/api/v1.1" name="Cells">
<description>%(text)s</description>
</extension>
diff --git a/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-details-resp.json.tpl b/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-details-resp.json.tpl
new file mode 100644
index 000000000..6d44692e1
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-details-resp.json.tpl
@@ -0,0 +1,48 @@
+{
+ "availabilityZoneInfo": [
+ {
+ "zoneName": "zone-1",
+ "zoneState": {
+ "available": true
+ },
+ "hosts": {
+ "fake_host-1": {
+ "nova-compute": {
+ "active": true,
+ "available": true,
+ "updated_at": "2012-12-26T14:45:25.000000"
+ }
+ }
+ }
+ },
+ {
+ "zoneName": "internal",
+ "zoneState": {
+ "available": true
+ },
+ "hosts": {
+ "fake_host-1": {
+ "nova-sched": {
+ "active": true,
+ "available": true,
+ "updated_at": "2012-12-26T14:45:25.000000"
+ }
+ },
+ "fake_host-2": {
+ "nova-network": {
+ "active": true,
+ "available": false,
+ "updated_at": "2012-12-26T14:45:24.000000"
+ }
+ }
+ }
+ },
+ {
+ "zoneName": "zone-2",
+ "zoneState": {
+ "available": false
+ },
+ "hosts": null
+ }
+ ]
+} \ No newline at end of file
diff --git a/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-details-resp.xml.tpl b/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-details-resp.xml.tpl
new file mode 100644
index 000000000..856a64957
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-details-resp.xml.tpl
@@ -0,0 +1,44 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<availabilityZones
+ xmlns:os-availability-zone="http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1">
+ <availabilityZone name="zone-1">
+ <zoneState available="True" />
+ <hosts>
+ <host name="fake_host-1">
+ <services>
+ <service name="nova-compute">
+ <serviceState available="True" active="True"
+ updated_at="2012-12-26 14:45:25" />
+ </service>
+ </services>
+ </host>
+ </hosts>
+ <metadata />
+ </availabilityZone>
+ <availabilityZone name="internal">
+ <zoneState available="True" />
+ <hosts>
+ <host name="fake_host-1">
+ <services>
+ <service name="nova-sched">
+ <serviceState available="True" active="True"
+ updated_at="2012-12-26 14:45:25" />
+ </service>
+ </services>
+ </host>
+ <host name="fake_host-2">
+ <services>
+ <service name="nova-network">
+ <serviceState available="False" active="True"
+ updated_at="2012-12-26 14:45:24" />
+ </service>
+ </services>
+ </host>
+ </hosts>
+ <metadata />
+ </availabilityZone>
+ <availabilityZone name="zone-2">
+ <zoneState available="False" />
+ <metadata />
+ </availabilityZone>
+</availabilityZones> \ No newline at end of file
diff --git a/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-get-resp.json.tpl
new file mode 100644
index 000000000..381708aaf
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-get-resp.json.tpl
@@ -0,0 +1,18 @@
+{
+ "availabilityZoneInfo": [
+ {
+ "zoneName": "zone-1",
+ "zoneState": {
+ "available": true
+ },
+ "hosts": null
+ },
+ {
+ "zoneName": "zone-2",
+ "zoneState": {
+ "available": false
+ },
+ "hosts": null
+ }
+ ]
+} \ No newline at end of file
diff --git a/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-get-resp.xml.tpl
new file mode 100644
index 000000000..1eff177de
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-availability-zone/availability-zone-get-resp.xml.tpl
@@ -0,0 +1,12 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<availabilityZones
+ xmlns:os-availability-zone="http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1">
+ <availabilityZone name="zone-1">
+ <zoneState available="True" />
+ <metadata />
+ </availabilityZone>
+ <availabilityZone name="zone-2">
+ <zoneState available="False" />
+ <metadata />
+ </availabilityZone>
+</availabilityZones> \ No newline at end of file
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json.tpl
new file mode 100644
index 000000000..fbc9e5b8d
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json.tpl
@@ -0,0 +1,5 @@
+{
+ "add_interface": {
+ "address": "%(address)s"
+ }
+}
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml.tpl
new file mode 100644
index 000000000..abbbe895b
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml.tpl
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<add_interface
+ address="%(address)s"
+/>
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json.tpl
new file mode 100644
index 000000000..268b41f08
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json.tpl
@@ -0,0 +1,8 @@
+{
+ "interface": {
+ "id": %(interface_id)s,
+ "address": "aa:aa:aa:aa:aa:aa",
+ "datapath_id": null,
+ "port_no": null
+ }
+}
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml.tpl
new file mode 100644
index 000000000..e5d34f92b
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml.tpl
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface
+ id="%(interface_id)s"
+ address="aa:aa:aa:aa:aa:aa"
+ datapath_id="None"
+ port_no="None"
+/>
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.json.tpl
new file mode 100644
index 000000000..fd2ae101f
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.json.tpl
@@ -0,0 +1,14 @@
+{
+ "node": {
+ "service_host": "host",
+ "cpus": 8,
+ "memory_mb": 8192,
+ "local_gb": 128,
+ "pm_address": "10.1.2.3",
+ "pm_user": "pm_user",
+ "pm_password": "pm_pass",
+ "prov_mac_address": "12:34:56:78:90:ab",
+ "prov_vlan_id": 1234,
+ "terminal_port": 8000
+ }
+}
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml.tpl
new file mode 100644
index 000000000..78a2c1c74
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml.tpl
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node
+ service_host="host"
+ cpus="8"
+ memory_mb="8192"
+ local_gb="128"
+ pm_address="10.1.2.3"
+ pm_user="pm_user"
+ prov_mac_address="12:34:56:78:90:ab"
+ prov_vlan_id="1234"
+ terminal_port="8000"
+/>
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json.tpl
new file mode 100644
index 000000000..d3911b49d
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json.tpl
@@ -0,0 +1,16 @@
+{
+ "node": {
+ "service_host": "host",
+ "cpus": 8,
+ "memory_mb": 8192,
+ "local_gb": 128,
+ "pm_address": "10.1.2.3",
+ "pm_user": "pm_user",
+ "prov_mac_address": "12:34:56:78:90:ab",
+ "prov_vlan_id": 1234,
+ "terminal_port": 8000,
+ "instance_uuid": null,
+ "id": %(node_id)s,
+ "interfaces": []
+ }
+}
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml.tpl
new file mode 100644
index 000000000..f21d16a11
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml.tpl
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node
+ service_host="host"
+ cpus="8"
+ memory_mb="8192"
+ local_gb="128"
+ pm_address="10.1.2.3"
+ pm_user="pm_user"
+ prov_mac_address="12:34:56:78:90:ab"
+ prov_vlan_id="1234"
+ terminal_port="8000"
+ instance_uuid="None"
+ id="%(node_id)s">
+ <interfaces/>
+</node>
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json.tpl
new file mode 100644
index 000000000..9b04a9cea
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json.tpl
@@ -0,0 +1,21 @@
+{
+ "nodes": [{
+ "service_host": "host",
+ "cpus": 8,
+ "memory_mb": 8192,
+ "local_gb": 128,
+ "pm_address": "10.1.2.3",
+ "pm_user": "pm_user",
+ "prov_mac_address": "12:34:56:78:90:ab",
+ "prov_vlan_id": 1234,
+ "terminal_port": 8000,
+ "instance_uuid": null,
+ "id": %(node_id)s,
+ "interfaces": [{
+ "id": %(interface_id)s,
+ "address": "%(address)s",
+ "datapath_id": null,
+ "port_no": null
+ }]
+ }]
+}
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml.tpl
new file mode 100644
index 000000000..f17b6cc20
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml.tpl
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<nodes>
+<node
+ service_host="host"
+ cpus="8"
+ memory_mb="8192"
+ local_gb="128"
+ pm_address="10.1.2.3"
+ pm_user="pm_user"
+ prov_mac_address="12:34:56:78:90:ab"
+ prov_vlan_id="1234"
+ terminal_port="8000"
+ instance_uuid="None"
+ id="%(node_id)s">
+ <interfaces>
+ <interface id="%(interface_id)s" address="%(address)s" datapath_id="None" port_no="None"/>
+ </interfaces>
+</node>
+</nodes>
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json.tpl
new file mode 100644
index 000000000..eb76a9140
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json.tpl
@@ -0,0 +1,5 @@
+{
+ "remove_interface": {
+ "address": "%(address)s"
+ }
+}
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml.tpl
new file mode 100644
index 000000000..089c94e86
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml.tpl
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<remove_interface
+ address="%(address)s"
+/>
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json.tpl
new file mode 100644
index 000000000..701b33d24
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json.tpl
@@ -0,0 +1,21 @@
+{
+ "node": {
+ "service_host": "host",
+ "cpus": 8,
+ "memory_mb": 8192,
+ "local_gb": 128,
+ "pm_address": "10.1.2.3",
+ "pm_user": "pm_user",
+ "prov_mac_address": "12:34:56:78:90:ab",
+ "prov_vlan_id": 1234,
+ "terminal_port": 8000,
+ "instance_uuid": null,
+ "id": %(node_id)s,
+ "interfaces": [{
+ "id": %(interface_id)s,
+ "address": "%(address)s",
+ "datapath_id": null,
+ "port_no": null
+ }]
+ }
+}
diff --git a/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml.tpl b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml.tpl
new file mode 100644
index 000000000..36e5568e5
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml.tpl
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<node
+ service_host="host"
+ cpus="8"
+ memory_mb="8192"
+ local_gb="128"
+ pm_address="10.1.2.3"
+ pm_user="pm_user"
+ prov_mac_address="12:34:56:78:90:ab"
+ prov_vlan_id="1234"
+ terminal_port="8000"
+ instance_uuid="None"
+ id="%(node_id)s">
+ <interfaces>
+ <interface id="%(interface_id)s" address="%(address)s" datapath_id="None" port_no="None"/>
+ </interfaces>
+</node>
diff --git a/nova/tests/integrated/api_samples/os-floating-ip-pools/floatingippools-list-resp.json.tpl b/nova/tests/integrated/api_samples/os-floating-ip-pools/floatingippools-list-resp.json.tpl
new file mode 100644
index 000000000..607109d70
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-floating-ip-pools/floatingippools-list-resp.json.tpl
@@ -0,0 +1,10 @@
+{
+ "floating_ip_pools": [
+ {
+ "name": "%(pool1)s"
+ },
+ {
+ "name": "%(pool2)s"
+ }
+ ]
+}
diff --git a/nova/tests/integrated/api_samples/os-floating-ip-pools/floatingippools-list-resp.xml.tpl b/nova/tests/integrated/api_samples/os-floating-ip-pools/floatingippools-list-resp.xml.tpl
new file mode 100644
index 000000000..ae4b3a4bb
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-floating-ip-pools/floatingippools-list-resp.xml.tpl
@@ -0,0 +1,4 @@
+<floating_ip_pools>
+ <floating_ip_pool name="%(pool1)s"/>
+ <floating_ip_pool name="%(pool2)s"/>
+</floating_ip_pools>
diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py
index 949f14177..4cadbf9e5 100644
--- a/nova/tests/integrated/test_api_samples.py
+++ b/nova/tests/integrated/test_api_samples.py
@@ -22,13 +22,13 @@ import re
import urllib
import uuid as uuid_lib
-from coverage import coverage
+import coverage
from lxml import etree
from nova.api.metadata import password
from nova.api.openstack.compute.contrib import coverage_ext
# Import extensions to pull in osapi_compute_extension CONF option used below.
-from nova.cloudpipe.pipelib import CloudPipe
+from nova.cloudpipe import pipelib
from nova import context
from nova import db
from nova.db.sqlalchemy import models
@@ -43,6 +43,7 @@ from nova.openstack.common import timeutils
import nova.quota
from nova.scheduler import driver
from nova import test
+from nova.tests.baremetal.db import base as bm_db_base
from nova.tests import fake_network
from nova.tests.image import fake
from nova.tests.integrated import integrated_helpers
@@ -171,23 +172,32 @@ class ApiSampleTestBase(integrated_helpers._IntegratedTestBase):
if not isinstance(result, list):
raise NoMatch(
_('Result: %(result)s is not a list.') % locals())
- if len(expected) != len(result):
- raise NoMatch(
- _('Length mismatch: %(result)s\n%(expected)s.')
- % locals())
+
+ expected = expected[:]
+ extra = []
for res_obj in result:
- for ex_obj in expected:
+ for i, ex_obj in enumerate(expected):
try:
- res = self._compare_result(subs, ex_obj, res_obj)
+ matched_value = self._compare_result(subs, ex_obj,
+ res_obj)
+ del expected[i]
break
except NoMatch:
pass
else:
- raise NoMatch(
- _('Result: %(res_obj)s not in %(expected)s.')
- % locals())
- matched_value = res or matched_value
+ extra.append(res_obj)
+ error = []
+ if expected:
+ error.append(_('Extra items in expected:'))
+ error.extend([repr(o) for o in expected])
+
+ if extra:
+ error.append(_('Extra items in result:'))
+ error.extend([repr(o) for o in extra])
+
+ if error:
+ raise NoMatch('\n'.join(error))
elif isinstance(expected, basestring) and '%' in expected:
# NOTE(vish): escape stuff for regex
for char in '[]<>?':
@@ -369,7 +379,6 @@ class ApiSamplesTrap(ApiSampleTestBase):
do_not_approve_additions.append('os-flavor-access')
do_not_approve_additions.append('os-flavor-extra-specs')
do_not_approve_additions.append('os-floating-ip-dns')
- do_not_approve_additions.append('os-floating-ip-pools')
do_not_approve_additions.append('os-fping')
do_not_approve_additions.append('os-hypervisors')
do_not_approve_additions.append('os-instance_usage_audit_log')
@@ -751,7 +760,7 @@ class CoverageExtJsonTests(ApiSampleTestBase):
self.stubs.Set(coverage_ext.CoverageController, '_check_coverage',
_fake_check_coverage)
- self.stubs.Set(coverage, 'xml_report', _fake_xml_report)
+ self.stubs.Set(coverage.coverage, 'xml_report', _fake_xml_report)
def test_start_coverage(self):
# Start coverage data collection.
@@ -1502,7 +1511,7 @@ class CloudPipeSampleJsonTest(ApiSampleTestBase):
return {'vpn_public_address': '127.0.0.1',
'vpn_public_port': 22}
- self.stubs.Set(CloudPipe, 'get_encoded_zip', get_user_data)
+ self.stubs.Set(pipelib.CloudPipe, 'get_encoded_zip', get_user_data)
self.stubs.Set(network_manager.NetworkManager, "get_network",
network_api_get)
@@ -2580,3 +2589,102 @@ class CellsSampleJsonTest(ApiSampleTestBase):
class CellsSampleXmlTest(CellsSampleJsonTest):
ctype = 'xml'
+
+
+class BareMetalNodesJsonTest(ApiSampleTestBase, bm_db_base.BMDBTestCase):
+ extension_name = ('nova.api.openstack.compute.contrib.baremetal_nodes.'
+ 'Baremetal_nodes')
+
+ def _create_node(self):
+ response = self._do_post("os-baremetal-nodes",
+ "baremetal-node-create-req",
+ {})
+ self.assertEqual(response.status, 200)
+ subs = {'node_id': '(?P<id>\d+)'}
+ return self._verify_response("baremetal-node-create-resp",
+ subs, response)
+
+ def test_create_node(self):
+ self._create_node()
+
+ def test_list_nodes(self):
+ node_id = self._create_node()
+ interface_id = self._add_interface(node_id)
+ response = self._do_get('os-baremetal-nodes')
+ self.assertEqual(response.status, 200)
+ subs = {'node_id': node_id,
+ 'interface_id': interface_id,
+ 'address': 'aa:aa:aa:aa:aa:aa',
+ }
+ return self._verify_response('baremetal-node-list-resp',
+ subs, response)
+
+ def test_show_node(self):
+ node_id = self._create_node()
+ interface_id = self._add_interface(node_id)
+ response = self._do_get('os-baremetal-nodes/%s' % node_id)
+ self.assertEqual(response.status, 200)
+ subs = {'node_id': node_id,
+ 'interface_id': interface_id,
+ 'address': 'aa:aa:aa:aa:aa:aa',
+ }
+ return self._verify_response('baremetal-node-show-resp',
+ subs, response)
+
+ def test_delete_node(self):
+ node_id = self._create_node()
+ response = self._do_delete("os-baremetal-nodes/%s" % node_id)
+ self.assertEqual(response.status, 202)
+
+ def _add_interface(self, node_id):
+ response = self._do_post("os-baremetal-nodes/%s/action" % node_id,
+ "baremetal-node-add-interface-req",
+ {'address': 'aa:aa:aa:aa:aa:aa'})
+ self.assertEqual(response.status, 200)
+ subs = {'interface_id': r'(?P<id>\d+)'}
+ return self._verify_response("baremetal-node-add-interface-resp",
+ subs, response)
+
+ def test_add_interface(self):
+ node_id = self._create_node()
+ self._add_interface(node_id)
+
+ def test_remove_interface(self):
+ node_id = self._create_node()
+ self._add_interface(node_id)
+ response = self._do_post("os-baremetal-nodes/%s/action" % node_id,
+ "baremetal-node-remove-interface-req",
+ {'address': 'aa:aa:aa:aa:aa:aa'})
+ self.assertEqual(response.status, 202)
+ self.assertEqual(response.read(), "")
+
+
+class BareMetalNodesXmlTest(BareMetalNodesJsonTest):
+ ctype = 'xml'
+
+
+class FloatingIPPoolsSampleJsonTests(ApiSampleTestBase):
+ extension_name = ("nova.api.openstack.compute.contrib.floating_ip_pools."
+ "Floating_ip_pools")
+
+ def test_list_floatingippools(self):
+ pool_list = ["pool1", "pool2"]
+
+ def fake_get_floating_ip_pools(self, context):
+ return [{'name': pool_list[0]},
+ {'name': pool_list[1]}]
+
+ self.stubs.Set(network_api.API, "get_floating_ip_pools",
+ fake_get_floating_ip_pools)
+ response = self._do_get('os-floating-ip-pools')
+ self.assertEqual(response.status, 200)
+ subs = {
+ 'pool1': pool_list[0],
+ 'pool2': pool_list[1]
+ }
+ return self._verify_response('floatingippools-list-resp',
+ subs, response)
+
+
+class FloatingIPPoolsSampleXmlTests(FloatingIPPoolsSampleJsonTests):
+ ctype = "xml"
diff --git a/nova/tests/integrated/test_multiprocess_api.py b/nova/tests/integrated/test_multiprocess_api.py
index 5a82e0033..b2361b13c 100644
--- a/nova/tests/integrated/test_multiprocess_api.py
+++ b/nova/tests/integrated/test_multiprocess_api.py
@@ -150,7 +150,7 @@ class MultiprocessWSGITest(integrated_helpers._IntegratedTestBase):
workers = self._get_workers()
LOG.info('workers: %r' % workers)
- self.assertFalse(workers, 'No OS processes left.')
+ self.assertFalse(workers, 'OS processes left %r' % workers)
def test_terminate_sigkill(self):
self._terminate_with_signal(signal.SIGKILL)
diff --git a/nova/tests/network/test_manager.py b/nova/tests/network/test_manager.py
index 1552630fb..9ccdffab5 100644
--- a/nova/tests/network/test_manager.py
+++ b/nova/tests/network/test_manager.py
@@ -16,8 +16,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import shutil
-import tempfile
+import fixtures
import mox
from nova import context
@@ -142,7 +142,7 @@ vifs = [{'id': 0,
class FlatNetworkTestCase(test.TestCase):
def setUp(self):
super(FlatNetworkTestCase, self).setUp()
- self.tempdir = tempfile.mkdtemp()
+ self.tempdir = self.useFixture(fixtures.TempDir()).path
self.flags(log_dir=self.tempdir)
self.network = network_manager.FlatManager(host=HOST)
self.network.instance_dns_domain = ''
@@ -150,10 +150,6 @@ class FlatNetworkTestCase(test.TestCase):
self.context = context.RequestContext('testuser', 'testproject',
is_admin=False)
- def tearDown(self):
- shutil.rmtree(self.tempdir)
- super(FlatNetworkTestCase, self).tearDown()
-
def test_get_instance_nw_info(self):
fake_get_instance_nw_info = fake_network.fake_get_instance_nw_info
@@ -189,6 +185,7 @@ class FlatNetworkTestCase(test.TestCase):
'vif_devname': None,
'vif_uuid':
'00000000-0000-0000-0000-00000000000000%02d' % nid,
+ 'ovs_interfaceid': None,
'should_create_vlan': False,
'should_create_bridge': False}
self.assertThat(info, matchers.DictMatches(check))
@@ -1629,7 +1626,7 @@ class FloatingIPTestCase(test.TestCase):
"""Tests nova.network.manager.FloatingIP."""
def setUp(self):
super(FloatingIPTestCase, self).setUp()
- self.tempdir = tempfile.mkdtemp()
+ self.tempdir = self.useFixture(fixtures.TempDir()).path
self.flags(log_dir=self.tempdir)
self.network = TestFloatingIPManager()
self.network.db = db
@@ -1637,10 +1634,6 @@ class FloatingIPTestCase(test.TestCase):
self.context = context.RequestContext('testuser', self.project_id,
is_admin=False)
- def tearDown(self):
- shutil.rmtree(self.tempdir)
- super(FloatingIPTestCase, self).tearDown()
-
def test_disassociate_floating_ip_multi_host_calls(self):
floating_ip = {
'fixed_ip_id': 12
@@ -2128,7 +2121,7 @@ class InstanceDNSTestCase(test.TestCase):
"""Tests nova.network.manager instance DNS."""
def setUp(self):
super(InstanceDNSTestCase, self).setUp()
- self.tempdir = tempfile.mkdtemp()
+ self.tempdir = self.useFixture(fixtures.TempDir()).path
self.flags(log_dir=self.tempdir)
self.network = TestFloatingIPManager()
self.network.db = db
@@ -2136,10 +2129,6 @@ class InstanceDNSTestCase(test.TestCase):
self.context = context.RequestContext('testuser', self.project_id,
is_admin=False)
- def tearDown(self):
- shutil.rmtree(self.tempdir)
- super(InstanceDNSTestCase, self).tearDown()
-
def test_dns_domains_private(self):
zone1 = 'testzone'
domain1 = 'example.org'
diff --git a/nova/tests/network/test_rpcapi.py b/nova/tests/network/test_rpcapi.py
index 90bffeeaf..5ba7459fb 100644
--- a/nova/tests/network/test_rpcapi.py
+++ b/nova/tests/network/test_rpcapi.py
@@ -108,8 +108,9 @@ class NetworkRpcAPITestCase(test.TestCase):
def test_get_floating_ip(self):
self._test_network_api('get_floating_ip', rpc_method='call', id='id')
- def test_get_floating_pools(self):
- self._test_network_api('get_floating_pools', rpc_method='call')
+ def test_get_floating_ip_pools(self):
+ self._test_network_api('get_floating_ip_pools', rpc_method='call',
+ version="1.7")
def test_get_floating_ip_by_address(self):
self._test_network_api('get_floating_ip_by_address', rpc_method='call',
diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py
index f8b9f9296..6fcd19d92 100644
--- a/nova/tests/scheduler/test_host_filters.py
+++ b/nova/tests/scheduler/test_host_filters.py
@@ -25,7 +25,7 @@ from nova.openstack.common import jsonutils
from nova.openstack.common import timeutils
from nova.scheduler import filters
from nova.scheduler.filters import extra_specs_ops
-from nova.scheduler.filters.trusted_filter import AttestationService
+from nova.scheduler.filters import trusted_filter
from nova import servicegroup
from nova import test
from nova.tests.scheduler import fakes
@@ -242,7 +242,8 @@ class HostFiltersTestCase(test.TestCase):
self.oat_data = ''
self.oat_attested = False
self.stubs = stubout.StubOutForTesting()
- self.stubs.Set(AttestationService, '_request', self.fake_oat_request)
+ self.stubs.Set(trusted_filter.AttestationService, '_request',
+ self.fake_oat_request)
self.context = context.RequestContext('fake', 'fake')
self.json_query = jsonutils.dumps(
['and', ['>=', '$free_ram_mb', 1024],
diff --git a/nova/tests/ssl_cert/ca.crt b/nova/tests/ssl_cert/ca.crt
new file mode 100644
index 000000000..9d66ca627
--- /dev/null
+++ b/nova/tests/ssl_cert/ca.crt
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGDDCCA/SgAwIBAgIJAPSvwQYk4qI4MA0GCSqGSIb3DQEBBQUAMGExCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMRUwEwYDVQQKEwxPcGVuc3RhY2sg
+Q0ExEjAQBgNVBAsTCUdsYW5jZSBDQTESMBAGA1UEAxMJR2xhbmNlIENBMB4XDTEy
+MDIwOTE3MTAwMloXDTIyMDIwNjE3MTAwMlowYTELMAkGA1UEBhMCQVUxEzARBgNV
+BAgTClNvbWUtU3RhdGUxFTATBgNVBAoTDE9wZW5zdGFjayBDQTESMBAGA1UECxMJ
+R2xhbmNlIENBMRIwEAYDVQQDEwlHbGFuY2UgQ0EwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQDmf+fapWfzy1Uylus0KGalw4X/5xZ+ltPVOr+IdCPbstvi
+RTC5g+O+TvXeOP32V/cnSY4ho/+f2q730za+ZA/cgWO252rcm3Q7KTJn3PoqzJvX
+/l3EXe3/TCrbzgZ7lW3QLTCTEE2eEzwYG3wfDTOyoBq+F6ct6ADh+86gmpbIRfYI
+N+ixB0hVyz9427PTof97fL7qxxkjAayB28OfwHrkEBl7iblNhUC0RoH+/H9r5GEl
+GnWiebxfNrONEHug6PHgiaGq7/Dj+u9bwr7J3/NoS84I08ajMnhlPZxZ8bS/O8If
+ceWGZv7clPozyhABT/otDfgVcNH1UdZ4zLlQwc1MuPYN7CwxrElxc8Quf94ttGjb
+tfGTl4RTXkDofYdG1qBWW962PsGl2tWmbYDXV0q5JhV/IwbrE1X9f+OksJQne1/+
+dZDxMhdf2Q1V0P9hZZICu4+YhmTMs5Mc9myKVnzp4NYdX5fXoB/uNYph+G7xG5IK
+WLSODKhr1wFGTTcuaa8LhOH5UREVenGDJuc6DdgX9a9PzyJGIi2ngQ03TJIkCiU/
+4J/r/vsm81ezDiYZSp2j5JbME+ixW0GBLTUWpOIxUSHgUFwH5f7lQwbXWBOgwXQk
+BwpZTmdQx09MfalhBtWeu4/6BnOCOj7e/4+4J0eVxXST0AmVyv8YjJ2nz1F9oQID
+AQABo4HGMIHDMB0GA1UdDgQWBBTk7Krj4bEsTjHXaWEtI2GZ5ACQyTCBkwYDVR0j
+BIGLMIGIgBTk7Krj4bEsTjHXaWEtI2GZ5ACQyaFlpGMwYTELMAkGA1UEBhMCQVUx
+EzARBgNVBAgTClNvbWUtU3RhdGUxFTATBgNVBAoTDE9wZW5zdGFjayBDQTESMBAG
+A1UECxMJR2xhbmNlIENBMRIwEAYDVQQDEwlHbGFuY2UgQ0GCCQD0r8EGJOKiODAM
+BgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQA8Zrss/MiwFHGmDlercE0h
+UvzA54n/EvKP9nP3jHM2qW/VPfKdnFw99nEPFLhb+lN553vdjOpCYFm+sW0Z5Mi4
+qsFkk4AmXIIEFOPt6zKxMioLYDQ9Sw/BUv6EZGeANWr/bhmaE+dMcKJt5le/0jJm
+2ahsVB9fbFu9jBFeYb7Ba/x2aLkEGMxaDLla+6EQhj148fTnS1wjmX9G2cNzJvj/
++C2EfKJIuDJDqw2oS2FGVpP37FA2Bz2vga0QatNneLkGKCFI3ZTenBznoN+fmurX
+TL3eJE4IFNrANCcdfMpdyLAtXz4KpjcehqpZMu70er3d30zbi1l0Ajz4dU+WKz/a
+NQES+vMkT2wqjXHVTjrNwodxw3oLK/EuTgwoxIHJuplx5E5Wrdx9g7Gl1PBIJL8V
+xiOYS5N7CakyALvdhP7cPubA2+TPAjNInxiAcmhdASS/Vrmpvrkat6XhGn8h9liv
+ysDOpMQmYQkmgZBpW8yBKK7JABGGsJADJ3E6J5MMWBX2RR4kFoqVGAzdOU3oyaTy
+I0kz5sfuahaWpdYJVlkO+esc0CRXw8fLDYivabK2tOgUEWeZsZGZ9uK6aV1VxTAY
+9Guu3BJ4Rv/KP/hk7mP8rIeCwotV66/2H8nq72ImQhzSVyWcxbFf2rJiFQJ3BFwA
+WoRMgEwjGJWqzhJZUYpUAQ==
+-----END CERTIFICATE-----
diff --git a/nova/tests/ssl_cert/certificate.crt b/nova/tests/ssl_cert/certificate.crt
new file mode 100644
index 000000000..3c1aa6363
--- /dev/null
+++ b/nova/tests/ssl_cert/certificate.crt
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFLjCCAxYCAQEwDQYJKoZIhvcNAQEFBQAwYTELMAkGA1UEBhMCQVUxEzARBgNV
+BAgTClNvbWUtU3RhdGUxFTATBgNVBAoTDE9wZW5zdGFjayBDQTESMBAGA1UECxMJ
+R2xhbmNlIENBMRIwEAYDVQQDEwlHbGFuY2UgQ0EwHhcNMTIwMjA5MTcxMDUzWhcN
+MjIwMjA2MTcxMDUzWjBZMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0
+ZTESMBAGA1UEChMJT3BlbnN0YWNrMQ8wDQYDVQQLEwZHbGFuY2UxEDAOBgNVBAMT
+BzAuMC4wLjAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXpUkQN6pu
+avo+gz3o1K4krVdPl1m7NjNJDyD/+ZH0EGNcEN7iag1qPE7JsjqGPNZsQK1dMoXb
+Sz+OSi9qvNeJnBcfwUx5qTAtwyAb9AxGkwuMafIU+lWbsclo+dPGsja01ywbXTCZ
+bF32iqnpOMYhfxWUdoQYiBkhxxhW9eMPKLS/KkP8/bx+Vaa2XJiAebqkd9nrksAA
+BeGc9mlafYBEmiChPdJEPw+1ePA4QVq9aPepDsqAKtGN8JLpmoC3BdxQQTbbwL3Q
+8fTXK4tCNUaVk4AbDy/McFq6y0ocQoBPJjihOY35mWG/OLtcI99yPOpWGnps/5aG
+/64DDJ2D67Fnaj6gKHV+6TXFO8KZxlnxtgtiZDJBZkneTBt9ArSOv+l6NBsumRz0
+iEJ4o4H1S2TSMnprAvX7WnGtc6Xi9gXahYcDHEelwwYzqAiTBv6hxSp4MZ2dNXa+
+KzOitC7ZbV2qsg0au0wjfE/oSQ3NvsvUr8nOmfutJTvHRAwbC1v4G/tuAsO7O0w2
+0u2B3u+pG06m5+rnEqp+rB9hmukRYTfgEFRRsVIvpFl/cwvPXKRcX03UIMx+lLr9
+Ft+ep7YooBhY3wY2kwCxD4lRYNmbwsCIVywZt40f/4ad98TkufR9NhsfycxGeqbr
+mTMFlZ8TTlmP82iohekKCOvoyEuTIWL2+wIDAQABMA0GCSqGSIb3DQEBBQUAA4IC
+AQBMUBgV0R+Qltf4Du7u/8IFmGAoKR/mktB7R1gRRAqsvecUt7kIwBexGdavGg1y
+0pU0+lgUZjJ20N1SlPD8gkNHfXE1fL6fmMjWz4dtYJjzRVhpufHPeBW4tl8DgHPN
+rBGAYQ+drDSXaEjiPQifuzKx8WS+DGA3ki4co5mPjVnVH1xvLIdFsk89z3b3YD1k
+yCJ/a9K36x6Z/c67JK7s6MWtrdRF9+MVnRKJ2PK4xznd1kBz16V+RA466wBDdARY
+vFbtkafbEqOb96QTonIZB7+fAldKDPZYnwPqasreLmaGOaM8sxtlPYAJ5bjDONbc
+AaXG8BMRQyO4FyH237otDKlxPyHOFV66BaffF5S8OlwIMiZoIvq+IcTZOdtDUSW2
+KHNLfe5QEDZdKjWCBrfqAfvNuG13m03WqfmcMHl3o/KiPJlx8l9Z4QEzZ9xcyQGL
+cncgeHM9wJtzi2cD/rTDNFsx/gxvoyutRmno7I3NRbKmpsXF4StZioU3USRspB07
+hYXOVnG3pS+PjVby7ThT3gvFHSocguOsxClx1epdUJAmJUbmM7NmOp5WVBVtMtC2
+Su4NG/xJciXitKzw+btb7C7RjO6OEqv/1X/oBDzKBWQAwxUC+lqmnM7W6oqWJFEM
+YfTLnrjs7Hj6ThMGcEnfvc46dWK3dz0RjsQzUxugPuEkLA==
+-----END CERTIFICATE-----
diff --git a/nova/tests/ssl_cert/privatekey.key b/nova/tests/ssl_cert/privatekey.key
new file mode 100644
index 000000000..b63df3d29
--- /dev/null
+++ b/nova/tests/ssl_cert/privatekey.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEA16VJEDeqbmr6PoM96NSuJK1XT5dZuzYzSQ8g//mR9BBjXBDe
+4moNajxOybI6hjzWbECtXTKF20s/jkovarzXiZwXH8FMeakwLcMgG/QMRpMLjGny
+FPpVm7HJaPnTxrI2tNcsG10wmWxd9oqp6TjGIX8VlHaEGIgZIccYVvXjDyi0vypD
+/P28flWmtlyYgHm6pHfZ65LAAAXhnPZpWn2ARJogoT3SRD8PtXjwOEFavWj3qQ7K
+gCrRjfCS6ZqAtwXcUEE228C90PH01yuLQjVGlZOAGw8vzHBaustKHEKATyY4oTmN
++Zlhvzi7XCPfcjzqVhp6bP+Whv+uAwydg+uxZ2o+oCh1fuk1xTvCmcZZ8bYLYmQy
+QWZJ3kwbfQK0jr/pejQbLpkc9IhCeKOB9Utk0jJ6awL1+1pxrXOl4vYF2oWHAxxH
+pcMGM6gIkwb+ocUqeDGdnTV2viszorQu2W1dqrINGrtMI3xP6EkNzb7L1K/Jzpn7
+rSU7x0QMGwtb+Bv7bgLDuztMNtLtgd7vqRtOpufq5xKqfqwfYZrpEWE34BBUUbFS
+L6RZf3MLz1ykXF9N1CDMfpS6/Rbfnqe2KKAYWN8GNpMAsQ+JUWDZm8LAiFcsGbeN
+H/+GnffE5Ln0fTYbH8nMRnqm65kzBZWfE05Zj/NoqIXpCgjr6MhLkyFi9vsCAwEA
+AQKCAgAA96baQcWr9SLmQOR4NOwLEhQAMWefpWCZhU3amB4FgEVR1mmJjnw868RW
+t0v36jH0Dl44us9K6o2Ab+jCi9JTtbWM2Osk6JNkwSlVtsSPVH2KxbbmTTExH50N
+sYE3tPj12rlB7isXpRrOzlRwzWZmJBHOtrFlAsdKFYCQc03vdXlKGkBv1BuSXYP/
+8W5ltSYXMspxehkOZvhaIejbFREMPbzDvGlDER1a7Q320qQ7kUr7ISvbY1XJUzj1
+f1HwgEA6w/AhED5Jv6wfgvx+8Yo9hYnflTPbsO1XRS4x7kJxGHTMlFuEsSF1ICYH
+Bcos0wUiGcBO2N6uAFuhe98BBn+nOwAPZYWwGkmVuK2psm2mXAHx94GT/XqgK/1r
+VWGSoOV7Fhjauc2Nv8/vJU18DXT3OY5hc4iXVeEBkuZwRb/NVUtnFoHxVO/Mp5Fh
+/W5KZaLWVrLghzvSQ/KUIM0k4lfKDZpY9ZpOdNgWDyZY8tNrXumUZZimzWdXZ9vR
+dBssmd8qEKs1AHGFnMDt56IjLGou6j0qnWsLdR1e/WEFsYzGXLVHCv6vXRNkbjqh
+WFw5nA+2Dw1YAsy+YkTfgx2pOe+exM/wxsVPa7tG9oZ374dywUi1k6VoHw5dkmJw
+1hbXqSLZtx2N51G+SpGmNAV4vLUF0y3dy2wnrzFkFT4uxh1w8QKCAQEA+h6LwHTK
+hgcJx6CQQ6zYRqXo4wdvMooY1FcqJOq7LvJUA2CX5OOLs8qN1TyFrOCuAUTurOrM
+ABlQ0FpsIaP8TOGz72dHe2eLB+dD6Bqjn10sEFMn54zWd/w9ympQrO9jb5X3ViTh
+sCcdYyXVS9Hz8nzbbIF+DaKlxF2Hh71uRDxXpMPxRcGbOIuKZXUj6RkTIulzqT6o
+uawlegWxch05QSgzq/1ASxtjTzo4iuDCAii3N45xqxnB+fV9NXEt4R2oOGquBRPJ
+LxKcOnaQKBD0YNX4muTq+zPlv/kOb8/ys2WGWDUrNkpyJXqhTve4KONjqM7+iL/U
+4WdJuiCjonzk/QKCAQEA3Lc+kNq35FNLxMcnCVcUgkmiCWZ4dyGZZPdqjOPww1+n
+bbudGPzY1nxOvE60dZM4or/tm6qlXYfb2UU3+OOJrK9s297EQybZ8DTZu2GHyitc
+NSFV3Gl4cgvKdbieGKkk9X2dV9xSNesNvX9lJEnQxuwHDTeo8ubLHtV88Ml1xokn
+7W+IFiyEuUIL4e5/fadbrI3EwMrbCF4+9VcfABx4PTNMzdc8LsncCMXE+jFX8AWp
+TsT2JezTe5o2WpvBoKMAYhJQNQiaWATn00pDVY/70H1vK3ljomAa1IUdOr/AhAF7
+3jL0MYMgXSHzXZOKAtc7yf+QfFWF1Ls8+sen1clJVwKCAQEAp59rB0r+Iz56RmgL
+5t7ifs5XujbURemY5E2aN+18DuVmenD0uvfoO1DnJt4NtCNLWhxpXEdq+jH9H/VJ
+fG4a+ydT4IC1vjVRTrWlo9qeh4H4suQX3S1c2kKY4pvHf25blH/Lp9bFzbkZD8Ze
+IRcOxxb4MsrBwL+dGnGYD9dbG63ZCtoqSxaKQSX7VS1hKKmeUopj8ivFBdIht5oz
+JogBQ/J+Vqg9u1gagRFCrYgdXTcOOtRix0lW336vL+6u0ax/fXe5MjvlW3+8Zc3p
+pIBgVrlvh9ccx8crFTIDg9m4DJRgqaLQV+0ifI2np3WK3RQvSQWYPetZ7sm69ltD
+bvUGvQKCAQAz5CEhjUqOs8asjOXwnDiGKSmfbCgGWi/mPQUf+rcwN9z1P5a/uTKB
+utgIDbj/q401Nkp2vrgCNV7KxitSqKxFnTjKuKUL5KZ4gvRtyZBTR751/1BgcauP
+pJYE91K0GZBG5zGG5pWtd4XTd5Af5/rdycAeq2ddNEWtCiRFuBeohbaNbBtimzTZ
+GV4R0DDJKf+zoeEQMqEsZnwG0mTHceoS+WylOGU92teQeG7HI7K5C5uymTwFzpgq
+ByegRd5QFgKRDB0vWsZuyzh1xI/wHdnmOpdYcUGre0zTijhFB7ALWQ32P6SJv3ps
+av78kSNxZ4j3BM7DbJf6W8sKasZazOghAoIBAHekpBcLq9gRv2+NfLYxWN2sTZVB
+1ldwioG7rWvk5YQR2akukecI3NRjtC5gG2vverawG852Y4+oLfgRMHxgp0qNStwX
+juTykzPkCwZn8AyR+avC3mkrtJyM3IigcYOu4/UoaRDFa0xvCC1EfumpnKXIpHag
+miSQZf2sVbgqb3/LWvHIg/ceOP9oGJve87/HVfQtBoLaIe5RXCWkqB7mcI/exvTS
+8ShaW6v2Fe5Bzdvawj7sbsVYRWe93Aq2tmIgSX320D2RVepb6mjD4nr0IUaM3Yed
+TFT7e2ikWXyDLLgVkDTU4Qe8fr3ZKGfanCIDzvgNw6H1gRi+2WQgOmjilMQ=
+-----END RSA PRIVATE KEY-----
diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py
index fb2e76e45..11c16d6dd 100644
--- a/nova/tests/test_api.py
+++ b/nova/tests/test_api.py
@@ -26,9 +26,9 @@ from boto.ec2 import regioninfo
from boto import exception as boto_exc
# newer versions of boto use their own wrapper on top of httplib.HTTPResponse
try:
- from boto.connection import HTTPResponse
+ import boto.connection as httplib
except ImportError:
- from httplib import HTTPResponse
+ import httplib
import fixtures
import webob
@@ -79,7 +79,7 @@ class FakeHttplibConnection(object):
# guess that's a function the web server usually provides.
resp = "HTTP/1.0 %s" % resp
self.sock = FakeHttplibSocket(resp)
- self.http_response = HTTPResponse(self.sock)
+ self.http_response = httplib.HTTPResponse(self.sock)
# NOTE(vish): boto is accessing private variables for some reason
self._HTTPConnection__response = self.http_response
self.http_response.begin()
diff --git a/nova/tests/test_availability_zones.py b/nova/tests/test_availability_zones.py
new file mode 100644
index 000000000..2c5c06921
--- /dev/null
+++ b/nova/tests/test_availability_zones.py
@@ -0,0 +1,114 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Netease Corporation
+# 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.
+
+"""
+Tests for availability zones
+"""
+
+from nova import availability_zones as az
+from nova import context
+from nova import db
+from nova.openstack.common import cfg
+from nova import service
+from nova import test
+
+CONF = cfg.CONF
+CONF.import_opt('internal_service_availability_zone',
+ 'nova.availability_zones')
+CONF.import_opt('default_availability_zone',
+ 'nova.availability_zones')
+
+
+class AvailabilityZoneTestCases(test.TestCase):
+ """Test case for aggregate based availability zone."""
+
+ def setUp(self):
+ super(AvailabilityZoneTestCases, self).setUp()
+ self.host = 'me'
+ self.availability_zone = 'nova-test'
+ self.default_az = CONF.default_availability_zone
+ self.default_in_az = CONF.internal_service_availability_zone
+ self.context = context.get_admin_context()
+
+ agg = {'name': 'agg1'}
+ self.agg = db.aggregate_create(self.context, agg)
+
+ metadata = {'availability_zone': self.availability_zone}
+ db.aggregate_metadata_add(self.context, self.agg['id'], metadata)
+
+ def tearDown(self):
+ db.aggregate_delete(self.context, self.agg['id'])
+ super(AvailabilityZoneTestCases, self).tearDown()
+
+ def _create_service_with_topic(self, topic):
+ values = {
+ 'binary': 'bin',
+ 'host': self.host,
+ 'topic': topic,
+ }
+ return db.service_create(self.context, values)
+
+ def _destroy_service(self, service):
+ return db.service_destroy(self.context, service['id'])
+
+ def _add_to_aggregate(self, service):
+ return db.aggregate_host_add(self.context,
+ self.agg['id'], service['host'])
+
+ def _delete_from_aggregate(self, service):
+ return db.aggregate_host_delete(self.context,
+ self.agg['id'], service['host'])
+
+ def test_set_availability_zone_compute_service(self):
+ """Test for compute service get right availability zone."""
+ service = self._create_service_with_topic('compute')
+ services = db.service_get_all(self.context)
+
+ # The service is not add into aggregate, so confirm it is default
+ # availability zone.
+ new_service = az.set_availability_zones(self.context, services)[0]
+ self.assertEquals(new_service['availability_zone'],
+ self.default_az)
+
+ # The service is added into aggregate, confirm return the aggregate
+ # availability zone.
+ self._add_to_aggregate(service)
+ new_service = az.set_availability_zones(self.context, services)[0]
+ self.assertEquals(new_service['availability_zone'],
+ self.availability_zone)
+
+ self._destroy_service(service)
+
+ def test_set_availability_zone_not_compute_service(self):
+ """Test not compute service get right availability zone."""
+ service = self._create_service_with_topic('network')
+ services = db.service_get_all(self.context)
+ new_service = az.set_availability_zones(self.context, services)[0]
+ self.assertEquals(new_service['availability_zone'],
+ self.default_in_az)
+ self._destroy_service(service)
+
+ def test_get_host_availability_zone(self):
+ """Test get right availability zone by given host."""
+ self.assertEquals(self.default_az,
+ az.get_host_availability_zone(self.context, self.host))
+
+ service = self._create_service_with_topic('compute')
+ self._add_to_aggregate(service)
+
+ self.assertEquals(self.availability_zone,
+ az.get_host_availability_zone(self.context, self.host))
diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py
index e43a32c19..40552d1c0 100644
--- a/nova/tests/test_db_api.py
+++ b/nova/tests/test_db_api.py
@@ -1671,3 +1671,47 @@ class VolumeUsageDBApiTestCase(test.TestCase):
for key, value in expected_vol_usages.items():
self.assertEqual(vol_usages[0][key], value)
timeutils.clear_time_override()
+
+
+class TaskLogTestCase(test.TestCase):
+
+ def setUp(self):
+ super(TaskLogTestCase, self).setUp()
+ self.context = context.get_admin_context()
+ now = timeutils.utcnow()
+ self.begin = now - datetime.timedelta(seconds=10)
+ self.end = now - datetime.timedelta(seconds=5)
+ self.task_name = 'fake-task-name'
+ self.host = 'fake-host'
+ self.message = 'Fake task message'
+ db.task_log_begin_task(self.context, self.task_name, self.begin,
+ self.end, self.host, message=self.message)
+
+ def test_task_log_get(self):
+ result = db.task_log_get(self.context, self.task_name, self.begin,
+ self.end, self.host)
+ self.assertEqual(result['task_name'], self.task_name)
+ self.assertEqual(result['period_beginning'], self.begin)
+ self.assertEqual(result['period_ending'], self.end)
+ self.assertEqual(result['host'], self.host)
+ self.assertEqual(result['message'], self.message)
+
+ def test_task_log_get_all(self):
+ result = db.task_log_get_all(self.context, self.task_name, self.begin,
+ self.end, host=self.host)
+ self.assertEqual(len(result), 1)
+
+ def test_task_log_begin_task(self):
+ db.task_log_begin_task(self.context, 'fake', self.begin,
+ self.end, self.host, message=self.message)
+ result = db.task_log_get(self.context, 'fake', self.begin,
+ self.end, self.host)
+ self.assertEqual(result['task_name'], 'fake')
+
+ def test_task_log_end_task(self):
+ errors = 1
+ db.task_log_end_task(self.context, self.task_name, self.begin,
+ self.end, self.host, errors, message=self.message)
+ result = db.task_log_get(self.context, self.task_name, self.begin,
+ self.end, self.host)
+ self.assertEqual(result['errors'], 1)
diff --git a/nova/tests/test_driver.py b/nova/tests/test_driver.py
new file mode 100644
index 000000000..2dee7725f
--- /dev/null
+++ b/nova/tests/test_driver.py
@@ -0,0 +1,60 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 Citrix Systems, Inc.
+# 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 nova import test
+from nova.virt import driver
+
+
+class FakeDriver(object):
+ def __init__(self, *args, **kwargs):
+ self.args = args
+ self.kwargs = kwargs
+
+
+class FakeDriver2(FakeDriver):
+ pass
+
+
+class ToDriverRegistryTestCase(test.TestCase):
+
+ def assertDriverInstance(self, inst, class_, *args, **kwargs):
+ self.assertEquals(class_, inst.__class__)
+ self.assertEquals(args, inst.args)
+ self.assertEquals(kwargs, inst.kwargs)
+
+ def test_driver_dict_from_config(self):
+ drvs = driver.driver_dict_from_config(
+ [
+ 'key1=nova.tests.test_driver.FakeDriver',
+ 'key2=nova.tests.test_driver.FakeDriver2',
+ ], 'arg1', 'arg2', param1='value1', param2='value2'
+ )
+
+ self.assertEquals(
+ sorted(['key1', 'key2']),
+ sorted(drvs.keys())
+ )
+
+ self.assertDriverInstance(
+ drvs['key1'],
+ FakeDriver, 'arg1', 'arg2', param1='value1',
+ param2='value2')
+
+ self.assertDriverInstance(
+ drvs['key2'],
+ FakeDriver2, 'arg1', 'arg2', param1='value1',
+ param2='value2')
diff --git a/nova/tests/test_imagebackend.py b/nova/tests/test_imagebackend.py
index a9865cb44..495e7c947 100644
--- a/nova/tests/test_imagebackend.py
+++ b/nova/tests/test_imagebackend.py
@@ -273,7 +273,7 @@ class LvmTestCase(_ImageTestCase, test.TestCase):
cmd = ('dd', 'if=%s' % self.TEMPLATE_PATH,
'of=%s' % self.PATH, 'bs=4M')
self.utils.execute(*cmd, run_as_root=True)
- self.disk.resize2fs(self.PATH)
+ self.disk.resize2fs(self.PATH, run_as_root=True)
self.mox.ReplayAll()
image = self.image_class(self.INSTANCE, self.NAME)
diff --git a/nova/tests/test_imagecache.py b/nova/tests/test_imagecache.py
index eaf244c56..8142312b9 100644
--- a/nova/tests/test_imagecache.py
+++ b/nova/tests/test_imagecache.py
@@ -721,7 +721,7 @@ class ImageCacheManagerTestCase(test.TestCase):
def fq_path(path):
return os.path.join('/instance_path/_base/', path)
- # Fake base directory existance
+ # Fake base directory existence
orig_exists = os.path.exists
def exists(path):
@@ -747,7 +747,7 @@ class ImageCacheManagerTestCase(test.TestCase):
'/instance_path/_base/%s_sm' % hashed_42]:
return False
- self.fail('Unexpected path existance check: %s' % path)
+ self.fail('Unexpected path existence check: %s' % path)
self.stubs.Set(os.path, 'exists', lambda x: exists(x))
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index 83b7f43bc..f96536893 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -570,7 +570,6 @@ class LibvirtConnTestCase(test.TestCase):
self.context = context.get_admin_context()
self.flags(instances_path='')
self.flags(libvirt_snapshots_directory='')
- self.call_libvirt_dependant_setup = False
self.useFixture(fixtures.MonkeyPatch(
'nova.virt.libvirt.driver.libvirt_utils',
fake_libvirt_utils))
@@ -3100,7 +3099,7 @@ class LibvirtConnTestCase(test.TestCase):
self.stubs.Set(conn, 'get_info', fake_get_info)
instance = {"name": "instancename", "id": "instanceid",
"uuid": "875a8070-d0b9-4949-8b31-104d125c9a64"}
- # NOTE(vish): verifies destory doesn't raise if the instance disappears
+ # NOTE(vish): verifies destroy doesn't raise if the instance disappears
conn._destroy(instance)
def test_available_least_handles_missing(self):
@@ -4595,7 +4594,7 @@ class LibvirtDriverTestCase(test.TestCase):
pass
def fake_to_xml(instance, network_info, image_meta=None, rescue=None,
- block_device_info=None):
+ block_device_info=None, write_to_disk=False):
return ""
def fake_plug_vifs(instance, network_info):
@@ -4748,6 +4747,25 @@ class LibvirtDriverTestCase(test.TestCase):
self.libvirtconnection._cleanup_resize(ins_ref,
_fake_network_info(self.stubs, 1))
+ def test_get_instance_disk_info_exception(self):
+ instance_name = "fake-instance-name"
+
+ class FakeExceptionDomain(FakeVirtDomain):
+ def __init__(self):
+ super(FakeExceptionDomain, self).__init__()
+
+ def XMLDesc(self, *args):
+ raise libvirt.libvirtError("Libvirt error")
+
+ def fake_lookup_by_name(instance_name):
+ return FakeExceptionDomain()
+
+ self.stubs.Set(self.libvirtconnection, '_lookup_by_name',
+ fake_lookup_by_name)
+ self.assertRaises(exception.InstanceNotFound,
+ self.libvirtconnection.get_instance_disk_info,
+ instance_name)
+
class LibvirtVolumeUsageTestCase(test.TestCase):
"""Test for nova.virt.libvirt.libvirt_driver.LibvirtDriver
diff --git a/nova/tests/test_libvirt_vif.py b/nova/tests/test_libvirt_vif.py
index 11ffa020f..3861d7dfa 100644
--- a/nova/tests/test_libvirt_vif.py
+++ b/nova/tests/test_libvirt_vif.py
@@ -27,7 +27,7 @@ CONF = cfg.CONF
class LibvirtVifTestCase(test.TestCase):
- net = {
+ net_bridge = {
'cidr': '101.168.1.0/24',
'cidr_v6': '101:1db9::/64',
'gateway_v6': '101:1db9::1',
@@ -42,15 +42,39 @@ class LibvirtVifTestCase(test.TestCase):
'id': 'network-id-xxx-yyy-zzz'
}
- mapping = {
+ mapping_bridge = {
'mac': 'ca:fe:de:ad:be:ef',
- 'gateway_v6': net['gateway_v6'],
+ 'gateway_v6': net_bridge['gateway_v6'],
'ips': [{'ip': '101.168.1.9'}],
'dhcp_server': '191.168.1.1',
'vif_uuid': 'vif-xxx-yyy-zzz',
'vif_devname': 'tap-xxx-yyy-zzz'
}
+ net_ovs = {
+ 'cidr': '101.168.1.0/24',
+ 'cidr_v6': '101:1db9::/64',
+ 'gateway_v6': '101:1db9::1',
+ 'netmask_v6': '64',
+ 'netmask': '255.255.255.0',
+ 'bridge': 'br0',
+ 'vlan': 99,
+ 'gateway': '101.168.1.1',
+ 'broadcast': '101.168.1.255',
+ 'dns1': '8.8.8.8',
+ 'id': 'network-id-xxx-yyy-zzz'
+ }
+
+ mapping_ovs = {
+ 'mac': 'ca:fe:de:ad:be:ef',
+ 'gateway_v6': net_ovs['gateway_v6'],
+ 'ips': [{'ip': '101.168.1.9'}],
+ 'dhcp_server': '191.168.1.1',
+ 'vif_uuid': 'vif-xxx-yyy-zzz',
+ 'vif_devname': 'tap-xxx-yyy-zzz',
+ 'ovs_interfaceid': 'aaa-bbb-ccc',
+ }
+
instance = {
'name': 'instance-name',
'uuid': 'instance-uuid'
@@ -67,7 +91,7 @@ class LibvirtVifTestCase(test.TestCase):
self.stubs.Set(utils, 'execute', fake_execute)
- def _get_instance_xml(self, driver):
+ def _get_instance_xml(self, driver, net, mapping):
conf = vconfig.LibvirtConfigGuest()
conf.virt_type = "qemu"
conf.name = "fake-name"
@@ -75,7 +99,7 @@ class LibvirtVifTestCase(test.TestCase):
conf.memory = 100 * 1024
conf.vcpus = 4
- nic = driver.get_config(self.instance, self.net, self.mapping)
+ nic = driver.get_config(self.instance, net, mapping)
conf.add_device(nic)
return conf.to_xml()
@@ -126,7 +150,9 @@ class LibvirtVifTestCase(test.TestCase):
libvirt_type='kvm')
d = vif.LibvirtBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -143,7 +169,9 @@ class LibvirtVifTestCase(test.TestCase):
libvirt_type='kvm')
d = vif.LibvirtBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -160,7 +188,9 @@ class LibvirtVifTestCase(test.TestCase):
libvirt_type='qemu')
d = vif.LibvirtBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -177,7 +207,9 @@ class LibvirtVifTestCase(test.TestCase):
libvirt_type='xen')
d = vif.LibvirtBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -191,7 +223,9 @@ class LibvirtVifTestCase(test.TestCase):
def test_bridge_driver(self):
d = vif.LibvirtBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -199,13 +233,15 @@ class LibvirtVifTestCase(test.TestCase):
node = ret[0]
self.assertEqual(node.get("type"), "bridge")
br_name = node.find("source").get("bridge")
- self.assertEqual(br_name, self.net['bridge'])
+ self.assertEqual(br_name, self.net_bridge['bridge'])
mac = node.find("mac").get("address")
- self.assertEqual(mac, self.mapping['mac'])
+ self.assertEqual(mac, self.mapping_bridge['mac'])
def test_ovs_ethernet_driver(self):
d = vif.LibvirtOpenVswitchDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_ovs,
+ self.mapping_ovs)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -215,13 +251,15 @@ class LibvirtVifTestCase(test.TestCase):
dev_name = node.find("target").get("dev")
self.assertTrue(dev_name.startswith("tap"))
mac = node.find("mac").get("address")
- self.assertEqual(mac, self.mapping['mac'])
+ self.assertEqual(mac, self.mapping_ovs['mac'])
script = node.find("script").get("path")
self.assertEquals(script, "")
def test_ovs_virtualport_driver(self):
d = vif.LibvirtOpenVswitchVirtualPortDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_ovs,
+ self.mapping_ovs)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -232,21 +270,24 @@ class LibvirtVifTestCase(test.TestCase):
br_name = node.find("source").get("bridge")
self.assertEqual(br_name, "br0")
mac = node.find("mac").get("address")
- self.assertEqual(mac, self.mapping['mac'])
+ self.assertEqual(mac, self.mapping_ovs['mac'])
vp = node.find("virtualport")
self.assertEqual(vp.get("type"), "openvswitch")
iface_id_found = False
for p_elem in vp.findall("parameters"):
iface_id = p_elem.get("interfaceid", None)
if iface_id:
- self.assertEqual(iface_id, self.mapping['vif_uuid'])
+ self.assertEqual(iface_id,
+ self.mapping_ovs['ovs_interfaceid'])
iface_id_found = True
self.assertTrue(iface_id_found)
def test_quantum_bridge_ethernet_driver(self):
d = vif.QuantumLinuxBridgeVIFDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_bridge,
+ self.mapping_bridge)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -256,13 +297,15 @@ class LibvirtVifTestCase(test.TestCase):
dev_name = node.find("target").get("dev")
self.assertTrue(dev_name.startswith("tap"))
mac = node.find("mac").get("address")
- self.assertEqual(mac, self.mapping['mac'])
+ self.assertEqual(mac, self.mapping_ovs['mac'])
br_name = node.find("source").get("bridge")
self.assertEqual(br_name, "br0")
def test_quantum_hybrid_driver(self):
d = vif.LibvirtHybridOVSBridgeDriver()
- xml = self._get_instance_xml(d)
+ xml = self._get_instance_xml(d,
+ self.net_ovs,
+ self.mapping_ovs)
doc = etree.fromstring(xml)
ret = doc.findall('./devices/interface')
@@ -270,6 +313,6 @@ class LibvirtVifTestCase(test.TestCase):
node = ret[0]
self.assertEqual(node.get("type"), "bridge")
br_name = node.find("source").get("bridge")
- self.assertEqual(br_name, self.net['bridge'])
+ self.assertEqual(br_name, self.net_ovs['bridge'])
mac = node.find("mac").get("address")
- self.assertEqual(mac, self.mapping['mac'])
+ self.assertEqual(mac, self.mapping_ovs['mac'])
diff --git a/nova/tests/test_metadata.py b/nova/tests/test_metadata.py
index f15d71633..c2f0b5a11 100644
--- a/nova/tests/test_metadata.py
+++ b/nova/tests/test_metadata.py
@@ -19,7 +19,7 @@
"""Tests for metadata service."""
import base64
-from copy import copy
+import copy
import json
import re
@@ -120,14 +120,14 @@ class MetadataTestCase(test.TestCase):
spectacular=True)
def test_user_data(self):
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
inst['user_data'] = base64.b64encode("happy")
md = fake_InstanceMetadata(self.stubs, inst)
self.assertEqual(
md.get_ec2_metadata(version='2009-04-04')['user-data'], "happy")
def test_no_user_data(self):
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
del inst['user_data']
md = fake_InstanceMetadata(self.stubs, inst)
obj = object()
@@ -136,7 +136,7 @@ class MetadataTestCase(test.TestCase):
obj)
def test_security_groups(self):
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
sgroups = [{'name': 'default'}, {'name': 'other'}]
expected = ['default', 'other']
@@ -145,7 +145,7 @@ class MetadataTestCase(test.TestCase):
self.assertEqual(data['meta-data']['security-groups'], expected)
def test_local_hostname_fqdn(self):
- md = fake_InstanceMetadata(self.stubs, copy(self.instance))
+ md = fake_InstanceMetadata(self.stubs, copy.copy(self.instance))
data = md.get_ec2_metadata(version='2009-04-04')
self.assertEqual(data['meta-data']['local-hostname'],
"%s.%s" % (self.instance['hostname'], CONF.dhcp_domain))
@@ -195,7 +195,7 @@ class MetadataTestCase(test.TestCase):
expected)
def test_pubkey(self):
- md = fake_InstanceMetadata(self.stubs, copy(self.instance))
+ md = fake_InstanceMetadata(self.stubs, copy.copy(self.instance))
pubkey_ent = md.lookup("/2009-04-04/meta-data/public-keys")
self.assertEqual(base.ec2_md_print(pubkey_ent),
@@ -204,7 +204,7 @@ class MetadataTestCase(test.TestCase):
self.instance['key_data'])
def test_image_type_ramdisk(self):
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
inst['ramdisk_id'] = 'ari-853667c0'
md = fake_InstanceMetadata(self.stubs, inst)
data = md.lookup("/latest/meta-data/ramdisk-id")
@@ -213,7 +213,7 @@ class MetadataTestCase(test.TestCase):
self.assertTrue(re.match('ari-[0-9a-f]{8}', data))
def test_image_type_kernel(self):
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
inst['kernel_id'] = 'aki-c2e26ff2'
md = fake_InstanceMetadata(self.stubs, inst)
data = md.lookup("/2009-04-04/meta-data/kernel-id")
@@ -229,7 +229,7 @@ class MetadataTestCase(test.TestCase):
md.lookup, "/2009-04-04/meta-data/kernel-id")
def test_check_version(self):
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
md = fake_InstanceMetadata(self.stubs, inst)
self.assertTrue(md._check_version('1.0', '2009-04-04'))
@@ -250,7 +250,7 @@ class OpenStackMetadataTestCase(test.TestCase):
def test_top_level_listing(self):
# request for /openstack/<version>/ should show metadata.json
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
mdinst = fake_InstanceMetadata(self.stubs, inst)
listing = mdinst.lookup("/openstack/")
@@ -267,14 +267,14 @@ class OpenStackMetadataTestCase(test.TestCase):
def test_version_content_listing(self):
# request for /openstack/<version>/ should show metadata.json
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
mdinst = fake_InstanceMetadata(self.stubs, inst)
listing = mdinst.lookup("/openstack/2012-08-10")
self.assertTrue("meta_data.json" in listing)
def test_metadata_json(self):
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
content = [
('/etc/my.conf', "content of my.conf"),
('/root/hello', "content of /root/hello"),
@@ -309,7 +309,7 @@ class OpenStackMetadataTestCase(test.TestCase):
def test_extra_md(self):
# make sure extra_md makes it through to metadata
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
extra = {'foo': 'bar', 'mylist': [1, 2, 3],
'mydict': {"one": 1, "two": 2}}
mdinst = fake_InstanceMetadata(self.stubs, inst, extra_md=extra)
@@ -322,14 +322,14 @@ class OpenStackMetadataTestCase(test.TestCase):
def test_password(self):
# make sure extra_md makes it through to metadata
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
mdinst = fake_InstanceMetadata(self.stubs, inst)
result = mdinst.lookup("/openstack/latest/password")
self.assertEqual(result, password.handle_password)
def test_userdata(self):
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
mdinst = fake_InstanceMetadata(self.stubs, inst)
userdata_found = mdinst.lookup("/openstack/2012-08-10/user_data")
@@ -348,7 +348,7 @@ class OpenStackMetadataTestCase(test.TestCase):
mdinst.lookup, "/openstack/2012-08-10/user_data")
def test_random_seed(self):
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
mdinst = fake_InstanceMetadata(self.stubs, inst)
# verify that 2013-04-04 has the 'random' field
@@ -364,7 +364,7 @@ class OpenStackMetadataTestCase(test.TestCase):
def test_no_dashes_in_metadata(self):
# top level entries in meta_data should not contain '-' in their name
- inst = copy(self.instance)
+ inst = copy.copy(self.instance)
mdinst = fake_InstanceMetadata(self.stubs, inst)
mdjson = json.loads(mdinst.lookup("/openstack/latest/meta_data.json"))
@@ -522,7 +522,7 @@ class MetadataPasswordTestCase(test.TestCase):
super(MetadataPasswordTestCase, self).setUp()
fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs,
spectacular=True)
- self.instance = copy(INSTANCES[0])
+ self.instance = copy.copy(INSTANCES[0])
self.mdinst = fake_InstanceMetadata(self.stubs, self.instance,
address=None, sgroups=None)
diff --git a/nova/tests/test_migrations.py b/nova/tests/test_migrations.py
index abd04a641..f0ed0a863 100644
--- a/nova/tests/test_migrations.py
+++ b/nova/tests/test_migrations.py
@@ -47,7 +47,7 @@ def _get_connect_string(backend,
passwd="openstack_citest",
database="openstack_citest"):
"""
- Try to get a connection with a very specfic set of values, if we get
+ Try to get a connection with a very specific set of values, if we get
these then we'll run the tests, otherwise they are skipped
"""
if backend == "postgres":
@@ -136,12 +136,6 @@ class TestMigrations(test.TestCase):
# and recreate it, which ensures that we have no side-effects
# from the tests
self._reset_databases()
-
- # remove these from the list so they aren't used in the migration tests
- if "mysqlcitest" in self.engines:
- del self.engines["mysqlcitest"]
- if "mysqlcitest" in self.test_databases:
- del self.test_databases["mysqlcitest"]
super(TestMigrations, self).tearDown()
def _reset_databases(self):
@@ -195,7 +189,7 @@ class TestMigrations(test.TestCase):
"~/.pgpass && chmod 0600 ~/.pgpass" % locals())
execute_cmd(createpgpass)
# note(boris-42): We must create and drop database, we can't
- # drop database wich we have connected to, so for such
+ # drop database which we have connected to, so for such
# operations there is a special database template1.
sqlcmd = ("psql -w -U %(user)s -h %(host)s -c"
" '%(sql)s' -d template1")
@@ -222,7 +216,7 @@ class TestMigrations(test.TestCase):
if _is_backend_avail('mysql', user="openstack_cifail"):
self.fail("Shouldn't have connected")
- def test_mysql_innodb(self):
+ def test_mysql_opportunistically(self):
# Test that table creation on mysql only builds InnoDB tables
if not _is_backend_avail('mysql'):
self.skipTest("mysql not available")
@@ -233,6 +227,12 @@ class TestMigrations(test.TestCase):
self.engines["mysqlcitest"] = engine
self.test_databases["mysqlcitest"] = connect_string
+ # Test that we end in an innodb
+ self._check_mysql_innodb(engine)
+ # Test IP transition
+ self._check_mysql_migration_149(engine)
+
+ def _check_mysql_innodb(self, engine):
# build a fully populated mysql database with all the tables
self._reset_databases()
self._walk_versions(engine, False, False)
@@ -298,16 +298,8 @@ class TestMigrations(test.TestCase):
"AND column_name='cidr'").scalar())
connection.close()
- def test_migration_149_mysql(self):
+ def _check_mysql_migration_149(self, engine):
"""Test updating a table with IPAddress columns."""
- if not _have_mysql():
- self.skipTest("mysql not available")
-
- connect_string = _get_connect_string("mysql")
- engine = sqlalchemy.create_engine(connect_string)
- self.engines["mysqlcitest"] = engine
- self.test_databases["mysqlcitest"] = connect_string
-
self._reset_databases()
migration_api.version_control(engine, TestMigrations.REPOSITORY,
migration.INIT_VERSION)
@@ -492,3 +484,77 @@ class TestMigrations(test.TestCase):
migration_api.downgrade(engine, TestMigrations.REPOSITORY, 146)
_146_check()
+
+ def test_migration_152(self):
+ host1 = 'compute-host1'
+ host2 = 'compute-host2'
+
+ def _151_check(services, volumes):
+ service = services.select(services.c.id == 1).execute().first()
+ self.assertEqual(False, service.deleted)
+ service = services.select(services.c.id == 2).execute().first()
+ self.assertEqual(True, service.deleted)
+
+ volume = volumes.select(volumes.c.id == "first").execute().first()
+ self.assertEqual(False, volume.deleted)
+ volume = volumes.select(volumes.c.id == "second").execute().first()
+ self.assertEqual(True, volume.deleted)
+
+ for key, engine in self.engines.items():
+ migration_api.version_control(engine, TestMigrations.REPOSITORY,
+ migration.INIT_VERSION)
+ migration_api.upgrade(engine, TestMigrations.REPOSITORY, 151)
+ metadata = sqlalchemy.schema.MetaData()
+ metadata.bind = engine
+
+ # NOTE(boris-42): It is enough to test one table with type of `id`
+ # column Integer and one with type String.
+ services = sqlalchemy.Table('services', metadata, autoload=True)
+ volumes = sqlalchemy.Table('volumes', metadata, autoload=True)
+
+ engine.execute(
+ services.insert(),
+ [
+ {'id': 1, 'host': host1, 'binary': 'nova-compute',
+ 'report_count': 0, 'topic': 'compute', 'deleted': False},
+ {'id': 2, 'host': host1, 'binary': 'nova-compute',
+ 'report_count': 0, 'topic': 'compute', 'deleted': True}
+ ]
+ )
+
+ engine.execute(
+ volumes.insert(),
+ [
+ {'id': 'first', 'host': host1, 'deleted': False},
+ {'id': 'second', 'host': host2, 'deleted': True}
+ ]
+ )
+
+ _151_check(services, volumes)
+
+ migration_api.upgrade(engine, TestMigrations.REPOSITORY, 152)
+ # NOTE(boris-42): One more time get from DB info about tables.
+ metadata2 = sqlalchemy.schema.MetaData()
+ metadata2.bind = engine
+
+ services = sqlalchemy.Table('services', metadata2, autoload=True)
+
+ service = services.select(services.c.id == 1).execute().first()
+ self.assertEqual(0, service.deleted)
+ service = services.select(services.c.id == 2).execute().first()
+ self.assertEqual(service.id, service.deleted)
+
+ volumes = sqlalchemy.Table('volumes', metadata2, autoload=True)
+ volume = volumes.select(volumes.c.id == "first").execute().first()
+ self.assertEqual("", volume.deleted)
+ volume = volumes.select(volumes.c.id == "second").execute().first()
+ self.assertEqual(volume.id, volume.deleted)
+
+ migration_api.downgrade(engine, TestMigrations.REPOSITORY, 151)
+ # NOTE(boris-42): One more time get from DB info about tables.
+ metadata = sqlalchemy.schema.MetaData()
+ metadata.bind = engine
+ services = sqlalchemy.Table('services', metadata, autoload=True)
+ volumes = sqlalchemy.Table('volumes', metadata, autoload=True)
+
+ _151_check(services, volumes)
diff --git a/nova/tests/test_periodic_tasks.py b/nova/tests/test_periodic_tasks.py
index 39669967f..3c63f6d4a 100644
--- a/nova/tests/test_periodic_tasks.py
+++ b/nova/tests/test_periodic_tasks.py
@@ -15,9 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
import fixtures
-import time
+from testtools import matchers
from nova import manager
from nova import test
@@ -44,10 +45,11 @@ class ManagerMetaTestCase(test.TestCase):
return 'baz'
m = Manager()
- self.assertEqual(2, len(m._periodic_tasks))
+ self.assertThat(m._periodic_tasks, matchers.HasLength(2))
self.assertEqual(None, m._periodic_spacing['foo'])
self.assertEqual(4, m._periodic_spacing['bar'])
- self.assertFalse('baz' in m._periodic_spacing)
+ self.assertThat(
+ m._periodic_spacing, matchers.Not(matchers.Contains('baz')))
class Manager(test.TestCase):
@@ -60,7 +62,7 @@ class Manager(test.TestCase):
return 'bar'
m = Manager()
- self.assertEqual(1, len(m._periodic_tasks))
+ self.assertThat(m._periodic_tasks, matchers.HasLength(1))
self.assertEqual(200, m._periodic_spacing['bar'])
# Now a single pass of the periodic tasks
@@ -87,8 +89,8 @@ class Manager(test.TestCase):
m.periodic_tasks(None)
time.sleep(0.1)
idle = m.periodic_tasks(None)
- self.assertTrue(idle > 9.7)
- self.assertTrue(idle < 9.9)
+ self.assertThat(idle, matchers.GreaterThan(9.7))
+ self.assertThat(idle, matchers.LessThan(9.9))
def test_periodic_tasks_disabled(self):
class Manager(manager.Manager):
@@ -109,7 +111,7 @@ class Manager(test.TestCase):
return 'bar'
m = Manager()
- self.assertEqual(1, len(m._periodic_tasks))
+ self.assertThat(m._periodic_tasks, matchers.HasLength(1))
def test_external_running_elsewhere(self):
self.flags(run_external_periodic_tasks=False)
@@ -120,4 +122,4 @@ class Manager(test.TestCase):
return 'bar'
m = Manager()
- self.assertEqual(0, len(m._periodic_tasks))
+ self.assertEqual([], m._periodic_tasks)
diff --git a/nova/tests/test_pipelib.py b/nova/tests/test_pipelib.py
index 85c2ca2cd..5cd715552 100644
--- a/nova/tests/test_pipelib.py
+++ b/nova/tests/test_pipelib.py
@@ -51,11 +51,11 @@ class PipelibTest(test.TestCase):
def test_setup_security_group(self):
group_name = "%s%s" % (self.project, CONF.vpn_key_suffix)
- # First attemp, does not exist (thus its created)
+ # First attempt, does not exist (thus its created)
res1_group = self.cloudpipe.setup_security_group(self.context)
self.assertEqual(res1_group, group_name)
- # Second attem, it exists in the DB
+ # Second attempt, it exists in the DB
res2_group = self.cloudpipe.setup_security_group(self.context)
self.assertEqual(res1_group, res2_group)
@@ -64,10 +64,10 @@ class PipelibTest(test.TestCase):
with utils.tempdir() as tmpdir:
self.flags(keys_path=tmpdir)
- # First attemp, key does not exist (thus it is generated)
+ # First attempt, key does not exist (thus it is generated)
res1_key = self.cloudpipe.setup_key_pair(self.context)
self.assertEqual(res1_key, key_name)
- # Second attem, it exists in the DB
+ # Second attempt, it exists in the DB
res2_key = self.cloudpipe.setup_key_pair(self.context)
self.assertEqual(res2_key, res1_key)
diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py
index 9eab72c5b..84d56cadf 100644
--- a/nova/tests/test_utils.py
+++ b/nova/tests/test_utils.py
@@ -778,3 +778,14 @@ class IntLikeTestCase(test.TestCase):
self.assertFalse(
utils.is_int_like("0cc3346e-9fef-4445-abe6-5d2b2690ec64"))
self.assertFalse(utils.is_int_like("a1"))
+
+
+class MetadataToDictTestCase(test.TestCase):
+ def test_metadata_to_dict(self):
+ self.assertEqual(utils.metadata_to_dict(
+ [{'key': 'foo1', 'value': 'bar'},
+ {'key': 'foo2', 'value': 'baz'}]),
+ {'foo1': 'bar', 'foo2': 'baz'})
+
+ def test_metadata_to_dict_empty(self):
+ self.assertEqual(utils.metadata_to_dict([]), {})
diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py
index 9747ecccd..6ea2d0ef7 100644
--- a/nova/tests/test_virt_drivers.py
+++ b/nova/tests/test_virt_drivers.py
@@ -20,7 +20,7 @@ import netaddr
import sys
import traceback
-from nova.compute.manager import ComputeManager
+from nova.compute import manager
from nova import exception
from nova.openstack.common import importutils
from nova.openstack.common import log as logging
@@ -159,7 +159,7 @@ class VirtDriverLoaderTestCase(_FakeDriverBackendTestCase, test.TestCase):
# NOTE(sdague) the try block is to make it easier to debug a
# failure by knowing which driver broke
try:
- cm = ComputeManager()
+ cm = manager.ComputeManager()
except Exception as e:
self.fail("Couldn't load driver %s - %s" % (cls, e))
@@ -173,7 +173,7 @@ class VirtDriverLoaderTestCase(_FakeDriverBackendTestCase, test.TestCase):
raise test.TestingException()
self.stubs.Set(sys, 'exit', _fake_exit)
- self.assertRaises(test.TestingException, ComputeManager)
+ self.assertRaises(test.TestingException, manager.ComputeManager)
class _VirtDriverTestCase(_FakeDriverBackendTestCase):
diff --git a/nova/tests/test_wsgi.py b/nova/tests/test_wsgi.py
index b4b25ed97..cd64688a2 100644
--- a/nova/tests/test_wsgi.py
+++ b/nova/tests/test_wsgi.py
@@ -21,9 +21,19 @@
import os.path
import tempfile
+import eventlet
+import httplib2
+import paste
+
import nova.exception
from nova import test
import nova.wsgi
+import urllib2
+import webob
+
+SSL_CERT_DIR = os.path.normpath(os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ 'ssl_cert'))
class TestLoaderNothingExists(test.TestCase):
@@ -99,3 +109,111 @@ class TestWSGIServer(test.TestCase):
self.assertNotEqual(0, server.port)
server.stop()
server.wait()
+
+ def test_uri_length_limit(self):
+ server = nova.wsgi.Server("test_uri_length_limit", None,
+ host="127.0.0.1", max_url_len=16384)
+ server.start()
+
+ uri = "http://127.0.0.1:%d/%s" % (server.port, 10000 * 'x')
+ resp, _ = httplib2.Http().request(uri)
+ eventlet.sleep(0)
+ self.assertNotEqual(resp.status,
+ paste.httpexceptions.HTTPRequestURITooLong.code)
+
+ uri = "http://127.0.0.1:%d/%s" % (server.port, 20000 * 'x')
+ resp, _ = httplib2.Http().request(uri)
+ eventlet.sleep(0)
+ self.assertEqual(resp.status,
+ paste.httpexceptions.HTTPRequestURITooLong.code)
+ server.stop()
+ server.wait()
+
+
+class TestWSGIServerWithSSL(test.TestCase):
+ """WSGI server with SSL tests."""
+
+ def setUp(self):
+ super(TestWSGIServerWithSSL, self).setUp()
+ self.flags(enabled_ssl_apis=['fake_ssl'],
+ ssl_cert_file=os.path.join(SSL_CERT_DIR, 'certificate.crt'),
+ ssl_key_file=os.path.join(SSL_CERT_DIR, 'privatekey.key'))
+
+ def test_ssl_server(self):
+
+ def test_app(env, start_response):
+ start_response('200 OK', {})
+ return ['PONG']
+
+ fake_ssl_server = nova.wsgi.Server("fake_ssl", test_app,
+ host="127.0.0.1", port=0,
+ use_ssl=True)
+ fake_ssl_server.start()
+ self.assertNotEqual(0, fake_ssl_server.port)
+
+ cli = eventlet.connect(("localhost", fake_ssl_server.port))
+ cli = eventlet.wrap_ssl(cli,
+ ca_certs=os.path.join(SSL_CERT_DIR, 'ca.crt'))
+
+ cli.write('POST / HTTP/1.1\r\nHost: localhost\r\n'
+ 'Connection: close\r\nContent-length:4\r\n\r\nPING')
+ response = cli.read(8192)
+ self.assertEquals(response[-4:], "PONG")
+
+ fake_ssl_server.stop()
+ fake_ssl_server.wait()
+
+ def test_two_servers(self):
+
+ def test_app(env, start_response):
+ start_response('200 OK', {})
+ return ['PONG']
+
+ fake_ssl_server = nova.wsgi.Server("fake_ssl", test_app,
+ host="127.0.0.1", port=0, use_ssl=True)
+ fake_ssl_server.start()
+ self.assertNotEqual(0, fake_ssl_server.port)
+
+ fake_server = nova.wsgi.Server("fake", test_app,
+ host="127.0.0.1", port=0)
+ fake_server.start()
+ self.assertNotEquals(0, fake_server.port)
+
+ cli = eventlet.connect(("localhost", fake_ssl_server.port))
+ cli = eventlet.wrap_ssl(cli,
+ ca_certs=os.path.join(SSL_CERT_DIR, 'ca.crt'))
+
+ cli.write('POST / HTTP/1.1\r\nHost: localhost\r\n'
+ 'Connection: close\r\nContent-length:4\r\n\r\nPING')
+ response = cli.read(8192)
+ self.assertEquals(response[-4:], "PONG")
+
+ cli = eventlet.connect(("localhost", fake_server.port))
+
+ cli.sendall('POST / HTTP/1.1\r\nHost: localhost\r\n'
+ 'Connection: close\r\nContent-length:4\r\n\r\nPING')
+ response = cli.recv(8192)
+ self.assertEquals(response[-4:], "PONG")
+
+ fake_ssl_server.stop()
+ fake_ssl_server.wait()
+
+ def test_app_using_ipv6_and_ssl(self):
+ greetings = 'Hello, World!!!'
+
+ @webob.dec.wsgify
+ def hello_world(req):
+ return greetings
+
+ server = nova.wsgi.Server("fake_ssl",
+ hello_world,
+ host="::1",
+ port=0,
+ use_ssl=True)
+ server.start()
+
+ response = urllib2.urlopen('https://[::1]:%d/' % server.port)
+ self.assertEquals(greetings, response.read())
+
+ server.stop()
+ server.wait()
diff --git a/nova/tests/virt/xenapi/test_volumeops.py b/nova/tests/virt/xenapi/test_volumeops.py
index 7cc5c70da..844ae8459 100644
--- a/nova/tests/virt/xenapi/test_volumeops.py
+++ b/nova/tests/virt/xenapi/test_volumeops.py
@@ -21,6 +21,13 @@ from nova.virt.xenapi import volumeops
class VolumeAttachTestCase(test.TestCase):
def test_detach_volume_call(self):
+ registered_calls = []
+
+ def regcall(label):
+ def side_effect(*args, **kwargs):
+ registered_calls.append(label)
+ return side_effect
+
ops = volumeops.VolumeOps('session')
self.mox.StubOutWithMock(volumeops.vm_utils, 'vm_ref_or_raise')
self.mox.StubOutWithMock(volumeops.vm_utils, 'find_vbd_by_number')
@@ -45,10 +52,12 @@ class VolumeAttachTestCase(test.TestCase):
volumeops.vm_utils.unplug_vbd('session', 'vbdref')
- volumeops.vm_utils.destroy_vbd('session', 'vbdref')
+ volumeops.vm_utils.destroy_vbd('session', 'vbdref').WithSideEffects(
+ regcall('destroy_vbd'))
volumeops.volume_utils.find_sr_from_vbd(
- 'session', 'vbdref').AndReturn('srref')
+ 'session', 'vbdref').WithSideEffects(
+ regcall('find_sr_from_vbd')).AndReturn('srref')
volumeops.volume_utils.purge_sr('session', 'srref')
@@ -58,6 +67,9 @@ class VolumeAttachTestCase(test.TestCase):
dict(driver_volume_type='iscsi', data='conn_data'),
'instance_1', 'mountpoint')
+ self.assertEquals(
+ ['find_sr_from_vbd', 'destroy_vbd'], registered_calls)
+
def test_attach_volume_call(self):
ops = volumeops.VolumeOps('session')
self.mox.StubOutWithMock(ops, '_connect_volume')
diff --git a/nova/utils.py b/nova/utils.py
index 75cba0a7c..97091e42c 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -507,14 +507,18 @@ def str_dict_replace(s, mapping):
class LazyPluggable(object):
"""A pluggable backend loaded lazily based on some value."""
- def __init__(self, pivot, **backends):
+ def __init__(self, pivot, config_group=None, **backends):
self.__backends = backends
self.__pivot = pivot
self.__backend = None
+ self.__config_group = config_group
def __get_backend(self):
if not self.__backend:
- backend_name = CONF[self.__pivot]
+ if self.__config_group is None:
+ backend_name = CONF[self.__pivot]
+ else:
+ backend_name = CONF[self.__config_group][self.__pivot]
if backend_name not in self.__backends:
msg = _('Invalid backend: %s') % backend_name
raise exception.NovaException(msg)
@@ -1257,3 +1261,10 @@ def last_bytes(file_like_object, num):
remaining = file_like_object.tell()
return (file_like_object.read(), remaining)
+
+
+def metadata_to_dict(metadata):
+ result = {}
+ for item in metadata:
+ result[item['key']] = item['value']
+ return result
diff --git a/nova/virt/baremetal/__init__.py b/nova/virt/baremetal/__init__.py
index e3ecef821..9c8318660 100644
--- a/nova/virt/baremetal/__init__.py
+++ b/nova/virt/baremetal/__init__.py
@@ -12,4 +12,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-from nova.virt.baremetal.driver import BareMetalDriver
+from nova.virt.baremetal import driver
+
+BareMetalDriver = driver.BareMetalDriver
diff --git a/nova/virt/baremetal/db/api.py b/nova/virt/baremetal/db/api.py
index 206a59b4f..002425333 100644
--- a/nova/virt/baremetal/db/api.py
+++ b/nova/virt/baremetal/db/api.py
@@ -50,16 +50,21 @@ from nova import utils
# because utils.LazyPluggable doesn't support reading from
# option groups. See bug #1093043.
db_opts = [
- cfg.StrOpt('baremetal_db_backend',
+ cfg.StrOpt('db_backend',
default='sqlalchemy',
- help='The backend to use for db'),
+ help='The backend to use for bare-metal database'),
]
+baremetal_group = cfg.OptGroup(name='baremetal',
+ title='Baremetal Options')
+
CONF = cfg.CONF
-CONF.register_opts(db_opts)
+CONF.register_group(baremetal_group)
+CONF.register_opts(db_opts, baremetal_group)
IMPL = utils.LazyPluggable(
- 'baremetal_db_backend',
+ 'db_backend',
+ config_group='baremetal',
sqlalchemy='nova.virt.baremetal.db.sqlalchemy.api')
diff --git a/nova/virt/baremetal/db/migration.py b/nova/virt/baremetal/db/migration.py
index 40631bf45..d630ccf65 100644
--- a/nova/virt/baremetal/db/migration.py
+++ b/nova/virt/baremetal/db/migration.py
@@ -22,7 +22,8 @@ from nova import utils
IMPL = utils.LazyPluggable(
- 'baremetal_db_backend',
+ 'db_backend',
+ config_group='baremetal',
sqlalchemy='nova.virt.baremetal.db.sqlalchemy.migration')
INIT_VERSION = 0
diff --git a/nova/virt/baremetal/db/sqlalchemy/api.py b/nova/virt/baremetal/db/sqlalchemy/api.py
index e2240053c..34bcd1229 100644
--- a/nova/virt/baremetal/db/sqlalchemy/api.py
+++ b/nova/virt/baremetal/db/sqlalchemy/api.py
@@ -23,14 +23,13 @@
from sqlalchemy.sql.expression import asc
from sqlalchemy.sql.expression import literal_column
-from nova.db.sqlalchemy.api import is_user_context
-from nova.db.sqlalchemy.api import require_admin_context
+from nova.db.sqlalchemy import api as sqlalchemy_api
from nova import exception
from nova.openstack.common import log as logging
from nova.openstack.common import timeutils
from nova.openstack.common import uuidutils
from nova.virt.baremetal.db.sqlalchemy import models
-from nova.virt.baremetal.db.sqlalchemy.session import get_session
+from nova.virt.baremetal.db.sqlalchemy import session as db_session
LOG = logging.getLogger(__name__)
@@ -44,7 +43,7 @@ def model_query(context, *args, **kwargs):
:param project_only: if present and context is user-type, then restrict
query to match the context's project_id.
"""
- session = kwargs.get('session') or get_session()
+ session = kwargs.get('session') or db_session.get_session()
read_deleted = kwargs.get('read_deleted') or context.read_deleted
project_only = kwargs.get('project_only')
@@ -60,7 +59,7 @@ def model_query(context, *args, **kwargs):
raise Exception(
_("Unrecognized read_deleted value '%s'") % read_deleted)
- if project_only and is_user_context(context):
+ if project_only and sqlalchemy_api.is_user_context(context):
query = query.filter_by(project_id=context.project_id)
return query
@@ -68,7 +67,7 @@ def model_query(context, *args, **kwargs):
def _save(ref, session=None):
if not session:
- session = get_session()
+ session = db_session.get_session()
# We must not call ref.save() with session=None, otherwise NovaBase
# uses nova-db's session, which cannot access bm-db.
ref.save(session=session)
@@ -81,7 +80,7 @@ def _build_node_order_by(query):
return query
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_node_get_all(context, service_host=None):
query = model_query(context, models.BareMetalNode, read_deleted="no")
if service_host:
@@ -89,7 +88,7 @@ def bm_node_get_all(context, service_host=None):
return query.all()
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_node_find_free(context, service_host=None,
cpus=None, memory_mb=None, local_gb=None):
query = model_query(context, models.BareMetalNode, read_deleted="no")
@@ -106,7 +105,7 @@ def bm_node_find_free(context, service_host=None,
return query.first()
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_node_get(context, bm_node_id):
# bm_node_id may be passed as a string. Convert to INT to improve DB perf.
bm_node_id = int(bm_node_id)
@@ -120,7 +119,7 @@ def bm_node_get(context, bm_node_id):
return result
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_node_get_by_instance_uuid(context, instance_uuid):
if not uuidutils.is_uuid_like(instance_uuid):
raise exception.InstanceNotFound(instance_id=instance_uuid)
@@ -135,7 +134,7 @@ def bm_node_get_by_instance_uuid(context, instance_uuid):
return result
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_node_create(context, values):
bm_node_ref = models.BareMetalNode()
bm_node_ref.update(values)
@@ -143,14 +142,14 @@ def bm_node_create(context, values):
return bm_node_ref
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_node_update(context, bm_node_id, values):
model_query(context, models.BareMetalNode, read_deleted="no").\
filter_by(id=bm_node_id).\
update(values)
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_node_set_uuid_safe(context, bm_node_id, values):
"""Associate an instance to a node safely
@@ -164,7 +163,7 @@ def bm_node_set_uuid_safe(context, bm_node_id, values):
raise exception.NovaException(_(
"instance_uuid must be supplied to bm_node_set_uuid_safe"))
- session = get_session()
+ session = db_session.get_session()
with session.begin():
query = model_query(context, models.BareMetalNode,
session=session, read_deleted="no").\
@@ -181,7 +180,7 @@ def bm_node_set_uuid_safe(context, bm_node_id, values):
return ref
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_node_destroy(context, bm_node_id):
model_query(context, models.BareMetalNode).\
filter_by(id=bm_node_id).\
@@ -190,13 +189,13 @@ def bm_node_destroy(context, bm_node_id):
'updated_at': literal_column('updated_at')})
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_pxe_ip_get_all(context):
query = model_query(context, models.BareMetalPxeIp, read_deleted="no")
return query.all()
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_pxe_ip_create(context, address, server_address):
ref = models.BareMetalPxeIp()
ref.address = address
@@ -205,7 +204,7 @@ def bm_pxe_ip_create(context, address, server_address):
return ref
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_pxe_ip_create_direct(context, bm_pxe_ip):
ref = bm_pxe_ip_create(context,
address=bm_pxe_ip['address'],
@@ -213,7 +212,7 @@ def bm_pxe_ip_create_direct(context, bm_pxe_ip):
return ref
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_pxe_ip_destroy(context, ip_id):
# Delete physically since it has unique columns
model_query(context, models.BareMetalPxeIp, read_deleted="no").\
@@ -221,7 +220,7 @@ def bm_pxe_ip_destroy(context, ip_id):
delete()
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_pxe_ip_destroy_by_address(context, address):
# Delete physically since it has unique columns
model_query(context, models.BareMetalPxeIp, read_deleted="no").\
@@ -229,7 +228,7 @@ def bm_pxe_ip_destroy_by_address(context, address):
delete()
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_pxe_ip_get(context, ip_id):
result = model_query(context, models.BareMetalPxeIp, read_deleted="no").\
filter_by(id=ip_id).\
@@ -238,7 +237,7 @@ def bm_pxe_ip_get(context, ip_id):
return result
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_pxe_ip_get_by_bm_node_id(context, bm_node_id):
result = model_query(context, models.BareMetalPxeIp, read_deleted="no").\
filter_by(bm_node_id=bm_node_id).\
@@ -250,9 +249,9 @@ def bm_pxe_ip_get_by_bm_node_id(context, bm_node_id):
return result
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_pxe_ip_associate(context, bm_node_id):
- session = get_session()
+ session = db_session.get_session()
with session.begin():
# Check if the node really exists
node_ref = model_query(context, models.BareMetalNode,
@@ -288,14 +287,14 @@ def bm_pxe_ip_associate(context, bm_node_id):
return ip_ref.id
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_pxe_ip_disassociate(context, bm_node_id):
model_query(context, models.BareMetalPxeIp, read_deleted="no").\
filter_by(bm_node_id=bm_node_id).\
update({'bm_node_id': None})
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_interface_get(context, if_id):
result = model_query(context, models.BareMetalInterface,
read_deleted="no").\
@@ -309,14 +308,14 @@ def bm_interface_get(context, if_id):
return result
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_interface_get_all(context):
query = model_query(context, models.BareMetalInterface,
read_deleted="no")
return query.all()
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_interface_destroy(context, if_id):
# Delete physically since it has unique columns
model_query(context, models.BareMetalInterface, read_deleted="no").\
@@ -324,7 +323,7 @@ def bm_interface_destroy(context, if_id):
delete()
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_interface_create(context, bm_node_id, address, datapath_id, port_no):
ref = models.BareMetalInterface()
ref.bm_node_id = bm_node_id
@@ -335,9 +334,9 @@ def bm_interface_create(context, bm_node_id, address, datapath_id, port_no):
return ref.id
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_interface_set_vif_uuid(context, if_id, vif_uuid):
- session = get_session()
+ session = db_session.get_session()
with session.begin():
bm_interface = model_query(context, models.BareMetalInterface,
read_deleted="no", session=session).\
@@ -361,7 +360,7 @@ def bm_interface_set_vif_uuid(context, if_id, vif_uuid):
raise e
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_interface_get_by_vif_uuid(context, vif_uuid):
result = model_query(context, models.BareMetalInterface,
read_deleted="no").\
@@ -375,7 +374,7 @@ def bm_interface_get_by_vif_uuid(context, vif_uuid):
return result
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_interface_get_all_by_bm_node_id(context, bm_node_id):
result = model_query(context, models.BareMetalInterface,
read_deleted="no").\
@@ -388,7 +387,7 @@ def bm_interface_get_all_by_bm_node_id(context, bm_node_id):
return result
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_deployment_create(context, key, image_path, pxe_config_path, root_mb,
swap_mb):
ref = models.BareMetalDeployment()
@@ -401,7 +400,7 @@ def bm_deployment_create(context, key, image_path, pxe_config_path, root_mb,
return ref.id
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_deployment_get(context, dep_id):
result = model_query(context, models.BareMetalDeployment,
read_deleted="no").\
@@ -410,7 +409,7 @@ def bm_deployment_get(context, dep_id):
return result
-@require_admin_context
+@sqlalchemy_api.require_admin_context
def bm_deployment_destroy(context, dep_id):
model_query(context, models.BareMetalDeployment).\
filter_by(id=dep_id).\
diff --git a/nova/virt/baremetal/db/sqlalchemy/migration.py b/nova/virt/baremetal/db/sqlalchemy/migration.py
index 929793e70..cfc26c04c 100644
--- a/nova/virt/baremetal/db/sqlalchemy/migration.py
+++ b/nova/virt/baremetal/db/sqlalchemy/migration.py
@@ -25,7 +25,7 @@ import sqlalchemy
from nova import exception
from nova.openstack.common import log as logging
from nova.virt.baremetal.db import migration
-from nova.virt.baremetal.db.sqlalchemy.session import get_engine
+from nova.virt.baremetal.db.sqlalchemy import session
LOG = logging.getLogger(__name__)
@@ -71,24 +71,25 @@ def db_sync(version=None):
current_version = db_version()
repository = _find_migrate_repo()
if version is None or version > current_version:
- return versioning_api.upgrade(get_engine(), repository, version)
+ return versioning_api.upgrade(session.get_engine(), repository,
+ version)
else:
- return versioning_api.downgrade(get_engine(), repository,
+ return versioning_api.downgrade(session.get_engine(), repository,
version)
def db_version():
repository = _find_migrate_repo()
try:
- return versioning_api.db_version(get_engine(), repository)
+ return versioning_api.db_version(session.get_engine(), repository)
except versioning_exceptions.DatabaseNotControlledError:
meta = sqlalchemy.MetaData()
- engine = get_engine()
+ engine = session.get_engine()
meta.reflect(bind=engine)
tables = meta.tables
if len(tables) == 0:
db_version_control(migration.INIT_VERSION)
- return versioning_api.db_version(get_engine(), repository)
+ return versioning_api.db_version(session.get_engine(), repository)
else:
# Some pre-Essex DB's may not be version controlled.
# Require them to upgrade using Essex first.
@@ -98,7 +99,7 @@ def db_version():
def db_version_control(version=None):
repository = _find_migrate_repo()
- versioning_api.version_control(get_engine(), repository, version)
+ versioning_api.version_control(session.get_engine(), repository, version)
return version
diff --git a/nova/virt/baremetal/fake.py b/nova/virt/baremetal/fake.py
index 7a400af6f..b3f39fdc3 100644
--- a/nova/virt/baremetal/fake.py
+++ b/nova/virt/baremetal/fake.py
@@ -17,7 +17,7 @@
# under the License.
from nova.virt.baremetal import base
-from nova.virt.firewall import NoopFirewallDriver
+from nova.virt import firewall
class FakeDriver(base.NodeDriver):
@@ -52,7 +52,7 @@ class FakePowerManager(base.PowerManager):
super(FakePowerManager, self).__init__(**kwargs)
-class FakeFirewallDriver(NoopFirewallDriver):
+class FakeFirewallDriver(firewall.NoopFirewallDriver):
def __init__(self):
super(FakeFirewallDriver, self).__init__()
diff --git a/nova/virt/baremetal/ipmi.py b/nova/virt/baremetal/ipmi.py
index 97c158727..5d4a87625 100644
--- a/nova/virt/baremetal/ipmi.py
+++ b/nova/virt/baremetal/ipmi.py
@@ -25,7 +25,7 @@ import os
import stat
import tempfile
-from nova.exception import InvalidParameterValue
+from nova import exception
from nova.openstack.common import cfg
from nova.openstack.common import log as logging
from nova import paths
@@ -104,13 +104,17 @@ class IPMI(base.PowerManager):
self.port = node['terminal_port']
if self.node_id == None:
- raise InvalidParameterValue(_("Node id not supplied to IPMI"))
+ raise exception.InvalidParameterValue(_("Node id not supplied "
+ "to IPMI"))
if self.address == None:
- raise InvalidParameterValue(_("Address not supplied to IPMI"))
+ raise exception.InvalidParameterValue(_("Address not supplied "
+ "to IPMI"))
if self.user == None:
- raise InvalidParameterValue(_("User not supplied to IPMI"))
+ raise exception.InvalidParameterValue(_("User not supplied "
+ "to IPMI"))
if self.password == None:
- raise InvalidParameterValue(_("Password not supplied to IPMI"))
+ raise exception.InvalidParameterValue(_("Password not supplied "
+ "to IPMI"))
def _exec_ipmitool(self, command):
args = ['ipmitool',
@@ -126,7 +130,7 @@ class IPMI(base.PowerManager):
args.append(pwfile)
args.extend(command.split(" "))
out, err = utils.execute(*args, attempts=3)
- LOG.debug(_("ipmitool stdout: '%(out)s', stderr: '%(err)%s'"),
+ LOG.debug(_("ipmitool stdout: '%(out)s', stderr: '%(err)s'"),
locals())
return out, err
finally:
diff --git a/nova/virt/baremetal/pxe.py b/nova/virt/baremetal/pxe.py
index 0daac1d46..91de43dd1 100644
--- a/nova/virt/baremetal/pxe.py
+++ b/nova/virt/baremetal/pxe.py
@@ -67,7 +67,8 @@ CHEETAH = None
def _get_cheetah():
global CHEETAH
if CHEETAH is None:
- from Cheetah.Template import Template as CHEETAH
+ from Cheetah import Template
+ CHEETAH = Template.Template
return CHEETAH
diff --git a/nova/virt/disk/api.py b/nova/virt/disk/api.py
index dd255bac1..3d7d0f516 100644
--- a/nova/virt/disk/api.py
+++ b/nova/virt/disk/api.py
@@ -96,9 +96,13 @@ def mkfs(os_type, fs_label, target):
utils.execute(*mkfs_command.split())
-def resize2fs(image, check_exit_code=False):
- utils.execute('e2fsck', '-fp', image, check_exit_code=check_exit_code)
- utils.execute('resize2fs', image, check_exit_code=check_exit_code)
+def resize2fs(image, check_exit_code=False, run_as_root=False):
+ utils.execute('e2fsck', '-fp', image,
+ check_exit_code=check_exit_code,
+ run_as_root=run_as_root)
+ utils.execute('resize2fs', image,
+ check_exit_code=check_exit_code,
+ run_as_root=run_as_root)
def get_disk_size(path):
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index aa0439e74..747b60714 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -49,6 +49,17 @@ CONF.register_opts(driver_opts)
LOG = logging.getLogger(__name__)
+def driver_dict_from_config(named_driver_config, *args, **kwargs):
+ driver_registry = dict()
+
+ for driver_str in named_driver_config:
+ driver_type, _sep, driver = driver_str.partition('=')
+ driver_class = importutils.import_class(driver)
+ driver_registry[driver_type] = driver_class(*args, **kwargs)
+
+ return driver_registry
+
+
def block_device_info_get_root(block_device_info):
block_device_info = block_device_info or {}
return block_device_info.get('root_device_name')
@@ -447,7 +458,8 @@ class ComputeDriver(object):
def post_live_migration_at_destination(self, ctxt, instance_ref,
network_info,
- block_migration=False):
+ block_migration=False,
+ block_device_info=None):
"""Post operation of live migration at destination host.
:param ctxt: security context
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 338d1dec1..04eeded72 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -166,6 +166,12 @@ class FakeDriver(driver.ComputeDriver):
block_device_info=None):
pass
+ def post_live_migration_at_destination(self, context, instance,
+ network_info,
+ block_migration=False,
+ block_device_info=None):
+ pass
+
def power_off(self, instance):
pass
diff --git a/nova/virt/firewall.py b/nova/virt/firewall.py
index bbc6034bd..ad38cd9a4 100644
--- a/nova/virt/firewall.py
+++ b/nova/virt/firewall.py
@@ -146,7 +146,7 @@ class IptablesFirewallDriver(FirewallDriver):
self.iptables = linux_net.iptables_manager
self.instances = {}
self.network_infos = {}
- self.basicly_filtered = False
+ self.basically_filtered = False
self.iptables.ipv4['filter'].add_chain('sg-fallback')
self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP')
diff --git a/nova/virt/hyperv/driver.py b/nova/virt/hyperv/driver.py
index 799ef7172..9316b2598 100644
--- a/nova/virt/hyperv/driver.py
+++ b/nova/virt/hyperv/driver.py
@@ -164,7 +164,7 @@ class HyperVDriver(driver.ComputeDriver):
block_device_info, network_info)
def post_live_migration_at_destination(self, ctxt, instance_ref,
- network_info, block_migration):
+ network_info, block_migration, block_device_info=None):
self._livemigrationops.post_live_migration_at_destination(ctxt,
instance_ref, network_info, block_migration)
diff --git a/nova/virt/hyperv/vif.py b/nova/virt/hyperv/vif.py
index a898d3ac2..e01006eaa 100644
--- a/nova/virt/hyperv/vif.py
+++ b/nova/virt/hyperv/vif.py
@@ -15,6 +15,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+import abc
import sys
import uuid
@@ -22,11 +23,12 @@ import uuid
if sys.platform == 'win32':
import wmi
-from abc import abstractmethod
+
from nova.openstack.common import cfg
from nova.openstack.common import log as logging
from nova.virt.hyperv import vmutils
+
hyperv_opts = [
cfg.StrOpt('vswitch_name',
default=None,
@@ -42,11 +44,11 @@ LOG = logging.getLogger(__name__)
class HyperVBaseVIFDriver(object):
- @abstractmethod
+ @abc.abstractmethod
def plug(self, instance, vif):
pass
- @abstractmethod
+ @abc.abstractmethod
def unplug(self, instance, vif):
pass
diff --git a/nova/virt/hyperv/volumeops.py b/nova/virt/hyperv/volumeops.py
index 192d6834c..b69cf7bf1 100644
--- a/nova/virt/hyperv/volumeops.py
+++ b/nova/virt/hyperv/volumeops.py
@@ -183,7 +183,7 @@ class VolumeOps(baseops.BaseOps):
"SELECT * FROM Msvm_ResourceAllocationSettingData \
WHERE ResourceSubType LIKE 'Microsoft Physical Disk Drive'\
AND Parent = '" + scsi_controller.path_() + "'")
- #Slots starts from 0, so the lenght of the disks gives us the free slot
+ #Slots starts from 0, so the length of the disks gives us the free slot
return len(volumes)
def detach_volume(self, connection_info, instance_name, mountpoint):
diff --git a/nova/virt/images.py b/nova/virt/images.py
index 9788a2b42..018badecf 100644
--- a/nova/virt/images.py
+++ b/nova/virt/images.py
@@ -123,7 +123,7 @@ class QemuImgInfo(object):
if len(line_pieces) != 6:
break
else:
- # Check against this pattern occuring in the final position
+ # Check against this pattern in the final position
# "%02d:%02d:%02d.%03d"
date_pieces = line_pieces[5].split(":")
if len(date_pieces) != 3:
diff --git a/nova/virt/libvirt/__init__.py b/nova/virt/libvirt/__init__.py
index 00f4fd6b0..535d6c729 100644
--- a/nova/virt/libvirt/__init__.py
+++ b/nova/virt/libvirt/__init__.py
@@ -14,4 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nova.virt.libvirt.driver import LibvirtDriver
+from nova.virt.libvirt import driver
+
+LibvirtDriver = driver.LibvirtDriver
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 7c8abc6ec..46b63d1c6 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -284,11 +284,10 @@ class LibvirtDriver(driver.ComputeDriver):
self.virtapi,
get_connection=self._get_connection)
self.vif_driver = importutils.import_object(CONF.libvirt_vif_driver)
- self.volume_drivers = {}
- for driver_str in CONF.libvirt_volume_drivers:
- driver_type, _sep, driver = driver_str.partition('=')
- driver_class = importutils.import_class(driver)
- self.volume_drivers[driver_type] = driver_class(self)
+
+ self.volume_drivers = driver.driver_dict_from_config(
+ CONF.libvirt_volume_drivers, self)
+
self._host_state = None
disk_prefix_map = {"lxc": "", "uml": "ubd", "xen": "sd"}
@@ -427,10 +426,10 @@ class LibvirtDriver(driver.ComputeDriver):
"""Efficient override of base instance_exists method."""
return self._conn.numOfDomains()
- def instance_exists(self, instance_id):
+ def instance_exists(self, instance_name):
"""Efficient override of base instance_exists method."""
try:
- self._lookup_by_name(instance_id)
+ self._lookup_by_name(instance_name)
return True
except exception.NovaException:
return False
@@ -707,7 +706,8 @@ class LibvirtDriver(driver.ComputeDriver):
if child.get('dev') == device:
return etree.tostring(node)
- def _get_domain_xml(self, instance, network_info, block_device_info=None):
+ def _get_existing_domain_xml(self, instance, network_info,
+ block_device_info=None):
try:
virt_dom = self._lookup_by_name(instance['name'])
xml = virt_dom.XMLDesc(0)
@@ -808,7 +808,7 @@ class LibvirtDriver(driver.ComputeDriver):
# NOTE(dkang): managedSave does not work for LXC
if CONF.libvirt_type != 'lxc':
- if state == power_state.RUNNING:
+ if state == power_state.RUNNING or state == power_state.PAUSED:
virt_dom.managedSave(0)
# Make the snapshot
@@ -832,6 +832,9 @@ class LibvirtDriver(driver.ComputeDriver):
if CONF.libvirt_type != 'lxc':
if state == power_state.RUNNING:
self._create_domain(domain=virt_dom)
+ elif state == power_state.PAUSED:
+ self._create_domain(domain=virt_dom,
+ launch_flags=libvirt.VIR_DOMAIN_START_PAUSED)
# Upload that image to the image service
@@ -855,8 +858,7 @@ class LibvirtDriver(driver.ComputeDriver):
else:
LOG.warn(_("Failed to soft reboot instance."),
instance=instance)
- return self._hard_reboot(instance, network_info,
- block_device_info=block_device_info)
+ return self._hard_reboot(instance, network_info, block_device_info)
def _soft_reboot(self, instance):
"""Attempt to shutdown and restart the instance gracefully.
@@ -872,6 +874,7 @@ class LibvirtDriver(driver.ComputeDriver):
dom = self._lookup_by_name(instance["name"])
(state, _max_mem, _mem, _cpus, _t) = dom.info()
state = LIBVIRT_POWER_STATE[state]
+ old_domid = dom.ID()
# NOTE(vish): This check allows us to reboot an instance that
# is already shutdown.
if state == power_state.RUNNING:
@@ -880,8 +883,10 @@ class LibvirtDriver(driver.ComputeDriver):
# FLAG defines depending on how long the get_info
# call takes to return.
for x in xrange(CONF.libvirt_wait_soft_reboot_seconds):
+ dom = self._lookup_by_name(instance["name"])
(state, _max_mem, _mem, _cpus, _t) = dom.info()
state = LIBVIRT_POWER_STATE[state]
+ new_domid = dom.ID()
if state in [power_state.SHUTDOWN,
power_state.CRASHED]:
@@ -892,11 +897,14 @@ class LibvirtDriver(driver.ComputeDriver):
instance)
timer.start(interval=0.5).wait()
return True
+ elif old_domid != new_domid:
+ LOG.info(_("Instance may have been rebooted during soft "
+ "reboot, so return now."), instance=instance)
+ return True
greenthread.sleep(1)
return False
- def _hard_reboot(self, instance, network_info, xml=None,
- block_device_info=None):
+ def _hard_reboot(self, instance, network_info, block_device_info=None):
"""Reboot a virtual machine, given an instance reference.
Performs a Libvirt reset (if supported) on the domain.
@@ -909,11 +917,10 @@ class LibvirtDriver(driver.ComputeDriver):
existing domain.
"""
- if not xml:
- xml = self._get_domain_xml(instance, network_info,
- block_device_info)
-
self._destroy(instance)
+ xml = self.to_xml(instance, network_info,
+ block_device_info=block_device_info,
+ write_to_disk=True)
self._create_domain_and_network(xml, instance, network_info,
block_device_info)
@@ -958,16 +965,31 @@ class LibvirtDriver(driver.ComputeDriver):
def resume(self, instance, network_info, block_device_info=None):
"""resume the specified instance."""
- xml = self._get_domain_xml(instance, network_info, block_device_info)
+ xml = self._get_existing_domain_xml(instance, network_info,
+ block_device_info)
self._create_domain_and_network(xml, instance, network_info,
block_device_info)
def resume_state_on_host_boot(self, context, instance, network_info,
block_device_info=None):
"""resume guest state when a host is booted."""
- xml = self._get_domain_xml(instance, network_info, block_device_info)
- self._create_domain_and_network(xml, instance, network_info,
- block_device_info)
+ # Check if the instance is running already and avoid doing
+ # anything if it is.
+ if self.instance_exists(instance['name']):
+ domain = self._lookup_by_name(instance['name'])
+ state = LIBVIRT_POWER_STATE[domain.info()[0]]
+
+ ignored_states = (power_state.RUNNING,
+ power_state.SUSPENDED,
+ power_state.PAUSED)
+
+ if state in ignored_states:
+ return
+
+ # Instance is not up and could be in an unknown state.
+ # Be as absolute as possible about getting it back into
+ # a known and running state.
+ self._hard_reboot(instance, network_info, block_device_info)
def rescue(self, context, instance, network_info, image_meta,
rescue_password):
@@ -980,7 +1002,7 @@ class LibvirtDriver(driver.ComputeDriver):
"""
instance_dir = libvirt_utils.get_instance_path(instance)
- unrescue_xml = self._get_domain_xml(instance, network_info)
+ unrescue_xml = self._get_existing_domain_xml(instance, network_info)
unrescue_xml_path = os.path.join(instance_dir, 'unrescue.xml')
libvirt_utils.write_to_file(unrescue_xml_path, unrescue_xml)
@@ -1854,11 +1876,18 @@ class LibvirtDriver(driver.ComputeDriver):
return guest
def to_xml(self, instance, network_info, image_meta=None, rescue=None,
- block_device_info=None):
+ block_device_info=None, write_to_disk=False):
LOG.debug(_('Starting toXML method'), instance=instance)
conf = self.get_guest_config(instance, network_info, image_meta,
rescue, block_device_info)
xml = conf.to_xml()
+
+ if write_to_disk:
+ instance_dir = os.path.join(CONF.instances_path,
+ instance["name"])
+ xml_path = os.path.join(instance_dir, 'libvirt.xml')
+ libvirt_utils.write_to_file(xml_path, xml)
+
LOG.debug(_('Finished toXML method'), instance=instance)
return xml
@@ -2760,28 +2789,24 @@ class LibvirtDriver(driver.ComputeDriver):
def post_live_migration_at_destination(self, ctxt,
instance_ref,
network_info,
- block_migration):
+ block_migration,
+ block_device_info=None):
"""Post operation of live migration at destination host.
:param ctxt: security context
:param instance_ref:
nova.db.sqlalchemy.models.Instance object
instance object that is migrated.
- :param network_info: instance network infomation
+ :param network_info: instance network information
:param block_migration: if true, post operation of block_migraiton.
"""
# Define migrated instance, otherwise, suspend/destroy does not work.
dom_list = self._conn.listDefinedDomains()
if instance_ref["name"] not in dom_list:
- instance_dir = libvirt_utils.get_instance_path(instance_ref)
- xml_path = os.path.join(instance_dir, 'libvirt.xml')
# In case of block migration, destination does not have
# libvirt.xml
- if not os.path.isfile(xml_path):
- xml = self.to_xml(instance_ref, network_info=network_info)
- f = open(os.path.join(instance_dir, 'libvirt.xml'), 'w+')
- f.write(xml)
- f.close()
+ self.to_xml(instance_ref, network_info, block_device_info,
+ write_to_disk=True)
# libvirt.xml should be made by to_xml(), but libvirt
# does not accept to_xml() result, since uuid is not
# included in to_xml() result.
@@ -2807,7 +2832,15 @@ class LibvirtDriver(driver.ComputeDriver):
disk_info = []
virt_dom = self._lookup_by_name(instance_name)
- xml = virt_dom.XMLDesc(0)
+ try:
+ xml = virt_dom.XMLDesc(0)
+ except libvirt.libvirtError as ex:
+ error_code = ex.get_error_code()
+ msg = _("Error from libvirt while getting description of "
+ "%(instance_name)s: [Error Code %(error_code)s] "
+ "%(ex)s") % locals()
+ LOG.warn(msg)
+ raise exception.InstanceNotFound(instance_id=instance_name)
doc = etree.fromstring(xml)
disk_nodes = doc.findall('.//devices/disk')
path_nodes = doc.findall('.//devices/disk/source')
diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py
index c47056ff2..3323b8f1d 100644
--- a/nova/virt/libvirt/firewall.py
+++ b/nova/virt/libvirt/firewall.py
@@ -228,11 +228,11 @@ class IptablesFirewallDriver(base_firewall.IptablesFirewallDriver):
def setup_basic_filtering(self, instance, network_info):
"""Set up provider rules and basic NWFilter."""
self.nwfilter.setup_basic_filtering(instance, network_info)
- if not self.basicly_filtered:
+ if not self.basically_filtered:
LOG.debug(_('iptables firewall: Setup Basic Filtering'),
instance=instance)
self.refresh_provider_fw_rules()
- self.basicly_filtered = True
+ self.basically_filtered = True
def apply_instance_filter(self, instance, network_info):
"""No-op. Everything is done in prepare_instance_filter."""
diff --git a/nova/virt/libvirt/imagebackend.py b/nova/virt/libvirt/imagebackend.py
index d272e408c..0815c142f 100644
--- a/nova/virt/libvirt/imagebackend.py
+++ b/nova/virt/libvirt/imagebackend.py
@@ -228,7 +228,7 @@ class Lvm(Image):
cmd = ('dd', 'if=%s' % base, 'of=%s' % self.path, 'bs=4M')
utils.execute(*cmd, run_as_root=True)
if resize:
- disk.resize2fs(self.path)
+ disk.resize2fs(self.path, run_as_root=True)
generated = 'ephemeral_size' in kwargs
diff --git a/nova/virt/libvirt/imagecache.py b/nova/virt/libvirt/imagecache.py
index 50fac9bb4..8f677b482 100644
--- a/nova/virt/libvirt/imagecache.py
+++ b/nova/virt/libvirt/imagecache.py
@@ -77,7 +77,7 @@ CONF.import_opt('instances_path', 'nova.compute.manager')
def get_info_filename(base_path):
- """Construct a filename for storing addtional information about a base
+ """Construct a filename for storing additional information about a base
image.
Returns a filename.
diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py
index 83d43a6db..d90a5e295 100644
--- a/nova/virt/libvirt/vif.py
+++ b/nova/virt/libvirt/vif.py
@@ -150,6 +150,9 @@ class LibvirtOpenVswitchDriver(LibvirtBaseVIFDriver):
def get_bridge_name(self, network):
return network.get('bridge') or CONF.libvirt_ovs_bridge
+ def get_ovs_interfaceid(self, mapping):
+ return mapping.get('ovs_interfaceid') or mapping['vif_uuid']
+
def get_config(self, instance, network, mapping):
dev = self.get_vif_devname(mapping)
@@ -162,55 +165,26 @@ class LibvirtOpenVswitchDriver(LibvirtBaseVIFDriver):
return conf
- def create_ovs_vif_port(self, bridge, dev, iface_id, mac, instance_id):
- utils.execute('ovs-vsctl', '--', '--may-exist', 'add-port',
- bridge, dev,
- '--', 'set', 'Interface', dev,
- 'external-ids:iface-id=%s' % iface_id,
- 'external-ids:iface-status=active',
- 'external-ids:attached-mac=%s' % mac,
- 'external-ids:vm-uuid=%s' % instance_id,
- run_as_root=True)
-
- def delete_ovs_vif_port(self, bridge, dev):
- utils.execute('ovs-vsctl', 'del-port', bridge, dev,
- run_as_root=True)
- utils.execute('ip', 'link', 'delete', dev, run_as_root=True)
-
def plug(self, instance, vif):
network, mapping = vif
- iface_id = mapping['vif_uuid']
+ iface_id = self.get_ovs_interfaceid(mapping)
dev = self.get_vif_devname(mapping)
- if not linux_net.device_exists(dev):
- # Older version of the command 'ip' from the iproute2 package
- # don't have support for the tuntap option (lp:882568). If it
- # turns out we're on an old version we work around this by using
- # tunctl.
- try:
- # First, try with 'ip'
- utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap',
- run_as_root=True)
- except exception.ProcessExecutionError:
- # Second option: tunctl
- 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(self.get_bridge_name(network),
- dev, iface_id, mapping['mac'],
- instance['uuid'])
+ linux_net.create_tap_dev(dev)
+ linux_net.create_ovs_vif_port(self.get_bridge_name(network),
+ dev, iface_id, mapping['mac'],
+ instance['uuid'])
def unplug(self, instance, vif):
"""Unplug the VIF by deleting the port from the bridge."""
try:
network, mapping = vif
- self.delete_ovs_vif_port(self.get_bridge_name(network),
- self.get_vif_devname(mapping))
+ linux_net.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)
-class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
- LibvirtOpenVswitchDriver):
+class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver):
"""VIF driver that uses OVS + Linux Bridge for iptables compatibility.
Enables the use of OVS-based Quantum plugins while at the same
@@ -229,6 +203,9 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
def get_bridge_name(self, network):
return network.get('bridge') or CONF.libvirt_ovs_bridge
+ def get_ovs_interfaceid(self, mapping):
+ return mapping.get('ovs_interfaceid') or mapping['vif_uuid']
+
def get_config(self, instance, network, mapping):
br_name = self.get_br_name(mapping['vif_uuid'])
network['bridge'] = br_name
@@ -247,9 +224,9 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
"""
network, mapping = vif
- iface_id = mapping['vif_uuid']
- br_name = self.get_br_name(iface_id)
- v1_name, v2_name = self.get_veth_pair_names(iface_id)
+ iface_id = self.get_ovs_interfaceid(mapping)
+ br_name = self.get_br_name(mapping['vif_uuid'])
+ v1_name, v2_name = self.get_veth_pair_names(mapping['vif_uuid'])
if not linux_net.device_exists(br_name):
utils.execute('brctl', 'addbr', br_name, run_as_root=True)
@@ -258,9 +235,9 @@ 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(self.get_bridge_name(network),
- v2_name, iface_id, mapping['mac'],
- instance['uuid'])
+ linux_net.create_ovs_vif_port(self.get_bridge_name(network),
+ v2_name, iface_id, mapping['mac'],
+ instance['uuid'])
def unplug(self, instance, vif):
"""UnPlug using hybrid strategy
@@ -270,16 +247,16 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver,
"""
try:
network, mapping = vif
- iface_id = mapping['vif_uuid']
- br_name = self.get_br_name(iface_id)
- v1_name, v2_name = self.get_veth_pair_names(iface_id)
+ br_name = self.get_br_name(mapping['vif_uuid'])
+ v1_name, v2_name = self.get_veth_pair_names(mapping['vif_uuid'])
utils.execute('brctl', 'delif', br_name, v1_name, run_as_root=True)
utils.execute('ip', 'link', 'set', br_name, 'down',
run_as_root=True)
utils.execute('brctl', 'delbr', br_name, run_as_root=True)
- self.delete_ovs_vif_port(self.get_bridge_name(network), v2_name)
+ linux_net.delete_ovs_vif_port(self.get_bridge_name(network),
+ v2_name)
except exception.ProcessExecutionError:
LOG.exception(_("Failed while unplugging vif"), instance=instance)
@@ -291,6 +268,9 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver):
def get_bridge_name(self, network):
return network.get('bridge') or CONF.libvirt_ovs_bridge
+ def get_ovs_interfaceid(self, mapping):
+ return mapping.get('ovs_interfaceid') or mapping['vif_uuid']
+
def get_config(self, instance, network, mapping):
"""Pass data required to create OVS virtual port element."""
conf = super(LibvirtOpenVswitchVirtualPortDriver,
@@ -299,7 +279,8 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver):
mapping)
designer.set_vif_host_backend_ovs_config(
- conf, self.get_bridge_name(network), mapping['vif_uuid'],
+ conf, self.get_bridge_name(network),
+ self.get_ovs_interfaceid(mapping),
self.get_vif_devname(mapping))
return conf
diff --git a/nova/virt/powervm/__init__.py b/nova/virt/powervm/__init__.py
index 83bbcd289..1b63f8310 100644
--- a/nova/virt/powervm/__init__.py
+++ b/nova/virt/powervm/__init__.py
@@ -26,4 +26,6 @@ refer to the IBM Redbook[1] publication.
May 2011. <http://www.redbooks.ibm.com/abstracts/sg247940.html>
"""
-from nova.virt.powervm.driver import PowerVMDriver
+from nova.virt.powervm import driver
+
+PowerVMDriver = driver.PowerVMDriver
diff --git a/nova/virt/powervm/operator.py b/nova/virt/powervm/operator.py
index b25a96159..87da30a14 100644
--- a/nova/virt/powervm/operator.py
+++ b/nova/virt/powervm/operator.py
@@ -55,7 +55,7 @@ def get_powervm_disk_adapter():
class PowerVMOperator(object):
"""PowerVM main operator.
- The PowerVMOperator is intented to wrapper all operations
+ The PowerVMOperator is intended to wrap all operations
from the driver and handle either IVM or HMC managed systems.
"""
@@ -372,7 +372,7 @@ class BaseOperator(object):
"""
cmd = self.command.lssyscfg('-r %s --filter "lpar_names=%s"'
% (resource_type, instance_name))
- output = self.run_command(cmd)
+ output = self.run_vios_command(cmd)
if not output:
return None
lpar = LPAR.load_from_conf_data(output[0])
@@ -383,7 +383,8 @@ class BaseOperator(object):
:returns: list -- list with instances names.
"""
- lpar_names = self.run_command(self.command.lssyscfg('-r lpar -F name'))
+ lpar_names = self.run_vios_command(self.command.lssyscfg(
+ '-r lpar -F name'))
if not lpar_names:
return []
return lpar_names
@@ -394,14 +395,15 @@ class BaseOperator(object):
:param lpar: LPAR object
"""
conf_data = lpar.to_string()
- self.run_command(self.command.mksyscfg('-r lpar -i "%s"' % conf_data))
+ self.run_vios_command(self.command.mksyscfg('-r lpar -i "%s"' %
+ conf_data))
def start_lpar(self, instance_name):
"""Start a LPAR instance.
:param instance_name: LPAR instance name
"""
- self.run_command(self.command.chsysstate('-r lpar -o on -n %s'
+ self.run_vios_command(self.command.chsysstate('-r lpar -o on -n %s'
% instance_name))
def stop_lpar(self, instance_name, timeout=30):
@@ -413,7 +415,7 @@ class BaseOperator(object):
"""
cmd = self.command.chsysstate('-r lpar -o shutdown --immed -n %s' %
instance_name)
- self.run_command(cmd)
+ self.run_vios_command(cmd)
# poll instance until stopped or raise exception
lpar_obj = self.get_lpar(instance_name)
@@ -435,7 +437,7 @@ class BaseOperator(object):
:param instance_name: LPAR instance name
"""
- self.run_command(self.command.rmsyscfg('-r lpar -n %s'
+ self.run_vios_command(self.command.rmsyscfg('-r lpar -n %s'
% instance_name))
def get_vhost_by_instance_id(self, instance_id):
@@ -446,7 +448,7 @@ class BaseOperator(object):
"""
instance_hex_id = '%#010x' % int(instance_id)
cmd = self.command.lsmap('-all -field clientid svsa -fmt :')
- output = self.run_command(cmd)
+ output = self.run_vios_command(cmd)
vhosts = dict(item.split(':') for item in list(output))
if instance_hex_id in vhosts:
@@ -463,10 +465,10 @@ class BaseOperator(object):
:returns: id of the virtual ethernet adapter.
"""
cmd = self.command.lsmap('-all -net -field sea -fmt :')
- output = self.run_command(cmd)
+ output = self.run_vios_command(cmd)
sea = output[0]
cmd = self.command.lsdev('-dev %s -attr pvid' % sea)
- output = self.run_command(cmd)
+ output = self.run_vios_command(cmd)
# Returned output looks like this: ['value', '', '1']
if output:
return output[2]
@@ -478,7 +480,7 @@ class BaseOperator(object):
:returns: string -- hostname
"""
- output = self.run_command(self.command.hostname())
+ output = self.run_vios_command(self.command.hostname())
return output[0]
def get_disk_name_by_vhost(self, vhost):
@@ -488,7 +490,7 @@ class BaseOperator(object):
:returns: string -- disk name
"""
cmd = self.command.lsmap('-vadapter %s -field backing -fmt :' % vhost)
- output = self.run_command(cmd)
+ output = self.run_vios_command(cmd)
if output:
return output[0]
@@ -501,7 +503,7 @@ class BaseOperator(object):
:param vhost: the vhost name
"""
cmd = self.command.mkvdev('-vdev %s -vadapter %s') % (disk, vhost)
- self.run_command(cmd)
+ self.run_vios_command(cmd)
def get_memory_info(self):
"""Get memory info.
@@ -510,7 +512,7 @@ class BaseOperator(object):
"""
cmd = self.command.lshwres(
'-r mem --level sys -F configurable_sys_mem,curr_avail_sys_mem')
- output = self.run_command(cmd)
+ output = self.run_vios_command(cmd)
total_mem, avail_mem = output[0].split(',')
return {'total_mem': int(total_mem),
'avail_mem': int(avail_mem)}
@@ -523,7 +525,7 @@ class BaseOperator(object):
cmd = self.command.lshwres(
'-r proc --level sys -F '
'configurable_sys_proc_units,curr_avail_sys_proc_units')
- output = self.run_command(cmd)
+ output = self.run_vios_command(cmd)
total_procs, avail_procs = output[0].split(',')
return {'total_procs': float(total_procs),
'avail_procs': float(avail_procs)}
@@ -533,12 +535,12 @@ class BaseOperator(object):
:returns: tuple - disk info (disk_total, disk_used, disk_avail)
"""
- vgs = self.run_command(self.command.lsvg())
+ vgs = self.run_vios_command(self.command.lsvg())
(disk_total, disk_used, disk_avail) = [0, 0, 0]
for vg in vgs:
cmd = self.command.lsvg('%s -field totalpps usedpps freepps -fmt :'
% vg)
- output = self.run_command(cmd)
+ output = self.run_vios_command(cmd)
# Output example:
# 1271 (10168 megabytes):0 (0 megabytes):1271 (10168 megabytes)
(d_total, d_used, d_avail) = re.findall(r'(\d+) megabytes',
@@ -551,7 +553,7 @@ class BaseOperator(object):
'disk_used': disk_used,
'disk_avail': disk_avail}
- def run_command(self, cmd, check_exit_code=True):
+ def run_vios_command(self, cmd, check_exit_code=True):
"""Run a remote command using an active ssh connection.
:param command: String with the command to run.
@@ -561,7 +563,7 @@ class BaseOperator(object):
check_exit_code=check_exit_code)
return stdout.strip().splitlines()
- def run_command_as_root(self, command, check_exit_code=True):
+ def run_vios_command_as_root(self, command, check_exit_code=True):
"""Run a remote command as root using an active ssh connection.
:param command: List of commands.
diff --git a/nova/virt/vmwareapi/__init__.py b/nova/virt/vmwareapi/__init__.py
index 66e7d9b02..37d816f8c 100644
--- a/nova/virt/vmwareapi/__init__.py
+++ b/nova/virt/vmwareapi/__init__.py
@@ -18,4 +18,6 @@
:mod:`vmwareapi` -- Nova support for VMware ESX/ESXi Server through VMware API.
"""
# NOTE(sdague) for nicer compute_driver specification
-from nova.virt.vmwareapi.driver import VMwareESXDriver
+from nova.virt.vmwareapi import driver
+
+VMwareESXDriver = driver.VMwareESXDriver
diff --git a/nova/virt/vmwareapi/driver.py b/nova/virt/vmwareapi/driver.py
index 986c4ef28..4000f1f9c 100644
--- a/nova/virt/vmwareapi/driver.py
+++ b/nova/virt/vmwareapi/driver.py
@@ -45,6 +45,7 @@ from nova.virt.vmwareapi import error_util
from nova.virt.vmwareapi import vim
from nova.virt.vmwareapi import vim_util
from nova.virt.vmwareapi import vmops
+from nova.virt.vmwareapi import volumeops
LOG = logging.getLogger(__name__)
@@ -52,7 +53,7 @@ LOG = logging.getLogger(__name__)
vmwareapi_opts = [
cfg.StrOpt('vmwareapi_host_ip',
default=None,
- help='URL for connection to VMware ESX host.Required if '
+ help='URL for connection to VMware ESX host. Required if '
'compute_driver is vmwareapi.VMwareESXDriver.'),
cfg.StrOpt('vmwareapi_host_username',
default=None,
@@ -109,9 +110,10 @@ class VMwareESXDriver(driver.ComputeDriver):
"and vmwareapi_host_password to use"
"compute_driver=vmwareapi.VMwareESXDriver"))
- session = VMwareAPISession(host_ip, host_username, host_password,
+ self._session = VMwareAPISession(host_ip, host_username, host_password,
api_retry_count, scheme=scheme)
- self._vmops = vmops.VMwareVMOps(session)
+ self._volumeops = volumeops.VMwareVolumeOps(self._session)
+ self._vmops = vmops.VMwareVMOps(self._session)
def init_host(self, host):
"""Do the initialization that needs to be done."""
@@ -172,23 +174,21 @@ class VMwareESXDriver(driver.ComputeDriver):
"""Return snapshot of console."""
return self._vmops.get_console_output(instance)
- def get_volume_connector(self, _instance):
+ def get_volume_connector(self, instance):
"""Return volume connector information."""
- # TODO(vish): When volume attaching is supported, return the
- # proper initiator iqn and host.
- return {
- 'ip': CONF.vmwareapi_host_ip,
- 'initiator': None,
- 'host': None
- }
+ return self._volumeops.get_volume_connector(instance)
def attach_volume(self, connection_info, instance, mountpoint):
"""Attach volume storage to VM instance."""
- pass
+ return self._volumeops.attach_volume(connection_info,
+ instance,
+ mountpoint)
def detach_volume(self, connection_info, instance, mountpoint):
"""Detach volume storage to VM instance."""
- pass
+ return self._volumeops.detach_volume(connection_info,
+ instance,
+ mountpoint)
def get_console_pool_info(self, console_type):
"""Get info about the host on which the VM resides."""
diff --git a/nova/virt/vmwareapi/fake.py b/nova/virt/vmwareapi/fake.py
index 3f5041c22..27e26526f 100644
--- a/nova/virt/vmwareapi/fake.py
+++ b/nova/virt/vmwareapi/fake.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2012 VMware, Inc.
# Copyright (c) 2011 Citrix Systems, Inc.
# Copyright 2011 OpenStack LLC.
#
@@ -140,16 +141,30 @@ class DataObject(object):
class VirtualDisk(DataObject):
"""
- Virtual Disk class. Does nothing special except setting
- __class__.__name__ to 'VirtualDisk'. Refer place where __class__.__name__
- is used in the code.
+ Virtual Disk class.
"""
- pass
+
+ def __init__(self):
+ super(VirtualDisk, self).__init__()
+ self.key = 0
+ self.unitNumber = 0
class VirtualDiskFlatVer2BackingInfo(DataObject):
"""VirtualDiskFlatVer2BackingInfo class."""
- pass
+
+ def __init__(self):
+ super(VirtualDiskFlatVer2BackingInfo, self).__init__()
+ self.thinProvisioned = False
+ self.eagerlyScrub = False
+
+
+class VirtualDiskRawDiskMappingVer1BackingInfo(DataObject):
+ """VirtualDiskRawDiskMappingVer1BackingInfo class."""
+
+ def __init__(self):
+ super(VirtualDiskRawDiskMappingVer1BackingInfo, self).__init__()
+ self.lunUuid = ""
class VirtualLsiLogicController(DataObject):
diff --git a/nova/virt/vmwareapi/io_util.py b/nova/virt/vmwareapi/io_util.py
index 999e7a085..6a50c4d6e 100644
--- a/nova/virt/vmwareapi/io_util.py
+++ b/nova/virt/vmwareapi/io_util.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2012 VMware, Inc.
# Copyright (c) 2011 Citrix Systems, Inc.
# Copyright 2011 OpenStack LLC.
#
@@ -57,6 +58,14 @@ class ThreadSafePipe(queue.LightQueue):
"""Put a data item in the pipe."""
self.put(data)
+ def seek(self, offset, whence=0):
+ """Set the file's current position at the offset."""
+ pass
+
+ def tell(self):
+ """Get size of the file to be read."""
+ return self.transfer_size
+
def close(self):
"""A place-holder to maintain consistency."""
pass
diff --git a/nova/virt/vmwareapi/network_util.py b/nova/virt/vmwareapi/network_util.py
index a3b20137d..d2bdad0c1 100644
--- a/nova/virt/vmwareapi/network_util.py
+++ b/nova/virt/vmwareapi/network_util.py
@@ -38,7 +38,7 @@ def get_network_with_the_name(session, network_name="vmnet0"):
vm_networks_ret = hostsystems[0].propSet[0].val
# Meaning there are no networks on the host. suds responds with a ""
# in the parent property field rather than a [] in the
- # ManagedObjectRefernce property field of the parent
+ # ManagedObjectReference property field of the parent
if not vm_networks_ret:
return None
vm_networks = vm_networks_ret.ManagedObjectReference
diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py
index e03b88804..381c47193 100644
--- a/nova/virt/vmwareapi/vm_util.py
+++ b/nova/virt/vmwareapi/vm_util.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2012 VMware, Inc.
# Copyright (c) 2011 Citrix Systems, Inc.
# Copyright 2011 OpenStack LLC.
#
@@ -18,6 +19,9 @@
The VMware API VM utility module to build SOAP object specs.
"""
+import copy
+from nova.virt.vmwareapi import vim_util
+
def build_datastore_path(datastore_name, path):
"""Build the datastore compliant path."""
@@ -42,7 +46,7 @@ def get_vm_create_spec(client_factory, instance, data_store_name,
vif_infos, os_type="otherGuest"):
"""Builds the VM Create spec."""
config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
- config_spec.name = instance.name
+ config_spec.name = instance['name']
config_spec.guestId = os_type
vm_file_info = client_factory.create('ns0:VirtualMachineFileInfo')
@@ -57,8 +61,8 @@ def get_vm_create_spec(client_factory, instance, data_store_name,
tools_info.beforeGuestReboot = True
config_spec.tools = tools_info
- config_spec.numCPUs = int(instance.vcpus)
- config_spec.memoryMB = int(instance.memory_mb)
+ config_spec.numCPUs = int(instance['vcpus'])
+ config_spec.memoryMB = int(instance['memory_mb'])
vif_spec_list = []
for vif_info in vif_infos:
@@ -71,9 +75,9 @@ def get_vm_create_spec(client_factory, instance, data_store_name,
return config_spec
-def create_controller_spec(client_factory, key):
+def create_controller_spec(client_factory, key, adapter_type="lsiLogic"):
"""
- Builds a Config Spec for the LSI Logic Controller's addition
+ Builds a Config Spec for the LSI or Bus Logic Controller's addition
which acts as the controller for the virtual hard disk to be attached
to the VM.
"""
@@ -81,11 +85,16 @@ def create_controller_spec(client_factory, key):
virtual_device_config = client_factory.create(
'ns0:VirtualDeviceConfigSpec')
virtual_device_config.operation = "add"
- virtual_lsi = client_factory.create('ns0:VirtualLsiLogicController')
- virtual_lsi.key = key
- virtual_lsi.busNumber = 0
- virtual_lsi.sharedBus = "noSharing"
- virtual_device_config.device = virtual_lsi
+ if adapter_type == "busLogic":
+ virtual_controller = client_factory.create(
+ 'ns0:VirtualBusLogicController')
+ else:
+ virtual_controller = client_factory.create(
+ 'ns0:VirtualLsiLogicController')
+ virtual_controller.key = key
+ virtual_controller.busNumber = 0
+ virtual_controller.sharedBus = "noSharing"
+ virtual_device_config.device = virtual_controller
return virtual_device_config
@@ -142,8 +151,15 @@ def create_network_spec(client_factory, vif_info):
return network_spec
-def get_vmdk_attach_config_spec(client_factory, disksize, file_path,
- adapter_type="lsiLogic"):
+def get_vmdk_attach_config_spec(client_factory,
+ adapter_type="lsiLogic",
+ disk_type="preallocated",
+ file_path=None,
+ disk_size=None,
+ linked_clone=False,
+ controller_key=None,
+ unit_number=None,
+ device_name=None):
"""Builds the vmdk attach config spec."""
config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
@@ -152,15 +168,19 @@ def get_vmdk_attach_config_spec(client_factory, disksize, file_path,
device_config_spec = []
# For IDE devices, there are these two default controllers created in the
# VM having keys 200 and 201
- if adapter_type == "ide":
- controller_key = 200
- else:
- controller_key = -101
- controller_spec = create_controller_spec(client_factory,
- controller_key)
- device_config_spec.append(controller_spec)
+ if controller_key is None:
+ if adapter_type == "ide":
+ controller_key = 200
+ else:
+ controller_key = -101
+ controller_spec = create_controller_spec(client_factory,
+ controller_key,
+ adapter_type)
+ device_config_spec.append(controller_spec)
virtual_device_config_spec = create_virtual_disk_spec(client_factory,
- disksize, controller_key, file_path)
+ controller_key, disk_type, file_path,
+ disk_size, linked_clone,
+ unit_number, device_name)
device_config_spec.append(virtual_device_config_spec)
@@ -168,20 +188,45 @@ def get_vmdk_attach_config_spec(client_factory, disksize, file_path,
return config_spec
-def get_vmdk_file_path_and_adapter_type(client_factory, hardware_devices):
+def get_vmdk_detach_config_spec(client_factory, device):
+ """Builds the vmdk detach config spec."""
+ config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
+
+ device_config_spec = []
+ virtual_device_config_spec = delete_virtual_disk_spec(client_factory,
+ device)
+
+ device_config_spec.append(virtual_device_config_spec)
+
+ config_spec.deviceChange = device_config_spec
+ return config_spec
+
+
+def get_vmdk_path_and_adapter_type(hardware_devices):
"""Gets the vmdk file path and the storage adapter type."""
if hardware_devices.__class__.__name__ == "ArrayOfVirtualDevice":
hardware_devices = hardware_devices.VirtualDevice
vmdk_file_path = None
vmdk_controler_key = None
+ disk_type = None
+ unit_number = 0
adapter_type_dict = {}
for device in hardware_devices:
- if (device.__class__.__name__ == "VirtualDisk" and
- device.backing.__class__.__name__ ==
- "VirtualDiskFlatVer2BackingInfo"):
- vmdk_file_path = device.backing.fileName
- vmdk_controler_key = device.controllerKey
+ if device.__class__.__name__ == "VirtualDisk":
+ if device.backing.__class__.__name__ == \
+ "VirtualDiskFlatVer2BackingInfo":
+ vmdk_file_path = device.backing.fileName
+ vmdk_controler_key = device.controllerKey
+ if getattr(device.backing, 'thinProvisioned', False):
+ disk_type = "thin"
+ else:
+ if getattr(device.backing, 'eagerlyScrub', False):
+ disk_type = "eagerZeroedThick"
+ else:
+ disk_type = "preallocated"
+ if device.unitNumber > unit_number:
+ unit_number = device.unitNumber
elif device.__class__.__name__ == "VirtualLsiLogicController":
adapter_type_dict[device.key] = "lsiLogic"
elif device.__class__.__name__ == "VirtualBusLogicController":
@@ -193,28 +238,59 @@ def get_vmdk_file_path_and_adapter_type(client_factory, hardware_devices):
adapter_type = adapter_type_dict.get(vmdk_controler_key, "")
- return vmdk_file_path, adapter_type
+ return (vmdk_file_path, vmdk_controler_key, adapter_type,
+ disk_type, unit_number)
+
+
+def get_rdm_disk(hardware_devices, uuid):
+ """Gets the RDM disk key."""
+ if hardware_devices.__class__.__name__ == "ArrayOfVirtualDevice":
+ hardware_devices = hardware_devices.VirtualDevice
+
+ for device in hardware_devices:
+ if (device.__class__.__name__ == "VirtualDisk" and
+ device.backing.__class__.__name__ ==
+ "VirtualDiskRawDiskMappingVer1BackingInfo" and
+ device.backing.lunUuid == uuid):
+ return device
-def get_copy_virtual_disk_spec(client_factory, adapter_type="lsilogic"):
+def get_copy_virtual_disk_spec(client_factory, adapter_type="lsilogic",
+ disk_type="preallocated"):
"""Builds the Virtual Disk copy spec."""
dest_spec = client_factory.create('ns0:VirtualDiskSpec')
dest_spec.adapterType = adapter_type
- dest_spec.diskType = "thick"
+ dest_spec.diskType = disk_type
return dest_spec
-def get_vmdk_create_spec(client_factory, size_in_kb, adapter_type="lsiLogic"):
+def get_vmdk_create_spec(client_factory, size_in_kb, adapter_type="lsiLogic",
+ disk_type="preallocated"):
"""Builds the virtual disk create spec."""
create_vmdk_spec = client_factory.create('ns0:FileBackedVirtualDiskSpec')
create_vmdk_spec.adapterType = adapter_type
- create_vmdk_spec.diskType = "thick"
+ create_vmdk_spec.diskType = disk_type
create_vmdk_spec.capacityKb = size_in_kb
return create_vmdk_spec
-def create_virtual_disk_spec(client_factory, disksize, controller_key,
- file_path=None):
+def get_rdm_create_spec(client_factory, device, adapter_type="lsiLogic",
+ disk_type="rdmp"):
+ """Builds the RDM virtual disk create spec."""
+ create_vmdk_spec = client_factory.create('ns0:DeviceBackedVirtualDiskSpec')
+ create_vmdk_spec.adapterType = adapter_type
+ create_vmdk_spec.diskType = disk_type
+ create_vmdk_spec.device = device
+ return create_vmdk_spec
+
+
+def create_virtual_disk_spec(client_factory, controller_key,
+ disk_type="preallocated",
+ file_path=None,
+ disk_size=None,
+ linked_clone=False,
+ unit_number=None,
+ device_name=None):
"""
Builds spec for the creation of a new/ attaching of an already existing
Virtual Disk to the VM.
@@ -222,26 +298,40 @@ def create_virtual_disk_spec(client_factory, disksize, controller_key,
virtual_device_config = client_factory.create(
'ns0:VirtualDeviceConfigSpec')
virtual_device_config.operation = "add"
- if file_path is None:
+ if (file_path is None) or linked_clone:
virtual_device_config.fileOperation = "create"
virtual_disk = client_factory.create('ns0:VirtualDisk')
- disk_file_backing = client_factory.create(
- 'ns0:VirtualDiskFlatVer2BackingInfo')
- disk_file_backing.diskMode = "persistent"
- disk_file_backing.thinProvisioned = False
- if file_path is not None:
- disk_file_backing.fileName = file_path
+ if disk_type == "rdm" or disk_type == "rdmp":
+ disk_file_backing = client_factory.create(
+ 'ns0:VirtualDiskRawDiskMappingVer1BackingInfo')
+ disk_file_backing.compatibilityMode = "virtualMode" \
+ if disk_type == "rdm" else "physicalMode"
+ disk_file_backing.diskMode = "independent_persistent"
+ disk_file_backing.deviceName = device_name or ""
else:
- disk_file_backing.fileName = ""
+ disk_file_backing = client_factory.create(
+ 'ns0:VirtualDiskFlatVer2BackingInfo')
+ disk_file_backing.diskMode = "persistent"
+ if disk_type == "thin":
+ disk_file_backing.thinProvisioned = True
+ else:
+ if disk_type == "eagerZeroedThick":
+ disk_file_backing.eagerlyScrub = True
+ disk_file_backing.fileName = file_path or ""
connectable_spec = client_factory.create('ns0:VirtualDeviceConnectInfo')
connectable_spec.startConnected = True
connectable_spec.allowGuestControl = False
connectable_spec.connected = True
- virtual_disk.backing = disk_file_backing
+ if not linked_clone:
+ virtual_disk.backing = disk_file_backing
+ else:
+ virtual_disk.backing = copy.copy(disk_file_backing)
+ virtual_disk.backing.fileName = ""
+ virtual_disk.backing.parent = disk_file_backing
virtual_disk.connectable = connectable_spec
# The Server assigns a Key to the device. Here we pass a -ve random key.
@@ -249,14 +339,27 @@ def create_virtual_disk_spec(client_factory, disksize, controller_key,
# want a clash with the key that server might associate with the device
virtual_disk.key = -100
virtual_disk.controllerKey = controller_key
- virtual_disk.unitNumber = 0
- virtual_disk.capacityInKB = disksize
+ virtual_disk.unitNumber = unit_number or 0
+ virtual_disk.capacityInKB = disk_size or 0
virtual_device_config.device = virtual_disk
return virtual_device_config
+def delete_virtual_disk_spec(client_factory, device):
+ """
+ Builds spec for the deletion of an already existing Virtual Disk from VM.
+ """
+ virtual_device_config = client_factory.create(
+ 'ns0:VirtualDeviceConfigSpec')
+ virtual_device_config.operation = "remove"
+ virtual_device_config.fileOperation = "destroy"
+ virtual_device_config.device = device
+
+ return virtual_device_config
+
+
def get_dummy_vm_create_spec(client_factory, name, data_store_name):
"""Builds the dummy VM create spec."""
config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
@@ -318,3 +421,13 @@ def get_add_vswitch_port_group_spec(client_factory, vswitch_name,
vswitch_port_group_spec.policy = policy
return vswitch_port_group_spec
+
+
+def get_vm_ref_from_name(session, vm_name):
+ """Get reference to the VM with the name specified."""
+ vms = session._call_method(vim_util, "get_objects",
+ "VirtualMachine", ["name"])
+ for vm in vms:
+ if vm.propSet[0].val == vm_name:
+ return vm.obj
+ return None
diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py
index 883e751a8..625d6290e 100644
--- a/nova/virt/vmwareapi/vmops.py
+++ b/nova/virt/vmwareapi/vmops.py
@@ -309,8 +309,8 @@ class VMwareVMOps(object):
"""
vmdk_attach_config_spec = vm_util.get_vmdk_attach_config_spec(
client_factory,
- vmdk_file_size_in_kb, uploaded_vmdk_path,
- adapter_type)
+ adapter_type, "preallocated",
+ uploaded_vmdk_path, vmdk_file_size_in_kb)
LOG.debug(_("Reconfiguring VM instance to attach the image disk"),
instance=instance)
reconfig_task = self._session._call_method(
@@ -361,19 +361,19 @@ class VMwareVMOps(object):
hardware_devices = self._session._call_method(vim_util,
"get_dynamic_property", vm_ref,
"VirtualMachine", "config.hardware.device")
- _vmdk_info = vm_util.get_vmdk_file_path_and_adapter_type(
- client_factory, hardware_devices)
- vmdk_file_path_before_snapshot, adapter_type = _vmdk_info
+ (vmdk_file_path_before_snapshot, controller_key, adapter_type,
+ disk_type, unit_number) = vm_util.get_vmdk_path_and_adapter_type(
+ hardware_devices)
datastore_name = vm_util.split_datastore_path(
vmdk_file_path_before_snapshot)[0]
os_type = self._session._call_method(vim_util,
"get_dynamic_property", vm_ref,
"VirtualMachine", "summary.config.guestId")
- return (vmdk_file_path_before_snapshot, adapter_type,
+ return (vmdk_file_path_before_snapshot, adapter_type, disk_type,
datastore_name, os_type)
- (vmdk_file_path_before_snapshot, adapter_type, datastore_name,
- os_type) = _get_vm_and_vmdk_attribs()
+ (vmdk_file_path_before_snapshot, adapter_type, disk_type,
+ datastore_name, os_type) = _get_vm_and_vmdk_attribs()
def _create_vm_snapshot():
# Create a snapshot of the VM
@@ -384,7 +384,7 @@ class VMwareVMOps(object):
"CreateSnapshot_Task", vm_ref,
name="%s-snapshot" % instance.name,
description="Taking Snapshot of the VM",
- memory=True,
+ memory=False,
quiesce=True)
self._session._wait_for_task(instance['uuid'], snapshot_task)
LOG.debug(_("Created Snapshot of the VM instance"),
diff --git a/nova/virt/vmwareapi/volume_util.py b/nova/virt/vmwareapi/volume_util.py
new file mode 100644
index 000000000..9d556cd26
--- /dev/null
+++ b/nova/virt/vmwareapi/volume_util.py
@@ -0,0 +1,178 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 VMware, Inc.
+#
+# 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.
+
+"""
+Helper methods for operations related to the management of volumes,
+and storage repositories
+"""
+
+import re
+import string
+
+from nova import exception
+from nova.openstack.common import log as logging
+from nova.virt.vmwareapi import vim_util
+
+LOG = logging.getLogger(__name__)
+
+
+class StorageError(Exception):
+ """To raise errors related to Volume commands."""
+
+ def __init__(self, message=None):
+ super(StorageError, self).__init__(message)
+
+
+def get_host_iqn(session):
+ """
+ Return the host iSCSI IQN.
+ """
+ host_mor = session._call_method(vim_util, "get_objects",
+ "HostSystem")[0].obj
+ hbas_ret = session._call_method(vim_util, "get_dynamic_property",
+ host_mor, "HostSystem",
+ "config.storageDevice.hostBusAdapter")
+
+ # Meaning there are no host bus adapters on the host
+ if not hbas_ret:
+ return
+ host_hbas = hbas_ret.HostHostBusAdapter
+ for hba in host_hbas:
+ if hba.__class__.__name__ == 'HostInternetScsiHba':
+ return hba.iScsiName
+
+
+def find_st(session, data):
+ """
+ Return the iSCSI Target given a volume info.
+ """
+ target_portal = data['target_portal']
+ target_iqn = data['target_iqn']
+ host_mor = session._call_method(vim_util, "get_objects",
+ "HostSystem")[0].obj
+
+ lst_properties = ["config.storageDevice.hostBusAdapter",
+ "config.storageDevice.scsiTopology",
+ "config.storageDevice.scsiLun"]
+ props = session._call_method(vim_util, "get_object_properties",
+ None, host_mor, "HostSystem",
+ lst_properties)
+ result = (None, None)
+ hbas_ret = None
+ scsi_topology = None
+ scsi_lun_ret = None
+ for elem in props:
+ for prop in elem.propSet:
+ if prop.name == "config.storageDevice.hostBusAdapter":
+ hbas_ret = prop.val
+ elif prop.name == "config.storageDevice.scsiTopology":
+ scsi_topology = prop.val
+ elif prop.name == "config.storageDevice.scsiLun":
+ scsi_lun_ret = prop.val
+
+ # Meaning there are no host bus adapters on the host
+ if hbas_ret is None:
+ return result
+ host_hbas = hbas_ret.HostHostBusAdapter
+ if not host_hbas:
+ return result
+ for hba in host_hbas:
+ if hba.__class__.__name__ == 'HostInternetScsiHba':
+ hba_key = hba.key
+ break
+ else:
+ return result
+
+ if scsi_topology is None:
+ return result
+ host_adapters = scsi_topology.adapter
+ if not host_adapters:
+ return result
+ scsi_lun_key = None
+ for adapter in host_adapters:
+ if adapter.adapter == hba_key:
+ if not getattr(adapter, 'target', None):
+ return result
+ for target in adapter.target:
+ if (getattr(target.transport, 'address', None) and
+ target.transport.address[0] == target_portal and
+ target.transport.iScsiName == target_iqn):
+ if not target.lun:
+ return result
+ for lun in target.lun:
+ if 'host.ScsiDisk' in lun.scsiLun:
+ scsi_lun_key = lun.scsiLun
+ break
+ break
+ break
+
+ if scsi_lun_key is None:
+ return result
+
+ if scsi_lun_ret is None:
+ return result
+ host_scsi_luns = scsi_lun_ret.ScsiLun
+ if not host_scsi_luns:
+ return result
+ for scsi_lun in host_scsi_luns:
+ if scsi_lun.key == scsi_lun_key:
+ return (scsi_lun.deviceName, scsi_lun.uuid)
+
+ return result
+
+
+def rescan_iscsi_hba(session):
+ """
+ Rescan the iSCSI HBA to discover iSCSI targets.
+ """
+ # There is only one default storage system in a standalone ESX host
+ storage_system_mor = session._call_method(vim_util, "get_objects",
+ "HostSystem", ["configManager.storageSystem"])[0].propSet[0].val
+ hbas_ret = session._call_method(vim_util,
+ "get_dynamic_property",
+ storage_system_mor,
+ "HostStorageSystem",
+ "storageDeviceInfo.hostBusAdapter")
+ # Meaning there are no host bus adapters on the host
+ if hbas_ret is None:
+ return
+ host_hbas = hbas_ret.HostHostBusAdapter
+ if not host_hbas:
+ return
+ for hba in host_hbas:
+ if hba.__class__.__name__ == 'HostInternetScsiHba':
+ hba_device = hba.device
+ break
+ else:
+ return
+
+ LOG.debug(_("Rescanning HBA %s") % hba_device)
+ session._call_method(session._get_vim(), "RescanHba", storage_system_mor,
+ hbaDevice=hba_device)
+ LOG.debug(_("Rescanned HBA %s ") % hba_device)
+
+
+def mountpoint_to_number(mountpoint):
+ """Translate a mountpoint like /dev/sdc into a numeric."""
+ if mountpoint.startswith('/dev/'):
+ mountpoint = mountpoint[5:]
+ if re.match('^[hsv]d[a-p]$', mountpoint):
+ return (ord(mountpoint[2:3]) - ord('a'))
+ elif re.match('^[0-9]+$', mountpoint):
+ return string.atoi(mountpoint, 10)
+ else:
+ LOG.warn(_("Mountpoint cannot be translated: %s") % mountpoint)
+ return -1
diff --git a/nova/virt/vmwareapi/volumeops.py b/nova/virt/vmwareapi/volumeops.py
new file mode 100644
index 000000000..5ec389f80
--- /dev/null
+++ b/nova/virt/vmwareapi/volumeops.py
@@ -0,0 +1,183 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2012 VMware, Inc.
+#
+# 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.
+
+"""
+Management class for Storage-related functions (attach, detach, etc).
+"""
+
+from nova import context
+from nova import exception
+from nova.openstack.common import cfg
+from nova.openstack.common import log as logging
+from nova.virt.vmwareapi import vim_util
+from nova.virt.vmwareapi import vm_util
+from nova.virt.vmwareapi import volume_util
+
+
+CONF = cfg.CONF
+LOG = logging.getLogger(__name__)
+
+
+class VMwareVolumeOps(object):
+ """
+ Management class for Volume-related tasks
+ """
+
+ def __init__(self, session):
+ self._session = session
+
+ def attach_disk_to_vm(self, vm_ref, instance_name,
+ adapter_type, disk_type, vmdk_path=None,
+ disk_size=None, linked_clone=False,
+ controller_key=None, unit_number=None,
+ device_name=None):
+ """
+ Attach disk to VM by reconfiguration.
+ """
+ client_factory = self._session._get_vim().client.factory
+ vmdk_attach_config_spec = vm_util.get_vmdk_attach_config_spec(
+ client_factory, adapter_type, disk_type,
+ vmdk_path, disk_size, linked_clone,
+ controller_key, unit_number, device_name)
+
+ LOG.debug(_("Reconfiguring VM instance %(instance_name)s to attach "
+ "disk %(vmdk_path)s or device %(device_name)s with type "
+ "%(disk_type)s") % locals())
+ reconfig_task = self._session._call_method(
+ self._session._get_vim(),
+ "ReconfigVM_Task", vm_ref,
+ spec=vmdk_attach_config_spec)
+ self._session._wait_for_task(instance_name, reconfig_task)
+ LOG.debug(_("Reconfigured VM instance %(instance_name)s to attach "
+ "disk %(vmdk_path)s or device %(device_name)s with type "
+ "%(disk_type)s") % locals())
+
+ def detach_disk_from_vm(self, vm_ref, instance_name, device):
+ """
+ Detach disk from VM by reconfiguration.
+ """
+ client_factory = self._session._get_vim().client.factory
+ vmdk_detach_config_spec = vm_util.get_vmdk_detach_config_spec(
+ client_factory, device)
+ disk_key = device.key
+ LOG.debug(_("Reconfiguring VM instance %(instance_name)s to detach "
+ "disk %(disk_key)s") % locals())
+ reconfig_task = self._session._call_method(
+ self._session._get_vim(),
+ "ReconfigVM_Task", vm_ref,
+ spec=vmdk_detach_config_spec)
+ self._session._wait_for_task(instance_name, reconfig_task)
+ LOG.debug(_("Reconfigured VM instance %(instance_name)s to detach "
+ "disk %(disk_key)s") % locals())
+
+ def discover_st(self, data):
+ """Discover iSCSI targets."""
+ target_portal = data['target_portal']
+ target_iqn = data['target_iqn']
+ LOG.debug(_("Discovering iSCSI target %(target_iqn)s from "
+ "%(target_portal)s.") % locals())
+ device_name, uuid = volume_util.find_st(self._session, data)
+ if device_name:
+ LOG.debug(_("Storage target found. No need to discover"))
+ return (device_name, uuid)
+ # Rescan iSCSI HBA
+ volume_util.rescan_iscsi_hba(self._session)
+ # Find iSCSI Target again
+ device_name, uuid = volume_util.find_st(self._session, data)
+ if device_name:
+ LOG.debug(_("Discovered iSCSI target %(target_iqn)s from "
+ "%(target_portal)s.") % locals())
+ else:
+ LOG.debug(_("Unable to discovered iSCSI target %(target_iqn)s "
+ "from %(target_portal)s.") % locals())
+ return (device_name, uuid)
+
+ def get_volume_connector(self, instance):
+ """Return volume connector information."""
+ iqn = volume_util.get_host_iqn(self._session)
+ return {
+ 'ip': CONF.vmwareapi_host_ip,
+ 'initiator': iqn
+ }
+
+ def attach_volume(self, connection_info, instance, mountpoint):
+ """Attach volume storage to VM instance."""
+ instance_name = instance['name']
+ vm_ref = vm_util.get_vm_ref_from_name(self._session, instance_name)
+ if vm_ref is None:
+ raise exception.InstanceNotFound(instance_id=instance_name)
+ # Attach Volume to VM
+ LOG.debug(_("Attach_volume: %(connection_info)s, %(instance_name)s, "
+ "%(mountpoint)s") % locals())
+ driver_type = connection_info['driver_volume_type']
+ if driver_type not in ['iscsi']:
+ raise exception.VolumeDriverNotFound(driver_type=driver_type)
+ data = connection_info['data']
+ mount_unit = volume_util.mountpoint_to_number(mountpoint)
+
+ # Discover iSCSI Target
+ device_name, uuid = self.discover_st(data)
+ if device_name is None:
+ raise volume_util.StorageError(_("Unable to find iSCSI Target"))
+
+ # Get the vmdk file name that the VM is pointing to
+ hardware_devices = self._session._call_method(vim_util,
+ "get_dynamic_property", vm_ref,
+ "VirtualMachine", "config.hardware.device")
+ vmdk_file_path, controller_key, adapter_type, disk_type, unit_number \
+ = vm_util.get_vmdk_path_and_adapter_type(hardware_devices)
+ # Figure out the correct unit number
+ if unit_number < mount_unit:
+ unit_number = mount_unit
+ else:
+ unit_number = unit_number + 1
+ self.attach_disk_to_vm(vm_ref, instance_name,
+ adapter_type, disk_type="rdmp",
+ controller_key=controller_key,
+ unit_number=unit_number,
+ device_name=device_name)
+ LOG.info(_("Mountpoint %(mountpoint)s attached to "
+ "instance %(instance_name)s") % locals())
+
+ def detach_volume(self, connection_info, instance, mountpoint):
+ """Detach volume storage to VM instance."""
+ instance_name = instance['name']
+ vm_ref = vm_util.get_vm_ref_from_name(self._session, instance_name)
+ if vm_ref is None:
+ raise exception.InstanceNotFound(instance_id=instance_name)
+ # Detach Volume from VM
+ LOG.debug(_("Detach_volume: %(instance_name)s, %(mountpoint)s")
+ % locals())
+ driver_type = connection_info['driver_volume_type']
+ if driver_type not in ['iscsi']:
+ raise exception.VolumeDriverNotFound(driver_type=driver_type)
+ data = connection_info['data']
+
+ # Discover iSCSI Target
+ device_name, uuid = volume_util.find_st(self._session, data)
+ if device_name is None:
+ raise volume_util.StorageError(_("Unable to find iSCSI Target"))
+
+ # Get the vmdk file name that the VM is pointing to
+ hardware_devices = self._session._call_method(vim_util,
+ "get_dynamic_property", vm_ref,
+ "VirtualMachine", "config.hardware.device")
+ device = vm_util.get_rdm_disk(hardware_devices, uuid)
+ if device is None:
+ raise volume_util.StorageError(_("Unable to find volume"))
+ self.detach_disk_from_vm(vm_ref, instance_name, device)
+ LOG.info(_("Mountpoint %(mountpoint)s detached from "
+ "instance %(instance_name)s") % locals())
diff --git a/nova/virt/xenapi/__init__.py b/nova/virt/xenapi/__init__.py
index 6a56a918c..3853f09f2 100644
--- a/nova/virt/xenapi/__init__.py
+++ b/nova/virt/xenapi/__init__.py
@@ -18,4 +18,6 @@
:mod:`xenapi` -- Nova support for XenServer and XCP through XenAPI
==================================================================
"""
-from nova.virt.xenapi.driver import XenAPIDriver
+from nova.virt.xenapi import driver
+
+XenAPIDriver = driver.XenAPIDriver
diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py
index 0acc360e8..a894e95b9 100644
--- a/nova/virt/xenapi/driver.py
+++ b/nova/virt/xenapi/driver.py
@@ -499,14 +499,15 @@ class XenAPIDriver(driver.ComputeDriver):
pass
def post_live_migration_at_destination(self, ctxt, instance_ref,
- network_info, block_migration):
+ network_info, block_migration,
+ block_device_info=None):
"""Post operation of live migration at destination host.
:params ctxt: security context
:params instance_ref:
nova.db.sqlalchemy.models.Instance object
instance object that is migrated.
- :params network_info: instance network infomation
+ :params network_info: instance network information
:params : block_migration: if true, post operation of block_migraiton.
"""
# TODO(JohnGarbutt) look at moving/downloading ramdisk and kernel
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index debba4f02..582a9320a 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -196,13 +196,6 @@ class ImageType(object):
}.get(image_type_id)
-def _system_metadata_to_dict(system_metadata):
- result = {}
- for item in system_metadata:
- result[item['key']] = item['value']
- return result
-
-
def create_vm(session, instance, name_label, kernel, ramdisk,
use_pv_kernel=False):
"""Create a VM record. Returns new VM reference.
@@ -994,7 +987,7 @@ def _create_image(context, session, instance, name_label, image_id,
elif cache_images == 'all':
cache = True
elif cache_images == 'some':
- sys_meta = _system_metadata_to_dict(instance['system_metadata'])
+ sys_meta = utils.metadata_to_dict(instance['system_metadata'])
try:
cache = utils.bool_from_str(sys_meta['image_cache_in_nova'])
except KeyError:
@@ -1087,7 +1080,7 @@ def _image_uses_bittorrent(context, instance):
if xenapi_torrent_images == 'all':
bittorrent = True
elif xenapi_torrent_images == 'some':
- sys_meta = _system_metadata_to_dict(instance['system_metadata'])
+ sys_meta = utils.metadata_to_dict(instance['system_metadata'])
try:
bittorrent = utils.bool_from_str(sys_meta['image_bittorrent'])
except KeyError:
@@ -1510,7 +1503,7 @@ def fetch_bandwidth(session):
def compile_metrics(start_time, stop_time=None):
"""Compile bandwidth usage, cpu, and disk metrics for all VMs on
this host.
- Note that some stats, like bandwith, do not seem to be very
+ Note that some stats, like bandwidth, do not seem to be very
accurate in some of the data from XenServer (mdragon). """
start_time = int(start_time)
diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py
index 5f79b6c3a..c2d717cfd 100644
--- a/nova/virt/xenapi/volumeops.py
+++ b/nova/virt/xenapi/volumeops.py
@@ -125,6 +125,7 @@ class VolumeOps(object):
try:
vbd_ref = vm_utils.find_vbd_by_number(self._session, vm_ref,
device_number)
+ sr_ref = volume_utils.find_sr_from_vbd(self._session, vbd_ref)
except volume_utils.StorageError, exc:
LOG.exception(exc)
raise Exception(_('Unable to locate volume %s') % mountpoint)
@@ -143,7 +144,6 @@ class VolumeOps(object):
# Forget SR only if no other volumes on this host are using it
try:
- sr_ref = volume_utils.find_sr_from_vbd(self._session, vbd_ref)
volume_utils.purge_sr(self._session, sr_ref)
except volume_utils.StorageError, exc:
LOG.exception(exc)
diff --git a/nova/volume/cinder.py b/nova/volume/cinder.py
index 3e1ccc66b..daca69854 100644
--- a/nova/volume/cinder.py
+++ b/nova/volume/cinder.py
@@ -20,7 +20,7 @@
Handles all requests relating to volumes + cinder.
"""
-from copy import deepcopy
+import copy
import sys
from cinderclient import exceptions as cinder_exception
@@ -139,7 +139,7 @@ def _untranslate_volume_summary_view(context, vol):
d['volume_metadata'].append(item)
if hasattr(vol, 'volume_image_metadata'):
- d['volume_image_metadata'] = deepcopy(vol.volume_image_metadata)
+ d['volume_image_metadata'] = copy.deepcopy(vol.volume_image_metadata)
return d
diff --git a/nova/wsgi.py b/nova/wsgi.py
index 16851dba8..651dbc4f6 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -28,6 +28,7 @@ import eventlet.wsgi
import greenlet
from paste import deploy
import routes.middleware
+import ssl
import webob.dec
import webob.exc
@@ -45,7 +46,21 @@ wsgi_opts = [
help='A python format string that is used as the template to '
'generate log lines. The following values can be formatted '
'into it: client_ip, date_time, request_line, status_code, '
- 'body_length, wall_seconds.')
+ 'body_length, wall_seconds.'),
+ cfg.StrOpt('ssl_ca_file',
+ default=None,
+ help="CA certificate file to use to verify "
+ "connecting clients"),
+ cfg.StrOpt('ssl_cert_file',
+ default=None,
+ help="SSL certificate of API server"),
+ cfg.StrOpt('ssl_key_file',
+ default=None,
+ help="SSL private key of API server"),
+ cfg.IntOpt('tcp_keepidle',
+ default=600,
+ help="Sets the value of TCP_KEEPIDLE in seconds for each "
+ "server socket. Not supported on OS X.")
]
CONF = cfg.CONF
CONF.register_opts(wsgi_opts)
@@ -59,7 +74,8 @@ class Server(object):
default_pool_size = 1000
def __init__(self, name, app, host='0.0.0.0', port=0, pool_size=None,
- protocol=eventlet.wsgi.HttpProtocol, backlog=128):
+ protocol=eventlet.wsgi.HttpProtocol, backlog=128,
+ use_ssl=False, max_url_len=None):
"""Initialize, but do not start, a WSGI server.
:param name: Pretty name for logging.
@@ -68,6 +84,7 @@ class Server(object):
:param port: Port number to server the application.
:param pool_size: Maximum number of eventlets to spawn concurrently.
:param backlog: Maximum number of queued connections.
+ :param max_url_len: Maximum length of permitted URLs.
:returns: None
:raises: nova.exception.InvalidInput
"""
@@ -78,6 +95,8 @@ class Server(object):
self._pool = eventlet.GreenPool(pool_size or self.default_pool_size)
self._logger = logging.getLogger("nova.%s.wsgi.server" % self.name)
self._wsgi_logger = logging.WritableLogger(self._logger)
+ self._use_ssl = use_ssl
+ self._max_url_len = max_url_len
if backlog < 1:
raise exception.InvalidInput(
@@ -106,13 +125,74 @@ class Server(object):
:returns: None
"""
- self._server = eventlet.spawn(eventlet.wsgi.server,
- self._socket,
- self.app,
- protocol=self._protocol,
- custom_pool=self._pool,
- log=self._wsgi_logger,
- log_format=CONF.wsgi_log_format)
+ if self._use_ssl:
+ try:
+ ca_file = CONF.ssl_ca_file
+ cert_file = CONF.ssl_cert_file
+ key_file = CONF.ssl_key_file
+
+ if cert_file and not os.path.exists(cert_file):
+ raise RuntimeError(
+ _("Unable to find cert_file : %s") % cert_file)
+
+ if ca_file and not os.path.exists(ca_file):
+ raise RuntimeError(
+ _("Unable to find ca_file : %s") % ca_file)
+
+ if key_file and not os.path.exists(key_file):
+ raise RuntimeError(
+ _("Unable to find key_file : %s") % key_file)
+
+ if self._use_ssl and (not cert_file or not key_file):
+ raise RuntimeError(
+ _("When running server in SSL mode, you must "
+ "specify both a cert_file and key_file "
+ "option value in your configuration file"))
+ ssl_kwargs = {
+ 'server_side': True,
+ 'certfile': cert_file,
+ 'keyfile': key_file,
+ 'cert_reqs': ssl.CERT_NONE,
+ }
+
+ if CONF.ssl_ca_file:
+ ssl_kwargs['ca_certs'] = ca_file
+ ssl_kwargs['cert_reqs'] = ssl.CERT_REQUIRED
+
+ self._socket = eventlet.wrap_ssl(self._socket,
+ **ssl_kwargs)
+
+ self._socket.setsockopt(socket.SOL_SOCKET,
+ socket.SO_REUSEADDR, 1)
+ # sockets can hang around forever without keepalive
+ self._socket.setsockopt(socket.SOL_SOCKET,
+ socket.SO_KEEPALIVE, 1)
+
+ # This option isn't available in the OS X version of eventlet
+ if hasattr(socket, 'TCP_KEEPIDLE'):
+ self._socket.setsockopt(socket.IPPROTO_TCP,
+ socket.TCP_KEEPIDLE,
+ CONF.tcp_keepidle)
+
+ except Exception:
+ LOG.error(_("Failed to start %(name)s on %(host)s"
+ ":%(port)s with SSL support") % self.__dict__)
+ raise
+
+ wsgi_kwargs = {
+ 'func': eventlet.wsgi.server,
+ 'sock': self._socket,
+ 'site': self.app,
+ 'protocol': self._protocol,
+ 'custom_pool': self._pool,
+ 'log': self._wsgi_logger,
+ 'log_format': CONF.wsgi_log_format
+ }
+
+ if self._max_url_len:
+ wsgi_kwargs['url_length_limit'] = self._max_url_len
+
+ self._server = eventlet.spawn(**wsgi_kwargs)
def stop(self):
"""Stop this server.
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration b/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration
index 35316a9b8..b9e9da2e2 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration
@@ -16,7 +16,7 @@
# under the License.
"""
-XenAPI Plugin for transfering data between host nodes
+XenAPI Plugin for transferring data between host nodes
"""
import utils
diff --git a/run_tests.sh b/run_tests.sh
index 39176d78b..11bc8b518 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -81,20 +81,31 @@ function run_tests {
if [ $coverage -eq 1 ]; then
# Do not test test_coverage_ext when gathering coverage.
if [ "x$testrargs" = "x" ]; then
- testrargs="^(?!.*test_coverage_ext).*$"
+ testrargs="^(?!.*test.*coverage).*$"
fi
- export PYTHON="${wrapper} coverage run --source nova --parallel-mode"
+ TESTRTESTS="$TESTRTESTS --coverage"
+ else
+ TESTRTESTS="$TESTRTESTS --slowest"
fi
+
# Just run the test suites in current environment
set +e
- TESTRTESTS="$TESTRTESTS $testrargs"
+ testrargs=`echo "$testrargs" | sed -e's/^\s*\(.*\)\s*$/\1/'`
+ TESTRTESTS="$TESTRTESTS --testr-args='$testrargs'"
echo "Running \`${wrapper} $TESTRTESTS\`"
- ${wrapper} $TESTRTESTS
+ bash -c "${wrapper} $TESTRTESTS"
RESULT=$?
set -e
copy_subunit_log
+ if [ $coverage -eq 1 ]; then
+ echo "Generating coverage report in covhtml/"
+ # Don't compute coverage for common code, which is tested elsewhere
+ ${wrapper} coverage combine
+ ${wrapper} coverage html --include='nova/*' --omit='nova/openstack/common/*' -d covhtml -i
+ fi
+
return $RESULT
}
@@ -114,7 +125,7 @@ function run_pep8 {
# NOTE(lzyeval): Avoid selecting *.pyc files to reduce pep8 check-up time
# when running on devstack.
srcfiles=`find nova -type f -name "*.py" ! -wholename "nova\/openstack*"`
- srcfiles+=" `find bin -type f ! -name "nova.conf*" ! -name "*api-paste.ini*"`"
+ srcfiles+=" `find bin -type f ! -name "nova.conf*" ! -name "*api-paste.ini*" ! -name "*~"`"
srcfiles+=" `find tools -type f -name "*.py"`"
srcfiles+=" `find plugins -type f -name "*.py"`"
srcfiles+=" `find smoketests -type f -name "*.py"`"
@@ -143,7 +154,7 @@ function run_pep8 {
}
-TESTRTESTS="testr run --parallel $testropts"
+TESTRTESTS="python setup.py testr $testropts"
if [ $never_venv -eq 0 ]
then
@@ -197,10 +208,3 @@ if [ -z "$testrargs" ]; then
run_pep8
fi
fi
-
-if [ $coverage -eq 1 ]; then
- echo "Generating coverage report in covhtml/"
- # Don't compute coverage for common code, which is tested elsewhere
- ${wrapper} coverage combine
- ${wrapper} coverage html --include='nova/*' --omit='nova/openstack/common/*' -d covhtml -i
-fi
diff --git a/smoketests/base.py b/smoketests/base.py
index f6cec3168..7c7d19838 100644
--- a/smoketests/base.py
+++ b/smoketests/base.py
@@ -17,7 +17,7 @@
# under the License.
import boto
-from boto.ec2.regioninfo import RegionInfo
+from boto.ec2 import regioninfo
import commands
import httplib
import os
@@ -123,7 +123,7 @@ class SmokeTestCase(unittest.TestCase):
return boto_v6.connect_ec2(aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
is_secure=parts['is_secure'],
- region=RegionInfo(None,
+ region=regioninfo.RegionInfo(None,
'nova',
parts['ip']),
port=parts['port'],
@@ -133,7 +133,7 @@ class SmokeTestCase(unittest.TestCase):
return boto.connect_ec2(aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
is_secure=parts['is_secure'],
- region=RegionInfo(None,
+ region=regioninfo.RegionInfo(None,
'nova',
parts['ip']),
port=parts['port'],
diff --git a/tools/conf/extract_opts.py b/tools/conf/extract_opts.py
index 3185cb93d..4dde53335 100644
--- a/tools/conf/extract_opts.py
+++ b/tools/conf/extract_opts.py
@@ -2,7 +2,6 @@
# Copyright 2012 SINA Corporation
# All Rights Reserved.
-# Author: Zhongyue Luo <lzyeval@gmail.com>
#
# 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
@@ -15,6 +14,9 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+#
+# @author: Zhongyue Luo, SINA Corporation.
+#
"""Extracts OpenStack config option info from module(s)."""
@@ -35,6 +37,15 @@ FLOATOPT = "FloatOpt"
LISTOPT = "ListOpt"
MULTISTROPT = "MultiStrOpt"
+OPT_TYPES = {
+ STROPT: 'string value',
+ BOOLOPT: 'boolean value',
+ INTOPT: 'integer value',
+ FLOATOPT: 'floating point value',
+ LISTOPT: 'list value',
+ MULTISTROPT: 'multi valued',
+}
+
OPTION_COUNT = 0
OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT,
FLOATOPT, LISTOPT,
@@ -63,10 +74,6 @@ def main(srcfiles):
# The options list is a list of (module, options) tuples
opts_by_group = {'DEFAULT': []}
- opts_by_group['DEFAULT'].append(
- (cfg.__name__ + ':' + cfg.CommonConfigOpts.__name__,
- _list_opts(cfg.CommonConfigOpts)[0][1]))
-
for pkg_name in pkg_names:
mods = mods_by_pkg.get(pkg_name)
mods.sort()
@@ -187,33 +194,19 @@ def _get_my_ip():
return None
-MY_IP = _get_my_ip()
-HOST = socket.getfqdn()
-
-
def _sanitize_default(s):
"""Set up a reasonably sensible default for pybasedir, my_ip and host."""
if s.startswith(BASEDIR):
return s.replace(BASEDIR, '/usr/lib/python/site-packages')
- elif s == MY_IP:
+ elif s == _get_my_ip():
return '10.0.0.1'
- elif s == HOST:
+ elif s == socket.getfqdn():
return 'nova'
elif s.strip() != s:
return '"%s"' % s
return s
-OPT_TYPES = {
- 'StrOpt': 'string value',
- 'BoolOpt': 'boolean value',
- 'IntOpt': 'integer value',
- 'FloatOpt': 'floating point value',
- 'ListOpt': 'list value',
- 'MultiStrOpt': 'multi valued',
-}
-
-
def _print_opt(opt):
opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help
if not opt_help:
diff --git a/tools/hacking.py b/tools/hacking.py
index 56f6694bd..801a87899 100755
--- a/tools/hacking.py
+++ b/tools/hacking.py
@@ -18,7 +18,7 @@
"""nova HACKING file compliance testing
-built on top of pep8.py
+Built on top of pep8.py
"""
import inspect
@@ -49,6 +49,8 @@ START_DOCSTRING_TRIPLE = ['u"""', 'r"""', '"""', "u'''", "r'''", "'''"]
END_DOCSTRING_TRIPLE = ['"""', "'''"]
VERBOSE_MISSING_IMPORT = os.getenv('HACKING_VERBOSE_MISSING_IMPORT', 'False')
+_missingImport = set([])
+
# Monkey patch broken excluded filter in pep8
# See https://github.com/jcrocholl/pep8/pull/111
@@ -103,7 +105,7 @@ def import_normalize(line):
return line
-def nova_todo_format(physical_line):
+def nova_todo_format(physical_line, tokens):
"""Check for 'TODO()'.
nova HACKING guide recommendation for TODO:
@@ -111,14 +113,13 @@ def nova_todo_format(physical_line):
Okay: #TODO(sdague)
N101: #TODO fail
+ N101: #TODO (jogo) fail
"""
# TODO(sdague): TODO check shouldn't fail inside of space
pos = physical_line.find('TODO')
pos1 = physical_line.find('TODO(')
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 len(tokens) == 0):
return pos, "N101: Use TODO(NAME)"
@@ -165,8 +166,6 @@ def nova_one_import_per_line(logical_line):
not is_import_exception(parts[1])):
yield pos, "N301: one import per line"
-_missingImport = set([])
-
def nova_import_module_only(logical_line):
r"""Check for import module only.
@@ -175,20 +174,23 @@ def nova_import_module_only(logical_line):
Do not import objects, only modules
Okay: from os import path
- N302 from os.path import mkdir as mkdir2
- N303 import bubba
- N304 import blueblue
+ Okay: import os.path
+ N302: from os.path import dirname as dirname2
+ N303 from os.path import *
+ N304 import flakes
"""
# N302 import only modules
# N303 Invalid Import
# N304 Relative Import
# TODO(sdague) actually get these tests working
- def importModuleCheck(mod, parent=None, added=False):
- """Import Module helper function.
+ # TODO(jogo) simplify this code
+ def import_module_check(mod, parent=None, added=False):
+ """Checks for relative, modules and invalid imports.
If can't find module on first try, recursively check for relative
- imports
+ imports.
+ When parsing 'from x import y,' x is the parent.
"""
current_path = os.path.dirname(pep8.current_file)
try:
@@ -196,8 +198,6 @@ def nova_import_module_only(logical_line):
warnings.simplefilter('ignore', DeprecationWarning)
valid = True
if parent:
- if is_import_exception(parent):
- return
parent_mod = __import__(parent, globals(), locals(),
[mod], -1)
valid = inspect.ismodule(getattr(parent_mod, mod))
@@ -209,7 +209,7 @@ def nova_import_module_only(logical_line):
sys.path.pop()
added = False
return logical_line.find(mod), ("N304: No "
- "relative imports. '%s' is a relative import"
+ "relative imports. '%s' is a relative import"
% logical_line)
return logical_line.find(mod), ("N302: import only "
"modules. '%s' does not import a module"
@@ -219,7 +219,7 @@ def nova_import_module_only(logical_line):
if not added:
added = True
sys.path.append(current_path)
- return importModuleCheck(mod, parent, added)
+ return import_module_check(mod, parent, added)
else:
name = logical_line.split()[1]
if name not in _missingImport:
@@ -234,23 +234,27 @@ def nova_import_module_only(logical_line):
except AttributeError:
# Invalid import
+ if "import *" in logical_line:
+ # TODO(jogo): handle "from x import *, by checking all
+ # "objects in x"
+ return
return logical_line.find(mod), ("N303: Invalid import, "
- "AttributeError raised")
+ "%s" % mod)
- # convert "from x import y" to " import x.y"
- # convert "from x import y as z" to " import x.y"
- import_normalize(logical_line)
split_line = logical_line.split()
-
- if (logical_line.startswith("import ") and "," not in logical_line and
- (len(split_line) == 2 or
- (len(split_line) == 4 and split_line[2] == "as"))):
- mod = split_line[1]
- rval = importModuleCheck(mod)
+ if (", " not in logical_line and
+ split_line[0] in ('import', 'from') and
+ (len(split_line) in (2, 4, 6)) and
+ split_line[1] != "__future__"):
+ if is_import_exception(split_line[1]):
+ return
+ if "from" == split_line[0]:
+ rval = import_module_check(split_line[3], parent=split_line[1])
+ else:
+ rval = import_module_check(split_line[1])
if rval is not None:
yield rval
- # TODO(jogo) handle "from x import *"
#TODO(jogo): import template: N305
@@ -329,6 +333,8 @@ def nova_docstring_one_line(physical_line):
A one line docstring looks like this and ends in punctuation.
Okay: '''This is good.'''
+ Okay: '''This is good too!'''
+ Okay: '''How about this?'''
N402: '''This is not'''
N402: '''Bad punctuation,'''
"""
diff --git a/tools/lintstack.sh b/tools/lintstack.sh
index 42c6a60b3..d8591d03d 100755
--- a/tools/lintstack.sh
+++ b/tools/lintstack.sh
@@ -20,7 +20,16 @@
# commit for review.
set -e
TOOLS_DIR=$(cd $(dirname "$0") && pwd)
-GITHEAD=`git rev-parse HEAD`
+# Get the current branch name.
+GITHEAD=`git rev-parse --abbrev-ref HEAD`
+if [[ "$GITHEAD" == "HEAD" ]]; then
+ # In detached head mode, get revision number instead
+ GITHEAD=`git rev-parse HEAD`
+ echo "Currently we are at commit $GITHEAD"
+else
+ echo "Currently we are at branch $GITHEAD"
+fi
+
cp -f $TOOLS_DIR/lintstack.py $TOOLS_DIR/lintstack.head.py
if git rev-parse HEAD^2 2>/dev/null; then
@@ -47,8 +56,4 @@ git checkout $GITHEAD
$TOOLS_DIR/lintstack.head.py
echo "Check passed. FYI: the pylint exceptions are:"
cat $TOOLS_DIR/pylint_exceptions
-echo
-echo "You are in detached HEAD mode. If you are a developer"
-echo "and not very familiar with git, you might want to do"
-echo "'git checkout branch-name' to go back to your branch."
diff --git a/tools/test-requires b/tools/test-requires
index c691a6bca..851023af4 100644
--- a/tools/test-requires
+++ b/tools/test-requires
@@ -12,5 +12,4 @@ pylint==0.25.2
python-subunit
sphinx>=1.1.2
testrepository>=0.0.13
-# testtools 0.9.25 is broken, change this when upstream is fixed (bug 1102400)
-testtools>=0.9.22,<=0.9.24
+testtools>=0.9.27
diff --git a/tools/xenserver/cleanup_sm_locks.py b/tools/xenserver/cleanup_sm_locks.py
new file mode 100755
index 000000000..de455b076
--- /dev/null
+++ b/tools/xenserver/cleanup_sm_locks.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+
+# 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.
+"""
+Script to cleanup old XenServer /var/lock/sm locks.
+
+XenServer 5.6 and 6.0 do not appear to always cleanup locks when using a
+FileSR. ext3 has a limit of 32K inode links, so when we have 32K-2 (31998)
+locks laying around, builds will begin to fail because we can't create any
+additional locks. This cleanup script is something we can run periodically as
+a stop-gap measure until this is fixed upstream.
+
+This script should be run on the dom0 of the affected machine.
+"""
+import errno
+import optparse
+import os
+import sys
+import time
+
+BASE = '/var/lock/sm'
+
+
+def _get_age_days(secs):
+ return float(time.time() - secs) / 86400
+
+
+def _parse_args():
+ parser = optparse.OptionParser()
+ parser.add_option("-d", "--dry-run",
+ action="store_true", dest="dry_run", default=False,
+ help="don't actually remove locks")
+ parser.add_option("-l", "--limit",
+ action="store", type='int', dest="limit",
+ default=sys.maxint,
+ help="max number of locks to delete (default: no limit)")
+ parser.add_option("-v", "--verbose",
+ action="store_true", dest="verbose", default=False,
+ help="don't print status messages to stdout")
+
+ options, args = parser.parse_args()
+
+ try:
+ days_old = int(args[0])
+ except (IndexError, ValueError):
+ parser.print_help()
+ sys.exit(1)
+
+ return options, days_old
+
+
+def main():
+ options, days_old = _parse_args()
+
+ if not os.path.exists(BASE):
+ print >> sys.stderr, "error: '%s' doesn't exist. Make sure you're"\
+ " running this on the dom0." % BASE
+ sys.exit(1)
+
+ lockpaths_removed = 0
+ nspaths_removed = 0
+
+ for nsname in os.listdir(BASE)[:options.limit]:
+ nspath = os.path.join(BASE, nsname)
+
+ if not os.path.isdir(nspath):
+ continue
+
+ # Remove old lockfiles
+ removed = 0
+ locknames = os.listdir(nspath)
+ for lockname in locknames:
+ lockpath = os.path.join(nspath, lockname)
+ lock_age_days = _get_age_days(os.path.getmtime(lockpath))
+ if lock_age_days > days_old:
+ lockpaths_removed += 1
+ removed += 1
+
+ if options.verbose:
+ print 'Removing old lock: %03d %s' % (lock_age_days,
+ lockpath)
+
+ if not options.dry_run:
+ os.unlink(lockpath)
+
+ # Remove empty namespace paths
+ if len(locknames) == removed:
+ nspaths_removed += 1
+
+ if options.verbose:
+ print 'Removing empty namespace: %s' % nspath
+
+ if not options.dry_run:
+ try:
+ os.rmdir(nspath)
+ except OSError, e:
+ if e.errno == errno.ENOTEMPTY:
+ print >> sys.stderr, "warning: directory '%s'"\
+ " not empty" % nspath
+ else:
+ raise
+
+ if options.dry_run:
+ print "** Dry Run **"
+
+ print "Total locks removed: ", lockpaths_removed
+ print "Total namespaces removed: ", nspaths_removed
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/xenserver/vm_vdi_cleaner.py b/tools/xenserver/vm_vdi_cleaner.py
index eeaf978b8..27b89d510 100755
--- a/tools/xenserver/vm_vdi_cleaner.py
+++ b/tools/xenserver/vm_vdi_cleaner.py
@@ -42,6 +42,7 @@ cleaner_opts = [
]
CONF = cfg.CONF
CONF.register_opts(cleaner_opts)
+CONF.import_opt('verbose', 'nova.openstack.common.log')
CONF.import_opt("resize_confirm_window", "nova.compute.manager")
diff --git a/tox.ini b/tox.ini
index 58468accb..e98f30151 100644
--- a/tox.ini
+++ b/tox.ini
@@ -38,7 +38,7 @@ commands = python tools/flakes.py nova
# tests conflict with coverage.
commands =
python setup.py testr --coverage \
- --testr-args='^(?!.*test_coverage_ext).*$'
+ --testr-args='^(?!.*test.*coverage).*$'
[testenv:venv]
commands = {posargs}