diff options
652 files changed, 13575 insertions, 5894 deletions
diff --git a/.coveragerc b/.coveragerc index 82fe47792..902a94349 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,7 @@ [run] branch = True -omit = /usr*,setup.py,*egg*,.venv/*,.tox/*,nova/tests/* +source = nova +omit = nova/tests/*,DynamicallyCompiledCheetahTemplate.py [report] ignore-errors = True 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/HACKING.rst b/HACKING.rst index be894f072..54e3b3275 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -9,6 +9,7 @@ Nova Style Commandments General ------- - Put two newlines between top-level code (funcs, classes, etc) +- Use only UNIX style newlines ("\n"), not Windows style ("\r\n") - Put one newline between methods in classes and anywhere else - Long lines should be wrapped in parentheses in preference to using a backslash for line continuation. @@ -28,6 +29,18 @@ General mylist = Foo().list() # OKAY, does not shadow built-in +- Use the "not in" operator for collection membership evaluation. Example:: + + if not X in Y: # BAD, hard to understand + pass + + if X not in Y: # OKAY, intuitive + pass + + if not (X in Y or X is Z): # OKAY, still better than all those 'not's + pass + + Imports ------- - Do not import objects, only modules (*) 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 beee143f5..617e2411d 100755 --- a/bin/nova-novncproxy +++ b/bin/nova-novncproxy @@ -16,25 +16,17 @@ # License for the specific language governing permissions and limitations # under the License. -''' +""" Websocket proxy that is compatible with OpenStack Nova noVNC consoles. Leverages websockify.py by Joel Martin -''' +""" -import Cookie import os -import socket import sys -import websockify - from nova import config -from nova.consoleauth import rpcapi as consoleauth_rpcapi -from nova import context +from nova.console import websocketproxy from nova.openstack.common import cfg -from nova.openstack.common import log as logging -from nova.openstack.common import rpc -from nova import utils opts = [ @@ -69,64 +61,7 @@ opts = [ CONF = cfg.CONF CONF.register_cli_opts(opts) -LOG = logging.getLogger(__name__) - - -class NovaWebSocketProxy(websockify.WebSocketProxy): - def __init__(self, *args, **kwargs): - websockify.WebSocketProxy.__init__(self, unix_target=None, - target_cfg=None, - ssl_target=None, *args, **kwargs) - - def new_client(self): - """ - Called after a new WebSocket connection has been established. - """ - cookie = Cookie.SimpleCookie() - cookie.load(self.headers.getheader('cookie')) - token = cookie['token'].value - ctxt = context.get_admin_context() - rpcapi = consoleauth_rpcapi.ConsoleAuthAPI() - connect_info = rpcapi.check_token(ctxt, token=token) - - if not connect_info: - LOG.audit("Invalid Token: %s", token) - raise Exception(_("Invalid Token")) - - host = connect_info['host'] - port = int(connect_info['port']) - - # Connect to the target - self.msg("connecting to: %s:%s" % (host, port)) - LOG.audit("connecting to: %s:%s" % (host, port)) - tsock = self.socket(host, port, connect=True) - - # Handshake as necessary - if connect_info.get('internal_access_path'): - tsock.send("CONNECT %s HTTP/1.1\r\n\r\n" % - connect_info['internal_access_path']) - while True: - data = tsock.recv(4096, socket.MSG_PEEK) - if data.find("\r\n\r\n") != -1: - if not data.split("\r\n")[0].find("200"): - LOG.audit("Invalid Connection Info %s", token) - raise Exception(_("Invalid Connection Info")) - tsock.recv(len(data)) - break - - if self.verbose and not self.daemon: - print(self.traffic_legend) - - # Start proxying - try: - self.do_proxy(tsock) - except Exception: - if tsock: - tsock.shutdown(socket.SHUT_RDWR) - tsock.close() - self.vmsg("%s:%s: Target closed" % (host, port)) - LOG.audit("%s:%s: Target closed" % (host, port)) - raise +CONF.import_opt('debug', 'nova.openstack.common.log') if __name__ == '__main__': @@ -142,18 +77,19 @@ if __name__ == '__main__': sys.exit(-1) # Create and start the NovaWebSockets proxy - server = NovaWebSocketProxy(listen_host=CONF.novncproxy_host, - listen_port=CONF.novncproxy_port, - source_is_ipv6=CONF.source_is_ipv6, - verbose=CONF.verbose, - cert=CONF.cert, - key=CONF.key, - ssl_only=CONF.ssl_only, - daemon=CONF.daemon, - record=CONF.record, - web=CONF.web, - target_host='ignore', - target_port='ignore', - wrap_mode='exit', - wrap_cmd=None) + server = websocketproxy.NovaWebSocketProxy( + listen_host=CONF.novncproxy_host, + listen_port=CONF.novncproxy_port, + source_is_ipv6=CONF.source_is_ipv6, + verbose=CONF.verbose, + cert=CONF.cert, + key=CONF.key, + ssl_only=CONF.ssl_only, + daemon=CONF.daemon, + record=CONF.record, + web=CONF.web, + target_host='ignore', + target_port='ignore', + wrap_mode='exit', + wrap_cmd=None) server.start_server() diff --git a/bin/nova-rootwrap b/bin/nova-rootwrap index c8e880d79..72a8c6309 100755 --- a/bin/nova-rootwrap +++ b/bin/nova-rootwrap @@ -16,20 +16,18 @@ # License for the specific language governing permissions and limitations # under the License. -"""Root wrapper for Nova +"""Root wrapper for OpenStack services - Filters which commands nova is allowed to run as another user. + Filters which commands a service is allowed to run as another user. - To use this, you should set the following in nova.conf: + To use this with nova, you should set the following in nova.conf: rootwrap_config=/etc/nova/rootwrap.conf You also need to let the nova user run nova-rootwrap as root in sudoers: nova ALL = (root) NOPASSWD: /usr/bin/nova-rootwrap /etc/nova/rootwrap.conf * - To make allowed commands node-specific, your packaging should only - install {compute,network,volume}.filters respectively on compute, network - and volume nodes (i.e. nova-api nodes should not have any of those files - installed). + Service packaging should deploy .filters files only on nodes where they are + needed, to avoid allowing more than is necessary. """ import ConfigParser @@ -75,7 +73,7 @@ if __name__ == '__main__': if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): sys.path.insert(0, possible_topdir) - from nova.rootwrap import wrapper + from nova.openstack.common.rootwrap import wrapper # Load configuration try: diff --git a/bin/nova-spicehtml5proxy b/bin/nova-spicehtml5proxy new file mode 100755 index 000000000..405092942 --- /dev/null +++ b/bin/nova-spicehtml5proxy @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2012 OpenStack, LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Websocket proxy that is compatible with OpenStack Nova +SPICE HTML5 consoles. Leverages websockify.py by Joel Martin +""" + +import os +import sys + +from nova import config +from nova.console import websocketproxy +from nova.openstack.common import cfg + + +opts = [ + cfg.BoolOpt('record', + default=False, + help='Record sessions to FILE.[session_number]'), + cfg.BoolOpt('daemon', + default=False, + help='Become a daemon (background process)'), + cfg.BoolOpt('ssl_only', + default=False, + help='Disallow non-encrypted connections'), + cfg.BoolOpt('source_is_ipv6', + default=False, + help='Source is ipv6'), + cfg.StrOpt('cert', + default='self.pem', + help='SSL certificate file'), + cfg.StrOpt('key', + default=None, + help='SSL key file (if separate from cert)'), + cfg.StrOpt('web', + default='/usr/share/spice-html5', + help='Run webserver on same port. Serve files from DIR.'), + cfg.StrOpt('spicehtml5proxy_host', + default='0.0.0.0', + help='Host on which to listen for incoming requests'), + cfg.IntOpt('spicehtml5proxy_port', + default=6082, + help='Port on which to listen for incoming requests'), + ] + +CONF = cfg.CONF +CONF.register_cli_opts(opts) +CONF.import_opt('debug', 'nova.openstack.common.log') + + +if __name__ == '__main__': + if CONF.ssl_only and not os.path.exists(CONF.cert): + parser.error("SSL only and %s not found" % CONF.cert) + + # Setup flags + config.parse_args(sys.argv) + + # Check to see if spice html/js/css files are present + if not os.path.exists(CONF.web): + print "Can not find spice html/js/css files at %s." % CONF.web + sys.exit(-1) + + # Create and start the NovaWebSockets proxy + server = websocketproxy.NovaWebSocketProxy( + listen_host=CONF.spicehtml5proxy_host, + listen_port=CONF.spicehtml5proxy_port, + source_is_ipv6=CONF.source_is_ipv6, + verbose=CONF.verbose, + cert=CONF.cert, + key=CONF.key, + ssl_only=CONF.ssl_only, + daemon=CONF.daemon, + record=CONF.record, + web=CONF.web, + target_host='ignore', + target_port='ignore', + wrap_mode='exit', + wrap_cmd=None) + server.start_server() diff --git a/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-consoles/get-spice-console-post-req.json b/doc/api_samples/os-consoles/get-spice-console-post-req.json new file mode 100644 index 000000000..d04f7c7ae --- /dev/null +++ b/doc/api_samples/os-consoles/get-spice-console-post-req.json @@ -0,0 +1,5 @@ +{ + "os-getSPICEConsole": { + "type": "spice-html5" + } +} diff --git a/doc/api_samples/os-consoles/get-spice-console-post-req.xml b/doc/api_samples/os-consoles/get-spice-console-post-req.xml new file mode 100644 index 000000000..59052abea --- /dev/null +++ b/doc/api_samples/os-consoles/get-spice-console-post-req.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<os-getSPICEConsole type="spice-html5" /> diff --git a/doc/api_samples/os-consoles/get-spice-console-post-resp.json b/doc/api_samples/os-consoles/get-spice-console-post-resp.json new file mode 100644 index 000000000..f4999e1ba --- /dev/null +++ b/doc/api_samples/os-consoles/get-spice-console-post-resp.json @@ -0,0 +1,6 @@ +{ + "console": { + "type": "spice-html5", + "url": "http://example.com:6080/spice_auto.html?token=f9906a48-b71e-4f18-baca-c987da3ebdb3&title=dafa(75ecef58-3b8e-4659-ab3b-5501454188e9)" + } +} diff --git a/doc/api_samples/os-consoles/get-spice-console-post-resp.xml b/doc/api_samples/os-consoles/get-spice-console-post-resp.xml new file mode 100644 index 000000000..acba8b1f0 --- /dev/null +++ b/doc/api_samples/os-consoles/get-spice-console-post-resp.xml @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='UTF-8'?> +<console> + <type>spice-html5</type> + <url>http://example.com:6080/spice_auto.html?token=f9906a48-b71e-4f18-baca-c987da3ebdb3</url> +</console> diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json new file mode 100644 index 000000000..63fc8738b --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "value1", + "key2": "value2" + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml new file mode 100644 index 000000000..95c1daab9 --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<extra_specs> + <key1>value1</key1> + <key2>value2</key2> +</extra_specs>
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json new file mode 100644 index 000000000..63fc8738b --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "value1", + "key2": "value2" + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml new file mode 100644 index 000000000..06b01a9fc --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_specs> + <key2>value2</key2> + <key1>value1</key1> +</extra_specs>
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json new file mode 100644 index 000000000..e71755fe6 --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json @@ -0,0 +1,3 @@ +{ + "key1": "value1" +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml new file mode 100644 index 000000000..d57579ba6 --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_spec key="key1">value1</extra_spec>
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json new file mode 100644 index 000000000..63fc8738b --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "value1", + "key2": "value2" + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml new file mode 100644 index 000000000..06b01a9fc --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_specs> + <key2>value2</key2> + <key1>value1</key1> +</extra_specs>
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json new file mode 100644 index 000000000..a40d79e32 --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json @@ -0,0 +1,3 @@ +{ + "key1": "new_value1" +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml new file mode 100644 index 000000000..b7ae6732b --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> + <key1>new_value1</key1>
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json new file mode 100644 index 000000000..a40d79e32 --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json @@ -0,0 +1,3 @@ +{ + "key1": "new_value1" +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml new file mode 100644 index 000000000..13208ad7c --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_spec key="key1">new_value1</extra_spec>
\ 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/api_samples/os-fping/fping-get-details-resp.json b/doc/api_samples/os-fping/fping-get-details-resp.json new file mode 100644 index 000000000..a5692832b --- /dev/null +++ b/doc/api_samples/os-fping/fping-get-details-resp.json @@ -0,0 +1,7 @@ +{ + "server": { + "alive": false, + "id": "f5e6fd6d-c0a3-4f9e-aabf-d69196b6d11a", + "project_id": "openstack" + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-fping/fping-get-details-resp.xml b/doc/api_samples/os-fping/fping-get-details-resp.xml new file mode 100644 index 000000000..5b3cb4785 --- /dev/null +++ b/doc/api_samples/os-fping/fping-get-details-resp.xml @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='UTF-8'?> +<server> + <project_id>openstack</project_id> + <id>69d3caab-ed51-4ee7-9d4b-941ee1b45484</id> + <alive>False</alive> +</server>
\ No newline at end of file diff --git a/doc/api_samples/os-fping/fping-get-resp.json b/doc/api_samples/os-fping/fping-get-resp.json new file mode 100644 index 000000000..11bf37edd --- /dev/null +++ b/doc/api_samples/os-fping/fping-get-resp.json @@ -0,0 +1,9 @@ +{ + "servers": [ + { + "alive": false, + "id": "1d1aea35-472b-40cf-9337-8eb68480aaa1", + "project_id": "openstack" + } + ] +}
\ No newline at end of file diff --git a/doc/api_samples/os-fping/fping-get-resp.xml b/doc/api_samples/os-fping/fping-get-resp.xml new file mode 100644 index 000000000..dbf03778b --- /dev/null +++ b/doc/api_samples/os-fping/fping-get-resp.xml @@ -0,0 +1,8 @@ +<?xml version='1.0' encoding='UTF-8'?> +<servers> + <server> + <project_id>openstack</project_id> + <id>6a576ebe-8777-473a-ab95-8df34a50dedd</id> + <alive>False</alive> + </server> +</servers>
\ No newline at end of file diff --git a/doc/api_samples/os-fping/server-post-req.json b/doc/api_samples/os-fping/server-post-req.json new file mode 100644 index 000000000..d88eb4122 --- /dev/null +++ b/doc/api_samples/os-fping/server-post-req.json @@ -0,0 +1,16 @@ +{ + "server" : { + "name" : "new-server-test", + "imageRef" : "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b", + "flavorRef" : "http://openstack.example.com/openstack/flavors/1", + "metadata" : { + "My Server Name" : "Apache1" + }, + "personality" : [ + { + "path" : "/etc/banner.txt", + "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ] + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-fping/server-post-req.xml b/doc/api_samples/os-fping/server-post-req.xml new file mode 100644 index 000000000..0a3c8bb53 --- /dev/null +++ b/doc/api_samples/os-fping/server-post-req.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b" flavorRef="http://openstack.example.com/openstack/flavors/1" name="new-server-test"> + <metadata> + <meta key="My Server Name">Apache1</meta> + </metadata> + <personality> + <file path="/etc/banner.txt"> + ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp + dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k + IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs + c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g + QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo + ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv + dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy + c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 + b25zLiINCg0KLVJpY2hhcmQgQmFjaA== + </file> + </personality> +</server>
\ No newline at end of file diff --git a/doc/api_samples/os-fping/server-post-resp.json b/doc/api_samples/os-fping/server-post-resp.json new file mode 100644 index 000000000..09d9fb612 --- /dev/null +++ b/doc/api_samples/os-fping/server-post-resp.json @@ -0,0 +1,16 @@ +{ + "server": { + "adminPass": "xrDLoBeMD28B", + "id": "3f69b6bd-00a8-4636-96ee-650093624304", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/3f69b6bd-00a8-4636-96ee-650093624304", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/3f69b6bd-00a8-4636-96ee-650093624304", + "rel": "bookmark" + } + ] + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-fping/server-post-resp.xml b/doc/api_samples/os-fping/server-post-resp.xml new file mode 100644 index 000000000..7f84ac03d --- /dev/null +++ b/doc/api_samples/os-fping/server-post-resp.xml @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='UTF-8'?> +<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="6ed1d112-6c33-4c8b-9780-e2f978bf5ffd" adminPass="uF9wWxBh3mWL"> + <metadata/> + <atom:link href="http://openstack.example.com/v2/openstack/servers/6ed1d112-6c33-4c8b-9780-e2f978bf5ffd" rel="self"/> + <atom:link href="http://openstack.example.com/openstack/servers/6ed1d112-6c33-4c8b-9780-e2f978bf5ffd" rel="bookmark"/> +</server>
\ No newline at end of file diff --git a/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.json b/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.json new file mode 100644 index 000000000..1d308d4ae --- /dev/null +++ b/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.json @@ -0,0 +1,17 @@ +{ + "instance_usage_audit_logs": { + "hosts_not_run": [ + "f4eb7cfd155f4574967f8b55a7faed75" + ], + "log": {}, + "num_hosts": 1, + "num_hosts_done": 0, + "num_hosts_not_run": 1, + "num_hosts_running": 0, + "overall_status": "0 of 1 hosts done. 0 errors.", + "period_beginning": "2012-12-01 00:00:00", + "period_ending": "2013-01-01 00:00:00", + "total_errors": 0, + "total_instances": 0 + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.xml b/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.xml new file mode 100644 index 000000000..82d157fb9 --- /dev/null +++ b/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.xml @@ -0,0 +1,16 @@ +<?xml version='1.0' encoding='UTF-8'?> +<instance_usage_audit_logs> + <total_errors>0</total_errors> + <total_instances>0</total_instances> + <log/> + <num_hosts_running>0</num_hosts_running> + <num_hosts_done>0</num_hosts_done> + <num_hosts_not_run>1</num_hosts_not_run> + <hosts_not_run> + <item>107debd115684f098d4c73ffac7ec515</item> + </hosts_not_run> + <overall_status>0 of 1 hosts done. 0 errors.</overall_status> + <period_ending>2013-01-01 00:00:00</period_ending> + <period_beginning>2012-12-01 00:00:00</period_beginning> + <num_hosts>1</num_hosts> +</instance_usage_audit_logs>
\ No newline at end of file diff --git a/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.json b/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.json new file mode 100644 index 000000000..2b5fe54c1 --- /dev/null +++ b/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.json @@ -0,0 +1,17 @@ +{ + "instance_usage_audit_log": { + "hosts_not_run": [ + "8e33da2b48684ef3ab165444d6a7384c" + ], + "log": {}, + "num_hosts": 1, + "num_hosts_done": 0, + "num_hosts_not_run": 1, + "num_hosts_running": 0, + "overall_status": "0 of 1 hosts done. 0 errors.", + "period_beginning": "2012-06-01 00:00:00", + "period_ending": "2012-07-01 00:00:00", + "total_errors": 0, + "total_instances": 0 + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.xml b/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.xml new file mode 100644 index 000000000..453689737 --- /dev/null +++ b/doc/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.xml @@ -0,0 +1,16 @@ +<?xml version='1.0' encoding='UTF-8'?> +<instance_usage_audit_log> + <total_errors>0</total_errors> + <total_instances>0</total_instances> + <log/> + <num_hosts_running>0</num_hosts_running> + <num_hosts_done>0</num_hosts_done> + <num_hosts_not_run>1</num_hosts_not_run> + <hosts_not_run> + <item>4b54478b73734afcbf0e2676a3303d1a</item> + </hosts_not_run> + <overall_status>0 of 1 hosts done. 0 errors.</overall_status> + <period_ending>2012-07-01 00:00:00</period_ending> + <period_beginning>2012-06-01 00:00:00</period_beginning> + <num_hosts>1</num_hosts> +</instance_usage_audit_log>
\ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py index 804080e79..0bdaeb08e 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -145,6 +145,8 @@ man_pages = [ [u'OpenStack'], 1), ('man/nova-novncproxy', 'nova-novncproxy', u'Cloud controller fabric', [u'OpenStack'], 1), + ('man/nova-spicehtml5proxy', 'nova-spicehtml5proxy', u'Cloud controller fabric', + [u'OpenStack'], 1), ('man/nova-objectstore', 'nova-objectstore', u'Cloud controller fabric', [u'OpenStack'], 1), ('man/nova-rootwrap', 'nova-rootwrap', u'Cloud controller fabric', diff --git a/doc/source/devref/aggregates.rst b/doc/source/devref/aggregates.rst index 979179768..ecc6329ba 100644 --- a/doc/source/devref/aggregates.rst +++ b/doc/source/devref/aggregates.rst @@ -23,7 +23,7 @@ Host aggregates can be regarded as a mechanism to further partition an availabil Xen Pool Host Aggregates =============== -Originally all aggregates were Xen resource pools, now a aggregate can be set up as a resource pool by giving the aggregate the correct key-value pair. +Originally all aggregates were Xen resource pools, now an aggregate can be set up as a resource pool by giving the aggregate the correct key-value pair. You can use aggregates for XenServer resource pools when you have multiple compute nodes installed (only XenServer/XCP via xenapi driver is currently supported), and you want to leverage the capabilities of the underlying hypervisor resource pools. For example, you want to enable VM live migration (i.e. VM migration within the pool) or enable host maintenance with zero-downtime for guest instances. Please, note that VM migration across pools (i.e. storage migration) is not yet supported in XenServer/XCP, but will be added when available. Bear in mind that the two migration techniques are not mutually exclusive and can be used in combination for a higher level of flexibility in your cloud management. @@ -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/doc/source/man/nova-spicehtml5proxy.rst b/doc/source/man/nova-spicehtml5proxy.rst new file mode 100644 index 000000000..4d0aaa202 --- /dev/null +++ b/doc/source/man/nova-spicehtml5proxy.rst @@ -0,0 +1,48 @@ +==================== +nova-spicehtml5proxy +==================== + +-------------------------------------------------------- +Websocket Proxy for OpenStack Nova SPICE HTML5 consoles. +-------------------------------------------------------- + +:Author: openstack@lists.launchpad.net +:Date: 2012-09-27 +:Copyright: OpenStack LLC +:Version: 2012.1 +:Manual section: 1 +:Manual group: cloud computing + +SYNOPSIS +======== + + nova-spicehtml5proxy [options] + +DESCRIPTION +=========== + +Websocket proxy that is compatible with OpenStack Nova +SPICE HTML5 consoles. + +OPTIONS +======= + + **General options** + +FILES +======== + +* /etc/nova/nova.conf +* /etc/nova/policy.json +* /etc/nova/rootwrap.conf +* /etc/nova/rootwrap.d/ + +SEE ALSO +======== + +* `OpenStack Nova <http://nova.openstack.org>`__ + +BUGS +==== + +* Nova is sourced in Launchpad so you can view current bugs at `OpenStack Nova <http://nova.openstack.org>`__ diff --git a/etc/nova/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 96118eb76..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 @@ -1756,7 +1774,7 @@ # value) #hyperv_attaching_volume_retry_count=10 -# The seconds to wait between an volume attachment attempt +# The seconds to wait between a volume attachment attempt # (integer value) #hyperv_wait_between_attach_retry=5 @@ -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,10 +2296,17 @@ # (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 requests to cinder (boolean +# value) +#cinder_api_insecure=false + [conductor] @@ -2476,7 +2501,7 @@ # # Do not set this out of dev/test environments. If a node does -# not have an fixed PXE IP address, volumes are exported with +# not have a fixed PXE IP address, volumes are exported with # globally opened ACL (boolean value) #use_unsafe_iscsi=false @@ -2546,4 +2571,4 @@ #keymap=en-us -# Total option count: 519 +# Total option count: 525 diff --git a/etc/nova/policy.json b/etc/nova/policy.json index fd1f9c2e0..1a446263f 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", @@ -82,7 +83,13 @@ "compute_extension:virtual_interfaces": "", "compute_extension:virtual_storage_arrays": "", "compute_extension:volumes": "", + "compute_extension:volume_attachments:index": "", + "compute_extension:volume_attachments:show": "", + "compute_extension:volume_attachments:create": "", + "compute_extension:volume_attachments:delete": "", "compute_extension:volumetypes": "", + "compute_extension:availability_zone:list": "", + "compute_extension:availability_zone:detail": "rule:admin_api", "volume:create": "", @@ -99,15 +106,20 @@ "volume_extension:volume_admin_actions:force_delete": "rule:admin_api", - "network:get_all_networks": "", - "network:get_network": "", - "network:delete_network": "", - "network:disassociate_network": "", + "network:get_all": "", + "network:get": "", + "network:create": "", + "network:delete": "", + "network:associate": "", + "network:disassociate": "", "network:get_vifs_by_instance": "", "network:allocate_for_instance": "", "network:deallocate_for_instance": "", "network:validate_networks": "", "network:get_instance_uuids_by_ip_filter": "", + "network:get_instance_id_by_floating_address": "", + "network:setup_networks_on_host": "", + "network:get_backdoor_port": "", "network:get_floating_ip": "", "network:get_floating_ip_pools": "", @@ -118,6 +130,9 @@ "network:deallocate_floating_ip": "", "network:associate_floating_ip": "", "network:disassociate_floating_ip": "", + "network:release_floating_ip": "", + "network:migrate_instance_start": "", + "network:migrate_instance_finish": "", "network:get_fixed_ip": "", "network:get_fixed_ip_by_address": "", diff --git a/etc/nova/rootwrap.d/baremetal-compute-pxe.filters b/etc/nova/rootwrap.d/baremetal-compute-pxe.filters deleted file mode 100644 index 35fa61723..000000000 --- a/etc/nova/rootwrap.d/baremetal-compute-pxe.filters +++ /dev/null @@ -1,11 +0,0 @@ -# nova-rootwrap command filters for compute nodes -# This file should be owned by (and only-writeable by) the root user - -[Filters] - -# nova/virt/baremetal/pxe.py: 'dnsmasq', ... -dnsmasq: CommandFilter, /usr/sbin/dnsmasq, root - -# nova/virt/baremetal/pxe.py: 'kill', '-TERM', str(dnsmasq_pid) -kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -15, -TERM - diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters index f344a1b1c..9562a23aa 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', ... @@ -172,3 +174,9 @@ vgs: CommandFilter, /sbin/vgs, root # nova/virt/baremetal/volume_driver.py: 'tgtadm', '--lld', 'iscsi', ... tgtadm: CommandFilter, /usr/sbin/tgtadm, root + +# nova/utils.py:read_file_as_root: 'cat', file_path +# (called from nova/virt/disk/vfs/localfs.py:VFSLocalFS.read_file) +read_passwd: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/passwd +read_shadow: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/shadow + diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 7cd7e1c7d..85b87e3e5 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -511,7 +511,13 @@ class Executor(wsgi.Application): except exception.KeyPairExists as ex: LOG.debug(_('KeyPairExists raised: %s'), unicode(ex), context=context) - return ec2_error(req, request_id, type(ex).__name__, unicode(ex)) + code = 'InvalidKeyPair.Duplicate' + return ec2_error(req, request_id, code, unicode(ex)) + except exception.InvalidKeypair as ex: + LOG.debug(_('InvalidKeypair raised: %s'), unicode(ex), + context) + code = 'InvalidKeyPair.Format' + return ec2_error(req, request_id, code, unicode(ex)) except exception.InvalidParameterValue as ex: LOG.debug(_('InvalidParameterValue raised: %s'), unicode(ex), context=context) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 414b2e969..b66b15852 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) @@ -301,7 +280,7 @@ class CloudController(object): host_services = {} for service in enabled_services: zone_hosts.setdefault(service['availability_zone'], []) - if not service['host'] in zone_hosts[service['availability_zone']]: + if service['host'] not in zone_hosts[service['availability_zone']]: zone_hosts[service['availability_zone']].append( service['host']) @@ -428,13 +407,14 @@ class CloudController(object): def describe_key_pairs(self, context, key_name=None, **kwargs): key_pairs = self.keypair_api.get_key_pairs(context, context.user_id) - if not key_name is None: + if key_name is not None: key_pairs = [x for x in key_pairs if x['name'] in key_name] #If looking for non existent key pair if key_name is not None and not key_pairs: msg = _('Could not find key pair(s): %s') % ','.join(key_name) - raise exception.EC2APIError(msg) + raise exception.KeypairNotFound(msg, + code="InvalidKeyPair.Duplicate") result = [] for key_pair in key_pairs: @@ -457,13 +437,7 @@ class CloudController(object): key_name) except exception.KeypairLimitExceeded: msg = _("Quota exceeded, too many key pairs.") - raise exception.EC2APIError(msg) - except exception.InvalidKeypair: - msg = _("Keypair data is invalid") - raise exception.EC2APIError(msg) - except exception.KeyPairExists: - msg = _("Key pair '%s' already exists.") % key_name - raise exception.KeyPairExists(msg) + raise exception.EC2APIError(msg, code='ResourceLimitExceeded') return {'keyName': key_name, 'keyFingerprint': keypair['fingerprint'], 'keyMaterial': keypair['private_key']} @@ -486,9 +460,6 @@ class CloudController(object): except exception.InvalidKeypair: msg = _("Keypair data is invalid") raise exception.EC2APIError(msg) - except exception.KeyPairExists: - msg = _("Key pair '%s' already exists.") % key_name - raise exception.EC2APIError(msg) return {'keyName': key_name, 'keyFingerprint': keypair['fingerprint']} @@ -556,7 +527,7 @@ class CloudController(object): def _rule_args_to_dict(self, context, kwargs): rules = [] - if not 'groups' in kwargs and not 'ip_ranges' in kwargs: + if 'groups' not in kwargs and 'ip_ranges' not in kwargs: rule = self._rule_dict_last_step(context, **kwargs) if rule: rules.append(rule) @@ -1020,18 +991,22 @@ class CloudController(object): def describe_instances(self, context, **kwargs): # Optional DescribeInstances argument instance_id = kwargs.get('instance_id', None) + filters = kwargs.get('filter', None) instances = self._enforce_valid_instance_ids(context, instance_id) return self._format_describe_instances(context, instance_id=instance_id, - instance_cache=instances) + instance_cache=instances, + filter=filters) def describe_instances_v6(self, context, **kwargs): # Optional DescribeInstancesV6 argument instance_id = kwargs.get('instance_id', None) + filters = kwargs.get('filter', None) instances = self._enforce_valid_instance_ids(context, instance_id) return self._format_describe_instances(context, instance_id=instance_id, instance_cache=instances, + filter=filters, use_v6=True) def _format_describe_instances(self, context, **kwargs): @@ -1574,11 +1549,11 @@ class CloudController(object): if attribute != 'launchPermission': raise exception.EC2APIError(_('attribute not supported: %s') % attribute) - if not 'user_group' in kwargs: + if 'user_group' not in kwargs: raise exception.EC2APIError(_('user or group not specified')) if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all': raise exception.EC2APIError(_('only group "all" is supported')) - if not operation_type in ['add', 'remove']: + if operation_type not in ['add', 'remove']: msg = _('operation_type must be add or remove') raise exception.EC2APIError(msg) LOG.audit(_("Updating image %s publicity"), image_id, context=context) diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index cfe0d7879..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' @@ -178,7 +179,7 @@ def ec2_vol_id_to_uuid(ec2_id): def is_ec2_timestamp_expired(request, expires=None): - """Checks the timestamp or expiry time included in a EC2 request + """Checks the timestamp or expiry time included in an EC2 request and returns true if the request is expired """ query_time = None diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index d812cef18..a76b74324 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -102,7 +102,7 @@ class APIMapper(routes.Mapper): class ProjectMapper(APIMapper): def resource(self, member_name, collection_name, **kwargs): - if not ('parent_resource' in kwargs): + if 'parent_resource' not in kwargs: kwargs['path_prefix'] = '{project_id}/' else: parent_resource = kwargs['parent_resource'] diff --git a/nova/api/openstack/compute/contrib/admin_actions.py b/nova/api/openstack/compute/contrib/admin_actions.py index fa7836b37..1c053ea59 100644 --- a/nova/api/openstack/compute/contrib/admin_actions.py +++ b/nova/api/openstack/compute/contrib/admin_actions.py @@ -130,7 +130,7 @@ class AdminActionsController(wsgi.Controller): @wsgi.action('resetNetwork') def _reset_network(self, req, id, body): - """Permit admins to reset networking on an server.""" + """Permit admins to reset networking on a server.""" context = req.environ['nova.context'] authorize(context, 'resetNetwork') try: diff --git a/nova/api/openstack/compute/contrib/aggregates.py b/nova/api/openstack/compute/contrib/aggregates.py index 91d138be4..84b0358a3 100644 --- a/nova/api/openstack/compute/contrib/aggregates.py +++ b/nova/api/openstack/compute/contrib/aggregates.py @@ -106,7 +106,7 @@ class AggregateController(object): raise exc.HTTPBadRequest for key in updates.keys(): - if not key in ["name", "availability_zone"]: + if key not in ["name", "availability_zone"]: raise exc.HTTPBadRequest try: diff --git a/nova/api/openstack/compute/contrib/availability_zone.py b/nova/api/openstack/compute/contrib/availability_zone.py index 2955b68eb..98c508bd7 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 service['host'] not 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/consoles.py b/nova/api/openstack/compute/contrib/consoles.py index 4f88d033c..4895a9e7b 100644 --- a/nova/api/openstack/compute/contrib/consoles.py +++ b/nova/api/openstack/compute/contrib/consoles.py @@ -53,10 +53,33 @@ class ConsolesController(wsgi.Controller): return {'console': {'type': console_type, 'url': output['url']}} + @wsgi.action('os-getSPICEConsole') + def get_spice_console(self, req, id, body): + """Get text console output.""" + context = req.environ['nova.context'] + authorize(context) + + # If type is not supplied or unknown, get_spice_console below will cope + console_type = body['os-getSPICEConsole'].get('type') + + try: + instance = self.compute_api.get(context, id) + output = self.compute_api.get_spice_console(context, + instance, + console_type) + except exception.InstanceNotFound as e: + raise webob.exc.HTTPNotFound(explanation=unicode(e)) + except exception.InstanceNotReady as e: + raise webob.exc.HTTPConflict(explanation=unicode(e)) + + return {'console': {'type': console_type, 'url': output['url']}} + def get_actions(self): """Return the actions the extension adds, as required by contract.""" actions = [extensions.ActionExtension("servers", "os-getVNCConsole", - self.get_vnc_console)] + self.get_vnc_console), + extensions.ActionExtension("servers", "os-getSPICEConsole", + self.get_spice_console)] return actions diff --git a/nova/api/openstack/compute/contrib/coverage_ext.py b/nova/api/openstack/compute/contrib/coverage_ext.py index 4b7d4e57f..6edf9244f 100644 --- a/nova/api/openstack/compute/contrib/coverage_ext.py +++ b/nova/api/openstack/compute/contrib/coverage_ext.py @@ -23,7 +23,6 @@ import sys import telnetlib import tempfile -from coverage import coverage from webob import exc from nova.api.openstack import extensions @@ -47,7 +46,6 @@ 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.compute_api = compute_api.API() self.network_api = network_api.API() self.conductor_api = conductor_api.API() @@ -57,6 +55,12 @@ class CoverageController(object): self.cert_api = cert_api.CertAPI() self.services = [] self.combine = False + try: + import coverage + self.coverInst = coverage.coverage(data_file=data_out) + self.has_coverage = True + except ImportError: + self.has_coverage = False super(CoverageController, self).__init__() def _find_services(self, req): @@ -238,6 +242,9 @@ class CoverageController(object): 'report': self._report_coverage, } authorize(req.environ['nova.context']) + if not self.has_coverage: + msg = _("Python coverage module is not installed.") + raise exc.HTTPServiceUnavailable(explanation=msg) for action, data in body.iteritems(): if action == 'stop': return _actions[action](req) diff --git a/nova/api/openstack/compute/contrib/flavorextraspecs.py b/nova/api/openstack/compute/contrib/flavorextraspecs.py index c8deb7b4c..12cc7d9ed 100644 --- a/nova/api/openstack/compute/contrib/flavorextraspecs.py +++ b/nova/api/openstack/compute/contrib/flavorextraspecs.py @@ -34,6 +34,15 @@ class ExtraSpecsTemplate(xmlutil.TemplateBuilder): return xmlutil.MasterTemplate(xmlutil.make_flat_dict('extra_specs'), 1) +class ExtraSpecTemplate(xmlutil.TemplateBuilder): + def construct(self): + sel = xmlutil.Selector(xmlutil.get_items, 0) + root = xmlutil.TemplateElement('extra_spec', selector=sel) + root.set('key', 0) + root.text = 1 + return xmlutil.MasterTemplate(root, 1) + + class FlavorExtraSpecsController(object): """The flavor extra specs API controller for the OpenStack API.""" @@ -70,12 +79,12 @@ class FlavorExtraSpecsController(object): raise exc.HTTPBadRequest(explanation=unicode(error)) return body - @wsgi.serializers(xml=ExtraSpecsTemplate) + @wsgi.serializers(xml=ExtraSpecTemplate) def update(self, req, flavor_id, id, body): context = req.environ['nova.context'] authorize(context) self._check_body(body) - if not id in body: + if id not in body: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) if len(body) > 1: @@ -87,10 +96,9 @@ class FlavorExtraSpecsController(object): body) except exception.MetadataLimitExceeded as error: raise exc.HTTPBadRequest(explanation=unicode(error)) - return body - @wsgi.serializers(xml=ExtraSpecsTemplate) + @wsgi.serializers(xml=ExtraSpecTemplate) def show(self, req, flavor_id, id): """Return a single extra spec item.""" context = req.environ['nova.context'] diff --git a/nova/api/openstack/compute/contrib/floating_ip_dns.py b/nova/api/openstack/compute/contrib/floating_ip_dns.py index fbea0acf9..bddf3580c 100644 --- a/nova/api/openstack/compute/contrib/floating_ip_dns.py +++ b/nova/api/openstack/compute/contrib/floating_ip_dns.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License +import socket import urllib import webob @@ -206,32 +207,40 @@ class FloatingIPDNSEntryController(object): context = req.environ['nova.context'] authorize(context) domain = _unquote_domain(domain_id) - name = id - entries = self.network_api.get_dns_entries_by_name(context, - name, domain) - entry = _create_dns_entry(entries[0], name, domain) - return _translate_dns_entry_view(entry) - - @wsgi.serializers(xml=FloatingIPDNSsTemplate) - def index(self, req, domain_id): - """Return a list of dns entries for the specified domain and ip.""" - context = req.environ['nova.context'] - authorize(context) - params = req.GET - floating_ip = params.get('ip') - domain = _unquote_domain(domain_id) + floating_ip = None + # Check whether id is a valid ipv4/ipv6 address. + try: + socket.inet_pton(socket.AF_INET, id) + floating_ip = id + except socket.error: + try: + socket.inet_pton(socket.AF_INET6, id) + floating_ip = id + except socket.error: + pass + + if floating_ip: + entries = self.network_api.get_dns_entries_by_address(context, + floating_ip, + domain) + else: + entries = self.network_api.get_dns_entries_by_name(context, id, + domain) - if not floating_ip: - raise webob.exc.HTTPUnprocessableEntity() + if not entries: + explanation = _("DNS entries not found.") + raise webob.exc.HTTPNotFound(explanation=explanation) - entries = self.network_api.get_dns_entries_by_address(context, - floating_ip, - domain) - entrylist = [_create_dns_entry(floating_ip, entry, domain) - for entry in entries] + if floating_ip: + entrylist = [_create_dns_entry(floating_ip, entry, domain) + for entry in entries] + dns_entries = _translate_dns_entries_view(entrylist) + return wsgi.ResponseObject(dns_entries, + xml=FloatingIPDNSsTemplate) - return _translate_dns_entries_view(entrylist) + entry = _create_dns_entry(entries[0], id, domain) + return _translate_dns_entry_view(entry) @wsgi.serializers(xml=FloatingIPDNSTemplate) def update(self, req, domain_id, id, body): diff --git a/nova/api/openstack/compute/contrib/floating_ips_bulk.py b/nova/api/openstack/compute/contrib/floating_ips_bulk.py index f5b8d24dd..db506a15d 100644 --- a/nova/api/openstack/compute/contrib/floating_ips_bulk.py +++ b/nova/api/openstack/compute/contrib/floating_ips_bulk.py @@ -80,13 +80,13 @@ class FloatingIPBulkController(object): context = req.environ['nova.context'] authorize(context) - if not 'floating_ips_bulk_create' in body: + if 'floating_ips_bulk_create' not in body: raise webob.exc.HTTPUnprocessableEntity() params = body['floating_ips_bulk_create'] LOG.debug(params) - if not 'ip_range' in params: + if 'ip_range' not in params: raise webob.exc.HTTPUnprocessableEntity() ip_range = params['ip_range'] diff --git a/nova/api/openstack/compute/contrib/simple_tenant_usage.py b/nova/api/openstack/compute/contrib/simple_tenant_usage.py index 8502e93c4..2313c00ac 100644 --- a/nova/api/openstack/compute/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/compute/contrib/simple_tenant_usage.py @@ -159,7 +159,7 @@ class SimpleTenantUsageController(object): info['uptime'] = delta.days * 24 * 3600 + delta.seconds - if not info['tenant_id'] in rval: + if info['tenant_id'] not in rval: summary = {} summary['tenant_id'] = info['tenant_id'] if detailed: diff --git a/nova/api/openstack/compute/contrib/volumes.py b/nova/api/openstack/compute/contrib/volumes.py index 47c717495..3fc503217 100644 --- a/nova/api/openstack/compute/contrib/volumes.py +++ b/nova/api/openstack/compute/contrib/volumes.py @@ -33,6 +33,15 @@ from nova import volume LOG = logging.getLogger(__name__) authorize = extensions.extension_authorizer('compute', 'volumes') +authorize_attach_index = extensions.extension_authorizer('compute', + 'volume_attachments:index') +authorize_attach_show = extensions.extension_authorizer('compute', + 'volume_attachments:show') +authorize_attach_create = extensions.extension_authorizer('compute', + 'volume_attachments:create') +authorize_attach_delete = extensions.extension_authorizer('compute', + 'volume_attachments:delete') + def _translate_volume_detail_view(context, vol): """Maps keys for volumes details view.""" @@ -329,6 +338,8 @@ class VolumeAttachmentController(wsgi.Controller): @wsgi.serializers(xml=VolumeAttachmentsTemplate) def index(self, req, server_id): """Returns the list of volume attachments for a given instance.""" + context = req.environ['nova.context'] + authorize_attach_index(context) return self._items(req, server_id, entity_maker=_translate_attachment_summary_view) @@ -337,6 +348,7 @@ class VolumeAttachmentController(wsgi.Controller): """Return data about the given volume attachment.""" context = req.environ['nova.context'] authorize(context) + authorize_attach_show(context) volume_id = id try: @@ -377,6 +389,7 @@ class VolumeAttachmentController(wsgi.Controller): """Attach a volume to an instance.""" context = req.environ['nova.context'] authorize(context) + authorize_attach_create(context) if not self.is_valid_body(body, 'volumeAttachment'): raise exc.HTTPUnprocessableEntity() @@ -423,6 +436,7 @@ class VolumeAttachmentController(wsgi.Controller): """Detach a volume from an instance.""" context = req.environ['nova.context'] authorize(context) + authorize_attach_delete(context) volume_id = id LOG.audit(_("Detach volume %s"), volume_id, context=context) diff --git a/nova/api/openstack/compute/image_metadata.py b/nova/api/openstack/compute/image_metadata.py index 1a467f3a7..7e78d6324 100644 --- a/nova/api/openstack/compute/image_metadata.py +++ b/nova/api/openstack/compute/image_metadata.py @@ -76,7 +76,7 @@ class Controller(object): expl = _('Incorrect request body format') raise exc.HTTPBadRequest(explanation=expl) - if not id in meta: + if id not in meta: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) if len(meta) > 1: @@ -105,7 +105,7 @@ class Controller(object): def delete(self, req, image_id, id): context = req.environ['nova.context'] image = self._get_image(context, image_id) - if not id in image['properties']: + if id not in image['properties']: msg = _("Invalid metadata key") raise exc.HTTPNotFound(explanation=msg) image['properties'].pop(id) 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 a6ad7a93a..88a52001c 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) @@ -561,17 +562,28 @@ class Controller(wsgi.Controller): req.cache_db_instance(instance) return instance - def _validate_server_name(self, value): + def _check_string_length(self, value, name, max_length=None): if not isinstance(value, basestring): - msg = _("Server name is not a string or unicode") + msg = _("%s is not a string or unicode") % name raise exc.HTTPBadRequest(explanation=msg) if not value.strip(): - msg = _("Server name is an empty string") + msg = _("%s is an empty string") % name + raise exc.HTTPBadRequest(explanation=msg) + + if max_length and len(value) > max_length: + msg = _("%(name)s can be at most %(max_length)s " + "characters.") % locals() raise exc.HTTPBadRequest(explanation=msg) - if not len(value) < 256: - msg = _("Server name must be less than 256 characters.") + def _validate_server_name(self, value): + self._check_string_length(value, 'Server name', max_length=255) + + def _validate_device_name(self, value): + self._check_string_length(value, 'Device name', max_length=255) + + if ' ' in value: + msg = _("Device name cannot include spaces.") raise exc.HTTPBadRequest(explanation=msg) def _get_injected_files(self, personality): @@ -739,7 +751,7 @@ class Controller(wsgi.Controller): server_dict = body['server'] password = self._get_server_admin_password(server_dict) - if not 'name' in server_dict: + if 'name' not in server_dict: msg = _("Server name is not defined") raise exc.HTTPBadRequest(explanation=msg) @@ -809,6 +821,7 @@ class Controller(wsgi.Controller): if self.ext_mgr.is_loaded('os-volumes'): block_device_mapping = server_dict.get('block_device_mapping', []) for bdm in block_device_mapping: + self._validate_device_name(bdm["device_name"]) if 'delete_on_termination' in bdm: bdm['delete_on_termination'] = utils.bool_from_str( bdm['delete_on_termination']) @@ -893,9 +906,13 @@ 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) + except exception.ImageNotActive as error: + raise exc.HTTPBadRequest(explanation=unicode(error)) except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) diff --git a/nova/api/openstack/compute/views/servers.py b/nova/api/openstack/compute/views/servers.py index d281f6a61..939515468 100644 --- a/nova/api/openstack/compute/views/servers.py +++ b/nova/api/openstack/compute/views/servers.py @@ -211,9 +211,9 @@ class ViewBuilder(common.ViewBuilder): if fault.get('details', None): is_admin = False - context = getattr(request, 'context', None) + context = request.environ["nova.context"] if context: - is_admin = getattr(request.context, 'is_admin', False) + is_admin = getattr(context, 'is_admin', False) if is_admin or fault['code'] != 500: fault_dict['details'] = fault["details"] diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 519669134..f68eff2a7 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -150,7 +150,7 @@ class Request(webob.Request): Does not do any body introspection, only checks header """ - if not "Content-Type" in self.headers: + if "Content-Type" not in self.headers: return None content_type = self.content_type @@ -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: @@ -919,6 +921,10 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return Fault(webob.exc.HTTPBadRequest(explanation=msg)) + if body: + LOG.info(_("Action: '%(action)s', body: %(body)s") % locals()) + LOG.debug(_("Calling method %s") % meth) + # Now, deserialize the request body... try: if content_type: @@ -1176,10 +1182,18 @@ class Fault(webob.exc.HTTPException): # Replace the body with fault details. code = self.wrapped_exc.status_int fault_name = self._fault_names.get(code, "computeFault") + explanation = self.wrapped_exc.explanation + offset = explanation.find("Traceback") + if offset is not -1: + LOG.debug(_("API request failed, fault raised to the top of" + " the stack. Detailed stacktrace %s") % + explanation) + explanation = explanation[0:offset - 1] + fault_data = { fault_name: { 'code': code, - 'message': self.wrapped_exc.explanation}} + 'message': explanation}} if code == 413: retry = self.wrapped_exc.headers.get('Retry-After', None) if retry: diff --git a/nova/availability_zones.py b/nova/availability_zones.py index cb5cce591..97faccc9f 100644 --- a/nova/availability_zones.py +++ b/nova/availability_zones.py @@ -13,11 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -"""utilities for multiple APIs.""" +"""Availability zone helper functions.""" from nova import db from nova.openstack.common import cfg -from nova.openstack.common import jsonutils from nova.openstack.common import log as logging availability_zone_opts = [ @@ -46,7 +45,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 +54,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 zone not 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/block_device.py b/nova/block_device.py index c95961911..7d43d15cb 100644 --- a/nova/block_device.py +++ b/nova/block_device.py @@ -17,8 +17,11 @@ import re +from nova.openstack.common import log as logging from nova.virt import driver +LOG = logging.getLogger(__name__) + DEFAULT_ROOT_DEV_NAME = '/dev/sda1' _DEFAULT_MAPPINGS = {'ami': 'sda1', 'ephemeral0': 'sda2', @@ -144,3 +147,22 @@ def match_device(device): if not match: return None return match.groups() + + +def volume_in_mapping(mount_device, block_device_info): + block_device_list = [strip_dev(vol['mount_device']) + for vol in + driver.block_device_info_get_mapping( + block_device_info)] + + swap = driver.block_device_info_get_swap(block_device_info) + if driver.swap_is_usable(swap): + block_device_list.append(strip_dev(swap['device_name'])) + + block_device_list += [strip_dev(ephemeral['device_name']) + for ephemeral in + driver.block_device_info_get_ephemerals( + block_device_info)] + + LOG.debug(_("block_device_list %s"), block_device_list) + return strip_dev(mount_device) in block_device_list diff --git a/nova/common/memorycache.py b/nova/common/memorycache.py index f77b3f51a..86057b6ae 100644 --- a/nova/common/memorycache.py +++ b/nova/common/memorycache.py @@ -70,7 +70,7 @@ class Client(object): def add(self, key, value, time=0, min_compress_len=0): """Sets the value for a key if it doesn't exist.""" - if not self.get(key) is None: + if self.get(key) is not None: return False return self.set(key, value, time, min_compress_len) diff --git a/nova/compute/api.py b/nova/compute/api.py index 8ba6b97aa..a9d0a1bdd 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']) @@ -504,6 +508,13 @@ class API(base.Base): availability_zone, forced_host = self._handle_availability_zone( availability_zone) + system_metadata = {} + instance_type_props = ['id', 'name', 'memory_mb', 'vcpus', + 'root_gb', 'ephemeral_gb', 'flavorid', + 'swap', 'rxtx_factor', 'vcpu_weight'] + for k in instance_type_props: + system_metadata["instance_type_%s" % k] = instance_type[k] + base_options = { 'reservation_id': reservation_id, 'image_ref': image_href, @@ -533,7 +544,8 @@ class API(base.Base): 'access_ip_v6': access_ip_v6, 'availability_zone': availability_zone, 'root_device_name': root_device_name, - 'progress': 0} + 'progress': 0, + 'system_metadata': system_metadata} options_from_image = self._inherit_properties_from_image( image, auto_disk_config) @@ -554,6 +566,11 @@ class API(base.Base): security_group, block_device_mapping) instances.append(instance) instance_uuids.append(instance['uuid']) + self._validate_bdm(context, instance) + # send a state update notification for the initial create to + # show it going from non-existent to BUILDING + notifications.send_update_with_states(context, instance, None, + vm_states.BUILDING, None, None, service="api") # In the case of any exceptions, attempt DB cleanup and rollback the # quota reservations. @@ -700,6 +717,23 @@ class API(base.Base): self.db.block_device_mapping_update_or_create(elevated_context, values) + def _validate_bdm(self, context, instance): + for bdm in self.db.block_device_mapping_get_all_by_instance( + context, instance['uuid']): + # NOTE(vish): For now, just make sure the volumes are accessible. + snapshot_id = bdm.get('snapshot_id') + volume_id = bdm.get('volume_id') + if volume_id is not None: + try: + self.volume_api.get(context, volume_id) + except Exception: + raise exception.InvalidBDMVolume(id=volume_id) + elif snapshot_id is not None: + try: + self.volume_api.get_snapshot(context, snapshot_id) + except Exception: + raise exception.InvalidBDMSnapshot(id=snapshot_id) + def _populate_instance_for_bdm(self, context, instance, instance_type, image, block_device_mapping): """Populate instance block device mapping information.""" @@ -814,11 +848,6 @@ class API(base.Base): self._populate_instance_for_bdm(context, instance, instance_type, image, block_device_mapping) - # send a state update notification for the initial create to - # show it going from non-existent to BUILDING - notifications.send_update_with_states(context, instance, None, - vm_states.BUILDING, None, None, service="api") - return instance def _check_create_policies(self, context, availability_zone, @@ -946,11 +975,10 @@ class API(base.Base): if (old['vm_state'] != vm_states.SOFT_DELETED and old['task_state'] not in (task_states.DELETING, task_states.SOFT_DELETING)): - reservations = QUOTAS.reserve(context, - project_id=project_id, - instances=-1, - cores=-instance['vcpus'], - ram=-instance['memory_mb']) + reservations = self._create_reservations(context, + old, + updated, + project_id) if not host: # Just update database, nothing else we can do @@ -1026,6 +1054,45 @@ class API(base.Base): reservations, project_id=project_id) + def _create_reservations(self, context, old_instance, new_instance, + project_id): + instance_vcpus = old_instance['vcpus'] + instance_memory_mb = old_instance['memory_mb'] + # NOTE(wangpan): if the instance is resizing, and the resources + # are updated to new instance type, we should use + # the old instance type to create reservation. + # see https://bugs.launchpad.net/nova/+bug/1099729 for more details + if old_instance['task_state'] in (task_states.RESIZE_MIGRATED, + task_states.RESIZE_FINISH): + get_migration = self.db.migration_get_by_instance_and_status + try: + migration_ref = get_migration(context.elevated(), + old_instance['uuid'], 'post-migrating') + except exception.MigrationNotFoundByStatus: + migration_ref = None + if (migration_ref and + new_instance['instance_type_id'] == + migration_ref['new_instance_type_id']): + old_inst_type_id = migration_ref['old_instance_type_id'] + get_inst_type_by_id = instance_types.get_instance_type + try: + old_inst_type = get_inst_type_by_id(old_inst_type_id) + except exception.InstanceTypeNotFound: + LOG.warning(_("instance type %(old_inst_type_id)d " + "not found") % locals()) + pass + else: + instance_vcpus = old_inst_type['vcpus'] + instance_memory_mb = old_inst_type['memory_mb'] + LOG.debug(_("going to delete a resizing instance")) + + reservations = QUOTAS.reserve(context, + project_id=project_id, + instances=-1, + cores=-instance_vcpus, + ram=-instance_memory_mb) + return reservations + def _local_delete(self, context, instance, bdms): LOG.warning(_("instance's host %s is down, deleting from " "database") % instance['host'], instance=instance) @@ -1182,8 +1249,10 @@ class API(base.Base): # NOTE(ameade): we still need to support integer ids for ec2 if uuidutils.is_uuid_like(instance_id): instance = self.db.instance_get_by_uuid(context, instance_id) - else: + elif utils.is_int_like(instance_id): instance = self.db.instance_get(context, instance_id) + else: + raise exception.InstanceNotFound(instance_id=instance_id) check_policy(context, 'get', instance) @@ -1313,9 +1382,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. @@ -1324,13 +1390,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. @@ -1341,9 +1413,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. @@ -1351,6 +1420,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 @@ -2031,6 +2105,29 @@ class API(base.Base): return connect_info @wrap_check_policy + def get_spice_console(self, context, instance, console_type): + """Get a url to an instance Console.""" + if not instance['host']: + raise exception.InstanceNotReady(instance_id=instance['uuid']) + + connect_info = self.compute_rpcapi.get_spice_console(context, + instance=instance, console_type=console_type) + + self.consoleauth_rpcapi.authorize_console(context, + connect_info['token'], console_type, connect_info['host'], + connect_info['port'], connect_info['internal_access_path']) + + return {'url': connect_info['access_url']} + + def get_spice_connect_info(self, context, instance, console_type): + """Used in a child cell to get console info.""" + if not instance['host']: + raise exception.InstanceNotReady(instance_id=instance['uuid']) + connect_info = self.compute_rpcapi.get_spice_console(context, + instance=instance, console_type=console_type) + return connect_info + + @wrap_check_policy def get_console_output(self, context, instance, tail_length=None): """Get console output for an instance.""" return self.compute_rpcapi.get_console_output(context, @@ -2135,6 +2232,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) @@ -2146,6 +2246,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/cells_api.py b/nova/compute/cells_api.py index d1d9a11d2..d5427a04b 100644 --- a/nova/compute/cells_api.py +++ b/nova/compute/cells_api.py @@ -439,6 +439,21 @@ class ComputeCellsAPI(compute_api.API): connect_info['port'], connect_info['internal_access_path']) return {'url': connect_info['access_url']} + @wrap_check_policy + @validate_cell + def get_spice_console(self, context, instance, console_type): + """Get a url to a SPICE Console.""" + if not instance['host']: + raise exception.InstanceNotReady(instance_id=instance['uuid']) + + connect_info = self._call_to_cells(context, instance, + 'get_spice_connect_info', console_type) + + self.consoleauth_rpcapi.authorize_console(context, + connect_info['token'], console_type, connect_info['host'], + connect_info['port'], connect_info['internal_access_path']) + return {'url': connect_info['access_url']} + @validate_cell def get_console_output(self, context, instance, *args, **kwargs): """Get console output for an an instance.""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 64931dfee..0ad5c1dc8 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -57,13 +57,11 @@ from nova import network from nova.network import model as network_model from nova.openstack.common import cfg from nova.openstack.common import excutils -from nova.openstack.common import importutils from nova.openstack.common import jsonutils from nova.openstack.common import lockutils from nova.openstack.common import log as logging from nova.openstack.common.notifier import api as notifier from nova.openstack.common import rpc -from nova.openstack.common.rpc import common as rpc_common from nova.openstack.common import timeutils from nova import paths from nova import quota @@ -172,7 +170,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 @@ -230,7 +227,8 @@ def wrap_instance_fault(function): with excutils.save_and_reraise_exception(): compute_utils.add_instance_fault_from_exc(context, - kwargs['instance']['uuid'], e, sys.exc_info()) + self.conductor_api, kwargs['instance'], + e, sys.exc_info()) return decorated_function @@ -293,7 +291,7 @@ class ComputeVirtAPI(virtapi.VirtAPI): class ComputeManager(manager.SchedulerDependentManager): """Manages the running instances from creation to destruction.""" - RPC_API_VERSION = '2.23' + RPC_API_VERSION = '2.24' def __init__(self, compute_driver=None, *args, **kwargs): """Load configuration options and connect to the hypervisor.""" @@ -301,8 +299,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 @@ -463,6 +459,11 @@ class ComputeManager(manager.SchedulerDependentManager): except NotImplementedError: LOG.warning(_('Hypervisor driver does not support ' 'resume guests'), instance=instance) + except Exception: + # NOTE(vish): The instance failed to resume, so we set the + # instance to error and attempt to continue. + LOG.warning(_('Failed to resume instance'), instance=instance) + self._set_instance_error_state(context, instance['uuid']) elif drv_state == power_state.RUNNING: # VMwareAPI drivers will raise an exception @@ -564,15 +565,8 @@ class ComputeManager(manager.SchedulerDependentManager): def _get_instance_nw_info(self, context, instance): """Get a list of dictionaries of network data of an instance.""" - # Get the network info from network API, but don't let it - # update the cache, as that will hit the DB. We'll update - # the cache ourselves via the conductor. network_info = self.network_api.get_instance_nw_info(context, - instance, update_cache=False) - cache = {'network_info': network_info.json()} - self.conductor_api.instance_info_cache_update(context, - instance, - cache) + instance, conductor_api=self.conductor_api) return network_info def _legacy_nw_info(self, network_info): @@ -640,13 +634,15 @@ class ComputeManager(manager.SchedulerDependentManager): 'delete_on_termination': bdm['delete_on_termination']} block_device_mapping.append(bdmap) - return { + block_device_info = { 'root_device_name': instance['root_device_name'], 'swap': swap, 'ephemerals': ephemerals, 'block_device_mapping': block_device_mapping } + return block_device_info + def _run_instance(self, context, request_spec, filter_properties, requested_networks, injected_files, admin_password, is_first_time, node, instance): @@ -654,7 +650,7 @@ class ComputeManager(manager.SchedulerDependentManager): context = context.elevated() try: - self._check_instance_not_already_created(context, instance) + self._check_instance_exists(context, instance) image_meta = self._check_image_size(context, instance) if node is None: @@ -668,25 +664,35 @@ class ComputeManager(manager.SchedulerDependentManager): extra_usage_info = {} self._start_building(context, instance) + self._notify_about_instance_usage( context, instance, "create.start", extra_usage_info=extra_usage_info) + network_info = None bdms = self.conductor_api.block_device_mapping_get_all_by_instance( context, instance) + rt = self._get_resource_tracker(node) try: limits = filter_properties.get('limits', {}) with rt.instance_claim(context, instance, limits): macs = self.driver.macs_for_instance(instance) + network_info = self._allocate_network(context, instance, requested_networks, macs) - block_device_info = self._prep_block_device(context, - instance, bdms) + + self._instance_update( + context, instance['uuid'], + vm_state=vm_states.BUILDING, + task_state=task_states.BLOCK_DEVICE_MAPPING) + + block_device_info = self._prep_block_device( + context, instance, bdms) + instance = self._spawn(context, instance, image_meta, network_info, block_device_info, injected_files, admin_password) - except exception.InstanceNotFound: # the instance got deleted during the spawn try: @@ -730,8 +736,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_uuid = instance['uuid'] rescheduled = False - compute_utils.add_instance_fault_from_exc(context, instance_uuid, - exc_info[0], exc_info=exc_info) + compute_utils.add_instance_fault_from_exc(context, self.conductor_api, + instance, exc_info[1], exc_info=exc_info) try: self._deallocate_network(context, instance) @@ -838,11 +844,10 @@ class ComputeManager(manager.SchedulerDependentManager): **update_info) return instance - def _check_instance_not_already_created(self, context, instance): + def _check_instance_exists(self, context, instance): """Ensure an instance with the same name is not already present.""" if self.driver.instance_exists(instance['name']): - _msg = _("Instance has already been created") - raise exception.Invalid(_msg) + raise exception.InstanceExists(name=instance['name']) def _check_image_size(self, context, instance): """Ensure image is smaller than the maximum size allowed by the @@ -923,7 +928,7 @@ class ComputeManager(manager.SchedulerDependentManager): network_info = self.network_api.allocate_for_instance( context, instance, vpn=is_vpn, requested_networks=requested_networks, - macs=macs) + macs=macs, conductor_api=self.conductor_api) except Exception: LOG.exception(_('Instance failed network setup'), instance=instance) @@ -936,9 +941,6 @@ class ComputeManager(manager.SchedulerDependentManager): def _prep_block_device(self, context, instance, bdms): """Set up the block device for an instance with error logging.""" - instance = self._instance_update(context, instance['uuid'], - vm_state=vm_states.BUILDING, - task_state=task_states.BLOCK_DEVICE_MAPPING) try: return self._setup_block_device_mapping(context, instance, bdms) except Exception: @@ -1130,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 @@ -1287,6 +1288,7 @@ class ComputeManager(manager.SchedulerDependentManager): :param injected_files: Files to inject :param new_pass: password to set on rebuilt instance :param orig_sys_metadata: instance system metadata from pre-rebuild + :param bdms: block-device-mappings to use for rebuild :param recreate: True if instance should be recreated with same disk :param on_shared_storage: True if instance files on shared storage """ @@ -1298,39 +1300,28 @@ class ComputeManager(manager.SchedulerDependentManager): instance=instance) if recreate: - if not self.driver.capabilities["supports_recreate"]: - # if driver doesn't support recreate return with failure - _msg = _('instance recreate is not implemented ' - 'by this driver.') + raise exception.InstanceRecreateNotSupported - LOG.warn(_msg, instance=instance) - self._instance_update(context, - instance['uuid'], - task_state=None, - expected_task_state=task_states. - REBUILDING) - raise exception.Invalid(_msg) - - self._check_instance_not_already_created(context, instance) + self._check_instance_exists(context, instance) - # to cover case when admin expects that instance files are on + # To cover case when admin expects that instance files are on # shared storage, but not accessible and vice versa if on_shared_storage != self.driver.instance_on_disk(instance): - _msg = _("Invalid state of instance files on " - "shared storage") - raise exception.Invalid(_msg) + raise exception.InvalidSharedStorage( + _("Invalid state of instance files on shared" + " storage")) if on_shared_storage: - LOG.info(_('disk on shared storage,' - 'recreating using existing disk')) + LOG.info(_('disk on shared storage, recreating using' + ' existing disk')) else: image_ref = orig_image_ref = instance['image_ref'] - LOG.info(_("disk not on shared storage" - "rebuilding from: '%s'") % str(image_ref)) + LOG.info(_("disk not on shared storagerebuilding from:" + " '%s'") % str(image_ref)) - instance = self._instance_update(context, instance['uuid'], - host=self.host) + instance = self._instance_update( + context, instance['uuid'], host=self.host) if image_ref: image_meta = _get_image_meta(context, image_ref) @@ -1351,64 +1342,64 @@ class ComputeManager(manager.SchedulerDependentManager): self._notify_about_instance_usage(context, instance, "rebuild.start", extra_usage_info=extra_usage_info) - current_power_state = self._get_power_state(context, instance) - instance = self._instance_update(context, instance['uuid'], - power_state=current_power_state, + instance = self._instance_update( + context, instance['uuid'], + power_state=self._get_power_state(context, instance), task_state=task_states.REBUILDING, expected_task_state=task_states.REBUILDING) if recreate: - # Detaching volumes. - for bdm in self._get_instance_volume_bdms(context, instance): - volume = self.volume_api.get(context, bdm['volume_id']) + self.network_api.setup_networks_on_host( + context, instance, self.host) - # We can't run volume disconnect on source because - # the host is down. Just marking volume as detached - # in db, anyway the zombie instance going to be deleted - # from source during init_host when host comes back - self.volume_api.detach(context.elevated(), volume) + network_info = self._get_instance_nw_info(context, instance) - self.network_api.setup_networks_on_host(context, - instance, self.host) - else: - network_info = self._get_instance_nw_info(context, instance) + if bdms is None: + bdms = self.conductor_api.\ + block_device_mapping_get_all_by_instance( + context, instance) + + # NOTE(sirp): this detach is necessary b/c we will reattach the + # volumes in _prep_block_devices below. + for bdm in self._get_volume_bdms(bdms): + volume = self.volume_api.get(context, bdm['volume_id']) + self.volume_api.detach(context, volume) + + if not recreate: + block_device_info = self._get_volume_block_device_info( + self._get_volume_bdms(bdms)) self.driver.destroy(instance, - self._legacy_nw_info(network_info)) + self._legacy_nw_info(network_info), + block_device_info=block_device_info) - instance = self._instance_update(context, instance['uuid'], + instance = self._instance_update( + context, instance['uuid'], task_state=task_states.REBUILD_BLOCK_DEVICE_MAPPING, expected_task_state=task_states.REBUILDING) + block_device_info = self._prep_block_device( + context, instance, bdms) + instance['injected_files'] = injected_files - network_info = self._get_instance_nw_info(context, instance) - if bdms is None: - capi = self.conductor_api - bdms = capi.block_device_mapping_get_all_by_instance( - context, instance) - device_info = self._setup_block_device_mapping(context, instance, - bdms) - expected_task_state = task_states.REBUILD_BLOCK_DEVICE_MAPPING - instance = self._instance_update(context, instance['uuid'], + instance = self._instance_update( + context, instance['uuid'], task_state=task_states.REBUILD_SPAWNING, - expected_task_state=expected_task_state) - - admin_password = new_pass + expected_task_state= + task_states.REBUILD_BLOCK_DEVICE_MAPPING) self.driver.spawn(context, instance, image_meta, - [], admin_password, - self._legacy_nw_info(network_info), - device_info) + [], new_pass, + network_info=self._legacy_nw_info(network_info), + block_device_info=block_device_info) - current_power_state = self._get_power_state(context, instance) - instance = self._instance_update(context, - instance['uuid'], - power_state=current_power_state, - vm_state=vm_states.ACTIVE, - task_state=None, - expected_task_state=task_states. - REBUILD_SPAWNING, - launched_at=timeutils.utcnow()) + instance = self._instance_update( + context, instance['uuid'], + power_state=self._get_power_state(context, instance), + vm_state=vm_states.ACTIVE, + task_state=None, + expected_task_state=task_states.REBUILD_SPAWNING, + launched_at=timeutils.utcnow()) LOG.info(_("bringing vm to original state: '%s'") % orig_vm_state) if orig_vm_state == vm_states.STOPPED: @@ -1464,7 +1455,7 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.error(_('Cannot reboot instance: %(exc)s'), locals(), context=context, instance=instance) compute_utils.add_instance_fault_from_exc(context, - instance['uuid'], exc, sys.exc_info()) + self.conductor_api, instance, exc, sys.exc_info()) # Fall through and reset task_state to None current_power_state = self._get_power_state(context, instance) @@ -1662,8 +1653,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') @@ -1984,8 +1974,8 @@ class ComputeManager(manager.SchedulerDependentManager): rescheduled = False instance_uuid = instance['uuid'] - compute_utils.add_instance_fault_from_exc(context, instance_uuid, - exc_info[0], exc_info=exc_info) + compute_utils.add_instance_fault_from_exc(context, self.conductor_api, + instance, exc_info[0], exc_info=exc_info) try: scheduler_method = self.scheduler_rpcapi.prep_resize @@ -2181,9 +2171,8 @@ class ComputeManager(manager.SchedulerDependentManager): self._notify_about_instance_usage( context, instance, "create_ip.start") - self.network_api.add_fixed_ip_to_instance(context, - instance, - network_id) + self.network_api.add_fixed_ip_to_instance(context, instance, + network_id, conductor_api=self.conductor_api) network_info = self._inject_network_info(context, instance=instance) self.reset_network(context, instance) @@ -2202,9 +2191,8 @@ class ComputeManager(manager.SchedulerDependentManager): self._notify_about_instance_usage( context, instance, "delete_ip.start") - self.network_api.remove_fixed_ip_from_instance(context, - instance, - address) + self.network_api.remove_fixed_ip_from_instance(context, instance, + address, conductor_api=self.conductor_api) network_info = self._inject_network_info(context, instance=instance) @@ -2376,6 +2364,9 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.debug(_("Getting vnc console"), instance=instance) token = str(uuid.uuid4()) + if not CONF.vnc_enabled: + raise exception.ConsoleTypeInvalid(console_type=console_type) + if console_type == 'novnc': # For essex, novncproxy_base_url must include the full path # including the html file (like http://myhost/vnc_auto.html) @@ -2393,6 +2384,33 @@ class ComputeManager(manager.SchedulerDependentManager): return connect_info + @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) + @wrap_instance_fault + def get_spice_console(self, context, console_type, instance): + """Return connection information for a spice console.""" + context = context.elevated() + LOG.debug(_("Getting spice console"), instance=instance) + token = str(uuid.uuid4()) + + if not CONF.spice.enabled: + raise exception.ConsoleTypeInvalid(console_type=console_type) + + if console_type == 'spice-html5': + # For essex, spicehtml5proxy_base_url must include the full path + # including the html file (like http://myhost/spice_auto.html) + access_url = '%s?token=%s' % (CONF.spice.html5proxy_base_url, + token) + else: + raise exception.ConsoleTypeInvalid(console_type=console_type) + + # Retrieve connect info from driver, and then decorate with our + # access info token + connect_info = self.driver.get_spice_console(instance) + connect_info['token'] = token + connect_info['access_url'] = access_url + + return connect_info + def _attach_volume_boot(self, context, instance, volume, mountpoint): """Attach a volume to an instance at boot time. So actual attach is done by instance creation""" @@ -2418,8 +2436,11 @@ class ComputeManager(manager.SchedulerDependentManager): @lockutils.synchronized(instance['uuid'], 'nova-') def do_reserve(): + bdms = self.conductor_api.block_device_mapping_get_all_by_instance( + context, instance) result = compute_utils.get_device_name_for_instance(context, instance, + bdms, device) # NOTE(vish): create bdm here to avoid race condition values = {'instance_uuid': instance['uuid'], @@ -2503,21 +2524,21 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.audit(_('Detach volume %(volume_id)s from mountpoint %(mp)s'), locals(), context=context, instance=instance) - if not self.driver.instance_exists(instance['name']): - LOG.warn(_('Detaching volume from unknown instance'), - context=context, instance=instance) connection_info = jsonutils.loads(bdm['connection_info']) # NOTE(vish): We currently don't use the serial when disconnecting, # but added for completeness in case we ever do. if connection_info and 'serial' not in connection_info: connection_info['serial'] = volume_id try: + if not self.driver.instance_exists(instance['name']): + LOG.warn(_('Detaching volume from unknown instance'), + context=context, instance=instance) self.driver.detach_volume(connection_info, instance, 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) @@ -2823,9 +2844,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'], @@ -3022,8 +3046,8 @@ class ComputeManager(manager.SchedulerDependentManager): vm_state = instance['vm_state'] task_state = instance['task_state'] if vm_state != vm_states.RESIZED or task_state is not None: - reason = _("In states %(vm_state)s/%(task_state)s, not" - "RESIZED/None") + reason = _("In states %(vm_state)s/%(task_state)s, not " + "RESIZED/None") _set_migration_to_error(migration, reason % locals(), instance=instance) continue @@ -3037,7 +3061,9 @@ class ComputeManager(manager.SchedulerDependentManager): @manager.periodic_task def _instance_usage_audit(self, context): if CONF.instance_usage_audit: - if not compute_utils.has_audit_been_run(context, self.host): + if not compute_utils.has_audit_been_run(context, + self.conductor_api, + self.host): begin, end = utils.last_completed_audit_period() capi = self.conductor_api instances = capi.instance_get_active_by_window_joined( @@ -3054,6 +3080,7 @@ class ComputeManager(manager.SchedulerDependentManager): number_instances=num_instances)) start_time = time.time() compute_utils.start_instance_usage_audit(context, + self.conductor_api, begin, end, self.host, num_instances) for instance in instances: @@ -3069,6 +3096,7 @@ class ComputeManager(manager.SchedulerDependentManager): instance=instance) errors += 1 compute_utils.finish_instance_usage_audit(context, + self.conductor_api, begin, end, self.host, errors, "Instance usage audit ran " @@ -3333,10 +3361,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) @@ -3344,6 +3370,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/resource_tracker.py b/nova/compute/resource_tracker.py index f3c3ae7a3..f5d3a8008 100644 --- a/nova/compute/resource_tracker.py +++ b/nova/compute/resource_tracker.py @@ -25,7 +25,6 @@ from nova.compute import task_states from nova.compute import vm_states from nova import conductor from nova import context -from nova import db from nova import exception from nova.openstack.common import cfg from nova.openstack.common import importutils @@ -252,14 +251,15 @@ class ResourceTracker(object): self._report_hypervisor_resource_view(resources) # Grab all instances assigned to this node: - instances = db.instance_get_all_by_host_and_node(context, self.host, - self.nodename) + instances = self.conductor_api.instance_get_all_by_host_and_node( + context, self.host, self.nodename) # Now calculate usage based on instance utilization: self._update_usage_from_instances(resources, instances) # Grab all in-progress migrations: - migrations = db.migration_get_in_progress_by_host_and_node(context, + capi = self.conductor_api + migrations = capi.migration_get_in_progress_by_host_and_node(context, self.host, self.nodename) self._update_usage_from_migrations(resources, migrations) @@ -293,22 +293,25 @@ class ResourceTracker(object): # Need to create the ComputeNode record: resources['service_id'] = service['id'] self._create(context, resources) - LOG.info(_('Compute_service record created for %s ') % self.host) + LOG.info(_('Compute_service record created for %(host)s:%(node)s') + % {'host': self.host, 'node': self.nodename}) else: # just update the record: self._update(context, resources, prune_stats=True) - LOG.info(_('Compute_service record updated for %s ') % self.host) + LOG.info(_('Compute_service record updated for %(host)s:%(node)s') + % {'host': self.host, 'node': self.nodename}) def _create(self, context, values): """Create the compute node in the DB.""" # initialize load stats from existing instances: - compute_node = db.compute_node_create(context, values) - self.compute_node = dict(compute_node) + self.compute_node = self.conductor_api.compute_node_create(context, + values) def _get_service(self, context): try: - return db.service_get_by_compute_host(context, self.host) + return self.conductor_api.service_get_by_compute_host(context, + self.host) except exception.NotFound: LOG.warn(_("No service record for host %s"), self.host) @@ -347,9 +350,10 @@ class ResourceTracker(object): def _update(self, context, values, prune_stats=False): """Persist the compute node updates to the DB.""" - compute_node = db.compute_node_update(context, - self.compute_node['id'], values, prune_stats) - self.compute_node = dict(compute_node) + if "service" in self.compute_node: + del self.compute_node['service'] + self.compute_node = self.conductor_api.compute_node_update( + context, self.compute_node, values, prune_stats) def confirm_resize(self, context, migration, status='confirmed'): """Cleanup usage for a confirmed resize.""" diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py index 3e7ed1cfd..525d1adc7 100644 --- a/nova/compute/rpcapi.py +++ b/nova/compute/rpcapi.py @@ -158,6 +158,7 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy): 2.22 - Add recreate, on_shared_storage and host arguments to rebuild_instance() 2.23 - Remove network_info from reboot_instance + 2.24 - Added get_spice_console method ''' # @@ -295,6 +296,13 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy): instance=instance_p, console_type=console_type), topic=_compute_topic(self.topic, ctxt, None, instance)) + def get_spice_console(self, ctxt, instance, console_type): + instance_p = jsonutils.to_primitive(instance) + return self.call(ctxt, self.make_msg('get_spice_console', + instance=instance_p, console_type=console_type), + topic=_compute_topic(self.topic, ctxt, None, instance), + version='2.24') + def host_maintenance_mode(self, ctxt, host_param, mode, host): '''Set host maintenance mode diff --git a/nova/compute/utils.py b/nova/compute/utils.py index 0c475d082..8d363fa1c 100644 --- a/nova/compute/utils.py +++ b/nova/compute/utils.py @@ -22,7 +22,6 @@ import traceback from nova import block_device from nova.compute import instance_types -from nova import db from nova import exception from nova.network import model as network_model from nova import notifications @@ -37,14 +36,8 @@ 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_uuid, fault, exc_info=None): +def add_instance_fault_from_exc(context, conductor, + instance, fault, exc_info=None): """Adds the specified fault to the database.""" code = 500 @@ -62,15 +55,16 @@ def add_instance_fault_from_exc(context, instance_uuid, fault, exc_info=None): details += '\n' + ''.join(traceback.format_tb(tb)) values = { - 'instance_uuid': instance_uuid, + 'instance_uuid': instance['uuid'], 'code': code, 'message': unicode(message), 'details': unicode(details), + 'host': CONF.host } - db.instance_fault_create(context, values) + conductor.instance_fault_create(context, values) -def get_device_name_for_instance(context, instance, device): +def get_device_name_for_instance(context, instance, bdms, device): """Validates (or generates) a device name for instance. If device is not set, it will generate a unique device appropriate @@ -87,8 +81,6 @@ def get_device_name_for_instance(context, instance, device): req_prefix, req_letters = block_device.match_device(device) except (TypeError, AttributeError, ValueError): raise exception.InvalidDevicePath(path=device) - bdms = db.block_device_mapping_get_all_by_instance(context, - instance['uuid']) mappings = block_device.instance_block_mapping(instance, bdms) try: prefix = block_device.match_device(mappings['root'])[0] @@ -160,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) @@ -212,24 +205,27 @@ def get_nw_info_for_instance(instance): return network_model.NetworkInfo.hydrate(cached_nwinfo) -def has_audit_been_run(context, host, timestamp=None): +def has_audit_been_run(context, conductor, host, timestamp=None): begin, end = utils.last_completed_audit_period(before=timestamp) - task_log = db.task_log_get(context, "instance_usage_audit", - begin, end, host) + task_log = conductor.task_log_get(context, "instance_usage_audit", + begin, end, host) if task_log: return True else: return False -def start_instance_usage_audit(context, begin, end, host, num_instances): - db.task_log_begin_task(context, "instance_usage_audit", begin, end, host, - num_instances, "Instance usage audit started...") +def start_instance_usage_audit(context, conductor, begin, end, host, + num_instances): + conductor.task_log_begin_task(context, "instance_usage_audit", begin, + end, host, num_instances, + "Instance usage audit started...") -def finish_instance_usage_audit(context, begin, end, host, errors, message): - db.task_log_end_task(context, "instance_usage_audit", begin, end, host, - errors, message) +def finish_instance_usage_audit(context, conductor, begin, end, host, errors, + message): + conductor.task_log_end_task(context, "instance_usage_audit", begin, end, + host, errors, message) def usage_volume_info(vol_usage): diff --git a/nova/compute/vm_mode.py b/nova/compute/vm_mode.py index 26e5ad8a0..cc1ca6978 100644 --- a/nova/compute/vm_mode.py +++ b/nova/compute/vm_mode.py @@ -52,7 +52,7 @@ def get_from_instance(instance): if mode == "hv": mode = HVM - if not mode in ALL: + if mode not in ALL: raise exception.Invalid("Unknown vm mode '%s'" % mode) return mode diff --git a/nova/conductor/api.py b/nova/conductor/api.py index 63b64f830..710639305 100644 --- a/nova/conductor/api.py +++ b/nova/conductor/api.py @@ -97,6 +97,9 @@ class LocalAPI(object): def instance_get_all_by_host(self, context, host): return self._manager.instance_get_all_by_host(context, host) + def instance_get_all_by_host_and_node(self, context, host, node): + return self._manager.instance_get_all_by_host(context, host, node) + def instance_get_all_by_filters(self, context, filters, sort_key='created_at', sort_dir='desc'): @@ -114,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, @@ -125,6 +133,9 @@ class LocalAPI(object): def instance_type_get(self, context, instance_type_id): return self._manager.instance_type_get(context, instance_type_id) + def instance_fault_create(self, context, values): + return self._manager.instance_fault_create(context, values) + def migration_get(self, context, migration_id): return self._manager.migration_get(context, migration_id) @@ -134,6 +145,10 @@ class LocalAPI(object): return self._manager.migration_get_unconfirmed_by_dest_compute( context, confirm_window, dest_compute) + def migration_get_in_progress_by_host_and_node(self, context, host, node): + return self._manager.migration_get_in_progress_by_host_and_node( + context, host, node) + def migration_create(self, context, instance, values): return self._manager.migration_create(context, instance, values) @@ -271,6 +286,32 @@ class LocalAPI(object): def service_destroy(self, context, service_id): return self._manager.service_destroy(context, service_id) + def compute_node_create(self, context, values): + return self._manager.compute_node_create(context, values) + + def compute_node_update(self, context, node, values, prune_stats=False): + return self._manager.compute_node_update(context, node, values, + prune_stats) + + def service_update(self, context, service, values): + return self._manager.service_update(context, service, values) + + def task_log_get(self, context, task_name, begin, end, host, state): + return self._manager.task_log_get(context, task_name, begin, end, + host, state) + + def task_log_begin_task(self, context, task_name, begin, end, host, + task_items=None, message=None): + return self._manager.task_log_begin_task(context, task_name, + begin, end, host, + task_items, message) + + def task_log_end_task(self, context, task_name, begin, end, host, + errors, message=None): + return self._manager.task_log_end_task(context, task_name, + begin, end, host, + errors, message) + class API(object): """Conductor API that does updates via RPC to the ConductorManager.""" @@ -331,6 +372,10 @@ class API(object): def instance_get_all_by_host(self, context, host): return self.conductor_rpcapi.instance_get_all_by_host(context, host) + def instance_get_all_by_host_and_node(self, context, host, node): + return self.conductor_rpcapi.instance_get_all_by_host(context, + host, node) + def instance_get_all_by_filters(self, context, filters, sort_key='created_at', sort_dir='desc'): @@ -348,6 +393,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) @@ -360,6 +410,9 @@ class API(object): return self.conductor_rpcapi.instance_type_get(context, instance_type_id) + def instance_fault_create(self, context, values): + return self.conductor_rpcapi.instance_fault_create(context, values) + def migration_get(self, context, migration_id): return self.conductor_rpcapi.migration_get(context, migration_id) @@ -370,6 +423,11 @@ class API(object): return crpcapi.migration_get_unconfirmed_by_dest_compute( context, confirm_window, dest_compute) + def migration_get_in_progress_by_host_and_node(self, context, host, node): + crpcapi = self.conductor_rpcapi + return crpcapi.migration_get_in_progress_by_host_and_node(context, + host, node) + def migration_create(self, context, instance, values): return self.conductor_rpcapi.migration_create(context, instance, values) @@ -518,3 +576,29 @@ class API(object): def service_destroy(self, context, service_id): return self.conductor_rpcapi.service_destroy(context, service_id) + + def compute_node_create(self, context, values): + return self.conductor_rpcapi.compute_node_create(context, values) + + def compute_node_update(self, context, node, values, prune_stats=False): + return self.conductor_rpcapi.compute_node_update(context, node, + values, prune_stats) + + def service_update(self, context, service, values): + return self.conductor_rpcapi.service_update(context, service, values) + + def task_log_get(self, context, task_name, begin, end, host, state): + return self.conductor_rpcapi.task_log_get(context, task_name, begin, + end, host, state) + + def task_log_begin_task(self, context, task_name, begin, end, host, + task_items=None, message=None): + return self.conductor_rpcapi.task_log_begin_task(context, task_name, + begin, end, host, + task_items, message) + + def task_log_end_task(self, context, task_name, begin, end, host, + errors, message=None): + return self.conductor_rpcapi.task_log_end_task(context, task_name, + begin, end, host, + errors, message) diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py index b0d4011ad..232e9da3e 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.30' + RPC_API_VERSION = '1.37' def __init__(self, *args, **kwargs): super(ConductorManager, self).__init__(service_name='conductor', @@ -83,9 +83,13 @@ class ConductorManager(manager.SchedulerDependentManager): def instance_get_all(self, context): return jsonutils.to_primitive(self.db.instance_get_all(context)) - def instance_get_all_by_host(self, context, host): - return jsonutils.to_primitive( - self.db.instance_get_all_by_host(context.elevated(), host)) + def instance_get_all_by_host(self, context, host, node=None): + if node is not None: + result = self.db.instance_get_all_by_host_and_node( + context.elevated(), host, node) + else: + result = self.db.instance_get_all_by_host(context.elevated(), host) + return jsonutils.to_primitive(result) @rpc_common.client_exceptions(exception.MigrationNotFound) def migration_get(self, context, migration_id): @@ -100,6 +104,12 @@ class ConductorManager(manager.SchedulerDependentManager): context, confirm_window, dest_compute) return jsonutils.to_primitive(migrations) + def migration_get_in_progress_by_host_and_node(self, context, + host, node): + migrations = self.db.migration_get_in_progress_by_host_and_node( + context, host, node) + return jsonutils.to_primitive(migrations) + def migration_create(self, context, instance, values): values.update({'instance_uuid': instance['uuid'], 'source_compute': instance['host'], @@ -224,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): @@ -244,6 +258,10 @@ class ConductorManager(manager.SchedulerDependentManager): result = self.db.instance_type_get(context, instance_type_id) return jsonutils.to_primitive(result) + def instance_fault_create(self, context, values): + result = self.db.instance_fault_create(context, values) + return jsonutils.to_primitive(result) + def vol_get_usage_by_time(self, context, start_time): result = self.db.vol_get_usage_by_time(context, start_time) return jsonutils.to_primitive(result) @@ -291,3 +309,35 @@ class ConductorManager(manager.SchedulerDependentManager): @rpc_common.client_exceptions(exception.ServiceNotFound) def service_destroy(self, context, service_id): self.db.service_destroy(context, service_id) + + def compute_node_create(self, context, values): + result = self.db.compute_node_create(context, values) + return jsonutils.to_primitive(result) + + def compute_node_update(self, context, node, values, prune_stats=False): + result = self.db.compute_node_update(context, node['id'], values, + prune_stats) + return jsonutils.to_primitive(result) + + @rpc_common.client_exceptions(exception.ServiceNotFound) + def service_update(self, context, service, values): + svc = self.db.service_update(context, service['id'], values) + return jsonutils.to_primitive(svc) + + def task_log_get(self, context, task_name, begin, end, host, state=None): + result = self.db.task_log_get(context, task_name, begin, end, host, + state) + return jsonutils.to_primitive(result) + + def task_log_begin_task(self, context, task_name, begin, end, host, + task_items=None, message=None): + result = self.db.task_log_begin_task(context.elevated(), task_name, + begin, end, host, task_items, + message) + return jsonutils.to_primitive(result) + + def task_log_end_task(self, context, task_name, begin, end, host, + errors, message=None): + result = self.db.task_log_end_task(context.elevated(), task_name, + begin, end, host, errors, message) + return jsonutils.to_primitive(result) diff --git a/nova/conductor/rpcapi.py b/nova/conductor/rpcapi.py index b7f760cf5..248a4e211 100644 --- a/nova/conductor/rpcapi.py +++ b/nova/conductor/rpcapi.py @@ -63,6 +63,13 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy): 1.28 - Added binary arg to service_get_all_by 1.29 - Added service_destroy 1.30 - Added migration_create + 1.31 - Added migration_get_in_progress_by_host_and_node + 1.32 - Added optional node to instance_get_all_by_host + 1.33 - Added compute_node_create and compute_node_update + 1.34 - Added service_update + 1.35 - Added instance_get_active_by_window_joined + 1.36 - Added instance_fault_create + 1.37 - Added task_log_get, task_log_begin_task, task_log_end_task """ BASE_RPC_API_VERSION = '1.0' @@ -106,6 +113,12 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy): dest_compute=dest_compute) return self.call(context, msg, version='1.20') + def migration_get_in_progress_by_host_and_node(self, context, + host, node): + msg = self.make_msg('migration_get_in_progress_by_host_and_node', + host=host, node=node) + return self.call(context, msg, version='1.31') + def migration_create(self, context, instance, values): instance_p = jsonutils.to_primitive(instance) msg = self.make_msg('migration_create', instance=instance_p, @@ -231,6 +244,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) @@ -271,9 +291,13 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy): msg = self.make_msg('instance_get_all') return self.call(context, msg, version='1.23') - def instance_get_all_by_host(self, context, host): - msg = self.make_msg('instance_get_all_by_host', host=host) - return self.call(context, msg, version='1.23') + def instance_get_all_by_host(self, context, host, node=None): + msg = self.make_msg('instance_get_all_by_host', host=host, node=node) + return self.call(context, msg, version='1.32') + + def instance_fault_create(self, context, values): + msg = self.make_msg('instance_fault_create', values=values) + return self.call(context, msg, version='1.36') def action_event_start(self, context, values): msg = self.make_msg('action_event_start', values=values) @@ -297,3 +321,37 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy): def service_destroy(self, context, service_id): msg = self.make_msg('service_destroy', service_id=service_id) return self.call(context, msg, version='1.29') + + def compute_node_create(self, context, values): + msg = self.make_msg('compute_node_create', values=values) + return self.call(context, msg, version='1.33') + + def compute_node_update(self, context, node, values, prune_stats=False): + node_p = jsonutils.to_primitive(node) + msg = self.make_msg('compute_node_update', node=node_p, values=values, + prune_stats=prune_stats) + return self.call(context, msg, version='1.33') + + def service_update(self, context, service, values): + service_p = jsonutils.to_primitive(service) + msg = self.make_msg('service_update', service=service_p, values=values) + return self.call(context, msg, version='1.34') + + def task_log_get(self, context, task_name, begin, end, host, state=None): + msg = self.make_msg('task_log_get', task_name=task_name, + begin=begin, end=end, host=host, state=state) + return self.call(context, msg, version='1.37') + + def task_log_begin_task(self, context, task_name, begin, end, host, + task_items=None, message=None): + msg = self.make_msg('task_log_begin_task', task_name=task_name, + begin=begin, end=end, host=host, + task_items=task_items, message=message) + return self.call(context, msg, version='1.37') + + def task_log_end_task(self, context, task_name, begin, end, host, errors, + message=None): + msg = self.make_msg('task_log_end_task', task_name=task_name, + begin=begin, end=end, host=host, errors=errors, + message=message) + return self.call(context, msg, version='1.37') diff --git a/nova/console/websocketproxy.py b/nova/console/websocketproxy.py new file mode 100644 index 000000000..ce9243d46 --- /dev/null +++ b/nova/console/websocketproxy.py @@ -0,0 +1,89 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2012 OpenStack, LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +''' +Websocket proxy that is compatible with OpenStack Nova. +Leverages websockify.py by Joel Martin +''' + +import Cookie +import socket + +import websockify + +from nova.consoleauth import rpcapi as consoleauth_rpcapi +from nova import context +from nova.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + + +class NovaWebSocketProxy(websockify.WebSocketProxy): + def __init__(self, *args, **kwargs): + websockify.WebSocketProxy.__init__(self, unix_target=None, + target_cfg=None, + ssl_target=None, *args, **kwargs) + + def new_client(self): + """ + Called after a new WebSocket connection has been established. + """ + cookie = Cookie.SimpleCookie() + cookie.load(self.headers.getheader('cookie')) + token = cookie['token'].value + ctxt = context.get_admin_context() + rpcapi = consoleauth_rpcapi.ConsoleAuthAPI() + connect_info = rpcapi.check_token(ctxt, token=token) + + if not connect_info: + LOG.audit("Invalid Token: %s", token) + raise Exception(_("Invalid Token")) + + host = connect_info['host'] + port = int(connect_info['port']) + + # Connect to the target + self.msg("connecting to: %s:%s" % (host, port)) + LOG.audit("connecting to: %s:%s" % (host, port)) + tsock = self.socket(host, port, connect=True) + + # Handshake as necessary + if connect_info.get('internal_access_path'): + tsock.send("CONNECT %s HTTP/1.1\r\n\r\n" % + connect_info['internal_access_path']) + while True: + data = tsock.recv(4096, socket.MSG_PEEK) + if data.find("\r\n\r\n") != -1: + if not data.split("\r\n")[0].find("200"): + LOG.audit("Invalid Connection Info %s", token) + raise Exception(_("Invalid Connection Info")) + tsock.recv(len(data)) + break + + if self.verbose and not self.daemon: + print(self.traffic_legend) + + # Start proxying + try: + self.do_proxy(tsock) + except Exception: + if tsock: + tsock.shutdown(socket.SHUT_RDWR) + tsock.close() + self.vmsg("%s:%s: Target closed" % (host, port)) + LOG.audit("%s:%s: Target closed" % (host, port)) + raise diff --git a/nova/context.py b/nova/context.py index 1a566cb5a..8731e012d 100644 --- a/nova/context.py +++ b/nova/context.py @@ -46,7 +46,7 @@ class RequestContext(object): roles=None, remote_address=None, timestamp=None, request_id=None, auth_token=None, overwrite=True, quota_class=None, user_name=None, project_name=None, - service_catalog=None, instance_lock_checked=False, **kwargs): + service_catalog=[], instance_lock_checked=False, **kwargs): """ :param read_deleted: 'no' indicates deleted records are hidden, 'yes' indicates deleted records are visible, 'only' indicates that @@ -79,7 +79,9 @@ class RequestContext(object): request_id = generate_request_id() self.request_id = request_id self.auth_token = auth_token - self.service_catalog = service_catalog + # Only include required parts of service_catalog + self.service_catalog = [s for s in service_catalog + if s.get('type') in ('volume')] self.instance_lock_checked = instance_lock_checked # NOTE(markmc): this attribute is currently only used by the diff --git a/nova/crypto.py b/nova/crypto.py index ff76a54d0..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) @@ -171,13 +172,44 @@ def decrypt_text(project_id, text): raise exception.ProjectNotFound(project_id=project_id) try: dec, _err = utils.execute('openssl', - 'rsautl', - '-decrypt', - '-inkey', '%s' % private_key, - process_input=text) + 'rsautl', + '-decrypt', + '-inkey', '%s' % private_key, + process_input=text) return dec - except exception.ProcessExecutionError: - raise exception.DecryptionFailure() + except exception.ProcessExecutionError as exc: + raise exception.DecryptionFailure(reason=exc.stderr) + + +def ssh_encrypt_text(ssh_public_key, text): + """Encrypt text with an ssh public key. + + Requires recent ssh-keygen binary in addition to openssl binary. + """ + with utils.tempdir() as tmpdir: + sshkey = os.path.abspath(os.path.join(tmpdir, 'ssh.key')) + with open(sshkey, 'w') as f: + f.write(ssh_public_key) + sslkey = os.path.abspath(os.path.join(tmpdir, 'ssl.key')) + try: + # NOTE(vish): -P is to skip prompt on bad keys + out, _err = utils.execute('ssh-keygen', + '-P', '', + '-e', + '-f', sshkey, + '-m', 'PKCS8') + with open(sslkey, 'w') as f: + f.write(out) + enc, _err = utils.execute('openssl', + 'rsautl', + '-encrypt', + '-pubin', + '-inkey', sslkey, + '-keyform', 'PEM', + process_input=text) + return enc + except exception.ProcessExecutionError as exc: + raise exception.EncryptionFailure(reason=exc.stderr) def revoke_cert(project_id, file_name): diff --git a/nova/db/api.py b/nova/db/api.py index ecfcfab15..3c1425691 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -305,7 +305,7 @@ def floating_ip_destroy(context, address): def floating_ip_disassociate(context, address): """Disassociate a floating ip from a fixed ip by address. - :returns: the address of the previous fixed ip or None + :returns: the fixed ip record joined to network record or None if the ip was not associated to an ip. """ @@ -316,7 +316,7 @@ def floating_ip_fixed_ip_associate(context, floating_address, fixed_address, host): """Associate a floating ip to a fixed_ip by address. - :returns: the address of the new fixed ip (fixed_address) or None + :returns: the fixed ip record joined to network record or None if the ip was already associated to the fixed ip. """ @@ -477,9 +477,12 @@ def fixed_ip_disassociate_all_by_timeout(context, host, time): return IMPL.fixed_ip_disassociate_all_by_timeout(context, host, time) -def fixed_ip_get(context, id): - """Get fixed ip by id or raise if it does not exist.""" - return IMPL.fixed_ip_get(context, id) +def fixed_ip_get(context, id, get_network=False): + """Get fixed ip by id or raise if it does not exist. + + If get_network is true, also return the assocated network. + """ + return IMPL.fixed_ip_get(context, id, get_network) def fixed_ip_get_all(context): @@ -497,6 +500,11 @@ def fixed_ip_get_by_address_detailed(context, address): return IMPL.fixed_ip_get_by_address_detailed(context, address) +def fixed_ip_get_by_floating_address(context, floating_address): + """Get a fixed ip by a floating address.""" + return IMPL.fixed_ip_get_by_floating_address(context, floating_address) + + def fixed_ip_get_by_instance(context, instance_uuid): """Get fixed ips by instance or raise if none exist.""" return IMPL.fixed_ip_get_by_instance(context, instance_uuid) @@ -754,12 +762,13 @@ def instance_info_cache_update(context, instance_uuid, values, :param values: = dict containing column values to update """ rv = IMPL.instance_info_cache_update(context, instance_uuid, values) - try: - cells_rpcapi.CellsAPI().instance_info_cache_update_at_top(context, - rv) - except Exception: - LOG.exception(_("Failed to notify cells of instance info cache " - "update")) + if update_cells: + try: + cells_rpcapi.CellsAPI().instance_info_cache_update_at_top( + context, rv) + except Exception: + LOG.exception(_("Failed to notify cells of instance info " + "cache update")) return rv @@ -1699,16 +1708,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, @@ -1716,25 +1723,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 038a47ca1..ad7e4f21f 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) @@ -253,6 +269,12 @@ def exact_filter(query, model, filters, legal_keys): return query +def convert_datetimes(values, *datetime_keys): + for key in values: + if key in datetime_keys and isinstance(values[key], basestring): + values[key] = timeutils.parse_strtime(values[key]) + return values + ################### @@ -402,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, @@ -442,6 +465,7 @@ def service_update(context, service_id, values): service_ref = service_get(context, service_id, session=session) service_ref.update(values) service_ref.save(session=session) + return service_ref ################### @@ -497,6 +521,7 @@ def compute_node_create(context, values): """Creates a new ComputeNode and populates the capacity fields with the most recent data.""" _prep_stats_dict(values) + convert_datetimes(values, 'created_at', 'deleted_at', 'updated_at') compute_node_ref = models.ComputeNode() compute_node_ref.update(values) @@ -532,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: @@ -545,19 +570,19 @@ def compute_node_update(context, compute_id, values, prune_stats=False): stats = values.pop('stats', {}) session = get_session() - with session.begin(subtransactions=True): + with session.begin(): _update_stats(context, stats, compute_id, session, prune_stats) compute_ref = _compute_node_get(context, compute_id, session=session) + convert_datetimes(values, 'created_at', 'deleted_at', 'updated_at') compute_ref.update(values) return compute_ref 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 @@ -577,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 @@ -651,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 @@ -766,15 +793,16 @@ def floating_ip_fixed_ip_associate(context, floating_address, floating_ip_ref = _floating_ip_get_by_address(context, floating_address, session=session) - fixed_ip_ref = fixed_ip_get_by_address(context, - fixed_address, - session=session) + fixed_ip_ref = model_query(context, models.FixedIp, session=session).\ + filter_by(address=fixed_address).\ + options(joinedload('network')).\ + first() if floating_ip_ref.fixed_ip_id == fixed_ip_ref["id"]: return None floating_ip_ref.fixed_ip_id = fixed_ip_ref["id"] floating_ip_ref.host = host floating_ip_ref.save(session=session) - return fixed_address + return fixed_ip_ref @require_context @@ -807,15 +835,12 @@ def floating_ip_disassociate(context, address): fixed_ip_ref = model_query(context, models.FixedIp, session=session).\ filter_by(id=floating_ip_ref['fixed_ip_id']).\ + options(joinedload('network')).\ first() - if fixed_ip_ref: - fixed_ip_address = fixed_ip_ref['address'] - else: - fixed_ip_address = None floating_ip_ref.fixed_ip_id = None floating_ip_ref.host = None floating_ip_ref.save(session=session) - return fixed_ip_address + return fixed_ip_ref @require_context @@ -882,15 +907,12 @@ def _floating_ip_get_by_address(context, address, session=None): @require_context def floating_ip_get_by_fixed_address(context, fixed_address): - subq = model_query(context, models.FixedIp.id).\ - filter_by(address=fixed_address).\ - limit(1).\ - subquery() return model_query(context, models.FloatingIp).\ - filter_by(fixed_ip_id=subq.as_scalar()).\ - all() - - # NOTE(tr3buchet) please don't invent an exception here, empty list is fine + outerjoin(models.FixedIp, + models.FixedIp.id == + models.FloatingIp.fixed_ip_id).\ + filter(models.FixedIp.address == fixed_address).\ + all() @require_context @@ -1088,37 +1110,39 @@ 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 - - -@require_context -def fixed_ip_get(context, id): - result = model_query(context, models.FixedIp).\ - filter_by(id=id).\ - first() + 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 +def fixed_ip_get(context, id, get_network=False): + query = model_query(context, models.FixedIp).filter_by(id=id) + if get_network: + query = query.options(joinedload('network')) + result = query.first() if not result: raise exception.FixedIpNotFound(id=id) @@ -1187,6 +1211,17 @@ def fixed_ip_get_by_address_detailed(context, address, session=None): @require_context +def fixed_ip_get_by_floating_address(context, floating_address): + return model_query(context, models.FixedIp).\ + outerjoin(models.FloatingIp, + models.FloatingIp.fixed_ip_id == + models.FixedIp.id).\ + filter(models.FloatingIp.address == floating_address).\ + first() + # NOTE(tr3buchet) please don't invent an exception here, empty list is fine + + +@require_context def fixed_ip_get_by_instance(context, instance_uuid): if not uuidutils.is_uuid_like(instance_uuid): raise exception.InvalidUUID(uuid=instance_uuid) @@ -1209,7 +1244,7 @@ def fixed_ip_get_by_network_host(context, network_id, host): first() if not result: - raise exception.FixedIpNotFoundForNetworkHost(network_uuid=network_id, + raise exception.FixedIpNotFoundForNetworkHost(network_id=network_id, host=host) return result @@ -1451,7 +1486,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() @@ -1576,12 +1611,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: @@ -1609,7 +1644,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) @@ -1678,6 +1714,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: @@ -2105,19 +2142,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) @@ -2196,9 +2235,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, @@ -2208,7 +2247,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)).\ @@ -2309,6 +2348,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 == @@ -3121,13 +3161,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: @@ -3578,7 +3619,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)) @@ -3649,7 +3690,7 @@ def instance_type_destroy(context, name): @require_context def _instance_type_access_query(context, session=None): return model_query(context, models.InstanceTypeProjects, session=session, - read_deleted="yes") + read_deleted="no") @require_admin_context @@ -3665,6 +3706,8 @@ def instance_type_access_get_by_flavor_id(context, flavor_id): @require_admin_context def instance_type_access_add(context, flavor_id, project_id): """Add given tenant to the flavor access list.""" + # NOTE(boris-42): There is a race condition in this method and it will be + # rewritten after bp/db-unique-keys implementation. session = get_session() with session.begin(): instance_type_ref = instance_type_get_by_flavor_id(context, flavor_id, @@ -3672,21 +3715,16 @@ def instance_type_access_add(context, flavor_id, project_id): instance_type_id = instance_type_ref['id'] access_ref = _instance_type_access_query(context, session=session).\ filter_by(instance_type_id=instance_type_id).\ - filter_by(project_id=project_id).first() - - if not access_ref: - access_ref = models.InstanceTypeProjects() - access_ref.instance_type_id = instance_type_id - access_ref.project_id = project_id - access_ref.save(session=session) - elif access_ref.deleted: - access_ref.update({'deleted': False, - 'deleted_at': None}) - access_ref.save(session=session) - else: + filter_by(project_id=project_id).\ + first() + if access_ref: raise exception.FlavorAccessExists(flavor_id=flavor_id, project_id=project_id) + access_ref = models.InstanceTypeProjects() + access_ref.update({"instance_type_id": instance_type_id, + "project_id": project_id}) + access_ref.save(session=session) return access_ref @@ -3702,7 +3740,6 @@ def instance_type_access_remove(context, flavor_id, project_id): filter_by(instance_type_id=instance_type_id).\ filter_by(project_id=project_id).\ soft_delete() - if count == 0: raise exception.FlavorAccessNotFound(flavor_id=flavor_id, project_id=project_id) @@ -4024,7 +4061,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, @@ -4070,21 +4108,43 @@ def instance_type_extra_specs_get_item(context, flavor_id, key, @require_context -def instance_type_extra_specs_update_or_create(context, flavor_id, - specs): +def instance_type_extra_specs_update_or_create(context, flavor_id, specs): + # NOTE(boris-42): There is a race condition in this method. We should add + # UniqueConstraint on (instance_type_id, key, deleted) to + # avoid duplicated instance_type_extra_specs. This will be + # possible after bp/db-unique-keys implementation. session = get_session() - spec_ref = None - instance_type = instance_type_get_by_flavor_id(context, flavor_id) - for key, value in specs.iteritems(): - try: - spec_ref = instance_type_extra_specs_get_item( - context, flavor_id, key, session) - except exception.InstanceTypeExtraSpecsNotFound: + 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() + if not instance_type_id: + raise exception.FlavorNotFound(flavor_id=flavor_id) + + instance_type_id = instance_type_id.id + + spec_refs = model_query(context, models.InstanceTypeExtraSpecs, + session=session, read_deleted="no").\ + filter_by(instance_type_id=instance_type_id).\ + filter(models.InstanceTypeExtraSpecs.key.in_(specs.keys())).\ + all() + + existing_keys = set() + for spec_ref in spec_refs: + key = spec_ref["key"] + existing_keys.add(key) + spec_ref.update({"value": specs[key]}) + + for key, value in specs.iteritems(): + if key in existing_keys: + continue spec_ref = models.InstanceTypeExtraSpecs() - spec_ref.update({"key": key, "value": value, - "instance_type_id": instance_type["id"], - "deleted": False}) - spec_ref.save(session=session) + spec_ref.update({"key": key, "value": value, + "instance_type_id": instance_type_id}) + session.add(spec_ref) + return specs @@ -4414,28 +4474,33 @@ def aggregate_metadata_get_item(context, aggregate_id, key, session=None): @require_admin_context @require_aggregate_exists def aggregate_metadata_add(context, aggregate_id, metadata, set_delete=False): + # NOTE(boris-42): There is a race condition in this method. We should add + # UniqueConstraint on (start_period, uuid, mac, deleted) to + # avoid duplicated aggregate_metadata. This will be + # possible after bp/db-unique-keys implementation. session = get_session() all_keys = metadata.keys() with session.begin(): query = aggregate_metadata_get_query(context, aggregate_id, + read_deleted='no', session=session) if set_delete: query.filter(~models.AggregateMetadata.key.in_(all_keys)).\ soft_delete(synchronize_session=False) query = query.filter(models.AggregateMetadata.key.in_(all_keys)) - already_existing_keys = [] + already_existing_keys = set() for meta_ref in query.all(): key = meta_ref.key - meta_ref.update({"value": metadata[key], - "deleted": False, - "deleted_at": None}) - already_existing_keys.append(key) + meta_ref.update({"value": metadata[key]}) + already_existing_keys.add(key) - for key in set(all_keys) - set(already_existing_keys): + for key, value in metadata.iteritems(): + if key in already_existing_keys: + continue meta_ref = models.AggregateMetadata() meta_ref.update({"key": key, - "value": metadata[key], + "value": value, "aggregate_id": aggregate_id}) session.add(meta_ref) @@ -4469,25 +4534,24 @@ def aggregate_host_delete(context, aggregate_id, host): @require_admin_context @require_aggregate_exists def aggregate_host_add(context, aggregate_id, host): + # NOTE(boris-42): There is a race condition in this method and it will be + # rewritten after bp/db-unique-keys implementation. session = get_session() - host_ref = _aggregate_get_query(context, - models.AggregateHost, - models.AggregateHost.aggregate_id, - aggregate_id, - session=session, - read_deleted='yes').\ - filter_by(host=host).first() - if not host_ref: + with session.begin(): + host_ref = _aggregate_get_query(context, + models.AggregateHost, + models.AggregateHost.aggregate_id, + aggregate_id, + session=session, + read_deleted='no').\ + filter_by(host=host).\ + first() + if host_ref: + raise exception.AggregateHostExists(host=host, + aggregate_id=aggregate_id) host_ref = models.AggregateHost() - values = {"host": host, "aggregate_id": aggregate_id, } - host_ref.update(values) - host_ref.save(session=session) - elif host_ref.deleted: - host_ref.update({'deleted': False, 'deleted_at': None}) + host_ref.update({"host": host, "aggregate_id": aggregate_id}) host_ref.save(session=session) - else: - raise exception.AggregateHostExists(host=host, - aggregate_id=aggregate_id) return host_ref @@ -4691,49 +4755,44 @@ def _ec2_instance_get_query(context, session=None): @require_admin_context -def task_log_get(context, task_name, period_beginning, - period_ending, host, state=None, session=None): +def _task_log_get_query(context, task_name, period_beginning, + period_ending, host=None, state=None, session=None): query = model_query(context, models.TaskLog, session=session).\ filter_by(task_name=task_name).\ filter_by(period_beginning=period_beginning).\ - filter_by(period_ending=period_ending).\ - filter_by(host=host) + filter_by(period_ending=period_ending) + if host is not None: + query = query.filter_by(host=host) if state is not None: query = query.filter_by(state=state) + return query - return query.first() + +@require_admin_context +def task_log_get(context, task_name, period_beginning, period_ending, host, + state=None): + return _task_log_get_query(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, session=None): - query = model_query(context, models.TaskLog, session=session).\ - filter_by(task_name=task_name).\ - filter_by(period_beginning=period_beginning).\ - filter_by(period_ending=period_ending) - if host is not None: - query = query.filter_by(host=host) - if state is not None: - query = query.filter_by(state=state) - return query.all() +def task_log_get_all(context, task_name, period_beginning, period_ending, + host=None, state=None): + return _task_log_get_query(context, task_name, period_beginning, + period_ending, host, state).all() @require_admin_context -def task_log_begin_task(context, task_name, - period_beginning, - period_ending, - host, - task_items=None, - message=None, - session=None): - session = session or get_session() +def task_log_begin_task(context, task_name, period_beginning, period_ending, + host, task_items=None, message=None): + # NOTE(boris-42): This method has a race condition and will be rewritten + # after bp/db-unique-keys implementation. + session = get_session() with session.begin(): - task = task_log_get(context, task_name, - period_beginning, - period_ending, - host, - session=session) - if task: + task_ref = _task_log_get_query(context, task_name, period_beginning, + period_ending, host, session=session).\ + first() + if task_ref: #It's already run(ning)! raise exception.TaskAlreadyRunning(task_name=task_name, host=host) task = models.TaskLog() @@ -4747,30 +4806,20 @@ def task_log_begin_task(context, task_name, if task_items: task.task_items = task_items task.save(session=session) - return task @require_admin_context -def task_log_end_task(context, task_name, - period_beginning, - period_ending, - host, - errors, - message=None, - session=None): - session = session or get_session() +def task_log_end_task(context, task_name, period_beginning, period_ending, + host, errors, message=None): + values = dict(state="DONE", errors=errors) + if message: + values["message"] = message + + session = get_session() with session.begin(): - task = task_log_get(context, task_name, - period_beginning, - period_ending, - host, - session=session) - if not task: + rows = _task_log_get_query(context, task_name, period_beginning, + period_ending, host, session=session).\ + update(values) + if rows == 0: #It's not running! raise exception.TaskNotRunning(task_name=task_name, host=host) - task.state = "DONE" - if message: - task.message = message - task.errors = errors - task.save(session=session) - return task diff --git a/nova/db/sqlalchemy/migrate_repo/versions/147_no_service_zones.py b/nova/db/sqlalchemy/migrate_repo/versions/147_no_service_zones.py index a20799fbe..d93cd1ead 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/147_no_service_zones.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/147_no_service_zones.py @@ -37,9 +37,9 @@ def upgrade(migrate_engine): if rec['binary'] != 'nova-compute': continue # if zone doesn't exist create - result = aggregate_metadata.select().where(aggregate_metadata.c.key == - 'availability_zone' and - aggregate_metadata.c.key == rec['availability_zone']).execute() + result = aggregate_metadata.select().where( + aggregate_metadata.c.key == 'availability_zone').where( + aggregate_metadata.c.value == rec['availability_zone']).execute() result = [r for r in result] if len(result) > 0: agg_id = result[0].aggregate_id diff --git a/nova/db/sqlalchemy/migrate_repo/versions/149_inet_datatype_for_postgres.py b/nova/db/sqlalchemy/migrate_repo/versions/149_inet_datatype_for_postgres.py new file mode 100644 index 000000000..fe9889e35 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/149_inet_datatype_for_postgres.py @@ -0,0 +1,70 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import MetaData, String, Table +from sqlalchemy.dialects import postgresql + + +TABLE_COLUMNS = [ + # table name, column name + ('instances', 'access_ip_v4'), + ('instances', 'access_ip_v6'), + ('security_group_rules', 'cidr'), + ('provider_fw_rules', 'cidr'), + ('networks', 'cidr'), + ('networks', 'cidr_v6'), + ('networks', 'gateway'), + ('networks', 'gateway_v6'), + ('networks', 'netmask'), + ('networks', 'netmask_v6'), + ('networks', 'broadcast'), + ('networks', 'dns1'), + ('networks', 'dns2'), + ('networks', 'vpn_public_address'), + ('networks', 'vpn_private_address'), + ('networks', 'dhcp_start'), + ('fixed_ips', 'address'), + ('floating_ips', 'address'), + ('console_pools', 'address')] + + +def upgrade(migrate_engine): + """Convert String columns holding IP addresses to INET for postgresql.""" + meta = MetaData() + meta.bind = migrate_engine + dialect = migrate_engine.url.get_dialect() + if dialect is postgresql.dialect: + for table, column in TABLE_COLUMNS: + # can't use migrate's alter() because it does not support + # explicit casting + migrate_engine.execute( + "ALTER TABLE %(table)s " + "ALTER COLUMN %(column)s TYPE INET USING %(column)s::INET" + % locals()) + else: + for table, column in TABLE_COLUMNS: + t = Table(table, meta, autoload=True) + getattr(t.c, column).alter(type=String(39)) + + +def downgrade(migrate_engine): + """Convert columns back to the larger String(255).""" + meta = MetaData() + meta.bind = migrate_engine + for table, column in TABLE_COLUMNS: + t = Table(table, meta, autoload=True) + getattr(t.c, column).alter(type=String(255)) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/150_add_host_to_instance_faults.py b/nova/db/sqlalchemy/migrate_repo/versions/150_add_host_to_instance_faults.py new file mode 100644 index 000000000..3fd87e1e1 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/150_add_host_to_instance_faults.py @@ -0,0 +1,36 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import Column, Index, MetaData, String, Table + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + instance_faults = Table('instance_faults', meta, autoload=True) + host = Column('host', String(length=255)) + instance_faults.create_column(host) + Index('instance_faults_host_idx', instance_faults.c.host).create( + migrate_engine) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + instance_faults = Table('instance_faults', meta, autoload=True) + instance_faults.drop_column('host') diff --git a/nova/db/sqlalchemy/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..c49e8272b --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/152_change_type_of_deleted_column.py @@ -0,0 +1,225 @@ +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 import 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/migrate_repo/versions/153_instance_type_in_system_metadata.py b/nova/db/sqlalchemy/migrate_repo/versions/153_instance_type_in_system_metadata.py new file mode 100644 index 000000000..20e75a6eb --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/153_instance_type_in_system_metadata.py @@ -0,0 +1,49 @@ +# Copyright 2013 IBM Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import MetaData, select, Table + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + instances = Table('instances', meta, autoload=True) + instance_types = Table('instance_types', meta, autoload=True) + sys_meta = Table('instance_system_metadata', meta, autoload=True) + + # Taken from nova/compute/api.py + instance_type_props = ['id', 'name', 'memory_mb', 'vcpus', + 'root_gb', 'ephemeral_gb', 'flavorid', + 'swap', 'rxtx_factor', 'vcpu_weight'] + + select_columns = [instances.c.uuid] + select_columns += [getattr(instance_types.c, name) + for name in instance_type_props] + + q = select(select_columns, from_obj=instances.join( + instance_types, + instances.c.instance_type_id == instance_types.c.id)) + + i = sys_meta.insert() + for values in q.execute(): + for index in range(0, len(instance_type_props)): + i.execute({"key": "instance_type_%s" % instance_type_props[index], + "value": str(values[index + 1]), + "instance_uuid": values[0]}) + + +def downgrade(migration_engine): + # This migration only touches data, and only metadata at that. No need + # to go through and delete old metadata items. + pass diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 52985a3eb..fd8348678 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -27,6 +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 import types from nova.openstack.common import cfg from nova.openstack.common import timeutils @@ -41,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): @@ -62,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) @@ -128,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) @@ -172,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) @@ -220,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)) @@ -290,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(String(255)) - access_ip_v6 = Column(String(255)) + access_ip_v4 = Column(types.IPAddress()) + access_ip_v6 = Column(types.IPAddress()) auto_disk_config = Column(Boolean()) progress = Column(Integer) @@ -357,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): @@ -464,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): @@ -506,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. @@ -541,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): @@ -566,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') @@ -587,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(String(255)) + cidr = Column(types.IPAddress()) # Note: This is not the parent SecurityGroup. It's SecurityGroup we're # granting access for. @@ -601,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): @@ -612,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(String(255)) + cidr = Column(types.IPAddress()) class KeyPair(BASE, NovaBase): @@ -650,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): @@ -662,25 +665,25 @@ class Network(BASE, NovaBase): label = Column(String(255)) injected = Column(Boolean, default=False) - cidr = Column(String(255), unique=True) - cidr_v6 = Column(String(255), unique=True) + cidr = Column(types.IPAddress(), unique=True) + cidr_v6 = Column(types.IPAddress(), unique=True) multi_host = Column(Boolean, default=False) - gateway_v6 = Column(String(255)) - netmask_v6 = Column(String(255)) - netmask = Column(String(255)) + 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(String(255)) - broadcast = Column(String(255)) - dns1 = Column(String(255)) - dns2 = Column(String(255)) + gateway = Column(types.IPAddress()) + broadcast = Column(types.IPAddress()) + dns1 = Column(types.IPAddress()) + dns2 = Column(types.IPAddress()) vlan = Column(Integer) - vpn_public_address = Column(String(255)) + vpn_public_address = Column(types.IPAddress()) vpn_public_port = Column(Integer) - vpn_private_address = Column(String(255)) - dhcp_start = Column(String(255)) + vpn_private_address = Column(types.IPAddress()) + dhcp_start = Column(types.IPAddress()) rxtx_base = Column(Integer) @@ -705,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(String(255)) + address = Column(types.IPAddress()) network_id = Column(Integer, nullable=True) virtual_interface_id = Column(Integer, nullable=True) instance_uuid = Column(String(36), nullable=True) @@ -716,13 +719,19 @@ class FixedIp(BASE, NovaBase): leased = Column(Boolean, default=False) reserved = Column(Boolean, default=False) host = Column(String(255)) + network = relationship(Network, + backref=backref('fixed_ips'), + foreign_keys=network_id, + primaryjoin='and_(' + 'FixedIp.network_id == Network.id,' + 'FixedIp.deleted == 0)') 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(String(255)) + address = Column(types.IPAddress()) fixed_ip_id = Column(Integer, nullable=True) project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) @@ -734,6 +743,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)) @@ -744,7 +754,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(String(255)) + address = Column(types.IPAddress()) username = Column(String(255)) password = Column(String(255)) console_type = Column(String(255)) @@ -778,7 +788,7 @@ class InstanceMetadata(BASE, NovaBase): primaryjoin='and_(' 'InstanceMetadata.instance_uuid == ' 'Instance.uuid,' - 'InstanceMetadata.deleted == False)') + 'InstanceMetadata.deleted == 0)') class InstanceSystemMetadata(BASE, NovaBase): @@ -792,7 +802,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) @@ -810,7 +820,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): @@ -825,7 +835,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): @@ -879,24 +889,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): @@ -991,6 +1001,7 @@ class InstanceFault(BASE, NovaBase): code = Column(Integer(), nullable=False) message = Column(String(255)) details = Column(Text) + host = Column(String(255)) class InstanceAction(BASE, NovaBase): @@ -1036,8 +1047,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..28ec613c5 100644 --- a/nova/db/sqlalchemy/session.py +++ b/nova/db/sqlalchemy/session.py @@ -228,17 +228,17 @@ from eventlet import db_pool from eventlet import greenthread try: import MySQLdb + from MySQLdb.constants import CLIENT as mysql_client_constants except ImportError: MySQLdb = None + mysql_client_constants = None from sqlalchemy.exc import DisconnectionError, OperationalError, IntegrityError import sqlalchemy.interfaces 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 +362,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 +370,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 +381,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 @@ -484,9 +484,21 @@ def create_engine(sql_connection): 'user': connection_dict.username, 'min_size': CONF.sql_min_pool_size, 'max_size': CONF.sql_max_pool_size, - 'max_idle': CONF.sql_idle_timeout} - creator = db_pool.ConnectionPool(MySQLdb, **pool_args) - engine_args['creator'] = creator.create + 'max_idle': CONF.sql_idle_timeout, + 'client_flag': mysql_client_constants.FOUND_ROWS} + + pool = db_pool.ConnectionPool(MySQLdb, **pool_args) + + def creator(): + conn = pool.create() + if isinstance(conn, tuple): + # NOTE(belliott) eventlet >= 0.10 returns a tuple + now, now, conn = conn + + return conn + + engine_args['creator'] = creator + else: engine_args['pool_size'] = CONF.sql_max_pool_size if CONF.sql_max_overflow is not None: @@ -536,7 +548,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) @@ -552,6 +564,10 @@ class Session(sqlalchemy.orm.session.Session): def flush(self, *args, **kwargs): return super(Session, self).flush(*args, **kwargs) + @wrap_db_error + def execute(self, *args, **kwargs): + return super(Session, self).execute(*args, **kwargs) + def get_maker(engine, autocommit=True, expire_on_commit=False): """Return a SQLAlchemy sessionmaker using the given engine.""" diff --git a/nova/virt/hyperv/ioutils.py b/nova/db/sqlalchemy/types.py index d927e317f..275e61a4c 100644 --- a/nova/virt/hyperv/ioutils.py +++ b/nova/db/sqlalchemy/types.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2012 Cloudbase Solutions Srl +# Copyright 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -15,12 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Utility class to ease the task of creating stubs of built in IO functions. -""" +"""Custom SQLAlchemy types.""" -import __builtin__ +from sqlalchemy.dialects import postgresql +from sqlalchemy import String -def open(name, mode): - return __builtin__.open(name, mode) +def IPAddress(): + """An SQLAlchemy type representing an IP-address.""" + return String(39).with_variant(postgresql.INET(), 'postgresql') diff --git a/nova/exception.py b/nova/exception.py index dcd75bf4e..6bb8097c3 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -25,7 +25,6 @@ SHOULD include dedicated exception logging. """ import functools -import itertools import webob.exc @@ -179,8 +178,12 @@ class DBDuplicateEntry(DBError): super(DBDuplicateEntry, self).__init__(inner_exception) +class EncryptionFailure(NovaException): + message = _("Failed to encrypt text: %(reason)s") + + class DecryptionFailure(NovaException): - message = _("Failed to decrypt text") + message = _("Failed to decrypt text: %(reason)s") class VirtualInterfaceCreateException(NovaException): @@ -223,6 +226,20 @@ class Invalid(NovaException): code = 400 +class InvalidBDM(Invalid): + message = _("Block Device Mapping is Invalid.") + + +class InvalidBDMSnapshot(InvalidBDM): + message = _("Block Device Mapping is Invalid: " + "failed to get snapshot %(id)s.") + + +class InvalidBDMVolume(InvalidBDM): + message = _("Block Device Mapping is Invalid: " + "failed to get volume %(id)s.") + + class VolumeUnattached(Invalid): message = _("Volume %(volume_id)s is not attached to anything") @@ -313,7 +330,15 @@ class InstanceSuspendFailure(Invalid): class InstanceResumeFailure(Invalid): - message = _("Failed to resume server") + ": %(reason)s." + message = _("Failed to resume instance: %(reason)s.") + + +class InstancePowerOnFailure(Invalid): + message = _("Failed to power on instance: %(reason)s.") + + +class InstancePowerOffFailure(Invalid): + message = _("Failed to power off instance: %(reason)s.") class InstanceRebootFailure(Invalid): @@ -526,6 +551,10 @@ class PortNotUsable(NovaException): message = _("Port %(port_id)s not usable for instance %(instance)s.") +class PortNotFree(NovaException): + message = _("No free port available for instance %(instance)s.") + + class FixedIpNotFound(NotFound): message = _("No fixed IP associated with id %(id)s.") @@ -1091,3 +1120,7 @@ class CryptoCAFileNotFound(FileNotFound): class CryptoCRLFileNotFound(FileNotFound): message = _("The CRL file for %(project)s could not be found") + + +class InstanceRecreateNotSupported(Invalid): + message = _('Instance recreate is not implemented by this virt driver.') diff --git a/nova/image/glance.py b/nova/image/glance.py index 75551d35c..6eac96c35 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -22,6 +22,7 @@ from __future__ import absolute_import import copy import itertools import random +import shutil import sys import time import urlparse @@ -58,7 +59,12 @@ glance_opts = [ cfg.IntOpt('glance_num_retries', default=0, help='Number retries when downloading an image from glance'), -] + cfg.ListOpt('allowed_direct_url_schemes', + default=[], + help='A list of url scheme that can be downloaded directly ' + 'via the direct_url. Currently supported schemes: ' + '[file].'), + ] LOG = logging.getLogger(__name__) CONF = cfg.CONF @@ -254,6 +260,18 @@ class GlanceImageService(object): def download(self, context, image_id, data): """Calls out to Glance for metadata and data and writes data.""" + if 'file' in CONF.allowed_direct_url_schemes: + location = self.get_location(context, image_id) + o = urlparse.urlparse(location) + if o.scheme == "file": + with open(o.path, "r") as f: + # FIXME(jbresnah) a system call to cp could have + # significant performance advantages, however we + # do not have the path to files at this point in + # the abstraction. + shutil.copyfileobj(f, data) + return + try: image_chunks = self._client.call(context, 1, 'data', image_id) except Exception: @@ -467,6 +485,8 @@ def get_remote_image_service(context, image_href): :returns: a tuple of the form (image_service, image_id) """ + # Calling out to another service may take a while, so lets log this + LOG.debug(_("fetching image %s from glance") % image_href) #NOTE(bcwaldon): If image_href doesn't look like a URI, assume its a # standalone image ID if '/' not in str(image_href): diff --git a/nova/network/api.py b/nova/network/api.py index 976be93ed..30338489d 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -25,6 +25,7 @@ from nova import exception from nova.network import model as network_model from nova.network import rpcapi as network_rpcapi from nova.openstack.common import log as logging +from nova import policy LOG = logging.getLogger(__name__) @@ -51,7 +52,7 @@ def refresh_cache(f): raise Exception(msg) update_instance_cache_with_nw_info(self, context, instance, - nw_info=res) + nw_info=res, conductor_api=kwargs.get('conductor_api')) # return the original function's return value return res @@ -59,8 +60,7 @@ def refresh_cache(f): def update_instance_cache_with_nw_info(api, context, instance, - nw_info=None): - + nw_info=None, conductor_api=None): try: if not isinstance(nw_info, network_model.NetworkInfo): nw_info = None @@ -68,11 +68,35 @@ def update_instance_cache_with_nw_info(api, context, instance, nw_info = api._get_instance_nw_info(context, instance) # update cache cache = {'network_info': nw_info.json()} - api.db.instance_info_cache_update(context, instance['uuid'], cache) + if conductor_api: + conductor_api.instance_info_cache_update(context, instance, cache) + else: + api.db.instance_info_cache_update(context, instance['uuid'], cache) except Exception: LOG.exception(_('Failed storing info cache'), instance=instance) +def wrap_check_policy(func): + """Check policy corresponding to the wrapped methods prior to execution.""" + + @functools.wraps(func) + def wrapped(self, context, *args, **kwargs): + action = func.__name__ + check_policy(context, action) + return func(self, context, *args, **kwargs) + + return wrapped + + +def check_policy(context, action): + target = { + 'project_id': context.project_id, + 'user_id': context.user_id, + } + _action = 'network:%s' % action + policy.enforce(context, _action, target) + + class API(base.Base): """API for doing networking via the nova-network network manager. @@ -86,58 +110,75 @@ class API(base.Base): self.network_rpcapi = network_rpcapi.NetworkAPI() super(API, self).__init__(**kwargs) + @wrap_check_policy def get_all(self, context): return self.network_rpcapi.get_all_networks(context) + @wrap_check_policy def get(self, context, network_uuid): return self.network_rpcapi.get_network(context, network_uuid) + @wrap_check_policy def create(self, context, **kwargs): return self.network_rpcapi.create_networks(context, **kwargs) + @wrap_check_policy def delete(self, context, network_uuid): return self.network_rpcapi.delete_network(context, network_uuid, None) + @wrap_check_policy def disassociate(self, context, network_uuid): return self.network_rpcapi.disassociate_network(context, network_uuid) + @wrap_check_policy def get_fixed_ip(self, context, id): return self.network_rpcapi.get_fixed_ip(context, id) + @wrap_check_policy def get_fixed_ip_by_address(self, context, address): return self.network_rpcapi.get_fixed_ip_by_address(context, address) + @wrap_check_policy def get_floating_ip(self, context, id): return self.network_rpcapi.get_floating_ip(context, id) + @wrap_check_policy def get_floating_ip_pools(self, context): - return self.network_rpcapi.get_floating_pools(context) + return self.network_rpcapi.get_floating_ip_pools(context) + @wrap_check_policy def get_floating_ip_by_address(self, context, address): return self.network_rpcapi.get_floating_ip_by_address(context, address) + @wrap_check_policy def get_floating_ips_by_project(self, context): return self.network_rpcapi.get_floating_ips_by_project(context) + @wrap_check_policy def get_floating_ips_by_fixed_address(self, context, fixed_address): return self.network_rpcapi.get_floating_ips_by_fixed_address(context, fixed_address) + @wrap_check_policy def get_backdoor_port(self, context, host): return self.network_rpcapi.get_backdoor_port(context, host) + @wrap_check_policy def get_instance_id_by_floating_address(self, context, address): # NOTE(tr3buchet): i hate this return self.network_rpcapi.get_instance_id_by_floating_address(context, address) + @wrap_check_policy def get_vifs_by_instance(self, context, instance): return self.network_rpcapi.get_vifs_by_instance(context, instance['id']) + @wrap_check_policy def get_vif_by_mac_address(self, context, mac_address): return self.network_rpcapi.get_vif_by_mac_address(context, mac_address) + @wrap_check_policy def allocate_floating_ip(self, context, pool=None): """Adds (allocates) a floating ip to a project from a pool.""" # NOTE(vish): We don't know which network host should get the ip @@ -147,12 +188,14 @@ class API(base.Base): return self.network_rpcapi.allocate_floating_ip(context, context.project_id, pool, False) + @wrap_check_policy def release_floating_ip(self, context, address, affect_auto_assigned=False): """Removes (deallocates) a floating ip with address from a project.""" return self.network_rpcapi.deallocate_floating_ip(context, address, affect_auto_assigned) + @wrap_check_policy @refresh_cache def associate_floating_ip(self, context, instance, floating_address, fixed_address, @@ -175,6 +218,7 @@ class API(base.Base): # purge cached nw info for the original instance update_instance_cache_with_nw_info(self, context, orig_instance) + @wrap_check_policy @refresh_cache def disassociate_floating_ip(self, context, instance, address, affect_auto_assigned=False): @@ -182,9 +226,11 @@ class API(base.Base): self.network_rpcapi.disassociate_floating_ip(context, address, affect_auto_assigned) + @wrap_check_policy @refresh_cache def allocate_for_instance(self, context, instance, vpn, - requested_networks, macs=None): + requested_networks, macs=None, + conductor_api=None): """Allocates all network structures for an instance. TODO(someone): document the rest of these parameters. @@ -207,6 +253,7 @@ class API(base.Base): return network_model.NetworkInfo.hydrate(nw_info) + @wrap_check_policy def deallocate_for_instance(self, context, instance): """Deallocates all network structures related to instance.""" @@ -216,16 +263,20 @@ class API(base.Base): args['host'] = instance['host'] self.network_rpcapi.deallocate_for_instance(context, **args) + @wrap_check_policy @refresh_cache - def add_fixed_ip_to_instance(self, context, instance, network_id): + def add_fixed_ip_to_instance(self, context, instance, network_id, + conductor_api=None): """Adds a fixed ip to instance from specified network.""" args = {'instance_id': instance['uuid'], 'host': instance['host'], 'network_id': network_id} self.network_rpcapi.add_fixed_ip_to_instance(context, **args) + @wrap_check_policy @refresh_cache - def remove_fixed_ip_from_instance(self, context, instance, address): + def remove_fixed_ip_from_instance(self, context, instance, address, + conductor_api=None): """Removes a fixed ip from instance from specified network.""" args = {'instance_id': instance['uuid'], @@ -233,11 +284,13 @@ class API(base.Base): 'address': address} self.network_rpcapi.remove_fixed_ip_from_instance(context, **args) + @wrap_check_policy def add_network_to_project(self, context, project_id, network_uuid=None): """Force adds another network to a project.""" self.network_rpcapi.add_network_to_project(context, project_id, network_uuid) + @wrap_check_policy def associate(self, context, network_uuid, host=_sentinel, project=_sentinel): """Associate or disassociate host or project to network.""" @@ -248,12 +301,12 @@ class API(base.Base): associations['project'] = project self.network_rpcapi.associate(context, network_uuid, associations) - def get_instance_nw_info(self, context, instance, update_cache=True): + @wrap_check_policy + def get_instance_nw_info(self, context, instance, conductor_api=None): """Returns all network info related to an instance.""" result = self._get_instance_nw_info(context, instance) - if update_cache: - update_instance_cache_with_nw_info(self, context, instance, - result) + update_instance_cache_with_nw_info(self, context, instance, + result, conductor_api) return result def _get_instance_nw_info(self, context, instance): @@ -267,6 +320,7 @@ class API(base.Base): return network_model.NetworkInfo.hydrate(nw_info) + @wrap_check_policy def validate_networks(self, context, requested_networks): """validate the networks passed at the time of creating the server @@ -274,6 +328,7 @@ class API(base.Base): return self.network_rpcapi.validate_networks(context, requested_networks) + @wrap_check_policy def get_instance_uuids_by_ip_filter(self, context, filters): """Returns a list of dicts in the form of {'instance_uuid': uuid, 'ip': ip} that matched the ip_filter @@ -281,12 +336,14 @@ class API(base.Base): return self.network_rpcapi.get_instance_uuids_by_ip_filter(context, filters) + @wrap_check_policy def get_dns_domains(self, context): """Returns a list of available dns domains. These can be used to create DNS entries for floating ips. """ return self.network_rpcapi.get_dns_domains(context) + @wrap_check_policy def add_dns_entry(self, context, address, name, dns_type, domain): """Create specified DNS entry for address.""" args = {'address': address, @@ -295,6 +352,7 @@ class API(base.Base): 'domain': domain} return self.network_rpcapi.add_dns_entry(context, **args) + @wrap_check_policy def modify_dns_entry(self, context, name, address, domain): """Create specified DNS entry for address.""" args = {'address': address, @@ -302,35 +360,42 @@ class API(base.Base): 'domain': domain} return self.network_rpcapi.modify_dns_entry(context, **args) + @wrap_check_policy def delete_dns_entry(self, context, name, domain): """Delete the specified dns entry.""" args = {'name': name, 'domain': domain} return self.network_rpcapi.delete_dns_entry(context, **args) + @wrap_check_policy def delete_dns_domain(self, context, domain): """Delete the specified dns domain.""" return self.network_rpcapi.delete_dns_domain(context, domain=domain) + @wrap_check_policy def get_dns_entries_by_address(self, context, address, domain): """Get entries for address and domain.""" args = {'address': address, 'domain': domain} return self.network_rpcapi.get_dns_entries_by_address(context, **args) + @wrap_check_policy def get_dns_entries_by_name(self, context, name, domain): """Get entries for name and domain.""" args = {'name': name, 'domain': domain} return self.network_rpcapi.get_dns_entries_by_name(context, **args) + @wrap_check_policy def create_private_dns_domain(self, context, domain, availability_zone): """Create a private DNS domain with nova availability zone.""" args = {'domain': domain, 'av_zone': availability_zone} return self.network_rpcapi.create_private_dns_domain(context, **args) + @wrap_check_policy def create_public_dns_domain(self, context, domain, project=None): """Create a public DNS domain with optional nova project.""" args = {'domain': domain, 'project': project} return self.network_rpcapi.create_public_dns_domain(context, **args) + @wrap_check_policy def setup_networks_on_host(self, context, instance, host=None, teardown=False): """Setup or teardown the network structures on hosts related to @@ -360,6 +425,7 @@ class API(base.Base): instance['uuid']) return [floating_ip['address'] for floating_ip in floating_ips] + @wrap_check_policy def migrate_instance_start(self, context, instance, migration): """Start to migrate the network of an instance.""" args = dict( @@ -378,6 +444,7 @@ class API(base.Base): self.network_rpcapi.migrate_instance_start(context, **args) + @wrap_check_policy def migrate_instance_finish(self, context, instance, migration): """Finish migrating the network of an instance.""" args = dict( diff --git a/nova/network/l3.py b/nova/network/l3.py index baf77c112..14abf41eb 100644 --- a/nova/network/l3.py +++ b/nova/network/l3.py @@ -48,13 +48,16 @@ class L3Driver(object): """:returns: True/False (whether the driver is initialized).""" raise NotImplementedError() - def add_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + def add_floating_ip(self, floating_ip, fixed_ip, l3_interface_id, + network=None): """Add a floating IP bound to the fixed IP with an optional l3_interface_id. Some drivers won't care about the - l3_interface_id so just pass None in that case""" + l3_interface_id so just pass None in that case. Network + is also an optional parameter.""" raise NotImplementedError() - def remove_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + def remove_floating_ip(self, floating_ip, fixed_ip, l3_interface_id, + network=None): raise NotImplementedError() def add_vpn(self, public_ip, port, private_ip): @@ -96,15 +99,17 @@ class LinuxNetL3(L3Driver): def remove_gateway(self, network_ref): linux_net.unplug(network_ref) - def add_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + def add_floating_ip(self, floating_ip, fixed_ip, l3_interface_id, + network=None): linux_net.bind_floating_ip(floating_ip, l3_interface_id) linux_net.ensure_floating_forward(floating_ip, fixed_ip, - l3_interface_id) + l3_interface_id, network) - def remove_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + def remove_floating_ip(self, floating_ip, fixed_ip, l3_interface_id, + network=None): linux_net.unbind_floating_ip(floating_ip, l3_interface_id) linux_net.remove_floating_forward(floating_ip, fixed_ip, - l3_interface_id) + l3_interface_id, network) def add_vpn(self, public_ip, port, private_ip): linux_net.ensure_vpn_forward(public_ip, port, private_ip) @@ -140,10 +145,12 @@ class NullL3(L3Driver): def remove_gateway(self, network_ref): pass - def add_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + def add_floating_ip(self, floating_ip, fixed_ip, l3_interface_id, + network=None): pass - def remove_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + def remove_floating_ip(self, floating_ip, fixed_ip, l3_interface_id, + network=None): pass def add_vpn(self, public_ip, port, private_ip): diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index e6abde609..49afc65c4 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -371,26 +371,45 @@ class IptablesManager(object): s += [('ip6tables', self.ipv6)] for cmd, tables in s: + all_tables, _err = self.execute('%s-save' % (cmd,), '-c', + run_as_root=True, + attempts=5) + all_lines = all_tables.split('\n') for table in tables: - current_table, _err = self.execute('%s-save' % (cmd,), '-c', - '-t', '%s' % (table,), - run_as_root=True, - attempts=5) - current_lines = current_table.split('\n') - new_filter = self._modify_rules(current_lines, - tables[table]) - self.execute('%s-restore' % (cmd,), '-c', run_as_root=True, - process_input='\n'.join(new_filter), - attempts=5) + start, end = self._find_table(all_lines, table) + all_lines[start:end] = self._modify_rules( + all_lines[start:end], tables[table], table_name=table) + self.execute('%s-restore' % (cmd,), '-c', run_as_root=True, + process_input='\n'.join(all_lines), + attempts=5) LOG.debug(_("IPTablesManager.apply completed with success")) - def _modify_rules(self, current_lines, table, binary=None): + def _find_table(self, lines, table_name): + if len(lines) < 3: + # length only <2 when fake iptables + return (0, 0) + try: + start = lines.index('*%s' % table_name) - 1 + except ValueError: + # Couldn't find table_name + return (0, 0) + end = lines[start:].index('COMMIT') + start + 2 + return (start, end) + + def _modify_rules(self, current_lines, table, binary=None, + table_name=None): unwrapped_chains = table.unwrapped_chains chains = table.chains remove_chains = table.remove_chains rules = table.rules remove_rules = table.remove_rules + if not current_lines: + fake_table = ['#Generated by nova', + '*' + table_name, 'COMMIT', + '#Completed by nova'] + current_lines = fake_table + # Remove any trace of our rules new_filter = filter(lambda line: binary_name not in line, current_lines) @@ -405,6 +424,9 @@ class IptablesManager(object): if not rule.startswith(':'): break + if not seen_chains: + rules_index = 2 + our_rules = [] bot_rules = [] for rule in rules: @@ -632,18 +654,29 @@ def ensure_vpn_forward(public_ip, port, private_ip): iptables_manager.apply() -def ensure_floating_forward(floating_ip, fixed_ip, device): +def ensure_floating_forward(floating_ip, fixed_ip, device, network): """Ensure floating ip forwarding rule.""" for chain, rule in floating_forward_rules(floating_ip, fixed_ip, device): iptables_manager.ipv4['nat'].add_rule(chain, rule) iptables_manager.apply() + if device != network['bridge']: + ensure_ebtables_rules(*floating_ebtables_rules(fixed_ip, network)) -def remove_floating_forward(floating_ip, fixed_ip, device): +def remove_floating_forward(floating_ip, fixed_ip, device, network): """Remove forwarding for floating ip.""" for chain, rule in floating_forward_rules(floating_ip, fixed_ip, device): iptables_manager.ipv4['nat'].remove_rule(chain, rule) iptables_manager.apply() + if device != network['bridge']: + remove_ebtables_rules(*floating_ebtables_rules(fixed_ip, network)) + + +def floating_ebtables_rules(fixed_ip, network): + """Makes sure only in-network traffic is bridged.""" + return (['PREROUTING --logical-in %s -p ipv4 --ip-src %s ' + '! --ip-dst %s -j redirect --redirect-target ACCEPT' % + (network['bridge'], fixed_ip, network['cidr'])], 'nat') def floating_forward_rules(floating_ip, fixed_ip, device): @@ -1111,6 +1144,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. @@ -1150,7 +1217,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): @@ -1374,23 +1441,23 @@ class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver): @lockutils.synchronized('ebtables', 'nova-', external=True) -def ensure_ebtables_rules(rules): +def ensure_ebtables_rules(rules, table='filter'): for rule in rules: - cmd = ['ebtables', '-D'] + rule.split() + cmd = ['ebtables', '-t', table, '-D'] + rule.split() _execute(*cmd, check_exit_code=False, run_as_root=True) - cmd[1] = '-I' + cmd[3] = '-I' _execute(*cmd, run_as_root=True) @lockutils.synchronized('ebtables', 'nova-', external=True) -def remove_ebtables_rules(rules): +def remove_ebtables_rules(rules, table='filter'): for rule in rules: - cmd = ['ebtables', '-D'] + rule.split() + cmd = ['ebtables', '-t', table, '-D'] + rule.split() _execute(*cmd, check_exit_code=False, run_as_root=True) 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)) @@ -1406,7 +1473,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) @@ -1416,7 +1483,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)) @@ -1432,7 +1499,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) @@ -1522,7 +1589,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) @@ -1557,22 +1624,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 7b69c7a36..d1dabdfd9 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -44,11 +44,9 @@ topologies. All of the network commands are issued to a subclass of """ import datetime -import functools import itertools import math import re -import socket import uuid from eventlet import greenpool @@ -73,7 +71,6 @@ from nova.openstack.common.notifier import api as notifier from nova.openstack.common.rpc import common as rpc_common from nova.openstack.common import timeutils from nova.openstack.common import uuidutils -import nova.policy from nova import quota from nova import servicegroup from nova import utils @@ -147,9 +144,6 @@ network_opts = [ cfg.BoolOpt('auto_assign_floating_ip', default=False, help='Autoassigning floating ip to VM'), - cfg.StrOpt('network_host', - default=socket.getfqdn(), - help='Network host to use for ip allocation in flat modes'), cfg.BoolOpt('fake_network', default=False, help='If passed, use fake network devices and addresses'), @@ -280,27 +274,6 @@ class RPCAllocateFixedIP(object): self.network_rpcapi.deallocate_fixed_ip(context, address, host) -def wrap_check_policy(func): - """Check policy corresponding to the wrapped methods prior to execution.""" - - @functools.wraps(func) - def wrapped(self, context, *args, **kwargs): - action = func.__name__ - check_policy(context, action) - return func(self, context, *args, **kwargs) - - return wrapped - - -def check_policy(context, action): - target = { - 'project_id': context.project_id, - 'user_id': context.user_id, - } - _action = 'network:%s' % action - nova.policy.enforce(context, _action, target) - - class FloatingIP(object): """Mixin class for adding floating IP functionality to a manager.""" @@ -320,22 +293,23 @@ class FloatingIP(object): fixed_ip_id = floating_ip.get('fixed_ip_id') if fixed_ip_id: try: - fixed_ip_ref = self.db.fixed_ip_get(admin_context, - fixed_ip_id) + fixed_ip = self.db.fixed_ip_get(admin_context, + fixed_ip_id, + get_network=True) except exception.FixedIpNotFound: msg = _('Fixed ip %(fixed_ip_id)s not found') % locals() LOG.debug(msg) continue - fixed_address = fixed_ip_ref['address'] interface = CONF.public_interface or floating_ip['interface'] try: self.l3driver.add_floating_ip(floating_ip['address'], - fixed_address, interface) + fixed_ip['address'], + interface, + fixed_ip['network']) except exception.ProcessExecutionError: LOG.debug(_('Interface %(interface)s not found'), locals()) raise exception.NoFloatingIpInterface(interface=interface) - @wrap_check_policy def allocate_for_instance(self, context, **kwargs): """Handles allocating the floating IP resources for an instance. @@ -376,7 +350,6 @@ class FloatingIP(object): return nw_info - @wrap_check_policy def deallocate_for_instance(self, context, **kwargs): """Handles deallocating floating IP resources for an instance. @@ -439,7 +412,6 @@ class FloatingIP(object): 'project': context.project_id}) raise exception.NotAuthorized() - @wrap_check_policy def allocate_floating_ip(self, context, project_id, auto_assigned=False, pool=None): """Gets a floating ip from the pool.""" @@ -479,10 +451,9 @@ class FloatingIP(object): return floating_ip @rpc_common.client_exceptions(exception.FloatingIpNotFoundForAddress) - @wrap_check_policy def deallocate_floating_ip(self, context, address, affect_auto_assigned=False): - """Returns an floating ip to the pool.""" + """Returns a floating ip to the pool.""" floating_ip = self.db.floating_ip_get_by_address(context, address) # handle auto_assigned @@ -526,7 +497,6 @@ class FloatingIP(object): QUOTAS.commit(context, reservations) @rpc_common.client_exceptions(exception.FloatingIpNotFoundForAddress) - @wrap_check_policy def associate_floating_ip(self, context, floating_address, fixed_address, affect_auto_assigned=False): """Associates a floating ip with a fixed ip. @@ -590,17 +560,17 @@ class FloatingIP(object): @lockutils.synchronized(unicode(floating_address), 'nova-') def do_associate(): # associate floating ip - res = self.db.floating_ip_fixed_ip_associate(context, - floating_address, - fixed_address, - self.host) - if not res: + fixed = self.db.floating_ip_fixed_ip_associate(context, + floating_address, + fixed_address, + self.host) + if not fixed: # NOTE(vish): ip was already associated return try: # gogo driver time self.l3driver.add_floating_ip(floating_address, fixed_address, - interface) + interface, fixed['network']) except exception.ProcessExecutionError as e: self.db.floating_ip_disassociate(context, floating_address) if "Cannot find device" in str(e): @@ -617,7 +587,6 @@ class FloatingIP(object): do_associate() @rpc_common.client_exceptions(exception.FloatingIpNotFoundForAddress) - @wrap_check_policy def disassociate_floating_ip(self, context, address, affect_auto_assigned=False): """Disassociates a floating ip from its fixed ip. @@ -681,18 +650,18 @@ 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) + fixed = self.db.floating_ip_disassociate(context, address) - if not fixed_address: + if not fixed: # NOTE(vish): ip was already disassociated return if interface: # go go driver time - self.l3driver.remove_floating_ip(address, fixed_address, - interface) + self.l3driver.remove_floating_ip(address, fixed['address'], + interface, fixed['network']) payload = dict(project_id=context.project_id, instance_id=instance_uuid, floating_ip=address) @@ -703,31 +672,32 @@ class FloatingIP(object): do_disassociate() @rpc_common.client_exceptions(exception.FloatingIpNotFound) - @wrap_check_policy def get_floating_ip(self, context, id): """Returns a floating IP as a dict.""" return dict(self.db.floating_ip_get(context, id).iteritems()) - @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) + + 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] - @wrap_check_policy def get_floating_ip_by_address(self, context, address): """Returns a floating IP as a dict.""" return dict(self.db.floating_ip_get_by_address(context, address).iteritems()) - @wrap_check_policy def get_floating_ips_by_project(self, context): """Returns the floating IPs allocated to a project.""" ips = self.db.floating_ip_get_all_by_project(context, context.project_id) return [dict(ip.iteritems()) for ip in ips] - @wrap_check_policy def get_floating_ips_by_fixed_address(self, context, fixed_address): """Returns the floating IPs associated with a fixed_address.""" floating_ips = self.db.floating_ip_get_by_fixed_address(context, @@ -741,7 +711,6 @@ class FloatingIP(object): return True return False if floating_ip.get('fixed_ip_id') else True - @wrap_check_policy def migrate_instance_start(self, context, instance_uuid, floating_addresses, rxtx_factor=None, project_id=None, @@ -765,10 +734,12 @@ class FloatingIP(object): interface = CONF.public_interface or floating_ip['interface'] fixed_ip = self.db.fixed_ip_get(context, - floating_ip['fixed_ip_id']) + floating_ip['fixed_ip_id'], + get_network=True) self.l3driver.remove_floating_ip(floating_ip['address'], fixed_ip['address'], - interface) + interface, + fixed_ip['network']) # NOTE(wenjianhn): Make this address will not be bound to public # interface when restarts nova-network on dest compute node @@ -776,7 +747,6 @@ class FloatingIP(object): floating_ip['address'], {'host': None}) - @wrap_check_policy def migrate_instance_finish(self, context, instance_uuid, floating_addresses, host=None, rxtx_factor=None, project_id=None, @@ -807,10 +777,12 @@ class FloatingIP(object): interface = CONF.public_interface or floating_ip['interface'] fixed_ip = self.db.fixed_ip_get(context, - floating_ip['fixed_ip_id']) + floating_ip['fixed_ip_id'], + get_network=True) self.l3driver.add_floating_ip(floating_ip['address'], fixed_ip['address'], - interface) + interface, + fixed_ip['network']) def _prepare_domain_entry(self, context, domain): domainref = self.db.dnsdomain_get(context, domain) @@ -827,7 +799,6 @@ class FloatingIP(object): 'project': project} return this_domain - @wrap_check_policy def get_dns_domains(self, context): domains = [] @@ -850,17 +821,14 @@ class FloatingIP(object): return domains - @wrap_check_policy def add_dns_entry(self, context, address, name, dns_type, domain): self.floating_dns_manager.create_entry(name, address, dns_type, domain) - @wrap_check_policy def modify_dns_entry(self, context, address, name, domain): self.floating_dns_manager.modify_address(name, address, domain) - @wrap_check_policy def delete_dns_entry(self, context, name, domain): self.floating_dns_manager.delete_entry(name, domain) @@ -873,17 +841,14 @@ class FloatingIP(object): for name in names: self.delete_dns_entry(context, name, domain['domain']) - @wrap_check_policy def get_dns_entries_by_address(self, context, address, domain): return self.floating_dns_manager.get_entries_by_address(address, domain) - @wrap_check_policy def get_dns_entries_by_name(self, context, name, domain): return self.floating_dns_manager.get_entries_by_name(name, domain) - @wrap_check_policy def create_private_dns_domain(self, context, domain, av_zone): self.db.dnsdomain_register_for_zone(context, domain, av_zone) try: @@ -893,7 +858,6 @@ class FloatingIP(object): 'changing zone to |%(av_zone)s|.'), {'domain': domain, 'av_zone': av_zone}) - @wrap_check_policy def create_public_dns_domain(self, context, domain, project): self.db.dnsdomain_register_for_project(context, domain, project) try: @@ -903,7 +867,6 @@ class FloatingIP(object): 'changing project to |%(project)s|.'), {'domain': domain, 'project': project}) - @wrap_check_policy def delete_dns_domain(self, context, domain): self.db.dnsdomain_unregister(context, domain) self.floating_dns_manager.delete_domain(domain) @@ -925,7 +888,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 @@ -1062,7 +1025,6 @@ class NetworkManager(manager.SchedulerDependentManager): # floating ips MUST override this or use the Mixin return [] - @wrap_check_policy def get_instance_uuids_by_ip_filter(self, context, filters): fixed_ip_filter = filters.get('fixed_ip') ip_filter = re.compile(str(filters.get('ip'))) @@ -1132,7 +1094,6 @@ class NetworkManager(manager.SchedulerDependentManager): return [network for network in networks if not network['vlan']] - @wrap_check_policy def allocate_for_instance(self, context, **kwargs): """Handles allocating the various network resources for an instance. @@ -1165,7 +1126,6 @@ class NetworkManager(manager.SchedulerDependentManager): return self.get_instance_nw_info(context, instance_id, instance_uuid, rxtx_factor, host) - @wrap_check_policy def deallocate_for_instance(self, context, **kwargs): """Handles deallocating various network resources for an instance. @@ -1201,7 +1161,6 @@ class NetworkManager(manager.SchedulerDependentManager): self.db.virtual_interface_delete_by_instance(read_deleted_context, instance['uuid']) - @wrap_check_policy def get_instance_nw_info(self, context, instance_id, instance_uuid, rxtx_factor, host, **kwargs): """Creates network info list for instance. @@ -1384,7 +1343,6 @@ class NetworkManager(manager.SchedulerDependentManager): instance_uuid) raise exception.VirtualInterfaceMacAddressException() - @wrap_check_policy def add_fixed_ip_to_instance(self, context, instance_id, host, network_id): """Adds a fixed ip to an instance from specified network.""" if uuidutils.is_uuid_like(network_id): @@ -1397,7 +1355,6 @@ class NetworkManager(manager.SchedulerDependentManager): """Return backdoor port for eventlet_backdoor.""" return self.backdoor_port - @wrap_check_policy def remove_fixed_ip_from_instance(self, context, instance_id, host, address): """Removes a fixed ip from an instance from specified network.""" @@ -1772,7 +1729,6 @@ class NetworkManager(manager.SchedulerDependentManager): self._create_fixed_ips(context, network['id'], fixed_cidr) return networks - @wrap_check_policy def delete_network(self, context, fixed_range, uuid, require_disassociated=True): @@ -1877,7 +1833,6 @@ class NetworkManager(manager.SchedulerDependentManager): """Sets up network on this host.""" raise NotImplementedError() - @wrap_check_policy def validate_networks(self, context, networks): """check if the networks exists and host is set to each network. @@ -1916,7 +1871,6 @@ class NetworkManager(manager.SchedulerDependentManager): return self.db.network_get_all_by_uuids(context, network_uuids, project_only="allow_none") - @wrap_check_policy def get_vifs_by_instance(self, context, instance_id): """Returns the vifs associated with an instance.""" instance = self.db.instance_get(context, instance_id) @@ -1926,28 +1880,16 @@ class NetworkManager(manager.SchedulerDependentManager): def get_instance_id_by_floating_address(self, context, address): """Returns the instance id a floating ip's fixed ip is allocated to.""" - floating_ip = self.db.floating_ip_get_by_address(context, address) - if floating_ip['fixed_ip_id'] is None: + fixed_ip = self.db.fixed_ip_get_by_floating_address(context, address) + if fixed_ip is None: return None + else: + return fixed_ip['instance_uuid'] - fixed_ip = self.db.fixed_ip_get(context, floating_ip['fixed_ip_id']) - - # NOTE(tr3buchet): this can be None - # NOTE(mikal): we need to return the instance id here because its used - # by ec2 (and possibly others) - uuid = fixed_ip['instance_uuid'] - if not uuid: - return uuid - - instance = self.db.instance_get_by_uuid(context, uuid) - return instance['id'] - - @wrap_check_policy def get_network(self, context, network_uuid): network = self.db.network_get_by_uuid(context.elevated(), network_uuid) return jsonutils.to_primitive(network) - @wrap_check_policy def get_all_networks(self, context): try: networks = self.db.network_get_all(context) @@ -1955,18 +1897,15 @@ class NetworkManager(manager.SchedulerDependentManager): return [] return [jsonutils.to_primitive(network) for network in networks] - @wrap_check_policy def disassociate_network(self, context, network_uuid): network = self.get_network(context, network_uuid) self.db.network_disassociate(context, network['id']) - @wrap_check_policy def get_fixed_ip(self, context, id): """Return a fixed ip.""" fixed = self.db.fixed_ip_get(context, id) return jsonutils.to_primitive(fixed) - @wrap_check_policy def get_fixed_ip_by_address(self, context, address): fixed = self.db.fixed_ip_get_by_address(context, address) return jsonutils.to_primitive(fixed) @@ -2070,27 +2009,28 @@ class FlatManager(NetworkManager): # We were throwing an exception, but this was messing up horizon. # Timing makes it difficult to implement floating ips here, in Essex. - @wrap_check_policy def get_floating_ip(self, context, id): """Returns a floating IP as a dict.""" return None - @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 {} + + def get_floating_ip_pools(self, context): + """Returns list of floating ip pools.""" return {} - @wrap_check_policy def get_floating_ip_by_address(self, context, address): """Returns a floating IP as a dict.""" return None - @wrap_check_policy def get_floating_ips_by_project(self, context): """Returns the floating IPs allocated to a project.""" return [] - @wrap_check_policy def get_floating_ips_by_fixed_address(self, context, fixed_address): """Returns the floating IPs associated with a fixed_address.""" return [] @@ -2247,7 +2187,6 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): self._setup_network_on_host(context, network) return address - @wrap_check_policy def add_network_to_project(self, context, project_id, network_uuid=None): """Force adds another network to a project.""" if network_uuid is not None: @@ -2256,7 +2195,6 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): network_id = None self.db.network_associate(context, project_id, network_id, force=True) - @wrap_check_policy def associate(self, context, network_uuid, associations): """Associate or disassociate host or project to network.""" network_id = self.get_network(context, network_uuid)['id'] 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/__init__.py b/nova/network/quantumv2/__init__.py index 914600ed8..13ce8b794 100644 --- a/nova/network/quantumv2/__init__.py +++ b/nova/network/quantumv2/__init__.py @@ -30,6 +30,7 @@ def _get_auth_token(): httpclient = client.HTTPClient( username=CONF.quantum_admin_username, tenant_name=CONF.quantum_admin_tenant_name, + region_name=CONF.quantum_region_name, password=CONF.quantum_admin_password, auth_url=CONF.quantum_admin_auth_url, timeout=CONF.quantum_url_timeout, diff --git a/nova/network/quantumv2/api.py b/nova/network/quantumv2/api.py index 0deb3a4bb..1c8c60113 100644 --- a/nova/network/quantumv2/api.py +++ b/nova/network/quantumv2/api.py @@ -41,6 +41,8 @@ quantum_opts = [ help='password for connecting to quantum in admin context'), cfg.StrOpt('quantum_admin_tenant_name', help='tenant name for connecting to quantum in admin context'), + cfg.StrOpt('quantum_region_name', + help='region name for connecting to quantum in admin context'), cfg.StrOpt('quantum_admin_auth_url', default='http://localhost:5000/v2.0', help='auth url for connecting to quantum in admin context'), @@ -111,9 +113,19 @@ class API(base.Base): :param macs: None or a set of MAC addresses that the instance should use. macs is supplied by the hypervisor driver (contrast with requested_networks which is user supplied). - NB: QuantumV2 does not yet honour mac address limits. + NB: QuantumV2 currently assigns hypervisor supplied MAC addresses + to arbitrary networks, which requires openflow switches to + function correctly if more than one network is being used with + the bare metal hypervisor (which is the only one known to limit + MAC addresses). """ hypervisor_macs = kwargs.get('macs', None) + available_macs = None + if hypervisor_macs is not None: + # Make a copy we can mutate: records macs that have not been used + # to create a port on a network. If we find a mac with a + # pre-allocated port we also remove it from this set. + available_macs = set(hypervisor_macs) quantum = quantumv2.get_client(context) LOG.debug(_('allocate_for_instance() for %s'), instance['display_name']) @@ -133,6 +145,12 @@ class API(base.Base): if port['mac_address'] not in hypervisor_macs: raise exception.PortNotUsable(port_id=port_id, instance=instance['display_name']) + else: + # Don't try to use this MAC if we need to create a + # port on the fly later. Identical MACs may be + # configured by users into multiple ports so we + # discard rather than popping. + available_macs.discard(port['mac_address']) network_id = port['network_id'] ports[network_id] = port elif fixed_ip: @@ -141,7 +159,6 @@ class API(base.Base): nets = self._get_available_networks(context, instance['project_id'], net_ids) - touched_port_ids = [] created_port_ids = [] for network in nets: @@ -161,6 +178,12 @@ class API(base.Base): port_req_body['port']['network_id'] = network_id port_req_body['port']['admin_state_up'] = True port_req_body['port']['tenant_id'] = instance['project_id'] + if available_macs is not None: + if not available_macs: + raise exception.PortNotFree( + instance=instance['display_name']) + mac_address = available_macs.pop() + port_req_body['port']['mac_address'] = mac_address created_port_ids.append( quantum.create_port(port_req_body)['port']['id']) except Exception: @@ -184,7 +207,8 @@ class API(base.Base): self.trigger_security_group_members_refresh(context, instance) self.trigger_instance_add_security_group_refresh(context, instance) - return self.get_instance_nw_info(context, instance, networks=nets) + return self.get_instance_nw_info(context, instance, networks=nets, + conductor_api=kwargs.get('conductor_api')) def deallocate_for_instance(self, context, instance, **kwargs): """Deallocate all network resources related to the instance.""" @@ -203,10 +227,10 @@ class API(base.Base): self.trigger_instance_remove_security_group_refresh(context, instance) def get_instance_nw_info(self, context, instance, networks=None, - update_cache=True): + conductor_api=None): result = self._get_instance_nw_info(context, instance, networks) - if update_cache: - update_instance_info_cache(self, context, instance, result) + update_instance_info_cache(self, context, instance, result, + conductor_api) return result def _get_instance_nw_info(self, context, instance, networks=None): @@ -215,13 +239,66 @@ class API(base.Base): nw_info = self._build_network_info_model(context, instance, networks) return network_model.NetworkInfo.hydrate(nw_info) - def add_fixed_ip_to_instance(self, context, instance, network_id): + def add_fixed_ip_to_instance(self, context, instance, network_id, + conductor_api=None): """Add a fixed ip to the instance from specified network.""" - raise NotImplementedError() + search_opts = {'network_id': network_id} + data = quantumv2.get_client(context).list_subnets(**search_opts) + ipam_subnets = data.get('subnets', []) + if not ipam_subnets: + raise exception.NetworkNotFoundForInstance( + instance_id=instance['uuid']) + + zone = 'compute:%s' % instance['availability_zone'] + search_opts = {'device_id': instance['uuid'], + 'device_owner': zone, + 'network_id': network_id} + data = quantumv2.get_client(context).list_ports(**search_opts) + ports = data['ports'] + for p in ports: + fixed_ips = p['fixed_ips'] + for subnet in ipam_subnets: + fixed_ip = {'subnet_id': subnet['id']} + fixed_ips.append(fixed_ip) + port_req_body = {'port': {'fixed_ips': fixed_ips}} + try: + quantumv2.get_client(context).update_port(p['id'], + port_req_body) + except Exception as ex: + msg = _("Unable to update port %(portid)s with" + " failure: %(exception)s") + LOG.debug(msg, {'portid': p['id'], 'exception': ex}) + return + raise exception.NetworkNotFoundForInstance( + instance_id=instance['uuid']) - def remove_fixed_ip_from_instance(self, context, instance, address): + def remove_fixed_ip_from_instance(self, context, instance, address, + conductor_api=None): """Remove a fixed ip from the instance.""" - raise NotImplementedError() + zone = 'compute:%s' % instance['availability_zone'] + search_opts = {'device_id': instance['uuid'], + 'device_owner': zone, + 'fixed_ips': 'ip_address=%s' % address} + data = quantumv2.get_client(context).list_ports(**search_opts) + ports = data['ports'] + for p in ports: + fixed_ips = p['fixed_ips'] + new_fixed_ips = [] + for fixed_ip in fixed_ips: + if fixed_ip['ip_address'] != address: + new_fixed_ips.append(fixed_ip) + port_req_body = {'port': {'fixed_ips': new_fixed_ips}} + try: + quantumv2.get_client(context).update_port(p['id'], + port_req_body) + except Exception as ex: + msg = _("Unable to update port %(portid)s with" + " failure: %(exception)s") + LOG.debug(msg, {'portid': p['id'], 'exception': ex}) + return + + raise exception.FixedIpNotFoundForSpecificInstance( + instance_uuid=instance['uuid'], ip=address) def validate_networks(self, context, requested_networks): """Validate that the tenant can use the requested networks.""" @@ -589,11 +666,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'] @@ -616,6 +695,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..f40fff7f2 100644 --- a/nova/notifications.py +++ b/nova/notifications.py @@ -21,7 +21,6 @@ the system. import nova.context from nova import db -from nova import exception from nova.image import glance from nova import network from nova.network import model as network_model @@ -283,12 +282,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/eventlet_backdoor.py b/nova/openstack/common/eventlet_backdoor.py index f18e84f6d..118385427 100644 --- a/nova/openstack/common/eventlet_backdoor.py +++ b/nova/openstack/common/eventlet_backdoor.py @@ -46,7 +46,7 @@ def _find_objects(t): def _print_greenthreads(): - for i, gt in enumerate(find_objects(greenlet.greenlet)): + for i, gt in enumerate(_find_objects(greenlet.greenlet)): print i, gt traceback.print_stack(gt.gr_frame) print diff --git a/nova/openstack/common/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/lockutils.py b/nova/openstack/common/lockutils.py index ba390dc69..6f80a1f67 100644 --- a/nova/openstack/common/lockutils.py +++ b/nova/openstack/common/lockutils.py @@ -28,6 +28,7 @@ from eventlet import semaphore from nova.openstack.common import cfg from nova.openstack.common import fileutils +from nova.openstack.common.gettextutils import _ from nova.openstack.common import log as logging @@ -219,6 +220,11 @@ def synchronized(name, lock_file_prefix, external=False, lock_path=None): 'method': f.__name__}) retval = f(*args, **kwargs) finally: + LOG.debug(_('Released file lock "%(lock)s" at %(path)s' + ' for method "%(method)s"...'), + {'lock': name, + 'path': lock_file_path, + 'method': f.__name__}) # NOTE(vish): This removes the tempdir if we needed # to create one. This is used to cleanup # the locks left behind by unit tests. diff --git a/nova/openstack/common/log.py b/nova/openstack/common/log.py index 6e25bb597..32513bb32 100644 --- a/nova/openstack/common/log.py +++ b/nova/openstack/common/log.py @@ -47,21 +47,82 @@ 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)d %(levelname)s %(name)s ' + default='%(asctime)s.%(msecs)03d %(levelname)s %(name)s ' '[%(request_id)s %(user)s %(tenant)s] %(instance)s' '%(message)s', help='format string to use for log messages with context'), cfg.StrOpt('logging_default_format_string', - default='%(asctime)s.%(msecs)d %(process)d %(levelname)s ' + default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s [-] %(instance)s%(message)s', help='format string to use for log messages without context'), cfg.StrOpt('logging_debug_format_suffix', default='%(funcName)s %(pathname)s:%(lineno)d', help='data to append to log format when level is DEBUG'), cfg.StrOpt('logging_exception_prefix', - default='%(asctime)s.%(msecs)d %(process)d TRACE %(name)s ' + default='%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s ' '%(instance)s', help='prefix each line of exception output with this format'), cfg.ListOpt('default_log_levels', @@ -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 @@ -259,7 +305,7 @@ class JSONFormatter(logging.Formatter): class PublishErrorsHandler(logging.Handler): def emit(self, record): if ('nova.openstack.common.notifier.log_notifier' in - CONF.notification_driver): + CONF.notification_driver): return notifier.api.notify(None, 'error.publisher', 'error_notification', @@ -361,10 +407,12 @@ def _setup_logging_from_conf(product_name): datefmt=datefmt)) handler.setFormatter(LegacyFormatter(datefmt=datefmt)) - if CONF.verbose or CONF.debug: + if CONF.debug: log_root.setLevel(logging.DEBUG) - else: + elif CONF.verbose: log_root.setLevel(logging.INFO) + else: + log_root.setLevel(logging.WARNING) level = logging.NOTSET for pair in CONF.default_log_levels: @@ -425,7 +473,7 @@ class LegacyFormatter(logging.Formatter): self._fmt = CONF.logging_default_format_string if (record.levelno == logging.DEBUG and - CONF.logging_debug_format_suffix): + CONF.logging_debug_format_suffix): self._fmt += " " + CONF.logging_debug_format_suffix # Cache this on the record, Logger will respect our formated copy diff --git a/nova/rootwrap/__init__.py b/nova/openstack/common/rootwrap/__init__.py index 671d3c173..671d3c173 100644 --- a/nova/rootwrap/__init__.py +++ b/nova/openstack/common/rootwrap/__init__.py diff --git a/nova/rootwrap/filters.py b/nova/openstack/common/rootwrap/filters.py index 8958f1ba1..905bbabea 100644 --- a/nova/rootwrap/filters.py +++ b/nova/openstack/common/rootwrap/filters.py @@ -20,7 +20,7 @@ import re class CommandFilter(object): - """Command filter only checking that the 1st argument matches exec_path.""" + """Command filter only checking that the 1st argument matches exec_path""" def __init__(self, exec_path, run_as, *args): self.name = '' @@ -30,7 +30,7 @@ class CommandFilter(object): self.real_exec = None def get_exec(self, exec_dirs=[]): - """Returns existing executable, or empty string if none found.""" + """Returns existing executable, or empty string if none found""" if self.real_exec is not None: return self.real_exec self.real_exec = "" @@ -46,7 +46,7 @@ class CommandFilter(object): return self.real_exec def match(self, userargs): - """Only check that the first argument (command) matches exec_path.""" + """Only check that the first argument (command) matches exec_path""" if (os.path.basename(self.exec_path) == userargs[0]): return True return False @@ -60,12 +60,12 @@ class CommandFilter(object): return [to_exec] + userargs[1:] def get_environment(self, userargs): - """Returns specific environment to set, None if none.""" + """Returns specific environment to set, None if none""" return None class RegExpFilter(CommandFilter): - """Command filter doing regexp matching for every argument.""" + """Command filter doing regexp matching for every argument""" def match(self, userargs): # Early skip if command or number of args don't match @@ -89,15 +89,15 @@ class RegExpFilter(CommandFilter): class DnsmasqFilter(CommandFilter): - """Specific filter for the dnsmasq call (which includes env).""" + """Specific filter for the dnsmasq call (which includes env)""" CONFIG_FILE_ARG = 'CONFIG_FILE' def match(self, userargs): if (userargs[0] == 'env' and - userargs[1].startswith(self.CONFIG_FILE_ARG) and - userargs[2].startswith('NETWORK_ID=') and - userargs[3] == 'dnsmasq'): + userargs[1].startswith(self.CONFIG_FILE_ARG) and + userargs[2].startswith('NETWORK_ID=') and + userargs[3] == 'dnsmasq'): return True return False @@ -114,7 +114,7 @@ class DnsmasqFilter(CommandFilter): class DeprecatedDnsmasqFilter(DnsmasqFilter): - """Variant of dnsmasq filter to support old-style FLAGFILE.""" + """Variant of dnsmasq filter to support old-style FLAGFILE""" CONFIG_FILE_ARG = 'FLAGFILE' @@ -164,7 +164,7 @@ class KillFilter(CommandFilter): class ReadFileFilter(CommandFilter): - """Specific filter for the utils.read_file_as_root call.""" + """Specific filter for the utils.read_file_as_root call""" def __init__(self, file_path, *args): self.file_path = file_path diff --git a/nova/rootwrap/wrapper.py b/nova/openstack/common/rootwrap/wrapper.py index 70bd63c47..4452177fe 100644 --- a/nova/rootwrap/wrapper.py +++ b/nova/openstack/common/rootwrap/wrapper.py @@ -22,7 +22,7 @@ import logging.handlers import os import string -from nova.rootwrap import filters +from nova.openstack.common.rootwrap import filters class NoFilterMatched(Exception): @@ -93,7 +93,7 @@ def setup_syslog(execname, facility, level): def build_filter(class_name, *args): - """Returns a filter object of class class_name.""" + """Returns a filter object of class class_name""" if not hasattr(filters, class_name): logging.warning("Skipping unknown filter class (%s) specified " "in filter definitions" % class_name) @@ -103,7 +103,7 @@ def build_filter(class_name, *args): def load_filters(filters_path): - """Load filters from a list of directories.""" + """Load filters from a list of directories""" filterlist = [] for filterdir in filters_path: if not os.path.isdir(filterdir): diff --git a/nova/openstack/common/rpc/impl_kombu.py b/nova/openstack/common/rpc/impl_kombu.py index bf38201f5..305dc7877 100644 --- a/nova/openstack/common/rpc/impl_kombu.py +++ b/nova/openstack/common/rpc/impl_kombu.py @@ -175,7 +175,7 @@ class ConsumerBase(object): try: self.queue.cancel(self.tag) except KeyError, e: - # NOTE(comstud): Kludge to get around a amqplib bug + # NOTE(comstud): Kludge to get around an amqplib bug if str(e) != "u'%s'" % self.tag: raise self.queue = None diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index d1ae1cd6e..01bef4185 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -23,11 +23,11 @@ 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 from nova.compute import vm_states +from nova.conductor import api as conductor_api from nova import db from nova import exception from nova import notifications @@ -56,8 +56,6 @@ CONF.register_opts(scheduler_driver_opts) def handle_schedule_error(context, ex, instance_uuid, request_spec): if not isinstance(ex, exception.NoValidHost): LOG.exception(_("Exception during scheduler.run_instance")) - compute_utils.add_instance_fault_from_exc(context, - instance_uuid, ex, sys.exc_info()) state = vm_states.ERROR.upper() LOG.warning(_('Setting instance to %(state)s state.'), locals(), instance_uuid=instance_uuid) @@ -68,6 +66,9 @@ def handle_schedule_error(context, ex, instance_uuid, request_spec): 'task_state': None}) notifications.send_update(context, old_ref, new_ref, service="scheduler") + compute_utils.add_instance_fault_from_exc(context, + conductor_api.LocalAPI(), + new_ref, ex, sys.exc_info()) properties = request_spec.get('instance_properties', {}) payload = dict(request_spec=request_spec, @@ -115,7 +116,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/scheduler/filters/affinity_filter.py b/nova/scheduler/filters/affinity_filter.py index 03bf0dd6e..7e51a15f2 100644 --- a/nova/scheduler/filters/affinity_filter.py +++ b/nova/scheduler/filters/affinity_filter.py @@ -25,12 +25,6 @@ class AffinityFilter(filters.BaseHostFilter): def __init__(self): self.compute_api = compute.API() - def _all_hosts(self, context): - all_hosts = {} - for instance in self.compute_api.get_all(context): - all_hosts[instance['uuid']] = instance['host'] - return all_hosts - class DifferentHostFilter(AffinityFilter): '''Schedule the instance on a different host from a set of instances.''' @@ -38,15 +32,15 @@ class DifferentHostFilter(AffinityFilter): def host_passes(self, host_state, filter_properties): context = filter_properties['context'] scheduler_hints = filter_properties.get('scheduler_hints') or {} - me = host_state.host affinity_uuids = scheduler_hints.get('different_host', []) if isinstance(affinity_uuids, basestring): affinity_uuids = [affinity_uuids] if affinity_uuids: - all_hosts = self._all_hosts(context) - return not any([i for i in affinity_uuids - if all_hosts.get(i) == me]) + return not self.compute_api.get_all(context, + {'host': host_state.host, + 'uuid': affinity_uuids, + 'deleted': False}) # With no different_host key return True @@ -59,16 +53,14 @@ class SameHostFilter(AffinityFilter): def host_passes(self, host_state, filter_properties): context = filter_properties['context'] scheduler_hints = filter_properties.get('scheduler_hints') or {} - me = host_state.host affinity_uuids = scheduler_hints.get('same_host', []) if isinstance(affinity_uuids, basestring): affinity_uuids = [affinity_uuids] if affinity_uuids: - all_hosts = self._all_hosts(context) - return any([i for i - in affinity_uuids - if all_hosts.get(i) == me]) + return self.compute_api.get_all(context, {'host': host_state.host, + 'uuid': affinity_uuids, + 'deleted': False}) # With no same_host key return True diff --git a/nova/scheduler/filters/availability_zone_filter.py b/nova/scheduler/filters/availability_zone_filter.py index 585acbaf8..390276ea3 100644 --- a/nova/scheduler/filters/availability_zone_filter.py +++ b/nova/scheduler/filters/availability_zone_filter.py @@ -14,7 +14,6 @@ # under the License. -from nova import availability_zones from nova import db from nova.openstack.common import cfg from nova.scheduler import filters diff --git a/nova/scheduler/filters/trusted_filter.py b/nova/scheduler/filters/trusted_filter.py index 4d0f2305f..14f1a37b0 100644 --- a/nova/scheduler/filters/trusted_filter.py +++ b/nova/scheduler/filters/trusted_filter.py @@ -48,9 +48,12 @@ import httplib import socket import ssl +from nova import context +from nova import db from nova.openstack.common import cfg from nova.openstack.common import jsonutils from nova.openstack.common import log as logging +from nova.openstack.common import timeutils from nova.scheduler import filters @@ -78,6 +81,9 @@ trusted_opts = [ deprecated_name='auth_blob', default=None, help='attestation authorization blob - must change'), + cfg.IntOpt('attestation_auth_timeout', + default=60, + help='Attestation status cache valid period length'), ] CONF = cfg.CONF @@ -119,7 +125,7 @@ class HTTPSClientAuthConnection(httplib.HTTPSConnection): cert_reqs=ssl.CERT_REQUIRED) -class AttestationService(httplib.HTTPSConnection): +class AttestationService(object): # Provide access wrapper to attestation server to get integrity report. def __init__(self): @@ -156,10 +162,10 @@ class AttestationService(httplib.HTTPSConnection): except (socket.error, IOError) as e: return IOError, None - def _request(self, cmd, subcmd, host): + def _request(self, cmd, subcmd, hosts): body = {} - body['count'] = 1 - body['hosts'] = host + body['count'] = len(hosts) + body['hosts'] = hosts cooked = jsonutils.dumps(body) headers = {} headers['content-type'] = 'application/json' @@ -173,39 +179,124 @@ class AttestationService(httplib.HTTPSConnection): else: return status, None - def _check_trust(self, data, host): - for item in data: - for state in item['hosts']: - if state['host_name'] == host: - return state['trust_lvl'] - return "" + def do_attestation(self, hosts): + """Attests compute nodes through OAT service. - def do_attestation(self, host): - state = [] - status, data = self._request("POST", "PollHosts", host) - if status != httplib.OK: - return {} - state.append(data) - return self._check_trust(state, host) + :param hosts: hosts list to be attested + :returns: dictionary for trust level and validate time + """ + result = None + status, data = self._request("POST", "PollHosts", hosts) + if data != None: + result = data.get('hosts') -class TrustedFilter(filters.BaseHostFilter): - """Trusted filter to support Trusted Compute Pools.""" + return result + + +class ComputeAttestationCache(object): + """Cache for compute node attestation + + Cache compute node's trust level for sometime, + if the cache is out of date, poll OAT service to flush the + cache. + + OAT service may have cache also. OAT service's cache valid time + should be set shorter than trusted filter's cache valid time. + """ def __init__(self): - self.attestation_service = AttestationService() + self.attestservice = AttestationService() + self.compute_nodes = {} + admin = context.get_admin_context() + + # Fetch compute node list to initialize the compute_nodes, + # so that we don't need poll OAT service one by one for each + # host in the first round that scheduler invokes us. + computes = db.compute_node_get_all(admin) + for compute in computes: + service = compute['service'] + if not service: + LOG.warn(_("No service for compute ID %s") % compute['id']) + continue + host = service['host'] + self._init_cache_entry(host) + + def _cache_valid(self, host): + cachevalid = False + if host in self.compute_nodes: + node_stats = self.compute_nodes.get(host) + if not timeutils.is_older_than( + node_stats['vtime'], + CONF.trusted_computing.attestation_auth_timeout): + cachevalid = True + return cachevalid + + def _init_cache_entry(self, host): + self.compute_nodes[host] = { + 'trust_lvl': 'unknown', + 'vtime': timeutils.normalize_time( + timeutils.parse_isotime("1970-01-01T00:00:00Z"))} + + def _invalidate_caches(self): + for host in self.compute_nodes: + self._init_cache_entry(host) + + def _update_cache_entry(self, state): + entry = {} + + host = state['host_name'] + entry['trust_lvl'] = state['trust_lvl'] - def _is_trusted(self, host, trust): - level = self.attestation_service.do_attestation(host) - LOG.debug(_("TCP: trust state of " - "%(host)s:%(level)s(%(trust)s)") % locals()) + try: + # Normalize as naive object to interoperate with utcnow(). + entry['vtime'] = timeutils.normalize_time( + timeutils.parse_isotime(state['vtime'])) + except ValueError: + # Mark the system as un-trusted if get invalid vtime. + entry['trust_lvl'] = 'unknown' + entry['vtime'] = timeutils.utcnow() + + self.compute_nodes[host] = entry + + def _update_cache(self): + self._invalidate_caches() + states = self.attestservice.do_attestation(self.compute_nodes.keys()) + if states is None: + return + for state in states: + self._update_cache_entry(state) + + def get_host_attestation(self, host): + """Check host's trust level.""" + if host not in self.compute_nodes: + self._init_cache_entry(host) + if not self._cache_valid(host): + self._update_cache() + level = self.compute_nodes.get(host).get('trust_lvl') + return level + + +class ComputeAttestation(object): + def __init__(self): + self.caches = ComputeAttestationCache() + + def is_trusted(self, host, trust): + level = self.caches.get_host_attestation(host) return trust == level + +class TrustedFilter(filters.BaseHostFilter): + """Trusted filter to support Trusted Compute Pools.""" + + def __init__(self): + self.compute_attestation = ComputeAttestation() + def host_passes(self, host_state, filter_properties): instance = filter_properties.get('instance_type', {}) extra = instance.get('extra_specs', {}) trust = extra.get('trust:trusted_host') host = host_state.host if trust: - return self._is_trusted(host, trust) + return self.compute_attestation.is_trusted(host, trust) return True diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 84bdcddb5..e6bf1a293 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -26,6 +26,7 @@ import sys from nova.compute import rpcapi as compute_rpcapi from nova.compute import utils as compute_utils from nova.compute import vm_states +from nova.conductor import api as conductor_api import nova.context from nova import db from nova import exception @@ -180,8 +181,6 @@ class SchedulerManager(manager.Manager): uuids = [properties.get('uuid')] for instance_uuid in request_spec.get('instance_uuids') or uuids: if instance_uuid: - compute_utils.add_instance_fault_from_exc(context, - instance_uuid, ex, sys.exc_info()) state = vm_state.upper() LOG.warning(_('Setting instance to %(state)s state.'), locals(), instance_uuid=instance_uuid) @@ -191,6 +190,9 @@ class SchedulerManager(manager.Manager): context, instance_uuid, updates) notifications.send_update(context, old_ref, new_ref, service="scheduler") + compute_utils.add_instance_fault_from_exc(context, + conductor_api.LocalAPI(), + new_ref, ex, sys.exc_info()) payload = dict(request_spec=request_spec, instance_properties=properties, diff --git a/nova/service.py b/nova/service.py index 0fde14baa..3d556a202 100644 --- a/nova/service.py +++ b/nova/service.py @@ -38,7 +38,6 @@ from nova.openstack.common import eventlet_backdoor from nova.openstack.common import importutils from nova.openstack.common import log as logging from nova.openstack.common import rpc -from nova.openstack.common.rpc import common as rpc_common from nova import servicegroup from nova import utils from nova import version @@ -61,6 +60,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 +401,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,25 +418,23 @@ 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() def start(self): verstr = version.version_string_with_package() LOG.audit(_('Starting %(topic)s node (version %(version)s)'), {'topic': self.topic, 'version': verstr}) + self.basic_config_check() self.manager.init_host() self.model_disconnected = False ctxt = context.get_admin_context() try: - service_ref = self.conductor_api.service_get_by_args(ctxt, - self.host, - self.binary) - self.service_id = service_ref['id'] + self.service_ref = self.conductor_api.service_get_by_args(ctxt, + self.host, self.binary) + self.service_id = self.service_ref['id'] except exception.NotFound: - self._create_service_ref(ctxt) + self.service_ref = self._create_service_ref(ctxt) if self.backdoor_port is not None: self.manager.backdoor_port = self.backdoor_port @@ -479,6 +487,7 @@ class Service(object): } service = self.conductor_api.service_create(context, svc_values) self.service_id = service['id'] + return service def __getattr__(self, key): manager = self.__dict__.get('manager', None) @@ -561,11 +570,21 @@ class Service(object): ctxt = context.get_admin_context() return self.manager.periodic_tasks(ctxt, raise_on_error=raise_on_error) + def basic_config_check(self): + """Perform basic config checks before starting processing.""" + # Make sure the tempdir exists and is writable + try: + with utils.tempdir() as tmpdir: + pass + except Exception as e: + LOG.error(_('Temporary directory is invalid: %s'), e) + sys.exit(1) + 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 +599,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 @@ -599,7 +621,7 @@ class WSGIService(object): """ fl = '%s_manager' % self.name - if not fl in CONF: + if fl not in CONF: return None manager_class_name = CONF.get(fl, 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 ebd0ee6ac..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__) @@ -45,6 +45,15 @@ class API(object): @lockutils.synchronized('nova.servicegroup.api.new', 'nova-') def __new__(cls, *args, **kwargs): + '''Create an instance of the servicegroup API. + + args and kwargs are passed down to the servicegroup driver when it gets + created. No args currently exist, though. Valid kwargs are: + + db_allowed - Boolean. False if direct db access is not allowed and + alternative data access (conductor) should be used + instead. + ''' if not cls._driver: LOG.debug(_('ServiceGroup driver defined as an instance of %s'), @@ -55,7 +64,8 @@ class API(object): except KeyError: raise TypeError(_("unknown ServiceGroup driver name: %s") % driver_name) - cls._driver = importutils.import_object(driver_class) + cls._driver = importutils.import_object(driver_class, + *args, **kwargs) utils.check_isinstance(cls._driver, ServiceGroupDriver) # we don't have to check that cls._driver is not NONE, # check_isinstance does it @@ -134,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/servicegroup/drivers/db.py b/nova/servicegroup/drivers/db.py index 075db3ed8..18b4b74e5 100644 --- a/nova/servicegroup/drivers/db.py +++ b/nova/servicegroup/drivers/db.py @@ -14,9 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from nova import conductor from nova import context -from nova import db -from nova import exception from nova.openstack.common import cfg from nova.openstack.common import log as logging from nova.openstack.common import timeutils @@ -32,6 +31,10 @@ LOG = logging.getLogger(__name__) class DbDriver(api.ServiceGroupDriver): + def __init__(self, *args, **kwargs): + self.db_allowed = kwargs.get('db_allowed', True) + self.conductor_api = conductor.API(use_local=self.db_allowed) + def join(self, member_id, group_id, service=None): """Join the given service with it's group.""" @@ -53,6 +56,11 @@ class DbDriver(api.ServiceGroupDriver): Check whether a service is up based on last heartbeat. """ last_heartbeat = service_ref['updated_at'] or service_ref['created_at'] + if isinstance(last_heartbeat, basestring): + # NOTE(russellb) If this service_ref came in over rpc via + # conductor, then the timestamp will be a string and needs to be + # converted back to a datetime. + last_heartbeat = timeutils.parse_strtime(last_heartbeat) # Timestamps in DB are UTC. elapsed = utils.total_seconds(timeutils.utcnow() - last_heartbeat) LOG.debug('DB_Driver.is_up last_heartbeat = %(lhb)s elapsed = %(el)s', @@ -66,7 +74,8 @@ class DbDriver(api.ServiceGroupDriver): LOG.debug(_('DB_Driver: get_all members of the %s group') % group_id) rs = [] ctxt = context.get_admin_context() - for service in db.service_get_all_by_topic(ctxt, group_id): + services = self.conductor_api.service_get_all_by_topic(ctxt, group_id) + for service in services: if self.is_up(service): rs.append(service['host']) return rs @@ -76,18 +85,11 @@ class DbDriver(api.ServiceGroupDriver): ctxt = context.get_admin_context() state_catalog = {} try: - try: - service_ref = db.service_get(ctxt, service.service_id) - except exception.NotFound: - LOG.debug(_('The service database object disappeared, ' - 'Recreating it.')) - service._create_service_ref(ctxt) - service_ref = db.service_get(ctxt, service.service_id) - - state_catalog['report_count'] = service_ref['report_count'] + 1 + report_count = service.service_ref['report_count'] + 1 + state_catalog['report_count'] = report_count - db.service_update(ctxt, - service.service_id, state_catalog) + service.service_ref = self.conductor_api.service_update(ctxt, + service.service_ref, state_catalog) # TODO(termie): make this pattern be more elegant. if getattr(service, 'model_disconnected', False): diff --git a/nova/tests/api/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/ec2/test_cloud.py b/nova/tests/api/ec2/test_cloud.py index 562473121..c60a0148e 100644 --- a/nova/tests/api/ec2/test_cloud.py +++ b/nova/tests/api/ec2/test_cloud.py @@ -824,6 +824,19 @@ class CloudTestCase(test.TestCase): self.cloud.describe_instances, self.context, instance_id=[instance_id]) + def test_describe_instances_with_filters(self): + # Makes sure describe_instances works and filters results. + filters = {'filter': [{'name': 'test', + 'value': ['a', 'b']}, + {'name': 'another_test', + 'value': 'a string'}]} + + self._stub_instance_get_with_fixed_ips('get_all') + self._stub_instance_get_with_fixed_ips('get') + + result = self.cloud.describe_instances(self.context, **filters) + self.assertEqual(result, {'reservationSet': []}) + def test_describe_instances_sorting(self): # Makes sure describe_instances works and is sorted as expected. self.flags(use_ipv6=True) @@ -1440,7 +1453,7 @@ class CloudTestCase(test.TestCase): self.assertTrue(filter(lambda k: k['keyName'] == 'test2', keys)) def test_describe_bad_key_pairs(self): - self.assertRaises(exception.EC2APIError, + self.assertRaises(exception.KeypairNotFound, self.cloud.describe_key_pairs, self.context, key_name=['DoesNotExist']) @@ -1490,7 +1503,7 @@ class CloudTestCase(test.TestCase): self.assertEqual(result['keyName'], key_name) for key_name in bad_names: - self.assertRaises(exception.EC2APIError, + self.assertRaises(exception.InvalidKeypair, self.cloud.create_key_pair, self.context, key_name) 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_consoles.py b/nova/tests/api/openstack/compute/contrib/test_consoles.py index d251c6b75..cf044dfcd 100644 --- a/nova/tests/api/openstack/compute/contrib/test_consoles.py +++ b/nova/tests/api/openstack/compute/contrib/test_consoles.py @@ -26,19 +26,36 @@ def fake_get_vnc_console(self, _context, _instance, _console_type): return {'url': 'http://fake'} +def fake_get_spice_console(self, _context, _instance, _console_type): + return {'url': 'http://fake'} + + def fake_get_vnc_console_invalid_type(self, _context, _instance, _console_type): raise exception.ConsoleTypeInvalid(console_type=_console_type) +def fake_get_spice_console_invalid_type(self, _context, + _instance, _console_type): + raise exception.ConsoleTypeInvalid(console_type=_console_type) + + def fake_get_vnc_console_not_ready(self, _context, instance, _console_type): raise exception.InstanceNotReady(instance_id=instance["uuid"]) +def fake_get_spice_console_not_ready(self, _context, instance, _console_type): + raise exception.InstanceNotReady(instance_id=instance["uuid"]) + + def fake_get_vnc_console_not_found(self, _context, instance, _console_type): raise exception.InstanceNotFound(instance_id=instance["uuid"]) +def fake_get_spice_console_not_found(self, _context, instance, _console_type): + raise exception.InstanceNotFound(instance_id=instance["uuid"]) + + def fake_get(self, context, instance_uuid): return {'uuid': instance_uuid} @@ -53,6 +70,8 @@ class ConsolesExtensionTest(test.TestCase): super(ConsolesExtensionTest, self).setUp() self.stubs.Set(compute_api.API, 'get_vnc_console', fake_get_vnc_console) + self.stubs.Set(compute_api.API, 'get_spice_console', + fake_get_spice_console) self.stubs.Set(compute_api.API, 'get', fake_get) self.flags( osapi_compute_extension=[ @@ -132,3 +151,76 @@ class ConsolesExtensionTest(test.TestCase): res = req.get_response(self.app) self.assertEqual(res.status_int, 400) + + def test_get_spice_console(self): + body = {'os-getSPICEConsole': {'type': 'spice-html5'}} + req = webob.Request.blank('/v2/fake/servers/1/action') + req.method = "POST" + req.body = jsonutils.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(self.app) + output = jsonutils.loads(res.body) + self.assertEqual(res.status_int, 200) + self.assertEqual(output, + {u'console': {u'url': u'http://fake', u'type': u'spice-html5'}}) + + def test_get_spice_console_not_ready(self): + self.stubs.Set(compute_api.API, 'get_spice_console', + fake_get_spice_console_not_ready) + body = {'os-getSPICEConsole': {'type': 'spice-html5'}} + req = webob.Request.blank('/v2/fake/servers/1/action') + req.method = "POST" + req.body = jsonutils.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(self.app) + output = jsonutils.loads(res.body) + self.assertEqual(res.status_int, 409) + + def test_get_spice_console_no_type(self): + self.stubs.Set(compute_api.API, 'get_spice_console', + fake_get_spice_console_invalid_type) + body = {'os-getSPICEConsole': {}} + req = webob.Request.blank('/v2/fake/servers/1/action') + req.method = "POST" + req.body = jsonutils.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(self.app) + self.assertEqual(res.status_int, 400) + + def test_get_spice_console_no_instance(self): + self.stubs.Set(compute_api.API, 'get', fake_get_not_found) + body = {'os-getSPICEConsole': {'type': 'spice-html5'}} + req = webob.Request.blank('/v2/fake/servers/1/action') + req.method = "POST" + req.body = jsonutils.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(self.app) + self.assertEqual(res.status_int, 404) + + def test_get_spice_console_no_instance_on_console_get(self): + self.stubs.Set(compute_api.API, 'get_spice_console', + fake_get_spice_console_not_found) + body = {'os-getSPICEConsole': {'type': 'spice-html5'}} + req = webob.Request.blank('/v2/fake/servers/1/action') + req.method = "POST" + req.body = jsonutils.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(self.app) + self.assertEqual(res.status_int, 404) + + def test_get_spice_console_invalid_type(self): + body = {'os-getSPICEConsole': {'type': 'invalid'}} + self.stubs.Set(compute_api.API, 'get_spice_console', + fake_get_spice_console_invalid_type) + req = webob.Request.blank('/v2/fake/servers/1/action') + req.method = "POST" + req.body = jsonutils.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(self.app) + self.assertEqual(res.status_int, 400) diff --git a/nova/tests/api/openstack/compute/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_flavors_extra_specs.py b/nova/tests/api/openstack/compute/contrib/test_flavors_extra_specs.py index bc9f66eb2..a3745d573 100644 --- a/nova/tests/api/openstack/compute/contrib/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/compute/contrib/test_flavors_extra_specs.py @@ -18,10 +18,9 @@ import webob from nova.api.openstack.compute.contrib import flavorextraspecs -from nova.api.openstack import wsgi +import nova.db from nova import test from nova.tests.api.openstack import fakes -import nova.wsgi def return_create_flavor_extra_specs(context, flavor_id, extra_specs): @@ -172,13 +171,11 @@ class FlavorsExtraSpecsXMLSerializerTest(test.TestCase): expected = ("<?xml version='1.0' encoding='UTF-8'?>\n" '<extra_specs><key1>value1</key1></extra_specs>') text = serializer.serialize(dict(extra_specs={"key1": "value1"})) - print text self.assertEqual(text, expected) - def test_deserializer(self): - deserializer = wsgi.XMLDeserializer() - expected = dict(extra_specs={"key1": "value1"}) - intext = ("<?xml version='1.0' encoding='UTF-8'?>\n" - '<extra_specs><key1>value1</key1></extra_specs>') - result = deserializer.deserialize(intext)['body'] - self.assertEqual(result, expected) + def test_show_update_serializer(self): + serializer = flavorextraspecs.ExtraSpecTemplate() + expected = ("<?xml version='1.0' encoding='UTF-8'?>\n" + '<extra_spec key="key1">value1</extra_spec>') + text = serializer.serialize(dict({"key1": "value1"})) + self.assertEqual(text, expected) diff --git a/nova/tests/api/openstack/compute/contrib/test_floating_ip_dns.py b/nova/tests/api/openstack/compute/contrib/test_floating_ip_dns.py index 4753ede32..ac636a4b5 100644 --- a/nova/tests/api/openstack/compute/contrib/test_floating_ip_dns.py +++ b/nova/tests/api/openstack/compute/contrib/test_floating_ip_dns.py @@ -29,8 +29,10 @@ from nova.tests.api.openstack import fakes name = "arbitraryname" name2 = "anotherarbitraryname" -testaddress = '10.0.0.66' -testaddress2 = '10.0.0.67' +test_ipv4_address = '10.0.0.66' +test_ipv4_address2 = '10.0.0.67' + +test_ipv6_address = 'fe80:0:0:0:0:0:a00:42' domain = "example.org" domain2 = "example.net" @@ -48,7 +50,7 @@ def _quote_domain(domain): def network_api_get_floating_ip(self, context, id): - return {'id': floating_ip_id, 'address': testaddress, + return {'id': floating_ip_id, 'address': test_ipv4_address, 'fixed_ip': None} @@ -65,11 +67,11 @@ def network_get_dns_entries_by_address(self, context, address, domain): def network_get_dns_entries_by_name(self, context, address, domain): - return [testaddress] + return [test_ipv4_address] def network_add_dns_entry(self, context, address, name, dns_type, domain): - return {'dns_entry': {'ip': testaddress, + return {'dns_entry': {'ip': test_ipv4_address, 'name': name, 'type': dns_type, 'domain': domain}} @@ -85,12 +87,16 @@ class FloatingIpDNSTest(test.TestCase): def _create_floating_ip(self): """Create a floating ip object.""" host = "fake_host" - return db.floating_ip_create(self.context, - {'address': testaddress, - 'host': host}) + db.floating_ip_create(self.context, + {'address': test_ipv4_address, + 'host': host}) + db.floating_ip_create(self.context, + {'address': test_ipv6_address, + 'host': host}) def _delete_floating_ip(self): - db.floating_ip_destroy(self.context, testaddress) + db.floating_ip_destroy(self.context, test_ipv4_address) + db.floating_ip_destroy(self.context, test_ipv6_address) def setUp(self): super(FloatingIpDNSTest, self).setUp() @@ -133,14 +139,17 @@ class FloatingIpDNSTest(test.TestCase): self.assertFalse(entries[2]['project']) self.assertEqual(entries[2]['availability_zone'], "avzone") - def test_get_dns_entries_by_address(self): - qparams = {'ip': testaddress} - params = "?%s" % urllib.urlencode(qparams) if qparams else "" + def _test_get_dns_entries_by_address(self, address): - req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s/entries%s' - % (_quote_domain(domain), params)) - entries = self.entry_controller.index(req, _quote_domain(domain)) + qparams = {'ip': address} + params = "?%s" % urllib.urlencode(qparams) if qparams else "" + req = fakes.HTTPRequest.blank( + '/v2/123/os-floating-ip-dns/%s/entries/%s' + % (_quote_domain(domain), params)) + entries = self.entry_controller.show(req, _quote_domain(domain), + address) + entries = entries.obj self.assertEqual(len(entries['dns_entries']), 2) self.assertEqual(entries['dns_entries'][0]['name'], name) @@ -149,6 +158,35 @@ class FloatingIpDNSTest(test.TestCase): self.assertEqual(entries['dns_entries'][0]['domain'], domain) + def test_get_dns_entries_by_ipv4_address(self): + self._test_get_dns_entries_by_address(test_ipv4_address) + + def test_get_dns_entries_by_ipv6_address(self): + self._test_get_dns_entries_by_address(test_ipv6_address) + + def test_get_dns_entries_by_invalid_ipv4_or_ipv6(self): + # If it's not a valid ipv4 neither ipv6, the method 'show' + # will try to get dns entries by name instead. We use this + # to test if address is being correctly validated. + def fake_get_dns_entries_by_name(self, context, address, domain): + raise webob.exc.HTTPUnprocessableEntity() + + self.stubs.Set(network.api.API, "get_dns_entries_by_name", + fake_get_dns_entries_by_name) + + invalid_addr = '333.333.333.333' + + qparams = {'ip': invalid_addr} + params = "?%s" % urllib.urlencode(qparams) if qparams else "" + + req = fakes.HTTPRequest.blank( + '/v2/123/os-floating-ip-dns/%s/entries/%s' + % (_quote_domain(domain), params)) + + self.assertRaises(webob.exc.HTTPUnprocessableEntity, + self.entry_controller.show, + req, _quote_domain(domain), invalid_addr) + def test_get_dns_entries_by_name(self): req = fakes.HTTPRequest.blank( '/v2/123/os-floating-ip-dns/%s/entries/%s' % @@ -156,20 +194,34 @@ class FloatingIpDNSTest(test.TestCase): entry = self.entry_controller.show(req, _quote_domain(domain), name) self.assertEqual(entry['dns_entry']['ip'], - testaddress) + test_ipv4_address) self.assertEqual(entry['dns_entry']['domain'], domain) + def test_dns_entries_not_found(self): + def fake_get_dns_entries_by_name(self, context, address, domain): + raise webob.exc.HTTPNotFound() + + self.stubs.Set(network.api.API, "get_dns_entries_by_name", + fake_get_dns_entries_by_name) + + req = fakes.HTTPRequest.blank( + '/v2/123/os-floating-ip-dns/%s/entries/%s' % + (_quote_domain(domain), 'nonexistent')) + self.assertRaises(webob.exc.HTTPNotFound, + self.entry_controller.show, + req, _quote_domain(domain), 'nonexistent') + def test_create_entry(self): body = {'dns_entry': - {'ip': testaddress, + {'ip': test_ipv4_address, 'dns_type': 'A'}} req = fakes.HTTPRequest.blank( '/v2/123/os-floating-ip-dns/%s/entries/%s' % (_quote_domain(domain), name)) entry = self.entry_controller.update(req, _quote_domain(domain), name, body) - self.assertEqual(entry['dns_entry']['ip'], testaddress) + self.assertEqual(entry['dns_entry']['ip'], test_ipv4_address) def test_create_domain(self): req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s' % @@ -264,13 +316,13 @@ class FloatingIpDNSTest(test.TestCase): def test_modify(self): body = {'dns_entry': - {'ip': testaddress2, + {'ip': test_ipv4_address2, 'dns_type': 'A'}} req = fakes.HTTPRequest.blank( '/v2/123/os-floating-ip-dns/%s/entries/%s' % (domain, name)) entry = self.entry_controller.update(req, domain, name, body) - self.assertEqual(entry['dns_entry']['ip'], testaddress2) + self.assertEqual(entry['dns_entry']['ip'], test_ipv4_address2) class FloatingIpDNSSerializerTest(test.TestCase): @@ -305,11 +357,11 @@ class FloatingIpDNSSerializerTest(test.TestCase): serializer = floating_ip_dns.FloatingIPDNSsTemplate() text = serializer.serialize(dict( dns_entries=[ - dict(ip=testaddress, + dict(ip=test_ipv4_address, type='A', domain=domain, name=name), - dict(ip=testaddress2, + dict(ip=test_ipv4_address2, type='C', domain=domain, name=name2)])) @@ -319,11 +371,11 @@ class FloatingIpDNSSerializerTest(test.TestCase): self.assertEqual(2, len(tree)) self.assertEqual('dns_entry', tree[0].tag) self.assertEqual('dns_entry', tree[1].tag) - self.assertEqual(testaddress, tree[0].get('ip')) + self.assertEqual(test_ipv4_address, tree[0].get('ip')) self.assertEqual('A', tree[0].get('type')) self.assertEqual(domain, tree[0].get('domain')) self.assertEqual(name, tree[0].get('name')) - self.assertEqual(testaddress2, tree[1].get('ip')) + self.assertEqual(test_ipv4_address2, tree[1].get('ip')) self.assertEqual('C', tree[1].get('type')) self.assertEqual(domain, tree[1].get('domain')) self.assertEqual(name2, tree[1].get('name')) @@ -332,7 +384,7 @@ class FloatingIpDNSSerializerTest(test.TestCase): serializer = floating_ip_dns.FloatingIPDNSTemplate() text = serializer.serialize(dict( dns_entry=dict( - ip=testaddress, + ip=test_ipv4_address, type='A', domain=domain, name=name))) @@ -340,6 +392,6 @@ class FloatingIpDNSSerializerTest(test.TestCase): tree = etree.fromstring(text) self.assertEqual('dns_entry', tree.tag) - self.assertEqual(testaddress, tree.get('ip')) + self.assertEqual(test_ipv4_address, tree.get('ip')) self.assertEqual(domain, tree.get('domain')) self.assertEqual(name, tree.get('name')) 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_quota_classes.py b/nova/tests/api/openstack/compute/contrib/test_quota_classes.py index a72f5bf0f..0c1378a67 100644 --- a/nova/tests/api/openstack/compute/contrib/test_quota_classes.py +++ b/nova/tests/api/openstack/compute/contrib/test_quota_classes.py @@ -138,7 +138,6 @@ class QuotaTemplateXMLSerializerTest(test.TestCase): cores=90)) text = self.serializer.serialize(exemplar) - print text tree = etree.fromstring(text) self.assertEqual('quota_class_set', tree.tag) diff --git a/nova/tests/api/openstack/compute/contrib/test_quotas.py b/nova/tests/api/openstack/compute/contrib/test_quotas.py index dab8c136e..8d518b815 100644 --- a/nova/tests/api/openstack/compute/contrib/test_quotas.py +++ b/nova/tests/api/openstack/compute/contrib/test_quotas.py @@ -166,7 +166,6 @@ class QuotaXMLSerializerTest(test.TestCase): cores=90)) text = self.serializer.serialize(exemplar) - print text tree = etree.fromstring(text) self.assertEqual('quota_set', tree.tag) diff --git a/nova/tests/api/openstack/compute/contrib/test_security_groups.py b/nova/tests/api/openstack/compute/contrib/test_security_groups.py index ccb58f858..231923e6d 100644 --- a/nova/tests/api/openstack/compute/contrib/test_security_groups.py +++ b/nova/tests/api/openstack/compute/contrib/test_security_groups.py @@ -1180,7 +1180,6 @@ class TestSecurityGroupXMLSerializer(test.TestCase): rule = dict(security_group_rule=raw_rule) text = self.rule_serializer.serialize(rule) - print text tree = etree.fromstring(text) self.assertEqual('security_group_rule', self._tag(tree)) @@ -1212,7 +1211,6 @@ class TestSecurityGroupXMLSerializer(test.TestCase): sg_group = dict(security_group=raw_group) text = self.default_serializer.serialize(sg_group) - print text tree = etree.fromstring(text) self._verify_security_group(raw_group, tree) @@ -1265,7 +1263,6 @@ class TestSecurityGroupXMLSerializer(test.TestCase): sg_groups = dict(security_groups=groups) text = self.index_serializer.serialize(sg_groups) - print text tree = etree.fromstring(text) self.assertEqual('security_groups', self._tag(tree)) diff --git a/nova/tests/api/openstack/compute/contrib/test_server_diagnostics.py b/nova/tests/api/openstack/compute/contrib/test_server_diagnostics.py index ea4565e14..783275ea2 100644 --- a/nova/tests/api/openstack/compute/contrib/test_server_diagnostics.py +++ b/nova/tests/api/openstack/compute/contrib/test_server_diagnostics.py @@ -74,7 +74,6 @@ class TestServerDiagnosticsXMLSerializer(test.TestCase): exemplar = dict(diag1='foo', diag2='bar') text = serializer.serialize(exemplar) - print text tree = etree.fromstring(text) self.assertEqual('diagnostics', self._tag(tree)) 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/contrib/test_simple_tenant_usage.py b/nova/tests/api/openstack/compute/contrib/test_simple_tenant_usage.py index b49a1feb4..13a4e9d61 100644 --- a/nova/tests/api/openstack/compute/contrib/test_simple_tenant_usage.py +++ b/nova/tests/api/openstack/compute/contrib/test_simple_tenant_usage.py @@ -293,7 +293,6 @@ class SimpleTenantUsageSerializerTest(test.TestCase): tenant_usage = dict(tenant_usage=raw_usage) text = serializer.serialize(tenant_usage) - print text tree = etree.fromstring(text) self._verify_tenant_usage(raw_usage, tree) @@ -378,7 +377,6 @@ class SimpleTenantUsageSerializerTest(test.TestCase): tenant_usages = dict(tenant_usages=raw_usages) text = serializer.serialize(tenant_usages) - print text tree = etree.fromstring(text) self.assertEqual('tenant_usages', tree.tag) diff --git a/nova/tests/api/openstack/compute/contrib/test_snapshots.py b/nova/tests/api/openstack/compute/contrib/test_snapshots.py index a223178fb..fa0c521fe 100644 --- a/nova/tests/api/openstack/compute/contrib/test_snapshots.py +++ b/nova/tests/api/openstack/compute/contrib/test_snapshots.py @@ -271,7 +271,6 @@ class SnapshotSerializerTest(test.TestCase): ) text = serializer.serialize(dict(snapshot=raw_snapshot)) - print text tree = etree.fromstring(text) self._verify_snapshot(raw_snapshot, tree) @@ -298,7 +297,6 @@ class SnapshotSerializerTest(test.TestCase): )] text = serializer.serialize(dict(snapshots=raw_snapshots)) - print text tree = etree.fromstring(text) self.assertEqual('snapshots', tree.tag) diff --git a/nova/tests/api/openstack/compute/contrib/test_virtual_interfaces.py b/nova/tests/api/openstack/compute/contrib/test_virtual_interfaces.py index 7c61cd51b..cf1c1593f 100644 --- a/nova/tests/api/openstack/compute/contrib/test_virtual_interfaces.py +++ b/nova/tests/api/openstack/compute/contrib/test_virtual_interfaces.py @@ -91,7 +91,6 @@ class ServerVirtualInterfaceSerializerTest(test.TestCase): vifs = dict(virtual_interfaces=raw_vifs) text = self.serializer.serialize(vifs) - print text tree = etree.fromstring(text) self.assertEqual('virtual_interfaces', self._tag(tree)) diff --git a/nova/tests/api/openstack/compute/contrib/test_volumes.py b/nova/tests/api/openstack/compute/contrib/test_volumes.py index 3119f55e8..1a8a570e8 100644 --- a/nova/tests/api/openstack/compute/contrib/test_volumes.py +++ b/nova/tests/api/openstack/compute/contrib/test_volumes.py @@ -348,7 +348,6 @@ class VolumeSerializerTest(test.TestCase): device='/foo') text = serializer.serialize(dict(volumeAttachment=raw_attach)) - print text tree = etree.fromstring(text) self.assertEqual('volumeAttachment', tree.tag) @@ -368,7 +367,6 @@ class VolumeSerializerTest(test.TestCase): device='/foo2')] text = serializer.serialize(dict(volumeAttachments=raw_attaches)) - print text tree = etree.fromstring(text) self.assertEqual('volumeAttachments', tree.tag) @@ -401,7 +399,6 @@ class VolumeSerializerTest(test.TestCase): ) text = serializer.serialize(dict(volume=raw_volume)) - print text tree = etree.fromstring(text) self._verify_volume(raw_volume, tree) @@ -450,7 +447,6 @@ class VolumeSerializerTest(test.TestCase): )] text = serializer.serialize(dict(volumes=raw_volumes)) - print text tree = etree.fromstring(text) self.assertEqual('volumes', tree.tag) diff --git a/nova/tests/api/openstack/compute/test_limits.py b/nova/tests/api/openstack/compute/test_limits.py index f0f2f02d5..e3fff380d 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): @@ -874,7 +874,6 @@ class LimitsXMLSerializationTest(test.TestCase): "absolute": {}}} output = serializer.serialize(fixture) - print output has_dec = output.startswith("<?xml version='1.0' encoding='UTF-8'?>") self.assertTrue(has_dec) @@ -905,7 +904,6 @@ class LimitsXMLSerializationTest(test.TestCase): "maxPersonalitySize": 10240}}} output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'limits') @@ -940,7 +938,6 @@ class LimitsXMLSerializationTest(test.TestCase): "absolute": {}}} output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'limits') 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 2567558ab..4bfb1c1e3 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 @@ -2197,6 +2252,74 @@ class ServersControllerCreateTest(test.TestCase): self.stubs.Set(compute_api.API, 'create', create) self._test_create_extra(params) + def test_create_instance_with_device_name_not_string(self): + self.ext_mgr.extensions = {'os-volumes': 'fake'} + bdm = [{'delete_on_termination': 1, + 'device_name': 123, + 'volume_size': 1, + 'volume_id': '11111111-1111-1111-1111-111111111111'}] + params = {'block_device_mapping': bdm} + old_create = compute_api.API.create + + def create(*args, **kwargs): + self.assertEqual(kwargs['block_device_mapping'], bdm) + return old_create(*args, **kwargs) + + self.stubs.Set(compute_api.API, 'create', create) + self.assertRaises(webob.exc.HTTPBadRequest, + self._test_create_extra, params) + + def test_create_instance_with_device_name_empty(self): + self.ext_mgr.extensions = {'os-volumes': 'fake'} + bdm = [{'delete_on_termination': 1, + 'device_name': '', + 'volume_size': 1, + 'volume_id': '11111111-1111-1111-1111-111111111111'}] + params = {'block_device_mapping': bdm} + old_create = compute_api.API.create + + def create(*args, **kwargs): + self.assertEqual(kwargs['block_device_mapping'], bdm) + return old_create(*args, **kwargs) + + self.stubs.Set(compute_api.API, 'create', create) + self.assertRaises(webob.exc.HTTPBadRequest, + self._test_create_extra, params) + + def test_create_instance_with_device_name_too_long(self): + self.ext_mgr.extensions = {'os-volumes': 'fake'} + bdm = [{'delete_on_termination': 1, + 'device_name': 'a' * 256, + 'volume_size': 1, + 'volume_id': '11111111-1111-1111-1111-111111111111'}] + params = {'block_device_mapping': bdm} + old_create = compute_api.API.create + + def create(*args, **kwargs): + self.assertEqual(kwargs['block_device_mapping'], bdm) + return old_create(*args, **kwargs) + + self.stubs.Set(compute_api.API, 'create', create) + self.assertRaises(webob.exc.HTTPBadRequest, + self._test_create_extra, params) + + def test_create_instance_with_space_in_device_name(self): + self.ext_mgr.extensions = {'os-volumes': 'fake'} + bdm = [{'delete_on_termination': 1, + 'device_name': 'vd a', + 'volume_size': 1, + 'volume_id': '11111111-1111-1111-1111-111111111111'}] + params = {'block_device_mapping': bdm} + old_create = compute_api.API.create + + def create(*args, **kwargs): + self.assertEqual(kwargs['block_device_mapping'], bdm) + return old_create(*args, **kwargs) + + self.stubs.Set(compute_api.API, 'create', create) + self.assertRaises(webob.exc.HTTPBadRequest, + self._test_create_extra, params) + def test_create_instance_with_bdm_delete_on_termination(self): self.ext_mgr.extensions = {'os-volumes': 'fake'} bdm = [{'device_name': 'foo1', 'delete_on_termination': 1}, @@ -4008,7 +4131,7 @@ class ServersViewBuilderTest(test.TestCase): "message": "Error", 'details': 'Stock details for test'} - self.request.context = context.get_admin_context() + self.request.environ['nova.context'].is_admin = True output = self.view_builder.show(self.request, self.instance) self.assertThat(output['server']['fault'], matchers.DictMatches(expected_fault)) @@ -4027,7 +4150,7 @@ class ServersViewBuilderTest(test.TestCase): "created": "2010-10-10T12:00:00Z", "message": "Error"} - self.request.context = context.get_admin_context() + self.request.environ['nova.context'].is_admin = True output = self.view_builder.show(self.request, self.instance) self.assertThat(output['server']['fault'], matchers.DictMatches(expected_fault)) @@ -4385,7 +4508,6 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture) - print output has_dec = output.startswith("<?xml version='1.0' encoding='UTF-8'?>") self.assertTrue(has_dec) @@ -4463,7 +4585,6 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'server') @@ -4594,7 +4715,6 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'server') @@ -4691,7 +4811,6 @@ class ServerXMLSerializationTest(test.TestCase): ]} output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'servers_index') server_elems = root.findall('{0}server'.format(NS)) @@ -4755,7 +4874,6 @@ class ServerXMLSerializationTest(test.TestCase): ]} output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'servers_index') server_elems = root.findall('{0}server'.format(NS)) @@ -5042,7 +5160,6 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'server') diff --git a/nova/tests/api/openstack/compute/test_versions.py b/nova/tests/api/openstack/compute/test_versions.py index 28b109215..bd2e9fa7b 100644 --- a/nova/tests/api/openstack/compute/test_versions.py +++ b/nova/tests/api/openstack/compute/test_versions.py @@ -228,7 +228,6 @@ class VersionsTest(test.TestCase): self.assertEqual(res.content_type, "application/xml") root = etree.XML(res.body) - print res.body xmlutil.validate_schema(root, 'versions') self.assertTrue(root.xpath('/ns:versions', namespaces=NS)) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 03fc87ac5..3ef98b902 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -372,7 +372,7 @@ def create_info_cache(nw_cache): def get_fake_uuid(token=0): - if not token in FAKE_UUIDS: + if token not in FAKE_UUIDS: FAKE_UUIDS[token] = str(uuid.uuid4()) return FAKE_UUIDS[token] @@ -399,12 +399,12 @@ def fake_instance_get_all_by_filters(num_servers=5, **kwargs): server = stub_instance(id=i + 1, uuid=uuid, **kwargs) servers_list.append(server) - if not marker is None and uuid == marker: + if marker is not None and uuid == marker: found_marker = True servers_list = [] - if not marker is None and not found_marker: + if marker is not None and not found_marker: raise exc.MarkerNotFound(marker=marker) - if not limit is None: + if limit is not None: servers_list = servers_list[:limit] return servers_list return _return_servers diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py index 7e49e4ab8..68a5f0bf4 100644 --- a/nova/tests/api/openstack/test_common.py +++ b/nova/tests/api/openstack/test_common.py @@ -377,7 +377,6 @@ class MetadataXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture) - print output has_dec = output.startswith("<?xml version='1.0' encoding='UTF-8'?>") self.assertTrue(has_dec) @@ -390,7 +389,6 @@ class MetadataXMLSerializationTest(test.TestCase): }, } output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'metadata') metadata_dict = fixture['metadata'] @@ -409,7 +407,6 @@ class MetadataXMLSerializationTest(test.TestCase): }, } output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'metadata') metadata_dict = fixture['metadata'] @@ -428,7 +425,6 @@ class MetadataXMLSerializationTest(test.TestCase): }, } output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'metadata') metadata_dict = fixture['metadata'] @@ -447,7 +443,6 @@ class MetadataXMLSerializationTest(test.TestCase): }, } output = serializer.serialize(fixture) - print output root = etree.XML(output) meta_dict = fixture['meta'] (meta_key, meta_value) = meta_dict.items()[0] @@ -463,7 +458,6 @@ class MetadataXMLSerializationTest(test.TestCase): }, } output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'metadata') metadata_dict = fixture['metadata'] @@ -482,7 +476,6 @@ class MetadataXMLSerializationTest(test.TestCase): }, } output = serializer.serialize(fixture) - print output root = etree.XML(output) meta_dict = fixture['meta'] (meta_key, meta_value) = meta_dict.items()[0] @@ -499,7 +492,6 @@ class MetadataXMLSerializationTest(test.TestCase): }, } output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'metadata') metadata_dict = fixture['metadata'] 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_nova_baremetal_manage.py b/nova/tests/baremetal/test_nova_baremetal_manage.py index 4d152a028..c4fdaac6b 100644 --- a/nova/tests/baremetal/test_nova_baremetal_manage.py +++ b/nova/tests/baremetal/test_nova_baremetal_manage.py @@ -20,10 +20,6 @@ import imp import os import sys -from nova import context -from nova import test -from nova.virt.baremetal import db as bmdb - from nova.tests.baremetal.db import base as bm_db_base TOPDIR = os.path.normpath(os.path.join( diff --git a/nova/tests/baremetal/test_pxe.py b/nova/tests/baremetal/test_pxe.py index 45c9ede43..09f1079bf 100644 --- a/nova/tests/baremetal/test_pxe.py +++ b/nova/tests/baremetal/test_pxe.py @@ -21,15 +21,10 @@ 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 -from nova import test from nova.tests.baremetal.db import base as bm_db_base from nova.tests.baremetal.db import utils as bm_db_utils from nova.tests.image import fake as fake_image @@ -120,26 +115,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): @@ -147,12 +142,6 @@ class PXEClassMethodsTestCase(BareMetalPXETestCase): config = pxe.build_network_config(net) self.assertIn('eth0', config) self.assertNotIn('eth1', config) - self.assertIn('hwaddress ether fake', config) - self.assertNotIn('hwaddress ether aa:bb:cc:dd', config) - - net[0][1]['mac'] = 'aa:bb:cc:dd' - config = pxe.build_network_config(net) - self.assertIn('hwaddress ether aa:bb:cc:dd', config) net = utils.get_test_network_info(2) config = pxe.build_network_config(net) @@ -254,6 +243,13 @@ class PXEClassMethodsTestCase(BareMetalPXETestCase): pxe.get_tftp_image_info, self.instance) + # Test that other non-true values also raise an exception + CONF.baremetal.deploy_kernel = "" + CONF.baremetal.deploy_ramdisk = "" + self.assertRaises(exception.NovaException, + pxe.get_tftp_image_info, + self.instance) + # Even if the instance includes kernel_id and ramdisk_id, # we still need deploy_kernel_id and deploy_ramdisk_id. # If those aren't present in instance[], and not specified in @@ -295,6 +291,17 @@ class PXEClassMethodsTestCase(BareMetalPXETestCase): self.assertEqual(res['deploy_kernel'][0], 'eeee') self.assertEqual(res['deploy_ramdisk'][0], 'ffff') + # However, if invalid values are passed on the image extra_specs, + # this should still raise an exception. + extra_specs = { + 'deploy_kernel_id': '', + 'deploy_ramdisk_id': '', + } + self.instance['extra_specs'] = extra_specs + self.assertRaises(exception.NovaException, + pxe.get_tftp_image_info, + self.instance) + class PXEPrivateMethodsTestCase(BareMetalPXETestCase): @@ -306,15 +313,6 @@ class PXEPrivateMethodsTestCase(BareMetalPXETestCase): macs = self.driver._collect_mac_addresses(self.context, self.node) self.assertEqual(macs, address_list) - def test_generate_udev_rules(self): - self._create_node() - address_list = [nic['address'] for nic in self.nic_info] - address_list.append(self.node_info['prov_mac_address']) - - rules = self.driver._generate_udev_rules(self.context, self.node) - for address in address_list: - self.assertIn('ATTR{address}=="%s"' % address, rules) - def test_cache_tftp_images(self): self.instance['kernel_id'] = 'aaaa' self.instance['ramdisk_id'] = 'bbbb' @@ -357,8 +355,6 @@ class PXEPrivateMethodsTestCase(BareMetalPXETestCase): # nova.virt.disk.api._inject_*_into_fs self._create_node() files = [] - files.append(('/etc/udev/rules.d/70-persistent-net.rules', - self.driver._generate_udev_rules(self.context, self.node))) self.instance['hostname'] = 'fake hostname' files.append(('/etc/hostname', 'fake hostname')) self.instance['key_data'] = 'fake ssh key' @@ -413,10 +409,11 @@ class PXEPublicMethodsTestCase(BareMetalPXETestCase): def test_destroy_images(self): self._create_node() - self.mox.StubOutWithMock(os, 'unlink') + self.mox.StubOutWithMock(bm_utils, 'unlink_without_raise') + self.mox.StubOutWithMock(bm_utils, 'rmtree_without_raise') - os.unlink(pxe.get_image_file_path(self.instance)) - os.unlink(pxe.get_image_dir_path(self.instance)) + bm_utils.unlink_without_raise(pxe.get_image_file_path(self.instance)) + bm_utils.rmtree_without_raise(pxe.get_image_dir_path(self.instance)) self.mox.ReplayAll() self.driver.destroy_images(self.context, self.node, self.instance) @@ -481,6 +478,7 @@ class PXEPublicMethodsTestCase(BareMetalPXETestCase): pxe_path = pxe.get_pxe_config_file_path(self.instance) self.mox.StubOutWithMock(bm_utils, 'unlink_without_raise') + self.mox.StubOutWithMock(bm_utils, 'rmtree_without_raise') self.mox.StubOutWithMock(pxe, 'get_tftp_image_info') self.mox.StubOutWithMock(self.driver, '_collect_mac_addresses') @@ -492,7 +490,7 @@ class PXEPublicMethodsTestCase(BareMetalPXETestCase): AndReturn(macs) for mac in macs: bm_utils.unlink_without_raise(pxe.get_pxe_mac_path(mac)) - bm_utils.unlink_without_raise( + bm_utils.rmtree_without_raise( os.path.join(CONF.baremetal.tftp_root, 'fake-uuid')) self.mox.ReplayAll() @@ -515,6 +513,7 @@ class PXEPublicMethodsTestCase(BareMetalPXETestCase): pxe_path = pxe.get_pxe_config_file_path(self.instance) self.mox.StubOutWithMock(bm_utils, 'unlink_without_raise') + self.mox.StubOutWithMock(bm_utils, 'rmtree_without_raise') self.mox.StubOutWithMock(pxe, 'get_tftp_image_info') self.mox.StubOutWithMock(self.driver, '_collect_mac_addresses') @@ -523,7 +522,7 @@ class PXEPublicMethodsTestCase(BareMetalPXETestCase): bm_utils.unlink_without_raise(pxe_path) self.driver._collect_mac_addresses(self.context, self.node).\ AndRaise(exception.DBError) - bm_utils.unlink_without_raise( + bm_utils.rmtree_without_raise( os.path.join(CONF.baremetal.tftp_root, 'fake-uuid')) self.mox.ReplayAll() diff --git a/nova/tests/baremetal/test_utils.py b/nova/tests/baremetal/test_utils.py index 827b1fcaf..df5112deb 100644 --- a/nova/tests/baremetal/test_utils.py +++ b/nova/tests/baremetal/test_utils.py @@ -18,9 +18,9 @@ """Tests for baremetal utils.""" -import mox +import errno +import os -from nova import exception from nova import test from nova.virt.baremetal import utils @@ -32,3 +32,36 @@ class BareMetalUtilsTestCase(test.TestCase): self.assertEqual(len(s), 10) s = utils.random_alnum(100) self.assertEqual(len(s), 100) + + def test_unlink(self): + self.mox.StubOutWithMock(os, "unlink") + os.unlink("/fake/path") + + self.mox.ReplayAll() + utils.unlink_without_raise("/fake/path") + self.mox.VerifyAll() + + def test_unlink_ENOENT(self): + self.mox.StubOutWithMock(os, "unlink") + os.unlink("/fake/path").AndRaise(OSError(errno.ENOENT)) + + self.mox.ReplayAll() + utils.unlink_without_raise("/fake/path") + self.mox.VerifyAll() + + def test_create_link(self): + self.mox.StubOutWithMock(os, "symlink") + os.symlink("/fake/source", "/fake/link") + + self.mox.ReplayAll() + utils.create_link_without_raise("/fake/source", "/fake/link") + self.mox.VerifyAll() + + def test_create_link_EEXIST(self): + self.mox.StubOutWithMock(os, "symlink") + os.symlink("/fake/source", "/fake/link").AndRaise( + OSError(errno.EEXIST)) + + self.mox.ReplayAll() + utils.create_link_without_raise("/fake/source", "/fake/link") + self.mox.VerifyAll() diff --git a/nova/tests/cells/test_cells_messaging.py b/nova/tests/cells/test_cells_messaging.py index da45721ed..1208368c2 100644 --- a/nova/tests/cells/test_cells_messaging.py +++ b/nova/tests/cells/test_cells_messaging.py @@ -14,8 +14,6 @@ """ Tests For Cells Messaging module """ -import mox - from nova.cells import messaging from nova.cells import utils as cells_utils from nova import context diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index 6ca548c17..b5a8b91a2 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 @@ -144,7 +144,22 @@ class BaseTestCase(test.TestCase): self.stubs.Set(self.compute, 'scheduler_rpcapi', fake_rpcapi) fake_network.set_stub_network_methods(self.stubs) + def fake_get_nw_info(cls, ctxt, instance, *args, **kwargs): + self.assertTrue(ctxt.is_admin) + return fake_network.fake_get_instance_nw_info(self.stubs, 1, 1, + spectacular=True) + + self.stubs.Set(network_api.API, 'get_instance_nw_info', + fake_get_nw_info) + self.stubs.Set(network_api.API, 'allocate_for_instance', + fake_get_nw_info) + self.compute_api = compute.API() + + # Just to make long lines short + self.rt = self.compute._get_resource_tracker(NODENAME) + def tearDown(self): + timeutils.clear_time_override() ctxt = context.get_admin_context() fake_image.FakeImageService_reset() instances = db.instance_get_all(ctxt) @@ -212,25 +227,6 @@ class BaseTestCase(test.TestCase): class ComputeTestCase(BaseTestCase): - def setUp(self): - def fake_get_nw_info(cls, ctxt, instance, *args, **kwargs): - self.assertTrue(ctxt.is_admin) - return fake_network.fake_get_instance_nw_info(self.stubs, 1, 1, - spectacular=True) - - super(ComputeTestCase, self).setUp() - self.stubs.Set(network_api.API, 'get_instance_nw_info', - fake_get_nw_info) - self.stubs.Set(network_api.API, 'allocate_for_instance', - fake_get_nw_info) - self.compute_api = compute.API() - # Just to make long lines short - self.rt = self.compute._get_resource_tracker(NODENAME) - - def tearDown(self): - super(ComputeTestCase, self).tearDown() - timeutils.clear_time_override() - def test_wrap_instance_fault(self): inst = {"uuid": "fake_uuid"} @@ -1340,6 +1336,9 @@ class ComputeTestCase(BaseTestCase): def test_novnc_vnc_console(self): # Make sure we can a vnc console for an instance. + self.flags(vnc_enabled=True) + self.flags(enabled=False, group='spice') + instance = jsonutils.to_primitive(self._create_fake_instance()) self.compute.run_instance(self.context, instance=instance) @@ -1352,6 +1351,9 @@ class ComputeTestCase(BaseTestCase): def test_xvpvnc_vnc_console(self): # Make sure we can a vnc console for an instance. + self.flags(vnc_enabled=True) + self.flags(enabled=False, group='spice') + instance = jsonutils.to_primitive(self._create_fake_instance()) self.compute.run_instance(self.context, instance=instance) @@ -1362,6 +1364,9 @@ class ComputeTestCase(BaseTestCase): def test_invalid_vnc_console_type(self): # Raise useful error if console type is an unrecognised string. + self.flags(vnc_enabled=True) + self.flags(enabled=False, group='spice') + instance = jsonutils.to_primitive(self._create_fake_instance()) self.compute.run_instance(self.context, instance=instance) @@ -1372,6 +1377,9 @@ class ComputeTestCase(BaseTestCase): def test_missing_vnc_console_type(self): # Raise useful error is console type is None. + self.flags(vnc_enabled=True) + self.flags(enabled=False, group='spice') + instance = jsonutils.to_primitive(self._create_fake_instance()) self.compute.run_instance(self.context, instance=instance) @@ -1380,6 +1388,47 @@ class ComputeTestCase(BaseTestCase): self.context, None, instance=instance) self.compute.terminate_instance(self.context, instance=instance) + def test_spicehtml5_spice_console(self): + # Make sure we can a spice console for an instance. + self.flags(vnc_enabled=False) + self.flags(enabled=True, group='spice') + + instance = jsonutils.to_primitive(self._create_fake_instance()) + self.compute.run_instance(self.context, instance=instance) + + # Try with the full instance + console = self.compute.get_spice_console(self.context, 'spice-html5', + instance=instance) + self.assert_(console) + + self.compute.terminate_instance(self.context, instance=instance) + + def test_invalid_spice_console_type(self): + # Raise useful error if console type is an unrecognised string + self.flags(vnc_enabled=False) + self.flags(enabled=True, group='spice') + + instance = jsonutils.to_primitive(self._create_fake_instance()) + self.compute.run_instance(self.context, instance=instance) + + self.assertRaises(exception.ConsoleTypeInvalid, + self.compute.get_spice_console, + self.context, 'invalid', instance=instance) + self.compute.terminate_instance(self.context, instance=instance) + + def test_missing_spice_console_type(self): + # Raise useful error is console type is None + self.flags(vnc_enabled=False) + self.flags(enabled=True, group='spice') + + instance = jsonutils.to_primitive(self._create_fake_instance()) + self.compute.run_instance(self.context, instance=instance) + + self.assertRaises(exception.ConsoleTypeInvalid, + self.compute.get_spice_console, + self.context, None, instance=instance) + self.compute.terminate_instance(self.context, instance=instance) + def test_diagnostics(self): # Make sure we can get diagnostics for an instance. expected_diagnostic = {'cpu0_time': 17300000000, @@ -1522,7 +1571,7 @@ class ComputeTestCase(BaseTestCase): # Ensure failure when running an instance that already exists. instance = jsonutils.to_primitive(self._create_fake_instance()) self.compute.run_instance(self.context, instance=instance) - self.assertRaises(exception.Invalid, + self.assertRaises(exception.InstanceExists, self.compute.run_instance, self.context, instance=instance) @@ -1541,7 +1590,8 @@ class ComputeTestCase(BaseTestCase): mox.IgnoreArg(), mox.IgnoreArg(), requested_networks=None, - vpn=False, macs=macs).AndReturn( + vpn=False, macs=macs, + conductor_api=self.compute.conductor_api).AndReturn( fake_network.fake_get_instance_nw_info(self.stubs, 1, 1, spectacular=True)) self.mox.StubOutWithMock(self.compute.driver, "macs_for_instance") @@ -1559,8 +1609,9 @@ class ComputeTestCase(BaseTestCase): mox.IgnoreArg(), mox.IgnoreArg(), requested_networks=None, - vpn=False, - macs=None).AndRaise(rpc_common.RemoteError()) + vpn=False, macs=None, + conductor_api=self.compute.conductor_api + ).AndRaise(rpc_common.RemoteError()) fake_network.unset_stub_network_methods(self.stubs) @@ -2019,7 +2070,7 @@ class ComputeTestCase(BaseTestCase): self.compute.terminate_instance(self.context, instance=new_instance) def test_prep_resize_instance_migration_error_on_none_host(self): - """Ensure prep_resize raise a migration error if destination host is + """Ensure prep_resize raises a migration error if destination host is not defined """ instance = jsonutils.to_primitive(self._create_fake_instance()) @@ -2484,7 +2535,7 @@ class ComputeTestCase(BaseTestCase): db.instance_destroy(c, inst_uuid) def test_post_live_migration_no_shared_storage_working_correctly(self): - """Confirm post_live_migration() works as expected correctly + """Confirm post_live_migration() works correctly as expected for non shared storage migration. """ # Create stubs @@ -2583,8 +2634,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') @@ -2602,10 +2651,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') @@ -2650,8 +2701,8 @@ class ComputeTestCase(BaseTestCase): self.assertEqual(task_states.POWERING_OFF, instances[0]['task_state']) def test_add_instance_fault(self): + instance = self._create_fake_instance() exc_info = None - instance_uuid = str(uuid.uuid4()) def fake_db_fault_create(ctxt, values): self.assertTrue(values['details'].startswith('test')) @@ -2661,7 +2712,8 @@ class ComputeTestCase(BaseTestCase): expected = { 'code': 500, 'message': 'NotImplementedError', - 'instance_uuid': instance_uuid, + 'instance_uuid': instance['uuid'], + 'host': self.compute.host } self.assertEquals(expected, values) @@ -2673,13 +2725,15 @@ class ComputeTestCase(BaseTestCase): self.stubs.Set(nova.db, 'instance_fault_create', fake_db_fault_create) ctxt = context.get_admin_context() - compute_utils.add_instance_fault_from_exc(ctxt, instance_uuid, + compute_utils.add_instance_fault_from_exc(ctxt, + self.compute.conductor_api, + instance, NotImplementedError('test'), exc_info) def test_add_instance_fault_with_remote_error(self): + instance = self._create_fake_instance() exc_info = None - instance_uuid = str(uuid.uuid4()) def fake_db_fault_create(ctxt, values): self.assertTrue(values['details'].startswith('Remote error')) @@ -2689,8 +2743,9 @@ class ComputeTestCase(BaseTestCase): expected = { 'code': 500, - 'instance_uuid': instance_uuid, - 'message': 'My Test Message' + 'instance_uuid': instance['uuid'], + 'message': 'My Test Message', + 'host': self.compute.host } self.assertEquals(expected, values) @@ -2702,13 +2757,12 @@ class ComputeTestCase(BaseTestCase): self.stubs.Set(nova.db, 'instance_fault_create', fake_db_fault_create) ctxt = context.get_admin_context() - compute_utils.add_instance_fault_from_exc(ctxt, instance_uuid, - exc, - exc_info) + compute_utils.add_instance_fault_from_exc(ctxt, + self.compute.conductor_api, instance, exc, exc_info) def test_add_instance_fault_user_error(self): + instance = self._create_fake_instance() exc_info = None - instance_uuid = str(uuid.uuid4()) def fake_db_fault_create(ctxt, values): @@ -2716,7 +2770,8 @@ class ComputeTestCase(BaseTestCase): 'code': 400, 'message': 'Invalid', 'details': 'fake details', - 'instance_uuid': instance_uuid, + 'instance_uuid': instance['uuid'], + 'host': self.compute.host } self.assertEquals(expected, values) @@ -2730,25 +2785,28 @@ class ComputeTestCase(BaseTestCase): self.stubs.Set(nova.db, 'instance_fault_create', fake_db_fault_create) ctxt = context.get_admin_context() - compute_utils.add_instance_fault_from_exc(ctxt, instance_uuid, - user_exc, exc_info) + compute_utils.add_instance_fault_from_exc(ctxt, + self.compute.conductor_api, instance, user_exc, exc_info) def test_add_instance_fault_no_exc_info(self): - instance_uuid = str(uuid.uuid4()) + instance = self._create_fake_instance() def fake_db_fault_create(ctxt, values): expected = { 'code': 500, 'message': 'NotImplementedError', 'details': 'test', - 'instance_uuid': instance_uuid, + 'instance_uuid': instance['uuid'], + 'host': self.compute.host } self.assertEquals(expected, values) self.stubs.Set(nova.db, 'instance_fault_create', fake_db_fault_create) ctxt = context.get_admin_context() - compute_utils.add_instance_fault_from_exc(ctxt, instance_uuid, + compute_utils.add_instance_fault_from_exc(ctxt, + self.compute.conductor_api, + instance, NotImplementedError('test')) def test_cleanup_running_deleted_instances(self): @@ -2822,16 +2880,12 @@ class ComputeTestCase(BaseTestCase): self.mox.StubOutWithMock(self.compute.network_api, 'get_instance_nw_info') - self.mox.StubOutWithMock(fake_nw_info, 'json') self.mox.StubOutWithMock(self.compute.conductor_api, 'instance_info_cache_update') self.compute.network_api.get_instance_nw_info(self.context, - fake_instance, update_cache=False).AndReturn(fake_nw_info) - fake_nw_info.json().AndReturn('fake-nw-info') - expected_cache = {'network_info': 'fake-nw-info'} - self.compute.conductor_api.instance_info_cache_update(self.context, - fake_instance, expected_cache) + fake_instance, conductor_api=self.compute.conductor_api + ).AndReturn(fake_nw_info) self.mox.ReplayAll() @@ -2911,7 +2965,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) @@ -3256,6 +3310,35 @@ class ComputeTestCase(BaseTestCase): self.mox.VerifyAll() self.mox.UnsetStubs() + def test_init_instance_failed_resume_sets_error(self): + instance = { + 'uuid': 'fake-uuid', + 'info_cache': None, + 'power_state': power_state.RUNNING, + 'vm_state': vm_states.ACTIVE, + } + self.flags(resume_guests_state_on_host_boot=True) + self.mox.StubOutWithMock(self.compute, '_get_power_state') + self.mox.StubOutWithMock(self.compute.driver, 'plug_vifs') + self.mox.StubOutWithMock(self.compute.driver, + 'resume_state_on_host_boot') + self.mox.StubOutWithMock(self.compute, + '_get_instance_volume_block_device_info') + self.mox.StubOutWithMock(self.compute, + '_set_instance_error_state') + self.compute._get_power_state(mox.IgnoreArg(), + instance).AndReturn(power_state.SHUTDOWN) + self.compute.driver.plug_vifs(instance, mox.IgnoreArg()) + self.compute._get_instance_volume_block_device_info(mox.IgnoreArg(), + instance['uuid']).AndReturn('fake-bdm') + self.compute.driver.resume_state_on_host_boot(mox.IgnoreArg(), + instance, mox.IgnoreArg(), + 'fake-bdm').AndRaise(test.TestingException) + self.compute._set_instance_error_state(mox.IgnoreArg(), + instance['uuid']) + self.mox.ReplayAll() + self.compute._init_instance('fake-context', instance) + def test_get_instances_on_driver(self): fake_context = context.get_admin_context() @@ -3313,255 +3396,6 @@ class ComputeTestCase(BaseTestCase): result = self.compute._get_instances_on_driver(fake_context) self.assertEqual(driver_instances, result) - def test_rebuild_on_host_updated_target(self): - """Confirm evacuate scenario updates host.""" - - # creating testdata - c = self.context.elevated() - - inst_ref = self._create_fake_instance({'host': 'someotherhost'}) - db.instance_update(self.context, inst_ref['uuid'], - {"task_state": task_states.REBUILDING}) - inst_id = inst_ref["id"] - inst_uuid = inst_ref["uuid"] - dest = self.compute.host - - def set_shared_storage(instance): - return True - - self.stubs.Set(self.compute.driver, 'instance_on_disk', - set_shared_storage) - - self.compute.rebuild_instance(c, instance=inst_ref, - injected_files=None, image_ref=None, - orig_image_ref=None, new_pass=None, - orig_sys_metadata=None, bdms=[], - recreate=True, on_shared_storage=True) - - # make sure instance is updated with destination hostname. - instance = db.instance_get(c, inst_id) - self.assertTrue(instance['host']) - self.assertEqual(instance['host'], dest) - - # cleanup - db.instance_destroy(c, inst_uuid) - - def test_rebuild_with_wrong_shared_storage(self): - """Confirm evacuate scenario updates host.""" - - # creating testdata - c = self.context.elevated() - - inst_ref = self._create_fake_instance({'host': 'srchost'}) - db.instance_update(self.context, inst_ref['uuid'], - {"task_state": task_states.REBUILDING}) - inst_id = inst_ref["id"] - inst_uuid = inst_ref["uuid"] - dest = self.compute.host - - def set_shared_storage(instance): - return True - - self.stubs.Set(self.compute.driver, 'instance_on_disk', - set_shared_storage) - - self.assertRaises(exception.Invalid, - self.compute.rebuild_instance, c, instance=inst_ref, - injected_files=None, image_ref=None, - orig_image_ref=None, new_pass=None, - orig_sys_metadata=None, - recreate=True, on_shared_storage=False) - - # make sure instance was not updated with destination hostname. - instance = db.instance_get(c, inst_id) - self.assertTrue(instance['host']) - self.assertEqual(instance['host'], 'srchost') - - # cleanup - db.instance_destroy(c, inst_uuid) - - def test_rebuild_on_host_with_volumes(self): - """Confirm evacuate scenario reconnects volumes.""" - - # creating testdata - inst_ref = jsonutils.to_primitive(self._create_fake_instance - ({'host': 'fake_host_2'})) - db.instance_update(self.context, inst_ref['uuid'], - {"task_state": task_states.REBUILDING}) - - inst_id = inst_ref["id"] - inst_uuid = inst_ref["uuid"] - - volume_id = 'fake' - values = {'instance_uuid': inst_ref['uuid'], - 'device_name': '/dev/vdc', - 'delete_on_termination': False, - 'volume_id': volume_id, - } - - admin = context.get_admin_context() - db.block_device_mapping_create(admin, values) - - def set_shared_storage(instance): - return True - - self.stubs.Set(self.compute.driver, 'instance_on_disk', - set_shared_storage) - - def fake_volume_get(self, context, volume): - return {'id': volume_id} - self.stubs.Set(cinder.API, "get", fake_volume_get) - - # Stub out and record whether it gets detached - result = {"detached": False} - - def fake_detach(self, context, volume): - result["detached"] = volume["id"] == volume_id - self.stubs.Set(cinder.API, "detach", fake_detach) - - def fake_terminate_connection(self, context, volume, connector): - return {} - self.stubs.Set(cinder.API, "terminate_connection", - fake_terminate_connection) - - # make sure volumes attach, detach are called - self.mox.StubOutWithMock(self.compute.volume_api, 'detach') - self.compute.volume_api.detach(mox.IsA(admin), mox.IgnoreArg()) - - self.mox.StubOutWithMock(self.compute, '_setup_block_device_mapping') - self.compute._setup_block_device_mapping(mox.IsA(admin), - mox.IsA(inst_ref), - mox.IgnoreArg()) - - # start test - self.mox.ReplayAll() - - self.compute.rebuild_instance(admin, instance=inst_ref, - injected_files=None, image_ref=None, - orig_image_ref=None, new_pass=None, - orig_sys_metadata=None, bdms=[], - recreate=True, on_shared_storage=True) - - # cleanup - for bdms in db.block_device_mapping_get_all_by_instance( - admin, inst_uuid): - db.block_device_mapping_destroy(admin, bdms['id']) - db.instance_destroy(admin, inst_uuid) - - def test_rebuild_on_host_with_shared_storage(self): - """Confirm evacuate scenario on shared storage.""" - - # creating testdata - c = self.context.elevated() - - inst_ref = jsonutils.to_primitive(self._create_fake_instance - ({'host': 'fake_host_2'})) - - inst_uuid = inst_ref["uuid"] - dest = self.compute.host - - def set_shared_storage(instance): - return True - - self.stubs.Set(self.compute.driver, 'instance_on_disk', - set_shared_storage) - - self.mox.StubOutWithMock(self.compute.driver, - 'spawn') - self.compute.driver.spawn(mox.IsA(c), mox.IsA(inst_ref), {}, - mox.IgnoreArg(), None, - mox.IgnoreArg(), mox.IgnoreArg()) - - # start test - self.mox.ReplayAll() - db.instance_update(self.context, inst_ref['uuid'], - {"task_state": task_states.REBUILDING}) - - self.compute.rebuild_instance(c, instance=inst_ref, - injected_files=None, image_ref=None, - orig_image_ref=None, new_pass=None, - orig_sys_metadata=None, bdms=[], - recreate=True, on_shared_storage=True) - - # cleanup - db.instance_destroy(c, inst_uuid) - - def test_rebuild_on_host_without_shared_storage(self): - """Confirm evacuate scenario without shared storage - (rebuild from image)""" - - # creating testdata - c = self.context.elevated() - - inst_ref = jsonutils.to_primitive(self._create_fake_instance - ({'host': 'fake_host_2'})) - - inst_uuid = inst_ref["uuid"] - dest = self.compute.host - - fake_image = { - 'id': 1, - 'name': 'fake_name', - 'properties': {'kernel_id': 'fake_kernel_id', - 'ramdisk_id': 'fake_ramdisk_id'}, - } - - def set_shared_storage(instance): - return False - - self.stubs.Set(self.compute.driver, 'instance_on_disk', - set_shared_storage) - - self.mox.StubOutWithMock(self.compute.driver, - 'spawn') - self.compute.driver.spawn(mox.IsA(c), mox.IsA(inst_ref), - mox.IsA(fake_image), mox.IgnoreArg(), - mox.IgnoreArg(), mox.IgnoreArg(), - mox.IgnoreArg()) - - # start test - self.mox.ReplayAll() - - db.instance_update(self.context, inst_ref['uuid'], - {"task_state": task_states.REBUILDING}) - - self.compute.rebuild_instance(c, instance=inst_ref, - injected_files=None, image_ref=None, - orig_image_ref=None, new_pass='newpass', - orig_sys_metadata=None, bdms=[], - recreate=True, on_shared_storage=False) - - # cleanup - db.instance_destroy(c, inst_uuid) - - def test_rebuild_on_host_instance_exists(self): - """Rebuild if instance exists raise an exception.""" - - # creating testdata - c = self.context.elevated() - inst_ref = self._create_fake_instance({'host': 'fake_host_2'}) - dest = self.compute.host - - instance = jsonutils.to_primitive(self._create_fake_instance()) - instance_uuid = instance['uuid'] - dest = self.compute.host - - self.compute.run_instance(self.context, instance=instance) - - db.instance_update(self.context, inst_ref['uuid'], - {"task_state": task_states.REBUILDING}) - - self.assertRaises(exception.Invalid, - self.compute.rebuild_instance, c, instance=inst_ref, - injected_files=None, image_ref=None, - orig_image_ref=None, new_pass=None, - orig_sys_metadata=None, - recreate=True, on_shared_storage=True) - - # cleanup - db.instance_destroy(c, inst_ref['uuid']) - self.compute.terminate_instance(self.context, instance=instance) - class ComputeAPITestCase(BaseTestCase): @@ -3701,6 +3535,28 @@ class ComputeAPITestCase(BaseTestCase): finally: db.instance_destroy(self.context, ref[0]['uuid']) + def test_create_saves_type_in_system_metadata(self): + instance_type = instance_types.get_default_instance_type() + (ref, resv_id) = self.compute_api.create( + self.context, + instance_type=instance_type, + image_href=None) + try: + sys_metadata = db.instance_system_metadata_get(self.context, + ref[0]['uuid']) + + instance_type_props = ['name', 'memory_mb', 'vcpus', 'root_gb', + 'ephemeral_gb', 'flavorid', 'swap', + 'rxtx_factor', 'vcpu_weight'] + for key in instance_type_props: + sys_meta_key = "instance_type_%s" % key + self.assertTrue(sys_meta_key in sys_metadata) + self.assertEqual(str(instance_type[key]), + str(sys_metadata[sys_meta_key])) + + finally: + db.instance_destroy(self.context, ref[0]['uuid']) + def test_create_instance_associates_security_groups(self): # Make sure create associates security groups. group = self._create_group() @@ -3908,6 +3764,38 @@ class ComputeAPITestCase(BaseTestCase): db.instance_destroy(self.context, instance['uuid']) + def test_delete_in_resizing(self): + def fake_quotas_reserve(context, expire=None, project_id=None, + **deltas): + old_type = instance_types.get_instance_type_by_name('m1.tiny') + # ensure using old instance type to create reservations + self.assertEqual(deltas['cores'], -old_type['vcpus']) + self.assertEqual(deltas['ram'], -old_type['memory_mb']) + + self.stubs.Set(QUOTAS, 'reserve', fake_quotas_reserve) + + instance, instance_uuid = self._run_instance(params={ + 'host': CONF.host}) + + # create a fake migration record (manager does this) + new_inst_type = instance_types.get_instance_type_by_name('m1.small') + db.migration_create(self.context.elevated(), + {'instance_uuid': instance['uuid'], + 'old_instance_type_id': instance['instance_type_id'], + 'new_instance_type_id': new_inst_type['id'], + 'status': 'post-migrating'}) + + # update instance type to resized one + db.instance_update(self.context, instance['uuid'], + {'instance_type_id': new_inst_type['id'], + 'vcpus': new_inst_type['vcpus'], + 'memory_mb': new_inst_type['memory_mb'], + 'task_state': task_states.RESIZE_FINISH}) + + self.compute_api.delete(self.context, instance) + + db.instance_destroy(self.context, instance['uuid']) + def test_delete_in_resized(self): instance, instance_uuid = self._run_instance(params={ 'host': CONF.host}) @@ -4577,6 +4465,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() @@ -5144,14 +5086,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'}]}) @@ -5176,7 +5118,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'}) @@ -5223,6 +5165,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() @@ -5517,6 +5477,50 @@ class ComputeAPITestCase(BaseTestCase): db.instance_destroy(self.context, instance['uuid']) + def test_spice_console(self): + # Make sure we can a spice console for an instance. + + fake_instance = {'uuid': 'fake_uuid', + 'host': 'fake_compute_host'} + fake_console_type = "spice-html5" + fake_connect_info = {'token': 'fake_token', + 'console_type': fake_console_type, + 'host': 'fake_console_host', + 'port': 'fake_console_port', + 'internal_access_path': 'fake_access_path'} + fake_connect_info2 = copy.deepcopy(fake_connect_info) + fake_connect_info2['access_url'] = 'fake_console_url' + + self.mox.StubOutWithMock(rpc, 'call') + + rpc_msg1 = {'method': 'get_spice_console', + 'args': {'instance': fake_instance, + 'console_type': fake_console_type}, + 'version': '2.24'} + rpc_msg2 = {'method': 'authorize_console', + 'args': fake_connect_info, + 'version': '1.0'} + + rpc.call(self.context, 'compute.%s' % fake_instance['host'], + rpc_msg1, None).AndReturn(fake_connect_info2) + rpc.call(self.context, CONF.consoleauth_topic, + rpc_msg2, None).AndReturn(None) + + self.mox.ReplayAll() + + console = self.compute_api.get_spice_console(self.context, + fake_instance, fake_console_type) + self.assertEqual(console, {'url': 'fake_console_url'}) + + def test_get_spice_console_no_host(self): + instance = self._create_fake_instance(params={'host': ''}) + + self.assertRaises(exception.InstanceNotReady, + self.compute_api.get_spice_console, + self.context, instance, 'spice') + + db.instance_destroy(self.context, instance['uuid']) + def test_get_backdoor_port(self): # Test api call to get backdoor_port. fake_backdoor_port = 59697 @@ -5611,6 +5615,88 @@ class ComputeAPITestCase(BaseTestCase): self.stubs.Set(compute_rpcapi.ComputeAPI, 'attach_volume', fake_rpc_attach_volume) + def test_detach_volume(self): + # Ensure volume can be detached from instance + + called = {} + instance = self._create_fake_instance() + + def fake_check_detach(*args, **kwargs): + called['fake_check_detach'] = True + + def fake_begin_detaching(*args, **kwargs): + called['fake_begin_detaching'] = True + + def fake_volume_get(self, context, volume_id): + called['fake_volume_get'] = True + return {'id': volume_id, 'attach_status': 'in-use', + 'instance_uuid': instance['uuid']} + + def fake_rpc_detach_volume(self, context, **kwargs): + called['fake_rpc_detach_volume'] = True + + self.stubs.Set(cinder.API, 'get', fake_volume_get) + self.stubs.Set(cinder.API, 'check_detach', fake_check_detach) + self.stubs.Set(cinder.API, 'begin_detaching', fake_begin_detaching) + self.stubs.Set(compute_rpcapi.ComputeAPI, 'detach_volume', + fake_rpc_detach_volume) + + self.compute_api.detach_volume(self.context, 1) + self.assertTrue(called.get('fake_volume_get')) + self.assertTrue(called.get('fake_check_detach')) + self.assertTrue(called.get('fake_begin_detaching')) + self.assertTrue(called.get('fake_rpc_detach_volume')) + + def test_detach_invalid_volume(self): + # Ensure exception is raised while detaching an un-attached volume + + def fake_volume_get(self, context, volume_id): + return {'id': volume_id, 'attach_status': 'detached'} + + self.stubs.Set(cinder.API, 'get', fake_volume_get) + self.assertRaises(exception.InvalidVolume, + self.compute_api.detach_volume, self.context, 1) + + def test_detach_volume_libvirt_is_down(self): + # Ensure rollback during detach if libvirt goes down + + called = {} + instance = self._create_fake_instance() + + def fake_get_instance_volume_bdm(*args, **kwargs): + return {'device_name': '/dev/vdb', 'volume_id': 1, + 'connection_info': '{"test": "test"}'} + + def fake_libvirt_driver_instance_exists(*args, **kwargs): + called['fake_libvirt_driver_instance_exists'] = True + return False + + def fake_libvirt_driver_detach_volume_fails(*args, **kwargs): + called['fake_libvirt_driver_detach_volume_fails'] = True + raise AttributeError + + def fake_roll_detaching(*args, **kwargs): + called['fake_roll_detaching'] = True + + def fake_volume_get(self, context, volume_id): + called['fake_volume_get'] = True + return {'id': volume_id, 'attach_status': 'in-use'} + + self.stubs.Set(cinder.API, 'get', fake_volume_get) + self.stubs.Set(cinder.API, 'roll_detaching', fake_roll_detaching) + self.stubs.Set(self.compute, "_get_instance_volume_bdm", + fake_get_instance_volume_bdm) + self.stubs.Set(self.compute.driver, "instance_exists", + fake_libvirt_driver_instance_exists) + self.stubs.Set(self.compute.driver, "detach_volume", + fake_libvirt_driver_detach_volume_fails) + + self.assertRaises(AttributeError, self.compute.detach_volume, + self.context, 1, instance) + self.assertTrue(called.get('fake_libvirt_driver_instance_exists')) + self.assertTrue(called.get('fake_volume_get')) + self.assertTrue(called.get('fake_roll_detaching')) + def test_terminate_with_volumes(self): # Make sure that volumes get detached during instance termination. admin = context.get_admin_context() @@ -5706,11 +5792,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( @@ -5735,11 +5821,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( @@ -5762,11 +5848,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( @@ -5784,7 +5870,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) @@ -5805,7 +5891,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) @@ -5824,7 +5910,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) @@ -6275,7 +6361,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 @@ -6525,7 +6611,8 @@ class ComputeRescheduleOrReraiseTestCase(BaseTestCase): exc_info = sys.exc_info() compute_utils.add_instance_fault_from_exc(self.context, - instance_uuid, exc_info[0], exc_info=exc_info) + self.compute.conductor_api, + self.instance, exc_info[0], exc_info=exc_info) self.compute._deallocate_network(self.context, self.instance).AndRaise(InnerTestingException("Error")) self.compute._log_original_error(exc_info, instance_uuid) @@ -6575,7 +6662,8 @@ class ComputeRescheduleOrReraiseTestCase(BaseTestCase): except Exception: exc_info = sys.exc_info() compute_utils.add_instance_fault_from_exc(self.context, - instance_uuid, exc_info[0], exc_info=exc_info) + self.compute.conductor_api, + self.instance, exc_info[0], exc_info=exc_info) self.compute._deallocate_network(self.context, self.instance) self.compute._reschedule(self.context, None, {}, instance_uuid, @@ -6603,7 +6691,8 @@ class ComputeRescheduleOrReraiseTestCase(BaseTestCase): exc_info = sys.exc_info() compute_utils.add_instance_fault_from_exc(self.context, - instance_uuid, exc_info[0], exc_info=exc_info) + self.compute.conductor_api, + self.instance, exc_info[0], exc_info=exc_info) self.compute._deallocate_network(self.context, self.instance) self.compute._reschedule(self.context, None, {}, instance_uuid, @@ -6739,3 +6828,144 @@ class ComputeInactiveImageTestCase(BaseTestCase): self.assertRaises(exception.ImageNotActive, self.compute_api.create, self.context, inst_type, 'fake-image-uuid') + + +class EvacuateHostTestCase(BaseTestCase): + def setUp(self): + super(EvacuateHostTestCase, self).setUp() + self.inst_ref = jsonutils.to_primitive(self._create_fake_instance + ({'host': 'fake_host_2'})) + db.instance_update(self.context, self.inst_ref['uuid'], + {"task_state": task_states.REBUILDING}) + + def tearDown(self): + db.instance_destroy(self.context, self.inst_ref['uuid']) + super(EvacuateHostTestCase, self).tearDown() + + def _rebuild(self, on_shared_storage=True): + orig_image_ref = None + image_ref = None + injected_files = None + self.compute.rebuild_instance( + self.context, self.inst_ref, orig_image_ref, image_ref, + injected_files, 'newpass', recreate=True, + on_shared_storage=on_shared_storage) + + def test_rebuild_on_host_updated_target(self): + """Confirm evacuate scenario updates host.""" + self.stubs.Set(self.compute.driver, 'instance_on_disk', lambda x: True) + self.mox.ReplayAll() + + self._rebuild() + + # Should be on destination host + instance = db.instance_get(self.context, self.inst_ref['id']) + self.assertEqual(instance['host'], self.compute.host) + + def test_rebuild_with_wrong_shared_storage(self): + """Confirm evacuate scenario does not update host.""" + self.stubs.Set(self.compute.driver, 'instance_on_disk', lambda x: True) + self.mox.ReplayAll() + + self.assertRaises(exception.InvalidSharedStorage, + lambda: self._rebuild(on_shared_storage=False)) + + # Should remain on original host + instance = db.instance_get(self.context, self.inst_ref['id']) + self.assertEqual(instance['host'], 'fake_host_2') + + def test_rebuild_on_host_with_volumes(self): + """Confirm evacuate scenario reconnects volumes.""" + values = {'instance_uuid': self.inst_ref['uuid'], + 'device_name': '/dev/vdc', + 'delete_on_termination': False, + 'volume_id': 'fake_volume_id'} + + db.block_device_mapping_create(self.context, values) + + def fake_volume_get(self, context, volume): + return {'id': 'fake_volume_id'} + self.stubs.Set(cinder.API, "get", fake_volume_get) + + # Stub out and record whether it gets detached + result = {"detached": False} + + def fake_detach(self, context, volume): + result["detached"] = volume["id"] == 'fake_volume_id' + self.stubs.Set(cinder.API, "detach", fake_detach) + + def fake_terminate_connection(self, context, volume, connector): + return {} + self.stubs.Set(cinder.API, "terminate_connection", + fake_terminate_connection) + + # make sure volumes attach, detach are called + self.mox.StubOutWithMock(self.compute.volume_api, 'detach') + self.compute.volume_api.detach(mox.IsA(self.context), mox.IgnoreArg()) + + self.mox.StubOutWithMock(self.compute, '_setup_block_device_mapping') + self.compute._setup_block_device_mapping(mox.IsA(self.context), + mox.IsA(self.inst_ref), + mox.IgnoreArg()) + + self.stubs.Set(self.compute.driver, 'instance_on_disk', lambda x: True) + self.mox.ReplayAll() + + self._rebuild() + + # cleanup + for bdms in db.block_device_mapping_get_all_by_instance( + self.context, self.inst_ref['uuid']): + db.block_device_mapping_destroy(self.context, bdms['id']) + + def test_rebuild_on_host_with_shared_storage(self): + """Confirm evacuate scenario on shared storage.""" + self.mox.StubOutWithMock(self.compute.driver, 'spawn') + self.compute.driver.spawn(mox.IsA(self.context), + mox.IsA(self.inst_ref), {}, mox.IgnoreArg(), 'newpass', + network_info=mox.IgnoreArg(), + block_device_info=mox.IgnoreArg()) + + self.stubs.Set(self.compute.driver, 'instance_on_disk', lambda x: True) + self.mox.ReplayAll() + + self._rebuild() + + def test_rebuild_on_host_without_shared_storage(self): + """Confirm evacuate scenario without shared storage + (rebuild from image) + """ + fake_image = {'id': 1, + 'name': 'fake_name', + 'properties': {'kernel_id': 'fake_kernel_id', + 'ramdisk_id': 'fake_ramdisk_id'}} + + self.mox.StubOutWithMock(self.compute.driver, 'spawn') + self.compute.driver.spawn(mox.IsA(self.context), + mox.IsA(self.inst_ref), mox.IsA(fake_image), mox.IgnoreArg(), + mox.IsA('newpass'), network_info=mox.IgnoreArg(), + block_device_info=mox.IgnoreArg()) + + self.stubs.Set(self.compute.driver, 'instance_on_disk', + lambda x: False) + self.mox.ReplayAll() + + self._rebuild(on_shared_storage=False) + + def test_rebuild_on_host_instance_exists(self): + """Rebuild if instance exists raises an exception.""" + db.instance_update(self.context, self.inst_ref['uuid'], + {"task_state": task_states.SCHEDULING}) + self.compute.run_instance(self.context, instance=self.inst_ref) + + self.stubs.Set(self.compute.driver, 'instance_on_disk', lambda x: True) + self.assertRaises(exception.InstanceExists, + lambda: self._rebuild(on_shared_storage=True)) + + def test_driver_doesnt_support_recreate(self): + with utils.temporary_mutation(self.compute.driver.capabilities, + supports_recreate=False): + self.stubs.Set(self.compute.driver, 'instance_on_disk', + lambda x: True) + self.assertRaises(exception.InstanceRecreateNotSupported, + lambda: self._rebuild(on_shared_storage=True)) diff --git a/nova/tests/compute/test_compute_cells.py b/nova/tests/compute/test_compute_cells.py index 3c25f9b43..df78c37f3 100644 --- a/nova/tests/compute/test_compute_cells.py +++ b/nova/tests/compute/test_compute_cells.py @@ -164,6 +164,10 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase): def test_backup(self): return super(CellsComputeAPITestCase, self).test_backup() + def test_detach_volume(self): + self.skipTest("This test is failing due to TypeError: " + "detach_volume() takes exactly 3 arguments (4 given).") + class CellsComputePolicyTestCase(test_compute.ComputePolicyTestCase): def setUp(self): diff --git a/nova/tests/compute/test_compute_utils.py b/nova/tests/compute/test_compute_utils.py index f29c68627..4372039e0 100644 --- a/nova/tests/compute/test_compute_utils.py +++ b/nova/tests/compute/test_compute_utils.py @@ -69,8 +69,11 @@ class ComputeValidateDeviceTestCase(test.TestCase): lambda context, instance: self.data) def _validate_device(self, device=None): + bdms = db.block_device_mapping_get_all_by_instance( + self.context, self.instance['uuid']) return compute_utils.get_device_name_for_instance(self.context, self.instance, + bdms, device) @staticmethod @@ -356,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) @@ -379,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/compute/test_multiple_nodes.py b/nova/tests/compute/test_multiple_nodes.py index 78ed0cea7..27ee7aaba 100644 --- a/nova/tests/compute/test_multiple_nodes.py +++ b/nova/tests/compute/test_multiple_nodes.py @@ -80,6 +80,9 @@ class MultiNodeComputeTestCase(BaseTestCase): super(MultiNodeComputeTestCase, self).setUp() self.flags(compute_driver='nova.virt.fake.FakeDriver') self.compute = importutils.import_object(CONF.compute_manager) + self.flags(use_local=True, group='conductor') + self.conductor = self.start_service('conductor', + manager=CONF.conductor.manager) def test_update_available_resource_add_remove_node(self): ctx = context.get_admin_context() diff --git a/nova/tests/compute/test_resource_tracker.py b/nova/tests/compute/test_resource_tracker.py index 53d92a13f..eaa0df5bf 100644 --- a/nova/tests/compute/test_resource_tracker.py +++ b/nova/tests/compute/test_resource_tracker.py @@ -391,7 +391,7 @@ class BaseTrackerTestCase(BaseTestCase): if tracker is None: tracker = self.tracker - if not field in tracker.compute_node: + if field not in tracker.compute_node: raise test.TestingException( "'%(field)s' not in compute node." % locals()) x = tracker.compute_node[field] diff --git a/nova/tests/compute/test_rpcapi.py b/nova/tests/compute/test_rpcapi.py index 00b90ea65..b81e049bf 100644 --- a/nova/tests/compute/test_rpcapi.py +++ b/nova/tests/compute/test_rpcapi.py @@ -165,6 +165,11 @@ class ComputeRpcAPITestCase(test.TestCase): self._test_compute_api('get_vnc_console', 'call', instance=self.fake_instance, console_type='type') + def test_get_spice_console(self): + self._test_compute_api('get_spice_console', 'call', + instance=self.fake_instance, console_type='type', + version='2.24') + def test_host_maintenance_mode(self): self._test_compute_api('host_maintenance_mode', 'call', host_param='param', mode='mode', host='host') diff --git a/nova/tests/conductor/test_conductor.py b/nova/tests/conductor/test_conductor.py index cc3dbfcc0..f21e67845 100644 --- a/nova/tests/conductor/test_conductor.py +++ b/nova/tests/conductor/test_conductor.py @@ -130,6 +130,16 @@ class _BaseTestCase(object): 'fake-window', 'fake-host') + def test_migration_get_in_progress_by_host_and_node(self): + self.mox.StubOutWithMock(db, + 'migration_get_in_progress_by_host_and_node') + db.migration_get_in_progress_by_host_and_node( + self.context, 'fake-host', 'fake-node').AndReturn('fake-result') + self.mox.ReplayAll() + result = self.conductor.migration_get_in_progress_by_host_and_node( + self.context, 'fake-host', 'fake-node') + self.assertEqual(result, 'fake-result') + def test_migration_create(self): inst = {'uuid': 'fake-uuid', 'host': 'fake-host', @@ -325,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') @@ -388,6 +407,62 @@ class _BaseTestCase(object): result = self.conductor.ping(self.context, 'foo') self.assertEqual(result, {'service': 'conductor', 'arg': 'foo'}) + def test_compute_node_create(self): + self.mox.StubOutWithMock(db, 'compute_node_create') + db.compute_node_create(self.context, 'fake-values').AndReturn( + 'fake-result') + self.mox.ReplayAll() + result = self.conductor.compute_node_create(self.context, + 'fake-values') + self.assertEqual(result, 'fake-result') + + def test_compute_node_update(self): + node = {'id': 'fake-id'} + self.mox.StubOutWithMock(db, 'compute_node_update') + db.compute_node_update(self.context, node['id'], 'fake-values', + False).AndReturn('fake-result') + self.mox.ReplayAll() + result = self.conductor.compute_node_update(self.context, node, + 'fake-values', False) + self.assertEqual(result, 'fake-result') + + def test_instance_fault_create(self): + self.mox.StubOutWithMock(db, 'instance_fault_create') + db.instance_fault_create(self.context, 'fake-values').AndReturn( + 'fake-result') + self.mox.ReplayAll() + result = self.conductor.instance_fault_create(self.context, + 'fake-values') + self.assertEqual(result, 'fake-result') + + def test_task_log_get(self): + self.mox.StubOutWithMock(db, 'task_log_get') + db.task_log_get(self.context, 'task', 'begin', 'end', 'host', + 'state').AndReturn('result') + self.mox.ReplayAll() + result = self.conductor.task_log_get(self.context, 'task', 'begin', + 'end', 'host', 'state') + self.assertEqual(result, 'result') + + def test_task_log_begin_task(self): + self.mox.StubOutWithMock(db, 'task_log_begin_task') + db.task_log_begin_task(self.context.elevated(), 'task', 'begin', + 'end', 'host', 'items', + 'message').AndReturn('result') + self.mox.ReplayAll() + result = self.conductor.task_log_begin_task( + self.context, 'task', 'begin', 'end', 'host', 'items', 'message') + self.assertEqual(result, 'result') + + def test_task_log_end_task(self): + self.mox.StubOutWithMock(db, 'task_log_end_task') + db.task_log_end_task(self.context.elevated(), 'task', 'begin', 'end', + 'host', 'errors', 'message').AndReturn('result') + self.mox.ReplayAll() + result = self.conductor.task_log_end_task( + self.context, 'task', 'begin', 'end', 'host', 'errors', 'message') + self.assertEqual(result, 'result') + class ConductorTestCase(_BaseTestCase, test.TestCase): """Conductor Manager Tests.""" @@ -451,8 +526,23 @@ class ConductorTestCase(_BaseTestCase, test.TestCase): self.conductor.instance_get_all_by_filters(self.context, filters, 'fake-key', 'fake-sort') + def test_instance_get_all_by_host(self): + self.mox.StubOutWithMock(db, 'instance_get_all_by_host') + self.mox.StubOutWithMock(db, 'instance_get_all_by_host_and_node') + db.instance_get_all_by_host(self.context.elevated(), + 'host').AndReturn('result') + db.instance_get_all_by_host_and_node(self.context.elevated(), 'host', + 'node').AndReturn('result') + self.mox.ReplayAll() + result = self.conductor.instance_get_all_by_host(self.context, 'host') + self.assertEqual(result, 'result') + result = self.conductor.instance_get_all_by_host(self.context, 'host', + 'node') + self.assertEqual(result, 'result') + def _test_stubbed(self, name, dbargs, condargs, db_result_listified=False): + self.mox.StubOutWithMock(db, name) getattr(db, name)(self.context, *dbargs).AndReturn('fake-result') self.mox.ReplayAll() @@ -655,19 +745,22 @@ class ConductorAPITestCase(_BaseTestCase, test.TestCase): def test_instance_get_all(self): self.mox.StubOutWithMock(db, 'instance_get_all_by_filters') db.instance_get_all(self.context) - db.instance_get_all_by_host(self.context.elevated(), 'fake-host') db.instance_get_all_by_filters(self.context, {'name': 'fake-inst'}, 'updated_at', 'asc') self.mox.ReplayAll() self.conductor.instance_get_all(self.context) - self.conductor.instance_get_all_by_host(self.context, 'fake-host') self.conductor.instance_get_all_by_filters(self.context, {'name': 'fake-inst'}, 'updated_at', 'asc') def _test_stubbed(self, name, *args, **kwargs): + if args and isinstance(args[0], FakeContext): + ctxt = args[0] + args = args[1:] + else: + ctxt = self.context self.mox.StubOutWithMock(db, name) - getattr(db, name)(self.context, *args).AndReturn('fake-result') + getattr(db, name)(ctxt, *args).AndReturn('fake-result') if name == 'service_destroy': # TODO(russellb) This is a hack ... SetUp() starts the conductor() # service. There is a cleanup step that runs after this test which @@ -700,6 +793,22 @@ class ConductorAPITestCase(_BaseTestCase, test.TestCase): def test_service_destroy(self): self._test_stubbed('service_destroy', '', returns=False) + def test_service_update(self): + ctxt = self.context + self.mox.StubOutWithMock(db, 'service_update') + db.service_update(ctxt, '', {}).AndReturn('fake-result') + self.mox.ReplayAll() + result = self.conductor.service_update(self.context, {'id': ''}, {}) + self.assertEqual(result, 'fake-result') + + def test_instance_get_all_by_host(self): + self._test_stubbed('instance_get_all_by_host', + self.context.elevated(), 'host') + + def test_instance_get_all_by_host_and_node(self): + self._test_stubbed('instance_get_all_by_host_and_node', + self.context.elevated(), 'host', 'node') + def test_ping(self): timeouts = [] calls = dict(count=0) 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 15890cdcd..ead43adea 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -42,6 +42,7 @@ policy_data = """ "compute:unlock": "", "compute:get_vnc_console": "", + "compute:get_spice_console": "", "compute:get_console_output": "", "compute:associate_floating_ip": "", @@ -104,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": "", @@ -155,8 +157,14 @@ policy_data = """ "compute_extension:virtual_interfaces": "", "compute_extension:virtual_storage_arrays": "", "compute_extension:volumes": "", + "compute_extension:volume_attachments:index": "", + "compute_extension:volume_attachments:show": "", + "compute_extension:volume_attachments:create": "", + "compute_extension:volume_attachments:delete": "", "compute_extension:volumetypes": "", "compute_extension:zones": "", + "compute_extension:availability_zone:list": "", + "compute_extension:availability_zone:detail": "is_admin:True", "volume:create": "", @@ -191,16 +199,20 @@ policy_data = """ "volume_extension:types_extra_specs": "", - "network:get_all_networks": "", - "network:get_network": "", - "network:create_networks": "", - "network:delete_network": "", - "network:disassociate_network": "", + "network:get_all": "", + "network:get": "", + "network:create": "", + "network:delete": "", + "network:associate": "", + "network:disassociate": "", "network:get_vifs_by_instance": "", "network:allocate_for_instance": "", "network:deallocate_for_instance": "", "network:validate_networks": "", "network:get_instance_uuids_by_ip_filter": "", + "network:get_instance_id_by_floating_address": "", + "network:setup_networks_on_host": "", + "network:get_backdoor_port": "", "network:get_floating_ip": "", "network:get_floating_ip_pools": "", @@ -211,6 +223,7 @@ policy_data = """ "network:deallocate_floating_ip": "", "network:associate_floating_ip": "", "network:disassociate_floating_ip": "", + "network:release_floating_ip": "", "network:migrate_instance_start": "", "network:migrate_instance_finish": "", diff --git a/nova/tests/fake_volume.py b/nova/tests/fake_volume.py index f2aa3ea91..c7430ee6d 100644 --- a/nova/tests/fake_volume.py +++ b/nova/tests/fake_volume.py @@ -136,7 +136,6 @@ class API(object): def create_with_kwargs(self, context, **kwargs): volume_id = kwargs.get('volume_id', None) - print volume_id v = fake_volume(kwargs['size'], kwargs['name'], kwargs['description'], @@ -145,7 +144,6 @@ class API(object): None, None, None) - print v.vol['id'] if kwargs.get('status', None) is not None: v.vol['status'] = kwargs['status'] if kwargs['host'] is not None: diff --git a/nova/tests/fakeguestfs.py b/nova/tests/fakeguestfs.py index 33ca49c33..ff006db68 100644 --- a/nova/tests/fakeguestfs.py +++ b/nova/tests/fakeguestfs.py @@ -50,7 +50,7 @@ class GuestFS(object): self.mounts.append((options, device, mntpoint)) def mkdir_p(self, path): - if not path in self.files: + if path not in self.files: self.files[path] = { "isdir": True, "gid": 100, @@ -59,7 +59,7 @@ class GuestFS(object): } def read_file(self, path): - if not path in self.files: + if path not in self.files: self.files[path] = { "isdir": False, "content": "Hello World", @@ -71,7 +71,7 @@ class GuestFS(object): return self.files[path]["content"] def write(self, path, content): - if not path in self.files: + if path not in self.files: self.files[path] = { "isdir": False, "content": "Hello World", @@ -83,7 +83,7 @@ class GuestFS(object): self.files[path]["content"] = content def write_append(self, path, content): - if not path in self.files: + if path not in self.files: self.files[path] = { "isdir": False, "content": "Hello World", @@ -95,13 +95,13 @@ class GuestFS(object): self.files[path]["content"] = self.files[path]["content"] + content def stat(self, path): - if not path in self.files: + if path not in self.files: raise RuntimeError("No such file: " + path) return self.files[path]["mode"] def chown(self, uid, gid, path): - if not path in self.files: + if path not in self.files: raise RuntimeError("No such file: " + path) if uid != -1: @@ -110,7 +110,7 @@ class GuestFS(object): self.files[path]["gid"] = gid def chmod(self, mode, path): - if not path in self.files: + if path not in self.files: raise RuntimeError("No such file: " + path) self.files[path]["mode"] = mode diff --git a/nova/tests/fakelibvirt.py b/nova/tests/fakelibvirt.py index 8d9561c7e..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'] @@ -414,6 +418,7 @@ class Domain(object): <input type='tablet' bus='usb'/> <input type='mouse' bus='ps2'/> <graphics type='vnc' port='-1' autoport='yes'/> + <graphics type='spice' port='-1' autoport='yes'/> <video> <model type='cirrus' vram='9216' heads='1'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x02' @@ -516,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/hyperv/README.rst b/nova/tests/hyperv/README.rst deleted file mode 100644 index c7ba16046..000000000 --- a/nova/tests/hyperv/README.rst +++ /dev/null @@ -1,83 +0,0 @@ -===================================== -OpenStack Hyper-V Nova Testing Architecture -===================================== - -The Hyper-V Nova Compute plugin uses Windows Management Instrumentation (WMI) -as the main API for hypervisor related operations. -WMI has a database / procedural oriented nature that can become difficult to -test with a traditional static mock / stub based unit testing approach. - -The included Hyper-V testing framework has been developed with the -following goals: - -1) Dynamic mock generation. -2) Decoupling. No dependencies on WMI or any other module. - The tests are designed to work with mocked objects in all cases, including - OS-dependent (e.g. wmi, os, subprocess) and non-deterministic - (e.g. time, uuid) modules -3) Transparency. Mocks and real objects can be swapped via DI - or monkey patching. -4) Platform independence. -5) Tests need to be executed against the real object or against the mocks - with a simple configuration switch. Development efforts can highly - benefit from this feature. -6) It must be possible to change a mock's behavior without running the tests - against the hypervisor (e.g. by manually adding a value / return value). - -The tests included in this package include dynamically generated mock objects, -based on the recording of the attribute values and invocations on the -real WMI objects and other OS dependent features. -The generated mock objects are serialized in the nova/tests/hyperv/stubs -directory as gzipped pickled objects. - -An environment variable controls the execution mode of the tests. - -Recording mode: - -NOVA_GENERATE_TEST_MOCKS=True -Tests are executed on the hypervisor (without mocks), and mock objects are -generated. - -Replay mode: - -NOVA_GENERATE_TEST_MOCKS= -Tests are executed with the existing mock objects (default). - -Mock generation is performed by nova.tests.hyperv.mockproxy.MockProxy. -Instances of this class wrap objects that need to be mocked and act as a -delegate on the wrapped object by leveraging Python's __getattr__ feature. -Attribute values and method call return values are recorded at each access. -Objects returned by attributes and method invocations are wrapped in a -MockProxy consistently. -From a caller perspective, the MockProxy is completely transparent, -with the exception of calls to the type(...) builtin function. - -At the end of the test, a mock is generated by each MockProxy by calling -the get_mock() method. A mock is represented by an instance of the -nova.tests.hyperv.mockproxy.Mock class. - -The Mock class task consists of replicating the behaviour of the mocked -objects / modules by returning the same values in the same order, for example: - -def check_path(path): - if not os.path.exists(path): - os.makedirs(path) - -check_path(path) -# The second time os.path.exists returns True -check_path(path) - -The injection of MockProxy / Mock instances is performed by the -nova.tests.hyperv.basetestcase.BaseTestCase class in the setUp() -method via selective monkey patching. -Mocks are serialized in tearDown() during recording. - -The actual Hyper-V test case inherits from BaseTestCase: -nova.tests.hyperv.test_hypervapi.HyperVAPITestCase - - -Future directions: - -1) Replace the pickled files with a more generic serialization option (e.g. json) -2) Add methods to statically extend the mocks (e.g. method call return values) -3) Extend an existing framework, e.g. mox diff --git a/nova/tests/hyperv/__init__.py b/nova/tests/hyperv/__init__.py index e69de29bb..090fc0639 100644 --- a/nova/tests/hyperv/__init__.py +++ b/nova/tests/hyperv/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# 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. diff --git a/nova/tests/hyperv/basetestcase.py b/nova/tests/hyperv/basetestcase.py deleted file mode 100644 index c4f6cf95f..000000000 --- a/nova/tests/hyperv/basetestcase.py +++ /dev/null @@ -1,105 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 Cloudbase Solutions Srl -# -# 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. - -""" -TestCase for MockProxy based tests and related classes. -""" - -import gzip -import os -import pickle -import sys - -from nova import test -from nova.tests.hyperv import mockproxy - -gen_test_mocks_key = 'NOVA_GENERATE_TEST_MOCKS' - - -class BaseTestCase(test.TestCase): - """TestCase for MockProxy based tests.""" - - def run(self, result=None): - self._currentResult = result - super(BaseTestCase, self).run(result) - - def setUp(self): - super(BaseTestCase, self).setUp() - self._mps = {} - - def tearDown(self): - super(BaseTestCase, self).tearDown() - - # python-subunit will wrap test results with a decorator. - # Need to access the decorated member of results to get the - # actual test result when using python-subunit. - if hasattr(self._currentResult, 'decorated'): - result = self._currentResult.decorated - else: - result = self._currentResult - has_errors = len([test for (test, msgs) in result.errors - if test.id() == self.id()]) > 0 - failed = len([test for (test, msgs) in result.failures - if test.id() == self.id()]) > 0 - - if not has_errors and not failed: - self._save_mock_proxies() - - def _save_mock(self, name, mock): - path = self._get_stub_file_path(self.id(), name) - pickle.dump(mock, gzip.open(path, 'wb')) - - def _get_stub_file_path(self, test_name, mock_name): - # test naming differs between platforms - prefix = 'nova.tests.' - if test_name.startswith(prefix): - test_name = test_name[len(prefix):] - file_name = '{0}_{1}.p.gz'.format(test_name, mock_name) - return os.path.join(os.path.dirname(mockproxy.__file__), - "stubs", file_name) - - def _load_mock(self, name): - path = self._get_stub_file_path(self.id(), name) - if os.path.exists(path): - return pickle.load(gzip.open(path, 'rb')) - else: - return None - - def _load_mock_or_create_proxy(self, module_name): - m = None - if not gen_test_mocks_key in os.environ or \ - os.environ[gen_test_mocks_key].lower() \ - not in ['true', 'yes', '1']: - m = self._load_mock(module_name) - else: - __import__(module_name) - module = sys.modules[module_name] - m = mockproxy.MockProxy(module) - self._mps[module_name] = m - return m - - def _inject_mocks_in_modules(self, objects_to_mock, modules_to_test): - for module_name in objects_to_mock: - mp = self._load_mock_or_create_proxy(module_name) - for mt in modules_to_test: - module_local_name = module_name.split('.')[-1] - setattr(mt, module_local_name, mp) - - def _save_mock_proxies(self): - for name, mp in self._mps.items(): - m = mp.get_mock() - if m.has_values(): - self._save_mock(name, m) diff --git a/nova/tests/hyperv/db_fakes.py b/nova/tests/hyperv/db_fakes.py index 16d894df8..e384e909a 100644 --- a/nova/tests/hyperv/db_fakes.py +++ b/nova/tests/hyperv/db_fakes.py @@ -29,35 +29,35 @@ from nova import utils def get_fake_instance_data(name, project_id, user_id): return {'name': name, - 'id': 1, - 'uuid': str(uuid.uuid4()), - 'project_id': project_id, - 'user_id': user_id, - 'image_ref': "1", - 'kernel_id': "1", - 'ramdisk_id': "1", - 'mac_address': "de:ad:be:ef:be:ef", - 'instance_type': - {'name': 'm1.tiny', - 'memory_mb': 512, - 'vcpus': 1, - 'root_gb': 0, - 'flavorid': 1, - 'rxtx_factor': 1} - } + 'id': 1, + 'uuid': str(uuid.uuid4()), + 'project_id': project_id, + 'user_id': user_id, + 'image_ref': "1", + 'kernel_id': "1", + 'ramdisk_id': "1", + 'mac_address': "de:ad:be:ef:be:ef", + 'instance_type': + {'name': 'm1.tiny', + 'memory_mb': 512, + 'vcpus': 1, + 'root_gb': 0, + 'flavorid': 1, + 'rxtx_factor': 1} + } def get_fake_image_data(project_id, user_id): return {'name': 'image1', - 'id': 1, - 'project_id': project_id, - 'user_id': user_id, - 'image_ref': "1", - 'kernel_id': "1", - 'ramdisk_id': "1", - 'mac_address': "de:ad:be:ef:be:ef", - 'instance_type': 'm1.tiny', - } + 'id': 1, + 'project_id': project_id, + 'user_id': user_id, + 'image_ref': "1", + 'kernel_id': "1", + 'ramdisk_id': "1", + 'mac_address': "de:ad:be:ef:be:ef", + 'instance_type': 'm1.tiny', + } def get_fake_volume_info_data(target_portal, volume_id): @@ -72,25 +72,25 @@ def get_fake_volume_info_data(target_portal, volume_id): 'auth_method': 'fake', 'auth_method': 'fake', } -} + } def get_fake_block_device_info(target_portal, volume_id): - return { - 'block_device_mapping': [{'connection_info': { - 'driver_volume_type': 'iscsi', - 'data': {'target_lun': 1, - 'volume_id': volume_id, - 'target_iqn': 'iqn.2010-10.org.openstack:volume-' + - volume_id, - 'target_portal': target_portal, - 'target_discovered': False}}, - 'mount_device': 'vda', - 'delete_on_termination': False}], + return {'block_device_mapping': [{'connection_info': { + 'driver_volume_type': 'iscsi', + 'data': {'target_lun': 1, + 'volume_id': volume_id, + 'target_iqn': + 'iqn.2010-10.org.openstack:volume-' + + volume_id, + 'target_portal': target_portal, + 'target_discovered': False}}, + 'mount_device': 'vda', + 'delete_on_termination': False}], 'root_device_name': None, 'ephemerals': [], 'swap': None - } + } def stub_out_db_instance_api(stubs): @@ -99,11 +99,9 @@ def stub_out_db_instance_api(stubs): INSTANCE_TYPES = { 'm1.tiny': dict(memory_mb=512, vcpus=1, root_gb=0, flavorid=1), 'm1.small': dict(memory_mb=2048, vcpus=1, root_gb=20, flavorid=2), - 'm1.medium': - dict(memory_mb=4096, vcpus=2, root_gb=40, flavorid=3), + 'm1.medium': dict(memory_mb=4096, vcpus=2, root_gb=40, flavorid=3), 'm1.large': dict(memory_mb=8192, vcpus=4, root_gb=80, flavorid=4), - 'm1.xlarge': - dict(memory_mb=16384, vcpus=8, root_gb=160, flavorid=5)} + 'm1.xlarge': dict(memory_mb=16384, vcpus=8, root_gb=160, flavorid=5)} class FakeModel(object): """Stubs out for model.""" @@ -152,7 +150,7 @@ def stub_out_db_instance_api(stubs): 'vcpus': instance_type['vcpus'], 'mac_addresses': [{'address': values['mac_address']}], 'root_gb': instance_type['root_gb'], - } + } return FakeModel(base_options) def fake_network_get_by_instance(context, instance_id): @@ -181,4 +179,4 @@ def stub_out_db_instance_api(stubs): stubs.Set(db, 'instance_type_get_all', fake_instance_type_get_all) stubs.Set(db, 'instance_type_get_by_name', fake_instance_type_get_by_name) stubs.Set(db, 'block_device_mapping_get_all_by_instance', - fake_block_device_mapping_get_all_by_instance) + fake_block_device_mapping_get_all_by_instance) diff --git a/nova/tests/hyperv/fake.py b/nova/tests/hyperv/fake.py new file mode 100644 index 000000000..9890a5462 --- /dev/null +++ b/nova/tests/hyperv/fake.py @@ -0,0 +1,46 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import io +import os + + +class PathUtils(object): + def open(self, path, mode): + return io.BytesIO(b'fake content') + + def get_instances_path(self): + return 'C:\\FakePath\\' + + def get_instance_path(self, instance_name): + return os.path.join(self.get_instances_path(), instance_name) + + def get_vhd_path(self, instance_name): + instance_path = self.get_instance_path(instance_name) + return os.path.join(instance_path, instance_name + ".vhd") + + def get_base_vhd_path(self, image_name): + base_dir = os.path.join(self.get_instances_path(), '_base') + return os.path.join(base_dir, image_name + ".vhd") + + def make_export_path(self, instance_name): + export_folder = os.path.join(self.get_instances_path(), "export", + instance_name) + return export_folder + + def vhd_exists(self, path): + return False diff --git a/nova/tests/hyperv/hypervutils.py b/nova/tests/hyperv/hypervutils.py deleted file mode 100644 index b71e60229..000000000 --- a/nova/tests/hyperv/hypervutils.py +++ /dev/null @@ -1,262 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 Cloudbase Solutions Srl -# -# 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. - -""" -Hyper-V classes to be used in testing. -""" - -import sys -import time - -from nova import exception -from nova.virt.hyperv import constants -from nova.virt.hyperv import volumeutilsV2 -from xml.etree import ElementTree - -# Check needed for unit testing on Unix -if sys.platform == 'win32': - import wmi - - -class HyperVUtils(object): - def __init__(self): - self.__conn = None - self.__conn_v2 = None - self.__conn_cimv2 = None - self.__conn_wmi = None - self.__conn_storage = None - self._volumeutils = volumeutilsV2.VolumeUtilsV2( - self._conn_storage, self._conn_wmi) - - @property - def _conn(self): - if self.__conn is None: - self.__conn = wmi.WMI(moniker='//./root/virtualization') - return self.__conn - - @property - def _conn_v2(self): - if self.__conn_v2 is None: - self.__conn_v2 = wmi.WMI(moniker='//./root/virtualization/v2') - return self.__conn_v2 - - @property - def _conn_cimv2(self): - if self.__conn_cimv2 is None: - self.__conn_cimv2 = wmi.WMI(moniker='//./root/cimv2') - return self.__conn_cimv2 - - @property - def _conn_wmi(self): - if self.__conn_wmi is None: - self.__conn_wmi = wmi.WMI(moniker='//./root/wmi') - return self.__conn_wmi - - @property - def _conn_storage(self): - if self.__conn_storage is None: - storage_namespace = '//./Root/Microsoft/Windows/Storage' - self.__conn_storage = wmi.WMI(moniker=storage_namespace) - return self.__conn_storage - - def create_vhd(self, path): - image_service = self._conn.query( - "Select * from Msvm_ImageManagementService")[0] - (job, ret_val) = image_service.CreateDynamicVirtualHardDisk( - Path=path, MaxInternalSize=3 * 1024 * 1024) - - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._check_job_status(job) - else: - success = (ret_val == 0) - if not success: - raise Exception('Failed to create Dynamic disk %s with error %d' - % (path, ret_val)) - - def _check_job_status(self, jobpath): - """Poll WMI job state for completion.""" - job_wmi_path = jobpath.replace('\\', '/') - job = wmi.WMI(moniker=job_wmi_path) - - while job.JobState == constants.WMI_JOB_STATE_RUNNING: - time.sleep(0.1) - job = wmi.WMI(moniker=job_wmi_path) - return job.JobState == constants.WMI_JOB_STATE_COMPLETED - - def _get_vm(self, vm_name, conn=None): - if conn is None: - conn = self._conn - vml = conn.Msvm_ComputerSystem(ElementName=vm_name) - if not len(vml): - raise exception.InstanceNotFound(instance=vm_name) - return vml[0] - - def remote_vm_exists(self, server, vm_name): - conn = wmi.WMI(moniker='//' + server + '/root/virtualization') - return self._vm_exists(conn, vm_name) - - def vm_exists(self, vm_name): - return self._vm_exists(self._conn, vm_name) - - def _vm_exists(self, conn, vm_name): - return len(conn.Msvm_ComputerSystem(ElementName=vm_name)) > 0 - - def _get_vm_summary(self, vm_name): - vm = self._get_vm(vm_name) - vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] - vmsettings = vm.associators( - wmi_association_class='Msvm_SettingsDefineState', - wmi_result_class='Msvm_VirtualSystemSettingData') - settings_paths = [v.path_() for v in vmsettings] - return vs_man_svc.GetSummaryInformation([100, 105], - settings_paths)[1][0] - - def get_vm_uptime(self, vm_name): - return self._get_vm_summary(vm_name).UpTime - - def get_vm_state(self, vm_name): - return self._get_vm_summary(vm_name).EnabledState - - def set_vm_state(self, vm_name, req_state): - self._set_vm_state(self._conn, vm_name, req_state) - - def _set_vm_state(self, conn, vm_name, req_state): - vm = self._get_vm(vm_name, conn) - (job, ret_val) = vm.RequestStateChange(req_state) - - success = False - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._check_job_status(job) - elif ret_val == 0: - success = True - elif ret_val == 32775: - #Invalid state for current operation. Typically means it is - #already in the state requested - success = True - if not success: - raise Exception(_("Failed to change vm state of %(vm_name)s" - " to %(req_state)s") % locals()) - - def get_vm_disks(self, vm_name): - return self._get_vm_disks(self._conn, vm_name) - - def _get_vm_disks(self, conn, vm_name): - vm = self._get_vm(vm_name, conn) - vmsettings = vm.associators( - wmi_result_class='Msvm_VirtualSystemSettingData') - rasds = vmsettings[0].associators( - wmi_result_class='MSVM_ResourceAllocationSettingData') - - disks = [r for r in rasds - if r.ResourceSubType == 'Microsoft Virtual Hard Disk'] - disk_files = [] - for disk in disks: - disk_files.extend([c for c in disk.Connection]) - - volumes = [r for r in rasds - if r.ResourceSubType == 'Microsoft Physical Disk Drive'] - volume_drives = [] - for volume in volumes: - hostResources = volume.HostResource - drive_path = hostResources[0] - volume_drives.append(drive_path) - - dvds = [r for r in rasds - if r.ResourceSubType == 'Microsoft Virtual CD/DVD Disk'] - dvd_files = [] - for dvd in dvds: - dvd_files.extend([c for c in dvd.Connection]) - - return (disk_files, volume_drives, dvd_files) - - def remove_remote_vm(self, server, vm_name): - conn = wmi.WMI(moniker='//' + server + '/root/virtualization') - conn_cimv2 = wmi.WMI(moniker='//' + server + '/root/cimv2') - self._remove_vm(vm_name, conn, conn_cimv2) - - def remove_vm(self, vm_name): - self._remove_vm(vm_name, self._conn, self._conn_cimv2) - - def _remove_vm(self, vm_name, conn, conn_cimv2): - vm = self._get_vm(vm_name, conn) - vs_man_svc = conn.Msvm_VirtualSystemManagementService()[0] - #Stop the VM first. - self._set_vm_state(conn, vm_name, 3) - - (disk_files, volume_drives, dvd_files) = self._get_vm_disks(conn, - vm_name) - - (job, ret_val) = vs_man_svc.DestroyVirtualSystem(vm.path_()) - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._check_job_status(job) - elif ret_val == 0: - success = True - if not success: - raise Exception(_('Failed to destroy vm %s') % vm_name) - - #Delete associated vhd disk files. - for disk in disk_files + dvd_files: - vhd_file = conn_cimv2.query( - "Select * from CIM_DataFile where Name = '" + - disk.replace("'", "''") + "'")[0] - vhd_file.Delete() - - def _get_target_iqn(self, volume_id): - return 'iqn.2010-10.org.openstack:volume-' + volume_id - - def logout_iscsi_volume_sessions(self, volume_id): - target_iqn = self._get_target_iqn(volume_id) - if (self.iscsi_volume_sessions_exist(volume_id)): - self._volumeutils.logout_storage_target(target_iqn) - - def iscsi_volume_sessions_exist(self, volume_id): - target_iqn = self._get_target_iqn(volume_id) - return len(self._conn_wmi.query( - "SELECT * FROM MSiSCSIInitiator_SessionClass \ - WHERE TargetName='" + target_iqn + "'")) > 0 - - def get_vm_count(self): - return len(self._conn.query( - "Select * from Msvm_ComputerSystem where Description " - "<> 'Microsoft Hosting Computer System'")) - - def get_vm_snapshots_count(self, vm_name): - return len(self._conn.query( - "Select * from Msvm_VirtualSystemSettingData where \ - SettingType = 5 and SystemName = '" + vm_name + "'")) - - def get_vhd_parent_path(self, vhd_path): - - image_man_svc = self._conn.Msvm_ImageManagementService()[0] - - (vhd_info, job_path, ret_val) = \ - image_man_svc.GetVirtualHardDiskInfo(vhd_path) - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._check_job_status(job_path) - else: - success = (ret_val == 0) - if not success: - raise Exception(_("Failed to get info for disk %s") % - (vhd_path)) - - base_disk_path = None - et = ElementTree.fromstring(vhd_info) - for item in et.findall("PROPERTY"): - if item.attrib["NAME"] == "ParentPath": - base_disk_path = item.find("VALUE").text - break - - return base_disk_path diff --git a/nova/tests/hyperv/mockproxy.py b/nova/tests/hyperv/mockproxy.py deleted file mode 100644 index 513422c13..000000000 --- a/nova/tests/hyperv/mockproxy.py +++ /dev/null @@ -1,272 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 Cloudbase Solutions Srl -# -# 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 - -""" -Classes for dynamic generation of mock objects. -""" - -import inspect - - -def serialize_obj(obj): - if isinstance(obj, float): - val = str(round(obj, 10)) - elif isinstance(obj, dict): - d = {} - for k1, v1 in obj.items(): - d[k1] = serialize_obj(v1) - val = str(d) - elif isinstance(obj, list): - l1 = [] - for i1 in obj: - l1.append(serialize_obj(i1)) - val = str(l1) - elif isinstance(obj, tuple): - l1 = () - for i1 in obj: - l1 = l1 + (serialize_obj(i1),) - val = str(l1) - else: - if isinstance(obj, str) or isinstance(obj, unicode): - val = obj - elif hasattr(obj, '__str__') and inspect.ismethod(obj.__str__): - val = str(obj) - else: - val = str(type(obj)) - return val - - -def serialize_args(*args, **kwargs): - """Workaround for float string conversion issues in Python 2.6.""" - return serialize_obj((args, kwargs)) - - -class MockException(Exception): - def __init__(self, message): - super(MockException, self).__init__(message) - - -class Mock(object): - def _get_next_value(self, name): - c = self._access_count.get(name) - if c is None: - c = 0 - else: - c = c + 1 - self._access_count[name] = c - - try: - value = self._values[name][c] - except IndexError as ex: - raise MockException(_('Couldn\'t find invocation num. %(c)d ' - 'of attribute "%(name)s"') % locals()) - return value - - def _get_next_ret_value(self, name, params): - d = self._access_count.get(name) - if d is None: - d = {} - self._access_count[name] = d - c = d.get(params) - if c is None: - c = 0 - else: - c = c + 1 - d[params] = c - - try: - m = self._values[name] - except KeyError as ex: - raise MockException(_('Couldn\'t find attribute "%s"') % (name)) - - try: - value = m[params][c] - except KeyError as ex: - raise MockException(_('Couldn\'t find attribute "%(name)s" ' - 'with arguments "%(params)s"') % locals()) - except IndexError as ex: - raise MockException(_('Couldn\'t find invocation num. %(c)d ' - 'of attribute "%(name)s" with arguments "%(params)s"') - % locals()) - - return value - - def __init__(self, values): - self._values = values - self._access_count = {} - - def has_values(self): - return len(self._values) > 0 - - def __getattr__(self, name): - if name.startswith('__') and name.endswith('__'): - return object.__getattribute__(self, name) - else: - try: - isdict = isinstance(self._values[name], dict) - except KeyError as ex: - raise MockException(_('Couldn\'t find attribute "%s"') - % (name)) - - if isdict: - def newfunc(*args, **kwargs): - params = serialize_args(args, kwargs) - return self._get_next_ret_value(name, params) - return newfunc - else: - return self._get_next_value(name) - - def __str__(self): - return self._get_next_value('__str__') - - def __iter__(self): - return getattr(self._get_next_value('__iter__'), '__iter__')() - - def __len__(self): - return self._get_next_value('__len__') - - def __getitem__(self, key): - return self._get_next_ret_value('__getitem__', str(key)) - - def __call__(self, *args, **kwargs): - params = serialize_args(args, kwargs) - return self._get_next_ret_value('__call__', params) - - -class MockProxy(object): - def __init__(self, wrapped): - self._wrapped = wrapped - self._recorded_values = {} - - def _get_proxy_object(self, obj): - if hasattr(obj, '__dict__') or isinstance(obj, tuple) or \ - isinstance(obj, list) or isinstance(obj, dict): - p = MockProxy(obj) - else: - p = obj - return p - - def __getattr__(self, name): - if name in ['_wrapped']: - return object.__getattribute__(self, name) - else: - attr = getattr(self._wrapped, name) - if inspect.isfunction(attr) or inspect.ismethod(attr) or \ - inspect.isbuiltin(attr): - def newfunc(*args, **kwargs): - result = attr(*args, **kwargs) - p = self._get_proxy_object(result) - params = serialize_args(args, kwargs) - self._add_recorded_ret_value(name, params, p) - return p - return newfunc - elif hasattr(attr, '__dict__') or (hasattr(attr, '__getitem__') - and not (isinstance(attr, str) or isinstance(attr, unicode))): - p = MockProxy(attr) - else: - p = attr - self._add_recorded_value(name, p) - return p - - def __setattr__(self, name, value): - if name in ['_wrapped', '_recorded_values']: - object.__setattr__(self, name, value) - else: - setattr(self._wrapped, name, value) - - def _add_recorded_ret_value(self, name, params, val): - d = self._recorded_values.get(name) - if d is None: - d = {} - self._recorded_values[name] = d - l = d.get(params) - if l is None: - l = [] - d[params] = l - l.append(val) - - def _add_recorded_value(self, name, val): - if not name in self._recorded_values: - self._recorded_values[name] = [] - self._recorded_values[name].append(val) - - def get_mock(self): - values = {} - for k, v in self._recorded_values.items(): - if isinstance(v, dict): - d = {} - values[k] = d - for k1, v1 in v.items(): - l = [] - d[k1] = l - for i1 in v1: - if isinstance(i1, MockProxy): - l.append(i1.get_mock()) - else: - l.append(i1) - else: - l = [] - values[k] = l - for i in v: - if isinstance(i, MockProxy): - l.append(i.get_mock()) - elif isinstance(i, dict): - d = {} - for k1, v1 in v.items(): - if isinstance(v1, MockProxy): - d[k1] = v1.get_mock() - else: - d[k1] = v1 - l.append(d) - elif isinstance(i, list): - l1 = [] - for i1 in i: - if isinstance(i1, MockProxy): - l1.append(i1.get_mock()) - else: - l1.append(i1) - l.append(l1) - else: - l.append(i) - return Mock(values) - - def __str__(self): - s = str(self._wrapped) - self._add_recorded_value('__str__', s) - return s - - def __len__(self): - l = len(self._wrapped) - self._add_recorded_value('__len__', l) - return l - - def __iter__(self): - it = [] - for i in self._wrapped: - it.append(self._get_proxy_object(i)) - self._add_recorded_value('__iter__', it) - return iter(it) - - def __getitem__(self, key): - p = self._get_proxy_object(self._wrapped[key]) - self._add_recorded_ret_value('__getitem__', str(key), p) - return p - - def __call__(self, *args, **kwargs): - c = self._wrapped(*args, **kwargs) - p = self._get_proxy_object(c) - params = serialize_args(args, kwargs) - self._add_recorded_ret_value('__call__', params, p) - return p diff --git a/nova/tests/hyperv/stubs/README.rst b/nova/tests/hyperv/stubs/README.rst deleted file mode 100644 index 150fd3ad1..000000000 --- a/nova/tests/hyperv/stubs/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -Files with extension p.gz are compressed pickle files containing serialized -mocks used during unit testing diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_os.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_os.p.gz Binary files differdeleted file mode 100644 index c65832c57..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_subprocess.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_subprocess.p.gz Binary files differdeleted file mode 100644 index 7076c4868..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_subprocess.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_time.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_time.p.gz Binary files differdeleted file mode 100644 index c251f9d6c..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_uuid.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_uuid.p.gz Binary files differdeleted file mode 100644 index cac08e3d0..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_os.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_os.p.gz Binary files differdeleted file mode 100644 index d6e624bb0..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_subprocess.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_subprocess.p.gz Binary files differdeleted file mode 100644 index bb18f7453..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_subprocess.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_time.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_time.p.gz Binary files differdeleted file mode 100644 index a5f592a74..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_uuid.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_uuid.p.gz Binary files differdeleted file mode 100644 index 4bebe0e72..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_wmi.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_wmi.p.gz Binary files differdeleted file mode 100644 index 29a610f36..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_wmi.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_wmi.p.gz Binary files differdeleted file mode 100644 index ca92ece00..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_attach_volume_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_subprocess.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_subprocess.p.gz Binary files differdeleted file mode 100644 index 58269455d..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_subprocess.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_time.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_time.p.gz Binary files differdeleted file mode 100644 index 97cd7e62b..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_uuid.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_uuid.p.gz Binary files differdeleted file mode 100644 index 708197430..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_wmi.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_wmi.p.gz Binary files differdeleted file mode 100644 index d5eb4d746..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_boot_from_volume_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_os.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_os.p.gz Binary files differdeleted file mode 100644 index d8c63d8ad..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_subprocess.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_subprocess.p.gz Binary files differdeleted file mode 100644 index d0b27d201..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_subprocess.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_time.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_time.p.gz Binary files differdeleted file mode 100644 index 657379cec..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_uuid.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_uuid.p.gz Binary files differdeleted file mode 100644 index 8bf58ef5c..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_wmi.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_wmi.p.gz Binary files differdeleted file mode 100644 index c20281811..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_detach_volume_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_os.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_os.p.gz Binary files differdeleted file mode 100644 index a198af844..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_time.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_time.p.gz Binary files differdeleted file mode 100644 index 749eabe40..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_uuid.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_uuid.p.gz Binary files differdeleted file mode 100644 index c40e6f995..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_os.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_os.p.gz Binary files differdeleted file mode 100644 index c67dc9271..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_time.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_time.p.gz Binary files differdeleted file mode 100644 index 0d671fc18..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_uuid.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_uuid.p.gz Binary files differdeleted file mode 100644 index 66583beb1..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_wmi.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_wmi.p.gz Binary files differdeleted file mode 100644 index efdef819f..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_wmi.p.gz b/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_wmi.p.gz Binary files differdeleted file mode 100644 index 5edd6f147..000000000 --- a/nova/tests/hyperv/stubs/nova.tests.test_hypervapi.HyperVAPITestCase.test_live_migration_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_nova.utils.p.gz Binary files differdeleted file mode 100644 index f968e2af5..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index bd5ced9f8..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_os.p.gz Binary files differdeleted file mode 100644 index a48a21ca9..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_shutil.p.gz Binary files differdeleted file mode 100644 index c662b602a..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_subprocess.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_subprocess.p.gz Binary files differdeleted file mode 100644 index 6a692b3d8..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_subprocess.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_time.p.gz Binary files differdeleted file mode 100644 index f2ae56be1..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_uuid.p.gz Binary files differdeleted file mode 100644 index 2d24523aa..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_nova.utils.p.gz Binary files differdeleted file mode 100644 index aca0d6f0c..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index bbeec53df..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_os.p.gz Binary files differdeleted file mode 100644 index 3bf9bd13a..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_shutil.p.gz Binary files differdeleted file mode 100644 index 62e3fa329..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_subprocess.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_subprocess.p.gz Binary files differdeleted file mode 100644 index 36970348a..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_subprocess.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_time.p.gz Binary files differdeleted file mode 100644 index 8db997abf..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_uuid.p.gz Binary files differdeleted file mode 100644 index 73f90ac2b..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_wmi.p.gz Binary files differdeleted file mode 100644 index 3ae9a6f46..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_with_target_connection_failure_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_wmi.p.gz Binary files differdeleted file mode 100644 index 5b851f9b7..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_attach_volume_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_nova.utils.p.gz Binary files differdeleted file mode 100644 index 7a1c47449..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 48583265e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_shutil.p.gz Binary files differdeleted file mode 100644 index 90d6a2ca6..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_subprocess.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_subprocess.p.gz Binary files differdeleted file mode 100644 index 3b17cc74f..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_subprocess.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_time.p.gz Binary files differdeleted file mode 100644 index 162f52457..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_uuid.p.gz Binary files differdeleted file mode 100644 index f88f8bc86..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_wmi.p.gz Binary files differdeleted file mode 100644 index f671dc247..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_boot_from_volume_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_nova.utils.p.gz Binary files differdeleted file mode 100644 index 37892d051..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 9aec45796..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_os.p.gz Binary files differdeleted file mode 100644 index ffc21536e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_shutil.p.gz Binary files differdeleted file mode 100644 index b47c49202..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_time.p.gz Binary files differdeleted file mode 100644 index 78e4292b6..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_uuid.p.gz Binary files differdeleted file mode 100644 index 5bc7602a8..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_wmi.p.gz Binary files differdeleted file mode 100644 index 9ba025e55..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_destroy_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_nova.utils.p.gz Binary files differdeleted file mode 100644 index 3341bca28..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 56cb9d103..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_os.p.gz Binary files differdeleted file mode 100644 index 81205e04d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_shutil.p.gz Binary files differdeleted file mode 100644 index 9d1311341..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_subprocess.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_subprocess.p.gz Binary files differdeleted file mode 100644 index a151a99b4..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_subprocess.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_time.p.gz Binary files differdeleted file mode 100644 index b1d0b0f3a..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_uuid.p.gz Binary files differdeleted file mode 100644 index c2985c424..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_wmi.p.gz Binary files differdeleted file mode 100644 index 2c4901c9f..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_detach_volume_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_ctypes.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_ctypes.p.gz Binary files differdeleted file mode 100644 index 2481a7b3e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_ctypes.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_multiprocessing.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_multiprocessing.p.gz Binary files differdeleted file mode 100644 index 61cbc1854..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_multiprocessing.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_os.p.gz Binary files differdeleted file mode 100644 index 09b86b24e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_shutil.p.gz Binary files differdeleted file mode 100644 index ba89bfd7e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_wmi.p.gz Binary files differdeleted file mode 100644 index cfce8c10a..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_available_resource_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_host_stats_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_host_stats_os.p.gz Binary files differdeleted file mode 100644 index 6092f36ab..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_host_stats_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_host_stats_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_host_stats_shutil.p.gz Binary files differdeleted file mode 100644 index 010c07e56..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_host_stats_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_host_stats_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_host_stats_wmi.p.gz Binary files differdeleted file mode 100644 index 9d3adec48..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_host_stats_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_nova.utils.p.gz Binary files differdeleted file mode 100644 index 995dde1b5..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 12d18d12e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_os.p.gz Binary files differdeleted file mode 100644 index 64c756ffa..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_shutil.p.gz Binary files differdeleted file mode 100644 index d2cefdc37..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_time.p.gz Binary files differdeleted file mode 100644 index 9fdef3b90..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_uuid.p.gz Binary files differdeleted file mode 100644 index c34d2308b..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_wmi.p.gz Binary files differdeleted file mode 100644 index 36a342e7c..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_get_info_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_detail_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_detail_shutil.p.gz Binary files differdeleted file mode 100644 index 3ab35a29f..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_detail_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_detail_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_detail_wmi.p.gz Binary files differdeleted file mode 100644 index 411c0ed07..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_detail_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_shutil.p.gz Binary files differdeleted file mode 100644 index 1af20acde..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_wmi.p.gz Binary files differdeleted file mode 100644 index d84122d77..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_list_instances_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_nova.utils.p.gz Binary files differdeleted file mode 100644 index d650f40a5..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index a03d442a4..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_os.p.gz Binary files differdeleted file mode 100644 index 993d9bb2d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_shutil.p.gz Binary files differdeleted file mode 100644 index 6693c2ce9..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_time.p.gz Binary files differdeleted file mode 100644 index 07898dd55..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_uuid.p.gz Binary files differdeleted file mode 100644 index 56e583449..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_nova.utils.p.gz Binary files differdeleted file mode 100644 index 5d4c0e111..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index cb52cb974..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_os.p.gz Binary files differdeleted file mode 100644 index 8b2ff15f3..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_shutil.p.gz Binary files differdeleted file mode 100644 index aee1fb14d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_time.p.gz Binary files differdeleted file mode 100644 index f926d206f..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_uuid.p.gz Binary files differdeleted file mode 100644 index 483b23d53..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_wmi.p.gz Binary files differdeleted file mode 100644 index 14d61039f..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_with_target_failure_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_wmi.p.gz Binary files differdeleted file mode 100644 index daecf0156..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_live_migration_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_nova.utils.p.gz Binary files differdeleted file mode 100644 index 548b88148..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 8545a1833..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_os.p.gz Binary files differdeleted file mode 100644 index c1daf3db9..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_shutil.p.gz Binary files differdeleted file mode 100644 index 750d68d29..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_time.p.gz Binary files differdeleted file mode 100644 index 6e91b72a2..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_uuid.p.gz Binary files differdeleted file mode 100644 index 2d0349d96..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_wmi.p.gz Binary files differdeleted file mode 100644 index 6b9ef360a..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_already_paused_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_nova.utils.p.gz Binary files differdeleted file mode 100644 index 3e582226f..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 723966011..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_os.p.gz Binary files differdeleted file mode 100644 index 29b73888b..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_shutil.p.gz Binary files differdeleted file mode 100644 index 595124af2..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_time.p.gz Binary files differdeleted file mode 100644 index 03d53be74..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_uuid.p.gz Binary files differdeleted file mode 100644 index 2a0663e6f..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_wmi.p.gz Binary files differdeleted file mode 100644 index e651c02fc..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pause_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_nova.utils.p.gz Binary files differdeleted file mode 100644 index a50935649..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 4b07271c1..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_os.p.gz Binary files differdeleted file mode 100644 index f62298ed7..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_shutil.p.gz Binary files differdeleted file mode 100644 index 12a164f23..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_time.p.gz Binary files differdeleted file mode 100644 index 33f1862e6..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_uuid.p.gz Binary files differdeleted file mode 100644 index 80853eea4..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_wmi.p.gz Binary files differdeleted file mode 100644 index 5cebe527d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_already_powered_off_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_nova.utils.p.gz Binary files differdeleted file mode 100644 index d0c431b9d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index d231f803d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_os.p.gz Binary files differdeleted file mode 100644 index 25fe5f3ff..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_shutil.p.gz Binary files differdeleted file mode 100644 index 8be80ba56..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_time.p.gz Binary files differdeleted file mode 100644 index 51b6f2df8..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_uuid.p.gz Binary files differdeleted file mode 100644 index 97812405e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_wmi.p.gz Binary files differdeleted file mode 100644 index 20b2e021e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_off_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_nova.utils.p.gz Binary files differdeleted file mode 100644 index c32f9ecd2..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 672376a0e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_os.p.gz Binary files differdeleted file mode 100644 index aa6f4ca8a..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_shutil.p.gz Binary files differdeleted file mode 100644 index 00f5770a7..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_time.p.gz Binary files differdeleted file mode 100644 index 1631f35df..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_uuid.p.gz Binary files differdeleted file mode 100644 index ec28756ad..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_wmi.p.gz Binary files differdeleted file mode 100644 index 699ccde76..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_already_running_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_nova.utils.p.gz Binary files differdeleted file mode 100644 index 2b99fb9cd..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index a43bfeb7e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_os.p.gz Binary files differdeleted file mode 100644 index 57e74e618..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_shutil.p.gz Binary files differdeleted file mode 100644 index 273364d95..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_time.p.gz Binary files differdeleted file mode 100644 index 732a0f2e6..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_uuid.p.gz Binary files differdeleted file mode 100644 index d6cb32559..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_wmi.p.gz Binary files differdeleted file mode 100644 index e44197039..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_power_on_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_nova.utils.p.gz Binary files differdeleted file mode 100644 index 456af2816..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_os.p.gz Binary files differdeleted file mode 100644 index 93568dcef..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_shutil.p.gz Binary files differdeleted file mode 100644 index 6a4b90850..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_time.p.gz Binary files differdeleted file mode 100644 index fc816320f..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_uuid.p.gz Binary files differdeleted file mode 100644 index 83cf9c071..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_wmi.p.gz Binary files differdeleted file mode 100644 index 93977743f..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_cow_image_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_nova.utils.p.gz Binary files differdeleted file mode 100644 index f58f80a79..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_shutil.p.gz Binary files differdeleted file mode 100644 index 18a8aed13..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_uuid.p.gz Binary files differdeleted file mode 100644 index 4225a72b0..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_wmi.p.gz Binary files differdeleted file mode 100644 index 363c431d4..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_pre_live_migration_no_cow_image_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_nova.utils.p.gz Binary files differdeleted file mode 100644 index 8761703dc..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index fc907ed31..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_os.p.gz Binary files differdeleted file mode 100644 index 0eca8e6ce..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_shutil.p.gz Binary files differdeleted file mode 100644 index 0886c942d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_time.p.gz Binary files differdeleted file mode 100644 index d0fb77bd1..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_uuid.p.gz Binary files differdeleted file mode 100644 index df3961276..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_wmi.p.gz Binary files differdeleted file mode 100644 index 4df451154..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_reboot_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_nova.utils.p.gz Binary files differdeleted file mode 100644 index 59724b43d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 4b3711ec0..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_os.p.gz Binary files differdeleted file mode 100644 index 2f9a5de9c..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_shutil.p.gz Binary files differdeleted file mode 100644 index 8ffa516c0..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_time.p.gz Binary files differdeleted file mode 100644 index 6aade88c6..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_uuid.p.gz Binary files differdeleted file mode 100644 index 276c06397..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_wmi.p.gz Binary files differdeleted file mode 100644 index 77a1650d4..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_already_running_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_nova.utils.p.gz Binary files differdeleted file mode 100644 index ce19ed290..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index b2dadcd4d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_os.p.gz Binary files differdeleted file mode 100644 index aa378fedd..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_shutil.p.gz Binary files differdeleted file mode 100644 index 333a27b89..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_time.p.gz Binary files differdeleted file mode 100644 index 16ca553f6..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_uuid.p.gz Binary files differdeleted file mode 100644 index 8cf3b564e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_wmi.p.gz Binary files differdeleted file mode 100644 index 0a2c8513b..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_resume_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_nova.utils.p.gz Binary files differdeleted file mode 100644 index ae42d7734..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 4fec34d08..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_os.p.gz Binary files differdeleted file mode 100644 index 74e8e95a6..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_shutil.p.gz Binary files differdeleted file mode 100644 index da0528797..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_time.p.gz Binary files differdeleted file mode 100644 index 63f02bc75..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_uuid.p.gz Binary files differdeleted file mode 100644 index c014d5003..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_nova.utils.p.gz Binary files differdeleted file mode 100644 index 592658541..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 892f3c346..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_os.p.gz Binary files differdeleted file mode 100644 index 9996339f5..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_shutil.p.gz Binary files differdeleted file mode 100644 index 409ee5ef7..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_time.p.gz Binary files differdeleted file mode 100644 index 9e799c196..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_uuid.p.gz Binary files differdeleted file mode 100644 index 848024366..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_wmi.p.gz Binary files differdeleted file mode 100644 index 687952c4c..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_with_update_failure_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_wmi.p.gz Binary files differdeleted file mode 100644 index 57988a6b6..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_snapshot_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_nova.utils.p.gz Binary files differdeleted file mode 100644 index 303a47019..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index c211622e1..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_os.p.gz Binary files differdeleted file mode 100644 index 5e5303cbc..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_shutil.p.gz Binary files differdeleted file mode 100644 index 1bcbd48f3..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_time.p.gz Binary files differdeleted file mode 100644 index ae557d73d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_uuid.p.gz Binary files differdeleted file mode 100644 index 90ebff4e7..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_wmi.p.gz Binary files differdeleted file mode 100644 index beccc2737..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_cdrom_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_nova.utils.p.gz Binary files differdeleted file mode 100644 index af5082ab6..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 837d81b70..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_os.p.gz Binary files differdeleted file mode 100644 index ecea62a01..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_shutil.p.gz Binary files differdeleted file mode 100644 index 283cd7fdd..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_time.p.gz Binary files differdeleted file mode 100644 index 44dcc89ae..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_uuid.p.gz Binary files differdeleted file mode 100644 index 5c520c768..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_wmi.p.gz Binary files differdeleted file mode 100644 index aec53305d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_config_drive_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_nova.utils.p.gz Binary files differdeleted file mode 100644 index a16c88e54..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index d9c4e9c82..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_os.p.gz Binary files differdeleted file mode 100644 index 94aafb39a..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_shutil.p.gz Binary files differdeleted file mode 100644 index e0ad00bf6..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_time.p.gz Binary files differdeleted file mode 100644 index 00f7839ba..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_uuid.p.gz Binary files differdeleted file mode 100644 index 77422d3f5..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_wmi.p.gz Binary files differdeleted file mode 100644 index 414194a9d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_cow_image_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_nova.utils.p.gz Binary files differdeleted file mode 100644 index b1e825822..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 1e3d89fea..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_os.p.gz Binary files differdeleted file mode 100644 index 627c78d7e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_shutil.p.gz Binary files differdeleted file mode 100644 index e577cdb5e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_time.p.gz Binary files differdeleted file mode 100644 index 72962fc52..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_uuid.p.gz Binary files differdeleted file mode 100644 index 5d1351a14..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_wmi.p.gz Binary files differdeleted file mode 100644 index eb0ed7241..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_config_drive_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_nova.utils.p.gz Binary files differdeleted file mode 100644 index c65264688..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index ca40d6413..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_os.p.gz Binary files differdeleted file mode 100644 index 1d8081a3e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_shutil.p.gz Binary files differdeleted file mode 100644 index e03633b90..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_time.p.gz Binary files differdeleted file mode 100644 index 00c56dacc..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_uuid.p.gz Binary files differdeleted file mode 100644 index 7381c3cc6..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_wmi.p.gz Binary files differdeleted file mode 100644 index 115ed1dd5..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_cow_image_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.utils.p.gz Binary files differdeleted file mode 100644 index df40b08c0..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index b51766f75..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_os.p.gz Binary files differdeleted file mode 100644 index 092a1f933..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_shutil.p.gz Binary files differdeleted file mode 100644 index 77f333c00..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_time.p.gz Binary files differdeleted file mode 100644 index 8ab166a60..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_uuid.p.gz Binary files differdeleted file mode 100644 index 97e96be17..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_wmi.p.gz Binary files differdeleted file mode 100644 index 728464ca9..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_spawn_no_vswitch_exception_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_nova.utils.p.gz Binary files differdeleted file mode 100644 index 4aa6d171a..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index df063a22e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_os.p.gz Binary files differdeleted file mode 100644 index b30363fcc..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_shutil.p.gz Binary files differdeleted file mode 100644 index 1681d9947..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_time.p.gz Binary files differdeleted file mode 100644 index 4469fd90e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_uuid.p.gz Binary files differdeleted file mode 100644 index f94f2ebb9..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_wmi.p.gz Binary files differdeleted file mode 100644 index 03afe2235..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_already_suspended_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_nova.utils.p.gz Binary files differdeleted file mode 100644 index 2f95f62bf..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 2e7ab44ad..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_os.p.gz Binary files differdeleted file mode 100644 index eb514d086..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_shutil.p.gz Binary files differdeleted file mode 100644 index 810c9e14d..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_time.p.gz Binary files differdeleted file mode 100644 index 2eb2a8372..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_uuid.p.gz Binary files differdeleted file mode 100644 index 67311757a..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_wmi.p.gz Binary files differdeleted file mode 100644 index 0779125b3..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_suspend_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_nova.utils.p.gz Binary files differdeleted file mode 100644 index 7e6cc708e..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index 0ce4bbf63..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_os.p.gz Binary files differdeleted file mode 100644 index 9068792c7..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_shutil.p.gz Binary files differdeleted file mode 100644 index 9b06cb884..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_time.p.gz Binary files differdeleted file mode 100644 index e91e6c965..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_uuid.p.gz Binary files differdeleted file mode 100644 index 271ded270..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_wmi.p.gz Binary files differdeleted file mode 100644 index 253bdfc82..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_already_running_wmi.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_nova.utils.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_nova.utils.p.gz Binary files differdeleted file mode 100644 index 20486b189..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_nova.utils.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_nova.virt.configdrive.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_nova.virt.configdrive.p.gz Binary files differdeleted file mode 100644 index be92217ed..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_nova.virt.configdrive.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_os.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_os.p.gz Binary files differdeleted file mode 100644 index 36059e753..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_os.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_shutil.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_shutil.p.gz Binary files differdeleted file mode 100644 index aea394e9f..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_shutil.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_time.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_time.p.gz Binary files differdeleted file mode 100644 index 4850d3cda..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_time.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_uuid.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_uuid.p.gz Binary files differdeleted file mode 100644 index 99bf1806c..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_uuid.p.gz +++ /dev/null diff --git a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_wmi.p.gz b/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_wmi.p.gz Binary files differdeleted file mode 100644 index 87b571e4a..000000000 --- a/nova/tests/hyperv/stubs/test_hypervapi.HyperVAPITestCase.test_unpause_wmi.p.gz +++ /dev/null diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index 7c13796a6..fb26fa4f1 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -17,7 +17,10 @@ import datetime +import filecmp +import os import random +import tempfile import time import glanceclient.exc @@ -336,7 +339,6 @@ class TestGlanceImageService(test.TestCase): def test_update(self): fixture = self._make_fixture(name='test image') image = self.service.create(self.context, fixture) - print image image_id = image['id'] fixture['name'] = 'new image name' self.service.update(self.context, image_id, fixture) @@ -468,6 +470,40 @@ class TestGlanceImageService(test.TestCase): self.flags(glance_num_retries=1) service.download(self.context, image_id, writer) + def test_download_file_url(self): + class MyGlanceStubClient(glance_stubs.StubGlanceClient): + """A client that returns a file url.""" + + (outfd, s_tmpfname) = tempfile.mkstemp(prefix='directURLsrc') + outf = os.fdopen(outfd, 'w') + inf = open('/dev/urandom', 'r') + for i in range(10): + _data = inf.read(1024) + outf.write(_data) + outf.close() + + def get(self, image_id): + return type('GlanceTestDirectUrlMeta', (object,), + {'direct_url': 'file://%s' + self.s_tmpfname}) + + client = MyGlanceStubClient() + (outfd, tmpfname) = tempfile.mkstemp(prefix='directURLdst') + writer = os.fdopen(outfd, 'w') + + service = self._create_image_service(client) + image_id = 1 # doesn't matter + + self.flags(allowed_direct_url_schemes=['file']) + service.download(self.context, image_id, writer) + writer.close() + + # compare the two files + rc = filecmp.cmp(tmpfname, client.s_tmpfname) + self.assertTrue(rc, "The file %s and %s should be the same" % + (tmpfname, client.s_tmpfname)) + os.remove(client.s_tmpfname) + os.remove(tmpfname) + def test_client_forbidden_converts_to_imagenotauthed(self): class MyGlanceStubClient(glance_stubs.StubGlanceClient): """A client that raises a Forbidden exception.""" 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/client.py b/nova/tests/integrated/api/client.py index a072b3128..958a5500b 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -155,7 +155,7 @@ class TestOpenStackClient(object): LOG.debug(_("%(relative_uri)s => code %(http_status)s") % locals()) if check_response_status: - if not http_status in check_response_status: + if http_status not in check_response_status: if http_status == 404: raise OpenStackApiNotFoundException(response=response) elif http_status == 401: 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-consoles/get-spice-console-post-req.json.tpl b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.json.tpl new file mode 100644 index 000000000..d04f7c7ae --- /dev/null +++ b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.json.tpl @@ -0,0 +1,5 @@ +{ + "os-getSPICEConsole": { + "type": "spice-html5" + } +} diff --git a/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.xml.tpl b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.xml.tpl new file mode 100644 index 000000000..c8cd2df9f --- /dev/null +++ b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-req.xml.tpl @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<os-getSPICEConsole> + <type>spice-html5</type> +</os-getSPICEConsole> diff --git a/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.json.tpl new file mode 100644 index 000000000..20e260e9e --- /dev/null +++ b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.json.tpl @@ -0,0 +1,6 @@ +{ + "console": { + "type": "spice-html5", + "url":"%(url)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.xml.tpl new file mode 100644 index 000000000..77e35ae5b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-consoles/get-spice-console-post-resp.xml.tpl @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='UTF-8'?> +<console> + <type>spice-html5</type> + <url>%(url)s</url> +</console> diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json.tpl new file mode 100644 index 000000000..dd858e76c --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json.tpl @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "%(value1)s", + "key2": "%(value2)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml.tpl new file mode 100644 index 000000000..c94595cad --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml.tpl @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<extra_specs> + <key1>%(value1)s</key1> + <key2>%(value2)s</key2> +</extra_specs> diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json.tpl new file mode 100644 index 000000000..dd858e76c --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json.tpl @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "%(value1)s", + "key2": "%(value2)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml.tpl new file mode 100644 index 000000000..1008b5bb0 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml.tpl @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_specs> + <key2>%(value2)s</key2> + <key1>%(value1)s</key1> +</extra_specs> diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json.tpl new file mode 100644 index 000000000..adfa77008 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json.tpl @@ -0,0 +1,3 @@ +{ + "key1": "%(value1)s" +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml.tpl new file mode 100644 index 000000000..e3de59a34 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml.tpl @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_spec key="key1">%(value1)s</extra_spec> diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json.tpl new file mode 100644 index 000000000..dd858e76c --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json.tpl @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "%(value1)s", + "key2": "%(value2)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml.tpl new file mode 100644 index 000000000..1008b5bb0 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml.tpl @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_specs> + <key2>%(value2)s</key2> + <key1>%(value1)s</key1> +</extra_specs> diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json.tpl new file mode 100644 index 000000000..adfa77008 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json.tpl @@ -0,0 +1,3 @@ +{ + "key1": "%(value1)s" +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml.tpl new file mode 100644 index 000000000..6421e5959 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml.tpl @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> + <key1>%(value1)s</key1> diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json.tpl new file mode 100644 index 000000000..adfa77008 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json.tpl @@ -0,0 +1,3 @@ +{ + "key1": "%(value1)s" +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml.tpl new file mode 100644 index 000000000..e3de59a34 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml.tpl @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_spec key="key1">%(value1)s</extra_spec> 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/api_samples/os-fping/fping-get-details-resp.json.tpl b/nova/tests/integrated/api_samples/os-fping/fping-get-details-resp.json.tpl new file mode 100644 index 000000000..f3b222c39 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-fping/fping-get-details-resp.json.tpl @@ -0,0 +1,7 @@ +{ + "server": { + "alive": false, + "id": "%(uuid)s", + "project_id": "openstack" + } +} diff --git a/nova/tests/integrated/api_samples/os-fping/fping-get-details-resp.xml.tpl b/nova/tests/integrated/api_samples/os-fping/fping-get-details-resp.xml.tpl new file mode 100644 index 000000000..758519b60 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-fping/fping-get-details-resp.xml.tpl @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='UTF-8'?> +<server> + <project_id>openstack</project_id> + <id>%(uuid)s</id> + <alive>False</alive> +</server> diff --git a/nova/tests/integrated/api_samples/os-fping/fping-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-fping/fping-get-resp.json.tpl new file mode 100644 index 000000000..b33e80668 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-fping/fping-get-resp.json.tpl @@ -0,0 +1,9 @@ +{ + "servers": [ + { + "alive": false, + "id": "%(uuid)s", + "project_id": "openstack" + } + ] +} diff --git a/nova/tests/integrated/api_samples/os-fping/fping-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-fping/fping-get-resp.xml.tpl new file mode 100644 index 000000000..290ad6ca6 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-fping/fping-get-resp.xml.tpl @@ -0,0 +1,8 @@ +<?xml version='1.0' encoding='UTF-8'?> +<servers> + <server> + <project_id>openstack</project_id> + <id>%(uuid)s</id> + <alive>False</alive> + </server> +</servers> diff --git a/nova/tests/integrated/api_samples/os-fping/server-post-req.json.tpl b/nova/tests/integrated/api_samples/os-fping/server-post-req.json.tpl new file mode 100644 index 000000000..d3916d1aa --- /dev/null +++ b/nova/tests/integrated/api_samples/os-fping/server-post-req.json.tpl @@ -0,0 +1,16 @@ +{ + "server" : { + "name" : "new-server-test", + "imageRef" : "%(host)s/openstack/images/%(image_id)s", + "flavorRef" : "%(host)s/openstack/flavors/1", + "metadata" : { + "My Server Name" : "Apache1" + }, + "personality" : [ + { + "path" : "/etc/banner.txt", + "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ] + } +} diff --git a/nova/tests/integrated/api_samples/os-fping/server-post-req.xml.tpl b/nova/tests/integrated/api_samples/os-fping/server-post-req.xml.tpl new file mode 100644 index 000000000..f92614984 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-fping/server-post-req.xml.tpl @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="%(host)s/openstack/images/%(image_id)s" flavorRef="%(host)s/openstack/flavors/1" name="new-server-test"> + <metadata> + <meta key="My Server Name">Apache1</meta> + </metadata> + <personality> + <file path="/etc/banner.txt"> + ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp + dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k + IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs + c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g + QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo + ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv + dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy + c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 + b25zLiINCg0KLVJpY2hhcmQgQmFjaA== + </file> + </personality> +</server> diff --git a/nova/tests/integrated/api_samples/os-fping/server-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-fping/server-post-resp.json.tpl new file mode 100644 index 000000000..d5f030c87 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-fping/server-post-resp.json.tpl @@ -0,0 +1,16 @@ +{ + "server": { + "adminPass": "%(password)s", + "id": "%(id)s", + "links": [ + { + "href": "%(host)s/v2/openstack/servers/%(uuid)s", + "rel": "self" + }, + { + "href": "%(host)s/openstack/servers/%(uuid)s", + "rel": "bookmark" + } + ] + } +} diff --git a/nova/tests/integrated/api_samples/os-fping/server-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-fping/server-post-resp.xml.tpl new file mode 100644 index 000000000..3bb13e69b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-fping/server-post-resp.xml.tpl @@ -0,0 +1,6 @@ +<?xml version='1.0' encoding='UTF-8'?> +<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="%(id)s" adminPass="%(password)s"> + <metadata/> + <atom:link href="%(host)s/v2/openstack/servers/%(uuid)s" rel="self"/> + <atom:link href="%(host)s/openstack/servers/%(uuid)s" rel="bookmark"/> +</server> diff --git a/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.json.tpl new file mode 100644 index 000000000..6974f360f --- /dev/null +++ b/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.json.tpl @@ -0,0 +1,17 @@ +{ + "instance_usage_audit_logs": { + "hosts_not_run": [ + "%(hostid)s" + ], + "log": {}, + "num_hosts": 1, + "num_hosts_done": 0, + "num_hosts_not_run": 1, + "num_hosts_running": 0, + "overall_status": "0 of 1 hosts done. 0 errors.", + "period_beginning": "%(timestamp)s", + "period_ending": "%(timestamp)s", + "total_errors": 0, + "total_instances": 0 + } +} diff --git a/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.xml.tpl new file mode 100644 index 000000000..4eafa8b4a --- /dev/null +++ b/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-index-get-resp.xml.tpl @@ -0,0 +1,16 @@ +<?xml version='1.0' encoding='UTF-8'?> +<instance_usage_audit_logs> + <total_errors>0</total_errors> + <total_instances>0</total_instances> + <log/> + <num_hosts_running>0</num_hosts_running> + <num_hosts_done>0</num_hosts_done> + <num_hosts_not_run>1</num_hosts_not_run> + <hosts_not_run> + <item>%(hostid)s</item> + </hosts_not_run> + <overall_status>0 of 1 hosts done. 0 errors.</overall_status> + <period_ending>%(timestamp)s</period_ending> + <period_beginning>%(timestamp)s</period_beginning> + <num_hosts>1</num_hosts> +</instance_usage_audit_logs> diff --git a/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.json.tpl new file mode 100644 index 000000000..eda952304 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.json.tpl @@ -0,0 +1,17 @@ +{ + "instance_usage_audit_log": { + "hosts_not_run": [ + "%(hostid)s" + ], + "log": {}, + "num_hosts": 1, + "num_hosts_done": 0, + "num_hosts_not_run": 1, + "num_hosts_running": 0, + "overall_status": "0 of 1 hosts done. 0 errors.", + "period_beginning": "%(timestamp)s", + "period_ending": "%(timestamp)s", + "total_errors": 0, + "total_instances": 0 + } +} diff --git a/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.xml.tpl new file mode 100644 index 000000000..1ef243292 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-instance_usage_audit_log/inst-usage-audit-log-show-get-resp.xml.tpl @@ -0,0 +1,16 @@ +<?xml version='1.0' encoding='UTF-8'?> +<instance_usage_audit_log> + <total_errors>0</total_errors> + <total_instances>0</total_instances> + <log/> + <num_hosts_running>0</num_hosts_running> + <num_hosts_done>0</num_hosts_done> + <num_hosts_not_run>1</num_hosts_not_run> + <hosts_not_run> + <item>%(hostid)s</item> + </hosts_not_run> + <overall_status>0 of 1 hosts done. 0 errors.</overall_status> + <period_ending>%(timestamp)s</period_ending> + <period_beginning>%(timestamp)s</period_beginning> + <num_hosts>1</num_hosts> +</instance_usage_audit_log> diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index f17dc025f..90e9a806e 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -58,7 +58,7 @@ def generate_new_element(items, prefix, numeric=False): candidate = prefix + generate_random_numeric(8) else: candidate = prefix + generate_random_alphanumeric(8) - if not candidate in items: + if candidate not in items: return candidate LOG.debug("Random collision on %s" % candidate) diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index 7c3157872..21a23d94e 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -22,13 +22,14 @@ 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 +from nova.api.openstack.compute.contrib import fping # 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,9 +44,12 @@ from nova.openstack.common import timeutils import nova.quota from nova.scheduler import driver from nova import test +from nova.tests.api.openstack.compute.contrib import test_fping +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 +from nova import utils CONF = cfg.CONF CONF.import_opt('allow_resize_to_same_host', 'nova.compute.api') @@ -171,23 +175,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 '[]<>?': @@ -282,7 +295,7 @@ class ApiSampleTestBase(integrated_helpers._IntegratedTestBase): # shouldn't be an issue for this case. 'timestamp': '\d{4}-[0,1]\d-[0-3]\d[ ,T]' '\d{2}:\d{2}:\d{2}' - '(Z|(\+|-)\d{2}:\d{2}|\.\d{6})', + '(Z|(\+|-)\d{2}:\d{2}|\.\d{6}|)', 'password': '[0-9a-zA-Z]{1,12}', 'ip': '[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}', 'ip6': '([0-9a-zA-Z]{1,4}:){1,7}:?[0-9a-zA-Z]{1,4}', @@ -367,12 +380,8 @@ class ApiSamplesTrap(ApiSampleTestBase): do_not_approve_additions.append('os-config-drive') do_not_approve_additions.append('os-create-server-ext') 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') do_not_approve_additions.append('os-networks') do_not_approve_additions.append('os-services') do_not_approve_additions.append('os-volumes') @@ -672,7 +681,7 @@ class ImagesSampleJsonTest(ApiSampleTestBase): return self._verify_response('images-details-get-resp', subs, response) def test_image_metadata_get(self): - # Get api sample of a image metadata request. + # Get api sample of an image metadata request. image_id = fake.get_valid_image_id() response = self._do_get('images/%s/metadata' % image_id) subs = self._get_regexes() @@ -701,7 +710,7 @@ class ImagesSampleJsonTest(ApiSampleTestBase): subs, response) def test_image_meta_key_get(self): - # Get api sample of a image metadata key request. + # Get api sample of an image metadata key request. image_id = fake.get_valid_image_id() key = "kernel_id" response = self._do_get('images/%s/metadata/%s' % (image_id, key)) @@ -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) @@ -2116,6 +2125,11 @@ class ConsolesSampleJsonTests(ServersSampleBase): extension_name = ("nova.api.openstack.compute.contrib" ".consoles.Consoles") + def setUp(self): + super(ConsolesSampleJsonTests, self).setUp() + self.flags(vnc_enabled=True) + self.flags(enabled=True, group='spice') + def test_get_vnc_console(self): uuid = self._post_server() response = self._do_post('servers/%s/action' % uuid, @@ -2128,6 +2142,18 @@ class ConsolesSampleJsonTests(ServersSampleBase): return self._verify_response('get-vnc-console-post-resp', subs, response) + def test_get_spice_console(self): + uuid = self._post_server() + response = self._do_post('servers/%s/action' % uuid, + 'get-spice-console-post-req', + {'action': 'os-getSPICEConsole'}) + self.assertEqual(response.status, 200) + subs = self._get_regexes() + subs["url"] = \ + "((https?):((//)|(\\\\))+([\w\d:#@%/;$()~_?\+-=\\\.&](#!)?)*)" + return self._verify_response('get-spice-console-post-resp', + subs, response) + class ConsolesSampleXmlTests(ConsolesSampleJsonTests): ctype = 'xml' @@ -2563,3 +2589,214 @@ 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" + + +class InstanceUsageAuditLogJsonTest(ApiSampleTestBase): + extension_name = ("nova.api.openstack.compute.contrib." + "instance_usage_audit_log.Instance_usage_audit_log") + + def test_show_instance_usage_audit_log(self): + response = self._do_get('os-instance_usage_audit_log/%s' % + urllib.quote('2012-07-05 10:00:00')) + self.assertEqual(response.status, 200) + subs = self._get_regexes() + subs['hostid'] = '[a-f0-9]+' + return self._verify_response('inst-usage-audit-log-show-get-resp', + subs, response) + + def test_index_instance_usage_audit_log(self): + response = self._do_get('os-instance_usage_audit_log') + self.assertEqual(response.status, 200) + subs = self._get_regexes() + subs['hostid'] = '[a-f0-9]+' + return self._verify_response('inst-usage-audit-log-index-get-resp', + subs, response) + + +class InstanceUsageAuditLogXmlTest(InstanceUsageAuditLogJsonTest): + ctype = "xml" + + +class FlavorExtraSpecsSampleJsonTests(ApiSampleTestBase): + extension_name = ("nova.api.openstack.compute.contrib.flavorextraspecs." + "Flavorextraspecs") + + def _flavor_extra_specs_create(self): + subs = {'value1': 'value1', + 'value2': 'value2' + } + response = self._do_post('flavors/1/os-extra_specs', + 'flavor-extra-specs-create-req', subs) + self.assertEqual(response.status, 200) + return self._verify_response('flavor-extra-specs-create-resp', + subs, response) + + def test_flavor_extra_specs_get(self): + subs = {'value1': 'value1'} + self._flavor_extra_specs_create() + response = self._do_get('flavors/1/os-extra_specs/key1') + self.assertEqual(response.status, 200) + return self._verify_response('flavor-extra-specs-get-resp', + subs, response) + + def test_flavor_extra_specs_list(self): + subs = {'value1': 'value1', + 'value2': 'value2' + } + self._flavor_extra_specs_create() + response = self._do_get('flavors/1/os-extra_specs') + self.assertEqual(response.status, 200) + return self._verify_response('flavor-extra-specs-list-resp', + subs, response) + + def test_flavor_extra_specs_create(self): + return self._flavor_extra_specs_create() + + def test_flavor_extra_specs_update(self): + subs = {'value1': 'new_value1'} + self._flavor_extra_specs_create() + response = self._do_put('flavors/1/os-extra_specs/key1', + 'flavor-extra-specs-update-req', subs) + self.assertEqual(response.status, 200) + return self._verify_response('flavor-extra-specs-update-resp', + subs, response) + + def test_flavor_extra_specs_delete(self): + self._flavor_extra_specs_create() + response = self._do_delete('flavors/1/os-extra_specs/key1') + self.assertEqual(response.status, 200) + self.assertEqual(response.read(), '') + + +class FlavorExtraSpecsSampleXmlTests(FlavorExtraSpecsSampleJsonTests): + ctype = 'xml' + + +class FpingSampleJsonTests(ServersSampleBase): + extension_name = ("nova.api.openstack.compute.contrib.fping.Fping") + + def setUp(self): + super(FpingSampleJsonTests, self).setUp() + + def fake_check_fping(self): + pass + self.stubs.Set(utils, "execute", test_fping.execute) + self.stubs.Set(fping.FpingController, "check_fping", + fake_check_fping) + + def test_get_fping(self): + self._post_server() + response = self._do_get('os-fping') + self.assertEqual(response.status, 200) + subs = self._get_regexes() + return self._verify_response('fping-get-resp', subs, response) + + def test_get_fping_details(self): + uuid = self._post_server() + response = self._do_get('os-fping/%s' % (uuid)) + self.assertEqual(response.status, 200) + subs = self._get_regexes() + return self._verify_response('fping-get-details-resp', subs, response) + + +class FpingSampleXmlTests(FpingSampleJsonTests): + ctype = 'xml' diff --git a/nova/tests/integrated/test_multiprocess_api.py b/nova/tests/integrated/test_multiprocess_api.py index 5a82e0033..ae4fcc32f 100644 --- a/nova/tests/integrated/test_multiprocess_api.py +++ b/nova/tests/integrated/test_multiprocess_api.py @@ -63,7 +63,7 @@ class MultiprocessWSGITest(integrated_helpers._IntegratedTestBase): try: traceback.print_exc() except BaseException: - print "Couldn't print traceback" + LOG.error("Couldn't print traceback") status = 2 # Really exit @@ -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_api.py b/nova/tests/network/test_api.py index 959c5a472..a0179ff94 100644 --- a/nova/tests/network/test_api.py +++ b/nova/tests/network/test_api.py @@ -25,14 +25,40 @@ import mox from nova import context from nova import exception from nova import network +from nova.network import api from nova.network import rpcapi as network_rpcapi from nova.openstack.common import rpc +from nova import policy from nova import test FAKE_UUID = 'a47ae74e-ab08-547f-9eee-ffd23fc46c16' +class NetworkPolicyTestCase(test.TestCase): + def setUp(self): + super(NetworkPolicyTestCase, self).setUp() + + policy.reset() + policy.init() + + self.context = context.get_admin_context() + + def tearDown(self): + super(NetworkPolicyTestCase, self).tearDown() + policy.reset() + + def test_check_policy(self): + self.mox.StubOutWithMock(policy, 'enforce') + target = { + 'project_id': self.context.project_id, + 'user_id': self.context.user_id, + } + policy.enforce(self.context, 'network:get_all', target) + self.mox.ReplayAll() + api.check_policy(self.context, 'get_all') + + class ApiTestCase(test.TestCase): def setUp(self): super(ApiTestCase, self).setUp() @@ -57,7 +83,7 @@ class ApiTestCase(test.TestCase): instance = dict(id='id', uuid='uuid', project_id='project_id', host='host', instance_type={'rxtx_factor': 0}) self.network_api.allocate_for_instance( - 'context', instance, 'vpn', 'requested_networks', macs=macs) + self.context, instance, 'vpn', 'requested_networks', macs=macs) def _do_test_associate_floating_ip(self, orig_instance_uuid): """Test post-association logic.""" diff --git a/nova/tests/network/test_linux_net.py b/nova/tests/network/test_linux_net.py index c0770902d..3c219f5f4 100644 --- a/nova/tests/network/test_linux_net.py +++ b/nova/tests/network/test_linux_net.py @@ -461,21 +461,17 @@ class LinuxNetworkTestCase(test.TestCase): 'bridge_interface': iface} driver.plug(network, 'fakemac') expected = [ - ('ebtables', '-D', 'INPUT', '-p', 'ARP', '-i', iface, - '--arp-ip-dst', dhcp, '-j', 'DROP'), - ('ebtables', '-I', 'INPUT', '-p', 'ARP', '-i', iface, - '--arp-ip-dst', dhcp, '-j', 'DROP'), - ('ebtables', '-D', 'OUTPUT', '-p', 'ARP', '-o', iface, - '--arp-ip-src', dhcp, '-j', 'DROP'), - ('ebtables', '-I', 'OUTPUT', '-p', 'ARP', '-o', iface, - '--arp-ip-src', dhcp, '-j', 'DROP'), - ('iptables-save', '-c', '-t', 'filter'), + ('ebtables', '-t', 'filter', '-D', 'INPUT', '-p', 'ARP', '-i', + iface, '--arp-ip-dst', dhcp, '-j', 'DROP'), + ('ebtables', '-t', 'filter', '-I', 'INPUT', '-p', 'ARP', '-i', + iface, '--arp-ip-dst', dhcp, '-j', 'DROP'), + ('ebtables', '-t', 'filter', '-D', 'OUTPUT', '-p', 'ARP', '-o', + iface, '--arp-ip-src', dhcp, '-j', 'DROP'), + ('ebtables', '-t', 'filter', '-I', 'OUTPUT', '-p', 'ARP', '-o', + iface, '--arp-ip-src', dhcp, '-j', 'DROP'), + ('iptables-save', '-c'), ('iptables-restore', '-c'), - ('iptables-save', '-c', '-t', 'mangle'), - ('iptables-restore', '-c'), - ('iptables-save', '-c', '-t', 'nat'), - ('iptables-restore', '-c'), - ('ip6tables-save', '-c', '-t', 'filter'), + ('ip6tables-save', '-c'), ('ip6tables-restore', '-c'), ] self.assertEqual(executes, expected) @@ -504,17 +500,13 @@ class LinuxNetworkTestCase(test.TestCase): driver.unplug(network) expected = [ - ('ebtables', '-D', 'INPUT', '-p', 'ARP', '-i', iface, - '--arp-ip-dst', dhcp, '-j', 'DROP'), - ('ebtables', '-D', 'OUTPUT', '-p', 'ARP', '-o', iface, - '--arp-ip-src', dhcp, '-j', 'DROP'), - ('iptables-save', '-c', '-t', 'filter'), - ('iptables-restore', '-c'), - ('iptables-save', '-c', '-t', 'mangle'), - ('iptables-restore', '-c'), - ('iptables-save', '-c', '-t', 'nat'), + ('ebtables', '-t', 'filter', '-D', 'INPUT', '-p', 'ARP', '-i', + iface, '--arp-ip-dst', dhcp, '-j', 'DROP'), + ('ebtables', '-t', 'filter', '-D', 'OUTPUT', '-p', 'ARP', '-o', + iface, '--arp-ip-src', dhcp, '-j', 'DROP'), + ('iptables-save', '-c'), ('iptables-restore', '-c'), - ('ip6tables-save', '-c', '-t', 'filter'), + ('ip6tables-save', '-c'), ('ip6tables-restore', '-c'), ] self.assertEqual(executes, expected) diff --git a/nova/tests/network/test_manager.py b/nova/tests/network/test_manager.py index 1552630fb..2cc19bbb8 100644 --- a/nova/tests/network/test_manager.py +++ b/nova/tests/network/test_manager.py @@ -15,9 +15,8 @@ # 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 shutil -import tempfile +import fixtures import mox from nova import context @@ -33,7 +32,6 @@ from nova.openstack.common import importutils from nova.openstack.common import log as logging from nova.openstack.common import rpc from nova.openstack.common.rpc import common as rpc_common -import nova.policy from nova import test from nova.tests import fake_ldap from nova.tests import fake_network @@ -142,7 +140,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 +148,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 +183,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)) @@ -673,7 +668,7 @@ class VlanNetworkTestCase(test.TestCase): is_admin=False) def fake1(*args, **kwargs): - return '10.0.0.1' + return {'address': '10.0.0.1', 'network': 'fakenet'} # floating ip that's already associated def fake2(*args, **kwargs): @@ -793,9 +788,9 @@ class VlanNetworkTestCase(test.TestCase): self.stubs.Set(self.network.db, 'floating_ip_get_all_by_host', get_all_by_host) - def fixed_ip_get(_context, fixed_ip_id): + def fixed_ip_get(_context, fixed_ip_id, get_network): if fixed_ip_id == 1: - return {'address': 'fakefixed'} + return {'address': 'fakefixed', 'network': 'fakenet'} raise exception.FixedIpNotFound(id=fixed_ip_id) self.stubs.Set(self.network.db, 'fixed_ip_get', fixed_ip_get) @@ -803,7 +798,8 @@ class VlanNetworkTestCase(test.TestCase): self.flags(public_interface=False) self.network.l3driver.add_floating_ip('fakefloat', 'fakefixed', - 'fakeiface') + 'fakeiface', + 'fakenet') self.mox.ReplayAll() self.network.init_host_floating_ips() self.mox.UnsetStubs() @@ -813,7 +809,8 @@ class VlanNetworkTestCase(test.TestCase): self.flags(public_interface='fooiface') self.network.l3driver.add_floating_ip('fakefloat', 'fakefixed', - 'fooiface') + 'fooiface', + 'fakenet') self.mox.ReplayAll() self.network.init_host_floating_ips() self.mox.UnsetStubs() @@ -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 @@ -1812,11 +1805,13 @@ class FloatingIPTestCase(test.TestCase): def fake_is_stale_floating_ip_address(context, floating_ip): return floating_ip['address'] == '172.24.4.23' - def fake_fixed_ip_get(context, fixed_ip_id): + def fake_fixed_ip_get(context, fixed_ip_id, get_network): return {'instance_uuid': 'fake_uuid', - 'address': '10.0.0.2'} + 'address': '10.0.0.2', + 'network': 'fakenet'} - def fake_remove_floating_ip(floating_addr, fixed_addr, interface): + def fake_remove_floating_ip(floating_addr, fixed_addr, interface, + network): called['count'] += 1 def fake_floating_ip_update(context, address, args): @@ -1853,11 +1848,13 @@ class FloatingIPTestCase(test.TestCase): def fake_is_stale_floating_ip_address(context, floating_ip): return floating_ip['address'] == '172.24.4.23' - def fake_fixed_ip_get(context, fixed_ip_id): + def fake_fixed_ip_get(context, fixed_ip_id, get_network): return {'instance_uuid': 'fake_uuid', - 'address': '10.0.0.2'} + 'address': '10.0.0.2', + 'network': 'fakenet'} - def fake_add_floating_ip(floating_addr, fixed_addr, interface): + def fake_add_floating_ip(floating_addr, fixed_addr, interface, + network): called['count'] += 1 def fake_floating_ip_update(context, address, args): @@ -2100,35 +2097,11 @@ class FloatingIPTestCase(test.TestCase): self.context, 'fake-id') -class NetworkPolicyTestCase(test.TestCase): - def setUp(self): - super(NetworkPolicyTestCase, self).setUp() - - nova.policy.reset() - nova.policy.init() - - self.context = context.get_admin_context() - - def tearDown(self): - super(NetworkPolicyTestCase, self).tearDown() - nova.policy.reset() - - def test_check_policy(self): - self.mox.StubOutWithMock(nova.policy, 'enforce') - target = { - 'project_id': self.context.project_id, - 'user_id': self.context.user_id, - } - nova.policy.enforce(self.context, 'network:get_all', target) - self.mox.ReplayAll() - network_manager.check_policy(self.context, 'get_all') - - 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 +2109,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_quantumv2.py b/nova/tests/network/test_quantumv2.py index 876bce90d..a7d6d68ac 100644 --- a/nova/tests/network/test_quantumv2.py +++ b/nova/tests/network/test_quantumv2.py @@ -349,6 +349,9 @@ class TestQuantumv2(test.TestCase): nets = self.nets[net_idx - 1] ports = {} fixed_ips = {} + macs = kwargs.get('macs') + if macs: + macs = set(macs) req_net_ids = [] if 'requested_networks' in kwargs: for id, fixed_ip, port_id in kwargs['requested_networks']: @@ -359,13 +362,15 @@ class TestQuantumv2(test.TestCase): 'mac_address': 'my_mac1'}}) ports['my_netid1'] = self.port_data1[0] id = 'my_netid1' + if macs is not None: + macs.discard('my_mac1') else: fixed_ips[id] = fixed_ip req_net_ids.append(id) expected_network_order = req_net_ids else: expected_network_order = [n['id'] for n in nets] - if kwargs.get('_break_list_networks'): + if kwargs.get('_break') == 'pre_list_networks': self.mox.ReplayAll() return api search_ids = [net['id'] for net in nets if net['id'] in req_net_ids] @@ -382,8 +387,10 @@ class TestQuantumv2(test.TestCase): mox_list_network_params['id'] = mox.SameElementsAs(search_ids) self.moxed_client.list_networks( **mox_list_network_params).AndReturn({'networks': []}) - for net_id in expected_network_order: + if kwargs.get('_break') == 'net_id2': + self.mox.ReplayAll() + return api port_req_body = { 'port': { 'device_id': self.instance['uuid'], @@ -406,13 +413,19 @@ class TestQuantumv2(test.TestCase): port_req_body['port']['admin_state_up'] = True port_req_body['port']['tenant_id'] = \ self.instance['project_id'] + if macs: + port_req_body['port']['mac_address'] = macs.pop() res_port = {'port': {'id': 'fake'}} self.moxed_client.create_port( MyComparator(port_req_body)).AndReturn(res_port) + if kwargs.get('_break') == 'pre_get_instance_nw_info': + self.mox.ReplayAll() + return api api.get_instance_nw_info(mox.IgnoreArg(), self.instance, - networks=nets).AndReturn(None) + networks=nets, + conductor_api=mox.IgnoreArg()).AndReturn(None) self.mox.ReplayAll() return api @@ -433,16 +446,63 @@ class TestQuantumv2(test.TestCase): self._allocate_for_instance(1, macs=None) def test_allocate_for_instance_accepts_macs_kwargs_set(self): - # The macs kwarg should be accepted, as a set. + # The macs kwarg should be accepted, as a set, the + # _allocate_for_instance helper checks that the mac is used to create a + # port. self._allocate_for_instance(1, macs=set(['ab:cd:ef:01:23:45'])) + def test_allocate_for_instance_not_enough_macs_via_ports(self): + # using a hypervisor MAC via a pre-created port will stop it being + # used to dynamically create a port on a network. We put the network + # first in requested_networks so that if the code were to not pre-check + # requested ports, it would incorrectly assign the mac and not fail. + requested_networks = [ + (self.nets2[1]['id'], None, None), + (None, None, 'my_portid1')] + api = self._stub_allocate_for_instance( + net_idx=2, requested_networks=requested_networks, + macs=set(['my_mac1']), + _break='net_id2') + self.assertRaises(exception.PortNotFree, + api.allocate_for_instance, self.context, + self.instance, requested_networks=requested_networks, + macs=set(['my_mac1'])) + + def test_allocate_for_instance_not_enough_macs(self): + # If not enough MAC addresses are available to allocate to networks, an + # error should be raised. + # We could pass in macs=set(), but that wouldn't tell us that + # allocate_for_instance tracks used macs properly, so we pass in one + # mac, and ask for two networks. + requested_networks = [ + (self.nets2[1]['id'], None, None), + (self.nets2[0]['id'], None, None)] + api = self._stub_allocate_for_instance( + net_idx=2, requested_networks=requested_networks, + macs=set(['my_mac2']), + _break='pre_get_instance_nw_info') + self.assertRaises(exception.PortNotFree, + api.allocate_for_instance, self.context, + self.instance, requested_networks=requested_networks, + macs=set(['my_mac2'])) + + def test_allocate_for_instance_two_macs_two_networks(self): + # If two MACs are available and two networks requested, two new ports + # get made and no exceptions raised. + requested_networks = [ + (self.nets2[1]['id'], None, None), + (self.nets2[0]['id'], None, None)] + self._allocate_for_instance( + net_idx=2, requested_networks=requested_networks, + macs=set(['my_mac2', 'my_mac1'])) + def test_allocate_for_instance_mac_conflicting_requested_port(self): # specify only first and last network requested_networks = [(None, None, 'my_portid1')] api = self._stub_allocate_for_instance( net_idx=1, requested_networks=requested_networks, macs=set(['unknown:mac']), - _break_list_networks=True) + _break='pre_list_networks') self.assertRaises(exception.PortNotUsable, api.allocate_for_instance, self.context, self.instance, requested_networks=requested_networks, @@ -943,6 +1003,54 @@ class TestQuantumv2(test.TestCase): self.mox.ReplayAll() api.disassociate_floating_ip(self.context, self.instance, address) + def test_add_fixed_ip_to_instance(self): + api = quantumapi.API() + network_id = 'my_netid1' + search_opts = {'network_id': network_id} + self.moxed_client.list_subnets( + **search_opts).AndReturn({'subnets': self.subnet_data1}) + + zone = 'compute:%s' % self.instance['availability_zone'] + search_opts = {'device_id': self.instance['uuid'], + 'device_owner': 'compute:nova', + 'network_id': network_id} + self.moxed_client.list_ports( + **search_opts).AndReturn({'ports': self.port_data1}) + port_req_body = { + 'port': { + 'fixed_ips': [{'subnet_id': 'my_subid1'}], + }, + } + port = self.port_data1[0] + port['fixed_ips'] = [{'subnet_id': 'my_subid1'}] + self.moxed_client.update_port('my_portid1', + MyComparator(port_req_body)).AndReturn({'port': port}) + + self.mox.ReplayAll() + api.add_fixed_ip_to_instance(self.context, self.instance, network_id) + + def test_remove_fixed_ip_from_instance(self): + api = quantumapi.API() + address = '10.0.0.3' + zone = 'compute:%s' % self.instance['availability_zone'] + search_opts = {'device_id': self.instance['uuid'], + 'device_owner': zone, + 'fixed_ips': 'ip_address=%s' % address} + self.moxed_client.list_ports( + **search_opts).AndReturn({'ports': self.port_data1}) + port_req_body = { + 'port': { + 'fixed_ips': [], + }, + } + port = self.port_data1[0] + port['fixed_ips'] = [] + self.moxed_client.update_port('my_portid1', + MyComparator(port_req_body)).AndReturn({'port': port}) + + self.mox.ReplayAll() + api.remove_fixed_ip_from_instance(self.context, self.instance, address) + class TestQuantumv2ModuleMethods(test.TestCase): def test_ensure_requested_network_ordering_no_preference(self): diff --git a/nova/tests/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_chance_scheduler.py b/nova/tests/scheduler/test_chance_scheduler.py index 26cde055b..dcbe86f75 100644 --- a/nova/tests/scheduler/test_chance_scheduler.py +++ b/nova/tests/scheduler/test_chance_scheduler.py @@ -25,6 +25,7 @@ import mox from nova.compute import rpcapi as compute_rpcapi from nova.compute import utils as compute_utils from nova.compute import vm_states +from nova.conductor import api as conductor_api from nova import context from nova import db from nova import exception @@ -130,11 +131,12 @@ class ChanceSchedulerTestCase(test_scheduler.SchedulerTestCase): # instance 1 ctxt.elevated().AndReturn(ctxt_elevated) self.driver.hosts_up(ctxt_elevated, 'compute').AndReturn([]) - compute_utils.add_instance_fault_from_exc(ctxt, - uuid, mox.IsA(exception.NoValidHost), mox.IgnoreArg()) - db.instance_update_and_get_original(ctxt, uuid, + old_ref, new_ref = db.instance_update_and_get_original(ctxt, uuid, {'vm_state': vm_states.ERROR, 'task_state': None}).AndReturn(({}, {})) + compute_utils.add_instance_fault_from_exc(ctxt, + mox.IsA(conductor_api.LocalAPI), new_ref, + mox.IsA(exception.NoValidHost), mox.IgnoreArg()) self.mox.ReplayAll() self.driver.schedule_run_instance( diff --git a/nova/tests/scheduler/test_filter_scheduler.py b/nova/tests/scheduler/test_filter_scheduler.py index 5d8e8236b..ff3a00f22 100644 --- a/nova/tests/scheduler/test_filter_scheduler.py +++ b/nova/tests/scheduler/test_filter_scheduler.py @@ -21,6 +21,7 @@ import mox from nova.compute import instance_types from nova.compute import utils as compute_utils from nova.compute import vm_states +from nova.conductor import api as conductor_api from nova import context from nova import db from nova import exception @@ -58,11 +59,12 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): self.mox.StubOutWithMock(compute_utils, 'add_instance_fault_from_exc') self.mox.StubOutWithMock(db, 'instance_update_and_get_original') + old_ref, new_ref = db.instance_update_and_get_original(fake_context, + uuid, {'vm_state': vm_states.ERROR, 'task_state': + None}).AndReturn(({}, {})) compute_utils.add_instance_fault_from_exc(fake_context, - uuid, mox.IsA(exception.NoValidHost), mox.IgnoreArg()) - db.instance_update_and_get_original(fake_context, uuid, - {'vm_state': vm_states.ERROR, - 'task_state': None}).AndReturn(({}, {})) + mox.IsA(conductor_api.LocalAPI), new_ref, + mox.IsA(exception.NoValidHost), mox.IgnoreArg()) self.mox.ReplayAll() sched.schedule_run_instance( fake_context, request_spec, None, None, None, None, {}) @@ -88,11 +90,12 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): 'instance_uuids': [uuid]} self.mox.StubOutWithMock(compute_utils, 'add_instance_fault_from_exc') self.mox.StubOutWithMock(db, 'instance_update_and_get_original') + old_ref, new_ref = db.instance_update_and_get_original(fake_context, + uuid, {'vm_state': vm_states.ERROR, 'task_state': + None}).AndReturn(({}, {})) compute_utils.add_instance_fault_from_exc(fake_context, - uuid, mox.IsA(exception.NoValidHost), mox.IgnoreArg()) - db.instance_update_and_get_original(fake_context, uuid, - {'vm_state': vm_states.ERROR, - 'task_state': None}).AndReturn(({}, {})) + mox.IsA(conductor_api.LocalAPI), new_ref, + mox.IsA(exception.NoValidHost), mox.IgnoreArg()) self.mox.ReplayAll() sched.schedule_run_instance( fake_context, request_spec, None, None, None, None, {}) diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py index 9f7f189cc..230e2ea03 100644 --- a/nova/tests/scheduler/test_host_filters.py +++ b/nova/tests/scheduler/test_host_filters.py @@ -22,9 +22,10 @@ from nova import context from nova import db from nova.openstack.common import cfg 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 @@ -233,13 +234,16 @@ class HostFiltersTestCase(test.TestCase): def fake_oat_request(self, *args, **kwargs): """Stubs out the response from OAT service.""" - return httplib.OK, jsonutils.loads(self.oat_data) + self.oat_attested = True + return httplib.OK, self.oat_data def setUp(self): super(HostFiltersTestCase, self).setUp() 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], @@ -333,6 +337,20 @@ class HostFiltersTestCase(test.TestCase): self.assertTrue(filt_cls.host_passes(host, filter_properties)) + def test_affinity_different_filter_handles_deleted_instance(self): + filt_cls = self.class_map['DifferentHostFilter']() + host = fakes.FakeHostState('host1', 'node1', {}) + instance = fakes.FakeInstance(context=self.context, + params={'host': 'host1'}) + instance_uuid = instance.uuid + db.instance_destroy(self.context, instance_uuid) + + filter_properties = {'context': self.context.elevated(), + 'scheduler_hints': { + 'different_host': [instance_uuid], }} + + self.assertTrue(filt_cls.host_passes(host, filter_properties)) + def test_affinity_same_filter_no_list_passes(self): filt_cls = self.class_map['SameHostFilter']() host = fakes.FakeHostState('host1', 'node1', {}) @@ -384,6 +402,20 @@ class HostFiltersTestCase(test.TestCase): self.assertTrue(filt_cls.host_passes(host, filter_properties)) + def test_affinity_same_filter_handles_deleted_instance(self): + filt_cls = self.class_map['SameHostFilter']() + host = fakes.FakeHostState('host1', 'node1', {}) + instance = fakes.FakeInstance(context=self.context, + params={'host': 'host1'}) + instance_uuid = instance.uuid + db.instance_destroy(self.context, instance_uuid) + + filter_properties = {'context': self.context.elevated(), + 'scheduler_hints': { + 'same_host': [instance_uuid], }} + + self.assertFalse(filt_cls.host_passes(host, filter_properties)) + def test_affinity_simple_cidr_filter_passes(self): filt_cls = self.class_map['SimpleCIDRAffinityFilter']() host = fakes.FakeHostState('host1', 'node1', {}) @@ -1147,54 +1179,121 @@ class HostFiltersTestCase(test.TestCase): def test_trusted_filter_default_passes(self): self._stub_service_is_up(True) filt_cls = self.class_map['TrustedFilter']() - filter_properties = {'instance_type': {'memory_mb': 1024}} + filter_properties = {'context': self.context.elevated(), + 'instance_type': {'memory_mb': 1024}} host = fakes.FakeHostState('host1', 'node1', {}) self.assertTrue(filt_cls.host_passes(host, filter_properties)) def test_trusted_filter_trusted_and_trusted_passes(self): - self.oat_data =\ - '{"hosts":[{"host_name":"host1","trust_lvl":"trusted"}]}' + self.oat_data = {"hosts": [{"host_name": "host1", + "trust_lvl": "trusted", + "vtime": timeutils.isotime()}]} self._stub_service_is_up(True) filt_cls = self.class_map['TrustedFilter']() extra_specs = {'trust:trusted_host': 'trusted'} - filter_properties = {'instance_type': {'memory_mb': 1024, + filter_properties = {'context': self.context.elevated(), + 'instance_type': {'memory_mb': 1024, 'extra_specs': extra_specs}} host = fakes.FakeHostState('host1', 'node1', {}) self.assertTrue(filt_cls.host_passes(host, filter_properties)) def test_trusted_filter_trusted_and_untrusted_fails(self): - self.oat_data =\ - '{"hosts":[{"host_name":"host1","trust_lvl":"untrusted"}]}' + self.oat_data = {"hosts": [{"host_name": "host1", + "trust_lvl": "untrusted", + "vtime": timeutils.isotime()}]} self._stub_service_is_up(True) filt_cls = self.class_map['TrustedFilter']() extra_specs = {'trust:trusted_host': 'trusted'} - filter_properties = {'instance_type': {'memory_mb': 1024, + filter_properties = {'context': self.context.elevated(), + 'instance_type': {'memory_mb': 1024, 'extra_specs': extra_specs}} host = fakes.FakeHostState('host1', 'node1', {}) self.assertFalse(filt_cls.host_passes(host, filter_properties)) def test_trusted_filter_untrusted_and_trusted_fails(self): - self.oat_data =\ - '{"hosts":[{"host_name":"host1","trust_lvl":"trusted"}]}' + self.oat_data = {"hosts": [{"host_name": "host1", + "trust_lvl": "trusted", + "vtime": timeutils.isotime()}]} self._stub_service_is_up(True) filt_cls = self.class_map['TrustedFilter']() extra_specs = {'trust:trusted_host': 'untrusted'} - filter_properties = {'instance_type': {'memory_mb': 1024, + filter_properties = {'context': self.context.elevated(), + 'instance_type': {'memory_mb': 1024, 'extra_specs': extra_specs}} host = fakes.FakeHostState('host1', 'node1', {}) self.assertFalse(filt_cls.host_passes(host, filter_properties)) def test_trusted_filter_untrusted_and_untrusted_passes(self): - self.oat_data =\ - '{"hosts":[{"host_name":"host1","trust_lvl":"untrusted"}]}' + self.oat_data = {"hosts": [{"host_name": "host1", + "trust_lvl": "untrusted", + "vtime":timeutils.isotime()}]} self._stub_service_is_up(True) filt_cls = self.class_map['TrustedFilter']() extra_specs = {'trust:trusted_host': 'untrusted'} - filter_properties = {'instance_type': {'memory_mb': 1024, + filter_properties = {'context': self.context.elevated(), + 'instance_type': {'memory_mb': 1024, 'extra_specs': extra_specs}} host = fakes.FakeHostState('host1', 'node1', {}) self.assertTrue(filt_cls.host_passes(host, filter_properties)) + def test_trusted_filter_update_cache(self): + self.oat_data = {"hosts": [{"host_name": + "host1", "trust_lvl": "untrusted", + "vtime": timeutils.isotime()}]} + + filt_cls = self.class_map['TrustedFilter']() + extra_specs = {'trust:trusted_host': 'untrusted'} + filter_properties = {'context': self.context.elevated(), + 'instance_type': {'memory_mb': 1024, + 'extra_specs': extra_specs}} + host = fakes.FakeHostState('host1', 'node1', {}) + + filt_cls.host_passes(host, filter_properties) # Fill the caches + + self.oat_attested = False + filt_cls.host_passes(host, filter_properties) + self.assertFalse(self.oat_attested) + + self.oat_attested = False + + timeutils.set_time_override(timeutils.utcnow()) + timeutils.advance_time_seconds( + CONF.trusted_computing.attestation_auth_timeout + 80) + filt_cls.host_passes(host, filter_properties) + self.assertTrue(self.oat_attested) + + timeutils.clear_time_override() + + def test_trusted_filter_update_cache_timezone(self): + self.oat_data = {"hosts": [{"host_name": "host1", + "trust_lvl": "untrusted", + "vtime": "2012-09-09T05:10:40-04:00"}]} + + filt_cls = self.class_map['TrustedFilter']() + extra_specs = {'trust:trusted_host': 'untrusted'} + filter_properties = {'context': self.context.elevated(), + 'instance_type': {'memory_mb': 1024, + 'extra_specs': extra_specs}} + host = fakes.FakeHostState('host1', 'node1', {}) + + timeutils.set_time_override( + timeutils.normalize_time( + timeutils.parse_isotime("2012-09-09T09:10:40Z"))) + + filt_cls.host_passes(host, filter_properties) # Fill the caches + + self.oat_attested = False + filt_cls.host_passes(host, filter_properties) + self.assertFalse(self.oat_attested) + + self.oat_attested = False + timeutils.advance_time_seconds( + CONF.trusted_computing.attestation_auth_timeout - 10) + filt_cls.host_passes(host, filter_properties) + self.assertFalse(self.oat_attested) + + timeutils.clear_time_override() + def test_core_filter_passes(self): filt_cls = self.class_map['CoreFilter']() filter_properties = {'instance_type': {'vcpus': 1}} diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index dd5b0ae32..142d8ea0e 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -26,10 +26,12 @@ from nova.compute import power_state from nova.compute import rpcapi as compute_rpcapi from nova.compute import utils as compute_utils from nova.compute import vm_states +from nova.conductor import api as conductor_api from nova import context from nova import db from nova import exception from nova.openstack.common import jsonutils +from nova.openstack.common.notifier import api as notifier from nova.openstack.common import rpc from nova.scheduler import driver from nova.scheduler import manager @@ -183,12 +185,13 @@ class SchedulerManagerTestCase(test.TestCase): self.manager.driver.schedule_run_instance(self.context, request_spec, None, None, None, None, {}).AndRaise( exception.NoValidHost(reason="")) - db.instance_update_and_get_original(self.context, fake_instance_uuid, + old, new_ref = db.instance_update_and_get_original(self.context, + fake_instance_uuid, {"vm_state": vm_states.ERROR, "task_state": None}).AndReturn((inst, inst)) compute_utils.add_instance_fault_from_exc(self.context, - fake_instance_uuid, mox.IsA(exception.NoValidHost), - mox.IgnoreArg()) + mox.IsA(conductor_api.LocalAPI), new_ref, + mox.IsA(exception.NoValidHost), mox.IgnoreArg()) self.mox.ReplayAll() self.manager.run_instance(self.context, request_spec, @@ -217,12 +220,13 @@ class SchedulerManagerTestCase(test.TestCase): } self.manager.driver.schedule_prep_resize(**kwargs).AndRaise( exception.NoValidHost(reason="")) - db.instance_update_and_get_original(self.context, fake_instance_uuid, + old_ref, new_ref = db.instance_update_and_get_original(self.context, + fake_instance_uuid, {"vm_state": vm_states.ACTIVE, "task_state": None}).AndReturn( (inst, inst)) compute_utils.add_instance_fault_from_exc(self.context, - fake_instance_uuid, mox.IsA(exception.NoValidHost), - mox.IgnoreArg()) + mox.IsA(conductor_api.LocalAPI), new_ref, + mox.IsA(exception.NoValidHost), mox.IgnoreArg()) self.mox.ReplayAll() self.manager.prep_resize(**kwargs) @@ -254,18 +258,38 @@ class SchedulerManagerTestCase(test.TestCase): "vm_state": "", "task_state": "", } - db.instance_update_and_get_original(self.context, fake_instance_uuid, + old_ref, new_ref = db.instance_update_and_get_original(self.context, + fake_instance_uuid, {"vm_state": vm_states.ERROR, "task_state": None}).AndReturn((inst, inst)) compute_utils.add_instance_fault_from_exc(self.context, - fake_instance_uuid, mox.IsA(test.TestingException), - mox.IgnoreArg()) + mox.IsA(conductor_api.LocalAPI), new_ref, + mox.IsA(test.TestingException), mox.IgnoreArg()) self.mox.ReplayAll() self.assertRaises(test.TestingException, self.manager.prep_resize, **kwargs) + def test_set_vm_state_and_notify_adds_instance_fault(self): + request = {'instance_properties': {'uuid': 'fake-uuid'}} + updates = {'vm_state': 'foo'} + fake_inst = {'uuid': 'fake-uuid'} + + self.mox.StubOutWithMock(db, 'instance_update_and_get_original') + self.mox.StubOutWithMock(db, 'instance_fault_create') + self.mox.StubOutWithMock(notifier, 'notify') + db.instance_update_and_get_original(self.context, 'fake-uuid', + updates).AndReturn((None, + fake_inst)) + db.instance_fault_create(self.context, mox.IgnoreArg()) + notifier.notify(self.context, mox.IgnoreArg(), 'scheduler.foo', + notifier.ERROR, mox.IgnoreArg()) + self.mox.ReplayAll() + + self.manager._set_vm_state_and_notify('foo', {'vm_state': 'foo'}, + self.context, None, request) + class SchedulerTestCase(test.TestCase): """Test case for base scheduler driver class.""" @@ -620,6 +644,24 @@ class SchedulerTestCase(test.TestCase): block_migration=block_migration, disk_over_commit=disk_over_commit) + def test_handle_schedule_error_adds_instance_fault(self): + instance = {'uuid': 'fake-uuid'} + self.mox.StubOutWithMock(db, 'instance_update_and_get_original') + self.mox.StubOutWithMock(db, 'instance_fault_create') + self.mox.StubOutWithMock(notifier, 'notify') + db.instance_update_and_get_original(self.context, instance['uuid'], + mox.IgnoreArg()).AndReturn( + (None, instance)) + db.instance_fault_create(self.context, mox.IgnoreArg()) + notifier.notify(self.context, mox.IgnoreArg(), + 'scheduler.run_instance', + notifier.ERROR, mox.IgnoreArg()) + self.mox.ReplayAll() + + driver.handle_schedule_error(self.context, + exception.NoValidHost('test'), + instance['uuid'], {}) + class SchedulerDriverBaseTestCase(SchedulerTestCase): """Test cases for base scheduler driver class methods 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 829a98334..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() @@ -309,11 +309,10 @@ class ApiEc2TestCase(test.TestCase): try: self.ec2.create_key_pair('test') except boto_exc.EC2ResponseError, e: - if e.code == 'KeyPairExists': + if e.code == 'InvalidKeyPair.Duplicate': pass else: - self.fail("Unexpected EC2ResponseError: %s " - "(expected KeyPairExists)" % e.code) + self.assertEqual('InvalidKeyPair.Duplicate', e.code) else: self.fail('Exception not raised.') diff --git a/nova/tests/test_availability_zones.py b/nova/tests/test_availability_zones.py new file mode 100644 index 000000000..4192fa08f --- /dev/null +++ b/nova/tests/test_availability_zones.py @@ -0,0 +1,113 @@ +# 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 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_bdm.py b/nova/tests/test_bdm.py index 4d62d6bbf..43ca4d7b0 100644 --- a/nova/tests/test_bdm.py +++ b/nova/tests/test_bdm.py @@ -246,6 +246,5 @@ class BlockDeviceMappingEc2CloudTestCase(test.TestCase): result = {} cloud._format_mappings(properties, result) - print result self.assertEqual(result['blockDeviceMapping'].sort(), expected_result['blockDeviceMapping'].sort()) diff --git a/nova/tests/test_block_device.py b/nova/tests/test_block_device.py index 6a77d98ae..8189057cb 100644 --- a/nova/tests/test_block_device.py +++ b/nova/tests/test_block_device.py @@ -91,3 +91,38 @@ class BlockDeviceTestCase(test.TestCase): self.assertEqual(block_device.strip_prefix('a'), 'a') self.assertEqual(block_device.strip_prefix('xvda'), 'a') self.assertEqual(block_device.strip_prefix('vda'), 'a') + + def test_volume_in_mapping(self): + swap = {'device_name': '/dev/sdb', + 'swap_size': 1} + ephemerals = [{'num': 0, + 'virtual_name': 'ephemeral0', + 'device_name': '/dev/sdc1', + 'size': 1}, + {'num': 2, + 'virtual_name': 'ephemeral2', + 'device_name': '/dev/sdd', + 'size': 1}] + block_device_mapping = [{'mount_device': '/dev/sde', + 'device_path': 'fake_device'}, + {'mount_device': '/dev/sdf', + 'device_path': 'fake_device'}] + block_device_info = { + 'root_device_name': '/dev/sda', + 'swap': swap, + 'ephemerals': ephemerals, + 'block_device_mapping': block_device_mapping} + + def _assert_volume_in_mapping(device_name, true_or_false): + in_mapping = block_device.volume_in_mapping( + device_name, block_device_info) + self.assertEquals(in_mapping, true_or_false) + + _assert_volume_in_mapping('sda', False) + _assert_volume_in_mapping('sdb', True) + _assert_volume_in_mapping('sdc1', True) + _assert_volume_in_mapping('sdd', True) + _assert_volume_in_mapping('sde', True) + _assert_volume_in_mapping('sdf', True) + _assert_volume_in_mapping('sdg', False) + _assert_volume_in_mapping('sdh1', False) diff --git a/nova/tests/test_cinder.py b/nova/tests/test_cinder.py index 29e2e978b..79b5ae66a 100644 --- a/nova/tests/test_cinder.py +++ b/nova/tests/test_cinder.py @@ -98,13 +98,14 @@ class FakeHTTPClient(cinder.cinder_client.client.HTTPClient): class FakeCinderClient(cinder.cinder_client.Client): def __init__(self, username, password, project_id=None, auth_url=None, - retries=None): + insecure=False, retries=None): super(FakeCinderClient, self).__init__(username, password, project_id=project_id, auth_url=auth_url, + insecure=insecure, retries=retries) self.client = FakeHTTPClient(username, password, project_id, auth_url, - retries=retries) + insecure=insecure, retries=retries) # keep a ref to the clients callstack for factory's assert_called self.callstack = self.client.callstack = [] @@ -177,6 +178,15 @@ class CinderTestCase(test.TestCase): self.assertTrue('volume_image_metadata' in volume) self.assertEqual(volume['volume_image_metadata'], _image_metadata) + def test_cinder_api_insecure(self): + # The True/False negation is awkward, but better for the client + # to pass us insecure=True and we check verify_cert == False + self.flags(cinder_api_insecure=True) + volume = self.api.get(self.context, '1234') + self.assert_called('GET', '/volumes/1234') + self.assertEquals( + self.fake_client_factory.client.client.verify_cert, False) + def test_cinder_http_retries(self): retries = 42 self.flags(cinder_http_retries=retries) diff --git a/nova/tests/test_context.py b/nova/tests/test_context.py index 0915bf157..527534fd5 100644 --- a/nova/tests/test_context.py +++ b/nova/tests/test_context.py @@ -74,3 +74,22 @@ class ContextTestCase(test.TestCase): self.assertTrue(c) self.assertIn("'extra_arg1': 'meow'", info['log_msg']) self.assertIn("'extra_arg2': 'wuff'", info['log_msg']) + + def test_service_catalog_default(self): + ctxt = context.RequestContext('111', '222') + self.assertEquals(ctxt.service_catalog, []) + + def test_service_catalog_cinder_only(self): + service_catalog = [ + {u'type': u'compute', u'name': u'nova'}, + {u'type': u's3', u'name': u's3'}, + {u'type': u'image', u'name': u'glance'}, + {u'type': u'volume', u'name': u'cinder'}, + {u'type': u'ec2', u'name': u'ec2'}, + {u'type': u'object-store', u'name': u'swift'}, + {u'type': u'identity', u'name': u'keystone'}] + + volume_catalog = [{u'type': u'volume', u'name': u'cinder'}] + ctxt = context.RequestContext('111', '222', + service_catalog=service_catalog) + self.assertEquals(ctxt.service_catalog, volume_catalog) diff --git a/nova/tests/test_crypto.py b/nova/tests/test_crypto.py index 83010cee2..25df336fb 100644 --- a/nova/tests/test_crypto.py +++ b/nova/tests/test_crypto.py @@ -149,3 +149,66 @@ class CertExceptionTests(test.TestCase): self.assertRaises(exception.CryptoCRLFileNotFound, crypto.fetch_crl, project_id='fake') + + +class EncryptionTests(test.TestCase): + pubkey = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDArtgrfBu/g2o28o+H2ng/crv" + "zgES91i/NNPPFTOutXelrJ9QiPTPTm+B8yspLsXifmbsmXztNOlBQgQXs6usxb4" + "fnJKNUZ84Vkp5esbqK/L7eyRqwPvqo7btKBMoAMVX/kUyojMpxb7Ssh6M6Y8cpi" + "goi+MSDPD7+5yRJ9z4mH9h7MCY6Ejv8KTcNYmVHvRhsFUcVhWcIISlNWUGiG7rf" + "oki060F5myQN3AXcL8gHG5/Qb1RVkQFUKZ5geQ39/wSyYA1Q65QTba/5G2QNbl2" + "0eAIBTyKZhN6g88ak+yARa6BLLDkrlP7L4WctHQMLsuXHohQsUO9AcOlVMARgrg" + "uF test@test") + prikey = """-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAwK7YK3wbv4NqNvKPh9p4P3K784BEvdYvzTTzxUzrrV3payfU +Ij0z05vgfMrKS7F4n5m7Jl87TTpQUIEF7OrrMW+H5ySjVGfOFZKeXrG6ivy+3ska +sD76qO27SgTKADFV/5FMqIzKcW+0rIejOmPHKYoKIvjEgzw+/uckSfc+Jh/YezAm +OhI7/Ck3DWJlR70YbBVHFYVnCCEpTVlBohu636JItOtBeZskDdwF3C/IBxuf0G9U +VZEBVCmeYHkN/f8EsmANUOuUE22v+RtkDW5dtHgCAU8imYTeoPPGpPsgEWugSyw5 +K5T+y+FnLR0DC7Llx6IULFDvQHDpVTAEYK4LhQIDAQABAoIBAF9ibrrgHnBpItx+ +qVUMbriiGK8LUXxUmqdQTljeolDZi6KzPc2RVKWtpazBSvG7skX3+XCediHd+0JP +DNri1HlNiA6B0aUIGjoNsf6YpwsE4YwyK9cR5k5YGX4j7se3pKX2jOdngxQyw1Mh +dkmCeWZz4l67nbSFz32qeQlwrsB56THJjgHB7elDoGCXTX/9VJyjFlCbfxVCsIng +inrNgT0uMSYMNpAjTNOjguJt/DtXpwzei5eVpsERe0TRRVH23ycS0fuq/ancYwI/ +MDr9KSB8r+OVGeVGj3popCxECxYLBxhqS1dAQyJjhQXKwajJdHFzidjXO09hLBBz +FiutpYUCgYEA6OFikTrPlCMGMJjSj+R9woDAOPfvCDbVZWfNo8iupiECvei88W28 +RYFnvUQRjSC0pHe//mfUSmiEaE+SjkNCdnNR+vsq9q+htfrADm84jl1mfeWatg/g +zuGz2hAcZnux3kQMI7ufOwZNNpM2bf5B4yKamvG8tZRRxSkkAL1NV48CgYEA08/Z +Ty9g9XPKoLnUWStDh1zwG+c0q14l2giegxzaUAG5DOgOXbXcw0VQ++uOWD5ARELG +g9wZcbBsXxJrRpUqx+GAlv2Y1bkgiPQS1JIyhsWEUtwfAC/G+uZhCX53aI3Pbsjh +QmkPCSp5DuOuW2PybMaw+wVe+CaI/gwAWMYDAasCgYEA4Fzkvc7PVoU33XIeywr0 +LoQkrb4QyPUrOvt7H6SkvuFm5thn0KJMlRpLfAksb69m2l2U1+HooZd4mZawN+eN +DNmlzgxWJDypq83dYwq8jkxmBj1DhMxfZnIE+L403nelseIVYAfPLOqxUTcbZXVk +vRQFp+nmSXqQHUe5rAy1ivkCgYEAqLu7cclchCxqDv/6mc5NTVhMLu5QlvO5U6fq +HqitgW7d69oxF5X499YQXZ+ZFdMBf19ypTiBTIAu1M3nh6LtIa4SsjXzus5vjKpj +FdQhTBus/hU83Pkymk1MoDOPDEtsI+UDDdSDldmv9pyKGWPVi7H86vusXCLWnwsQ +e6fCXWECgYEAqgpGvva5kJ1ISgNwnJbwiNw0sOT9BMOsdNZBElf0kJIIy6FMPvap +6S1ziw+XWfdQ83VIUOCL5DrwmcYzLIogS0agmnx/monfDx0Nl9+OZRxy6+AI9vkK +86A1+DXdo+IgX3grFK1l1gPhAZPRWJZ+anrEkyR4iLq6ZoPZ3BQn97U= +-----END RSA PRIVATE KEY-----""" + text = "Some text! %$*" + + def _ssh_decrypt_text(self, ssh_private_key, text): + with utils.tempdir() as tmpdir: + sshkey = os.path.abspath(os.path.join(tmpdir, 'ssh.key')) + with open(sshkey, 'w') as f: + f.write(ssh_private_key) + try: + dec, _err = utils.execute('openssl', + 'rsautl', + '-decrypt', + '-inkey', sshkey, + process_input=text) + return dec + except exception.ProcessExecutionError as exc: + raise exception.DecryptionFailure(reason=exc.stderr) + + def test_ssh_encrypt_decrypt_text(self): + enc = crypto.ssh_encrypt_text(self.pubkey, self.text) + self.assertNotEqual(enc, self.text) + result = self._ssh_decrypt_text(self.prikey, enc) + self.assertEqual(result, self.text) + + def test_ssh_encrypt_failure(self): + self.assertRaises(exception.EncryptionFailure, + crypto.ssh_encrypt_text, '', self.text) diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index c70e96cdc..684f9fded 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -253,14 +253,40 @@ class DbApiTestCase(test.TestCase): values = {'address': 'fixed'} fixed = db.fixed_ip_create(ctxt, values) res = db.floating_ip_fixed_ip_associate(ctxt, floating, fixed, 'foo') - self.assertEqual(res, fixed) + self.assertEqual(res['address'], fixed) res = db.floating_ip_fixed_ip_associate(ctxt, floating, fixed, 'foo') self.assertEqual(res, None) res = db.floating_ip_disassociate(ctxt, floating) - self.assertEqual(res, fixed) + self.assertEqual(res['address'], fixed) res = db.floating_ip_disassociate(ctxt, floating) self.assertEqual(res, None) + def test_fixed_ip_get_by_floating_address(self): + ctxt = context.get_admin_context() + values = {'address': 'fixed'} + fixed = db.fixed_ip_create(ctxt, values) + fixed_ip_ref = db.fixed_ip_get_by_address(ctxt, fixed) + values = {'address': 'floating', + 'fixed_ip_id': fixed_ip_ref['id']} + floating = db.floating_ip_create(ctxt, values) + fixed_ip_ref = db.fixed_ip_get_by_floating_address(ctxt, floating) + self.assertEqual(fixed, fixed_ip_ref['address']) + + def test_floating_ip_get_by_fixed_address(self): + ctxt = context.get_admin_context() + values = {'address': 'fixed'} + fixed = db.fixed_ip_create(ctxt, values) + fixed_ip_ref = db.fixed_ip_get_by_address(ctxt, fixed) + values = {'address': 'floating1', + 'fixed_ip_id': fixed_ip_ref['id']} + floating1 = db.floating_ip_create(ctxt, values) + values = {'address': 'floating2', + 'fixed_ip_id': fixed_ip_ref['id']} + floating2 = db.floating_ip_create(ctxt, values) + floating_ip_refs = db.floating_ip_get_by_fixed_address(ctxt, fixed) + self.assertEqual(floating1, floating_ip_refs[0]['address']) + self.assertEqual(floating2, floating_ip_refs[1]['address']) + def test_network_create_safe(self): ctxt = context.get_admin_context() values = {'host': 'localhost', 'project_id': 'project1'} @@ -1484,6 +1510,25 @@ class MigrationTestCase(test.TestCase): self.assertEqual(migration['instance_uuid'], instance['uuid']) +class TestFixedIPGetByNetworkHost(test.TestCase): + def test_not_found_exception(self): + ctxt = context.get_admin_context() + + self.assertRaises( + exception.FixedIpNotFoundForNetworkHost, + db.fixed_ip_get_by_network_host, + ctxt, 1, 'ignore') + + def test_fixed_ip_found(self): + ctxt = context.get_admin_context() + db.fixed_ip_create(ctxt, dict(network_id=1, host='host')) + + fip = db.fixed_ip_get_by_network_host(ctxt, 1, 'host') + + self.assertEquals(1, fip['network_id']) + self.assertEquals('host', fip['host']) + + class TestIpAllocation(test.TestCase): def setUp(self): @@ -1645,3 +1690,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_fakelibvirt.py b/nova/tests/test_fakelibvirt.py index fea666f36..32c85a95a 100644 --- a/nova/tests/test_fakelibvirt.py +++ b/nova/tests/test_fakelibvirt.py @@ -53,6 +53,7 @@ def get_vm_xml(name="testname", uuid=None, source_type='file', </interface> <input type='mouse' bus='ps2'/> <graphics type='vnc' port='5901' autoport='yes' keymap='en-us'/> + <graphics type='spice' port='5901' autoport='yes' keymap='en-us'/> </devices> </domain>''' % {'name': name, 'uuid_tag': uuid_tag, diff --git a/nova/tests/test_hypervapi.py b/nova/tests/test_hypervapi.py index 9fec9d151..3b624e6d1 100644 --- a/nova/tests/test_hypervapi.py +++ b/nova/tests/test_hypervapi.py @@ -18,37 +18,53 @@ Test suite for the Hyper-V driver and related APIs. """ -import json +import io +import mox import os import platform import shutil -import sys +import time import uuid +from nova.api.metadata import base as instance_metadata from nova.compute import power_state from nova.compute import task_states from nova import context from nova import db from nova.image import glance from nova.openstack.common import cfg +from nova import test from nova.tests import fake_network -from nova.tests.hyperv import basetestcase from nova.tests.hyperv import db_fakes -from nova.tests.hyperv import hypervutils -from nova.tests.hyperv import mockproxy -import nova.tests.image.fake as fake_image +from nova.tests.hyperv import fake +from nova.tests.image import fake as fake_image from nova.tests import matchers +from nova import utils +from nova.virt import configdrive +from nova.virt.hyperv import basevolumeutils from nova.virt.hyperv import constants from nova.virt.hyperv import driver as driver_hyperv +from nova.virt.hyperv import hostutils +from nova.virt.hyperv import livemigrationutils +from nova.virt.hyperv import networkutils +from nova.virt.hyperv import pathutils +from nova.virt.hyperv import vhdutils from nova.virt.hyperv import vmutils +from nova.virt.hyperv import volumeutils +from nova.virt.hyperv import volumeutilsv2 from nova.virt import images CONF = cfg.CONF +CONF.import_opt('vswitch_name', 'nova.virt.hyperv.vif') -class HyperVAPITestCase(basetestcase.BaseTestCase): +class HyperVAPITestCase(test.TestCase): """Unit tests for Hyper-V driver calls.""" + def __init__(self, test_case_name): + self._mox = mox.Mox() + super(HyperVAPITestCase, self).__init__(test_case_name) + def setUp(self): super(HyperVAPITestCase, self).setUp() @@ -56,22 +72,22 @@ class HyperVAPITestCase(basetestcase.BaseTestCase): self._project_id = 'fake' self._instance_data = None self._image_metadata = None - self._dest_server = None self._fetched_image = None self._update_image_raise_exception = False - self._post_method_called = False - self._recover_method_called = False self._volume_target_portal = 'testtargetportal:3260' - self._volume_id = '8957e088-dbee-4216-8056-978353a3e737' + self._volume_id = '0ef5d708-45ab-4129-8c59-d774d2837eb7' self._context = context.RequestContext(self._user_id, self._project_id) + self._instance_ide_disks = [] + self._instance_ide_dvds = [] + self._instance_volume_disks = [] self._setup_stubs() self.flags(instances_path=r'C:\Hyper-V\test\instances', vswitch_name='external', - network_api_class='nova.network.quantumv2.api.API') + network_api_class='nova.network.quantumv2.api.API', + force_volumeutils_v1=True) - self._hypervutils = hypervutils.HyperVUtils() self._conn = driver_hyperv.HyperVDriver(None) def _setup_stubs(self): @@ -79,14 +95,8 @@ class HyperVAPITestCase(basetestcase.BaseTestCase): fake_image.stub_out_image_service(self.stubs) fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs) - def fake_dumps(msg, default=None, **kwargs): - return '""' - self.stubs.Set(json, 'dumps', fake_dumps) - def fake_fetch(context, image_id, target, user, project): self._fetched_image = target - if not os.path.exists(target): - self._hypervutils.create_vhd(target) self.stubs.Set(images, 'fetch', fake_fetch) def fake_get_remote_image_service(context, name): @@ -98,104 +108,198 @@ class HyperVAPITestCase(basetestcase.BaseTestCase): self._image_metadata = image_metadata return (FakeGlanceImageService(), 1) self.stubs.Set(glance, 'get_remote_image_service', - fake_get_remote_image_service) - - # Modules to mock - modules_to_mock = [ - 'wmi', - 'os', - 'shutil', - 'uuid', - 'time', - 'multiprocessing', - '_winreg', - 'nova.virt.configdrive', - 'nova.utils', - 'ctypes' - ] + fake_get_remote_image_service) + + def fake_sleep(ms): + pass + self.stubs.Set(time, 'sleep', fake_sleep) + + self.stubs.Set(pathutils, 'PathUtils', fake.PathUtils) + self._mox.StubOutWithMock(fake.PathUtils, 'open') + + self._mox.StubOutWithMock(vmutils.VMUtils, 'vm_exists') + self._mox.StubOutWithMock(vmutils.VMUtils, 'create_vm') + self._mox.StubOutWithMock(vmutils.VMUtils, 'destroy_vm') + self._mox.StubOutWithMock(vmutils.VMUtils, 'attach_ide_drive') + self._mox.StubOutWithMock(vmutils.VMUtils, 'create_scsi_controller') + self._mox.StubOutWithMock(vmutils.VMUtils, 'create_nic') + self._mox.StubOutWithMock(vmutils.VMUtils, 'set_vm_state') + self._mox.StubOutWithMock(vmutils.VMUtils, 'list_instances') + self._mox.StubOutWithMock(vmutils.VMUtils, 'get_vm_summary_info') + self._mox.StubOutWithMock(vmutils.VMUtils, 'take_vm_snapshot') + self._mox.StubOutWithMock(vmutils.VMUtils, 'remove_vm_snapshot') + self._mox.StubOutWithMock(vmutils.VMUtils, 'set_nic_connection') + self._mox.StubOutWithMock(vmutils.VMUtils, 'get_vm_iscsi_controller') + self._mox.StubOutWithMock(vmutils.VMUtils, 'get_vm_ide_controller') + self._mox.StubOutWithMock(vmutils.VMUtils, 'get_attached_disks_count') + self._mox.StubOutWithMock(vmutils.VMUtils, + 'attach_volume_to_controller') + self._mox.StubOutWithMock(vmutils.VMUtils, + 'get_mounted_disk_by_drive_number') + self._mox.StubOutWithMock(vmutils.VMUtils, 'detach_vm_disk') + + self._mox.StubOutWithMock(vhdutils.VHDUtils, 'create_differencing_vhd') + self._mox.StubOutWithMock(vhdutils.VHDUtils, 'reconnect_parent_vhd') + self._mox.StubOutWithMock(vhdutils.VHDUtils, 'merge_vhd') + self._mox.StubOutWithMock(vhdutils.VHDUtils, 'get_vhd_parent_path') + + self._mox.StubOutWithMock(hostutils.HostUtils, 'get_cpus_info') + self._mox.StubOutWithMock(hostutils.HostUtils, + 'is_cpu_feature_present') + self._mox.StubOutWithMock(hostutils.HostUtils, 'get_memory_info') + self._mox.StubOutWithMock(hostutils.HostUtils, 'get_volume_info') + self._mox.StubOutWithMock(hostutils.HostUtils, 'get_windows_version') + + self._mox.StubOutWithMock(networkutils.NetworkUtils, + 'get_external_vswitch') + self._mox.StubOutWithMock(networkutils.NetworkUtils, + 'create_vswitch_port') + + self._mox.StubOutWithMock(livemigrationutils.LiveMigrationUtils, + 'live_migrate_vm') + self._mox.StubOutWithMock(livemigrationutils.LiveMigrationUtils, + 'check_live_migration_config') + + self._mox.StubOutWithMock(basevolumeutils.BaseVolumeUtils, + 'volume_in_mapping') + self._mox.StubOutWithMock(basevolumeutils.BaseVolumeUtils, + 'get_session_id_from_mounted_disk') + self._mox.StubOutWithMock(basevolumeutils.BaseVolumeUtils, + 'get_device_number_for_target') + + self._mox.StubOutWithMock(volumeutils.VolumeUtils, + 'login_storage_target') + self._mox.StubOutWithMock(volumeutils.VolumeUtils, + 'logout_storage_target') + self._mox.StubOutWithMock(volumeutils.VolumeUtils, + 'execute_log_out') + + self._mox.StubOutWithMock(volumeutilsv2.VolumeUtilsV2, + 'login_storage_target') + self._mox.StubOutWithMock(volumeutilsv2.VolumeUtilsV2, + 'logout_storage_target') + self._mox.StubOutWithMock(volumeutilsv2.VolumeUtilsV2, + 'execute_log_out') + + self._mox.StubOutWithMock(shutil, 'copyfile') + self._mox.StubOutWithMock(shutil, 'rmtree') + + self._mox.StubOutWithMock(os, 'remove') + + self._mox.StubOutClassWithMocks(instance_metadata, 'InstanceMetadata') + self._mox.StubOutWithMock(instance_metadata.InstanceMetadata, + 'metadata_for_config_drive') + + # Can't use StubOutClassWithMocks due to __exit__ and __enter__ + self._mox.StubOutWithMock(configdrive, 'ConfigDriveBuilder') + self._mox.StubOutWithMock(configdrive.ConfigDriveBuilder, 'make_drive') + + self._mox.StubOutWithMock(utils, 'execute') - # Modules in which the mocks are going to be injected - from nova.virt.hyperv import baseops - from nova.virt.hyperv import basevolumeutils - from nova.virt.hyperv import hostops - from nova.virt.hyperv import livemigrationops - from nova.virt.hyperv import snapshotops - from nova.virt.hyperv import vif - from nova.virt.hyperv import vmops - from nova.virt.hyperv import volumeops - from nova.virt.hyperv import volumeutils - from nova.virt.hyperv import volumeutilsV2 - - modules_to_test = [ - driver_hyperv, - basevolumeutils, - baseops, - hostops, - vif, - vmops, - vmutils, - volumeops, - volumeutils, - volumeutilsV2, - snapshotops, - livemigrationops, - hypervutils, - db_fakes, - sys.modules[__name__] - ] + def tearDown(self): + self._mox.UnsetStubs() + super(HyperVAPITestCase, self).tearDown() - self._inject_mocks_in_modules(modules_to_mock, modules_to_test) + def test_get_available_resource(self): + cpu_info = {'Architecture': 'fake', + 'Name': 'fake', + 'Manufacturer': 'ACME, Inc.', + 'NumberOfCores': 2, + 'NumberOfLogicalProcessors': 4} - if isinstance(snapshotops.wmi, mockproxy.Mock): - from nova.virt.hyperv import ioutils - import StringIO + tot_mem_kb = 2000000L + free_mem_kb = 1000000L - def fake_open(name, mode): - return StringIO.StringIO("fake file content") - self.stubs.Set(ioutils, 'open', fake_open) + tot_hdd_b = 4L * 1024 ** 3 + free_hdd_b = 3L * 1024 ** 3 - def tearDown(self): - try: - if self._instance_data and self._hypervutils.vm_exists( - self._instance_data["name"]): - self._hypervutils.remove_vm(self._instance_data["name"]) + windows_version = '6.2.9200' - if self._dest_server and \ - self._hypervutils.remote_vm_exists(self._dest_server, - self._instance_data["name"]): - self._hypervutils.remove_remote_vm(self._dest_server, - self._instance_data["name"]) + hostutils.HostUtils.get_memory_info().AndReturn((tot_mem_kb, + free_mem_kb)) - self._hypervutils.logout_iscsi_volume_sessions(self._volume_id) + m = hostutils.HostUtils.get_volume_info(mox.IsA(str)) + m.AndReturn((tot_hdd_b, free_hdd_b)) - shutil.rmtree(CONF.instances_path, True) + hostutils.HostUtils.get_cpus_info().AndReturn([cpu_info]) + m = hostutils.HostUtils.is_cpu_feature_present(mox.IsA(int)) + m.MultipleTimes() - fake_image.FakeImageService_reset() - finally: - super(HyperVAPITestCase, self).tearDown() + m = hostutils.HostUtils.get_windows_version() + m.AndReturn(windows_version) - def test_get_available_resource(self): + self._mox.ReplayAll() dic = self._conn.get_available_resource(None) + self._mox.VerifyAll() + self.assertEquals(dic['vcpus'], cpu_info['NumberOfLogicalProcessors']) self.assertEquals(dic['hypervisor_hostname'], platform.node()) + self.assertEquals(dic['memory_mb'], tot_mem_kb / 1024) + self.assertEquals(dic['memory_mb_used'], + tot_mem_kb / 1024 - free_mem_kb / 1024) + self.assertEquals(dic['local_gb'], tot_hdd_b / 1024 ** 3) + self.assertEquals(dic['local_gb_used'], + tot_hdd_b / 1024 ** 3 - free_hdd_b / 1024 ** 3) + self.assertEquals(dic['hypervisor_version'], + windows_version.replace('.', '')) def test_get_host_stats(self): + tot_mem_kb = 2000000L + free_mem_kb = 1000000L + + tot_hdd_b = 4L * 1024 ** 3 + free_hdd_b = 3L * 1024 ** 3 + + hostutils.HostUtils.get_memory_info().AndReturn((tot_mem_kb, + free_mem_kb)) + + m = hostutils.HostUtils.get_volume_info(mox.IsA(str)) + m.AndReturn((tot_hdd_b, free_hdd_b)) + + self._mox.ReplayAll() dic = self._conn.get_host_stats(True) + self._mox.VerifyAll() + + self.assertEquals(dic['disk_total'], tot_hdd_b / 1024 ** 3) + self.assertEquals(dic['disk_available'], free_hdd_b / 1024 ** 3) + + self.assertEquals(dic['host_memory_total'], tot_mem_kb / 1024) + self.assertEquals(dic['host_memory_free'], free_mem_kb / 1024) self.assertEquals(dic['disk_total'], - dic['disk_used'] + dic['disk_available']) + dic['disk_used'] + dic['disk_available']) self.assertEquals(dic['host_memory_total'], - dic['host_memory_overhead'] + dic['host_memory_free']) + dic['host_memory_overhead'] + + dic['host_memory_free']) def test_list_instances(self): - num_vms = self._hypervutils.get_vm_count() + fake_instances = ['fake1', 'fake2'] + vmutils.VMUtils.list_instances().AndReturn(fake_instances) + + self._mox.ReplayAll() instances = self._conn.list_instances() + self._mox.VerifyAll() - self.assertEquals(len(instances), num_vms) + self.assertEquals(instances, fake_instances) def test_get_info(self): - self._spawn_instance(True) + self._instance_data = self._get_instance_data() + + summary_info = {'NumberOfProcessors': 2, + 'EnabledState': constants.HYPERV_VM_STATE_ENABLED, + 'MemoryUsage': 1000, + 'UpTime': 1} + + m = vmutils.VMUtils.vm_exists(mox.Func(self._check_instance_name)) + m.AndReturn(True) + + func = mox.Func(self._check_instance_name) + m = vmutils.VMUtils.get_vm_summary_info(func) + m.AndReturn(summary_info) + + self._mox.ReplayAll() info = self._conn.get_info(self._instance_data) + self._mox.VerifyAll() self.assertEquals(info["state"], power_state.RUNNING) @@ -205,189 +309,237 @@ class HyperVAPITestCase(basetestcase.BaseTestCase): def test_spawn_no_cow_image(self): self._test_spawn_instance(False) - def test_spawn_config_drive(self): - self.skip('broken by move to contextlib for configdrive') - + def _setup_spawn_config_drive_mocks(self, use_cdrom): + im = instance_metadata.InstanceMetadata(mox.IgnoreArg(), + content=mox.IsA(list), + extra_md=mox.IsA(dict)) + + cdb = self._mox.CreateMockAnything() + m = configdrive.ConfigDriveBuilder(instance_md=mox.IgnoreArg()) + m.AndReturn(cdb) + # __enter__ and __exit__ are required by "with" + cdb.__enter__().AndReturn(cdb) + cdb.make_drive(mox.IsA(str)) + cdb.__exit__(None, None, None).AndReturn(None) + + if not use_cdrom: + utils.execute(CONF.qemu_img_cmd, + 'convert', + '-f', + 'raw', + '-O', + 'vpc', + mox.IsA(str), + mox.IsA(str), + attempts=1) + os.remove(mox.IsA(str)) + + m = vmutils.VMUtils.attach_ide_drive(mox.IsA(str), + mox.IsA(str), + mox.IsA(int), + mox.IsA(int), + mox.IsA(str)) + m.WithSideEffects(self._add_ide_disk) + + def _test_spawn_config_drive(self, use_cdrom): self.flags(force_config_drive=True) + self.flags(config_drive_cdrom=use_cdrom) self.flags(mkisofs_cmd='mkisofs.exe') - self._spawn_instance(True) + self._setup_spawn_config_drive_mocks(use_cdrom) - (vhd_paths, _, dvd_paths) = self._hypervutils.get_vm_disks( - self._instance_data["name"]) - self.assertEquals(len(dvd_paths), 0) - self.assertEquals(len(vhd_paths), 2) + if use_cdrom: + expected_ide_disks = 1 + expected_ide_dvds = 1 + else: + expected_ide_disks = 2 + expected_ide_dvds = 0 - def test_spawn_config_drive_cdrom(self): - self.skip('broken by move to contextlib for configdrive') + self._test_spawn_instance(expected_ide_disks=expected_ide_disks, + expected_ide_dvds=expected_ide_dvds) - self.flags(force_config_drive=True) - self.flags(config_drive_cdrom=True) - self.flags(mkisofs_cmd='mkisofs.exe') - - self._spawn_instance(True) + def test_spawn_config_drive(self): + self._test_spawn_config_drive(False) - (vhd_paths, _, dvd_paths) = self._hypervutils.get_vm_disks( - self._instance_data["name"]) - self.assertEquals(len(dvd_paths), 1) - self.assertEquals(len(vhd_paths), 1) - self.assertTrue(os.path.exists(dvd_paths[0])) + def test_spawn_config_drive_cdrom(self): + self._test_spawn_config_drive(True) def test_spawn_no_config_drive(self): self.flags(force_config_drive=False) - self._spawn_instance(True) + expected_ide_disks = 1 + expected_ide_dvds = 0 + + self._test_spawn_instance(expected_ide_disks=expected_ide_disks, + expected_ide_dvds=expected_ide_dvds) + + def test_spawn_nova_net_vif(self): + self.flags(network_api_class='nova.network.api.API') + # Reinstantiate driver, as the VIF plugin is loaded during __init__ + self._conn = driver_hyperv.HyperVDriver(None) + + def setup_vif_mocks(): + fake_vswitch_path = 'fake vswitch path' + fake_vswitch_port = 'fake port' + + m = networkutils.NetworkUtils.get_external_vswitch( + CONF.vswitch_name) + m.AndReturn(fake_vswitch_path) + + m = networkutils.NetworkUtils.create_vswitch_port( + fake_vswitch_path, mox.IsA(str)) + m.AndReturn(fake_vswitch_port) - (_, _, dvd_paths) = self._hypervutils.get_vm_disks( - self._instance_data["name"]) - self.assertEquals(len(dvd_paths), 0) + vmutils.VMUtils.set_nic_connection(mox.IsA(str), mox.IsA(str), + fake_vswitch_port) - def test_spawn_no_vswitch_exception(self): + self._test_spawn_instance(setup_vif_mocks_func=setup_vif_mocks) + + def test_spawn_nova_net_vif_no_vswitch_exception(self): self.flags(network_api_class='nova.network.api.API') # Reinstantiate driver, as the VIF plugin is loaded during __init__ self._conn = driver_hyperv.HyperVDriver(None) - # Set flag to a non existing vswitch - self.flags(vswitch_name=str(uuid.uuid4())) - self.assertRaises(vmutils.HyperVException, self._spawn_instance, True) - self.assertFalse(self._hypervutils.vm_exists( - self._instance_data["name"])) + def setup_vif_mocks(): + m = networkutils.NetworkUtils.get_external_vswitch( + CONF.vswitch_name) + m.AndRaise(vmutils.HyperVException(_('fake vswitch not found'))) + + self.assertRaises(vmutils.HyperVException, self._test_spawn_instance, + setup_vif_mocks_func=setup_vif_mocks, + with_exception=True) + + def _check_instance_name(self, vm_name): + return vm_name == self._instance_data['name'] def _test_vm_state_change(self, action, from_state, to_state): - self._spawn_instance(True) - if from_state: - self._hypervutils.set_vm_state(self._instance_data["name"], - from_state) - action(self._instance_data) + self._instance_data = self._get_instance_data() - vmstate = self._hypervutils.get_vm_state(self._instance_data["name"]) - self.assertEquals(vmstate, to_state) + vmutils.VMUtils.set_vm_state(mox.Func(self._check_instance_name), + to_state) + + self._mox.ReplayAll() + action(self._instance_data) + self._mox.VerifyAll() def test_pause(self): self._test_vm_state_change(self._conn.pause, None, - constants.HYPERV_VM_STATE_PAUSED) + constants.HYPERV_VM_STATE_PAUSED) def test_pause_already_paused(self): self._test_vm_state_change(self._conn.pause, - constants.HYPERV_VM_STATE_PAUSED, - constants.HYPERV_VM_STATE_PAUSED) + constants.HYPERV_VM_STATE_PAUSED, + constants.HYPERV_VM_STATE_PAUSED) def test_unpause(self): self._test_vm_state_change(self._conn.unpause, - constants.HYPERV_VM_STATE_PAUSED, - constants.HYPERV_VM_STATE_ENABLED) + constants.HYPERV_VM_STATE_PAUSED, + constants.HYPERV_VM_STATE_ENABLED) def test_unpause_already_running(self): self._test_vm_state_change(self._conn.unpause, None, - constants.HYPERV_VM_STATE_ENABLED) + constants.HYPERV_VM_STATE_ENABLED) def test_suspend(self): self._test_vm_state_change(self._conn.suspend, None, - constants.HYPERV_VM_STATE_SUSPENDED) + constants.HYPERV_VM_STATE_SUSPENDED) def test_suspend_already_suspended(self): self._test_vm_state_change(self._conn.suspend, - constants.HYPERV_VM_STATE_SUSPENDED, - constants.HYPERV_VM_STATE_SUSPENDED) + constants.HYPERV_VM_STATE_SUSPENDED, + constants.HYPERV_VM_STATE_SUSPENDED) def test_resume(self): self._test_vm_state_change(lambda i: self._conn.resume(i, None), - constants.HYPERV_VM_STATE_SUSPENDED, - constants.HYPERV_VM_STATE_ENABLED) + constants.HYPERV_VM_STATE_SUSPENDED, + constants.HYPERV_VM_STATE_ENABLED) def test_resume_already_running(self): self._test_vm_state_change(lambda i: self._conn.resume(i, None), None, - constants.HYPERV_VM_STATE_ENABLED) + constants.HYPERV_VM_STATE_ENABLED) def test_power_off(self): self._test_vm_state_change(self._conn.power_off, None, - constants.HYPERV_VM_STATE_DISABLED) + constants.HYPERV_VM_STATE_DISABLED) def test_power_off_already_powered_off(self): - self._test_vm_state_change(self._conn.suspend, - constants.HYPERV_VM_STATE_DISABLED, - constants.HYPERV_VM_STATE_DISABLED) + self._test_vm_state_change(self._conn.power_off, + constants.HYPERV_VM_STATE_DISABLED, + constants.HYPERV_VM_STATE_DISABLED) def test_power_on(self): self._test_vm_state_change(self._conn.power_on, - constants.HYPERV_VM_STATE_DISABLED, - constants.HYPERV_VM_STATE_ENABLED) + constants.HYPERV_VM_STATE_DISABLED, + constants.HYPERV_VM_STATE_ENABLED) def test_power_on_already_running(self): self._test_vm_state_change(self._conn.power_on, None, - constants.HYPERV_VM_STATE_ENABLED) + constants.HYPERV_VM_STATE_ENABLED) def test_reboot(self): - self._spawn_instance(True) network_info = fake_network.fake_get_instance_nw_info(self.stubs, spectacular=True) - self._conn.reboot(self._instance_data, network_info, None) + self._instance_data = self._get_instance_data() - vmstate = self._hypervutils.get_vm_state(self._instance_data["name"]) - self.assertEquals(vmstate, constants.HYPERV_VM_STATE_ENABLED) + vmutils.VMUtils.set_vm_state(mox.Func(self._check_instance_name), + constants.HYPERV_VM_STATE_REBOOT) + + self._mox.ReplayAll() + self._conn.reboot(self._instance_data, network_info, None) + self._mox.VerifyAll() def test_destroy(self): - self._spawn_instance(True) - (vhd_paths, _, _) = self._hypervutils.get_vm_disks( - self._instance_data["name"]) + self._instance_data = self._get_instance_data() - self._conn.destroy(self._instance_data) + m = vmutils.VMUtils.vm_exists(mox.Func(self._check_instance_name)) + m.AndReturn(True) - self.assertFalse(self._hypervutils.vm_exists( - self._instance_data["name"])) - self._instance_data = None + m = vmutils.VMUtils.destroy_vm(mox.Func(self._check_instance_name), + True) + m.AndReturn([]) - for vhd_path in vhd_paths: - self.assertFalse(os.path.exists(vhd_path)) + self._mox.ReplayAll() + self._conn.destroy(self._instance_data) + self._mox.VerifyAll() def test_live_migration(self): - self.flags(limit_cpu_features=True) - self._spawn_instance(False) + self._test_live_migration(False) - # Existing server - self._dest_server = "HV12OSDEMO2" + def test_live_migration_with_target_failure(self): + self._test_live_migration(True) - self._live_migration(self._dest_server) + def _test_live_migration(self, test_failure): + dest_server = 'fake_server' - instance_name = self._instance_data["name"] - self.assertFalse(self._hypervutils.vm_exists(instance_name)) - self.assertTrue(self._hypervutils.remote_vm_exists(self._dest_server, - instance_name)) + instance_data = self._get_instance_data() - self.assertTrue(self._post_method_called) - self.assertFalse(self._recover_method_called) + fake_post_method = self._mox.CreateMockAnything() + if not test_failure: + fake_post_method(self._context, instance_data, dest_server, + False) - def test_live_migration_with_target_failure(self): - self.flags(limit_cpu_features=True) - self._spawn_instance(False) + fake_recover_method = self._mox.CreateMockAnything() + if test_failure: + fake_recover_method(self._context, instance_data, dest_server, + False) - dest_server = "nonexistingserver" + m = livemigrationutils.LiveMigrationUtils.live_migrate_vm( + instance_data['name'], dest_server) + if test_failure: + m.AndRaise(Exception('Simulated failure')) - exception_raised = False + self._mox.ReplayAll() try: - self._live_migration(dest_server) + self._conn.live_migration(self._context, instance_data, + dest_server, fake_post_method, + fake_recover_method) + exception_raised = False except Exception: exception_raised = True - # Cannot use assertRaises with pythoncom.com_error on Linux - self.assertTrue(exception_raised) - - instance_name = self._instance_data["name"] - self.assertTrue(self._hypervutils.vm_exists(instance_name)) - - self.assertFalse(self._post_method_called) - self.assertTrue(self._recover_method_called) - - def _live_migration(self, dest_server): - def fake_post_method(context, instance_ref, dest, block_migration): - self._post_method_called = True - - def fake_recover_method(context, instance_ref, dest, block_migration): - self._recover_method_called = True - - self._conn.live_migration(self._context, self._instance_data, - dest_server, fake_post_method, fake_recover_method) + self.assertTrue(not test_failure ^ exception_raised) + self._mox.VerifyAll() def test_pre_live_migration_cow_image(self): self._test_pre_live_migration(True) @@ -398,83 +550,134 @@ class HyperVAPITestCase(basetestcase.BaseTestCase): def _test_pre_live_migration(self, cow): self.flags(use_cow_images=cow) - instance_name = 'openstack_unit_test_vm_' + str(uuid.uuid4()) + instance_data = self._get_instance_data() network_info = fake_network.fake_get_instance_nw_info(self.stubs, spectacular=True) - instance_data = db_fakes.get_fake_instance_data(instance_name, - self._project_id, self._user_id) - block_device_info = None + m = livemigrationutils.LiveMigrationUtils.check_live_migration_config() + m.AndReturn(True) + + if cow: + m = basevolumeutils.BaseVolumeUtils.volume_in_mapping(mox.IsA(str), + None) + m.AndReturn([]) + + self._mox.ReplayAll() self._conn.pre_live_migration(self._context, instance_data, - block_device_info, network_info) + None, network_info) + self._mox.VerifyAll() if cow: - self.assertTrue(not self._fetched_image is None) + self.assertTrue(self._fetched_image is not None) else: self.assertTrue(self._fetched_image is None) def test_snapshot_with_update_failure(self): - expected_calls = [ - {'args': (), - 'kwargs': - {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, - {'args': (), - 'kwargs': - {'task_state': task_states.IMAGE_UPLOADING, - 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] - func_call_matcher = matchers.FunctionCallMatcher(expected_calls) - - self._spawn_instance(True) + (snapshot_name, func_call_matcher) = self._setup_snapshot_mocks() self._update_image_raise_exception = True - snapshot_name = 'test_snapshot_' + str(uuid.uuid4()) + + self._mox.ReplayAll() self.assertRaises(vmutils.HyperVException, self._conn.snapshot, self._context, self._instance_data, snapshot_name, func_call_matcher.call) + self._mox.VerifyAll() - # assert states changed in correct order + # Assert states changed in correct order self.assertIsNone(func_call_matcher.match()) - # assert VM snapshots have been removed - self.assertEquals(self._hypervutils.get_vm_snapshots_count( - self._instance_data["name"]), 0) - - def test_snapshot(self): + def _setup_snapshot_mocks(self): expected_calls = [ {'args': (), - 'kwargs': - {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, - {'args': (), - 'kwargs': - {'task_state': task_states.IMAGE_UPLOADING, - 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] + 'kwargs': {'task_state': task_states.IMAGE_PENDING_UPLOAD}}, + {'args': (), + 'kwargs': {'task_state': task_states.IMAGE_UPLOADING, + 'expected_state': task_states.IMAGE_PENDING_UPLOAD}} + ] func_call_matcher = matchers.FunctionCallMatcher(expected_calls) - self._spawn_instance(True) - snapshot_name = 'test_snapshot_' + str(uuid.uuid4()) + + fake_hv_snapshot_path = 'fake_snapshot_path' + fake_parent_vhd_path = 'C:\\fake_vhd_path\\parent.vhd' + + self._instance_data = self._get_instance_data() + + func = mox.Func(self._check_instance_name) + m = vmutils.VMUtils.take_vm_snapshot(func) + m.AndReturn(fake_hv_snapshot_path) + + m = vhdutils.VHDUtils.get_vhd_parent_path(mox.IsA(str)) + m.AndReturn(fake_parent_vhd_path) + + self._fake_dest_disk_path = None + + def copy_dest_disk_path(src, dest): + self._fake_dest_disk_path = dest + + m = shutil.copyfile(mox.IsA(str), mox.IsA(str)) + m.WithSideEffects(copy_dest_disk_path) + + self._fake_dest_base_disk_path = None + + def copy_dest_base_disk_path(src, dest): + self._fake_dest_base_disk_path = dest + + m = shutil.copyfile(fake_parent_vhd_path, mox.IsA(str)) + m.WithSideEffects(copy_dest_base_disk_path) + + def check_dest_disk_path(path): + return path == self._fake_dest_disk_path + + def check_dest_base_disk_path(path): + return path == self._fake_dest_base_disk_path + + func1 = mox.Func(check_dest_disk_path) + func2 = mox.Func(check_dest_base_disk_path) + # Make sure that the hyper-v base and differential VHDs are merged + vhdutils.VHDUtils.reconnect_parent_vhd(func1, func2) + vhdutils.VHDUtils.merge_vhd(func1, func2) + + def check_snapshot_path(snapshot_path): + return snapshot_path == fake_hv_snapshot_path + + # Make sure that the Hyper-V snapshot is removed + func = mox.Func(check_snapshot_path) + vmutils.VMUtils.remove_vm_snapshot(func) + + shutil.rmtree(mox.IsA(str)) + + m = fake.PathUtils.open(func2, 'rb') + m.AndReturn(io.BytesIO(b'fake content')) + + return (snapshot_name, func_call_matcher) + + def test_snapshot(self): + (snapshot_name, func_call_matcher) = self._setup_snapshot_mocks() + + self._mox.ReplayAll() self._conn.snapshot(self._context, self._instance_data, snapshot_name, func_call_matcher.call) + self._mox.VerifyAll() self.assertTrue(self._image_metadata and - "disk_format" in self._image_metadata and - self._image_metadata["disk_format"] == "vhd") + "disk_format" in self._image_metadata and + self._image_metadata["disk_format"] == "vhd") - # assert states changed in correct order + # Assert states changed in correct order self.assertIsNone(func_call_matcher.match()) - # assert VM snapshots have been removed - self.assertEquals(self._hypervutils.get_vm_snapshots_count( - self._instance_data["name"]), 0) + def _get_instance_data(self): + instance_name = 'openstack_unit_test_vm_' + str(uuid.uuid4()) + return db_fakes.get_fake_instance_data(instance_name, + self._project_id, + self._user_id) def _spawn_instance(self, cow, block_device_info=None): self.flags(use_cow_images=cow) - instance_name = 'openstack_unit_test_vm_' + str(uuid.uuid4()) - - self._instance_data = db_fakes.get_fake_instance_data(instance_name, - self._project_id, self._user_id) + self._instance_data = self._get_instance_data() instance = db.instance_create(self._context, self._instance_data) image = db_fakes.get_fake_image_data(self._project_id, self._user_id) @@ -487,73 +690,216 @@ class HyperVAPITestCase(basetestcase.BaseTestCase): network_info=network_info, block_device_info=block_device_info) - def _test_spawn_instance(self, cow): - self._spawn_instance(cow) + def _add_ide_disk(self, vm_name, path, ctrller_addr, + drive_addr, drive_type): + if drive_type == constants.IDE_DISK: + self._instance_ide_disks.append(path) + elif drive_type == constants.IDE_DVD: + self._instance_ide_dvds.append(path) + + def _add_volume_disk(self, vm_name, controller_path, address, + mounted_disk_path): + self._instance_volume_disks.append(mounted_disk_path) - self.assertTrue(self._hypervutils.vm_exists( - self._instance_data["name"])) + def _setup_spawn_instance_mocks(self, cow, setup_vif_mocks_func=None, + with_exception=False, + block_device_info=None): + self._test_vm_name = None - vmstate = self._hypervutils.get_vm_state(self._instance_data["name"]) - self.assertEquals(vmstate, constants.HYPERV_VM_STATE_ENABLED) + def set_vm_name(vm_name): + self._test_vm_name = vm_name - (vhd_paths, _, _) = self._hypervutils.get_vm_disks( - self._instance_data["name"]) - self.assertEquals(len(vhd_paths), 1) + def check_vm_name(vm_name): + return vm_name == self._test_vm_name + + m = vmutils.VMUtils.vm_exists(mox.IsA(str)) + m.WithSideEffects(set_vm_name).AndReturn(False) + + if not block_device_info: + m = basevolumeutils.BaseVolumeUtils.volume_in_mapping(mox.IsA(str), + None) + m.AndReturn([]) + else: + m = basevolumeutils.BaseVolumeUtils.volume_in_mapping( + mox.IsA(str), block_device_info) + m.AndReturn(True) - parent_path = self._hypervutils.get_vhd_parent_path(vhd_paths[0]) if cow: - self.assertTrue(not parent_path is None) - self.assertEquals(self._fetched_image, parent_path) + def check_path(parent_path): + return parent_path == self._fetched_image + + vhdutils.VHDUtils.create_differencing_vhd(mox.IsA(str), + mox.Func(check_path)) + + vmutils.VMUtils.create_vm(mox.Func(check_vm_name), mox.IsA(int), + mox.IsA(int), mox.IsA(bool)) + + if not block_device_info: + m = vmutils.VMUtils.attach_ide_drive(mox.Func(check_vm_name), + mox.IsA(str), + mox.IsA(int), + mox.IsA(int), + mox.IsA(str)) + m.WithSideEffects(self._add_ide_disk).InAnyOrder() + + m = vmutils.VMUtils.create_scsi_controller(mox.Func(check_vm_name)) + m.InAnyOrder() + + vmutils.VMUtils.create_nic(mox.Func(check_vm_name), mox.IsA(str), + mox.IsA(str)).InAnyOrder() + + if setup_vif_mocks_func: + setup_vif_mocks_func() + + # TODO(alexpilotti) Based on where the exception is thrown + # some of the above mock calls need to be skipped + if with_exception: + m = vmutils.VMUtils.vm_exists(mox.Func(check_vm_name)) + m.AndReturn(True) + + vmutils.VMUtils.destroy_vm(mox.Func(check_vm_name), True) else: - self.assertTrue(parent_path is None) - self.assertEquals(self._fetched_image, vhd_paths[0]) + vmutils.VMUtils.set_vm_state(mox.Func(check_vm_name), + constants.HYPERV_VM_STATE_ENABLED) + + def _test_spawn_instance(self, cow=True, + expected_ide_disks=1, + expected_ide_dvds=0, + setup_vif_mocks_func=None, + with_exception=False): + self._setup_spawn_instance_mocks(cow, setup_vif_mocks_func, + with_exception) + + self._mox.ReplayAll() + self._spawn_instance(cow, ) + self._mox.VerifyAll() + + self.assertEquals(len(self._instance_ide_disks), expected_ide_disks) + self.assertEquals(len(self._instance_ide_dvds), expected_ide_dvds) + + if not cow: + self.assertEquals(self._fetched_image, self._instance_ide_disks[0]) + + def test_attach_volume(self): + instance_data = self._get_instance_data() + instance_name = instance_data['name'] - def _attach_volume(self): - self._spawn_instance(True) connection_info = db_fakes.get_fake_volume_info_data( self._volume_target_portal, self._volume_id) + data = connection_info['data'] + target_lun = data['target_lun'] + target_iqn = data['target_iqn'] + target_portal = data['target_portal'] - self._conn.attach_volume(connection_info, - self._instance_data, '/dev/sdc') + mount_point = '/dev/sdc' - def test_attach_volume(self): - self._attach_volume() + volumeutils.VolumeUtils.login_storage_target(target_lun, + target_iqn, + target_portal) + + fake_mounted_disk = "fake_mounted_disk" + fake_device_number = 0 + fake_controller_path = 'fake_scsi_controller_path' + fake_free_slot = 1 + + m = volumeutils.VolumeUtils.get_device_number_for_target(target_iqn, + target_lun) + m.AndReturn(fake_device_number) + + m = vmutils.VMUtils.get_mounted_disk_by_drive_number( + fake_device_number) + m.AndReturn(fake_mounted_disk) - (_, volumes_paths, _) = self._hypervutils.get_vm_disks( - self._instance_data["name"]) - self.assertEquals(len(volumes_paths), 1) + m = vmutils.VMUtils.get_vm_iscsi_controller(instance_name) + m.AndReturn(fake_controller_path) - sessions_exist = self._hypervutils.iscsi_volume_sessions_exist( - self._volume_id) - self.assertTrue(sessions_exist) + m = vmutils.VMUtils.get_attached_disks_count(fake_controller_path) + m.AndReturn(fake_free_slot) + + m = vmutils.VMUtils.attach_volume_to_controller(instance_name, + fake_controller_path, + fake_free_slot, + fake_mounted_disk) + m.WithSideEffects(self._add_volume_disk) + + self._mox.ReplayAll() + self._conn.attach_volume(connection_info, instance_data, mount_point) + self._mox.VerifyAll() + + self.assertEquals(len(self._instance_volume_disks), 1) def test_detach_volume(self): - self._attach_volume() + instance_data = self._get_instance_data() + instance_name = instance_data['name'] + connection_info = db_fakes.get_fake_volume_info_data( self._volume_target_portal, self._volume_id) + data = connection_info['data'] + target_lun = data['target_lun'] + target_iqn = data['target_iqn'] + target_portal = data['target_portal'] + mount_point = '/dev/sdc' - self._conn.detach_volume(connection_info, - self._instance_data, '/dev/sdc') + fake_mounted_disk = "fake_mounted_disk" + fake_device_number = 0 + fake_free_slot = 1 + m = volumeutils.VolumeUtils.get_device_number_for_target(target_iqn, + target_lun) + m.AndReturn(fake_device_number) - (_, volumes_paths, _) = self._hypervutils.get_vm_disks( - self._instance_data["name"]) - self.assertEquals(len(volumes_paths), 0) + m = vmutils.VMUtils.get_mounted_disk_by_drive_number( + fake_device_number) + m.AndReturn(fake_mounted_disk) - sessions_exist = self._hypervutils.iscsi_volume_sessions_exist( - self._volume_id) - self.assertFalse(sessions_exist) + vmutils.VMUtils.detach_vm_disk(mox.IsA(str), fake_mounted_disk) + + volumeutils.VolumeUtils.logout_storage_target(mox.IsA(str)) + + self._mox.ReplayAll() + self._conn.detach_volume(connection_info, instance_data, mount_point) + self._mox.VerifyAll() def test_boot_from_volume(self): + connection_info = db_fakes.get_fake_volume_info_data( + self._volume_target_portal, self._volume_id) + data = connection_info['data'] + target_lun = data['target_lun'] + target_iqn = data['target_iqn'] + target_portal = data['target_portal'] + block_device_info = db_fakes.get_fake_block_device_info( self._volume_target_portal, self._volume_id) - self._spawn_instance(False, block_device_info) + fake_mounted_disk = "fake_mounted_disk" + fake_device_number = 0 + fake_controller_path = 'fake_scsi_controller_path' + + volumeutils.VolumeUtils.login_storage_target(target_lun, + target_iqn, + target_portal) + + m = volumeutils.VolumeUtils.get_device_number_for_target(target_iqn, + target_lun) + m.AndReturn(fake_device_number) - (_, volumes_paths, _) = self._hypervutils.get_vm_disks( - self._instance_data["name"]) + m = vmutils.VMUtils.get_mounted_disk_by_drive_number( + fake_device_number) + m.AndReturn(fake_mounted_disk) - self.assertEquals(len(volumes_paths), 1) + m = vmutils.VMUtils.get_vm_ide_controller(mox.IsA(str), mox.IsA(int)) + m.AndReturn(fake_controller_path) + + m = vmutils.VMUtils.attach_volume_to_controller(mox.IsA(str), + fake_controller_path, + 0, + fake_mounted_disk) + m.WithSideEffects(self._add_volume_disk) + + self._setup_spawn_instance_mocks(cow=False, + block_device_info=block_device_info) + + self._mox.ReplayAll() + self._spawn_instance(False, block_device_info) + self._mox.VerifyAll() - sessions_exist = self._hypervutils.iscsi_volume_sessions_exist( - self._volume_id) - self.assertTrue(sessions_exist) + self.assertEquals(len(self._instance_volume_disks), 1) 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..611519514 100644 --- a/nova/tests/test_imagecache.py +++ b/nova/tests/test_imagecache.py @@ -331,7 +331,6 @@ class ImageCacheManagerTestCase(test.TestCase): base_file1 = os.path.join(base_dir, fingerprint) base_file2 = os.path.join(base_dir, fingerprint + '_sm') base_file3 = os.path.join(base_dir, fingerprint + '_10737418240') - print res self.assertTrue(res == [(base_file1, False, False), (base_file2, True, False), (base_file3, False, True)]) @@ -721,7 +720,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 +746,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_instance_types.py b/nova/tests/test_instance_types.py index 4a136cf13..b70b96b7f 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -142,6 +142,67 @@ class InstanceTypeTestCase(test.TestCase): self.assertRaises(exception.InvalidInput, instance_types.create, name, 256, 1, 120, 100, flavorid) + def test_add_instance_type_access(self): + user_id = 'fake' + project_id = 'fake' + ctxt = context.RequestContext(user_id, project_id, is_admin=True) + flavor_id = 'flavor1' + type_ref = instance_types.create('some flavor', 256, 1, 120, 100, + flavorid=flavor_id) + access_ref = instance_types.add_instance_type_access(flavor_id, + project_id, + ctxt=ctxt) + self.assertEqual(access_ref["project_id"], project_id) + self.assertEqual(access_ref["instance_type_id"], type_ref["id"]) + + def test_add_instance_type_access_already_exists(self): + user_id = 'fake' + project_id = 'fake' + ctxt = context.RequestContext(user_id, project_id, is_admin=True) + flavor_id = 'flavor1' + type_ref = instance_types.create('some flavor', 256, 1, 120, 100, + flavorid=flavor_id) + access_ref = instance_types.add_instance_type_access(flavor_id, + project_id, + ctxt=ctxt) + self.assertRaises(exception.FlavorAccessExists, + instance_types.add_instance_type_access, + flavor_id, project_id, ctxt) + + def test_add_instance_type_access_invalid_flavor(self): + user_id = 'fake' + project_id = 'fake' + ctxt = context.RequestContext(user_id, project_id, is_admin=True) + flavor_id = 'no_such_flavor' + self.assertRaises(exception.FlavorNotFound, + instance_types.add_instance_type_access, + flavor_id, project_id, ctxt) + + def test_remove_instance_type_access(self): + user_id = 'fake' + project_id = 'fake' + ctxt = context.RequestContext(user_id, project_id, is_admin=True) + flavor_id = 'flavor1' + it = instance_types + type_ref = it.create('some flavor', 256, 1, 120, 100, + flavorid=flavor_id) + access_ref = it.add_instance_type_access(flavor_id, project_id, ctxt) + it.remove_instance_type_access(flavor_id, project_id, ctxt) + + projects = it.get_instance_type_access_by_flavor_id(flavor_id, ctxt) + self.assertEqual([], projects) + + def test_remove_instance_type_access_doesnt_exists(self): + user_id = 'fake' + project_id = 'fake' + ctxt = context.RequestContext(user_id, project_id, is_admin=True) + flavor_id = 'flavor1' + type_ref = instance_types.create('some flavor', 256, 1, 120, 100, + flavorid=flavor_id) + self.assertRaises(exception.FlavorAccessNotFound, + instance_types.remove_instance_type_access, + flavor_id, project_id, ctxt=ctxt) + def test_get_all_instance_types(self): # Ensures that all instance types can be retrieved. session = sql_session.get_session() diff --git a/nova/tests/test_instance_types_extra_specs.py b/nova/tests/test_instance_types_extra_specs.py index f53840b86..f48c2efe8 100644 --- a/nova/tests/test_instance_types_extra_specs.py +++ b/nova/tests/test_instance_types_extra_specs.py @@ -18,6 +18,7 @@ Unit Tests for instance types extra specs code from nova import context from nova import db +from nova import exception from nova import test @@ -87,6 +88,13 @@ class InstanceTypeExtraSpecsTestCase(test.TestCase): self.flavorid) self.assertEquals(expected_specs, actual_specs) + def test_instance_type_extra_specs_update_with_nonexisting_flavor(self): + extra_specs = dict(cpu_arch="x86_64") + nonexisting_flavorid = "some_flavor_that_doesnt_exists" + self.assertRaises(exception.FlavorNotFound, + db.instance_type_extra_specs_update_or_create, + self.context, nonexisting_flavorid, extra_specs) + def test_instance_type_extra_specs_create(self): expected_specs = dict(cpu_arch="x86_64", cpu_model="Nehalem", diff --git a/nova/tests/test_iptables_network.py b/nova/tests/test_iptables_network.py index c8f310303..95af25ebd 100644 --- a/nova/tests/test_iptables_network.py +++ b/nova/tests/test_iptables_network.py @@ -170,3 +170,22 @@ class IptablesManagerTestCase(test.TestCase): self.assertTrue('[0:0] -A %s -j %s-%s' % (chain, self.binary_name, chain) in new_lines, "Built-in chain %s not wrapped" % (chain,)) + + def test_missing_table(self): + current_lines = [] + new_lines = self.manager._modify_rules(current_lines, + self.manager.ipv4['filter'], + table_name='filter') + + for line in ['*filter', + 'COMMIT']: + self.assertTrue(line in new_lines, "One of iptables key lines" + "went missing.") + + self.assertTrue(len(new_lines) > 4, "No iptables rules added") + + self.assertTrue("#Generated by nova" == new_lines[0] and + "*filter" == new_lines[1] and + "COMMIT" == new_lines[-2] and + "#Completed by nova" == new_lines[-1], + "iptables rules not generated in the correct order") diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 0abf16801..06db4f5ff 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)) @@ -769,6 +768,20 @@ class LibvirtConnTestCase(test.TestCase): vconfig.LibvirtConfigGuestDisk) self.assertEquals(cfg.devices[3].target_dev, 'vdd') + def test_get_guest_config_with_configdrive(self): + conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + instance_ref = db.instance_create(self.context, self.test_instance) + + # make configdrive.enabled_for() return True + instance_ref['config_drive'] = 'ANY_ID' + + cfg = conn.get_guest_config(instance_ref, [], None, None) + + self.assertEquals(type(cfg.devices[2]), + vconfig.LibvirtConfigGuestDisk) + self.assertEquals(cfg.devices[2].target_dev, + conn.default_last_device) + def test_get_guest_config_with_vnc(self): self.flags(libvirt_type='kvm', vnc_enabled=True, @@ -2854,11 +2867,11 @@ class LibvirtConnTestCase(test.TestCase): conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) self.mox.StubOutWithMock(conn, "_wrapped_conn") - self.mox.StubOutWithMock(conn._wrapped_conn, "getCapabilities") + self.mox.StubOutWithMock(conn._wrapped_conn, "getLibVersion") self.mox.StubOutWithMock(libvirt.libvirtError, "get_error_code") self.mox.StubOutWithMock(libvirt.libvirtError, "get_error_domain") - conn._wrapped_conn.getCapabilities().AndRaise( + conn._wrapped_conn.getLibVersion().AndRaise( libvirt.libvirtError("fake failure")) libvirt.libvirtError.get_error_code().AndReturn(error) @@ -2870,42 +2883,6 @@ class LibvirtConnTestCase(test.TestCase): self.mox.UnsetStubs() - def test_volume_in_mapping(self): - conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - swap = {'device_name': '/dev/sdb', - 'swap_size': 1} - ephemerals = [{'num': 0, - 'virtual_name': 'ephemeral0', - 'device_name': '/dev/sdc1', - 'size': 1}, - {'num': 2, - 'virtual_name': 'ephemeral2', - 'device_name': '/dev/sdd', - 'size': 1}] - block_device_mapping = [{'mount_device': '/dev/sde', - 'device_path': 'fake_device'}, - {'mount_device': '/dev/sdf', - 'device_path': 'fake_device'}] - block_device_info = { - 'root_device_name': '/dev/sda', - 'swap': swap, - 'ephemerals': ephemerals, - 'block_device_mapping': block_device_mapping} - - def _assert_volume_in_mapping(device_name, true_or_false): - self.assertEquals(conn._volume_in_mapping(device_name, - block_device_info), - true_or_false) - - _assert_volume_in_mapping('sda', False) - _assert_volume_in_mapping('sdb', True) - _assert_volume_in_mapping('sdc1', True) - _assert_volume_in_mapping('sdd', True) - _assert_volume_in_mapping('sde', True) - _assert_volume_in_mapping('sdf', True) - _assert_volume_in_mapping('sdg', False) - _assert_volume_in_mapping('sdh1', False) - def test_immediate_delete(self): def fake_lookup_by_name(instance_name): raise exception.InstanceNotFound(instance_id=instance_name) @@ -3086,7 +3063,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): @@ -3693,30 +3670,25 @@ class IptablesFirewallTestCase(test.TestCase): fake.FakeVirtAPI(), get_connection=lambda: self.fake_libvirt_connection) - in_nat_rules = [ + in_rules = [ '# Generated by iptables-save v1.4.10 on Sat Feb 19 00:03:19 2011', '*nat', ':PREROUTING ACCEPT [1170:189210]', ':INPUT ACCEPT [844:71028]', ':OUTPUT ACCEPT [5149:405186]', ':POSTROUTING ACCEPT [5063:386098]', - ] - - in_mangle_rules = [ - '# Generated by iptables-save v1.4.12 on Tue Dec 18 15:50:25 201;', - '*mangle', - ':PREROUTING ACCEPT [241:39722]', - ':INPUT ACCEPT [230:39282]', - ':FORWARD ACCEPT [0:0]', - ':OUTPUT ACCEPT [266:26558]', - ':POSTROUTING ACCEPT [267:26590]', - '-A POSTROUTING -o virbr0 -p udp -m udp --dport 68 -j CHECKSUM ' - '--checksum-fill', - 'COMMIT', - '# Completed on Tue Dec 18 15:50:25 2012', - ] - - in_filter_rules = [ + '# Completed on Tue Dec 18 15:50:25 2012', + '# Generated by iptables-save v1.4.12 on Tue Dec 18 15:50:25 201;', + '*mangle', + ':PREROUTING ACCEPT [241:39722]', + ':INPUT ACCEPT [230:39282]', + ':FORWARD ACCEPT [0:0]', + ':OUTPUT ACCEPT [266:26558]', + ':POSTROUTING ACCEPT [267:26590]', + '-A POSTROUTING -o virbr0 -p udp -m udp --dport 68 -j CHECKSUM ' + '--checksum-fill', + 'COMMIT', + '# Completed on Tue Dec 18 15:50:25 2012', '# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010', '*filter', ':INPUT ACCEPT [969615:281627771]', @@ -3811,15 +3783,11 @@ class IptablesFirewallTestCase(test.TestCase): # self.fw.add_instance(instance_ref) def fake_iptables_execute(*cmd, **kwargs): process_input = kwargs.get('process_input', None) - if cmd == ('ip6tables-save', '-c', '-t', 'filter'): + if cmd == ('ip6tables-save', '-c'): return '\n'.join(self.in6_filter_rules), None - if cmd == ('iptables-save', '-c', '-t', 'filter'): - return '\n'.join(self.in_filter_rules), None - if cmd == ('iptables-save', '-c', '-t', 'nat'): - return '\n'.join(self.in_nat_rules), None - if cmd == ('iptables-save', '-c', '-t', 'mangle'): - return '\n'.join(self.in_mangle_rules), None - if cmd == ('iptables-restore', '-c',): + if cmd == ('iptables-save', '-c'): + return '\n'.join(self.in_rules), None + if cmd == ('iptables-restore', '-c'): lines = process_input.split('\n') if '*filter' in lines: self.out_rules = lines @@ -3829,7 +3797,6 @@ class IptablesFirewallTestCase(test.TestCase): if '*filter' in lines: self.out6_rules = lines return '', '' - print cmd, kwargs network_model = _fake_network_info(self.stubs, 1, spectacular=True) @@ -3843,9 +3810,9 @@ class IptablesFirewallTestCase(test.TestCase): self.fw.apply_instance_filter(instance_ref, network_info) in_rules = filter(lambda l: not l.startswith('#'), - self.in_filter_rules) + self.in_rules) for rule in in_rules: - if not 'nova' in rule: + if 'nova' not in rule: self.assertTrue(rule in self.out_rules, 'Rule went missing: %s' % rule) @@ -4246,7 +4213,7 @@ class LibvirtUtilsTestCase(test.TestCase): def test_pick_disk_driver_name(self): type_map = {'kvm': ([True, 'qemu'], [False, 'qemu'], [None, 'qemu']), 'qemu': ([True, 'qemu'], [False, 'qemu'], [None, 'qemu']), - 'xen': ([True, 'phy'], [False, 'file'], [None, 'file']), + 'xen': ([True, 'phy'], [False, 'tap'], [None, 'tap']), 'uml': ([True, None], [False, None], [None, None]), 'lxc': ([True, None], [False, None], [None, None])} @@ -4590,7 +4557,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): @@ -4743,6 +4710,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..7ce81cc09 100644 --- a/nova/tests/test_libvirt_vif.py +++ b/nova/tests/test_libvirt_vif.py @@ -16,6 +16,8 @@ from lxml import etree +from nova import exception +from nova.network import model as network_model from nova.openstack.common import cfg from nova import test from nova import utils @@ -27,7 +29,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,13 +44,47 @@ 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' + 'vif_devname': 'tap-xxx-yyy-zzz', + 'vif_type': network_model.VIF_TYPE_BRIDGE, + } + + 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', + } + + mapping_none = { + 'mac': 'ca:fe:de:ad:be:ef', + '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', } instance = { @@ -67,7 +103,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 +111,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() @@ -125,8 +161,10 @@ class LibvirtVifTestCase(test.TestCase): self.flags(libvirt_use_virtio_for_bridges=False, libvirt_type='kvm') - d = vif.LibvirtBridgeDriver() - xml = self._get_instance_xml(d) + d = vif.LibvirtGenericVIFDriver() + xml = self._get_instance_xml(d, + self.net_bridge, + self.mapping_bridge) doc = etree.fromstring(xml) ret = doc.findall('./devices/interface') @@ -142,8 +180,10 @@ class LibvirtVifTestCase(test.TestCase): self.flags(libvirt_use_virtio_for_bridges=True, libvirt_type='kvm') - d = vif.LibvirtBridgeDriver() - xml = self._get_instance_xml(d) + d = vif.LibvirtGenericVIFDriver() + xml = self._get_instance_xml(d, + self.net_bridge, + self.mapping_bridge) doc = etree.fromstring(xml) ret = doc.findall('./devices/interface') @@ -159,8 +199,10 @@ class LibvirtVifTestCase(test.TestCase): self.flags(libvirt_use_virtio_for_bridges=True, libvirt_type='qemu') - d = vif.LibvirtBridgeDriver() - xml = self._get_instance_xml(d) + d = vif.LibvirtGenericVIFDriver() + xml = self._get_instance_xml(d, + self.net_bridge, + self.mapping_bridge) doc = etree.fromstring(xml) ret = doc.findall('./devices/interface') @@ -176,8 +218,10 @@ class LibvirtVifTestCase(test.TestCase): self.flags(libvirt_use_virtio_for_bridges=True, libvirt_type='xen') - d = vif.LibvirtBridgeDriver() - xml = self._get_instance_xml(d) + d = vif.LibvirtGenericVIFDriver() + xml = self._get_instance_xml(d, + self.net_bridge, + self.mapping_bridge) doc = etree.fromstring(xml) ret = doc.findall('./devices/interface') @@ -189,9 +233,18 @@ class LibvirtVifTestCase(test.TestCase): ret = node.findall("driver") self.assertEqual(len(ret), 0) - def test_bridge_driver(self): - d = vif.LibvirtBridgeDriver() - xml = self._get_instance_xml(d) + def test_generic_driver_none(self): + d = vif.LibvirtGenericVIFDriver() + self.assertRaises(exception.NovaException, + self._get_instance_xml, + d, + self.net_bridge, + self.mapping_none) + + def _check_bridge_driver(self, d): + xml = self._get_instance_xml(d, + self.net_bridge, + self.mapping_bridge) doc = etree.fromstring(xml) ret = doc.findall('./devices/interface') @@ -199,13 +252,23 @@ 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_bridge_driver(self): + d = vif.LibvirtBridgeDriver() + self._check_bridge_driver(d) + + def test_generic_driver_bridge(self): + d = vif.LibvirtGenericVIFDriver() + self._check_bridge_driver(d) 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 +278,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 +297,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 +324,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 +340,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 750326592..8c18d5511 100644 --- a/nova/tests/test_migrations.py +++ b/nova/tests/test_migrations.py @@ -24,6 +24,7 @@ properly both upgrading and downgrading, and that no data loss occurs if possible. """ +import collections import commands import ConfigParser import os @@ -42,40 +43,61 @@ from nova import test LOG = logging.getLogger(__name__) -def _mysql_get_connect_string(user="openstack_citest", - passwd="openstack_citest", - database="openstack_citest"): +def _get_connect_string(backend, + user="openstack_citest", + passwd="openstack_citest", + database="openstack_citest"): """ - Try to get a connection with a very specfic set of values, if we get - these then we'll run the mysql tests, otherwise they are skipped + 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 """ - return "mysql://%(user)s:%(passwd)s@localhost/%(database)s" % locals() + if backend == "postgres": + backend = "postgresql+psycopg2" + return ("%(backend)s://%(user)s:%(passwd)s@localhost/%(database)s" + % locals()) -def _is_mysql_avail(user="openstack_citest", - passwd="openstack_citest", - database="openstack_citest"): + +def _is_backend_avail(backend, + user="openstack_citest", + passwd="openstack_citest", + database="openstack_citest"): try: - connect_uri = _mysql_get_connect_string( - user=user, passwd=passwd, database=database) + if backend == "mysql": + connect_uri = _get_connect_string("mysql", + user=user, passwd=passwd, database=database) + elif backend == "postgres": + connect_uri = _get_connect_string("postgres", + user=user, passwd=passwd, database=database) engine = sqlalchemy.create_engine(connect_uri) connection = engine.connect() except Exception: # intentionally catch all to handle exceptions even if we don't - # have mysql code loaded at all. + # have any backend code loaded. return False else: connection.close() + engine.dispose() return True def _have_mysql(): present = os.environ.get('NOVA_TEST_MYSQL_PRESENT') if present is None: - return _is_mysql_avail() + return _is_backend_avail('mysql') return present.lower() in ('', 'true') +def get_table(engine, name): + """Returns an sqlalchemy table dynamically from db. + + Needed because the models don't work for us in migrations + as models will be far out of sync with the current data.""" + metadata = sqlalchemy.schema.MetaData() + metadata.bind = engine + return sqlalchemy.Table(name, metadata, autoload=True) + + class TestMigrations(test.TestCase): """Test sqlalchemy-migrate migrations.""" @@ -121,17 +143,10 @@ class TestMigrations(test.TestCase): self._reset_databases() def tearDown(self): - # We destroy the test data store between each test case, # 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): @@ -142,6 +157,7 @@ class TestMigrations(test.TestCase): for key, engine in self.engines.items(): conn_string = self.test_databases[key] conn_pieces = urlparse.urlparse(conn_string) + engine.dispose() if conn_string.startswith('sqlite'): # We can just delete the SQLite database, which is # the easiest and cleanest solution @@ -172,6 +188,7 @@ class TestMigrations(test.TestCase): database = conn_pieces.path.strip('/') loc_pieces = conn_pieces.netloc.split('@') host = loc_pieces[1] + auth_pieces = loc_pieces[0].split(':') user = auth_pieces[0] password = "" @@ -183,7 +200,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") @@ -207,16 +224,16 @@ class TestMigrations(test.TestCase): Test that we can trigger a mysql connection failure and we fail gracefully to ensure we don't break people without mysql """ - if _is_mysql_avail(user="openstack_cifail"): + 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 _have_mysql(): + if not _is_backend_avail('mysql'): self.skipTest("mysql not available") # add this to the global lists to make reset work with it, it's removed # automatically in tearDown so no need to clean it up here. - connect_string = _mysql_get_connect_string() + connect_string = _get_connect_string("mysql") engine = sqlalchemy.create_engine(connect_string) self.engines["mysqlcitest"] = engine self.test_databases["mysqlcitest"] = connect_string @@ -225,9 +242,7 @@ class TestMigrations(test.TestCase): self._reset_databases() self._walk_versions(engine, False, False) - uri = _mysql_get_connect_string(database="information_schema") - connection = sqlalchemy.create_engine(uri).connect() - + connection = engine.connect() # sanity check total = connection.execute("SELECT count(*) " "from information_schema.TABLES " @@ -241,6 +256,7 @@ class TestMigrations(test.TestCase): "and TABLE_NAME!='migrate_version'") count = noninnodb.scalar() self.assertEqual(count, 0, "%d non InnoDB tables created" % count) + connection.close() def _walk_versions(self, engine=None, snake_walk=False, downgrade=True): # Determine latest version script from the repo, then @@ -263,7 +279,7 @@ class TestMigrations(test.TestCase): for version in xrange(migration.INIT_VERSION + 2, TestMigrations.REPOSITORY.latest + 1): # upgrade -> downgrade -> upgrade - self._migrate_up(engine, version) + self._migrate_up(engine, version, with_data=True) if snake_walk: self._migrate_down(engine, version) self._migrate_up(engine, version) @@ -288,7 +304,19 @@ class TestMigrations(test.TestCase): migration_api.db_version(engine, TestMigrations.REPOSITORY)) - def _migrate_up(self, engine, version): + def _migrate_up(self, engine, version, with_data=False): + """migrate up to a new version of the db. + + We allow for data insertion and post checks at every + migration version with special _prerun_### and + _check_### functions in the main test. + """ + if with_data: + data = None + prerun = getattr(self, "_prerun_%d" % version, None) + if prerun: + data = prerun(engine) + migration_api.upgrade(engine, TestMigrations.REPOSITORY, version) @@ -296,94 +324,198 @@ class TestMigrations(test.TestCase): migration_api.db_version(engine, TestMigrations.REPOSITORY)) - def test_migration_146(self): - name = 'name' - az = 'custom_az' - - def _145_check(): - agg = aggregates.select(aggregates.c.id == 1).execute().first() - self.assertEqual(name, agg.name) - self.assertEqual(az, agg.availability_zone) - - for key, engine in self.engines.items(): - migration_api.version_control(engine, TestMigrations.REPOSITORY, - migration.INIT_VERSION) - migration_api.upgrade(engine, TestMigrations.REPOSITORY, 145) - metadata = sqlalchemy.schema.MetaData() - metadata.bind = engine - aggregates = sqlalchemy.Table('aggregates', metadata, - autoload=True) - - aggregates.insert().values(id=1, availability_zone=az, - aggregate_name=1, name=name).execute() - - _145_check() - - migration_api.upgrade(engine, TestMigrations.REPOSITORY, 146) - - aggregate_metadata = sqlalchemy.Table('aggregate_metadata', - metadata, autoload=True) - metadata = aggregate_metadata.select(aggregate_metadata.c. - aggregate_id == 1).execute().first() - self.assertEqual(az, metadata['value']) - - migration_api.downgrade(engine, TestMigrations.REPOSITORY, 145) - _145_check() - - def test_migration_147(self): + if with_data: + check = getattr(self, "_check_%d" % version, None) + if check: + check(engine, data) + + # migration 146, availability zone transition + def _prerun_146(self, engine): + data = { + 'id': 1, + 'availability_zone': 'custom_az', + 'aggregate_name': 1, + 'name': 'name', + } + + aggregates = get_table(engine, 'aggregates') + aggregates.insert().values(data).execute() + return data + + def _check_146(self, engine, data): + aggregate_md = get_table(engine, 'aggregate_metadata') + md = aggregate_md.select( + aggregate_md.c.aggregate_id == 1).execute().first() + self.assertEqual(data['availability_zone'], md['value']) + + # migration 147, availability zone transition for services + def _prerun_147(self, engine): az = 'test_zone' host1 = 'compute-host1' host2 = 'compute-host2' - - def _146_check(): - service = services.select(services.c.id == 1).execute().first() - self.assertEqual(az, service.availability_zone) - self.assertEqual(host1, service.host) - service = services.select(services.c.id == 2).execute().first() - self.assertNotEqual(az, service.availability_zone) - service = services.select(services.c.id == 3).execute().first() - self.assertEqual(az, service.availability_zone) - self.assertEqual(host2, service.host) - - for key, engine in self.engines.items(): - migration_api.version_control(engine, TestMigrations.REPOSITORY, - migration.INIT_VERSION) - migration_api.upgrade(engine, TestMigrations.REPOSITORY, 146) - metadata = sqlalchemy.schema.MetaData() - metadata.bind = engine - - #populate service table - services = sqlalchemy.Table('services', metadata, - autoload=True) - services.insert().values(id=1, host=host1, - binary='nova-compute', topic='compute', report_count=0, - availability_zone=az).execute() - services.insert().values(id=2, host='sched-host', - binary='nova-scheduler', topic='scheduler', report_count=0, - availability_zone='ignore_me').execute() - services.insert().values(id=3, host=host2, - binary='nova-compute', topic='compute', report_count=0, - availability_zone=az).execute() - - _146_check() - - migration_api.upgrade(engine, TestMigrations.REPOSITORY, 147) - - # check aggregate metadata - aggregate_metadata = sqlalchemy.Table('aggregate_metadata', - metadata, autoload=True) - aggregate_hosts = sqlalchemy.Table('aggregate_hosts', - metadata, autoload=True) - metadata = aggregate_metadata.select(aggregate_metadata.c. - aggregate_id == 1).execute().first() - self.assertEqual(az, metadata['value']) - self.assertEqual(aggregate_hosts.select( - aggregate_hosts.c.aggregate_id == 1).execute(). - first().host, host1) - blank = [h for h in aggregate_hosts.select( - aggregate_hosts.c.aggregate_id == 2).execute()] - self.assertEqual(blank, []) - - migration_api.downgrade(engine, TestMigrations.REPOSITORY, 146) - - _146_check() + # start at id == 2 because we already inserted one + data = [ + {'id': 1, 'host': host1, + 'binary': 'nova-compute', 'topic': 'compute', + 'report_count': 0, 'availability_zone': az}, + {'id': 2, 'host': 'sched-host', + 'binary': 'nova-scheduler', 'topic': 'scheduler', + 'report_count': 0, 'availability_zone': 'ignore_me'}, + {'id': 3, 'host': host2, + 'binary': 'nova-compute', 'topic': 'compute', + 'report_count': 0, 'availability_zone': az}, + ] + + services = get_table(engine, 'services') + engine.execute(services.insert(), data) + return data + + def _check_147(self, engine, data): + aggregate_md = get_table(engine, 'aggregate_metadata') + aggregate_hosts = get_table(engine, 'aggregate_hosts') + # NOTE(sdague): hard coded to id == 2, because we added to + # aggregate_metadata previously + for item in data: + md = aggregate_md.select( + aggregate_md.c.aggregate_id == 2).execute().first() + if item['binary'] == "nova-compute": + self.assertEqual(item['availability_zone'], md['value']) + + host = aggregate_hosts.select( + aggregate_hosts.c.aggregate_id == 2 + ).execute().first() + self.assertEqual(host['host'], data[0]['host']) + + # NOTE(sdague): id 3 is just non-existent + host = aggregate_hosts.select( + aggregate_hosts.c.aggregate_id == 3 + ).execute().first() + self.assertEqual(host, None) + + # migration 149, changes IPAddr storage format + def _prerun_149(self, engine): + provider_fw_rules = get_table(engine, 'provider_fw_rules') + data = [ + {'protocol': 'tcp', 'from_port': 1234, + 'to_port': 1234, 'cidr': "127.0.0.1"}, + {'protocol': 'tcp', 'from_port': 1234, + 'to_port': 1234, 'cidr': "255.255.255.255"}, + {'protocol': 'tcp', 'from_port': 1234, + 'to_port': 1234, 'cidr': "2001:db8::1:2"}, + {'protocol': 'tcp', 'from_port': 1234, + 'to_port': 1234, 'cidr': "::1"} + ] + engine.execute(provider_fw_rules.insert(), data) + return data + + def _check_149(self, engine, data): + provider_fw_rules = get_table(engine, 'provider_fw_rules') + result = provider_fw_rules.select().execute() + + iplist = map(lambda x: x['cidr'], data) + + for row in result: + self.assertIn(row['cidr'], iplist) + + # migration 152 - convert deleted from boolean to int + def _prerun_152(self, engine): + host1 = 'compute-host1' + host2 = 'compute-host2' + # NOTE(sdague): start at #4 because services data already in table + # from 147 + services_data = [ + {'id': 4, 'host': host1, 'binary': 'nova-compute', + 'report_count': 0, 'topic': 'compute', 'deleted': False}, + {'id': 5, 'host': host1, 'binary': 'nova-compute', + 'report_count': 0, 'topic': 'compute', 'deleted': True} + ] + volumes_data = [ + {'id': 'first', 'host': host1, 'deleted': False}, + {'id': 'second', 'host': host2, 'deleted': True} + ] + + services = get_table(engine, 'services') + engine.execute(services.insert(), services_data) + + volumes = get_table(engine, 'volumes') + engine.execute(volumes.insert(), volumes_data) + return dict(services=services_data, volumes=volumes_data) + + def _check_152(self, engine, data): + services = get_table(engine, 'services') + service = services.select(services.c.id == 4).execute().first() + self.assertEqual(0, service.deleted) + service = services.select(services.c.id == 5).execute().first() + self.assertEqual(service.id, service.deleted) + + volumes = get_table(engine, 'volumes') + 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 153, copy flavor information into system_metadata + def _prerun_153(self, engine): + fake_types = [ + dict(id=10, name='type1', memory_mb=128, vcpus=1, + root_gb=10, ephemeral_gb=0, flavorid="1", swap=0, + rxtx_factor=1.0, vcpu_weight=1, disabled=False, + is_public=True), + dict(id=11, name='type2', memory_mb=512, vcpus=1, + root_gb=10, ephemeral_gb=5, flavorid="2", swap=0, + rxtx_factor=1.5, vcpu_weight=2, disabled=False, + is_public=True), + dict(id=12, name='type3', memory_mb=128, vcpus=1, + root_gb=10, ephemeral_gb=0, flavorid="3", swap=0, + rxtx_factor=1.0, vcpu_weight=1, disabled=False, + is_public=False), + dict(id=13, name='type4', memory_mb=128, vcpus=1, + root_gb=10, ephemeral_gb=0, flavorid="4", swap=0, + rxtx_factor=1.0, vcpu_weight=1, disabled=True, + is_public=True), + dict(id=14, name='type5', memory_mb=128, vcpus=1, + root_gb=10, ephemeral_gb=0, flavorid="5", swap=0, + rxtx_factor=1.0, vcpu_weight=1, disabled=True, + is_public=False), + ] + + fake_instances = [ + dict(uuid='m153-uuid1', instance_type_id=10), + dict(uuid='m153-uuid2', instance_type_id=11), + dict(uuid='m153-uuid3', instance_type_id=12), + dict(uuid='m153-uuid4', instance_type_id=13), + # NOTE(danms): no use of type5 + ] + + instances = get_table(engine, 'instances') + instance_types = get_table(engine, 'instance_types') + engine.execute(instance_types.insert(), fake_types) + engine.execute(instances.insert(), fake_instances) + + return fake_types, fake_instances + + def _check_153(self, engine, data): + fake_types, fake_instances = data + # NOTE(danms): Fetch all the tables and data from scratch after change + instances = get_table(engine, 'instances') + instance_types = get_table(engine, 'instance_types') + sys_meta = get_table(engine, 'instance_system_metadata') + + # Collect all system metadata, indexed by instance_uuid + metadata = collections.defaultdict(dict) + for values in sys_meta.select().execute(): + metadata[values['instance_uuid']][values['key']] = values['value'] + + # Taken from nova/compute/api.py + instance_type_props = ['id', 'name', 'memory_mb', 'vcpus', + 'root_gb', 'ephemeral_gb', 'flavorid', + 'swap', 'rxtx_factor', 'vcpu_weight'] + + for instance in fake_instances: + inst_sys_meta = metadata[instance['uuid']] + inst_type = fake_types[instance['instance_type_id'] - 10] + for prop in instance_type_props: + prop_name = 'instance_type_%s' % prop + self.assertIn(prop_name, inst_sys_meta) + self.assertEqual(str(inst_sys_meta[prop_name]), + str(inst_type[prop])) diff --git a/nova/tests/test_notifications.py b/nova/tests/test_notifications.py index a300028a0..aec6c8f67 100644 --- a/nova/tests/test_notifications.py +++ b/nova/tests/test_notifications.py @@ -187,8 +187,6 @@ class NotificationsTestCase(test.TestCase): params = {"task_state": task_states.SPAWNING} (old_ref, new_ref) = db.instance_update_and_get_original(self.context, self.instance['uuid'], params) - print old_ref["task_state"] - print new_ref["task_state"] notifications.send_update(self.context, old_ref, new_ref) self.assertEquals(1, len(test_notifier.NOTIFICATIONS)) diff --git a/nova/tests/test_nova_rootwrap.py b/nova/tests/test_nova_rootwrap.py deleted file mode 100644 index 1029e0c2c..000000000 --- a/nova/tests/test_nova_rootwrap.py +++ /dev/null @@ -1,198 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 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. - -import ConfigParser -import logging -import logging.handlers -import os -import subprocess - -from nova.rootwrap import filters -from nova.rootwrap import wrapper -from nova import test - - -class RootwrapTestCase(test.TestCase): - - def setUp(self): - super(RootwrapTestCase, self).setUp() - self.filters = [ - filters.RegExpFilter("/bin/ls", "root", 'ls', '/[a-z]+'), - filters.CommandFilter("/usr/bin/foo_bar_not_exist", "root"), - filters.RegExpFilter("/bin/cat", "root", 'cat', '/[a-z]+'), - filters.CommandFilter("/nonexistent/cat", "root"), - filters.CommandFilter("/bin/cat", "root") # Keep this one last - ] - - def test_RegExpFilter_match(self): - usercmd = ["ls", "/root"] - filtermatch = wrapper.match_filter(self.filters, usercmd) - self.assertFalse(filtermatch is None) - self.assertEqual(filtermatch.get_command(usercmd), - ["/bin/ls", "/root"]) - - def test_RegExpFilter_reject(self): - usercmd = ["ls", "root"] - self.assertRaises(wrapper.NoFilterMatched, - wrapper.match_filter, self.filters, usercmd) - - def test_missing_command(self): - valid_but_missing = ["foo_bar_not_exist"] - invalid = ["foo_bar_not_exist_and_not_matched"] - self.assertRaises(wrapper.FilterMatchNotExecutable, - wrapper.match_filter, self.filters, valid_but_missing) - self.assertRaises(wrapper.NoFilterMatched, - wrapper.match_filter, self.filters, invalid) - - def _test_DnsmasqFilter(self, filter_class, config_file_arg): - usercmd = ['env', config_file_arg + '=A', 'NETWORK_ID=foobar', - 'dnsmasq', 'foo'] - f = filter_class("/usr/bin/dnsmasq", "root") - self.assertTrue(f.match(usercmd)) - self.assertEqual(f.get_command(usercmd), ['/usr/bin/dnsmasq', 'foo']) - env = f.get_environment(usercmd) - self.assertEqual(env.get(config_file_arg), 'A') - self.assertEqual(env.get('NETWORK_ID'), 'foobar') - - def test_DnsmasqFilter(self): - self._test_DnsmasqFilter(filters.DnsmasqFilter, 'CONFIG_FILE') - - def test_DeprecatedDnsmasqFilter(self): - self._test_DnsmasqFilter(filters.DeprecatedDnsmasqFilter, 'FLAGFILE') - - def test_KillFilter(self): - if not os.path.exists("/proc/%d" % os.getpid()): - self.skipTest("Test requires /proc filesystem (procfs)") - p = subprocess.Popen(["cat"], stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - try: - f = filters.KillFilter("root", "/bin/cat", "-9", "-HUP") - f2 = filters.KillFilter("root", "/usr/bin/cat", "-9", "-HUP") - usercmd = ['kill', '-ALRM', p.pid] - # Incorrect signal should fail - self.assertFalse(f.match(usercmd) or f2.match(usercmd)) - usercmd = ['kill', p.pid] - # Providing no signal should fail - self.assertFalse(f.match(usercmd) or f2.match(usercmd)) - # Providing matching signal should be allowed - usercmd = ['kill', '-9', p.pid] - self.assertTrue(f.match(usercmd) or f2.match(usercmd)) - - f = filters.KillFilter("root", "/bin/cat") - f2 = filters.KillFilter("root", "/usr/bin/cat") - usercmd = ['kill', os.getpid()] - # Our own PID does not match /bin/sleep, so it should fail - self.assertFalse(f.match(usercmd) or f2.match(usercmd)) - usercmd = ['kill', 999999] - # Nonexistent PID should fail - self.assertFalse(f.match(usercmd) or f2.match(usercmd)) - usercmd = ['kill', p.pid] - # Providing no signal should work - self.assertTrue(f.match(usercmd) or f2.match(usercmd)) - finally: - # Terminate the "cat" process and wait for it to finish - p.terminate() - p.wait() - - def test_KillFilter_no_raise(self): - # Makes sure ValueError from bug 926412 is gone. - f = filters.KillFilter("root", "") - # Providing anything other than kill should be False - usercmd = ['notkill', 999999] - self.assertFalse(f.match(usercmd)) - # Providing something that is not a pid should be False - usercmd = ['kill', 'notapid'] - self.assertFalse(f.match(usercmd)) - - def test_KillFilter_deleted_exe(self): - # Makes sure deleted exe's are killed correctly. - # See bug #967931. - def fake_readlink(blah): - return '/bin/commandddddd (deleted)' - - f = filters.KillFilter("root", "/bin/commandddddd") - usercmd = ['kill', 1234] - # Providing no signal should work - self.stubs.Set(os, 'readlink', fake_readlink) - self.assertTrue(f.match(usercmd)) - - def test_ReadFileFilter(self): - goodfn = '/good/file.name' - f = filters.ReadFileFilter(goodfn) - usercmd = ['cat', '/bad/file'] - self.assertFalse(f.match(['cat', '/bad/file'])) - usercmd = ['cat', goodfn] - self.assertEqual(f.get_command(usercmd), ['/bin/cat', goodfn]) - self.assertTrue(f.match(usercmd)) - - def test_exec_dirs_search(self): - # This test supposes you have /bin/cat or /usr/bin/cat locally - f = filters.CommandFilter("cat", "root") - usercmd = ['cat', '/f'] - self.assertTrue(f.match(usercmd)) - self.assertTrue(f.get_command(usercmd, exec_dirs=['/bin', - '/usr/bin']) in (['/bin/cat', '/f'], ['/usr/bin/cat', '/f'])) - - def test_skips(self): - # Check that all filters are skipped and that the last matches - usercmd = ["cat", "/"] - filtermatch = wrapper.match_filter(self.filters, usercmd) - self.assertTrue(filtermatch is self.filters[-1]) - - def test_RootwrapConfig(self): - raw = ConfigParser.RawConfigParser() - - # Empty config should raise ConfigParser.Error - self.assertRaises(ConfigParser.Error, wrapper.RootwrapConfig, raw) - - # Check default values - raw.set('DEFAULT', 'filters_path', '/a,/b') - config = wrapper.RootwrapConfig(raw) - self.assertEqual(config.filters_path, ['/a', '/b']) - self.assertEqual(config.exec_dirs, os.environ["PATH"].split(':')) - self.assertFalse(config.use_syslog) - self.assertEqual(config.syslog_log_facility, - logging.handlers.SysLogHandler.LOG_SYSLOG) - self.assertEqual(config.syslog_log_level, logging.ERROR) - - # Check general values - raw.set('DEFAULT', 'exec_dirs', '/a,/x') - config = wrapper.RootwrapConfig(raw) - self.assertEqual(config.exec_dirs, ['/a', '/x']) - - raw.set('DEFAULT', 'use_syslog', 'oui') - self.assertRaises(ValueError, wrapper.RootwrapConfig, raw) - raw.set('DEFAULT', 'use_syslog', 'true') - config = wrapper.RootwrapConfig(raw) - self.assertTrue(config.use_syslog) - - raw.set('DEFAULT', 'syslog_log_facility', 'moo') - self.assertRaises(ValueError, wrapper.RootwrapConfig, raw) - raw.set('DEFAULT', 'syslog_log_facility', 'local0') - config = wrapper.RootwrapConfig(raw) - self.assertEqual(config.syslog_log_facility, - logging.handlers.SysLogHandler.LOG_LOCAL0) - raw.set('DEFAULT', 'syslog_log_facility', 'LOG_AUTH') - config = wrapper.RootwrapConfig(raw) - self.assertEqual(config.syslog_log_facility, - logging.handlers.SysLogHandler.LOG_AUTH) - - raw.set('DEFAULT', 'syslog_log_level', 'bar') - self.assertRaises(ValueError, wrapper.RootwrapConfig, raw) - raw.set('DEFAULT', 'syslog_log_level', 'INFO') - config = wrapper.RootwrapConfig(raw) - self.assertEqual(config.syslog_log_level, logging.INFO) diff --git a/nova/tests/test_periodic_tasks.py b/nova/tests/test_periodic_tasks.py index 39669967f..621e86b3a 100644 --- a/nova/tests/test_periodic_tasks.py +++ b/nova/tests/test_periodic_tasks.py @@ -15,10 +15,10 @@ # License for the specific language governing permissions and limitations # under the License. - -import fixtures import time +from testtools import matchers + from nova import manager from nova import test @@ -44,10 +44,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 +61,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 +88,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 +110,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 +121,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_sqlalchemy.py b/nova/tests/test_sqlalchemy.py index f79d607f8..5c7f4450b 100644 --- a/nova/tests/test_sqlalchemy.py +++ b/nova/tests/test_sqlalchemy.py @@ -22,8 +22,14 @@ try: except ImportError: MySQLdb = None +from sqlalchemy import Column, MetaData, Table, UniqueConstraint +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import DateTime, Integer + from nova import context +from nova.db.sqlalchemy import models from nova.db.sqlalchemy import session +from nova import exception from nova import test @@ -64,3 +70,60 @@ class DbPoolTestCase(test.TestCase): self.assertEqual(info['kwargs']['max_idle'], 11) self.assertEqual(info['kwargs']['min_size'], 21) self.assertEqual(info['kwargs']['max_size'], 42) + + +BASE = declarative_base() +_TABLE_NAME = '__tmp__test__tmp__' + + +class TmpTable(BASE, models.NovaBase): + __tablename__ = _TABLE_NAME + id = Column(Integer, primary_key=True) + foo = Column(Integer) + + +class SessionErrorWrapperTestCase(test.TestCase): + def setUp(self): + super(SessionErrorWrapperTestCase, self).setUp() + meta = MetaData() + meta.bind = session.get_engine() + test_table = Table(_TABLE_NAME, meta, + Column('id', Integer, primary_key=True, + nullable=False), + Column('deleted', Integer, default=0), + Column('deleted_at', DateTime), + Column('updated_at', DateTime), + Column('created_at', DateTime), + Column('foo', Integer), + UniqueConstraint('foo', name='uniq_foo')) + test_table.create() + + def tearDown(self): + super(SessionErrorWrapperTestCase, self).tearDown() + meta = MetaData() + meta.bind = session.get_engine() + test_table = Table(_TABLE_NAME, meta, autoload=True) + test_table.drop() + + def test_flush_wrapper(self): + tbl = TmpTable() + tbl.update({'foo': 10}) + tbl.save() + + tbl2 = TmpTable() + tbl2.update({'foo': 10}) + self.assertRaises(exception.DBDuplicateEntry, tbl2.save) + + def test_execute_wrapper(self): + _session = session.get_session() + with _session.begin(): + for i in [10, 20]: + tbl = TmpTable() + tbl.update({'foo': i}) + tbl.save(session=_session) + + method = _session.query(TmpTable).\ + filter_by(foo=10).\ + update + self.assertRaises(exception.DBDuplicateEntry, + method, {'foo': 20}) diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index 2c46b27bd..84d56cadf 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -757,3 +757,35 @@ class LastBytesTestCase(test.TestCase): content = '1234567890' flo.write(content) self.assertEqual((content, 0), utils.last_bytes(flo, 1000)) + + +class IntLikeTestCase(test.TestCase): + + def test_is_int_like(self): + self.assertTrue(utils.is_int_like(1)) + self.assertTrue(utils.is_int_like("1")) + self.assertTrue(utils.is_int_like("514")) + self.assertTrue(utils.is_int_like("0")) + + self.assertFalse(utils.is_int_like(1.1)) + self.assertFalse(utils.is_int_like("1.1")) + self.assertFalse(utils.is_int_like("1.1.1")) + self.assertFalse(utils.is_int_like(None)) + self.assertFalse(utils.is_int_like("0.")) + self.assertFalse(utils.is_int_like("aaaaaa")) + self.assertFalse(utils.is_int_like("....")) + self.assertFalse(utils.is_int_like("1g")) + self.assertFalse( + utils.is_int_like("0cc3346e-9fef-4445-abe6-5d2b2690ec64")) + self.assertFalse(utils.is_int_like("a1")) + + +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_disk.py b/nova/tests/test_virt_disk.py index 902d49704..0c51e8267 100644 --- a/nova/tests/test_virt_disk.py +++ b/nova/tests/test_virt_disk.py @@ -14,8 +14,10 @@ # License for the specific language governing permissions and limitations # under the License. +import os import sys +from nova import exception from nova import test from nova.tests import fakeguestfs from nova.virt.disk import api as diskapi @@ -29,6 +31,25 @@ class VirtDiskTest(test.TestCase): sys.modules['guestfs'] = fakeguestfs vfsguestfs.guestfs = fakeguestfs + def test_inject_data(self): + + self.assertTrue(diskapi.inject_data("/some/file", use_cow=True)) + + self.assertTrue(diskapi.inject_data("/some/file", + mandatory=('files',))) + + self.assertTrue(diskapi.inject_data("/some/file", key="mysshkey", + mandatory=('key',))) + + os_name = os.name + os.name = 'nt' # Cause password injection to fail + self.assertRaises(exception.NovaException, + diskapi.inject_data, + "/some/file", admin_password="p", + mandatory=('admin_password',)) + self.assertFalse(diskapi.inject_data("/some/file", admin_password="p")) + os.name = os_name + def test_inject_data_key(self): vfs = vfsguestfs.VFSGuestFS("/some/file", "qcow2") @@ -46,7 +67,7 @@ class VirtDiskTest(test.TestCase): "key was injected by Nova\nmysshkey\n", 'gid': 100, 'uid': 100, - 'mode': 0700}) + 'mode': 0600}) vfs.teardown() @@ -80,7 +101,7 @@ class VirtDiskTest(test.TestCase): "key was injected by Nova\nmysshkey\n", 'gid': 100, 'uid': 100, - 'mode': 0700}) + 'mode': 0600}) vfs.teardown() diff --git a/nova/tests/test_virt_disk_vfs_localfs.py b/nova/tests/test_virt_disk_vfs_localfs.py index 806ed01d8..096a75964 100644 --- a/nova/tests/test_virt_disk_vfs_localfs.py +++ b/nova/tests/test_virt_disk_vfs_localfs.py @@ -46,7 +46,7 @@ def fake_execute(*args, **kwargs): elif args[0] == "chown": owner = args[1] path = args[2] - if not path in files: + if path not in files: raise Exception("No such file: " + path) sep = owner.find(':') @@ -72,7 +72,7 @@ def fake_execute(*args, **kwargs): elif args[0] == "chgrp": group = args[1] path = args[2] - if not path in files: + if path not in files: raise Exception("No such file: " + path) if group == "users": @@ -83,13 +83,13 @@ def fake_execute(*args, **kwargs): elif args[0] == "chmod": mode = args[1] path = args[2] - if not path in files: + if path not in files: raise Exception("No such file: " + path) files[path]["mode"] = int(mode, 8) elif args[0] == "cat": path = args[1] - if not path in files: + if path not in files: files[path] = { "content": "Hello World", "gid": 100, @@ -104,8 +104,7 @@ def fake_execute(*args, **kwargs): else: path = args[1] append = False - print str(files) - if not path in files: + if path not in files: files[path] = { "content": "Hello World", "gid": 100, diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py index 199ae30b1..e8e7c329a 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 @@ -119,8 +119,6 @@ class _FakeDriverBackendTestCase(object): def _teardown_fakelibvirt(self): # Restore libvirt - import nova.virt.libvirt.driver - import nova.virt.libvirt.firewall if self.saved_libvirt: sys.modules['libvirt'] = self.saved_libvirt @@ -159,7 +157,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 +171,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): @@ -446,6 +444,15 @@ class _VirtDriverTestCase(_FakeDriverBackendTestCase): self.assertIn('port', vnc_console) @catch_notimplementederror + def test_get_spice_console(self): + instance_ref, network_info = self._get_running_instance() + spice_console = self.connection.get_spice_console(instance_ref) + self.assertIn('internal_access_path', spice_console) + self.assertIn('host', spice_console) + self.assertIn('port', spice_console) + self.assertIn('tlsPort', spice_console) + + @catch_notimplementederror def test_get_console_pool_info(self): instance_ref, network_info = self._get_running_instance() console_pool = self.connection.get_console_pool_info(instance_ref) diff --git a/nova/tests/test_vmwareapi.py b/nova/tests/test_vmwareapi.py index 577d227ce..34f03a555 100644 --- a/nova/tests/test_vmwareapi.py +++ b/nova/tests/test_vmwareapi.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. # @@ -41,7 +42,9 @@ class VMwareAPIVMTestCase(test.TestCase): self.context = context.RequestContext('fake', 'fake', is_admin=False) self.flags(vmwareapi_host_ip='test_url', vmwareapi_host_username='test_username', - vmwareapi_host_password='test_pass') + vmwareapi_host_password='test_pass', + vnc_enabled=False, + use_linked_clone=False) self.user_id = 'fake' self.project_id = 'fake' self.context = context.RequestContext(self.user_id, self.project_id) @@ -155,6 +158,12 @@ class VMwareAPIVMTestCase(test.TestCase): instances = self.conn.list_instances() self.assertEquals(len(instances), 1) + def test_list_interfaces(self): + self._create_vm() + interfaces = self.conn.list_interfaces(1) + self.assertEquals(len(interfaces), 1) + self.assertEquals(interfaces[0], 4000) + def test_spawn(self): self._create_vm() info = self.conn.get_info({'name': 1}) @@ -205,7 +214,7 @@ class VMwareAPIVMTestCase(test.TestCase): self._check_vm_info(info, power_state.RUNNING) self.conn.suspend(self.instance) info = self.conn.get_info({'name': 1}) - self._check_vm_info(info, power_state.PAUSED) + self._check_vm_info(info, power_state.SUSPENDED) self.assertRaises(exception.InstanceRebootFailure, self.conn.reboot, self.instance, self.network_info, 'SOFT') @@ -215,7 +224,7 @@ class VMwareAPIVMTestCase(test.TestCase): self._check_vm_info(info, power_state.RUNNING) self.conn.suspend(self.instance) info = self.conn.get_info({'name': 1}) - self._check_vm_info(info, power_state.PAUSED) + self._check_vm_info(info, power_state.SUSPENDED) def test_suspend_non_existent(self): self._create_instance_in_the_db() @@ -228,7 +237,7 @@ class VMwareAPIVMTestCase(test.TestCase): self._check_vm_info(info, power_state.RUNNING) self.conn.suspend(self.instance) info = self.conn.get_info({'name': 1}) - self._check_vm_info(info, power_state.PAUSED) + self._check_vm_info(info, power_state.SUSPENDED) self.conn.resume(self.instance, self.network_info) info = self.conn.get_info({'name': 1}) self._check_vm_info(info, power_state.RUNNING) @@ -245,6 +254,43 @@ class VMwareAPIVMTestCase(test.TestCase): self.assertRaises(exception.InstanceResumeFailure, self.conn.resume, self.instance, self.network_info) + def test_power_on(self): + self._create_vm() + info = self.conn.get_info({'name': 1}) + self._check_vm_info(info, power_state.RUNNING) + self.conn.power_off(self.instance) + info = self.conn.get_info({'name': 1}) + self._check_vm_info(info, power_state.SHUTDOWN) + self.conn.power_on(self.instance) + info = self.conn.get_info({'name': 1}) + self._check_vm_info(info, power_state.RUNNING) + + def test_power_on_non_existent(self): + self._create_instance_in_the_db() + self.assertRaises(exception.InstanceNotFound, self.conn.power_on, + self.instance) + + def test_power_off(self): + self._create_vm() + info = self.conn.get_info({'name': 1}) + self._check_vm_info(info, power_state.RUNNING) + self.conn.power_off(self.instance) + info = self.conn.get_info({'name': 1}) + self._check_vm_info(info, power_state.SHUTDOWN) + + def test_power_off_non_existent(self): + self._create_instance_in_the_db() + self.assertRaises(exception.InstanceNotFound, self.conn.power_off, + self.instance) + + def test_power_off_suspended(self): + self._create_vm() + self.conn.suspend(self.instance) + info = self.conn.get_info({'name': 1}) + self._check_vm_info(info, power_state.SUSPENDED) + self.assertRaises(exception.InstancePowerOffFailure, + self.conn.power_off, self.instance) + def test_get_info(self): self._create_vm() info = self.conn.get_info({'name': 1}) @@ -276,3 +322,48 @@ class VMwareAPIVMTestCase(test.TestCase): def test_get_console_output(self): pass + + +class VMwareAPIHostTestCase(test.TestCase): + """Unit tests for Vmware API host calls.""" + + def setUp(self): + super(VMwareAPIHostTestCase, self).setUp() + self.flags(vmwareapi_host_ip='test_url', + vmwareapi_host_username='test_username', + vmwareapi_host_password='test_pass') + vmwareapi_fake.reset() + stubs.set_stubs(self.stubs) + self.conn = driver.VMwareESXDriver(False) + + def tearDown(self): + super(VMwareAPIHostTestCase, self).tearDown() + vmwareapi_fake.cleanup() + + def test_host_state(self): + stats = self.conn.get_host_stats() + self.assertEquals(stats['vcpus'], 16) + self.assertEquals(stats['disk_total'], 1024) + self.assertEquals(stats['disk_available'], 500) + self.assertEquals(stats['disk_used'], 1024 - 500) + self.assertEquals(stats['host_memory_total'], 1024) + self.assertEquals(stats['host_memory_free'], 1024 - 500) + + def _test_host_action(self, method, action, expected=None): + result = method('host', action) + self.assertEqual(result, expected) + + def test_host_reboot(self): + self._test_host_action(self.conn.host_power_action, 'reboot') + + def test_host_shutdown(self): + self._test_host_action(self.conn.host_power_action, 'shutdown') + + def test_host_startup(self): + self._test_host_action(self.conn.host_power_action, 'startup') + + def test_host_maintenance_on(self): + self._test_host_action(self.conn.host_maintenance_mode, True) + + def test_host_maintenance_off(self): + self._test_host_action(self.conn.host_maintenance_mode, False) 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/test_xenapi.py b/nova/tests/test_xenapi.py index 0b1c5d0e7..c480d5c5f 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -19,7 +19,6 @@ import ast import base64 import contextlib -import cPickle as pickle import functools import os import re @@ -48,6 +47,7 @@ from nova.virt.xenapi import agent from nova.virt.xenapi import driver as xenapi_conn from nova.virt.xenapi import fake as xenapi_fake from nova.virt.xenapi import host +from nova.virt.xenapi.imageupload import glance from nova.virt.xenapi import pool from nova.virt.xenapi import pool_states from nova.virt.xenapi import vm_utils @@ -431,15 +431,29 @@ class XenAPIVMTestCase(stubs.XenAPITestBase): {'task_state': task_states.IMAGE_UPLOADING, 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + image_id = "my_snapshot_id" stubs.stubout_instance_snapshot(self.stubs) stubs.stubout_is_snapshot(self.stubs) # Stubbing out firewall driver as previous stub sets alters # xml rpc result parsing stubs.stubout_firewall_driver(self.stubs, self.conn) + instance = self._create_instance() - image_id = "my_snapshot_id" + self.fake_upload_called = False + + def fake_image_upload(_self, ctx, session, inst, vdi_uuids, + img_id): + self.fake_upload_called = True + self.assertEqual(ctx, self.context) + self.assertEqual(inst, instance) + self.assertTrue(isinstance(vdi_uuids, list)) + self.assertEqual(img_id, image_id) + + self.stubs.Set(glance.GlanceStore, 'upload_image', + fake_image_upload) + self.conn.snapshot(self.context, instance, image_id, func_call_matcher.call) @@ -469,6 +483,8 @@ class XenAPIVMTestCase(stubs.XenAPITestBase): name_label = vdi_rec["name_label"] self.assert_(not name_label.endswith('snapshot')) + self.assertTrue(self.fake_upload_called) + def create_vm_record(self, conn, os_type, name): instances = conn.list_instances() self.assertEquals(instances, [name]) @@ -579,7 +595,7 @@ class XenAPIVMTestCase(stubs.XenAPITestBase): def _check_vdis(self, start_list, end_list): for vdi_ref in end_list: - if not vdi_ref in start_list: + if vdi_ref not in start_list: vdi_rec = xenapi_fake.get_record('VDI', vdi_ref) # If the cache is turned on then the base disk will be # there even after the cleanup @@ -1822,16 +1838,31 @@ class XenAPIBWCountersTestCase(stubs.XenAPITestBase): # Consider abstracting common code in a base class for firewall driver testing. class XenAPIDom0IptablesFirewallTestCase(stubs.XenAPITestBase): - _in_nat_rules = [ + _in_rules = [ '# Generated by iptables-save v1.4.10 on Sat Feb 19 00:03:19 2011', '*nat', ':PREROUTING ACCEPT [1170:189210]', ':INPUT ACCEPT [844:71028]', ':OUTPUT ACCEPT [5149:405186]', ':POSTROUTING ACCEPT [5063:386098]', - ] - - _in_filter_rules = [ + '# Completed on Mon Dec 6 11:54:13 2010', + '# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010', + '*mangle', + ':INPUT ACCEPT [969615:281627771]', + ':FORWARD ACCEPT [0:0]', + ':OUTPUT ACCEPT [915599:63811649]', + ':nova-block-ipv4 - [0:0]', + '[0:0] -A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ', + '[0:0] -A FORWARD -d 192.168.122.0/24 -o virbr0 -m state --state RELATED' + ',ESTABLISHED -j ACCEPT ', + '[0:0] -A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT ', + '[0:0] -A FORWARD -i virbr0 -o virbr0 -j ACCEPT ', + '[0:0] -A FORWARD -o virbr0 -j REJECT ' + '--reject-with icmp-port-unreachable ', + '[0:0] -A FORWARD -i virbr0 -j REJECT ' + '--reject-with icmp-port-unreachable ', + 'COMMIT', + '# Completed on Mon Dec 6 11:54:13 2010', '# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010', '*filter', ':INPUT ACCEPT [969615:281627771]', @@ -1916,9 +1947,9 @@ class XenAPIDom0IptablesFirewallTestCase(stubs.XenAPITestBase): def _validate_security_group(self): in_rules = filter(lambda l: not l.startswith('#'), - self._in_filter_rules) + self._in_rules) for rule in in_rules: - if not 'nova' in rule: + if 'nova' not in rule: self.assertTrue(rule in self._out_rules, 'Rule went missing: %s' % rule) @@ -2559,54 +2590,6 @@ class SwapXapiHostTestCase(test.TestCase): "http://someserver", 'otherserver')) -class VmUtilsTestCase(test.TestCase): - """Unit tests for xenapi utils.""" - - def test_upload_image(self): - def fake_instance_system_metadata_get(context, uuid): - return dict(image_a=1, image_b=2, image_c='c', d='d') - - def fake_get_sr_path(session): - return "foo" - - class FakeInstance(dict): - def __init__(self): - super(FakeInstance, self).__init__({ - 'auto_disk_config': 'auto disk config', - 'os_type': 'os type'}) - - def __missing__(self, item): - return "whatever" - - class FakeSession(object): - def call_plugin(session_self, service, command, kwargs): - self.kwargs = kwargs - - def call_plugin_serialized(session_self, service, command, *args, - **kwargs): - self.kwargs = kwargs - - def fake_dumps(thing): - return thing - - self.stubs.Set(db, "instance_system_metadata_get", - fake_instance_system_metadata_get) - self.stubs.Set(vm_utils, "get_sr_path", fake_get_sr_path) - self.stubs.Set(pickle, "dumps", fake_dumps) - - ctx = context.get_admin_context() - - instance = FakeInstance() - session = FakeSession() - vm_utils.upload_image(ctx, session, instance, "vmi uuids", "image id") - - actual = self.kwargs['properties'] - # Inheritance happens in another place, now - expected = dict(auto_disk_config='auto disk config', - os_type='os type') - self.assertEquals(expected, actual) - - class XenAPILiveMigrateTestCase(stubs.XenAPITestBase): """Unit tests for live_migration.""" def setUp(self): diff --git a/nova/tests/virt/xenapi/imageupload/__init__.py b/nova/tests/virt/xenapi/imageupload/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/nova/tests/virt/xenapi/imageupload/__init__.py diff --git a/nova/tests/virt/xenapi/imageupload/test_glance.py b/nova/tests/virt/xenapi/imageupload/test_glance.py new file mode 100644 index 000000000..b0518228d --- /dev/null +++ b/nova/tests/virt/xenapi/imageupload/test_glance.py @@ -0,0 +1,74 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import mox + +from nova import context +from nova import test +from nova.virt.xenapi.imageupload import glance +from nova.virt.xenapi import vm_utils + + +class TestGlanceStore(test.TestCase): + def setUp(self): + super(TestGlanceStore, self).setUp() + self.store = glance.GlanceStore() + self.mox = mox.Mox() + + def tearDown(self): + super(TestGlanceStore, self).tearDown() + + def test_upload_image(self): + glance_host = '0.1.2.3' + glance_port = 8143 + glance_use_ssl = False + sr_path = '/fake/sr/path' + self.flags(glance_host=glance_host) + self.flags(glance_port=glance_port) + self.flags(glance_api_insecure=glance_use_ssl) + + def fake_get_sr_path(*_args, **_kwargs): + return sr_path + + self.stubs.Set(vm_utils, 'get_sr_path', fake_get_sr_path) + + ctx = context.RequestContext('user', 'project', auth_token='foobar') + properties = { + 'auto_disk_config': True, + 'os_type': 'default', + } + image_id = 'fake_image_uuid' + vdi_uuids = ['fake_vdi_uuid'] + instance = {'uuid': 'blah'} + instance.update(properties) + + params = {'vdi_uuids': vdi_uuids, + 'image_id': image_id, + 'glance_host': glance_host, + 'glance_port': glance_port, + 'glance_use_ssl': glance_use_ssl, + 'sr_path': sr_path, + 'auth_token': 'foobar', + 'properties': properties} + session = self.mox.CreateMockAnything() + session.call_plugin_serialized('glance', 'upload_vhd', **params) + self.mox.ReplayAll() + + self.store.upload_image(ctx, session, instance, vdi_uuids, image_id) + + self.mox.VerifyAll() diff --git a/nova/tests/virt/xenapi/test_vm_utils.py b/nova/tests/virt/xenapi/test_vm_utils.py index 275088af0..633e6f835 100644 --- a/nova/tests/virt/xenapi/test_vm_utils.py +++ b/nova/tests/virt/xenapi/test_vm_utils.py @@ -19,10 +19,8 @@ import contextlib import fixtures import mox -import uuid from nova import test -from nova.tests.xenapi import stubs from nova import utils from nova.virt.xenapi import vm_utils 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/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 85c85b5e2..fa214b23e 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -208,12 +208,10 @@ class FakeSessionForFirewallTests(FakeSessionForVMTests): def __init__(self, uri, test_case=None): super(FakeSessionForFirewallTests, self).__init__(uri) - if hasattr(test_case, '_in_filter_rules'): - self._in_filter_rules = test_case._in_filter_rules + if hasattr(test_case, '_in_rules'): + self._in_rules = test_case._in_rules if hasattr(test_case, '_in6_filter_rules'): self._in6_filter_rules = test_case._in6_filter_rules - if hasattr(test_case, '_in_nat_rules'): - self._in_nat_rules = test_case._in_nat_rules self._test_case = test_case def host_call_plugin(self, _1, _2, plugin, method, args): @@ -230,12 +228,10 @@ class FakeSessionForFirewallTests(FakeSessionForVMTests): else: output = '' process_input = args.get('process_input', None) - if cmd == ['ip6tables-save', '-c', '-t', 'filter']: + if cmd == ['ip6tables-save', '-c']: output = '\n'.join(self._in6_filter_rules) - if cmd == ['iptables-save', '-c', '-t', 'filter']: - output = '\n'.join(self._in_filter_rules) - if cmd == ['iptables-save', '-c', '-t', 'nat']: - output = '\n'.join(self._in_nat_rules) + if cmd == ['iptables-save', '-c']: + output = '\n'.join(self._in_rules) if cmd == ['iptables-restore', '-c', ]: lines = process_input.split('\n') if '*filter' in lines: diff --git a/nova/tests/xenapi/test_vm_utils.py b/nova/tests/xenapi/test_vm_utils.py index 6d7f9a624..a701efdd9 100644 --- a/nova/tests/xenapi/test_vm_utils.py +++ b/nova/tests/xenapi/test_vm_utils.py @@ -14,7 +14,7 @@ XENSM_TYPE = 'xensm' ISCSI_TYPE = 'iscsi' -def get_fake_dev_params(sr_type): +def get_fake_connection_data(sr_type): fakes = {XENSM_TYPE: {'sr_uuid': 'falseSR', 'name_label': 'fake_storage', 'name_description': 'test purposes', @@ -73,16 +73,16 @@ class GetInstanceForVdisForSrTestCase(stubs.XenAPITestBase): self.assertEquals([], result) - def test_get_vdis_for_boot_from_vol_with_sr_uuid(self): - dev_params = get_fake_dev_params(XENSM_TYPE) + def test_get_vdi_uuid_for_volume_with_sr_uuid(self): + connection_data = get_fake_connection_data(XENSM_TYPE) stubs.stubout_session(self.stubs, fake.SessionBase) driver = xenapi_conn.XenAPIDriver(False) - result = vm_utils.get_vdis_for_boot_from_vol(driver._session, - dev_params) - self.assertEquals(result['root']['uuid'], 'falseVDI') + vdi_uuid = vm_utils.get_vdi_uuid_for_volume( + driver._session, connection_data) + self.assertEquals(vdi_uuid, 'falseVDI') - def test_get_vdis_for_boot_from_vol_failure(self): + def test_get_vdi_uuid_for_volume_failure(self): stubs.stubout_session(self.stubs, fake.SessionBase) driver = xenapi_conn.XenAPIDriver(False) @@ -90,19 +90,19 @@ class GetInstanceForVdisForSrTestCase(stubs.XenAPITestBase): return None self.stubs.Set(volume_utils, 'introduce_sr', bad_introduce_sr) - dev_params = get_fake_dev_params(XENSM_TYPE) + connection_data = get_fake_connection_data(XENSM_TYPE) self.assertRaises(exception.NovaException, - vm_utils.get_vdis_for_boot_from_vol, - driver._session, dev_params) + vm_utils.get_vdi_uuid_for_volume, + driver._session, connection_data) - def test_get_vdis_for_boot_from_iscsi_vol_missing_sr_uuid(self): - dev_params = get_fake_dev_params(ISCSI_TYPE) + def test_get_vdi_uuid_for_volume_from_iscsi_vol_missing_sr_uuid(self): + connection_data = get_fake_connection_data(ISCSI_TYPE) stubs.stubout_session(self.stubs, fake.SessionBase) driver = xenapi_conn.XenAPIDriver(False) - result = vm_utils.get_vdis_for_boot_from_vol(driver._session, - dev_params) - self.assertNotEquals(result['root']['uuid'], None) + vdi_uuid = vm_utils.get_vdi_uuid_for_volume( + driver._session, connection_data) + self.assertNotEquals(vdi_uuid, None) class VMRefOrRaiseVMFoundTestCase(test.TestCase): diff --git a/nova/utils.py b/nova/utils.py index 115791b64..83bf55583 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -76,6 +76,9 @@ utils_opts = [ default="/etc/nova/rootwrap.conf", help='Path to the rootwrap configuration file to use for ' 'running commands as root'), + cfg.StrOpt('tempdir', + default=None, + help='Explicitly specify the temporary working directory'), ] CONF = cfg.CONF CONF.register_opts(monkey_patch_opts) @@ -507,14 +510,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) @@ -621,7 +628,7 @@ class DynamicLoopingCall(LoopingCallBase): if not self._running: break - if not periodic_interval_max is None: + if periodic_interval_max is not None: idle = min(idle, periodic_interval_max) LOG.debug(_('Periodic task processor sleeping for %.02f ' 'seconds'), idle) @@ -859,6 +866,14 @@ def bool_from_str(val): val.lower() == 'y' +def is_int_like(val): + """Check if a value looks like an int.""" + try: + return str(int(val)) == str(val) + except Exception: + return False + + def is_valid_boolstr(val): """Check if the provided string is a valid bool string or not.""" val = str(val).lower() @@ -1072,21 +1087,42 @@ def temporary_mutation(obj, **kwargs): with temporary_mutation(context, read_deleted="yes"): do_something_that_needed_deleted_objects() """ + def is_dict_like(thing): + return hasattr(thing, 'has_key') + + def get(thing, attr, default): + if is_dict_like(thing): + return thing.get(attr, default) + else: + return getattr(thing, attr, default) + + def set_value(thing, attr, val): + if is_dict_like(thing): + thing[attr] = val + else: + setattr(thing, attr, val) + + def delete(thing, attr): + if is_dict_like(thing): + del thing[attr] + else: + delattr(thing, attr) + NOT_PRESENT = object() old_values = {} for attr, new_value in kwargs.items(): - old_values[attr] = getattr(obj, attr, NOT_PRESENT) - setattr(obj, attr, new_value) + old_values[attr] = get(obj, attr, NOT_PRESENT) + set_value(obj, attr, new_value) try: yield finally: for attr, old_value in old_values.items(): if old_value is NOT_PRESENT: - del obj[attr] + delete(obj, attr) else: - setattr(obj, attr, old_value) + set_value(obj, attr, old_value) def generate_mac_address(): @@ -1135,6 +1171,7 @@ def temporary_chown(path, owner_uid=None): @contextlib.contextmanager def tempdir(**kwargs): + tempfile.tempdir = CONF.tempdir tmpdir = tempfile.mkdtemp(**kwargs) try: yield tmpdir @@ -1249,3 +1286,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/driver.py b/nova/virt/baremetal/driver.py index 9904fdcd4..631a9a8c4 100644 --- a/nova/virt/baremetal/driver.py +++ b/nova/virt/baremetal/driver.py @@ -140,7 +140,7 @@ class BareMetalDriver(driver.ComputeDriver): keyval[0] = keyval[0].strip() keyval[1] = keyval[1].strip() extra_specs[keyval[0]] = keyval[1] - if not 'cpu_arch' in extra_specs: + if 'cpu_arch' not in extra_specs: LOG.warning( _('cpu_arch is not found in instance_type_extra_specs')) extra_specs['cpu_arch'] = '' 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/net-dhcp.ubuntu.template b/nova/virt/baremetal/net-dhcp.ubuntu.template index e8824a88d..34a9e8be7 100644 --- a/nova/virt/baremetal/net-dhcp.ubuntu.template +++ b/nova/virt/baremetal/net-dhcp.ubuntu.template @@ -10,9 +10,6 @@ iface lo inet loopback #for $ifc in $interfaces auto ${ifc.name} iface ${ifc.name} inet dhcp -#if $ifc.hwaddress - hwaddress ether ${ifc.hwaddress} -#end if #if $use_ipv6 iface ${ifc.name} inet6 dhcp diff --git a/nova/virt/baremetal/net-static.ubuntu.template b/nova/virt/baremetal/net-static.ubuntu.template index f14f0ce8c..1fe5a1ab8 100644 --- a/nova/virt/baremetal/net-static.ubuntu.template +++ b/nova/virt/baremetal/net-static.ubuntu.template @@ -16,9 +16,6 @@ iface ${ifc.name} inet static #if $ifc.dns dns-nameservers ${ifc.dns} #end if -#if $ifc.hwaddress - hwaddress ether ${ifc.hwaddress} -#end if #if $use_ipv6 iface ${ifc.name} inet6 static diff --git a/nova/virt/baremetal/pxe.py b/nova/virt/baremetal/pxe.py index b94ac9032..5a6f58655 100644 --- a/nova/virt/baremetal/pxe.py +++ b/nova/virt/baremetal/pxe.py @@ -21,18 +21,15 @@ Class for PXE bare-metal nodes. """ import os -import shutil from nova.compute import instance_types from nova import exception from nova.openstack.common import cfg from nova.openstack.common import fileutils from nova.openstack.common import log as logging -from nova import utils from nova.virt.baremetal import base from nova.virt.baremetal import db from nova.virt.baremetal import utils as bm_utils -from nova.virt.disk import api as disk pxe_opts = [ @@ -67,7 +64,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 @@ -121,7 +119,6 @@ def build_network_config(network_info): gateway_v6 = mapping['gateway_v6'] interface = { 'name': 'eth%d' % id, - 'hwaddress': mapping['mac'], 'address': mapping['ips'][0]['ip'], 'gateway': mapping['gateway'], 'netmask': mapping['ips'][0]['netmask'], @@ -219,7 +216,7 @@ def get_tftp_image_info(instance): missing_labels = [] for label in image_info.keys(): (uuid, path) = image_info[label] - if uuid is None: + if not uuid: missing_labels.append(label) else: image_info[label][1] = os.path.join(CONF.baremetal.tftp_root, @@ -238,27 +235,12 @@ class PXE(base.NodeDriver): super(PXE, self).__init__() def _collect_mac_addresses(self, context, node): - macs = [] - macs.append(db.bm_node_get(context, node['id'])['prov_mac_address']) + macs = set() + macs.add(db.bm_node_get(context, node['id'])['prov_mac_address']) for nic in db.bm_interface_get_all_by_bm_node_id(context, node['id']): if nic['address']: - macs.append(nic['address']) - macs.sort() - return macs - - def _generate_udev_rules(self, context, node): - # TODO(deva): fix assumption that device names begin with "eth" - # and fix assumption of ordering - macs = self._collect_mac_addresses(context, node) - rules = '' - for (i, mac) in enumerate(macs): - rules += 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ' \ - 'ATTR{address}=="%(mac)s", ATTR{dev_id}=="0x0", ' \ - 'ATTR{type}=="1", KERNEL=="eth*", NAME="%(name)s"\n' \ - % {'mac': mac.lower(), - 'name': 'eth%d' % i, - } - return rules + macs.add(nic['address']) + return sorted(macs) def _cache_tftp_images(self, context, instance, image_info): """Fetch the necessary kernels and ramdisks for the instance.""" @@ -330,9 +312,6 @@ class PXE(base.NodeDriver): injected_files = [] net_config = build_network_config(network_info) - udev_rules = self._generate_udev_rules(context, node) - injected_files.append( - ('/etc/udev/rules.d/70-persistent-net.rules', udev_rules)) if instance['hostname']: injected_files.append(('/etc/hostname', instance['hostname'])) @@ -363,7 +342,7 @@ class PXE(base.NodeDriver): def destroy_images(self, context, node, instance): """Delete instance's image file.""" bm_utils.unlink_without_raise(get_image_file_path(instance)) - bm_utils.unlink_without_raise(get_image_dir_path(instance)) + bm_utils.rmtree_without_raise(get_image_dir_path(instance)) def activate_bootloader(self, context, node, instance): """Configure PXE boot loader for an instance @@ -385,7 +364,6 @@ class PXE(base.NodeDriver): config ./pxelinux.cfg/ {mac} -> ../{uuid}/config - """ image_info = get_tftp_image_info(instance) (root_mb, swap_mb) = get_partition_sizes(instance) @@ -439,7 +417,7 @@ class PXE(base.NodeDriver): for mac in macs: bm_utils.unlink_without_raise(get_pxe_mac_path(mac)) - bm_utils.unlink_without_raise( + bm_utils.rmtree_without_raise( os.path.join(CONF.baremetal.tftp_root, instance['uuid'])) def activate_node(self, context, node, instance): diff --git a/nova/virt/baremetal/utils.py b/nova/virt/baremetal/utils.py index 0842ae201..fa2c05983 100644 --- a/nova/virt/baremetal/utils.py +++ b/nova/virt/baremetal/utils.py @@ -15,7 +15,9 @@ # License for the specific language governing permissions and limitations # under the License. +import errno import os +import shutil from nova.openstack.common import log as logging from nova.virt.disk import api as disk_api @@ -43,8 +45,19 @@ def inject_into_image(image, key, net, metadata, admin_password, def unlink_without_raise(path): try: os.unlink(path) - except OSError: - LOG.exception(_("Failed to unlink %s") % path) + except OSError, e: + if e.errno == errno.ENOENT: + return + else: + LOG.warn(_("Failed to unlink %(path)s, error: %(e)s") % locals()) + + +def rmtree_without_raise(path): + try: + if os.path.isdir(path): + shutil.rmtree(path) + except OSError, e: + LOG.warn(_("Failed to remove dir %(path)s, error: %(e)s") % locals()) def write_to_file(path, contents): @@ -55,9 +68,12 @@ def write_to_file(path, contents): def create_link_without_raise(source, link): try: os.symlink(source, link) - except OSError: - LOG.exception(_("Failed to create symlink from %(source)s to %(link)s") - % locals()) + except OSError, e: + if e.errno == errno.EEXIST: + return + else: + LOG.warn(_("Failed to create symlink from %(source)s to %(link)s" + ", error: %(e)s") % locals()) def random_alnum(count): diff --git a/nova/virt/baremetal/volume_driver.py b/nova/virt/baremetal/volume_driver.py index 2e6f82b93..e92325b97 100644 --- a/nova/virt/baremetal/volume_driver.py +++ b/nova/virt/baremetal/volume_driver.py @@ -31,7 +31,7 @@ opts = [ cfg.BoolOpt('use_unsafe_iscsi', default=False, help='Do not set this out of dev/test environments. ' - 'If a node does not have an fixed PXE IP address, ' + 'If a node does not have a fixed PXE IP address, ' 'volumes are exported with globally opened ACL'), cfg.StrOpt('iscsi_iqn_prefix', default='iqn.2010-10.org.openstack.baremetal', @@ -210,7 +210,7 @@ class LibvirtVolumeDriver(VolumeDriver): def _volume_driver_method(self, method_name, connection_info, *args, **kwargs): driver_type = connection_info.get('driver_volume_type') - if not driver_type in self.volume_drivers: + if driver_type not in self.volume_drivers: raise exception.VolumeDriverNotFound(driver_type=driver_type) driver = self.volume_drivers[driver_type] method = getattr(driver, method_name) diff --git a/nova/virt/disk/api.py b/nova/virt/disk/api.py index 26fb86f1e..304781a64 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): @@ -267,14 +271,19 @@ class _DiskImage(object): # Public module functions def inject_data(image, key=None, net=None, metadata=None, admin_password=None, - files=None, partition=None, use_cow=False): - """Injects a ssh key and optionally net data into a disk image. + files=None, partition=None, use_cow=False, mandatory=()): + """Inject the specified items into a disk image. + + If an item name is not specified in the MANDATORY iterable, then a warning + is logged on failure to inject that item, rather than raising an exception. it will mount the image as a fully partitioned disk and attempt to inject into the specified partition number. - If partition is not specified it mounts the image as a single partition. + If PARTITION is not specified the image is mounted as a single partition. + Returns True if all requested operations completed without issue. + Raises an exception if a mandatory item can't be injected. """ LOG.debug(_("Inject data image=%(image)s key=%(key)s net=%(net)s " "metadata=%(metadata)s admin_password=ha-ha-not-telling-you " @@ -283,11 +292,23 @@ def inject_data(image, key=None, net=None, metadata=None, admin_password=None, fmt = "raw" if use_cow: fmt = "qcow2" - fs = vfs.VFS.instance_for_image(image, fmt, partition) - fs.setup() try: - inject_data_into_fs(fs, - key, net, metadata, admin_password, files) + fs = vfs.VFS.instance_for_image(image, fmt, partition) + fs.setup() + except Exception as e: + # If a mandatory item is passed to this function, + # then reraise the exception to indicate the error. + for inject in mandatory: + inject_val = locals()[inject] + if inject_val: + raise + LOG.warn(_('Ignoring error injecting data into image ' + '(%(e)s)') % locals()) + return False + + try: + return inject_data_into_fs(fs, key, net, metadata, + admin_password, files, mandatory) finally: fs.teardown() @@ -320,22 +341,37 @@ def teardown_container(container_dir): LOG.exception(_('Failed to unmount container filesystem: %s'), exn) -def inject_data_into_fs(fs, key, net, metadata, admin_password, files): +def inject_data_into_fs(fs, key, net, metadata, admin_password, files, + mandatory=()): """Injects data into a filesystem already mounted by the caller. Virt connections can call this directly if they mount their fs - in a different way to inject_data + in a different way to inject_data. + + If an item name is not specified in the MANDATORY iterable, then a warning + is logged on failure to inject that item, rather than raising an exception. + + Returns True if all requested operations completed without issue. + Raises an exception if a mandatory item can't be injected. """ - if key: - _inject_key_into_fs(key, fs) - if net: - _inject_net_into_fs(net, fs) - if metadata: - _inject_metadata_into_fs(metadata, fs) - if admin_password: - _inject_admin_password_into_fs(admin_password, fs) - if files: - for (path, contents) in files: - _inject_file_into_fs(fs, path, contents) + status = True + for inject in ('key', 'net', 'metadata', 'admin_password', 'files'): + inject_val = locals()[inject] + inject_func = globals()['_inject_%s_into_fs' % inject] + if inject_val: + try: + inject_func(inject_val, fs) + except Exception as e: + if inject in mandatory: + raise + LOG.warn(_('Ignoring error injecting %(inject)s into image ' + '(%(e)s)') % locals()) + status = False + return status + + +def _inject_files_into_fs(files, fs): + for (path, contents) in files: + _inject_file_into_fs(fs, path, contents) def _inject_file_into_fs(fs, path, contents, append=False): @@ -407,6 +443,7 @@ def _inject_key_into_fs(key, fs): ]) _inject_file_into_fs(fs, keyfile, key_data, append=True) + fs.set_permissions(keyfile, 0600) _setup_selinux_for_keys(fs, sshdir) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index a8f779e66..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') @@ -258,6 +269,10 @@ class ComputeDriver(object): # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() + def get_spice_console(self, instance): + # TODO(Vek): Need to pass context in for access to auth_token + raise NotImplementedError() + def get_diagnostics(self, instance): """Return data about VM diagnostics.""" # TODO(Vek): Need to pass context in for access to auth_token @@ -443,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 0a29a6d67..5a5bb7b13 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -125,7 +125,7 @@ class FakeDriver(driver.ComputeDriver): self.instances[name] = fake_instance def snapshot(self, context, instance, name, update_task_state): - if not instance['name'] in self.instances: + if instance['name'] not in self.instances: raise exception.InstanceNotRunning(instance_id=instance['uuid']) update_task_state(task_state=task_states.IMAGE_UPLOADING) @@ -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 @@ -203,7 +209,7 @@ class FakeDriver(driver.ComputeDriver): def attach_volume(self, connection_info, instance, mountpoint): """Attach the disk to the instance at mountpoint using info.""" instance_name = instance['name'] - if not instance_name in self._mounts: + if instance_name not in self._mounts: self._mounts[instance_name] = {} self._mounts[instance_name][mountpoint] = connection_info return True @@ -271,6 +277,12 @@ class FakeDriver(driver.ComputeDriver): 'host': 'fakevncconsole.com', 'port': 6969} + def get_spice_console(self, instance): + return {'internal_access_path': 'FAKE', + 'host': 'fakespiceconsole.com', + 'port': 6969, + 'tlsPort': 6970} + def get_console_pool_info(self, console_type): return {'address': '127.0.0.1', 'username': 'fakeuser', diff --git a/nova/virt/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/__init__.py b/nova/virt/hyperv/__init__.py index e69de29bb..090fc0639 100644 --- a/nova/virt/hyperv/__init__.py +++ b/nova/virt/hyperv/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# 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. diff --git a/nova/virt/hyperv/baseops.py b/nova/virt/hyperv/baseops.py deleted file mode 100644 index 5b617f898..000000000 --- a/nova/virt/hyperv/baseops.py +++ /dev/null @@ -1,69 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 Cloudbase Solutions Srl -# 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. - -""" -Management base class for Hyper-V operations. -""" -import sys - -from nova.openstack.common import log as logging - -# Check needed for unit testing on Unix -if sys.platform == 'win32': - import wmi - -LOG = logging.getLogger(__name__) - - -class BaseOps(object): - def __init__(self): - self.__conn = None - self.__conn_v2 = None - self.__conn_cimv2 = None - self.__conn_wmi = None - self.__conn_storage = None - - @property - def _conn(self): - if self.__conn is None: - self.__conn = wmi.WMI(moniker='//./root/virtualization') - return self.__conn - - @property - def _conn_v2(self): - if self.__conn_v2 is None: - self.__conn_v2 = wmi.WMI(moniker='//./root/virtualization/v2') - return self.__conn_v2 - - @property - def _conn_cimv2(self): - if self.__conn_cimv2 is None: - self.__conn_cimv2 = wmi.WMI(moniker='//./root/cimv2') - return self.__conn_cimv2 - - @property - def _conn_wmi(self): - if self.__conn_wmi is None: - self.__conn_wmi = wmi.WMI(moniker='//./root/wmi') - return self.__conn_wmi - - @property - def _conn_storage(self): - if self.__conn_storage is None: - storage_namespace = '//./Root/Microsoft/Windows/Storage' - self.__conn_storage = wmi.WMI(moniker=storage_namespace) - return self.__conn_storage diff --git a/nova/virt/hyperv/basevolumeutils.py b/nova/virt/hyperv/basevolumeutils.py index 2352c3bef..34b15ea53 100644 --- a/nova/virt/hyperv/basevolumeutils.py +++ b/nova/virt/hyperv/basevolumeutils.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 Pedro Navarro Perez +# Copyright 2013 Cloudbase Solutions Srl # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -20,17 +21,18 @@ Helper methods for operations related to the management of volumes, and storage repositories """ +import abc import sys +if sys.platform == 'win32': + import _winreg + import wmi + from nova import block_device from nova.openstack.common import cfg from nova.openstack.common import log as logging from nova.virt import driver -# Check needed for unit testing on Unix -if sys.platform == 'win32': - import _winreg - LOG = logging.getLogger(__name__) CONF = cfg.CONF CONF.import_opt('my_ip', 'nova.netconf') @@ -38,25 +40,40 @@ CONF.import_opt('my_ip', 'nova.netconf') class BaseVolumeUtils(object): + def __init__(self): + if sys.platform == 'win32': + self._conn_wmi = wmi.WMI(moniker='//./root/wmi') + + @abc.abstractmethod + def login_storage_target(self, target_lun, target_iqn, target_portal): + pass + + @abc.abstractmethod + def logout_storage_target(self, target_iqn): + pass + + @abc.abstractmethod + def execute_log_out(self, session_id): + pass + def get_iscsi_initiator(self, cim_conn): """Get iscsi initiator name for this machine.""" computer_system = cim_conn.Win32_ComputerSystem()[0] hostname = computer_system.name - keypath = \ - r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\iSCSI\Discovery" + keypath = ("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\" + "iSCSI\\Discovery") try: key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, keypath, 0, - _winreg.KEY_ALL_ACCESS) + _winreg.KEY_ALL_ACCESS) temp = _winreg.QueryValueEx(key, 'DefaultInitiatorName') initiator_name = str(temp[0]) _winreg.CloseKey(key) except Exception: LOG.info(_("The ISCSI initiator name can't be found. " - "Choosing the default one")) + "Choosing the default one")) computer_system = cim_conn.Win32_ComputerSystem()[0] - initiator_name = "iqn.1991-05.com.microsoft:" + \ - hostname.lower() + initiator_name = "iqn.1991-05.com.microsoft:" + hostname.lower() return { 'ip': CONF.my_ip, 'initiator': initiator_name, @@ -78,3 +95,33 @@ class BaseVolumeUtils(object): LOG.debug(_("block_device_list %s"), block_device_list) return block_device.strip_dev(mount_device) in block_device_list + + def _get_drive_number_from_disk_path(self, disk_path): + # TODO(pnavarro) replace with regex + start_device_id = disk_path.find('"', disk_path.find('DeviceID')) + end_device_id = disk_path.find('"', start_device_id + 1) + device_id = disk_path[start_device_id + 1:end_device_id] + return device_id[device_id.find("\\") + 2:] + + def get_session_id_from_mounted_disk(self, physical_drive_path): + drive_number = self._get_drive_number_from_disk_path( + physical_drive_path) + initiator_sessions = self._conn_wmi.query("SELECT * FROM " + "MSiSCSIInitiator_Session" + "Class") + for initiator_session in initiator_sessions: + devices = initiator_session.Devices + for device in devices: + device_number = str(device.DeviceNumber) + if device_number == drive_number: + return initiator_session.SessionId + + def get_device_number_for_target(self, target_iqn, target_lun): + initiator_session = self._conn_wmi.query("SELECT * FROM " + "MSiSCSIInitiator_Session" + "Class WHERE TargetName='%s'" + % target_iqn)[0] + devices = initiator_session.Devices + for device in devices: + if device.ScsiLun == target_lun: + return device.DeviceNumber diff --git a/nova/virt/hyperv/constants.py b/nova/virt/hyperv/constants.py index 4be569e88..31323f0f4 100644 --- a/nova/virt/hyperv/constants.py +++ b/nova/virt/hyperv/constants.py @@ -35,15 +35,6 @@ HYPERV_POWER_STATE = { HYPERV_VM_STATE_SUSPENDED: power_state.SUSPENDED } -REQ_POWER_STATE = { - 'Enabled': HYPERV_VM_STATE_ENABLED, - 'Disabled': HYPERV_VM_STATE_DISABLED, - 'Reboot': HYPERV_VM_STATE_REBOOT, - 'Reset': HYPERV_VM_STATE_RESET, - 'Paused': HYPERV_VM_STATE_PAUSED, - 'Suspended': HYPERV_VM_STATE_SUSPENDED, -} - WMI_WIN32_PROCESSOR_ARCHITECTURE = { 0: 'x86', 1: 'MIPS', diff --git a/nova/virt/hyperv/driver.py b/nova/virt/hyperv/driver.py index 799ef7172..e8bf8c416 100644 --- a/nova/virt/hyperv/driver.py +++ b/nova/virt/hyperv/driver.py @@ -16,49 +16,7 @@ # under the License. """ -A connection to Hyper-V . -Uses Windows Management Instrumentation (WMI) calls to interact with Hyper-V -Hyper-V WMI usage: - http://msdn.microsoft.com/en-us/library/cc723875%28v=VS.85%29.aspx -The Hyper-V object model briefly: - The physical computer and its hosted virtual machines are each represented - by the Msvm_ComputerSystem class. - - Each virtual machine is associated with a - Msvm_VirtualSystemGlobalSettingData (vs_gs_data) instance and one or more - Msvm_VirtualSystemSettingData (vmsetting) instances. For each vmsetting - there is a series of Msvm_ResourceAllocationSettingData (rasd) objects. - The rasd objects describe the settings for each device in a VM. - Together, the vs_gs_data, vmsettings and rasds describe the configuration - of the virtual machine. - - Creating new resources such as disks and nics involves cloning a default - rasd object and appropriately modifying the clone and calling the - AddVirtualSystemResources WMI method - Changing resources such as memory uses the ModifyVirtualSystemResources - WMI method - -Using the Python WMI library: - Tutorial: - http://timgolden.me.uk/python/wmi/tutorial.html - Hyper-V WMI objects can be retrieved simply by using the class name - of the WMI object and optionally specifying a column to filter the - result set. More complex filters can be formed using WQL (sql-like) - queries. - The parameters and return tuples of WMI method calls can gleaned by - examining the doc string. For example: - >>> vs_man_svc.ModifyVirtualSystemResources.__doc__ - ModifyVirtualSystemResources (ComputerSystem, ResourceSettingData[]) - => (Job, ReturnValue)' - When passing setting data (ResourceSettingData) to the WMI method, - an XML representation of the data is passed in using GetText_(1). - Available methods on a service can be determined using method.keys(): - >>> vs_man_svc.methods.keys() - vmsettings and rasds for a vm can be retrieved using the 'associators' - method with the appropriate return class. - Long running WMI commands generally return a Job (an instance of - Msvm_ConcreteJob) whose state can be polled to determine when it finishes - +A Hyper-V Nova Compute driver. """ from nova.openstack.common import log as logging @@ -84,7 +42,7 @@ class HyperVDriver(driver.ComputeDriver): self._volumeops) def init_host(self, host): - self._host = host + pass def list_instances(self): return self._vmops.list_instances() @@ -92,7 +50,7 @@ class HyperVDriver(driver.ComputeDriver): def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info=None, block_device_info=None): self._vmops.spawn(context, instance, image_meta, injected_files, - admin_password, network_info, block_device_info) + admin_password, network_info, block_device_info) def reboot(self, instance, network_info, reboot_type, block_device_info=None): @@ -106,16 +64,12 @@ class HyperVDriver(driver.ComputeDriver): return self._vmops.get_info(instance) def attach_volume(self, connection_info, instance, mountpoint): - """Attach volume storage to VM instance.""" return self._volumeops.attach_volume(connection_info, - instance['name'], - mountpoint) + instance['name']) def detach_volume(self, connection_info, instance, mountpoint): - """Detach volume storage to VM instance.""" return self._volumeops.detach_volume(connection_info, - instance['name'], - mountpoint) + instance['name']) def get_volume_connector(self, instance): return self._volumeops.get_volume_connector(instance) @@ -151,30 +105,38 @@ class HyperVDriver(driver.ComputeDriver): self._vmops.power_on(instance) def live_migration(self, context, instance_ref, dest, post_method, - recover_method, block_migration=False, migrate_data=None): + recover_method, block_migration=False, + migrate_data=None): self._livemigrationops.live_migration(context, instance_ref, dest, - post_method, recover_method, block_migration, migrate_data) + post_method, recover_method, + block_migration, migrate_data) def compare_cpu(self, cpu_info): return self._livemigrationops.compare_cpu(cpu_info) def pre_live_migration(self, context, instance, block_device_info, - network_info, migrate_data=None): + network_info, migrate_data=None): self._livemigrationops.pre_live_migration(context, instance, - block_device_info, network_info) + block_device_info, + network_info) def post_live_migration_at_destination(self, ctxt, instance_ref, - network_info, block_migration): + network_info, + block_migr=False, + block_device_info=None): self._livemigrationops.post_live_migration_at_destination(ctxt, - instance_ref, network_info, block_migration) - - def check_can_live_migrate_destination(self, ctxt, instance, - src_compute_info, dst_compute_info, - block_migration, disk_over_commit): + instance_ref, + network_info, + block_migr) + + def check_can_live_migrate_destination(self, ctxt, instance_ref, + src_compute_info, dst_compute_info, + block_migration=False, + disk_over_commit=False): pass def check_can_live_migrate_destination_cleanup(self, ctxt, - dest_check_data): + dest_check_data): pass def check_can_live_migrate_source(self, ctxt, instance, dest_check_data): @@ -188,25 +150,21 @@ class HyperVDriver(driver.ComputeDriver): def ensure_filtering_rules_for_instance(self, instance_ref, network_info): LOG.debug(_("ensure_filtering_rules_for_instance called"), - instance=instance_ref) + instance=instance_ref) def unfilter_instance(self, instance, network_info): - """Stop filtering instance.""" LOG.debug(_("unfilter_instance called"), instance=instance) def confirm_migration(self, migration, instance, network_info): - """Confirms a resize, destroying the source VM.""" LOG.debug(_("confirm_migration called"), instance=instance) def finish_revert_migration(self, instance, network_info, block_device_info=None): - """Finish reverting a resize, powering back on the instance.""" LOG.debug(_("finish_revert_migration called"), instance=instance) def finish_migration(self, context, migration, instance, disk_info, - network_info, image_meta, resize_instance=False, - block_device_info=None): - """Completes a resize, turning on the migrated instance.""" + network_info, image_meta, resize_instance=False, + block_device_info=None): LOG.debug(_("finish_migration called"), instance=instance) def get_console_output(self, instance): diff --git a/nova/virt/hyperv/hostops.py b/nova/virt/hyperv/hostops.py index 5cbe46c1c..5a22b60de 100644 --- a/nova/virt/hyperv/hostops.py +++ b/nova/virt/hyperv/hostops.py @@ -18,25 +18,23 @@ """ Management class for host operations. """ -import ctypes -import multiprocessing import os import platform -from nova.openstack.common import cfg from nova.openstack.common import jsonutils from nova.openstack.common import log as logging -from nova.virt.hyperv import baseops from nova.virt.hyperv import constants +from nova.virt.hyperv import hostutils +from nova.virt.hyperv import pathutils -CONF = cfg.CONF LOG = logging.getLogger(__name__) -class HostOps(baseops.BaseOps): +class HostOps(object): def __init__(self): - super(HostOps, self).__init__() self._stats = None + self._hostutils = hostutils.HostUtils() + self._pathutils = pathutils.PathUtils() def _get_cpu_info(self): """Get the CPU information. @@ -44,94 +42,51 @@ class HostOps(baseops.BaseOps): of the central processor in the hypervisor. """ cpu_info = dict() - processor = self._conn_cimv2.query( - "SELECT * FROM Win32_Processor WHERE ProcessorType = 3") - cpu_info['arch'] = constants.WMI_WIN32_PROCESSOR_ARCHITECTURE\ - .get(processor[0].Architecture, 'Unknown') - cpu_info['model'] = processor[0].Name - cpu_info['vendor'] = processor[0].Manufacturer + processors = self._hostutils.get_cpus_info() + + w32_arch_dict = constants.WMI_WIN32_PROCESSOR_ARCHITECTURE + cpu_info['arch'] = w32_arch_dict.get(processors[0]['Architecture'], + 'Unknown') + cpu_info['model'] = processors[0]['Name'] + cpu_info['vendor'] = processors[0]['Manufacturer'] topology = dict() - topology['sockets'] = len(processor) - topology['cores'] = processor[0].NumberOfCores - topology['threads'] = processor[0].NumberOfLogicalProcessors\ - / processor[0].NumberOfCores + topology['sockets'] = len(processors) + topology['cores'] = processors[0]['NumberOfCores'] + topology['threads'] = (processors[0]['NumberOfLogicalProcessors'] / + processors[0]['NumberOfCores']) cpu_info['topology'] = topology features = list() for fkey, fname in constants.PROCESSOR_FEATURE.items(): - if ctypes.windll.kernel32.IsProcessorFeaturePresent(fkey): + if self._hostutils.is_cpu_feature_present(fkey): features.append(fname) cpu_info['features'] = features - return jsonutils.dumps(cpu_info) + return cpu_info - def _get_vcpu_total(self): - """Get vcpu number of physical computer. - :returns: the number of cpu core. - """ - # On certain platforms, this will raise a NotImplementedError. - try: - return multiprocessing.cpu_count() - except NotImplementedError: - LOG.warn(_("Cannot get the number of cpu, because this " - "function is not implemented for this platform. " - "This error can be safely ignored for now.")) - return 0 - - def _get_memory_mb_total(self): - """Get the total memory size(MB) of physical computer. - :returns: the total amount of memory(MB). - """ - total_kb = self._conn_cimv2.query( - "SELECT TotalVisibleMemorySize FROM win32_operatingsystem")[0]\ - .TotalVisibleMemorySize - total_mb = long(total_kb) / 1024 - return total_mb + def _get_memory_info(self): + (total_mem_kb, free_mem_kb) = self._hostutils.get_memory_info() + total_mem_mb = total_mem_kb / 1024 + free_mem_mb = free_mem_kb / 1024 + return (total_mem_mb, free_mem_mb, total_mem_mb - free_mem_mb) def _get_local_hdd_info_gb(self): - """Get the total and used size of the volume containing - CONF.instances_path expressed in GB. - :returns: - A tuple with the total and used space in GB. - """ - normalized_path = os.path.normpath(CONF.instances_path) - drive, path = os.path.splitdrive(normalized_path) - hdd_info = self._conn_cimv2.query( - ("SELECT FreeSpace,Size FROM win32_logicaldisk WHERE DeviceID='%s'" - ) % drive)[0] - total_gb = long(hdd_info.Size) / (1024 ** 3) - free_gb = long(hdd_info.FreeSpace) / (1024 ** 3) - used_gb = total_gb - free_gb - return total_gb, used_gb + (drive, _) = os.path.splitdrive(self._pathutils.get_instances_path()) + (size, free_space) = self._hostutils.get_volume_info(drive) - def _get_vcpu_used(self): - """Get vcpu usage number of physical computer. - :returns: The total number of vcpu that currently used. - """ - #TODO(jordanrinke) figure out a way to count assigned VCPUs - total_vcpu = 0 - return total_vcpu - - def _get_memory_mb_used(self): - """Get the free memory size(MB) of physical computer. - :returns: the total usage of memory(MB). - """ - total_kb = self._conn_cimv2.query( - "SELECT FreePhysicalMemory FROM win32_operatingsystem")[0]\ - .FreePhysicalMemory - total_mb = long(total_kb) / 1024 - - return total_mb + total_gb = size / (1024 ** 3) + free_gb = free_space / (1024 ** 3) + used_gb = total_gb - free_gb + return (total_gb, free_gb, used_gb) def _get_hypervisor_version(self): """Get hypervisor version. :returns: hypervisor version (ex. 12003) """ - version = self._conn_cimv2.Win32_OperatingSystem()[0]\ - .Version.replace('.', '') - LOG.info(_('Windows version: %s ') % version) + version = self._hostutils.get_windows_version().replace('.', '') + LOG.debug(_('Windows version: %s ') % version) return version def get_available_resource(self): @@ -143,36 +98,53 @@ class HostOps(baseops.BaseOps): :returns: dictionary describing resources """ - LOG.info(_('get_available_resource called')) - - local_gb, used_gb = self._get_local_hdd_info_gb() - dic = {'vcpus': self._get_vcpu_total(), - 'memory_mb': self._get_memory_mb_total(), - 'local_gb': local_gb, - 'vcpus_used': self._get_vcpu_used(), - 'memory_mb_used': self._get_memory_mb_used(), - 'local_gb_used': used_gb, + LOG.debug(_('get_available_resource called')) + + (total_mem_mb, + free_mem_mb, + used_mem_mb) = self._get_memory_info() + + (total_hdd_gb, + free_hdd_gb, + used_hdd_gb) = self._get_local_hdd_info_gb() + + cpu_info = self._get_cpu_info() + cpu_topology = cpu_info['topology'] + vcpus = (cpu_topology['sockets'] * + cpu_topology['cores'] * + cpu_topology['threads']) + + dic = {'vcpus': vcpus, + 'memory_mb': total_mem_mb, + 'memory_mb_used': used_mem_mb, + 'local_gb': total_hdd_gb, + 'local_gb_used': used_hdd_gb, 'hypervisor_type': "hyperv", 'hypervisor_version': self._get_hypervisor_version(), 'hypervisor_hostname': platform.node(), - 'cpu_info': self._get_cpu_info()} + 'vcpus_used': 0, + 'cpu_info': jsonutils.dumps(cpu_info)} return dic def _update_stats(self): LOG.debug(_("Updating host stats")) + (total_mem_mb, free_mem_mb, used_mem_mb) = self._get_memory_info() + (total_hdd_gb, + free_hdd_gb, + used_hdd_gb) = self._get_local_hdd_info_gb() + data = {} - data["disk_total"], data["disk_used"] = self._get_local_hdd_info_gb() - data["disk_available"] = data["disk_total"] - data["disk_used"] - data["host_memory_total"] = self._get_memory_mb_total() - data["host_memory_overhead"] = self._get_memory_mb_used() - data["host_memory_free"] = \ - data["host_memory_total"] - data["host_memory_overhead"] - data["host_memory_free_computed"] = data["host_memory_free"] - data["supported_instances"] = \ - [('i686', 'hyperv', 'hvm'), - ('x86_64', 'hyperv', 'hvm')] + data["disk_total"] = total_hdd_gb + data["disk_used"] = used_hdd_gb + data["disk_available"] = free_hdd_gb + data["host_memory_total"] = total_mem_mb + data["host_memory_overhead"] = used_mem_mb + data["host_memory_free"] = free_mem_mb + data["host_memory_free_computed"] = free_mem_mb + data["supported_instances"] = [('i686', 'hyperv', 'hvm'), + ('x86_64', 'hyperv', 'hvm')] data["hypervisor_hostname"] = platform.node() self._stats = data diff --git a/nova/virt/hyperv/hostutils.py b/nova/virt/hyperv/hostutils.py new file mode 100644 index 000000000..71f3bc5b2 --- /dev/null +++ b/nova/virt/hyperv/hostutils.py @@ -0,0 +1,74 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# 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 ctypes +import sys + +if sys.platform == 'win32': + import wmi + +from nova.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + + +class HostUtils(object): + def __init__(self): + if sys.platform == 'win32': + self._conn_cimv2 = wmi.WMI(moniker='//./root/cimv2') + + def get_cpus_info(self): + cpus = self._conn_cimv2.query("SELECT * FROM Win32_Processor " + "WHERE ProcessorType = 3") + cpus_list = [] + for cpu in cpus: + cpu_info = {'Architecture': cpu.Architecture, + 'Name': cpu.Name, + 'Manufacturer': cpu.Manufacturer, + 'NumberOfCores': cpu.NumberOfCores, + 'NumberOfLogicalProcessors': + cpu.NumberOfLogicalProcessors} + cpus_list.append(cpu_info) + return cpus_list + + def is_cpu_feature_present(self, feature_key): + return ctypes.windll.kernel32.IsProcessorFeaturePresent(feature_key) + + def get_memory_info(self): + """ + Returns a tuple with total visible memory and free physical memory + expressed in kB. + """ + mem_info = self._conn_cimv2.query("SELECT TotalVisibleMemorySize, " + "FreePhysicalMemory " + "FROM win32_operatingsystem")[0] + return (long(mem_info.TotalVisibleMemorySize), + long(mem_info.FreePhysicalMemory)) + + def get_volume_info(self, drive): + """ + Returns a tuple with total size and free space + expressed in bytes. + """ + logical_disk = self._conn_cimv2.query("SELECT Size, FreeSpace " + "FROM win32_logicaldisk " + "WHERE DeviceID='%s'" + % drive)[0] + return (long(logical_disk.Size), long(logical_disk.FreeSpace)) + + def get_windows_version(self): + return self._conn_cimv2.Win32_OperatingSystem()[0].Version diff --git a/nova/virt/hyperv/livemigrationops.py b/nova/virt/hyperv/livemigrationops.py index 232cbd660..8ee3005f1 100644 --- a/nova/virt/hyperv/livemigrationops.py +++ b/nova/virt/hyperv/livemigrationops.py @@ -19,144 +19,66 @@ Management class for live migration VM operations. """ import os -import sys -from nova import exception from nova.openstack.common import cfg from nova.openstack.common import excutils from nova.openstack.common import log as logging -from nova.virt.hyperv import baseops -from nova.virt.hyperv import constants +from nova.virt.hyperv import livemigrationutils +from nova.virt.hyperv import pathutils from nova.virt.hyperv import vmutils - -# Check needed for unit testing on Unix -if sys.platform == 'win32': - import wmi +from nova.virt import images LOG = logging.getLogger(__name__) CONF = cfg.CONF CONF.import_opt('use_cow_images', 'nova.virt.driver') -class LiveMigrationOps(baseops.BaseOps): +class LiveMigrationOps(object): def __init__(self, volumeops): - super(LiveMigrationOps, self).__init__() + self._pathutils = pathutils.PathUtils() self._vmutils = vmutils.VMUtils() + self._livemigrutils = livemigrationutils.LiveMigrationUtils() self._volumeops = volumeops - def _check_live_migration_config(self): - try: - self._conn_v2 - except Exception: - raise vmutils.HyperVException( - _('Live migration is not supported " \ - "by this version of Hyper-V')) - - migration_svc = self._conn_v2.Msvm_VirtualSystemMigrationService()[0] - vsmssd = migration_svc.associators( - wmi_association_class='Msvm_ElementSettingData', - wmi_result_class='Msvm_VirtualSystemMigrationServiceSettingData')[0] - if not vsmssd.EnableVirtualSystemMigration: - raise vmutils.HyperVException( - _('Live migration is not enabled on this host')) - if not migration_svc.MigrationServiceListenerIPAddressList: - raise vmutils.HyperVException( - _('Live migration networks are not configured on this host')) - def live_migration(self, context, instance_ref, dest, post_method, - recover_method, block_migration=False, migrate_data=None): + recover_method, block_migration=False, + migrate_data=None): LOG.debug(_("live_migration called"), instance=instance_ref) instance_name = instance_ref["name"] try: - self._check_live_migration_config() - - vm_name = self._vmutils.lookup(self._conn, instance_name) - if vm_name is None: - raise exception.InstanceNotFound(instance=instance_name) - vm = self._conn_v2.Msvm_ComputerSystem( - ElementName=instance_name)[0] - vm_settings = vm.associators( - wmi_association_class='Msvm_SettingsDefineState', - wmi_result_class='Msvm_VirtualSystemSettingData')[0] - - new_resource_setting_data = [] - sasds = vm_settings.associators( - wmi_association_class='Msvm_VirtualSystemSettingDataComponent', - wmi_result_class='Msvm_StorageAllocationSettingData') - for sasd in sasds: - if sasd.ResourceType == 31 and \ - sasd.ResourceSubType == \ - "Microsoft:Hyper-V:Virtual Hard Disk": - #sasd.PoolId = "" - new_resource_setting_data.append(sasd.GetText_(1)) - - LOG.debug(_("Getting live migration networks for remote " - "host: %s"), dest) - _conn_v2_remote = wmi.WMI( - moniker='//' + dest + '/root/virtualization/v2') - migration_svc_remote = \ - _conn_v2_remote.Msvm_VirtualSystemMigrationService()[0] - remote_ip_address_list = \ - migration_svc_remote.MigrationServiceListenerIPAddressList - - # VirtualSystemAndStorage - vsmsd = self._conn_v2.query("select * from " - "Msvm_VirtualSystemMigrationSettingData " - "where MigrationType = 32771")[0] - vsmsd.DestinationIPAddressList = remote_ip_address_list - migration_setting_data = vsmsd.GetText_(1) - - migration_svc =\ - self._conn_v2.Msvm_VirtualSystemMigrationService()[0] - - LOG.debug(_("Starting live migration for instance: %s"), - instance_name) - (job_path, ret_val) = migration_svc.MigrateVirtualSystemToHost( - ComputerSystem=vm.path_(), - DestinationHost=dest, - MigrationSettingData=migration_setting_data, - NewResourceSettingData=new_resource_setting_data) - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._vmutils.check_job_status(job_path) - else: - success = (ret_val == 0) - if not success: - raise vmutils.HyperVException( - _('Failed to live migrate VM %s') % instance_name) + self._livemigrutils.live_migrate_vm(instance_name, dest) except Exception: with excutils.save_and_reraise_exception(): LOG.debug(_("Calling live migration recover_method " - "for instance: %s"), instance_name) + "for instance: %s"), instance_name) recover_method(context, instance_ref, dest, block_migration) LOG.debug(_("Calling live migration post_method for instance: %s"), - instance_name) + instance_name) post_method(context, instance_ref, dest, block_migration) def pre_live_migration(self, context, instance, block_device_info, - network_info): + network_info): LOG.debug(_("pre_live_migration called"), instance=instance) - self._check_live_migration_config() + self._livemigrutils.check_live_migration_config() if CONF.use_cow_images: ebs_root = self._volumeops.volume_in_mapping( self._volumeops.get_default_root_device(), block_device_info) if not ebs_root: - base_vhd_path = self._vmutils.get_base_vhd_path( + base_vhd_path = self._pathutils.get_base_vhd_path( instance["image_ref"]) if not os.path.exists(base_vhd_path): - self._vmutils.fetch_image(base_vhd_path, context, - instance["image_ref"], - instance["user_id"], - instance["project_id"]) + images.fetch(context, instance["image_ref"], base_vhd_path, + instance["user_id"], instance["project_id"]) def post_live_migration_at_destination(self, ctxt, instance_ref, - network_info, block_migration): + network_info, block_migration): LOG.debug(_("post_live_migration_at_destination called"), - instance=instance_ref) + instance=instance_ref) def compare_cpu(self, cpu_info): LOG.debug(_("compare_cpu called %s"), cpu_info) diff --git a/nova/virt/hyperv/livemigrationutils.py b/nova/virt/hyperv/livemigrationutils.py new file mode 100644 index 000000000..6af4f0fa5 --- /dev/null +++ b/nova/virt/hyperv/livemigrationutils.py @@ -0,0 +1,115 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# 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 sys + +if sys.platform == 'win32': + import wmi + +from nova.openstack.common import log as logging +from nova.virt.hyperv import vmutils + +LOG = logging.getLogger(__name__) + + +class LiveMigrationUtils(object): + + def __init__(self): + self._vmutils = vmutils.VMUtils() + + def _get_conn_v2(self, host='localhost'): + try: + return wmi.WMI(moniker='//%s/root/virtualization/v2' % host) + except wmi.x_wmi as ex: + LOG.exception(ex) + if ex.com_error.hresult == -2147217394: + msg = (_('Live migration is not supported on target host "%s"') + % host) + elif ex.com_error.hresult == -2147023174: + msg = (_('Target live migration host "%s" is unreachable') + % host) + else: + msg = _('Live migration failed: %s') % ex.message + raise vmutils.HyperVException(msg) + + def check_live_migration_config(self): + conn_v2 = self._get_conn_v2() + migration_svc = conn_v2.Msvm_VirtualSystemMigrationService()[0] + vsmssds = migration_svc.associators( + wmi_association_class='Msvm_ElementSettingData', + wmi_result_class='Msvm_VirtualSystemMigrationServiceSettingData') + vsmssd = vsmssds[0] + if not vsmssd.EnableVirtualSystemMigration: + raise vmutils.HyperVException( + _('Live migration is not enabled on this host')) + if not migration_svc.MigrationServiceListenerIPAddressList: + raise vmutils.HyperVException( + _('Live migration networks are not configured on this host')) + + def _get_vm(self, conn_v2, vm_name): + vms = conn_v2.Msvm_ComputerSystem(ElementName=vm_name) + n = len(vms) + if not n: + raise vmutils.HyperVException(_('VM not found: %s') % vm_name) + elif n > 1: + raise vmutils.HyperVException(_('Duplicate VM name found: %s') + % vm_name) + return vms[0] + + def live_migrate_vm(self, vm_name, dest_host): + self.check_live_migration_config() + + # We need a v2 namespace VM object + conn_v2_local = self._get_conn_v2() + + vm = self._get_vm(conn_v2_local, vm_name) + vm_settings = vm.associators( + wmi_association_class='Msvm_SettingsDefineState', + wmi_result_class='Msvm_VirtualSystemSettingData')[0] + + new_resource_setting_data = [] + sasds = vm_settings.associators( + wmi_association_class='Msvm_VirtualSystemSettingDataComponent', + wmi_result_class='Msvm_StorageAllocationSettingData') + for sasd in sasds: + if (sasd.ResourceType == 31 and sasd.ResourceSubType == + "Microsoft:Hyper-V:Virtual Hard Disk"): + #sasd.PoolId = "" + new_resource_setting_data.append(sasd.GetText_(1)) + + LOG.debug(_("Getting live migration networks for remote host: %s"), + dest_host) + conn_v2_remote = self._get_conn_v2(dest_host) + migr_svc_rmt = conn_v2_remote.Msvm_VirtualSystemMigrationService()[0] + rmt_ip_addr_list = migr_svc_rmt.MigrationServiceListenerIPAddressList + + # VirtualSystemAndStorage + vsmsd = conn_v2_local.query("select * from " + "Msvm_VirtualSystemMigrationSettingData " + "where MigrationType = 32771")[0] + vsmsd.DestinationIPAddressList = rmt_ip_addr_list + migration_setting_data = vsmsd.GetText_(1) + + migr_svc = conn_v2_local.Msvm_VirtualSystemMigrationService()[0] + + LOG.debug(_("Starting live migration for VM: %s"), vm_name) + (job_path, ret_val) = migr_svc.MigrateVirtualSystemToHost( + ComputerSystem=vm.path_(), + DestinationHost=dest_host, + MigrationSettingData=migration_setting_data, + NewResourceSettingData=new_resource_setting_data) + self._vmutils.check_ret_val(ret_val, job_path) diff --git a/nova/virt/hyperv/networkutils.py b/nova/virt/hyperv/networkutils.py new file mode 100644 index 000000000..4e1f68685 --- /dev/null +++ b/nova/virt/hyperv/networkutils.py @@ -0,0 +1,62 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Cloudbase Solutions Srl +# 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. + +""" +Utility class for network related operations. +""" + +import sys +import uuid + +if sys.platform == 'win32': + import wmi + +from nova.virt.hyperv import vmutils + + +class NetworkUtils(object): + def __init__(self): + if sys.platform == 'win32': + self._conn = wmi.WMI(moniker='//./root/virtualization') + + def get_external_vswitch(self, vswitch_name): + if vswitch_name: + vswitches = self._conn.Msvm_VirtualSwitch(ElementName=vswitch_name) + else: + # Find the vswitch that is connected to the first physical nic. + ext_port = self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')[0] + port = ext_port.associators(wmi_result_class='Msvm_SwitchPort')[0] + vswitches = port.associators(wmi_result_class='Msvm_VirtualSwitch') + + if not len(vswitches): + raise vmutils.HyperVException(_('vswitch "%s" not found') + % vswitch_name) + return vswitches[0].path_() + + def create_vswitch_port(self, vswitch_path, port_name): + switch_svc = self._conn.Msvm_VirtualSwitchManagementService()[0] + #Create a port on the vswitch. + (new_port, ret_val) = switch_svc.CreateSwitchPort( + Name=str(uuid.uuid4()), + FriendlyName=port_name, + ScopeOfResidence="", + VirtualSwitch=vswitch_path) + if ret_val != 0: + raise vmutils.HyperVException(_("Failed to create vswitch port " + "%(port_name)s on switch " + "%(vswitch_path)s") % locals()) + return new_port diff --git a/nova/virt/hyperv/pathutils.py b/nova/virt/hyperv/pathutils.py new file mode 100644 index 000000000..7bc2e7ac2 --- /dev/null +++ b/nova/virt/hyperv/pathutils.py @@ -0,0 +1,67 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Cloudbase Solutions Srl +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import shutil + +from nova.openstack.common import cfg +from nova.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + +CONF = cfg.CONF +CONF.import_opt('instances_path', 'nova.compute.manager') + + +class PathUtils(object): + def open(self, path, mode): + """Wrapper on __builin__.open used to simplify unit testing.""" + import __builtin__ + return __builtin__.open(path, mode) + + def get_instances_path(self): + return os.path.normpath(CONF.instances_path) + + def get_instance_path(self, instance_name): + instance_path = os.path.join(self.get_instances_path(), instance_name) + if not os.path.exists(instance_path): + LOG.debug(_('Creating folder %s '), instance_path) + os.makedirs(instance_path) + return instance_path + + def get_vhd_path(self, instance_name): + instance_path = self.get_instance_path(instance_name) + return os.path.join(instance_path, instance_name + ".vhd") + + def get_base_vhd_path(self, image_name): + base_dir = os.path.join(self.get_instances_path(), '_base') + if not os.path.exists(base_dir): + os.makedirs(base_dir) + return os.path.join(base_dir, image_name + ".vhd") + + def make_export_path(self, instance_name): + export_folder = os.path.join(self.get_instances_path(), "export", + instance_name) + if os.path.isdir(export_folder): + LOG.debug(_('Removing existing folder %s '), export_folder) + shutil.rmtree(export_folder) + LOG.debug(_('Creating folder %s '), export_folder) + os.makedirs(export_folder) + return export_folder + + def vhd_exists(self, path): + return os.path.exists(path) diff --git a/nova/virt/hyperv/snapshotops.py b/nova/virt/hyperv/snapshotops.py index cdc6e45a4..c43f59b70 100644 --- a/nova/virt/hyperv/snapshotops.py +++ b/nova/virt/hyperv/snapshotops.py @@ -20,173 +20,97 @@ Management class for VM snapshot operations. """ import os import shutil -import sys from nova.compute import task_states -from nova import exception from nova.image import glance from nova.openstack.common import cfg from nova.openstack.common import log as logging -from nova.virt.hyperv import baseops -from nova.virt.hyperv import constants -from nova.virt.hyperv import ioutils +from nova.virt.hyperv import pathutils +from nova.virt.hyperv import vhdutils from nova.virt.hyperv import vmutils -from xml.etree import ElementTree - -# Check needed for unit testing on Unix -if sys.platform == 'win32': - import wmi CONF = cfg.CONF LOG = logging.getLogger(__name__) -class SnapshotOps(baseops.BaseOps): +class SnapshotOps(object): def __init__(self): - super(SnapshotOps, self).__init__() + self._pathutils = pathutils.PathUtils() self._vmutils = vmutils.VMUtils() + self._vhdutils = vhdutils.VHDUtils() + + def _save_glance_image(self, context, name, image_vhd_path): + (glance_image_service, + image_id) = glance.get_remote_image_service(context, name) + image_metadata = {"is_public": False, + "disk_format": "vhd", + "container_format": "bare", + "properties": {}} + with self._pathutils.open(image_vhd_path, 'rb') as f: + glance_image_service.update(context, image_id, image_metadata, f) def snapshot(self, context, instance, name, update_task_state): """Create snapshot from a running VM instance.""" instance_name = instance["name"] - vm = self._vmutils.lookup(self._conn, instance_name) - if vm is None: - raise exception.InstanceNotFound(instance=instance_name) - vm = self._conn.Msvm_ComputerSystem(ElementName=instance_name)[0] - vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] LOG.debug(_("Creating snapshot for instance %s"), instance_name) - (job_path, ret_val, snap_setting_data) = \ - vs_man_svc.CreateVirtualSystemSnapshot(vm.path_()) - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._vmutils.check_job_status(job_path) - if success: - job_wmi_path = job_path.replace('\\', '/') - job = wmi.WMI(moniker=job_wmi_path) - snap_setting_data = job.associators( - wmi_result_class='Msvm_VirtualSystemSettingData')[0] - else: - success = (ret_val == 0) - if not success: - raise vmutils.HyperVException( - _('Failed to create snapshot for VM %s') % - instance_name) - else: - update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) + snapshot_path = self._vmutils.take_vm_snapshot(instance_name) + update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) export_folder = None - f = None try: - src_vhd_path = os.path.join(CONF.instances_path, instance_name, - instance_name + ".vhd") - - image_man_svc = self._conn.Msvm_ImageManagementService()[0] + src_vhd_path = self._pathutils.get_vhd_path(instance_name) LOG.debug(_("Getting info for VHD %s"), src_vhd_path) - (src_vhd_info, job_path, ret_val) = \ - image_man_svc.GetVirtualHardDiskInfo(src_vhd_path) - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._vmutils.check_job_status(job_path) - else: - success = (ret_val == 0) - if not success: - raise vmutils.HyperVException( - _("Failed to get info for disk %s") % - (src_vhd_path)) + src_base_disk_path = self._vhdutils.get_vhd_parent_path( + src_vhd_path) - src_base_disk_path = None - et = ElementTree.fromstring(src_vhd_info) - for item in et.findall("PROPERTY"): - if item.attrib["NAME"] == "ParentPath": - src_base_disk_path = item.find("VALUE").text - break - - export_folder = self._vmutils.make_export_path(instance_name) + export_folder = self._pathutils.make_export_path(instance_name) dest_vhd_path = os.path.join(export_folder, os.path.basename( src_vhd_path)) LOG.debug(_('Copying VHD %(src_vhd_path)s to %(dest_vhd_path)s'), - locals()) + locals()) shutil.copyfile(src_vhd_path, dest_vhd_path) image_vhd_path = None if not src_base_disk_path: image_vhd_path = dest_vhd_path else: - dest_base_disk_path = os.path.join(export_folder, - os.path.basename(src_base_disk_path)) + basename = os.path.basename(src_base_disk_path) + dest_base_disk_path = os.path.join(export_folder, basename) LOG.debug(_('Copying base disk %(src_vhd_path)s to ' - '%(dest_base_disk_path)s'), locals()) + '%(dest_base_disk_path)s'), locals()) shutil.copyfile(src_base_disk_path, dest_base_disk_path) LOG.debug(_("Reconnecting copied base VHD " - "%(dest_base_disk_path)s and diff VHD %(dest_vhd_path)s"), - locals()) - (job_path, ret_val) = \ - image_man_svc.ReconnectParentVirtualHardDisk( - ChildPath=dest_vhd_path, - ParentPath=dest_base_disk_path, - Force=True) - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._vmutils.check_job_status(job_path) - else: - success = (ret_val == 0) - if not success: - raise vmutils.HyperVException( - _("Failed to reconnect base disk " - "%(dest_base_disk_path)s and diff disk " - "%(dest_vhd_path)s") % - locals()) + "%(dest_base_disk_path)s and diff " + "VHD %(dest_vhd_path)s"), locals()) + self._vhdutils.reconnect_parent_vhd(dest_vhd_path, + dest_base_disk_path) LOG.debug(_("Merging base disk %(dest_base_disk_path)s and " - "diff disk %(dest_vhd_path)s"), - locals()) - (job_path, ret_val) = image_man_svc.MergeVirtualHardDisk( - SourcePath=dest_vhd_path, - DestinationPath=dest_base_disk_path) - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._vmutils.check_job_status(job_path) - else: - success = (ret_val == 0) - if not success: - raise vmutils.HyperVException( - _("Failed to merge base disk %(dest_base_disk_path)s " - "and diff disk %(dest_vhd_path)s") % - locals()) + "diff disk %(dest_vhd_path)s"), locals()) + self._vhdutils.merge_vhd(dest_vhd_path, dest_base_disk_path) image_vhd_path = dest_base_disk_path - (glance_image_service, image_id) = \ - glance.get_remote_image_service(context, name) - image_metadata = {"is_public": False, - "disk_format": "vhd", - "container_format": "bare", - "properties": {}} - f = ioutils.open(image_vhd_path, 'rb') - LOG.debug( - _("Updating Glance image %(image_id)s with content from " - "merged disk %(image_vhd_path)s"), - locals()) + LOG.debug(_("Updating Glance image %(image_id)s with content from " + "merged disk %(image_vhd_path)s"), locals()) update_task_state(task_state=task_states.IMAGE_UPLOADING, expected_state=task_states.IMAGE_PENDING_UPLOAD) - glance_image_service.update(context, image_id, image_metadata, f) + self._save_glance_image(context, name, image_vhd_path) LOG.debug(_("Snapshot image %(image_id)s updated for VM " - "%(instance_name)s"), locals()) + "%(instance_name)s"), locals()) finally: - LOG.debug(_("Removing snapshot %s"), name) - (job_path, ret_val) = vs_man_svc.RemoveVirtualSystemSnapshot( - snap_setting_data.path_()) - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._vmutils.check_job_status(job_path) - else: - success = (ret_val == 0) - if not success: - raise vmutils.HyperVException( - _('Failed to remove snapshot for VM %s') % - instance_name) - if f: - f.close() + try: + LOG.debug(_("Removing snapshot %s"), name) + self._vmutils.remove_vm_snapshot(snapshot_path) + except Exception as ex: + LOG.exception(ex) + LOG.warning(_('Failed to remove snapshot for VM %s') + % instance_name) if export_folder: LOG.debug(_('Removing folder %s '), export_folder) shutil.rmtree(export_folder) diff --git a/nova/virt/hyperv/vhdutils.py b/nova/virt/hyperv/vhdutils.py new file mode 100644 index 000000000..21c4b4a6d --- /dev/null +++ b/nova/virt/hyperv/vhdutils.py @@ -0,0 +1,72 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Cloudbase Solutions Srl +# 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 sys + +if sys.platform == 'win32': + import wmi + +from nova.virt.hyperv import vmutils +from xml.etree import ElementTree + + +class VHDUtils(object): + + def __init__(self): + self._vmutils = vmutils.VMUtils() + if sys.platform == 'win32': + self._conn = wmi.WMI(moniker='//./root/virtualization') + + def create_differencing_vhd(self, path, parent_path): + image_man_svc = self._conn.Msvm_ImageManagementService()[0] + + (job_path, ret_val) = image_man_svc.CreateDifferencingVirtualHardDisk( + Path=path, ParentPath=parent_path) + self._vmutils.check_ret_val(ret_val, job_path) + + def reconnect_parent_vhd(self, child_vhd_path, parent_vhd_path): + image_man_svc = self._conn.Msvm_ImageManagementService()[0] + + (job_path, ret_val) = image_man_svc.ReconnectParentVirtualHardDisk( + ChildPath=child_vhd_path, + ParentPath=parent_vhd_path, + Force=True) + self._vmutils.check_ret_val(ret_val, job_path) + + def merge_vhd(self, src_vhd_path, dest_vhd_path): + image_man_svc = self._conn.Msvm_ImageManagementService()[0] + + (job_path, ret_val) = image_man_svc.MergeVirtualHardDisk( + SourcePath=src_vhd_path, + DestinationPath=dest_vhd_path) + self._vmutils.check_ret_val(ret_val, job_path) + + def get_vhd_parent_path(self, vhd_path): + image_man_svc = self._conn.Msvm_ImageManagementService()[0] + + (vhd_info, + job_path, + ret_val) = image_man_svc.GetVirtualHardDiskInfo(vhd_path) + self._vmutils.check_ret_val(ret_val, job_path) + + base_disk_path = None + et = ElementTree.fromstring(vhd_info) + for item in et.findall("PROPERTY"): + if item.attrib["NAME"] == "ParentPath": + base_disk_path = item.find("VALUE").text + break + return base_disk_path diff --git a/nova/virt/hyperv/vif.py b/nova/virt/hyperv/vif.py index a898d3ac2..cfe7c6a4c 100644 --- a/nova/virt/hyperv/vif.py +++ b/nova/virt/hyperv/vif.py @@ -15,18 +15,15 @@ # 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 sys -import uuid -# Check needed for unit testing on Unix -if sys.platform == 'win32': - import wmi +import abc -from abc import abstractmethod from nova.openstack.common import cfg from nova.openstack.common import log as logging +from nova.virt.hyperv import networkutils from nova.virt.hyperv import vmutils + hyperv_opts = [ cfg.StrOpt('vswitch_name', default=None, @@ -42,11 +39,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 @@ -68,65 +65,17 @@ class HyperVNovaNetworkVIFDriver(HyperVBaseVIFDriver): def __init__(self): self._vmutils = vmutils.VMUtils() - self._conn = wmi.WMI(moniker='//./root/virtualization') - - def _find_external_network(self): - """Find the vswitch that is connected to the physical nic. - Assumes only one physical nic on the host - """ - #If there are no physical nics connected to networks, return. - LOG.debug(_("Attempting to bind NIC to %s ") - % CONF.vswitch_name) - if CONF.vswitch_name: - LOG.debug(_("Attempting to bind NIC to %s ") - % CONF.vswitch_name) - bound = self._conn.Msvm_VirtualSwitch( - ElementName=CONF.vswitch_name) - else: - LOG.debug(_("No vSwitch specified, attaching to default")) - self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE') - if len(bound) == 0: - return None - if CONF.vswitch_name: - return self._conn.Msvm_VirtualSwitch( - ElementName=CONF.vswitch_name)[0]\ - .associators(wmi_result_class='Msvm_SwitchPort')[0]\ - .associators(wmi_result_class='Msvm_VirtualSwitch')[0] - else: - return self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')\ - .associators(wmi_result_class='Msvm_SwitchPort')[0]\ - .associators(wmi_result_class='Msvm_VirtualSwitch')[0] + self._netutils = networkutils.NetworkUtils() def plug(self, instance, vif): - extswitch = self._find_external_network() - if extswitch is None: - raise vmutils.HyperVException(_('Cannot find vSwitch')) + vswitch_path = self._netutils.get_external_vswitch( + CONF.vswitch_name) vm_name = instance['name'] - - nic_data = self._conn.Msvm_SyntheticEthernetPortSettingData( - ElementName=vif['id'])[0] - - switch_svc = self._conn.Msvm_VirtualSwitchManagementService()[0] - #Create a port on the vswitch. - (new_port, ret_val) = switch_svc.CreateSwitchPort( - Name=str(uuid.uuid4()), - FriendlyName=vm_name, - ScopeOfResidence="", - VirtualSwitch=extswitch.path_()) - if ret_val != 0: - LOG.error(_('Failed creating a port on the external vswitch')) - raise vmutils.HyperVException(_('Failed creating port for %s') % - vm_name) - ext_path = extswitch.path_() - LOG.debug(_("Created switch port %(vm_name)s on switch %(ext_path)s") - % locals()) - - vms = self._conn.MSVM_ComputerSystem(ElementName=vm_name) - vm = vms[0] - - nic_data.Connection = [new_port] - self._vmutils.modify_virt_resource(self._conn, nic_data, vm) + LOG.debug(_('Creating vswitch port for instance: %s') % vm_name) + vswitch_port = self._netutils.create_vswitch_port(vswitch_path, + vm_name) + self._vmutils.set_nic_connection(vm_name, vif['id'], vswitch_port) def unplug(self, instance, vif): #TODO(alepilotti) Not implemented diff --git a/nova/virt/hyperv/vmops.py b/nova/virt/hyperv/vmops.py index 3d8958266..8ce1d508b 100644 --- a/nova/virt/hyperv/vmops.py +++ b/nova/virt/hyperv/vmops.py @@ -19,7 +19,6 @@ Management class for basic VM operations. """ import os -import uuid from nova.api.metadata import base as instance_metadata from nova import exception @@ -29,29 +28,31 @@ from nova.openstack.common import lockutils from nova.openstack.common import log as logging from nova import utils from nova.virt import configdrive -from nova.virt.hyperv import baseops from nova.virt.hyperv import constants +from nova.virt.hyperv import pathutils +from nova.virt.hyperv import vhdutils from nova.virt.hyperv import vmutils +from nova.virt import images LOG = logging.getLogger(__name__) hyperv_opts = [ cfg.BoolOpt('limit_cpu_features', - default=False, - help='Required for live migration among ' - 'hosts with different CPU features'), + default=False, + help='Required for live migration among ' + 'hosts with different CPU features'), cfg.BoolOpt('config_drive_inject_password', - default=False, - help='Sets the admin password in the config drive image'), + default=False, + help='Sets the admin password in the config drive image'), cfg.StrOpt('qemu_img_cmd', default="qemu-img.exe", help='qemu-img is used to convert between ' 'different image types'), cfg.BoolOpt('config_drive_cdrom', - default=False, - help='Attaches the Config Drive image as a cdrom drive ' - 'instead of a disk drive') - ] + default=False, + help='Attaches the Config Drive image as a cdrom drive ' + 'instead of a disk drive') +] CONF = cfg.CONF CONF.register_opts(hyperv_opts) @@ -59,19 +60,20 @@ CONF.import_opt('use_cow_images', 'nova.virt.driver') CONF.import_opt('network_api_class', 'nova.network') -class VMOps(baseops.BaseOps): +class VMOps(object): _vif_driver_class_map = { 'nova.network.quantumv2.api.API': - 'nova.virt.hyperv.vif.HyperVQuantumVIFDriver', + 'nova.virt.hyperv.vif.HyperVQuantumVIFDriver', 'nova.network.api.API': - 'nova.virt.hyperv.vif.HyperVNovaNetworkVIFDriver', + 'nova.virt.hyperv.vif.HyperVNovaNetworkVIFDriver', } def __init__(self, volumeops): - super(VMOps, self).__init__() - self._vmutils = vmutils.VMUtils() + self._vhdutils = vhdutils.VHDUtils() + self._pathutils = pathutils.PathUtils() self._volumeops = volumeops + self._vif_driver = None self._load_vif_driver_class() def _load_vif_driver_class(self): @@ -84,124 +86,106 @@ class VMOps(baseops.BaseOps): CONF.network_api_class) def list_instances(self): - """Return the names of all the instances known to Hyper-V.""" - vms = [v.ElementName - for v in self._conn.Msvm_ComputerSystem(['ElementName'], - Caption="Virtual Machine")] - return vms + return self._vmutils.list_instances() def get_info(self, instance): """Get information about the VM.""" LOG.debug(_("get_info called for instance"), instance=instance) - return self._get_info(instance['name']) - - def _get_info(self, instance_name): - vm = self._vmutils.lookup(self._conn, instance_name) - if vm is None: - raise exception.InstanceNotFound(instance=instance_name) - vm = self._conn.Msvm_ComputerSystem( - ElementName=instance_name)[0] - vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] - vmsettings = vm.associators( - wmi_association_class='Msvm_SettingsDefineState', - wmi_result_class='Msvm_VirtualSystemSettingData') - settings_paths = [v.path_() for v in vmsettings] - #See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx - summary_info = vs_man_svc.GetSummaryInformation( - [constants.VM_SUMMARY_NUM_PROCS, - constants.VM_SUMMARY_ENABLED_STATE, - constants.VM_SUMMARY_MEMORY_USAGE, - constants.VM_SUMMARY_UPTIME], - settings_paths)[1] - info = summary_info[0] - - LOG.debug(_("hyperv vm state: %s"), info.EnabledState) - state = constants.HYPERV_POWER_STATE[info.EnabledState] - memusage = str(info.MemoryUsage) - numprocs = str(info.NumberOfProcessors) - uptime = str(info.UpTime) - - LOG.debug(_("Got Info for vm %(instance_name)s: state=%(state)d," - " mem=%(memusage)s, num_cpu=%(numprocs)s," - " uptime=%(uptime)s"), locals()) + instance_name = instance['name'] + if not self._vmutils.vm_exists(instance_name): + raise exception.InstanceNotFound(instance=instance) + + info = self._vmutils.get_vm_summary_info(instance_name) + + state = constants.HYPERV_POWER_STATE[info['EnabledState']] return {'state': state, - 'max_mem': info.MemoryUsage, - 'mem': info.MemoryUsage, - 'num_cpu': info.NumberOfProcessors, - 'cpu_time': info.UpTime} + 'max_mem': info['MemoryUsage'], + 'mem': info['MemoryUsage'], + 'num_cpu': info['NumberOfProcessors'], + 'cpu_time': info['UpTime']} def spawn(self, context, instance, image_meta, injected_files, - admin_password, network_info, block_device_info=None): + admin_password, network_info, block_device_info=None): """Create a new VM and start it.""" - vm = self._vmutils.lookup(self._conn, instance['name']) - if vm is not None: - raise exception.InstanceExists(name=instance['name']) + + instance_name = instance['name'] + if self._vmutils.vm_exists(instance_name): + raise exception.InstanceExists(name=instance_name) ebs_root = self._volumeops.volume_in_mapping( self._volumeops.get_default_root_device(), - block_device_info) + block_device_info) #If is not a boot from volume spawn if not (ebs_root): #Fetch the file, assume it is a VHD file. - vhdfile = self._vmutils.get_vhd_path(instance['name']) + vhdfile = self._pathutils.get_vhd_path(instance_name) try: - self._cache_image(fn=self._vmutils.fetch_image, - context=context, - target=vhdfile, - fname=instance['image_ref'], - image_id=instance['image_ref'], - user=instance['user_id'], - project=instance['project_id'], - cow=CONF.use_cow_images) + self._cache_image(fn=self._fetch_image, + context=context, + target=vhdfile, + fname=instance['image_ref'], + image_id=instance['image_ref'], + user=instance['user_id'], + project=instance['project_id'], + cow=CONF.use_cow_images) except Exception as exn: LOG.exception(_('cache image failed: %s'), exn) - self.destroy(instance) + raise try: - self._create_vm(instance) + self._vmutils.create_vm(instance_name, + instance['memory_mb'], + instance['vcpus'], + CONF.limit_cpu_features) if not ebs_root: - self._attach_ide_drive(instance['name'], vhdfile, 0, 0, - constants.IDE_DISK) + self._vmutils.attach_ide_drive(instance_name, + vhdfile, + 0, + 0, + constants.IDE_DISK) else: self._volumeops.attach_boot_volume(block_device_info, - instance['name']) + instance_name) - #A SCSI controller for volumes connection is created - self._create_scsi_controller(instance['name']) + self._vmutils.create_scsi_controller(instance_name) for vif in network_info: - self._create_nic(instance['name'], vif) + LOG.debug(_('Creating nic for instance: %s'), instance_name) + self._vmutils.create_nic(instance_name, + vif['id'], + vif['address']) self._vif_driver.plug(instance, vif) if configdrive.required_by(instance): self._create_config_drive(instance, injected_files, - admin_password) + admin_password) - LOG.debug(_('Starting VM %s '), instance['name']) - self._set_vm_state(instance['name'], 'Enabled') - LOG.info(_('Started VM %s '), instance['name']) - except Exception as exn: - LOG.exception(_('spawn vm failed: %s'), exn) + self._set_vm_state(instance_name, + constants.HYPERV_VM_STATE_ENABLED) + except Exception as ex: + LOG.exception(ex) self.destroy(instance) - raise exn + raise vmutils.HyperVException(_('Spawn instance failed')) def _create_config_drive(self, instance, injected_files, admin_password): if CONF.config_drive_format != 'iso9660': vmutils.HyperVException(_('Invalid config_drive_format "%s"') % - CONF.config_drive_format) + CONF.config_drive_format) + + LOG.info(_('Using config drive for instance: %s'), instance=instance) - LOG.info(_('Using config drive'), instance=instance) extra_md = {} if admin_password and CONF.config_drive_inject_password: extra_md['admin_pass'] = admin_password inst_md = instance_metadata.InstanceMetadata(instance, - content=injected_files, extra_md=extra_md) + content=injected_files, + extra_md=extra_md) - instance_path = self._vmutils.get_instance_path( + instance_path = self._pathutils.get_instance_path( instance['name']) configdrive_path_iso = os.path.join(instance_path, 'configdrive.iso') LOG.info(_('Creating config drive at %(path)s'), @@ -218,7 +202,7 @@ class VMOps(baseops.BaseOps): if not CONF.config_drive_cdrom: drive_type = constants.IDE_DISK configdrive_path = os.path.join(instance_path, - 'configdrive.vhd') + 'configdrive.vhd') utils.execute(CONF.qemu_img_cmd, 'convert', '-f', @@ -233,302 +217,88 @@ class VMOps(baseops.BaseOps): drive_type = constants.IDE_DVD configdrive_path = configdrive_path_iso - self._attach_ide_drive(instance['name'], configdrive_path, 1, 0, - drive_type) - - def _create_vm(self, instance): - """Create a VM but don't start it.""" - vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + self._vmutils.attach_ide_drive(instance['name'], configdrive_path, + 1, 0, drive_type) - vs_gs_data = self._conn.Msvm_VirtualSystemGlobalSettingData.new() - vs_gs_data.ElementName = instance["name"] - (job, ret_val) = vs_man_svc.DefineVirtualSystem( - [], None, vs_gs_data.GetText_(1))[1:] - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._vmutils.check_job_status(job) - else: - success = (ret_val == 0) - - if not success: - raise vmutils.HyperVException(_('Failed to create VM %s') % - instance["name"]) - - LOG.debug(_('Created VM %s...'), instance["name"]) - vm = self._conn.Msvm_ComputerSystem(ElementName=instance["name"])[0] - - vmsettings = vm.associators( - wmi_result_class='Msvm_VirtualSystemSettingData') - vmsetting = [s for s in vmsettings - if s.SettingType == 3][0] # avoid snapshots - memsetting = vmsetting.associators( - wmi_result_class='Msvm_MemorySettingData')[0] - #No Dynamic Memory, so reservation, limit and quantity are identical. - mem = long(str(instance['memory_mb'])) - memsetting.VirtualQuantity = mem - memsetting.Reservation = mem - memsetting.Limit = mem - - (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources( - vm.path_(), [memsetting.GetText_(1)]) - LOG.debug(_('Set memory for vm %s...'), instance["name"]) - procsetting = vmsetting.associators( - wmi_result_class='Msvm_ProcessorSettingData')[0] - vcpus = long(instance['vcpus']) - procsetting.VirtualQuantity = vcpus - procsetting.Reservation = vcpus - procsetting.Limit = 100000 # static assignment to 100% - - if CONF.limit_cpu_features: - procsetting.LimitProcessorFeatures = True - - (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources( - vm.path_(), [procsetting.GetText_(1)]) - LOG.debug(_('Set vcpus for vm %s...'), instance["name"]) - - def _create_scsi_controller(self, vm_name): - """Create an iscsi controller ready to mount volumes.""" - LOG.debug(_('Creating a scsi controller for %(vm_name)s for volume ' - 'attaching') % locals()) - vms = self._conn.MSVM_ComputerSystem(ElementName=vm_name) - vm = vms[0] - scsicontrldefault = self._conn.query( - "SELECT * FROM Msvm_ResourceAllocationSettingData \ - WHERE ResourceSubType = 'Microsoft Synthetic SCSI Controller'\ - AND InstanceID LIKE '%Default%'")[0] - if scsicontrldefault is None: - raise vmutils.HyperVException(_('Controller not found')) - scsicontrl = self._vmutils.clone_wmi_obj(self._conn, - 'Msvm_ResourceAllocationSettingData', scsicontrldefault) - scsicontrl.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}'] - scsiresource = self._vmutils.add_virt_resource(self._conn, - scsicontrl, vm) - if scsiresource is None: - raise vmutils.HyperVException( - _('Failed to add scsi controller to VM %s') % - vm_name) - - def _get_ide_controller(self, vm, ctrller_addr): - #Find the IDE controller for the vm. - vmsettings = vm.associators( - wmi_result_class='Msvm_VirtualSystemSettingData') - rasds = vmsettings[0].associators( - wmi_result_class='MSVM_ResourceAllocationSettingData') - ctrller = [r for r in rasds - if r.ResourceSubType == 'Microsoft Emulated IDE Controller' - and r.Address == str(ctrller_addr)] - return ctrller - - def _attach_ide_drive(self, vm_name, path, ctrller_addr, drive_addr, - drive_type=constants.IDE_DISK): - """Create an IDE drive and attach it to the vm.""" - LOG.debug(_('Creating disk for %(vm_name)s by attaching' - ' disk file %(path)s') % locals()) - - vms = self._conn.MSVM_ComputerSystem(ElementName=vm_name) - vm = vms[0] - - ctrller = self._get_ide_controller(vm, ctrller_addr) - - if drive_type == constants.IDE_DISK: - resSubType = 'Microsoft Synthetic Disk Drive' - elif drive_type == constants.IDE_DVD: - resSubType = 'Microsoft Synthetic DVD Drive' - - #Find the default disk drive object for the vm and clone it. - drivedflt = self._conn.query( - "SELECT * FROM Msvm_ResourceAllocationSettingData \ - WHERE ResourceSubType LIKE '%(resSubType)s'\ - AND InstanceID LIKE '%%Default%%'" % locals())[0] - drive = self._vmutils.clone_wmi_obj(self._conn, - 'Msvm_ResourceAllocationSettingData', drivedflt) - #Set the IDE ctrller as parent. - drive.Parent = ctrller[0].path_() - drive.Address = drive_addr - #Add the cloned disk drive object to the vm. - new_resources = self._vmutils.add_virt_resource(self._conn, - drive, vm) - if new_resources is None: - raise vmutils.HyperVException( - _('Failed to add drive to VM %s') % - vm_name) - drive_path = new_resources[0] - LOG.debug(_('New %(drive_type)s drive path is %(drive_path)s') % - locals()) - - if drive_type == constants.IDE_DISK: - resSubType = 'Microsoft Virtual Hard Disk' - elif drive_type == constants.IDE_DVD: - resSubType = 'Microsoft Virtual CD/DVD Disk' - - #Find the default VHD disk object. - drivedefault = self._conn.query( - "SELECT * FROM Msvm_ResourceAllocationSettingData \ - WHERE ResourceSubType LIKE '%(resSubType)s' AND \ - InstanceID LIKE '%%Default%%' " % locals())[0] - - #Clone the default and point it to the image file. - res = self._vmutils.clone_wmi_obj(self._conn, - 'Msvm_ResourceAllocationSettingData', drivedefault) - #Set the new drive as the parent. - res.Parent = drive_path - res.Connection = [path] - - #Add the new vhd object as a virtual hard disk to the vm. - new_resources = self._vmutils.add_virt_resource(self._conn, res, vm) - if new_resources is None: - raise vmutils.HyperVException( - _('Failed to add %(drive_type)s image to VM %(vm_name)s') % - locals()) - LOG.info(_('Created drive type %(drive_type)s for %(vm_name)s') % - locals()) - - def _create_nic(self, vm_name, vif): - """Create a (synthetic) nic and attach it to the vm.""" - LOG.debug(_('Creating nic for %s '), vm_name) - - #Create a new nic - syntheticnics_data = self._conn.Msvm_SyntheticEthernetPortSettingData() - default_nic_data = [n for n in syntheticnics_data - if n.InstanceID.rfind('Default') > 0] - new_nic_data = self._vmutils.clone_wmi_obj(self._conn, - 'Msvm_SyntheticEthernetPortSettingData', - default_nic_data[0]) - - #Configure the nic - new_nic_data.ElementName = vif['id'] - new_nic_data.Address = vif['address'].replace(':', '') - new_nic_data.StaticMacAddress = 'True' - new_nic_data.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}'] - - #Add the new nic to the vm - vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name) - vm = vms[0] - - new_resources = self._vmutils.add_virt_resource(self._conn, - new_nic_data, vm) - if new_resources is None: - raise vmutils.HyperVException(_('Failed to add nic to VM %s') % - vm_name) - LOG.info(_("Created nic for %s "), vm_name) + def destroy(self, instance, network_info=None, cleanup=True, + destroy_disks=True): + instance_name = instance['name'] + LOG.debug(_("Got request to destroy instance: %s"), instance_name) + try: + if self._vmutils.vm_exists(instance_name): + volumes_drives_list = self._vmutils.destroy_vm(instance_name, + destroy_disks) + #Disconnect volumes + for volume_drive in volumes_drives_list: + self._volumeops.disconnect_volume(volume_drive) + else: + LOG.debug(_("Instance not found: %s"), instance_name) + except Exception as ex: + LOG.exception(ex) + raise vmutils.HyperVException(_('Failed to destroy instance: %s') % + instance_name) def reboot(self, instance, network_info, reboot_type): """Reboot the specified instance.""" - vm = self._vmutils.lookup(self._conn, instance['name']) - if vm is None: - raise exception.InstanceNotFound(instance_id=instance["id"]) - self._set_vm_state(instance['name'], 'Reboot') - - def destroy(self, instance, network_info=None, cleanup=True, - destroy_disks=True): - """Destroy the VM. Also destroy the associated VHD disk files.""" - LOG.debug(_("Got request to destroy vm %s"), instance['name']) - vm = self._vmutils.lookup(self._conn, instance['name']) - if vm is None: - return - vm = self._conn.Msvm_ComputerSystem(ElementName=instance['name'])[0] - vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] - #Stop the VM first. - self._set_vm_state(instance['name'], 'Disabled') - vmsettings = vm.associators( - wmi_result_class='Msvm_VirtualSystemSettingData') - rasds = vmsettings[0].associators( - wmi_result_class='MSVM_ResourceAllocationSettingData') - disks = [r for r in rasds - if r.ResourceSubType == 'Microsoft Virtual Hard Disk'] - disk_files = [] - volumes = [r for r in rasds - if r.ResourceSubType == 'Microsoft Physical Disk Drive'] - volumes_drives_list = [] - #collect the volumes information before destroying the VM. - for volume in volumes: - hostResources = volume.HostResource - drive_path = hostResources[0] - #Appending the Msvm_Disk path - volumes_drives_list.append(drive_path) - #Collect disk file information before destroying the VM. - for disk in disks: - disk_files.extend([c for c in disk.Connection]) - #Nuke the VM. Does not destroy disks. - (job, ret_val) = vs_man_svc.DestroyVirtualSystem(vm.path_()) - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._vmutils.check_job_status(job) - elif ret_val == 0: - success = True - if not success: - raise vmutils.HyperVException(_('Failed to destroy vm %s') % - instance['name']) - if destroy_disks: - #Disconnect volumes - for volume_drive in volumes_drives_list: - self._volumeops.disconnect_volume(volume_drive) - #Delete associated vhd disk files. - for disk in disk_files: - vhdfile = self._conn_cimv2.query( - "Select * from CIM_DataFile where Name = '" + - disk.replace("'", "''") + "'")[0] - LOG.debug(_("Del: disk %(vhdfile)s vm %(name)s") - % {'vhdfile': vhdfile, 'name': instance['name']}) - vhdfile.Delete() + LOG.debug(_("reboot instance"), instance=instance) + self._set_vm_state(instance['name'], + constants.HYPERV_VM_STATE_REBOOT) def pause(self, instance): """Pause VM instance.""" LOG.debug(_("Pause instance"), instance=instance) - self._set_vm_state(instance["name"], 'Paused') + self._set_vm_state(instance["name"], + constants.HYPERV_VM_STATE_PAUSED) def unpause(self, instance): """Unpause paused VM instance.""" LOG.debug(_("Unpause instance"), instance=instance) - self._set_vm_state(instance["name"], 'Enabled') + self._set_vm_state(instance["name"], + constants.HYPERV_VM_STATE_ENABLED) def suspend(self, instance): """Suspend the specified instance.""" print instance LOG.debug(_("Suspend instance"), instance=instance) - self._set_vm_state(instance["name"], 'Suspended') + self._set_vm_state(instance["name"], + constants.HYPERV_VM_STATE_SUSPENDED) def resume(self, instance): """Resume the suspended VM instance.""" LOG.debug(_("Resume instance"), instance=instance) - self._set_vm_state(instance["name"], 'Enabled') + self._set_vm_state(instance["name"], + constants.HYPERV_VM_STATE_ENABLED) def power_off(self, instance): """Power off the specified instance.""" LOG.debug(_("Power off instance"), instance=instance) - self._set_vm_state(instance["name"], 'Disabled') + self._set_vm_state(instance["name"], + constants.HYPERV_VM_STATE_DISABLED) def power_on(self, instance): """Power on the specified instance.""" LOG.debug(_("Power on instance"), instance=instance) - self._set_vm_state(instance["name"], 'Enabled') + self._set_vm_state(instance["name"], + constants.HYPERV_VM_STATE_ENABLED) def _set_vm_state(self, vm_name, req_state): - """Set the desired state of the VM.""" - vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name) - if len(vms) == 0: - return False - (job, ret_val) = vms[0].RequestStateChange( - constants.REQ_POWER_STATE[req_state]) - success = False - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._vmutils.check_job_status(job) - elif ret_val == 0: - success = True - elif ret_val == 32775: - #Invalid state for current operation. Typically means it is - #already in the state requested - success = True - if success: - LOG.info(_("Successfully changed vm state of %(vm_name)s" - " to %(req_state)s") % locals()) - else: + try: + self._vmutils.set_vm_state(vm_name, req_state) + LOG.debug(_("Successfully changed state of VM %(vm_name)s" + " to: %(req_state)s") % locals()) + except Exception as ex: + LOG.exception(ex) msg = _("Failed to change vm state of %(vm_name)s" " to %(req_state)s") % locals() - LOG.error(msg) raise vmutils.HyperVException(msg) - def _cache_image(self, fn, target, fname, cow=False, Size=None, - *args, **kwargs): - """Wrapper for a method that creates an image that caches the image. + def _fetch_image(self, target, context, image_id, user, project, + *args, **kwargs): + images.fetch(context, image_id, target, user, project) + + def _cache_image(self, fn, target, fname, cow=False, size=None, + *args, **kwargs): + """Wrapper for a method that creates and caches an image. This wrapper will save the image into a common store and create a copy for use by the hypervisor. @@ -543,32 +313,23 @@ class VMOps(baseops.BaseOps): """ @lockutils.synchronized(fname, 'nova-') def call_if_not_exists(path, fn, *args, **kwargs): - if not os.path.exists(path): - fn(target=path, *args, **kwargs) + if not os.path.exists(path): + fn(target=path, *args, **kwargs) - if not os.path.exists(target): - LOG.debug(_("use_cow_image:%s"), cow) + if not self._pathutils.vhd_exists(target): + LOG.debug(_("Use CoW image: %s"), cow) if cow: - base = self._vmutils.get_base_vhd_path(fname) - call_if_not_exists(base, fn, *args, **kwargs) - - image_service = self._conn.query( - "Select * from Msvm_ImageManagementService")[0] - (job, ret_val) = \ - image_service.CreateDifferencingVirtualHardDisk( - Path=target, ParentPath=base) - LOG.debug( - "Creating difference disk: JobID=%s, Source=%s, Target=%s", - job, base, target) - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self._vmutils.check_job_status(job) - else: - success = (ret_val == 0) - - if not success: + parent_path = self._pathutils.get_base_vhd_path(fname) + call_if_not_exists(parent_path, fn, *args, **kwargs) + + LOG.debug(_("Creating differencing VHD. Parent: " + "%(parent_path)s, Target: %(target)s") % locals()) + try: + self._vhdutils.create_differencing_vhd(target, parent_path) + except Exception as ex: + LOG.exception(ex) raise vmutils.HyperVException( - _('Failed to create Difference Disk from ' - '%(base)s to %(target)s') % locals()) - + _('Failed to create a differencing disk from ' + '%(parent_path)s to %(target)s') % locals()) else: call_if_not_exists(target, fn, *args, **kwargs) diff --git a/nova/virt/hyperv/vmutils.py b/nova/virt/hyperv/vmutils.py index d899f977d..0305d8306 100644 --- a/nova/virt/hyperv/vmutils.py +++ b/nova/virt/hyperv/vmutils.py @@ -16,24 +16,20 @@ # under the License. """ -Utility class for VM related operations. +Utility class for VM related operations on Hyper-V. """ -import os -import shutil import sys import time import uuid +if sys.platform == 'win32': + import wmi + from nova import exception from nova.openstack.common import cfg from nova.openstack.common import log as logging from nova.virt.hyperv import constants -from nova.virt import images - -# Check needed for unit testing on Unix -if sys.platform == 'win32': - import wmi CONF = cfg.CONF LOG = logging.getLogger(__name__) @@ -45,19 +41,342 @@ class HyperVException(exception.NovaException): class VMUtils(object): - def lookup(self, conn, i): - vms = conn.Msvm_ComputerSystem(ElementName=i) + + def __init__(self): + if sys.platform == 'win32': + self._conn = wmi.WMI(moniker='//./root/virtualization') + self._conn_cimv2 = wmi.WMI(moniker='//./root/cimv2') + + def list_instances(self): + """Return the names of all the instances known to Hyper-V.""" + vm_names = [v.ElementName + for v in self._conn.Msvm_ComputerSystem(['ElementName'], + Caption="Virtual Machine")] + return vm_names + + def get_vm_summary_info(self, vm_name): + vm = self._lookup_vm_check(vm_name) + + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + vmsettings = vm.associators( + wmi_association_class='Msvm_SettingsDefineState', + wmi_result_class='Msvm_VirtualSystemSettingData') + settings_paths = [v.path_() for v in vmsettings] + #See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx + (ret_val, summary_info) = vs_man_svc.GetSummaryInformation( + [constants.VM_SUMMARY_NUM_PROCS, + constants.VM_SUMMARY_ENABLED_STATE, + constants.VM_SUMMARY_MEMORY_USAGE, + constants.VM_SUMMARY_UPTIME], + settings_paths) + if ret_val: + raise HyperVException(_('Cannot get VM summary data for: %s') + % vm_name) + + si = summary_info[0] + memory_usage = None + if si.MemoryUsage is not None: + memory_usage = long(si.MemoryUsage) + up_time = None + if si.UpTime is not None: + up_time = long(si.UpTime) + + summary_info_dict = {'NumberOfProcessors': si.NumberOfProcessors, + 'EnabledState': si.EnabledState, + 'MemoryUsage': memory_usage, + 'UpTime': up_time} + return summary_info_dict + + def _lookup_vm_check(self, vm_name): + vm = self._lookup_vm(vm_name) + if not vm: + raise HyperVException(_('VM not found: %s') % vm_name) + return vm + + def _lookup_vm(self, vm_name): + vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name) n = len(vms) if n == 0: return None elif n > 1: - raise HyperVException(_('duplicate name found: %s') % i) + raise HyperVException(_('Duplicate VM name found: %s') % vm_name) else: - return vms[0].ElementName + return vms[0] + + def vm_exists(self, vm_name): + return self._lookup_vm(vm_name) is not None + + def _get_vm_setting_data(self, vm): + vmsettings = vm.associators( + wmi_result_class='Msvm_VirtualSystemSettingData') + # Avoid snapshots + return [s for s in vmsettings if s.SettingType == 3][0] + + def _set_vm_memory(self, vm, vmsetting, memory_mb): + memsetting = vmsetting.associators( + wmi_result_class='Msvm_MemorySettingData')[0] + #No Dynamic Memory, so reservation, limit and quantity are identical. + mem = long(memory_mb) + memsetting.VirtualQuantity = mem + memsetting.Reservation = mem + memsetting.Limit = mem + + self._modify_virt_resource(memsetting, vm.path_()) + + def _set_vm_vcpus(self, vm, vmsetting, vcpus_num, limit_cpu_features): + procsetting = vmsetting.associators( + wmi_result_class='Msvm_ProcessorSettingData')[0] + vcpus = long(vcpus_num) + procsetting.VirtualQuantity = vcpus + procsetting.Reservation = vcpus + procsetting.Limit = 100000 # static assignment to 100% + procsetting.LimitProcessorFeatures = limit_cpu_features + + self._modify_virt_resource(procsetting, vm.path_()) + + def create_vm(self, vm_name, memory_mb, vcpus_num, limit_cpu_features): + """Creates a VM.""" + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + + vs_gs_data = self._conn.Msvm_VirtualSystemGlobalSettingData.new() + vs_gs_data.ElementName = vm_name + + LOG.debug(_('Creating VM %s'), vm_name) + (job_path, + ret_val) = vs_man_svc.DefineVirtualSystem([], None, + vs_gs_data.GetText_(1))[1:] + self.check_ret_val(ret_val, job_path) + + vm = self._lookup_vm_check(vm_name) + vmsetting = self._get_vm_setting_data(vm) + + LOG.debug(_('Setting memory for vm %s'), vm_name) + self._set_vm_memory(vm, vmsetting, memory_mb) + + LOG.debug(_('Set vCPUs for vm %s'), vm_name) + self._set_vm_vcpus(vm, vmsetting, vcpus_num, limit_cpu_features) + + def get_vm_iscsi_controller(self, vm_name): + vm = self._lookup_vm_check(vm_name) + + vmsettings = vm.associators( + wmi_result_class='Msvm_VirtualSystemSettingData') + rasds = vmsettings[0].associators( + wmi_result_class='MSVM_ResourceAllocationSettingData') + res = [r for r in rasds + if r.ResourceSubType == + 'Microsoft Synthetic SCSI Controller'][0] + return res.path_() + + def _get_vm_ide_controller(self, vm, ctrller_addr): + vmsettings = vm.associators( + wmi_result_class='Msvm_VirtualSystemSettingData') + rasds = vmsettings[0].associators( + wmi_result_class='MSVM_ResourceAllocationSettingData') + return [r for r in rasds + if r.ResourceSubType == 'Microsoft Emulated IDE Controller' + and r.Address == str(ctrller_addr)][0].path_() + + def get_vm_ide_controller(self, vm_name, ctrller_addr): + vm = self._lookup_vm_check(vm_name) + return self._get_vm_ide_controller(vm, ctrller_addr) + + def get_attached_disks_count(self, scsi_controller_path): + volumes = self._conn.query("SELECT * FROM " + "Msvm_ResourceAllocationSettingData " + "WHERE ResourceSubType LIKE " + "'Microsoft Physical Disk Drive' " + "AND Parent = '%s'" % + scsi_controller_path.replace("'", "''")) + return len(volumes) + + def attach_ide_drive(self, vm_name, path, ctrller_addr, drive_addr, + drive_type=constants.IDE_DISK): + """Create an IDE drive and attach it to the vm.""" + + vm = self._lookup_vm_check(vm_name) + + ctrller_path = self._get_vm_ide_controller(vm, ctrller_addr) + + if drive_type == constants.IDE_DISK: + res_sub_type = 'Microsoft Synthetic Disk Drive' + elif drive_type == constants.IDE_DVD: + res_sub_type = 'Microsoft Synthetic DVD Drive' + + #Find the default disk drive object for the vm and clone it. + drivedflt = self._conn.query("SELECT * FROM " + "Msvm_ResourceAllocationSettingData " + "WHERE ResourceSubType LIKE " + "'%(res_sub_type)s' AND InstanceID LIKE " + "'%%Default%%'" % locals())[0] + drive = self._clone_wmi_obj('Msvm_ResourceAllocationSettingData', + drivedflt) + #Set the IDE ctrller as parent. + drive.Parent = ctrller_path + drive.Address = drive_addr + #Add the cloned disk drive object to the vm. + new_resources = self._add_virt_resource(drive, vm.path_()) + drive_path = new_resources[0] + + if drive_type == constants.IDE_DISK: + res_sub_type = 'Microsoft Virtual Hard Disk' + elif drive_type == constants.IDE_DVD: + res_sub_type = 'Microsoft Virtual CD/DVD Disk' + + #Find the default VHD disk object. + drivedefault = self._conn.query("SELECT * FROM " + "Msvm_ResourceAllocationSettingData " + "WHERE ResourceSubType LIKE " + "'%(res_sub_type)s' AND " + "InstanceID LIKE '%%Default%%'" + % locals())[0] + + #Clone the default and point it to the image file. + res = self._clone_wmi_obj('Msvm_ResourceAllocationSettingData', + drivedefault) + #Set the new drive as the parent. + res.Parent = drive_path + res.Connection = [path] + + #Add the new vhd object as a virtual hard disk to the vm. + self._add_virt_resource(res, vm.path_()) + + def create_scsi_controller(self, vm_name): + """Create an iscsi controller ready to mount volumes.""" + + vm = self._lookup_vm_check(vm_name) + scsicontrldflt = self._conn.query("SELECT * FROM " + "Msvm_ResourceAllocationSettingData " + "WHERE ResourceSubType = 'Microsoft " + "Synthetic SCSI Controller' AND " + "InstanceID LIKE '%Default%'")[0] + if scsicontrldflt is None: + raise HyperVException(_('Controller not found')) + scsicontrl = self._clone_wmi_obj('Msvm_ResourceAllocationSettingData', + scsicontrldflt) + scsicontrl.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}'] + scsiresource = self._add_virt_resource(scsicontrl, vm.path_()) + + def attach_volume_to_controller(self, vm_name, controller_path, address, + mounted_disk_path): + """Attach a volume to a controller.""" - def check_job_status(self, jobpath): - """Poll WMI job state for completion.""" - job_wmi_path = jobpath.replace('\\', '/') + vm = self._lookup_vm_check(vm_name) + + diskdflt = self._conn.query("SELECT * FROM " + "Msvm_ResourceAllocationSettingData " + "WHERE ResourceSubType LIKE " + "'Microsoft Physical Disk Drive' " + "AND InstanceID LIKE '%Default%'")[0] + diskdrive = self._clone_wmi_obj('Msvm_ResourceAllocationSettingData', + diskdflt) + diskdrive.Address = address + diskdrive.Parent = controller_path + diskdrive.HostResource = [mounted_disk_path] + self._add_virt_resource(diskdrive, vm.path_()) + + def set_nic_connection(self, vm_name, nic_name, vswitch_port): + nic_data = self._get_nic_data_by_name(nic_name) + nic_data.Connection = [vswitch_port] + + vm = self._lookup_vm_check(vm_name) + self._modify_virt_resource(nic_data, vm.path_()) + + def _get_nic_data_by_name(self, name): + return self._conn.Msvm_SyntheticEthernetPortSettingData( + ElementName=name)[0] + + def create_nic(self, vm_name, nic_name, mac_address): + """Create a (synthetic) nic and attach it to the vm.""" + #Create a new nic + syntheticnics_data = self._conn.Msvm_SyntheticEthernetPortSettingData() + default_nic_data = [n for n in syntheticnics_data + if n.InstanceID.rfind('Default') > 0] + new_nic_data = self._clone_wmi_obj( + 'Msvm_SyntheticEthernetPortSettingData', default_nic_data[0]) + + #Configure the nic + new_nic_data.ElementName = nic_name + new_nic_data.Address = mac_address.replace(':', '') + new_nic_data.StaticMacAddress = 'True' + new_nic_data.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}'] + + #Add the new nic to the vm + vm = self._lookup_vm_check(vm_name) + + self._add_virt_resource(new_nic_data, vm.path_()) + + def set_vm_state(self, vm_name, req_state): + """Set the desired state of the VM.""" + + vm = self._lookup_vm_check(vm_name) + (job_path, ret_val) = vm.RequestStateChange(req_state) + #Invalid state for current operation (32775) typically means that + #the VM is already in the state requested + self.check_ret_val(ret_val, job_path, [0, 32775]) + LOG.debug(_("Successfully changed vm state of %(vm_name)s" + " to %(req_state)s") % locals()) + + def destroy_vm(self, vm_name, destroy_disks=True): + """Destroy the VM. Also destroy the associated VHD disk files.""" + + vm = self._lookup_vm_check(vm_name) + + #Stop the VM first. + self.set_vm_state(vm_name, constants.HYPERV_VM_STATE_DISABLED) + + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + vmsettings = vm.associators( + wmi_result_class='Msvm_VirtualSystemSettingData') + rasds = vmsettings[0].associators( + wmi_result_class='MSVM_ResourceAllocationSettingData') + disk_resources = [r for r in rasds + if r.ResourceSubType == + 'Microsoft Virtual Hard Disk'] + volume_resources = [r for r in rasds + if r.ResourceSubType == + 'Microsoft Physical Disk Drive'] + + #Collect volumes information before destroying the VM. + volumes_drives_list = [] + for volume_resource in volume_resources: + drive_path = volume_resource.HostResource[0] + #Appending the Msvm_Disk path + volumes_drives_list.append(drive_path) + + #Collect disk file information before destroying the VM. + disk_files = [] + for disk_resource in disk_resources: + disk_files.extend([c for c in disk_resource.Connection]) + + #Remove the VM. Does not destroy disks. + (job_path, ret_val) = vs_man_svc.DestroyVirtualSystem(vm.path_()) + self.check_ret_val(ret_val, job_path) + + if destroy_disks: + #Delete associated vhd disk files. + for disk in disk_files: + LOG.debug(_("Deleting disk file: %(disk)s") % locals()) + self._delete_file(disk) + + return volumes_drives_list + + def _delete_file(self, path): + f = self._conn_cimv2.query("Select * from CIM_DataFile where " + "Name = '%s'" % path.replace("'", "''"))[0] + f.Delete() + + def check_ret_val(self, ret_val, job_path, success_values=[0]): + if ret_val == constants.WMI_JOB_STATUS_STARTED: + self._wait_for_job(job_path) + elif ret_val not in success_values: + raise HyperVException(_('Operation failed with return value: %s') + % ret_val) + + def _wait_for_job(self, job_path): + """Poll WMI job state and wait for completion.""" + + job_wmi_path = job_path.replace('\\', '/') job = wmi.WMI(moniker=job_wmi_path) while job.JobState == constants.WMI_JOB_STATE_RUNNING: @@ -69,54 +388,30 @@ class VMUtils(object): err_sum_desc = job.ErrorSummaryDescription err_desc = job.ErrorDescription err_code = job.ErrorCode - LOG.debug(_("WMI job failed with status %(job_state)d. " - "Error details: %(err_sum_desc)s - %(err_desc)s - " - "Error code: %(err_code)d") % locals()) + raise HyperVException(_("WMI job failed with status " + "%(job_state)d. Error details: " + "%(err_sum_desc)s - %(err_desc)s - " + "Error code: %(err_code)d") + % locals()) else: (error, ret_val) = job.GetError() if not ret_val and error: - LOG.debug(_("WMI job failed with status %(job_state)d. " - "Error details: %(error)s") % locals()) + raise HyperVException(_("WMI job failed with status " + "%(job_state)d. Error details: " + "%(error)s") % locals()) else: - LOG.debug(_("WMI job failed with status %(job_state)d. " - "No error description available") % locals()) - return False + raise HyperVException(_("WMI job failed with status " + "%(job_state)d. No error " + "description available") + % locals()) desc = job.Description elap = job.ElapsedTime LOG.debug(_("WMI job succeeded: %(desc)s, Elapsed=%(elap)s") - % locals()) - return True - - def get_instance_path(self, instance_name): - instance_path = os.path.join(CONF.instances_path, instance_name) - if not os.path.exists(instance_path): - LOG.debug(_('Creating folder %s '), instance_path) - os.makedirs(instance_path) - return instance_path - - def get_vhd_path(self, instance_name): - instance_path = self.get_instance_path(instance_name) - return os.path.join(instance_path, instance_name + ".vhd") - - def get_base_vhd_path(self, image_name): - base_dir = os.path.join(CONF.instances_path, '_base') - if not os.path.exists(base_dir): - os.makedirs(base_dir) - return os.path.join(base_dir, image_name + ".vhd") - - def make_export_path(self, instance_name): - export_folder = os.path.join(CONF.instances_path, "export", - instance_name) - if os.path.isdir(export_folder): - LOG.debug(_('Removing existing folder %s '), export_folder) - shutil.rmtree(export_folder) - LOG.debug(_('Creating folder %s '), export_folder) - os.makedirs(export_folder) - return export_folder - - def clone_wmi_obj(self, conn, wmi_class, wmi_obj): + % locals()) + + def _clone_wmi_obj(self, wmi_class, wmi_obj): """Clone a WMI object.""" - cl = conn.__getattr__(wmi_class) # get the class + cl = getattr(self._conn, wmi_class) # get the class newinst = cl.new() #Copy the properties from the original. for prop in wmi_obj._properties: @@ -125,51 +420,78 @@ class VMUtils(object): strguid.append(str(uuid.uuid4())) newinst.Properties_.Item(prop).Value = strguid else: - newinst.Properties_.Item(prop).Value = \ - wmi_obj.Properties_.Item(prop).Value + prop_value = wmi_obj.Properties_.Item(prop).Value + newinst.Properties_.Item(prop).Value = prop_value + return newinst - def add_virt_resource(self, conn, res_setting_data, target_vm): + def _add_virt_resource(self, res_setting_data, vm_path): """Adds a new resource to the VM.""" - vs_man_svc = conn.Msvm_VirtualSystemManagementService()[0] - (job, new_resources, ret_val) = vs_man_svc.\ - AddVirtualSystemResources([res_setting_data.GetText_(1)], - target_vm.path_()) - success = True - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self.check_job_status(job) - else: - success = (ret_val == 0) - if success: - return new_resources - else: - return None + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + res_xml = [res_setting_data.GetText_(1)] + (job_path, + new_resources, + ret_val) = vs_man_svc.AddVirtualSystemResources(res_xml, vm_path) + self.check_ret_val(ret_val, job_path) + return new_resources - def modify_virt_resource(self, conn, res_setting_data, target_vm): + def _modify_virt_resource(self, res_setting_data, vm_path): """Updates a VM resource.""" - vs_man_svc = conn.Msvm_VirtualSystemManagementService()[0] - (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources( + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + (job_path, ret_val) = vs_man_svc.ModifyVirtualSystemResources( ResourceSettingData=[res_setting_data.GetText_(1)], - ComputerSystem=target_vm.path_()) - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self.check_job_status(job) - else: - success = (ret_val == 0) - return success + ComputerSystem=vm_path) + self.check_ret_val(ret_val, job_path) - def remove_virt_resource(self, conn, res_setting_data, target_vm): + def _remove_virt_resource(self, res_setting_data, vm_path): """Removes a VM resource.""" - vs_man_svc = conn.Msvm_VirtualSystemManagementService()[0] - (job, ret_val) = vs_man_svc.\ - RemoveVirtualSystemResources([res_setting_data.path_()], - target_vm.path_()) - success = True - if ret_val == constants.WMI_JOB_STATUS_STARTED: - success = self.check_job_status(job) - else: - success = (ret_val == 0) - return success + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + res_path = [res_setting_data.path_()] + (job_path, ret_val) = vs_man_svc.RemoveVirtualSystemResources(res_path, + vm_path) + self.check_ret_val(ret_val, job_path) + + def take_vm_snapshot(self, vm_name): + vm = self._lookup_vm_check(vm_name) + + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + + (job_path, ret_val, + snp_setting_data) = vs_man_svc.CreateVirtualSystemSnapshot(vm.path_()) + self.check_ret_val(ret_val, job_path) + + job_wmi_path = job_path.replace('\\', '/') + job = wmi.WMI(moniker=job_wmi_path) + snp_setting_data = job.associators( + wmi_result_class='Msvm_VirtualSystemSettingData')[0] + return snp_setting_data.path_() + + def remove_vm_snapshot(self, snapshot_path): + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + + (job_path, ret_val) = vs_man_svc.RemoveVirtualSystemSnapshot( + snapshot_path) + self.check_ret_val(ret_val, job_path) + + def detach_vm_disk(self, vm_name, disk_path): + vm = self._lookup_vm_check(vm_name) + physical_disk = self._get_mounted_disk_resource_from_path( + disk_path) + self._remove_virt_resource(physical_disk, vm.path_()) + + def _get_mounted_disk_resource_from_path(self, disk_path): + physical_disks = self._conn.query("SELECT * FROM " + "Msvm_ResourceAllocationSettingData" + " WHERE ResourceSubType = " + "'Microsoft Physical Disk Drive'") + for physical_disk in physical_disks: + if physical_disk.HostResource: + if physical_disk.HostResource[0].lower() == disk_path.lower(): + return physical_disk - def fetch_image(self, target, context, image_id, user, project, - *args, **kwargs): - images.fetch(context, image_id, target, user, project) + def get_mounted_disk_by_drive_number(self, device_number): + mounted_disks = self._conn.query("SELECT * FROM Msvm_DiskDrive " + "WHERE DriveNumber=" + + str(device_number)) + if len(mounted_disks): + return mounted_disks[0].path_() diff --git a/nova/virt/hyperv/volumeops.py b/nova/virt/hyperv/volumeops.py index 200236233..a7e56b739 100644 --- a/nova/virt/hyperv/volumeops.py +++ b/nova/virt/hyperv/volumeops.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 Pedro Navarro Perez +# Copyright 2013 Cloudbase Solutions Srl # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -20,210 +21,140 @@ Management class for Storage-related functions (attach, detach, etc). """ import time -from nova import block_device from nova.openstack.common import cfg from nova.openstack.common import log as logging from nova.virt import driver -from nova.virt.hyperv import baseops +from nova.virt.hyperv import hostutils from nova.virt.hyperv import vmutils from nova.virt.hyperv import volumeutils -from nova.virt.hyperv import volumeutilsV2 +from nova.virt.hyperv import volumeutilsv2 LOG = logging.getLogger(__name__) hyper_volumeops_opts = [ cfg.IntOpt('hyperv_attaching_volume_retry_count', - default=10, - help='The number of times we retry on attaching volume '), + default=10, + help='The number of times we retry on attaching volume '), cfg.IntOpt('hyperv_wait_between_attach_retry', - default=5, - help='The seconds to wait between an volume attachment attempt'), + default=5, + help='The seconds to wait between an volume ' + 'attachment attempt'), cfg.BoolOpt('force_volumeutils_v1', - default=False, - help='Force volumeutils v1'), - ] + default=False, + help='Force volumeutils v1'), +] CONF = cfg.CONF CONF.register_opts(hyper_volumeops_opts) CONF.import_opt('my_ip', 'nova.netconf') -class VolumeOps(baseops.BaseOps): +class VolumeOps(object): """ Management class for Volume-related tasks """ def __init__(self): - super(VolumeOps, self).__init__() - + self._hostutils = hostutils.HostUtils() self._vmutils = vmutils.VMUtils() - self._driver = driver - self._block_device = block_device - self._time = time + self._volutils = self._get_volume_utils() self._initiator = None self._default_root_device = 'vda' - self._attaching_volume_retry_count = \ - CONF.hyperv_attaching_volume_retry_count - self._wait_between_attach_retry = \ - CONF.hyperv_wait_between_attach_retry - self._volutils = self._get_volume_utils() def _get_volume_utils(self): - if(not CONF.force_volumeutils_v1) and \ - (self._get_hypervisor_version() >= 6.2): - return volumeutilsV2.VolumeUtilsV2( - self._conn_storage, self._conn_wmi) + if(not CONF.force_volumeutils_v1 and + self._hostutils.get_windows_version() >= 6.2): + return volumeutilsv2.VolumeUtilsV2() else: - return volumeutils.VolumeUtils(self._conn_wmi) - - def _get_hypervisor_version(self): - """Get hypervisor version. - :returns: hypervisor version (ex. 12003) - """ - version = self._conn_cimv2.Win32_OperatingSystem()[0]\ - .Version - LOG.info(_('Windows version: %s ') % version) - return version + return volumeutils.VolumeUtils() def attach_boot_volume(self, block_device_info, vm_name): """Attach the boot volume to the IDE controller.""" + LOG.debug(_("block device info: %s"), block_device_info) - ebs_root = self._driver.block_device_info_get_mapping( + ebs_root = driver.block_device_info_get_mapping( block_device_info)[0] + connection_info = ebs_root['connection_info'] data = connection_info['data'] target_lun = data['target_lun'] target_iqn = data['target_iqn'] target_portal = data['target_portal'] self._volutils.login_storage_target(target_lun, target_iqn, - target_portal) + target_portal) try: #Getting the mounted disk - mounted_disk = self._get_mounted_disk_from_lun(target_iqn, - target_lun) - #Attach to IDE controller + mounted_disk_path = self._get_mounted_disk_from_lun(target_iqn, + target_lun) #Find the IDE controller for the vm. - vms = self._conn.MSVM_ComputerSystem(ElementName=vm_name) - vm = vms[0] - vmsettings = vm.associators( - wmi_result_class='Msvm_VirtualSystemSettingData') - rasds = vmsettings[0].associators( - wmi_result_class='MSVM_ResourceAllocationSettingData') - ctrller = [r for r in rasds - if r.ResourceSubType == 'Microsoft Emulated IDE Controller' - and r.Address == "0"] + ctrller_path = self._vmutils.get_vm_ide_controller(vm_name, 0) #Attaching to the same slot as the VHD disk file - self._attach_volume_to_controller(ctrller, 0, mounted_disk, vm) + self._vmutils.attach_volume_to_controller(vm_name, + ctrller_path, 0, + mounted_disk_path) except Exception as exn: LOG.exception(_('Attach boot from volume failed: %s'), exn) self._volutils.logout_storage_target(target_iqn) raise vmutils.HyperVException( - _('Unable to attach boot volume to instance %s') - % vm_name) + _('Unable to attach boot volume to instance %s') % vm_name) def volume_in_mapping(self, mount_device, block_device_info): return self._volutils.volume_in_mapping(mount_device, - block_device_info) + block_device_info) - def attach_volume(self, connection_info, instance_name, mountpoint): + def attach_volume(self, connection_info, instance_name): """Attach a volume to the SCSI controller.""" - LOG.debug(_("Attach_volume: %(connection_info)s, %(instance_name)s," - " %(mountpoint)s") % locals()) + LOG.debug(_("Attach_volume: %(connection_info)s to %(instance_name)s") + % locals()) data = connection_info['data'] target_lun = data['target_lun'] target_iqn = data['target_iqn'] target_portal = data['target_portal'] self._volutils.login_storage_target(target_lun, target_iqn, - target_portal) + target_portal) try: #Getting the mounted disk - mounted_disk = self._get_mounted_disk_from_lun(target_iqn, - target_lun) + mounted_disk_path = self._get_mounted_disk_from_lun(target_iqn, + target_lun) #Find the SCSI controller for the vm - vms = self._conn.MSVM_ComputerSystem(ElementName=instance_name) - vm = vms[0] - vmsettings = vm.associators( - wmi_result_class='Msvm_VirtualSystemSettingData') - rasds = vmsettings[0].associators( - wmi_result_class='MSVM_ResourceAllocationSettingData') - ctrller = [r for r in rasds - if r.ResourceSubType == 'Microsoft Synthetic SCSI Controller'] - self._attach_volume_to_controller( - ctrller, self._get_free_controller_slot(ctrller[0]), - mounted_disk, vm) + ctrller_path = self._vmutils.get_vm_iscsi_controller(instance_name) + + slot = self._get_free_controller_slot(ctrller_path) + self._vmutils.attach_volume_to_controller(instance_name, + ctrller_path, + slot, + mounted_disk_path) except Exception as exn: LOG.exception(_('Attach volume failed: %s'), exn) self._volutils.logout_storage_target(target_iqn) - raise vmutils.HyperVException( - _('Unable to attach volume to instance %s') - % instance_name) + raise vmutils.HyperVException(_('Unable to attach volume ' + 'to instance %s') % instance_name) - def _attach_volume_to_controller(self, controller, address, mounted_disk, - instance): - """Attach a volume to a controller.""" - #Find the default disk drive object for the vm and clone it. - diskdflt = self._conn.query( - "SELECT * FROM Msvm_ResourceAllocationSettingData \ - WHERE ResourceSubType LIKE 'Microsoft Physical Disk Drive'\ - AND InstanceID LIKE '%Default%'")[0] - diskdrive = self._vmutils.clone_wmi_obj(self._conn, - 'Msvm_ResourceAllocationSettingData', diskdflt) - diskdrive.Address = address - diskdrive.Parent = controller[0].path_() - diskdrive.HostResource = [mounted_disk[0].path_()] - new_resources = self._vmutils.add_virt_resource(self._conn, diskdrive, - instance) - if new_resources is None: - raise vmutils.HyperVException(_('Failed to add volume to VM %s') % - instance) - - def _get_free_controller_slot(self, scsi_controller): - #Getting volumes mounted in the SCSI controller - volumes = self._conn.query( - "SELECT * FROM Msvm_ResourceAllocationSettingData \ - WHERE ResourceSubType LIKE 'Microsoft Physical Disk Drive'\ - AND Parent = '" + scsi_controller.path_() + "'") + def _get_free_controller_slot(self, scsi_controller_path): #Slots starts from 0, so the lenght of the disks gives us the free slot - return len(volumes) + return self._vmutils.get_attached_disks_count(scsi_controller_path) - def detach_volume(self, connection_info, instance_name, mountpoint): + def detach_volume(self, connection_info, instance_name): """Dettach a volume to the SCSI controller.""" - LOG.debug(_("Detach_volume: %(connection_info)s, %(instance_name)s," - " %(mountpoint)s") % locals()) + LOG.debug(_("Detach_volume: %(connection_info)s " + "from %(instance_name)s") % locals()) data = connection_info['data'] target_lun = data['target_lun'] target_iqn = data['target_iqn'] #Getting the mounted disk - mounted_disk = self._get_mounted_disk_from_lun(target_iqn, target_lun) - physical_list = self._conn.query( - "SELECT * FROM Msvm_ResourceAllocationSettingData \ - WHERE ResourceSubType LIKE 'Microsoft Physical Disk Drive'") - physical_disk = 0 - for phydisk in physical_list: - host_resource_list = phydisk.HostResource - if host_resource_list is None: - continue - host_resource = str(host_resource_list[0].lower()) - mounted_disk_path = str(mounted_disk[0].path_().lower()) - LOG.debug(_("Mounted disk to detach is: %s"), mounted_disk_path) - LOG.debug(_("host_resource disk detached is: %s"), host_resource) - if host_resource == mounted_disk_path: - physical_disk = phydisk - LOG.debug(_("Physical disk detached is: %s"), physical_disk) - vms = self._conn.MSVM_ComputerSystem(ElementName=instance_name) - vm = vms[0] - remove_result = self._vmutils.remove_virt_resource(self._conn, - physical_disk, vm) - if remove_result is False: - raise vmutils.HyperVException( - _('Failed to remove volume from VM %s') % - instance_name) + mounted_disk_path = self._get_mounted_disk_from_lun(target_iqn, + target_lun) + + LOG.debug(_("Detaching physical disk from instance: %s"), + mounted_disk_path) + self._vmutils.detach_vm_disk(instance_name, mounted_disk_path) + #Sending logout self._volutils.logout_storage_target(target_iqn) def get_volume_connector(self, instance): if not self._initiator: - self._initiator = self._get_iscsi_initiator() + self._initiator = self._volutils.get_iscsi_initiator() if not self._initiator: LOG.warn(_('Could not determine iscsi initiator name'), instance=instance) @@ -232,87 +163,35 @@ class VolumeOps(baseops.BaseOps): 'initiator': self._initiator, } - def _get_iscsi_initiator(self): - return self._volutils.get_iscsi_initiator(self._conn_cimv2) - def _get_mounted_disk_from_lun(self, target_iqn, target_lun): - initiator_session = self._conn_wmi.query( - "SELECT * FROM MSiSCSIInitiator_SessionClass \ - WHERE TargetName='" + target_iqn + "'")[0] - devices = initiator_session.Devices - device_number = None - for device in devices: - LOG.debug(_("device.InitiatorName: %s"), device.InitiatorName) - LOG.debug(_("device.TargetName: %s"), device.TargetName) - LOG.debug(_("device.ScsiPortNumber: %s"), device.ScsiPortNumber) - LOG.debug(_("device.ScsiPathId: %s"), device.ScsiPathId) - LOG.debug(_("device.ScsiTargetId): %s"), device.ScsiTargetId) - LOG.debug(_("device.ScsiLun: %s"), device.ScsiLun) - LOG.debug(_("device.DeviceInterfaceGuid :%s"), - device.DeviceInterfaceGuid) - LOG.debug(_("device.DeviceInterfaceName: %s"), - device.DeviceInterfaceName) - LOG.debug(_("device.LegacyName: %s"), device.LegacyName) - LOG.debug(_("device.DeviceType: %s"), device.DeviceType) - LOG.debug(_("device.DeviceNumber %s"), device.DeviceNumber) - LOG.debug(_("device.PartitionNumber :%s"), device.PartitionNumber) - scsi_lun = device.ScsiLun - if scsi_lun == target_lun: - device_number = device.DeviceNumber + device_number = self._volutils.get_device_number_for_target(target_iqn, + target_lun) if device_number is None: - raise vmutils.HyperVException( - _('Unable to find a mounted disk for' - ' target_iqn: %s') % target_iqn) - LOG.debug(_("Device number : %s"), device_number) - LOG.debug(_("Target lun : %s"), target_lun) + raise vmutils.HyperVException(_('Unable to find a mounted ' + 'disk for target_iqn: %s') + % target_iqn) + LOG.debug(_('Device number: %(device_number)s, ' + 'target lun: %(target_lun)s') % locals()) #Finding Mounted disk drive - for i in range(1, self._attaching_volume_retry_count): - mounted_disk = self._conn.query( - "SELECT * FROM Msvm_DiskDrive WHERE DriveNumber=" + - str(device_number) + "") - LOG.debug(_("Mounted disk is: %s"), mounted_disk) - if len(mounted_disk) > 0: + for i in range(1, CONF.hyperv_attaching_volume_retry_count): + mounted_disk_path = self._vmutils.get_mounted_disk_by_drive_number( + device_number) + if mounted_disk_path: break - self._time.sleep(self._wait_between_attach_retry) - mounted_disk = self._conn.query( - "SELECT * FROM Msvm_DiskDrive WHERE DriveNumber=" + - str(device_number) + "") - LOG.debug(_("Mounted disk is: %s"), mounted_disk) - if len(mounted_disk) == 0: - raise vmutils.HyperVException( - _('Unable to find a mounted disk for' - ' target_iqn: %s') % target_iqn) - return mounted_disk + time.sleep(CONF.hyperv_wait_between_attach_retry) + + if not mounted_disk_path: + raise vmutils.HyperVException(_('Unable to find a mounted disk ' + 'for target_iqn: %s') + % target_iqn) + return mounted_disk_path def disconnect_volume(self, physical_drive_path): #Get the session_id of the ISCSI connection - session_id = self._get_session_id_from_mounted_disk( + session_id = self._volutils.get_session_id_from_mounted_disk( physical_drive_path) #Logging out the target self._volutils.execute_log_out(session_id) - def _get_session_id_from_mounted_disk(self, physical_drive_path): - drive_number = self._get_drive_number_from_disk_path( - physical_drive_path) - LOG.debug(_("Drive number to disconnect is: %s"), drive_number) - initiator_sessions = self._conn_wmi.query( - "SELECT * FROM MSiSCSIInitiator_SessionClass") - for initiator_session in initiator_sessions: - devices = initiator_session.Devices - for device in devices: - deviceNumber = str(device.DeviceNumber) - LOG.debug(_("DeviceNumber : %s"), deviceNumber) - if deviceNumber == drive_number: - return initiator_session.SessionId - - def _get_drive_number_from_disk_path(self, disk_path): - LOG.debug(_("Disk path to parse: %s"), disk_path) - start_device_id = disk_path.find('"', disk_path.find('DeviceID')) - LOG.debug(_("start_device_id: %s"), start_device_id) - end_device_id = disk_path.find('"', start_device_id + 1) - LOG.debug(_("end_device_id: %s"), end_device_id) - deviceID = disk_path[start_device_id + 1:end_device_id] - return deviceID[deviceID.find("\\") + 2:] - def get_default_root_device(self): return self._default_root_device diff --git a/nova/virt/hyperv/volumeutils.py b/nova/virt/hyperv/volumeutils.py index 051c37fd6..713ace258 100644 --- a/nova/virt/hyperv/volumeutils.py +++ b/nova/virt/hyperv/volumeutils.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 Pedro Navarro Perez +# Copyright 2013 Cloudbase Solutions Srl # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -35,47 +36,48 @@ CONF = cfg.CONF class VolumeUtils(basevolumeutils.BaseVolumeUtils): - def __init__(self, conn_wmi): - self._conn_wmi = conn_wmi + def __init__(self): + super(VolumeUtils, self).__init__() - def execute(self, *args, **kwargs): - _PIPE = subprocess.PIPE # pylint: disable=E1101 - proc = subprocess.Popen( - [args], - stdin=_PIPE, - stdout=_PIPE, - stderr=_PIPE, - ) - stdout_value, stderr_value = proc.communicate() - if stdout_value.find('The operation completed successfully') == -1: - raise vmutils.HyperVException(_('An error has occurred when ' - 'calling the iscsi initiator: %s') % stdout_value) + def execute(self, *args, **kwargs): + _PIPE = subprocess.PIPE # pylint: disable=E1101 + proc = subprocess.Popen( + [args], + stdin=_PIPE, + stdout=_PIPE, + stderr=_PIPE, + ) + stdout_value, stderr_value = proc.communicate() + if stdout_value.find('The operation completed successfully') == -1: + raise vmutils.HyperVException(_('An error has occurred when ' + 'calling the iscsi initiator: %s') + % stdout_value) - def login_storage_target(self, target_lun, target_iqn, target_portal): - """Add target portal, list targets and logins to the target.""" - separator = target_portal.find(':') - target_address = target_portal[:separator] - target_port = target_portal[separator + 1:] - #Adding target portal to iscsi initiator. Sending targets - self.execute('iscsicli.exe ' + 'AddTargetPortal ' + - target_address + ' ' + target_port + - ' * * * * * * * * * * * * *') - #Listing targets - self.execute('iscsicli.exe ' + 'LisTargets') - #Sending login - self.execute('iscsicli.exe ' + 'qlogintarget ' + target_iqn) - #Waiting the disk to be mounted. Research this to avoid sleep - time.sleep(CONF.hyperv_wait_between_attach_retry) + def login_storage_target(self, target_lun, target_iqn, target_portal): + """Add target portal, list targets and logins to the target.""" + separator = target_portal.find(':') + target_address = target_portal[:separator] + target_port = target_portal[separator + 1:] + #Adding target portal to iscsi initiator. Sending targets + self.execute('iscsicli.exe ' + 'AddTargetPortal ' + + target_address + ' ' + target_port + + ' * * * * * * * * * * * * *') + #Listing targets + self.execute('iscsicli.exe ' + 'LisTargets') + #Sending login + self.execute('iscsicli.exe ' + 'qlogintarget ' + target_iqn) + #Waiting the disk to be mounted. Research this to avoid sleep + time.sleep(CONF.hyperv_wait_between_attach_retry) - def logout_storage_target(self, target_iqn): - """Logs out storage target through its session id.""" + def logout_storage_target(self, target_iqn): + """Logs out storage target through its session id.""" - sessions = self._conn_wmi.query( - "SELECT * FROM MSiSCSIInitiator_SessionClass \ - WHERE TargetName='" + target_iqn + "'") - for session in sessions: - self.execute_log_out(session.SessionId) + sessions = self._conn_wmi.query("SELECT * FROM " + "MSiSCSIInitiator_SessionClass " + "WHERE TargetName='%s'" % target_iqn) + for session in sessions: + self.execute_log_out(session.SessionId) - def execute_log_out(self, session_id): - """Executes log out of the session described by its session ID.""" - self.execute('iscsicli.exe ' + 'logouttarget ' + session_id) + def execute_log_out(self, session_id): + """Executes log out of the session described by its session ID.""" + self.execute('iscsicli.exe ' + 'logouttarget ' + session_id) diff --git a/nova/virt/hyperv/volumeutilsV2.py b/nova/virt/hyperv/volumeutilsV2.py deleted file mode 100644 index 6f5bcdac9..000000000 --- a/nova/virt/hyperv/volumeutilsV2.py +++ /dev/null @@ -1,70 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# Copyright 2012 Pedro Navarro Perez -# 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. - -""" -Helper methods for operations related to the management of volumes, -and storage repositories for Windows 2012 -""" -import time - -from nova.openstack.common import cfg -from nova.openstack.common import log as logging -from nova.virt.hyperv import basevolumeutils - -LOG = logging.getLogger(__name__) -CONF = cfg.CONF - - -class VolumeUtilsV2(basevolumeutils.BaseVolumeUtils): - - def __init__(self, conn_storage, conn_wmi): - self._conn_storage = conn_storage - self._conn_wmi = conn_wmi - - def login_storage_target(self, target_lun, target_iqn, - target_portal): - """Add target portal, list targets and logins to the target.""" - separator = target_portal.find(':') - target_address = target_portal[:separator] - target_port = target_portal[separator + 1:] - #Adding target portal to iscsi initiator. Sending targets - portal = self._conn_storage.__getattr__("MSFT_iSCSITargetPortal") - portal.New(TargetPortalAddress=target_address, - TargetPortalPortNumber=target_port) - #Connecting to the target - target = self._conn_storage.__getattr__("MSFT_iSCSITarget") - target.Connect(NodeAddress=target_iqn, - IsPersistent=True) - #Waiting the disk to be mounted. Research this - time.sleep(CONF.hyperv_wait_between_attach_retry) - - def logout_storage_target(self, target_iqn): - """Logs out storage target through its session id.""" - - target = self._conn_storage.MSFT_iSCSITarget( - NodeAddress=target_iqn)[0] - if target.IsConnected: - session = self._conn_storage.MSFT_iSCSISession( - TargetNodeAddress=target_iqn)[0] - if session.IsPersistent: - session.Unregister() - target.Disconnect() - - def execute_log_out(self, session_id): - session = self._conn_wmi.MSiSCSIInitiator_SessionClass( - SessionId=session_id)[0] - self.logout_storage_target(session.TargetName) diff --git a/nova/virt/hyperv/volumeutilsv2.py b/nova/virt/hyperv/volumeutilsv2.py new file mode 100644 index 000000000..8322d31d3 --- /dev/null +++ b/nova/virt/hyperv/volumeutilsv2.py @@ -0,0 +1,75 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Pedro Navarro Perez +# Copyright 2013 Cloudbase Solutions Srl +# 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. + +""" +Helper methods for operations related to the management of volumes +and storage repositories on Windows Server 2012 and above +""" +import sys +import time + +if sys.platform == 'win32': + import wmi + +from nova.openstack.common import cfg +from nova.openstack.common import log as logging +from nova.virt.hyperv import basevolumeutils + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF + + +class VolumeUtilsV2(basevolumeutils.BaseVolumeUtils): + def __init__(self): + super(VolumeUtilsV2, self).__init__() + + storage_namespace = '//./root/microsoft/windows/storage' + if sys.platform == 'win32': + self._conn_storage = wmi.WMI(moniker=storage_namespace) + + def login_storage_target(self, target_lun, target_iqn, target_portal): + """Add target portal, list targets and logins to the target.""" + separator = target_portal.find(':') + target_address = target_portal[:separator] + target_port = target_portal[separator + 1:] + #Adding target portal to iscsi initiator. Sending targets + portal = self._conn_storage.MSFT_iSCSITargetPortal + portal.New(TargetPortalAddress=target_address, + TargetPortalPortNumber=target_port) + #Connecting to the target + target = self._conn_storage.MSFT_iSCSITarget + target.Connect(NodeAddress=target_iqn, + IsPersistent=True) + #Waiting the disk to be mounted. Research this + time.sleep(CONF.hyperv_wait_between_attach_retry) + + def logout_storage_target(self, target_iqn): + """Logs out storage target through its session id.""" + + target = self._conn_storage.MSFT_iSCSITarget(NodeAddress=target_iqn)[0] + if target.IsConnected: + session = self._conn_storage.MSFT_iSCSISession( + TargetNodeAddress=target_iqn)[0] + if session.IsPersistent: + session.Unregister() + target.Disconnect() + + def execute_log_out(self, session_id): + session = self._conn_wmi.MSiSCSIInitiator_SessionClass( + SessionId=session_id)[0] + self.logout_storage_target(session.TargetName) diff --git a/nova/virt/images.py b/nova/virt/images.py index f80c19999..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: @@ -175,7 +175,7 @@ class QemuImgInfo(object): def qemu_img_info(path): - """Return a object containing the parsed output from qemu-img info.""" + """Return an object containing the parsed output from qemu-img info.""" if not os.path.exists(path): return QemuImgInfo() 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 557818a99..bd2f51e69 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -47,6 +47,7 @@ import os import shutil import sys import tempfile +import time import uuid from eventlet import greenthread @@ -140,7 +141,7 @@ libvirt_opts = [ 'raw, qcow2, vmdk, vdi). ' 'Defaults to same as source image'), cfg.StrOpt('libvirt_vif_driver', - default='nova.virt.libvirt.vif.LibvirtBridgeDriver', + default='nova.virt.libvirt.vif.LibvirtGenericVIFDriver', help='The libvirt VIF driver to configure the VIFs.'), cfg.ListOpt('libvirt_volume_drivers', default=[ @@ -254,6 +255,10 @@ MIN_LIBVIRT_VERSION = (0, 9, 6) # When the above version matches/exceeds this version # delete it & corresponding code using it MIN_LIBVIRT_HOST_CPU_VERSION = (0, 9, 10) +# Live snapshot requirements +REQ_HYPERVISOR_LIVESNAPSHOT = "QEMU" +MIN_LIBVIRT_LIVESNAPSHOT_VERSION = (1, 0, 0) +MIN_QEMU_LIVESNAPSHOT_VERSION = (1, 3, 0) def _get_eph_disk(ephemeral): @@ -277,17 +282,17 @@ class LibvirtDriver(driver.ComputeDriver): self._host_state = None self._initiator = None self._wrapped_conn = None + self._caps = None self.read_only = read_only self.firewall_driver = firewall.load_driver( DEFAULT_FIREWALL_DRIVER, 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"} @@ -325,16 +330,29 @@ class LibvirtDriver(driver.ComputeDriver): self._host_state = HostState(self.virtapi, self.read_only) return self._host_state - def has_min_version(self, ver): - libvirt_version = self._conn.getLibVersion() - + def has_min_version(self, lv_ver=None, hv_ver=None, hv_type=None): def _munge_version(ver): return ver[0] * 1000000 + ver[1] * 1000 + ver[2] - if libvirt_version < _munge_version(ver): - return False + try: + if lv_ver is not None: + libvirt_version = self._conn.getLibVersion() + if libvirt_version < _munge_version(lv_ver): + return False - return True + if hv_ver is not None: + hypervisor_version = self._conn.getVersion() + if hypervisor_version < _munge_version(hv_ver): + return False + + if hv_type is not None: + hypervisor_type = self._conn.getType() + if hypervisor_type != hv_type: + return False + + return True + except Exception: + return False def init_host(self, host): if not self.has_min_version(MIN_LIBVIRT_VERSION): @@ -362,7 +380,7 @@ class LibvirtDriver(driver.ComputeDriver): def _test_connection(self): try: - self._wrapped_conn.getCapabilities() + self._wrapped_conn.getLibVersion() return True except libvirt.libvirtError as e: if (e.get_error_code() == libvirt.VIR_ERR_SYSTEM_ERROR and @@ -426,10 +444,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 @@ -655,7 +673,7 @@ class LibvirtDriver(driver.ComputeDriver): def volume_driver_method(self, method_name, connection_info, *args, **kwargs): driver_type = connection_info.get('driver_volume_type') - if not driver_type in self.volume_drivers: + if driver_type not in self.volume_drivers: raise exception.VolumeDriverNotFound(driver_type=driver_type) driver = self.volume_drivers[driver_type] method = getattr(driver, method_name) @@ -706,7 +724,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) @@ -805,32 +824,64 @@ class LibvirtDriver(driver.ComputeDriver): (state, _max_mem, _mem, _cpus, _t) = virt_dom.info() state = LIBVIRT_POWER_STATE[state] + # NOTE(rmk): Live snapshots require QEMU 1.3 and Libvirt 1.0.0. + # These restrictions can be relaxed as other configurations + # can be validated. + if self.has_min_version(MIN_LIBVIRT_LIVESNAPSHOT_VERSION, + MIN_QEMU_LIVESNAPSHOT_VERSION, + REQ_HYPERVISOR_LIVESNAPSHOT): + live_snapshot = True + else: + live_snapshot = False + + # NOTE(rmk): We cannot perform live snapshots when a managedSave + # file is present, so we will use the cold/legacy method + # for instances which are shutdown. + if state == power_state.SHUTDOWN: + live_snapshot = False + # NOTE(dkang): managedSave does not work for LXC - if CONF.libvirt_type != 'lxc': - if state == power_state.RUNNING: + if CONF.libvirt_type != 'lxc' and not live_snapshot: + if state == power_state.RUNNING or state == power_state.PAUSED: virt_dom.managedSave(0) - # Make the snapshot - snapshot = self.image_backend.snapshot(disk_path, snapshot_name, - image_type=source_format) + if live_snapshot: + LOG.info(_("Beginning live snapshot process"), + instance=instance) + else: + LOG.info(_("Beginning cold snapshot process"), + instance=instance) + snapshot = self.image_backend.snapshot(disk_path, snapshot_name, + image_type=source_format) + snapshot.create() - snapshot.create() update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) - - # Export the snapshot to a raw image snapshot_directory = CONF.libvirt_snapshots_directory fileutils.ensure_tree(snapshot_directory) with utils.tempdir(dir=snapshot_directory) as tmpdir: try: out_path = os.path.join(tmpdir, snapshot_name) - snapshot.extract(out_path, image_format) + if live_snapshot: + # NOTE (rmk): libvirt needs to be able to write to the + # temp directory, which is owned nova. + utils.execute('chmod', '777', tmpdir, run_as_root=True) + self._live_snapshot(virt_dom, disk_path, out_path, + image_format) + else: + snapshot.extract(out_path, image_format) finally: - snapshot.delete() + if not live_snapshot: + snapshot.delete() # NOTE(dkang): because previous managedSave is not called # for LXC, _create_domain must not be called. - if CONF.libvirt_type != 'lxc': + if CONF.libvirt_type != 'lxc' and not live_snapshot: 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) + LOG.info(_("Snapshot extracted, beginning image upload"), + instance=instance) # Upload that image to the image service @@ -841,6 +892,72 @@ class LibvirtDriver(driver.ComputeDriver): image_href, metadata, image_file) + LOG.info(_("Snapshot image upload complete"), + instance=instance) + + def _live_snapshot(self, domain, disk_path, out_path, image_format): + """Snapshot an instance without downtime.""" + # Save a copy of the domain's running XML file + xml = domain.XMLDesc(0) + + # Abort is an idempotent operation, so make sure any block + # jobs which may have failed are ended. + try: + domain.blockJobAbort(disk_path, 0) + except Exception: + pass + + def _wait_for_block_job(domain, disk_path): + status = domain.blockJobInfo(disk_path, 0) + try: + cur = status.get('cur', 0) + end = status.get('end', 0) + except Exception: + return False + + if cur == end and cur != 0 and end != 0: + return False + else: + return True + + # NOTE (rmk): We are using shallow rebases as a workaround to a bug + # in QEMU 1.3. In order to do this, we need to create + # a destination image with the original backing file + # and matching size of the instance root disk. + src_disk_size = libvirt_utils.get_disk_size(disk_path) + src_back_path = libvirt_utils.get_disk_backing_file(disk_path, + basename=False) + disk_delta = out_path + '.delta' + libvirt_utils.create_cow_image(src_back_path, disk_delta, + src_disk_size) + + try: + # NOTE (rmk): blockRebase cannot be executed on persistent + # domains, so we need to temporarily undefine it. + # If any part of this block fails, the domain is + # re-defined regardless. + if domain.isPersistent(): + domain.undefine() + + # NOTE (rmk): Establish a temporary mirror of our root disk and + # issue an abort once we have a complete copy. + domain.blockRebase(disk_path, disk_delta, 0, + libvirt.VIR_DOMAIN_BLOCK_REBASE_COPY | + libvirt.VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT | + libvirt.VIR_DOMAIN_BLOCK_REBASE_SHALLOW) + + while _wait_for_block_job(domain, disk_path): + time.sleep(0.5) + + domain.blockJobAbort(disk_path, 0) + libvirt_utils.chown(disk_delta, os.getuid()) + finally: + self._conn.defineXML(xml) + + # Convert the delta (CoW) image with a backing file to a flat + # image with no backing file. + libvirt_utils.extract_snapshot(disk_delta, 'qcow2', None, + out_path, image_format) def reboot(self, instance, network_info, reboot_type='SOFT', block_device_info=None): @@ -854,8 +971,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. @@ -871,6 +987,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: @@ -879,8 +996,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]: @@ -891,11 +1010,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. @@ -908,11 +1030,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) @@ -957,16 +1078,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): @@ -979,7 +1115,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) @@ -1146,6 +1282,27 @@ class LibvirtDriver(driver.ComputeDriver): return {'host': host, 'port': port, 'internal_access_path': None} + @exception.wrap_exception() + def get_spice_console(self, instance): + def get_spice_ports_for_instance(instance_name): + virt_dom = self._lookup_by_name(instance_name) + xml = virt_dom.XMLDesc(0) + # TODO(sleepsonthefloor): use etree instead of minidom + dom = minidom.parseString(xml) + + for graphic in dom.getElementsByTagName('graphics'): + if graphic.getAttribute('type') == 'spice': + return (graphic.getAttribute('port'), + graphic.getAttribute('tlsPort')) + + return (None, None) + + ports = get_spice_ports_for_instance(instance['name']) + host = CONF.spice.server_proxyclient_address + + return {'host': host, 'port': ports[0], + 'tlsPort': ports[1], 'internal_access_path': None} + @staticmethod def _supports_direct_io(dirpath): @@ -1275,8 +1432,8 @@ class LibvirtDriver(driver.ComputeDriver): if size == 0 or suffix == '.rescue': size = None - if not self._volume_in_mapping(self.default_root_device, - block_device_info): + if not block_device.volume_in_mapping( + self.default_root_device, block_device_info): image('disk').cache(fetch_func=libvirt_utils.fetch_image, context=context, filename=root_fname, @@ -1291,7 +1448,7 @@ class LibvirtDriver(driver.ComputeDriver): os_type_with_default = 'default' ephemeral_gb = instance['ephemeral_gb'] - if ephemeral_gb and not self._volume_in_mapping( + if ephemeral_gb and not block_device.volume_in_mapping( self.default_second_device, block_device_info): swap_device = self.default_third_device fn = functools.partial(self._create_ephemeral, @@ -1323,7 +1480,8 @@ class LibvirtDriver(driver.ComputeDriver): if driver.swap_is_usable(swap): swap_mb = swap['swap_size'] elif (inst_type['swap'] > 0 and - not self._volume_in_mapping(swap_device, block_device_info)): + not block_device.volume_in_mapping( + swap_device, block_device_info)): swap_mb = inst_type['swap'] if swap_mb > 0: @@ -1381,52 +1539,33 @@ class LibvirtDriver(driver.ComputeDriver): injection_path = image('disk').path img_id = instance['image_ref'] - for injection in ('metadata', 'key', 'net', 'admin_pass', - 'files'): - if locals()[injection]: - LOG.info(_('Injecting %(injection)s into image' + for inject in ('key', 'net', 'metadata', 'admin_pass', 'files'): + if locals()[inject]: + LOG.info(_('Injecting %(inject)s into image' ' %(img_id)s'), locals(), instance=instance) try: disk.inject_data(injection_path, key, net, metadata, admin_pass, files, partition=target_partition, - use_cow=CONF.use_cow_images) - + use_cow=CONF.use_cow_images, + mandatory=('files',)) except Exception as e: - # This could be a windows image, or a vmdk format disk - LOG.warn(_('Ignoring error injecting data into image ' - '%(img_id)s (%(e)s)') % locals(), - instance=instance) + LOG.error(_('Error injecting data into image ' + '%(img_id)s (%(e)s)') % locals(), + instance=instance) + raise if CONF.libvirt_type == 'uml': libvirt_utils.chown(image('disk').path, 'root') - @staticmethod - def _volume_in_mapping(mount_device, block_device_info): - block_device_list = [block_device.strip_dev(vol['mount_device']) - for vol in - driver.block_device_info_get_mapping( - block_device_info)] - swap = driver.block_device_info_get_swap(block_device_info) - if driver.swap_is_usable(swap): - block_device_list.append( - block_device.strip_dev(swap['device_name'])) - block_device_list += [block_device.strip_dev(ephemeral['device_name']) - for ephemeral in - driver.block_device_info_get_ephemerals( - block_device_info)] - - LOG.debug(_("block_device_list %s"), block_device_list) - return block_device.strip_dev(mount_device) in block_device_list - def get_host_capabilities(self): """Returns an instance of config.LibvirtConfigCaps representing the capabilities of the host""" - xmlstr = self._conn.getCapabilities() - - caps = vconfig.LibvirtConfigCaps() - caps.parse_str(xmlstr) - return caps + if not self._caps: + xmlstr = self._conn.getCapabilities() + self._caps = vconfig.LibvirtConfigCaps() + self._caps.parse_str(xmlstr) + return self._caps def get_host_uuid(self): """Returns a UUID representing the host.""" @@ -1552,8 +1691,8 @@ class LibvirtDriver(driver.ComputeDriver): self.default_second_device) devices.append(diskos) else: - ebs_root = self._volume_in_mapping(self.default_root_device, - block_device_info) + ebs_root = block_device.volume_in_mapping( + self.default_root_device, block_device_info) if not ebs_root: if root_device_type == "cdrom": @@ -1567,8 +1706,8 @@ class LibvirtDriver(driver.ComputeDriver): devices.append(diskos) ephemeral_device = None - if not (self._volume_in_mapping(self.default_second_device, - block_device_info) or + if not (block_device.volume_in_mapping( + self.default_second_device, block_device_info) or 0 in [eph['num'] for eph in driver.block_device_info_get_ephemerals( block_device_info)]): @@ -1602,8 +1741,8 @@ class LibvirtDriver(driver.ComputeDriver): swap['device_name'])) devices.append(diskswap) elif (inst_type['swap'] > 0 and - not self._volume_in_mapping(swap_device, - block_device_info)): + not block_device.volume_in_mapping( + swap_device, block_device_info)): diskswap = disk_info('disk.swap', swap_device) devices.append(diskswap) self.virtapi.instance_update( @@ -1624,7 +1763,7 @@ class LibvirtDriver(driver.ComputeDriver): diskconfig.driver_format = "raw" diskconfig.driver_cache = self.disk_cachemode diskconfig.source_path = os.path.join( - libvirt_utils.get_instance_path(instane), "disk.config") + libvirt_utils.get_instance_path(instance), "disk.config") diskconfig.target_dev = self.default_last_device diskconfig.target_bus = default_disk_bus devices.append(diskconfig) @@ -1833,11 +1972,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 @@ -1973,7 +2119,7 @@ class LibvirtDriver(driver.ComputeDriver): def get_interfaces(self, xml): """ - Note that this function takes an domain xml. + Note that this function takes a domain xml. Returns a list of all network interfaces for this instance. """ @@ -2739,28 +2885,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. @@ -2786,7 +2928,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..5b712cf42 100644 --- a/nova/virt/libvirt/firewall.py +++ b/nova/virt/libvirt/firewall.py @@ -29,11 +29,7 @@ LOG = logging.getLogger(__name__) CONF = cfg.CONF CONF.import_opt('use_ipv6', 'nova.netconf') -try: - import libvirt -except ImportError: - LOG.warn(_("Libvirt module could not be loaded. NWFilterFirewall will " - "not work correctly.")) +libvirt = None class NWFilterFirewall(base_firewall.FirewallDriver): @@ -47,6 +43,13 @@ class NWFilterFirewall(base_firewall.FirewallDriver): def __init__(self, virtapi, get_connection, **kwargs): super(NWFilterFirewall, self).__init__(virtapi) + global libvirt + if libvirt is None: + try: + libvirt = __import__('libvirt') + except ImportError: + LOG.warn(_("Libvirt module could not be loaded. " + "NWFilterFirewall will not work correctly.")) self._libvirt_get_connection = get_connection self.static_filters_configured = False self.handle_security_groups = False @@ -228,11 +231,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..ea7bded95 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. @@ -305,7 +305,7 @@ class ImageCacheManager(object): backing_path = os.path.join(CONF.instances_path, CONF.base_dir_name, backing_file) - if not backing_path in inuse_images: + if backing_path not in inuse_images: inuse_images.append(backing_path) if backing_path in self.unexplained_images: @@ -464,7 +464,7 @@ class ImageCacheManager(object): # _verify_checksum returns True if the checksum is ok, and None if # there is no checksum file checksum_result = self._verify_checksum(img_id, base_file) - if not checksum_result is None: + if checksum_result is not None: image_bad = not checksum_result # Give other threads a chance to run @@ -555,7 +555,7 @@ class ImageCacheManager(object): # Elements remaining in unexplained_images might be in use inuse_backing_images = self._list_backing_images() for backing_path in inuse_backing_images: - if not backing_path in self.active_base_files: + if backing_path not in self.active_base_files: self.active_base_files.append(backing_path) # Anything left is an unknown base image diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py index 4b3517da7..1830cac33 100644..100755 --- a/nova/virt/libvirt/utils.py +++ b/nova/virt/libvirt/utils.py @@ -29,7 +29,15 @@ from nova.openstack.common import log as logging from nova import utils from nova.virt import images +libvirt_opts = [ + cfg.BoolOpt('libvirt_snapshot_compression', + default=False, + help='Compress snapshot images when possible. This ' + 'currently applies exclusively to qcow2 images'), + ] + CONF = cfg.CONF +CONF.register_opts(libvirt_opts) CONF.import_opt('instances_path', 'nova.compute.manager') LOG = logging.getLogger(__name__) @@ -63,7 +71,7 @@ def create_image(disk_format, path, size): execute('qemu-img', 'create', '-f', disk_format, path, size) -def create_cow_image(backing_file, path): +def create_cow_image(backing_file, path, size=None): """Create COW image Creates a COW image with the given backing file @@ -89,6 +97,8 @@ def create_cow_image(backing_file, path): # cow_opts += ['preallocation=%s' % base_details['preallocation']] if base_details and base_details.encryption: cow_opts += ['encryption=%s' % base_details.encryption] + if size is not None: + cow_opts += ['size=%s' % size] if cow_opts: # Format as a comma separated list csv_opts = ",".join(cow_opts) @@ -273,7 +283,7 @@ def pick_disk_driver_name(is_block_dev=False): if is_block_dev: return "phy" else: - return "file" + return "tap" elif CONF.libvirt_type in ('kvm', 'qemu'): return "qemu" else: @@ -292,14 +302,14 @@ def get_disk_size(path): return int(size) -def get_disk_backing_file(path): +def get_disk_backing_file(path, basename=True): """Get the backing file of a disk image :param path: Path to the disk image :returns: a path to the image's backing store """ backing_file = images.qemu_img_info(path).backing_file - if backing_file: + if backing_file and basename: backing_file = os.path.basename(backing_file) return backing_file @@ -403,16 +413,19 @@ def extract_snapshot(disk_path, source_fmt, snapshot_name, out_path, dest_fmt): # NOTE(markmc): ISO is just raw to qemu-img if dest_fmt == 'iso': dest_fmt = 'raw' - qemu_img_cmd = ('qemu-img', - 'convert', - '-f', - source_fmt, - '-O', - dest_fmt, - '-s', - snapshot_name, - disk_path, - out_path) + + qemu_img_cmd = ('qemu-img', 'convert', '-f', source_fmt, '-O', dest_fmt) + + # Conditionally enable compression of snapshots. + if CONF.libvirt_snapshot_compression and dest_fmt == "qcow2": + qemu_img_cmd += ('-c',) + + # When snapshot name is omitted we do a basic convert, which + # is used by live snapshots. + if snapshot_name is not None: + qemu_img_cmd += ('-s', snapshot_name) + + qemu_img_cmd += (disk_path, out_path) execute(*qemu_img_cmd) diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 54de9da2d..45c299830 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -28,10 +28,15 @@ from nova import utils from nova.virt.libvirt import config as vconfig from nova.virt.libvirt import designer -from nova.virt import netutils + LOG = logging.getLogger(__name__) libvirt_vif_opts = [ + # quantum_ovs_bridge is used, if Quantum provides Nova + # the 'vif_type' portbinding field + cfg.StrOpt('libvirt_ovs_bridge', + default='br-int', + help='Name of Integration Bridge used by Open vSwitch'), cfg.BoolOpt('libvirt_use_virtio_for_bridges', default=True, help='Use virtio for bridge interfaces with KVM/QEMU'), @@ -67,23 +72,31 @@ class LibvirtBaseVIFDriver(object): return conf + def plug(self, instance, vif): + pass -class LibvirtBridgeDriver(LibvirtBaseVIFDriver): - """VIF driver for Linux bridge.""" + def unplug(self, instance, vif): + pass - def get_config(self, instance, network, mapping): - """Get VIF configurations for bridge type.""" - mac_id = mapping['mac'].replace(':', '') +class LibvirtGenericVIFDriver(LibvirtBaseVIFDriver): + """Generic VIF driver for libvirt networking.""" + + def get_bridge_name(self, network): + return network['bridge'] - conf = super(LibvirtBridgeDriver, + def get_config_bridge(self, instance, network, mapping): + """Get VIF configurations for bridge type.""" + conf = super(LibvirtGenericVIFDriver, self).get_config(instance, network, mapping) designer.set_vif_host_backend_bridge_config( - conf, network['bridge'], self.get_vif_devname(mapping)) + conf, self.get_bridge_name(network), + self.get_vif_devname(mapping)) + mac_id = mapping['mac'].replace(':', '') name = "nova-instance-" + instance['name'] + "-" + mac_id primary_addr = mapping['ips'][0]['ip'] dhcp_server = ra_server = ipv4_cidr = ipv6_cidr = None @@ -103,8 +116,29 @@ class LibvirtBridgeDriver(LibvirtBaseVIFDriver): return conf - def plug(self, instance, vif): + def get_config(self, instance, network, mapping): + vif_type = mapping.get('vif_type') + + LOG.debug(_("vif_type=%(vif_type)s instance=%(instance)s " + "network=%(network)s mapping=%(mapping)s") + % locals()) + + if vif_type is None: + raise exception.NovaException( + _("vif_type parameter must be present " + "for this vif_driver implementation")) + + if vif_type == network_model.VIF_TYPE_BRIDGE: + return self.get_config_bridge(instance, network, mapping) + else: + raise exception.NovaException( + _("Unexpected vif_type=%s") % vif_type) + + def plug_bridge(self, instance, vif): """Ensure that the bridge exists, and add VIF to it.""" + super(LibvirtGenericVIFDriver, + self).plug(instance, vif) + network, mapping = vif if (not network.get('multi_host') and mapping.get('should_create_bridge')): @@ -112,23 +146,85 @@ class LibvirtBridgeDriver(LibvirtBaseVIFDriver): iface = CONF.vlan_interface or network['bridge_interface'] LOG.debug(_('Ensuring vlan %(vlan)s and bridge %(bridge)s'), {'vlan': network['vlan'], - 'bridge': network['bridge']}, + 'bridge': self.get_bridge_name(network)}, instance=instance) linux_net.LinuxBridgeInterfaceDriver.ensure_vlan_bridge( network['vlan'], - network['bridge'], + self.get_bridge_name(network), iface) else: iface = CONF.flat_interface or network['bridge_interface'] - LOG.debug(_("Ensuring bridge %s"), network['bridge'], - instance=instance) + LOG.debug(_("Ensuring bridge %s"), + self.get_bridge_name(network), instance=instance) linux_net.LinuxBridgeInterfaceDriver.ensure_bridge( - network['bridge'], + self.get_bridge_name(network), iface) - def unplug(self, instance, vif): + def plug(self, instance, vif): + network, mapping = vif + vif_type = mapping.get('vif_type') + + LOG.debug(_("vif_type=%(vif_type)s instance=%(instance)s " + "network=%(network)s mapping=%(mapping)s") + % locals()) + + if vif_type is None: + raise exception.NovaException( + _("vif_type parameter must be present " + "for this vif_driver implementation")) + + if vif_type == network_model.VIF_TYPE_BRIDGE: + self.plug_bridge(instance, vif) + else: + raise exception.NovaException( + _("Unexpected vif_type=%s") % vif_type) + + def unplug_bridge(self, instance, vif): """No manual unplugging required.""" - pass + super(LibvirtGenericVIFDriver, + self).unplug(instance, vif) + + def unplug(self, instance, vif): + network, mapping = vif + vif_type = mapping.get('vif_type') + + LOG.debug(_("vif_type=%(vif_type)s instance=%(instance)s " + "network=%(network)s mapping=%(mapping)s") + % locals()) + + if vif_type is None: + raise exception.NovaException( + _("vif_type parameter must be present " + "for this vif_driver implementation")) + + if vif_type == network_model.VIF_TYPE_BRIDGE: + self.unplug_bridge(instance, vif) + else: + raise exception.NovaException( + _("Unexpected vif_type=%s") % vif_type) + + +class LibvirtBridgeDriver(LibvirtGenericVIFDriver): + """Deprecated in favour of LibvirtGenericVIFDriver. + Retained in Grizzly for compatibility with Quantum + drivers which do not yet report 'vif_type' port binding. + To be removed in Hxxxx.""" + + def __init__(self): + LOG.deprecated( + _("LibvirtBridgeDriver is deprecated and " + "will be removed in the Hxxxx release. Please " + "update the 'libvirt_vif_driver' config parameter " + "to use the LibvirtGenericVIFDriver class instead")) + + def get_config(self, instance, network, mapping): + return self.get_config_bridge(instance, network, mapping) + + def plug(self, instance, vif): + self.plug_bridge(instance, vif) + + def unplug(self, instance, vif): + self.unplug_bridge(instance, vif) class LibvirtOpenVswitchDriver(LibvirtBaseVIFDriver): @@ -138,6 +234,12 @@ class LibvirtOpenVswitchDriver(LibvirtBaseVIFDriver): OVS virtual port XML (0.9.10 or earlier). """ + def get_bridge_name(self, network): + return network.get('bridge') or CONF.libvirt_ovs_bridge + + def get_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) @@ -150,55 +252,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(network['bridge'], - 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(network['bridge'], - 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 @@ -214,6 +287,12 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver, return (("qvb%s" % iface_id)[:network_model.NIC_NAME_LEN], ("qvo%s" % iface_id)[:network_model.NIC_NAME_LEN]) + def get_bridge_name(self, network): + return network.get('bridge') or CONF.libvirt_ovs_bridge + + def get_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 @@ -232,9 +311,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) @@ -243,9 +322,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(network['bridge'], - 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 @@ -255,16 +334,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(network['bridge'], 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) @@ -273,6 +352,12 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver): """VIF driver for Open vSwitch that uses integrated libvirt OVS virtual port XML (introduced in libvirt 0.9.11).""" + def get_bridge_name(self, network): + return network.get('bridge') or CONF.libvirt_ovs_bridge + + def get_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, @@ -281,7 +366,8 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver): mapping) designer.set_vif_host_backend_ovs_config( - conf, network['bridge'], mapping['vif_uuid'], + conf, self.get_bridge_name(network), + self.get_ovs_interfaceid(mapping), self.get_vif_devname(mapping)) return conf @@ -297,10 +383,15 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver): class QuantumLinuxBridgeVIFDriver(LibvirtBaseVIFDriver): """VIF driver for Linux Bridge when running Quantum.""" + def get_bridge_name(self, network): + def_bridge = ("brq" + network['id'])[:network_model.NIC_NAME_LEN] + return network.get('bridge') or def_bridge + def get_config(self, instance, network, mapping): - linux_net.LinuxBridgeInterfaceDriver.ensure_bridge(network['bridge'], - None, - filtering=False) + linux_net.LinuxBridgeInterfaceDriver.ensure_bridge( + self.get_bridge_name(network), + None, + filtering=False) conf = super(QuantumLinuxBridgeVIFDriver, self).get_config(instance, @@ -308,7 +399,8 @@ class QuantumLinuxBridgeVIFDriver(LibvirtBaseVIFDriver): mapping) designer.set_vif_host_backend_bridge_config( - conf, network['bridge'], self.get_vif_devname(mapping)) + conf, self.get_bridge_name(network), + self.get_vif_devname(mapping)) return conf diff --git a/nova/virt/libvirt/volume.py b/nova/virt/libvirt/volume.py index f9a948fb5..d02db22f3 100644 --- a/nova/virt/libvirt/volume.py +++ b/nova/virt/libvirt/volume.py @@ -47,19 +47,19 @@ CONF = cfg.CONF CONF.register_opts(volume_opts) -class LibvirtVolumeDriver(object): +class LibvirtBaseVolumeDriver(object): """Base class for volume drivers.""" - def __init__(self, connection): + def __init__(self, connection, is_block_dev): self.connection = connection + self.is_block_dev = is_block_dev def connect_volume(self, connection_info, mount_device): """Connect the volume. Returns xml for libvirt.""" + conf = vconfig.LibvirtConfigGuestDisk() - conf.source_type = "block" - conf.driver_name = virtutils.pick_disk_driver_name(is_block_dev=True) + conf.driver_name = virtutils.pick_disk_driver_name(self.is_block_dev) conf.driver_format = "raw" conf.driver_cache = "none" - conf.source_path = connection_info['data']['device_path'] conf.target_dev = mount_device conf.target_bus = "virtio" conf.serial = connection_info.get('serial') @@ -70,37 +70,49 @@ class LibvirtVolumeDriver(object): pass -class LibvirtFakeVolumeDriver(LibvirtVolumeDriver): - """Driver to attach Network volumes to libvirt.""" +class LibvirtVolumeDriver(LibvirtBaseVolumeDriver): + """Class for volumes backed by local file.""" + def __init__(self, connection): + super(LibvirtVolumeDriver, + self).__init__(connection, is_block_dev=True) def connect_volume(self, connection_info, mount_device): - conf = vconfig.LibvirtConfigGuestDisk() + """Connect the volume to a local device.""" + conf = super(LibvirtVolumeDriver, + self).connect_volume(connection_info, mount_device) + conf.source_type = "block" + conf.source_path = connection_info['data']['device_path'] + return conf + + +class LibvirtFakeVolumeDriver(LibvirtBaseVolumeDriver): + """Driver to attach fake volumes to libvirt.""" + def __init__(self, connection): + super(LibvirtFakeVolumeDriver, + self).__init__(connection, is_block_dev=True) + + def connect_volume(self, connection_info, mount_device): + """Connect the volume to a fake device.""" + conf = super(LibvirtFakeVolumeDriver, + self).connect_volume(connection_info, mount_device) conf.source_type = "network" - conf.driver_name = "qemu" - conf.driver_format = "raw" - conf.driver_cache = "none" conf.source_protocol = "fake" conf.source_host = "fake" - conf.target_dev = mount_device - conf.target_bus = "virtio" - conf.serial = connection_info.get('serial') return conf -class LibvirtNetVolumeDriver(LibvirtVolumeDriver): +class LibvirtNetVolumeDriver(LibvirtBaseVolumeDriver): """Driver to attach Network volumes to libvirt.""" + def __init__(self, connection): + super(LibvirtNetVolumeDriver, + self).__init__(connection, is_block_dev=False) def connect_volume(self, connection_info, mount_device): - conf = vconfig.LibvirtConfigGuestDisk() + conf = super(LibvirtNetVolumeDriver, + self).connect_volume(connection_info, mount_device) conf.source_type = "network" - conf.driver_name = virtutils.pick_disk_driver_name(is_block_dev=False) - conf.driver_format = "raw" - conf.driver_cache = "none" conf.source_protocol = connection_info['driver_volume_type'] conf.source_host = connection_info['data']['name'] - conf.target_dev = mount_device - conf.target_bus = "virtio" - conf.serial = connection_info.get('serial') netdisk_properties = connection_info['data'] auth_enabled = netdisk_properties.get('auth_enabled') if (conf.source_protocol == 'rbd' and @@ -118,8 +130,11 @@ class LibvirtNetVolumeDriver(LibvirtVolumeDriver): return conf -class LibvirtISCSIVolumeDriver(LibvirtVolumeDriver): +class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver): """Driver to attach Network volumes to libvirt.""" + def __init__(self, connection): + super(LibvirtISCSIVolumeDriver, + self).__init__(connection, is_block_dev=False) def _run_iscsiadm(self, iscsi_properties, iscsi_command, **kwargs): check_exit_code = kwargs.pop('check_exit_code', 0) @@ -141,6 +156,9 @@ class LibvirtISCSIVolumeDriver(LibvirtVolumeDriver): @lockutils.synchronized('connect_volume', 'nova-') def connect_volume(self, connection_info, mount_device): """Attach the volume to instance_name.""" + conf = super(LibvirtISCSIVolumeDriver, + self).connect_volume(connection_info, mount_device) + iscsi_properties = connection_info['data'] # NOTE(vish): If we are on the same host as nova volume, the # discovery makes the target so we don't need to @@ -204,15 +222,15 @@ class LibvirtISCSIVolumeDriver(LibvirtVolumeDriver): "(after %(tries)s rescans)") % locals()) - connection_info['data']['device_path'] = host_device - sup = super(LibvirtISCSIVolumeDriver, self) - return sup.connect_volume(connection_info, mount_device) + conf.source_type = "block" + conf.source_path = host_device + return conf @lockutils.synchronized('connect_volume', 'nova-') def disconnect_volume(self, connection_info, mount_device): """Detach the volume from instance_name.""" - sup = super(LibvirtISCSIVolumeDriver, self) - sup.disconnect_volume(connection_info, mount_device) + super(LibvirtISCSIVolumeDriver, + self).disconnect_volume(connection_info, mount_device) iscsi_properties = connection_info['data'] # NOTE(vish): Only disconnect from the target if no luns from the # target are in use. diff --git a/nova/virt/libvirt/volume_nfs.py b/nova/virt/libvirt/volume_nfs.py index b5083937d..70bb8c38f 100644 --- a/nova/virt/libvirt/volume_nfs.py +++ b/nova/virt/libvirt/volume_nfs.py @@ -38,27 +38,24 @@ CONF = cfg.CONF CONF.register_opts(volume_opts) -class NfsVolumeDriver(volume.LibvirtVolumeDriver): +class NfsVolumeDriver(volume.LibvirtBaseVolumeDriver): """Class implements libvirt part of volume driver for NFS.""" - def __init__(self, *args, **kwargs): - """Create back-end to nfs and check connection.""" - super(NfsVolumeDriver, self).__init__(*args, **kwargs) + def __init__(self, connection): + """Create back-end to nfs.""" + super(NfsVolumeDriver, + self).__init__(connection, is_block_dev=False) def connect_volume(self, connection_info, mount_device): """Connect the volume. Returns xml for libvirt.""" + conf = super(NfsVolumeDriver, + self).connect_volume(connection_info, mount_device) path = self._ensure_mounted(connection_info['data']['export']) path = os.path.join(path, connection_info['data']['name']) - connection_info['data']['device_path'] = path - conf = super(NfsVolumeDriver, self).connect_volume(connection_info, - mount_device) conf.source_type = 'file' + conf.source_path = path return conf - def disconnect_volume(self, connection_info, mount_device): - """Disconnect the volume.""" - pass - def _ensure_mounted(self, nfs_export): """ @type nfs_export: string 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/blockdev.py b/nova/virt/powervm/blockdev.py index fb3a0210c..76caca1b9 100644 --- a/nova/virt/powervm/blockdev.py +++ b/nova/virt/powervm/blockdev.py @@ -18,16 +18,11 @@ import hashlib import os import re -from eventlet import greenthread - -from nova import utils - from nova.image import glance - from nova.openstack.common import cfg from nova.openstack.common import excutils from nova.openstack.common import log as logging - +from nova import utils from nova.virt import images from nova.virt.powervm import command from nova.virt.powervm import common diff --git a/nova/virt/powervm/driver.py b/nova/virt/powervm/driver.py index ccba3cf73..0ce313535 100644 --- a/nova/virt/powervm/driver.py +++ b/nova/virt/powervm/driver.py @@ -14,19 +14,11 @@ # License for the specific language governing permissions and limitations # under the License. -import os import time -from nova.compute import task_states -from nova.compute import vm_states - -from nova import context as nova_context - from nova.image import glance - from nova.openstack.common import cfg from nova.openstack.common import log as logging - from nova.virt import driver from nova.virt.powervm import operator 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..67822f2c9 100644 --- a/nova/virt/vmwareapi/driver.py +++ b/nova/virt/vmwareapi/driver.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. # @@ -20,16 +21,19 @@ A connection to the VMware ESX platform. **Related Flags** -:vmwareapi_host_ip: IPAddress of VMware ESX server. -:vmwareapi_host_username: Username for connection to VMware ESX Server. -:vmwareapi_host_password: Password for connection to VMware ESX Server. -:vmwareapi_task_poll_interval: The interval (seconds) used for polling of - remote tasks - (default: 1.0). -:vmwareapi_api_retry_count: The API retry count in case of failure such as - network failures (socket errors etc.) - (default: 10). - +:vmwareapi_host_ip: IP address of VMware ESX server. +:vmwareapi_host_username: Username for connection to VMware ESX Server. +:vmwareapi_host_password: Password for connection to VMware ESX Server. +:vmwareapi_task_poll_interval: The interval (seconds) used for polling of + remote tasks + (default: 5.0). +:vmwareapi_api_retry_count: The API retry count in case of failure such as + network failures (socket errors etc.) + (default: 10). +:vnc_port: VNC starting port (default: 5900) +:vnc_port_total: Total number of VNC ports (default: 10000) +:vnc_password: VNC password +:use_linked_clone: Whether to use linked clone (default: True) """ import time @@ -38,13 +42,16 @@ from eventlet import event from nova import exception from nova.openstack.common import cfg +from nova.openstack.common import jsonutils from nova.openstack.common import log as logging from nova import utils from nova.virt import driver from nova.virt.vmwareapi import error_util +from nova.virt.vmwareapi import host 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 +59,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, @@ -75,6 +82,18 @@ vmwareapi_opts = [ 'socket error, etc. ' 'Used only if compute_driver is ' 'vmwareapi.VMwareESXDriver.'), + cfg.IntOpt('vnc_port', + default=5900, + help='VNC starting port'), + cfg.IntOpt('vnc_port_total', + default=10000, + help='Total number of VNC ports'), + cfg.StrOpt('vnc_password', + default=None, + help='VNC password'), + cfg.BoolOpt('use_linked_clone', + default=True, + help='Whether to use linked clone'), ] CONF = cfg.CONF @@ -99,19 +118,31 @@ class VMwareESXDriver(driver.ComputeDriver): def __init__(self, virtapi, read_only=False, scheme="https"): super(VMwareESXDriver, self).__init__(virtapi) - host_ip = CONF.vmwareapi_host_ip + self._host_ip = CONF.vmwareapi_host_ip host_username = CONF.vmwareapi_host_username host_password = CONF.vmwareapi_host_password api_retry_count = CONF.vmwareapi_api_retry_count - if not host_ip or host_username is None or host_password is None: + if not self._host_ip or host_username is None or host_password is None: raise Exception(_("Must specify vmwareapi_host_ip," "vmwareapi_host_username " "and vmwareapi_host_password to use" "compute_driver=vmwareapi.VMwareESXDriver")) - session = VMwareAPISession(host_ip, host_username, host_password, - api_retry_count, scheme=scheme) - self._vmops = vmops.VMwareVMOps(session) + self._session = VMwareAPISession(self._host_ip, + host_username, host_password, + api_retry_count, scheme=scheme) + self._volumeops = volumeops.VMwareVolumeOps(self._session) + self._vmops = vmops.VMwareVMOps(self._session, self.virtapi, + self._volumeops) + self._host = host.Host(self._session) + self._host_state = None + + @property + def host_state(self): + if not self._host_state: + self._host_state = host.HostState(self._session, + self._host_ip) + return self._host_state def init_host(self, host): """Do the initialization that needs to be done.""" @@ -128,7 +159,8 @@ class VMwareESXDriver(driver.ComputeDriver): def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info=None, block_device_info=None): """Create VM instance.""" - self._vmops.spawn(context, instance, image_meta, network_info) + self._vmops.spawn(context, instance, image_meta, network_info, + block_device_info) def snapshot(self, context, instance, name, update_task_state): """Create snapshot from a running VM instance.""" @@ -160,6 +192,61 @@ class VMwareESXDriver(driver.ComputeDriver): """Resume the suspended VM instance.""" self._vmops.resume(instance) + def rescue(self, context, instance, network_info, image_meta, + rescue_password): + """Rescue the specified instance.""" + self._vmops.rescue(context, instance, network_info, image_meta) + + def unrescue(self, instance, network_info): + """Unrescue the specified instance.""" + self._vmops.unrescue(instance) + + def power_off(self, instance): + """Power off the specified instance.""" + self._vmops.power_off(instance) + + def power_on(self, instance): + """Power on the specified instance.""" + self._vmops.power_on(instance) + + def migrate_disk_and_power_off(self, context, instance, dest, + instance_type, network_info, + block_device_info=None): + """ + Transfers the disk of a running instance in multiple phases, turning + off the instance before the end. + """ + return self._vmops.migrate_disk_and_power_off(context, instance, + dest, instance_type) + + def confirm_migration(self, migration, instance, network_info): + """Confirms a resize, destroying the source VM.""" + self._vmops.confirm_migration(migration, instance, network_info) + + def finish_revert_migration(self, instance, network_info, + block_device_info=None): + """Finish reverting a resize, powering back on the instance.""" + self._vmops.finish_revert_migration(instance) + + def finish_migration(self, context, migration, instance, disk_info, + network_info, image_meta, resize_instance=False, + block_device_info=None): + """Completes a resize, turning on the migrated instance.""" + self._vmops.finish_migration(context, migration, instance, disk_info, + network_info, image_meta, resize_instance) + + def live_migration(self, context, instance_ref, dest, + post_method, recover_method, block_migration=False, + migrate_data=None): + """Live migration of an instance to another host.""" + self._vmops.live_migration(context, instance_ref, dest, + post_method, recover_method, + block_migration) + + def poll_rebooting_instances(self, timeout, instances): + """Poll for rebooting instances.""" + self._vmops.poll_rebooting_instances(timeout, instances) + def get_info(self, instance): """Return info about the VM instance.""" return self._vmops.get_info(instance) @@ -172,23 +259,29 @@ class VMwareESXDriver(driver.ComputeDriver): """Return snapshot of console.""" return self._vmops.get_console_output(instance) - def get_volume_connector(self, _instance): + def get_vnc_console(self, instance): + """Return link to instance's VNC console.""" + return self._vmops.get_vnc_console(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 get_host_ip_addr(self): + """Retrieves the IP address of the ESX host.""" + return self._host_ip 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.""" @@ -197,8 +290,57 @@ class VMwareESXDriver(driver.ComputeDriver): 'password': CONF.vmwareapi_host_password} def get_available_resource(self, nodename): - """This method is supported only by libvirt.""" - return + """Retrieve resource info. + + This method is called when nova-compute launches, and + as part of a periodic task. + + :returns: dictionary describing resources + + """ + host_stats = self.get_host_stats(refresh=True) + + # Updating host information + dic = {'vcpus': host_stats["vcpus"], + 'memory_mb': host_stats['host_memory_total'], + 'local_gb': host_stats['disk_total'], + 'vcpus_used': 0, + 'memory_mb_used': host_stats['host_memory_total'] - + host_stats['host_memory_free'], + 'local_gb_used': host_stats['disk_used'], + 'hypervisor_type': host_stats['hypervisor_type'], + 'hypervisor_version': host_stats['hypervisor_version'], + 'hypervisor_hostname': host_stats['hypervisor_hostname'], + 'cpu_info': jsonutils.dumps(host_stats['cpu_info'])} + + return dic + + def update_host_status(self): + """Update the status info of the host, and return those values + to the calling program.""" + return self.host_state.update_status() + + def get_host_stats(self, refresh=False): + """Return the current state of the host. If 'refresh' is + True, run the update first.""" + return self.host_state.get_host_stats(refresh=refresh) + + def host_power_action(self, host, action): + """Reboots, shuts down or powers up the host.""" + return self._host.host_power_action(host, action) + + def host_maintenance_mode(self, host, mode): + """Start/Stop host maintenance window. On start, it triggers + guest VMs evacuation.""" + return self._host.host_maintenance_mode(host, mode) + + def set_host_enabled(self, host, enabled): + """Sets the specified host's ability to accept new instances.""" + return self._host.set_host_enabled(host, enabled) + + def inject_network_info(self, instance, network_info): + """inject network info for specified instance.""" + self._vmops.inject_network_info(instance, network_info) def plug_vifs(self, instance, network_info): """Plug VIFs into networks.""" @@ -208,6 +350,16 @@ class VMwareESXDriver(driver.ComputeDriver): """Unplug VIFs from networks.""" self._vmops.unplug_vifs(instance, network_info) + def list_interfaces(self, instance_name): + """ + Return the IDs of all the virtual network interfaces attached to the + specified instance, as a list. These IDs are opaque to the caller + (they are only useful for giving back to this layer as a parameter to + interface_stats). These IDs only need to be unique for a given + instance. + """ + return self._vmops.list_interfaces(instance_name) + class VMwareAPISession(object): """ diff --git a/nova/virt/vmwareapi/fake.py b/nova/virt/vmwareapi/fake.py index 3f5041c22..692e5f253 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): @@ -157,6 +172,14 @@ class VirtualLsiLogicController(DataObject): pass +class VirtualPCNet32(DataObject): + """VirtualPCNet32 class.""" + + def __init__(self): + super(VirtualPCNet32, self).__init__() + self.key = 4000 + + class VirtualMachine(ManagedObject): """Virtual Machine class.""" @@ -177,7 +200,7 @@ class VirtualMachine(ManagedObject): self.set("config.files.vmPathName", kwargs.get("vmPathName")) self.set("summary.config.numCpu", kwargs.get("numCpu", 1)) self.set("summary.config.memorySizeMB", kwargs.get("mem", 1)) - self.set("config.hardware.device", kwargs.get("virtual_disk", None)) + self.set("config.hardware.device", kwargs.get("virtual_device", None)) self.set("config.extraConfig", kwargs.get("extra_config", None)) def reconfig(self, factory, val): @@ -201,7 +224,9 @@ class VirtualMachine(ManagedObject): controller = VirtualLsiLogicController() controller.key = controller_key - self.set("config.hardware.device", [disk, controller]) + nic = VirtualPCNet32() + + self.set("config.hardware.device", [disk, controller, nic]) except AttributeError: # Case of Reconfig of VM to set extra params self.set("config.extraConfig", val.extraConfig) @@ -230,6 +255,8 @@ class Datastore(ManagedObject): super(Datastore, self).__init__("Datastore") self.set("summary.type", "VMFS") self.set("summary.name", "fake-ds") + self.set("summary.capacity", 1024 * 1024 * 1024) + self.set("summary.freeSpace", 500 * 1024 * 1024) class HostNetworkSystem(ManagedObject): @@ -260,6 +287,29 @@ class HostSystem(ManagedObject): host_net_sys = _db_content["HostNetworkSystem"][host_net_key].obj self.set("configManager.networkSystem", host_net_sys) + summary = DataObject() + hardware = DataObject() + hardware.numCpuCores = 8 + hardware.numCpuPkgs = 2 + hardware.numCpuThreads = 16 + hardware.vendor = "Intel" + hardware.cpuModel = "Intel(R) Xeon(R)" + hardware.memorySize = 1024 * 1024 * 1024 + summary.hardware = hardware + + quickstats = DataObject() + quickstats.overallMemoryUsage = 500 + summary.quickStats = quickstats + + product = DataObject() + product.name = "VMware ESXi" + product.version = "5.0.0" + config = DataObject() + config.product = product + summary.config = config + + self.set("summary", summary) + if _db_content.get("Network", None) is None: create_network() net_ref = _db_content["Network"][_db_content["Network"].keys()[0]].obj @@ -574,6 +624,11 @@ class FakeVim(object): """Fakes a return.""" return + def _just_return_task(self, method): + """Fakes a task return.""" + task_mdo = create_task(method, "success") + return task_mdo.obj + def _unregister_vm(self, method, *args, **kwargs): """Unregisters a VM from the Host System.""" vm_ref = args[0] @@ -602,7 +657,7 @@ class FakeVim(object): def _set_power_state(self, method, vm_ref, pwr_state="poweredOn"): """Sets power state for the VM.""" if _db_content.get("VirtualMachine", None) is None: - raise exception.NotFound(_(" No Virtual Machine has been " + raise exception.NotFound(_("No Virtual Machine has been " "registered yet")) if vm_ref not in _db_content.get("VirtualMachine"): raise exception.NotFound(_("Virtual Machine with ref %s is not " @@ -697,6 +752,9 @@ class FakeVim(object): elif attr_name == "DeleteVirtualDisk_Task": return lambda *args, **kwargs: self._delete_disk(attr_name, *args, **kwargs) + elif attr_name == "Destroy_Task": + return lambda *args, **kwargs: self._unregister_vm(attr_name, + *args, **kwargs) elif attr_name == "UnregisterVM": return lambda *args, **kwargs: self._unregister_vm(attr_name, *args, **kwargs) @@ -714,3 +772,15 @@ class FakeVim(object): elif attr_name == "AddPortGroup": return lambda *args, **kwargs: self._add_port_group(attr_name, *args, **kwargs) + elif attr_name == "RebootHost_Task": + return lambda *args, **kwargs: self._just_return_task(attr_name) + elif attr_name == "ShutdownHost_Task": + return lambda *args, **kwargs: self._just_return_task(attr_name) + elif attr_name == "PowerDownHostToStandBy_Task": + return lambda *args, **kwargs: self._just_return_task(attr_name) + elif attr_name == "PowerUpHostFromStandBy_Task": + return lambda *args, **kwargs: self._just_return_task(attr_name) + elif attr_name == "EnterMaintenanceMode_Task": + return lambda *args, **kwargs: self._just_return_task(attr_name) + elif attr_name == "ExitMaintenanceMode_Task": + return lambda *args, **kwargs: self._just_return_task(attr_name) diff --git a/nova/virt/vmwareapi/host.py b/nova/virt/vmwareapi/host.py new file mode 100644 index 000000000..09b8f1fe3 --- /dev/null +++ b/nova/virt/vmwareapi/host.py @@ -0,0 +1,140 @@ +# 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 host-related functions (start, reboot, etc). +""" + +from nova import exception +from nova.openstack.common import log as logging +from nova.virt.vmwareapi import vim_util +from nova.virt.vmwareapi import vm_util + +LOG = logging.getLogger(__name__) + + +class Host(object): + """ + Implements host related operations. + """ + def __init__(self, session): + self._session = session + + def host_power_action(self, host, action): + """Reboots or shuts down the host.""" + host_mor = self._session._call_method(vim_util, "get_objects", + "HostSystem")[0].obj + LOG.debug(_("%(action)s %(host)s") % locals()) + if action == "reboot": + host_task = self._session._call_method( + self._session._get_vim(), + "RebootHost_Task", host_mor, + force=False) + elif action == "shutdown": + host_task = self._session._call_method( + self._session._get_vim(), + "ShutdownHost_Task", host_mor, + force=False) + elif action == "startup": + host_task = self._session._call_method( + self._session._get_vim(), + "PowerUpHostFromStandBy_Task", host_mor, + timeoutSec=60) + self._session._wait_for_task(host, host_task) + + def host_maintenance_mode(self, host, mode): + """Start/Stop host maintenance window. On start, it triggers + guest VMs evacuation.""" + host_mor = self._session._call_method(vim_util, "get_objects", + "HostSystem")[0].obj + LOG.debug(_("Set maintenance mod on %(host)s to %(mode)s") % locals()) + if mode: + host_task = self._session._call_method( + self._session._get_vim(), + "EnterMaintenanceMode_Task", + host_mor, timeout=0, + evacuatePoweredOffVms=True) + else: + host_task = self._session._call_method( + self._session._get_vim(), + "ExitMaintenanceMode_Task", + host_mor, timeout=0) + self._session._wait_for_task(host, host_task) + + def set_host_enabled(self, _host, enabled): + """Sets the specified host's ability to accept new instances.""" + pass + + +class HostState(object): + """Manages information about the ESX host this compute + node is running on. + """ + def __init__(self, session, host_name): + super(HostState, self).__init__() + self._session = session + self._host_name = host_name + self._stats = {} + self.update_status() + + def get_host_stats(self, refresh=False): + """Return the current state of the host. If 'refresh' is + True, run the update first. + """ + if refresh: + self.update_status() + return self._stats + + def update_status(self): + """Update the current state of the host. + """ + host_mor = self._session._call_method(vim_util, "get_objects", + "HostSystem")[0].obj + summary = self._session._call_method(vim_util, + "get_dynamic_property", + host_mor, + "HostSystem", + "summary") + + if summary is None: + return + + try: + ds = vm_util.get_datastore_ref_and_name(self._session) + except exception.DatastoreNotFound: + ds = (None, None, 0, 0) + + data = {} + data["vcpus"] = summary.hardware.numCpuThreads + data["cpu_info"] = \ + {"vendor": summary.hardware.vendor, + "model": summary.hardware.cpuModel, + "topology": {"cores": summary.hardware.numCpuCores, + "sockets": summary.hardware.numCpuPkgs, + "threads": summary.hardware.numCpuThreads} + } + data["disk_total"] = ds[2] / (1024 * 1024) + data["disk_available"] = ds[3] / (1024 * 1024) + data["disk_used"] = data["disk_total"] - data["disk_available"] + data["host_memory_total"] = summary.hardware.memorySize / (1024 * 1024) + data["host_memory_free"] = data["host_memory_total"] - \ + summary.quickStats.overallMemoryUsage + data["hypervisor_type"] = summary.config.product.name + data["hypervisor_version"] = summary.config.product.version + data["hypervisor_hostname"] = self._host_name + + self._stats = data + return data 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..f63d7f723 100644 --- a/nova/virt/vmwareapi/network_util.py +++ b/nova/virt/vmwareapi/network_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. # @@ -38,7 +39,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 @@ -58,7 +59,11 @@ def get_network_with_the_name(session, network_name="vmnet0"): if props.name == network_name: network_obj['type'] = 'DistributedVirtualPortgroup' network_obj['dvpg'] = props.key - network_obj['dvsw'] = props.distributedVirtualSwitch.value + dvs_props = session._call_method(vim_util, + "get_dynamic_property", + props.distributedVirtualSwitch, + "VmwareDistributedVirtualSwitch", "uuid") + network_obj['dvsw'] = dvs_props else: props = session._call_method(vim_util, "get_dynamic_property", network, @@ -102,11 +107,11 @@ def get_vswitch_for_vlan_interface(session, vlan_interface): def check_if_vlan_interface_exists(session, vlan_interface): """Checks if the vlan_inteface exists on the esx host.""" - host_net_system_mor = session._call_method(vim_util, "get_objects", - "HostSystem", ["configManager.networkSystem"])[0].propSet[0].val + host_mor = session._call_method(vim_util, "get_objects", + "HostSystem")[0].obj physical_nics_ret = session._call_method(vim_util, - "get_dynamic_property", host_net_system_mor, - "HostNetworkSystem", "networkInfo.pnic") + "get_dynamic_property", host_mor, + "HostSystem", "config.network.pnic") # Meaning there are no physical nics on the host if not physical_nics_ret: return False diff --git a/nova/virt/vmwareapi/read_write_util.py b/nova/virt/vmwareapi/read_write_util.py index 39ea8e2e8..5dcdc6fdb 100644 --- a/nova/virt/vmwareapi/read_write_util.py +++ b/nova/virt/vmwareapi/read_write_util.py @@ -140,7 +140,7 @@ class VMwareHTTPWriteFile(VMwareHTTPFile): self.conn.getresponse() except Exception, excep: LOG.debug(_("Exception during HTTP connection close in " - "VMwareHTTpWrite. Exception is %s") % excep) + "VMwareHTTPWrite. Exception is %s") % excep) super(VMwareHTTPWriteFile, self).close() diff --git a/nova/virt/vmwareapi/vif.py b/nova/virt/vmwareapi/vif.py index c5b524186..5684e6aa6 100644 --- a/nova/virt/vmwareapi/vif.py +++ b/nova/virt/vmwareapi/vif.py @@ -45,7 +45,7 @@ def ensure_vlan_bridge(self, session, network): # Check if the vlan_interface physical network adapter exists on the # host. if not network_util.check_if_vlan_interface_exists(session, - vlan_interface): + vlan_interface): raise exception.NetworkAdapterNotFound(adapter=vlan_interface) # Get the vSwitch associated with the Physical Adapter diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py index e03b88804..af481b566 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,10 @@ The VMware API VM utility module to build SOAP object specs. """ +import copy +from nova import exception +from nova.virt.vmwareapi import vim_util + def build_datastore_path(datastore_name, path): """Build the datastore compliant path.""" @@ -42,7 +47,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 +62,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 +76,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 +86,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 +152,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 +169,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 +189,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 +239,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 +299,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 +340,48 @@ 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 clone_vm_spec(client_factory, location, + power_on=False, snapshot=None, template=False): + """Builds the VM clone spec.""" + clone_spec = client_factory.create('ns0:VirtualMachineCloneSpec') + clone_spec.location = location + clone_spec.powerOn = power_on + clone_spec.snapshot = snapshot + clone_spec.template = template + return clone_spec + + +def relocate_vm_spec(client_factory, datastore=None, host=None, + disk_move_type="moveAllDiskBackingsAndAllowSharing"): + """Builds the VM relocation spec.""" + rel_spec = client_factory.create('ns0:VirtualMachineRelocateSpec') + rel_spec.datastore = datastore + rel_spec.diskMoveType = disk_move_type + rel_spec.host = host + return rel_spec + + 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 +443,66 @@ def get_add_vswitch_port_group_spec(client_factory, vswitch_name, vswitch_port_group_spec.policy = policy return vswitch_port_group_spec + + +def get_vnc_config_spec(client_factory, port, password): + """Builds the vnc config spec.""" + virtual_machine_config_spec = client_factory.create( + 'ns0:VirtualMachineConfigSpec') + + opt_enabled = client_factory.create('ns0:OptionValue') + opt_enabled.key = "RemoteDisplay.vnc.enabled" + opt_enabled.value = "true" + opt_port = client_factory.create('ns0:OptionValue') + opt_port.key = "RemoteDisplay.vnc.port" + opt_port.value = port + opt_pass = client_factory.create('ns0:OptionValue') + opt_pass.key = "RemoteDisplay.vnc.password" + opt_pass.value = password + virtual_machine_config_spec.extraConfig = [opt_enabled, opt_port, opt_pass] + return virtual_machine_config_spec + + +def search_datastore_spec(client_factory, file_name): + """Builds the datastore search spec.""" + search_spec = client_factory.create('ns0:HostDatastoreBrowserSearchSpec') + search_spec.matchPattern = [file_name] + return search_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 + + +def get_datastore_ref_and_name(session): + """Get the datastore list and choose the first local storage.""" + data_stores = session._call_method(vim_util, "get_objects", + "Datastore", ["summary.type", "summary.name", + "summary.capacity", "summary.freeSpace"]) + for elem in data_stores: + ds_name = None + ds_type = None + ds_cap = None + ds_free = None + for prop in elem.propSet: + if prop.name == "summary.type": + ds_type = prop.val + elif prop.name == "summary.name": + ds_name = prop.val + elif prop.name == "summary.capacity": + ds_cap = prop.val + elif prop.name == "summary.freeSpace": + ds_free = prop.val + # Local storage identifier + if ds_type == "VMFS" or ds_type == "NFS": + data_store_name = ds_name + return elem.obj, data_store_name, ds_cap, ds_free + + if data_store_name is None: + raise exception.DatastoreNotFound() diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index 883e751a8..708a29fad 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.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. # @@ -26,12 +27,16 @@ import urllib import urllib2 import uuid +from nova import block_device +from nova.compute import api as compute from nova.compute import power_state from nova.compute import task_states +from nova import context as nova_context from nova import exception from nova.openstack.common import cfg -from nova.openstack.common import importutils +from nova.openstack.common import excutils from nova.openstack.common import log as logging +from nova.virt import driver from nova.virt.vmwareapi import network_util from nova.virt.vmwareapi import vif as vmwarevif from nova.virt.vmwareapi import vim_util @@ -39,22 +44,45 @@ from nova.virt.vmwareapi import vm_util from nova.virt.vmwareapi import vmware_images +vmware_vif_opts = [ + cfg.StrOpt('integration_bridge', + default='br-int', + help='Name of Integration Bridge'), + ] + +vmware_group = cfg.OptGroup(name='vmware', + title='VMware Options') + CONF = cfg.CONF +CONF.register_group(vmware_group) +CONF.register_opts(vmware_vif_opts, vmware_group) +CONF.import_opt('vnc_enabled', 'nova.vnc') LOG = logging.getLogger(__name__) VMWARE_POWER_STATES = { 'poweredOff': power_state.SHUTDOWN, 'poweredOn': power_state.RUNNING, - 'suspended': power_state.PAUSED} + 'suspended': power_state.SUSPENDED} +VMWARE_PREFIX = 'vmware' + + +RESIZE_TOTAL_STEPS = 4 class VMwareVMOps(object): """Management class for VM-related tasks.""" - def __init__(self, session): + def __init__(self, session, virtapi, volumeops): """Initializer.""" + self.compute_api = compute.API() self._session = session + self._virtapi = virtapi + self._volumeops = volumeops + self._instance_path_base = VMWARE_PREFIX + CONF.base_dir_name + self._default_root_device = 'vda' + self._rescue_suffix = '-rescue' + self._poll_rescue_last_ran = None def list_instances(self): """Lists the VM instances that are registered with the ESX host.""" @@ -71,13 +99,14 @@ class VMwareVMOps(object): vm_name = prop.val elif prop.name == "runtime.connectionState": conn_state = prop.val - # Ignoring the oprhaned or inaccessible VMs + # Ignoring the orphaned or inaccessible VMs if conn_state not in ["orphaned", "inaccessible"]: lst_vm_names.append(vm_name) LOG.debug(_("Got total of %s instances") % str(len(lst_vm_names))) return lst_vm_names - def spawn(self, context, instance, image_meta, network_info): + def spawn(self, context, instance, image_meta, network_info, + block_device_info=None): """ Creates a VM instance. @@ -85,44 +114,28 @@ class VMwareVMOps(object): 1. Create a VM with no disk and the specifics in the instance object like RAM size. - 2. Create a dummy vmdk of the size of the disk file that is to be - uploaded. This is required just to create the metadata file. - 3. Delete the -flat.vmdk file created in the above step and retain - the metadata .vmdk file. - 4. Upload the disk file. - 5. Attach the disk to the VM by reconfiguring the same. - 6. Power on the VM. + 2. For flat disk + 2.1. Create a dummy vmdk of the size of the disk file that is to be + uploaded. This is required just to create the metadata file. + 2.2. Delete the -flat.vmdk file created in the above step and retain + the metadata .vmdk file. + 2.3. Upload the disk file. + 3. For sparse disk + 3.1. Upload the disk file to a -sparse.vmdk file. + 3.2. Copy/Clone the -sparse.vmdk file to a thin vmdk. + 3.3. Delete the -sparse.vmdk file. + 4. Attach the disk to the VM by reconfiguring the same. + 5. Power on the VM. """ - vm_ref = self._get_vm_ref_from_the_name(instance.name) + vm_ref = vm_util.get_vm_ref_from_name(self._session, instance['name']) if vm_ref: - raise exception.InstanceExists(name=instance.name) + raise exception.InstanceExists(name=instance['name']) client_factory = self._session._get_vim().client.factory service_content = self._session._get_vim().get_service_content() - - def _get_datastore_ref(): - """Get the datastore list and choose the first local storage.""" - data_stores = self._session._call_method(vim_util, "get_objects", - "Datastore", ["summary.type", "summary.name"]) - for elem in data_stores: - ds_name = None - ds_type = None - for prop in elem.propSet: - if prop.name == "summary.type": - ds_type = prop.val - elif prop.name == "summary.name": - ds_name = prop.val - # Local storage identifier - if ds_type == "VMFS": - data_store_name = ds_name - return data_store_name - - if data_store_name is None: - msg = _("Couldn't get a local Datastore reference") - LOG.error(msg, instance=instance) - raise exception.NovaException(msg) - - data_store_name = _get_datastore_ref() + ds = vm_util.get_datastore_ref_and_name(self._session) + data_store_ref = ds[0] + data_store_name = ds[1] def _get_image_properties(): """ @@ -130,31 +143,21 @@ class VMwareVMOps(object): repository. """ _image_info = vmware_images.get_vmdk_size_and_properties(context, - instance.image_ref, - instance) + instance['image_ref'], + instance) image_size, image_properties = _image_info vmdk_file_size_in_kb = int(image_size) / 1024 os_type = image_properties.get("vmware_ostype", "otherGuest") adapter_type = image_properties.get("vmware_adaptertype", "lsiLogic") - return vmdk_file_size_in_kb, os_type, adapter_type - - vmdk_file_size_in_kb, os_type, adapter_type = _get_image_properties() - - def _get_vmfolder_and_res_pool_mors(): - """Get the Vm folder ref from the datacenter.""" - dc_objs = self._session._call_method(vim_util, "get_objects", - "Datacenter", ["vmFolder"]) - # There is only one default datacenter in a standalone ESX host - vm_folder_mor = dc_objs[0].propSet[0].val + disk_type = image_properties.get("vmware_disktype", + "preallocated") + return vmdk_file_size_in_kb, os_type, adapter_type, disk_type - # Get the resource pool. Taking the first resource pool coming our - # way. Assuming that is the default resource pool. - res_pool_mor = self._session._call_method(vim_util, "get_objects", - "ResourcePool")[0].obj - return vm_folder_mor, res_pool_mor + (vmdk_file_size_in_kb, os_type, adapter_type, + disk_type) = _get_image_properties() - vm_folder_mor, res_pool_mor = _get_vmfolder_and_res_pool_mors() + vm_folder_ref, res_pool_ref = self._get_vmfolder_and_res_pool_refs() def _check_if_network_bridge_exists(network_name): network_ref = network_util.get_network_with_the_name( @@ -165,9 +168,12 @@ class VMwareVMOps(object): def _get_vif_infos(): vif_infos = [] + if network_info is None: + return vif_infos for (network, mapping) in network_info: mac_address = mapping['mac'] - network_name = network['bridge'] + network_name = network['bridge'] or \ + CONF.vmware.integration_bridge if mapping.get('should_create_vlan'): network_ref = vmwarevif.ensure_vlan_bridge( self._session, network) @@ -188,33 +194,29 @@ class VMwareVMOps(object): def _execute_create_vm(): """Create VM on ESX host.""" - LOG.debug(_("Creating VM on the ESX host"), instance=instance) + LOG.debug(_("Creating VM on the ESX host"), instance=instance) # Create the VM on the ESX host vm_create_task = self._session._call_method( self._session._get_vim(), - "CreateVM_Task", vm_folder_mor, - config=config_spec, pool=res_pool_mor) + "CreateVM_Task", vm_folder_ref, + config=config_spec, pool=res_pool_ref) self._session._wait_for_task(instance['uuid'], vm_create_task) - LOG.debug(_("Created VM on the ESX host"), instance=instance) + LOG.debug(_("Created VM on the ESX host"), instance=instance) _execute_create_vm() + vm_ref = vm_util.get_vm_ref_from_name(self._session, instance['name']) # Set the machine.id parameter of the instance to inject # the NIC configuration inside the VM if CONF.flat_injected: self._set_machine_id(client_factory, instance, network_info) - # Naming the VM files in correspondence with the VM instance name - # The flat vmdk file name - flat_uploaded_vmdk_name = "%s/%s-flat.vmdk" % (instance.name, - instance.name) - # The vmdk meta-data file - uploaded_vmdk_name = "%s/%s.vmdk" % (instance.name, instance.name) - flat_uploaded_vmdk_path = vm_util.build_datastore_path(data_store_name, - flat_uploaded_vmdk_name) - uploaded_vmdk_path = vm_util.build_datastore_path(data_store_name, - uploaded_vmdk_name) + # Set the vnc configuration of the instance, vnc port starts from 5900 + if CONF.vnc_enabled: + vnc_port = self._get_vnc_port(vm_ref) + vnc_pass = CONF.vnc_password or '' + self._set_vnc_config(client_factory, instance, vnc_port, vnc_pass) def _create_virtual_disk(): """Create a virtual disk of the size of flat vmdk file.""" @@ -225,103 +227,186 @@ class VMwareVMOps(object): # Here we assume thick provisioning and lsiLogic for the adapter # type LOG.debug(_("Creating Virtual Disk of size " - "%(vmdk_file_size_in_kb)s KB and adapter type " - "%(adapter_type)s on the ESX host local store" - " %(data_store_name)s") % + "%(vmdk_file_size_in_kb)s KB and adapter type " + "%(adapter_type)s on the ESX host local store " + "%(data_store_name)s") % {"vmdk_file_size_in_kb": vmdk_file_size_in_kb, "adapter_type": adapter_type, "data_store_name": data_store_name}, instance=instance) vmdk_create_spec = vm_util.get_vmdk_create_spec(client_factory, - vmdk_file_size_in_kb, adapter_type) + vmdk_file_size_in_kb, adapter_type, + disk_type) vmdk_create_task = self._session._call_method( self._session._get_vim(), "CreateVirtualDisk_Task", service_content.virtualDiskManager, name=uploaded_vmdk_path, - datacenter=self._get_datacenter_name_and_ref()[0], + datacenter=dc_ref, spec=vmdk_create_spec) self._session._wait_for_task(instance['uuid'], vmdk_create_task) LOG.debug(_("Created Virtual Disk of size %(vmdk_file_size_in_kb)s" - " KB on the ESX host local store " - "%(data_store_name)s") % + " KB and type %(disk_type)s on " + "the ESX host local store %(data_store_name)s") % {"vmdk_file_size_in_kb": vmdk_file_size_in_kb, + "disk_type": disk_type, "data_store_name": data_store_name}, instance=instance) - _create_virtual_disk() - - def _delete_disk_file(): - LOG.debug(_("Deleting the file %(flat_uploaded_vmdk_path)s " + def _delete_disk_file(vmdk_path): + LOG.debug(_("Deleting the file %(vmdk_path)s " "on the ESX host local" "store %(data_store_name)s") % - {"flat_uploaded_vmdk_path": flat_uploaded_vmdk_path, + {"vmdk_path": vmdk_path, "data_store_name": data_store_name}, instance=instance) - # Delete the -flat.vmdk file created. .vmdk file is retained. + # Delete the vmdk file. vmdk_delete_task = self._session._call_method( self._session._get_vim(), "DeleteDatastoreFile_Task", service_content.fileManager, - name=flat_uploaded_vmdk_path) + name=vmdk_path, + datacenter=dc_ref) self._session._wait_for_task(instance['uuid'], vmdk_delete_task) - LOG.debug(_("Deleted the file %(flat_uploaded_vmdk_path)s on the " + LOG.debug(_("Deleted the file %(vmdk_path)s on the " "ESX host local store %(data_store_name)s") % - {"flat_uploaded_vmdk_path": flat_uploaded_vmdk_path, + {"vmdk_path": vmdk_path, "data_store_name": data_store_name}, instance=instance) - _delete_disk_file() - - cookies = self._session._get_vim().client.options.transport.cookiejar - def _fetch_image_on_esx_datastore(): """Fetch image from Glance to ESX datastore.""" LOG.debug(_("Downloading image file data %(image_ref)s to the ESX " "data store %(data_store_name)s") % - {'image_ref': instance.image_ref, + {'image_ref': instance['image_ref'], 'data_store_name': data_store_name}, instance=instance) - # Upload the -flat.vmdk file whose meta-data file we just created - # above + # For flat disk, upload the -flat.vmdk file whose meta-data file + # we just created above + # For sparse disk, upload the -sparse.vmdk file to be copied into + # a flat vmdk + upload_vmdk_name = sparse_uploaded_vmdk_name \ + if disk_type == "sparse" else flat_uploaded_vmdk_name vmware_images.fetch_image( context, - instance.image_ref, + instance['image_ref'], instance, host=self._session._host_ip, - data_center_name=self._get_datacenter_name_and_ref()[1], + data_center_name=self._get_datacenter_ref_and_name()[1], datastore_name=data_store_name, cookies=cookies, - file_path=flat_uploaded_vmdk_name) - LOG.debug(_("Downloaded image file data %(image_ref)s to the ESX " - "data store %(data_store_name)s") % - {'image_ref': instance.image_ref, + file_path=upload_vmdk_name) + LOG.debug(_("Downloaded image file data %(image_ref)s to " + "%(upload_vmdk_name)s on the ESX data store " + "%(data_store_name)s") % + {'image_ref': instance['image_ref'], + 'upload_vmdk_name': upload_vmdk_name, 'data_store_name': data_store_name}, instance=instance) - _fetch_image_on_esx_datastore() - vm_ref = self._get_vm_ref_from_the_name(instance.name) - - def _attach_vmdk_to_the_vm(): - """ - Attach the vmdk uploaded to the VM. VM reconfigure is done - to do so. - """ - vmdk_attach_config_spec = vm_util.get_vmdk_attach_config_spec( - client_factory, - vmdk_file_size_in_kb, uploaded_vmdk_path, - adapter_type) - LOG.debug(_("Reconfiguring VM instance to attach the image disk"), - instance=instance) - 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['uuid'], reconfig_task) - LOG.debug(_("Reconfigured VM instance to attach the image disk"), + def _copy_virtual_disk(): + """Copy a sparse virtual disk to a thin virtual disk.""" + # Copy a sparse virtual disk to a thin virtual disk. This is also + # done to generate the meta-data file whose specifics + # depend on the size of the disk, thin/thick provisioning and the + # storage adapter type. + LOG.debug(_("Copying Virtual Disk of size " + "%(vmdk_file_size_in_kb)s KB and adapter type " + "%(adapter_type)s on the ESX host local store " + "%(data_store_name)s to disk type %(disk_type)s") % + {"vmdk_file_size_in_kb": vmdk_file_size_in_kb, + "adapter_type": adapter_type, + "data_store_name": data_store_name, + "disk_type": disk_type}, instance=instance) + vmdk_copy_spec = vm_util.get_vmdk_create_spec(client_factory, + vmdk_file_size_in_kb, adapter_type, + disk_type) + vmdk_copy_task = self._session._call_method( + self._session._get_vim(), + "CopyVirtualDisk_Task", + service_content.virtualDiskManager, + sourceName=sparse_uploaded_vmdk_path, + sourceDatacenter=self._get_datacenter_ref_and_name()[0], + destName=uploaded_vmdk_path, + destSpec=vmdk_copy_spec) + self._session._wait_for_task(instance['uuid'], vmdk_copy_task) + LOG.debug(_("Copied Virtual Disk of size %(vmdk_file_size_in_kb)s" + " KB and type %(disk_type)s on " + "the ESX host local store %(data_store_name)s") % + {"vmdk_file_size_in_kb": vmdk_file_size_in_kb, + "disk_type": disk_type, + "data_store_name": data_store_name}, + instance=instance) - _attach_vmdk_to_the_vm() + ebs_root = block_device.volume_in_mapping( + self._default_root_device, block_device_info) + + if not ebs_root: + linked_clone = CONF.use_linked_clone + if linked_clone: + upload_folder = self._instance_path_base + upload_name = instance['image_ref'] + else: + upload_folder = instance['name'] + upload_name = instance['name'] + + # The vmdk meta-data file + uploaded_vmdk_name = "%s/%s.vmdk" % (upload_folder, upload_name) + uploaded_vmdk_path = vm_util.build_datastore_path(data_store_name, + uploaded_vmdk_name) + + if not (linked_clone and self._check_if_folder_file_exists( + data_store_ref, data_store_name, + upload_folder, upload_name + ".vmdk")): + + # Naming the VM files in correspondence with the VM instance + # The flat vmdk file name + flat_uploaded_vmdk_name = "%s/%s-flat.vmdk" % ( + upload_folder, upload_name) + # The sparse vmdk file name for sparse disk image + sparse_uploaded_vmdk_name = "%s/%s-sparse.vmdk" % ( + upload_folder, upload_name) + + flat_uploaded_vmdk_path = vm_util.build_datastore_path( + data_store_name, + flat_uploaded_vmdk_name) + sparse_uploaded_vmdk_path = vm_util.build_datastore_path( + data_store_name, + sparse_uploaded_vmdk_name) + dc_ref = self._get_datacenter_ref_and_name()[0] + + if disk_type != "sparse": + # Create a flat virtual disk and retain the metadata file. + _create_virtual_disk() + _delete_disk_file(flat_uploaded_vmdk_path) + + cookies = \ + self._session._get_vim().client.options.transport.cookiejar + _fetch_image_on_esx_datastore() + + if disk_type == "sparse": + # Copy the sparse virtual disk to a thin virtual disk. + disk_type = "thin" + _copy_virtual_disk() + _delete_disk_file(sparse_uploaded_vmdk_path) + else: + # linked clone base disk exists + if disk_type == "sparse": + disk_type = "thin" + + # Attach the vmdk uploaded to the VM. + self._volumeops.attach_disk_to_vm( + vm_ref, instance, + adapter_type, disk_type, uploaded_vmdk_path, + vmdk_file_size_in_kb, linked_clone) + else: + # Attach the root disk to the VM. + root_disk = driver.block_device_info_get_mapping( + block_device_info)[0] + connection_info = root_disk['connection_info'] + self._volumeops.attach_volume(connection_info, instance['name'], + self._default_root_device) def _power_on_vm(): """Power on the VM.""" @@ -349,9 +434,9 @@ class VMwareVMOps(object): 4. Now upload the -flat.vmdk file to the image store. 5. Delete the coalesced .vmdk and -flat.vmdk created. """ - vm_ref = self._get_vm_ref_from_the_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.id) + raise exception.InstanceNotFound(instance_id=instance['uuid']) client_factory = self._session._get_vim().client.factory service_content = self._session._get_vim().get_service_content() @@ -361,19 +446,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] + 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 @@ -382,9 +467,9 @@ class VMwareVMOps(object): snapshot_task = self._session._call_method( self._session._get_vim(), "CreateSnapshot_Task", vm_ref, - name="%s-snapshot" % instance.name, + 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"), @@ -424,13 +509,14 @@ class VMwareVMOps(object): random_name = str(uuid.uuid4()) dest_vmdk_file_location = vm_util.build_datastore_path(datastore_name, "vmware-tmp/%s.vmdk" % random_name) - dc_ref = self._get_datacenter_name_and_ref()[0] + dc_ref = self._get_datacenter_ref_and_name()[0] def _copy_vmdk_content(): # Copy the contents of the disk ( or disks, if there were snapshots # done earlier) to a temporary vmdk file. copy_spec = vm_util.get_copy_virtual_disk_spec(client_factory, - adapter_type) + adapter_type, + disk_type) LOG.debug(_('Copying disk data before snapshot of the VM'), instance=instance) copy_disk_task = self._session._call_method( @@ -463,7 +549,7 @@ class VMwareVMOps(object): adapter_type=adapter_type, image_version=1, host=self._session._host_ip, - data_center_name=self._get_datacenter_name_and_ref()[1], + data_center_name=self._get_datacenter_ref_and_name()[1], datastore_name=datastore_name, cookies=cookies, file_path="vmware-tmp/%s-flat.vmdk" % random_name) @@ -496,9 +582,9 @@ class VMwareVMOps(object): def reboot(self, instance, network_info): """Reboot a VM instance.""" - vm_ref = self._get_vm_ref_from_the_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.id) + raise exception.InstanceNotFound(instance_id=instance['uuid']) self.plug_vifs(instance, network_info) @@ -539,6 +625,38 @@ class VMwareVMOps(object): self._session._wait_for_task(instance['uuid'], reset_task) LOG.debug(_("Did hard reboot of VM"), instance=instance) + def _delete(self, instance, network_info): + """ + Destroy a VM instance. Steps followed are: + 1. Power off the VM, if it is in poweredOn state. + 2. Destroy the VM. + """ + try: + vm_ref = vm_util.get_vm_ref_from_name(self._session, + instance['name']) + if vm_ref is None: + LOG.debug(_("instance not present"), instance=instance) + return + + self.power_off(instance) + + try: + LOG.debug(_("Destroying the VM"), instance=instance) + destroy_task = self._session._call_method( + self._session._get_vim(), + "Destroy_Task", vm_ref) + self._session._wait_for_task(instance['uuid'], destroy_task) + LOG.debug(_("Destroyed the VM"), instance=instance) + except Exception, excep: + LOG.warn(_("In vmwareapi:vmops:delete, got this exception" + " while destroying the VM: %s") % str(excep), + instance=instance) + + if network_info: + self.unplug_vifs(instance, network_info) + except Exception, exc: + LOG.exception(exc, instance=instance) + def destroy(self, instance, network_info, destroy_disks=True): """ Destroy a VM instance. Steps followed are: @@ -547,7 +665,8 @@ class VMwareVMOps(object): 3. Delete the contents of the folder holding the VM related data. """ try: - vm_ref = self._get_vm_ref_from_the_name(instance.name) + vm_ref = vm_util.get_vm_ref_from_name(self._session, + instance['name']) if vm_ref is None: LOG.debug(_("instance not present"), instance=instance) return @@ -579,14 +698,15 @@ class VMwareVMOps(object): try: LOG.debug(_("Unregistering the VM"), instance=instance) self._session._call_method(self._session._get_vim(), - "UnregisterVM", vm_ref) + "UnregisterVM", vm_ref) LOG.debug(_("Unregistered the VM"), instance=instance) except Exception, excep: LOG.warn(_("In vmwareapi:vmops:destroy, got this exception" " while un-registering the VM: %s") % str(excep), instance=instance) - self.unplug_vifs(instance, network_info) + if network_info: + self.unplug_vifs(instance, network_info) # Delete the folder holding the VM related content on # the datastore. @@ -604,7 +724,8 @@ class VMwareVMOps(object): vim, "DeleteDatastoreFile_Task", vim.get_service_content().fileManager, - name=dir_ds_compliant_path) + name=dir_ds_compliant_path, + datacenter=self._get_datacenter_ref_and_name()[0]) self._session._wait_for_task(instance['uuid'], delete_task) LOG.debug(_("Deleted contents of the VM from " "datastore %(datastore_name)s") % @@ -629,9 +750,9 @@ class VMwareVMOps(object): def suspend(self, instance): """Suspend the specified instance.""" - vm_ref = self._get_vm_ref_from_the_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.id) + raise exception.InstanceNotFound(instance_id=instance['uuid']) pwr_state = self._session._call_method(vim_util, "get_dynamic_property", vm_ref, @@ -645,17 +766,17 @@ class VMwareVMOps(object): LOG.debug(_("Suspended the VM"), instance=instance) # Raise Exception if VM is poweredOff elif pwr_state == "poweredOff": - reason = _("instance is powered off and can not be suspended.") + reason = _("instance is powered off and cannot be suspended.") raise exception.InstanceSuspendFailure(reason=reason) - - LOG.debug(_("VM was already in suspended state. So returning " - "without doing anything"), instance=instance) + else: + LOG.debug(_("VM was already in suspended state. So returning " + "without doing anything"), instance=instance) def resume(self, instance): """Resume the specified instance.""" - vm_ref = self._get_vm_ref_from_the_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.id) + raise exception.InstanceNotFound(instance_id=instance['uuid']) pwr_state = self._session._call_method(vim_util, "get_dynamic_property", vm_ref, @@ -671,9 +792,263 @@ class VMwareVMOps(object): reason = _("instance is not in a suspended state") raise exception.InstanceResumeFailure(reason=reason) + def rescue(self, context, instance, network_info, image_meta): + """Rescue the specified instance. + + - shutdown the instance VM. + - spawn a rescue VM (the vm name-label will be instance-N-rescue). + + """ + vm_ref = vm_util.get_vm_ref_from_name(self._session, instance['name']) + if vm_ref is None: + raise exception.InstanceNotFound(instance_id=instance['uuid']) + + self.power_off(instance) + instance['name'] = instance['name'] + self._rescue_suffix + self.spawn(context, instance, image_meta, network_info) + + # Attach vmdk to the rescue VM + hardware_devices = self._session._call_method(vim_util, + "get_dynamic_property", vm_ref, + "VirtualMachine", "config.hardware.device") + vmdk_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 + unit_number = unit_number + 1 + rescue_vm_ref = vm_util.get_vm_ref_from_name(self._session, + instance['name']) + self._volumeops.attach_disk_to_vm( + rescue_vm_ref, instance, + adapter_type, disk_type, vmdk_path, + controller_key=controller_key, + unit_number=unit_number) + + def unrescue(self, instance): + """Unrescue the specified instance.""" + instance_orig_name = instance['name'] + instance['name'] = instance['name'] + self._rescue_suffix + self.destroy(instance, None) + instance['name'] = instance_orig_name + self.power_on(instance) + + def power_off(self, instance): + """Power off the specified instance.""" + vm_ref = vm_util.get_vm_ref_from_name(self._session, instance['name']) + if vm_ref is None: + raise exception.InstanceNotFound(instance_id=instance['uuid']) + + pwr_state = self._session._call_method(vim_util, + "get_dynamic_property", vm_ref, + "VirtualMachine", "runtime.powerState") + # Only PoweredOn VMs can be powered off. + if pwr_state == "poweredOn": + LOG.debug(_("Powering off the VM"), instance=instance) + poweroff_task = self._session._call_method( + self._session._get_vim(), + "PowerOffVM_Task", vm_ref) + self._session._wait_for_task(instance['uuid'], poweroff_task) + LOG.debug(_("Powered off the VM"), instance=instance) + # Raise Exception if VM is suspended + elif pwr_state == "suspended": + reason = _("instance is suspended and cannot be powered off.") + raise exception.InstancePowerOffFailure(reason=reason) + else: + LOG.debug(_("VM was already in powered off state. So returning " + "without doing anything"), instance=instance) + + def power_on(self, instance): + """Power on the specified instance.""" + vm_ref = vm_util.get_vm_ref_from_name(self._session, instance['name']) + if vm_ref is None: + raise exception.InstanceNotFound(instance_id=instance['uuid']) + + pwr_state = self._session._call_method(vim_util, + "get_dynamic_property", vm_ref, + "VirtualMachine", "runtime.powerState") + if pwr_state == "poweredOn": + LOG.debug(_("VM was already in powered on state. So returning " + "without doing anything"), instance=instance) + # Only PoweredOff and Suspended VMs can be powered on. + else: + LOG.debug(_("Powering on the VM"), instance=instance) + poweron_task = self._session._call_method( + self._session._get_vim(), + "PowerOnVM_Task", vm_ref) + self._session._wait_for_task(instance['uuid'], poweron_task) + LOG.debug(_("Powered on the VM"), instance=instance) + + def _get_orig_vm_name_label(self, instance): + return instance['name'] + '-orig' + + def _update_instance_progress(self, context, instance, step, total_steps): + """Update instance progress percent to reflect current step number + """ + # Divide the action's workflow into discrete steps and "bump" the + # instance's progress field as each step is completed. + # + # For a first cut this should be fine, however, for large VM images, + # the clone disk step begins to dominate the equation. A + # better approximation would use the percentage of the VM image that + # has been streamed to the destination host. + progress = round(float(step) / total_steps * 100) + instance_uuid = instance['uuid'] + LOG.debug(_("Updating instance '%(instance_uuid)s' progress to" + " %(progress)d") % locals(), instance=instance) + self._virtapi.instance_update(context, instance_uuid, + {'progress': progress}) + + def migrate_disk_and_power_off(self, context, instance, dest, + instance_type): + """ + Transfers the disk of a running instance in multiple phases, turning + off the instance before the end. + """ + # 0. Zero out the progress to begin + self._update_instance_progress(context, instance, + step=0, + total_steps=RESIZE_TOTAL_STEPS) + + 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']) + host_ref = self._get_host_ref_from_name(dest) + if host_ref is None: + raise exception.HostNotFound(host=dest) + + # 1. Power off the instance + self.power_off(instance) + self._update_instance_progress(context, instance, + step=1, + total_steps=RESIZE_TOTAL_STEPS) + + # 2. Rename the original VM with suffix '-orig' + name_label = self._get_orig_vm_name_label(instance) + LOG.debug(_("Renaming the VM to %s") % name_label, + instance=instance) + rename_task = self._session._call_method( + self._session._get_vim(), + "Rename_Task", vm_ref, newName=name_label) + self._session._wait_for_task(instance['uuid'], rename_task) + LOG.debug(_("Renamed the VM to %s") % name_label, + instance=instance) + self._update_instance_progress(context, instance, + step=2, + total_steps=RESIZE_TOTAL_STEPS) + + # Get the clone vm spec + ds_ref = vm_util.get_datastore_ref_and_name(self._session)[0] + client_factory = self._session._get_vim().client.factory + rel_spec = vm_util.relocate_vm_spec(client_factory, ds_ref, host_ref) + clone_spec = vm_util.clone_vm_spec(client_factory, rel_spec) + vm_folder_ref, res_pool_ref = self._get_vmfolder_and_res_pool_refs() + + # 3. Clone VM on ESX host + LOG.debug(_("Cloning VM to host %s") % dest, instance=instance) + vm_clone_task = self._session._call_method( + self._session._get_vim(), + "CloneVM_Task", vm_ref, + folder=vm_folder_ref, + name=instance['name'], + spec=clone_spec) + self._session._wait_for_task(instance['uuid'], vm_clone_task) + LOG.debug(_("Cloned VM to host %s") % dest, instance=instance) + self._update_instance_progress(context, instance, + step=3, + total_steps=RESIZE_TOTAL_STEPS) + + def confirm_migration(self, migration, instance, network_info): + """Confirms a resize, destroying the source VM.""" + instance_name = self._get_orig_vm_name_label(instance) + # Destroy the original VM. + vm_ref = vm_util.get_vm_ref_from_name(self._session, instance_name) + if vm_ref is None: + LOG.debug(_("instance not present"), instance=instance) + return + + try: + LOG.debug(_("Destroying the VM"), instance=instance) + destroy_task = self._session._call_method( + self._session._get_vim(), + "Destroy_Task", vm_ref) + self._session._wait_for_task(instance['uuid'], destroy_task) + LOG.debug(_("Destroyed the VM"), instance=instance) + except Exception, excep: + LOG.warn(_("In vmwareapi:vmops:confirm_migration, got this " + "exception while destroying the VM: %s") % str(excep)) + + if network_info: + self.unplug_vifs(instance, network_info) + + def finish_revert_migration(self, instance): + """Finish reverting a resize, powering back on the instance.""" + # The original vm was suffixed with '-orig'; find it using + # the old suffix, remove the suffix, then power it back on. + name_label = self._get_orig_vm_name_label(instance) + vm_ref = vm_util.get_vm_ref_from_name(self._session, name_label) + if vm_ref is None: + raise exception.InstanceNotFound(instance_id=name_label) + + LOG.debug(_("Renaming the VM from %s") % name_label, + instance=instance) + rename_task = self._session._call_method( + self._session._get_vim(), + "Rename_Task", vm_ref, newName=instance['name']) + self._session._wait_for_task(instance['uuid'], rename_task) + LOG.debug(_("Renamed the VM from %s") % name_label, + instance=instance) + self.power_on(instance) + + def finish_migration(self, context, migration, instance, disk_info, + network_info, image_meta, resize_instance=False): + """Completes a resize, turning on the migrated instance.""" + # 4. Start VM + self.power_on(instance) + self._update_instance_progress(context, instance, + step=4, + total_steps=RESIZE_TOTAL_STEPS) + + def live_migration(self, context, instance_ref, dest, + post_method, recover_method, block_migration=False): + """Spawning live_migration operation for distributing high-load.""" + vm_ref = vm_util.get_vm_ref_from_name(self._session, instance_ref.name) + if vm_ref is None: + raise exception.InstanceNotFound(instance_id=instance_ref.name) + host_ref = self._get_host_ref_from_name(dest) + if host_ref is None: + raise exception.HostNotFound(host=dest) + + LOG.debug(_("Migrating VM to host %s") % dest, instance=instance_ref) + try: + vm_migrate_task = self._session._call_method( + self._session._get_vim(), + "MigrateVM_Task", vm_ref, + host=host_ref, + priority="defaultPriority") + self._session._wait_for_task(instance_ref['uuid'], vm_migrate_task) + except Exception: + with excutils.save_and_reraise_exception(): + recover_method(context, instance_ref, dest, block_migration) + post_method(context, instance_ref, dest, block_migration) + LOG.debug(_("Migrated VM to host %s") % dest, instance=instance_ref) + + def poll_rebooting_instances(self, timeout, instances): + """Poll for rebooting instances.""" + ctxt = nova_context.get_admin_context() + + instances_info = dict(instance_count=len(instances), + timeout=timeout) + + if instances_info["instance_count"] > 0: + LOG.info(_("Found %(instance_count)d hung reboots " + "older than %(timeout)d seconds") % instances_info) + + for instance in instances: + LOG.info(_("Automatically hard rebooting %d") % instance['uuid']) + self.compute_api.reboot(ctxt, instance, "HARD") + def get_info(self, instance): """Return data about the VM instance.""" - vm_ref = self._get_vm_ref_from_the_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']) @@ -709,9 +1084,9 @@ class VMwareVMOps(object): def get_console_output(self, instance): """Return snapshot of console.""" - vm_ref = self._get_vm_ref_from_the_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.id) + raise exception.InstanceNotFound(instance_id=instance['uuid']) param_list = {"id": str(vm_ref)} base_url = "%s://%s/screen?%s" % (self._session._scheme, self._session._host_ip, @@ -728,14 +1103,32 @@ class VMwareVMOps(object): else: return "" + def get_vnc_console(self, instance): + """Return connection info for a vnc console.""" + vm_ref = vm_util.get_vm_ref_from_name(self._session, instance['name']) + if vm_ref is None: + raise exception.InstanceNotFound(instance_id=instance['uuid']) + + return {'host': CONF.vmwareapi_host_ip, + 'port': self._get_vnc_port(vm_ref), + 'internal_access_path': None} + + @staticmethod + def _get_vnc_port(vm_ref): + """Return VNC port for an VM.""" + vm_id = int(vm_ref.value.replace('vm-', '')) + port = CONF.vnc_port + vm_id % CONF.vnc_port_total + + return port + def _set_machine_id(self, client_factory, instance, network_info): """ Set the machine id of the VM for guest tools to pick up and reconfigure the network interfaces. """ - vm_ref = self._get_vm_ref_from_the_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.id) + raise exception.InstanceNotFound(instance_id=instance['uuid']) machine_id_str = '' for (network, info) in network_info: @@ -763,24 +1156,66 @@ class VMwareVMOps(object): client_factory, machine_id_str) LOG.debug(_("Reconfiguring VM instance to set the machine id " - "with ip - %(ip_addr)s") % - {'ip_addr': ip_v4['ip']}, + "with ip - %(ip_addr)s") % {'ip_addr': ip_v4['ip']}, instance=instance) reconfig_task = self._session._call_method(self._session._get_vim(), "ReconfigVM_Task", vm_ref, spec=machine_id_change_spec) self._session._wait_for_task(instance['uuid'], reconfig_task) LOG.debug(_("Reconfigured VM instance to set the machine id " - "with ip - %(ip_addr)s") % - {'ip_addr': ip_v4['ip']}, + "with ip - %(ip_addr)s") % {'ip_addr': ip_v4['ip']}, + instance=instance) + + def _set_vnc_config(self, client_factory, instance, port, password): + """ + Set the vnc configuration of the VM. + """ + vm_ref = vm_util.get_vm_ref_from_name(self._session, instance['name']) + if vm_ref is None: + raise exception.InstanceNotFound(instance_id=instance['uuid']) + + vnc_config_spec = vm_util.get_vnc_config_spec( + client_factory, port, password) + + LOG.debug(_("Reconfiguring VM instance to enable vnc on " + "port - %(port)s") % {'port': port}, + instance=instance) + reconfig_task = self._session._call_method(self._session._get_vim(), + "ReconfigVM_Task", vm_ref, + spec=vnc_config_spec) + self._session._wait_for_task(instance['uuid'], reconfig_task) + LOG.debug(_("Reconfigured VM instance to enable vnc on " + "port - %(port)s") % {'port': port}, instance=instance) - def _get_datacenter_name_and_ref(self): + def _get_datacenter_ref_and_name(self): """Get the datacenter name and the reference.""" dc_obj = self._session._call_method(vim_util, "get_objects", "Datacenter", ["name"]) return dc_obj[0].obj, dc_obj[0].propSet[0].val + def _get_host_ref_from_name(self, host_name): + """Get reference to the host with the name specified.""" + host_objs = self._session._call_method(vim_util, "get_objects", + "HostSystem", ["name"]) + for host in host_objs: + if host.propSet[0].val == host_name: + return host.obj + return None + + def _get_vmfolder_and_res_pool_refs(self): + """Get the Vm folder ref from the datacenter.""" + dc_objs = self._session._call_method(vim_util, "get_objects", + "Datacenter", ["vmFolder"]) + # There is only one default datacenter in a standalone ESX host + vm_folder_ref = dc_objs[0].propSet[0].val + + # Get the resource pool. Taking the first resource pool coming our + # way. Assuming that is the default resource pool. + res_pool_ref = self._session._call_method(vim_util, "get_objects", + "ResourcePool")[0].obj + return vm_folder_ref, res_pool_ref + def _path_exists(self, ds_browser, ds_path): """Check if the path exists on the datastore.""" search_task = self._session._call_method(self._session._get_vim(), @@ -801,6 +1236,32 @@ class VMwareVMOps(object): return False return True + def _path_file_exists(self, ds_browser, ds_path, file_name): + """Check if the path and file exists on the datastore.""" + client_factory = self._session._get_vim().client.factory + search_spec = vm_util.search_datastore_spec(client_factory, file_name) + search_task = self._session._call_method(self._session._get_vim(), + "SearchDatastore_Task", + ds_browser, + datastorePath=ds_path, + searchSpec=search_spec) + # Wait till the state changes from queued or running. + # If an error state is returned, it means that the path doesn't exist. + while True: + task_info = self._session._call_method(vim_util, + "get_dynamic_property", + search_task, "Task", "info") + if task_info.state in ['queued', 'running']: + time.sleep(2) + continue + break + if task_info.state == "error": + return False, False + + file_exists = (getattr(task_info.result, 'file', False) and + task_info.result.file[0].path == file_name) + return True, file_exists + def _mkdir(self, ds_path): """ Creates a directory at the path specified. If it is just "NAME", @@ -813,14 +1274,30 @@ class VMwareVMOps(object): name=ds_path, createParentDirectories=False) LOG.debug(_("Created directory with path %s") % ds_path) - def _get_vm_ref_from_the_name(self, vm_name): - """Get reference to the VM with the name specified.""" - vms = self._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 + def _check_if_folder_file_exists(self, ds_ref, ds_name, + folder_name, file_name): + ds_browser = vim_util.get_dynamic_property( + self._session._get_vim(), + ds_ref, + "Datastore", + "browser") + # Check if the folder exists or not. If not, create one + # Check if the file exists or not. + folder_path = vm_util.build_datastore_path(ds_name, folder_name) + folder_exists, file_exists = self._path_file_exists(ds_browser, + folder_path, + file_name) + if not folder_exists: + self._mkdir(vm_util.build_datastore_path(ds_name, folder_name)) + + return file_exists + + def inject_network_info(self, instance, network_info): + """inject network info for specified instance.""" + # Set the machine.id parameter of the instance to inject + # the NIC configuration inside the VM + client_factory = self._session._get_vim().client.factory + self._set_machine_id(client_factory, instance, network_info) def plug_vifs(self, instance, network_info): """Plug VIFs into networks.""" @@ -829,3 +1306,28 @@ class VMwareVMOps(object): def unplug_vifs(self, instance, network_info): """Unplug VIFs from networks.""" pass + + def list_interfaces(self, instance_name): + """ + Return the IDs of all the virtual network interfaces attached to the + specified instance, as a list. These IDs are opaque to the caller + (they are only useful for giving back to this layer as a parameter to + interface_stats). These IDs only need to be unique for a given + instance. + """ + 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) + + interfaces = [] + # Get the virtual network interfaces attached to the VM + hardware_devices = self._session._call_method(vim_util, + "get_dynamic_property", vm_ref, + "VirtualMachine", "config.hardware.device") + + for device in hardware_devices: + if device.__class__.__name__ in ["VirtualE1000", "VirtualE1000e", + "VirtualPCNet32", "VirtualVmxnet"]: + interfaces.append(device.key) + + return interfaces diff --git a/nova/virt/vmwareapi/vmware_images.py b/nova/virt/vmwareapi/vmware_images.py index 7c4480ea0..e8510b36e 100644 --- a/nova/virt/vmwareapi/vmware_images.py +++ b/nova/virt/vmwareapi/vmware_images.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. # @@ -17,7 +18,6 @@ """ Utility functions for Image transfer. """ -import StringIO from nova import exception from nova.image import glance @@ -56,7 +56,7 @@ def start_transfer(context, read_file_handle, data_size, write_thread = io_util.IOThread(thread_safe_pipe, write_file_handle) # In case of VMware - Glance transfer, we relinquish VMware HTTP file read # handle to Glance Client instance, but to be sure of the transfer we need - # to be sure of the status of the image on glnace changing to active. + # to be sure of the status of the image on glance changing to active. # The GlanceWriteThread handles the same for us. elif image_service and image_id: write_thread = io_util.GlanceWriteThread(context, thread_safe_pipe, @@ -93,9 +93,8 @@ def fetch_image(context, image, instance, **kwargs): (image_service, image_id) = glance.get_remote_image_service(context, image) metadata = image_service.show(context, image_id) file_size = int(metadata['size']) - f = StringIO.StringIO() - image_service.download(context, image_id, f) - read_file_handle = read_write_util.GlanceFileRead(f) + read_iter = image_service.download(context, image_id) + read_file_handle = read_write_util.GlanceFileRead(read_iter) write_file_handle = read_write_util.VMwareHTTPWriteFile( kwargs.get("host"), kwargs.get("data_center_name"), @@ -122,10 +121,9 @@ def upload_image(context, image, instance, **kwargs): file_size = read_file_handle.get_size() (image_service, image_id) = glance.get_remote_image_service(context, image) # The properties and other fields that we need to set for the image. - image_metadata = {"is_public": True, - "disk_format": "vmdk", + image_metadata = {"disk_format": "vmdk", "container_format": "bare", - "type": "vmdk", + "size": file_size, "properties": {"vmware_adaptertype": kwargs.get("adapter_type"), "vmware_ostype": kwargs.get("os_type"), diff --git a/nova/virt/vmwareapi/volume_util.py b/nova/virt/vmwareapi/volume_util.py new file mode 100644 index 000000000..2af3381a4 --- /dev/null +++ b/nova/virt/vmwareapi/volume_util.py @@ -0,0 +1,177 @@ +# 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.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..922d2135b --- /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 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, + 'host': CONF.vmwareapi_host_ip + } + + 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/agent.py b/nova/virt/xenapi/agent.py index 4345db198..e8a81f552 100644 --- a/nova/virt/xenapi/agent.py +++ b/nova/virt/xenapi/agent.py @@ -21,6 +21,9 @@ import os import time import uuid +from nova.api.metadata import password +from nova import context +from nova import crypto from nova.openstack.common import cfg from nova.openstack.common import jsonutils from nova.openstack.common import log as logging @@ -207,6 +210,12 @@ class XenAPIBasedAgent(object): LOG.error(msg, instance=self.instance) raise NotImplementedError(msg) + sshkey = self.instance.get('key_data') + if sshkey: + enc = crypto.ssh_encrypt_text(sshkey, new_pass) + password.set_password(context.get_admin_context(), + self.instance['uuid'], base64.b64encode(enc)) + return resp['message'] def inject_file(self, path, contents): diff --git a/nova/virt/xenapi/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/fake.py b/nova/virt/xenapi/fake.py index 666e46754..bdadfbc38 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -89,7 +89,7 @@ def reset(): def reset_table(table): - if not table in _CLASSES: + if table not in _CLASSES: return _db_content[table] = {} @@ -417,7 +417,7 @@ class SessionBase(object): def VBD_add_to_other_config(self, _1, vbd_ref, key, value): db_ref = _db_content['VBD'][vbd_ref] - if not 'other_config' in db_ref: + if 'other_config' not in db_ref: db_ref['other_config'] = {} if key in db_ref['other_config']: raise Failure(['MAP_DUPLICATE_KEY', 'VBD', 'other_config', @@ -426,7 +426,7 @@ class SessionBase(object): def VBD_get_other_config(self, _1, vbd_ref): db_ref = _db_content['VBD'][vbd_ref] - if not 'other_config' in db_ref: + if 'other_config' not in db_ref: return {} return db_ref['other_config'] @@ -497,14 +497,14 @@ class SessionBase(object): def VM_remove_from_xenstore_data(self, _1, vm_ref, key): db_ref = _db_content['VM'][vm_ref] - if not 'xenstore_data' in db_ref: + if 'xenstore_data' not in db_ref: return if key in db_ref['xenstore_data']: del db_ref['xenstore_data'][key] def VM_add_to_xenstore_data(self, _1, vm_ref, key, value): db_ref = _db_content['VM'][vm_ref] - if not 'xenstore_data' in db_ref: + if 'xenstore_data' not in db_ref: db_ref['xenstore_data'] = {} db_ref['xenstore_data'][key] = value @@ -513,14 +513,14 @@ class SessionBase(object): def VDI_remove_from_other_config(self, _1, vdi_ref, key): db_ref = _db_content['VDI'][vdi_ref] - if not 'other_config' in db_ref: + if 'other_config' not in db_ref: return if key in db_ref['other_config']: del db_ref['other_config'][key] def VDI_add_to_other_config(self, _1, vdi_ref, key, value): db_ref = _db_content['VDI'][vdi_ref] - if not 'other_config' in db_ref: + if 'other_config' not in db_ref: db_ref['other_config'] = {} if key in db_ref['other_config']: raise Failure(['MAP_DUPLICATE_KEY', 'VDI', 'other_config', diff --git a/nova/virt/xenapi/imageupload/__init__.py b/nova/virt/xenapi/imageupload/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/nova/virt/xenapi/imageupload/__init__.py diff --git a/nova/virt/xenapi/imageupload/glance.py b/nova/virt/xenapi/imageupload/glance.py new file mode 100644 index 000000000..adc06f65b --- /dev/null +++ b/nova/virt/xenapi/imageupload/glance.py @@ -0,0 +1,54 @@ +# Copyright 2013 OpenStack, LLC +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.image import glance +from nova.openstack.common import cfg +import nova.openstack.common.log as logging +from nova.virt.xenapi import vm_utils + +LOG = logging.getLogger(__name__) + +CONF = cfg.CONF + + +class GlanceStore(object): + + def upload_image(self, context, session, instance, vdi_uuids, image_id): + """Requests that the Glance plugin bundle the specified VDIs and + push them into Glance using the specified human-friendly name. + """ + # NOTE(sirp): Currently we only support uploading images as VHD, there + # is no RAW equivalent (yet) + LOG.debug(_("Asking xapi to upload to glance %(vdi_uuids)s as" + " ID %(image_id)s"), locals(), instance=instance) + + glance_api_servers = glance.get_api_servers() + glance_host, glance_port, glance_use_ssl = glance_api_servers.next() + + properties = { + 'auto_disk_config': instance['auto_disk_config'], + 'os_type': instance['os_type'] or CONF.default_os_type, + } + + params = {'vdi_uuids': vdi_uuids, + 'image_id': image_id, + 'glance_host': glance_host, + 'glance_port': glance_port, + 'glance_use_ssl': glance_use_ssl, + 'sr_path': vm_utils.get_sr_path(session), + 'auth_token': getattr(context, 'auth_token', None), + 'properties': properties} + + session.call_plugin_serialized('glance', 'upload_vhd', **params) diff --git a/nova/virt/xenapi/pool_states.py b/nova/virt/xenapi/pool_states.py index 5bf326117..138f84831 100644 --- a/nova/virt/xenapi/pool_states.py +++ b/nova/virt/xenapi/pool_states.py @@ -19,10 +19,10 @@ A pool may be 'created', in which case the admin has triggered its creation, but the underlying hypervisor pool has not actually being set up -yet. An pool may be 'changing', meaning that the underlying hypervisor -pool is being setup. An pool may be 'active', in which case the underlying -hypervisor pool is up and running. An pool may be 'dismissed' when it has -no hosts and it has been deleted. An pool may be in 'error' in all other +yet. A pool may be 'changing', meaning that the underlying hypervisor +pool is being setup. A pool may be 'active', in which case the underlying +hypervisor pool is up and running. A pool may be 'dismissed' when it has +no hosts and it has been deleted. A pool may be in 'error' in all other cases. A 'created' pool becomes 'changing' during the first request of adding a host. During a 'changing' status no other requests will be accepted; @@ -34,7 +34,7 @@ All other operations (e.g. add/remove hosts) that succeed will keep the pool in the 'active' state. If a number of continuous requests fail, an 'active' pool goes into an 'error' state. To recover from such a state, admin intervention is required. Currently an error state is irreversible, -that is, in order to recover from it an pool must be deleted. +that is, in order to recover from it a pool must be deleted. """ CREATED = 'created' diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index debba4f02..ec6450d9f 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -47,7 +47,6 @@ from nova import utils from nova.virt import configdrive from nova.virt.disk import api as disk from nova.virt.disk.vfs import localfs as vfsimpl -from nova.virt import driver from nova.virt.xenapi import agent from nova.virt.xenapi import volume_utils @@ -196,13 +195,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. @@ -468,66 +460,66 @@ def create_vdi(session, sr_ref, instance, name_label, disk_type, virtual_size, return vdi_ref -def get_vdis_for_boot_from_vol(session, dev_params): - vdis = {} - sr_uuid, label, sr_params = volume_utils.parse_sr_info(dev_params) +def get_vdi_uuid_for_volume(session, connection_data): + sr_uuid, label, sr_params = volume_utils.parse_sr_info(connection_data) sr_ref = volume_utils.find_sr_by_uuid(session, sr_uuid) - # Try introducing SR if it is not present + if not sr_ref: sr_ref = volume_utils.introduce_sr(session, sr_uuid, label, sr_params) if sr_ref is None: raise exception.NovaException(_('SR not present and could not be ' 'introduced')) - else: - if 'vdi_uuid' in dev_params: - session.call_xenapi("SR.scan", sr_ref) - vdis = {'root': dict(uuid=dev_params['vdi_uuid'], - file=None, osvol=True)} - else: - try: - vdi_ref = volume_utils.introduce_vdi(session, sr_ref) - vdi_rec = session.call_xenapi("VDI.get_record", vdi_ref) - vdis = {'root': dict(uuid=vdi_rec['uuid'], - file=None, osvol=True)} - except volume_utils.StorageError, exc: - LOG.exception(exc) - volume_utils.forget_sr(session, sr_uuid) - return vdis + vdi_uuid = None + + if 'vdi_uuid' in connection_data: + session.call_xenapi("SR.scan", sr_ref) + vdi_uuid = connection_data['vdi_uuid'] + else: + try: + vdi_ref = volume_utils.introduce_vdi(session, sr_ref) + vdi_rec = session.call_xenapi("VDI.get_record", vdi_ref) + vdi_uuid = vdi_rec['uuid'] + except volume_utils.StorageError, exc: + LOG.exception(exc) + volume_utils.forget_sr(session, sr_uuid) -def _volume_in_mapping(mount_device, block_device_info): - block_device_list = [block_device.strip_prefix(vol['mount_device']) - for vol in - driver.block_device_info_get_mapping( - block_device_info)] - swap = driver.block_device_info_get_swap(block_device_info) - if driver.swap_is_usable(swap): - swap_dev = swap['device_name'] - block_device_list.append(block_device.strip_prefix(swap_dev)) - block_device_list += [block_device.strip_prefix(ephemeral['device_name']) - for ephemeral in - driver.block_device_info_get_ephemerals( - block_device_info)] - LOG.debug(_("block_device_list %s"), block_device_list) - return block_device.strip_prefix(mount_device) in block_device_list + return vdi_uuid def get_vdis_for_instance(context, session, instance, name_label, image, image_type, block_device_info=None): + vdis = {} + if block_device_info: LOG.debug(_("block device info: %s"), block_device_info) - rootdev = block_device_info['root_device_name'] - if _volume_in_mapping(rootdev, block_device_info): - # call function to return the vdi in connection info of block - # device. - # make it a point to return from here. - bdm_root_dev = block_device_info['block_device_mapping'][0] - dev_params = bdm_root_dev['connection_info']['data'] - LOG.debug(dev_params) - return get_vdis_for_boot_from_vol(session, dev_params) - return _create_image(context, session, instance, name_label, image, - image_type) + root_device_name = block_device_info['root_device_name'] + + for bdm in block_device_info['block_device_mapping']: + if (block_device.strip_prefix(bdm['mount_device']) == + block_device.strip_prefix(root_device_name)): + # If we're a root-device, record that fact so we don't download + # a root image via Glance + type_ = 'root' + else: + # Otherwise, use mount_device as `type_` so that we have easy + # access to it in _attach_disks to create the VBD + type_ = bdm['mount_device'] + + connection_data = bdm['connection_info']['data'] + vdi_uuid = get_vdi_uuid_for_volume(session, connection_data) + if vdi_uuid: + vdis[type_] = dict(uuid=vdi_uuid, file=None, osvol=True) + + # If we didn't get a root VDI from volumes, then use the Glance image as + # the root device + if 'root' not in vdis: + create_image_vdis = _create_image( + context, session, instance, name_label, image, image_type) + vdis.update(create_image_vdis) + + return vdis @contextlib.contextmanager @@ -722,35 +714,6 @@ def _find_cached_image(session, image_id, sr_ref): return cached_images.get(image_id) -def upload_image(context, session, instance, vdi_uuids, image_id): - """Requests that the Glance plugin bundle the specified VDIs and - push them into Glance using the specified human-friendly name. - """ - # NOTE(sirp): Currently we only support uploading images as VHD, there - # is no RAW equivalent (yet) - LOG.debug(_("Asking xapi to upload %(vdi_uuids)s as" - " ID %(image_id)s"), locals(), instance=instance) - - glance_api_servers = glance.get_api_servers() - glance_host, glance_port, glance_use_ssl = glance_api_servers.next() - - properties = { - 'auto_disk_config': instance['auto_disk_config'], - 'os_type': instance['os_type'] or CONF.default_os_type, - } - - params = {'vdi_uuids': vdi_uuids, - 'image_id': image_id, - 'glance_host': glance_host, - 'glance_port': glance_port, - 'glance_use_ssl': glance_use_ssl, - 'sr_path': get_sr_path(session), - 'auth_token': getattr(context, 'auth_token', None), - 'properties': properties} - - session.call_plugin_serialized('glance', 'upload_vhd', **params) - - def resize_disk(session, instance, vdi_ref, instance_type): # Copy VDI over to something we can resize # NOTE(jerdfelt): Would be nice to just set vdi_ref to read/write @@ -994,7 +957,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 +1050,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 +1473,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) @@ -1596,7 +1559,7 @@ def _find_iso_sr(session): if not sr_rec['content_type'] == 'iso': LOG.debug(_("ISO: not iso content")) continue - if not 'i18n-key' in sr_rec['other_config']: + if 'i18n-key' not in sr_rec['other_config']: LOG.debug(_("ISO: iso content_type, no 'i18n-key' key")) continue if not sr_rec['other_config']['i18n-key'] == 'local-storage-iso': diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 4a8372cda..8a76f3368 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -26,6 +26,7 @@ import time from eventlet import greenthread import netaddr +from nova import block_device from nova.compute import api as compute from nova.compute import power_state from nova.compute import task_states @@ -59,7 +60,10 @@ xenapi_vmops_opts = [ 'to go to running state'), cfg.StrOpt('xenapi_vif_driver', default='nova.virt.xenapi.vif.XenAPIBridgeDriver', - help='The XenAPI VIF driver using XenServer Network APIs.') + help='The XenAPI VIF driver using XenServer Network APIs.'), + cfg.StrOpt('xenapi_image_upload_handler', + default='nova.virt.xenapi.imageupload.glance.GlanceStore', + help='Object Store Driver used to handle image uploads.'), ] CONF = cfg.CONF @@ -161,6 +165,11 @@ class VMOps(object): self.vif_driver = vif_impl(xenapi_session=self._session) self.default_root_dev = '/dev/sda' + msg = _("Importing image upload handler: %s") + LOG.debug(msg % CONF.xenapi_image_upload_handler) + self.image_upload_handler = importutils.import_object( + CONF.xenapi_image_upload_handler) + @property def agent_enabled(self): return not CONF.xenapi_disable_agent @@ -183,7 +192,7 @@ class VMOps(object): def confirm_migration(self, migration, instance, network_info): name_label = self._get_orig_vm_name_label(instance) vm_ref = vm_utils.lookup(self._session, name_label) - return self._destroy(instance, vm_ref, network_info) + return self._destroy(instance, vm_ref, network_info=network_info) def _attach_mapped_block_devices(self, instance, block_device_info): # We are attaching these volumes before start (no hotplugging) @@ -294,7 +303,7 @@ class VMOps(object): def create_disks_step(undo_mgr, disk_image_type, image_meta): vdis = self._create_disks(context, instance, name_label, disk_image_type, image_meta, - block_device_info) + block_device_info=block_device_info) def undo_create_disks(): vdi_refs = [vdi['ref'] for vdi in vdis.values() @@ -338,7 +347,7 @@ class VMOps(object): vdis, disk_image_type, kernel_file, ramdisk_file) def undo_create_vm(): - self._destroy(instance, vm_ref, network_info) + self._destroy(instance, vm_ref, network_info=network_info) undo_mgr.undo_with(undo_create_vm) return vm_ref @@ -503,8 +512,9 @@ class VMOps(object): ctx = nova_context.get_admin_context() instance_type = instance['instance_type'] - # DISK_ISO needs two VBDs: the ISO disk and a blank RW disk + # Attach (required) root disk if disk_image_type == vm_utils.ImageType.DISK_ISO: + # DISK_ISO needs two VBDs: the ISO disk and a blank RW disk LOG.debug(_("Detected ISO image type, creating blank VM " "for install"), instance=instance) @@ -532,6 +542,19 @@ class VMOps(object): DEVICE_ROOT, bootable=True, osvol=root_vdi.get('osvol')) + # Attach (optional) additional block-devices + for type_, vdi_info in vdis.items(): + # Additional block-devices for boot use their device-name as the + # type. + if not type_.startswith('/dev'): + continue + + # Convert device name to userdevice number, e.g. /dev/xvdb -> 1 + userdevice = ord(block_device.strip_prefix(type_)) - ord('a') + vm_utils.create_vbd(self._session, vm_ref, vdi_info['ref'], + userdevice, bootable=False, + osvol=vdi_info.get('osvol')) + # Attach (optional) swap disk swap_mb = instance_type['swap'] if swap_mb: @@ -661,9 +684,11 @@ class VMOps(object): coalesce together, so, we must wait for this coalescing to occur to get a stable representation of the data on disk. - 3. Push-to-glance: Once coalesced, we call a plugin on the XenServer - that will bundle the VHDs together and then push the bundle into - Glance. + 3. Push-to-data-store: Once coalesced, we call a plugin on the + XenServer that will bundle the VHDs together and then push the + bundle. Depending on the configured value of + 'xenapi_image_upload_handler', image data may be pushed to + Glance or the specified data store. """ vm_ref = self._get_vm_opaque_ref(instance) @@ -674,8 +699,11 @@ class VMOps(object): update_task_state) as vdi_uuids: update_task_state(task_state=task_states.IMAGE_UPLOADING, expected_state=task_states.IMAGE_PENDING_UPLOAD) - vm_utils.upload_image( - context, self._session, instance, vdi_uuids, image_id) + self.image_upload_handler.upload_image(context, + self._session, + instance, + vdi_uuids, + image_id) LOG.debug(_("Finished snapshot and upload for VM"), instance=instance) @@ -998,7 +1026,7 @@ class VMOps(object): raise exception.NotFound(_("Unable to find root VBD/VDI for VM")) - def _detach_vm_vols(self, instance, vm_ref, block_device_info=None): + def _detach_vm_vols(self, instance, vm_ref): """Detach any external nova/cinder volumes and purge the SRs. This differs from a normal detach in that the VM has been shutdown, so there is no need for unplugging VBDs. They do @@ -1020,7 +1048,7 @@ class VMOps(object): LOG.exception(exc) raise - def _destroy_vdis(self, instance, vm_ref, block_device_info=None): + def _destroy_vdis(self, instance, vm_ref): """Destroys all VDIs associated with a VM.""" LOG.debug(_("Destroying VDIs"), instance=instance) @@ -1102,12 +1130,14 @@ class VMOps(object): if rescue_vm_ref: self._destroy_rescue_instance(rescue_vm_ref, vm_ref) - return self._destroy(instance, vm_ref, network_info, - block_device_info=block_device_info, + # NOTE(sirp): `block_device_info` is not used, information about which + # volumes should be detached is determined by the + # VBD.other_config['osvol'] attribute + return self._destroy(instance, vm_ref, network_info=network_info, destroy_disks=destroy_disks) def _destroy(self, instance, vm_ref, network_info=None, - block_device_info=None, destroy_disks=True): + destroy_disks=True): """Destroys VM instance by performing: 1. A shutdown @@ -1123,10 +1153,9 @@ class VMOps(object): vm_utils.hard_shutdown_vm(self._session, instance, vm_ref) - # Destroy VDIs (if necessary) if destroy_disks: - self._detach_vm_vols(instance, vm_ref, block_device_info) - self._destroy_vdis(instance, vm_ref, block_device_info) + self._detach_vm_vols(instance, vm_ref) + self._destroy_vdis(instance, vm_ref) self._destroy_kernel_ramdisk(instance, vm_ref) vm_utils.destroy_vm(self._session, instance, vm_ref) 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 fccdedac8..05918f83d 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 @@ -48,6 +48,9 @@ cinder_opts = [ cfg.IntOpt('cinder_http_retries', default=3, help='Number of cinderclient retries on failed http calls'), + cfg.BoolOpt('cinder_api_insecure', + default=False, + help='Allow to perform insecure SSL requests to cinder'), ] CONF = cfg.CONF @@ -60,8 +63,10 @@ def cinderclient(context): # FIXME: the cinderclient ServiceCatalog object is mis-named. # It actually contains the entire access blob. + # Only needed parts of the service catalog are passed in, see + # nova/context.py. compat_catalog = { - 'access': {'serviceCatalog': context.service_catalog or {}} + 'access': {'serviceCatalog': context.service_catalog or []} } sc = service_catalog.ServiceCatalog(compat_catalog) if CONF.cinder_endpoint_template: @@ -88,6 +93,7 @@ def cinderclient(context): context.auth_token, project_id=context.project_id, auth_url=url, + insecure=CONF.cinder_api_insecure, retries=CONF.cinder_http_retries) # noauth extracts user_id:project_id from auth_token c.client.auth_token = context.auth_token or '%s:%s' % (context.user_id, @@ -135,7 +141,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/openstack-common.conf b/openstack-common.conf index ea33ab235..f9d38ea1d 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=cfg,cliutils,context,excutils,eventlet_backdoor,fileutils,gettextutils,importutils,iniparser,jsonutils,local,lockutils,log,network_utils,notifier,plugin,policy,setup,timeutils,rpc,uuidutils +modules=cfg,cliutils,context,excutils,eventlet_backdoor,fileutils,gettextutils,importutils,iniparser,jsonutils,local,lockutils,log,network_utils,notifier,plugin,policy,rootwrap,setup,timeutils,rpc,uuidutils,install_venv_common # The base module to hold the copy of openstack.common base=nova 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 3a579ca36..3bf996fa2 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='$testropts $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,14 +125,14 @@ 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"`" srcfiles+=" setup.py" # Until all these issues get fixed, ignore. - ignore='--ignore=E12,E711,E721,E712' + ignore='--ignore=E12,E711,E721,E712,N403,N404' # First run the hacking selftest, to make sure it's right echo "Running hacking.py self test" @@ -131,6 +142,7 @@ function run_pep8 { echo "Running pep8" ${wrapper} python tools/hacking.py ${ignore} ${srcfiles} + ${wrapper} bash tools/unused_imports.sh # NOTE(sdague): as of grizzly-2 these are passing however leaving the comment # in here in case we need to break it out when we get more of our hacking working # again. @@ -143,7 +155,7 @@ function run_pep8 { } -TESTRTESTS="testr run --parallel $testropts" +TESTRTESTS="python setup.py testr" if [ $never_venv -eq 0 ] then @@ -197,10 +209,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 @@ -66,6 +66,7 @@ setuptools.setup(name='nova', 'bin/nova-objectstore', 'bin/nova-rootwrap', 'bin/nova-scheduler', + 'bin/nova-spicehtml5proxy', 'bin/nova-xvpvncproxy', ], py_modules=[]) diff --git a/smoketests/base.py b/smoketests/base.py index f6cec3168..c90da102c 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'], @@ -169,7 +169,6 @@ class SmokeTestCase(unittest.TestCase): cmd += ' --kernel true' status, output = commands.getstatusoutput(cmd) if status != 0: - print '%s -> \n %s' % (cmd, output) raise Exception(output) return True @@ -178,7 +177,6 @@ class SmokeTestCase(unittest.TestCase): cmd += '%s -m %s/%s.manifest.xml' % (bucket_name, tempdir, image) status, output = commands.getstatusoutput(cmd) if status != 0: - print '%s -> \n %s' % (cmd, output) raise Exception(output) return True @@ -186,7 +184,6 @@ class SmokeTestCase(unittest.TestCase): cmd = 'euca-delete-bundle --clear -b %s' % (bucket_name) status, output = commands.getstatusoutput(cmd) if status != 0: - print '%s -> \n%s' % (cmd, output) raise Exception(output) return True diff --git a/smoketests/public_network_smoketests.py b/smoketests/public_network_smoketests.py index 4fb843e0f..f20b0923e 100644 --- a/smoketests/public_network_smoketests.py +++ b/smoketests/public_network_smoketests.py @@ -97,7 +97,6 @@ class InstanceTestsFromPublic(base.UserSmokeTestCase): self.data['ip_v6'], TEST_KEY) conn.close() except Exception as ex: - print ex time.sleep(1) else: break 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/flakes.py b/tools/flakes.py index 4b93abc21..f805fd156 100644 --- a/tools/flakes.py +++ b/tools/flakes.py @@ -8,7 +8,7 @@ import __builtin__ import os import sys -from pyflakes.scripts.pyflakes import main +from pyflakes.scripts import pyflakes if __name__ == "__main__": names = os.environ.get('PYFLAKES_BUILTINS', '_') @@ -19,4 +19,4 @@ if __name__ == "__main__": del names, os, __builtin__ - sys.exit(main()) + sys.exit(pyflakes.main()) diff --git a/tools/hacking.py b/tools/hacking.py index ed22956eb..801a87899 100755 --- a/tools/hacking.py +++ b/tools/hacking.py @@ -18,10 +18,9 @@ """nova HACKING file compliance testing -built on top of pep8.py +Built on top of pep8.py """ -import fnmatch import inspect import logging import os @@ -46,16 +45,17 @@ logging.disable('LOG') #N8xx git commit messages IMPORT_EXCEPTIONS = ['sqlalchemy', 'migrate', 'nova.db.sqlalchemy.session'] -DOCSTRING_TRIPLE = ['"""', "'''"] +START_DOCSTRING_TRIPLE = ['u"""', 'r"""', '"""', "u'''", "r'''", "'''"] +END_DOCSTRING_TRIPLE = ['"""', "'''"] VERBOSE_MISSING_IMPORT = os.getenv('HACKING_VERBOSE_MISSING_IMPORT', 'False') +_missingImport = set([]) + # Monkey patch broken excluded filter in pep8 # See https://github.com/jcrocholl/pep8/pull/111 def excluded(self, filename): - """ - Check if options.exclude contains a pattern that matches filename. - """ + """Check if options.exclude contains a pattern that matches filename.""" basename = os.path.basename(filename) return any((pep8.filename_match(filename, self.options.exclude, default=False), @@ -105,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: @@ -113,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)" @@ -167,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. @@ -177,19 +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): - """ + # 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: @@ -197,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)) @@ -210,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" @@ -220,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: @@ -235,31 +234,34 @@ 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 def nova_import_alphabetical(logical_line, blank_lines, previous_logical, indent_level, previous_indent_level): - r""" - Check for imports in alphabetical order. + r"""Check for imports in alphabetical order. nova HACKING guide recommendation for imports: imports in human alphabetical order @@ -294,6 +296,11 @@ def nova_import_no_db_in_virt(logical_line, filename): yield (0, "N307: nova.db import not allowed in nova/virt/*") +def in_docstring_position(previous_logical): + return (previous_logical.startswith("def ") or + previous_logical.startswith("class ")) + + def nova_docstring_start_space(physical_line, previous_logical): r"""Check for docstring not start with space. @@ -311,11 +318,10 @@ def nova_docstring_start_space(physical_line, previous_logical): # it's important that we determine this is actually a docstring, # and not a doc block used somewhere after the first line of a # function def - if (previous_logical.startswith("def ") or - previous_logical.startswith("class ")): - pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) - if (pos != -1 and len(physical_line) > pos + 4): - if (physical_line[pos + 3] == ' '): + if in_docstring_position(previous_logical): + pos = max([physical_line.find(i) for i in START_DOCSTRING_TRIPLE]) + if pos != -1 and len(physical_line) > pos + 4: + if physical_line[pos + 3] == ' ': return (pos, "N401: docstring should not start with" " a space") @@ -327,36 +333,68 @@ 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,''' """ + #TODO(jogo) make this apply to multi line docstrings as well line = physical_line.lstrip() if line.startswith('"') or line.startswith("'"): - pos = max([line.find(i) for i in DOCSTRING_TRIPLE]) # start - end = max([line[-4:-1] == i for i in DOCSTRING_TRIPLE]) # end + pos = max([line.find(i) for i in START_DOCSTRING_TRIPLE]) # start + end = max([line[-4:-1] == i for i in END_DOCSTRING_TRIPLE]) # end - if (pos != -1 and end and len(line) > pos + 4): - if (line[-5] not in ['.', '?', '!']): + if pos != -1 and end and len(line) > pos + 4: + if line[-5] not in ['.', '?', '!']: return pos, "N402: one line docstring needs punctuation." -def nova_docstring_multiline_end(physical_line): +def nova_docstring_multiline_end(physical_line, previous_logical): r"""Check multi line docstring end. nova HACKING guide recommendation for docstring: Docstring should end on a new line - Okay: '''\nfoo\nbar\n''' - # This test is not triggered, don't think it's right, removing - # the colon prevents it from running - N403 '''\nfoo\nbar\n ''' \n\n + Okay: '''foobar\nfoo\nbar\n''' + N403: def foo():\n'''foobar\nfoo\nbar\n d'''\n\n """ - # TODO(sdague) actually get these tests working - pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) # start - if (pos != -1 and len(physical_line) == pos): - if (physical_line[pos + 3] == ' '): - return (pos, "N403: multi line docstring end on new line") + if in_docstring_position(previous_logical): + pos = max(physical_line.find(i) for i in END_DOCSTRING_TRIPLE) + if pos != -1 and len(physical_line) == pos + 4: + if physical_line.strip() not in START_DOCSTRING_TRIPLE: + return (pos, "N403: multi line docstring end on new line") + + +def nova_docstring_multiline_start(physical_line, previous_logical, tokens): + r"""Check multi line docstring start with summary. + + nova HACKING guide recommendation for docstring: + Docstring should start with A multi line docstring has a one-line summary + + Okay: '''foobar\nfoo\nbar\n''' + N404: def foo():\n'''\nfoo\nbar\n''' \n\n + """ + if in_docstring_position(previous_logical): + pos = max([physical_line.find(i) for i in START_DOCSTRING_TRIPLE]) + # start of docstring when len(tokens)==0 + if len(tokens) == 0 and pos != -1 and len(physical_line) == pos + 4: + if physical_line.strip() in START_DOCSTRING_TRIPLE: + return (pos, "N404: multi line docstring " + "should start with a summary") + + +def nova_no_cr(physical_line): + r"""Check that we only use newlines not cariage returns. + + Okay: import os\nimport sys + # pep8 doesn't yet replace \r in strings, will work on an + # upstream fix + N901 import os\r\nimport sys + """ + pos = physical_line.find('\r') + if pos != -1 and pos == (len(physical_line) - 2): + return (pos, "N901: Windows style line endings not allowed in code") FORMAT_RE = re.compile("%(?:" diff --git a/tools/install_venv.py b/tools/install_venv.py index b1ceb74f0..abf56ea0e 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -1,4 +1,3 @@ - # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the @@ -6,6 +5,7 @@ # All Rights Reserved. # # Copyright 2010 OpenStack, LLC +# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain @@ -19,186 +19,10 @@ # License for the specific language governing permissions and limitations # under the License. -"""Installation script for Nova's development virtualenv.""" - -import optparse import os -import subprocess import sys - -ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -VENV = os.path.join(ROOT, '.venv') -PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires') -TEST_REQUIRES = os.path.join(ROOT, 'tools', 'test-requires') -PY_VERSION = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) - - -def die(message, *args): - print >> sys.stderr, message % args - sys.exit(1) - - -def check_python_version(): - if sys.version_info < (2, 6): - die("Need Python Version >= 2.6") - - -def run_command_with_code(cmd, redirect_output=True, check_exit_code=True): - """Runs a command in an out-of-process shell. - - Returns the output of that command. Working directory is ROOT. - """ - if redirect_output: - stdout = subprocess.PIPE - else: - stdout = None - - proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout) - output = proc.communicate()[0] - if check_exit_code and proc.returncode != 0: - die('Command "%s" failed.\n%s', ' '.join(cmd), output) - return (output, proc.returncode) - - -def run_command(cmd, redirect_output=True, check_exit_code=True): - return run_command_with_code(cmd, redirect_output, check_exit_code)[0] - - -class Distro(object): - - def check_cmd(self, cmd): - return bool(run_command(['which', cmd], check_exit_code=False).strip()) - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if self.check_cmd('easy_install'): - print 'Installing virtualenv via easy_install...', - if run_command(['easy_install', 'virtualenv']): - print 'Succeeded' - return - else: - print 'Failed' - - die('ERROR: virtualenv not found.\n\nNova development' - ' requires virtualenv, please install it using your' - ' favorite package management tool') - - def post_process(self): - """Any distribution-specific post-processing gets done here. - - In particular, this is useful for applying patches to code inside - the venv. - """ - pass - - -class Fedora(Distro): - """This covers all Fedora-based distributions. - - Includes: Fedora, RHEL, CentOS, Scientific Linux""" - - def check_pkg(self, pkg): - return run_command_with_code(['rpm', '-q', pkg], - check_exit_code=False)[1] == 0 - - def yum_install(self, pkg, **kwargs): - print "Attempting to install '%s' via yum" % pkg - run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) - - def apply_patch(self, originalfile, patchfile): - run_command(['patch', originalfile, patchfile]) - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if not self.check_pkg('python-virtualenv'): - self.yum_install('python-virtualenv', check_exit_code=False) - - super(Fedora, self).install_virtualenv() - - def post_process(self): - """Workaround for a bug in eventlet. - - This currently affects RHEL6.1, but the fix can safely be - applied to all RHEL and Fedora distributions. - - This can be removed when the fix is applied upstream. - - Nova: https://bugs.launchpad.net/nova/+bug/884915 - Upstream: https://bitbucket.org/which_linden/eventlet/issue/89 - """ - - # Install "patch" program if it's not there - if not self.check_pkg('patch'): - self.yum_install('patch') - - # Apply the eventlet patch - self.apply_patch(os.path.join(VENV, 'lib', PY_VERSION, 'site-packages', - 'eventlet/green/subprocess.py'), - 'contrib/redhat-eventlet.patch') - - -def get_distro(): - if (os.path.exists('/etc/fedora-release') or - os.path.exists('/etc/redhat-release')): - return Fedora() - else: - return Distro() - - -def check_dependencies(): - get_distro().install_virtualenv() - - -def create_virtualenv(venv=VENV, no_site_packages=True): - """Creates the virtual environment and installs PIP. - - Creates the virtual environment and installs PIP only into the - virtual environment. - """ - print 'Creating venv...', - if no_site_packages: - run_command(['virtualenv', '-q', '--no-site-packages', VENV]) - else: - run_command(['virtualenv', '-q', VENV]) - print 'done.' - print 'Installing pip in virtualenv...', - if not run_command(['tools/with_venv.sh', 'easy_install', - 'pip>1.0']).strip(): - die("Failed to install pip.") - print 'done.' - - -def pip_install(*args): - run_command(['tools/with_venv.sh', - 'pip', 'install', '--upgrade'] + list(args), - redirect_output=False) - - -def install_dependencies(venv=VENV): - print 'Installing dependencies with pip (this can take a while)...' - - # First things first, make sure our venv has the latest pip and distribute. - # NOTE: we keep pip at version 1.1 since the most recent version causes - # the .venv creation to fail. See: - # https://bugs.launchpad.net/nova/+bug/1047120 - pip_install('pip==1.1') - pip_install('distribute') - - # Install greenlet by hand - just listing it in the requires file does not - # get it in stalled in the right order - pip_install('greenlet') - - pip_install('-r', PIP_REQUIRES) - pip_install('-r', TEST_REQUIRES) - - -def post_process(): - get_distro().post_process() +import install_venv_common as install_venv def print_help(): @@ -223,22 +47,21 @@ def print_help(): print help -def parse_args(): - """Parses command-line arguments.""" - parser = optparse.OptionParser() - parser.add_option("-n", "--no-site-packages", dest="no_site_packages", - default=False, action="store_true", - help="Do not inherit packages from global Python install") - return parser.parse_args() - - def main(argv): - (options, args) = parse_args() - check_python_version() - check_dependencies() - create_virtualenv(no_site_packages=options.no_site_packages) - install_dependencies() - post_process() + root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + venv = os.path.join(root, '.venv') + pip_requires = os.path.join(root, 'tools', 'pip-requires') + test_requires = os.path.join(root, 'tools', 'test-requires') + py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) + project = 'Nova' + install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, + py_version, project) + options = install.parse_args(argv) + install.check_python_version() + install.check_dependencies() + install.create_virtualenv(no_site_packages=options.no_site_packages) + install.install_dependencies() + install.post_process() print_help() if __name__ == '__main__': diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py new file mode 100644 index 000000000..b15011a00 --- /dev/null +++ b/tools/install_venv_common.py @@ -0,0 +1,225 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack, LLC +# Copyright 2013 IBM Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Provides methods needed by installation script for OpenStack development +virtual environments. + +Synced in from openstack-common +""" + +import os +import subprocess +import sys + +from nova.openstack.common import cfg + + +class InstallVenv(object): + + def __init__(self, root, venv, pip_requires, test_requires, py_version, + project): + self.root = root + self.venv = venv + self.pip_requires = pip_requires + self.test_requires = test_requires + self.py_version = py_version + self.project = project + + def die(self, message, *args): + print >> sys.stderr, message % args + sys.exit(1) + + def check_python_version(self): + if sys.version_info < (2, 6): + self.die("Need Python Version >= 2.6") + + def run_command_with_code(self, cmd, redirect_output=True, + check_exit_code=True): + """Runs a command in an out-of-process shell. + + Returns the output of that command. Working directory is ROOT. + """ + if redirect_output: + stdout = subprocess.PIPE + else: + stdout = None + + proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) + output = proc.communicate()[0] + if check_exit_code and proc.returncode != 0: + self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) + return (output, proc.returncode) + + def run_command(self, cmd, redirect_output=True, check_exit_code=True): + return self.run_command_with_code(cmd, redirect_output, + check_exit_code)[0] + + def get_distro(self): + if (os.path.exists('/etc/fedora-release') or + os.path.exists('/etc/redhat-release')): + return Fedora(self.root, self.venv, self.pip_requires, + self.test_requires, self.py_version, self.project) + else: + return Distro(self.root, self.venv, self.pip_requires, + self.test_requires, self.py_version, self.project) + + def check_dependencies(self): + self.get_distro().install_virtualenv() + + def create_virtualenv(self, no_site_packages=True): + """Creates the virtual environment and installs PIP. + + Creates the virtual environment and installs PIP only into the + virtual environment. + """ + if not os.path.isdir(self.venv): + print 'Creating venv...', + if no_site_packages: + self.run_command(['virtualenv', '-q', '--no-site-packages', + self.venv]) + else: + self.run_command(['virtualenv', '-q', self.venv]) + print 'done.' + print 'Installing pip in virtualenv...', + if not self.run_command(['tools/with_venv.sh', 'easy_install', + 'pip>1.0']).strip(): + self.die("Failed to install pip.") + print 'done.' + else: + print "venv already exists..." + pass + + def pip_install(self, *args): + self.run_command(['tools/with_venv.sh', + 'pip', 'install', '--upgrade'] + list(args), + redirect_output=False) + + def install_dependencies(self): + print 'Installing dependencies with pip (this can take a while)...' + + # First things first, make sure our venv has the latest pip and + # distribute. + # NOTE: we keep pip at version 1.1 since the most recent version causes + # the .venv creation to fail. See: + # https://bugs.launchpad.net/nova/+bug/1047120 + self.pip_install('pip==1.1') + self.pip_install('distribute') + + # Install greenlet by hand - just listing it in the requires file does + # not + # get it installed in the right order + self.pip_install('greenlet') + + self.pip_install('-r', self.pip_requires) + self.pip_install('-r', self.test_requires) + + def post_process(self): + self.get_distro().post_process() + + def parse_args(self, argv): + """Parses command-line arguments.""" + cli_opts = [ + cfg.BoolOpt('no-site-packages', + default=False, + short='n', + help="Do not inherit packages from global Python" + "install"), + ] + CLI = cfg.ConfigOpts() + CLI.register_cli_opts(cli_opts) + CLI(argv[1:]) + return CLI + + +class Distro(InstallVenv): + + def check_cmd(self, cmd): + return bool(self.run_command(['which', cmd], + check_exit_code=False).strip()) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if self.check_cmd('easy_install'): + print 'Installing virtualenv via easy_install...', + if self.run_command(['easy_install', 'virtualenv']): + print 'Succeeded' + return + else: + print 'Failed' + + self.die('ERROR: virtualenv not found.\n\n%s development' + ' requires virtualenv, please install it using your' + ' favorite package management tool' % self.project) + + def post_process(self): + """Any distribution-specific post-processing gets done here. + + In particular, this is useful for applying patches to code inside + the venv. + """ + pass + + +class Fedora(Distro): + """This covers all Fedora-based distributions. + + Includes: Fedora, RHEL, CentOS, Scientific Linux + """ + + def check_pkg(self, pkg): + return self.run_command_with_code(['rpm', '-q', pkg], + check_exit_code=False)[1] == 0 + + def yum_install(self, pkg, **kwargs): + print "Attempting to install '%s' via yum" % pkg + self.run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) + + def apply_patch(self, originalfile, patchfile): + self.run_command(['patch', originalfile, patchfile]) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if not self.check_pkg('python-virtualenv'): + self.yum_install('python-virtualenv', check_exit_code=False) + + super(Fedora, self).install_virtualenv() + + def post_process(self): + """Workaround for a bug in eventlet. + + This currently affects RHEL6.1, but the fix can safely be + applied to all RHEL and Fedora distributions. + + This can be removed when the fix is applied upstream. + + Nova: https://bugs.launchpad.net/nova/+bug/884915 + Upstream: https://bitbucket.org/which_linden/eventlet/issue/89 + """ + + # Install "patch" program if it's not there + if not self.check_pkg('patch'): + self.yum_install('patch') + + # Apply the eventlet patch + self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, + 'site-packages', + 'eventlet/green/subprocess.py'), + 'contrib/redhat-eventlet.patch') 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/pip-requires b/tools/pip-requires index 1845ba7dd..126f0125c 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -20,8 +20,9 @@ Babel>=0.9.6 iso8601>=0.1.4 httplib2 setuptools_git>=0.4 -python-cinderclient +python-cinderclient>=1.0.1 python-quantumclient>=2.1 python-glanceclient>=0.5.0,<2 python-keystoneclient>=0.2.0 stevedore>=0.7 +websockify diff --git a/tools/test-requires b/tools/test-requires index 6ee42d31c..fce1bc8f1 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -1,7 +1,7 @@ # Packages needed for dev testing distribute>=0.6.24 -coverage +coverage>=3.6 discover feedparser fixtures>=0.3.12 @@ -11,5 +11,5 @@ pep8==1.3.3 pylint==0.25.2 python-subunit sphinx>=1.1.2 -testrepository>=0.0.8 -testtools>=0.9.22 +testrepository>=0.0.13 +testtools>=0.9.27 diff --git a/tools/unused_imports.sh b/tools/unused_imports.sh new file mode 100755 index 000000000..0e0294517 --- /dev/null +++ b/tools/unused_imports.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +#snakefood sfood-checker detects even more unused imports +! pyflakes nova/ | grep "imported but unused" 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") @@ -8,21 +8,23 @@ setenv = VIRTUAL_ENV={envdir} LC_ALL=C deps = -r{toxinidir}/tools/pip-requires -r{toxinidir}/tools/test-requires -commands = bash -c 'if [ ! -d ./.testrepository ] ; then testr init ; fi' - bash -c 'testr run --parallel {posargs} ; RET=$? ; echo "Slowest Tests" ; testr slowest && exit $RET' +commands = python setup.py testr --slowest --testr-args='{posargs}' [tox:jenkins] sitepackages = True downloadcache = ~/cache/pip [testenv:pep8] -deps=pep8==1.3.3 +deps= + pep8==1.3.3 + pyflakes commands = python tools/hacking.py --doctest - python tools/hacking.py --ignore=E12,E711,E721,E712 --repeat --show-source \ + python tools/hacking.py --ignore=E12,E711,E721,E712,N403,N404 --show-source \ --exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg . - python tools/hacking.py --ignore=E12,E711,E721,E712 --repeat --show-source \ + python tools/hacking.py --ignore=E12,E711,E721,E712,N403,N404 --show-source \ --filename=nova* bin + bash tools/unused_imports.sh [testenv:pylint] setenv = VIRTUAL_ENV={envdir} @@ -35,13 +37,11 @@ deps = pyflakes commands = python tools/flakes.py nova [testenv:cover] -# Need to omit DynamicallyCompiledCheetahTemplate.py from coverage because -# it ceases to exist post test run. Also do not run test_coverage_ext tests -# while gathering coverage as those tests conflict with coverage. -setenv = OMIT=--omit=DynamicallyCompiledCheetahTemplate.py - PYTHON=coverage run --source nova --parallel-mode -commands = bash -c 'if [ ! -d ./.testrepository ] ; then testr init ; fi' - bash -c 'testr run --parallel \^\(\?\!\.\*test_coverage_ext\)\.\*\$ ; RET=$? ; coverage combine ; coverage html -d ./cover $OMIT && exit $RET' +# Also do not run test_coverage_ext tests while gathering coverage as those +# tests conflict with coverage. +commands = + python setup.py testr --coverage \ + --testr-args='^(?!.*test.*coverage).*$' [testenv:venv] commands = {posargs} |