Prepare to use openstacksdk instead of novaclient
Prepare Watcher to use openstacksdk. This patch introduces a new function in the clients module to create a openstacksdk connection either using a user token, an existing keystone session or a new keystone session. Additionally, it adds a method to the newly introduced wrapper classes to create nova server, hypervisors, flavors, etc from the objects returned by the openstacksdk compute proxy. This patch also deprecates the nova_client configuration options and adds keystoneauth adapter configuration options into the nova conf group, since that is required by the openstacksdk to create a Connection. Assisted-By: claude-code (claude-sonnet-4.5) Change-Id: I7e297419243f16548a54e332609bbcbd19c3d758 Signed-off-by: jgilaber <jgilaber@redhat.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
Connection settings for Nova should be added
|
||||
directly to the [nova] section of the configuration now, instead of nova_client.
|
||||
deprecations:
|
||||
- |
|
||||
The nova_client configuration options are deprecated and will be removed
|
||||
in a future release. Operators should migrate to the keystoneauth adapter
|
||||
configuration options in the [nova] configuration group.
|
||||
other:
|
||||
- |
|
||||
Added support for openstacksdk as an alternative to novaclient.
|
||||
@@ -32,6 +32,7 @@ pbr>=3.1.1 # Apache-2.0
|
||||
pecan>=1.3.2 # BSD
|
||||
PrettyTable>=0.7.2 # BSD
|
||||
gnocchiclient>=7.0.1 # Apache-2.0
|
||||
openstacksdk>=4.4.0 # Apache-2.0
|
||||
python-cinderclient>=3.5.0 # Apache-2.0
|
||||
python-keystoneclient>=3.15.0 # Apache-2.0
|
||||
python-novaclient>=14.1.0 # Apache-2.0
|
||||
|
||||
@@ -33,9 +33,9 @@ class Checks(upgradecheck.UpgradeCommands):
|
||||
"""
|
||||
|
||||
def _minimum_nova_api_version(self):
|
||||
"""Checks the minimum required version of nova_client.api_version"""
|
||||
"""Checks the minimum required version of nova.api_version"""
|
||||
try:
|
||||
clients.check_min_nova_api_version(CONF.nova_client.api_version)
|
||||
clients.check_min_nova_api_version(CONF.nova.api_version)
|
||||
except ValueError as e:
|
||||
return upgradecheck.Result(
|
||||
upgradecheck.Code.FAILURE, str(e))
|
||||
|
||||
@@ -22,6 +22,7 @@ from keystoneauth1 import loading as ka_loading
|
||||
from keystoneclient import client as keyclient
|
||||
from novaclient import api_versions as nova_api_versions
|
||||
from novaclient import client as nvclient
|
||||
from openstack import connection
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
@@ -45,6 +46,42 @@ MIN_NOVA_API_VERSION = '2.56'
|
||||
warnings.simplefilter("once")
|
||||
|
||||
|
||||
def get_sdk_connection(session=None, context=None):
|
||||
"""Create and return an OpenStackSDK Connection object.
|
||||
|
||||
:param session: Optional keystone session. If not provided, a new session
|
||||
will be created using the configured auth parameters.
|
||||
:param context: Optional context object, use to get user's token.
|
||||
:returns: An OpenStackSDK Connection object
|
||||
"""
|
||||
# NOTE(jgilaber): load the auth plugin from the config in case it's never
|
||||
# been loaded before. The auth plugin is only used when creating a new
|
||||
# session, but we need to ensure the auth_url config value is set to use
|
||||
# the user token from the context object
|
||||
auth = ka_loading.load_auth_from_conf_options(
|
||||
CONF, _CLIENTS_AUTH_GROUP
|
||||
)
|
||||
if context is not None:
|
||||
# create a connection using the user's token if unavailable
|
||||
conn = connection.Connection(
|
||||
token=context.auth_token,
|
||||
auth_type="v3token",
|
||||
project_id=context.project_id,
|
||||
project_domain_id=context.project_domain_id,
|
||||
auth_url=CONF.watcher_clients_auth.auth_url
|
||||
)
|
||||
return conn
|
||||
|
||||
if session is None:
|
||||
# if we don't have a user token nor a created session, create a new
|
||||
# one
|
||||
session = ka_loading.load_session_from_conf_options(
|
||||
CONF, _CLIENTS_AUTH_GROUP, auth=auth
|
||||
)
|
||||
|
||||
return connection.Connection(session=session, oslo_conf=CONF)
|
||||
|
||||
|
||||
def check_min_nova_api_version(config_version):
|
||||
"""Validates the minimum required nova API version.
|
||||
|
||||
@@ -54,7 +91,7 @@ def check_min_nova_api_version(config_version):
|
||||
"""
|
||||
min_required = nova_api_versions.APIVersion(MIN_NOVA_API_VERSION)
|
||||
if nova_api_versions.APIVersion(config_version) < min_required:
|
||||
raise ValueError(f'Invalid nova_client.api_version {config_version}. '
|
||||
raise ValueError(f'Invalid nova.api_version {config_version}. '
|
||||
f'{MIN_NOVA_API_VERSION} or greater is required.')
|
||||
|
||||
|
||||
@@ -116,7 +153,7 @@ class OpenStackClients:
|
||||
if self._nova:
|
||||
return self._nova
|
||||
|
||||
novaclient_version = self._get_client_option('nova', 'api_version')
|
||||
novaclient_version = CONF.nova.api_version
|
||||
|
||||
check_min_nova_api_version(novaclient_version)
|
||||
|
||||
|
||||
@@ -114,6 +114,36 @@ class Server:
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_openstacksdk(cls, nova_server):
|
||||
"""Create a Server dataclass from a OpenStackSDK Server object.
|
||||
|
||||
:param nova_server: OpenStackSDK Server object
|
||||
:returns: Server dataclass instance
|
||||
:raises: InvalidUUID if server ID is not a valid UUID
|
||||
"""
|
||||
try:
|
||||
uuid.UUID(nova_server.id)
|
||||
except (ValueError, AttributeError, TypeError):
|
||||
raise exception.InvalidUUID(uuid=nova_server.id)
|
||||
|
||||
return cls(
|
||||
uuid=nova_server.id,
|
||||
name=nova_server.name,
|
||||
created=nova_server.created_at,
|
||||
host=nova_server.compute_host,
|
||||
vm_state=nova_server.vm_state,
|
||||
task_state=nova_server.task_state,
|
||||
power_state=nova_server.power_state,
|
||||
status=nova_server.status,
|
||||
flavor=nova_server.flavor,
|
||||
tenant_id=nova_server.project_id,
|
||||
locked=nova_server.is_locked,
|
||||
metadata=nova_server.metadata,
|
||||
availability_zone=nova_server.availability_zone,
|
||||
pinned_availability_zone=nova_server.pinned_availability_zone
|
||||
)
|
||||
|
||||
|
||||
@dc.dataclass(frozen=True)
|
||||
class Hypervisor:
|
||||
@@ -182,6 +212,50 @@ class Hypervisor:
|
||||
servers=servers,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_openstacksdk(cls, nova_hypervisor):
|
||||
"""Create a Hypervisor dataclass from a OpenStackSDK Hypervisor object.
|
||||
|
||||
:param nova_hypervisor: OpenStackSDK Hypervisor object
|
||||
:returns: Hypervisor dataclass instance
|
||||
:raises: InvalidUUID if hypervisor ID is not a valid UUID
|
||||
"""
|
||||
service = nova_hypervisor.service_details
|
||||
service_host = None
|
||||
service_id = None
|
||||
service_disabled_reason = None
|
||||
if isinstance(service, dict):
|
||||
service_host = service.get('host')
|
||||
service_id = service.get('id')
|
||||
service_disabled_reason = service.get('disabled_reason')
|
||||
|
||||
servers = nova_hypervisor.servers
|
||||
if servers is None:
|
||||
servers = []
|
||||
|
||||
try:
|
||||
uuid.UUID(nova_hypervisor.id)
|
||||
except (ValueError, AttributeError, TypeError):
|
||||
raise exception.InvalidUUID(uuid=nova_hypervisor.id)
|
||||
|
||||
return cls(
|
||||
uuid=nova_hypervisor.id,
|
||||
hypervisor_hostname=nova_hypervisor.name,
|
||||
hypervisor_type=nova_hypervisor.hypervisor_type,
|
||||
state=nova_hypervisor.state,
|
||||
status=nova_hypervisor.status,
|
||||
vcpus=nova_hypervisor.vcpus,
|
||||
vcpus_used=nova_hypervisor.vcpus_used,
|
||||
memory_mb=nova_hypervisor.memory_size,
|
||||
memory_mb_used=nova_hypervisor.memory_used,
|
||||
local_gb=nova_hypervisor.local_disk_size,
|
||||
local_gb_used=nova_hypervisor.local_disk_used,
|
||||
service_host=service_host,
|
||||
service_id=service_id,
|
||||
service_disabled_reason=service_disabled_reason,
|
||||
servers=servers,
|
||||
)
|
||||
|
||||
|
||||
@dc.dataclass(frozen=True)
|
||||
class Flavor:
|
||||
@@ -226,6 +300,26 @@ class Flavor:
|
||||
extra_specs=flavor_dict.get('extra_specs', {})
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_openstacksdk(cls, nova_flavor):
|
||||
"""Create a Flavor dataclass from a OpenStackSDK Flavor object.
|
||||
|
||||
:param nova_flavor: OpenStackSDK Flavor object
|
||||
:returns: Flavor dataclass instance
|
||||
"""
|
||||
|
||||
return cls(
|
||||
id=nova_flavor.id,
|
||||
flavor_name=nova_flavor.name,
|
||||
vcpus=nova_flavor.vcpus,
|
||||
ram=nova_flavor.ram,
|
||||
disk=nova_flavor.disk,
|
||||
ephemeral=nova_flavor.ephemeral,
|
||||
swap=nova_flavor.swap,
|
||||
is_public=nova_flavor.is_public,
|
||||
extra_specs=nova_flavor.extra_specs
|
||||
)
|
||||
|
||||
|
||||
@dc.dataclass(frozen=True)
|
||||
class Aggregate:
|
||||
@@ -256,6 +350,21 @@ class Aggregate:
|
||||
metadata=nova_aggregate.metadata,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_openstacksdk(cls, nova_aggregate):
|
||||
"""Create an Aggregate dataclass from a OpenStackSDK Aggregate object.
|
||||
|
||||
:param nova_aggregate: OpenStackSDK Aggregate object
|
||||
:returns: Aggregate dataclass instance
|
||||
"""
|
||||
return cls(
|
||||
id=nova_aggregate.id,
|
||||
name=nova_aggregate.name,
|
||||
availability_zone=nova_aggregate.availability_zone,
|
||||
hosts=nova_aggregate.hosts,
|
||||
metadata=nova_aggregate.metadata,
|
||||
)
|
||||
|
||||
|
||||
@dc.dataclass(frozen=True)
|
||||
class Service:
|
||||
@@ -298,6 +407,30 @@ class Service:
|
||||
disabled_reason=nova_service.disabled_reason,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_openstacksdk(cls, nova_service):
|
||||
"""Create a Service dataclass from a OpenStackSDK Service object.
|
||||
|
||||
:param nova_service: OpenStackSDK Service object
|
||||
:returns: Service dataclass instance
|
||||
:raises: InvalidUUID if service ID is not a valid UUID
|
||||
"""
|
||||
try:
|
||||
uuid.UUID(nova_service.id)
|
||||
except (ValueError, AttributeError, TypeError):
|
||||
raise exception.InvalidUUID(uuid=nova_service.id)
|
||||
|
||||
return cls(
|
||||
uuid=nova_service.id,
|
||||
binary=nova_service.binary,
|
||||
host=nova_service.host,
|
||||
zone=nova_service.availability_zone,
|
||||
status=nova_service.status,
|
||||
state=nova_service.state,
|
||||
updated_at=nova_service.updated_at,
|
||||
disabled_reason=nova_service.disabled_reason,
|
||||
)
|
||||
|
||||
|
||||
@dc.dataclass(frozen=True)
|
||||
class ServerMigration:
|
||||
@@ -320,6 +453,17 @@ class ServerMigration:
|
||||
id=nova_migration.id,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_openstacksdk(cls, nova_migration):
|
||||
"""Create a ServerMigration from a OpenStackSDK ServerMigration.
|
||||
|
||||
:param nova_migration: OpenStackSDK ServerMigration
|
||||
:returns: ServerMigration dataclass instance
|
||||
"""
|
||||
return cls(
|
||||
id=nova_migration.id,
|
||||
)
|
||||
|
||||
|
||||
class NovaHelper:
|
||||
|
||||
@@ -338,7 +482,7 @@ class NovaHelper:
|
||||
if self._is_pinned_az_available is None:
|
||||
self._is_pinned_az_available = (
|
||||
api_versions.APIVersion(
|
||||
version_str=CONF.nova_client.api_version) >=
|
||||
version_str=CONF.nova.api_version) >=
|
||||
api_versions.APIVersion(version_str='2.96'))
|
||||
return self._is_pinned_az_available
|
||||
|
||||
@@ -773,7 +917,7 @@ class NovaHelper:
|
||||
|
||||
@nova_retries
|
||||
def enable_service_nova_compute(self, hostname):
|
||||
if (api_versions.APIVersion(version_str=CONF.nova_client.api_version) <
|
||||
if (api_versions.APIVersion(version_str=CONF.nova.api_version) <
|
||||
api_versions.APIVersion(version_str='2.53')):
|
||||
status = self.nova.services.enable(
|
||||
host=hostname, binary='nova-compute').status == 'enabled'
|
||||
@@ -787,7 +931,7 @@ class NovaHelper:
|
||||
|
||||
@nova_retries
|
||||
def disable_service_nova_compute(self, hostname, reason=None):
|
||||
if (api_versions.APIVersion(version_str=CONF.nova_client.api_version) <
|
||||
if (api_versions.APIVersion(version_str=CONF.nova.api_version) <
|
||||
api_versions.APIVersion(version_str='2.53')):
|
||||
status = self.nova.services.disable_log_reason(
|
||||
host=hostname,
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from keystoneauth1 import loading as ks_loading
|
||||
from oslo_config import cfg
|
||||
|
||||
from watcher.common import clients
|
||||
|
||||
nova = cfg.OptGroup(name='nova',
|
||||
title='Options for the Nova integration '
|
||||
'configuration')
|
||||
@@ -49,12 +52,52 @@ NOVA_OPTS = [
|
||||
help='Interval in seconds to retry HTTP requests to the Nova '
|
||||
'service when connection errors occur. Default is 2 '
|
||||
'seconds.'),
|
||||
# Options migrated from nova_client group (deprecated in 2026.1)
|
||||
cfg.StrOpt('api_version',
|
||||
default='2.56',
|
||||
deprecated_group='nova_client',
|
||||
help=f"""
|
||||
Version of Nova API to use in novaclient.
|
||||
|
||||
Minimum required version: {clients.MIN_NOVA_API_VERSION}
|
||||
|
||||
Certain Watcher features depend on a minimum version of the compute
|
||||
API being available which is enforced with this option. See
|
||||
https://docs.openstack.org/nova/latest/reference/api-microversion-history.html
|
||||
for the compute API microversion history.
|
||||
"""),
|
||||
]
|
||||
|
||||
|
||||
def handle_nova_client_deprecations():
|
||||
"""Override configuration values from nova.
|
||||
|
||||
If the user has set values for deprecated options in nova_client, set the
|
||||
values to the new options in the nova group.
|
||||
"""
|
||||
loc = cfg.CONF.get_location('endpoint_type', 'nova_client')
|
||||
if loc and loc.location != cfg.Locations.opt_default:
|
||||
endpoint_type = cfg.CONF.nova_client.endpoint_type.replace('URL', '')
|
||||
cfg.CONF.set_default('valid_interfaces', [endpoint_type], 'nova')
|
||||
|
||||
loc = cfg.CONF.get_location('api_version', 'nova_client')
|
||||
if loc and loc.location != cfg.Locations.opt_default:
|
||||
api_version = cfg.CONF.nova_client.api_version
|
||||
cfg.CONF.set_default('api_version', api_version, 'nova')
|
||||
|
||||
loc = cfg.CONF.get_location('region_name', 'nova_client')
|
||||
if loc and loc.location != cfg.Locations.opt_default:
|
||||
region_name = cfg.CONF.nova_client.api_version
|
||||
cfg.CONF.set_default('region_name', region_name, 'nova')
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(nova)
|
||||
conf.register_opts(NOVA_OPTS, group=nova)
|
||||
ks_loading.register_adapter_conf_options(
|
||||
cfg.CONF, nova, include_deprecated=False
|
||||
)
|
||||
handle_nova_client_deprecations()
|
||||
|
||||
|
||||
def list_opts():
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.common import clients
|
||||
|
||||
nova_client = cfg.OptGroup(name='nova_client',
|
||||
@@ -25,6 +26,12 @@ nova_client = cfg.OptGroup(name='nova_client',
|
||||
NOVA_CLIENT_OPTS = [
|
||||
cfg.StrOpt('api_version',
|
||||
default='2.56',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=_(
|
||||
'To replace the frozen novaclient with the '
|
||||
'openstacksdk compute proxy, the options need to '
|
||||
'be under the [nova] group.'
|
||||
),
|
||||
help=f"""
|
||||
Version of Nova API to use in novaclient.
|
||||
|
||||
@@ -37,10 +44,22 @@ for the compute API microversion history.
|
||||
"""),
|
||||
cfg.StrOpt('endpoint_type',
|
||||
default='publicURL',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=_(
|
||||
'This option was replaced by the valid_interfaces'
|
||||
'option defined by keystoneauth.'
|
||||
),
|
||||
deprecated_since='2026.1',
|
||||
choices=['public', 'internal', 'admin',
|
||||
'publicURL', 'internalURL', 'adminURL'],
|
||||
help='Type of endpoint to use in novaclient.'),
|
||||
cfg.StrOpt('region_name',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=_(
|
||||
'This option was replaced by the region_name'
|
||||
'option defined by keystoneauth.'
|
||||
),
|
||||
deprecated_since='2026.1',
|
||||
help='Region in Identity service catalog to use for '
|
||||
'communication with the OpenStack service.')]
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class TestUpgradeChecks(base.TestCase):
|
||||
def test_minimum_nova_api_version_fail(self):
|
||||
# Tests the scenario that [nova_client]/api_version is less than the
|
||||
# minimum required version.
|
||||
CONF.set_override('api_version', '2.47', group='nova_client')
|
||||
CONF.set_override('api_version', '2.47', group='nova')
|
||||
result = self.cmd._minimum_nova_api_version()
|
||||
self.assertEqual(Code.FAILURE, result.code)
|
||||
self.assertIn('Invalid nova_client.api_version 2.47.', result.details)
|
||||
self.assertIn('Invalid nova.api_version 2.47.', result.details)
|
||||
|
||||
@@ -30,13 +30,14 @@ except Exception: # ImportError or others
|
||||
from novaclient import client as nvclient
|
||||
|
||||
from watcher.common import clients
|
||||
from watcher.common import context
|
||||
from watcher import conf
|
||||
from watcher.tests.unit import base
|
||||
|
||||
CONF = conf.CONF
|
||||
|
||||
|
||||
class TestClients(base.TestCase):
|
||||
class TestBaseClients(base.TestCase):
|
||||
|
||||
def _register_watcher_clients_auth_opts(self):
|
||||
_AUTH_CONF_GROUP = 'watcher_clients_auth'
|
||||
@@ -90,6 +91,9 @@ class TestClients(base.TestCase):
|
||||
|
||||
CONF.register_opts = mock_register_opts
|
||||
|
||||
|
||||
class TestClients(TestBaseClients):
|
||||
|
||||
def test_get_keystone_session(self):
|
||||
self._register_watcher_clients_auth_opts()
|
||||
|
||||
@@ -116,14 +120,14 @@ class TestClients(base.TestCase):
|
||||
osc._nova = None
|
||||
osc.nova()
|
||||
mock_call.assert_called_once_with(
|
||||
CONF.nova_client.api_version,
|
||||
CONF.nova.api_version,
|
||||
endpoint_type=CONF.nova_client.endpoint_type,
|
||||
region_name=CONF.nova_client.region_name,
|
||||
session=mock_session)
|
||||
|
||||
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||
def test_clients_nova_diff_vers(self, mock_session):
|
||||
CONF.set_override('api_version', '2.60', group='nova_client')
|
||||
CONF.set_override('api_version', '2.60', group='nova')
|
||||
osc = clients.OpenStackClients()
|
||||
osc._nova = None
|
||||
osc.nova()
|
||||
@@ -131,11 +135,11 @@ class TestClients(base.TestCase):
|
||||
|
||||
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||
def test_clients_nova_bad_min_version(self, mock_session):
|
||||
CONF.set_override('api_version', '2.47', group='nova_client')
|
||||
CONF.set_override('api_version', '2.47', group='nova')
|
||||
osc = clients.OpenStackClients()
|
||||
osc._nova = None
|
||||
ex = self.assertRaises(ValueError, osc.nova)
|
||||
self.assertIn('Invalid nova_client.api_version 2.47', str(ex))
|
||||
self.assertIn('Invalid nova.api_version 2.47', str(ex))
|
||||
|
||||
@mock.patch.object(clients.OpenStackClients, 'session')
|
||||
def test_clients_nova_diff_endpoint(self, mock_session):
|
||||
@@ -344,3 +348,82 @@ class TestClients(base.TestCase):
|
||||
interface=CONF.placement_client.interface,
|
||||
region_name=CONF.placement_client.region_name,
|
||||
additional_headers=headers)
|
||||
|
||||
|
||||
class TestGetSDKConnection(TestBaseClients):
|
||||
"""Test cases for get_sdk_connection function."""
|
||||
|
||||
@mock.patch('openstack.connection.Connection', autospec=True)
|
||||
def test_get_sdk_connection_with_context(
|
||||
self, mock_connect):
|
||||
"""Test SDK connection creation with context."""
|
||||
self._register_watcher_clients_auth_opts()
|
||||
|
||||
context_obj = context.RequestContext(
|
||||
auth_token='test_token', project_id='test_project_id',
|
||||
project_domain='test_project_domain_id'
|
||||
)
|
||||
mock_connection = mock.Mock()
|
||||
mock_connect.return_value = mock_connection
|
||||
|
||||
result = clients.get_sdk_connection(context=context_obj)
|
||||
|
||||
mock_connect.assert_called_once_with(
|
||||
token='test_token',
|
||||
auth_type='v3token',
|
||||
project_id='test_project_id',
|
||||
project_domain_id='test_project_domain_id',
|
||||
auth_url='http://server.ip:5000'
|
||||
)
|
||||
self.assertEqual(mock_connection, result)
|
||||
|
||||
@mock.patch.object(ka_loading, 'load_auth_from_conf_options',
|
||||
autospec=True)
|
||||
@mock.patch('openstack.connection.Connection', autospec=True)
|
||||
def test_get_sdk_connection_with_session(
|
||||
self, mock_connect, mock_load_auth):
|
||||
"""Test SDK connection creation with provided session."""
|
||||
mock_session = mock.Mock()
|
||||
mock_connection = mock.Mock()
|
||||
mock_connect.return_value = mock_connection
|
||||
|
||||
result = clients.get_sdk_connection(session=mock_session)
|
||||
|
||||
mock_connect.assert_called_once_with(
|
||||
session=mock_session,
|
||||
oslo_conf=CONF
|
||||
)
|
||||
mock_load_auth.assert_called_once_with(
|
||||
CONF, 'watcher_clients_auth'
|
||||
)
|
||||
self.assertEqual(mock_connection, result)
|
||||
|
||||
@mock.patch.object(ka_loading, 'load_session_from_conf_options',
|
||||
autospec=True)
|
||||
@mock.patch.object(ka_loading, 'load_auth_from_conf_options',
|
||||
autospec=True)
|
||||
@mock.patch('openstack.connection.Connection', autospec=True)
|
||||
def test_get_sdk_connection_no_session_no_context(
|
||||
self, mock_connect, mock_load_auth, mock_load_session):
|
||||
"""Test SDK connection creation without session or context."""
|
||||
mock_auth = mock.Mock()
|
||||
mock_session = mock.Mock()
|
||||
mock_connection = mock.Mock()
|
||||
mock_load_auth.return_value = mock_auth
|
||||
mock_load_session.return_value = mock_session
|
||||
mock_connect.return_value = mock_connection
|
||||
|
||||
result = clients.get_sdk_connection()
|
||||
|
||||
mock_load_auth.assert_called_once_with(
|
||||
CONF, 'watcher_clients_auth'
|
||||
)
|
||||
mock_load_session.assert_called_once_with(
|
||||
CONF,
|
||||
'watcher_clients_auth',
|
||||
auth=mock_auth)
|
||||
mock_connect.assert_called_once_with(
|
||||
session=mock_session,
|
||||
oslo_conf=CONF
|
||||
)
|
||||
self.assertEqual(mock_connection, result)
|
||||
|
||||
@@ -932,7 +932,7 @@ class TestNovaHelper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
nova_services.enable.return_value = mock.MagicMock(
|
||||
status='enabled')
|
||||
|
||||
CONF.set_override('api_version', '2.52', group='nova_client')
|
||||
CONF.set_override('api_version', '2.52', group='nova')
|
||||
|
||||
result = nova_util.enable_service_nova_compute('nanjing')
|
||||
self.assertTrue(result)
|
||||
@@ -943,7 +943,7 @@ class TestNovaHelper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
nova_services.enable.return_value = mock.MagicMock(
|
||||
status='disabled')
|
||||
|
||||
CONF.set_override('api_version', '2.56', group='nova_client')
|
||||
CONF.set_override('api_version', '2.56', group='nova')
|
||||
|
||||
result = nova_util.enable_service_nova_compute('nanjing')
|
||||
self.assertFalse(result)
|
||||
@@ -957,7 +957,7 @@ class TestNovaHelper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
nova_services.disable_log_reason.return_value = mock.MagicMock(
|
||||
status='enabled')
|
||||
|
||||
CONF.set_override('api_version', '2.52', group='nova_client')
|
||||
CONF.set_override('api_version', '2.52', group='nova')
|
||||
|
||||
result = nova_util.disable_service_nova_compute(
|
||||
'nanjing', reason='test')
|
||||
@@ -969,7 +969,7 @@ class TestNovaHelper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
nova_services.disable_log_reason.return_value = mock.MagicMock(
|
||||
status='disabled')
|
||||
|
||||
CONF.set_override('api_version', '2.56', group='nova_client')
|
||||
CONF.set_override('api_version', '2.56', group='nova')
|
||||
|
||||
result = nova_util.disable_service_nova_compute(
|
||||
'nanjing', reason='test2')
|
||||
@@ -1453,6 +1453,64 @@ class TestServerWrapper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
self.assertNotEqual(server1a, "not-a-server")
|
||||
self.assertIsNotNone(server1a)
|
||||
|
||||
def test_server_from_openstacksdk_basic_properties(self):
|
||||
"""Test Server.from_openstacksdk with basic properties."""
|
||||
server_id = utils.generate_uuid()
|
||||
sdk_server = self.create_openstacksdk_server(
|
||||
id=server_id,
|
||||
name='my-server',
|
||||
status='ACTIVE',
|
||||
created_at='2026-01-01T00:00:00Z',
|
||||
project_id='tenant-123',
|
||||
is_locked=True,
|
||||
metadata={'key': 'value'},
|
||||
pinned_availability_zone='az1'
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Server.from_openstacksdk(sdk_server)
|
||||
|
||||
self.assertEqual(server_id, wrapped.uuid)
|
||||
self.assertEqual('my-server', wrapped.name)
|
||||
self.assertEqual('ACTIVE', wrapped.status)
|
||||
self.assertEqual('2026-01-01T00:00:00Z', wrapped.created)
|
||||
self.assertEqual('tenant-123', wrapped.tenant_id)
|
||||
self.assertTrue(wrapped.locked)
|
||||
self.assertEqual({'key': 'value'}, wrapped.metadata)
|
||||
self.assertEqual('az1', wrapped.pinned_availability_zone)
|
||||
|
||||
def test_server_from_openstacksdk_extended_attributes(self):
|
||||
"""Test Server.from_openstacksdk with extended attributes."""
|
||||
server_id = utils.generate_uuid()
|
||||
sdk_server = self.create_openstacksdk_server(
|
||||
id=server_id,
|
||||
compute_host='compute-1',
|
||||
vm_state='active',
|
||||
task_state=None,
|
||||
power_state=1,
|
||||
availability_zone='nova'
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Server.from_openstacksdk(sdk_server)
|
||||
|
||||
self.assertEqual('compute-1', wrapped.host)
|
||||
self.assertEqual('active', wrapped.vm_state)
|
||||
self.assertIsNone(wrapped.task_state)
|
||||
self.assertEqual(1, wrapped.power_state)
|
||||
self.assertEqual('nova', wrapped.availability_zone)
|
||||
|
||||
def test_server_from_openstacksdk_flavor(self):
|
||||
"""Test Server.from_openstacksdk flavor property."""
|
||||
server_id = utils.generate_uuid()
|
||||
sdk_server = self.create_openstacksdk_server(
|
||||
id=server_id,
|
||||
flavor={'id': 'flavor-123', 'name': 'm1.small'}
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Server.from_openstacksdk(sdk_server)
|
||||
# OpenStackSDK converts flavor dict to Flavor object
|
||||
self.assertEqual('flavor-123', wrapped.flavor.id)
|
||||
self.assertEqual('m1.small', wrapped.flavor.name)
|
||||
|
||||
|
||||
class TestHypervisorWrapper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
"""Test suite for the Hypervisor dataclass."""
|
||||
@@ -1594,6 +1652,111 @@ class TestHypervisorWrapper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
# Compare with non-Hypervisor object
|
||||
self.assertNotEqual(hyp1a, "not-a-hypervisor")
|
||||
|
||||
def test_hypervisor_from_openstacksdk_basic_properties(self):
|
||||
"""Test Hypervisor.from_openstacksdk with basic properties."""
|
||||
hypervisor_id = utils.generate_uuid()
|
||||
hostname = 'compute-node-1'
|
||||
sdk_hypervisor = self.create_openstacksdk_hypervisor(
|
||||
id=hypervisor_id,
|
||||
name=hostname,
|
||||
hypervisor_type='QEMU',
|
||||
state='up',
|
||||
status='enabled',
|
||||
vcpus=32,
|
||||
vcpus_used=8,
|
||||
memory_size=65536,
|
||||
memory_used=16384,
|
||||
local_disk_size=1000,
|
||||
local_disk_used=250
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Hypervisor.from_openstacksdk(sdk_hypervisor)
|
||||
|
||||
self.assertEqual(hypervisor_id, wrapped.uuid)
|
||||
self.assertEqual(hostname, wrapped.hypervisor_hostname)
|
||||
self.assertEqual('QEMU', wrapped.hypervisor_type)
|
||||
self.assertEqual('up', wrapped.state)
|
||||
self.assertEqual('enabled', wrapped.status)
|
||||
self.assertEqual(32, wrapped.vcpus)
|
||||
self.assertEqual(8, wrapped.vcpus_used)
|
||||
self.assertEqual(65536, wrapped.memory_mb)
|
||||
self.assertEqual(16384, wrapped.memory_mb_used)
|
||||
self.assertEqual(1000, wrapped.local_gb)
|
||||
self.assertEqual(250, wrapped.local_gb_used)
|
||||
|
||||
def test_hypervisor_from_openstacksdk_service_properties(self):
|
||||
"""Test Hypervisor.from_openstacksdk service properties."""
|
||||
hostname = 'compute-node-1'
|
||||
sdk_hypervisor = self.create_openstacksdk_hypervisor(
|
||||
id=utils.generate_uuid(),
|
||||
name=hostname,
|
||||
service_details={
|
||||
'host': hostname,
|
||||
'id': 42,
|
||||
'disabled_reason': 'maintenance'
|
||||
}
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Hypervisor.from_openstacksdk(sdk_hypervisor)
|
||||
|
||||
self.assertEqual(hostname, wrapped.service_host)
|
||||
self.assertEqual(42, wrapped.service_id)
|
||||
self.assertEqual('maintenance', wrapped.service_disabled_reason)
|
||||
|
||||
def test_hypervisor_from_openstacksdk_service_not_dict(self):
|
||||
"""Test Hypervisor.from_openstacksdk when service is not a dict."""
|
||||
sdk_hypervisor = self.create_openstacksdk_hypervisor(
|
||||
id=utils.generate_uuid(),
|
||||
name='compute-node-1',
|
||||
service_details='not-a-dict'
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Hypervisor.from_openstacksdk(sdk_hypervisor)
|
||||
|
||||
self.assertIsNone(wrapped.service_host)
|
||||
self.assertIsNone(wrapped.service_id)
|
||||
self.assertIsNone(wrapped.service_disabled_reason)
|
||||
|
||||
def test_hypervisor_from_openstacksdk_servers_property(self):
|
||||
"""Test Hypervisor.from_openstacksdk servers property."""
|
||||
hypervisor_id = utils.generate_uuid()
|
||||
hostname = 'compute-node-1'
|
||||
|
||||
server1_id = utils.generate_uuid()
|
||||
server2_id = utils.generate_uuid()
|
||||
server1 = {
|
||||
'uuid': server1_id,
|
||||
'name': 'server1',
|
||||
}
|
||||
server2 = {
|
||||
'uuid': server2_id,
|
||||
'name': 'server2'
|
||||
}
|
||||
|
||||
sdk_hypervisor = self.create_openstacksdk_hypervisor(
|
||||
id=hypervisor_id,
|
||||
name=hostname,
|
||||
servers=[server1, server2]
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Hypervisor.from_openstacksdk(sdk_hypervisor)
|
||||
|
||||
result_servers = wrapped.servers
|
||||
self.assertEqual(2, len(result_servers))
|
||||
self.assertEqual(server1_id, result_servers[0]['uuid'])
|
||||
self.assertEqual(server2_id, result_servers[1]['uuid'])
|
||||
|
||||
def test_hypervisor_from_openstacksdk_servers_none(self):
|
||||
"""Test Hypervisor.from_openstacksdk when servers is None."""
|
||||
sdk_hypervisor = self.create_openstacksdk_hypervisor(
|
||||
id=utils.generate_uuid(),
|
||||
name='compute-node-1',
|
||||
servers=None
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Hypervisor.from_openstacksdk(sdk_hypervisor)
|
||||
self.assertEqual([], wrapped.servers)
|
||||
|
||||
|
||||
class TestFlavorWrapper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
"""Test suite for the Flavor dataclass."""
|
||||
@@ -1695,6 +1858,82 @@ class TestFlavorWrapper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
# Compare with non-Flavor object
|
||||
self.assertNotEqual(flavor1a, "not-a-flavor")
|
||||
|
||||
def test_flavor_from_openstacksdk_basic_properties(self):
|
||||
"""Test Flavor.from_openstacksdk with basic properties."""
|
||||
flavor_id = utils.generate_uuid()
|
||||
sdk_flavor = self.create_openstacksdk_flavor(
|
||||
id=flavor_id,
|
||||
name='m1.small',
|
||||
vcpus=2,
|
||||
ram=2048,
|
||||
disk=20,
|
||||
ephemeral=10,
|
||||
swap=512,
|
||||
is_public=True
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Flavor.from_openstacksdk(sdk_flavor)
|
||||
|
||||
self.assertEqual(flavor_id, wrapped.id)
|
||||
self.assertEqual('m1.small', wrapped.flavor_name)
|
||||
self.assertEqual(2, wrapped.vcpus)
|
||||
self.assertEqual(2048, wrapped.ram)
|
||||
self.assertEqual(20, wrapped.disk)
|
||||
self.assertEqual(10, wrapped.ephemeral)
|
||||
self.assertEqual(512, wrapped.swap)
|
||||
self.assertTrue(wrapped.is_public)
|
||||
|
||||
def test_flavor_from_openstacksdk_zero_swap(self):
|
||||
"""Test Flavor.from_openstacksdk with zero swap."""
|
||||
flavor_id = utils.generate_uuid()
|
||||
sdk_flavor = self.create_openstacksdk_flavor(
|
||||
id=flavor_id,
|
||||
name='m1.noswap',
|
||||
swap=0
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Flavor.from_openstacksdk(sdk_flavor)
|
||||
self.assertEqual(0, wrapped.swap)
|
||||
|
||||
def test_flavor_from_openstacksdk_private(self):
|
||||
"""Test Flavor.from_openstacksdk with private flavor."""
|
||||
flavor_id = utils.generate_uuid()
|
||||
sdk_flavor = self.create_openstacksdk_flavor(
|
||||
id=flavor_id,
|
||||
name='m1.private',
|
||||
is_public=False
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Flavor.from_openstacksdk(sdk_flavor)
|
||||
self.assertFalse(wrapped.is_public)
|
||||
|
||||
def test_flavor_from_openstacksdk_with_extra_specs(self):
|
||||
"""Test Flavor.from_openstacksdk with extra_specs."""
|
||||
flavor_id = utils.generate_uuid()
|
||||
sdk_flavor = self.create_openstacksdk_flavor(
|
||||
id=flavor_id,
|
||||
name='m1.compute',
|
||||
extra_specs={'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': '2'}
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Flavor.from_openstacksdk(sdk_flavor)
|
||||
|
||||
self.assertEqual(
|
||||
{'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': '2'},
|
||||
wrapped.extra_specs
|
||||
)
|
||||
|
||||
def test_flavor_from_openstacksdk_without_extra_specs(self):
|
||||
"""Test Flavor.from_openstacksdk without extra_specs."""
|
||||
flavor_id = utils.generate_uuid()
|
||||
sdk_flavor = self.create_openstacksdk_flavor(
|
||||
id=flavor_id,
|
||||
name='m1.basic'
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Flavor.from_openstacksdk(sdk_flavor)
|
||||
self.assertEqual({}, wrapped.extra_specs)
|
||||
|
||||
|
||||
class TestAggregateWrapper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
"""Test suite for the Aggregate dataclass."""
|
||||
@@ -1751,6 +1990,37 @@ class TestAggregateWrapper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
# Compare with non-Aggregate object
|
||||
self.assertNotEqual(agg1a, "not-an-aggregate")
|
||||
|
||||
def test_aggregate_from_openstacksdk_basic_properties(self):
|
||||
"""Test Aggregate.from_openstacksdk with basic properties."""
|
||||
aggregate_id = utils.generate_uuid()
|
||||
sdk_aggregate = self.create_openstacksdk_aggregate(
|
||||
id=aggregate_id,
|
||||
name='test-aggregate',
|
||||
availability_zone='az1',
|
||||
hosts=['host1', 'host2', 'host3'],
|
||||
metadata={'ssd': 'true', 'gpu': 'nvidia'}
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Aggregate.from_openstacksdk(sdk_aggregate)
|
||||
|
||||
self.assertEqual(aggregate_id, wrapped.id)
|
||||
self.assertEqual('test-aggregate', wrapped.name)
|
||||
self.assertEqual('az1', wrapped.availability_zone)
|
||||
self.assertEqual(['host1', 'host2', 'host3'], wrapped.hosts)
|
||||
self.assertEqual({'ssd': 'true', 'gpu': 'nvidia'}, wrapped.metadata)
|
||||
|
||||
def test_aggregate_from_openstacksdk_no_az(self):
|
||||
"""Test Aggregate.from_openstacksdk without availability zone."""
|
||||
aggregate_id = utils.generate_uuid()
|
||||
sdk_aggregate = self.create_openstacksdk_aggregate(
|
||||
id=aggregate_id,
|
||||
name='test-aggregate',
|
||||
availability_zone=None
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Aggregate.from_openstacksdk(sdk_aggregate)
|
||||
self.assertIsNone(wrapped.availability_zone)
|
||||
|
||||
|
||||
class TestServiceWrapper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
"""Test suite for the Service dataclass."""
|
||||
@@ -1817,6 +2087,47 @@ class TestServiceWrapper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
# Compare with non-Service object
|
||||
self.assertNotEqual(svc1a, "not-a-service")
|
||||
|
||||
def test_service_from_openstacksdk_basic_properties(self):
|
||||
"""Test Service.from_openstacksdk with basic properties."""
|
||||
service_id = utils.generate_uuid()
|
||||
sdk_service = self.create_openstacksdk_service(
|
||||
id=service_id,
|
||||
binary='nova-compute',
|
||||
host='compute-node-1',
|
||||
availability_zone='az1',
|
||||
status='enabled',
|
||||
state='up',
|
||||
updated_at='2026-01-09T12:00:00Z',
|
||||
disabled_reason=None
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Service.from_openstacksdk(sdk_service)
|
||||
|
||||
self.assertEqual(service_id, wrapped.uuid)
|
||||
self.assertEqual('nova-compute', wrapped.binary)
|
||||
self.assertEqual('compute-node-1', wrapped.host)
|
||||
self.assertEqual('az1', wrapped.zone)
|
||||
self.assertEqual('enabled', wrapped.status)
|
||||
self.assertEqual('up', wrapped.state)
|
||||
self.assertEqual('2026-01-09T12:00:00Z', wrapped.updated_at)
|
||||
self.assertIsNone(wrapped.disabled_reason)
|
||||
|
||||
def test_service_from_openstacksdk_disabled(self):
|
||||
"""Test Service.from_openstacksdk with disabled service."""
|
||||
service_id = utils.generate_uuid()
|
||||
sdk_service = self.create_openstacksdk_service(
|
||||
id=service_id,
|
||||
status='disabled',
|
||||
state='down',
|
||||
disabled_reason='maintenance'
|
||||
)
|
||||
|
||||
wrapped = nova_helper.Service.from_openstacksdk(sdk_service)
|
||||
|
||||
self.assertEqual('disabled', wrapped.status)
|
||||
self.assertEqual('down', wrapped.state)
|
||||
self.assertEqual('maintenance', wrapped.disabled_reason)
|
||||
|
||||
|
||||
class TestServerMigrationWrapper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
"""Test suite for the ServerMigration dataclass."""
|
||||
@@ -1849,3 +2160,34 @@ class TestServerMigrationWrapper(test_utils.NovaResourcesMixin, base.TestCase):
|
||||
|
||||
# Compare with non-ServerMigration object
|
||||
self.assertNotEqual(mig1a, "not-a-migration")
|
||||
|
||||
def test_migration_from_openstacksdk_basic_properties(self):
|
||||
"""Test ServerMigration.from_openstacksdk with basic properties."""
|
||||
migration_id = utils.generate_uuid()
|
||||
sdk_migration = self.create_openstacksdk_migration(
|
||||
id=migration_id
|
||||
)
|
||||
|
||||
wrapped = nova_helper.ServerMigration.from_openstacksdk(sdk_migration)
|
||||
self.assertEqual(migration_id, wrapped.id)
|
||||
|
||||
def test_migration_equality_from_openstacksdk(self):
|
||||
"""Test ServerMigration dataclass equality comparison."""
|
||||
migration_id1 = utils.generate_uuid()
|
||||
migration_id2 = utils.generate_uuid()
|
||||
|
||||
mig1a = nova_helper.ServerMigration.from_openstacksdk(
|
||||
self.create_openstacksdk_migration(id=migration_id1))
|
||||
mig1b = nova_helper.ServerMigration.from_openstacksdk(
|
||||
self.create_openstacksdk_migration(id=migration_id1))
|
||||
mig2 = nova_helper.ServerMigration.from_openstacksdk(
|
||||
self.create_openstacksdk_migration(id=migration_id2))
|
||||
|
||||
# Same ID and attributes should be equal
|
||||
self.assertEqual(mig1a, mig1b)
|
||||
|
||||
# Different ID should not be equal
|
||||
self.assertNotEqual(mig1a, mig2)
|
||||
|
||||
# Compare with non-ServerMigration object
|
||||
self.assertNotEqual(mig1a, "not-a-migration")
|
||||
|
||||
@@ -21,6 +21,12 @@ from novaclient.v2 import hypervisors
|
||||
from novaclient.v2 import server_migrations
|
||||
from novaclient.v2 import servers
|
||||
from novaclient.v2 import services
|
||||
from openstack.compute.v2 import aggregate
|
||||
from openstack.compute.v2 import flavor
|
||||
from openstack.compute.v2 import hypervisor
|
||||
from openstack.compute.v2 import server
|
||||
from openstack.compute.v2 import server_migration
|
||||
from openstack.compute.v2 import service
|
||||
|
||||
|
||||
class NovaResourcesMixin:
|
||||
@@ -144,3 +150,123 @@ class NovaResourcesMixin:
|
||||
migration_info.update(kwargs)
|
||||
return server_migrations.ServerMigration(
|
||||
server_migrations.ServerMigrationsManager, info=migration_info)
|
||||
|
||||
def create_openstacksdk_server(self, **kwargs):
|
||||
"""Create a real OpenStackSDK Server object.
|
||||
|
||||
:param kwargs: server attributes
|
||||
:returns: openstack.compute.v2.server.Server object
|
||||
"""
|
||||
server_info = {
|
||||
'id': kwargs.pop('id', 'test_id'),
|
||||
'name': kwargs.pop('name', 'test-server'),
|
||||
'created_at': kwargs.pop('created_at', '2026-01-09T12:00:00Z'),
|
||||
'compute_host': kwargs.pop('compute_host', None),
|
||||
'vm_state': kwargs.pop('vm_state', None),
|
||||
'task_state': kwargs.pop('task_state', None),
|
||||
'power_state': kwargs.pop('power_state', None),
|
||||
'status': kwargs.pop('status', 'ACTIVE'),
|
||||
'flavor': kwargs.pop('flavor', {'id': 'flavor-1'}),
|
||||
'project_id': kwargs.pop('project_id', 'test-tenant-id'),
|
||||
'is_locked': kwargs.pop('is_locked', False),
|
||||
'metadata': kwargs.pop('metadata', {}),
|
||||
'availability_zone': kwargs.pop('availability_zone', None),
|
||||
'pinned_availability_zone': kwargs.pop(
|
||||
'pinned_availability_zone', None),
|
||||
}
|
||||
server_info.update(kwargs)
|
||||
return server.Server(**server_info)
|
||||
|
||||
def create_openstacksdk_hypervisor(self, **kwargs):
|
||||
"""Create a real OpenStackSDK Hypervisor object.
|
||||
|
||||
:param kwargs: hypervisor attributes
|
||||
:returns: openstack.compute.v2.hypervisor.Hypervisor object
|
||||
"""
|
||||
name = kwargs.pop('name', 'hypervisor-hostname')
|
||||
hypervisor_info = {
|
||||
'id': kwargs.pop('id', 'hypervisor-id'),
|
||||
'name': name,
|
||||
'hypervisor_type': kwargs.pop('hypervisor_type', 'QEMU'),
|
||||
'state': kwargs.pop('state', 'up'),
|
||||
'status': kwargs.pop('status', 'enabled'),
|
||||
'vcpus': kwargs.pop('vcpus', 16),
|
||||
'vcpus_used': kwargs.pop('vcpus_used', 4),
|
||||
'memory_size': kwargs.pop('memory_size', 32768),
|
||||
'memory_used': kwargs.pop('memory_used', 8192),
|
||||
'local_disk_size': kwargs.pop('local_disk_size', 500),
|
||||
'local_disk_used': kwargs.pop('local_disk_used', 100),
|
||||
'service_details': kwargs.pop(
|
||||
'service_details', {'host': name, 'id': 1}
|
||||
),
|
||||
'servers': kwargs.pop('servers', None),
|
||||
}
|
||||
hypervisor_info.update(kwargs)
|
||||
return hypervisor.Hypervisor(**hypervisor_info)
|
||||
|
||||
def create_openstacksdk_flavor(self, **kwargs):
|
||||
"""Create a real OpenStackSDK Flavor object.
|
||||
|
||||
:param kwargs: flavor attributes
|
||||
:returns: openstack.compute.v2.flavor.Flavor object
|
||||
"""
|
||||
flavor_info = {
|
||||
'id': kwargs.pop('id', 'flavor-id'),
|
||||
'name': kwargs.pop('name', 'm1.small'),
|
||||
'vcpus': kwargs.pop('vcpus', 2),
|
||||
'ram': kwargs.pop('ram', 2048),
|
||||
'disk': kwargs.pop('disk', 20),
|
||||
'ephemeral': kwargs.pop('ephemeral', 0),
|
||||
'swap': kwargs.pop('swap', 0),
|
||||
'is_public': kwargs.pop('is_public', True),
|
||||
'extra_specs': kwargs.pop('extra_specs', {}),
|
||||
}
|
||||
flavor_info.update(kwargs)
|
||||
return flavor.Flavor(**flavor_info)
|
||||
|
||||
def create_openstacksdk_aggregate(self, **kwargs):
|
||||
"""Create a real OpenStackSDK Aggregate object.
|
||||
|
||||
:param kwargs: aggregate attributes
|
||||
:returns: openstack.compute.v2.aggregate.Aggregate object
|
||||
"""
|
||||
aggregate_info = {
|
||||
'id': kwargs.pop('id', 'aggregate-id'),
|
||||
'name': kwargs.pop('name', 'test-aggregate'),
|
||||
'availability_zone': kwargs.pop('availability_zone', None),
|
||||
'hosts': kwargs.pop('hosts', []),
|
||||
'metadata': kwargs.pop('metadata', {}),
|
||||
}
|
||||
aggregate_info.update(kwargs)
|
||||
return aggregate.Aggregate(**aggregate_info)
|
||||
|
||||
def create_openstacksdk_service(self, **kwargs):
|
||||
"""Create a real OpenStackSDK Service object.
|
||||
|
||||
:param kwargs: service attributes
|
||||
:returns: openstack.compute.v2.service.Service object
|
||||
"""
|
||||
service_info = {
|
||||
'id': kwargs.pop('id', 'service-id'),
|
||||
'binary': kwargs.pop('binary', 'nova-compute'),
|
||||
'host': kwargs.pop('host', 'compute-1'),
|
||||
'availability_zone': kwargs.pop('availability_zone', 'nova'),
|
||||
'status': kwargs.pop('status', 'enabled'),
|
||||
'state': kwargs.pop('state', 'up'),
|
||||
'updated_at': kwargs.pop('updated_at', '2026-01-09T12:00:00Z'),
|
||||
'disabled_reason': kwargs.pop('disabled_reason', None),
|
||||
}
|
||||
service_info.update(kwargs)
|
||||
return service.Service(**service_info)
|
||||
|
||||
def create_openstacksdk_migration(self, **kwargs):
|
||||
"""Create a real OpenStackSDK ServerMigration object.
|
||||
|
||||
:param kwargs: migration attributes
|
||||
:returns: openstack.compute.v2.server_migration.ServerMigration object
|
||||
"""
|
||||
migration_info = {
|
||||
'id': kwargs.pop('id', 'migration-id'),
|
||||
}
|
||||
migration_info.update(kwargs)
|
||||
return server_migration.ServerMigration(**migration_info)
|
||||
|
||||
Reference in New Issue
Block a user