diff options
526 files changed, 8941 insertions, 4013 deletions
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-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 477510b99..617e2411d 100755 --- a/bin/nova-novncproxy +++ b/bin/nova-novncproxy @@ -16,16 +16,16 @@ # 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 os import sys from nova import config -from nova.console import websocketproxy as ws +from nova.console import websocketproxy from nova.openstack.common import cfg @@ -77,7 +77,8 @@ if __name__ == '__main__': sys.exit(-1) # Create and start the NovaWebSockets proxy - server = ws.NovaWebSocketProxy(listen_host=CONF.novncproxy_host, + server = websocketproxy.NovaWebSocketProxy( + listen_host=CONF.novncproxy_host, listen_port=CONF.novncproxy_port, source_is_ipv6=CONF.source_is_ipv6, verbose=CONF.verbose, diff --git a/bin/nova-spicehtml5proxy b/bin/nova-spicehtml5proxy index 089ff9d71..405092942 100755 --- a/bin/nova-spicehtml5proxy +++ b/bin/nova-spicehtml5proxy @@ -16,16 +16,16 @@ # License for the specific language governing permissions and limitations # under the License. -''' +""" Websocket proxy that is compatible with OpenStack Nova SPICE HTML5 consoles. Leverages websockify.py by Joel Martin -''' +""" import os import sys from nova import config -from nova.console import websocketproxy as ws +from nova.console import websocketproxy from nova.openstack.common import cfg @@ -77,7 +77,8 @@ if __name__ == '__main__': sys.exit(-1) # Create and start the NovaWebSockets proxy - server = ws.NovaWebSocketProxy(listen_host=CONF.spicehtml5proxy_host, + server = websocketproxy.NovaWebSocketProxy( + listen_host=CONF.spicehtml5proxy_host, listen_port=CONF.spicehtml5proxy_port, source_is_ipv6=CONF.source_is_ipv6, verbose=CONF.verbose, diff --git a/contrib/boto_v6/ec2/connection.py b/contrib/boto_v6/ec2/connection.py index 940608ffd..4cec65ad8 100644 --- a/contrib/boto_v6/ec2/connection.py +++ b/contrib/boto_v6/ec2/connection.py @@ -6,7 +6,7 @@ Created on 2010/12/20 import base64 import boto import boto.ec2 -from boto.ec2.securitygroup import SecurityGroup +import boto.ec2.securitygroup as securitygroup from boto_v6.ec2.instance import ReservationV6 @@ -114,7 +114,7 @@ class EC2ConnectionV6(boto.ec2.EC2Connection): if security_groups: l = [] for group in security_groups: - if isinstance(group, SecurityGroup): + if isinstance(group, securitygroup.SecurityGroup): l.append(group.name) else: l.append(group) diff --git a/contrib/boto_v6/ec2/instance.py b/contrib/boto_v6/ec2/instance.py index 74adccc00..6f088c67e 100644 --- a/contrib/boto_v6/ec2/instance.py +++ b/contrib/boto_v6/ec2/instance.py @@ -3,31 +3,29 @@ Created on 2010/12/20 @author: Nachi Ueno <ueno.nachi@lab.ntt.co.jp> ''' -from boto.ec2.instance import Group -from boto.ec2.instance import Instance -from boto.ec2.instance import Reservation -from boto.resultset import ResultSet +from boto.ec2 import instance +from boto import resultset -class ReservationV6(Reservation): +class ReservationV6(instance.Reservation): def startElement(self, name, attrs, connection): if name == 'instancesSet': - self.instances = ResultSet([('item', InstanceV6)]) + self.instances = resultset.ResultSet([('item', InstanceV6)]) return self.instances elif name == 'groupSet': - self.groups = ResultSet([('item', Group)]) + self.groups = resultset.ResultSet([('item', instance.Group)]) return self.groups else: return None -class InstanceV6(Instance): +class InstanceV6(instance.Instance): def __init__(self, connection=None): - Instance.__init__(self, connection) + instance.Instance.__init__(self, connection) self.dns_name_v6 = None def endElement(self, name, value, connection): - Instance.endElement(self, name, value, connection) + instance.Instance.endElement(self, name, value, connection) if name == 'dnsNameV6': self.dns_name_v6 = value diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json index bd002c080..604ad6763 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.json +++ b/doc/api_samples/all_extensions/extensions-get-resp.json @@ -89,6 +89,14 @@ "updated": "2012-08-09T00:00:00+00:00" }, { + "alias": "os-baremetal-nodes", + "description": "Admin-only bare-metal node administration.", + "links": [], + "name": "BareMetalNodes", + "namespace": "http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2", + "updated": "2013-01-04T00:00:00+00:00" + }, + { "alias": "os-cells", "description": "Enables cells-related functionality such as adding neighbor cells,\n listing neighbor cells, and getting the capabilities of the local cell.\n ", "links": [], diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml index ebb1c4302..d7f483745 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.xml +++ b/doc/api_samples/all_extensions/extensions-get-resp.xml @@ -37,6 +37,9 @@ <extension alias="os-availability-zone" updated="2012-08-09T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/availabilityzone/api/v1.1" name="AvailabilityZone"> <description>Add availability_zone to the Create Server v1.1 API.</description> </extension> + <extension alias="os-baremetal-nodes" updated="2013-01-04T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2" name="BareMetalNodes"> + <description>Admin-only bare-metal node administration.</description> + </extension> <extension alias="os-cells" updated="2011-09-21T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/cells/api/v1.1" name="Cells"> <description>Enables cells-related functionality such as adding child cells, listing child cells, getting the capabilities of the local cell, diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json new file mode 100644 index 000000000..2e795e483 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.json @@ -0,0 +1,5 @@ +{ + "add_interface": { + "address": "aa:aa:aa:aa:aa:aa" + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml new file mode 100644 index 000000000..63ca9c21e --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-req.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<add_interface + address="aa:aa:aa:aa:aa:aa" +/>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json new file mode 100644 index 000000000..d0b9cc3fb --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.json @@ -0,0 +1,8 @@ +{ + "interface": { + "address": "aa:aa:aa:aa:aa:aa", + "datapath_id": null, + "id": 1, + "port_no": null + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml new file mode 100644 index 000000000..1da1dd284 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-add-interface-resp.xml @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<interface datapath_id="None" id="1" port_no="None" address="aa:aa:aa:aa:aa:aa"/>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.json new file mode 100644 index 000000000..d8b9eb452 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.json @@ -0,0 +1,14 @@ +{ + "node": { + "service_host": "host", + "cpus": 8, + "memory_mb": 8192, + "local_gb": 128, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "pm_password": "pm_pass", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "terminal_port": 8000 + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml new file mode 100644 index 000000000..85c863a97 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-req.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<node + service_host="host" + cpus="8" + memory_mb="8192" + local_gb="128" + pm_address="10.1.2.3" + pm_user="pm_user" + prov_mac_address="12:34:56:78:90:ab" + prov_vlan_id="1234" + terminal_port="8000" +/>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json new file mode 100644 index 000000000..b62a9e663 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.json @@ -0,0 +1,16 @@ +{ + "node": { + "cpus": 8, + "id": 1, + "instance_uuid": null, + "interfaces": [], + "local_gb": 128, + "memory_mb": 8192, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "service_host": "host", + "terminal_port": 8000 + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml new file mode 100644 index 000000000..9b8421f0f --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-create-resp.xml @@ -0,0 +1,15 @@ +<?xml version='1.0' encoding='UTF-8'?> +<node + instance_uuid="None" + pm_address="10.1.2.3" + cpus="8" + prov_vlan_id="1234" + memory_mb="8192" + prov_mac_address="12:34:56:78:90:ab" + service_host="host" + local_gb="128" + id="1" + pm_user="pm_user" + terminal_port="8000"> + <interfaces/> +</node>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json new file mode 100644 index 000000000..d43d580ed --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.json @@ -0,0 +1,25 @@ +{ + "nodes": [ + { + "cpus": 8, + "id": 1, + "instance_uuid": null, + "interfaces": [ + { + "address": "aa:aa:aa:aa:aa:aa", + "datapath_id": null, + "id": 1, + "port_no": null + } + ], + "local_gb": 128, + "memory_mb": 8192, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "service_host": "host", + "terminal_port": 8000 + } + ] +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml new file mode 100644 index 000000000..7cd1b5d8a --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-list-resp.xml @@ -0,0 +1,23 @@ +<?xml version='1.0' encoding='UTF-8'?> +<nodes> + <node + instance_uuid="None" + pm_address="10.1.2.3" + cpus="8" + prov_vlan_id="1234" + memory_mb="8192" + prov_mac_address="12:34:56:78:90:ab" + service_host="host" + local_gb="128" + id="1" + pm_user="pm_user" + terminal_port="8000"> + <interfaces> + <interface + datapath_id="None" + id="1" + port_no="None" + address="aa:aa:aa:aa:aa:aa"/> + </interfaces> + </node> +</nodes>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json new file mode 100644 index 000000000..0ce85577d --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.json @@ -0,0 +1,5 @@ +{ + "remove_interface": { + "address": "aa:aa:aa:aa:aa:aa" + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml new file mode 100644 index 000000000..6457b059b --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-remove-interface-req.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<remove_interface + address="aa:aa:aa:aa:aa:aa" +/>
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json new file mode 100644 index 000000000..d42365752 --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.json @@ -0,0 +1,23 @@ +{ + "node": { + "cpus": 8, + "id": 1, + "instance_uuid": null, + "interfaces": [ + { + "address": "aa:aa:aa:aa:aa:aa", + "datapath_id": null, + "id": 1, + "port_no": null + } + ], + "local_gb": 128, + "memory_mb": 8192, + "pm_address": "10.1.2.3", + "pm_user": "pm_user", + "prov_mac_address": "12:34:56:78:90:ab", + "prov_vlan_id": 1234, + "service_host": "host", + "terminal_port": 8000 + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml new file mode 100644 index 000000000..6d5f9719f --- /dev/null +++ b/doc/api_samples/os-baremetal-nodes/baremetal-node-show-resp.xml @@ -0,0 +1,21 @@ +<?xml version='1.0' encoding='UTF-8'?> +<node + instance_uuid="None" + pm_address="10.1.2.3" + cpus="8" + prov_vlan_id="1234" + memory_mb="8192" + prov_mac_address="12:34:56:78:90:ab" + service_host="host" + local_gb="128" + id="1" + pm_user="pm_user" + terminal_port="8000"> + <interfaces> + <interface + datapath_id="None" + id="1" + port_no="None" + address="aa:aa:aa:aa:aa:aa"/> + </interfaces> +</node>
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-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-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-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/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/compute.filters b/etc/nova/rootwrap.d/compute.filters index e1113a9e7..9562a23aa 100644 --- a/etc/nova/rootwrap.d/compute.filters +++ b/etc/nova/rootwrap.d/compute.filters @@ -174,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/cloud.py b/nova/api/ec2/cloud.py index 31f486b81..48b0f632f 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -250,32 +250,10 @@ class CloudController(object): else: return self._describe_availability_zones(context, **kwargs) - def _get_zones(self, context): - """Return available and unavailable zones.""" - enabled_services = db.service_get_all(context, False) - disabled_services = db.service_get_all(context, True) - enabled_services = availability_zones.set_availability_zones(context, - enabled_services) - disabled_services = availability_zones.set_availability_zones(context, - disabled_services) - - available_zones = [] - for zone in [service['availability_zone'] for service - in enabled_services]: - if not zone in available_zones: - available_zones.append(zone) - - not_available_zones = [] - zones = [service['available_zones'] for service in disabled_services - if service['available_zones'] not in available_zones] - for zone in zones: - if zone not in not_available_zones: - not_available_zones.append(zone) - return (available_zones, not_available_zones) - def _describe_availability_zones(self, context, **kwargs): ctxt = context.elevated() - available_zones, not_available_zones = self._get_zones(ctxt) + available_zones, not_available_zones = \ + availability_zones.get_availability_zones(ctxt) result = [] for zone in available_zones: @@ -291,7 +269,8 @@ class CloudController(object): def _describe_availability_zones_verbose(self, context, **kwargs): ctxt = context.elevated() - available_zones, not_available_zones = self._get_zones(ctxt) + available_zones, not_available_zones = \ + availability_zones.get_availability_zones(ctxt) # Available services enabled_services = db.service_get_all(context, False) diff --git a/nova/api/openstack/compute/contrib/availability_zone.py b/nova/api/openstack/compute/contrib/availability_zone.py index 2955b68eb..6cde5ca64 100644 --- a/nova/api/openstack/compute/contrib/availability_zone.py +++ b/nova/api/openstack/compute/contrib/availability_zone.py @@ -14,14 +14,165 @@ # License for the specific language governing permissions and limitations # under the License +from nova.api.openstack import common from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova import availability_zones +from nova import db +from nova.openstack.common import cfg +from nova.openstack.common import log as logging +from nova import servicegroup + + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF + +authorize_list = extensions.extension_authorizer('compute', + 'availability_zone:list') +authorize_detail = extensions.extension_authorizer('compute', + 'availability_zone:detail') + + +def make_availability_zone(elem): + elem.set('name', 'zoneName') + + zoneStateElem = xmlutil.SubTemplateElement(elem, 'zoneState', + selector='zoneState') + zoneStateElem.set('available') + + hostsElem = xmlutil.SubTemplateElement(elem, 'hosts', selector='hosts') + hostElem = xmlutil.SubTemplateElement(hostsElem, 'host', + selector=xmlutil.get_items) + hostElem.set('name', 0) + + svcsElem = xmlutil.SubTemplateElement(hostElem, 'services', selector=1) + svcElem = xmlutil.SubTemplateElement(svcsElem, 'service', + selector=xmlutil.get_items) + svcElem.set('name', 0) + + svcStateElem = xmlutil.SubTemplateElement(svcElem, 'serviceState', + selector=1) + svcStateElem.set('available') + svcStateElem.set('active') + svcStateElem.set('updated_at') + + # Attach metadata node + elem.append(common.MetadataTemplate()) + + +class AvailabilityZonesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('availabilityZones') + zoneElem = xmlutil.SubTemplateElement(root, 'availabilityZone', + selector='availabilityZoneInfo') + make_availability_zone(zoneElem) + return xmlutil.MasterTemplate(root, 1, nsmap={ + Availability_zone.alias: Availability_zone.namespace}) + + +class AvailabilityZoneController(wsgi.Controller): + """The Availability Zone API controller for the OpenStack API.""" + + def __init__(self): + super(AvailabilityZoneController, self).__init__() + self.servicegroup_api = servicegroup.API() + + def _describe_availability_zones(self, context, **kwargs): + ctxt = context.elevated() + available_zones, not_available_zones = \ + availability_zones.get_availability_zones(ctxt) + + result = [] + for zone in available_zones: + # Hide internal_service_availability_zone + if zone == CONF.internal_service_availability_zone: + continue + result.append({'zoneName': zone, + 'zoneState': {'available': True}, + "hosts": None}) + for zone in not_available_zones: + result.append({'zoneName': zone, + 'zoneState': {'available': False}, + "hosts": None}) + return {'availabilityZoneInfo': result} + + def _describe_availability_zones_verbose(self, context, **kwargs): + ctxt = context.elevated() + available_zones, not_available_zones = \ + availability_zones.get_availability_zones(ctxt) + + # Available services + enabled_services = db.service_get_all(context, False) + enabled_services = availability_zones.set_availability_zones(context, + enabled_services) + zone_hosts = {} + host_services = {} + for service in enabled_services: + zone_hosts.setdefault(service['availability_zone'], []) + if not service['host'] in zone_hosts[service['availability_zone']]: + zone_hosts[service['availability_zone']].append( + service['host']) + + host_services.setdefault(service['availability_zone'] + + service['host'], []) + host_services[service['availability_zone'] + service['host']].\ + append(service) + + result = [] + for zone in available_zones: + hosts = {} + for host in zone_hosts[zone]: + hosts[host] = {} + for service in host_services[zone + host]: + alive = self.servicegroup_api.service_is_up(service) + hosts[host][service['binary']] = {'available': alive, + 'active': True != service['disabled'], + 'updated_at': service['updated_at']} + result.append({'zoneName': zone, + 'zoneState': {'available': True}, + "hosts": hosts}) + + for zone in not_available_zones: + result.append({'zoneName': zone, + 'zoneState': {'available': False}, + "hosts": None}) + return {'availabilityZoneInfo': result} + + @wsgi.serializers(xml=AvailabilityZonesTemplate) + def index(self, req): + """Returns a summary list of availability zone.""" + context = req.environ['nova.context'] + authorize_list(context) + + return self._describe_availability_zones(context) + + @wsgi.serializers(xml=AvailabilityZonesTemplate) + def detail(self, req): + """Returns a detailed list of availability zone.""" + context = req.environ['nova.context'] + authorize_detail(context) + + return self._describe_availability_zones_verbose(context) class Availability_zone(extensions.ExtensionDescriptor): - """Add availability_zone to the Create Server v1.1 API.""" + """1. Add availability_zone to the Create Server v1.1 API. + 2. Add availability zones describing. + """ name = "AvailabilityZone" alias = "os-availability-zone" namespace = ("http://docs.openstack.org/compute/ext/" "availabilityzone/api/v1.1") - updated = "2012-08-09T00:00:00+00:00" + updated = "2012-12-21T00:00:00+00:00" + + def get_resources(self): + resources = [] + + res = extensions.ResourceExtension('os-availability-zone', + AvailabilityZoneController(), + collection_actions={'detail': 'GET'}) + resources.append(res) + + return resources diff --git a/nova/api/openstack/compute/contrib/baremetal_nodes.py b/nova/api/openstack/compute/contrib/baremetal_nodes.py new file mode 100644 index 000000000..38d66d2ae --- /dev/null +++ b/nova/api/openstack/compute/contrib/baremetal_nodes.py @@ -0,0 +1,210 @@ +# Copyright (c) 2013 NTT DOCOMO, INC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""The bare-metal admin extension.""" + +import webob + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova import exception +from nova.openstack.common import log as logging +from nova.virt.baremetal import db + +LOG = logging.getLogger(__name__) +authorize = extensions.extension_authorizer('compute', 'baremetal_nodes') + +node_fields = ['id', 'cpus', 'local_gb', 'memory_mb', 'pm_address', + 'pm_user', 'prov_mac_address', 'prov_vlan_id', + 'service_host', 'terminal_port', 'instance_uuid', + ] + +interface_fields = ['id', 'address', 'datapath_id', 'port_no'] + + +def _node_dict(node_ref): + d = {} + for f in node_fields: + d[f] = node_ref.get(f) + return d + + +def _interface_dict(interface_ref): + d = {} + for f in interface_fields: + d[f] = interface_ref.get(f) + return d + + +def _make_node_elem(elem): + for f in node_fields: + elem.set(f) + + +def _make_interface_elem(elem): + for f in interface_fields: + elem.set(f) + + +class NodeTemplate(xmlutil.TemplateBuilder): + def construct(self): + node_elem = xmlutil.TemplateElement('node', selector='node') + _make_node_elem(node_elem) + ifs_elem = xmlutil.TemplateElement('interfaces') + if_elem = xmlutil.SubTemplateElement(ifs_elem, 'interface', + selector='interfaces') + _make_interface_elem(if_elem) + node_elem.append(ifs_elem) + return xmlutil.MasterTemplate(node_elem, 1) + + +class NodesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('nodes') + node_elem = xmlutil.SubTemplateElement(root, 'node', selector='nodes') + _make_node_elem(node_elem) + ifs_elem = xmlutil.TemplateElement('interfaces') + if_elem = xmlutil.SubTemplateElement(ifs_elem, 'interface', + selector='interfaces') + _make_interface_elem(if_elem) + node_elem.append(ifs_elem) + return xmlutil.MasterTemplate(root, 1) + + +class InterfaceTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('interface', selector='interface') + _make_interface_elem(root) + return xmlutil.MasterTemplate(root, 1) + + +class BareMetalNodeController(wsgi.Controller): + """The Bare-Metal Node API controller for the OpenStack API.""" + + @wsgi.serializers(xml=NodesTemplate) + def index(self, req): + context = req.environ['nova.context'] + authorize(context) + nodes_from_db = db.bm_node_get_all(context) + nodes = [] + for node_from_db in nodes_from_db: + try: + ifs = db.bm_interface_get_all_by_bm_node_id( + context, node_from_db['id']) + except exception.InstanceNotFound: + ifs = [] + node = _node_dict(node_from_db) + node['interfaces'] = [_interface_dict(i) for i in ifs] + nodes.append(node) + return {'nodes': nodes} + + @wsgi.serializers(xml=NodeTemplate) + def show(self, req, id): + context = req.environ['nova.context'] + authorize(context) + try: + node = db.bm_node_get(context, id) + except exception.InstanceNotFound: + raise webob.exc.HTTPNotFound + try: + ifs = db.bm_interface_get_all_by_bm_node_id(context, id) + except exception.InstanceNotFound: + ifs = [] + node = _node_dict(node) + node['interfaces'] = [_interface_dict(i) for i in ifs] + return {'node': node} + + @wsgi.serializers(xml=NodeTemplate) + def create(self, req, body): + context = req.environ['nova.context'] + authorize(context) + node = db.bm_node_create(context, body['node']) + node = _node_dict(node) + node['interfaces'] = [] + return {'node': node} + + def delete(self, req, id): + context = req.environ['nova.context'] + authorize(context) + try: + db.bm_node_destroy(context, id) + except exception.InstanceNotFound: + raise webob.exc.HTTPNotFound + return webob.Response(status_int=202) + + def _check_node_exists(self, context, node_id): + try: + db.bm_node_get(context, node_id) + except exception.InstanceNotFound: + raise webob.exc.HTTPNotFound + + @wsgi.serializers(xml=InterfaceTemplate) + @wsgi.action('add_interface') + def _add_interface(self, req, id, body): + context = req.environ['nova.context'] + authorize(context) + self._check_node_exists(context, id) + body = body['add_interface'] + address = body['address'] + datapath_id = body.get('datapath_id') + port_no = body.get('port_no') + if_id = db.bm_interface_create(context, + bm_node_id=id, + address=address, + datapath_id=datapath_id, + port_no=port_no) + if_ref = db.bm_interface_get(context, if_id) + return {'interface': _interface_dict(if_ref)} + + @wsgi.response(202) + @wsgi.action('remove_interface') + def _remove_interface(self, req, id, body): + context = req.environ['nova.context'] + authorize(context) + self._check_node_exists(context, id) + body = body['remove_interface'] + print "body(%s)" % body + if_id = body.get('id') + address = body.get('address') + if not if_id and not address: + raise webob.exc.HTTPBadRequest( + explanation=_("Must specify id or address")) + ifs = db.bm_interface_get_all_by_bm_node_id(context, id) + for i in ifs: + if if_id and if_id != i['id']: + continue + if address and address != i['address']: + continue + db.bm_interface_destroy(context, i['id']) + return webob.Response(status_int=202) + raise webob.exc.HTTPNotFound + + +class Baremetal_nodes(extensions.ExtensionDescriptor): + """Admin-only bare-metal node administration.""" + + name = "BareMetalNodes" + alias = "os-baremetal-nodes" + namespace = "http://docs.openstack.org/compute/ext/baremetal_nodes/api/v2" + updated = "2013-01-04T00:00:00+00:00" + + def get_resources(self): + resources = [] + res = extensions.ResourceExtension('os-baremetal-nodes', + BareMetalNodeController(), + member_actions={"action": "POST", }) + resources.append(res) + return resources diff --git a/nova/api/openstack/compute/contrib/coverage_ext.py b/nova/api/openstack/compute/contrib/coverage_ext.py index 4b7d4e57f..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..84f157b6a 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,7 +79,7 @@ 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) @@ -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/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/servers.py b/nova/api/openstack/compute/servers.py index f7f186870..90d4c37b3 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -906,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/wsgi.py b/nova/api/openstack/wsgi.py index 733685b14..a6f255081 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -406,6 +406,8 @@ class XMLDictSerializer(DictSerializer): if k in attrs: result.setAttribute(k, str(v)) else: + if k == "deleted": + v = str(bool(v)) node = self._to_xml_node(doc, metadata, k, v) result.appendChild(node) else: @@ -1180,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 62c83f6ed..711eee1fa 100644 --- a/nova/availability_zones.py +++ b/nova/availability_zones.py @@ -17,7 +17,6 @@ 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 = [ @@ -60,3 +59,25 @@ def get_host_availability_zone(context, host): return list(metadata['availability_zone'])[0] else: return CONF.default_availability_zone + + +def get_availability_zones(context): + """Return available and unavailable zones.""" + enabled_services = db.service_get_all(context, False) + disabled_services = db.service_get_all(context, True) + enabled_services = set_availability_zones(context, enabled_services) + disabled_services = set_availability_zones(context, disabled_services) + + available_zones = [] + for zone in [service['availability_zone'] for service + in enabled_services]: + if not zone in available_zones: + available_zones.append(zone) + + not_available_zones = [] + zones = [service['available_zones'] for service in disabled_services + if service['available_zones'] not in available_zones] + for zone in zones: + if zone not in not_available_zones: + not_available_zones.append(zone) + return (available_zones, not_available_zones) diff --git a/nova/compute/api.py b/nova/compute/api.py index f6090b40c..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, @@ -1353,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. @@ -1364,6 +1390,11 @@ 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) @@ -1382,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. @@ -1392,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 diff --git a/nova/compute/manager.py b/nova/compute/manager.py index d006ea049..73361fc23 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'], e, sys.exc_info()) + self.conductor_api, kwargs['instance'], + e, sys.exc_info()) return decorated_function @@ -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 @@ -735,8 +731,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_uuid = instance['uuid'] rescheduled = False - compute_utils.add_instance_fault_from_exc(context, instance, - exc_info[1], 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) @@ -1135,8 +1131,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 @@ -1469,7 +1464,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, 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) @@ -1678,8 +1673,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') @@ -2000,8 +1994,8 @@ class ComputeManager(manager.SchedulerDependentManager): rescheduled = False instance_uuid = instance['uuid'] - compute_utils.add_instance_fault_from_exc(context, instance, - 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 @@ -3074,8 +3068,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 @@ -3089,7 +3083,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( @@ -3106,6 +3102,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: @@ -3121,6 +3118,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 " diff --git a/nova/compute/resource_tracker.py b/nova/compute/resource_tracker.py index be0360185..f5d3a8008 100644 --- a/nova/compute/resource_tracker.py +++ b/nova/compute/resource_tracker.py @@ -293,12 +293,14 @@ 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.""" diff --git a/nova/compute/utils.py b/nova/compute/utils.py index 2b1286e16..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, 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 @@ -68,7 +61,7 @@ def add_instance_fault_from_exc(context, instance, fault, exc_info=None): '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, bdms, device): @@ -159,7 +152,8 @@ def notify_usage_exists(context, instance_ref, current_period=False, ignore_missing_network_data) if system_metadata is None: - system_metadata = metadata_to_dict(instance_ref['system_metadata']) + system_metadata = utils.metadata_to_dict( + instance_ref['system_metadata']) # add image metadata to the notification: image_meta = notifications.image_meta(system_metadata) @@ -211,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/conductor/api.py b/nova/conductor/api.py index d05c94877..710639305 100644 --- a/nova/conductor/api.py +++ b/nova/conductor/api.py @@ -133,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) @@ -293,6 +296,22 @@ class LocalAPI(object): 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.""" @@ -391,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) @@ -564,3 +586,19 @@ class API(object): 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 87b143912..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.35' + RPC_API_VERSION = '1.37' def __init__(self, *args, **kwargs): super(ConductorManager, self).__init__(service_name='conductor', @@ -258,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) @@ -319,3 +323,21 @@ class ConductorManager(manager.SchedulerDependentManager): 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 1699c85ed..248a4e211 100644 --- a/nova/conductor/rpcapi.py +++ b/nova/conductor/rpcapi.py @@ -68,6 +68,8 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy): 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' @@ -293,6 +295,10 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy): 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) return self.call(context, msg, version='1.25') @@ -330,3 +336,22 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy): 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/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/db/api.py b/nova/db/api.py index d8a16c52d..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): @@ -1705,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, @@ -1722,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 5317487cd..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) @@ -408,7 +424,8 @@ def service_get_all_compute_sorted(context): label = 'instance_cores' subq = model_query(context, models.Instance.host, func.sum(models.Instance.vcpus).label(label), - session=session, read_deleted="no").\ + base_model=models.Instance, session=session, + read_deleted="no").\ group_by(models.Instance.host).\ subquery() return _service_get_all_topic_subquery(context, @@ -540,7 +557,7 @@ def _update_stats(context, new_stats, compute_id, session, prune_stats=False): # prune un-touched old stats: for stat in statmap.values(): session.add(stat) - stat.update({'deleted': True}) + stat.soft_delete(session=session) # add new and updated stats for stat in stats: @@ -563,10 +580,9 @@ def compute_node_update(context, compute_id, values, prune_stats=False): def compute_node_get_by_host(context, host): """Get all capacity entries for the given host.""" - result = model_query(context, models.ComputeNode).\ + result = model_query(context, models.ComputeNode, read_deleted="no").\ join('service').\ filter(models.Service.host == host).\ - filter_by(deleted=False).\ first() return result @@ -586,6 +602,7 @@ def compute_node_statistics(context): func.sum(models.ComputeNode.current_workload), func.sum(models.ComputeNode.running_vms), func.sum(models.ComputeNode.disk_available_least), + base_model=models.ComputeNode, read_deleted="no").first() # Build a dict of the info--making no assumptions about result @@ -660,7 +677,8 @@ def floating_ip_get(context, id): @require_context def floating_ip_get_pools(context): pools = [] - for result in model_query(context, models.FloatingIp.pool).distinct(): + for result in model_query(context, models.FloatingIp.pool, + base_model=models.FloatingIp).distinct(): pools.append({'name': result[0]}) return pools @@ -775,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 @@ -816,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 @@ -1094,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) @@ -1226,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 @@ -1468,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() @@ -1593,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: @@ -1626,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) @@ -1695,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: @@ -2122,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) @@ -2213,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, @@ -2225,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)).\ @@ -2326,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 == @@ -3138,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: @@ -3595,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)) @@ -4037,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, @@ -4091,6 +4116,7 @@ def instance_type_extra_specs_update_or_create(context, flavor_id, specs): session = get_session() with session.begin(): instance_type_id = model_query(context, models.InstanceTypes.id, + base_model=models.InstanceTypes, session=session, read_deleted="no").\ filter(models.InstanceTypes.flavorid == flavor_id).\ first() @@ -4745,15 +4771,15 @@ def _task_log_get_query(context, task_name, period_beginning, @require_admin_context def task_log_get(context, task_name, period_beginning, period_ending, host, state=None): - return _task_log_get_query(task_name, period_beginning, period_ending, - host, state).first() + return _task_log_get_query(context, task_name, period_beginning, + period_ending, host, state).first() @require_admin_context def task_log_get_all(context, task_name, period_beginning, period_ending, host=None, state=None): - return _task_log_get_query(task_name, period_beginning, period_ending, - host, state).all() + return _task_log_get_query(context, task_name, period_beginning, + period_ending, host, state).all() @require_admin_context diff --git a/nova/db/sqlalchemy/migrate_repo/versions/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/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 baa966dbc..fd8348678 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -27,7 +27,7 @@ from sqlalchemy import ForeignKey, DateTime, Boolean, Text, Float from sqlalchemy.orm import relationship, backref, object_mapper from nova.db.sqlalchemy.session import get_session -from nova.db.sqlalchemy.types import IPAddress +from nova.db.sqlalchemy import types from nova.openstack.common import cfg from nova.openstack.common import timeutils @@ -42,7 +42,7 @@ class NovaBase(object): created_at = Column(DateTime, default=timeutils.utcnow) updated_at = Column(DateTime, onupdate=timeutils.utcnow) deleted_at = Column(DateTime) - deleted = Column(Boolean, default=False) + deleted = Column(Integer, default=0) metadata = None def save(self, session=None): @@ -63,7 +63,7 @@ class NovaBase(object): def soft_delete(self, session=None): """Mark this object as deleted.""" - self.deleted = True + self.deleted = self.id self.deleted_at = timeutils.utcnow() self.save(session=session) @@ -129,7 +129,7 @@ class ComputeNode(BASE, NovaBase): foreign_keys=service_id, primaryjoin='and_(' 'ComputeNode.service_id == Service.id,' - 'ComputeNode.deleted == False)') + 'ComputeNode.deleted == 0)') vcpus = Column(Integer) memory_mb = Column(Integer) @@ -173,7 +173,7 @@ class ComputeNodeStat(BASE, NovaBase): compute_node_id = Column(Integer, ForeignKey('compute_nodes.id')) primary_join = ('and_(ComputeNodeStat.compute_node_id == ' - 'ComputeNode.id, ComputeNodeStat.deleted == False)') + 'ComputeNode.id, ComputeNodeStat.deleted == 0)') stats = relationship("ComputeNode", backref="stats", primaryjoin=primary_join) @@ -221,7 +221,7 @@ class Instance(BASE, NovaBase): return base_name def _extra_keys(self): - return ['name'] + return ['name', 'system_metadata'] user_id = Column(String(255)) project_id = Column(String(255)) @@ -291,8 +291,8 @@ class Instance(BASE, NovaBase): # User editable field meant to represent what ip should be used # to connect to the instance - access_ip_v4 = Column(IPAddress()) - access_ip_v6 = Column(IPAddress()) + access_ip_v4 = Column(types.IPAddress()) + access_ip_v6 = Column(types.IPAddress()) auto_disk_config = Column(Boolean()) progress = Column(Integer) @@ -358,6 +358,7 @@ class Volume(BASE, NovaBase): """Represents a block storage device that can be attached to a VM.""" __tablename__ = 'volumes' id = Column(String(36), primary_key=True) + deleted = Column(String(36), default="") @property def name(self): @@ -465,13 +466,14 @@ class Reservation(BASE, NovaBase): "QuotaUsage", foreign_keys=usage_id, primaryjoin='and_(Reservation.usage_id == QuotaUsage.id,' - 'QuotaUsage.deleted == False)') + 'QuotaUsage.deleted == 0)') class Snapshot(BASE, NovaBase): """Represents a block storage device that can be attached to a VM.""" __tablename__ = 'snapshots' id = Column(String(36), primary_key=True) + deleted = Column(String(36), default="") @property def name(self): @@ -507,7 +509,7 @@ class BlockDeviceMapping(BASE, NovaBase): 'instance_uuid==' 'Instance.uuid,' 'BlockDeviceMapping.deleted==' - 'False)') + '0)') device_name = Column(String(255), nullable=False) # default=False for compatibility of the existing code. @@ -542,7 +544,7 @@ class IscsiTarget(BASE, NovaBase): backref=backref('iscsi_target', uselist=False), foreign_keys=volume_id, primaryjoin='and_(IscsiTarget.volume_id==Volume.id,' - 'IscsiTarget.deleted==False)') + 'IscsiTarget.deleted==0)') class SecurityGroupInstanceAssociation(BASE, NovaBase): @@ -567,14 +569,14 @@ class SecurityGroup(BASE, NovaBase): primaryjoin='and_(' 'SecurityGroup.id == ' 'SecurityGroupInstanceAssociation.security_group_id,' - 'SecurityGroupInstanceAssociation.deleted == False,' - 'SecurityGroup.deleted == False)', + 'SecurityGroupInstanceAssociation.deleted == 0,' + 'SecurityGroup.deleted == 0)', secondaryjoin='and_(' 'SecurityGroupInstanceAssociation.instance_uuid == Instance.uuid,' # (anthony) the condition below shouldn't be necessary now that the # association is being marked as deleted. However, removing this # may cause existing deployments to choke, so I'm leaving it - 'Instance.deleted == False)', + 'Instance.deleted == 0)', backref='security_groups') @@ -588,12 +590,12 @@ class SecurityGroupIngressRule(BASE, NovaBase): foreign_keys=parent_group_id, primaryjoin='and_(' 'SecurityGroupIngressRule.parent_group_id == SecurityGroup.id,' - 'SecurityGroupIngressRule.deleted == False)') + 'SecurityGroupIngressRule.deleted == 0)') protocol = Column(String(5)) # "tcp", "udp", or "icmp" from_port = Column(Integer) to_port = Column(Integer) - cidr = Column(IPAddress()) + cidr = Column(types.IPAddress()) # Note: This is not the parent SecurityGroup. It's SecurityGroup we're # granting access for. @@ -602,7 +604,7 @@ class SecurityGroupIngressRule(BASE, NovaBase): foreign_keys=group_id, primaryjoin='and_(' 'SecurityGroupIngressRule.group_id == SecurityGroup.id,' - 'SecurityGroupIngressRule.deleted == False)') + 'SecurityGroupIngressRule.deleted == 0)') class ProviderFirewallRule(BASE, NovaBase): @@ -613,7 +615,7 @@ class ProviderFirewallRule(BASE, NovaBase): protocol = Column(String(5)) # "tcp", "udp", or "icmp" from_port = Column(Integer) to_port = Column(Integer) - cidr = Column(IPAddress()) + cidr = Column(types.IPAddress()) class KeyPair(BASE, NovaBase): @@ -651,7 +653,7 @@ class Migration(BASE, NovaBase): instance = relationship("Instance", foreign_keys=instance_uuid, primaryjoin='and_(Migration.instance_uuid == ' 'Instance.uuid, Instance.deleted == ' - 'False)') + '0)') class Network(BASE, NovaBase): @@ -663,25 +665,25 @@ class Network(BASE, NovaBase): label = Column(String(255)) injected = Column(Boolean, default=False) - cidr = Column(IPAddress(), unique=True) - cidr_v6 = Column(IPAddress(), unique=True) + cidr = Column(types.IPAddress(), unique=True) + cidr_v6 = Column(types.IPAddress(), unique=True) multi_host = Column(Boolean, default=False) - gateway_v6 = Column(IPAddress()) - netmask_v6 = Column(IPAddress()) - netmask = Column(IPAddress()) + gateway_v6 = Column(types.IPAddress()) + netmask_v6 = Column(types.IPAddress()) + netmask = Column(types.IPAddress()) bridge = Column(String(255)) bridge_interface = Column(String(255)) - gateway = Column(IPAddress()) - broadcast = Column(IPAddress()) - dns1 = Column(IPAddress()) - dns2 = Column(IPAddress()) + gateway = Column(types.IPAddress()) + broadcast = Column(types.IPAddress()) + dns1 = Column(types.IPAddress()) + dns2 = Column(types.IPAddress()) vlan = Column(Integer) - vpn_public_address = Column(IPAddress()) + vpn_public_address = Column(types.IPAddress()) vpn_public_port = Column(Integer) - vpn_private_address = Column(IPAddress()) - dhcp_start = Column(IPAddress()) + vpn_private_address = Column(types.IPAddress()) + dhcp_start = Column(types.IPAddress()) rxtx_base = Column(Integer) @@ -706,7 +708,7 @@ class FixedIp(BASE, NovaBase): """Represents a fixed ip for an instance.""" __tablename__ = 'fixed_ips' id = Column(Integer, primary_key=True) - address = Column(IPAddress()) + address = Column(types.IPAddress()) network_id = Column(Integer, nullable=True) virtual_interface_id = Column(Integer, nullable=True) instance_uuid = Column(String(36), nullable=True) @@ -717,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(IPAddress()) + address = Column(types.IPAddress()) fixed_ip_id = Column(Integer, nullable=True) project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) @@ -735,6 +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)) @@ -745,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(IPAddress()) + address = Column(types.IPAddress()) username = Column(String(255)) password = Column(String(255)) console_type = Column(String(255)) @@ -779,7 +788,7 @@ class InstanceMetadata(BASE, NovaBase): primaryjoin='and_(' 'InstanceMetadata.instance_uuid == ' 'Instance.uuid,' - 'InstanceMetadata.deleted == False)') + 'InstanceMetadata.deleted == 0)') class InstanceSystemMetadata(BASE, NovaBase): @@ -793,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) @@ -811,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): @@ -826,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): @@ -880,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): 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/exception.py b/nova/exception.py index c15fc1e43..6915c14bb 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 @@ -227,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") @@ -317,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): diff --git a/nova/image/glance.py b/nova/image/glance.py index 1a6bba62f..6eac96c35 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -485,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 5e3762e89..59172d9ec 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__) @@ -73,6 +74,27 @@ def update_instance_cache_with_nw_info(api, context, instance, 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 +108,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_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 +186,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 +216,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,6 +224,7 @@ 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): @@ -207,6 +250,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,6 +260,7 @@ 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): """Adds a fixed ip to instance from specified network.""" @@ -224,6 +269,7 @@ class API(base.Base): '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): """Removes a fixed ip from instance from specified network.""" @@ -233,11 +279,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,6 +296,7 @@ class API(base.Base): associations['project'] = project self.network_rpcapi.associate(context, network_uuid, associations) + @wrap_check_policy def get_instance_nw_info(self, context, instance, update_cache=True): """Returns all network info related to an instance.""" result = self._get_instance_nw_info(context, instance) @@ -267,6 +316,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 +324,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 +332,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 +348,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 +356,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 +421,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 +440,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 4fefb2db4..49afc65c4 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -378,7 +378,7 @@ class IptablesManager(object): for table in tables: start, end = self._find_table(all_lines, table) all_lines[start:end] = self._modify_rules( - all_lines[start:end], tables[table]) + 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) @@ -392,18 +392,24 @@ class IptablesManager(object): start = lines.index('*%s' % table_name) - 1 except ValueError: # Couldn't find table_name - # For Unit Tests return (0, 0) end = lines[start:].index('COMMIT') + start + 2 return (start, end) - def _modify_rules(self, current_lines, table, binary=None): + 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) @@ -418,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: @@ -645,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): @@ -1124,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. @@ -1387,18 +1441,18 @@ 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) @@ -1535,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) @@ -1570,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 9ca7680a5..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 @@ -277,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.""" @@ -317,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. @@ -373,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. @@ -436,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.""" @@ -476,7 +451,6 @@ 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 a floating ip to the pool.""" @@ -523,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. @@ -587,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): @@ -614,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,15 +653,15 @@ class FloatingIP(object): # 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) @@ -700,38 +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) - @wrap_check_policy def get_floating_ip_pools(self, context): """Returns list of floating ip pools.""" pools = self.db.floating_ip_get_pools(context) return [dict(pool.iteritems()) for pool in pools] - @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, @@ -745,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, @@ -769,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 @@ -780,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, @@ -811,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) @@ -831,7 +799,6 @@ class FloatingIP(object): 'project': project} return this_domain - @wrap_check_policy def get_dns_domains(self, context): domains = [] @@ -854,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) @@ -877,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: @@ -897,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: @@ -907,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) @@ -929,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 @@ -1066,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'))) @@ -1136,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. @@ -1169,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. @@ -1205,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. @@ -1388,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): @@ -1401,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.""" @@ -1776,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): @@ -1881,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. @@ -1920,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) @@ -1936,12 +1886,10 @@ class NetworkManager(manager.SchedulerDependentManager): else: return fixed_ip['instance_uuid'] - @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) @@ -1949,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) @@ -2064,34 +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 {} - @wrap_check_policy 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 [] @@ -2248,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: @@ -2257,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 0771156c1..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): @@ -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 29e5e2f06..b2c20e225 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'), @@ -661,11 +663,13 @@ class API(base.Base): if fixed_ip.is_in_subnet(subnet)] bridge = None + ovs_interfaceid = None vif_type = port.get('binding:vif_type') # TODO(berrange) Quantum should pass the bridge name # in another binding metadata field if vif_type == network_model.VIF_TYPE_OVS: bridge = CONF.quantum_ovs_bridge + ovs_interfaceid = port['id'] elif vif_type == network_model.VIF_TYPE_BRIDGE: bridge = "brq" + port['network_id'] @@ -688,6 +692,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/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/scheduler/driver.py b/nova/scheduler/driver.py index 09de10388..16714a5ff 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -23,7 +23,6 @@ Scheduler base class that all Schedulers should inherit from import sys -from nova.compute import api as compute_api from nova.compute import power_state from nova.compute import rpcapi as compute_rpcapi from nova.compute import utils as compute_utils @@ -115,7 +114,6 @@ class Scheduler(object): def __init__(self): self.host_manager = importutils.import_object( CONF.scheduler_host_manager) - self.compute_api = compute_api.API() self.compute_rpcapi = compute_rpcapi.ComputeAPI() self.servicegroup_api = servicegroup.API() diff --git a/nova/scheduler/filter_scheduler.py b/nova/scheduler/filter_scheduler.py index 07a3f578a..9384e1495 100644 --- a/nova/scheduler/filter_scheduler.py +++ b/nova/scheduler/filter_scheduler.py @@ -47,15 +47,15 @@ class FilterScheduler(driver.Scheduler): Returns a list of the instances created. """ - instance_uuids = request_spec.get('instance_uuids') - num_instances = len(instance_uuids) - LOG.debug(_("Attempting to build %(num_instances)d instance(s)") % - locals()) - payload = dict(request_spec=request_spec) notifier.notify(context, notifier.publisher_id("scheduler"), 'scheduler.run_instance.start', notifier.INFO, payload) + instance_uuids = request_spec.pop('instance_uuids') + num_instances = len(instance_uuids) + LOG.debug(_("Attempting to build %(num_instances)d instance(s)") % + locals()) + weighed_hosts = self._schedule(context, request_spec, filter_properties, instance_uuids) @@ -124,6 +124,8 @@ class FilterScheduler(driver.Scheduler): filter_properties, requested_networks, injected_files, admin_password, is_first_time, instance_uuid=None): """Create the requested resource in this Zone.""" + # NOTE(vish): add our current instance back into the request spec + request_spec['instance_uuids'] = [instance_uuid] payload = dict(request_spec=request_spec, weighted_host=weighed_host.to_dict(), instance_id=instance_uuid) diff --git a/nova/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/service.py b/nova/service.py index df8cf020f..2daceba80 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,15 +418,14 @@ class Service(object): self.saved_args, self.saved_kwargs = args, kwargs self.timers = [] self.backdoor_port = None - self.db_allowed = db_allowed self.conductor_api = conductor.API(use_local=db_allowed) self.conductor_api.wait_until_ready(context.get_admin_context()) - self.servicegroup_api = servicegroup.API(db_allowed=db_allowed) def start(self): verstr = version.version_string_with_package() 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() @@ -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 diff --git a/nova/servicegroup/__init__.py b/nova/servicegroup/__init__.py index 318d30ff7..a804c62fa 100644 --- a/nova/servicegroup/__init__.py +++ b/nova/servicegroup/__init__.py @@ -19,4 +19,6 @@ The membership service for Nova. Different implementations can be plugged according to the Nova configuration. """ -from nova.servicegroup.api import API +from nova.servicegroup import api + +API = api.API diff --git a/nova/servicegroup/api.py b/nova/servicegroup/api.py index 358b7dcbc..0fb30cdf5 100644 --- a/nova/servicegroup/api.py +++ b/nova/servicegroup/api.py @@ -23,7 +23,7 @@ from nova.openstack.common import lockutils from nova.openstack.common import log as logging from nova import utils -from random import choice +import random LOG = logging.getLogger(__name__) @@ -144,4 +144,4 @@ class ServiceGroupDriver(object): length = len(members) if length == 0: return None - return choice(members) + return random.choice(members) diff --git a/nova/servicegroup/drivers/db.py b/nova/servicegroup/drivers/db.py index 686ee728b..18b4b74e5 100644 --- a/nova/servicegroup/drivers/db.py +++ b/nova/servicegroup/drivers/db.py @@ -16,7 +16,6 @@ from nova import conductor from nova import context -from nova import exception from nova.openstack.common import cfg from nova.openstack.common import log as logging from nova.openstack.common import timeutils diff --git a/nova/tests/api/openstack/compute/contrib/test_availability_zone.py b/nova/tests/api/openstack/compute/contrib/test_availability_zone.py new file mode 100644 index 000000000..fb9a36ba9 --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_availability_zone.py @@ -0,0 +1,247 @@ +# Copyright 2012 IBM +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import datetime +from lxml import etree +import webob + +from nova.api.openstack.compute.contrib import availability_zone +from nova import availability_zones +from nova import context +from nova import db +from nova.openstack.common import jsonutils +from nova import servicegroup +from nova import test +from nova.tests.api.openstack import fakes + + +def fake_service_get_all(context, disabled=None): + def __fake_service(binary, availability_zone, + created_at, updated_at, host, disabled): + return {'binary': binary, + 'availability_zone': availability_zone, + 'available_zones': availability_zone, + 'created_at': created_at, + 'updated_at': updated_at, + 'host': host, + 'disabled': disabled} + + if disabled: + return [__fake_service("nova-compute", "zone-2", + datetime.datetime(2012, 11, 14, 9, 53, 25, 0), + datetime.datetime(2012, 12, 26, 14, 45, 25, 0), + "fake_host-1", True), + __fake_service("nova-scheduler", "internal", + datetime.datetime(2012, 11, 14, 9, 57, 3, 0), + datetime.datetime(2012, 12, 26, 14, 45, 25, 0), + "fake_host-1", True), + __fake_service("nova-network", "internal", + datetime.datetime(2012, 11, 16, 7, 25, 46, 0), + datetime.datetime(2012, 12, 26, 14, 45, 24, 0), + "fake_host-2", True)] + else: + return [__fake_service("nova-compute", "zone-1", + datetime.datetime(2012, 11, 14, 9, 53, 25, 0), + datetime.datetime(2012, 12, 26, 14, 45, 25, 0), + "fake_host-1", False), + __fake_service("nova-sched", "internal", + datetime.datetime(2012, 11, 14, 9, 57, 03, 0), + datetime.datetime(2012, 12, 26, 14, 45, 25, 0), + "fake_host-1", False), + __fake_service("nova-network", "internal", + datetime.datetime(2012, 11, 16, 7, 25, 46, 0), + datetime.datetime(2012, 12, 26, 14, 45, 24, 0), + "fake_host-2", False)] + + +def fake_service_is_up(self, service): + return service['binary'] != u"nova-network" + + +def fake_set_availability_zones(context, services): + return services + + +class AvailabilityZoneApiTest(test.TestCase): + def setUp(self): + super(AvailabilityZoneApiTest, self).setUp() + self.stubs.Set(db, 'service_get_all', fake_service_get_all) + self.stubs.Set(availability_zones, 'set_availability_zones', + fake_set_availability_zones) + self.stubs.Set(servicegroup.API, 'service_is_up', fake_service_is_up) + + def test_availability_zone_index(self): + req = webob.Request.blank('/v2/fake/os-availability-zone') + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 200) + resp_dict = jsonutils.loads(resp.body) + + self.assertTrue('availabilityZoneInfo' in resp_dict) + zones = resp_dict['availabilityZoneInfo'] + self.assertEqual(len(zones), 2) + self.assertEqual(zones[0]['zoneName'], u'zone-1') + self.assertTrue(zones[0]['zoneState']['available']) + self.assertIsNone(zones[0]['hosts']) + self.assertEqual(zones[1]['zoneName'], u'zone-2') + self.assertFalse(zones[1]['zoneState']['available']) + self.assertIsNone(zones[1]['hosts']) + + def test_availability_zone_detail(self): + def _formatZone(zone_dict): + result = [] + + # Zone tree view item + result.append({'zoneName': zone_dict['zoneName'], + 'zoneState': u'available' + if zone_dict['zoneState']['available'] else + u'not available'}) + + if zone_dict['hosts'] is not None: + for (host, services) in zone_dict['hosts'].items(): + # Host tree view item + result.append({'zoneName': u'|- %s' % host, + 'zoneState': u''}) + for (svc, state) in services.items(): + # Service tree view item + result.append({'zoneName': u'| |- %s' % svc, + 'zoneState': u'%s %s %s' % ( + 'enabled' if state['active'] else + 'disabled', + ':-)' if state['available'] else + 'XXX', + jsonutils.to_primitive( + state['updated_at']))}) + return result + + def _assertZone(zone, name, status): + self.assertEqual(zone['zoneName'], name) + self.assertEqual(zone['zoneState'], status) + + availabilityZone = availability_zone.AvailabilityZoneController() + + req = webob.Request.blank('/v2/fake/os-availability-zone/detail') + req.method = 'GET' + req.environ['nova.context'] = context.get_admin_context() + resp_dict = availabilityZone.detail(req) + + self.assertTrue('availabilityZoneInfo' in resp_dict) + zones = resp_dict['availabilityZoneInfo'] + self.assertEqual(len(zones), 3) + + ''' availabilityZoneInfo field content in response body: + [{'zoneName': 'zone-1', + 'zoneState': {'available': True}, + 'hosts': {'fake_host-1': { + 'nova-compute': {'active': True, 'available': True, + 'updated_at': datetime(2012, 12, 26, 14, 45, 25)}}}}, + {'zoneName': 'internal', + 'zoneState': {'available': True}, + 'hosts': {'fake_host-1': { + 'nova-sched': {'active': True, 'available': True, + 'updated_at': datetime(2012, 12, 26, 14, 45, 25)}}, + 'fake_host-2': { + 'nova-network': {'active': True, 'available': False, + 'updated_at': datetime(2012, 12, 26, 14, 45, 24)}}}}, + {'zoneName': 'zone-2', + 'zoneState': {'available': False}, + 'hosts': None}] + ''' + + l0 = [u'zone-1', u'available'] + l1 = [u'|- fake_host-1', u''] + l2 = [u'| |- nova-compute', u'enabled :-) 2012-12-26T14:45:25.000000'] + l3 = [u'internal', u'available'] + l4 = [u'|- fake_host-1', u''] + l5 = [u'| |- nova-sched', u'enabled :-) 2012-12-26T14:45:25.000000'] + l6 = [u'|- fake_host-2', u''] + l7 = [u'| |- nova-network', u'enabled XXX 2012-12-26T14:45:24.000000'] + l8 = [u'zone-2', u'not available'] + + z0 = _formatZone(zones[0]) + z1 = _formatZone(zones[1]) + z2 = _formatZone(zones[2]) + + self.assertEqual(len(z0), 3) + self.assertEqual(len(z1), 5) + self.assertEqual(len(z2), 1) + + _assertZone(z0[0], l0[0], l0[1]) + _assertZone(z0[1], l1[0], l1[1]) + _assertZone(z0[2], l2[0], l2[1]) + _assertZone(z1[0], l3[0], l3[1]) + _assertZone(z1[1], l4[0], l4[1]) + _assertZone(z1[2], l5[0], l5[1]) + _assertZone(z1[3], l6[0], l6[1]) + _assertZone(z1[4], l7[0], l7[1]) + _assertZone(z2[0], l8[0], l8[1]) + + +class AvailabilityZoneSerializerTest(test.TestCase): + def test_availability_zone_index_detail_serializer(self): + def _verify_zone(zone_dict, tree): + self.assertEqual(tree.tag, 'availabilityZone') + self.assertEqual(zone_dict['zoneName'], tree.get('name')) + self.assertEqual(str(zone_dict['zoneState']['available']), + tree[0].get('available')) + + for _idx, host_child in enumerate(tree[1]): + self.assertTrue(host_child.get('name') in zone_dict['hosts']) + svcs = zone_dict['hosts'][host_child.get('name')] + for _idx, svc_child in enumerate(host_child[0]): + self.assertTrue(svc_child.get('name') in svcs) + svc = svcs[svc_child.get('name')] + self.assertEqual(len(svc_child), 1) + + self.assertEqual(str(svc['available']), + svc_child[0].get('available')) + self.assertEqual(str(svc['active']), + svc_child[0].get('active')) + self.assertEqual(str(svc['updated_at']), + svc_child[0].get('updated_at')) + + serializer = availability_zone.AvailabilityZonesTemplate() + raw_availability_zones = \ + [{'zoneName': 'zone-1', + 'zoneState': {'available': True}, + 'hosts': {'fake_host-1': { + 'nova-compute': {'active': True, 'available': True, + 'updated_at': + datetime.datetime( + 2012, 12, 26, 14, 45, 25)}}}}, + {'zoneName': 'internal', + 'zoneState': {'available': True}, + 'hosts': {'fake_host-1': { + 'nova-sched': {'active': True, 'available': True, + 'updated_at': + datetime.datetime( + 2012, 12, 26, 14, 45, 25)}}, + 'fake_host-2': { + 'nova-network': {'active': True, + 'available': False, + 'updated_at': + datetime.datetime( + 2012, 12, 26, 14, 45, 24)}}}}, + {'zoneName': 'zone-2', + 'zoneState': {'available': False}, + 'hosts': None}] + + text = serializer.serialize( + dict(availabilityZoneInfo=raw_availability_zones)) + tree = etree.fromstring(text) + + self.assertEqual('availabilityZones', tree.tag) + self.assertEqual(len(raw_availability_zones), len(tree)) + for idx, child in enumerate(tree): + _verify_zone(raw_availability_zones[idx], child) diff --git a/nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py b/nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py new file mode 100644 index 000000000..381d452a7 --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_baremetal_nodes.py @@ -0,0 +1,197 @@ +# Copyright (c) 2013 NTT DOCOMO, INC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from webob import exc + +from nova.api.openstack.compute.contrib import baremetal_nodes +from nova import context +from nova import exception +from nova import test +from nova.virt.baremetal import db + + +class FakeRequest(object): + + def __init__(self, context): + self.environ = {"nova.context": context} + + +class BareMetalNodesTest(test.TestCase): + + def setUp(self): + super(BareMetalNodesTest, self).setUp() + + self.context = context.get_admin_context() + self.controller = baremetal_nodes.BareMetalNodeController() + self.request = FakeRequest(self.context) + + def test_create(self): + node = { + 'service_host': "host", + 'cpus': 8, + 'memory_mb': 8192, + 'local_gb': 128, + 'pm_address': "10.1.2.3", + 'pm_user': "pm_user", + 'pm_password': "pm_pass", + 'prov_mac_address': "12:34:56:78:90:ab", + 'prov_vlan_id': 1234, + 'terminal_port': 8000, + 'interfaces': [], + } + response = node.copy() + response['id'] = 100 + del response['pm_password'] + response['instance_uuid'] = None + self.mox.StubOutWithMock(db, 'bm_node_create') + db.bm_node_create(self.context, node).AndReturn(response) + self.mox.ReplayAll() + res_dict = self.controller.create(self.request, {'node': node}) + self.assertEqual({'node': response}, res_dict) + + def test_delete(self): + self.mox.StubOutWithMock(db, 'bm_node_destroy') + db.bm_node_destroy(self.context, 1) + self.mox.ReplayAll() + self.controller.delete(self.request, 1) + + def test_index(self): + nodes = [{'id': 1}, + {'id': 2}, + ] + interfaces = [{'id': 1, 'address': '11:11:11:11:11:11'}, + {'id': 2, 'address': '22:22:22:22:22:22'}, + ] + self.mox.StubOutWithMock(db, 'bm_node_get_all') + self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id') + db.bm_node_get_all(self.context).AndReturn(nodes) + db.bm_interface_get_all_by_bm_node_id(self.context, 1).\ + AndRaise(exception.InstanceNotFound(instance_id=1)) + db.bm_interface_get_all_by_bm_node_id(self.context, 2).\ + AndReturn(interfaces) + self.mox.ReplayAll() + res_dict = self.controller.index(self.request) + self.assertEqual(2, len(res_dict['nodes'])) + self.assertEqual([], res_dict['nodes'][0]['interfaces']) + self.assertEqual(2, len(res_dict['nodes'][1]['interfaces'])) + + def test_show(self): + node_id = 1 + node = {'id': node_id} + interfaces = [{'id': 1, 'address': '11:11:11:11:11:11'}, + {'id': 2, 'address': '22:22:22:22:22:22'}, + ] + self.mox.StubOutWithMock(db, 'bm_node_get') + self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id') + db.bm_node_get(self.context, node_id).AndReturn(node) + db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\ + AndReturn(interfaces) + self.mox.ReplayAll() + res_dict = self.controller.show(self.request, node_id) + self.assertEqual(node_id, res_dict['node']['id']) + self.assertEqual(2, len(res_dict['node']['interfaces'])) + + def test_add_interface(self): + node_id = 1 + address = '11:22:33:44:55:66' + body = {'add_interface': {'address': address}} + self.mox.StubOutWithMock(db, 'bm_node_get') + self.mox.StubOutWithMock(db, 'bm_interface_create') + self.mox.StubOutWithMock(db, 'bm_interface_get') + db.bm_node_get(self.context, node_id) + db.bm_interface_create(self.context, + bm_node_id=node_id, + address=address, + datapath_id=None, + port_no=None).\ + AndReturn(12345) + db.bm_interface_get(self.context, 12345).\ + AndReturn({'id': 12345, 'address': address}) + self.mox.ReplayAll() + res_dict = self.controller._add_interface(self.request, node_id, body) + self.assertEqual(12345, res_dict['interface']['id']) + self.assertEqual(address, res_dict['interface']['address']) + + def test_remove_interface(self): + node_id = 1 + interfaces = [{'id': 1}, + {'id': 2}, + {'id': 3}, + ] + body = {'remove_interface': {'id': 2}} + self.mox.StubOutWithMock(db, 'bm_node_get') + self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id') + self.mox.StubOutWithMock(db, 'bm_interface_destroy') + db.bm_node_get(self.context, node_id) + db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\ + AndReturn(interfaces) + db.bm_interface_destroy(self.context, 2) + self.mox.ReplayAll() + self.controller._remove_interface(self.request, node_id, body) + + def test_remove_interface_by_address(self): + node_id = 1 + interfaces = [{'id': 1, 'address': '11:11:11:11:11:11'}, + {'id': 2, 'address': '22:22:22:22:22:22'}, + {'id': 3, 'address': '33:33:33:33:33:33'}, + ] + self.mox.StubOutWithMock(db, 'bm_node_get') + self.mox.StubOutWithMock(db, 'bm_interface_get_all_by_bm_node_id') + self.mox.StubOutWithMock(db, 'bm_interface_destroy') + db.bm_node_get(self.context, node_id) + db.bm_interface_get_all_by_bm_node_id(self.context, node_id).\ + AndReturn(interfaces) + db.bm_interface_destroy(self.context, 2) + self.mox.ReplayAll() + body = {'remove_interface': {'address': '22:22:22:22:22:22'}} + self.controller._remove_interface(self.request, node_id, body) + + def test_remove_interface_no_id_no_address(self): + node_id = 1 + self.mox.StubOutWithMock(db, 'bm_node_get') + db.bm_node_get(self.context, node_id) + self.mox.ReplayAll() + body = {'remove_interface': {}} + self.assertRaises(exc.HTTPBadRequest, + self.controller._remove_interface, + self.request, + node_id, + body) + + def test_add_interface_node_not_found(self): + node_id = 1 + self.mox.StubOutWithMock(db, 'bm_node_get') + db.bm_node_get(self.context, node_id).\ + AndRaise(exception.InstanceNotFound(instance_id=node_id)) + self.mox.ReplayAll() + body = {'add_interface': {'address': '11:11:11:11:11:11'}} + self.assertRaises(exc.HTTPNotFound, + self.controller._add_interface, + self.request, + node_id, + body) + + def test_remove_interface_node_not_found(self): + node_id = 1 + self.mox.StubOutWithMock(db, 'bm_node_get') + db.bm_node_get(self.context, node_id).\ + AndRaise(exception.InstanceNotFound(instance_id=node_id)) + self.mox.ReplayAll() + body = {'remove_interface': {'address': '11:11:11:11:11:11'}} + self.assertRaises(exc.HTTPNotFound, + self.controller._remove_interface, + self.request, + node_id, + body) diff --git a/nova/tests/api/openstack/compute/contrib/test_coverage_ext.py b/nova/tests/api/openstack/compute/contrib/test_coverage_ext.py index 39a883049..66a8a8f82 100644 --- a/nova/tests/api/openstack/compute/contrib/test_coverage_ext.py +++ b/nova/tests/api/openstack/compute/contrib/test_coverage_ext.py @@ -16,7 +16,7 @@ import telnetlib -from coverage import coverage +import coverage import webob from nova.api.openstack.compute.contrib import coverage_ext @@ -48,8 +48,8 @@ class CoverageExtensionTest(test.TestCase): super(CoverageExtensionTest, self).setUp() self.stubs.Set(telnetlib.Telnet, 'write', fake_telnet) self.stubs.Set(telnetlib.Telnet, 'expect', fake_telnet) - self.stubs.Set(coverage, 'report', fake_report) - self.stubs.Set(coverage, 'xml_report', fake_xml_report) + self.stubs.Set(coverage.coverage, 'report', fake_report) + self.stubs.Set(coverage.coverage, 'xml_report', fake_xml_report) self.admin_context = context.RequestContext('fakeadmin_0', 'fake', is_admin=True) diff --git a/nova/tests/api/openstack/compute/contrib/test_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_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 3a6e5db7c..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): @@ -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,11 +154,11 @@ 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): @@ -166,11 +168,11 @@ 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)}, {'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 375355a70..e3fff380d 100644 --- a/nova/tests/api/openstack/compute/test_limits.py +++ b/nova/tests/api/openstack/compute/test_limits.py @@ -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_servers.py b/nova/tests/api/openstack/compute/test_servers.py index af769a6ca..4bfb1c1e3 100644 --- a/nova/tests/api/openstack/compute/test_servers.py +++ b/nova/tests/api/openstack/compute/test_servers.py @@ -2008,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 @@ -4459,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) @@ -4537,7 +4585,6 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'server') @@ -4668,7 +4715,6 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture) - print output root = etree.XML(output) xmlutil.validate_schema(root, 'server') @@ -4765,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)) @@ -4829,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)) @@ -5116,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/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_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 73ef8caa3..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): @@ -414,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) @@ -482,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') @@ -493,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() @@ -516,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') @@ -524,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 b8212848c..6bd2c3cac 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 @@ -2067,7 +2067,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()) @@ -2532,7 +2532,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 @@ -2722,8 +2722,11 @@ 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, - NotImplementedError('test'), exc_info) + 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() @@ -2751,8 +2754,8 @@ 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, 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() @@ -2779,8 +2782,8 @@ 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, 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 = self._create_fake_instance() @@ -2798,8 +2801,10 @@ 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, - NotImplementedError('test')) + compute_utils.add_instance_fault_from_exc(ctxt, + self.compute.conductor_api, + instance, + NotImplementedError('test')) def test_cleanup_running_deleted_instances(self): admin_context = context.get_admin_context() @@ -3426,7 +3431,7 @@ class ComputeTestCase(BaseTestCase): db.instance_destroy(c, inst_uuid) def test_rebuild_with_wrong_shared_storage(self): - """Confirm evacuate scenario updates host.""" + """Confirm evacuate scenario does not update host.""" # creating testdata c = self.context.elevated() @@ -3780,6 +3785,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() @@ -4688,6 +4715,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() @@ -5287,7 +5368,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'}) @@ -5879,11 +5960,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( @@ -5908,11 +5989,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( @@ -5935,11 +6016,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( @@ -5957,7 +6038,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) @@ -5978,7 +6059,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) @@ -5997,7 +6078,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) @@ -6698,6 +6779,7 @@ class ComputeRescheduleOrReraiseTestCase(BaseTestCase): exc_info = sys.exc_info() compute_utils.add_instance_fault_from_exc(self.context, + self.compute.conductor_api, self.instance, exc_info[0], exc_info=exc_info) self.compute._deallocate_network(self.context, self.instance).AndRaise(InnerTestingException("Error")) @@ -6748,6 +6830,7 @@ class ComputeRescheduleOrReraiseTestCase(BaseTestCase): except Exception: exc_info = sys.exc_info() compute_utils.add_instance_fault_from_exc(self.context, + self.compute.conductor_api, self.instance, exc_info[0], exc_info=exc_info) self.compute._deallocate_network(self.context, self.instance) @@ -6776,6 +6859,7 @@ class ComputeRescheduleOrReraiseTestCase(BaseTestCase): exc_info = sys.exc_info() compute_utils.add_instance_fault_from_exc(self.context, + self.compute.conductor_api, self.instance, exc_info[0], exc_info=exc_info) self.compute._deallocate_network(self.context, self.instance) diff --git a/nova/tests/compute/test_compute_utils.py b/nova/tests/compute/test_compute_utils.py index 6e7227d4c..4372039e0 100644 --- a/nova/tests/compute/test_compute_utils.py +++ b/nova/tests/compute/test_compute_utils.py @@ -359,6 +359,9 @@ class UsageInfoTestCase(test.TestCase): extra_usage_info = {'image_name': 'fake_name'} db.instance_system_metadata_update(self.context, instance['uuid'], sys_metadata, False) + # NOTE(russellb) Make sure our instance has the latest system_metadata + # in it. + instance = db.instance_get(self.context, instance_id) compute_utils.notify_about_instance_usage(self.context, instance, 'create.start', extra_usage_info=extra_usage_info) self.assertEquals(len(test_notifier.NOTIFICATIONS), 1) @@ -382,14 +385,3 @@ class UsageInfoTestCase(test.TestCase): image_ref_url = "%s/images/1" % glance.generate_glance_url() self.assertEquals(payload['image_ref_url'], image_ref_url) self.compute.terminate_instance(self.context, instance) - - -class MetadataToDictTestCase(test.TestCase): - def test_metadata_to_dict(self): - self.assertEqual(compute_utils.metadata_to_dict( - [{'key': 'foo1', 'value': 'bar'}, - {'key': 'foo2', 'value': 'baz'}]), - {'foo1': 'bar', 'foo2': 'baz'}) - - def test_metadata_to_dict_empty(self): - self.assertEqual(compute_utils.metadata_to_dict([]), {}) diff --git a/nova/tests/conductor/test_conductor.py b/nova/tests/conductor/test_conductor.py index 30d176bbd..f21e67845 100644 --- a/nova/tests/conductor/test_conductor.py +++ b/nova/tests/conductor/test_conductor.py @@ -426,6 +426,43 @@ class _BaseTestCase(object): '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.""" diff --git a/nova/tests/conf_fixture.py b/nova/tests/conf_fixture.py index 9155a3f68..2f4d0ebb1 100644 --- a/nova/tests/conf_fixture.py +++ b/nova/tests/conf_fixture.py @@ -22,7 +22,7 @@ from nova import config from nova import ipv6 from nova.openstack.common import cfg from nova import paths -from nova.tests.utils import cleanup_dns_managers +from nova.tests import utils CONF = cfg.CONF CONF.import_opt('use_ipv6', 'nova.netconf') @@ -70,5 +70,5 @@ class ConfFixture(fixtures.Fixture): self.conf.set_default('vlan_interface', 'eth0') config.parse_args([], default_config_files=[]) self.addCleanup(self.conf.reset) - self.addCleanup(cleanup_dns_managers) + self.addCleanup(utils.cleanup_dns_managers) self.addCleanup(ipv6.api.reset_backend) diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index acefa856c..ead43adea 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -105,6 +105,7 @@ policy_data = """ "compute_extension:admin_actions:migrate": "", "compute_extension:aggregates": "", "compute_extension:agents": "", + "compute_extension:baremetal_nodes": "", "compute_extension:cells": "", "compute_extension:certificates": "", "compute_extension:cloudpipe": "", @@ -156,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": "", @@ -192,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": "", @@ -212,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/fakelibvirt.py b/nova/tests/fakelibvirt.py index a573b7d1c..259d192cb 100644 --- a/nova/tests/fakelibvirt.py +++ b/nova/tests/fakelibvirt.py @@ -179,6 +179,7 @@ class Domain(object): self._def = self._parse_definition(xml) self._has_saved_state = False self._snapshots = {} + self._id = self._connection._id_counter def _parse_definition(self, xml): try: @@ -299,6 +300,9 @@ class Domain(object): self._state = VIR_DOMAIN_SHUTOFF self._connection._mark_not_running(self) + def ID(self): + return self._id + def name(self): return self._def['name'] @@ -517,6 +521,8 @@ class Connection(object): if dom._transient: self._undefine(dom) + dom._id = -1 + for (k, v) in self._running_vms.iteritems(): if v == dom: del self._running_vms[k] diff --git a/nova/tests/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 9dd9e5121..fb26fa4f1 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -339,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) 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-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-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/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index f101da243..eafc31c61 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -22,13 +22,13 @@ import re import urllib import uuid as uuid_lib -from coverage import coverage +import coverage from lxml import etree from nova.api.metadata import password from nova.api.openstack.compute.contrib import coverage_ext # Import extensions to pull in osapi_compute_extension CONF option used below. -from nova.cloudpipe.pipelib import CloudPipe +from nova.cloudpipe import pipelib from nova import context from nova import db from nova.db.sqlalchemy import models @@ -43,6 +43,7 @@ from nova.openstack.common import timeutils import nova.quota from nova.scheduler import driver from nova import test +from nova.tests.baremetal.db import base as bm_db_base from nova.tests import fake_network from nova.tests.image import fake from nova.tests.integrated import integrated_helpers @@ -291,7 +292,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}', @@ -376,12 +377,9 @@ 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') @@ -760,7 +758,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. @@ -1511,7 +1509,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) @@ -2589,3 +2587,183 @@ 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' 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 8a7865b83..3c219f5f4 100644 --- a/nova/tests/network/test_linux_net.py +++ b/nova/tests/network/test_linux_net.py @@ -461,14 +461,14 @@ 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'), + ('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'), ('ip6tables-save', '-c'), @@ -500,10 +500,10 @@ 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'), + ('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'), diff --git a/nova/tests/network/test_manager.py b/nova/tests/network/test_manager.py index b5b3ec107..2cc19bbb8 100644 --- a/nova/tests/network/test_manager.py +++ b/nova/tests/network/test_manager.py @@ -15,7 +15,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. -import shutil import fixtures import mox @@ -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 @@ -185,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)) @@ -669,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): @@ -789,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) @@ -799,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() @@ -809,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() @@ -1804,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): @@ -1845,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): @@ -2092,30 +2097,6 @@ 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): diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py index f8b9f9296..230e2ea03 100644 --- a/nova/tests/scheduler/test_host_filters.py +++ b/nova/tests/scheduler/test_host_filters.py @@ -25,7 +25,7 @@ from nova.openstack.common import jsonutils from nova.openstack.common import timeutils from nova.scheduler import filters from nova.scheduler.filters import extra_specs_ops -from nova.scheduler.filters.trusted_filter import AttestationService +from nova.scheduler.filters import trusted_filter from nova import servicegroup from nova import test from nova.tests.scheduler import fakes @@ -242,7 +242,8 @@ class HostFiltersTestCase(test.TestCase): self.oat_data = '' self.oat_attested = False self.stubs = stubout.StubOutForTesting() - self.stubs.Set(AttestationService, '_request', self.fake_oat_request) + self.stubs.Set(trusted_filter.AttestationService, '_request', + self.fake_oat_request) self.context = context.RequestContext('fake', 'fake') self.json_query = jsonutils.dumps( ['and', ['>=', '$free_ram_mb', 1024], @@ -336,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', {}) @@ -387,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', {}) diff --git a/nova/tests/ssl_cert/ca.crt b/nova/tests/ssl_cert/ca.crt new file mode 100644 index 000000000..9d66ca627 --- /dev/null +++ b/nova/tests/ssl_cert/ca.crt @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIGDDCCA/SgAwIBAgIJAPSvwQYk4qI4MA0GCSqGSIb3DQEBBQUAMGExCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMRUwEwYDVQQKEwxPcGVuc3RhY2sg +Q0ExEjAQBgNVBAsTCUdsYW5jZSBDQTESMBAGA1UEAxMJR2xhbmNlIENBMB4XDTEy +MDIwOTE3MTAwMloXDTIyMDIwNjE3MTAwMlowYTELMAkGA1UEBhMCQVUxEzARBgNV +BAgTClNvbWUtU3RhdGUxFTATBgNVBAoTDE9wZW5zdGFjayBDQTESMBAGA1UECxMJ +R2xhbmNlIENBMRIwEAYDVQQDEwlHbGFuY2UgQ0EwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDmf+fapWfzy1Uylus0KGalw4X/5xZ+ltPVOr+IdCPbstvi +RTC5g+O+TvXeOP32V/cnSY4ho/+f2q730za+ZA/cgWO252rcm3Q7KTJn3PoqzJvX +/l3EXe3/TCrbzgZ7lW3QLTCTEE2eEzwYG3wfDTOyoBq+F6ct6ADh+86gmpbIRfYI +N+ixB0hVyz9427PTof97fL7qxxkjAayB28OfwHrkEBl7iblNhUC0RoH+/H9r5GEl +GnWiebxfNrONEHug6PHgiaGq7/Dj+u9bwr7J3/NoS84I08ajMnhlPZxZ8bS/O8If +ceWGZv7clPozyhABT/otDfgVcNH1UdZ4zLlQwc1MuPYN7CwxrElxc8Quf94ttGjb +tfGTl4RTXkDofYdG1qBWW962PsGl2tWmbYDXV0q5JhV/IwbrE1X9f+OksJQne1/+ +dZDxMhdf2Q1V0P9hZZICu4+YhmTMs5Mc9myKVnzp4NYdX5fXoB/uNYph+G7xG5IK +WLSODKhr1wFGTTcuaa8LhOH5UREVenGDJuc6DdgX9a9PzyJGIi2ngQ03TJIkCiU/ +4J/r/vsm81ezDiYZSp2j5JbME+ixW0GBLTUWpOIxUSHgUFwH5f7lQwbXWBOgwXQk +BwpZTmdQx09MfalhBtWeu4/6BnOCOj7e/4+4J0eVxXST0AmVyv8YjJ2nz1F9oQID +AQABo4HGMIHDMB0GA1UdDgQWBBTk7Krj4bEsTjHXaWEtI2GZ5ACQyTCBkwYDVR0j +BIGLMIGIgBTk7Krj4bEsTjHXaWEtI2GZ5ACQyaFlpGMwYTELMAkGA1UEBhMCQVUx +EzARBgNVBAgTClNvbWUtU3RhdGUxFTATBgNVBAoTDE9wZW5zdGFjayBDQTESMBAG +A1UECxMJR2xhbmNlIENBMRIwEAYDVQQDEwlHbGFuY2UgQ0GCCQD0r8EGJOKiODAM +BgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQA8Zrss/MiwFHGmDlercE0h +UvzA54n/EvKP9nP3jHM2qW/VPfKdnFw99nEPFLhb+lN553vdjOpCYFm+sW0Z5Mi4 +qsFkk4AmXIIEFOPt6zKxMioLYDQ9Sw/BUv6EZGeANWr/bhmaE+dMcKJt5le/0jJm +2ahsVB9fbFu9jBFeYb7Ba/x2aLkEGMxaDLla+6EQhj148fTnS1wjmX9G2cNzJvj/ ++C2EfKJIuDJDqw2oS2FGVpP37FA2Bz2vga0QatNneLkGKCFI3ZTenBznoN+fmurX +TL3eJE4IFNrANCcdfMpdyLAtXz4KpjcehqpZMu70er3d30zbi1l0Ajz4dU+WKz/a +NQES+vMkT2wqjXHVTjrNwodxw3oLK/EuTgwoxIHJuplx5E5Wrdx9g7Gl1PBIJL8V +xiOYS5N7CakyALvdhP7cPubA2+TPAjNInxiAcmhdASS/Vrmpvrkat6XhGn8h9liv +ysDOpMQmYQkmgZBpW8yBKK7JABGGsJADJ3E6J5MMWBX2RR4kFoqVGAzdOU3oyaTy +I0kz5sfuahaWpdYJVlkO+esc0CRXw8fLDYivabK2tOgUEWeZsZGZ9uK6aV1VxTAY +9Guu3BJ4Rv/KP/hk7mP8rIeCwotV66/2H8nq72ImQhzSVyWcxbFf2rJiFQJ3BFwA +WoRMgEwjGJWqzhJZUYpUAQ== +-----END CERTIFICATE----- diff --git a/nova/tests/ssl_cert/certificate.crt b/nova/tests/ssl_cert/certificate.crt new file mode 100644 index 000000000..3c1aa6363 --- /dev/null +++ b/nova/tests/ssl_cert/certificate.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFLjCCAxYCAQEwDQYJKoZIhvcNAQEFBQAwYTELMAkGA1UEBhMCQVUxEzARBgNV +BAgTClNvbWUtU3RhdGUxFTATBgNVBAoTDE9wZW5zdGFjayBDQTESMBAGA1UECxMJ +R2xhbmNlIENBMRIwEAYDVQQDEwlHbGFuY2UgQ0EwHhcNMTIwMjA5MTcxMDUzWhcN +MjIwMjA2MTcxMDUzWjBZMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0 +ZTESMBAGA1UEChMJT3BlbnN0YWNrMQ8wDQYDVQQLEwZHbGFuY2UxEDAOBgNVBAMT +BzAuMC4wLjAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXpUkQN6pu +avo+gz3o1K4krVdPl1m7NjNJDyD/+ZH0EGNcEN7iag1qPE7JsjqGPNZsQK1dMoXb +Sz+OSi9qvNeJnBcfwUx5qTAtwyAb9AxGkwuMafIU+lWbsclo+dPGsja01ywbXTCZ +bF32iqnpOMYhfxWUdoQYiBkhxxhW9eMPKLS/KkP8/bx+Vaa2XJiAebqkd9nrksAA +BeGc9mlafYBEmiChPdJEPw+1ePA4QVq9aPepDsqAKtGN8JLpmoC3BdxQQTbbwL3Q +8fTXK4tCNUaVk4AbDy/McFq6y0ocQoBPJjihOY35mWG/OLtcI99yPOpWGnps/5aG +/64DDJ2D67Fnaj6gKHV+6TXFO8KZxlnxtgtiZDJBZkneTBt9ArSOv+l6NBsumRz0 +iEJ4o4H1S2TSMnprAvX7WnGtc6Xi9gXahYcDHEelwwYzqAiTBv6hxSp4MZ2dNXa+ +KzOitC7ZbV2qsg0au0wjfE/oSQ3NvsvUr8nOmfutJTvHRAwbC1v4G/tuAsO7O0w2 +0u2B3u+pG06m5+rnEqp+rB9hmukRYTfgEFRRsVIvpFl/cwvPXKRcX03UIMx+lLr9 +Ft+ep7YooBhY3wY2kwCxD4lRYNmbwsCIVywZt40f/4ad98TkufR9NhsfycxGeqbr +mTMFlZ8TTlmP82iohekKCOvoyEuTIWL2+wIDAQABMA0GCSqGSIb3DQEBBQUAA4IC +AQBMUBgV0R+Qltf4Du7u/8IFmGAoKR/mktB7R1gRRAqsvecUt7kIwBexGdavGg1y +0pU0+lgUZjJ20N1SlPD8gkNHfXE1fL6fmMjWz4dtYJjzRVhpufHPeBW4tl8DgHPN +rBGAYQ+drDSXaEjiPQifuzKx8WS+DGA3ki4co5mPjVnVH1xvLIdFsk89z3b3YD1k +yCJ/a9K36x6Z/c67JK7s6MWtrdRF9+MVnRKJ2PK4xznd1kBz16V+RA466wBDdARY +vFbtkafbEqOb96QTonIZB7+fAldKDPZYnwPqasreLmaGOaM8sxtlPYAJ5bjDONbc +AaXG8BMRQyO4FyH237otDKlxPyHOFV66BaffF5S8OlwIMiZoIvq+IcTZOdtDUSW2 +KHNLfe5QEDZdKjWCBrfqAfvNuG13m03WqfmcMHl3o/KiPJlx8l9Z4QEzZ9xcyQGL +cncgeHM9wJtzi2cD/rTDNFsx/gxvoyutRmno7I3NRbKmpsXF4StZioU3USRspB07 +hYXOVnG3pS+PjVby7ThT3gvFHSocguOsxClx1epdUJAmJUbmM7NmOp5WVBVtMtC2 +Su4NG/xJciXitKzw+btb7C7RjO6OEqv/1X/oBDzKBWQAwxUC+lqmnM7W6oqWJFEM +YfTLnrjs7Hj6ThMGcEnfvc46dWK3dz0RjsQzUxugPuEkLA== +-----END CERTIFICATE----- diff --git a/nova/tests/ssl_cert/privatekey.key b/nova/tests/ssl_cert/privatekey.key new file mode 100644 index 000000000..b63df3d29 --- /dev/null +++ b/nova/tests/ssl_cert/privatekey.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEA16VJEDeqbmr6PoM96NSuJK1XT5dZuzYzSQ8g//mR9BBjXBDe +4moNajxOybI6hjzWbECtXTKF20s/jkovarzXiZwXH8FMeakwLcMgG/QMRpMLjGny +FPpVm7HJaPnTxrI2tNcsG10wmWxd9oqp6TjGIX8VlHaEGIgZIccYVvXjDyi0vypD +/P28flWmtlyYgHm6pHfZ65LAAAXhnPZpWn2ARJogoT3SRD8PtXjwOEFavWj3qQ7K +gCrRjfCS6ZqAtwXcUEE228C90PH01yuLQjVGlZOAGw8vzHBaustKHEKATyY4oTmN ++Zlhvzi7XCPfcjzqVhp6bP+Whv+uAwydg+uxZ2o+oCh1fuk1xTvCmcZZ8bYLYmQy +QWZJ3kwbfQK0jr/pejQbLpkc9IhCeKOB9Utk0jJ6awL1+1pxrXOl4vYF2oWHAxxH +pcMGM6gIkwb+ocUqeDGdnTV2viszorQu2W1dqrINGrtMI3xP6EkNzb7L1K/Jzpn7 +rSU7x0QMGwtb+Bv7bgLDuztMNtLtgd7vqRtOpufq5xKqfqwfYZrpEWE34BBUUbFS +L6RZf3MLz1ykXF9N1CDMfpS6/Rbfnqe2KKAYWN8GNpMAsQ+JUWDZm8LAiFcsGbeN +H/+GnffE5Ln0fTYbH8nMRnqm65kzBZWfE05Zj/NoqIXpCgjr6MhLkyFi9vsCAwEA +AQKCAgAA96baQcWr9SLmQOR4NOwLEhQAMWefpWCZhU3amB4FgEVR1mmJjnw868RW +t0v36jH0Dl44us9K6o2Ab+jCi9JTtbWM2Osk6JNkwSlVtsSPVH2KxbbmTTExH50N +sYE3tPj12rlB7isXpRrOzlRwzWZmJBHOtrFlAsdKFYCQc03vdXlKGkBv1BuSXYP/ +8W5ltSYXMspxehkOZvhaIejbFREMPbzDvGlDER1a7Q320qQ7kUr7ISvbY1XJUzj1 +f1HwgEA6w/AhED5Jv6wfgvx+8Yo9hYnflTPbsO1XRS4x7kJxGHTMlFuEsSF1ICYH +Bcos0wUiGcBO2N6uAFuhe98BBn+nOwAPZYWwGkmVuK2psm2mXAHx94GT/XqgK/1r +VWGSoOV7Fhjauc2Nv8/vJU18DXT3OY5hc4iXVeEBkuZwRb/NVUtnFoHxVO/Mp5Fh +/W5KZaLWVrLghzvSQ/KUIM0k4lfKDZpY9ZpOdNgWDyZY8tNrXumUZZimzWdXZ9vR +dBssmd8qEKs1AHGFnMDt56IjLGou6j0qnWsLdR1e/WEFsYzGXLVHCv6vXRNkbjqh +WFw5nA+2Dw1YAsy+YkTfgx2pOe+exM/wxsVPa7tG9oZ374dywUi1k6VoHw5dkmJw +1hbXqSLZtx2N51G+SpGmNAV4vLUF0y3dy2wnrzFkFT4uxh1w8QKCAQEA+h6LwHTK +hgcJx6CQQ6zYRqXo4wdvMooY1FcqJOq7LvJUA2CX5OOLs8qN1TyFrOCuAUTurOrM +ABlQ0FpsIaP8TOGz72dHe2eLB+dD6Bqjn10sEFMn54zWd/w9ympQrO9jb5X3ViTh +sCcdYyXVS9Hz8nzbbIF+DaKlxF2Hh71uRDxXpMPxRcGbOIuKZXUj6RkTIulzqT6o +uawlegWxch05QSgzq/1ASxtjTzo4iuDCAii3N45xqxnB+fV9NXEt4R2oOGquBRPJ +LxKcOnaQKBD0YNX4muTq+zPlv/kOb8/ys2WGWDUrNkpyJXqhTve4KONjqM7+iL/U +4WdJuiCjonzk/QKCAQEA3Lc+kNq35FNLxMcnCVcUgkmiCWZ4dyGZZPdqjOPww1+n +bbudGPzY1nxOvE60dZM4or/tm6qlXYfb2UU3+OOJrK9s297EQybZ8DTZu2GHyitc +NSFV3Gl4cgvKdbieGKkk9X2dV9xSNesNvX9lJEnQxuwHDTeo8ubLHtV88Ml1xokn +7W+IFiyEuUIL4e5/fadbrI3EwMrbCF4+9VcfABx4PTNMzdc8LsncCMXE+jFX8AWp +TsT2JezTe5o2WpvBoKMAYhJQNQiaWATn00pDVY/70H1vK3ljomAa1IUdOr/AhAF7 +3jL0MYMgXSHzXZOKAtc7yf+QfFWF1Ls8+sen1clJVwKCAQEAp59rB0r+Iz56RmgL +5t7ifs5XujbURemY5E2aN+18DuVmenD0uvfoO1DnJt4NtCNLWhxpXEdq+jH9H/VJ +fG4a+ydT4IC1vjVRTrWlo9qeh4H4suQX3S1c2kKY4pvHf25blH/Lp9bFzbkZD8Ze +IRcOxxb4MsrBwL+dGnGYD9dbG63ZCtoqSxaKQSX7VS1hKKmeUopj8ivFBdIht5oz +JogBQ/J+Vqg9u1gagRFCrYgdXTcOOtRix0lW336vL+6u0ax/fXe5MjvlW3+8Zc3p +pIBgVrlvh9ccx8crFTIDg9m4DJRgqaLQV+0ifI2np3WK3RQvSQWYPetZ7sm69ltD +bvUGvQKCAQAz5CEhjUqOs8asjOXwnDiGKSmfbCgGWi/mPQUf+rcwN9z1P5a/uTKB +utgIDbj/q401Nkp2vrgCNV7KxitSqKxFnTjKuKUL5KZ4gvRtyZBTR751/1BgcauP +pJYE91K0GZBG5zGG5pWtd4XTd5Af5/rdycAeq2ddNEWtCiRFuBeohbaNbBtimzTZ +GV4R0DDJKf+zoeEQMqEsZnwG0mTHceoS+WylOGU92teQeG7HI7K5C5uymTwFzpgq +ByegRd5QFgKRDB0vWsZuyzh1xI/wHdnmOpdYcUGre0zTijhFB7ALWQ32P6SJv3ps +av78kSNxZ4j3BM7DbJf6W8sKasZazOghAoIBAHekpBcLq9gRv2+NfLYxWN2sTZVB +1ldwioG7rWvk5YQR2akukecI3NRjtC5gG2vverawG852Y4+oLfgRMHxgp0qNStwX +juTykzPkCwZn8AyR+avC3mkrtJyM3IigcYOu4/UoaRDFa0xvCC1EfumpnKXIpHag +miSQZf2sVbgqb3/LWvHIg/ceOP9oGJve87/HVfQtBoLaIe5RXCWkqB7mcI/exvTS +8ShaW6v2Fe5Bzdvawj7sbsVYRWe93Aq2tmIgSX320D2RVepb6mjD4nr0IUaM3Yed +TFT7e2ikWXyDLLgVkDTU4Qe8fr3ZKGfanCIDzvgNw6H1gRi+2WQgOmjilMQ= +-----END RSA PRIVATE KEY----- diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index fb2e76e45..11c16d6dd 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -26,9 +26,9 @@ from boto.ec2 import regioninfo from boto import exception as boto_exc # newer versions of boto use their own wrapper on top of httplib.HTTPResponse try: - from boto.connection import HTTPResponse + import boto.connection as httplib except ImportError: - from httplib import HTTPResponse + import httplib import fixtures import webob @@ -79,7 +79,7 @@ class FakeHttplibConnection(object): # guess that's a function the web server usually provides. resp = "HTTP/1.0 %s" % resp self.sock = FakeHttplibSocket(resp) - self.http_response = HTTPResponse(self.sock) + self.http_response = httplib.HTTPResponse(self.sock) # NOTE(vish): boto is accessing private variables for some reason self._HTTPConnection__response = self.http_response self.http_response.begin() diff --git a/nova/tests/test_availability_zones.py b/nova/tests/test_availability_zones.py index 2c5c06921..4192fa08f 100644 --- a/nova/tests/test_availability_zones.py +++ b/nova/tests/test_availability_zones.py @@ -23,7 +23,6 @@ from nova import availability_zones as az from nova import context from nova import db from nova.openstack.common import cfg -from nova import service from nova import test CONF = cfg.CONF 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_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_db_api.py b/nova/tests/test_db_api.py index e43a32c19..684f9fded 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -253,11 +253,11 @@ 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) @@ -1510,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): @@ -1671,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_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_imagecache.py b/nova/tests/test_imagecache.py index 8142312b9..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)]) 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 75e758cde..72c886529 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -3833,7 +3833,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) @@ -4250,7 +4249,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])} @@ -4747,6 +4746,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 a6c150971..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 @@ -87,6 +88,16 @@ def _have_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.""" @@ -136,12 +147,6 @@ class TestMigrations(test.TestCase): # and recreate it, which ensures that we have no side-effects # from the tests self._reset_databases() - - # remove these from the list so they aren't used in the migration tests - if "mysqlcitest" in self.engines: - del self.engines["mysqlcitest"] - if "mysqlcitest" in self.test_databases: - del self.test_databases["mysqlcitest"] super(TestMigrations, self).tearDown() def _reset_databases(self): @@ -233,19 +238,11 @@ class TestMigrations(test.TestCase): self.engines["mysqlcitest"] = engine self.test_databases["mysqlcitest"] = connect_string - # Test that we end in an innodb - self._check_mysql_innodb(engine) - # Test IP transition - self._check_mysql_migration_149(engine) - - def _check_mysql_innodb(self, engine): # build a fully populated mysql database with all the tables self._reset_databases() self._walk_versions(engine, False, False) - uri = _get_connect_string("mysql", database="information_schema") - connection = sqlalchemy.create_engine(uri).connect() - + connection = engine.connect() # sanity check total = connection.execute("SELECT count(*) " "from information_schema.TABLES " @@ -259,92 +256,8 @@ class TestMigrations(test.TestCase): "and TABLE_NAME!='migrate_version'") count = noninnodb.scalar() self.assertEqual(count, 0, "%d non InnoDB tables created" % count) - - def test_migration_149_postgres(self): - """Test updating a table with IPAddress columns.""" - if not _is_backend_avail('postgres'): - self.skipTest("postgres not available") - - connect_string = _get_connect_string("postgres") - engine = sqlalchemy.create_engine(connect_string) - - self.engines["postgrescitest"] = engine - self.test_databases["postgrescitest"] = connect_string - - self._reset_databases() - migration_api.version_control(engine, TestMigrations.REPOSITORY, - migration.INIT_VERSION) - - connection = engine.connect() - - self._migrate_up(engine, 148) - IPS = ("127.0.0.1", "255.255.255.255", "2001:db8::1:2", "::1") - connection.execute("INSERT INTO provider_fw_rules " - " (protocol, from_port, to_port, cidr)" - "VALUES ('tcp', 1234, 1234, '%s'), " - " ('tcp', 1234, 1234, '%s'), " - " ('tcp', 1234, 1234, '%s'), " - " ('tcp', 1234, 1234, '%s')" % IPS) - self.assertEqual('character varying', - connection.execute( - "SELECT data_type FROM INFORMATION_SCHEMA.COLUMNS " - "WHERE table_name='provider_fw_rules' " - "AND table_catalog='openstack_citest' " - "AND column_name='cidr'").scalar()) - - self._migrate_up(engine, 149) - self.assertEqual(IPS, - tuple(tup[0] for tup in connection.execute( - "SELECT cidr from provider_fw_rules").fetchall())) - self.assertEqual('inet', - connection.execute( - "SELECT data_type FROM INFORMATION_SCHEMA.COLUMNS " - "WHERE table_name='provider_fw_rules' " - "AND table_catalog='openstack_citest' " - "AND column_name='cidr'").scalar()) - connection.close() - - def _check_mysql_migration_149(self, engine): - """Test updating a table with IPAddress columns.""" - self._reset_databases() - migration_api.version_control(engine, TestMigrations.REPOSITORY, - migration.INIT_VERSION) - - uri = _get_connect_string("mysql", database="openstack_citest") - connection = sqlalchemy.create_engine(uri).connect() - - self._migrate_up(engine, 148) - - IPS = ("127.0.0.1", "255.255.255.255", "2001:db8::1:2", "::1") - connection.execute("INSERT INTO provider_fw_rules " - " (protocol, from_port, to_port, cidr)" - "VALUES ('tcp', 1234, 1234, '%s'), " - " ('tcp', 1234, 1234, '%s'), " - " ('tcp', 1234, 1234, '%s'), " - " ('tcp', 1234, 1234, '%s')" % IPS) - self.assertEqual('varchar(255)', - connection.execute( - "SELECT column_type FROM INFORMATION_SCHEMA.COLUMNS " - "WHERE table_name='provider_fw_rules' " - "AND table_schema='openstack_citest' " - "AND column_name='cidr'").scalar()) - connection.close() - self._migrate_up(engine, 149) - - connection = sqlalchemy.create_engine(uri).connect() - - self.assertEqual(IPS, - tuple(tup[0] for tup in connection.execute( - "SELECT cidr from provider_fw_rules").fetchall())) - self.assertEqual('varchar(39)', - connection.execute( - "SELECT column_type FROM INFORMATION_SCHEMA.COLUMNS " - "WHERE table_name='provider_fw_rules' " - "AND table_schema='openstack_citest' " - "AND column_name='cidr'").scalar()) - def _walk_versions(self, engine=None, snake_walk=False, downgrade=True): # Determine latest version script from the repo, then # upgrade from 1 through to the latest, with no data @@ -366,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) @@ -391,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) @@ -399,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_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_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 9eab72c5b..84d56cadf 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -778,3 +778,14 @@ class IntLikeTestCase(test.TestCase): self.assertFalse( utils.is_int_like("0cc3346e-9fef-4445-abe6-5d2b2690ec64")) self.assertFalse(utils.is_int_like("a1")) + + +class MetadataToDictTestCase(test.TestCase): + def test_metadata_to_dict(self): + self.assertEqual(utils.metadata_to_dict( + [{'key': 'foo1', 'value': 'bar'}, + {'key': 'foo2', 'value': 'baz'}]), + {'foo1': 'bar', 'foo2': 'baz'}) + + def test_metadata_to_dict_empty(self): + self.assertEqual(utils.metadata_to_dict([]), {}) diff --git a/nova/tests/test_virt_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..af4571dd2 100644 --- a/nova/tests/test_virt_disk_vfs_localfs.py +++ b/nova/tests/test_virt_disk_vfs_localfs.py @@ -104,7 +104,6 @@ def fake_execute(*args, **kwargs): else: path = args[1] append = False - print str(files) if not path in files: files[path] = { "content": "Hello World", diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py index 9747ecccd..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): 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 067e28a13..aa640810b 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]) @@ -2574,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/utils.py b/nova/utils.py index 75cba0a7c..52d4868c9 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) @@ -1143,6 +1150,7 @@ def temporary_chown(path, owner_uid=None): @contextlib.contextmanager def tempdir(**kwargs): + tempfile.tempdir = CONF.tempdir tmpdir = tempfile.mkdtemp(**kwargs) try: yield tmpdir @@ -1257,3 +1265,10 @@ def last_bytes(file_like_object, num): remaining = file_like_object.tell() return (file_like_object.read(), remaining) + + +def metadata_to_dict(metadata): + result = {} + for item in metadata: + result[item['key']] = item['value'] + return result diff --git a/nova/virt/baremetal/__init__.py b/nova/virt/baremetal/__init__.py index e3ecef821..9c8318660 100644 --- a/nova/virt/baremetal/__init__.py +++ b/nova/virt/baremetal/__init__.py @@ -12,4 +12,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from nova.virt.baremetal.driver import BareMetalDriver +from nova.virt.baremetal import driver + +BareMetalDriver = driver.BareMetalDriver diff --git a/nova/virt/baremetal/db/api.py b/nova/virt/baremetal/db/api.py index 206a59b4f..002425333 100644 --- a/nova/virt/baremetal/db/api.py +++ b/nova/virt/baremetal/db/api.py @@ -50,16 +50,21 @@ from nova import utils # because utils.LazyPluggable doesn't support reading from # option groups. See bug #1093043. db_opts = [ - cfg.StrOpt('baremetal_db_backend', + cfg.StrOpt('db_backend', default='sqlalchemy', - help='The backend to use for db'), + help='The backend to use for bare-metal database'), ] +baremetal_group = cfg.OptGroup(name='baremetal', + title='Baremetal Options') + CONF = cfg.CONF -CONF.register_opts(db_opts) +CONF.register_group(baremetal_group) +CONF.register_opts(db_opts, baremetal_group) IMPL = utils.LazyPluggable( - 'baremetal_db_backend', + 'db_backend', + config_group='baremetal', sqlalchemy='nova.virt.baremetal.db.sqlalchemy.api') diff --git a/nova/virt/baremetal/db/migration.py b/nova/virt/baremetal/db/migration.py index 40631bf45..d630ccf65 100644 --- a/nova/virt/baremetal/db/migration.py +++ b/nova/virt/baremetal/db/migration.py @@ -22,7 +22,8 @@ from nova import utils IMPL = utils.LazyPluggable( - 'baremetal_db_backend', + 'db_backend', + config_group='baremetal', sqlalchemy='nova.virt.baremetal.db.sqlalchemy.migration') INIT_VERSION = 0 diff --git a/nova/virt/baremetal/db/sqlalchemy/api.py b/nova/virt/baremetal/db/sqlalchemy/api.py index e2240053c..34bcd1229 100644 --- a/nova/virt/baremetal/db/sqlalchemy/api.py +++ b/nova/virt/baremetal/db/sqlalchemy/api.py @@ -23,14 +23,13 @@ from sqlalchemy.sql.expression import asc from sqlalchemy.sql.expression import literal_column -from nova.db.sqlalchemy.api import is_user_context -from nova.db.sqlalchemy.api import require_admin_context +from nova.db.sqlalchemy import api as sqlalchemy_api from nova import exception from nova.openstack.common import log as logging from nova.openstack.common import timeutils from nova.openstack.common import uuidutils from nova.virt.baremetal.db.sqlalchemy import models -from nova.virt.baremetal.db.sqlalchemy.session import get_session +from nova.virt.baremetal.db.sqlalchemy import session as db_session LOG = logging.getLogger(__name__) @@ -44,7 +43,7 @@ def model_query(context, *args, **kwargs): :param project_only: if present and context is user-type, then restrict query to match the context's project_id. """ - session = kwargs.get('session') or get_session() + session = kwargs.get('session') or db_session.get_session() read_deleted = kwargs.get('read_deleted') or context.read_deleted project_only = kwargs.get('project_only') @@ -60,7 +59,7 @@ def model_query(context, *args, **kwargs): raise Exception( _("Unrecognized read_deleted value '%s'") % read_deleted) - if project_only and is_user_context(context): + if project_only and sqlalchemy_api.is_user_context(context): query = query.filter_by(project_id=context.project_id) return query @@ -68,7 +67,7 @@ def model_query(context, *args, **kwargs): def _save(ref, session=None): if not session: - session = get_session() + session = db_session.get_session() # We must not call ref.save() with session=None, otherwise NovaBase # uses nova-db's session, which cannot access bm-db. ref.save(session=session) @@ -81,7 +80,7 @@ def _build_node_order_by(query): return query -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_node_get_all(context, service_host=None): query = model_query(context, models.BareMetalNode, read_deleted="no") if service_host: @@ -89,7 +88,7 @@ def bm_node_get_all(context, service_host=None): return query.all() -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_node_find_free(context, service_host=None, cpus=None, memory_mb=None, local_gb=None): query = model_query(context, models.BareMetalNode, read_deleted="no") @@ -106,7 +105,7 @@ def bm_node_find_free(context, service_host=None, return query.first() -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_node_get(context, bm_node_id): # bm_node_id may be passed as a string. Convert to INT to improve DB perf. bm_node_id = int(bm_node_id) @@ -120,7 +119,7 @@ def bm_node_get(context, bm_node_id): return result -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_node_get_by_instance_uuid(context, instance_uuid): if not uuidutils.is_uuid_like(instance_uuid): raise exception.InstanceNotFound(instance_id=instance_uuid) @@ -135,7 +134,7 @@ def bm_node_get_by_instance_uuid(context, instance_uuid): return result -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_node_create(context, values): bm_node_ref = models.BareMetalNode() bm_node_ref.update(values) @@ -143,14 +142,14 @@ def bm_node_create(context, values): return bm_node_ref -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_node_update(context, bm_node_id, values): model_query(context, models.BareMetalNode, read_deleted="no").\ filter_by(id=bm_node_id).\ update(values) -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_node_set_uuid_safe(context, bm_node_id, values): """Associate an instance to a node safely @@ -164,7 +163,7 @@ def bm_node_set_uuid_safe(context, bm_node_id, values): raise exception.NovaException(_( "instance_uuid must be supplied to bm_node_set_uuid_safe")) - session = get_session() + session = db_session.get_session() with session.begin(): query = model_query(context, models.BareMetalNode, session=session, read_deleted="no").\ @@ -181,7 +180,7 @@ def bm_node_set_uuid_safe(context, bm_node_id, values): return ref -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_node_destroy(context, bm_node_id): model_query(context, models.BareMetalNode).\ filter_by(id=bm_node_id).\ @@ -190,13 +189,13 @@ def bm_node_destroy(context, bm_node_id): 'updated_at': literal_column('updated_at')}) -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_pxe_ip_get_all(context): query = model_query(context, models.BareMetalPxeIp, read_deleted="no") return query.all() -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_pxe_ip_create(context, address, server_address): ref = models.BareMetalPxeIp() ref.address = address @@ -205,7 +204,7 @@ def bm_pxe_ip_create(context, address, server_address): return ref -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_pxe_ip_create_direct(context, bm_pxe_ip): ref = bm_pxe_ip_create(context, address=bm_pxe_ip['address'], @@ -213,7 +212,7 @@ def bm_pxe_ip_create_direct(context, bm_pxe_ip): return ref -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_pxe_ip_destroy(context, ip_id): # Delete physically since it has unique columns model_query(context, models.BareMetalPxeIp, read_deleted="no").\ @@ -221,7 +220,7 @@ def bm_pxe_ip_destroy(context, ip_id): delete() -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_pxe_ip_destroy_by_address(context, address): # Delete physically since it has unique columns model_query(context, models.BareMetalPxeIp, read_deleted="no").\ @@ -229,7 +228,7 @@ def bm_pxe_ip_destroy_by_address(context, address): delete() -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_pxe_ip_get(context, ip_id): result = model_query(context, models.BareMetalPxeIp, read_deleted="no").\ filter_by(id=ip_id).\ @@ -238,7 +237,7 @@ def bm_pxe_ip_get(context, ip_id): return result -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_pxe_ip_get_by_bm_node_id(context, bm_node_id): result = model_query(context, models.BareMetalPxeIp, read_deleted="no").\ filter_by(bm_node_id=bm_node_id).\ @@ -250,9 +249,9 @@ def bm_pxe_ip_get_by_bm_node_id(context, bm_node_id): return result -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_pxe_ip_associate(context, bm_node_id): - session = get_session() + session = db_session.get_session() with session.begin(): # Check if the node really exists node_ref = model_query(context, models.BareMetalNode, @@ -288,14 +287,14 @@ def bm_pxe_ip_associate(context, bm_node_id): return ip_ref.id -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_pxe_ip_disassociate(context, bm_node_id): model_query(context, models.BareMetalPxeIp, read_deleted="no").\ filter_by(bm_node_id=bm_node_id).\ update({'bm_node_id': None}) -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_interface_get(context, if_id): result = model_query(context, models.BareMetalInterface, read_deleted="no").\ @@ -309,14 +308,14 @@ def bm_interface_get(context, if_id): return result -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_interface_get_all(context): query = model_query(context, models.BareMetalInterface, read_deleted="no") return query.all() -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_interface_destroy(context, if_id): # Delete physically since it has unique columns model_query(context, models.BareMetalInterface, read_deleted="no").\ @@ -324,7 +323,7 @@ def bm_interface_destroy(context, if_id): delete() -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_interface_create(context, bm_node_id, address, datapath_id, port_no): ref = models.BareMetalInterface() ref.bm_node_id = bm_node_id @@ -335,9 +334,9 @@ def bm_interface_create(context, bm_node_id, address, datapath_id, port_no): return ref.id -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_interface_set_vif_uuid(context, if_id, vif_uuid): - session = get_session() + session = db_session.get_session() with session.begin(): bm_interface = model_query(context, models.BareMetalInterface, read_deleted="no", session=session).\ @@ -361,7 +360,7 @@ def bm_interface_set_vif_uuid(context, if_id, vif_uuid): raise e -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_interface_get_by_vif_uuid(context, vif_uuid): result = model_query(context, models.BareMetalInterface, read_deleted="no").\ @@ -375,7 +374,7 @@ def bm_interface_get_by_vif_uuid(context, vif_uuid): return result -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_interface_get_all_by_bm_node_id(context, bm_node_id): result = model_query(context, models.BareMetalInterface, read_deleted="no").\ @@ -388,7 +387,7 @@ def bm_interface_get_all_by_bm_node_id(context, bm_node_id): return result -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_deployment_create(context, key, image_path, pxe_config_path, root_mb, swap_mb): ref = models.BareMetalDeployment() @@ -401,7 +400,7 @@ def bm_deployment_create(context, key, image_path, pxe_config_path, root_mb, return ref.id -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_deployment_get(context, dep_id): result = model_query(context, models.BareMetalDeployment, read_deleted="no").\ @@ -410,7 +409,7 @@ def bm_deployment_get(context, dep_id): return result -@require_admin_context +@sqlalchemy_api.require_admin_context def bm_deployment_destroy(context, dep_id): model_query(context, models.BareMetalDeployment).\ filter_by(id=dep_id).\ diff --git a/nova/virt/baremetal/db/sqlalchemy/migration.py b/nova/virt/baremetal/db/sqlalchemy/migration.py index 929793e70..cfc26c04c 100644 --- a/nova/virt/baremetal/db/sqlalchemy/migration.py +++ b/nova/virt/baremetal/db/sqlalchemy/migration.py @@ -25,7 +25,7 @@ import sqlalchemy from nova import exception from nova.openstack.common import log as logging from nova.virt.baremetal.db import migration -from nova.virt.baremetal.db.sqlalchemy.session import get_engine +from nova.virt.baremetal.db.sqlalchemy import session LOG = logging.getLogger(__name__) @@ -71,24 +71,25 @@ def db_sync(version=None): current_version = db_version() repository = _find_migrate_repo() if version is None or version > current_version: - return versioning_api.upgrade(get_engine(), repository, version) + return versioning_api.upgrade(session.get_engine(), repository, + version) else: - return versioning_api.downgrade(get_engine(), repository, + return versioning_api.downgrade(session.get_engine(), repository, version) def db_version(): repository = _find_migrate_repo() try: - return versioning_api.db_version(get_engine(), repository) + return versioning_api.db_version(session.get_engine(), repository) except versioning_exceptions.DatabaseNotControlledError: meta = sqlalchemy.MetaData() - engine = get_engine() + engine = session.get_engine() meta.reflect(bind=engine) tables = meta.tables if len(tables) == 0: db_version_control(migration.INIT_VERSION) - return versioning_api.db_version(get_engine(), repository) + return versioning_api.db_version(session.get_engine(), repository) else: # Some pre-Essex DB's may not be version controlled. # Require them to upgrade using Essex first. @@ -98,7 +99,7 @@ def db_version(): def db_version_control(version=None): repository = _find_migrate_repo() - versioning_api.version_control(get_engine(), repository, version) + versioning_api.version_control(session.get_engine(), repository, version) return version diff --git a/nova/virt/baremetal/fake.py b/nova/virt/baremetal/fake.py index 7a400af6f..b3f39fdc3 100644 --- a/nova/virt/baremetal/fake.py +++ b/nova/virt/baremetal/fake.py @@ -17,7 +17,7 @@ # under the License. from nova.virt.baremetal import base -from nova.virt.firewall import NoopFirewallDriver +from nova.virt import firewall class FakeDriver(base.NodeDriver): @@ -52,7 +52,7 @@ class FakePowerManager(base.PowerManager): super(FakePowerManager, self).__init__(**kwargs) -class FakeFirewallDriver(NoopFirewallDriver): +class FakeFirewallDriver(firewall.NoopFirewallDriver): def __init__(self): super(FakeFirewallDriver, self).__init__() diff --git a/nova/virt/baremetal/ipmi.py b/nova/virt/baremetal/ipmi.py index 97c158727..5d4a87625 100644 --- a/nova/virt/baremetal/ipmi.py +++ b/nova/virt/baremetal/ipmi.py @@ -25,7 +25,7 @@ import os import stat import tempfile -from nova.exception import InvalidParameterValue +from nova import exception from nova.openstack.common import cfg from nova.openstack.common import log as logging from nova import paths @@ -104,13 +104,17 @@ class IPMI(base.PowerManager): self.port = node['terminal_port'] if self.node_id == None: - raise InvalidParameterValue(_("Node id not supplied to IPMI")) + raise exception.InvalidParameterValue(_("Node id not supplied " + "to IPMI")) if self.address == None: - raise InvalidParameterValue(_("Address not supplied to IPMI")) + raise exception.InvalidParameterValue(_("Address not supplied " + "to IPMI")) if self.user == None: - raise InvalidParameterValue(_("User not supplied to IPMI")) + raise exception.InvalidParameterValue(_("User not supplied " + "to IPMI")) if self.password == None: - raise InvalidParameterValue(_("Password not supplied to IPMI")) + raise exception.InvalidParameterValue(_("Password not supplied " + "to IPMI")) def _exec_ipmitool(self, command): args = ['ipmitool', @@ -126,7 +130,7 @@ class IPMI(base.PowerManager): args.append(pwfile) args.extend(command.split(" ")) out, err = utils.execute(*args, attempts=3) - LOG.debug(_("ipmitool stdout: '%(out)s', stderr: '%(err)%s'"), + LOG.debug(_("ipmitool stdout: '%(out)s', stderr: '%(err)s'"), locals()) return out, err finally: diff --git a/nova/virt/baremetal/pxe.py b/nova/virt/baremetal/pxe.py index 0daac1d46..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 @@ -344,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 @@ -419,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/disk/api.py b/nova/virt/disk/api.py index d080f6d36..304781a64 100644 --- a/nova/virt/disk/api.py +++ b/nova/virt/disk/api.py @@ -271,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 " @@ -287,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() @@ -324,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): @@ -411,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/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 9316b2598..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, block_device_info=None): + 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/ioutils.py b/nova/virt/hyperv/ioutils.py deleted file mode 100644 index d927e317f..000000000 --- a/nova/virt/hyperv/ioutils.py +++ /dev/null @@ -1,26 +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. - -""" -Utility class to ease the task of creating stubs of built in IO functions. -""" - -import __builtin__ - - -def open(name, mode): - return __builtin__.open(name, mode) 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 b69cf7bf1..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 a 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_path): + #Slots starts from 0, so the lenght of the disks gives us the free slot + return self._vmutils.get_attached_disks_count(scsi_controller_path) - 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_() + "'") - #Slots starts from 0, so the length of the disks gives us the free slot - return len(volumes) - - def detach_volume(self, connection_info, instance_name, mountpoint): + 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/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 e4da5cbde..9ed7a054c 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): @@ -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): @@ -806,35 +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 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 @@ -845,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): @@ -874,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: @@ -882,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]: @@ -894,6 +1010,10 @@ 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 @@ -966,11 +1086,6 @@ class LibvirtDriver(driver.ComputeDriver): 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_existing_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']): @@ -1423,22 +1538,21 @@ 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') @@ -2831,7 +2945,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 3323b8f1d..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 diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py index 4b3517da7..88110055c 100644 --- a/nova/virt/libvirt/utils.py +++ b/nova/virt/libvirt/utils.py @@ -63,7 +63,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 +89,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 +275,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 +294,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 +405,16 @@ 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, '-s', snapshot_name, disk_path, out_path) + + # When snapshot name is omitted we do a basic convert, which + # is used by live snapshots. + if snapshot_name is None: + qemu_img_cmd = ('qemu-img', 'convert', '-f', source_fmt, '-O', + dest_fmt, disk_path, out_path) + execute(*qemu_img_cmd) diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 83d43a6db..45c299830 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -28,7 +28,7 @@ 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 = [ @@ -72,19 +72,22 @@ class LibvirtBaseVIFDriver(object): return conf + def plug(self, instance, vif): + pass + + def unplug(self, instance, vif): + pass + -class LibvirtBridgeDriver(LibvirtBaseVIFDriver): - """VIF driver for Linux bridge.""" +class LibvirtGenericVIFDriver(LibvirtBaseVIFDriver): + """Generic VIF driver for libvirt networking.""" def get_bridge_name(self, network): return network['bridge'] - def get_config(self, instance, network, mapping): + def get_config_bridge(self, instance, network, mapping): """Get VIF configurations for bridge type.""" - - mac_id = mapping['mac'].replace(':', '') - - conf = super(LibvirtBridgeDriver, + conf = super(LibvirtGenericVIFDriver, self).get_config(instance, network, mapping) @@ -93,6 +96,7 @@ class LibvirtBridgeDriver(LibvirtBaseVIFDriver): 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 @@ -112,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')): @@ -135,9 +160,71 @@ class LibvirtBridgeDriver(LibvirtBaseVIFDriver): 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): @@ -150,6 +237,9 @@ class LibvirtOpenVswitchDriver(LibvirtBaseVIFDriver): def get_bridge_name(self, network): return network.get('bridge') or CONF.libvirt_ovs_bridge + def get_ovs_interfaceid(self, mapping): + return mapping.get('ovs_interfaceid') or mapping['vif_uuid'] + def get_config(self, instance, network, mapping): dev = self.get_vif_devname(mapping) @@ -162,55 +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(self.get_bridge_name(network), - dev, iface_id, mapping['mac'], - instance['uuid']) + linux_net.create_tap_dev(dev) + linux_net.create_ovs_vif_port(self.get_bridge_name(network), + dev, iface_id, mapping['mac'], + instance['uuid']) def unplug(self, instance, vif): """Unplug the VIF by deleting the port from the bridge.""" try: network, mapping = vif - self.delete_ovs_vif_port(self.get_bridge_name(network), - self.get_vif_devname(mapping)) + linux_net.delete_ovs_vif_port(self.get_bridge_name(network), + self.get_vif_devname(mapping)) except exception.ProcessExecutionError: LOG.exception(_("Failed while unplugging vif"), instance=instance) -class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver, - LibvirtOpenVswitchDriver): +class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver): """VIF driver that uses OVS + Linux Bridge for iptables compatibility. Enables the use of OVS-based Quantum plugins while at the same @@ -229,6 +290,9 @@ class LibvirtHybridOVSBridgeDriver(LibvirtBridgeDriver, def get_bridge_name(self, network): return network.get('bridge') or CONF.libvirt_ovs_bridge + def get_ovs_interfaceid(self, mapping): + return mapping.get('ovs_interfaceid') or mapping['vif_uuid'] + def get_config(self, instance, network, mapping): br_name = self.get_br_name(mapping['vif_uuid']) network['bridge'] = br_name @@ -247,9 +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) @@ -258,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(self.get_bridge_name(network), - v2_name, iface_id, mapping['mac'], - instance['uuid']) + linux_net.create_ovs_vif_port(self.get_bridge_name(network), + v2_name, iface_id, mapping['mac'], + instance['uuid']) def unplug(self, instance, vif): """UnPlug using hybrid strategy @@ -270,16 +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(self.get_bridge_name(network), v2_name) + linux_net.delete_ovs_vif_port(self.get_bridge_name(network), + v2_name) except exception.ProcessExecutionError: LOG.exception(_("Failed while unplugging vif"), instance=instance) @@ -291,6 +355,9 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver): def get_bridge_name(self, network): return network.get('bridge') or CONF.libvirt_ovs_bridge + def get_ovs_interfaceid(self, mapping): + return mapping.get('ovs_interfaceid') or mapping['vif_uuid'] + def get_config(self, instance, network, mapping): """Pass data required to create OVS virtual port element.""" conf = super(LibvirtOpenVswitchVirtualPortDriver, @@ -299,7 +366,8 @@ class LibvirtOpenVswitchVirtualPortDriver(LibvirtBaseVIFDriver): mapping) designer.set_vif_host_backend_ovs_config( - conf, self.get_bridge_name(network), mapping['vif_uuid'], + conf, self.get_bridge_name(network), + self.get_ovs_interfaceid(mapping), self.get_vif_devname(mapping)) return conf diff --git a/nova/virt/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 5a4a2938b..87da30a14 100644 --- a/nova/virt/powervm/operator.py +++ b/nova/virt/powervm/operator.py @@ -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 d2bdad0c1..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. # @@ -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..75f85454b 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 = self._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 _get_datacenter_name_and_ref(self): + 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_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,46 @@ 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 + + @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 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/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/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 52a5f37b2..fe4ce9409 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -196,13 +196,6 @@ class ImageType(object): }.get(image_type_id) -def _system_metadata_to_dict(system_metadata): - result = {} - for item in system_metadata: - result[item['key']] = item['value'] - return result - - def create_vm(session, instance, name_label, kernel, ramdisk, use_pv_kernel=False): """Create a VM record. Returns new VM reference. @@ -722,35 +715,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 +958,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 +1051,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: diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 4a8372cda..8d3a3bed2 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -59,7 +59,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 +164,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 @@ -661,9 +669,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 +684,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) diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index 5f79b6c3a..c2d717cfd 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -125,6 +125,7 @@ class VolumeOps(object): try: vbd_ref = vm_utils.find_vbd_by_number(self._session, vm_ref, device_number) + sr_ref = volume_utils.find_sr_from_vbd(self._session, vbd_ref) except volume_utils.StorageError, exc: LOG.exception(exc) raise Exception(_('Unable to locate volume %s') % mountpoint) @@ -143,7 +144,6 @@ class VolumeOps(object): # Forget SR only if no other volumes on this host are using it try: - sr_ref = volume_utils.find_sr_from_vbd(self._session, vbd_ref) volume_utils.purge_sr(self._session, sr_ref) except volume_utils.StorageError, exc: LOG.exception(exc) diff --git a/nova/volume/cinder.py b/nova/volume/cinder.py index 3e1ccc66b..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 @@ -63,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: @@ -139,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/run_tests.sh b/run_tests.sh index 238f5e194..3bf996fa2 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -91,7 +91,7 @@ function run_tests { # Just run the test suites in current environment set +e testrargs=`echo "$testrargs" | sed -e's/^\s*\(.*\)\s*$/\1/'` - TESTRTESTS="$TESTRTESTS --testr-args='$testrargs'" + TESTRTESTS="$TESTRTESTS --testr-args='$testropts $testrargs'" echo "Running \`${wrapper} $TESTRTESTS\`" bash -c "${wrapper} $TESTRTESTS" RESULT=$? @@ -99,6 +99,13 @@ function run_tests { 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 } @@ -118,7 +125,7 @@ function run_pep8 { # NOTE(lzyeval): Avoid selecting *.pyc files to reduce pep8 check-up time # when running on devstack. srcfiles=`find nova -type f -name "*.py" ! -wholename "nova\/openstack*"` - srcfiles+=" `find bin -type f ! -name "nova.conf*" ! -name "*api-paste.ini*"`" + srcfiles+=" `find bin -type f ! -name "nova.conf*" ! -name "*api-paste.ini*" ! -name "*~"`" srcfiles+=" `find tools -type f -name "*.py"`" srcfiles+=" `find plugins -type f -name "*.py"`" srcfiles+=" `find smoketests -type f -name "*.py"`" @@ -135,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. @@ -147,7 +155,7 @@ function run_pep8 { } -TESTRTESTS="python setup.py testr $testropts" +TESTRTESTS="python setup.py testr" if [ $never_venv -eq 0 ] then @@ -201,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 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/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 56f6694bd..801a87899 100755 --- a/tools/hacking.py +++ b/tools/hacking.py @@ -18,7 +18,7 @@ """nova HACKING file compliance testing -built on top of pep8.py +Built on top of pep8.py """ import inspect @@ -49,6 +49,8 @@ START_DOCSTRING_TRIPLE = ['u"""', 'r"""', '"""', "u'''", "r'''", "'''"] END_DOCSTRING_TRIPLE = ['"""', "'''"] VERBOSE_MISSING_IMPORT = os.getenv('HACKING_VERBOSE_MISSING_IMPORT', 'False') +_missingImport = set([]) + # Monkey patch broken excluded filter in pep8 # See https://github.com/jcrocholl/pep8/pull/111 @@ -103,7 +105,7 @@ def import_normalize(line): return line -def nova_todo_format(physical_line): +def nova_todo_format(physical_line, tokens): """Check for 'TODO()'. nova HACKING guide recommendation for TODO: @@ -111,14 +113,13 @@ def nova_todo_format(physical_line): Okay: #TODO(sdague) N101: #TODO fail + N101: #TODO (jogo) fail """ # TODO(sdague): TODO check shouldn't fail inside of space pos = physical_line.find('TODO') pos1 = physical_line.find('TODO(') pos2 = physical_line.find('#') # make sure it's a comment - # TODO(sdague): should be smarter on this test - this_test = physical_line.find('N101: #TODO fail') - if pos != pos1 and pos2 >= 0 and pos2 < pos and this_test == -1: + if (pos != pos1 and pos2 >= 0 and pos2 < pos and len(tokens) == 0): return pos, "N101: Use TODO(NAME)" @@ -165,8 +166,6 @@ def nova_one_import_per_line(logical_line): not is_import_exception(parts[1])): yield pos, "N301: one import per line" -_missingImport = set([]) - def nova_import_module_only(logical_line): r"""Check for import module only. @@ -175,20 +174,23 @@ def nova_import_module_only(logical_line): Do not import objects, only modules Okay: from os import path - N302 from os.path import mkdir as mkdir2 - N303 import bubba - N304 import blueblue + Okay: import os.path + N302: from os.path import dirname as dirname2 + N303 from os.path import * + N304 import flakes """ # N302 import only modules # N303 Invalid Import # N304 Relative Import # TODO(sdague) actually get these tests working - def importModuleCheck(mod, parent=None, added=False): - """Import Module helper function. + # TODO(jogo) simplify this code + def import_module_check(mod, parent=None, added=False): + """Checks for relative, modules and invalid imports. If can't find module on first try, recursively check for relative - imports + imports. + When parsing 'from x import y,' x is the parent. """ current_path = os.path.dirname(pep8.current_file) try: @@ -196,8 +198,6 @@ def nova_import_module_only(logical_line): warnings.simplefilter('ignore', DeprecationWarning) valid = True if parent: - if is_import_exception(parent): - return parent_mod = __import__(parent, globals(), locals(), [mod], -1) valid = inspect.ismodule(getattr(parent_mod, mod)) @@ -209,7 +209,7 @@ def nova_import_module_only(logical_line): sys.path.pop() added = False return logical_line.find(mod), ("N304: No " - "relative imports. '%s' is a relative import" + "relative imports. '%s' is a relative import" % logical_line) return logical_line.find(mod), ("N302: import only " "modules. '%s' does not import a module" @@ -219,7 +219,7 @@ def nova_import_module_only(logical_line): if not added: added = True sys.path.append(current_path) - return importModuleCheck(mod, parent, added) + return import_module_check(mod, parent, added) else: name = logical_line.split()[1] if name not in _missingImport: @@ -234,23 +234,27 @@ def nova_import_module_only(logical_line): except AttributeError: # Invalid import + if "import *" in logical_line: + # TODO(jogo): handle "from x import *, by checking all + # "objects in x" + return return logical_line.find(mod), ("N303: Invalid import, " - "AttributeError raised") + "%s" % mod) - # convert "from x import y" to " import x.y" - # convert "from x import y as z" to " import x.y" - import_normalize(logical_line) split_line = logical_line.split() - - if (logical_line.startswith("import ") and "," not in logical_line and - (len(split_line) == 2 or - (len(split_line) == 4 and split_line[2] == "as"))): - mod = split_line[1] - rval = importModuleCheck(mod) + if (", " not in logical_line and + split_line[0] in ('import', 'from') and + (len(split_line) in (2, 4, 6)) and + split_line[1] != "__future__"): + if is_import_exception(split_line[1]): + return + if "from" == split_line[0]: + rval = import_module_check(split_line[3], parent=split_line[1]) + else: + rval = import_module_check(split_line[1]) if rval is not None: yield rval - # TODO(jogo) handle "from x import *" #TODO(jogo): import template: N305 @@ -329,6 +333,8 @@ def nova_docstring_one_line(physical_line): A one line docstring looks like this and ends in punctuation. Okay: '''This is good.''' + Okay: '''This is good too!''' + Okay: '''How about this?''' N402: '''This is not''' N402: '''Bad punctuation,''' """ diff --git a/tools/pip-requires b/tools/pip-requires index 231d5cfe5..126f0125c 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -20,7 +20,7 @@ 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 diff --git a/tools/test-requires b/tools/test-requires index bc279166e..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 @@ -12,4 +12,4 @@ pylint==0.25.2 python-subunit sphinx>=1.1.2 testrepository>=0.0.13 -testtools>=0.9.26 +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() @@ -15,13 +15,16 @@ 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,N403,N404 --show-source \ --exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg . 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} |