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
This commit is contained in:
Andrey Kurilin 2016-11-24 19:59:13 +02:00
parent 43bbe88ac8
commit f834711d2f
35 changed files with 916 additions and 747 deletions

View File

@ -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():

View File

@ -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'}]})

View File

@ -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()

View File

@ -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()

View File

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

View File

@ -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', {})

View File

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

View File

@ -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'

View File

@ -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)

View File

@ -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'

View File

@ -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()

View File

@ -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')

View File

@ -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',

View File

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

View File

@ -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 "<Snapshot: %s>" % 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)))

44
novaclient/v2/cells.py Normal file
View File

@ -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 "<Cell: %s>" % 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")

View File

@ -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

View File

@ -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)

View File

@ -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 "<Snapshot: %s>" % 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()

View File

@ -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 "<Cell: %s>" % 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='<cell-name>',
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='<cell-name>',
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()

View File

@ -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='<server>', 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='<server>', 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)

View File

@ -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='<host>', help='Name of host.')
@utils.arg(
'--target_host',
metavar='<target_host>',
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)

View File

@ -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='<host>', help='Name of host.')
@utils.arg(
'--target-host',
metavar='<target_host>',
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='<max_servers>',
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)

View File

@ -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='<host>', 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)

View File

@ -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='<server>',
help=_('Name or UUID of the server to show actions for.'),
start_version="2.0", end_version="2.20")
@utils.arg(
'server',
metavar='<server>',
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='<request_id>',
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='<server>',
help=_('Name or UUID of the server to list actions for.'),
start_version="2.0", end_version="2.20")
@utils.arg(
'server',
metavar='<server>',
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()

View File

@ -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()

View File

@ -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='<host>',
help=_('Name of host.'))
@utils.arg(
'action',
metavar='<action>',
choices=['set', 'delete'],
help=_("Actions: 'set' or 'delete'"))
@utils.arg(
'metadata',
metavar='<key=value>',
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)

View File

@ -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 "<Migration: %s>" % 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='<host>',
help=_('Fetch migrations for the given host.'))
@utils.arg(
'--status',
dest='status',
metavar='<status>',
help=_('Fetch migrations for the given status.'))
@utils.arg(
'--cell_name',
dest='cell_name',
metavar='<cell_name>',
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()

View File

@ -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 "<Event: %s>" % 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()

View File

@ -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')

View File

@ -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')

View File

@ -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 "<Migration: %s>" % 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")

View File

@ -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 "<Event: %s>" % 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)

View File

@ -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='<cell-name>',
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='<cell-name>',
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='<server>', 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='<server>', 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='<host>', help='Name of host.')
@utils.arg(
'--target_host',
metavar='<target_host>',
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='<host>', help='Name of host.')
@utils.arg(
'--target-host',
metavar='<target_host>',
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='<max_servers>',
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='<host>', 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='<server>',
help=_('Name or UUID of the server to show actions for.'),
start_version="2.0", end_version="2.20")
@utils.arg(
'server',
metavar='<server>',
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='<request_id>',
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='<server>',
help=_('Name or UUID of the server to list actions for.'),
start_version="2.0", end_version="2.20")
@utils.arg(
'server',
metavar='<server>',
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='<host>',
help=_('Name of host.'))
@utils.arg(
'action',
metavar='<action>',
choices=['set', 'delete'],
help=_("Actions: 'set' or 'delete'"))
@utils.arg(
'metadata',
metavar='<key=value>',
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='<host>',
help=_('Fetch migrations for the given host.'))
@utils.arg(
'--status',
dest='status',
metavar='<status>',
help=_('Fetch migrations for the given status.'))
@utils.arg(
'--cell_name',
dest='cell_name',
metavar='<cell_name>',
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)

View File

@ -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.