From f834711d2f4a6052a054ffc79918f615e400bdbe Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Thu, 24 Nov 2016 19:59:13 +0200 Subject: [PATCH] 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 --- novaclient/client.py | 21 +- novaclient/tests/unit/v2/contrib/fakes.py | 114 ------ .../tests/unit/v2/contrib/test_baremetal.py | 6 +- .../unit/v2/contrib/test_tenant_networks.py | 6 +- novaclient/tests/unit/v2/fakes.py | 99 ++++- .../test_assisted_volume_snapshots.py | 11 +- novaclient/tests/unit/v2/test_auth.py | 50 ++- .../tests/unit/v2/{contrib => }/test_cells.py | 11 +- novaclient/tests/unit/v2/test_client.py | 3 + .../v2/{contrib => }/test_instance_actions.py | 11 +- .../v2/{contrib => }/test_list_extensions.py | 9 +- .../unit/v2/{contrib => }/test_migrations.py | 13 +- .../test_server_external_events.py | 11 +- novaclient/tests/unit/v2/test_shell.py | 7 +- novaclient/v2/assisted_volume_snapshots.py | 54 +++ novaclient/v2/cells.py | 44 +++ novaclient/v2/client.py | 35 +- novaclient/v2/contrib/__init__.py | 51 +++ .../v2/contrib/assisted_volume_snapshots.py | 40 +- novaclient/v2/contrib/cells.py | 60 +-- novaclient/v2/contrib/deferred_delete.py | 14 +- novaclient/v2/contrib/host_evacuate.py | 71 +--- novaclient/v2/contrib/host_evacuate_live.py | 85 +---- novaclient/v2/contrib/host_servers_migrate.py | 38 +- novaclient/v2/contrib/instance_action.py | 79 +--- novaclient/v2/contrib/list_extensions.py | 33 +- novaclient/v2/contrib/metadata_extensions.py | 32 +- novaclient/v2/contrib/migrations.py | 85 +---- .../v2/contrib/server_external_events.py | 26 +- novaclient/v2/instance_action.py | 40 ++ novaclient/v2/list_extensions.py | 36 ++ novaclient/v2/migrations.py | 51 +++ novaclient/v2/server_external_events.py | 39 ++ novaclient/v2/shell.py | 357 ++++++++++++++++++ ...e_contrib_extensions-0ec70c070b09eedb.yaml | 21 ++ 35 files changed, 916 insertions(+), 747 deletions(-) delete mode 100644 novaclient/tests/unit/v2/contrib/fakes.py rename novaclient/tests/unit/v2/{contrib => }/test_assisted_volume_snapshots.py (78%) rename novaclient/tests/unit/v2/{contrib => }/test_cells.py (82%) rename novaclient/tests/unit/v2/{contrib => }/test_instance_actions.py (80%) rename novaclient/tests/unit/v2/{contrib => }/test_list_extensions.py (75%) rename novaclient/tests/unit/v2/{contrib => }/test_migrations.py (79%) rename novaclient/tests/unit/v2/{contrib => }/test_server_external_events.py (80%) create mode 100644 novaclient/v2/assisted_volume_snapshots.py create mode 100644 novaclient/v2/cells.py create mode 100644 novaclient/v2/instance_action.py create mode 100644 novaclient/v2/list_extensions.py create mode 100644 novaclient/v2/migrations.py create mode 100644 novaclient/v2/server_external_events.py create mode 100644 releasenotes/notes/deprecate_contrib_extensions-0ec70c070b09eedb.yaml diff --git a/novaclient/client.py b/novaclient/client.py index f9fecaa4f..79f36342f 100644 --- a/novaclient/client.py +++ b/novaclient/client.py @@ -22,12 +22,9 @@ OpenStack Client interface. Handles the REST calls and responses. import copy import functools -import glob import hashlib -import imp import itertools import logging -import os import pkgutil import re import warnings @@ -772,18 +769,14 @@ def _discover_via_python_path(): def _discover_via_contrib_path(version): - module_path = os.path.dirname(os.path.abspath(__file__)) - ext_path = os.path.join(module_path, "v%s" % version.ver_major, 'contrib') - ext_glob = os.path.join(ext_path, "*.py") + if version.ver_major == 2: + modules = {"baremetal": "novaclient.v2.contrib.baremetal", + "tenant_networks": "novaclient.v2.contrib.tenant_networks"} - for ext_path in glob.iglob(ext_glob): - name = os.path.basename(ext_path)[:-3] - - if name in extensions_ignored_name: - continue - - module = imp.load_source(name, ext_path) - yield name, module + for name, module_name in modules.items(): + module_loader = pkgutil.get_loader(module_name) + module = module_loader.load_module(module_name) + yield name, module def _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 04bd341cf..000000000 --- a/novaclient/tests/unit/v2/contrib/fakes.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from novaclient.tests.unit.v2 import fakes -from novaclient.v2 import client - -FAKE_REQUEST_ID_LIST = fakes.FAKE_REQUEST_ID_LIST -FAKE_RESPONSE_HEADERS = fakes.FAKE_RESPONSE_HEADERS - - -class FakeClient(fakes.FakeClient): - def __init__(self, *args, **kwargs): - client.Client.__init__(self, 'username', 'password', - 'project_id', 'auth_url', - extensions=kwargs.get('extensions'), - direct_use=False) - self.client = FakeHTTPClient(**kwargs) - - -class FakeHTTPClient(fakes.FakeHTTPClient): - def get_os_tenant_networks(self): - return (200, FAKE_RESPONSE_HEADERS, { - 'networks': [{"label": "1", "cidr": "10.0.0.0/24", - 'project_id': '4ffc664c198e435e9853f2538fbcd7a7', - 'id': '1'}]}) - - def get_os_tenant_networks_1(self, **kw): - return (200, FAKE_RESPONSE_HEADERS, { - 'network': {"label": "1", "cidr": "10.0.0.0/24", - 'project_id': '4ffc664c198e435e9853f2538fbcd7a7', - 'id': '1'}}) - - def post_os_tenant_networks(self, **kw): - return (201, FAKE_RESPONSE_HEADERS, { - 'network': {"label": "1", "cidr": "10.0.0.0/24", - 'project_id': '4ffc664c198e435e9853f2538fbcd7a7', - 'id': '1'}}) - - def delete_os_tenant_networks_1(self, **kw): - return (204, FAKE_RESPONSE_HEADERS, None) - - def get_os_baremetal_nodes(self, **kw): - return ( - 200, FAKE_RESPONSE_HEADERS, { - 'nodes': [ - { - "id": 1, - "instance_uuid": None, - "interfaces": [], - "cpus": 2, - "local_gb": 10, - "memory_mb": 5, - "pm_address": "2.3.4.5", - "pm_user": "pmuser", - "pm_password": "pmpass", - "prov_mac_address": "aa:bb:cc:dd:ee:ff", - "prov_vlan_id": 1, - "service_host": "somehost", - "terminal_port": 8080, - } - ] - } - ) - - def get_os_baremetal_nodes_1(self, **kw): - return ( - 200, FAKE_RESPONSE_HEADERS, { - 'node': { - "id": 1, - "instance_uuid": None, - "pm_address": "1.2.3.4", - "interfaces": [], - "cpus": 2, - "local_gb": 10, - "memory_mb": 5, - "pm_user": "pmuser", - "pm_password": "pmpass", - "prov_mac_address": "aa:bb:cc:dd:ee:ff", - "prov_vlan_id": 1, - "service_host": "somehost", - "terminal_port": 8080, - } - } - ) - - def post_os_assisted_volume_snapshots(self, **kw): - return (202, FAKE_RESPONSE_HEADERS, - {'snapshot': {'id': 'blah', 'volumeId': '1'}}) - - def delete_os_assisted_volume_snapshots_x(self, **kw): - return (202, FAKE_RESPONSE_HEADERS, {}) - - def post_os_server_external_events(self, **kw): - return (200, FAKE_RESPONSE_HEADERS, { - 'events': [ - {'name': 'test-event', - 'status': 'completed', - 'tag': 'tag', - 'server_uuid': 'fake-uuid1'}, - {'name': 'test-event', - 'status': 'completed', - 'tag': 'tag', - '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 0f4326a87..512245402 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 import mock +from novaclient import api_versions from novaclient import extension from novaclient.tests.unit import utils -from novaclient.tests.unit.v2.contrib import fakes +from novaclient.tests.unit.v2 import fakes from novaclient.v2.contrib import baremetal @@ -31,7 +32,8 @@ class BaremetalExtensionTest(utils.TestCase): extensions = [ extension.Extension(baremetal.__name__.split(".")[-1], baremetal), ] - self.cs = fakes.FakeClient(extensions=extensions) + self.cs = fakes.FakeClient(api_versions.APIVersion("2.0"), + extensions=extensions) def test_list_nodes(self, mock_warn): 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 e19942729..99e0b2704 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 @@ # License for the specific language governing permissions and limitations # under the License. +from novaclient import api_versions from novaclient import extension from novaclient.tests.unit import utils -from novaclient.tests.unit.v2.contrib import fakes +from novaclient.tests.unit.v2 import fakes from novaclient.v2.contrib import tenant_networks @@ -27,7 +28,8 @@ class TenantNetworkExtensionTests(utils.TestCase): extension.Extension(tenant_networks.__name__.split(".")[-1], tenant_networks), ] - self.cs = fakes.FakeClient(extensions=extensions) + self.cs = fakes.FakeClient(api_versions.APIVersion("2.0"), + extensions=extensions) def test_list_tenant_networks(self): nets = self.cs.tenant_networks.list() diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index 39775e28c..646cc0961 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): client.Client.__init__(self, 'username', 'password', 'project_id', 'auth_url', extensions=kwargs.get('extensions'), - direct_use=False) - kwargs["api_version"] = api_version - self.client = FakeHTTPClient(**kwargs) + direct_use=False, api_version=api_version) + self.client = FakeHTTPClient(api_version=api_version, **kwargs) class FakeHTTPClient(base_client.HTTPClient): @@ -2142,11 +2141,6 @@ class FakeHTTPClient(base_client.HTTPClient): return (200, FAKE_RESPONSE_HEADERS, migrations) - def post_os_server_external_events(self, **kw): - return (200, {}, {'events': [ - {'name': 'network-changed', - 'server_uuid': '1234'}]}) - # # Server Groups # @@ -2242,6 +2236,90 @@ class FakeHTTPClient(base_client.HTTPClient): def delete_servers_1234_tags(self, **kw): return (204, {}, None) + def get_os_tenant_networks(self): + return (200, FAKE_RESPONSE_HEADERS, { + 'networks': [{"label": "1", "cidr": "10.0.0.0/24", + 'project_id': '4ffc664c198e435e9853f2538fbcd7a7', + 'id': '1'}]}) + + def get_os_tenant_networks_1(self, **kw): + return (200, FAKE_RESPONSE_HEADERS, { + 'network': {"label": "1", "cidr": "10.0.0.0/24", + 'project_id': '4ffc664c198e435e9853f2538fbcd7a7', + 'id': '1'}}) + + def post_os_tenant_networks(self, **kw): + return (201, FAKE_RESPONSE_HEADERS, { + 'network': {"label": "1", "cidr": "10.0.0.0/24", + 'project_id': '4ffc664c198e435e9853f2538fbcd7a7', + 'id': '1'}}) + + def delete_os_tenant_networks_1(self, **kw): + return (204, FAKE_RESPONSE_HEADERS, None) + + def get_os_baremetal_nodes(self, **kw): + return ( + 200, FAKE_RESPONSE_HEADERS, { + 'nodes': [ + { + "id": 1, + "instance_uuid": None, + "interfaces": [], + "cpus": 2, + "local_gb": 10, + "memory_mb": 5, + "pm_address": "2.3.4.5", + "pm_user": "pmuser", + "pm_password": "pmpass", + "prov_mac_address": "aa:bb:cc:dd:ee:ff", + "prov_vlan_id": 1, + "service_host": "somehost", + "terminal_port": 8080, + } + ] + } + ) + + def get_os_baremetal_nodes_1(self, **kw): + return ( + 200, FAKE_RESPONSE_HEADERS, { + 'node': { + "id": 1, + "instance_uuid": None, + "pm_address": "1.2.3.4", + "interfaces": [], + "cpus": 2, + "local_gb": 10, + "memory_mb": 5, + "pm_user": "pmuser", + "pm_password": "pmpass", + "prov_mac_address": "aa:bb:cc:dd:ee:ff", + "prov_vlan_id": 1, + "service_host": "somehost", + "terminal_port": 8080, + } + } + ) + + def post_os_assisted_volume_snapshots(self, **kw): + return (202, FAKE_RESPONSE_HEADERS, + {'snapshot': {'id': 'blah', 'volumeId': '1'}}) + + def delete_os_assisted_volume_snapshots_x(self, **kw): + return (202, FAKE_RESPONSE_HEADERS, {}) + + def post_os_server_external_events(self, **kw): + return (200, FAKE_RESPONSE_HEADERS, { + 'events': [ + {'name': 'test-event', + 'status': 'completed', + 'tag': 'tag', + 'server_uuid': 'fake-uuid1'}, + {'name': 'test-event', + 'status': 'completed', + 'tag': 'tag', + 'server_uuid': 'fake-uuid2'}]}) + class FakeSessionClient(fakes.FakeClient, client.Client): @@ -2249,9 +2327,8 @@ class FakeSessionClient(fakes.FakeClient, client.Client): client.Client.__init__(self, 'username', 'password', 'project_id', 'auth_url', extensions=kwargs.get('extensions'), - direct_use=False) - kwargs["api_version"] = api_version - self.client = FakeSessionMockClient(**kwargs) + direct_use=False, api_version=api_version) + self.client = FakeSessionMockClient(api_version=api_version, **kwargs) class 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 similarity index 78% rename from novaclient/tests/unit/v2/contrib/test_assisted_volume_snapshots.py rename to novaclient/tests/unit/v2/test_assisted_volume_snapshots.py index 374d2d847..8fa4cb598 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 @@ Assisted volume snapshots - to be used by Cinder and not end users. """ -from novaclient import extension +from novaclient import api_versions from novaclient.tests.unit import utils -from novaclient.tests.unit.v2.contrib import fakes -from novaclient.v2.contrib import assisted_volume_snapshots as assisted_snaps +from novaclient.tests.unit.v2 import fakes class AssistedVolumeSnapshotsTestCase(utils.TestCase): def setUp(self): super(AssistedVolumeSnapshotsTestCase, self).setUp() - extensions = [ - extension.Extension(assisted_snaps.__name__.split(".")[-1], - assisted_snaps), - ] - self.cs = fakes.FakeClient(extensions=extensions) + self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) def test_create_snap(self): 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 70d9467a3..1fdbf3ad2 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 import mock import requests +from novaclient import client from novaclient import exceptions from novaclient.tests.unit import utils -from novaclient.v2 import client + + +def Client(*args, **kwargs): + return client.Client("2", *args, **kwargs) class AuthenticateAgainstKeystoneTests(utils.TestCase): @@ -35,9 +39,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): return resp def test_authenticate_success(self): - cs = client.Client("username", "password", "project_id", - utils.AUTH_URL_V2, service_type='compute', - direct_use=False) + cs = Client("username", "password", "project_id", utils.AUTH_URL_V2, + service_type='compute') resp = self.get_token() auth_response = utils.TestResponse({ @@ -83,8 +86,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): test_auth_call() def test_authenticate_failure(self): - cs = client.Client("username", "password", "project_id", - utils.AUTH_URL_V2, direct_use=False) + cs = Client("username", "password", "project_id", utils.AUTH_URL_V2) resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}} auth_response = utils.TestResponse({ "status_code": 401, @@ -100,9 +102,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): test_auth_call() def test_v1_auth_redirect(self): - cs = client.Client("username", "password", "project_id", - utils.AUTH_URL_V1, service_type='compute', - direct_use=False) + cs = Client("username", "password", "project_id", utils.AUTH_URL_V1, + service_type='compute') dict_correct_response = self.get_token() correct_response = json.dumps(dict_correct_response) dict_responses = [ @@ -166,9 +167,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): test_auth_call() def test_v2_auth_redirect(self): - cs = client.Client("username", "password", "project_id", - utils.AUTH_URL_V2, service_type='compute', - direct_use=False) + cs = Client("username", "password", "project_id", utils.AUTH_URL_V2, + service_type='compute') dict_correct_response = self.get_token() correct_response = json.dumps(dict_correct_response) dict_responses = [ @@ -232,9 +232,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): test_auth_call() def test_ambiguous_endpoints(self): - cs = client.Client("username", "password", "project_id", - utils.AUTH_URL_V2, service_type='compute', - direct_use=False) + cs = Client("username", "password", "project_id", utils.AUTH_URL_V2, + service_type='compute') resp = self.get_token() # duplicate existing service @@ -256,9 +255,8 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): test_auth_call() def test_authenticate_with_token_success(self): - cs = client.Client("username", None, "project_id", - utils.AUTH_URL_V2, service_type='compute', - direct_use=False) + cs = Client("username", None, "project_id", utils.AUTH_URL_V2, + service_type='compute') cs.client.auth_token = "FAKE_ID" resp = self.get_token(token_id="FAKE_ID") auth_response = utils.TestResponse({ @@ -300,8 +298,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): self.assertEqual(cs.client.auth_token, token_id) def test_authenticate_with_token_failure(self): - cs = client.Client("username", None, "project_id", utils.AUTH_URL_V2, - direct_use=False) + cs = Client("username", None, "project_id", utils.AUTH_URL_V2) cs.client.auth_token = "FAKE_ID" resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}} auth_response = utils.TestResponse({ @@ -317,8 +314,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): class AuthenticationTests(utils.TestCase): def test_authenticate_success(self): - cs = client.Client("username", "password", - "project_id", utils.AUTH_URL, direct_use=False) + cs = Client("username", "password", "project_id", utils.AUTH_URL) management_url = 'https://localhost/v1.1/443470' auth_response = utils.TestResponse({ 'status_code': 204, @@ -353,8 +349,7 @@ class AuthenticationTests(utils.TestCase): test_auth_call() def test_authenticate_failure(self): - cs = client.Client("username", "password", - "project_id", utils.AUTH_URL, direct_use=False) + cs = Client("username", "password", "project_id", utils.AUTH_URL) auth_response = utils.TestResponse({'status_code': 401}) mock_request = mock.Mock(return_value=(auth_response)) @@ -365,8 +360,8 @@ class AuthenticationTests(utils.TestCase): test_auth_call() def test_auth_automatic(self): - cs = client.Client("username", "password", - "project_id", utils.AUTH_URL, direct_use=False) + cs = Client("username", "password", "project_id", utils.AUTH_URL, + direct_use=False) http_client = cs.client http_client.management_url = '' http_client.get_service_url = mock.Mock(return_value='') @@ -382,8 +377,7 @@ class AuthenticationTests(utils.TestCase): test_auth_call() def test_auth_manual(self): - cs = client.Client("username", "password", - "project_id", utils.AUTH_URL, direct_use=False) + cs = Client("username", "password", "project_id", utils.AUTH_URL) @mock.patch.object(cs.client, 'authenticate') def test_auth_call(m): diff --git a/novaclient/tests/unit/v2/contrib/test_cells.py b/novaclient/tests/unit/v2/test_cells.py similarity index 82% rename from novaclient/tests/unit/v2/contrib/test_cells.py rename to novaclient/tests/unit/v2/test_cells.py index 95a5c4b4e..5a47e1c6b 100644 --- a/novaclient/tests/unit/v2/contrib/test_cells.py +++ b/novaclient/tests/unit/v2/test_cells.py @@ -13,20 +13,15 @@ # License for the specific language governing permissions and limitations # under the License. -from novaclient import extension +from novaclient import api_versions from novaclient.tests.unit import utils -from novaclient.tests.unit.v2.contrib import fakes -from novaclient.v2.contrib import cells +from novaclient.tests.unit.v2 import fakes class CellsExtensionTests(utils.TestCase): def setUp(self): super(CellsExtensionTests, self).setUp() - extensions = [ - extension.Extension(cells.__name__.split(".")[-1], - cells), - ] - self.cs = fakes.FakeClient(extensions=extensions) + self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) def test_get_cells(self): cell_name = 'child_cell' diff --git a/novaclient/tests/unit/v2/test_client.py b/novaclient/tests/unit/v2/test_client.py index dc92c9327..94ba44cee 100644 --- a/novaclient/tests/unit/v2/test_client.py +++ b/novaclient/tests/unit/v2/test_client.py @@ -14,6 +14,7 @@ import uuid from keystoneauth1 import session +from novaclient import api_versions from novaclient.tests.unit import utils from novaclient.v2 import client @@ -27,6 +28,7 @@ class ClientTest(utils.TestCase): s = session.Session() c = client.Client(session=s, + api_version=api_versions.APIVersion("2.0"), user_agent=user_agent, endpoint_override=endpoint_override, direct_use=False) @@ -40,6 +42,7 @@ class ClientTest(utils.TestCase): s = session.Session() c = client.Client(session=s, + api_version=api_versions.APIVersion("2.0"), interface=interface, endpoint_type=endpoint_type, direct_use=False) diff --git a/novaclient/tests/unit/v2/contrib/test_instance_actions.py b/novaclient/tests/unit/v2/test_instance_actions.py similarity index 80% rename from novaclient/tests/unit/v2/contrib/test_instance_actions.py rename to novaclient/tests/unit/v2/test_instance_actions.py index 394b37e40..8eed17197 100644 --- a/novaclient/tests/unit/v2/contrib/test_instance_actions.py +++ b/novaclient/tests/unit/v2/test_instance_actions.py @@ -13,20 +13,15 @@ # License for the specific language governing permissions and limitations # under the License. -from novaclient import extension +from novaclient import api_versions from novaclient.tests.unit import utils -from novaclient.tests.unit.v2.contrib import fakes -from novaclient.v2.contrib import instance_action +from novaclient.tests.unit.v2 import fakes class InstanceActionExtensionTests(utils.TestCase): def setUp(self): super(InstanceActionExtensionTests, self).setUp() - extensions = [ - extension.Extension(instance_action.__name__.split(".")[-1], - instance_action), - ] - self.cs = fakes.FakeClient(extensions=extensions) + self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) def test_list_instance_actions(self): server_uuid = '1234' diff --git a/novaclient/tests/unit/v2/contrib/test_list_extensions.py b/novaclient/tests/unit/v2/test_list_extensions.py similarity index 75% rename from novaclient/tests/unit/v2/contrib/test_list_extensions.py rename to novaclient/tests/unit/v2/test_list_extensions.py index 02fe296ca..f473ed753 100644 --- a/novaclient/tests/unit/v2/contrib/test_list_extensions.py +++ b/novaclient/tests/unit/v2/test_list_extensions.py @@ -12,21 +12,14 @@ # under the License. from novaclient import api_versions -from novaclient import extension from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes -from novaclient.v2.contrib import list_extensions class ListExtensionsTests(utils.TestCase): def setUp(self): super(ListExtensionsTests, self).setUp() - extensions = [ - extension.Extension(list_extensions.__name__.split(".")[-1], - list_extensions), - ] - self.cs = fakes.FakeClient(api_versions.APIVersion("2.0"), - extensions=extensions) + self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) def test_list_extensions(self): 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 similarity index 79% rename from novaclient/tests/unit/v2/contrib/test_migrations.py rename to novaclient/tests/unit/v2/test_migrations.py index 4cf5d45eb..b270f9c3c 100644 --- a/novaclient/tests/unit/v2/contrib/test_migrations.py +++ b/novaclient/tests/unit/v2/test_migrations.py @@ -11,21 +11,15 @@ # under the License. from novaclient import api_versions -from novaclient import extension from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes -from novaclient.v2.contrib import migrations +from novaclient.v2 import migrations class MigrationsTest(utils.TestCase): def setUp(self): super(MigrationsTest, self).setUp() - self.extensions = [ - extension.Extension(migrations.__name__.split(".")[-1], - migrations), - ] - self.cs = fakes.FakeClient(api_versions.APIVersion("2.0"), - extensions=self.extensions) + self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) def test_list_migrations(self): ml = self.cs.migrations.list() @@ -36,8 +30,7 @@ class MigrationsTest(utils.TestCase): self.assertRaises(AttributeError, getattr, m, "migration_type") def test_list_migrations_v223(self): - cs = fakes.FakeClient(extensions=self.extensions, - api_version=api_versions.APIVersion("2.23")) + cs = fakes.FakeClient(api_versions.APIVersion("2.23")) ml = cs.migrations.list() self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST) 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 similarity index 80% rename from novaclient/tests/unit/v2/contrib/test_server_external_events.py rename to novaclient/tests/unit/v2/test_server_external_events.py index 009a0d83a..4b640750c 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 @@ External event triggering for servers, not to be used by users. """ -from novaclient import extension +from novaclient import api_versions from novaclient.tests.unit import utils -from novaclient.tests.unit.v2.contrib import fakes -from novaclient.v2.contrib import server_external_events as ext_events +from novaclient.tests.unit.v2 import fakes class ServerExternalEventsTestCase(utils.TestCase): def setUp(self): super(ServerExternalEventsTestCase, self).setUp() - extensions = [ - extension.Extension(ext_events.__name__.split(".")[-1], - ext_events), - ] - self.cs = fakes.FakeClient(extensions=extensions) + self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) def test_external_event(self): events = [{'server_uuid': 'fake-uuid1', diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index 9e4494439..1b3af35b6 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): self.shell = self.useFixture(ShellFixture()).shell self.useFixture(fixtures.MonkeyPatch( - 'novaclient.client.Client', - lambda *args, **kwargs: fakes.FakeClient(*args, **kwargs))) + 'novaclient.client.Client', fakes.FakeClient)) @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) @@ -3150,9 +3149,9 @@ class ShellWithSessionClientTest(ShellTest): def setUp(self): """Run before each test.""" super(ShellWithSessionClientTest, self).setUp() + self.useFixture(fixtures.MonkeyPatch( - 'novaclient.client.Client', - lambda *args, **kwargs: fakes.FakeSessionClient(*args, **kwargs))) + 'novaclient.client.Client', fakes.FakeSessionClient)) class GetSecgroupTest(utils.TestCase): diff --git a/novaclient/v2/assisted_volume_snapshots.py b/novaclient/v2/assisted_volume_snapshots.py new file mode 100644 index 000000000..79807897d --- /dev/null +++ b/novaclient/v2/assisted_volume_snapshots.py @@ -0,0 +1,54 @@ +# Copyright (C) 2013, Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Assisted volume snapshots - to be used by Cinder and not end users. +""" + +import json + +from novaclient import base + + +class Snapshot(base.Resource): + def __repr__(self): + return "" % self.id + + def delete(self): + """ + Delete this snapshot. + + :returns: An instance of novaclient.base.TupleWithMeta + """ + return self.manager.delete(self) + + +class AssistedSnapshotManager(base.Manager): + resource_class = Snapshot + + def create(self, volume_id, create_info): + body = {'snapshot': {'volume_id': volume_id, + 'create_info': create_info}} + return self._create('/os-assisted-volume-snapshots', body, 'snapshot') + + def delete(self, snapshot, delete_info): + """ + Delete a specified assisted volume snapshot. + + :param snapshot: an assisted volume snapshot to delete + :param delete_info: Information for snapshot deletion + :returns: An instance of novaclient.base.TupleWithMeta + """ + return self._delete("/os-assisted-volume-snapshots/%s?delete_info=%s" % + (base.getid(snapshot), json.dumps(delete_info))) diff --git a/novaclient/v2/cells.py b/novaclient/v2/cells.py new file mode 100644 index 000000000..e6a166671 --- /dev/null +++ b/novaclient/v2/cells.py @@ -0,0 +1,44 @@ +# Copyright 2013 Rackspace Hosting +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from novaclient import base + + +class Cell(base.Resource): + def __repr__(self): + return "" % self.name + + +class CellsManager(base.Manager): + resource_class = Cell + + def get(self, cell_name): + """ + Get a cell. + + :param cell_name: Name of the :class:`Cell` to get. + :rtype: :class:`Cell` + """ + return self._get("/os-cells/%s" % cell_name, "cell") + + def capacities(self, cell_name=None): + """ + Get capacities for a cell. + + :param cell_name: Name of the :class:`Cell` to get capacities for. + :rtype: :class:`Cell` + """ + path = ["%s/capacities" % cell_name, "capacities"][cell_name is None] + return self._get("/os-cells/%s" % path, "cell") diff --git a/novaclient/v2/client.py b/novaclient/v2/client.py index 93c3f4069..961e805c0 100644 --- a/novaclient/v2/client.py +++ b/novaclient/v2/client.py @@ -22,9 +22,12 @@ from novaclient import exceptions from novaclient.i18n import _LE from novaclient.v2 import agents from novaclient.v2 import aggregates +from novaclient.v2 import assisted_volume_snapshots from novaclient.v2 import availability_zones +from novaclient.v2 import cells from novaclient.v2 import certs from novaclient.v2 import cloudpipe +from novaclient.v2 import contrib from novaclient.v2 import fixed_ips from novaclient.v2 import flavor_access from novaclient.v2 import flavors @@ -36,14 +39,18 @@ from novaclient.v2 import fping from novaclient.v2 import hosts from novaclient.v2 import hypervisors from novaclient.v2 import images +from novaclient.v2 import instance_action from novaclient.v2 import keypairs from novaclient.v2 import limits +from novaclient.v2 import list_extensions +from novaclient.v2 import migrations from novaclient.v2 import networks from novaclient.v2 import quota_classes from novaclient.v2 import quotas from novaclient.v2 import security_group_default_rules from novaclient.v2 import security_group_rules from novaclient.v2 import security_groups +from novaclient.v2 import server_external_events from novaclient.v2 import server_groups from novaclient.v2 import server_migrations from novaclient.v2 import servers @@ -172,16 +179,36 @@ class Client(object): self.server_migrations = \ server_migrations.ServerMigrationsManager(self) + # V2.0 extensions: + # NOTE(andreykurilin): baremetal and tenant_networks extensions are + # deprecated now, which is why they are not initialized by default. + self.assisted_volume_snapshots = \ + assisted_volume_snapshots.AssistedSnapshotManager(self) + self.cells = cells.CellsManager(self) + self.instance_action = instance_action.InstanceActionManager(self) + self.list_extensions = list_extensions.ListExtManager(self) + self.migrations = migrations.MigrationManager(self) + self.server_external_events = \ + server_external_events.ServerExternalEventManager(self) + + self.logger = logger or logging.getLogger(__name__) + # Add in any extensions... if extensions: for extension in extensions: + # do not import extensions from contrib directory twice. + if extension.name in contrib.V2_0_EXTENSIONS: + # NOTE(andreykurilin): this message looks more like + # warning or note, but it is not critical, so let's do + # not flood "warning" logging level and use just debug.. + self.logger.debug("Nova 2.0 extenstion '%s' is auto-loaded" + " by default. You do not need to specify" + " it manually.", extension.name) + continue if extension.manager_class: setattr(self, extension.name, extension.manager_class(self)) - if not logger: - logger = logging.getLogger(__name__) - self.client = client._construct_http_client( username=username, password=password, @@ -208,7 +235,7 @@ class Client(object): session=session, auth=auth, api_version=api_version, - logger=logger, + logger=self.logger, **kwargs) @property diff --git a/novaclient/v2/contrib/__init__.py b/novaclient/v2/contrib/__init__.py index e69de29bb..b419ae0c2 100644 --- a/novaclient/v2/contrib/__init__.py +++ b/novaclient/v2/contrib/__init__.py @@ -0,0 +1,51 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import inspect +import warnings + +from novaclient.i18n import _LW + +# NOTE(andreykurilin): "baremetal" and "tenant_networks" extensions excluded +# here deliberately. They were deprecated separately from deprecation +# extension mechanism and I prefer to not auto-load them by default +# (V2_0_EXTENSIONS is designed for such behaviour). +V2_0_EXTENSIONS = { + 'assisted_volume_snapshots': + 'novaclient.v2.assisted_volume_snapshots', + 'cells': 'novaclient.v2.cells', + 'instance_action': 'novaclient.v2.instance_action', + 'list_extensions': 'novaclient.v2.list_extensions', + 'migrations': 'novaclient.v2.migrations', + 'server_external_events': 'novaclient.v2.server_external_events', +} + + +def warn(alternative=True): + """Prints warning msg for contrib modules.""" + frm = inspect.stack()[1] + module_name = inspect.getmodule(frm[0]).__name__ + if module_name.startswith("novaclient.v2.contrib."): + msg = (_LW("Module `%s` is deprecated as of OpenStack Ocata") % + module_name) + if alternative: + new_module_name = module_name.replace("contrib.", "") + msg += _LW(" in favor of `%s`") % new_module_name + + msg += (_LW(" and will be removed after OpenStack Pike.")) + + if not alternative: + msg += _LW(" All shell commands were moved to " + "`novaclient.v2.shell` and will be automatically " + "loaded.") + + warnings.warn(msg) diff --git a/novaclient/v2/contrib/assisted_volume_snapshots.py b/novaclient/v2/contrib/assisted_volume_snapshots.py index a6c6a163d..95b5073a2 100644 --- a/novaclient/v2/contrib/assisted_volume_snapshots.py +++ b/novaclient/v2/contrib/assisted_volume_snapshots.py @@ -16,42 +16,14 @@ Assisted volume snapshots - to be used by Cinder and not end users. """ -import json - -from novaclient import base +from novaclient.v2 import assisted_volume_snapshots +from novaclient.v2 import contrib -class Snapshot(base.Resource): - def __repr__(self): - return "" % self.id - - def delete(self): - """ - Delete this snapshot. - - :returns: An instance of novaclient.base.TupleWithMeta - """ - return self.manager.delete(self) - - -class AssistedSnapshotManager(base.Manager): - resource_class = Snapshot - - def create(self, volume_id, create_info): - body = {'snapshot': {'volume_id': volume_id, - 'create_info': create_info}} - return self._create('/os-assisted-volume-snapshots', body, 'snapshot') - - def delete(self, snapshot, delete_info): - """ - Delete a specified assisted volume snapshot. - - :param snapshot: an assisted volume snapshot to delete - :param delete_info: Information for snapshot deletion - :returns: An instance of novaclient.base.TupleWithMeta - """ - return self._delete("/os-assisted-volume-snapshots/%s?delete_info=%s" % - (base.getid(snapshot), json.dumps(delete_info))) +AssistedSnapshotManager = assisted_volume_snapshots.AssistedSnapshotManager +Snapshot = assisted_volume_snapshots.Snapshot manager_class = AssistedSnapshotManager name = 'assisted_volume_snapshots' + +contrib.warn() diff --git a/novaclient/v2/contrib/cells.py b/novaclient/v2/contrib/cells.py index e5de8e5ee..a689d00a9 100644 --- a/novaclient/v2/contrib/cells.py +++ b/novaclient/v2/contrib/cells.py @@ -13,61 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. -from novaclient import base -from novaclient.i18n import _ -from novaclient import utils +from novaclient.v2 import cells +from novaclient.v2 import contrib -class Cell(base.Resource): - def __repr__(self): - return "" % self.name +Cell = cells.Cell +CellsManager = cells.CellsManager - -class CellsManager(base.Manager): - resource_class = Cell - - def get(self, cell_name): - """ - Get a cell. - - :param cell_name: Name of the :class:`Cell` to get. - :rtype: :class:`Cell` - """ - return self._get("/os-cells/%s" % cell_name, "cell") - - def capacities(self, cell_name=None): - """ - Get capacities for a cell. - - :param cell_name: Name of the :class:`Cell` to get capacities for. - :rtype: :class:`Cell` - """ - path = ["%s/capacities" % cell_name, "capacities"][cell_name is None] - return self._get("/os-cells/%s" % path, "cell") - - -@utils.arg( - 'cell', - metavar='', - help=_('Name of the cell.')) -def do_cell_show(cs, args): - """Show details of a given cell.""" - cell = cs.cells.get(args.cell) - utils.print_dict(cell._info) - - -@utils.arg( - '--cell', - metavar='', - help=_("Name of the cell to get the capacities."), - default=None) -def do_cell_capacities(cs, args): - """Get cell capacities for all cells or a given cell.""" - cell = cs.cells.capacities(args.cell) - print(_("Ram Available: %s MB") % cell.capacities['ram_free']['total_mb']) - utils.print_dict(cell.capacities['ram_free']['units_by_mb'], - dict_property='Ram(MB)', dict_value="Units") - print(_("\nDisk Available: %s MB") % - cell.capacities['disk_free']['total_mb']) - utils.print_dict(cell.capacities['disk_free']['units_by_mb'], - dict_property='Disk(MB)', dict_value="Units") +contrib.warn() diff --git a/novaclient/v2/contrib/deferred_delete.py b/novaclient/v2/contrib/deferred_delete.py index 3d49104aa..bd5798ac7 100644 --- a/novaclient/v2/contrib/deferred_delete.py +++ b/novaclient/v2/contrib/deferred_delete.py @@ -12,16 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from novaclient import utils +from novaclient.v2 import contrib - -@utils.arg('server', metavar='', help='Name or ID of server.') -def do_force_delete(cs, args): - """Force delete a server.""" - utils.find_resource(cs.servers, args.server).force_delete() - - -@utils.arg('server', metavar='', help='Name or ID of server.') -def do_restore(cs, args): - """Restore a soft-deleted server.""" - utils.find_resource(cs.servers, args.server, deleted=True).restore() +contrib.warn(alternative=False) diff --git a/novaclient/v2/contrib/host_evacuate.py b/novaclient/v2/contrib/host_evacuate.py index 22055656b..b5305bcb7 100644 --- a/novaclient/v2/contrib/host_evacuate.py +++ b/novaclient/v2/contrib/host_evacuate.py @@ -13,73 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from novaclient import api_versions -from novaclient import base -from novaclient.i18n import _ -from novaclient import utils +from novaclient.v2 import contrib +from novaclient.v2 import shell -class EvacuateHostResponse(base.Resource): - pass +EvacuateHostResponse = shell.EvacuateHostResponse - -def _server_evacuate(cs, server, args): - success = True - error_message = "" - try: - if api_versions.APIVersion("2.29") <= cs.api_version: - # if microversion >= 2.29 - force = getattr(args, 'force', None) - cs.servers.evacuate(server=server['uuid'], host=args.target_host, - force=force) - elif api_versions.APIVersion("2.14") <= cs.api_version: - # if microversion 2.14 - 2.28 - cs.servers.evacuate(server=server['uuid'], host=args.target_host) - else: - # else microversion 2.0 - 2.13 - on_shared_storage = getattr(args, 'on_shared_storage', None) - cs.servers.evacuate(server=server['uuid'], - host=args.target_host, - on_shared_storage=on_shared_storage) - except Exception as e: - success = False - error_message = _("Error while evacuating instance: %s") % e - return EvacuateHostResponse(base.Manager, - {"server_uuid": server['uuid'], - "evacuate_accepted": success, - "error_message": error_message}) - - -@utils.arg('host', metavar='', help='Name of host.') -@utils.arg( - '--target_host', - metavar='', - default=None, - help=_('Name of target host. If no host is specified the scheduler will ' - 'select a target.')) -@utils.arg( - '--on-shared-storage', - dest='on_shared_storage', - action="store_true", - default=False, - help=_('Specifies whether all instances files are on shared storage'), - start_version='2.0', - end_version='2.13') -@utils.arg( - '--force', - dest='force', - action='store_true', - default=False, - help=_('Force to not verify the scheduler if a host is provided.'), - start_version='2.29') -def do_host_evacuate(cs, args): - """Evacuate all instances from failed host.""" - hypervisors = cs.hypervisors.search(args.host, servers=True) - response = [] - for hyper in hypervisors: - if hasattr(hyper, 'servers'): - for server in hyper.servers: - response.append(_server_evacuate(cs, server, args)) - - utils.print_list(response, - ["Server UUID", "Evacuate Accepted", "Error Message"]) +contrib.warn(alternative=False) diff --git a/novaclient/v2/contrib/host_evacuate_live.py b/novaclient/v2/contrib/host_evacuate_live.py index f55dda1a4..7e27d7359 100644 --- a/novaclient/v2/contrib/host_evacuate_live.py +++ b/novaclient/v2/contrib/host_evacuate_live.py @@ -13,87 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from novaclient.i18n import _ -from novaclient import utils +from novaclient.v2 import contrib - -def _server_live_migrate(cs, server, args): - class HostEvacuateLiveResponse(object): - def __init__(self, server_uuid, live_migration_accepted, - error_message): - self.server_uuid = server_uuid - self.live_migration_accepted = live_migration_accepted - self.error_message = error_message - success = True - error_message = "" - update_kwargs = {} - try: - # API >= 2.30 - if 'force' in args and args.force: - update_kwargs['force'] = args.force - # API 2.0->2.24 - if 'disk_over_commit' in args: - update_kwargs['disk_over_commit'] = args.disk_over_commit - cs.servers.live_migrate(server['uuid'], args.target_host, - args.block_migrate, **update_kwargs) - except Exception as e: - success = False - error_message = _("Error while live migrating instance: %s") % e - return HostEvacuateLiveResponse(server['uuid'], - success, - error_message) - - -@utils.arg('host', metavar='', help='Name of host.') -@utils.arg( - '--target-host', - metavar='', - default=None, - help=_('Name of target host.')) -@utils.arg( - '--block-migrate', - action='store_true', - default=False, - help=_('Enable block migration. (Default=False)'), - start_version="2.0", end_version="2.24") -@utils.arg( - '--block-migrate', - action='store_true', - default="auto", - help=_('Enable block migration. (Default=auto)'), - start_version="2.25") -@utils.arg( - '--disk-over-commit', - action='store_true', - default=False, - help=_('Enable disk overcommit.'), - start_version="2.0", end_version="2.24") -@utils.arg( - '--max-servers', - type=int, - dest='max_servers', - metavar='', - help='Maximum number of servers to live migrate simultaneously') -@utils.arg( - '--force', - dest='force', - action='store_true', - default=False, - help=_('Force to not verify the scheduler if a host is provided.'), - start_version='2.30') -def do_host_evacuate_live(cs, args): - """Live migrate all instances of the specified host - to other available hosts. - """ - hypervisors = cs.hypervisors.search(args.host, servers=True) - response = [] - migrating = 0 - for hyper in hypervisors: - for server in getattr(hyper, 'servers', []): - response.append(_server_live_migrate(cs, server, args)) - migrating = migrating + 1 - if args.max_servers is not None and migrating >= args.max_servers: - break - - utils.print_list(response, ["Server UUID", "Live Migration Accepted", - "Error Message"]) +contrib.warn(alternative=False) diff --git a/novaclient/v2/contrib/host_servers_migrate.py b/novaclient/v2/contrib/host_servers_migrate.py index 96e606526..e88da876b 100644 --- a/novaclient/v2/contrib/host_servers_migrate.py +++ b/novaclient/v2/contrib/host_servers_migrate.py @@ -13,40 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from novaclient import base -from novaclient.i18n import _ -from novaclient import utils +from novaclient.v2 import contrib +from novaclient.v2 import shell -class HostServersMigrateResponse(base.Resource): - pass +HostServersMigrateResponse = shell.HostServersMigrateResponse - -def _server_migrate(cs, server): - success = True - error_message = "" - try: - cs.servers.migrate(server['uuid']) - except Exception as e: - success = False - error_message = _("Error while migrating instance: %s") % e - return HostServersMigrateResponse(base.Manager, - {"server_uuid": server['uuid'], - "migration_accepted": success, - "error_message": error_message}) - - -@utils.arg('host', metavar='', help='Name of host.') -def do_host_servers_migrate(cs, args): - """Cold migrate all instances off the specified host to other available - hosts. - """ - hypervisors = cs.hypervisors.search(args.host, servers=True) - response = [] - for hyper in hypervisors: - if hasattr(hyper, 'servers'): - for server in hyper.servers: - response.append(_server_migrate(cs, server)) - - utils.print_list(response, - ["Server UUID", "Migration Accepted", "Error Message"]) +contrib.warn(alternative=False) diff --git a/novaclient/v2/contrib/instance_action.py b/novaclient/v2/contrib/instance_action.py index 05b847c2b..a90b99bd5 100644 --- a/novaclient/v2/contrib/instance_action.py +++ b/novaclient/v2/contrib/instance_action.py @@ -13,81 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -import pprint - -from novaclient import api_versions -from novaclient import base -from novaclient.i18n import _ -from novaclient import utils -from novaclient.v2 import shell +from novaclient.v2 import contrib +from novaclient.v2 import instance_action -class InstanceActionManager(base.ManagerWithFind): - resource_class = base.Resource +InstanceActionManager = instance_action.InstanceActionManager - def get(self, server, request_id): - """ - Get details of an action performed on an instance. - - :param request_id: The request_id of the action to get. - """ - return self._get("/servers/%s/os-instance-actions/%s" % - (base.getid(server), request_id), 'instanceAction') - - def list(self, server): - """ - Get a list of actions performed on a server. - """ - return self._list('/servers/%s/os-instance-actions' % - base.getid(server), 'instanceActions') - - -@utils.arg( - 'server', - metavar='', - help=_('Name or UUID of the server to show actions for.'), - start_version="2.0", end_version="2.20") -@utils.arg( - 'server', - metavar='', - help=_('Name or UUID of the server to show actions for. Only UUID can be ' - 'used to show actions for a deleted server.'), - start_version="2.21") -@utils.arg( - 'request_id', - metavar='', - help=_('Request ID of the action to get.')) -def do_instance_action(cs, args): - """Show an action.""" - if cs.api_version < api_versions.APIVersion("2.21"): - server = shell._find_server(cs, args.server) - else: - server = shell._find_server(cs, args.server, raise_if_notfound=False) - action_resource = cs.instance_action.get(server, args.request_id) - action = action_resource._info - if 'events' in action: - action['events'] = pprint.pformat(action['events']) - utils.print_dict(action) - - -@utils.arg( - 'server', - metavar='', - help=_('Name or UUID of the server to list actions for.'), - start_version="2.0", end_version="2.20") -@utils.arg( - 'server', - metavar='', - help=_('Name or UUID of the server to list actions for. Only UUID can be ' - 'used to list actions on a deleted server.'), - start_version="2.21") -def do_instance_action_list(cs, args): - """List actions on a server.""" - if cs.api_version < api_versions.APIVersion("2.21"): - server = shell._find_server(cs, args.server) - else: - server = shell._find_server(cs, args.server, raise_if_notfound=False) - actions = cs.instance_action.list(server) - utils.print_list(actions, - ['Action', 'Request_ID', 'Message', 'Start_Time'], - sortby_index=3) +contrib.warn() diff --git a/novaclient/v2/contrib/list_extensions.py b/novaclient/v2/contrib/list_extensions.py index 7eb9f16c8..9177fa0cd 100644 --- a/novaclient/v2/contrib/list_extensions.py +++ b/novaclient/v2/contrib/list_extensions.py @@ -13,34 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. -from novaclient import base -from novaclient import utils +from novaclient.v2 import contrib +from novaclient.v2 import list_extensions -class ListExtResource(base.Resource): - @property - def summary(self): - descr = self.description.strip() - if not descr: - return '??' - lines = descr.split("\n") - if len(lines) == 1: - return lines[0] - else: - return lines[0] + "..." +ListExtResource = list_extensions.ListExtResource +ListExtManager = list_extensions.ListExtResource - -class ListExtManager(base.Manager): - resource_class = ListExtResource - - def show_all(self): - return self._list("/extensions", 'extensions') - - -def do_list_extensions(client, _args): - """ - List all the os-api extensions that are available. - """ - extensions = client.list_extensions.show_all() - fields = ["Name", "Summary", "Alias", "Updated"] - utils.print_list(extensions, fields) +contrib.warn() diff --git a/novaclient/v2/contrib/metadata_extensions.py b/novaclient/v2/contrib/metadata_extensions.py index 8d2d22f89..eb6fbd224 100644 --- a/novaclient/v2/contrib/metadata_extensions.py +++ b/novaclient/v2/contrib/metadata_extensions.py @@ -13,35 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -from novaclient.i18n import _ -from novaclient import utils -from novaclient.v2 import shell +from novaclient.v2 import contrib -@utils.arg( - 'host', - metavar='', - help=_('Name of host.')) -@utils.arg( - 'action', - metavar='', - choices=['set', 'delete'], - help=_("Actions: 'set' or 'delete'")) -@utils.arg( - 'metadata', - metavar='', - nargs='+', - action='append', - default=[], - help=_('Metadata to set or delete (only key is necessary on delete)')) -def do_host_meta(cs, args): - """Set or Delete metadata on all instances of a host.""" - hypervisors = cs.hypervisors.search(args.host, servers=True) - for hyper in hypervisors: - metadata = shell._extract_metadata(args) - if hasattr(hyper, 'servers'): - for server in hyper.servers: - if args.action == 'set': - cs.servers.set_meta(server['uuid'], metadata) - elif args.action == 'delete': - cs.servers.delete_meta(server['uuid'], metadata.keys()) +contrib.warn(alternative=False) diff --git a/novaclient/v2/contrib/migrations.py b/novaclient/v2/contrib/migrations.py index 47856444d..909b569c6 100644 --- a/novaclient/v2/contrib/migrations.py +++ b/novaclient/v2/contrib/migrations.py @@ -14,86 +14,11 @@ migration interface """ -from six.moves.urllib import parse - -from novaclient import api_versions -from novaclient import base -from novaclient.i18n import _ -from novaclient import utils +from novaclient.v2 import contrib +from novaclient.v2 import migrations -class Migration(base.Resource): - def __repr__(self): - return "" % self.id +Migration = migrations.Migration +MigrationManager = migrations.MigrationManager - -class MigrationManager(base.ManagerWithFind): - resource_class = Migration - - def list(self, host=None, status=None, cell_name=None): - """ - Get a list of migrations. - :param host: (optional) filter migrations by host name. - :param status: (optional) filter migrations by status. - :param cell_name: (optional) filter migrations for a cell. - """ - opts = {} - if host: - opts['host'] = host - if status: - opts['status'] = status - if cell_name: - opts['cell_name'] = cell_name - - # Transform the dict to a sequence of two-element tuples in fixed - # order, then the encoded string will be consistent in Python 2&3. - new_opts = sorted(opts.items(), key=lambda x: x[0]) - - query_string = "?%s" % parse.urlencode(new_opts) if new_opts else "" - - return self._list("/os-migrations%s" % query_string, "migrations") - - -@utils.arg( - '--host', - dest='host', - metavar='', - help=_('Fetch migrations for the given host.')) -@utils.arg( - '--status', - dest='status', - metavar='', - help=_('Fetch migrations for the given status.')) -@utils.arg( - '--cell_name', - dest='cell_name', - metavar='', - help=_('Fetch migrations for the given cell_name.')) -def do_migration_list(cs, args): - """Print a list of migrations.""" - migrations = cs.migrations.list(args.host, args.status, args.cell_name) - _print_migrations(cs, migrations) - - -def _print_migrations(cs, migrations): - fields = ['Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', - 'Dest Host', 'Status', 'Instance UUID', 'Old Flavor', - 'New Flavor', 'Created At', 'Updated At'] - - def old_flavor(migration): - return migration.old_instance_type_id - - def new_flavor(migration): - return migration.new_instance_type_id - - def migration_type(migration): - return migration.migration_type - - formatters = {'Old Flavor': old_flavor, 'New Flavor': new_flavor} - - if cs.api_version >= api_versions.APIVersion("2.23"): - fields.insert(0, "Id") - fields.append("Type") - formatters.update({"Type": migration_type}) - - utils.print_list(migrations, fields, formatters) +contrib.warn() diff --git a/novaclient/v2/contrib/server_external_events.py b/novaclient/v2/contrib/server_external_events.py index a45914b55..bbd1b032f 100644 --- a/novaclient/v2/contrib/server_external_events.py +++ b/novaclient/v2/contrib/server_external_events.py @@ -16,28 +16,14 @@ External event triggering for servers, not to be used by users. """ -from novaclient import base +from novaclient.v2 import contrib +from novaclient.v2 import server_external_events -class Event(base.Resource): - def __repr__(self): - return "" % self.name - - -class ServerExternalEventManager(base.Manager): - resource_class = Event - - def create(self, events): - """Create one or more server events. - - :param:events: A list of dictionaries containing 'server_uuid', 'name', - 'status', and 'tag' (which may be absent) - """ - - body = {'events': events} - return self._create('/os-server-external-events', body, 'events', - return_raw=True) - +Event = server_external_events.Event +ServerExternalEventManager = server_external_events.ServerExternalEventManager manager_class = ServerExternalEventManager name = 'server_external_events' + +contrib.warn() diff --git a/novaclient/v2/instance_action.py b/novaclient/v2/instance_action.py new file mode 100644 index 000000000..5531d35fc --- /dev/null +++ b/novaclient/v2/instance_action.py @@ -0,0 +1,40 @@ +# Copyright 2013 Rackspace Hosting +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from novaclient import base + + +class InstanceAction(base.Resource): + pass + + +class InstanceActionManager(base.ManagerWithFind): + resource_class = InstanceAction + + def get(self, server, request_id): + """ + Get details of an action performed on an instance. + + :param request_id: The request_id of the action to get. + """ + return self._get("/servers/%s/os-instance-actions/%s" % + (base.getid(server), request_id), 'instanceAction') + + def list(self, server): + """ + Get a list of actions performed on a server. + """ + return self._list('/servers/%s/os-instance-actions' % + base.getid(server), 'instanceActions') diff --git a/novaclient/v2/list_extensions.py b/novaclient/v2/list_extensions.py new file mode 100644 index 000000000..faeead601 --- /dev/null +++ b/novaclient/v2/list_extensions.py @@ -0,0 +1,36 @@ +# Copyright 2011 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from novaclient import base + + +class ListExtResource(base.Resource): + @property + def summary(self): + descr = self.description.strip() + if not descr: + return '??' + lines = descr.split("\n") + if len(lines) == 1: + return lines[0] + else: + return lines[0] + "..." + + +class ListExtManager(base.Manager): + resource_class = ListExtResource + + def show_all(self): + return self._list("/extensions", 'extensions') diff --git a/novaclient/v2/migrations.py b/novaclient/v2/migrations.py new file mode 100644 index 000000000..51b5988b9 --- /dev/null +++ b/novaclient/v2/migrations.py @@ -0,0 +1,51 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +migration interface +""" + +from six.moves.urllib import parse + +from novaclient import base + + +class Migration(base.Resource): + def __repr__(self): + return "" % self.id + + +class MigrationManager(base.ManagerWithFind): + resource_class = Migration + + def list(self, host=None, status=None, cell_name=None): + """ + Get a list of migrations. + :param host: (optional) filter migrations by host name. + :param status: (optional) filter migrations by status. + :param cell_name: (optional) filter migrations for a cell. + """ + opts = {} + if host: + opts['host'] = host + if status: + opts['status'] = status + if cell_name: + opts['cell_name'] = cell_name + + # Transform the dict to a sequence of two-element tuples in fixed + # order, then the encoded string will be consistent in Python 2&3. + new_opts = sorted(opts.items(), key=lambda x: x[0]) + + query_string = "?%s" % parse.urlencode(new_opts) if new_opts else "" + + 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 000000000..7c2506a2c --- /dev/null +++ b/novaclient/v2/server_external_events.py @@ -0,0 +1,39 @@ +# Copyright (C) 2014, Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +External event triggering for servers, not to be used by users. +""" + +from novaclient import base + + +class Event(base.Resource): + def __repr__(self): + return "" % self.name + + +class ServerExternalEventManager(base.Manager): + resource_class = Event + + def create(self, events): + """Create one or more server events. + + :param:events: A list of dictionaries containing 'server_uuid', 'name', + 'status', and 'tag' (which may be absent) + """ + + body = {'events': events} + return self._create('/os-server-external-events', body, 'events', + return_raw=True) diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index db72b6bc3..b621025a2 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -26,6 +26,7 @@ import getpass import locale import logging import os +import pprint import sys import time @@ -5185,3 +5186,359 @@ def do_server_tag_delete_all(cs, args): """Delete all tags from a server.""" server = _find_server(cs, args.server) server.delete_all_tags() + + +@utils.arg( + 'cell', + metavar='', + help=_('Name of the cell.')) +def do_cell_show(cs, args): + """Show details of a given cell.""" + cell = cs.cells.get(args.cell) + utils.print_dict(cell._info) + + +@utils.arg( + '--cell', + metavar='', + help=_("Name of the cell to get the capacities."), + default=None) +def do_cell_capacities(cs, args): + """Get cell capacities for all cells or a given cell.""" + cell = cs.cells.capacities(args.cell) + print(_("Ram Available: %s MB") % cell.capacities['ram_free']['total_mb']) + utils.print_dict(cell.capacities['ram_free']['units_by_mb'], + dict_property='Ram(MB)', dict_value="Units") + print(_("\nDisk Available: %s MB") % + cell.capacities['disk_free']['total_mb']) + utils.print_dict(cell.capacities['disk_free']['units_by_mb'], + dict_property='Disk(MB)', dict_value="Units") + + +@utils.arg('server', metavar='', help='Name or ID of server.') +def do_force_delete(cs, args): + """Force delete a server.""" + utils.find_resource(cs.servers, args.server).force_delete() + + +@utils.arg('server', metavar='', help='Name or ID of server.') +def do_restore(cs, args): + """Restore a soft-deleted server.""" + utils.find_resource(cs.servers, args.server, deleted=True).restore() + + +class EvacuateHostResponse(base.Resource): + pass + + +def _server_evacuate(cs, server, args): + success = True + error_message = "" + try: + if api_versions.APIVersion("2.29") <= cs.api_version: + # if microversion >= 2.29 + force = getattr(args, 'force', None) + cs.servers.evacuate(server=server['uuid'], host=args.target_host, + force=force) + elif api_versions.APIVersion("2.14") <= cs.api_version: + # if microversion 2.14 - 2.28 + cs.servers.evacuate(server=server['uuid'], host=args.target_host) + else: + # else microversion 2.0 - 2.13 + on_shared_storage = getattr(args, 'on_shared_storage', None) + cs.servers.evacuate(server=server['uuid'], + host=args.target_host, + on_shared_storage=on_shared_storage) + except Exception as e: + success = False + error_message = _("Error while evacuating instance: %s") % e + return EvacuateHostResponse(base.Manager, {"server_uuid": server['uuid'], + "evacuate_accepted": success, + "error_message": error_message}) + + +@utils.arg('host', metavar='', help='Name of host.') +@utils.arg( + '--target_host', + metavar='', + default=None, + help=_('Name of target host. If no host is specified the scheduler will ' + 'select a target.')) +@utils.arg( + '--on-shared-storage', + dest='on_shared_storage', + action="store_true", + default=False, + help=_('Specifies whether all instances files are on shared storage'), + start_version='2.0', + end_version='2.13') +@utils.arg( + '--force', + dest='force', + action='store_true', + default=False, + help=_('Force to not verify the scheduler if a host is provided.'), + start_version='2.29') +def do_host_evacuate(cs, args): + """Evacuate all instances from failed host.""" + + hypervisors = cs.hypervisors.search(args.host, servers=True) + response = [] + for hyper in hypervisors: + if hasattr(hyper, 'servers'): + for server in hyper.servers: + response.append(_server_evacuate(cs, server, args)) + + utils.print_list(response, + ["Server UUID", "Evacuate Accepted", "Error Message"]) + + +def _server_live_migrate(cs, server, args): + class HostEvacuateLiveResponse(object): + def __init__(self, server_uuid, live_migration_accepted, + error_message): + self.server_uuid = server_uuid + self.live_migration_accepted = live_migration_accepted + self.error_message = error_message + success = True + error_message = "" + update_kwargs = {} + try: + # API >= 2.30 + if 'force' in args and args.force: + update_kwargs['force'] = args.force + # API 2.0->2.24 + if 'disk_over_commit' in args: + update_kwargs['disk_over_commit'] = args.disk_over_commit + cs.servers.live_migrate(server['uuid'], args.target_host, + args.block_migrate, **update_kwargs) + except Exception as e: + success = False + error_message = _("Error while live migrating instance: %s") % e + return HostEvacuateLiveResponse(server['uuid'], + success, + error_message) + + +@utils.arg('host', metavar='', help='Name of host.') +@utils.arg( + '--target-host', + metavar='', + default=None, + help=_('Name of target host.')) +@utils.arg( + '--block-migrate', + action='store_true', + default=False, + help=_('Enable block migration. (Default=False)'), + start_version="2.0", end_version="2.24") +@utils.arg( + '--block-migrate', + action='store_true', + default="auto", + help=_('Enable block migration. (Default=auto)'), + start_version="2.25") +@utils.arg( + '--disk-over-commit', + action='store_true', + default=False, + help=_('Enable disk overcommit.'), + start_version="2.0", end_version="2.24") +@utils.arg( + '--max-servers', + type=int, + dest='max_servers', + metavar='', + help='Maximum number of servers to live migrate simultaneously') +@utils.arg( + '--force', + dest='force', + action='store_true', + default=False, + help=_('Force to not verify the scheduler if a host is provided.'), + start_version='2.30') +def do_host_evacuate_live(cs, args): + """Live migrate all instances of the specified host + to other available hosts. + """ + hypervisors = cs.hypervisors.search(args.host, servers=True) + response = [] + migrating = 0 + for hyper in hypervisors: + for server in getattr(hyper, 'servers', []): + response.append(_server_live_migrate(cs, server, args)) + migrating += 1 + if args.max_servers is not None and migrating >= args.max_servers: + break + + utils.print_list(response, ["Server UUID", "Live Migration Accepted", + "Error Message"]) + + +class HostServersMigrateResponse(base.Resource): + pass + + +def _server_migrate(cs, server): + success = True + error_message = "" + try: + cs.servers.migrate(server['uuid']) + except Exception as e: + success = False + error_message = _("Error while migrating instance: %s") % e + return HostServersMigrateResponse(base.Manager, + {"server_uuid": server['uuid'], + "migration_accepted": success, + "error_message": error_message}) + + +@utils.arg('host', metavar='', help='Name of host.') +def do_host_servers_migrate(cs, args): + """Cold migrate all instances off the specified host to other available + hosts. + """ + + hypervisors = cs.hypervisors.search(args.host, servers=True) + response = [] + for hyper in hypervisors: + if hasattr(hyper, 'servers'): + for server in hyper.servers: + response.append(_server_migrate(cs, server)) + + utils.print_list(response, + ["Server UUID", "Migration Accepted", "Error Message"]) + + +@utils.arg( + 'server', + metavar='', + help=_('Name or UUID of the server to show actions for.'), + start_version="2.0", end_version="2.20") +@utils.arg( + 'server', + metavar='', + help=_('Name or UUID of the server to show actions for. Only UUID can be ' + 'used to show actions for a deleted server.'), + start_version="2.21") +@utils.arg( + 'request_id', + metavar='', + help=_('Request ID of the action to get.')) +def do_instance_action(cs, args): + """Show an action.""" + if cs.api_version < api_versions.APIVersion("2.21"): + server = _find_server(cs, args.server) + else: + server = _find_server(cs, args.server, raise_if_notfound=False) + action_resource = cs.instance_action.get(server, args.request_id) + action = action_resource._info + if 'events' in action: + action['events'] = pprint.pformat(action['events']) + utils.print_dict(action) + + +@utils.arg( + 'server', + metavar='', + help=_('Name or UUID of the server to list actions for.'), + start_version="2.0", end_version="2.20") +@utils.arg( + 'server', + metavar='', + help=_('Name or UUID of the server to list actions for. Only UUID can be ' + 'used to list actions on a deleted server.'), + start_version="2.21") +def do_instance_action_list(cs, args): + """List actions on a server.""" + if cs.api_version < api_versions.APIVersion("2.21"): + server = _find_server(cs, args.server) + else: + server = _find_server(cs, args.server, raise_if_notfound=False) + actions = cs.instance_action.list(server) + utils.print_list(actions, + ['Action', 'Request_ID', 'Message', 'Start_Time'], + sortby_index=3) + + +def do_list_extensions(cs, _args): + """ + List all the os-api extensions that are available. + """ + extensions = cs.list_extensions.show_all() + fields = ["Name", "Summary", "Alias", "Updated"] + utils.print_list(extensions, fields) + + +@utils.arg( + 'host', + metavar='', + help=_('Name of host.')) +@utils.arg( + 'action', + metavar='', + choices=['set', 'delete'], + help=_("Actions: 'set' or 'delete'")) +@utils.arg( + 'metadata', + metavar='', + nargs='+', + action='append', + default=[], + help=_('Metadata to set or delete (only key is necessary on delete)')) +def do_host_meta(cs, args): + """Set or Delete metadata on all instances of a host.""" + hypervisors = cs.hypervisors.search(args.host, servers=True) + for hyper in hypervisors: + metadata = _extract_metadata(args) + if hasattr(hyper, 'servers'): + for server in hyper.servers: + if args.action == 'set': + cs.servers.set_meta(server['uuid'], metadata) + elif args.action == 'delete': + cs.servers.delete_meta(server['uuid'], metadata.keys()) + + +def _print_migrations(cs, migrations): + fields = ['Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', + 'Dest Host', 'Status', 'Instance UUID', 'Old Flavor', + 'New Flavor', 'Created At', 'Updated At'] + + def old_flavor(migration): + return migration.old_instance_type_id + + def new_flavor(migration): + return migration.new_instance_type_id + + def migration_type(migration): + return migration.migration_type + + formatters = {'Old Flavor': old_flavor, 'New Flavor': new_flavor} + + if cs.api_version >= api_versions.APIVersion("2.23"): + fields.insert(0, "Id") + fields.append("Type") + formatters.update({"Type": migration_type}) + + utils.print_list(migrations, fields, formatters) + + +@utils.arg( + '--host', + dest='host', + metavar='', + help=_('Fetch migrations for the given host.')) +@utils.arg( + '--status', + dest='status', + metavar='', + help=_('Fetch migrations for the given status.')) +@utils.arg( + '--cell_name', + dest='cell_name', + metavar='', + help=_('Fetch migrations for the given cell_name.')) +def do_migration_list(cs, args): + """Print a list of migrations.""" + migrations = cs.migrations.list(args.host, args.status, args.cell_name) + _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 000000000..66810756f --- /dev/null +++ b/releasenotes/notes/deprecate_contrib_extensions-0ec70c070b09eedb.yaml @@ -0,0 +1,21 @@ +--- +prelude: > + All extensions of API V2.0 were merged to 2.1, but NovaClient continued + to store them as a separate entities. +upgrade: + - All managers and resources from novaclient.v2.contrib submodules are moved + to appropriate submodules of novaclient.v2 (except barametal and + tenant_networks, which were deprecated previously) + - All shell commands from novaclient.v2.contrib submodules are moved to + novaclient.v2.shell module. + - novaclient.v2.client.Client imports all modules (which were located in + submodules of novaclient.v2.contrib) by-default for api version v2 + - Method novaclient.client.discover_extensions returns only barametal and + tenant_networks extensions, since they are not included by default. + - There are no modules and extensions for "deferred_delete", "host_evacuate", + "host_evacuate_live" and "metadata_extensions" anymore. Previously, they + contained only shell commands and shell module auto loads them (there is + no ability to not do it). +deprecations: + - All modules of novaclient.v2.contrib are deprecated now and will be + removed after OpenStack Pike.