Browse Source

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
tags/7.0.0
Andrey Kurilin 2 years ago
parent
commit
f834711d2f
35 changed files with 916 additions and 747 deletions
  1. 7
    14
      novaclient/client.py
  2. 0
    114
      novaclient/tests/unit/v2/contrib/fakes.py
  3. 4
    2
      novaclient/tests/unit/v2/contrib/test_baremetal.py
  4. 4
    2
      novaclient/tests/unit/v2/contrib/test_tenant_networks.py
  5. 88
    11
      novaclient/tests/unit/v2/fakes.py
  6. 3
    8
      novaclient/tests/unit/v2/test_assisted_volume_snapshots.py
  7. 22
    28
      novaclient/tests/unit/v2/test_auth.py
  8. 3
    8
      novaclient/tests/unit/v2/test_cells.py
  9. 3
    0
      novaclient/tests/unit/v2/test_client.py
  10. 3
    8
      novaclient/tests/unit/v2/test_instance_actions.py
  11. 1
    8
      novaclient/tests/unit/v2/test_list_extensions.py
  12. 3
    10
      novaclient/tests/unit/v2/test_migrations.py
  13. 3
    8
      novaclient/tests/unit/v2/test_server_external_events.py
  14. 3
    4
      novaclient/tests/unit/v2/test_shell.py
  15. 54
    0
      novaclient/v2/assisted_volume_snapshots.py
  16. 44
    0
      novaclient/v2/cells.py
  17. 31
    4
      novaclient/v2/client.py
  18. 51
    0
      novaclient/v2/contrib/__init__.py
  19. 6
    34
      novaclient/v2/contrib/assisted_volume_snapshots.py
  20. 5
    55
      novaclient/v2/contrib/cells.py
  21. 2
    12
      novaclient/v2/contrib/deferred_delete.py
  22. 4
    67
      novaclient/v2/contrib/host_evacuate.py
  23. 2
    83
      novaclient/v2/contrib/host_evacuate_live.py
  24. 4
    34
      novaclient/v2/contrib/host_servers_migrate.py
  25. 4
    75
      novaclient/v2/contrib/instance_action.py
  26. 5
    28
      novaclient/v2/contrib/list_extensions.py
  27. 2
    30
      novaclient/v2/contrib/metadata_extensions.py
  28. 5
    80
      novaclient/v2/contrib/migrations.py
  29. 6
    20
      novaclient/v2/contrib/server_external_events.py
  30. 40
    0
      novaclient/v2/instance_action.py
  31. 36
    0
      novaclient/v2/list_extensions.py
  32. 51
    0
      novaclient/v2/migrations.py
  33. 39
    0
      novaclient/v2/server_external_events.py
  34. 357
    0
      novaclient/v2/shell.py
  35. 21
    0
      releasenotes/notes/deprecate_contrib_extensions-0ec70c070b09eedb.yaml

+ 7
- 14
novaclient/client.py View File

@@ -22,12 +22,9 @@ OpenStack Client interface. Handles the REST calls and responses.
22 22
 
23 23
 import copy
24 24
 import functools
25
-import glob
26 25
 import hashlib
27
-import imp
28 26
 import itertools
29 27
 import logging
30
-import os
31 28
 import pkgutil
32 29
 import re
33 30
 import warnings
@@ -772,18 +769,14 @@ def _discover_via_python_path():
772 769
 
773 770
 
774 771
 def _discover_via_contrib_path(version):
775
-    module_path = os.path.dirname(os.path.abspath(__file__))
776
-    ext_path = os.path.join(module_path, "v%s" % version.ver_major, 'contrib')
777
-    ext_glob = os.path.join(ext_path, "*.py")
772
+    if version.ver_major == 2:
773
+        modules = {"baremetal": "novaclient.v2.contrib.baremetal",
774
+                   "tenant_networks": "novaclient.v2.contrib.tenant_networks"}
778 775
 
779
-    for ext_path in glob.iglob(ext_glob):
780
-        name = os.path.basename(ext_path)[:-3]
781
-
782
-        if name in extensions_ignored_name:
783
-            continue
784
-
785
-        module = imp.load_source(name, ext_path)
786
-        yield name, module
776
+        for name, module_name in modules.items():
777
+            module_loader = pkgutil.get_loader(module_name)
778
+            module = module_loader.load_module(module_name)
779
+            yield name, module
787 780
 
788 781
 
789 782
 def _discover_via_entry_points():

+ 0
- 114
novaclient/tests/unit/v2/contrib/fakes.py View File

@@ -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
-
15
-from novaclient.tests.unit.v2 import fakes
16
-from novaclient.v2 import client
17
-
18
-FAKE_REQUEST_ID_LIST = fakes.FAKE_REQUEST_ID_LIST
19
-FAKE_RESPONSE_HEADERS = fakes.FAKE_RESPONSE_HEADERS
20
-
21
-
22
-class 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
-
31
-class 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'}]})

+ 4
- 2
novaclient/tests/unit/v2/contrib/test_baremetal.py View File

@@ -18,9 +18,10 @@ import warnings
18 18
 
19 19
 import mock
20 20
 
21
+from novaclient import api_versions
21 22
 from novaclient import extension
22 23
 from novaclient.tests.unit import utils
23
-from novaclient.tests.unit.v2.contrib import fakes
24
+from novaclient.tests.unit.v2 import fakes
24 25
 from novaclient.v2.contrib import baremetal
25 26
 
26 27
 
@@ -31,7 +32,8 @@ class BaremetalExtensionTest(utils.TestCase):
31 32
         extensions = [
32 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 38
     def test_list_nodes(self, mock_warn):
37 39
         nl = self.cs.baremetal.list()

+ 4
- 2
novaclient/tests/unit/v2/contrib/test_tenant_networks.py View File

@@ -13,9 +13,10 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
+from novaclient import api_versions
16 17
 from novaclient import extension
17 18
 from novaclient.tests.unit import utils
18
-from novaclient.tests.unit.v2.contrib import fakes
19
+from novaclient.tests.unit.v2 import fakes
19 20
 from novaclient.v2.contrib import tenant_networks
20 21
 
21 22
 
@@ -27,7 +28,8 @@ class TenantNetworkExtensionTests(utils.TestCase):
27 28
             extension.Extension(tenant_networks.__name__.split(".")[-1],
28 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 34
     def test_list_tenant_networks(self):
33 35
         nets = self.cs.tenant_networks.list()

+ 88
- 11
novaclient/tests/unit/v2/fakes.py View File

@@ -60,9 +60,8 @@ class FakeClient(fakes.FakeClient, client.Client):
60 60
         client.Client.__init__(self, 'username', 'password',
61 61
                                'project_id', 'auth_url',
62 62
                                extensions=kwargs.get('extensions'),
63
-                               direct_use=False)
64
-        kwargs["api_version"] = api_version
65
-        self.client = FakeHTTPClient(**kwargs)
63
+                               direct_use=False, api_version=api_version)
64
+        self.client = FakeHTTPClient(api_version=api_version, **kwargs)
66 65
 
67 66
 
68 67
 class FakeHTTPClient(base_client.HTTPClient):
@@ -2142,11 +2141,6 @@ class FakeHTTPClient(base_client.HTTPClient):
2142 2141
 
2143 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 2145
     # Server Groups
2152 2146
     #
@@ -2242,6 +2236,90 @@ class FakeHTTPClient(base_client.HTTPClient):
2242 2236
     def delete_servers_1234_tags(self, **kw):
2243 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
 
2246 2324
 class FakeSessionClient(fakes.FakeClient, client.Client):
2247 2325
 
@@ -2249,9 +2327,8 @@ class FakeSessionClient(fakes.FakeClient, client.Client):
2249 2327
         client.Client.__init__(self, 'username', 'password',
2250 2328
                                'project_id', 'auth_url',
2251 2329
                                extensions=kwargs.get('extensions'),
2252
-                               direct_use=False)
2253
-        kwargs["api_version"] = api_version
2254
-        self.client = FakeSessionMockClient(**kwargs)
2330
+                               direct_use=False, api_version=api_version)
2331
+        self.client = FakeSessionMockClient(api_version=api_version, **kwargs)
2255 2332
 
2256 2333
 
2257 2334
 class FakeSessionMockClient(base_client.SessionClient, FakeHTTPClient):

novaclient/tests/unit/v2/contrib/test_assisted_volume_snapshots.py → novaclient/tests/unit/v2/test_assisted_volume_snapshots.py View File

@@ -16,20 +16,15 @@
16 16
 Assisted volume snapshots - to be used by Cinder and not end users.
17 17
 """
18 18
 
19
-from novaclient import extension
19
+from novaclient import api_versions
20 20
 from novaclient.tests.unit import utils
21
-from novaclient.tests.unit.v2.contrib import fakes
22
-from novaclient.v2.contrib import assisted_volume_snapshots as assisted_snaps
21
+from novaclient.tests.unit.v2 import fakes
23 22
 
24 23
 
25 24
 class AssistedVolumeSnapshotsTestCase(utils.TestCase):
26 25
     def setUp(self):
27 26
         super(AssistedVolumeSnapshotsTestCase, self).setUp()
28
-        extensions = [
29
-            extension.Extension(assisted_snaps.__name__.split(".")[-1],
30
-                                assisted_snaps),
31
-        ]
32
-        self.cs = fakes.FakeClient(extensions=extensions)
27
+        self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
33 28
 
34 29
     def test_create_snap(self):
35 30
         vs = self.cs.assisted_volume_snapshots.create('1', {})

+ 22
- 28
novaclient/tests/unit/v2/test_auth.py View File

@@ -18,9 +18,13 @@ from keystoneauth1 import fixture
18 18
 import mock
19 19
 import requests
20 20
 
21
+from novaclient import client
21 22
 from novaclient import exceptions
22 23
 from novaclient.tests.unit import utils
23
-from novaclient.v2 import client
24
+
25
+
26
+def Client(*args, **kwargs):
27
+    return client.Client("2", *args, **kwargs)
24 28
 
25 29
 
26 30
 class AuthenticateAgainstKeystoneTests(utils.TestCase):
@@ -35,9 +39,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
35 39
         return resp
36 40
 
37 41
     def test_authenticate_success(self):
38
-        cs = client.Client("username", "password", "project_id",
39
-                           utils.AUTH_URL_V2, service_type='compute',
40
-                           direct_use=False)
42
+        cs = Client("username", "password", "project_id", utils.AUTH_URL_V2,
43
+                    service_type='compute')
41 44
         resp = self.get_token()
42 45
 
43 46
         auth_response = utils.TestResponse({
@@ -83,8 +86,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
83 86
         test_auth_call()
84 87
 
85 88
     def test_authenticate_failure(self):
86
-        cs = client.Client("username", "password", "project_id",
87
-                           utils.AUTH_URL_V2, direct_use=False)
89
+        cs = Client("username", "password", "project_id", utils.AUTH_URL_V2)
88 90
         resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
89 91
         auth_response = utils.TestResponse({
90 92
             "status_code": 401,
@@ -100,9 +102,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
100 102
         test_auth_call()
101 103
 
102 104
     def test_v1_auth_redirect(self):
103
-        cs = client.Client("username", "password", "project_id",
104
-                           utils.AUTH_URL_V1, service_type='compute',
105
-                           direct_use=False)
105
+        cs = Client("username", "password", "project_id", utils.AUTH_URL_V1,
106
+                    service_type='compute')
106 107
         dict_correct_response = self.get_token()
107 108
         correct_response = json.dumps(dict_correct_response)
108 109
         dict_responses = [
@@ -166,9 +167,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
166 167
         test_auth_call()
167 168
 
168 169
     def test_v2_auth_redirect(self):
169
-        cs = client.Client("username", "password", "project_id",
170
-                           utils.AUTH_URL_V2, service_type='compute',
171
-                           direct_use=False)
170
+        cs = Client("username", "password", "project_id", utils.AUTH_URL_V2,
171
+                    service_type='compute')
172 172
         dict_correct_response = self.get_token()
173 173
         correct_response = json.dumps(dict_correct_response)
174 174
         dict_responses = [
@@ -232,9 +232,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
232 232
         test_auth_call()
233 233
 
234 234
     def test_ambiguous_endpoints(self):
235
-        cs = client.Client("username", "password", "project_id",
236
-                           utils.AUTH_URL_V2, service_type='compute',
237
-                           direct_use=False)
235
+        cs = Client("username", "password", "project_id", utils.AUTH_URL_V2,
236
+                    service_type='compute')
238 237
         resp = self.get_token()
239 238
 
240 239
         # duplicate existing service
@@ -256,9 +255,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
256 255
         test_auth_call()
257 256
 
258 257
     def test_authenticate_with_token_success(self):
259
-        cs = client.Client("username", None, "project_id",
260
-                           utils.AUTH_URL_V2, service_type='compute',
261
-                           direct_use=False)
258
+        cs = Client("username", None, "project_id", utils.AUTH_URL_V2,
259
+                    service_type='compute')
262 260
         cs.client.auth_token = "FAKE_ID"
263 261
         resp = self.get_token(token_id="FAKE_ID")
264 262
         auth_response = utils.TestResponse({
@@ -300,8 +298,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
300 298
             self.assertEqual(cs.client.auth_token, token_id)
301 299
 
302 300
     def test_authenticate_with_token_failure(self):
303
-        cs = client.Client("username", None, "project_id", utils.AUTH_URL_V2,
304
-                           direct_use=False)
301
+        cs = Client("username", None, "project_id", utils.AUTH_URL_V2)
305 302
         cs.client.auth_token = "FAKE_ID"
306 303
         resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
307 304
         auth_response = utils.TestResponse({
@@ -317,8 +314,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
317 314
 
318 315
 class AuthenticationTests(utils.TestCase):
319 316
     def test_authenticate_success(self):
320
-        cs = client.Client("username", "password",
321
-                           "project_id", utils.AUTH_URL, direct_use=False)
317
+        cs = Client("username", "password", "project_id", utils.AUTH_URL)
322 318
         management_url = 'https://localhost/v1.1/443470'
323 319
         auth_response = utils.TestResponse({
324 320
             'status_code': 204,
@@ -353,8 +349,7 @@ class AuthenticationTests(utils.TestCase):
353 349
         test_auth_call()
354 350
 
355 351
     def test_authenticate_failure(self):
356
-        cs = client.Client("username", "password",
357
-                           "project_id", utils.AUTH_URL, direct_use=False)
352
+        cs = Client("username", "password", "project_id", utils.AUTH_URL)
358 353
         auth_response = utils.TestResponse({'status_code': 401})
359 354
         mock_request = mock.Mock(return_value=(auth_response))
360 355
 
@@ -365,8 +360,8 @@ class AuthenticationTests(utils.TestCase):
365 360
         test_auth_call()
366 361
 
367 362
     def test_auth_automatic(self):
368
-        cs = client.Client("username", "password",
369
-                           "project_id", utils.AUTH_URL, direct_use=False)
363
+        cs = Client("username", "password", "project_id", utils.AUTH_URL,
364
+                    direct_use=False)
370 365
         http_client = cs.client
371 366
         http_client.management_url = ''
372 367
         http_client.get_service_url = mock.Mock(return_value='')
@@ -382,8 +377,7 @@ class AuthenticationTests(utils.TestCase):
382 377
         test_auth_call()
383 378
 
384 379
     def test_auth_manual(self):
385
-        cs = client.Client("username", "password",
386
-                           "project_id", utils.AUTH_URL, direct_use=False)
380
+        cs = Client("username", "password", "project_id", utils.AUTH_URL)
387 381
 
388 382
         @mock.patch.object(cs.client, 'authenticate')
389 383
         def test_auth_call(m):

novaclient/tests/unit/v2/contrib/test_cells.py → novaclient/tests/unit/v2/test_cells.py View File

@@ -13,20 +13,15 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
-from novaclient import extension
16
+from novaclient import api_versions
17 17
 from novaclient.tests.unit import utils
18
-from novaclient.tests.unit.v2.contrib import fakes
19
-from novaclient.v2.contrib import cells
18
+from novaclient.tests.unit.v2 import fakes
20 19
 
21 20
 
22 21
 class CellsExtensionTests(utils.TestCase):
23 22
     def setUp(self):
24 23
         super(CellsExtensionTests, self).setUp()
25
-        extensions = [
26
-            extension.Extension(cells.__name__.split(".")[-1],
27
-                                cells),
28
-        ]
29
-        self.cs = fakes.FakeClient(extensions=extensions)
24
+        self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
30 25
 
31 26
     def test_get_cells(self):
32 27
         cell_name = 'child_cell'

+ 3
- 0
novaclient/tests/unit/v2/test_client.py View File

@@ -14,6 +14,7 @@ import uuid
14 14
 
15 15
 from keystoneauth1 import session
16 16
 
17
+from novaclient import api_versions
17 18
 from novaclient.tests.unit import utils
18 19
 from novaclient.v2 import client
19 20
 
@@ -27,6 +28,7 @@ class ClientTest(utils.TestCase):
27 28
 
28 29
         s = session.Session()
29 30
         c = client.Client(session=s,
31
+                          api_version=api_versions.APIVersion("2.0"),
30 32
                           user_agent=user_agent,
31 33
                           endpoint_override=endpoint_override,
32 34
                           direct_use=False)
@@ -40,6 +42,7 @@ class ClientTest(utils.TestCase):
40 42
 
41 43
         s = session.Session()
42 44
         c = client.Client(session=s,
45
+                          api_version=api_versions.APIVersion("2.0"),
43 46
                           interface=interface,
44 47
                           endpoint_type=endpoint_type,
45 48
                           direct_use=False)

novaclient/tests/unit/v2/contrib/test_instance_actions.py → novaclient/tests/unit/v2/test_instance_actions.py View File

@@ -13,20 +13,15 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
-from novaclient import extension
16
+from novaclient import api_versions
17 17
 from novaclient.tests.unit import utils
18
-from novaclient.tests.unit.v2.contrib import fakes
19
-from novaclient.v2.contrib import instance_action
18
+from novaclient.tests.unit.v2 import fakes
20 19
 
21 20
 
22 21
 class InstanceActionExtensionTests(utils.TestCase):
23 22
     def setUp(self):
24 23
         super(InstanceActionExtensionTests, self).setUp()
25
-        extensions = [
26
-            extension.Extension(instance_action.__name__.split(".")[-1],
27
-                                instance_action),
28
-        ]
29
-        self.cs = fakes.FakeClient(extensions=extensions)
24
+        self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
30 25
 
31 26
     def test_list_instance_actions(self):
32 27
         server_uuid = '1234'

novaclient/tests/unit/v2/contrib/test_list_extensions.py → novaclient/tests/unit/v2/test_list_extensions.py View File

@@ -12,21 +12,14 @@
12 12
 #    under the License.
13 13
 
14 14
 from novaclient import api_versions
15
-from novaclient import extension
16 15
 from novaclient.tests.unit import utils
17 16
 from novaclient.tests.unit.v2 import fakes
18
-from novaclient.v2.contrib import list_extensions
19 17
 
20 18
 
21 19
 class ListExtensionsTests(utils.TestCase):
22 20
     def setUp(self):
23 21
         super(ListExtensionsTests, self).setUp()
24
-        extensions = [
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)
22
+        self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
30 23
 
31 24
     def test_list_extensions(self):
32 25
         all_exts = self.cs.list_extensions.show_all()

novaclient/tests/unit/v2/contrib/test_migrations.py → novaclient/tests/unit/v2/test_migrations.py View File

@@ -11,21 +11,15 @@
11 11
 #    under the License.
12 12
 
13 13
 from novaclient import api_versions
14
-from novaclient import extension
15 14
 from novaclient.tests.unit import utils
16 15
 from novaclient.tests.unit.v2 import fakes
17
-from novaclient.v2.contrib import migrations
16
+from novaclient.v2 import migrations
18 17
 
19 18
 
20 19
 class MigrationsTest(utils.TestCase):
21 20
     def setUp(self):
22 21
         super(MigrationsTest, self).setUp()
23
-        self.extensions = [
24
-            extension.Extension(migrations.__name__.split(".")[-1],
25
-                                migrations),
26
-        ]
27
-        self.cs = fakes.FakeClient(api_versions.APIVersion("2.0"),
28
-                                   extensions=self.extensions)
22
+        self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
29 23
 
30 24
     def test_list_migrations(self):
31 25
         ml = self.cs.migrations.list()
@@ -36,8 +30,7 @@ class MigrationsTest(utils.TestCase):
36 30
             self.assertRaises(AttributeError, getattr, m, "migration_type")
37 31
 
38 32
     def test_list_migrations_v223(self):
39
-        cs = fakes.FakeClient(extensions=self.extensions,
40
-                              api_version=api_versions.APIVersion("2.23"))
33
+        cs = fakes.FakeClient(api_versions.APIVersion("2.23"))
41 34
         ml = cs.migrations.list()
42 35
         self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST)
43 36
         cs.assert_called('GET', '/os-migrations')

novaclient/tests/unit/v2/contrib/test_server_external_events.py → novaclient/tests/unit/v2/test_server_external_events.py View File

@@ -16,20 +16,15 @@
16 16
 External event triggering for servers, not to be used by users.
17 17
 """
18 18
 
19
-from novaclient import extension
19
+from novaclient import api_versions
20 20
 from novaclient.tests.unit import utils
21
-from novaclient.tests.unit.v2.contrib import fakes
22
-from novaclient.v2.contrib import server_external_events as ext_events
21
+from novaclient.tests.unit.v2 import fakes
23 22
 
24 23
 
25 24
 class ServerExternalEventsTestCase(utils.TestCase):
26 25
     def setUp(self):
27 26
         super(ServerExternalEventsTestCase, self).setUp()
28
-        extensions = [
29
-            extension.Extension(ext_events.__name__.split(".")[-1],
30
-                                ext_events),
31
-        ]
32
-        self.cs = fakes.FakeClient(extensions=extensions)
27
+        self.cs = fakes.FakeClient(api_versions.APIVersion("2.1"))
33 28
 
34 29
     def test_external_event(self):
35 30
         events = [{'server_uuid': 'fake-uuid1',

+ 3
- 4
novaclient/tests/unit/v2/test_shell.py View File

@@ -77,8 +77,7 @@ class ShellTest(utils.TestCase):
77 77
         self.shell = self.useFixture(ShellFixture()).shell
78 78
 
79 79
         self.useFixture(fixtures.MonkeyPatch(
80
-            'novaclient.client.Client',
81
-            lambda *args, **kwargs: fakes.FakeClient(*args, **kwargs)))
80
+            'novaclient.client.Client', fakes.FakeClient))
82 81
 
83 82
     @mock.patch('sys.stdout', new_callable=six.StringIO)
84 83
     @mock.patch('sys.stderr', new_callable=six.StringIO)
@@ -3150,9 +3149,9 @@ class ShellWithSessionClientTest(ShellTest):
3150 3149
     def setUp(self):
3151 3150
         """Run before each test."""
3152 3151
         super(ShellWithSessionClientTest, self).setUp()
3152
+
3153 3153
         self.useFixture(fixtures.MonkeyPatch(
3154
-            'novaclient.client.Client',
3155
-            lambda *args, **kwargs: fakes.FakeSessionClient(*args, **kwargs)))
3154
+            'novaclient.client.Client', fakes.FakeSessionClient))
3156 3155
 
3157 3156
 
3158 3157
 class GetSecgroupTest(utils.TestCase):

+ 54
- 0
novaclient/v2/assisted_volume_snapshots.py View File

@@ -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
+"""
16
+Assisted volume snapshots - to be used by Cinder and not end users.
17
+"""
18
+
19
+import json
20
+
21
+from novaclient import base
22
+
23
+
24
+class 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
+
37
+class 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)))

+ 44
- 0
novaclient/v2/cells.py View File

@@ -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
+
16
+from novaclient import base
17
+
18
+
19
+class Cell(base.Resource):
20
+    def __repr__(self):
21
+        return "<Cell: %s>" % self.name
22
+
23
+
24
+class 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")

+ 31
- 4
novaclient/v2/client.py View File

@@ -22,9 +22,12 @@ from novaclient import exceptions
22 22
 from novaclient.i18n import _LE
23 23
 from novaclient.v2 import agents
24 24
 from novaclient.v2 import aggregates
25
+from novaclient.v2 import assisted_volume_snapshots
25 26
 from novaclient.v2 import availability_zones
27
+from novaclient.v2 import cells
26 28
 from novaclient.v2 import certs
27 29
 from novaclient.v2 import cloudpipe
30
+from novaclient.v2 import contrib
28 31
 from novaclient.v2 import fixed_ips
29 32
 from novaclient.v2 import flavor_access
30 33
 from novaclient.v2 import flavors
@@ -36,14 +39,18 @@ from novaclient.v2 import fping
36 39
 from novaclient.v2 import hosts
37 40
 from novaclient.v2 import hypervisors
38 41
 from novaclient.v2 import images
42
+from novaclient.v2 import instance_action
39 43
 from novaclient.v2 import keypairs
40 44
 from novaclient.v2 import limits
45
+from novaclient.v2 import list_extensions
46
+from novaclient.v2 import migrations
41 47
 from novaclient.v2 import networks
42 48
 from novaclient.v2 import quota_classes
43 49
 from novaclient.v2 import quotas
44 50
 from novaclient.v2 import security_group_default_rules
45 51
 from novaclient.v2 import security_group_rules
46 52
 from novaclient.v2 import security_groups
53
+from novaclient.v2 import server_external_events
47 54
 from novaclient.v2 import server_groups
48 55
 from novaclient.v2 import server_migrations
49 56
 from novaclient.v2 import servers
@@ -172,16 +179,36 @@ class Client(object):
172 179
         self.server_migrations = \
173 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 196
         # Add in any extensions...
176 197
         if extensions:
177 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 208
                 if extension.manager_class:
179 209
                     setattr(self, extension.name,
180 210
                             extension.manager_class(self))
181 211
 
182
-        if not logger:
183
-            logger = logging.getLogger(__name__)
184
-
185 212
         self.client = client._construct_http_client(
186 213
             username=username,
187 214
             password=password,
@@ -208,7 +235,7 @@ class Client(object):
208 235
             session=session,
209 236
             auth=auth,
210 237
             api_version=api_version,
211
-            logger=logger,
238
+            logger=self.logger,
212 239
             **kwargs)
213 240
 
214 241
     @property

+ 51
- 0
novaclient/v2/contrib/__init__.py View File

@@ -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
+import inspect
14
+import warnings
15
+
16
+from 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).
22
+V2_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
+
33
+def 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)

+ 6
- 34
novaclient/v2/contrib/assisted_volume_snapshots.py View File

@@ -16,42 +16,14 @@
16 16
 Assisted volume snapshots - to be used by Cinder and not end users.
17 17
 """
18 18
 
19
-import json
19
+from novaclient.v2 import assisted_volume_snapshots
20
+from novaclient.v2 import contrib
20 21
 
21
-from novaclient import base
22 22
 
23
-
24
-class 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
-
37
-class 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)))
23
+AssistedSnapshotManager = assisted_volume_snapshots.AssistedSnapshotManager
24
+Snapshot = assisted_volume_snapshots.Snapshot
55 25
 
56 26
 manager_class = AssistedSnapshotManager
57 27
 name = 'assisted_volume_snapshots'
28
+
29
+contrib.warn()

+ 5
- 55
novaclient/v2/contrib/cells.py View File

@@ -13,61 +13,11 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
-from novaclient import base
17
-from novaclient.i18n import _
18
-from novaclient import utils
16
+from novaclient.v2 import cells
17
+from novaclient.v2 import contrib
19 18
 
20 19
 
21
-class Cell(base.Resource):
22
-    def __repr__(self):
23
-        return "<Cell: %s>" % self.name
20
+Cell = cells.Cell
21
+CellsManager = cells.CellsManager
24 22
 
25
-
26
-class 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.'))
53
-def 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)
64
-def 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")
23
+contrib.warn()

+ 2
- 12
novaclient/v2/contrib/deferred_delete.py View File

@@ -12,16 +12,6 @@
12 12
 # See the License for the specific language governing permissions and
13 13
 # limitations under the License.
14 14
 
15
-from novaclient import utils
15
+from novaclient.v2 import contrib
16 16
 
17
-
18
-@utils.arg('server', metavar='<server>', help='Name or ID of server.')
19
-def 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.')
25
-def do_restore(cs, args):
26
-    """Restore a soft-deleted server."""
27
-    utils.find_resource(cs.servers, args.server, deleted=True).restore()
17
+contrib.warn(alternative=False)

+ 4
- 67
novaclient/v2/contrib/host_evacuate.py View File

@@ -13,73 +13,10 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
-from novaclient import api_versions
17
-from novaclient import base
18
-from novaclient.i18n import _
19
-from novaclient import utils
16
+from novaclient.v2 import contrib
17
+from novaclient.v2 import shell
20 18
 
21 19
 
22
-class EvacuateHostResponse(base.Resource):
23
-    pass
20
+EvacuateHostResponse = shell.EvacuateHostResponse
24 21
 
25
-
26
-def _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')
75
-def 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"])
22
+contrib.warn(alternative=False)

+ 2
- 83
novaclient/v2/contrib/host_evacuate_live.py View File

@@ -13,87 +13,6 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
-from novaclient.i18n import _
17
-from novaclient import utils
16
+from novaclient.v2 import contrib
18 17
 
19
-
20
-def _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')
84
-def 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"])
18
+contrib.warn(alternative=False)

+ 4
- 34
novaclient/v2/contrib/host_servers_migrate.py View File

@@ -13,40 +13,10 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
-from novaclient import base
17
-from novaclient.i18n import _
18
-from novaclient import utils
16
+from novaclient.v2 import contrib
17
+from novaclient.v2 import shell
19 18
 
20 19
 
21
-class HostServersMigrateResponse(base.Resource):
22
-    pass
20
+HostServersMigrateResponse = shell.HostServersMigrateResponse
23 21
 
24
-
25
-def _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.')
40
-def 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"])
22
+contrib.warn(alternative=False)

+ 4
- 75
novaclient/v2/contrib/instance_action.py View File

@@ -13,81 +13,10 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
-import pprint
16
+from novaclient.v2 import contrib
17
+from novaclient.v2 import instance_action
17 18
 
18
-from novaclient import api_versions
19
-from novaclient import base
20
-from novaclient.i18n import _
21
-from novaclient import utils
22
-from novaclient.v2 import shell
23 19
 
20
+InstanceActionManager = instance_action.InstanceActionManager
24 21
 
25
-class InstanceActionManager(base.ManagerWithFind):
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.'))
60
-def 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")
84
-def 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)
22
+contrib.warn()

+ 5
- 28
novaclient/v2/contrib/list_extensions.py View File

@@ -13,34 +13,11 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
-from novaclient import base
17
-from novaclient import utils
16
+from novaclient.v2 import contrib
17
+from novaclient.v2 import list_extensions
18 18
 
19 19
 
20
-class ListExtResource(base.Resource):
21
-    @property
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] + "..."
20
+ListExtResource = list_extensions.ListExtResource
21
+ListExtManager = list_extensions.ListExtResource
31 22
 
32
-
33
-class ListExtManager(base.Manager):
34
-    resource_class = ListExtResource
35
-
36
-    def show_all(self):
37
-        return self._list("/extensions", 'extensions')
38
-
39
-
40
-def 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)
23
+contrib.warn()

+ 2
- 30
novaclient/v2/contrib/metadata_extensions.py View File

@@ -13,35 +13,7 @@
13 13
 #    License for the specific language governing permissions and limitations
14 14
 #    under the License.
15 15
 
16
-from novaclient.i18n import _
17
-from novaclient import utils
18
-from novaclient.v2 import shell
16
+from novaclient.v2 import contrib
19 17
 
20 18
 
21
-@utils.arg(
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)'))
37
-def 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())
19
+contrib.warn(alternative=False)

+ 5
- 80
novaclient/v2/contrib/migrations.py View File

@@ -14,86 +14,11 @@
14 14
 migration interface
15 15
 """
16 16
 
17
-from six.moves.urllib import parse
17
+from novaclient.v2 import contrib
18
+from novaclient.v2 import migrations
18 19
 
19
-from novaclient import api_versions
20
-from novaclient import base
21
-from novaclient.i18n import _
22
-from novaclient import utils
23 20
 
21
+Migration = migrations.Migration
22
+MigrationManager = migrations.MigrationManager
24 23
 
25
-class Migration(base.Resource):
26
-    def __repr__(self):
27
-        return "<Migration: %s>" % self.id
28
-
29
-
30
-class 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.'))
72
-def 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
-
78
-def _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)
24
+contrib.warn()

+ 6
- 20
novaclient/v2/contrib/server_external_events.py View File

@@ -16,28 +16,14 @@
16 16
 External event triggering for servers, not to be used by users.
17 17
 """
18 18
 
19
-from novaclient import base
19
+from novaclient.v2 import contrib
20
+from novaclient.v2 import server_external_events
20 21
 
21 22
 
22
-class Event(base.Resource):
23
-    def __repr__(self):
24
-        return "<Event: %s>" % self.name
25
-
26
-
27
-class 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
-
23
+Event = server_external_events.Event
24
+ServerExternalEventManager = server_external_events.ServerExternalEventManager
41 25
 
42 26
 manager_class = ServerExternalEventManager
43 27
 name = 'server_external_events'
28
+
29
+contrib.warn()

+ 40
- 0
novaclient/v2/instance_action.py View File

@@ -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
+
16
+from novaclient import base
17
+
18
+
19
+class InstanceAction(base.Resource):
20
+    pass
21
+
22
+
23
+class 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')

+ 36
- 0
novaclient/v2/list_extensions.py View File

@@ -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
+
16
+from novaclient import base
17
+
18
+
19
+class 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
+
32
+class ListExtManager(base.Manager):
33
+    resource_class = ListExtResource
34
+
35
+    def show_all(self):
36
+        return self._list("/extensions", 'extensions')

+ 51
- 0
novaclient/v2/migrations.py View File

@@ -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
+"""
14
+migration interface
15
+"""
16
+
17
+from six.moves.urllib import parse
18
+
19
+from novaclient import base
20
+
21
+
22
+class Migration(base.Resource):
23
+    def __repr__(self):
24
+        return "<Migration: %s>" % self.id
25
+
26
+
27
+class 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")

+ 39
- 0
novaclient/v2/server_external_events.py View File

@@ -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
+"""
16
+External event triggering for servers, not to be used by users.
17
+"""
18
+
19
+from novaclient import base
20
+
21
+
22
+class Event(base.Resource):
23
+    def __repr__(self):
24
+        return "<Event: %s>" % self.name
25
+
26
+
27
+class 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)

+ 357
- 0
novaclient/v2/shell.py View File

@@ -26,6 +26,7 @@ import getpass
26 26
 import locale
27 27
 import logging
28 28
 import os
29
+import pprint
29 30
 import sys
30 31
 import time
31 32
 
@@ -5185,3 +5186,359 @@ def do_server_tag_delete_all(cs, args):
5185 5186
     """Delete all tags from a server."""
5186 5187
     server = _find_server(cs, args.server)
5187 5188
     server.delete_all_tags()
5189
+
5190
+
5191
+@utils.arg(
5192
+    'cell',
5193
+    metavar='<cell-name>',
5194
+    help=_('Name of the cell.'))
5195
+def 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)
5206
+def 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.')
5219
+def 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.')
5225
+def do_restore(cs, args):
5226
+    """Restore a soft-deleted server."""
5227
+    utils.find_resource(cs.servers, args.server, deleted=True).restore()
5228
+
5229
+
5230
+class EvacuateHostResponse(base.Resource):
5231
+    pass
5232
+
5233
+
5234
+def _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')
5282
+def 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
+
5296
+def _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')
5360
+def 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
+
5378
+class HostServersMigrateResponse(base.Resource):
5379
+    pass
5380
+
5381
+
5382
+def _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.')
5397
+def 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.'))
5428
+def 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")
5452
+def 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
+
5464
+def 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)'))
5489
+def 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
+
5502
+def _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.'))
5541
+def 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)

+ 21
- 0
releasenotes/notes/deprecate_contrib_extensions-0ec70c070b09eedb.yaml View File

@@ -0,0 +1,21 @@
1
+---
2
+prelude: >
3
+    All extensions of API V2.0 were merged to 2.1, but NovaClient continued
4
+    to store them as a separate entities.
5
+upgrade:
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).
19
+deprecations:
20
+  - All modules of novaclient.v2.contrib are deprecated now and will be
21
+    removed after OpenStack Pike.

Loading…
Cancel
Save