diff options
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): @@ -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 |