summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Kurilin <akurilin@mirantis.com>2016-11-24 19:59:13 +0200
committerAndrey Kurilin <akurilin@mirantis.com>2016-11-30 18:00:05 +0000
commitf834711d2f4a6052a054ffc79918f615e400bdbe (patch)
tree633a1d48267edd975c11ab1ce94694e76012701a
parent43bbe88ac84a5486db7c4140e155f8f52af3118d (diff)
Move all extensions from contrib dir
All extensions from novaclient.v2.contrib should be not be extensions in case of api version >=2.0;<=3.0 (historically, they are turned on by default for cli layer), so let's move it from contrib dir and turn on by default. Change-Id: I4ef4e44cf970947dad33110ce658a133e4f2893e
Notes
Notes (review): Code-Review+2: Kevin L. Mitchell <klmitch@mit.edu> Code-Review+1: Zhenyu Zheng <zhengzhenyu@huawei.com> Code-Review+1: Eric Xie <eric_xiett@163.com> Code-Review+2: Sean Dague <sean@dague.net> Workflow+1: Sean Dague <sean@dague.net> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Fri, 02 Dec 2016 13:56:53 +0000 Reviewed-on: https://review.openstack.org/280726 Project: openstack/python-novaclient Branch: refs/heads/master
-rw-r--r--novaclient/client.py21
-rw-r--r--novaclient/tests/unit/v2/contrib/fakes.py114
-rw-r--r--novaclient/tests/unit/v2/contrib/test_baremetal.py6
-rw-r--r--novaclient/tests/unit/v2/contrib/test_tenant_networks.py6
-rw-r--r--novaclient/tests/unit/v2/fakes.py99
-rw-r--r--novaclient/tests/unit/v2/test_assisted_volume_snapshots.py (renamed from novaclient/tests/unit/v2/contrib/test_assisted_volume_snapshots.py)11
-rw-r--r--novaclient/tests/unit/v2/test_auth.py50
-rw-r--r--novaclient/tests/unit/v2/test_cells.py (renamed from novaclient/tests/unit/v2/contrib/test_cells.py)11
-rw-r--r--novaclient/tests/unit/v2/test_client.py3
-rw-r--r--novaclient/tests/unit/v2/test_instance_actions.py (renamed from novaclient/tests/unit/v2/contrib/test_instance_actions.py)11
-rw-r--r--novaclient/tests/unit/v2/test_list_extensions.py (renamed from novaclient/tests/unit/v2/contrib/test_list_extensions.py)9
-rw-r--r--novaclient/tests/unit/v2/test_migrations.py (renamed from novaclient/tests/unit/v2/contrib/test_migrations.py)13
-rw-r--r--novaclient/tests/unit/v2/test_server_external_events.py (renamed from novaclient/tests/unit/v2/contrib/test_server_external_events.py)11
-rw-r--r--novaclient/tests/unit/v2/test_shell.py7
-rw-r--r--novaclient/v2/assisted_volume_snapshots.py54
-rw-r--r--novaclient/v2/cells.py44
-rw-r--r--novaclient/v2/client.py35
-rw-r--r--novaclient/v2/contrib/__init__.py51
-rw-r--r--novaclient/v2/contrib/assisted_volume_snapshots.py40
-rw-r--r--novaclient/v2/contrib/cells.py60
-rw-r--r--novaclient/v2/contrib/deferred_delete.py14
-rw-r--r--novaclient/v2/contrib/host_evacuate.py71
-rw-r--r--novaclient/v2/contrib/host_evacuate_live.py85
-rw-r--r--novaclient/v2/contrib/host_servers_migrate.py38
-rw-r--r--novaclient/v2/contrib/instance_action.py79
-rw-r--r--novaclient/v2/contrib/list_extensions.py33
-rw-r--r--novaclient/v2/contrib/metadata_extensions.py32
-rw-r--r--novaclient/v2/contrib/migrations.py85
-rw-r--r--novaclient/v2/contrib/server_external_events.py26
-rw-r--r--novaclient/v2/instance_action.py40
-rw-r--r--novaclient/v2/list_extensions.py36
-rw-r--r--novaclient/v2/migrations.py51
-rw-r--r--novaclient/v2/server_external_events.py39
-rw-r--r--novaclient/v2/shell.py357
-rw-r--r--releasenotes/notes/deprecate_contrib_extensions-0ec70c070b09eedb.yaml21
35 files changed, 916 insertions, 747 deletions
diff --git a/novaclient/client.py b/novaclient/client.py
index f9fecaa..79f3634 100644
--- a/novaclient/client.py
+++ b/novaclient/client.py
@@ -22,12 +22,9 @@ OpenStack Client interface. Handles the REST calls and responses.
22 22
23import copy 23import copy
24import functools 24import functools
25import glob
26import hashlib 25import hashlib
27import imp
28import itertools 26import itertools
29import logging 27import logging
30import os
31import pkgutil 28import pkgutil
32import re 29import re
33import warnings 30import warnings
@@ -772,18 +769,14 @@ def _discover_via_python_path():
772 769
773 770
774def _discover_via_contrib_path(version): 771def _discover_via_contrib_path(version):
775 module_path = os.path.dirname(os.path.abspath(__file__)) 772 if version.ver_major == 2:
776 ext_path = os.path.join(module_path, "v%s" % version.ver_major, 'contrib') 773 modules = {"baremetal": "novaclient.v2.contrib.baremetal",
777 ext_glob = os.path.join(ext_path, "*.py") 774 "tenant_networks": "novaclient.v2.contrib.tenant_networks"}
778 775
779 for ext_path in glob.iglob(ext_glob): 776 for name, module_name in modules.items():
780 name = os.path.basename(ext_path)[:-3] 777 module_loader = pkgutil.get_loader(module_name)
781 778 module = module_loader.load_module(module_name)
782 if name in extensions_ignored_name: 779 yield name, module
783 continue
784
785 module = imp.load_source(name, ext_path)
786 yield name, module
787 780
788 781
789def _discover_via_entry_points(): 782def _discover_via_entry_points():
diff --git a/novaclient/tests/unit/v2/contrib/fakes.py b/novaclient/tests/unit/v2/contrib/fakes.py
deleted file mode 100644
index 04bd341..0000000
--- a/novaclient/tests/unit/v2/contrib/fakes.py
+++ /dev/null
@@ -1,114 +0,0 @@
1# Copyright 2012 OpenStack Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from novaclient.tests.unit.v2 import fakes
16from novaclient.v2 import client
17
18FAKE_REQUEST_ID_LIST = fakes.FAKE_REQUEST_ID_LIST
19FAKE_RESPONSE_HEADERS = fakes.FAKE_RESPONSE_HEADERS
20
21
22class FakeClient(fakes.FakeClient):
23 def __init__(self, *args, **kwargs):
24 client.Client.__init__(self, 'username', 'password',
25 'project_id', 'auth_url',
26 extensions=kwargs.get('extensions'),
27 direct_use=False)
28 self.client = FakeHTTPClient(**kwargs)
29
30
31class FakeHTTPClient(fakes.FakeHTTPClient):
32 def get_os_tenant_networks(self):
33 return (200, FAKE_RESPONSE_HEADERS, {
34 'networks': [{"label": "1", "cidr": "10.0.0.0/24",
35 'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
36 'id': '1'}]})
37
38 def get_os_tenant_networks_1(self, **kw):
39 return (200, FAKE_RESPONSE_HEADERS, {
40 'network': {"label": "1", "cidr": "10.0.0.0/24",
41 'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
42 'id': '1'}})
43
44 def post_os_tenant_networks(self, **kw):
45 return (201, FAKE_RESPONSE_HEADERS, {
46 'network': {"label": "1", "cidr": "10.0.0.0/24",
47 'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
48 'id': '1'}})
49
50 def delete_os_tenant_networks_1(self, **kw):
51 return (204, FAKE_RESPONSE_HEADERS, None)
52
53 def get_os_baremetal_nodes(self, **kw):
54 return (
55 200, FAKE_RESPONSE_HEADERS, {
56 'nodes': [
57 {
58 "id": 1,
59 "instance_uuid": None,
60 "interfaces": [],
61 "cpus": 2,
62 "local_gb": 10,
63 "memory_mb": 5,
64 "pm_address": "2.3.4.5",
65 "pm_user": "pmuser",
66 "pm_password": "pmpass",
67 "prov_mac_address": "aa:bb:cc:dd:ee:ff",
68 "prov_vlan_id": 1,
69 "service_host": "somehost",
70 "terminal_port": 8080,
71 }
72 ]
73 }
74 )
75
76 def get_os_baremetal_nodes_1(self, **kw):
77 return (
78 200, FAKE_RESPONSE_HEADERS, {
79 'node': {
80 "id": 1,
81 "instance_uuid": None,
82 "pm_address": "1.2.3.4",
83 "interfaces": [],
84 "cpus": 2,
85 "local_gb": 10,
86 "memory_mb": 5,
87 "pm_user": "pmuser",
88 "pm_password": "pmpass",
89 "prov_mac_address": "aa:bb:cc:dd:ee:ff",
90 "prov_vlan_id": 1,
91 "service_host": "somehost",
92 "terminal_port": 8080,
93 }
94 }
95 )
96
97 def post_os_assisted_volume_snapshots(self, **kw):
98 return (202, FAKE_RESPONSE_HEADERS,
99 {'snapshot': {'id': 'blah', 'volumeId': '1'}})
100
101 def delete_os_assisted_volume_snapshots_x(self, **kw):
102 return (202, FAKE_RESPONSE_HEADERS, {})
103
104 def post_os_server_external_events(self, **kw):
105 return (200, FAKE_RESPONSE_HEADERS, {
106 'events': [
107 {'name': 'test-event',
108 'status': 'completed',
109 'tag': 'tag',
110 'server_uuid': 'fake-uuid1'},
111 {'name': 'test-event',
112 'status': 'completed',
113 'tag': 'tag',
114 'server_uuid': 'fake-uuid2'}]})
diff --git a/novaclient/tests/unit/v2/contrib/test_baremetal.py b/novaclient/tests/unit/v2/contrib/test_baremetal.py
index 0f4326a..5122454 100644
--- a/novaclient/tests/unit/v2/contrib/test_baremetal.py
+++ b/novaclient/tests/unit/v2/contrib/test_baremetal.py
@@ -18,9 +18,10 @@ import warnings
18 18
19import mock 19import mock
20 20
21from novaclient import api_versions
21from novaclient import extension 22from novaclient import extension
22from novaclient.tests.unit import utils 23from novaclient.tests.unit import utils
23from novaclient.tests.unit.v2.contrib import fakes 24from novaclient.tests.unit.v2 import fakes
24from novaclient.v2.contrib import baremetal 25from novaclient.v2.contrib import baremetal
25 26
26 27
@@ -31,7 +32,8 @@ class BaremetalExtensionTest(utils.TestCase):
31 extensions = [ 32 extensions = [
32 extension.Extension(baremetal.__name__.split(".")[-1], baremetal), 33 extension.Extension(baremetal.__name__.split(".")[-1], baremetal),
33 ] 34 ]
34 self.cs = fakes.FakeClient(extensions=extensions) 35 self.cs = fakes.FakeClient(api_versions.APIVersion("2.0"),
36 extensions=extensions)
35 37
36 def test_list_nodes(self, mock_warn): 38 def test_list_nodes(self, mock_warn):
37 nl = self.cs.baremetal.list() 39 nl = self.cs.baremetal.list()
diff --git a/novaclient/tests/unit/v2/contrib/test_tenant_networks.py b/novaclient/tests/unit/v2/contrib/test_tenant_networks.py
index e199427..99e0b27 100644
--- a/novaclient/tests/unit/v2/contrib/test_tenant_networks.py
+++ b/novaclient/tests/unit/v2/contrib/test_tenant_networks.py
@@ -13,9 +13,10 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16from novaclient import api_versions
16from novaclient import extension 17from novaclient import extension
17from novaclient.tests.unit import utils 18from novaclient.tests.unit import utils
18from novaclient.tests.unit.v2.contrib import fakes 19from novaclient.tests.unit.v2 import fakes
19from novaclient.v2.contrib import tenant_networks 20from novaclient.v2.contrib import tenant_networks
20 21
21 22
@@ -27,7 +28,8 @@ class TenantNetworkExtensionTests(utils.TestCase):
27 extension.Extension(tenant_networks.__name__.split(".")[-1], 28 extension.Extension(tenant_networks.__name__.split(".")[-1],
28 tenant_networks), 29 tenant_networks),
29 ] 30 ]
30 self.cs = fakes.FakeClient(extensions=extensions) 31 self.cs = fakes.FakeClient(api_versions.APIVersion("2.0"),
32 extensions=extensions)
31 33
32 def test_list_tenant_networks(self): 34 def test_list_tenant_networks(self):
33 nets = self.cs.tenant_networks.list() 35 nets = self.cs.tenant_networks.list()
diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py
index 39775e2..646cc09 100644
--- a/novaclient/tests/unit/v2/fakes.py
+++ b/novaclient/tests/unit/v2/fakes.py
@@ -60,9 +60,8 @@ class FakeClient(fakes.FakeClient, client.Client):
60 client.Client.__init__(self, 'username', 'password', 60 client.Client.__init__(self, 'username', 'password',
61 'project_id', 'auth_url', 61 'project_id', 'auth_url',
62 extensions=kwargs.get('extensions'), 62 extensions=kwargs.get('extensions'),
63 direct_use=False) 63 direct_use=False, api_version=api_version)
64 kwargs["api_version"] = api_version 64 self.client = FakeHTTPClient(api_version=api_version, **kwargs)
65 self.client = FakeHTTPClient(**kwargs)
66 65
67 66
68class FakeHTTPClient(base_client.HTTPClient): 67class FakeHTTPClient(base_client.HTTPClient):
@@ -2142,11 +2141,6 @@ class FakeHTTPClient(base_client.HTTPClient):
2142 2141
2143 return (200, FAKE_RESPONSE_HEADERS, migrations) 2142 return (200, FAKE_RESPONSE_HEADERS, migrations)
2144 2143
2145 def post_os_server_external_events(self, **kw):
2146 return (200, {}, {'events': [
2147 {'name': 'network-changed',
2148 'server_uuid': '1234'}]})
2149
2150 # 2144 #
2151 # Server Groups 2145 # Server Groups
2152 # 2146 #
@@ -2242,6 +2236,90 @@ class FakeHTTPClient(base_client.HTTPClient):
2242 def delete_servers_1234_tags(self, **kw): 2236 def delete_servers_1234_tags(self, **kw):
2243 return (204, {}, None) 2237 return (204, {}, None)
2244 2238
2239 def get_os_tenant_networks(self):
2240 return (200, FAKE_RESPONSE_HEADERS, {
2241 'networks': [{"label": "1", "cidr": "10.0.0.0/24",
2242 'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
2243 'id': '1'}]})
2244
2245 def get_os_tenant_networks_1(self, **kw):
2246 return (200, FAKE_RESPONSE_HEADERS, {
2247 'network': {"label": "1", "cidr": "10.0.0.0/24",
2248 'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
2249 'id': '1'}})
2250
2251 def post_os_tenant_networks(self, **kw):
2252 return (201, FAKE_RESPONSE_HEADERS, {
2253 'network': {"label": "1", "cidr": "10.0.0.0/24",
2254 'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
2255 'id': '1'}})
2256
2257 def delete_os_tenant_networks_1(self, **kw):
2258 return (204, FAKE_RESPONSE_HEADERS, None)
2259
2260 def get_os_baremetal_nodes(self, **kw):
2261 return (
2262 200, FAKE_RESPONSE_HEADERS, {
2263 'nodes': [
2264 {
2265 "id": 1,
2266 "instance_uuid": None,
2267 "interfaces": [],
2268 "cpus": 2,
2269 "local_gb": 10,
2270 "memory_mb": 5,
2271 "pm_address": "2.3.4.5",
2272 "pm_user": "pmuser",
2273 "pm_password": "pmpass",
2274 "prov_mac_address": "aa:bb:cc:dd:ee:ff",
2275 "prov_vlan_id": 1,
2276 "service_host": "somehost",
2277 "terminal_port": 8080,
2278 }
2279 ]
2280 }
2281 )
2282
2283 def get_os_baremetal_nodes_1(self, **kw):
2284 return (
2285 200, FAKE_RESPONSE_HEADERS, {
2286 'node': {
2287 "id": 1,
2288 "instance_uuid": None,
2289 "pm_address": "1.2.3.4",
2290 "interfaces": [],
2291 "cpus": 2,
2292 "local_gb": 10,
2293 "memory_mb": 5,
2294 "pm_user": "pmuser",
2295 "pm_password": "pmpass",
2296 "prov_mac_address": "aa:bb:cc:dd:ee:ff",
2297 "prov_vlan_id": 1,
2298 "service_host": "somehost",
2299 "terminal_port": 8080,
2300 }
2301 }
2302 )
2303
2304 def post_os_assisted_volume_snapshots(self, **kw):
2305 return (202, FAKE_RESPONSE_HEADERS,
2306 {'snapshot': {'id': 'blah', 'volumeId': '1'}})
2307
2308 def delete_os_assisted_volume_snapshots_x(self, **kw):
2309 return (202, FAKE_RESPONSE_HEADERS, {})
2310
2311 def post_os_server_external_events(self, **kw):
2312 return (200, FAKE_RESPONSE_HEADERS, {
2313 'events': [
2314 {'name': 'test-event',
2315 'status': 'completed',
2316 'tag': 'tag',
2317 'server_uuid': 'fake-uuid1'},
2318 {'name': 'test-event',
2319 'status': 'completed',
2320 'tag': 'tag',
2321 'server_uuid': 'fake-uuid2'}]})
2322
2245 2323
2246class FakeSessionClient(fakes.FakeClient, client.Client): 2324class FakeSessionClient(fakes.FakeClient, client.Client):
2247 2325
@@ -2249,9 +2327,8 @@ class FakeSessionClient(fakes.FakeClient, client.Client):
2249 client.Client.__init__(self, 'username', 'password', 2327 client.Client.__init__(self, 'username', 'password',
2250 'project_id', 'auth_url', 2328 'project_id', 'auth_url',
2251 extensions=kwargs.get('extensions'), 2329 extensions=kwargs.get('extensions'),
2252 direct_use=False) 2330 direct_use=False, api_version=api_version)
2253 kwargs["api_version"] = api_version 2331 self.client = FakeSessionMockClient(api_version=api_version, **kwargs)
2254 self.client = FakeSessionMockClient(**kwargs)
2255 2332
2256 2333
2257class FakeSessionMockClient(base_client.SessionClient, FakeHTTPClient): 2334class FakeSessionMockClient(base_client.SessionClient, FakeHTTPClient):
diff --git a/novaclient/tests/unit/v2/contrib/test_assisted_volume_snapshots.py b/novaclient/tests/unit/v2/test_assisted_volume_snapshots.py
index 374d2d8..8fa4cb5 100644
--- a/novaclient/tests/unit/v2/contrib/test_assisted_volume_snapshots.py
+++ b/novaclient/tests/unit/v2/test_assisted_volume_snapshots.py
@@ -16,20 +16,15 @@
16Assisted volume snapshots - to be used by Cinder and not end users. 16Assisted volume snapshots - to be used by Cinder and not end users.
17""" 17"""
18 18
19from novaclient import extension 19from novaclient import api_versions
20from novaclient.tests.unit import utils 20from novaclient.tests.unit import utils
21from novaclient.tests.unit.v2.contrib import fakes 21from novaclient.tests.unit.v2 import fakes
22from novaclient.v2.contrib import assisted_volume_snapshots as assisted_snaps
23 22
24 23
25class AssistedVolumeSnapshotsTestCase(utils.TestCase): 24class AssistedVolumeSnapshotsTestCase(utils.TestCase):
26 def setUp(self): 25 def setUp(self):
27 super(AssistedVolumeSnapshotsTestCase, self).setUp() 26 super(AssistedVolumeSnapshotsTestCase, self).setUp()
28 extensions = [ 27 self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
29 extension.Extension(assisted_snaps.__name__.split(".")[-1],
30 assisted_snaps),
31 ]
32 self.cs = fakes.FakeClient(extensions=extensions)
33 28
34 def test_create_snap(self): 29 def test_create_snap(self):
35 vs = self.cs.assisted_volume_snapshots.create('1', {}) 30 vs = self.cs.assisted_volume_snapshots.create('1', {})
diff --git a/novaclient/tests/unit/v2/test_auth.py b/novaclient/tests/unit/v2/test_auth.py
index 70d9467..1fdbf3a 100644
--- a/novaclient/tests/unit/v2/test_auth.py
+++ b/novaclient/tests/unit/v2/test_auth.py
@@ -18,9 +18,13 @@ from keystoneauth1 import fixture
18import mock 18import mock
19import requests 19import requests
20 20
21from novaclient import client
21from novaclient import exceptions 22from novaclient import exceptions
22from novaclient.tests.unit import utils 23from novaclient.tests.unit import utils
23from novaclient.v2 import client 24
25
26def Client(*args, **kwargs):
27 return client.Client("2", *args, **kwargs)
24 28
25 29
26class AuthenticateAgainstKeystoneTests(utils.TestCase): 30class AuthenticateAgainstKeystoneTests(utils.TestCase):
@@ -35,9 +39,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
35 return resp 39 return resp
36 40
37 def test_authenticate_success(self): 41 def test_authenticate_success(self):
38 cs = client.Client("username", "password", "project_id", 42 cs = Client("username", "password", "project_id", utils.AUTH_URL_V2,
39 utils.AUTH_URL_V2, service_type='compute', 43 service_type='compute')
40 direct_use=False)
41 resp = self.get_token() 44 resp = self.get_token()
42 45
43 auth_response = utils.TestResponse({ 46 auth_response = utils.TestResponse({
@@ -83,8 +86,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
83 test_auth_call() 86 test_auth_call()
84 87
85 def test_authenticate_failure(self): 88 def test_authenticate_failure(self):
86 cs = client.Client("username", "password", "project_id", 89 cs = Client("username", "password", "project_id", utils.AUTH_URL_V2)
87 utils.AUTH_URL_V2, direct_use=False)
88 resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}} 90 resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
89 auth_response = utils.TestResponse({ 91 auth_response = utils.TestResponse({
90 "status_code": 401, 92 "status_code": 401,
@@ -100,9 +102,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
100 test_auth_call() 102 test_auth_call()
101 103
102 def test_v1_auth_redirect(self): 104 def test_v1_auth_redirect(self):
103 cs = client.Client("username", "password", "project_id", 105 cs = Client("username", "password", "project_id", utils.AUTH_URL_V1,
104 utils.AUTH_URL_V1, service_type='compute', 106 service_type='compute')
105 direct_use=False)
106 dict_correct_response = self.get_token() 107 dict_correct_response = self.get_token()
107 correct_response = json.dumps(dict_correct_response) 108 correct_response = json.dumps(dict_correct_response)
108 dict_responses = [ 109 dict_responses = [
@@ -166,9 +167,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
166 test_auth_call() 167 test_auth_call()
167 168
168 def test_v2_auth_redirect(self): 169 def test_v2_auth_redirect(self):
169 cs = client.Client("username", "password", "project_id", 170 cs = Client("username", "password", "project_id", utils.AUTH_URL_V2,
170 utils.AUTH_URL_V2, service_type='compute', 171 service_type='compute')
171 direct_use=False)
172 dict_correct_response = self.get_token() 172 dict_correct_response = self.get_token()
173 correct_response = json.dumps(dict_correct_response) 173 correct_response = json.dumps(dict_correct_response)
174 dict_responses = [ 174 dict_responses = [
@@ -232,9 +232,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
232 test_auth_call() 232 test_auth_call()
233 233
234 def test_ambiguous_endpoints(self): 234 def test_ambiguous_endpoints(self):
235 cs = client.Client("username", "password", "project_id", 235 cs = Client("username", "password", "project_id", utils.AUTH_URL_V2,
236 utils.AUTH_URL_V2, service_type='compute', 236 service_type='compute')
237 direct_use=False)
238 resp = self.get_token() 237 resp = self.get_token()
239 238
240 # duplicate existing service 239 # duplicate existing service
@@ -256,9 +255,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
256 test_auth_call() 255 test_auth_call()
257 256
258 def test_authenticate_with_token_success(self): 257 def test_authenticate_with_token_success(self):
259 cs = client.Client("username", None, "project_id", 258 cs = Client("username", None, "project_id", utils.AUTH_URL_V2,
260 utils.AUTH_URL_V2, service_type='compute', 259 service_type='compute')
261 direct_use=False)
262 cs.client.auth_token = "FAKE_ID" 260 cs.client.auth_token = "FAKE_ID"
263 resp = self.get_token(token_id="FAKE_ID") 261 resp = self.get_token(token_id="FAKE_ID")
264 auth_response = utils.TestResponse({ 262 auth_response = utils.TestResponse({
@@ -300,8 +298,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
300 self.assertEqual(cs.client.auth_token, token_id) 298 self.assertEqual(cs.client.auth_token, token_id)
301 299
302 def test_authenticate_with_token_failure(self): 300 def test_authenticate_with_token_failure(self):
303 cs = client.Client("username", None, "project_id", utils.AUTH_URL_V2, 301 cs = Client("username", None, "project_id", utils.AUTH_URL_V2)
304 direct_use=False)
305 cs.client.auth_token = "FAKE_ID" 302 cs.client.auth_token = "FAKE_ID"
306 resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}} 303 resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
307 auth_response = utils.TestResponse({ 304 auth_response = utils.TestResponse({
@@ -317,8 +314,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
317 314
318class AuthenticationTests(utils.TestCase): 315class AuthenticationTests(utils.TestCase):
319 def test_authenticate_success(self): 316 def test_authenticate_success(self):
320 cs = client.Client("username", "password", 317 cs = Client("username", "password", "project_id", utils.AUTH_URL)
321 "project_id", utils.AUTH_URL, direct_use=False)
322 management_url = 'https://localhost/v1.1/443470' 318 management_url = 'https://localhost/v1.1/443470'
323 auth_response = utils.TestResponse({ 319 auth_response = utils.TestResponse({
324 'status_code': 204, 320 'status_code': 204,
@@ -353,8 +349,7 @@ class AuthenticationTests(utils.TestCase):
353 test_auth_call() 349 test_auth_call()
354 350
355 def test_authenticate_failure(self): 351 def test_authenticate_failure(self):
356 cs = client.Client("username", "password", 352 cs = Client("username", "password", "project_id", utils.AUTH_URL)
357 "project_id", utils.AUTH_URL, direct_use=False)
358 auth_response = utils.TestResponse({'status_code': 401}) 353 auth_response = utils.TestResponse({'status_code': 401})
359 mock_request = mock.Mock(return_value=(auth_response)) 354 mock_request = mock.Mock(return_value=(auth_response))
360 355
@@ -365,8 +360,8 @@ class AuthenticationTests(utils.TestCase):
365 test_auth_call() 360 test_auth_call()
366 361
367 def test_auth_automatic(self): 362 def test_auth_automatic(self):
368 cs = client.Client("username", "password", 363 cs = Client("username", "password", "project_id", utils.AUTH_URL,
369 "project_id", utils.AUTH_URL, direct_use=False) 364 direct_use=False)
370 http_client = cs.client 365 http_client = cs.client
371 http_client.management_url = '' 366 http_client.management_url = ''
372 http_client.get_service_url = mock.Mock(return_value='') 367 http_client.get_service_url = mock.Mock(return_value='')
@@ -382,8 +377,7 @@ class AuthenticationTests(utils.TestCase):
382 test_auth_call() 377 test_auth_call()
383 378
384 def test_auth_manual(self): 379 def test_auth_manual(self):
385 cs = client.Client("username", "password", 380 cs = Client("username", "password", "project_id", utils.AUTH_URL)
386 "project_id", utils.AUTH_URL, direct_use=False)
387 381
388 @mock.patch.object(cs.client, 'authenticate') 382 @mock.patch.object(cs.client, 'authenticate')
389 def test_auth_call(m): 383 def test_auth_call(m):
diff --git a/novaclient/tests/unit/v2/contrib/test_cells.py b/novaclient/tests/unit/v2/test_cells.py
index 95a5c4b..5a47e1c 100644
--- a/novaclient/tests/unit/v2/contrib/test_cells.py
+++ b/novaclient/tests/unit/v2/test_cells.py
@@ -13,20 +13,15 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16from novaclient import extension 16from novaclient import api_versions
17from novaclient.tests.unit import utils 17from novaclient.tests.unit import utils
18from novaclient.tests.unit.v2.contrib import fakes 18from novaclient.tests.unit.v2 import fakes
19from novaclient.v2.contrib import cells
20 19
21 20
22class CellsExtensionTests(utils.TestCase): 21class CellsExtensionTests(utils.TestCase):
23 def setUp(self): 22 def setUp(self):
24 super(CellsExtensionTests, self).setUp() 23 super(CellsExtensionTests, self).setUp()
25 extensions = [ 24 self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
26 extension.Extension(cells.__name__.split(".")[-1],
27 cells),
28 ]
29 self.cs = fakes.FakeClient(extensions=extensions)
30 25
31 def test_get_cells(self): 26 def test_get_cells(self):
32 cell_name = 'child_cell' 27 cell_name = 'child_cell'
diff --git a/novaclient/tests/unit/v2/test_client.py b/novaclient/tests/unit/v2/test_client.py
index dc92c93..94ba44c 100644
--- a/novaclient/tests/unit/v2/test_client.py
+++ b/novaclient/tests/unit/v2/test_client.py
@@ -14,6 +14,7 @@ import uuid
14 14
15from keystoneauth1 import session 15from keystoneauth1 import session
16 16
17from novaclient import api_versions
17from novaclient.tests.unit import utils 18from novaclient.tests.unit import utils
18from novaclient.v2 import client 19from novaclient.v2 import client
19 20
@@ -27,6 +28,7 @@ class ClientTest(utils.TestCase):
27 28
28 s = session.Session() 29 s = session.Session()
29 c = client.Client(session=s, 30 c = client.Client(session=s,
31 api_version=api_versions.APIVersion("2.0"),
30 user_agent=user_agent, 32 user_agent=user_agent,
31 endpoint_override=endpoint_override, 33 endpoint_override=endpoint_override,
32 direct_use=False) 34 direct_use=False)
@@ -40,6 +42,7 @@ class ClientTest(utils.TestCase):
40 42
41 s = session.Session() 43 s = session.Session()
42 c = client.Client(session=s, 44 c = client.Client(session=s,
45 api_version=api_versions.APIVersion("2.0"),
43 interface=interface, 46 interface=interface,
44 endpoint_type=endpoint_type, 47 endpoint_type=endpoint_type,
45 direct_use=False) 48 direct_use=False)
diff --git a/novaclient/tests/unit/v2/contrib/test_instance_actions.py b/novaclient/tests/unit/v2/test_instance_actions.py
index 394b37e..8eed171 100644
--- a/novaclient/tests/unit/v2/contrib/test_instance_actions.py
+++ b/novaclient/tests/unit/v2/test_instance_actions.py
@@ -13,20 +13,15 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16from novaclient import extension 16from novaclient import api_versions
17from novaclient.tests.unit import utils 17from novaclient.tests.unit import utils
18from novaclient.tests.unit.v2.contrib import fakes 18from novaclient.tests.unit.v2 import fakes
19from novaclient.v2.contrib import instance_action
20 19
21 20
22class InstanceActionExtensionTests(utils.TestCase): 21class InstanceActionExtensionTests(utils.TestCase):
23 def setUp(self): 22 def setUp(self):
24 super(InstanceActionExtensionTests, self).setUp() 23 super(InstanceActionExtensionTests, self).setUp()
25 extensions = [ 24 self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
26 extension.Extension(instance_action.__name__.split(".")[-1],
27 instance_action),
28 ]
29 self.cs = fakes.FakeClient(extensions=extensions)
30 25
31 def test_list_instance_actions(self): 26 def test_list_instance_actions(self):
32 server_uuid = '1234' 27 server_uuid = '1234'
diff --git a/novaclient/tests/unit/v2/contrib/test_list_extensions.py b/novaclient/tests/unit/v2/test_list_extensions.py
index 02fe296..f473ed7 100644
--- a/novaclient/tests/unit/v2/contrib/test_list_extensions.py
+++ b/novaclient/tests/unit/v2/test_list_extensions.py
@@ -12,21 +12,14 @@
12# under the License. 12# under the License.
13 13
14from novaclient import api_versions 14from novaclient import api_versions
15from novaclient import extension
16from novaclient.tests.unit import utils 15from novaclient.tests.unit import utils
17from novaclient.tests.unit.v2 import fakes 16from novaclient.tests.unit.v2 import fakes
18from novaclient.v2.contrib import list_extensions
19 17
20 18
21class ListExtensionsTests(utils.TestCase): 19class ListExtensionsTests(utils.TestCase):
22 def setUp(self): 20 def setUp(self):
23 super(ListExtensionsTests, self).setUp() 21 super(ListExtensionsTests, self).setUp()
24 extensions = [ 22 self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
25 extension.Extension(list_extensions.__name__.split(".")[-1],
26 list_extensions),
27 ]
28 self.cs = fakes.FakeClient(api_versions.APIVersion("2.0"),
29 extensions=extensions)
30 23
31 def test_list_extensions(self): 24 def test_list_extensions(self):
32 all_exts = self.cs.list_extensions.show_all() 25 all_exts = self.cs.list_extensions.show_all()
diff --git a/novaclient/tests/unit/v2/contrib/test_migrations.py b/novaclient/tests/unit/v2/test_migrations.py
index 4cf5d45..b270f9c 100644
--- a/novaclient/tests/unit/v2/contrib/test_migrations.py
+++ b/novaclient/tests/unit/v2/test_migrations.py
@@ -11,21 +11,15 @@
11# under the License. 11# under the License.
12 12
13from novaclient import api_versions 13from novaclient import api_versions
14from novaclient import extension
15from novaclient.tests.unit import utils 14from novaclient.tests.unit import utils
16from novaclient.tests.unit.v2 import fakes 15from novaclient.tests.unit.v2 import fakes
17from novaclient.v2.contrib import migrations 16from novaclient.v2 import migrations
18 17
19 18
20class MigrationsTest(utils.TestCase): 19class MigrationsTest(utils.TestCase):
21 def setUp(self): 20 def setUp(self):
22 super(MigrationsTest, self).setUp() 21 super(MigrationsTest, self).setUp()
23 self.extensions = [ 22 self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
24 extension.Extension(migrations.__name__.split(".")[-1],
25 migrations),
26 ]
27 self.cs = fakes.FakeClient(api_versions.APIVersion("2.0"),
28 extensions=self.extensions)
29 23
30 def test_list_migrations(self): 24 def test_list_migrations(self):
31 ml = self.cs.migrations.list() 25 ml = self.cs.migrations.list()
@@ -36,8 +30,7 @@ class MigrationsTest(utils.TestCase):
36 self.assertRaises(AttributeError, getattr, m, "migration_type") 30 self.assertRaises(AttributeError, getattr, m, "migration_type")
37 31
38 def test_list_migrations_v223(self): 32 def test_list_migrations_v223(self):
39 cs = fakes.FakeClient(extensions=self.extensions, 33 cs = fakes.FakeClient(api_versions.APIVersion("2.23"))
40 api_version=api_versions.APIVersion("2.23"))
41 ml = cs.migrations.list() 34 ml = cs.migrations.list()
42 self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST) 35 self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST)
43 cs.assert_called('GET', '/os-migrations') 36 cs.assert_called('GET', '/os-migrations')
diff --git a/novaclient/tests/unit/v2/contrib/test_server_external_events.py b/novaclient/tests/unit/v2/test_server_external_events.py
index 009a0d8..4b64075 100644
--- a/novaclient/tests/unit/v2/contrib/test_server_external_events.py
+++ b/novaclient/tests/unit/v2/test_server_external_events.py
@@ -16,20 +16,15 @@
16External event triggering for servers, not to be used by users. 16External event triggering for servers, not to be used by users.
17""" 17"""
18 18
19from novaclient import extension 19from novaclient import api_versions
20from novaclient.tests.unit import utils 20from novaclient.tests.unit import utils
21from novaclient.tests.unit.v2.contrib import fakes 21from novaclient.tests.unit.v2 import fakes
22from novaclient.v2.contrib import server_external_events as ext_events
23 22
24 23
25class ServerExternalEventsTestCase(utils.TestCase): 24class ServerExternalEventsTestCase(utils.TestCase):
26 def setUp(self): 25 def setUp(self):
27 super(ServerExternalEventsTestCase, self).setUp() 26 super(ServerExternalEventsTestCase, self).setUp()
28 extensions = [ 27 self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
29 extension.Extension(ext_events.__name__.split(".")[-1],
30 ext_events),
31 ]
32 self.cs = fakes.FakeClient(extensions=extensions)
33 28
34 def test_external_event(self): 29 def test_external_event(self):
35 events = [{'server_uuid': 'fake-uuid1', 30 events = [{'server_uuid': 'fake-uuid1',
diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py
index 9e44944..1b3af35 100644
--- a/novaclient/tests/unit/v2/test_shell.py
+++ b/novaclient/tests/unit/v2/test_shell.py
@@ -77,8 +77,7 @@ class ShellTest(utils.TestCase):
77 self.shell = self.useFixture(ShellFixture()).shell 77 self.shell = self.useFixture(ShellFixture()).shell
78 78
79 self.useFixture(fixtures.MonkeyPatch( 79 self.useFixture(fixtures.MonkeyPatch(
80 'novaclient.client.Client', 80 'novaclient.client.Client', fakes.FakeClient))
81 lambda *args, **kwargs: fakes.FakeClient(*args, **kwargs)))
82 81
83 @mock.patch('sys.stdout', new_callable=six.StringIO) 82 @mock.patch('sys.stdout', new_callable=six.StringIO)
84 @mock.patch('sys.stderr', new_callable=six.StringIO) 83 @mock.patch('sys.stderr', new_callable=six.StringIO)
@@ -3150,9 +3149,9 @@ class ShellWithSessionClientTest(ShellTest):
3150 def setUp(self): 3149 def setUp(self):
3151 """Run before each test.""" 3150 """Run before each test."""
3152 super(ShellWithSessionClientTest, self).setUp() 3151 super(ShellWithSessionClientTest, self).setUp()
3152
3153 self.useFixture(fixtures.MonkeyPatch( 3153 self.useFixture(fixtures.MonkeyPatch(
3154 'novaclient.client.Client', 3154 'novaclient.client.Client', fakes.FakeSessionClient))
3155 lambda *args, **kwargs: fakes.FakeSessionClient(*args, **kwargs)))
3156 3155
3157 3156
3158class GetSecgroupTest(utils.TestCase): 3157class GetSecgroupTest(utils.TestCase):
diff --git a/novaclient/v2/assisted_volume_snapshots.py b/novaclient/v2/assisted_volume_snapshots.py
new file mode 100644
index 0000000..7980789
--- /dev/null
+++ b/novaclient/v2/assisted_volume_snapshots.py
@@ -0,0 +1,54 @@
1# Copyright (C) 2013, Red Hat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15"""
16Assisted volume snapshots - to be used by Cinder and not end users.
17"""
18
19import json
20
21from novaclient import base
22
23
24class Snapshot(base.Resource):
25 def __repr__(self):
26 return "<Snapshot: %s>" % self.id
27
28 def delete(self):
29 """
30 Delete this snapshot.
31
32 :returns: An instance of novaclient.base.TupleWithMeta
33 """
34 return self.manager.delete(self)
35
36
37class AssistedSnapshotManager(base.Manager):
38 resource_class = Snapshot
39
40 def create(self, volume_id, create_info):
41 body = {'snapshot': {'volume_id': volume_id,
42 'create_info': create_info}}
43 return self._create('/os-assisted-volume-snapshots', body, 'snapshot')
44
45 def delete(self, snapshot, delete_info):
46 """
47 Delete a specified assisted volume snapshot.
48
49 :param snapshot: an assisted volume snapshot to delete
50 :param delete_info: Information for snapshot deletion
51 :returns: An instance of novaclient.base.TupleWithMeta
52 """
53 return self._delete("/os-assisted-volume-snapshots/%s?delete_info=%s" %
54 (base.getid(snapshot), json.dumps(delete_info)))
diff --git a/novaclient/v2/cells.py b/novaclient/v2/cells.py
new file mode 100644
index 0000000..e6a1666
--- /dev/null
+++ b/novaclient/v2/cells.py
@@ -0,0 +1,44 @@
1# Copyright 2013 Rackspace Hosting
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from novaclient import base
17
18
19class Cell(base.Resource):
20 def __repr__(self):
21 return "<Cell: %s>" % self.name
22
23
24class CellsManager(base.Manager):
25 resource_class = Cell
26
27 def get(self, cell_name):
28 """
29 Get a cell.
30
31 :param cell_name: Name of the :class:`Cell` to get.
32 :rtype: :class:`Cell`
33 """
34 return self._get("/os-cells/%s" % cell_name, "cell")
35
36 def capacities(self, cell_name=None):
37 """
38 Get capacities for a cell.
39
40 :param cell_name: Name of the :class:`Cell` to get capacities for.
41 :rtype: :class:`Cell`
42 """
43 path = ["%s/capacities" % cell_name, "capacities"][cell_name is None]
44 return self._get("/os-cells/%s" % path, "cell")
diff --git a/novaclient/v2/client.py b/novaclient/v2/client.py
index 93c3f40..961e805 100644
--- a/novaclient/v2/client.py
+++ b/novaclient/v2/client.py
@@ -22,9 +22,12 @@ from novaclient import exceptions
22from novaclient.i18n import _LE 22from novaclient.i18n import _LE
23from novaclient.v2 import agents 23from novaclient.v2 import agents
24from novaclient.v2 import aggregates 24from novaclient.v2 import aggregates
25from novaclient.v2 import assisted_volume_snapshots
25from novaclient.v2 import availability_zones 26from novaclient.v2 import availability_zones
27from novaclient.v2 import cells
26from novaclient.v2 import certs 28from novaclient.v2 import certs
27from novaclient.v2 import cloudpipe 29from novaclient.v2 import cloudpipe
30from novaclient.v2 import contrib
28from novaclient.v2 import fixed_ips 31from novaclient.v2 import fixed_ips
29from novaclient.v2 import flavor_access 32from novaclient.v2 import flavor_access
30from novaclient.v2 import flavors 33from novaclient.v2 import flavors
@@ -36,14 +39,18 @@ from novaclient.v2 import fping
36from novaclient.v2 import hosts 39from novaclient.v2 import hosts
37from novaclient.v2 import hypervisors 40from novaclient.v2 import hypervisors
38from novaclient.v2 import images 41from novaclient.v2 import images
42from novaclient.v2 import instance_action
39from novaclient.v2 import keypairs 43from novaclient.v2 import keypairs
40from novaclient.v2 import limits 44from novaclient.v2 import limits
45from novaclient.v2 import list_extensions
46from novaclient.v2 import migrations
41from novaclient.v2 import networks 47from novaclient.v2 import networks
42from novaclient.v2 import quota_classes 48from novaclient.v2 import quota_classes
43from novaclient.v2 import quotas 49from novaclient.v2 import quotas
44from novaclient.v2 import security_group_default_rules 50from novaclient.v2 import security_group_default_rules
45from novaclient.v2 import security_group_rules 51from novaclient.v2 import security_group_rules
46from novaclient.v2 import security_groups 52from novaclient.v2 import security_groups
53from novaclient.v2 import server_external_events
47from novaclient.v2 import server_groups 54from novaclient.v2 import server_groups
48from novaclient.v2 import server_migrations 55from novaclient.v2 import server_migrations
49from novaclient.v2 import servers 56from novaclient.v2 import servers
@@ -172,16 +179,36 @@ class Client(object):
172 self.server_migrations = \ 179 self.server_migrations = \
173 server_migrations.ServerMigrationsManager(self) 180 server_migrations.ServerMigrationsManager(self)
174 181
182 # V2.0 extensions:
183 # NOTE(andreykurilin): baremetal and tenant_networks extensions are
184 # deprecated now, which is why they are not initialized by default.
185 self.assisted_volume_snapshots = \
186 assisted_volume_snapshots.AssistedSnapshotManager(self)
187 self.cells = cells.CellsManager(self)
188 self.instance_action = instance_action.InstanceActionManager(self)
189 self.list_extensions = list_extensions.ListExtManager(self)
190 self.migrations = migrations.MigrationManager(self)
191 self.server_external_events = \
192 server_external_events.ServerExternalEventManager(self)
193
194 self.logger = logger or logging.getLogger(__name__)
195
175 # Add in any extensions... 196 # Add in any extensions...
176 if extensions: 197 if extensions:
177 for extension in extensions: 198 for extension in extensions:
199 # do not import extensions from contrib directory twice.
200 if extension.name in contrib.V2_0_EXTENSIONS:
201 # NOTE(andreykurilin): this message looks more like
202 # warning or note, but it is not critical, so let's do
203 # not flood "warning" logging level and use just debug..
204 self.logger.debug("Nova 2.0 extenstion '%s' is auto-loaded"
205 " by default. You do not need to specify"
206 " it manually.", extension.name)
207 continue
178 if extension.manager_class: 208 if extension.manager_class:
179 setattr(self, extension.name, 209 setattr(self, extension.name,
180 extension.manager_class(self)) 210 extension.manager_class(self))
181 211
182 if not logger:
183 logger = logging.getLogger(__name__)
184
185 self.client = client._construct_http_client( 212 self.client = client._construct_http_client(
186 username=username, 213 username=username,
187 password=password, 214 password=password,
@@ -208,7 +235,7 @@ class Client(object):
208 session=session, 235 session=session,
209 auth=auth, 236 auth=auth,
210 api_version=api_version, 237 api_version=api_version,
211 logger=logger, 238 logger=self.logger,
212 **kwargs) 239 **kwargs)
213 240
214 @property 241 @property
diff --git a/novaclient/v2/contrib/__init__.py b/novaclient/v2/contrib/__init__.py
index e69de29..b419ae0 100644
--- a/novaclient/v2/contrib/__init__.py
+++ b/novaclient/v2/contrib/__init__.py
@@ -0,0 +1,51 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import inspect
14import warnings
15
16from novaclient.i18n import _LW
17
18# NOTE(andreykurilin): "baremetal" and "tenant_networks" extensions excluded
19# here deliberately. They were deprecated separately from deprecation
20# extension mechanism and I prefer to not auto-load them by default
21# (V2_0_EXTENSIONS is designed for such behaviour).
22V2_0_EXTENSIONS = {
23 'assisted_volume_snapshots':
24 'novaclient.v2.assisted_volume_snapshots',
25 'cells': 'novaclient.v2.cells',
26 'instance_action': 'novaclient.v2.instance_action',
27 'list_extensions': 'novaclient.v2.list_extensions',
28 'migrations': 'novaclient.v2.migrations',
29 'server_external_events': 'novaclient.v2.server_external_events',
30}
31
32
33def warn(alternative=True):
34 """Prints warning msg for contrib modules."""
35 frm = inspect.stack()[1]
36 module_name = inspect.getmodule(frm[0]).__name__
37 if module_name.startswith("novaclient.v2.contrib."):
38 msg = (_LW("Module `%s` is deprecated as of OpenStack Ocata") %
39 module_name)
40 if alternative:
41 new_module_name = module_name.replace("contrib.", "")
42 msg += _LW(" in favor of `%s`") % new_module_name
43
44 msg += (_LW(" and will be removed after OpenStack Pike."))
45
46 if not alternative:
47 msg += _LW(" All shell commands were moved to "
48 "`novaclient.v2.shell` and will be automatically "
49 "loaded.")
50
51 warnings.warn(msg)
diff --git a/novaclient/v2/contrib/assisted_volume_snapshots.py b/novaclient/v2/contrib/assisted_volume_snapshots.py
index a6c6a16..95b5073 100644
--- a/novaclient/v2/contrib/assisted_volume_snapshots.py
+++ b/novaclient/v2/contrib/assisted_volume_snapshots.py
@@ -16,42 +16,14 @@
16Assisted volume snapshots - to be used by Cinder and not end users. 16Assisted volume snapshots - to be used by Cinder and not end users.
17""" 17"""
18 18
19import json 19from novaclient.v2 import assisted_volume_snapshots
20from novaclient.v2 import contrib
20 21
21from novaclient import base
22 22
23 23AssistedSnapshotManager = assisted_volume_snapshots.AssistedSnapshotManager
24class Snapshot(base.Resource): 24Snapshot = assisted_volume_snapshots.Snapshot
25 def __repr__(self):
26 return "<Snapshot: %s>" % self.id
27
28 def delete(self):
29 """
30 Delete this snapshot.
31
32 :returns: An instance of novaclient.base.TupleWithMeta
33 """
34 return self.manager.delete(self)
35
36
37class AssistedSnapshotManager(base.Manager):
38 resource_class = Snapshot
39
40 def create(self, volume_id, create_info):
41 body = {'snapshot': {'volume_id': volume_id,
42 'create_info': create_info}}
43 return self._create('/os-assisted-volume-snapshots', body, 'snapshot')
44
45 def delete(self, snapshot, delete_info):
46 """
47 Delete a specified assisted volume snapshot.
48
49 :param snapshot: an assisted volume snapshot to delete
50 :param delete_info: Information for snapshot deletion
51 :returns: An instance of novaclient.base.TupleWithMeta
52 """
53 return self._delete("/os-assisted-volume-snapshots/%s?delete_info=%s" %
54 (base.getid(snapshot), json.dumps(delete_info)))
55 25
56manager_class = AssistedSnapshotManager 26manager_class = AssistedSnapshotManager
57name = 'assisted_volume_snapshots' 27name = 'assisted_volume_snapshots'
28
29contrib.warn()
diff --git a/novaclient/v2/contrib/cells.py b/novaclient/v2/contrib/cells.py
index e5de8e5..a689d00 100644
--- a/novaclient/v2/contrib/cells.py
+++ b/novaclient/v2/contrib/cells.py
@@ -13,61 +13,11 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16from novaclient import base 16from novaclient.v2 import cells
17from novaclient.i18n import _ 17from novaclient.v2 import contrib
18from novaclient import utils
19 18
20 19
21class Cell(base.Resource): 20Cell = cells.Cell
22 def __repr__(self): 21CellsManager = cells.CellsManager
23 return "<Cell: %s>" % self.name
24 22
25 23contrib.warn()
26class CellsManager(base.Manager):
27 resource_class = Cell
28
29 def get(self, cell_name):
30 """
31 Get a cell.
32
33 :param cell_name: Name of the :class:`Cell` to get.
34 :rtype: :class:`Cell`
35 """
36 return self._get("/os-cells/%s" % cell_name, "cell")
37
38 def capacities(self, cell_name=None):
39 """
40 Get capacities for a cell.
41
42 :param cell_name: Name of the :class:`Cell` to get capacities for.
43 :rtype: :class:`Cell`
44 """
45 path = ["%s/capacities" % cell_name, "capacities"][cell_name is None]
46 return self._get("/os-cells/%s" % path, "cell")
47
48
49@utils.arg(
50 'cell',
51 metavar='<cell-name>',
52 help=_('Name of the cell.'))
53def do_cell_show(cs, args):
54 """Show details of a given cell."""
55 cell = cs.cells.get(args.cell)
56 utils.print_dict(cell._info)
57
58
59@utils.arg(
60 '--cell',
61 metavar='<cell-name>',
62 help=_("Name of the cell to get the capacities."),
63 default=None)
64def do_cell_capacities(cs, args):
65 """Get cell capacities for all cells or a given cell."""
66 cell = cs.cells.capacities(args.cell)
67 print(_("Ram Available: %s MB") % cell.capacities['ram_free']['total_mb'])
68 utils.print_dict(cell.capacities['ram_free']['units_by_mb'],
69 dict_property='Ram(MB)', dict_value="Units")
70 print(_("\nDisk Available: %s MB") %
71 cell.capacities['disk_free']['total_mb'])
72 utils.print_dict(cell.capacities['disk_free']['units_by_mb'],
73 dict_property='Disk(MB)', dict_value="Units")
diff --git a/novaclient/v2/contrib/deferred_delete.py b/novaclient/v2/contrib/deferred_delete.py
index 3d49104..bd5798a 100644
--- a/novaclient/v2/contrib/deferred_delete.py
+++ b/novaclient/v2/contrib/deferred_delete.py
@@ -12,16 +12,6 @@
12# See the License for the specific language governing permissions and 12# See the License for the specific language governing permissions and
13# limitations under the License. 13# limitations under the License.
14 14
15from novaclient import utils 15from novaclient.v2 import contrib
16 16
17 17contrib.warn(alternative=False)
18@utils.arg('server', metavar='<server>', help='Name or ID of server.')
19def do_force_delete(cs, args):
20 """Force delete a server."""
21 utils.find_resource(cs.servers, args.server).force_delete()
22
23
24@utils.arg('server', metavar='<server>', help='Name or ID of server.')
25def do_restore(cs, args):
26 """Restore a soft-deleted server."""
27 utils.find_resource(cs.servers, args.server, deleted=True).restore()
diff --git a/novaclient/v2/contrib/host_evacuate.py b/novaclient/v2/contrib/host_evacuate.py
index 2205565..b5305bc 100644
--- a/novaclient/v2/contrib/host_evacuate.py
+++ b/novaclient/v2/contrib/host_evacuate.py
@@ -13,73 +13,10 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16from novaclient import api_versions 16from novaclient.v2 import contrib
17from novaclient import base 17from novaclient.v2 import shell
18from novaclient.i18n import _
19from novaclient import utils
20 18
21 19
22class EvacuateHostResponse(base.Resource): 20EvacuateHostResponse = shell.EvacuateHostResponse
23 pass
24 21
25 22contrib.warn(alternative=False)
26def _server_evacuate(cs, server, args):
27 success = True
28 error_message = ""
29 try:
30 if api_versions.APIVersion("2.29") <= cs.api_version:
31 # if microversion >= 2.29
32 force = getattr(args, 'force', None)
33 cs.servers.evacuate(server=server['uuid'], host=args.target_host,
34 force=force)
35 elif api_versions.APIVersion("2.14") <= cs.api_version:
36 # if microversion 2.14 - 2.28
37 cs.servers.evacuate(server=server['uuid'], host=args.target_host)
38 else:
39 # else microversion 2.0 - 2.13
40 on_shared_storage = getattr(args, 'on_shared_storage', None)
41 cs.servers.evacuate(server=server['uuid'],
42 host=args.target_host,
43 on_shared_storage=on_shared_storage)
44 except Exception as e:
45 success = False
46 error_message = _("Error while evacuating instance: %s") % e
47 return EvacuateHostResponse(base.Manager,
48 {"server_uuid": server['uuid'],
49 "evacuate_accepted": success,
50 "error_message": error_message})
51
52
53@utils.arg('host', metavar='<host>', help='Name of host.')
54@utils.arg(
55 '--target_host',
56 metavar='<target_host>',
57 default=None,
58 help=_('Name of target host. If no host is specified the scheduler will '
59 'select a target.'))
60@utils.arg(
61 '--on-shared-storage',
62 dest='on_shared_storage',
63 action="store_true",
64 default=False,
65 help=_('Specifies whether all instances files are on shared storage'),
66 start_version='2.0',
67 end_version='2.13')
68@utils.arg(
69 '--force',
70 dest='force',
71 action='store_true',
72 default=False,
73 help=_('Force to not verify the scheduler if a host is provided.'),
74 start_version='2.29')
75def do_host_evacuate(cs, args):
76 """Evacuate all instances from failed host."""
77 hypervisors = cs.hypervisors.search(args.host, servers=True)
78 response = []
79 for hyper in hypervisors:
80 if hasattr(hyper, 'servers'):
81 for server in hyper.servers:
82 response.append(_server_evacuate(cs, server, args))
83
84 utils.print_list(response,
85 ["Server UUID", "Evacuate Accepted", "Error Message"])
diff --git a/novaclient/v2/contrib/host_evacuate_live.py b/novaclient/v2/contrib/host_evacuate_live.py
index f55dda1..7e27d73 100644
--- a/novaclient/v2/contrib/host_evacuate_live.py
+++ b/novaclient/v2/contrib/host_evacuate_live.py
@@ -13,87 +13,6 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16from novaclient.i18n import _ 16from novaclient.v2 import contrib
17from novaclient import utils
18 17
19 18contrib.warn(alternative=False)
20def _server_live_migrate(cs, server, args):
21 class HostEvacuateLiveResponse(object):
22 def __init__(self, server_uuid, live_migration_accepted,
23 error_message):
24 self.server_uuid = server_uuid
25 self.live_migration_accepted = live_migration_accepted
26 self.error_message = error_message
27 success = True
28 error_message = ""
29 update_kwargs = {}
30 try:
31 # API >= 2.30
32 if 'force' in args and args.force:
33 update_kwargs['force'] = args.force
34 # API 2.0->2.24
35 if 'disk_over_commit' in args:
36 update_kwargs['disk_over_commit'] = args.disk_over_commit
37 cs.servers.live_migrate(server['uuid'], args.target_host,
38 args.block_migrate, **update_kwargs)
39 except Exception as e:
40 success = False
41 error_message = _("Error while live migrating instance: %s") % e
42 return HostEvacuateLiveResponse(server['uuid'],
43 success,
44 error_message)
45
46
47@utils.arg('host', metavar='<host>', help='Name of host.')
48@utils.arg(
49 '--target-host',
50 metavar='<target_host>',
51 default=None,
52 help=_('Name of target host.'))
53@utils.arg(
54 '--block-migrate',
55 action='store_true',
56 default=False,
57 help=_('Enable block migration. (Default=False)'),
58 start_version="2.0", end_version="2.24")
59@utils.arg(
60 '--block-migrate',
61 action='store_true',
62 default="auto",
63 help=_('Enable block migration. (Default=auto)'),
64 start_version="2.25")
65@utils.arg(
66 '--disk-over-commit',
67 action='store_true',
68 default=False,
69 help=_('Enable disk overcommit.'),
70 start_version="2.0", end_version="2.24")
71@utils.arg(
72 '--max-servers',
73 type=int,
74 dest='max_servers',
75 metavar='<max_servers>',
76 help='Maximum number of servers to live migrate simultaneously')
77@utils.arg(
78 '--force',
79 dest='force',
80 action='store_true',
81 default=False,
82 help=_('Force to not verify the scheduler if a host is provided.'),
83 start_version='2.30')
84def do_host_evacuate_live(cs, args):
85 """Live migrate all instances of the specified host
86 to other available hosts.
87 """
88 hypervisors = cs.hypervisors.search(args.host, servers=True)
89 response = []
90 migrating = 0
91 for hyper in hypervisors:
92 for server in getattr(hyper, 'servers', []):
93 response.append(_server_live_migrate(cs, server, args))
94 migrating = migrating + 1
95 if args.max_servers is not None and migrating >= args.max_servers:
96 break
97
98 utils.print_list(response, ["Server UUID", "Live Migration Accepted",
99 "Error Message"])
diff --git a/novaclient/v2/contrib/host_servers_migrate.py b/novaclient/v2/contrib/host_servers_migrate.py
index 96e6065..e88da87 100644
--- a/novaclient/v2/contrib/host_servers_migrate.py
+++ b/novaclient/v2/contrib/host_servers_migrate.py
@@ -13,40 +13,10 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16from novaclient import base 16from novaclient.v2 import contrib
17from novaclient.i18n import _ 17from novaclient.v2 import shell
18from novaclient import utils
19 18
20 19
21class HostServersMigrateResponse(base.Resource): 20HostServersMigrateResponse = shell.HostServersMigrateResponse
22 pass
23 21
24 22contrib.warn(alternative=False)
25def _server_migrate(cs, server):
26 success = True
27 error_message = ""
28 try:
29 cs.servers.migrate(server['uuid'])
30 except Exception as e:
31 success = False
32 error_message = _("Error while migrating instance: %s") % e
33 return HostServersMigrateResponse(base.Manager,
34 {"server_uuid": server['uuid'],
35 "migration_accepted": success,
36 "error_message": error_message})
37
38
39@utils.arg('host', metavar='<host>', help='Name of host.')
40def do_host_servers_migrate(cs, args):
41 """Cold migrate all instances off the specified host to other available
42 hosts.
43 """
44 hypervisors = cs.hypervisors.search(args.host, servers=True)
45 response = []
46 for hyper in hypervisors:
47 if hasattr(hyper, 'servers'):
48 for server in hyper.servers:
49 response.append(_server_migrate(cs, server))
50
51 utils.print_list(response,
52 ["Server UUID", "Migration Accepted", "Error Message"])
diff --git a/novaclient/v2/contrib/instance_action.py b/novaclient/v2/contrib/instance_action.py
index 05b847c..a90b99b 100644
--- a/novaclient/v2/contrib/instance_action.py
+++ b/novaclient/v2/contrib/instance_action.py
@@ -13,81 +13,10 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16import pprint 16from novaclient.v2 import contrib
17from novaclient.v2 import instance_action
17 18
18from novaclient import api_versions
19from novaclient import base
20from novaclient.i18n import _
21from novaclient import utils
22from novaclient.v2 import shell
23 19
20InstanceActionManager = instance_action.InstanceActionManager
24 21
25class InstanceActionManager(base.ManagerWithFind): 22contrib.warn()
26 resource_class = base.Resource
27
28 def get(self, server, request_id):
29 """
30 Get details of an action performed on an instance.
31
32 :param request_id: The request_id of the action to get.
33 """
34 return self._get("/servers/%s/os-instance-actions/%s" %
35 (base.getid(server), request_id), 'instanceAction')
36
37 def list(self, server):
38 """
39 Get a list of actions performed on a server.
40 """
41 return self._list('/servers/%s/os-instance-actions' %
42 base.getid(server), 'instanceActions')
43
44
45@utils.arg(
46 'server',
47 metavar='<server>',
48 help=_('Name or UUID of the server to show actions for.'),
49 start_version="2.0", end_version="2.20")
50@utils.arg(
51 'server',
52 metavar='<server>',
53 help=_('Name or UUID of the server to show actions for. Only UUID can be '
54 'used to show actions for a deleted server.'),
55 start_version="2.21")
56@utils.arg(
57 'request_id',
58 metavar='<request_id>',
59 help=_('Request ID of the action to get.'))
60def do_instance_action(cs, args):
61 """Show an action."""
62 if cs.api_version < api_versions.APIVersion("2.21"):
63 server = shell._find_server(cs, args.server)
64 else:
65 server = shell._find_server(cs, args.server, raise_if_notfound=False)
66 action_resource = cs.instance_action.get(server, args.request_id)
67 action = action_resource._info
68 if 'events' in action:
69 action['events'] = pprint.pformat(action['events'])
70 utils.print_dict(action)
71
72
73@utils.arg(
74 'server',
75 metavar='<server>',
76 help=_('Name or UUID of the server to list actions for.'),
77 start_version="2.0", end_version="2.20")
78@utils.arg(
79 'server',
80 metavar='<server>',
81 help=_('Name or UUID of the server to list actions for. Only UUID can be '
82 'used to list actions on a deleted server.'),
83 start_version="2.21")
84def do_instance_action_list(cs, args):
85 """List actions on a server."""
86 if cs.api_version < api_versions.APIVersion("2.21"):
87 server = shell._find_server(cs, args.server)
88 else:
89 server = shell._find_server(cs, args.server, raise_if_notfound=False)
90 actions = cs.instance_action.list(server)
91 utils.print_list(actions,
92 ['Action', 'Request_ID', 'Message', 'Start_Time'],
93 sortby_index=3)
diff --git a/novaclient/v2/contrib/list_extensions.py b/novaclient/v2/contrib/list_extensions.py
index 7eb9f16..9177fa0 100644
--- a/novaclient/v2/contrib/list_extensions.py
+++ b/novaclient/v2/contrib/list_extensions.py
@@ -13,34 +13,11 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16from novaclient import base 16from novaclient.v2 import contrib
17from novaclient import utils 17from novaclient.v2 import list_extensions
18 18
19 19
20class ListExtResource(base.Resource): 20ListExtResource = list_extensions.ListExtResource
21 @property 21ListExtManager = list_extensions.ListExtResource
22 def summary(self):
23 descr = self.description.strip()
24 if not descr:
25 return '??'
26 lines = descr.split("\n")
27 if len(lines) == 1:
28 return lines[0]
29 else:
30 return lines[0] + "..."
31 22
32 23contrib.warn()
33class ListExtManager(base.Manager):
34 resource_class = ListExtResource
35
36 def show_all(self):
37 return self._list("/extensions", 'extensions')
38
39
40def do_list_extensions(client, _args):
41 """
42 List all the os-api extensions that are available.
43 """
44 extensions = client.list_extensions.show_all()
45 fields = ["Name", "Summary", "Alias", "Updated"]
46 utils.print_list(extensions, fields)
diff --git a/novaclient/v2/contrib/metadata_extensions.py b/novaclient/v2/contrib/metadata_extensions.py
index 8d2d22f..eb6fbd2 100644
--- a/novaclient/v2/contrib/metadata_extensions.py
+++ b/novaclient/v2/contrib/metadata_extensions.py
@@ -13,35 +13,7 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16from novaclient.i18n import _ 16from novaclient.v2 import contrib
17from novaclient import utils
18from novaclient.v2 import shell
19 17
20 18
21@utils.arg( 19contrib.warn(alternative=False)
22 'host',
23 metavar='<host>',
24 help=_('Name of host.'))
25@utils.arg(
26 'action',
27 metavar='<action>',
28 choices=['set', 'delete'],
29 help=_("Actions: 'set' or 'delete'"))
30@utils.arg(
31 'metadata',
32 metavar='<key=value>',
33 nargs='+',
34 action='append',
35 default=[],
36 help=_('Metadata to set or delete (only key is necessary on delete)'))
37def do_host_meta(cs, args):
38 """Set or Delete metadata on all instances of a host."""
39 hypervisors = cs.hypervisors.search(args.host, servers=True)
40 for hyper in hypervisors:
41 metadata = shell._extract_metadata(args)
42 if hasattr(hyper, 'servers'):
43 for server in hyper.servers:
44 if args.action == 'set':
45 cs.servers.set_meta(server['uuid'], metadata)
46 elif args.action == 'delete':
47 cs.servers.delete_meta(server['uuid'], metadata.keys())
diff --git a/novaclient/v2/contrib/migrations.py b/novaclient/v2/contrib/migrations.py
index 4785644..909b569 100644
--- a/novaclient/v2/contrib/migrations.py
+++ b/novaclient/v2/contrib/migrations.py
@@ -14,86 +14,11 @@
14migration interface 14migration interface
15""" 15"""
16 16
17from six.moves.urllib import parse 17from novaclient.v2 import contrib
18from novaclient.v2 import migrations
18 19
19from novaclient import api_versions
20from novaclient import base
21from novaclient.i18n import _
22from novaclient import utils
23 20
21Migration = migrations.Migration
22MigrationManager = migrations.MigrationManager
24 23
25class Migration(base.Resource): 24contrib.warn()
26 def __repr__(self):
27 return "<Migration: %s>" % self.id
28
29
30class MigrationManager(base.ManagerWithFind):
31 resource_class = Migration
32
33 def list(self, host=None, status=None, cell_name=None):
34 """
35 Get a list of migrations.
36 :param host: (optional) filter migrations by host name.
37 :param status: (optional) filter migrations by status.
38 :param cell_name: (optional) filter migrations for a cell.
39 """
40 opts = {}
41 if host:
42 opts['host'] = host
43 if status:
44 opts['status'] = status
45 if cell_name:
46 opts['cell_name'] = cell_name
47
48 # Transform the dict to a sequence of two-element tuples in fixed
49 # order, then the encoded string will be consistent in Python 2&3.
50 new_opts = sorted(opts.items(), key=lambda x: x[0])
51
52 query_string = "?%s" % parse.urlencode(new_opts) if new_opts else ""
53
54 return self._list("/os-migrations%s" % query_string, "migrations")
55
56
57@utils.arg(
58 '--host',
59 dest='host',
60 metavar='<host>',
61 help=_('Fetch migrations for the given host.'))
62@utils.arg(
63 '--status',
64 dest='status',
65 metavar='<status>',
66 help=_('Fetch migrations for the given status.'))
67@utils.arg(
68 '--cell_name',
69 dest='cell_name',
70 metavar='<cell_name>',
71 help=_('Fetch migrations for the given cell_name.'))
72def do_migration_list(cs, args):
73 """Print a list of migrations."""
74 migrations = cs.migrations.list(args.host, args.status, args.cell_name)
75 _print_migrations(cs, migrations)
76
77
78def _print_migrations(cs, migrations):
79 fields = ['Source Node', 'Dest Node', 'Source Compute', 'Dest Compute',
80 'Dest Host', 'Status', 'Instance UUID', 'Old Flavor',
81 'New Flavor', 'Created At', 'Updated At']
82
83 def old_flavor(migration):
84 return migration.old_instance_type_id
85
86 def new_flavor(migration):
87 return migration.new_instance_type_id
88
89 def migration_type(migration):
90 return migration.migration_type
91
92 formatters = {'Old Flavor': old_flavor, 'New Flavor': new_flavor}
93
94 if cs.api_version >= api_versions.APIVersion("2.23"):
95 fields.insert(0, "Id")
96 fields.append("Type")
97 formatters.update({"Type": migration_type})
98
99 utils.print_list(migrations, fields, formatters)
diff --git a/novaclient/v2/contrib/server_external_events.py b/novaclient/v2/contrib/server_external_events.py
index a45914b..bbd1b03 100644
--- a/novaclient/v2/contrib/server_external_events.py
+++ b/novaclient/v2/contrib/server_external_events.py
@@ -16,28 +16,14 @@
16External event triggering for servers, not to be used by users. 16External event triggering for servers, not to be used by users.
17""" 17"""
18 18
19from novaclient import base 19from novaclient.v2 import contrib
20from novaclient.v2 import server_external_events
20 21
21 22
22class Event(base.Resource): 23Event = server_external_events.Event
23 def __repr__(self): 24ServerExternalEventManager = server_external_events.ServerExternalEventManager
24 return "<Event: %s>" % self.name
25
26
27class ServerExternalEventManager(base.Manager):
28 resource_class = Event
29
30 def create(self, events):
31 """Create one or more server events.
32
33 :param:events: A list of dictionaries containing 'server_uuid', 'name',
34 'status', and 'tag' (which may be absent)
35 """
36
37 body = {'events': events}
38 return self._create('/os-server-external-events', body, 'events',
39 return_raw=True)
40
41 25
42manager_class = ServerExternalEventManager 26manager_class = ServerExternalEventManager
43name = 'server_external_events' 27name = 'server_external_events'
28
29contrib.warn()
diff --git a/novaclient/v2/instance_action.py b/novaclient/v2/instance_action.py
new file mode 100644
index 0000000..5531d35
--- /dev/null
+++ b/novaclient/v2/instance_action.py
@@ -0,0 +1,40 @@
1# Copyright 2013 Rackspace Hosting
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from novaclient import base
17
18
19class InstanceAction(base.Resource):
20 pass
21
22
23class InstanceActionManager(base.ManagerWithFind):
24 resource_class = InstanceAction
25
26 def get(self, server, request_id):
27 """
28 Get details of an action performed on an instance.
29
30 :param request_id: The request_id of the action to get.
31 """
32 return self._get("/servers/%s/os-instance-actions/%s" %
33 (base.getid(server), request_id), 'instanceAction')
34
35 def list(self, server):
36 """
37 Get a list of actions performed on a server.
38 """
39 return self._list('/servers/%s/os-instance-actions' %
40 base.getid(server), 'instanceActions')
diff --git a/novaclient/v2/list_extensions.py b/novaclient/v2/list_extensions.py
new file mode 100644
index 0000000..faeead6
--- /dev/null
+++ b/novaclient/v2/list_extensions.py
@@ -0,0 +1,36 @@
1# Copyright 2011 OpenStack Foundation
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from novaclient import base
17
18
19class ListExtResource(base.Resource):
20 @property
21 def summary(self):
22 descr = self.description.strip()
23 if not descr:
24 return '??'
25 lines = descr.split("\n")
26 if len(lines) == 1:
27 return lines[0]
28 else:
29 return lines[0] + "..."
30
31
32class ListExtManager(base.Manager):
33 resource_class = ListExtResource
34
35 def show_all(self):
36 return self._list("/extensions", 'extensions')
diff --git a/novaclient/v2/migrations.py b/novaclient/v2/migrations.py
new file mode 100644
index 0000000..51b5988
--- /dev/null
+++ b/novaclient/v2/migrations.py
@@ -0,0 +1,51 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13"""
14migration interface
15"""
16
17from six.moves.urllib import parse
18
19from novaclient import base
20
21
22class Migration(base.Resource):
23 def __repr__(self):
24 return "<Migration: %s>" % self.id
25
26
27class MigrationManager(base.ManagerWithFind):
28 resource_class = Migration
29
30 def list(self, host=None, status=None, cell_name=None):
31 """
32 Get a list of migrations.
33 :param host: (optional) filter migrations by host name.
34 :param status: (optional) filter migrations by status.
35 :param cell_name: (optional) filter migrations for a cell.
36 """
37 opts = {}
38 if host:
39 opts['host'] = host
40 if status:
41 opts['status'] = status
42 if cell_name:
43 opts['cell_name'] = cell_name
44
45 # Transform the dict to a sequence of two-element tuples in fixed
46 # order, then the encoded string will be consistent in Python 2&3.
47 new_opts = sorted(opts.items(), key=lambda x: x[0])
48
49 query_string = "?%s" % parse.urlencode(new_opts) if new_opts else ""
50
51 return self._list("/os-migrations%s" % query_string, "migrations")
diff --git a/novaclient/v2/server_external_events.py b/novaclient/v2/server_external_events.py
new file mode 100644
index 0000000..7c2506a
--- /dev/null
+++ b/novaclient/v2/server_external_events.py
@@ -0,0 +1,39 @@
1# Copyright (C) 2014, Red Hat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15"""
16External event triggering for servers, not to be used by users.
17"""
18
19from novaclient import base
20
21
22class Event(base.Resource):
23 def __repr__(self):
24 return "<Event: %s>" % self.name
25
26
27class ServerExternalEventManager(base.Manager):
28 resource_class = Event
29
30 def create(self, events):
31 """Create one or more server events.
32
33 :param:events: A list of dictionaries containing 'server_uuid', 'name',
34 'status', and 'tag' (which may be absent)
35 """
36
37 body = {'events': events}
38 return self._create('/os-server-external-events', body, 'events',
39 return_raw=True)
diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py
index db72b6b..b621025 100644
--- a/novaclient/v2/shell.py
+++ b/novaclient/v2/shell.py
@@ -26,6 +26,7 @@ import getpass
26import locale 26import locale
27import logging 27import logging
28import os 28import os
29import pprint
29import sys 30import sys
30import time 31import time
31 32
@@ -5185,3 +5186,359 @@ def do_server_tag_delete_all(cs, args):
5185 """Delete all tags from a server.""" 5186 """Delete all tags from a server."""
5186 server = _find_server(cs, args.server) 5187 server = _find_server(cs, args.server)
5187 server.delete_all_tags() 5188 server.delete_all_tags()
5189
5190
5191@utils.arg(
5192 'cell',
5193 metavar='<cell-name>',
5194 help=_('Name of the cell.'))
5195def do_cell_show(cs, args):
5196 """Show details of a given cell."""
5197 cell = cs.cells.get(args.cell)
5198 utils.print_dict(cell._info)
5199
5200
5201@utils.arg(
5202 '--cell',
5203 metavar='<cell-name>',
5204 help=_("Name of the cell to get the capacities."),
5205 default=None)
5206def do_cell_capacities(cs, args):
5207 """Get cell capacities for all cells or a given cell."""
5208 cell = cs.cells.capacities(args.cell)
5209 print(_("Ram Available: %s MB") % cell.capacities['ram_free']['total_mb'])
5210 utils.print_dict(cell.capacities['ram_free']['units_by_mb'],
5211 dict_property='Ram(MB)', dict_value="Units")
5212 print(_("\nDisk Available: %s MB") %
5213 cell.capacities['disk_free']['total_mb'])
5214 utils.print_dict(cell.capacities['disk_free']['units_by_mb'],
5215 dict_property='Disk(MB)', dict_value="Units")
5216
5217
5218@utils.arg('server', metavar='<server>', help='Name or ID of server.')
5219def do_force_delete(cs, args):
5220 """Force delete a server."""
5221 utils.find_resource(cs.servers, args.server).force_delete()
5222
5223
5224@utils.arg('server', metavar='<server>', help='Name or ID of server.')
5225def do_restore(cs, args):
5226 """Restore a soft-deleted server."""
5227 utils.find_resource(cs.servers, args.server, deleted=True).restore()
5228
5229
5230class EvacuateHostResponse(base.Resource):
5231 pass
5232
5233
5234def _server_evacuate(cs, server, args):
5235 success = True
5236 error_message = ""
5237 try:
5238 if api_versions.APIVersion("2.29") <= cs.api_version:
5239 # if microversion >= 2.29
5240 force = getattr(args, 'force', None)
5241 cs.servers.evacuate(server=server['uuid'], host=args.target_host,
5242 force=force)
5243 elif api_versions.APIVersion("2.14") <= cs.api_version:
5244 # if microversion 2.14 - 2.28
5245 cs.servers.evacuate(server=server['uuid'], host=args.target_host)
5246 else:
5247 # else microversion 2.0 - 2.13
5248 on_shared_storage = getattr(args, 'on_shared_storage', None)
5249 cs.servers.evacuate(server=server['uuid'],
5250 host=args.target_host,
5251 on_shared_storage=on_shared_storage)
5252 except Exception as e:
5253 success = False
5254 error_message = _("Error while evacuating instance: %s") % e
5255 return EvacuateHostResponse(base.Manager, {"server_uuid": server['uuid'],
5256 "evacuate_accepted": success,
5257 "error_message": error_message})
5258
5259
5260@utils.arg('host', metavar='<host>', help='Name of host.')
5261@utils.arg(
5262 '--target_host',
5263 metavar='<target_host>',
5264 default=None,
5265 help=_('Name of target host. If no host is specified the scheduler will '
5266 'select a target.'))
5267@utils.arg(
5268 '--on-shared-storage',
5269 dest='on_shared_storage',
5270 action="store_true",
5271 default=False,
5272 help=_('Specifies whether all instances files are on shared storage'),
5273 start_version='2.0',
5274 end_version='2.13')
5275@utils.arg(
5276 '--force',
5277 dest='force',
5278 action='store_true',
5279 default=False,
5280 help=_('Force to not verify the scheduler if a host is provided.'),
5281 start_version='2.29')
5282def do_host_evacuate(cs, args):
5283 """Evacuate all instances from failed host."""
5284
5285 hypervisors = cs.hypervisors.search(args.host, servers=True)
5286 response = []
5287 for hyper in hypervisors:
5288 if hasattr(hyper, 'servers'):
5289 for server in hyper.servers:
5290 response.append(_server_evacuate(cs, server, args))
5291
5292 utils.print_list(response,
5293 ["Server UUID", "Evacuate Accepted", "Error Message"])
5294
5295
5296def _server_live_migrate(cs, server, args):
5297 class HostEvacuateLiveResponse(object):
5298 def __init__(self, server_uuid, live_migration_accepted,
5299 error_message):
5300 self.server_uuid = server_uuid
5301 self.live_migration_accepted = live_migration_accepted
5302 self.error_message = error_message
5303 success = True
5304 error_message = ""
5305 update_kwargs = {}
5306 try:
5307 # API >= 2.30
5308 if 'force' in args and args.force:
5309 update_kwargs['force'] = args.force
5310 # API 2.0->2.24
5311 if 'disk_over_commit' in args:
5312 update_kwargs['disk_over_commit'] = args.disk_over_commit
5313 cs.servers.live_migrate(server['uuid'], args.target_host,
5314 args.block_migrate, **update_kwargs)
5315 except Exception as e:
5316 success = False
5317 error_message = _("Error while live migrating instance: %s") % e
5318 return HostEvacuateLiveResponse(server['uuid'],
5319 success,
5320 error_message)
5321
5322
5323@utils.arg('host', metavar='<host>', help='Name of host.')
5324@utils.arg(
5325 '--target-host',
5326 metavar='<target_host>',
5327 default=None,
5328 help=_('Name of target host.'))
5329@utils.arg(
5330 '--block-migrate',
5331 action='store_true',
5332 default=False,
5333 help=_('Enable block migration. (Default=False)'),
5334 start_version="2.0", end_version="2.24")
5335@utils.arg(
5336 '--block-migrate',
5337 action='store_true',
5338 default="auto",
5339 help=_('Enable block migration. (Default=auto)'),
5340 start_version="2.25")
5341@utils.arg(
5342 '--disk-over-commit',
5343 action='store_true',
5344 default=False,
5345 help=_('Enable disk overcommit.'),
5346 start_version="2.0", end_version="2.24")
5347@utils.arg(
5348 '--max-servers',
5349 type=int,
5350 dest='max_servers',
5351 metavar='<max_servers>',
5352 help='Maximum number of servers to live migrate simultaneously')
5353@utils.arg(
5354 '--force',
5355 dest='force',
5356 action='store_true',
5357 default=False,
5358 help=_('Force to not verify the scheduler if a host is provided.'),
5359 start_version='2.30')
5360def do_host_evacuate_live(cs, args):
5361 """Live migrate all instances of the specified host
5362 to other available hosts.
5363 """
5364 hypervisors = cs.hypervisors.search(args.host, servers=True)
5365 response = []
5366 migrating = 0
5367 for hyper in hypervisors:
5368 for server in getattr(hyper, 'servers', []):
5369 response.append(_server_live_migrate(cs, server, args))
5370 migrating += 1
5371 if args.max_servers is not None and migrating >= args.max_servers:
5372 break
5373
5374 utils.print_list(response, ["Server UUID", "Live Migration Accepted",
5375 "Error Message"])
5376
5377
5378class HostServersMigrateResponse(base.Resource):
5379 pass
5380
5381
5382def _server_migrate(cs, server):
5383 success = True
5384 error_message = ""
5385 try:
5386 cs.servers.migrate(server['uuid'])
5387 except Exception as e:
5388 success = False
5389 error_message = _("Error while migrating instance: %s") % e
5390 return HostServersMigrateResponse(base.Manager,
5391 {"server_uuid": server['uuid'],
5392 "migration_accepted": success,
5393 "error_message": error_message})
5394
5395
5396@utils.arg('host', metavar='<host>', help='Name of host.')
5397def do_host_servers_migrate(cs, args):
5398 """Cold migrate all instances off the specified host to other available
5399 hosts.
5400 """
5401
5402 hypervisors = cs.hypervisors.search(args.host, servers=True)
5403 response = []
5404 for hyper in hypervisors:
5405 if hasattr(hyper, 'servers'):
5406 for server in hyper.servers:
5407 response.append(_server_migrate(cs, server))
5408
5409 utils.print_list(response,
5410 ["Server UUID", "Migration Accepted", "Error Message"])
5411
5412
5413@utils.arg(
5414 'server',
5415 metavar='<server>',
5416 help=_('Name or UUID of the server to show actions for.'),
5417 start_version="2.0", end_version="2.20")
5418@utils.arg(
5419 'server',
5420 metavar='<server>',
5421 help=_('Name or UUID of the server to show actions for. Only UUID can be '
5422 'used to show actions for a deleted server.'),
5423 start_version="2.21")
5424@utils.arg(
5425 'request_id',
5426 metavar='<request_id>',
5427 help=_('Request ID of the action to get.'))
5428def do_instance_action(cs, args):
5429 """Show an action."""
5430 if cs.api_version < api_versions.APIVersion("2.21"):
5431 server = _find_server(cs, args.server)
5432 else:
5433 server = _find_server(cs, args.server, raise_if_notfound=False)
5434 action_resource = cs.instance_action.get(server, args.request_id)
5435 action = action_resource._info
5436 if 'events' in action:
5437 action['events'] = pprint.pformat(action['events'])
5438 utils.print_dict(action)
5439
5440
5441@utils.arg(
5442 'server',
5443 metavar='<server>',
5444 help=_('Name or UUID of the server to list actions for.'),
5445 start_version="2.0", end_version="2.20")
5446@utils.arg(
5447 'server',
5448 metavar='<server>',
5449 help=_('Name or UUID of the server to list actions for. Only UUID can be '
5450 'used to list actions on a deleted server.'),
5451 start_version="2.21")
5452def do_instance_action_list(cs, args):
5453 """List actions on a server."""
5454 if cs.api_version < api_versions.APIVersion("2.21"):
5455 server = _find_server(cs, args.server)
5456 else:
5457 server = _find_server(cs, args.server, raise_if_notfound=False)
5458 actions = cs.instance_action.list(server)
5459 utils.print_list(actions,
5460 ['Action', 'Request_ID', 'Message', 'Start_Time'],
5461 sortby_index=3)
5462
5463
5464def do_list_extensions(cs, _args):
5465 """
5466 List all the os-api extensions that are available.
5467 """
5468 extensions = cs.list_extensions.show_all()
5469 fields = ["Name", "Summary", "Alias", "Updated"]
5470 utils.print_list(extensions, fields)
5471
5472
5473@utils.arg(
5474 'host',
5475 metavar='<host>',
5476 help=_('Name of host.'))
5477@utils.arg(
5478 'action',
5479 metavar='<action>',
5480 choices=['set', 'delete'],
5481 help=_("Actions: 'set' or 'delete'"))
5482@utils.arg(
5483 'metadata',
5484 metavar='<key=value>',
5485 nargs='+',
5486 action='append',
5487 default=[],
5488 help=_('Metadata to set or delete (only key is necessary on delete)'))
5489def do_host_meta(cs, args):
5490 """Set or Delete metadata on all instances of a host."""
5491 hypervisors = cs.hypervisors.search(args.host, servers=True)
5492 for hyper in hypervisors:
5493 metadata = _extract_metadata(args)
5494 if hasattr(hyper, 'servers'):
5495 for server in hyper.servers:
5496 if args.action == 'set':
5497 cs.servers.set_meta(server['uuid'], metadata)
5498 elif args.action == 'delete':
5499 cs.servers.delete_meta(server['uuid'], metadata.keys())
5500
5501
5502def _print_migrations(cs, migrations):
5503 fields = ['Source Node', 'Dest Node', 'Source Compute', 'Dest Compute',
5504 'Dest Host', 'Status', 'Instance UUID', 'Old Flavor',
5505 'New Flavor', 'Created At', 'Updated At']
5506
5507 def old_flavor(migration):
5508 return migration.old_instance_type_id
5509
5510 def new_flavor(migration):
5511 return migration.new_instance_type_id
5512
5513 def migration_type(migration):
5514 return migration.migration_type
5515
5516 formatters = {'Old Flavor': old_flavor, 'New Flavor': new_flavor}
5517
5518 if cs.api_version >= api_versions.APIVersion("2.23"):
5519 fields.insert(0, "Id")
5520 fields.append("Type")
5521 formatters.update({"Type": migration_type})
5522
5523 utils.print_list(migrations, fields, formatters)
5524
5525
5526@utils.arg(
5527 '--host',
5528 dest='host',
5529 metavar='<host>',
5530 help=_('Fetch migrations for the given host.'))
5531@utils.arg(
5532 '--status',
5533 dest='status',
5534 metavar='<status>',
5535 help=_('Fetch migrations for the given status.'))
5536@utils.arg(
5537 '--cell_name',
5538 dest='cell_name',
5539 metavar='<cell_name>',
5540 help=_('Fetch migrations for the given cell_name.'))
5541def do_migration_list(cs, args):
5542 """Print a list of migrations."""
5543 migrations = cs.migrations.list(args.host, args.status, args.cell_name)
5544 _print_migrations(cs, migrations)
diff --git a/releasenotes/notes/deprecate_contrib_extensions-0ec70c070b09eedb.yaml b/releasenotes/notes/deprecate_contrib_extensions-0ec70c070b09eedb.yaml
new file mode 100644
index 0000000..6681075
--- /dev/null
+++ b/releasenotes/notes/deprecate_contrib_extensions-0ec70c070b09eedb.yaml
@@ -0,0 +1,21 @@
1---
2prelude: >
3 All extensions of API V2.0 were merged to 2.1, but NovaClient continued
4 to store them as a separate entities.
5upgrade:
6 - All managers and resources from novaclient.v2.contrib submodules are moved
7 to appropriate submodules of novaclient.v2 (except barametal and
8 tenant_networks, which were deprecated previously)
9 - All shell commands from novaclient.v2.contrib submodules are moved to
10 novaclient.v2.shell module.
11 - novaclient.v2.client.Client imports all modules (which were located in
12 submodules of novaclient.v2.contrib) by-default for api version v2
13 - Method novaclient.client.discover_extensions returns only barametal and
14 tenant_networks extensions, since they are not included by default.
15 - There are no modules and extensions for "deferred_delete", "host_evacuate",
16 "host_evacuate_live" and "metadata_extensions" anymore. Previously, they
17 contained only shell commands and shell module auto loads them (there is
18 no ability to not do it).
19deprecations:
20 - All modules of novaclient.v2.contrib are deprecated now and will be
21 removed after OpenStack Pike.