summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/api_samples/OS-SRV-USG/server-get-resp.json56
-rw-r--r--doc/api_samples/OS-SRV-USG/server-get-resp.xml19
-rw-r--r--doc/api_samples/OS-SRV-USG/server-post-req.json16
-rw-r--r--doc/api_samples/OS-SRV-USG/server-post-req.xml19
-rw-r--r--doc/api_samples/OS-SRV-USG/server-post-resp.json16
-rw-r--r--doc/api_samples/OS-SRV-USG/server-post-resp.xml6
-rw-r--r--doc/api_samples/OS-SRV-USG/servers-detail-resp.json58
-rw-r--r--doc/api_samples/OS-SRV-USG/servers-detail-resp.xml21
-rw-r--r--doc/api_samples/all_extensions/extensions-get-resp.json8
-rw-r--r--doc/api_samples/all_extensions/extensions-get-resp.xml3
-rw-r--r--doc/api_samples/all_extensions/server-get-resp.json22
-rw-r--r--doc/api_samples/all_extensions/server-get-resp.xml11
-rw-r--r--doc/api_samples/all_extensions/servers-details-resp.json22
-rw-r--r--doc/api_samples/all_extensions/servers-details-resp.xml12
-rw-r--r--etc/nova/policy.json1
-rw-r--r--nova/api/openstack/compute/contrib/fixed_ips.py7
-rw-r--r--nova/api/openstack/compute/contrib/floating_ips.py4
-rw-r--r--nova/api/openstack/compute/contrib/server_usage.py97
-rw-r--r--nova/api/openstack/compute/plugins/v3/consoles.py144
-rw-r--r--nova/cells/manager.py10
-rw-r--r--nova/cells/messaging.py13
-rw-r--r--nova/cells/rpcapi.py14
-rw-r--r--nova/cells/scheduler.py153
-rw-r--r--nova/compute/api.py253
-rw-r--r--nova/compute/cells_api.py16
-rw-r--r--nova/conductor/api.py22
-rw-r--r--nova/conductor/manager.py23
-rw-r--r--nova/conductor/rpcapi.py24
-rw-r--r--nova/consoleauth/manager.py4
-rw-r--r--nova/db/sqlalchemy/api.py92
-rw-r--r--nova/filters.py2
-rw-r--r--nova/image/s3.py133
-rw-r--r--nova/network/linux_net.py2
-rw-r--r--nova/objects/base.py33
-rw-r--r--nova/objects/instance.py6
-rw-r--r--nova/objects/utils.py8
-rw-r--r--nova/scheduler/utils.py33
-rw-r--r--nova/storage/linuxscsi.py22
-rw-r--r--nova/tests/api/ec2/test_cinder_cloud.py2
-rw-r--r--nova/tests/api/ec2/test_cloud.py2
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_fixed_ips.py19
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_server_usage.py131
-rw-r--r--nova/tests/api/openstack/compute/plugins/v3/test_consoles.py295
-rw-r--r--nova/tests/api/openstack/fakes.py8
-rw-r--r--nova/tests/cells/test_cells_manager.py9
-rw-r--r--nova/tests/cells/test_cells_messaging.py10
-rw-r--r--nova/tests/cells/test_cells_rpcapi.py15
-rw-r--r--nova/tests/cells/test_cells_scheduler.py260
-rw-r--r--nova/tests/compute/test_compute.py4
-rw-r--r--nova/tests/compute/test_compute_utils.py1
-rw-r--r--nova/tests/conductor/test_conductor.py36
-rw-r--r--nova/tests/console/test_console.py1
-rw-r--r--nova/tests/fake_instance.py40
-rw-r--r--nova/tests/fake_policy.py1
-rw-r--r--nova/tests/fake_utils.py28
-rw-r--r--nova/tests/integrated/api_samples/OS-SRV-USG/server-get-resp.json.tpl56
-rw-r--r--nova/tests/integrated/api_samples/OS-SRV-USG/server-get-resp.xml.tpl19
-rw-r--r--nova/tests/integrated/api_samples/OS-SRV-USG/server-post-req.json.tpl16
-rw-r--r--nova/tests/integrated/api_samples/OS-SRV-USG/server-post-req.xml.tpl19
-rw-r--r--nova/tests/integrated/api_samples/OS-SRV-USG/server-post-resp.json.tpl16
-rw-r--r--nova/tests/integrated/api_samples/OS-SRV-USG/server-post-resp.xml.tpl6
-rw-r--r--nova/tests/integrated/api_samples/OS-SRV-USG/servers-detail-resp.json.tpl57
-rw-r--r--nova/tests/integrated/api_samples/OS-SRV-USG/servers-detail-resp.xml.tpl21
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl8
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl3
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/server-get-resp.json.tpl2
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/server-get-resp.xml.tpl2
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/servers-details-resp.json.tpl2
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/servers-details-resp.xml.tpl4
-rw-r--r--nova/tests/integrated/test_api_samples.py26
-rw-r--r--nova/tests/objects/test_instance.py10
-rw-r--r--nova/tests/objects/test_objects.py8
-rw-r--r--nova/tests/scheduler/fakes.py1
-rw-r--r--nova/tests/test_db_api.py54
-rw-r--r--nova/tests/test_linuxscsi.py61
-rw-r--r--nova/tests/virt/hyperv/db_fakes.py2
-rw-r--r--nova/tests/virt/libvirt/fakelibvirt.py4
-rw-r--r--nova/tests/virt/libvirt/test_libvirt.py1
-rw-r--r--nova/tests/virt/vmwareapi/db_fakes.py2
-rw-r--r--nova/utils.py10
-rw-r--r--nova/virt/baremetal/ipmi.py2
-rwxr-xr-xnova/virt/libvirt/driver.py6
-rw-r--r--setup.cfg1
83 files changed, 2282 insertions, 404 deletions
diff --git a/doc/api_samples/OS-SRV-USG/server-get-resp.json b/doc/api_samples/OS-SRV-USG/server-get-resp.json
new file mode 100644
index 000000000..ffb03ebc6
--- /dev/null
+++ b/doc/api_samples/OS-SRV-USG/server-get-resp.json
@@ -0,0 +1,56 @@
+{
+ "server": {
+ "OS-SRV-USG:launched_at": "2013-05-02T19:26:38.326387",
+ "OS-SRV-USG:terminated_at": null,
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "addresses": {
+ "private": [
+ {
+ "addr": "192.168.0.3",
+ "version": 4
+ }
+ ]
+ },
+ "created": "2013-05-02T19:26:37Z",
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "http://openstack.example.com/openstack/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "hostId": "1127931217d16e2c8b1e1ec54de06f17805bfaadc0d36525879450d1",
+ "id": "cbdfb561-b94b-4b98-bb22-7d62575114b1",
+ "image": {
+ "id": "70a599e0-31e7-49b7-b260-868f441e862b",
+ "links": [
+ {
+ "href": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "http://openstack.example.com/v2/openstack/servers/cbdfb561-b94b-4b98-bb22-7d62575114b1",
+ "rel": "self"
+ },
+ {
+ "href": "http://openstack.example.com/openstack/servers/cbdfb561-b94b-4b98-bb22-7d62575114b1",
+ "rel": "bookmark"
+ }
+ ],
+ "metadata": {
+ "My Server Name": "Apache1"
+ },
+ "name": "new-server-test",
+ "progress": 0,
+ "status": "ACTIVE",
+ "tenant_id": "openstack",
+ "updated": "2013-05-02T19:26:38Z",
+ "user_id": "fake"
+ }
+} \ No newline at end of file
diff --git a/doc/api_samples/OS-SRV-USG/server-get-resp.xml b/doc/api_samples/OS-SRV-USG/server-get-resp.xml
new file mode 100644
index 000000000..f5336a147
--- /dev/null
+++ b/doc/api_samples/OS-SRV-USG/server-get-resp.xml
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<server xmlns:OS-SRV-USG="http://docs.openstack.org/compute/ext/server_usage/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="2013-05-02T19:26:46Z" hostId="d2e0951aac9e7db88dd51f90746dc2094ce3fe7d8e1bc6a46057f7f7" name="new-server-test" created="2013-05-02T19:26:45Z" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="19dd0e0b-d59f-480f-b175-82c8f921ad5b" OS-SRV-USG:terminated_at="None" OS-SRV-USG:launched_at="2013-05-02 19:26:46.727461">
+ <image id="70a599e0-31e7-49b7-b260-868f441e862b">
+ <atom:link href="http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b" rel="bookmark"/>
+ </image>
+ <flavor id="1">
+ <atom:link href="http://openstack.example.com/openstack/flavors/1" rel="bookmark"/>
+ </flavor>
+ <metadata>
+ <meta key="My Server Name">Apache1</meta>
+ </metadata>
+ <addresses>
+ <network id="private">
+ <ip version="4" addr="192.168.0.3"/>
+ </network>
+ </addresses>
+ <atom:link href="http://openstack.example.com/v2/openstack/servers/19dd0e0b-d59f-480f-b175-82c8f921ad5b" rel="self"/>
+ <atom:link href="http://openstack.example.com/openstack/servers/19dd0e0b-d59f-480f-b175-82c8f921ad5b" rel="bookmark"/>
+</server> \ No newline at end of file
diff --git a/doc/api_samples/OS-SRV-USG/server-post-req.json b/doc/api_samples/OS-SRV-USG/server-post-req.json
new file mode 100644
index 000000000..d88eb4122
--- /dev/null
+++ b/doc/api_samples/OS-SRV-USG/server-post-req.json
@@ -0,0 +1,16 @@
+{
+ "server" : {
+ "name" : "new-server-test",
+ "imageRef" : "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
+ "flavorRef" : "http://openstack.example.com/openstack/flavors/1",
+ "metadata" : {
+ "My Server Name" : "Apache1"
+ },
+ "personality" : [
+ {
+ "path" : "/etc/banner.txt",
+ "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
+ }
+ ]
+ }
+} \ No newline at end of file
diff --git a/doc/api_samples/OS-SRV-USG/server-post-req.xml b/doc/api_samples/OS-SRV-USG/server-post-req.xml
new file mode 100644
index 000000000..0a3c8bb53
--- /dev/null
+++ b/doc/api_samples/OS-SRV-USG/server-post-req.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b" flavorRef="http://openstack.example.com/openstack/flavors/1" name="new-server-test">
+ <metadata>
+ <meta key="My Server Name">Apache1</meta>
+ </metadata>
+ <personality>
+ <file path="/etc/banner.txt">
+ ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
+ dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k
+ IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs
+ c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g
+ QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo
+ ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv
+ dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy
+ c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6
+ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==
+ </file>
+ </personality>
+</server> \ No newline at end of file
diff --git a/doc/api_samples/OS-SRV-USG/server-post-resp.json b/doc/api_samples/OS-SRV-USG/server-post-resp.json
new file mode 100644
index 000000000..24a497544
--- /dev/null
+++ b/doc/api_samples/OS-SRV-USG/server-post-resp.json
@@ -0,0 +1,16 @@
+{
+ "server": {
+ "adminPass": "8dRuZpE2Pr7z",
+ "id": "cbdfb561-b94b-4b98-bb22-7d62575114b1",
+ "links": [
+ {
+ "href": "http://openstack.example.com/v2/openstack/servers/cbdfb561-b94b-4b98-bb22-7d62575114b1",
+ "rel": "self"
+ },
+ {
+ "href": "http://openstack.example.com/openstack/servers/cbdfb561-b94b-4b98-bb22-7d62575114b1",
+ "rel": "bookmark"
+ }
+ ]
+ }
+} \ No newline at end of file
diff --git a/doc/api_samples/OS-SRV-USG/server-post-resp.xml b/doc/api_samples/OS-SRV-USG/server-post-resp.xml
new file mode 100644
index 000000000..bac663bd3
--- /dev/null
+++ b/doc/api_samples/OS-SRV-USG/server-post-resp.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="ecaa5d95-14b1-4ee7-ad13-f91614e44167" adminPass="Tczo7hUJw2US">
+ <metadata/>
+ <atom:link href="http://openstack.example.com/v2/openstack/servers/ecaa5d95-14b1-4ee7-ad13-f91614e44167" rel="self"/>
+ <atom:link href="http://openstack.example.com/openstack/servers/ecaa5d95-14b1-4ee7-ad13-f91614e44167" rel="bookmark"/>
+</server> \ No newline at end of file
diff --git a/doc/api_samples/OS-SRV-USG/servers-detail-resp.json b/doc/api_samples/OS-SRV-USG/servers-detail-resp.json
new file mode 100644
index 000000000..183a8c92e
--- /dev/null
+++ b/doc/api_samples/OS-SRV-USG/servers-detail-resp.json
@@ -0,0 +1,58 @@
+{
+ "servers": [
+ {
+ "OS-SRV-USG:launched_at": "2013-05-02T19:26:45.066375",
+ "OS-SRV-USG:terminated_at": null,
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "addresses": {
+ "private": [
+ {
+ "addr": "192.168.0.3",
+ "version": 4
+ }
+ ]
+ },
+ "created": "2013-05-02T19:26:44Z",
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "http://openstack.example.com/openstack/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "hostId": "15ac731a430372d42a333d9d316ff2af70e0bf5eee523a0bc43779f5",
+ "id": "fe828a86-aad7-464b-8995-280bedf4fa9b",
+ "image": {
+ "id": "70a599e0-31e7-49b7-b260-868f441e862b",
+ "links": [
+ {
+ "href": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "http://openstack.example.com/v2/openstack/servers/fe828a86-aad7-464b-8995-280bedf4fa9b",
+ "rel": "self"
+ },
+ {
+ "href": "http://openstack.example.com/openstack/servers/fe828a86-aad7-464b-8995-280bedf4fa9b",
+ "rel": "bookmark"
+ }
+ ],
+ "metadata": {
+ "My Server Name": "Apache1"
+ },
+ "name": "new-server-test",
+ "progress": 0,
+ "status": "ACTIVE",
+ "tenant_id": "openstack",
+ "updated": "2013-05-02T19:26:45Z",
+ "user_id": "fake"
+ }
+ ]
+} \ No newline at end of file
diff --git a/doc/api_samples/OS-SRV-USG/servers-detail-resp.xml b/doc/api_samples/OS-SRV-USG/servers-detail-resp.xml
new file mode 100644
index 000000000..099f2ded1
--- /dev/null
+++ b/doc/api_samples/OS-SRV-USG/servers-detail-resp.xml
@@ -0,0 +1,21 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<servers xmlns:OS-SRV-USG="http://docs.openstack.org/compute/ext/server_usage/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1">
+ <server status="ACTIVE" updated="2013-05-02T19:26:40Z" hostId="6f0c555353e6a6269b27264081a68cd31eab18b7f5c008975757689a" name="new-server-test" created="2013-05-02T19:26:39Z" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="ecaa5d95-14b1-4ee7-ad13-f91614e44167" OS-SRV-USG:terminated_at="None" OS-SRV-USG:launched_at="2013-05-02 19:26:40.101839">
+ <image id="70a599e0-31e7-49b7-b260-868f441e862b">
+ <atom:link href="http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b" rel="bookmark"/>
+ </image>
+ <flavor id="1">
+ <atom:link href="http://openstack.example.com/openstack/flavors/1" rel="bookmark"/>
+ </flavor>
+ <metadata>
+ <meta key="My Server Name">Apache1</meta>
+ </metadata>
+ <addresses>
+ <network id="private">
+ <ip version="4" addr="192.168.0.3"/>
+ </network>
+ </addresses>
+ <atom:link href="http://openstack.example.com/v2/openstack/servers/ecaa5d95-14b1-4ee7-ad13-f91614e44167" rel="self"/>
+ <atom:link href="http://openstack.example.com/openstack/servers/ecaa5d95-14b1-4ee7-ad13-f91614e44167" rel="bookmark"/>
+ </server>
+</servers> \ No newline at end of file
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json
index 0b587a6b1..ace0dc9bb 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": "2011-09-14T00:00:00+00:00"
},
{
+ "alias": "OS-SRV-USG",
+ "description": "Adds launched_at and terminated_at on Instances.",
+ "links": [],
+ "name": "ServerUsage",
+ "namespace": "http://docs.openstack.org/compute/ext/server_usage/api/v1.1",
+ "updated": "2013-04-29T00:00:00+00:00"
+ },
+ {
"alias": "OS-SCH-HNT",
"description": "Pass arbitrary key/value pairs to the scheduler.",
"links": [],
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml
index 9924a188a..23d8b30d7 100644
--- a/doc/api_samples/all_extensions/extensions-get-resp.xml
+++ b/doc/api_samples/all_extensions/extensions-get-resp.xml
@@ -33,6 +33,9 @@
<extension alias="OS-FLV-EXT-DATA" updated="2011-09-14T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1" name="FlavorExtraData">
<description>Provide additional data for flavors.</description>
</extension>
+ <extension alias="OS-SRV-USG" updated="2013-04-29T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/server_usage/api/v1.1" name="ServerUsage">
+ <description>Adds launched_at and terminated_at on Servers.</description>
+ </extension>
<extension alias="OS-SCH-HNT" updated="2011-07-19T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/scheduler-hints/api/v2" name="SchedulerHints">
<description>Pass arbitrary key/value pairs to the scheduler.</description>
</extension>
diff --git a/doc/api_samples/all_extensions/server-get-resp.json b/doc/api_samples/all_extensions/server-get-resp.json
index 67a33cab1..d5b995027 100644
--- a/doc/api_samples/all_extensions/server-get-resp.json
+++ b/doc/api_samples/all_extensions/server-get-resp.json
@@ -2,26 +2,28 @@
"server": {
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "nova",
- "OS-EXT-SRV-ATTR:host": "b00875071c774b5487d217b82f03dfa2",
+ "OS-EXT-SRV-ATTR:host": "1e6e2aca70404eddb6120b83ba0ba990",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
+ "OS-SRV-USG:launched_at": "2013-05-02T19:13:48.734609",
+ "OS-SRV-USG:terminated_at": null,
"accessIPv4": "",
"accessIPv6": "",
"addresses": {
"private": [
{
+ "OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"addr": "192.168.0.3",
- "version": 4,
- "OS-EXT-IPS-MAC:mac_addr": "00:0c:29:e1:42:90"
+ "version": 4
}
]
},
"config_drive": "",
- "created": "2013-02-07T18:58:56Z",
+ "created": "2013-05-02T19:13:48Z",
"flavor": {
"id": "1",
"links": [
@@ -31,8 +33,8 @@
}
]
},
- "hostId": "ecbf72ad08d0d4f26768f526d17c2813812b4bc1598f081e16eb9b8b",
- "id": "12c05fea-29ec-4f77-9025-b3d72584ef1d",
+ "hostId": "2e41621ca04684ffb8b3616c554bcfa402856e1678aaf9473bfced1a",
+ "id": "a594696d-f35a-4238-a152-ae471395bdfe",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
@@ -45,11 +47,11 @@
"key_name": null,
"links": [
{
- "href": "http://openstack.example.com/v2/openstack/servers/12c05fea-29ec-4f77-9025-b3d72584ef1d",
+ "href": "http://openstack.example.com/v2/openstack/servers/a594696d-f35a-4238-a152-ae471395bdfe",
"rel": "self"
},
{
- "href": "http://openstack.example.com/openstack/servers/12c05fea-29ec-4f77-9025-b3d72584ef1d",
+ "href": "http://openstack.example.com/openstack/servers/a594696d-f35a-4238-a152-ae471395bdfe",
"rel": "bookmark"
}
],
@@ -65,7 +67,7 @@
],
"status": "ACTIVE",
"tenant_id": "openstack",
- "updated": "2013-02-07T18:58:57Z",
+ "updated": "2013-05-02T19:13:48Z",
"user_id": "fake"
}
-}
+} \ No newline at end of file
diff --git a/doc/api_samples/all_extensions/server-get-resp.xml b/doc/api_samples/all_extensions/server-get-resp.xml
index 6db40414b..42f62cfeb 100644
--- a/doc/api_samples/all_extensions/server-get-resp.xml
+++ b/doc/api_samples/all_extensions/server-get-resp.xml
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
-<server xmlns:OS-DCF="http://docs.openstack.org/compute/ext/disk_config/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:OS-EXT-SRV-ATTR="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-IPS="http://docs.openstack.org/compute/ext/extended_ips/api/v1.1" xmlns:OS-EXT-IPS-MAC="http://docs.openstack.org/compute/ext/extended_ips_mac/api/v1.1" xmlns:OS-EXT-STS="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-AZ="http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="2013-02-07T19:01:59Z" hostId="06d1cfd52be5f5d197193db2842978235fd085bd2dfaea32e5068468" name="new-server-test" created="2013-02-07T19:01:58Z" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="6be39927-53b2-4aee-8593-1c72b3673168" key_name="None" config_drive="" OS-EXT-SRV-ATTR:vm_state="active" OS-EXT-SRV-ATTR:task_state="None" OS-EXT-SRV-ATTR:power_state="1" OS-EXT-SRV-ATTR:instance_name="instance-00000001" OS-EXT-SRV-ATTR:host="b98603db318e495e819601702d16c512" OS-EXT-SRV-ATTR:hypervisor_hostname="fake-mini" OS-EXT-AZ:availability_zone="nova" OS-DCF:diskConfig="AUTO">
+<server xmlns:OS-DCF="http://docs.openstack.org/compute/ext/disk_config/api/v1.1" xmlns:OS-EXT-IPS-MAC="http://docs.openstack.org/compute/ext/extended_ips_mac/api/v1.1" xmlns:OS-EXT-IPS="http://docs.openstack.org/compute/ext/extended_ips/api/v1.1" xmlns:OS-SRV-USG="http://docs.openstack.org/compute/ext/server_usage/api/v1.1" xmlns:OS-EXT-SRV-ATTR="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:OS-EXT-STS="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-AZ="http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="2013-05-02T19:13:53Z" hostId="02da39e5db251d57b0ab8e8a2f902617a4800891ae726af602678299" name="new-server-test" created="2013-05-02T19:13:53Z" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="f51be755-f78e-4899-b4b9-d8ab979a1589" key_name="None" config_drive="" OS-SRV-USG:terminated_at="None" OS-SRV-USG:launched_at="2013-05-02 19:13:53.859580" OS-EXT-SRV-ATTR:vm_state="active" OS-EXT-SRV-ATTR:task_state="None" OS-EXT-SRV-ATTR:power_state="1" OS-EXT-SRV-ATTR:instance_name="instance-00000001" OS-EXT-SRV-ATTR:host="7729a44884d94767a2d239da3d5b4a8a" OS-EXT-SRV-ATTR:hypervisor_hostname="fake-mini" OS-EXT-AZ:availability_zone="nova" OS-DCF:diskConfig="AUTO">
<image id="70a599e0-31e7-49b7-b260-868f441e862b">
<atom:link href="http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b" rel="bookmark"/>
</image>
@@ -11,13 +11,12 @@
</metadata>
<addresses>
<network id="private">
- <ip OS-EXT-IPS:type="fixed" version="4" addr="192.168.0.3"
- OS-EXT-IPS-MAC:mac_addr="00:0c:29:e1:42:90"/>
+ <ip OS-EXT-IPS:type="fixed" version="4" addr="192.168.0.3" OS-EXT-IPS-MAC:mac_addr="aa:bb:cc:dd:ee:ff"/>
</network>
</addresses>
- <atom:link href="http://openstack.example.com/v2/openstack/servers/6be39927-53b2-4aee-8593-1c72b3673168" rel="self"/>
- <atom:link href="http://openstack.example.com/openstack/servers/6be39927-53b2-4aee-8593-1c72b3673168" rel="bookmark"/>
+ <atom:link href="http://openstack.example.com/v2/openstack/servers/f51be755-f78e-4899-b4b9-d8ab979a1589" rel="self"/>
+ <atom:link href="http://openstack.example.com/openstack/servers/f51be755-f78e-4899-b4b9-d8ab979a1589" rel="bookmark"/>
<security_groups>
<security_group name="default"/>
</security_groups>
-</server>
+</server> \ No newline at end of file
diff --git a/doc/api_samples/all_extensions/servers-details-resp.json b/doc/api_samples/all_extensions/servers-details-resp.json
index 05f11a246..038df104e 100644
--- a/doc/api_samples/all_extensions/servers-details-resp.json
+++ b/doc/api_samples/all_extensions/servers-details-resp.json
@@ -3,26 +3,28 @@
{
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "nova",
- "OS-EXT-SRV-ATTR:host": "33924d68ef4e4214bb9bc200178d23b8",
+ "OS-EXT-SRV-ATTR:host": "f43a596b8ec841e4bd486473ba0693bb",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
+ "OS-SRV-USG:launched_at": "2013-05-02T19:14:01.806067",
+ "OS-SRV-USG:terminated_at": null,
"accessIPv4": "",
"accessIPv6": "",
"addresses": {
"private": [
{
+ "OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"addr": "192.168.0.3",
- "version": 4,
- "OS-EXT-IPS-MAC:mac_addr": "00:0c:29:e1:42:90"
+ "version": 4
}
]
},
"config_drive": "",
- "created": "2013-02-07T18:58:56Z",
+ "created": "2013-05-02T19:14:01Z",
"flavor": {
"id": "1",
"links": [
@@ -32,8 +34,8 @@
}
]
},
- "hostId": "e0028a678cb7760fe5987947ab495dbb0f79c1071850f87a9aa8227f",
- "id": "3ec52036-bfee-4869-9c4c-81a579d72196",
+ "hostId": "6a892e9c0d3105ce7c93fd44982253a16d6bd760fc80cb686cfe1c18",
+ "id": "cdd530d5-140d-4f16-88a6-690cbbabc186",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
@@ -46,11 +48,11 @@
"key_name": null,
"links": [
{
- "href": "http://openstack.example.com/v2/openstack/servers/3ec52036-bfee-4869-9c4c-81a579d72196",
+ "href": "http://openstack.example.com/v2/openstack/servers/cdd530d5-140d-4f16-88a6-690cbbabc186",
"rel": "self"
},
{
- "href": "http://openstack.example.com/openstack/servers/3ec52036-bfee-4869-9c4c-81a579d72196",
+ "href": "http://openstack.example.com/openstack/servers/cdd530d5-140d-4f16-88a6-690cbbabc186",
"rel": "bookmark"
}
],
@@ -66,8 +68,8 @@
],
"status": "ACTIVE",
"tenant_id": "openstack",
- "updated": "2013-02-07T18:58:57Z",
+ "updated": "2013-05-02T19:14:01Z",
"user_id": "fake"
}
]
-}
+} \ No newline at end of file
diff --git a/doc/api_samples/all_extensions/servers-details-resp.xml b/doc/api_samples/all_extensions/servers-details-resp.xml
index 8061caf21..45a96d349 100644
--- a/doc/api_samples/all_extensions/servers-details-resp.xml
+++ b/doc/api_samples/all_extensions/servers-details-resp.xml
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
-<servers xmlns:OS-DCF="http://docs.openstack.org/compute/ext/disk_config/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:OS-EXT-SRV-ATTR="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-IPS="http://docs.openstack.org/compute/ext/extended_ips/api/v1.1" xmlns:OS-EXT-IPS-MAC="http://docs.openstack.org/compute/ext/extended_ips_mac/api/v1.1" xmlns:OS-EXT-STS="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-AZ="http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2" xmlns="http://docs.openstack.org/compute/api/v1.1">
- <server status="ACTIVE" updated="2013-02-07T19:01:59Z" hostId="641edaad8dd6a670afec58a4ce7e908d50379a6060f845236cd063db" name="new-server-test" created="2013-02-07T19:01:58Z" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="b45edf9d-30f6-41e8-a00b-ef8962376586" key_name="None" config_drive="" OS-EXT-SRV-ATTR:vm_state="active" OS-EXT-SRV-ATTR:task_state="None" OS-EXT-SRV-ATTR:power_state="1" OS-EXT-SRV-ATTR:instance_name="instance-00000001" OS-EXT-SRV-ATTR:host="f7954cfa4a5544278876b1d9224efe48" OS-EXT-SRV-ATTR:hypervisor_hostname="fake-mini" OS-EXT-AZ:availability_zone="nova" OS-DCF:diskConfig="AUTO">
+<servers xmlns:OS-DCF="http://docs.openstack.org/compute/ext/disk_config/api/v1.1" xmlns:OS-EXT-IPS-MAC="http://docs.openstack.org/compute/ext/extended_ips_mac/api/v1.1" xmlns:OS-EXT-IPS="http://docs.openstack.org/compute/ext/extended_ips/api/v1.1" xmlns:OS-SRV-USG="http://docs.openstack.org/compute/ext/server_usage/api/v1.1" xmlns:OS-EXT-SRV-ATTR="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:OS-EXT-STS="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-AZ="http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2" xmlns="http://docs.openstack.org/compute/api/v1.1">
+ <server status="ACTIVE" updated="2013-05-02T19:13:58Z" hostId="13233ec6d4eb3659bff165e8db9605421526bb461766ca658f343e50" name="new-server-test" created="2013-05-02T19:13:58Z" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="99e9e5b5-5a27-4181-88d4-ade79f132103" key_name="None" config_drive="" OS-SRV-USG:terminated_at="None" OS-SRV-USG:launched_at="2013-05-02 19:13:58.791386" OS-EXT-SRV-ATTR:vm_state="active" OS-EXT-SRV-ATTR:task_state="None" OS-EXT-SRV-ATTR:power_state="1" OS-EXT-SRV-ATTR:instance_name="instance-00000001" OS-EXT-SRV-ATTR:host="a7b375c2d6fa4fd2a4c2b7eee5154f1e" OS-EXT-SRV-ATTR:hypervisor_hostname="fake-mini" OS-EXT-AZ:availability_zone="nova" OS-DCF:diskConfig="AUTO">
<image id="70a599e0-31e7-49b7-b260-868f441e862b">
<atom:link href="http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b" rel="bookmark"/>
</image>
@@ -12,13 +12,13 @@
</metadata>
<addresses>
<network id="private">
- <ip OS-EXT-IPS:type="fixed" version="4" addr="192.168.0.3" OS-EXT-IPS-MAC:mac_addr="00:0c:29:e1:42:90"/>
+ <ip OS-EXT-IPS:type="fixed" version="4" addr="192.168.0.3" OS-EXT-IPS-MAC:mac_addr="aa:bb:cc:dd:ee:ff"/>
</network>
</addresses>
- <atom:link href="http://openstack.example.com/v2/openstack/servers/b45edf9d-30f6-41e8-a00b-ef8962376586" rel="self"/>
- <atom:link href="http://openstack.example.com/openstack/servers/b45edf9d-30f6-41e8-a00b-ef8962376586" rel="bookmark"/>
+ <atom:link href="http://openstack.example.com/v2/openstack/servers/99e9e5b5-5a27-4181-88d4-ade79f132103" rel="self"/>
+ <atom:link href="http://openstack.example.com/openstack/servers/99e9e5b5-5a27-4181-88d4-ade79f132103" rel="bookmark"/>
<security_groups>
<security_group name="default"/>
</security_groups>
</server>
-</servers>
+</servers> \ No newline at end of file
diff --git a/etc/nova/policy.json b/etc/nova/policy.json
index 0bfffa7c4..f33feb689 100644
--- a/etc/nova/policy.json
+++ b/etc/nova/policy.json
@@ -90,6 +90,7 @@
"compute_extension:security_groups": "",
"compute_extension:server_diagnostics": "rule:admin_api",
"compute_extension:server_password": "",
+ "compute_extension:server_usage": "",
"compute_extension:services": "rule:admin_api",
"compute_extension:simple_tenant_usage:show": "rule:admin_or_owner",
"compute_extension:simple_tenant_usage:list": "rule:admin_api",
diff --git a/nova/api/openstack/compute/contrib/fixed_ips.py b/nova/api/openstack/compute/contrib/fixed_ips.py
index 8c7e4f7d3..b474b685d 100644
--- a/nova/api/openstack/compute/contrib/fixed_ips.py
+++ b/nova/api/openstack/compute/contrib/fixed_ips.py
@@ -31,7 +31,8 @@ class FixedIPController(object):
try:
fixed_ip = db.fixed_ip_get_by_address_detailed(context, id)
- except exception.FixedIpNotFoundForAddress as ex:
+ except (exception.FixedIpNotFoundForAddress,
+ exception.FixedIpInvalid) as ex:
raise webob.exc.HTTPNotFound(explanation=ex.format_message())
fixed_ip_info = {"fixed_ip": {}}
@@ -54,6 +55,7 @@ class FixedIPController(object):
def action(self, req, id, body):
context = req.environ['nova.context']
authorize(context)
+
if 'reserve' in body:
return self._set_reserved(context, id, True)
elif 'unreserve' in body:
@@ -67,7 +69,8 @@ class FixedIPController(object):
fixed_ip = db.fixed_ip_get_by_address(context, address)
db.fixed_ip_update(context, fixed_ip['address'],
{'reserved': reserved})
- except exception.FixedIpNotFoundForAddress:
+ except (exception.FixedIpNotFoundForAddress,
+ exception.FixedIpInvalid) as ex:
msg = _("Fixed IP %s not found") % address
raise webob.exc.HTTPNotFound(explanation=msg)
diff --git a/nova/api/openstack/compute/contrib/floating_ips.py b/nova/api/openstack/compute/contrib/floating_ips.py
index a3d03e8de..284a211cd 100644
--- a/nova/api/openstack/compute/contrib/floating_ips.py
+++ b/nova/api/openstack/compute/contrib/floating_ips.py
@@ -125,7 +125,7 @@ class FloatingIPController(object):
try:
floating_ip = self.network_api.get_floating_ip(context, id)
- except exception.NotFound:
+ except (exception.NotFound, exception.InvalidID):
msg = _("Floating ip not found for id %s") % id
raise webob.exc.HTTPNotFound(explanation=msg)
@@ -173,7 +173,7 @@ class FloatingIPController(object):
# get the floating ip object
try:
floating_ip = self.network_api.get_floating_ip(context, id)
- except exception.NotFound:
+ except (exception.NotFound, exception.InvalidID):
msg = _("Floating ip not found for id %s") % id
raise webob.exc.HTTPNotFound(explanation=msg)
address = floating_ip['address']
diff --git a/nova/api/openstack/compute/contrib/server_usage.py b/nova/api/openstack/compute/contrib/server_usage.py
new file mode 100644
index 000000000..fb9dc9f6a
--- /dev/null
+++ b/nova/api/openstack/compute/contrib/server_usage.py
@@ -0,0 +1,97 @@
+# Copyright 2013 Openstack Foundation
+#
+# 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.api.openstack import extensions
+from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
+from nova import compute
+from nova.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+authorize = extensions.soft_extension_authorizer('compute', 'server_usage')
+
+
+class ServerUsageController(wsgi.Controller):
+ def __init__(self, *args, **kwargs):
+ super(ServerUsageController, self).__init__(*args, **kwargs)
+ self.compute_api = compute.API()
+
+ def _extend_server(self, server, instance):
+ for k in ['launched_at', 'terminated_at']:
+ key = "%s:%s" % (Server_usage.alias, k)
+ server[key] = instance[k]
+
+ @wsgi.extends
+ def show(self, req, resp_obj, id):
+ context = req.environ['nova.context']
+ if authorize(context):
+ # Attach our slave template to the response object
+ resp_obj.attach(xml=ServerUsageTemplate())
+ server = resp_obj.obj['server']
+ db_instance = req.get_db_instance(server['id'])
+ # server['id'] is guaranteed to be in the cache due to
+ # the core API adding it in its 'show' method.
+ self._extend_server(server, db_instance)
+
+ @wsgi.extends
+ def detail(self, req, resp_obj):
+ context = req.environ['nova.context']
+ if authorize(context):
+ # Attach our slave template to the response object
+ resp_obj.attach(xml=ServerUsagesTemplate())
+ servers = list(resp_obj.obj['servers'])
+ for server in servers:
+ db_instance = req.get_db_instance(server['id'])
+ # server['id'] is guaranteed to be in the cache due to
+ # the core API adding it in its 'detail' method.
+ self._extend_server(server, db_instance)
+
+
+class Server_usage(extensions.ExtensionDescriptor):
+ """Adds launched_at and terminated_at on Servers."""
+
+ name = "ServerUsage"
+ alias = "OS-SRV-USG"
+ namespace = ("http://docs.openstack.org/compute/ext/"
+ "server_usage/api/v1.1")
+ updated = "2013-04-29T00:00:00+00:00"
+
+ def get_controller_extensions(self):
+ controller = ServerUsageController()
+ extension = extensions.ControllerExtension(self, 'servers', controller)
+ return [extension]
+
+
+def make_server(elem):
+ elem.set('{%s}launched_at' % Server_usage.namespace,
+ '%s:launched_at' % Server_usage.alias)
+ elem.set('{%s}terminated_at' % Server_usage.namespace,
+ '%s:terminated_at' % Server_usage.alias)
+
+
+class ServerUsageTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('server', selector='server')
+ make_server(root)
+ return xmlutil.SlaveTemplate(root, 1, nsmap={
+ Server_usage.alias: Server_usage.namespace})
+
+
+class ServerUsagesTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('servers')
+ elem = xmlutil.SubTemplateElement(root, 'server', selector='servers')
+ make_server(elem)
+ return xmlutil.SlaveTemplate(root, 1, nsmap={
+ Server_usage.alias: Server_usage.namespace})
diff --git a/nova/api/openstack/compute/plugins/v3/consoles.py b/nova/api/openstack/compute/plugins/v3/consoles.py
new file mode 100644
index 000000000..d99395652
--- /dev/null
+++ b/nova/api/openstack/compute/plugins/v3/consoles.py
@@ -0,0 +1,144 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 OpenStack Foundation
+# 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 webob
+from webob import exc
+
+from nova.api.openstack import extensions
+from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
+from nova.console import api as console_api
+from nova import exception
+
+
+def _translate_keys(cons):
+ """Coerces a console instance into proper dictionary format."""
+ pool = cons['pool']
+ info = {'id': cons['id'],
+ 'console_type': pool['console_type']}
+ return dict(console=info)
+
+
+def _translate_detail_keys(cons):
+ """Coerces a console instance into proper dictionary format with detail."""
+ pool = cons['pool']
+ info = {'id': cons['id'],
+ 'console_type': pool['console_type'],
+ 'password': cons['password'],
+ 'instance_name': cons['instance_name'],
+ 'port': cons['port'],
+ 'host': pool['public_hostname']}
+ return dict(console=info)
+
+
+class ConsoleTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('console', selector='console')
+
+ id_elem = xmlutil.SubTemplateElement(root, 'id', selector='id')
+ id_elem.text = xmlutil.Selector()
+
+ port_elem = xmlutil.SubTemplateElement(root, 'port', selector='port')
+ port_elem.text = xmlutil.Selector()
+
+ host_elem = xmlutil.SubTemplateElement(root, 'host', selector='host')
+ host_elem.text = xmlutil.Selector()
+
+ passwd_elem = xmlutil.SubTemplateElement(root, 'password',
+ selector='password')
+ passwd_elem.text = xmlutil.Selector()
+
+ constype_elem = xmlutil.SubTemplateElement(root, 'console_type',
+ selector='console_type')
+ constype_elem.text = xmlutil.Selector()
+
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class ConsolesTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('consoles')
+ console = xmlutil.SubTemplateElement(root, 'console',
+ selector='consoles')
+ console.append(ConsoleTemplate())
+
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class ConsolesController(object):
+ """The Consoles controller for the OpenStack API."""
+
+ def __init__(self):
+ self.console_api = console_api.API()
+
+ @wsgi.serializers(xml=ConsolesTemplate)
+ def index(self, req, server_id):
+ """Returns a list of consoles for this instance."""
+ consoles = self.console_api.get_consoles(
+ req.environ['nova.context'],
+ server_id)
+ return dict(consoles=[_translate_keys(console)
+ for console in consoles])
+
+ def create(self, req, server_id):
+ """Creates a new console."""
+ self.console_api.create_console(
+ req.environ['nova.context'], server_id)
+
+ @wsgi.serializers(xml=ConsoleTemplate)
+ def show(self, req, server_id, id):
+ """Shows in-depth information on a specific console."""
+ try:
+ console = self.console_api.get_console(
+ req.environ['nova.context'],
+ server_id,
+ int(id))
+ except exception.NotFound:
+ raise exc.HTTPNotFound()
+ return _translate_detail_keys(console)
+
+ def delete(self, req, server_id, id):
+ """Deletes a console."""
+ try:
+ self.console_api.delete_console(req.environ['nova.context'],
+ server_id,
+ int(id))
+ except exception.NotFound:
+ raise exc.HTTPNotFound()
+ return webob.Response(status_int=202)
+
+
+class Consoles(extensions.V3APIExtensionBase):
+ """Consoles."""
+
+ name = "Consoles"
+ alias = "consoles"
+ namespace = "http://docs.openstack.org/compute/core/consoles/v3"
+ version = 1
+
+ def get_resources(self):
+ parent = {'member_name': 'server',
+ 'collection_name': 'servers'}
+ resources = [
+ extensions.ResourceExtension(
+ 'consoles', ConsolesController(), parent=parent,
+ member_name='console')]
+
+ return resources
+
+ def get_controller_extensions(self):
+ return []
diff --git a/nova/cells/manager.py b/nova/cells/manager.py
index 15ee2224c..ba909c034 100644
--- a/nova/cells/manager.py
+++ b/nova/cells/manager.py
@@ -64,7 +64,7 @@ class CellsManager(manager.Manager):
Scheduling requests get passed to the scheduler class.
"""
- RPC_API_VERSION = '1.7'
+ RPC_API_VERSION = '1.8'
def __init__(self, *args, **kwargs):
# Mostly for tests.
@@ -186,6 +186,14 @@ class CellsManager(manager.Manager):
self.msg_runner.schedule_run_instance(ctxt, our_cell,
host_sched_kwargs)
+ def build_instances(self, ctxt, build_inst_kwargs):
+ """Pick a cell (possibly ourselves) to build new instance(s) and
+ forward the request accordingly.
+ """
+ # Target is ourselves first.
+ our_cell = self.state_manager.get_my_state()
+ self.msg_runner.build_instances(ctxt, our_cell, build_inst_kwargs)
+
def get_cell_info_for_neighbors(self, _ctxt):
"""Return cell information for our neighbor cells."""
return self.state_manager.get_cell_info_for_neighbors()
diff --git a/nova/cells/messaging.py b/nova/cells/messaging.py
index 1ea68d29f..319067836 100644
--- a/nova/cells/messaging.py
+++ b/nova/cells/messaging.py
@@ -651,6 +651,10 @@ class _TargetedMessageMethods(_BaseMessageMethods):
"""Parent cell told us to schedule new instance creation."""
self.msg_runner.scheduler.run_instance(message, host_sched_kwargs)
+ def build_instances(self, message, build_inst_kwargs):
+ """Parent cell told us to schedule new instance creation."""
+ self.msg_runner.scheduler.build_instances(message, build_inst_kwargs)
+
def run_compute_api_method(self, message, method_info):
"""Run a method in the compute api class."""
method = method_info['method']
@@ -1132,6 +1136,15 @@ class MessageRunner(object):
method_kwargs, 'down', target_cell)
message.process()
+ def build_instances(self, ctxt, target_cell, build_inst_kwargs):
+ """Called by the cell scheduler to tell a child cell to build
+ instance(s).
+ """
+ method_kwargs = dict(build_inst_kwargs=build_inst_kwargs)
+ message = _TargetedMessage(self, ctxt, 'build_instances',
+ method_kwargs, 'down', target_cell)
+ message.process()
+
def run_compute_api_method(self, ctxt, cell_name, method_info, call):
"""Call a compute API method in a specific cell."""
message = _TargetedMessage(self, ctxt, 'run_compute_api_method',
diff --git a/nova/cells/rpcapi.py b/nova/cells/rpcapi.py
index c8b7c680f..4a45af255 100644
--- a/nova/cells/rpcapi.py
+++ b/nova/cells/rpcapi.py
@@ -50,6 +50,7 @@ class CellsAPI(rpc_proxy.RpcProxy):
action_events_get()
1.6 - Adds consoleauth_delete_tokens() and validate_console_port()
1.7 - Adds service_update()
+ 1.8 - Adds build_instances(), deprecates schedule_run_instance()
'''
BASE_RPC_API_VERSION = '1.0'
@@ -79,11 +80,24 @@ class CellsAPI(rpc_proxy.RpcProxy):
method_info=method_info,
call=True))
+ # NOTE(alaski): Deprecated and should be removed later.
def schedule_run_instance(self, ctxt, **kwargs):
"""Schedule a new instance for creation."""
self.cast(ctxt, self.make_msg('schedule_run_instance',
host_sched_kwargs=kwargs))
+ def build_instances(self, ctxt, **kwargs):
+ """Build instances."""
+ build_inst_kwargs = kwargs
+ instances = build_inst_kwargs['instances']
+ instances_p = [jsonutils.to_primitive(inst) for inst in instances]
+ build_inst_kwargs['instances'] = instances_p
+ build_inst_kwargs['image'] = jsonutils.to_primitive(
+ build_inst_kwargs['image'])
+ self.cast(ctxt, self.make_msg('build_instances',
+ build_inst_kwargs=build_inst_kwargs),
+ version=1.8)
+
def instance_update_at_top(self, ctxt, instance):
"""Update instance at API level."""
if not CONF.cells.enable:
diff --git a/nova/cells/scheduler.py b/nova/cells/scheduler.py
index c95498fa0..c54b9b578 100644
--- a/nova/cells/scheduler.py
+++ b/nova/cells/scheduler.py
@@ -27,10 +27,12 @@ from nova import compute
from nova.compute import instance_actions
from nova.compute import utils as compute_utils
from nova.compute import vm_states
+from nova import conductor
from nova.db import base
from nova import exception
from nova.openstack.common import log as logging
from nova.scheduler import rpcapi as scheduler_rpcapi
+from nova.scheduler import utils as scheduler_utils
cell_scheduler_opts = [
cfg.ListOpt('scheduler_filter_classes',
@@ -67,6 +69,7 @@ class CellsScheduler(base.Base):
self.state_manager = msg_runner.state_manager
self.compute_api = compute.API()
self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI()
+ self.compute_task_api = conductor.ComputeTaskAPI()
self.filter_handler = filters.CellFilterHandler()
self.filter_classes = self.filter_handler.get_matching_classes(
CONF.cells.scheduler_filter_classes)
@@ -74,18 +77,19 @@ class CellsScheduler(base.Base):
self.weigher_classes = self.weight_handler.get_matching_classes(
CONF.cells.scheduler_weight_classes)
- def _create_instances_here(self, ctxt, request_spec):
- instance_values = request_spec['instance_properties']
- num_instances = len(request_spec['instance_uuids'])
- for i, instance_uuid in enumerate(request_spec['instance_uuids']):
+ def _create_instances_here(self, ctxt, instance_uuids, instance_properties,
+ instance_type, image, security_groups, block_device_mapping):
+ instance_values = copy.copy(instance_properties)
+ num_instances = len(instance_uuids)
+ for i, instance_uuid in enumerate(instance_uuids):
instance_values['uuid'] = instance_uuid
instance = self.compute_api.create_db_entry_for_new_instance(
ctxt,
- request_spec['instance_type'],
- request_spec['image'],
+ instance_type,
+ image,
instance_values,
- request_spec['security_group'],
- request_spec['block_device_mapping'],
+ security_groups,
+ block_device_mapping,
num_instances, i)
self.msg_runner.instance_update_at_top(ctxt, instance)
@@ -104,24 +108,7 @@ class CellsScheduler(base.Base):
cells.append(our_cell)
return cells
- def _run_instance(self, message, host_sched_kwargs):
- """Attempt to schedule instance(s). If we have no cells
- to try, raise exception.NoCellsAvailable
- """
- ctxt = message.ctxt
- routing_path = message.routing_path
- request_spec = host_sched_kwargs['request_spec']
-
- LOG.debug(_("Scheduling with routing_path=%(routing_path)s"),
- {'routing_path': routing_path})
-
- filter_properties = copy.copy(host_sched_kwargs['filter_properties'])
- filter_properties.update({'context': ctxt,
- 'scheduler': self,
- 'routing_path': routing_path,
- 'host_sched_kwargs': host_sched_kwargs,
- 'request_spec': request_spec})
-
+ def _grab_target_cells(self, filter_properties):
cells = self._get_possible_cells()
cells = self.filter_handler.get_filtered_objects(self.filter_classes,
cells,
@@ -140,42 +127,124 @@ class CellsScheduler(base.Base):
self.weigher_classes, cells, filter_properties)
LOG.debug(_("Weighted cells: %(weighted_cells)s"),
{'weighted_cells': weighted_cells})
+ target_cells = [cell.obj for cell in weighted_cells]
+ return target_cells
- # Keep trying until one works
- for weighted_cell in weighted_cells:
- cell = weighted_cell.obj
+ def _run_instance(self, message, target_cells, instance_uuids,
+ host_sched_kwargs):
+ """Attempt to schedule instance(s)."""
+ ctxt = message.ctxt
+ request_spec = host_sched_kwargs['request_spec']
+ instance_properties = request_spec['instance_properties']
+ instance_type = request_spec['instance_type']
+ image = request_spec['image']
+ security_groups = request_spec['security_group']
+ block_device_mapping = request_spec['block_device_mapping']
+
+ LOG.debug(_("Scheduling with routing_path=%(routing_path)s"),
+ {'routing_path': message.routing_path})
+
+ for target_cell in target_cells:
try:
- if cell.is_me:
- # Need to create instance DB entry as scheduler
- # thinks it's already created... At least how things
- # currently work.
- self._create_instances_here(ctxt, request_spec)
+ if target_cell.is_me:
+ # Need to create instance DB entries as the host scheduler
+ # expects that the instance(s) already exists.
+ self._create_instances_here(ctxt, instance_uuids,
+ instance_properties, instance_type, image,
+ security_groups, block_device_mapping)
# Need to record the create action in the db as the
# scheduler expects it to already exist.
- self._create_action_here(
- ctxt, request_spec['instance_uuids'])
+ self._create_action_here(ctxt, instance_uuids)
self.scheduler_rpcapi.run_instance(ctxt,
**host_sched_kwargs)
return
- # Forward request to cell
- self.msg_runner.schedule_run_instance(ctxt, cell,
+ self.msg_runner.schedule_run_instance(ctxt, target_cell,
host_sched_kwargs)
return
except Exception:
LOG.exception(_("Couldn't communicate with cell '%s'") %
- cell.name)
+ target_cell.name)
# FIXME(comstud): Would be nice to kick this back up so that
# the parent cell could retry, if we had a parent.
msg = _("Couldn't communicate with any cells")
LOG.error(msg)
raise exception.NoCellsAvailable()
+ def _build_instances(self, message, target_cells, instance_uuids,
+ build_inst_kwargs):
+ """Attempt to build instance(s) or send msg to child cell."""
+ ctxt = message.ctxt
+ instance_properties = build_inst_kwargs['instances'][0]
+ instance_type = build_inst_kwargs['instance_type']
+ image = build_inst_kwargs['image']
+ security_groups = build_inst_kwargs['security_group']
+ block_device_mapping = build_inst_kwargs['block_device_mapping']
+
+ LOG.debug(_("Building instances with routing_path=%(routing_path)s"),
+ {'routing_path': message.routing_path})
+
+ for target_cell in target_cells:
+ try:
+ if target_cell.is_me:
+ # Need to create instance DB entries as the conductor
+ # expects that the instance(s) already exists.
+ self._create_instances_here(ctxt, instance_uuids,
+ instance_properties, instance_type, image,
+ security_groups, block_device_mapping)
+ # Need to record the create action in the db as the
+ # conductor expects it to already exist.
+ self._create_action_here(ctxt, instance_uuids)
+ self.compute_task_api.build_instances(ctxt,
+ **build_inst_kwargs)
+ return
+ self.msg_runner.build_instances(ctxt, target_cell,
+ build_inst_kwargs)
+ return
+ except Exception:
+ LOG.exception(_("Couldn't communicate with cell '%s'") %
+ target_cell.name)
+ # FIXME(comstud): Would be nice to kick this back up so that
+ # the parent cell could retry, if we had a parent.
+ msg = _("Couldn't communicate with any cells")
+ LOG.error(msg)
+ raise exception.NoCellsAvailable()
+
+ def build_instances(self, message, build_inst_kwargs):
+ image = build_inst_kwargs['image']
+ instance_uuids = [inst['uuid'] for inst in
+ build_inst_kwargs['instances']]
+ instances = build_inst_kwargs['instances']
+ request_spec = scheduler_utils.build_request_spec(image, instances)
+ filter_properties = copy.copy(build_inst_kwargs['filter_properties'])
+ filter_properties.update({'context': message.ctxt,
+ 'scheduler': self,
+ 'routing_path': message.routing_path,
+ 'host_sched_kwargs': build_inst_kwargs,
+ 'request_spec': request_spec})
+ self._schedule_build_to_cells(message, instance_uuids,
+ filter_properties, self._build_instances, build_inst_kwargs)
+
def run_instance(self, message, host_sched_kwargs):
- """Pick a cell where we should create a new instance."""
+ request_spec = host_sched_kwargs['request_spec']
+ instance_uuids = request_spec['instance_uuids']
+ filter_properties = copy.copy(host_sched_kwargs['filter_properties'])
+ filter_properties.update({'context': message.ctxt,
+ 'scheduler': self,
+ 'routing_path': message.routing_path,
+ 'host_sched_kwargs': host_sched_kwargs,
+ 'request_spec': request_spec})
+ self._schedule_build_to_cells(message, instance_uuids,
+ filter_properties, self._run_instance, host_sched_kwargs)
+
+ def _schedule_build_to_cells(self, message, instance_uuids,
+ filter_properties, method, method_kwargs):
+ """Pick a cell where we should create a new instance(s)."""
try:
for i in xrange(max(0, CONF.cells.scheduler_retries) + 1):
try:
- return self._run_instance(message, host_sched_kwargs)
+ target_cells = self._grab_target_cells(filter_properties)
+ return method(message, target_cells, instance_uuids,
+ method_kwargs)
except exception.NoCellsAvailable:
if i == max(0, CONF.cells.scheduler_retries):
raise
@@ -186,8 +255,6 @@ class CellsScheduler(base.Base):
time.sleep(sleep_time)
continue
except Exception:
- request_spec = host_sched_kwargs['request_spec']
- instance_uuids = request_spec['instance_uuids']
LOG.exception(_("Error scheduling instances %(instance_uuids)s"),
{'instance_uuids': instance_uuids})
ctxt = message.ctxt
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 80c8074d9..f676c9797 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -25,7 +25,6 @@ import base64
import functools
import re
import string
-import time
import uuid
from oslo.config import cfg
@@ -542,34 +541,20 @@ class API(base.Base):
self._check_injected_file_quota(context, files_to_inject)
self._check_requested_image(context, image_id, image, instance_type)
- def _validate_and_provision_instance(self, context, instance_type,
- image_href, kernel_id, ramdisk_id,
- min_count, max_count,
- display_name, display_description,
- key_name, key_data, security_groups,
+ def _validate_and_build_base_options(self, context, instance_type,
+ image, image_href, image_id,
+ kernel_id, ramdisk_id, min_count,
+ max_count, display_name,
+ display_description, key_name,
+ key_data, security_groups,
availability_zone, user_data,
metadata, injected_files,
access_ip_v4, access_ip_v6,
requested_networks, config_drive,
block_device_mapping,
- auto_disk_config, reservation_id,
- scheduler_hints):
+ auto_disk_config, reservation_id):
"""Verify all the input parameters regardless of the provisioning
strategy being performed."""
-
- if not metadata:
- metadata = {}
- if not security_groups:
- security_groups = ['default']
-
- if not instance_type:
- instance_type = flavors.get_default_instance_type()
- if not min_count:
- min_count = 1
- if not max_count:
- 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'
@@ -592,90 +577,87 @@ class API(base.Base):
except base64.binascii.Error:
raise exception.InstanceUserDataMalformed()
+ self._checks_for_create_and_rebuild(context, image_id, image,
+ instance_type, metadata, injected_files)
+
+ self._check_requested_secgroups(context, security_groups)
+ self._check_requested_networks(context, requested_networks)
+
+ kernel_id, ramdisk_id = self._handle_kernel_and_ramdisk(
+ context, kernel_id, ramdisk_id, image)
+
+ config_drive_id, config_drive = self._check_config_drive(
+ context, config_drive)
+
+ if key_data is None and key_name:
+ key_pair = self.db.key_pair_get(context, context.user_id,
+ key_name)
+ key_data = key_pair['public_key']
+
+ root_device_name = block_device.properties_root_device_name(
+ image.get('properties', {}))
+
+ system_metadata = flavors.save_instance_type_info(
+ dict(), instance_type)
+
+ base_options = {
+ 'reservation_id': reservation_id,
+ 'image_ref': image_href,
+ 'kernel_id': kernel_id or '',
+ 'ramdisk_id': ramdisk_id or '',
+ 'power_state': power_state.NOSTATE,
+ 'vm_state': vm_states.BUILDING,
+ 'config_drive_id': config_drive_id or '',
+ 'config_drive': config_drive or '',
+ 'user_id': context.user_id,
+ 'project_id': context.project_id,
+ 'instance_type_id': instance_type['id'],
+ 'memory_mb': instance_type['memory_mb'],
+ 'vcpus': instance_type['vcpus'],
+ 'root_gb': instance_type['root_gb'],
+ 'ephemeral_gb': instance_type['ephemeral_gb'],
+ 'display_name': display_name,
+ 'display_description': display_description or '',
+ 'user_data': user_data,
+ 'key_name': key_name,
+ 'key_data': key_data,
+ 'locked': False,
+ 'metadata': metadata,
+ 'access_ip_v4': access_ip_v4,
+ 'access_ip_v6': access_ip_v6,
+ 'availability_zone': availability_zone,
+ 'root_device_name': root_device_name,
+ 'progress': 0,
+ 'system_metadata': system_metadata}
+
+ options_from_image = self._inherit_properties_from_image(
+ image, auto_disk_config)
+
+ base_options.update(options_from_image)
+
+ return base_options
+
+ def _build_filter_properties(self, context, scheduler_hints, forced_host,
+ forced_node, instance_type):
+ filter_properties = dict(scheduler_hints=scheduler_hints)
+ filter_properties['instance_type'] = instance_type
+ if forced_host:
+ check_policy(context, 'create:forced_host', {})
+ filter_properties['force_hosts'] = [forced_host]
+ if forced_node:
+ check_policy(context, 'create:forced_host', {})
+ filter_properties['force_nodes'] = [forced_node]
+ return filter_properties
+
+ def _provision_instances(self, context, instance_type, min_count,
+ max_count, base_options, image, security_groups,
+ block_device_mapping):
# Reserve quotas
num_instances, quota_reservations = self._check_num_instances_quota(
context, instance_type, min_count, max_count)
-
- # Try to create the instance
+ LOG.debug(_("Going to run %s instances...") % num_instances)
+ instances = []
try:
- instances = []
- instance_uuids = []
-
- image_id, image = self._get_image(context, image_href)
-
- self._checks_for_create_and_rebuild(context, image_id, image,
- instance_type, metadata, injected_files)
-
- self._check_requested_secgroups(context, security_groups)
- self._check_requested_networks(context, requested_networks)
-
- kernel_id, ramdisk_id = self._handle_kernel_and_ramdisk(
- context, kernel_id, ramdisk_id, image)
-
- config_drive_id, config_drive = self._check_config_drive(
- context, config_drive)
-
- if key_data is None and key_name:
- key_pair = self.db.key_pair_get(context, context.user_id,
- key_name)
- key_data = key_pair['public_key']
-
- root_device_name = block_device.properties_root_device_name(
- image.get('properties', {}))
-
- availability_zone, forced_host, forced_node = \
- self._handle_availability_zone(availability_zone)
-
- system_metadata = flavors.save_instance_type_info(
- dict(), instance_type)
-
- base_options = {
- 'reservation_id': reservation_id,
- 'image_ref': image_href,
- 'kernel_id': kernel_id or '',
- 'ramdisk_id': ramdisk_id or '',
- 'power_state': power_state.NOSTATE,
- 'vm_state': vm_states.BUILDING,
- 'config_drive_id': config_drive_id or '',
- 'config_drive': config_drive or '',
- 'user_id': context.user_id,
- 'project_id': context.project_id,
- 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ',
- time.gmtime()),
- 'instance_type_id': instance_type['id'],
- 'memory_mb': instance_type['memory_mb'],
- 'vcpus': instance_type['vcpus'],
- 'root_gb': instance_type['root_gb'],
- 'ephemeral_gb': instance_type['ephemeral_gb'],
- 'display_name': display_name,
- 'display_description': display_description or '',
- 'user_data': user_data,
- 'key_name': key_name,
- 'key_data': key_data,
- 'locked': False,
- 'metadata': metadata,
- 'access_ip_v4': access_ip_v4,
- 'access_ip_v6': access_ip_v6,
- 'availability_zone': availability_zone,
- 'root_device_name': root_device_name,
- 'progress': 0,
- 'system_metadata': system_metadata}
-
- options_from_image = self._inherit_properties_from_image(
- image, auto_disk_config)
-
- base_options.update(options_from_image)
-
- LOG.debug(_("Going to run %s instances...") % num_instances)
-
- filter_properties = dict(scheduler_hints=scheduler_hints)
- if forced_host:
- check_policy(context, 'create:forced_host', {})
- filter_properties['force_hosts'] = [forced_host]
- if forced_node:
- check_policy(context, 'create:forced_host', {})
- filter_properties['force_nodes'] = [forced_node]
-
for i in xrange(num_instances):
options = base_options.copy()
instance = self.create_db_entry_for_new_instance(
@@ -684,7 +666,6 @@ class API(base.Base):
num_instances, i)
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
@@ -696,30 +677,20 @@ class API(base.Base):
except Exception:
with excutils.save_and_reraise_exception():
try:
- for instance_uuid in instance_uuids:
- self.db.instance_destroy(context, instance_uuid)
+ for instance in instances:
+ self.db.instance_destroy(context, instance['uuid'])
finally:
QUOTAS.rollback(context, quota_reservations)
# Commit the reservations
QUOTAS.commit(context, quota_reservations)
-
- request_spec = {
- 'image': jsonutils.to_primitive(image),
- 'instance_properties': base_options,
- 'instance_type': instance_type,
- 'instance_uuids': instance_uuids,
- 'block_device_mapping': block_device_mapping,
- 'security_group': security_groups,
- }
-
- return (instances, request_spec, filter_properties)
+ return instances
def _create_instance(self, context, instance_type,
image_href, kernel_id, ramdisk_id,
min_count, max_count,
display_name, display_description,
- key_name, key_data, security_group,
+ key_name, key_data, security_groups,
availability_zone, user_data, metadata,
injected_files, admin_password,
access_ip_v4, access_ip_v6,
@@ -730,28 +701,48 @@ class API(base.Base):
strategy being performed and schedule the instance(s) for
creation."""
+ # Normalize and setup some parameters
if reservation_id is None:
reservation_id = utils.generate_uid('r')
+ security_groups = security_groups or ['default']
+ min_count = min_count or 1
+ max_count = max_count or min_count
+ block_device_mapping = block_device_mapping or []
+ if not instance_type:
+ instance_type = flavors.get_default_instance_type()
+ image_id, image = self._get_image(context, image_href)
+
+ handle_az = self._handle_availability_zone
+ availability_zone, forced_host, forced_node = handle_az(
+ availability_zone)
+
+ base_options = self._validate_and_build_base_options(context,
+ instance_type, image, image_href, image_id, kernel_id,
+ ramdisk_id, min_count, max_count, display_name,
+ display_description, key_name, key_data, security_groups,
+ availability_zone, user_data, metadata, injected_files,
+ access_ip_v4, access_ip_v6, requested_networks, config_drive,
+ block_device_mapping, auto_disk_config, reservation_id)
+
+ instances = self._provision_instances(context, instance_type,
+ min_count, max_count, base_options, image, security_groups,
+ block_device_mapping)
- (instances, request_spec, filter_properties) = \
- self._validate_and_provision_instance(context, instance_type,
- image_href, kernel_id, ramdisk_id, min_count,
- max_count, display_name, display_description,
- key_name, key_data, security_group, availability_zone,
- user_data, metadata, injected_files, access_ip_v4,
- access_ip_v6, requested_networks, config_drive,
- block_device_mapping, auto_disk_config,
- reservation_id, scheduler_hints)
+ filter_properties = self._build_filter_properties(context,
+ scheduler_hints, forced_host, forced_node, instance_type)
for instance in instances:
self._record_action_start(context, instance,
instance_actions.CREATE)
- self.scheduler_rpcapi.run_instance(context,
- request_spec=request_spec,
- admin_password=admin_password, injected_files=injected_files,
- requested_networks=requested_networks, is_first_time=True,
- filter_properties=filter_properties)
+ self.compute_task_api.build_instances(context,
+ instances=instances, image=image,
+ filter_properties=filter_properties,
+ admin_password=admin_password,
+ injected_files=injected_files,
+ requested_networks=requested_networks,
+ security_groups=security_groups,
+ block_device_mapping=block_device_mapping)
return (instances, reservation_id)
diff --git a/nova/compute/cells_api.py b/nova/compute/cells_api.py
index 4beeaeb3d..5ac5dd475 100644
--- a/nova/compute/cells_api.py
+++ b/nova/compute/cells_api.py
@@ -57,8 +57,18 @@ class SchedulerRPCAPIRedirect(object):
return None
return _noop_rpc_wrapper
- def run_instance(self, context, **kwargs):
- self.cells_rpcapi.schedule_run_instance(context, **kwargs)
+
+class ConductorTaskRPCAPIRedirect(object):
+ def __init__(self, cells_rpcapi_obj):
+ self.cells_rpcapi = cells_rpcapi_obj
+
+ def __getattr__(self, key):
+ def _noop_rpc_wrapper(*args, **kwargs):
+ return None
+ return _noop_rpc_wrapper
+
+ def build_instances(self, context, **kwargs):
+ self.cells_rpcapi.build_instances(context, **kwargs)
class ComputeRPCProxyAPI(compute_rpcapi.ComputeAPI):
@@ -90,6 +100,8 @@ class ComputeCellsAPI(compute_api.API):
self.compute_rpcapi = ComputeRPCAPINoOp()
# Redirect scheduler run_instance to cells.
self.scheduler_rpcapi = SchedulerRPCAPIRedirect(self.cells_rpcapi)
+ # Redirect conductor build_instances to cells
+ self._compute_task_api = ConductorTaskRPCAPIRedirect(self.cells_rpcapi)
def _cell_read_only(self, cell_name):
"""Is the target cell in a read-only mode?"""
diff --git a/nova/conductor/api.py b/nova/conductor/api.py
index ec247d1ac..33415233e 100644
--- a/nova/conductor/api.py
+++ b/nova/conductor/api.py
@@ -349,6 +349,17 @@ class LocalComputeTaskAPI(object):
return self._manager.migrate_server(context, instance, scheduler_hint,
live, rebuild, flavor, block_migration, disk_over_commit)
+ def build_instances(self, context, instances, image,
+ filter_properties, admin_password, injected_files,
+ requested_networks, security_groups, block_device_mapping):
+ utils.spawn_n(self._manager.build_instances, context,
+ instances=instances, image=image,
+ filter_properties=filter_properties,
+ admin_password=admin_password, injected_files=injected_files,
+ requested_networks=requested_networks,
+ security_groups=security_groups,
+ block_device_mapping=block_device_mapping)
+
class API(object):
"""Conductor API that does updates via RPC to the ConductorManager."""
@@ -692,3 +703,14 @@ class ComputeTaskAPI(object):
return self.conductor_compute_rpcapi.migrate_server(context, instance,
scheduler_hint, live, rebuild, flavor, block_migration,
disk_over_commit)
+
+ def build_instances(self, context, instances, image, filter_properties,
+ admin_password, injected_files, requested_networks,
+ security_groups, block_device_mapping):
+ self.conductor_compute_rpcapi.build_instances(context,
+ instances=instances, image=image,
+ filter_properties=filter_properties,
+ admin_password=admin_password, injected_files=injected_files,
+ requested_networks=requested_networks,
+ security_groups=security_groups,
+ block_device_mapping=block_device_mapping)
diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py
index c7f139e4f..bd4268963 100644
--- a/nova/conductor/manager.py
+++ b/nova/conductor/manager.py
@@ -33,6 +33,7 @@ from nova.openstack.common.rpc import common as rpc_common
from nova.openstack.common import timeutils
from nova import quota
from nova.scheduler import rpcapi as scheduler_rpcapi
+from nova.scheduler import utils as scheduler_utils
LOG = logging.getLogger(__name__)
@@ -479,16 +480,16 @@ class ConductorManager(manager.Manager):
self.compute_api.unrescue(context, instance)
def object_class_action(self, context, objname, objmethod,
- objver, **kwargs):
+ objver, args, kwargs):
"""Perform a classmethod action on an object."""
objclass = nova_object.NovaObject.obj_class_from_name(objname,
objver)
- return getattr(objclass, objmethod)(context, **kwargs)
+ return getattr(objclass, objmethod)(context, *args, **kwargs)
- def object_action(self, context, objinst, objmethod, **kwargs):
+ def object_action(self, context, objinst, objmethod, args, kwargs):
"""Perform an action on an object."""
oldobj = copy.copy(objinst)
- result = getattr(objinst, objmethod)(context, **kwargs)
+ result = getattr(objinst, objmethod)(context, *args, **kwargs)
updates = dict()
# NOTE(danms): Diff the object with the one passed to us and
# generate a list of changes to forward back
@@ -514,7 +515,7 @@ class ComputeTaskManager(object):
"""
RPC_API_NAMESPACE = 'compute_task'
- RPC_API_VERSION = '1.1'
+ RPC_API_VERSION = '1.2'
def __init__(self):
self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI()
@@ -535,3 +536,15 @@ class ComputeTaskManager(object):
destination = scheduler_hint.get("host")
self.scheduler_rpcapi.live_migration(context, block_migration,
disk_over_commit, instance, destination)
+
+ def build_instances(self, context, instances, image, filter_properties,
+ admin_password, injected_files, requested_networks,
+ security_groups, block_device_mapping):
+ request_spec = scheduler_utils.build_request_spec(image, instances)
+ # NOTE(alaski): For compatibility until a new scheduler method is used.
+ request_spec.update({'block_device_mapping': block_device_mapping,
+ 'security_group': security_groups})
+ self.scheduler_rpcapi.run_instance(context, request_spec=request_spec,
+ admin_password=admin_password, injected_files=injected_files,
+ requested_networks=requested_networks, is_first_time=True,
+ filter_properties=filter_properties)
diff --git a/nova/conductor/rpcapi.py b/nova/conductor/rpcapi.py
index c2a0e1b93..e1f65fae2 100644
--- a/nova/conductor/rpcapi.py
+++ b/nova/conductor/rpcapi.py
@@ -444,14 +444,16 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy):
msg = self.make_msg('compute_unrescue', instance=instance_p)
return self.call(context, msg, version='1.48')
- def object_class_action(self, context, objname, objmethod, objver, kwargs):
+ def object_class_action(self, context, objname, objmethod, objver,
+ args, kwargs):
msg = self.make_msg('object_class_action', objname=objname,
- objmethod=objmethod, objver=objver, **kwargs)
+ objmethod=objmethod, objver=objver,
+ args=args, kwargs=kwargs)
return self.call(context, msg, version='1.50')
- def object_action(self, context, objinst, objmethod, kwargs):
+ def object_action(self, context, objinst, objmethod, args, kwargs):
msg = self.make_msg('object_action', objinst=objinst,
- objmethod=objmethod, **kwargs)
+ objmethod=objmethod, args=args, kwargs=kwargs)
return self.call(context, msg, version='1.50')
@@ -462,6 +464,7 @@ class ComputeTaskAPI(nova.openstack.common.rpc.proxy.RpcProxy):
1.0 - Initial version (empty).
1.1 - Added unified migrate_server call.
+ 1.2 - Added build_instances
"""
BASE_RPC_API_VERSION = '1.0'
@@ -481,3 +484,16 @@ class ComputeTaskAPI(nova.openstack.common.rpc.proxy.RpcProxy):
flavor=flavor_p, block_migration=block_migration,
disk_over_commit=disk_over_commit)
return self.call(context, msg, version='1.1')
+
+ def build_instances(self, context, instances, image, filter_properties,
+ admin_password, injected_files, requested_networks,
+ security_groups, block_device_mapping):
+ instances_p = [jsonutils.to_primitive(inst) for inst in instances]
+ image_p = jsonutils.to_primitive(image)
+ msg = self.make_msg('build_instances', instances=instances_p,
+ image=image_p, filter_properties=filter_properties,
+ admin_password=admin_password, injected_files=injected_files,
+ requested_networks=requested_networks,
+ security_groups=security_groups,
+ block_device_mapping=block_device_mapping)
+ self.cast(context, msg, version='1.2')
diff --git a/nova/consoleauth/manager.py b/nova/consoleauth/manager.py
index a235eafb6..6e822cf18 100644
--- a/nova/consoleauth/manager.py
+++ b/nova/consoleauth/manager.py
@@ -24,7 +24,7 @@ from oslo.config import cfg
from nova.cells import rpcapi as cells_rpcapi
from nova.compute import rpcapi as compute_rpcapi
-from nova.conductor import api as conductor_api
+from nova import conductor
from nova import manager
from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
@@ -56,7 +56,7 @@ class ConsoleAuthManager(manager.Manager):
super(ConsoleAuthManager, self).__init__(service_name='consoleauth',
*args, **kwargs)
self.mc = memorycache.get_client()
- self.conductor_api = conductor_api.API()
+ self.conductor_api = conductor.API()
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
self.cells_rpcapi = cells_rpcapi.CellsAPI()
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 18aa185ab..3b1d0b3a4 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -649,14 +649,18 @@ def certificate_get_all_by_user_and_project(context, user_id, project_id):
@require_context
def floating_ip_get(context, id):
- result = model_query(context, models.FloatingIp, project_only=True).\
- filter_by(id=id).\
- options(joinedload_all('fixed_ip.instance')).\
- first()
-
- if not result:
- raise exception.FloatingIpNotFound(id=id)
+ try:
+ result = model_query(context, models.FloatingIp, project_only=True).\
+ filter_by(id=id).\
+ options(joinedload_all('fixed_ip.instance')).\
+ first()
+ if not result:
+ raise exception.FloatingIpNotFound(id=id)
+ except DataError:
+ msg = _("Invalid floating ip id %s in request") % id
+ LOG.warn(msg)
+ raise exception.InvalidID(id=id)
return result
@@ -866,14 +870,18 @@ def _floating_ip_get_by_address(context, address, session=None):
# if address string is empty explicitly set it to None
if not address:
address = None
+ try:
+ result = model_query(context, models.FloatingIp, session=session).\
+ filter_by(address=address).\
+ options(joinedload_all('fixed_ip.instance')).\
+ first()
- result = model_query(context, models.FloatingIp, session=session).\
- filter_by(address=address).\
- options(joinedload_all('fixed_ip.instance')).\
- first()
-
- if not result:
- raise exception.FloatingIpNotFoundForAddress(address=address)
+ if not result:
+ raise exception.FloatingIpNotFoundForAddress(address=address)
+ except DataError:
+ msg = _("Invalid floating IP %s in request") % address
+ LOG.warn(msg)
+ raise exception.InvalidIpAddressError(msg)
# If the floating IP has a project ID set, check to make sure
# the non-admin user has access.
@@ -1149,11 +1157,16 @@ def _fixed_ip_get_by_address(context, address, session=None):
session = get_session()
with session.begin(subtransactions=True):
- result = model_query(context, models.FixedIp, session=session).\
- filter_by(address=address).\
- first()
- if not result:
- raise exception.FixedIpNotFoundForAddress(address=address)
+ try:
+ result = model_query(context, models.FixedIp, session=session).\
+ filter_by(address=address).\
+ first()
+ if not result:
+ raise exception.FixedIpNotFoundForAddress(address=address)
+ except DataError:
+ msg = _("Invalid fixed IP Address %s in request") % address
+ LOG.warn(msg)
+ raise exception.FixedIpInvalid(msg)
# NOTE(sirp): shouldn't we just use project_only here to restrict the
# results?
@@ -1175,19 +1188,25 @@ def fixed_ip_get_by_address_detailed(context, address):
"""
:returns: a tuple of (models.FixedIp, models.Network, models.Instance)
"""
- result = model_query(context, models.FixedIp,
- models.Network, models.Instance).\
- filter_by(address=address).\
- outerjoin((models.Network,
- models.Network.id ==
- models.FixedIp.network_id)).\
- outerjoin((models.Instance,
- models.Instance.uuid ==
- models.FixedIp.instance_uuid)).\
- first()
+ try:
+ result = model_query(context, models.FixedIp,
+ models.Network, models.Instance).\
+ filter_by(address=address).\
+ outerjoin((models.Network,
+ models.Network.id ==
+ models.FixedIp.network_id)).\
+ outerjoin((models.Instance,
+ models.Instance.uuid ==
+ models.FixedIp.instance_uuid)).\
+ first()
- if not result:
- raise exception.FixedIpNotFoundForAddress(address=address)
+ if not result:
+ raise exception.FixedIpNotFoundForAddress(address=address)
+
+ except DataError:
+ msg = _("Invalid fixed IP Address %s in request") % address
+ LOG.warn(msg)
+ raise exception.FixedIpInvalid(msg)
return result
@@ -1317,9 +1336,14 @@ def virtual_interface_get_by_address(context, address):
:param address: = the address of the interface you're looking to get
"""
- vif_ref = _virtual_interface_query(context).\
- filter_by(address=address).\
- first()
+ try:
+ vif_ref = _virtual_interface_query(context).\
+ filter_by(address=address).\
+ first()
+ except DataError:
+ msg = _("Invalid virtual interface address %s in request") % address
+ LOG.warn(msg)
+ raise exception.InvalidIpAddressError(msg)
return vif_ref
diff --git a/nova/filters.py b/nova/filters.py
index 4b7f9ff10..e1f65658a 100644
--- a/nova/filters.py
+++ b/nova/filters.py
@@ -64,4 +64,6 @@ class BaseFilterHandler(loadables.BaseLoader):
list_objs = list(objs)
LOG.debug("Filter %(cls_name)s returned %(obj_len)d host(s)",
{'cls_name': cls_name, 'obj_len': len(list_objs)})
+ if len(list_objs) == 0:
+ break
return list_objs
diff --git a/nova/image/s3.py b/nova/image/s3.py
index f08cbcdcb..14ce692b9 100644
--- a/nova/image/s3.py
+++ b/nova/image/s3.py
@@ -313,74 +313,79 @@ class S3ImageService(object):
self.service.update(context, image_uuid, metadata, image_data,
purge_props=False)
- _update_image_state(context, image_uuid, 'downloading')
-
- try:
- parts = []
- elements = manifest.find('image').getiterator('filename')
- for fn_element in elements:
- part = self._download_file(bucket,
- fn_element.text,
- image_path)
- parts.append(part)
-
- # NOTE(vish): this may be suboptimal, should we use cat?
- enc_filename = os.path.join(image_path, 'image.encrypted')
- with open(enc_filename, 'w') as combined:
- for filename in parts:
- with open(filename) as part:
- shutil.copyfileobj(part, combined)
-
- except Exception:
- LOG.exception(_("Failed to download %(image_location)s "
- "to %(image_path)s"), log_vars)
- _update_image_state(context, image_uuid, 'failed_download')
- return
-
- _update_image_state(context, image_uuid, 'decrypting')
-
- try:
- hex_key = manifest.find('image/ec2_encrypted_key').text
- encrypted_key = binascii.a2b_hex(hex_key)
- hex_iv = manifest.find('image/ec2_encrypted_iv').text
- encrypted_iv = binascii.a2b_hex(hex_iv)
-
- dec_filename = os.path.join(image_path, 'image.tar.gz')
- self._decrypt_image(context, enc_filename, encrypted_key,
- encrypted_iv, dec_filename)
- except Exception:
- LOG.exception(_("Failed to decrypt %(image_location)s "
- "to %(image_path)s"), log_vars)
- _update_image_state(context, image_uuid, 'failed_decrypt')
- return
-
- _update_image_state(context, image_uuid, 'untarring')
-
try:
- unz_filename = self._untarzip_image(image_path, dec_filename)
- except Exception:
- LOG.exception(_("Failed to untar %(image_location)s "
- "to %(image_path)s"), log_vars)
- _update_image_state(context, image_uuid, 'failed_untar')
- return
+ _update_image_state(context, image_uuid, 'downloading')
+
+ try:
+ parts = []
+ elements = manifest.find('image').getiterator('filename')
+ for fn_element in elements:
+ part = self._download_file(bucket,
+ fn_element.text,
+ image_path)
+ parts.append(part)
+
+ # NOTE(vish): this may be suboptimal, should we use cat?
+ enc_filename = os.path.join(image_path, 'image.encrypted')
+ with open(enc_filename, 'w') as combined:
+ for filename in parts:
+ with open(filename) as part:
+ shutil.copyfileobj(part, combined)
+
+ except Exception:
+ LOG.exception(_("Failed to download %(image_location)s "
+ "to %(image_path)s"), log_vars)
+ _update_image_state(context, image_uuid, 'failed_download')
+ return
+
+ _update_image_state(context, image_uuid, 'decrypting')
+
+ try:
+ hex_key = manifest.find('image/ec2_encrypted_key').text
+ encrypted_key = binascii.a2b_hex(hex_key)
+ hex_iv = manifest.find('image/ec2_encrypted_iv').text
+ encrypted_iv = binascii.a2b_hex(hex_iv)
+
+ dec_filename = os.path.join(image_path, 'image.tar.gz')
+ self._decrypt_image(context, enc_filename, encrypted_key,
+ encrypted_iv, dec_filename)
+ except Exception:
+ LOG.exception(_("Failed to decrypt %(image_location)s "
+ "to %(image_path)s"), log_vars)
+ _update_image_state(context, image_uuid, 'failed_decrypt')
+ return
+
+ _update_image_state(context, image_uuid, 'untarring')
+
+ try:
+ unz_filename = self._untarzip_image(image_path,
+ dec_filename)
+ except Exception:
+ LOG.exception(_("Failed to untar %(image_location)s "
+ "to %(image_path)s"), log_vars)
+ _update_image_state(context, image_uuid, 'failed_untar')
+ return
+
+ _update_image_state(context, image_uuid, 'uploading')
+ try:
+ with open(unz_filename) as image_file:
+ _update_image_data(context, image_uuid, image_file)
+ except Exception:
+ LOG.exception(_("Failed to upload %(image_location)s "
+ "to %(image_path)s"), log_vars)
+ _update_image_state(context, image_uuid, 'failed_upload')
+ return
+
+ metadata = {'status': 'active',
+ 'properties': {'image_state': 'available'}}
+ self.service.update(context, image_uuid, metadata,
+ purge_props=False)
- _update_image_state(context, image_uuid, 'uploading')
- try:
- with open(unz_filename) as image_file:
- _update_image_data(context, image_uuid, image_file)
- except Exception:
- LOG.exception(_("Failed to upload %(image_location)s "
- "to %(image_path)s"), log_vars)
- _update_image_state(context, image_uuid, 'failed_upload')
+ shutil.rmtree(image_path)
+ except exception.ImageNotFound:
+ LOG.info(_("Image %s was deleted underneath us"), image_uuid)
return
- metadata = {'status': 'active',
- 'properties': {'image_state': 'available'}}
- self.service.update(context, image_uuid, metadata,
- purge_props=False)
-
- shutil.rmtree(image_path)
-
eventlet.spawn_n(delayed_create)
return image
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index c47b50060..dad496f23 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -896,6 +896,8 @@ def _remove_dnsmasq_accept_rules(dev):
iptables_manager.apply()
+# NOTE(russellb) Curious why this is needed? Check out this explanation from
+# markmc: https://bugzilla.redhat.com/show_bug.cgi?id=910619#c6
def _add_dhcp_mangle_rule(dev):
if not os.path.exists('/dev/vhost-net'):
return
diff --git a/nova/objects/base.py b/nova/objects/base.py
index abeebf990..1ba0f03b7 100644
--- a/nova/objects/base.py
+++ b/nova/objects/base.py
@@ -16,9 +16,11 @@
import collections
+from nova import context
from nova import exception
from nova.objects import utils as obj_utils
from nova.openstack.common import log as logging
+from nova.openstack.common.rpc import common as rpc_common
import nova.openstack.common.rpc.dispatcher
import nova.openstack.common.rpc.proxy
import nova.openstack.common.rpc.serializer
@@ -81,12 +83,13 @@ class NovaObjectMetaclass(type):
# requested action and the result will be returned here.
def remotable_classmethod(fn):
"""Decorator for remotable classmethods."""
- def wrapper(cls, context, **kwargs):
+ def wrapper(cls, context, *args, **kwargs):
if NovaObject.indirection_api:
result = NovaObject.indirection_api.object_class_action(
- context, cls.obj_name(), fn.__name__, cls.version, kwargs)
+ context, cls.obj_name(), fn.__name__, cls.version,
+ args, kwargs)
else:
- result = fn(cls, context, **kwargs)
+ result = fn(cls, context, *args, **kwargs)
if isinstance(result, NovaObject):
result._context = context
return result
@@ -100,22 +103,28 @@ def remotable_classmethod(fn):
# "orphaned" and remotable methods cannot be called.
def remotable(fn):
"""Decorator for remotable object methods."""
- def wrapper(self, context=None, **kwargs):
- if context is None:
- context = self._context
- if context is None:
+ def wrapper(self, *args, **kwargs):
+ ctxt = self._context
+ try:
+ if isinstance(args[0], (context.RequestContext,
+ rpc_common.CommonRpcContext)):
+ ctxt = args[0]
+ args = args[1:]
+ except IndexError:
+ pass
+ if ctxt is None:
raise exception.OrphanedObjectError(method=fn.__name__,
objtype=self.obj_name())
if NovaObject.indirection_api:
updates, result = NovaObject.indirection_api.object_action(
- context, self, fn.__name__, kwargs)
+ ctxt, self, fn.__name__, args, kwargs)
for key, value in updates.iteritems():
if key in self.fields:
self[key] = self._attr_from_primitive(key, value)
self._changed_fields = set(updates.get('obj_what_changed', []))
return result
else:
- return fn(self, context, **kwargs)
+ return fn(self, ctxt, *args, **kwargs)
return wrapper
@@ -168,9 +177,9 @@ class NovaObject(object):
# by subclasses, but that is a special case. Objects inheriting from
# other objects will not receive this merging of fields contents.
fields = {
- 'created_at': obj_utils.datetime_or_none,
- 'updated_at': obj_utils.datetime_or_none,
- 'deleted_at': obj_utils.datetime_or_none,
+ 'created_at': obj_utils.datetime_or_str_or_none,
+ 'updated_at': obj_utils.datetime_or_str_or_none,
+ 'deleted_at': obj_utils.datetime_or_str_or_none,
}
def __init__(self):
diff --git a/nova/objects/instance.py b/nova/objects/instance.py
index 836d78c08..dbf9786df 100644
--- a/nova/objects/instance.py
+++ b/nova/objects/instance.py
@@ -58,9 +58,9 @@ class Instance(base.NovaObject):
'reservation_id': obj_utils.str_or_none,
- 'scheduled_at': obj_utils.datetime_or_none,
- 'launched_at': obj_utils.datetime_or_none,
- 'terminated_at': obj_utils.datetime_or_none,
+ 'scheduled_at': obj_utils.datetime_or_str_or_none,
+ 'launched_at': obj_utils.datetime_or_str_or_none,
+ 'terminated_at': obj_utils.datetime_or_str_or_none,
'availability_zone': obj_utils.str_or_none,
diff --git a/nova/objects/utils.py b/nova/objects/utils.py
index 042b7b36e..dd654045c 100644
--- a/nova/objects/utils.py
+++ b/nova/objects/utils.py
@@ -27,6 +27,14 @@ def datetime_or_none(dt):
raise ValueError('A datetime.datetime is required here')
+# NOTE(danms): Being tolerant of isotime strings here will help us
+# during our objects transition
+def datetime_or_str_or_none(val):
+ if isinstance(val, basestring):
+ return timeutils.parse_isotime(val)
+ return datetime_or_none(val)
+
+
def int_or_none(val):
"""Attempt to parse an integer value, or None."""
if val is None:
diff --git a/nova/scheduler/utils.py b/nova/scheduler/utils.py
new file mode 100644
index 000000000..315f9df2b
--- /dev/null
+++ b/nova/scheduler/utils.py
@@ -0,0 +1,33 @@
+# 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 methods for scheduling."""
+
+from nova.compute import flavors
+from nova.openstack.common import jsonutils
+
+
+def build_request_spec(image, instances):
+ """Build a request_spec for the scheduler.
+
+ The request_spec assumes that all instances to be scheduled are the same
+ type.
+ """
+ instance = jsonutils.to_primitive(instances[0])
+ request_spec = {
+ 'image': image,
+ 'instance_properties': instance,
+ 'instance_type': flavors.extract_instance_type(instance),
+ 'instance_uuids': [inst['uuid'] for inst in instances]}
+ return request_spec
diff --git a/nova/storage/linuxscsi.py b/nova/storage/linuxscsi.py
index 95cec192d..afd8aa81c 100644
--- a/nova/storage/linuxscsi.py
+++ b/nova/storage/linuxscsi.py
@@ -123,17 +123,19 @@ def find_multipath_device(device):
LOG.debug(_("Found multipath device = %(mdev)s") % locals())
device_lines = lines[3:]
for dev_line in device_lines:
- dev_line = dev_line.strip()
- dev_line = dev_line[3:]
+ if dev_line.find("policy") != -1:
+ continue
+
+ dev_line = dev_line.lstrip(' |-`')
dev_info = dev_line.split()
- if dev_line.find("policy") == -1:
- address = dev_info[0].split(":")
-
- dev = {'device': '/dev/%s' % dev_info[1],
- 'host': address[0], 'channel': address[1],
- 'id': address[2], 'lun': address[3]
- }
- devices.append(dev)
+ address = dev_info[0].split(":")
+
+ dev = {'device': '/dev/%s' % dev_info[1],
+ 'host': address[0], 'channel': address[1],
+ 'id': address[2], 'lun': address[3]
+ }
+
+ devices.append(dev)
if mdev is not None:
info = {"device": mdev,
diff --git a/nova/tests/api/ec2/test_cinder_cloud.py b/nova/tests/api/ec2/test_cinder_cloud.py
index 5d02a28d7..a307eaa2f 100644
--- a/nova/tests/api/ec2/test_cinder_cloud.py
+++ b/nova/tests/api/ec2/test_cinder_cloud.py
@@ -34,6 +34,7 @@ from nova import exception
from nova.openstack.common import rpc
from nova import test
from nova.tests import fake_network
+from nova.tests import fake_utils
from nova.tests.image import fake
from nova.tests import matchers
from nova import volume
@@ -88,6 +89,7 @@ class CinderCloudTestCase(test.TestCase):
super(CinderCloudTestCase, self).setUp()
ec2utils.reset_cache()
vol_tmpdir = self.useFixture(fixtures.TempDir()).path
+ fake_utils.stub_out_utils_spawn_n(self.stubs)
self.flags(compute_driver='nova.virt.fake.FakeDriver',
volume_api_class='nova.tests.fake_volume.API')
diff --git a/nova/tests/api/ec2/test_cloud.py b/nova/tests/api/ec2/test_cloud.py
index e9ff10be0..22f9c2d81 100644
--- a/nova/tests/api/ec2/test_cloud.py
+++ b/nova/tests/api/ec2/test_cloud.py
@@ -50,6 +50,7 @@ from nova import test
from nova.tests.api.openstack.compute.contrib import (
test_quantum_security_groups as test_quantum)
from nova.tests import fake_network
+from nova.tests import fake_utils
from nova.tests.image import fake
from nova.tests import matchers
from nova import utils
@@ -112,6 +113,7 @@ class CloudTestCase(test.TestCase):
self.flags(compute_driver='nova.virt.fake.FakeDriver',
volume_api_class='nova.tests.fake_volume.API')
self.useFixture(fixtures.FakeLogger('boto'))
+ fake_utils.stub_out_utils_spawn_n(self.stubs)
def fake_show(meh, context, id):
return {'id': id,
diff --git a/nova/tests/api/openstack/compute/contrib/test_fixed_ips.py b/nova/tests/api/openstack/compute/contrib/test_fixed_ips.py
index 67417e60e..2f9f6c5bc 100644
--- a/nova/tests/api/openstack/compute/contrib/test_fixed_ips.py
+++ b/nova/tests/api/openstack/compute/contrib/test_fixed_ips.py
@@ -132,6 +132,11 @@ class FixedIpTest(test.TestCase):
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, req,
'10.0.0.1')
+ def test_fixed_ips_get_invalid_ip_address(self):
+ req = fakes.HTTPRequest.blank('/v2/fake/os-fixed-ips/inv.ali.d.ip')
+ self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, req,
+ 'inv.ali.d.ip')
+
def test_fixed_ips_get_deleted_ip_fail(self):
req = fakes.HTTPRequest.blank('/v2/fake/os-fixed-ips/10.0.0.2')
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, req,
@@ -154,6 +159,13 @@ class FixedIpTest(test.TestCase):
self.assertRaises(webob.exc.HTTPNotFound, self.controller.action, req,
'10.0.0.1', body)
+ def test_fixed_ip_reserve_invalid_ip_address(self):
+ body = {'reserve': None}
+ req = fakes.HTTPRequest.blank(
+ '/v2/fake/os-fixed-ips/inv.ali.d.ip/action')
+ self.assertRaises(webob.exc.HTTPNotFound,
+ self.controller.action, req, 'inv.ali.d.ip', body)
+
def test_fixed_ip_reserve_deleted_ip(self):
body = {'reserve': None}
req = fakes.HTTPRequest.blank(
@@ -178,6 +190,13 @@ class FixedIpTest(test.TestCase):
self.assertRaises(webob.exc.HTTPNotFound, self.controller.action, req,
'10.0.0.1', body)
+ def test_fixed_ip_unreserve_invalid_ip_address(self):
+ body = {'unreserve': None}
+ req = fakes.HTTPRequest.blank(
+ '/v2/fake/os-fixed-ips/inv.ali.d.ip/action')
+ self.assertRaises(webob.exc.HTTPNotFound,
+ self.controller.action, req, 'inv.ali.d.ip', body)
+
def test_fixed_ip_unreserve_deleted_ip(self):
body = {'unreserve': None}
req = fakes.HTTPRequest.blank(
diff --git a/nova/tests/api/openstack/compute/contrib/test_server_usage.py b/nova/tests/api/openstack/compute/contrib/test_server_usage.py
new file mode 100644
index 000000000..0c06a1738
--- /dev/null
+++ b/nova/tests/api/openstack/compute/contrib/test_server_usage.py
@@ -0,0 +1,131 @@
+# Copyright 2013 OpenStack Foundation
+# 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
+
+from nova.api.openstack.compute.contrib import server_usage
+from nova import compute
+from nova import exception
+from nova.openstack.common import jsonutils
+from nova.openstack.common import timeutils
+from nova import test
+from nova.tests.api.openstack import fakes
+
+UUID1 = '00000000-0000-0000-0000-000000000001'
+UUID2 = '00000000-0000-0000-0000-000000000002'
+UUID3 = '00000000-0000-0000-0000-000000000003'
+
+DATE1 = datetime.datetime(year=2013, month=4, day=5, hour=12)
+DATE2 = datetime.datetime(year=2013, month=4, day=5, hour=13)
+DATE3 = datetime.datetime(year=2013, month=4, day=5, hour=14)
+
+
+def fake_compute_get(*args, **kwargs):
+ return fakes.stub_instance(1, uuid=UUID3, launched_at=DATE1,
+ terminated_at=DATE2)
+
+
+def fake_compute_get_all(*args, **kwargs):
+ return [
+ fakes.stub_instance(2, uuid=UUID1, launched_at=DATE2,
+ terminated_at=DATE3),
+ fakes.stub_instance(3, uuid=UUID2, launched_at=DATE1,
+ terminated_at=DATE3),
+ ]
+
+
+class ServerUsageTest(test.TestCase):
+ content_type = 'application/json'
+ prefix = 'OS-SRV-USG:'
+
+ def setUp(self):
+ super(ServerUsageTest, self).setUp()
+ fakes.stub_out_nw_api(self.stubs)
+ self.stubs.Set(compute.api.API, 'get', fake_compute_get)
+ self.stubs.Set(compute.api.API, 'get_all', fake_compute_get_all)
+ self.flags(
+ osapi_compute_extension=[
+ 'nova.api.openstack.compute.contrib.select_extensions'],
+ osapi_compute_ext_list=['Server_usage'])
+
+ def _make_request(self, url):
+ req = fakes.HTTPRequest.blank(url)
+ req.accept = self.content_type
+ res = req.get_response(fakes.wsgi_app(init_only=('servers',)))
+ return res
+
+ def _get_server(self, body):
+ return jsonutils.loads(body).get('server')
+
+ def _get_servers(self, body):
+ return jsonutils.loads(body).get('servers')
+
+ def assertServerUsage(self, server, launched_at, terminated_at):
+ resp_launched_at = timeutils.parse_isotime(
+ server.get('%slaunched_at' % self.prefix))
+ self.assertEqual(timeutils.normalize_time(resp_launched_at),
+ launched_at)
+ resp_terminated_at = timeutils.parse_isotime(
+ server.get('%sterminated_at' % self.prefix))
+ self.assertEqual(timeutils.normalize_time(resp_terminated_at),
+ terminated_at)
+
+ def test_show(self):
+ url = '/v2/fake/servers/%s' % UUID3
+ res = self._make_request(url)
+
+ self.assertEqual(res.status_int, 200)
+ now = datetime.datetime.utcnow()
+ timeutils.set_time_override(now)
+ self.assertServerUsage(self._get_server(res.body),
+ launched_at=DATE1,
+ terminated_at=DATE2)
+
+ def test_detail(self):
+ url = '/v2/fake/servers/detail'
+ res = self._make_request(url)
+
+ self.assertEqual(res.status_int, 200)
+ servers = self._get_servers(res.body)
+ self.assertServerUsage(servers[0],
+ launched_at=DATE2,
+ terminated_at=DATE3)
+ self.assertServerUsage(servers[1],
+ launched_at=DATE1,
+ terminated_at=DATE3)
+
+ def test_no_instance_passthrough_404(self):
+
+ def fake_compute_get(*args, **kwargs):
+ raise exception.InstanceNotFound(instance_id='fake')
+
+ self.stubs.Set(compute.api.API, 'get', fake_compute_get)
+ url = '/v2/fake/servers/70f6db34-de8d-4fbd-aafb-4065bdfa6115'
+ res = self._make_request(url)
+
+ self.assertEqual(res.status_int, 404)
+
+
+class ServerUsageXmlTest(ServerUsageTest):
+ content_type = 'application/xml'
+ prefix = '{%s}' % server_usage.Server_usage.namespace
+
+ def _get_server(self, body):
+ return etree.XML(body)
+
+ def _get_servers(self, body):
+ return etree.XML(body).getchildren()
diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_consoles.py b/nova/tests/api/openstack/compute/plugins/v3/test_consoles.py
new file mode 100644
index 000000000..2f84af434
--- /dev/null
+++ b/nova/tests/api/openstack/compute/plugins/v3/test_consoles.py
@@ -0,0 +1,295 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010-2011 OpenStack Foundation
+# Copyright 2011 Piston Cloud Computing, 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.
+
+import datetime
+import uuid as stdlib_uuid
+
+from lxml import etree
+import webob
+
+from nova.api.openstack.compute.plugins.v3 import consoles
+from nova.compute import vm_states
+from nova import console
+from nova import db
+from nova import exception
+from nova.openstack.common import timeutils
+from nova import test
+from nova.tests.api.openstack import fakes
+from nova.tests import matchers
+
+
+FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
+
+
+class FakeInstanceDB(object):
+
+ def __init__(self):
+ self.instances_by_id = {}
+ self.ids_by_uuid = {}
+ self.max_id = 0
+
+ def return_server_by_id(self, context, id):
+ if id not in self.instances_by_id:
+ self._add_server(id=id)
+ return dict(self.instances_by_id[id])
+
+ def return_server_by_uuid(self, context, uuid):
+ if uuid not in self.ids_by_uuid:
+ self._add_server(uuid=uuid)
+ return dict(self.instances_by_id[self.ids_by_uuid[uuid]])
+
+ def _add_server(self, id=None, uuid=None):
+ if id is None:
+ id = self.max_id + 1
+ if uuid is None:
+ uuid = str(stdlib_uuid.uuid4())
+ instance = stub_instance(id, uuid=uuid)
+ self.instances_by_id[id] = instance
+ self.ids_by_uuid[uuid] = id
+ if id > self.max_id:
+ self.max_id = id
+
+
+def stub_instance(id, user_id='fake', project_id='fake', host=None,
+ vm_state=None, task_state=None,
+ reservation_id="", uuid=FAKE_UUID, image_ref="10",
+ flavor_id="1", name=None, key_name='',
+ access_ipv4=None, access_ipv6=None, progress=0):
+
+ if host is not None:
+ host = str(host)
+
+ if key_name:
+ key_data = 'FAKE'
+ else:
+ key_data = ''
+
+ # ReservationID isn't sent back, hack it in there.
+ server_name = name or "server%s" % id
+ if reservation_id != "":
+ server_name = "reservation_%s" % (reservation_id, )
+
+ instance = {
+ "id": int(id),
+ "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
+ "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
+ "admin_pass": "",
+ "user_id": user_id,
+ "project_id": project_id,
+ "image_ref": image_ref,
+ "kernel_id": "",
+ "ramdisk_id": "",
+ "launch_index": 0,
+ "key_name": key_name,
+ "key_data": key_data,
+ "vm_state": vm_state or vm_states.BUILDING,
+ "task_state": task_state,
+ "memory_mb": 0,
+ "vcpus": 0,
+ "root_gb": 0,
+ "hostname": "",
+ "host": host,
+ "instance_type": {},
+ "user_data": "",
+ "reservation_id": reservation_id,
+ "mac_address": "",
+ "scheduled_at": timeutils.utcnow(),
+ "launched_at": timeutils.utcnow(),
+ "terminated_at": timeutils.utcnow(),
+ "availability_zone": "",
+ "display_name": server_name,
+ "display_description": "",
+ "locked": False,
+ "metadata": [],
+ "access_ip_v4": access_ipv4,
+ "access_ip_v6": access_ipv6,
+ "uuid": uuid,
+ "progress": progress}
+
+ return instance
+
+
+class ConsolesControllerTest(test.TestCase):
+ def setUp(self):
+ super(ConsolesControllerTest, self).setUp()
+ self.flags(verbose=True)
+ self.instance_db = FakeInstanceDB()
+ self.stubs.Set(db, 'instance_get',
+ self.instance_db.return_server_by_id)
+ self.stubs.Set(db, 'instance_get_by_uuid',
+ self.instance_db.return_server_by_uuid)
+ self.uuid = str(stdlib_uuid.uuid4())
+ self.url = '/v3/fake/servers/%s/consoles' % self.uuid
+ self.controller = consoles.ConsolesController()
+
+ def test_create_console(self):
+ def fake_create_console(cons_self, context, instance_id):
+ self.assertEqual(instance_id, self.uuid)
+ return {}
+ self.stubs.Set(console.api.API, 'create_console', fake_create_console)
+
+ req = fakes.HTTPRequestV3.blank(self.url)
+ self.controller.create(req, self.uuid)
+
+ def test_show_console(self):
+ def fake_get_console(cons_self, context, instance_id, console_id):
+ self.assertEqual(instance_id, self.uuid)
+ self.assertEqual(console_id, 20)
+ pool = dict(console_type='fake_type',
+ public_hostname='fake_hostname')
+ return dict(id=console_id, password='fake_password',
+ port='fake_port', pool=pool, instance_name='inst-0001')
+
+ expected = {'console': {'id': 20,
+ 'port': 'fake_port',
+ 'host': 'fake_hostname',
+ 'password': 'fake_password',
+ 'instance_name': 'inst-0001',
+ 'console_type': 'fake_type'}}
+
+ self.stubs.Set(console.api.API, 'get_console', fake_get_console)
+
+ req = fakes.HTTPRequestV3.blank(self.url + '/20')
+ res_dict = self.controller.show(req, self.uuid, '20')
+ self.assertThat(res_dict, matchers.DictMatches(expected))
+
+ def test_show_console_unknown_console(self):
+ def fake_get_console(cons_self, context, instance_id, console_id):
+ raise exception.ConsoleNotFound(console_id=console_id)
+
+ self.stubs.Set(console.api.API, 'get_console', fake_get_console)
+
+ req = fakes.HTTPRequestV3.blank(self.url + '/20')
+ self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
+ req, self.uuid, '20')
+
+ def test_show_console_unknown_instance(self):
+ def fake_get_console(cons_self, context, instance_id, console_id):
+ raise exception.InstanceNotFound(instance_id=instance_id)
+
+ self.stubs.Set(console.api.API, 'get_console', fake_get_console)
+
+ req = fakes.HTTPRequestV3.blank(self.url + '/20')
+ self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
+ req, self.uuid, '20')
+
+ def test_list_consoles(self):
+ def fake_get_consoles(cons_self, context, instance_id):
+ self.assertEqual(instance_id, self.uuid)
+
+ pool1 = dict(console_type='fake_type',
+ public_hostname='fake_hostname')
+ cons1 = dict(id=10, password='fake_password',
+ port='fake_port', pool=pool1)
+ pool2 = dict(console_type='fake_type2',
+ public_hostname='fake_hostname2')
+ cons2 = dict(id=11, password='fake_password2',
+ port='fake_port2', pool=pool2)
+ return [cons1, cons2]
+
+ expected = {'consoles':
+ [{'console': {'id': 10, 'console_type': 'fake_type'}},
+ {'console': {'id': 11, 'console_type': 'fake_type2'}}]}
+
+ self.stubs.Set(console.api.API, 'get_consoles', fake_get_consoles)
+
+ req = fakes.HTTPRequestV3.blank(self.url)
+ res_dict = self.controller.index(req, self.uuid)
+ self.assertThat(res_dict, matchers.DictMatches(expected))
+
+ def test_delete_console(self):
+ def fake_get_console(cons_self, context, instance_id, console_id):
+ self.assertEqual(instance_id, self.uuid)
+ self.assertEqual(console_id, 20)
+ pool = dict(console_type='fake_type',
+ public_hostname='fake_hostname')
+ return dict(id=console_id, password='fake_password',
+ port='fake_port', pool=pool)
+
+ def fake_delete_console(cons_self, context, instance_id, console_id):
+ self.assertEqual(instance_id, self.uuid)
+ self.assertEqual(console_id, 20)
+
+ self.stubs.Set(console.api.API, 'get_console', fake_get_console)
+ self.stubs.Set(console.api.API, 'delete_console', fake_delete_console)
+
+ req = fakes.HTTPRequestV3.blank(self.url + '/20')
+ self.controller.delete(req, self.uuid, '20')
+
+ def test_delete_console_unknown_console(self):
+ def fake_delete_console(cons_self, context, instance_id, console_id):
+ raise exception.ConsoleNotFound(console_id=console_id)
+
+ self.stubs.Set(console.api.API, 'delete_console', fake_delete_console)
+
+ req = fakes.HTTPRequestV3.blank(self.url + '/20')
+ self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
+ req, self.uuid, '20')
+
+ def test_delete_console_unknown_instance(self):
+ def fake_delete_console(cons_self, context, instance_id, console_id):
+ raise exception.InstanceNotFound(instance_id=instance_id)
+
+ self.stubs.Set(console.api.API, 'delete_console', fake_delete_console)
+
+ req = fakes.HTTPRequestV3.blank(self.url + '/20')
+ self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
+ req, self.uuid, '20')
+
+
+class TestConsolesXMLSerializer(test.TestCase):
+ def test_show(self):
+ fixture = {'console': {'id': 20,
+ 'password': 'fake_password',
+ 'port': 'fake_port',
+ 'host': 'fake_hostname',
+ 'console_type': 'fake_type'}}
+
+ output = consoles.ConsoleTemplate().serialize(fixture)
+ res_tree = etree.XML(output)
+
+ self.assertEqual(res_tree.tag, 'console')
+ self.assertEqual(res_tree.xpath('id')[0].text, '20')
+ self.assertEqual(res_tree.xpath('port')[0].text, 'fake_port')
+ self.assertEqual(res_tree.xpath('host')[0].text, 'fake_hostname')
+ self.assertEqual(res_tree.xpath('password')[0].text, 'fake_password')
+ self.assertEqual(res_tree.xpath('console_type')[0].text, 'fake_type')
+
+ def test_index(self):
+ fixture = {'consoles': [{'console': {'id': 10,
+ 'console_type': 'fake_type'}},
+ {'console': {'id': 11,
+ 'console_type': 'fake_type2'}}]}
+
+ output = consoles.ConsolesTemplate().serialize(fixture)
+ res_tree = etree.XML(output)
+
+ self.assertEqual(res_tree.tag, 'consoles')
+ self.assertEqual(len(res_tree), 2)
+ self.assertEqual(res_tree[0].tag, 'console')
+ self.assertEqual(res_tree[1].tag, 'console')
+ self.assertEqual(len(res_tree[0]), 1)
+ self.assertEqual(res_tree[0][0].tag, 'console')
+ self.assertEqual(len(res_tree[1]), 1)
+ self.assertEqual(res_tree[1][0].tag, 'console')
+ self.assertEqual(res_tree[0][0].xpath('id')[0].text, '10')
+ self.assertEqual(res_tree[1][0].xpath('id')[0].text, '11')
+ self.assertEqual(res_tree[0][0].xpath('console_type')[0].text,
+ 'fake_type')
+ self.assertEqual(res_tree[1][0].xpath('console_type')[0].text,
+ 'fake_type2')
diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py
index 640e77b33..04857b59c 100644
--- a/nova/tests/api/openstack/fakes.py
+++ b/nova/tests/api/openstack/fakes.py
@@ -460,7 +460,9 @@ def stub_instance(id, user_id=None, project_id=None, host=None,
include_fake_metadata=True, config_drive=None,
power_state=None, nw_cache=None, metadata=None,
security_groups=None, root_device_name=None,
- limit=None, marker=None):
+ limit=None, marker=None,
+ launched_at=datetime.datetime.utcnow(),
+ terminated_at=datetime.datetime.utcnow()):
if user_id is None:
user_id = 'fake_user'
@@ -524,8 +526,8 @@ def stub_instance(id, user_id=None, project_id=None, host=None,
"reservation_id": reservation_id,
"mac_address": "",
"scheduled_at": timeutils.utcnow(),
- "launched_at": timeutils.utcnow(),
- "terminated_at": timeutils.utcnow(),
+ "launched_at": launched_at,
+ "terminated_at": terminated_at,
"availability_zone": "",
"display_name": display_name or server_name,
"display_description": "",
diff --git a/nova/tests/cells/test_cells_manager.py b/nova/tests/cells/test_cells_manager.py
index 137d48ff6..543ff66e7 100644
--- a/nova/tests/cells/test_cells_manager.py
+++ b/nova/tests/cells/test_cells_manager.py
@@ -122,6 +122,15 @@ class CellsManagerClassTestCase(test.TestCase):
self.cells_manager.schedule_run_instance(self.ctxt,
host_sched_kwargs=host_sched_kwargs)
+ def test_build_instances(self):
+ build_inst_kwargs = {'instances': [1, 2]}
+ self.mox.StubOutWithMock(self.msg_runner, 'build_instances')
+ our_cell = self.msg_runner.state_manager.get_my_state()
+ self.msg_runner.build_instances(self.ctxt, our_cell, build_inst_kwargs)
+ self.mox.ReplayAll()
+ self.cells_manager.build_instances(self.ctxt,
+ build_inst_kwargs=build_inst_kwargs)
+
def test_run_compute_api_method(self):
# Args should just be silently passed through
cell_name = 'fake-cell-name'
diff --git a/nova/tests/cells/test_cells_messaging.py b/nova/tests/cells/test_cells_messaging.py
index 1de39de1f..9aae11201 100644
--- a/nova/tests/cells/test_cells_messaging.py
+++ b/nova/tests/cells/test_cells_messaging.py
@@ -608,6 +608,16 @@ class CellsTargetedMethodsTestCase(test.TestCase):
self.tgt_cell_name,
host_sched_kwargs)
+ def test_build_instances(self):
+ build_inst_kwargs = {'filter_properties': {},
+ 'key1': 'value1',
+ 'key2': 'value2'}
+ self.mox.StubOutWithMock(self.tgt_scheduler, 'build_instances')
+ self.tgt_scheduler.build_instances(self.ctxt, build_inst_kwargs)
+ self.mox.ReplayAll()
+ self.src_msg_runner.build_instances(self.ctxt, self.tgt_cell_name,
+ build_inst_kwargs)
+
def test_run_compute_api_method(self):
instance_uuid = 'fake_instance_uuid'
diff --git a/nova/tests/cells/test_cells_rpcapi.py b/nova/tests/cells/test_cells_rpcapi.py
index 76c9f05d3..172b54831 100644
--- a/nova/tests/cells/test_cells_rpcapi.py
+++ b/nova/tests/cells/test_cells_rpcapi.py
@@ -114,6 +114,21 @@ class CellsAPITestCase(test.TestCase):
self._check_result(call_info, 'schedule_run_instance',
expected_args)
+ def test_build_instances(self):
+ call_info = self._stub_rpc_method('cast', None)
+
+ self.cells_rpcapi.build_instances(
+ self.fake_context, instances=['1', '2'],
+ image={'fake': 'image'}, arg1=1, arg2=2, arg3=3)
+
+ expected_args = {'build_inst_kwargs': {'instances': ['1', '2'],
+ 'image': {'fake': 'image'},
+ 'arg1': 1,
+ 'arg2': 2,
+ 'arg3': 3}}
+ self._check_result(call_info, 'build_instances',
+ expected_args, version=1.8)
+
def test_instance_update_at_top(self):
fake_info_cache = {'id': 1,
'instance': 'fake_instance',
diff --git a/nova/tests/cells/test_cells_scheduler.py b/nova/tests/cells/test_cells_scheduler.py
index c8f90619e..9cd637cdf 100644
--- a/nova/tests/cells/test_cells_scheduler.py
+++ b/nova/tests/cells/test_cells_scheduler.py
@@ -26,6 +26,7 @@ from nova import context
from nova import db
from nova import exception
from nova.openstack.common import uuidutils
+from nova.scheduler import utils as scheduler_utils
from nova import test
from nova.tests.cells import fakes
@@ -73,24 +74,32 @@ class CellsSchedulerTestCase(test.TestCase):
for x in xrange(3):
instance_uuids.append(uuidutils.generate_uuid())
self.instance_uuids = instance_uuids
- self.request_spec = {'instance_uuids': instance_uuids,
- 'other': 'stuff'}
+ self.instances = [{'uuid': uuid} for uuid in instance_uuids]
+ self.request_spec = {
+ 'instance_uuids': instance_uuids,
+ 'instance_properties': 'fake_properties',
+ 'instance_type': 'fake_type',
+ 'image': 'fake_image',
+ 'security_group': 'fake_sec_groups',
+ 'block_device_mapping': 'fake_bdm'}
+ self.build_inst_kwargs = {
+ 'instances': self.instances,
+ 'instance_type': 'fake_type',
+ 'image': 'fake_image',
+ 'filter_properties': {},
+ 'security_group': 'fake_sec_groups',
+ 'block_device_mapping': 'fake_bdm'}
def test_create_instances_here(self):
# Just grab the first instance type
inst_type = db.instance_type_get(self.ctxt, 1)
image = {'properties': {}}
+ instance_uuids = self.instance_uuids
instance_props = {'hostname': 'meow',
'display_name': 'moo',
'image_ref': 'fake_image_ref',
'user_id': self.ctxt.user_id,
'project_id': self.ctxt.project_id}
- request_spec = {'instance_type': inst_type,
- 'image': image,
- 'security_group': ['default'],
- 'block_device_mapping': [],
- 'instance_properties': instance_props,
- 'instance_uuids': self.instance_uuids}
call_info = {'uuids': []}
@@ -100,10 +109,11 @@ class CellsSchedulerTestCase(test.TestCase):
self.stubs.Set(self.msg_runner, 'instance_update_at_top',
_fake_instance_update_at_top)
- self.scheduler._create_instances_here(self.ctxt, request_spec)
- self.assertEqual(self.instance_uuids, call_info['uuids'])
+ self.scheduler._create_instances_here(self.ctxt, instance_uuids,
+ instance_props, inst_type, image, ['default'], [])
+ self.assertEqual(instance_uuids, call_info['uuids'])
- for instance_uuid in self.instance_uuids:
+ for instance_uuid in instance_uuids:
instance = db.instance_get_by_uuid(self.ctxt, instance_uuid)
self.assertEqual('meow', instance['hostname'])
self.assertEqual('moo-%s' % instance['uuid'],
@@ -146,6 +156,48 @@ class CellsSchedulerTestCase(test.TestCase):
child_cells = self.state_manager.get_child_cells()
self.assertIn(call_info['target_cell'], child_cells)
+ def test_build_instances_selects_child_cell(self):
+ # Make sure there's no capacity info so we're sure to
+ # select a child cell
+ our_cell_info = self.state_manager.get_my_state()
+ our_cell_info.capacities = {}
+
+ call_info = {'times': 0}
+
+ orig_fn = self.msg_runner.build_instances
+
+ def msg_runner_build_instances(ctxt, target_cell, build_inst_kwargs):
+ # This gets called twice. Once for our running it
+ # in this cell.. and then it'll get called when the
+ # child cell is picked. So, first time.. just run it
+ # like normal.
+ if not call_info['times']:
+ call_info['times'] += 1
+ return orig_fn(ctxt, target_cell, build_inst_kwargs)
+ call_info['ctxt'] = ctxt
+ call_info['target_cell'] = target_cell
+ call_info['build_inst_kwargs'] = build_inst_kwargs
+
+ def fake_build_request_spec(image, instances):
+ request_spec = {
+ 'instance_uuids': [inst['uuid'] for inst in instances],
+ 'image': image}
+ return request_spec
+
+ self.stubs.Set(self.msg_runner, 'build_instances',
+ msg_runner_build_instances)
+ self.stubs.Set(scheduler_utils, 'build_request_spec',
+ fake_build_request_spec)
+
+ self.msg_runner.build_instances(self.ctxt, self.my_cell_state,
+ self.build_inst_kwargs)
+
+ self.assertEqual(self.ctxt, call_info['ctxt'])
+ self.assertEqual(self.build_inst_kwargs,
+ call_info['build_inst_kwargs'])
+ child_cells = self.state_manager.get_child_cells()
+ self.assertIn(call_info['target_cell'], child_cells)
+
def test_run_instance_selects_current_cell(self):
# Make sure there's no child cells so that we will be
# selected
@@ -153,9 +205,16 @@ class CellsSchedulerTestCase(test.TestCase):
call_info = {}
- def fake_create_instances_here(ctxt, request_spec):
+ def fake_create_instances_here(ctxt, instance_uuids,
+ instance_properties, instance_type, image, security_groups,
+ block_device_mapping):
call_info['ctxt'] = ctxt
- call_info['request_spec'] = request_spec
+ call_info['instance_uuids'] = instance_uuids
+ call_info['instance_properties'] = instance_properties
+ call_info['instance_type'] = instance_type
+ call_info['image'] = image
+ call_info['security_groups'] = security_groups
+ call_info['block_device_mapping'] = block_device_mapping
def fake_rpc_run_instance(ctxt, **host_sched_kwargs):
call_info['host_sched_kwargs'] = host_sched_kwargs
@@ -172,8 +231,69 @@ class CellsSchedulerTestCase(test.TestCase):
self.my_cell_state, host_sched_kwargs)
self.assertEqual(self.ctxt, call_info['ctxt'])
- self.assertEqual(self.request_spec, call_info['request_spec'])
self.assertEqual(host_sched_kwargs, call_info['host_sched_kwargs'])
+ self.assertEqual(self.instance_uuids, call_info['instance_uuids'])
+ self.assertEqual(self.request_spec['instance_properties'],
+ call_info['instance_properties'])
+ self.assertEqual(self.request_spec['instance_type'],
+ call_info['instance_type'])
+ self.assertEqual(self.request_spec['image'], call_info['image'])
+ self.assertEqual(self.request_spec['security_group'],
+ call_info['security_groups'])
+ self.assertEqual(self.request_spec['block_device_mapping'],
+ call_info['block_device_mapping'])
+
+ def test_build_instances_selects_current_cell(self):
+ # Make sure there's no child cells so that we will be
+ # selected
+ self.state_manager.child_cells = {}
+
+ call_info = {}
+
+ def fake_create_instances_here(ctxt, instance_uuids,
+ instance_properties, instance_type, image, security_groups,
+ block_device_mapping):
+ call_info['ctxt'] = ctxt
+ call_info['instance_uuids'] = instance_uuids
+ call_info['instance_properties'] = instance_properties
+ call_info['instance_type'] = instance_type
+ call_info['image'] = image
+ call_info['security_groups'] = security_groups
+ call_info['block_device_mapping'] = block_device_mapping
+
+ def fake_rpc_build_instances(ctxt, **build_inst_kwargs):
+ call_info['build_inst_kwargs'] = build_inst_kwargs
+
+ def fake_build_request_spec(image, instances):
+ request_spec = {
+ 'instance_uuids': [inst['uuid'] for inst in instances],
+ 'image': image}
+ return request_spec
+
+ self.stubs.Set(self.scheduler, '_create_instances_here',
+ fake_create_instances_here)
+ self.stubs.Set(self.scheduler.compute_task_api,
+ 'build_instances', fake_rpc_build_instances)
+ self.stubs.Set(scheduler_utils, 'build_request_spec',
+ fake_build_request_spec)
+
+ self.msg_runner.build_instances(self.ctxt, self.my_cell_state,
+ self.build_inst_kwargs)
+
+ self.assertEqual(self.ctxt, call_info['ctxt'])
+ self.assertEqual(self.instance_uuids, call_info['instance_uuids'])
+ self.assertEqual(self.build_inst_kwargs['instances'][0],
+ call_info['instance_properties'])
+ self.assertEqual(self.build_inst_kwargs['instance_type'],
+ call_info['instance_type'])
+ self.assertEqual(self.build_inst_kwargs['image'], call_info['image'])
+ self.assertEqual(self.build_inst_kwargs['security_group'],
+ call_info['security_groups'])
+ self.assertEqual(self.build_inst_kwargs['block_device_mapping'],
+ call_info['block_device_mapping'])
+ self.assertEqual(self.build_inst_kwargs,
+ call_info['build_inst_kwargs'])
+ self.assertEqual(self.instance_uuids, call_info['instance_uuids'])
def test_run_instance_retries_when_no_cells_avail(self):
self.flags(scheduler_retries=7, group='cells')
@@ -183,7 +303,7 @@ class CellsSchedulerTestCase(test.TestCase):
call_info = {'num_tries': 0, 'errored_uuids': []}
- def fake_run_instance(message, host_sched_kwargs):
+ def fake_grab_target_cells(filter_properties):
call_info['num_tries'] += 1
raise exception.NoCellsAvailable()
@@ -194,7 +314,8 @@ class CellsSchedulerTestCase(test.TestCase):
self.assertEqual(vm_states.ERROR, values['vm_state'])
call_info['errored_uuids'].append(instance_uuid)
- self.stubs.Set(self.scheduler, '_run_instance', fake_run_instance)
+ self.stubs.Set(self.scheduler, '_grab_target_cells',
+ fake_grab_target_cells)
self.stubs.Set(time, 'sleep', fake_sleep)
self.stubs.Set(db, 'instance_update', fake_instance_update)
@@ -204,17 +325,55 @@ class CellsSchedulerTestCase(test.TestCase):
self.assertEqual(8, call_info['num_tries'])
self.assertEqual(self.instance_uuids, call_info['errored_uuids'])
- def test_run_instance_on_random_exception(self):
+ def test_build_instances_retries_when_no_cells_avail(self):
self.flags(scheduler_retries=7, group='cells')
- host_sched_kwargs = {'request_spec': self.request_spec,
- 'filter_properties': {}}
+ call_info = {'num_tries': 0, 'errored_uuids': []}
+
+ def fake_grab_target_cells(filter_properties):
+ call_info['num_tries'] += 1
+ raise exception.NoCellsAvailable()
+
+ def fake_sleep(_secs):
+ return
+
+ def fake_instance_update(ctxt, instance_uuid, values):
+ self.assertEqual(vm_states.ERROR, values['vm_state'])
+ call_info['errored_uuids'].append(instance_uuid)
+
+ def fake_build_request_spec(image, instances):
+ request_spec = {
+ 'instance_uuids': [inst['uuid'] for inst in instances],
+ 'image': image}
+ return request_spec
+
+ self.stubs.Set(self.scheduler, '_grab_target_cells',
+ fake_grab_target_cells)
+ self.stubs.Set(time, 'sleep', fake_sleep)
+ self.stubs.Set(db, 'instance_update', fake_instance_update)
+ self.stubs.Set(scheduler_utils, 'build_request_spec',
+ fake_build_request_spec)
+
+ self.msg_runner.build_instances(self.ctxt, self.my_cell_state,
+ self.build_inst_kwargs)
+
+ self.assertEqual(8, call_info['num_tries'])
+ self.assertEqual(self.instance_uuids, call_info['errored_uuids'])
+
+ def test_schedule_method_on_random_exception(self):
+ self.flags(scheduler_retries=7, group='cells')
+
+ instances = [{'uuid': uuid} for uuid in self.instance_uuids]
+ method_kwargs = {
+ 'image': 'fake_image',
+ 'instances': instances,
+ 'filter_properties': {}}
call_info = {'num_tries': 0,
'errored_uuids1': [],
'errored_uuids2': []}
- def fake_run_instance(message, host_sched_kwargs):
+ def fake_grab_target_cells(filter_properties):
call_info['num_tries'] += 1
raise test.TestingException()
@@ -226,13 +385,22 @@ class CellsSchedulerTestCase(test.TestCase):
self.assertEqual(vm_states.ERROR, instance['vm_state'])
call_info['errored_uuids2'].append(instance['uuid'])
- self.stubs.Set(self.scheduler, '_run_instance', fake_run_instance)
+ def fake_build_request_spec(image, instances):
+ request_spec = {
+ 'instance_uuids': [inst['uuid'] for inst in instances],
+ 'image': image}
+ return request_spec
+
+ self.stubs.Set(self.scheduler, '_grab_target_cells',
+ fake_grab_target_cells)
self.stubs.Set(db, 'instance_update', fake_instance_update)
self.stubs.Set(self.msg_runner, 'instance_update_at_top',
- fake_instance_update_at_top)
+ fake_instance_update_at_top)
+ self.stubs.Set(scheduler_utils, 'build_request_spec',
+ fake_build_request_spec)
- self.msg_runner.schedule_run_instance(self.ctxt,
- self.my_cell_state, host_sched_kwargs)
+ self.msg_runner.build_instances(self.ctxt, self.my_cell_state,
+ method_kwargs)
# Shouldn't retry
self.assertEqual(1, call_info['num_tries'])
self.assertEqual(self.instance_uuids, call_info['errored_uuids1'])
@@ -252,9 +420,16 @@ class CellsSchedulerTestCase(test.TestCase):
call_info = {}
- def fake_create_instances_here(ctxt, request_spec):
+ def fake_create_instances_here(ctxt, instance_uuids,
+ instance_properties, instance_type, image, security_groups,
+ block_device_mapping):
call_info['ctxt'] = ctxt
- call_info['request_spec'] = request_spec
+ call_info['instance_uuids'] = instance_uuids
+ call_info['instance_properties'] = instance_properties
+ call_info['instance_type'] = instance_type
+ call_info['image'] = image
+ call_info['security_groups'] = security_groups
+ call_info['block_device_mapping'] = block_device_mapping
def fake_rpc_run_instance(ctxt, **host_sched_kwargs):
call_info['host_sched_kwargs'] = host_sched_kwargs
@@ -281,7 +456,16 @@ class CellsSchedulerTestCase(test.TestCase):
self.my_cell_state, host_sched_kwargs)
# Our cell was selected.
self.assertEqual(self.ctxt, call_info['ctxt'])
- self.assertEqual(self.request_spec, call_info['request_spec'])
+ self.assertEqual(self.instance_uuids, call_info['instance_uuids'])
+ self.assertEqual(self.request_spec['instance_properties'],
+ call_info['instance_properties'])
+ self.assertEqual(self.request_spec['instance_type'],
+ call_info['instance_type'])
+ self.assertEqual(self.request_spec['image'], call_info['image'])
+ self.assertEqual(self.request_spec['security_group'],
+ call_info['security_groups'])
+ self.assertEqual(self.request_spec['block_device_mapping'],
+ call_info['block_device_mapping'])
self.assertEqual(host_sched_kwargs, call_info['host_sched_kwargs'])
# Filter args are correct
expected_filt_props = {'context': self.ctxt,
@@ -341,9 +525,16 @@ class CellsSchedulerTestCase(test.TestCase):
call_info = {}
- def fake_create_instances_here(ctxt, request_spec):
+ def fake_create_instances_here(ctxt, instance_uuids,
+ instance_properties, instance_type, image, security_groups,
+ block_device_mapping):
call_info['ctxt'] = ctxt
- call_info['request_spec'] = request_spec
+ call_info['instance_uuids'] = instance_uuids
+ call_info['instance_properties'] = instance_properties
+ call_info['instance_type'] = instance_type
+ call_info['image'] = image
+ call_info['security_groups'] = security_groups
+ call_info['block_device_mapping'] = block_device_mapping
def fake_rpc_run_instance(ctxt, **host_sched_kwargs):
call_info['host_sched_kwargs'] = host_sched_kwargs
@@ -370,7 +561,16 @@ class CellsSchedulerTestCase(test.TestCase):
self.my_cell_state, host_sched_kwargs)
# Our cell was selected.
self.assertEqual(self.ctxt, call_info['ctxt'])
- self.assertEqual(self.request_spec, call_info['request_spec'])
+ self.assertEqual(self.instance_uuids, call_info['instance_uuids'])
+ self.assertEqual(self.request_spec['instance_properties'],
+ call_info['instance_properties'])
+ self.assertEqual(self.request_spec['instance_type'],
+ call_info['instance_type'])
+ self.assertEqual(self.request_spec['image'], call_info['image'])
+ self.assertEqual(self.request_spec['security_group'],
+ call_info['security_groups'])
+ self.assertEqual(self.request_spec['block_device_mapping'],
+ call_info['block_device_mapping'])
self.assertEqual(host_sched_kwargs, call_info['host_sched_kwargs'])
# Weight args are correct
expected_filt_props = {'context': self.ctxt,
diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py
index 491ecf544..d94ca662e 100644
--- a/nova/tests/compute/test_compute.py
+++ b/nova/tests/compute/test_compute.py
@@ -238,7 +238,6 @@ class BaseTestCase(test.TestCase):
inst['vm_state'] = vm_states.ACTIVE
inst['image_ref'] = FAKE_IMAGE_REF
inst['reservation_id'] = 'r-fakeres'
- inst['launch_time'] = '10'
inst['user_id'] = self.user_id
inst['project_id'] = self.project_id
inst['host'] = 'fake_host'
@@ -1437,7 +1436,7 @@ class ComputeTestCase(BaseTestCase):
orig_sys_metadata=sys_metadata)
self.compute.terminate_instance(self.context, instance=instance)
- def test_rebuild_launch_time(self):
+ def test_rebuild_launched_at_time(self):
# Ensure instance can be rebuilt.
old_time = datetime.datetime(2012, 4, 1)
cur_time = datetime.datetime(2012, 12, 21, 12, 21)
@@ -7960,7 +7959,6 @@ class ComputeAPITestCase(BaseTestCase):
inst['vm_state'] = vm_states.ACTIVE
inst['image_ref'] = FAKE_IMAGE_REF
inst['reservation_id'] = 'r-fakeres'
- inst['launch_time'] = '10'
inst['user_id'] = self.user_id
inst['project_id'] = self.project_id
inst['host'] = 'fake_host'
diff --git a/nova/tests/compute/test_compute_utils.py b/nova/tests/compute/test_compute_utils.py
index 4366bf5ac..84dd67eb5 100644
--- a/nova/tests/compute/test_compute_utils.py
+++ b/nova/tests/compute/test_compute_utils.py
@@ -263,7 +263,6 @@ class UsageInfoTestCase(test.TestCase):
inst = {}
inst['image_ref'] = 1
inst['reservation_id'] = 'r-fakeres'
- inst['launch_time'] = '10'
inst['user_id'] = self.user_id
inst['project_id'] = self.project_id
inst['instance_type_id'] = instance_type['id']
diff --git a/nova/tests/conductor/test_conductor.py b/nova/tests/conductor/test_conductor.py
index 28d0074f2..8b397db02 100644
--- a/nova/tests/conductor/test_conductor.py
+++ b/nova/tests/conductor/test_conductor.py
@@ -76,7 +76,6 @@ class _BaseTestCase(object):
inst['vm_state'] = vm_states.ACTIVE
inst['image_ref'] = FAKE_IMAGE_REF
inst['reservation_id'] = 'r-fakeres'
- inst['launch_time'] = '10'
inst['user_id'] = self.user_id
inst['project_id'] = self.project_id
inst['host'] = 'fake_host'
@@ -1197,6 +1196,41 @@ class _BaseTaskTestCase(object):
self.assertRaises(NotImplementedError, self.conductor.migrate_server,
self.context, None, None, True, False, "dummy", None, None)
+ def test_build_instances(self):
+ instance_type = flavors.get_default_instance_type()
+ system_metadata = flavors.save_instance_type_info({}, instance_type)
+ # NOTE(alaski): instance_type -> system_metadata -> instance_type loses
+ # some data (extra_specs) so we need both for testing.
+ instance_type_extract = flavors.extract_instance_type(
+ {'system_metadata': system_metadata})
+ self.mox.StubOutWithMock(self.conductor_manager.scheduler_rpcapi,
+ 'run_instance')
+ self.conductor_manager.scheduler_rpcapi.run_instance(self.context,
+ request_spec={
+ 'image': {'fake_data': 'should_pass_silently'},
+ 'instance_properties': {'system_metadata': system_metadata,
+ 'uuid': 'fakeuuid'},
+ 'instance_type': instance_type_extract,
+ 'instance_uuids': ['fakeuuid', 'fakeuuid2'],
+ 'block_device_mapping': 'block_device_mapping',
+ 'security_group': 'security_groups'},
+ admin_password='admin_password',
+ injected_files='injected_files',
+ requested_networks='requested_networks', is_first_time=True,
+ filter_properties={})
+ self.mox.ReplayAll()
+ self.conductor.build_instances(self.context,
+ instances=[{'uuid': 'fakeuuid',
+ 'system_metadata': system_metadata},
+ {'uuid': 'fakeuuid2'}],
+ image={'fake_data': 'should_pass_silently'},
+ filter_properties={},
+ admin_password='admin_password',
+ injected_files='injected_files',
+ requested_networks='requested_networks',
+ security_groups='security_groups',
+ block_device_mapping='block_device_mapping')
+
class ConductorTaskTestCase(_BaseTaskTestCase, test.TestCase):
"""ComputeTaskManager Tests."""
diff --git a/nova/tests/console/test_console.py b/nova/tests/console/test_console.py
index e4211f258..76a71936a 100644
--- a/nova/tests/console/test_console.py
+++ b/nova/tests/console/test_console.py
@@ -53,7 +53,6 @@ class ConsoleTestCase(test.TestCase):
#inst['name'] = 'instance-1234'
inst['image_id'] = 1
inst['reservation_id'] = 'r-fakeres'
- inst['launch_time'] = '10'
inst['user_id'] = self.user_id
inst['project_id'] = self.project_id
inst['instance_type_id'] = 1
diff --git a/nova/tests/fake_instance.py b/nova/tests/fake_instance.py
new file mode 100644
index 000000000..b63d16555
--- /dev/null
+++ b/nova/tests/fake_instance.py
@@ -0,0 +1,40 @@
+# 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.
+
+import datetime
+import uuid
+
+from nova.objects import instance as instance_obj
+
+
+def fake_db_instance(**updates):
+ db_instance = {
+ 'id': 1,
+ 'uuid': str(uuid.uuid4()),
+ 'user_id': 'fake-user',
+ 'project_id': 'fake-project',
+ 'host': 'fake-host',
+ 'created_at': datetime.datetime(1955, 11, 5),
+ }
+ for field, typefn in instance_obj.Instance.fields.items():
+ if field in db_instance:
+ continue
+ try:
+ db_instance[field] = typefn(None)
+ except TypeError:
+ db_instance[field] = typefn()
+
+ if updates:
+ db_instance.update(updates)
+ return db_instance
diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py
index 64f91b1a2..0ccbd368b 100644
--- a/nova/tests/fake_policy.py
+++ b/nova/tests/fake_policy.py
@@ -170,6 +170,7 @@ policy_data = """
"compute_extension:security_groups": "",
"compute_extension:server_diagnostics": "",
"compute_extension:server_password": "",
+ "compute_extension:server_usage": "",
"compute_extension:services": "",
"compute_extension:simple_tenant_usage:show": "",
"compute_extension:simple_tenant_usage:list": "",
diff --git a/nova/tests/fake_utils.py b/nova/tests/fake_utils.py
new file mode 100644
index 000000000..cb73bc8bb
--- /dev/null
+++ b/nova/tests/fake_utils.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2013 Rackspace Hosting
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""This modules stubs out functions in nova.utils."""
+
+from nova import utils
+
+
+def stub_out_utils_spawn_n(stubs):
+ """Stubs out spawn_n with a blocking version.
+
+ This aids testing async processes by blocking until they're done.
+ """
+ def no_spawn(func, *args, **kwargs):
+ return func(*args, **kwargs)
+
+ stubs.Set(utils, 'spawn_n', no_spawn)
diff --git a/nova/tests/integrated/api_samples/OS-SRV-USG/server-get-resp.json.tpl b/nova/tests/integrated/api_samples/OS-SRV-USG/server-get-resp.json.tpl
new file mode 100644
index 000000000..e59dbeeef
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-SRV-USG/server-get-resp.json.tpl
@@ -0,0 +1,56 @@
+{
+ "server": {
+ "OS-SRV-USG:launched_at": "%(timestamp)s",
+ "OS-SRV-USG:terminated_at": null,
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "addresses": {
+ "private": [
+ {
+ "addr": "%(ip)s",
+ "version": 4
+ }
+ ]
+ },
+ "created": "%(timestamp)s",
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "%(host)s/openstack/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "hostId": "%(hostid)s",
+ "id": "%(id)s",
+ "image": {
+ "id": "%(uuid)s",
+ "links": [
+ {
+ "href": "%(host)s/openstack/images/%(uuid)s",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "%(host)s/v2/openstack/servers/%(uuid)s",
+ "rel": "self"
+ },
+ {
+ "href": "%(host)s/openstack/servers/%(uuid)s",
+ "rel": "bookmark"
+ }
+ ],
+ "metadata": {
+ "My Server Name": "Apache1"
+ },
+ "name": "new-server-test",
+ "progress": 0,
+ "status": "ACTIVE",
+ "tenant_id": "openstack",
+ "updated": "%(timestamp)s",
+ "user_id": "fake"
+ }
+}
diff --git a/nova/tests/integrated/api_samples/OS-SRV-USG/server-get-resp.xml.tpl b/nova/tests/integrated/api_samples/OS-SRV-USG/server-get-resp.xml.tpl
new file mode 100644
index 000000000..d258b03a4
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-SRV-USG/server-get-resp.xml.tpl
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<server xmlns:OS-SRV-USG="http://docs.openstack.org/compute/ext/server_usage/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="%(timestamp)s" hostId="%(hostid)s" name="new-server-test" created="%(timestamp)s" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="%(id)s" OS-SRV-USG:terminated_at="None" OS-SRV-USG:launched_at="%(timestamp)s">
+ <image id="%(uuid)s">
+ <atom:link href="%(host)s/openstack/images/%(uuid)s" rel="bookmark"/>
+ </image>
+ <flavor id="1">
+ <atom:link href="%(host)s/openstack/flavors/1" rel="bookmark"/>
+ </flavor>
+ <metadata>
+ <meta key="My Server Name">Apache1</meta>
+ </metadata>
+ <addresses>
+ <network id="private">
+ <ip version="4" addr="%(ip)s"/>
+ </network>
+ </addresses>
+ <atom:link href="%(host)s/v2/openstack/servers/%(uuid)s" rel="self"/>
+ <atom:link href="%(host)s/openstack/servers/%(uuid)s" rel="bookmark"/>
+</server>
diff --git a/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-req.json.tpl b/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-req.json.tpl
new file mode 100644
index 000000000..d3916d1aa
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-req.json.tpl
@@ -0,0 +1,16 @@
+{
+ "server" : {
+ "name" : "new-server-test",
+ "imageRef" : "%(host)s/openstack/images/%(image_id)s",
+ "flavorRef" : "%(host)s/openstack/flavors/1",
+ "metadata" : {
+ "My Server Name" : "Apache1"
+ },
+ "personality" : [
+ {
+ "path" : "/etc/banner.txt",
+ "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
+ }
+ ]
+ }
+}
diff --git a/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-req.xml.tpl b/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-req.xml.tpl
new file mode 100644
index 000000000..f92614984
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-req.xml.tpl
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="%(host)s/openstack/images/%(image_id)s" flavorRef="%(host)s/openstack/flavors/1" name="new-server-test">
+ <metadata>
+ <meta key="My Server Name">Apache1</meta>
+ </metadata>
+ <personality>
+ <file path="/etc/banner.txt">
+ ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
+ dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k
+ IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs
+ c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g
+ QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo
+ ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv
+ dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy
+ c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6
+ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==
+ </file>
+ </personality>
+</server>
diff --git a/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-resp.json.tpl b/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-resp.json.tpl
new file mode 100644
index 000000000..d5f030c87
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-resp.json.tpl
@@ -0,0 +1,16 @@
+{
+ "server": {
+ "adminPass": "%(password)s",
+ "id": "%(id)s",
+ "links": [
+ {
+ "href": "%(host)s/v2/openstack/servers/%(uuid)s",
+ "rel": "self"
+ },
+ {
+ "href": "%(host)s/openstack/servers/%(uuid)s",
+ "rel": "bookmark"
+ }
+ ]
+ }
+}
diff --git a/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-resp.xml.tpl b/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-resp.xml.tpl
new file mode 100644
index 000000000..3bb13e69b
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-SRV-USG/server-post-resp.xml.tpl
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="%(id)s" adminPass="%(password)s">
+ <metadata/>
+ <atom:link href="%(host)s/v2/openstack/servers/%(uuid)s" rel="self"/>
+ <atom:link href="%(host)s/openstack/servers/%(uuid)s" rel="bookmark"/>
+</server>
diff --git a/nova/tests/integrated/api_samples/OS-SRV-USG/servers-detail-resp.json.tpl b/nova/tests/integrated/api_samples/OS-SRV-USG/servers-detail-resp.json.tpl
new file mode 100644
index 000000000..cb431c6db
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-SRV-USG/servers-detail-resp.json.tpl
@@ -0,0 +1,57 @@
+{
+ "servers": [
+ {
+ "status": "ACTIVE",
+ "updated": "%(timestamp)s",
+ "OS-SRV-USG:launched_at": "%(timestamp)s",
+ "user_id": "fake",
+ "addresses": {
+ "private": [
+ {
+ "addr": "%(ip)s",
+ "version": 4
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "%(host)s/v2/openstack/servers/%(uuid)s",
+ "rel": "self"
+ },
+ {
+ "href": "%(host)s/openstack/servers/%(uuid)s",
+ "rel": "bookmark"
+ }
+ ],
+ "created": "%(timestamp)s",
+ "name": "new-server-test",
+ "image": {
+ "id": "%(uuid)s",
+ "links": [
+ {
+ "href": "%(host)s/openstack/images/%(uuid)s",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "id": "%(uuid)s",
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "OS-SRV-USG:terminated_at": null,
+ "tenant_id": "openstack",
+ "progress": 0,
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "%(host)s/openstack/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "hostId": "%(hostid)s",
+ "metadata": {
+ "My Server Name": "Apache1"
+ }
+ }]
+}
diff --git a/nova/tests/integrated/api_samples/OS-SRV-USG/servers-detail-resp.xml.tpl b/nova/tests/integrated/api_samples/OS-SRV-USG/servers-detail-resp.xml.tpl
new file mode 100644
index 000000000..e01a50899
--- /dev/null
+++ b/nova/tests/integrated/api_samples/OS-SRV-USG/servers-detail-resp.xml.tpl
@@ -0,0 +1,21 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<servers xmlns:OS-SRV-USG="http://docs.openstack.org/compute/ext/server_usage/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1">
+ <server xmlns:OS-SRV-USG="http://docs.openstack.org/compute/ext/server_usage/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="%(timestamp)s" hostId="%(hostid)s" name="new-server-test" created="%(timestamp)s" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="%(id)s" OS-SRV-USG:launched_at="%(timestamp)s" OS-SRV-USG:terminated_at="None" >
+ <image id="%(uuid)s">
+ <atom:link href="%(host)s/openstack/images/%(uuid)s" rel="bookmark"/>
+ </image>
+ <flavor id="1">
+ <atom:link href="%(host)s/openstack/flavors/1" rel="bookmark"/>
+ </flavor>
+ <metadata>
+ <meta key="My Server Name">Apache1</meta>
+ </metadata>
+ <addresses>
+ <network id="private">
+ <ip version="4" addr="%(ip)s"/>
+ </network>
+ </addresses>
+ <atom:link href="%(host)s/v2/openstack/servers/%(uuid)s" rel="self"/>
+ <atom:link href="%(host)s/openstack/servers/%(uuid)s" rel="bookmark"/>
+ </server>
+</servers>
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 7a636036a..af539c1e3 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-SRV-USG",
+ "description": "%(text)s",
+ "links": [],
+ "name": "ServerUsage",
+ "namespace": "http://docs.openstack.org/compute/ext/server_usage/api/v1.1",
+ "updated": "%(timestamp)s"
+ },
+ {
"alias": "OS-SCH-HNT",
"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 ee01eb33c..205c5bc4d 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-FLV-EXT-DATA" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1" name="FlavorExtraData">
<description>%(text)s</description>
</extension>
+ <extension alias="OS-SRV-USG" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/server_usage/api/v1.1" name="ServerUsage">
+ <description>%(text)s</description>
+ </extension>
<extension alias="OS-SCH-HNT" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/scheduler-hints/api/v2" name="SchedulerHints">
<description>%(text)s</description>
</extension>
diff --git a/nova/tests/integrated/api_samples/all_extensions/server-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/server-get-resp.json.tpl
index 22be331e4..c05cbc557 100644
--- a/nova/tests/integrated/api_samples/all_extensions/server-get-resp.json.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/server-get-resp.json.tpl
@@ -8,6 +8,8 @@
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
+ "OS-SRV-USG:launched_at": "%(timestamp)s",
+ "OS-SRV-USG:terminated_at": null,
"accessIPv4": "",
"accessIPv6": "",
"addresses": {
diff --git a/nova/tests/integrated/api_samples/all_extensions/server-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/server-get-resp.xml.tpl
index 35fe0a6c3..176d6a834 100644
--- a/nova/tests/integrated/api_samples/all_extensions/server-get-resp.xml.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/server-get-resp.xml.tpl
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
-<server xmlns:OS-DCF="http://docs.openstack.org/compute/ext/disk_config/api/v1.1" xmlns:OS-EXT-AZ="http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2" xmlns:OS-EXT-SRV-ATTR="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-IPS="http://docs.openstack.org/compute/ext/extended_ips/api/v1.1" xmlns:OS-EXT-IPS-MAC="http://docs.openstack.org/compute/ext/extended_ips_mac/api/v1.1" xmlns:OS-EXT-STS="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="%(timestamp)s" hostId="%(hostid)s" name="new-server-test" created="%(timestamp)s" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="%(id)s" key_name="None" config_drive="" OS-EXT-SRV-ATTR:vm_state="active" OS-EXT-SRV-ATTR:task_state="None" OS-EXT-SRV-ATTR:power_state="1" OS-EXT-SRV-ATTR:instance_name="instance-00000001" OS-EXT-SRV-ATTR:host="%(compute_host)s" OS-EXT-SRV-ATTR:hypervisor_hostname="%(hypervisor_hostname)s" OS-EXT-AZ:availability_zone="nova" OS-DCF:diskConfig="AUTO">
+<server xmlns:OS-DCF="http://docs.openstack.org/compute/ext/disk_config/api/v1.1" xmlns:OS-EXT-AZ="http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2" xmlns:OS-EXT-SRV-ATTR="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-IPS="http://docs.openstack.org/compute/ext/extended_ips/api/v1.1" xmlns:OS-EXT-IPS-MAC="http://docs.openstack.org/compute/ext/extended_ips_mac/api/v1.1" xmlns:OS-EXT-STS="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-SRV-USG="http://docs.openstack.org/compute/ext/server_usage/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="%(timestamp)s" hostId="%(hostid)s" name="new-server-test" created="%(timestamp)s" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="%(id)s" key_name="None" config_drive="" OS-EXT-SRV-ATTR:vm_state="active" OS-EXT-SRV-ATTR:task_state="None" OS-EXT-SRV-ATTR:power_state="1" OS-EXT-SRV-ATTR:instance_name="instance-00000001" OS-EXT-SRV-ATTR:host="%(compute_host)s" OS-EXT-SRV-ATTR:hypervisor_hostname="%(hypervisor_hostname)s" OS-EXT-AZ:availability_zone="nova" OS-DCF:diskConfig="AUTO" OS-SRV-USG:launched_at="%(timestamp)s" OS-SRV-USG:terminated_at="None">
<image id="%(uuid)s">
<atom:link href="%(host)s/openstack/images/%(uuid)s" rel="bookmark"/>
</image>
diff --git a/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.json.tpl
index 649aa6f9b..f5d261426 100644
--- a/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.json.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.json.tpl
@@ -9,6 +9,8 @@
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
+ "OS-SRV-USG:launched_at": "%(timestamp)s",
+ "OS-SRV-USG:terminated_at": null,
"accessIPv4": "",
"accessIPv6": "",
"addresses": {
diff --git a/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.xml.tpl
index 6ac363176..f7eba82bb 100644
--- a/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.xml.tpl
+++ b/nova/tests/integrated/api_samples/all_extensions/servers-details-resp.xml.tpl
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
-<servers xmlns:OS-DCF="http://docs.openstack.org/compute/ext/disk_config/api/v1.1" xmlns:OS-EXT-AZ="http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2" xmlns:OS-EXT-SRV-ATTR="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-IPS="http://docs.openstack.org/compute/ext/extended_ips/api/v1.1" xmlns:OS-EXT-IPS-MAC="http://docs.openstack.org/compute/ext/extended_ips_mac/api/v1.1" xmlns:OS-EXT-STS="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1">
- <server xmlns:OS-DCF="http://docs.openstack.org/compute/ext/disk_config/api/v1.1" xmlns:OS-EXT-AZ="http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2" xmlns:OS-EXT-SRV-ATTR="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-IPS="http://docs.openstack.org/compute/ext/extended_ips/api/v1.1" xmlns:OS-EXT-STS="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="%(timestamp)s" hostId="%(hostid)s" name="new-server-test" created="%(timestamp)s" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="%(id)s" key_name="None" config_drive="" OS-EXT-SRV-ATTR:vm_state="active" OS-EXT-SRV-ATTR:task_state="None" OS-EXT-SRV-ATTR:power_state="1" OS-EXT-SRV-ATTR:instance_name="instance-00000001" OS-EXT-SRV-ATTR:host="%(compute_host)s" OS-EXT-SRV-ATTR:hypervisor_hostname="%(hypervisor_hostname)s" OS-EXT-AZ:availability_zone="nova" OS-DCF:diskConfig="AUTO">
+<servers xmlns:OS-DCF="http://docs.openstack.org/compute/ext/disk_config/api/v1.1" xmlns:OS-EXT-AZ="http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2" xmlns:OS-EXT-SRV-ATTR="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-IPS="http://docs.openstack.org/compute/ext/extended_ips/api/v1.1" xmlns:OS-EXT-IPS-MAC="http://docs.openstack.org/compute/ext/extended_ips_mac/api/v1.1" xmlns:OS-EXT-STS="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-SRV-USG="http://docs.openstack.org/compute/ext/server_usage/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1">
+ <server xmlns:OS-DCF="http://docs.openstack.org/compute/ext/disk_config/api/v1.1" xmlns:OS-EXT-AZ="http://docs.openstack.org/compute/ext/extended_availability_zone/api/v2" xmlns:OS-EXT-SRV-ATTR="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:OS-EXT-IPS="http://docs.openstack.org/compute/ext/extended_ips/api/v1.1" xmlns:OS-EXT-STS="http://docs.openstack.org/compute/ext/extended_status/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" status="ACTIVE" updated="%(timestamp)s" hostId="%(hostid)s" name="new-server-test" created="%(timestamp)s" userId="fake" tenantId="openstack" accessIPv4="" accessIPv6="" progress="0" id="%(id)s" key_name="None" config_drive="" OS-EXT-SRV-ATTR:vm_state="active" OS-EXT-SRV-ATTR:task_state="None" OS-EXT-SRV-ATTR:power_state="1" OS-EXT-SRV-ATTR:instance_name="instance-00000001" OS-EXT-SRV-ATTR:host="%(compute_host)s" OS-EXT-SRV-ATTR:hypervisor_hostname="%(hypervisor_hostname)s" OS-EXT-AZ:availability_zone="nova" OS-DCF:diskConfig="AUTO" OS-SRV-USG:launched_at="%(timestamp)s" OS-SRV-USG:terminated_at="None">
<image id="%(uuid)s">
<atom:link href="%(host)s/openstack/images/%(uuid)s" rel="bookmark"/>
</image>
diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py
index cfd1877f5..6cd39177e 100644
--- a/nova/tests/integrated/test_api_samples.py
+++ b/nova/tests/integrated/test_api_samples.py
@@ -54,6 +54,7 @@ from nova.tests.api.openstack.compute.contrib import test_services
from nova.tests.api.openstack import fakes
from nova.tests import fake_instance_actions
from nova.tests import fake_network
+from nova.tests import fake_utils
from nova.tests.image import fake
from nova.tests.integrated import integrated_helpers
from nova.tests import utils as test_utils
@@ -95,6 +96,7 @@ class ApiSampleTestBase(integrated_helpers._IntegratedTestBase):
super(ApiSampleTestBase, self).setUp()
self.useFixture(test.SampleNetworks())
fake_network.stub_compute_with_ips(self.stubs)
+ fake_utils.stub_out_utils_spawn_n(self.stubs)
self.generate_samples = os.getenv('GENERATE_SAMPLES') is not None
def _pretty_data(self, data):
@@ -2374,6 +2376,30 @@ class ExtendedStatusSampleXmlTests(ExtendedStatusSampleJsonTests):
ctype = 'xml'
+class ServerUsageSampleJsonTests(ServersSampleBase):
+ extension_name = ("nova.api.openstack.compute.contrib"
+ ".server_usage.Server_usage")
+
+ def test_show(self):
+ uuid = self._post_server()
+ response = self._do_get('servers/%s' % uuid)
+ subs = self._get_regexes()
+ subs['hostid'] = '[a-f0-9]+'
+ return self._verify_response('server-get-resp', subs, response, 200)
+
+ def test_detail(self):
+ self._post_server()
+ response = self._do_get('servers/detail')
+ subs = self._get_regexes()
+ subs['hostid'] = '[a-f0-9]+'
+ return self._verify_response('servers-detail-resp', subs,
+ response, 200)
+
+
+class ServerUsageSampleXmlTests(ServerUsageSampleJsonTests):
+ ctype = 'xml'
+
+
class ExtendedVIFNetSampleJsonTests(ServersSampleBase):
extension_name = ("nova.api.openstack.compute.contrib"
".extended_virtual_interfaces_net.Extended_virtual_interfaces_net")
diff --git a/nova/tests/objects/test_instance.py b/nova/tests/objects/test_instance.py
index 8136a4f1c..498767865 100644
--- a/nova/tests/objects/test_instance.py
+++ b/nova/tests/objects/test_instance.py
@@ -101,7 +101,7 @@ class _TestInstanceObject(object):
['metadata', 'system_metadata']).AndReturn(self.fake_instance)
self.mox.ReplayAll()
inst = instance.Instance.get_by_uuid(
- ctxt, uuid='uuid', expected_attrs=['metadata', 'system_metadata'])
+ ctxt, 'uuid', expected_attrs=['metadata', 'system_metadata'])
self.assertTrue(hasattr(inst, '_metadata'))
self.assertTrue(hasattr(inst, '_system_metadata'))
self.assertRemotes()
@@ -117,7 +117,7 @@ class _TestInstanceObject(object):
db.instance_get_by_uuid(ctxt, fake_uuid, ['system_metadata']
).AndReturn(fake_inst2)
self.mox.ReplayAll()
- inst = instance.Instance.get_by_uuid(ctxt, uuid=fake_uuid)
+ inst = instance.Instance.get_by_uuid(ctxt, fake_uuid)
self.assertFalse(hasattr(inst, '_system_metadata'))
sys_meta = inst.system_metadata
self.assertEqual(sys_meta, {'foo': 'bar'})
@@ -135,7 +135,7 @@ class _TestInstanceObject(object):
db.instance_get_by_uuid(ctxt, 'fake-uuid', []).AndReturn(
fake_instance)
self.mox.ReplayAll()
- inst = instance.Instance.get_by_uuid(ctxt, uuid='fake-uuid')
+ inst = instance.Instance.get_by_uuid(ctxt, 'fake-uuid')
self.assertEqual(inst.id, fake_instance['id'])
self.assertEqual(inst.launched_at, fake_instance['launched_at'])
self.assertEqual(str(inst.access_ip_v4),
@@ -153,7 +153,7 @@ class _TestInstanceObject(object):
db.instance_get_by_uuid(ctxt, fake_uuid, []).AndReturn(
dict(self.fake_instance, host='new-host'))
self.mox.ReplayAll()
- inst = instance.Instance.get_by_uuid(ctxt, uuid=fake_uuid)
+ inst = instance.Instance.get_by_uuid(ctxt, fake_uuid)
self.assertEqual(inst.host, 'orig-host')
inst.refresh()
self.assertEqual(inst.host, 'new-host')
@@ -170,7 +170,7 @@ class _TestInstanceObject(object):
ctxt, fake_uuid, {'user_data': 'foo'}).AndReturn(
(fake_inst, dict(fake_inst, host='newhost')))
self.mox.ReplayAll()
- inst = instance.Instance.get_by_uuid(ctxt, uuid=fake_uuid)
+ inst = instance.Instance.get_by_uuid(ctxt, fake_uuid)
inst.user_data = 'foo'
inst.save()
self.assertEqual(inst.host, 'newhost')
diff --git a/nova/tests/objects/test_objects.py b/nova/tests/objects/test_objects.py
index 754358230..cbaf8cb17 100644
--- a/nova/tests/objects/test_objects.py
+++ b/nova/tests/objects/test_objects.py
@@ -123,6 +123,14 @@ class TestUtils(test.TestCase):
self.assertEqual(utils.datetime_or_none(None), None)
self.assertRaises(ValueError, utils.datetime_or_none, 'foo')
+ def test_datetime_or_str_or_none(self):
+ dts = timeutils.isotime()
+ dt = timeutils.parse_isotime(dts)
+ self.assertEqual(utils.datetime_or_str_or_none(dt), dt)
+ self.assertEqual(utils.datetime_or_str_or_none(None), None)
+ self.assertEqual(utils.datetime_or_str_or_none(dts), dt)
+ self.assertRaises(ValueError, utils.datetime_or_str_or_none, 'foo')
+
def test_int_or_none(self):
self.assertEqual(utils.int_or_none(1), 1)
self.assertEqual(utils.int_or_none('1'), 1)
diff --git a/nova/tests/scheduler/fakes.py b/nova/tests/scheduler/fakes.py
index 024d37969..c9157d11c 100644
--- a/nova/tests/scheduler/fakes.py
+++ b/nova/tests/scheduler/fakes.py
@@ -123,7 +123,6 @@ class FakeInstance(object):
inst['vm_state'] = vm_states.ACTIVE
inst['image_ref'] = 1
inst['reservation_id'] = 'r-fakeres'
- inst['launch_time'] = '10'
inst['user_id'] = 'fake'
inst['project_id'] = 'fake'
type_id = flavors.get_instance_type_by_name(type_name)['id']
diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py
index ca542790c..efe243d1b 100644
--- a/nova/tests/test_db_api.py
+++ b/nova/tests/test_db_api.py
@@ -27,8 +27,10 @@ import uuid as stdlib_uuid
import mox
from oslo.config import cfg
from sqlalchemy.dialects import sqlite
+from sqlalchemy import exc
from sqlalchemy.exc import IntegrityError
from sqlalchemy import MetaData
+from sqlalchemy.orm import query
from sqlalchemy.sql.expression import select
from nova.compute import vm_states
@@ -2782,6 +2784,13 @@ class FixedIPTestCase(BaseInstanceTypeTestCase):
network_id=None,
updated_at=new))
+ def mock_db_query_first_to_raise_data_error_exception(self):
+ self.mox.StubOutWithMock(query.Query, 'first')
+ query.Query.first().AndRaise(exc.DataError(mox.IgnoreArg(),
+ mox.IgnoreArg(),
+ mox.IgnoreArg()))
+ self.mox.ReplayAll()
+
def test_fixed_ip_disassociate_all_by_timeout_single_host(self):
now = timeutils.utcnow()
self._timeout_test(self.ctxt, now, False)
@@ -3118,7 +3127,7 @@ class FixedIPTestCase(BaseInstanceTypeTestCase):
self.assertRaises(exception.FixedIpNotFound,
db.fixed_ip_get, self.ctxt, 0)
- def test_fixed_ip_get_sucsess2(self):
+ def test_fixed_ip_get_success2(self):
adress = 'fixed_ip_adress'
instance_uuid = self._create_instance()
network_id = db.network_create_safe(self.ctxt, {})['id']
@@ -3139,7 +3148,7 @@ class FixedIPTestCase(BaseInstanceTypeTestCase):
self.assertRaises(exception.NotAuthorized, db.fixed_ip_get,
self.ctxt, fixed_ip_id)
- def test_fixed_ip_get_sucsess(self):
+ def test_fixed_ip_get_success(self):
adress = 'fixed_ip_adress'
instance_uuid = self._create_instance()
network_id = db.network_create_safe(self.ctxt, {})['id']
@@ -3165,6 +3174,11 @@ class FixedIPTestCase(BaseInstanceTypeTestCase):
self.assertRaises(exception.FixedIpNotFoundForAddress,
db.fixed_ip_get_by_address_detailed, self.ctxt, 'x')
+ def test_fixed_ip_get_by_address_with_data_error_exception(self):
+ self.mock_db_query_first_to_raise_data_error_exception()
+ self.assertRaises(exception.FixedIpInvalid,
+ db.fixed_ip_get_by_address_detailed, self.ctxt, 'x')
+
def test_fixed_ip_get_by_address_detailed_sucsess(self):
adress = 'fixed_ip_adress_123'
instance_uuid = self._create_instance()
@@ -3246,6 +3260,13 @@ class FloatingIpTestCase(test.TestCase, ModelsObjectComparatorMixin):
'interface': 'fake_interface',
}
+ def mock_db_query_first_to_raise_data_error_exception(self):
+ self.mox.StubOutWithMock(query.Query, 'first')
+ query.Query.first().AndRaise(exc.DataError(mox.IgnoreArg(),
+ mox.IgnoreArg(),
+ mox.IgnoreArg()))
+ self.mox.ReplayAll()
+
def _create_floating_ip(self, values):
if not values:
values = {}
@@ -3266,6 +3287,11 @@ class FloatingIpTestCase(test.TestCase, ModelsObjectComparatorMixin):
self.assertRaises(exception.FloatingIpNotFound,
db.floating_ip_get, self.ctxt, 100500)
+ def test_floating_ip_get_with_long_id_not_found(self):
+ self.mock_db_query_first_to_raise_data_error_exception()
+ self.assertRaises(exception.InvalidID,
+ db.floating_ip_get, self.ctxt, 123456789101112)
+
def test_floating_ip_get_pools(self):
values = [
{'address': '0.0.0.0', 'pool': 'abc'},
@@ -3571,6 +3597,12 @@ class FloatingIpTestCase(test.TestCase, ModelsObjectComparatorMixin):
db.floating_ip_get_by_address,
self.ctxt, 'non_exists_host')
+ def test_floating_ip_get_by_invalid_address(self):
+ self.mock_db_query_first_to_raise_data_error_exception()
+ self.assertRaises(exception.InvalidIpAddressError,
+ db.floating_ip_get_by_address,
+ self.ctxt, 'non_exists_host')
+
def test_floating_ip_get_by_fixed_address(self):
fixed_float = [
('1.1.1.1', '2.2.2.1'),
@@ -4186,6 +4218,13 @@ class VirtualInterfaceTestCase(test.TestCase, ModelsObjectComparatorMixin):
'uuid': str(stdlib_uuid.uuid4())
}
+ def mock_db_query_first_to_raise_data_error_exception(self):
+ self.mox.StubOutWithMock(query.Query, 'first')
+ query.Query.first().AndRaise(exc.DataError(mox.IgnoreArg(),
+ mox.IgnoreArg(),
+ mox.IgnoreArg()))
+ self.mox.ReplayAll()
+
def _create_virt_interface(self, values):
v = self._get_base_values()
v.update(values)
@@ -4222,6 +4261,17 @@ class VirtualInterfaceTestCase(test.TestCase, ModelsObjectComparatorMixin):
vif['address'])
self._assertEqualObjects(vif, real_vif)
+ def test_virtual_interface_get_by_address_not_found(self):
+ self.assertIsNone(db.virtual_interface_get_by_address(self.ctxt,
+ "i.nv.ali.ip"))
+
+ def test_virtual_interface_get_by_address_data_error_exception(self):
+ self.mock_db_query_first_to_raise_data_error_exception()
+ self.assertRaises(exception.InvalidIpAddressError,
+ db.virtual_interface_get_by_address,
+ self.ctxt,
+ "i.nv.ali.ip")
+
def test_virtual_interface_get_by_uuid(self):
vifs = [self._create_virt_interface({}),
self._create_virt_interface({})]
diff --git a/nova/tests/test_linuxscsi.py b/nova/tests/test_linuxscsi.py
index 7f66974d0..0775b9d5b 100644
--- a/nova/tests/test_linuxscsi.py
+++ b/nova/tests/test_linuxscsi.py
@@ -38,7 +38,7 @@ class StorageLinuxSCSITestCase(test.TestCase):
self.stubs.Set(utils, 'execute', fake_execute)
- def test_find_multipath_device(self):
+ def test_find_multipath_device_3par(self):
def fake_execute(*cmd, **kwargs):
out = ("mpath6 (350002ac20398383d) dm-3 3PARdata,VV\n"
"size=2.0G features='0' hwhandler='0' wp=rw\n"
@@ -64,3 +64,62 @@ class StorageLinuxSCSITestCase(test.TestCase):
self.assertEqual("0", info['devices'][1]['id'])
self.assertEqual("0", info['devices'][1]['channel'])
self.assertEqual("1", info['devices'][1]['lun'])
+
+ def test_find_multipath_device_svc(self):
+ def fake_execute(*cmd, **kwargs):
+ out = ("36005076da00638089c000000000004d5 dm-2 IBM,2145\n"
+ "size=954M features='1 queue_if_no_path' hwhandler='0'"
+ " wp=rw\n"
+ "|-+- policy='round-robin 0' prio=-1 status=active\n"
+ "| |- 6:0:2:0 sde 8:64 active undef running\n"
+ "| `- 6:0:4:0 sdg 8:96 active undef running\n"
+ "`-+- policy='round-robin 0' prio=-1 status=enabled\n"
+ " |- 6:0:3:0 sdf 8:80 active undef running\n"
+ " `- 6:0:5:0 sdh 8:112 active undef running\n"
+ )
+ return out, None
+
+ self.stubs.Set(utils, 'execute', fake_execute)
+
+ info = linuxscsi.find_multipath_device('/dev/sde')
+ LOG.error("info = %s" % info)
+ self.assertEqual("/dev/dm-2", info["device"])
+ self.assertEqual("/dev/sde", info['devices'][0]['device'])
+ self.assertEqual("6", info['devices'][0]['host'])
+ self.assertEqual("0", info['devices'][0]['channel'])
+ self.assertEqual("2", info['devices'][0]['id'])
+ self.assertEqual("0", info['devices'][0]['lun'])
+
+ self.assertEqual("/dev/sdf", info['devices'][2]['device'])
+ self.assertEqual("6", info['devices'][2]['host'])
+ self.assertEqual("0", info['devices'][2]['channel'])
+ self.assertEqual("3", info['devices'][2]['id'])
+ self.assertEqual("0", info['devices'][2]['lun'])
+
+ def test_find_multipath_device_ds8000(self):
+ def fake_execute(*cmd, **kwargs):
+ out = ("36005076303ffc48e0000000000000101 dm-2 IBM,2107900\n"
+ "size=1.0G features='1 queue_if_no_path' hwhandler='0'"
+ " wp=rw\n"
+ "`-+- policy='round-robin 0' prio=-1 status=active\n"
+ " |- 6:0:2:0 sdd 8:64 active undef running\n"
+ " `- 6:1:0:3 sdc 8:32 active undef running\n"
+ )
+ return out, None
+
+ self.stubs.Set(utils, 'execute', fake_execute)
+
+ info = linuxscsi.find_multipath_device('/dev/sdd')
+ LOG.error("info = %s" % info)
+ self.assertEqual("/dev/dm-2", info["device"])
+ self.assertEqual("/dev/sdd", info['devices'][0]['device'])
+ self.assertEqual("6", info['devices'][0]['host'])
+ self.assertEqual("0", info['devices'][0]['channel'])
+ self.assertEqual("2", info['devices'][0]['id'])
+ self.assertEqual("0", info['devices'][0]['lun'])
+
+ self.assertEqual("/dev/sdc", info['devices'][1]['device'])
+ self.assertEqual("6", info['devices'][1]['host'])
+ self.assertEqual("1", info['devices'][1]['channel'])
+ self.assertEqual("0", info['devices'][1]['id'])
+ self.assertEqual("3", info['devices'][1]['lun'])
diff --git a/nova/tests/virt/hyperv/db_fakes.py b/nova/tests/virt/hyperv/db_fakes.py
index 5152bd035..77f98d36c 100644
--- a/nova/tests/virt/hyperv/db_fakes.py
+++ b/nova/tests/virt/hyperv/db_fakes.py
@@ -18,7 +18,6 @@
Stubouts, mocks and fixtures for the test suite
"""
-import time
import uuid
from nova.compute import task_states
@@ -145,7 +144,6 @@ def stub_out_db_instance_api(stubs):
'task_state': task_states.SCHEDULING,
'user_id': values['user_id'],
'project_id': values['project_id'],
- 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
'instance_type': instance_type,
'memory_mb': instance_type['memory_mb'],
'vcpus': instance_type['vcpus'],
diff --git a/nova/tests/virt/libvirt/fakelibvirt.py b/nova/tests/virt/libvirt/fakelibvirt.py
index 30c3e4d9c..9594757a8 100644
--- a/nova/tests/virt/libvirt/fakelibvirt.py
+++ b/nova/tests/virt/libvirt/fakelibvirt.py
@@ -914,6 +914,10 @@ def virEventRegisterDefaultImpl():
pass
+def registerErrorHandler(handler, ctxt):
+ pass
+
+
virDomain = Domain
diff --git a/nova/tests/virt/libvirt/test_libvirt.py b/nova/tests/virt/libvirt/test_libvirt.py
index 9eb2a9acb..22defe13f 100644
--- a/nova/tests/virt/libvirt/test_libvirt.py
+++ b/nova/tests/virt/libvirt/test_libvirt.py
@@ -4817,7 +4817,6 @@ class LibvirtDriverTestCase(test.TestCase):
inst = {}
inst['image_ref'] = '1'
inst['reservation_id'] = 'r-fakeres'
- inst['launch_time'] = '10'
inst['user_id'] = 'fake'
inst['project_id'] = 'fake'
type_id = flavors.get_instance_type_by_name('m1.tiny')['id']
diff --git a/nova/tests/virt/vmwareapi/db_fakes.py b/nova/tests/virt/vmwareapi/db_fakes.py
index 54e3cf43b..8f2df43e3 100644
--- a/nova/tests/virt/vmwareapi/db_fakes.py
+++ b/nova/tests/virt/vmwareapi/db_fakes.py
@@ -19,7 +19,6 @@
Stubouts, mocks and fixtures for the test suite
"""
-import time
import uuid
from nova.compute import task_states
@@ -72,7 +71,6 @@ def stub_out_db_instance_api(stubs):
'task_state': task_states.SCHEDULING,
'user_id': values['user_id'],
'project_id': values['project_id'],
- 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
'instance_type': values['instance_type'],
'memory_mb': type_data['memory_mb'],
'vcpus': type_data['vcpus'],
diff --git a/nova/utils.py b/nova/utils.py
index 94c425cc1..f54e72d63 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -37,6 +37,7 @@ import tempfile
import time
from xml.sax import saxutils
+import eventlet
import netaddr
from oslo.config import cfg
@@ -1101,3 +1102,12 @@ def check_string_length(value, name, min_length=0, max_length=None):
msg = _("%(name)s has more than %(max_length)s "
"characters.") % locals()
raise exception.InvalidInput(message=msg)
+
+
+def spawn_n(func, *args, **kwargs):
+ """Passthrough method for eventlet.spawn_n.
+
+ This utility exists so that it can be stubbed for testing without
+ interfering with the service spawns.
+ """
+ eventlet.spawn_n(func, *args, **kwargs)
diff --git a/nova/virt/baremetal/ipmi.py b/nova/virt/baremetal/ipmi.py
index 5d4f2b0ed..73d871e42 100644
--- a/nova/virt/baremetal/ipmi.py
+++ b/nova/virt/baremetal/ipmi.py
@@ -184,7 +184,7 @@ class IPMI(base.PowerManager):
def _set_pxe_for_next_boot(self):
try:
- self._exec_ipmitool("chassis bootdev pxe")
+ self._exec_ipmitool("chassis bootdev pxe options=persistent")
except Exception:
LOG.exception(_("IPMI set next bootdev failed"))
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index becc132de..19613acd4 100755
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -289,6 +289,11 @@ MIN_LIBVIRT_LIVESNAPSHOT_VERSION = (1, 0, 0)
MIN_QEMU_LIVESNAPSHOT_VERSION = (1, 3, 0)
+def libvirt_error_handler(ctxt, err):
+ # Just ignore instead of default outputting to stderr.
+ pass
+
+
class LibvirtDriver(driver.ComputeDriver):
capabilities = {
@@ -543,6 +548,7 @@ class LibvirtDriver(driver.ComputeDriver):
dispatch_thread = eventlet.spawn(self._dispatch_thread)
def init_host(self, host):
+ libvirt.registerErrorHandler(libvirt_error_handler, None)
libvirt.virEventRegisterDefaultImpl()
if not self.has_min_version(MIN_LIBVIRT_VERSION):
diff --git a/setup.cfg b/setup.cfg
index bad19611f..8e6e77186 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -59,6 +59,7 @@ nova.api.v3.extensions =
servers = nova.api.openstack.compute.plugins.v3.servers:Servers
keypairs = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs
ips = nova.api.openstack.compute.plugins.v3.ips:IPs
+ consoles = nova.api.openstack.compute.plugins.v3.consoles:Consoles
nova.api.v3.extensions.server.create =
keypairs_create = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs