Complete compute.hypervisor functions
For the OSC ensure we do support everything what nova supports on the hypervisors front Change-Id: I9051123e905f7df44f9cb9be62c90f24140cb9ac
This commit is contained in:
parent
e674ee3f76
commit
30116cf3c2
@ -140,7 +140,8 @@ Hypervisor Operations
|
||||
|
||||
.. autoclass:: openstack.compute.v2._proxy.Proxy
|
||||
:noindex:
|
||||
:members: get_hypervisor, find_hypervisor, hypervisors
|
||||
:members: get_hypervisor, find_hypervisor, hypervisors,
|
||||
get_hypervisor_uptime
|
||||
|
||||
Extension Operations
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -12,3 +12,4 @@ Compute Resources
|
||||
v2/server
|
||||
v2/server_interface
|
||||
v2/server_ip
|
||||
v2/hypervisor
|
||||
|
12
doc/source/user/resources/compute/v2/hypervisor.rst
Normal file
12
doc/source/user/resources/compute/v2/hypervisor.rst
Normal file
@ -0,0 +1,12 @@
|
||||
openstack.compute.v2.hypervisor
|
||||
===============================
|
||||
|
||||
.. automodule:: openstack.compute.v2.hypervisor
|
||||
|
||||
The Hypervisor Class
|
||||
--------------------
|
||||
|
||||
The ``Hypervisor`` class inherits from :class:`~openstack.resource.Resource`.
|
||||
|
||||
.. autoclass:: openstack.compute.v2.hypervisor.Hypervisor
|
||||
:members:
|
@ -1362,6 +1362,8 @@ class Proxy(proxy.Proxy):
|
||||
"""
|
||||
return self._list(_server_group.ServerGroup, **query)
|
||||
|
||||
# ========== Hypervisors ==========
|
||||
|
||||
def hypervisors(self, details=False, **query):
|
||||
"""Return a generator of hypervisor
|
||||
|
||||
@ -1374,9 +1376,16 @@ class Proxy(proxy.Proxy):
|
||||
:rtype: class: `~openstack.compute.v2.hypervisor.Hypervisor`
|
||||
"""
|
||||
base_path = '/os-hypervisors/detail' if details else None
|
||||
if (
|
||||
'hypervisor_hostname_pattern' in query
|
||||
and not utils.supports_microversion(self, '2.53')
|
||||
):
|
||||
# Until 2.53 we need to use other API
|
||||
base_path = '/os-hypervisors/{pattern}/search'.format(
|
||||
pattern=query.pop('hypervisor_hostname_pattern'))
|
||||
return self._list(_hypervisor.Hypervisor, base_path=base_path, **query)
|
||||
|
||||
def find_hypervisor(self, name_or_id, ignore_missing=True):
|
||||
def find_hypervisor(self, name_or_id, ignore_missing=True, details=True):
|
||||
"""Find a hypervisor from name or id to get the corresponding info
|
||||
|
||||
:param name_or_id: The name or id of a hypervisor
|
||||
@ -1386,23 +1395,40 @@ class Proxy(proxy.Proxy):
|
||||
or None
|
||||
"""
|
||||
|
||||
list_base_path = '/os-hypervisors/detail' if details else None
|
||||
return self._find(_hypervisor.Hypervisor, name_or_id,
|
||||
list_base_path=list_base_path,
|
||||
ignore_missing=ignore_missing)
|
||||
|
||||
def get_hypervisor(self, hypervisor):
|
||||
"""Get a single hypervisor
|
||||
|
||||
:param hypervisor: The value can be the ID of a hypervisor or a
|
||||
:class:`~openstack.compute.v2.hypervisor.Hypervisor`
|
||||
instance.
|
||||
:class:`~openstack.compute.v2.hypervisor.Hypervisor`
|
||||
instance.
|
||||
|
||||
:returns:
|
||||
A :class:`~openstack.compute.v2.hypervisor.Hypervisor` object.
|
||||
:raises: :class:`~openstack.exceptions.ResourceNotFound`
|
||||
when no resource can be found.
|
||||
when no resource can be found.
|
||||
"""
|
||||
return self._get(_hypervisor.Hypervisor, hypervisor)
|
||||
|
||||
def get_hypervisor_uptime(self, hypervisor):
|
||||
"""Get uptime information for hypervisor
|
||||
|
||||
:param hypervisor: The value can be the ID of a hypervisor or a
|
||||
:class:`~openstack.compute.v2.hypervisor.Hypervisor`
|
||||
instance.
|
||||
|
||||
:returns:
|
||||
A :class:`~openstack.compute.v2.hypervisor.Hypervisor` object.
|
||||
:raises: :class:`~openstack.exceptions.ResourceNotFound`
|
||||
when no resource can be found.
|
||||
"""
|
||||
hypervisor = self._get_resource(_hypervisor.Hypervisor, hypervisor)
|
||||
return hypervisor.get_uptime(self)
|
||||
|
||||
# ========== Services ==========
|
||||
|
||||
def update_service_forced_down(
|
||||
|
@ -10,8 +10,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import warnings
|
||||
|
||||
from openstack import exceptions
|
||||
from openstack import resource
|
||||
from openstack import utils
|
||||
|
||||
|
||||
class Hypervisor(resource.Resource):
|
||||
@ -27,48 +30,72 @@ class Hypervisor(resource.Resource):
|
||||
'hypervisor_hostname_pattern', 'with_servers'
|
||||
)
|
||||
|
||||
# Hypervisor id is a UUID starting with 2.53
|
||||
_max_microversion = '2.53'
|
||||
# Lot of attributes are dropped in 2.88
|
||||
_max_microversion = '2.88'
|
||||
|
||||
# Properties
|
||||
#: Status of hypervisor
|
||||
status = resource.Body('status')
|
||||
#: State of hypervisor
|
||||
state = resource.Body('state')
|
||||
#: Name of hypervisor
|
||||
name = resource.Body('hypervisor_hostname')
|
||||
#: Service details
|
||||
service_details = resource.Body('service')
|
||||
#: Count of the VCPUs in use
|
||||
vcpus_used = resource.Body('vcpus_used')
|
||||
#: Count of all VCPUs
|
||||
vcpus = resource.Body('vcpus')
|
||||
#: Count of the running virtual machines
|
||||
running_vms = resource.Body('running_vms')
|
||||
#: Information about the hypervisor's CPU. Up to 2.28 it was string.
|
||||
cpu_info = resource.Body('cpu_info')
|
||||
#: IP address of the host
|
||||
host_ip = resource.Body('host_ip')
|
||||
#: The type of hypervisor
|
||||
hypervisor_type = resource.Body('hypervisor_type')
|
||||
#: Version of the hypervisor
|
||||
hypervisor_version = resource.Body('hypervisor_version')
|
||||
#: The amount, in gigabytes, of local storage used
|
||||
local_disk_used = resource.Body('local_gb_used')
|
||||
#: The amount, in gigabytes, of the local storage device
|
||||
local_disk_size = resource.Body('local_gb')
|
||||
#: The amount, in gigabytes, of free space on the local storage device
|
||||
local_disk_free = resource.Body('free_disk_gb')
|
||||
#: The amount, in megabytes, of memory
|
||||
memory_used = resource.Body('memory_mb_used')
|
||||
#: The amount, in megabytes, of total memory
|
||||
memory_size = resource.Body('memory_mb')
|
||||
#: The amount, in megabytes, of available memory
|
||||
memory_free = resource.Body('free_ram_mb')
|
||||
#: Name of hypervisor
|
||||
name = resource.Body('hypervisor_hostname')
|
||||
#: Service details
|
||||
service_details = resource.Body('service', type=dict)
|
||||
#: List of Servers
|
||||
servers = resource.Body('servers', type=list, list_type=dict)
|
||||
#: State of hypervisor
|
||||
state = resource.Body('state')
|
||||
#: Status of hypervisor
|
||||
status = resource.Body('status')
|
||||
#: The total uptime of the hypervisor and information about average load.
|
||||
#: This attribute is set only when querying uptime explicitly.
|
||||
uptime = resource.Body('uptime')
|
||||
|
||||
# Attributes deprecated with 2.88
|
||||
#: Measurement of the hypervisor's current workload
|
||||
current_workload = resource.Body('current_workload')
|
||||
#: Information about the hypervisor's CPU
|
||||
cpu_info = resource.Body('cpu_info')
|
||||
#: IP address of the host
|
||||
host_ip = resource.Body('host_ip')
|
||||
current_workload = resource.Body('current_workload', deprecated=True)
|
||||
#: Disk space available to the scheduler
|
||||
disk_available = resource.Body("disk_available_least")
|
||||
disk_available = resource.Body("disk_available_least", deprecated=True)
|
||||
#: The amount, in gigabytes, of local storage used
|
||||
local_disk_used = resource.Body('local_gb_used', deprecated=True)
|
||||
#: The amount, in gigabytes, of the local storage device
|
||||
local_disk_size = resource.Body('local_gb', deprecated=True)
|
||||
#: The amount, in gigabytes, of free space on the local storage device
|
||||
local_disk_free = resource.Body('free_disk_gb', deprecated=True)
|
||||
#: The amount, in megabytes, of memory
|
||||
memory_used = resource.Body('memory_mb_used', deprecated=True)
|
||||
#: The amount, in megabytes, of total memory
|
||||
memory_size = resource.Body('memory_mb', deprecated=True)
|
||||
#: The amount, in megabytes, of available memory
|
||||
memory_free = resource.Body('free_ram_mb', deprecated=True)
|
||||
#: Count of the running virtual machines
|
||||
running_vms = resource.Body('running_vms', deprecated=True)
|
||||
#: Count of the VCPUs in use
|
||||
vcpus_used = resource.Body('vcpus_used', deprecated=True)
|
||||
#: Count of all VCPUs
|
||||
vcpus = resource.Body('vcpus', deprecated=True)
|
||||
|
||||
def get_uptime(self, session):
|
||||
"""Get uptime information for the hypervisor
|
||||
|
||||
Updates uptime attribute of the hypervisor object
|
||||
"""
|
||||
warnings.warn(
|
||||
"This call is deprecated and is only available until Nova 2.88")
|
||||
if utils.supports_microversion(session, '2.88'):
|
||||
raise exceptions.SDKException(
|
||||
'Hypervisor.get_uptime is not supported anymore')
|
||||
url = utils.urljoin(self.base_path, self.id, 'uptime')
|
||||
microversion = self._get_microversion_for(session, 'fetch')
|
||||
response = session.get(
|
||||
url, microversion=microversion)
|
||||
self._translate_response(response)
|
||||
return self
|
||||
|
||||
|
||||
HypervisorDetail = Hypervisor
|
||||
|
31
openstack/tests/functional/compute/v2/test_hypervisor.py
Normal file
31
openstack/tests/functional/compute/v2/test_hypervisor.py
Normal file
@ -0,0 +1,31 @@
|
||||
# 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 openstack.tests.functional import base
|
||||
|
||||
|
||||
class TestHypervisor(base.BaseFunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHypervisor, self).setUp()
|
||||
|
||||
def test_list_hypervisors(self):
|
||||
rslt = list(self.conn.compute.hypervisors())
|
||||
self.assertIsNotNone(rslt)
|
||||
|
||||
rslt = list(self.conn.compute.hypervisors(details=True))
|
||||
self.assertIsNotNone(rslt)
|
||||
|
||||
def test_get_find_hypervisors(self):
|
||||
for hypervisor in self.conn.compute.hypervisors():
|
||||
self.conn.compute.get_hypervisor(hypervisor.id)
|
||||
self.conn.compute.find_hypervisor(hypervisor.id)
|
@ -9,41 +9,79 @@
|
||||
# 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 copy
|
||||
from unittest import mock
|
||||
|
||||
from keystoneauth1 import adapter
|
||||
|
||||
from openstack import exceptions
|
||||
from openstack.tests.unit import base
|
||||
|
||||
from openstack.compute.v2 import hypervisor
|
||||
|
||||
EXAMPLE = {
|
||||
"status": "enabled",
|
||||
"service": {
|
||||
"host": "fake-mini",
|
||||
"disabled_reason": None,
|
||||
"id": 6
|
||||
"cpu_info": {
|
||||
"arch": "x86_64",
|
||||
"model": "Nehalem",
|
||||
"vendor": "Intel",
|
||||
"features": [
|
||||
"pge",
|
||||
"clflush"
|
||||
],
|
||||
"topology": {
|
||||
"cores": 1,
|
||||
"threads": 1,
|
||||
"sockets": 4
|
||||
}
|
||||
},
|
||||
"state": "up",
|
||||
"status": "enabled",
|
||||
"servers": [
|
||||
{
|
||||
"name": "test_server1",
|
||||
"uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
|
||||
},
|
||||
{
|
||||
"name": "test_server2",
|
||||
"uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
|
||||
}
|
||||
],
|
||||
"host_ip": "1.1.1.1",
|
||||
"hypervisor_hostname": "fake-mini",
|
||||
"hypervisor_type": "fake",
|
||||
"hypervisor_version": 1000,
|
||||
"id": "b1e43b5f-eec1-44e0-9f10-7b4945c0226d",
|
||||
"uptime": (
|
||||
" 08:32:11 up 93 days, 18:25, 12 users, "
|
||||
"load average: 0.20, 0.12, 0.14"),
|
||||
"service": {
|
||||
"host": "043b3cacf6f34c90a7245151fc8ebcda",
|
||||
"id": "5d343e1d-938e-4284-b98b-6a2b5406ba76",
|
||||
"disabled_reason": None
|
||||
},
|
||||
# deprecated attributes
|
||||
"vcpus_used": 0,
|
||||
"hypervisor_type": "QEMU",
|
||||
"local_gb_used": 0,
|
||||
"vcpus": 8,
|
||||
"hypervisor_hostname": "fake-mini",
|
||||
"memory_mb_used": 512,
|
||||
"memory_mb": 7980,
|
||||
"current_workload": 0,
|
||||
"state": "up",
|
||||
"host_ip": "23.253.248.171",
|
||||
"cpu_info": "some cpu info",
|
||||
"running_vms": 0,
|
||||
"free_disk_gb": 157,
|
||||
"hypervisor_version": 2000000,
|
||||
"disk_available_least": 140,
|
||||
"local_gb": 157,
|
||||
"free_ram_mb": 7468,
|
||||
"id": 1
|
||||
}
|
||||
|
||||
|
||||
class TestHypervisor(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHypervisor, self).setUp()
|
||||
self.sess = mock.Mock(spec=adapter.Adapter)
|
||||
self.sess.default_microversion = 1
|
||||
self.sess._get_connection = mock.Mock(return_value=self.cloud)
|
||||
|
||||
def test_basic(self):
|
||||
sot = hypervisor.Hypervisor()
|
||||
self.assertEqual('hypervisor', sot.resource_key)
|
||||
@ -62,10 +100,17 @@ class TestHypervisor(base.TestCase):
|
||||
def test_make_it(self):
|
||||
sot = hypervisor.Hypervisor(**EXAMPLE)
|
||||
self.assertEqual(EXAMPLE['id'], sot.id)
|
||||
self.assertEqual(EXAMPLE['cpu_info'], sot.cpu_info)
|
||||
self.assertEqual(EXAMPLE['host_ip'], sot.host_ip)
|
||||
self.assertEqual(EXAMPLE['hypervisor_type'], sot.hypervisor_type)
|
||||
self.assertEqual(EXAMPLE['hypervisor_version'], sot.hypervisor_version)
|
||||
self.assertEqual(EXAMPLE['hypervisor_hostname'], sot.name)
|
||||
self.assertEqual(EXAMPLE['service'], sot.service_details)
|
||||
self.assertEqual(EXAMPLE['servers'], sot.servers)
|
||||
self.assertEqual(EXAMPLE['state'], sot.state)
|
||||
self.assertEqual(EXAMPLE['status'], sot.status)
|
||||
self.assertEqual(EXAMPLE['service'], sot.service_details)
|
||||
self.assertEqual(EXAMPLE['uptime'], sot.uptime)
|
||||
# Verify deprecated attributes
|
||||
self.assertEqual(EXAMPLE['vcpus_used'], sot.vcpus_used)
|
||||
self.assertEqual(EXAMPLE['hypervisor_type'], sot.hypervisor_type)
|
||||
self.assertEqual(EXAMPLE['local_gb_used'], sot.local_disk_used)
|
||||
@ -74,11 +119,46 @@ class TestHypervisor(base.TestCase):
|
||||
self.assertEqual(EXAMPLE['memory_mb_used'], sot.memory_used)
|
||||
self.assertEqual(EXAMPLE['memory_mb'], sot.memory_size)
|
||||
self.assertEqual(EXAMPLE['current_workload'], sot.current_workload)
|
||||
self.assertEqual(EXAMPLE['host_ip'], sot.host_ip)
|
||||
self.assertEqual(EXAMPLE['cpu_info'], sot.cpu_info)
|
||||
self.assertEqual(EXAMPLE['running_vms'], sot.running_vms)
|
||||
self.assertEqual(EXAMPLE['free_disk_gb'], sot.local_disk_free)
|
||||
self.assertEqual(EXAMPLE['hypervisor_version'], sot.hypervisor_version)
|
||||
self.assertEqual(EXAMPLE['disk_available_least'], sot.disk_available)
|
||||
self.assertEqual(EXAMPLE['local_gb'], sot.local_disk_size)
|
||||
self.assertEqual(EXAMPLE['free_ram_mb'], sot.memory_free)
|
||||
|
||||
@mock.patch('openstack.utils.supports_microversion', autospec=True,
|
||||
return_value=False)
|
||||
def test_get_uptime(self, mv_mock):
|
||||
sot = hypervisor.Hypervisor(**copy.deepcopy(EXAMPLE))
|
||||
rsp = {
|
||||
"hypervisor": {
|
||||
"hypervisor_hostname": "fake-mini",
|
||||
"id": sot.id,
|
||||
"state": "up",
|
||||
"status": "enabled",
|
||||
"uptime": "08:32:11 up 93 days, 18:25, 12 users"
|
||||
}
|
||||
}
|
||||
resp = mock.Mock()
|
||||
resp.body = copy.deepcopy(rsp)
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
resp.headers = {}
|
||||
resp.status_code = 200
|
||||
self.sess.get = mock.Mock(return_value=resp)
|
||||
|
||||
hyp = sot.get_uptime(self.sess)
|
||||
self.sess.get.assert_called_with(
|
||||
'os-hypervisors/{id}/uptime'.format(id=sot.id),
|
||||
microversion=self.sess.default_microversion
|
||||
)
|
||||
self.assertEqual(rsp['hypervisor']['uptime'], hyp.uptime)
|
||||
self.assertEqual(rsp['hypervisor']['status'], sot.status)
|
||||
|
||||
@mock.patch('openstack.utils.supports_microversion', autospec=True,
|
||||
return_value=True)
|
||||
def test_get_uptime_after_2_88(self, mv_mock):
|
||||
sot = hypervisor.Hypervisor(**copy.deepcopy(EXAMPLE))
|
||||
self.assertRaises(
|
||||
exceptions.SDKException,
|
||||
sot.get_uptime,
|
||||
self.sess
|
||||
)
|
||||
|
@ -445,6 +445,74 @@ class TestService(TestComputeProxy):
|
||||
)
|
||||
|
||||
|
||||
class TestHypervisor(TestComputeProxy):
|
||||
|
||||
def test_hypervisors_not_detailed(self):
|
||||
self.verify_list(self.proxy.hypervisors, hypervisor.Hypervisor,
|
||||
method_kwargs={"details": False})
|
||||
|
||||
def test_hypervisors_detailed(self):
|
||||
self.verify_list(self.proxy.hypervisors, hypervisor.HypervisorDetail,
|
||||
method_kwargs={"details": True})
|
||||
|
||||
@mock.patch('openstack.utils.supports_microversion', autospec=True,
|
||||
return_value=False)
|
||||
def test_hypervisors_search_before_253_no_qp(self, sm):
|
||||
self.verify_list(
|
||||
self.proxy.hypervisors,
|
||||
hypervisor.Hypervisor,
|
||||
method_kwargs={'details': True},
|
||||
base_path='/os-hypervisors/detail'
|
||||
)
|
||||
|
||||
@mock.patch('openstack.utils.supports_microversion', autospec=True,
|
||||
return_value=False)
|
||||
def test_hypervisors_search_before_253(self, sm):
|
||||
self.verify_list(
|
||||
self.proxy.hypervisors,
|
||||
hypervisor.Hypervisor,
|
||||
method_kwargs={'hypervisor_hostname_pattern': 'substring'},
|
||||
base_path='/os-hypervisors/substring/search'
|
||||
)
|
||||
|
||||
@mock.patch('openstack.utils.supports_microversion', autospec=True,
|
||||
return_value=True)
|
||||
def test_hypervisors_search_after_253(self, sm):
|
||||
self.verify_list(
|
||||
self.proxy.hypervisors,
|
||||
hypervisor.Hypervisor,
|
||||
method_kwargs={'hypervisor_hostname_pattern': 'substring'},
|
||||
base_path=None,
|
||||
expected_kwargs={'hypervisor_hostname_pattern': 'substring'}
|
||||
)
|
||||
|
||||
def test_find_hypervisor_detail(self):
|
||||
self.verify_find(self.proxy.find_hypervisor,
|
||||
hypervisor.Hypervisor,
|
||||
expected_kwargs={
|
||||
'list_base_path': '/os-hypervisors/detail',
|
||||
'ignore_missing': False})
|
||||
|
||||
def test_find_hypervisor_no_detail(self):
|
||||
self.verify_find(self.proxy.find_hypervisor,
|
||||
hypervisor.Hypervisor,
|
||||
method_kwargs={'details': False},
|
||||
expected_kwargs={
|
||||
'list_base_path': None,
|
||||
'ignore_missing': False})
|
||||
|
||||
def test_get_hypervisor(self):
|
||||
self.verify_get(self.proxy.get_hypervisor,
|
||||
hypervisor.Hypervisor)
|
||||
|
||||
def test_get_hypervisor_uptime(self):
|
||||
self._verify(
|
||||
"openstack.compute.v2.hypervisor.Hypervisor.get_uptime",
|
||||
self.proxy.get_hypervisor_uptime,
|
||||
method_args=["value"],
|
||||
expected_args=[])
|
||||
|
||||
|
||||
class TestCompute(TestComputeProxy):
|
||||
def test_extension_find(self):
|
||||
self.verify_find(self.proxy.find_extension, extension.Extension)
|
||||
@ -870,22 +938,6 @@ class TestCompute(TestComputeProxy):
|
||||
def test_server_groups(self):
|
||||
self.verify_list(self.proxy.server_groups, server_group.ServerGroup)
|
||||
|
||||
def test_hypervisors_not_detailed(self):
|
||||
self.verify_list(self.proxy.hypervisors, hypervisor.Hypervisor,
|
||||
method_kwargs={"details": False})
|
||||
|
||||
def test_hypervisors_detailed(self):
|
||||
self.verify_list(self.proxy.hypervisors, hypervisor.HypervisorDetail,
|
||||
method_kwargs={"details": True})
|
||||
|
||||
def test_find_hypervisor(self):
|
||||
self.verify_find(self.proxy.find_hypervisor,
|
||||
hypervisor.Hypervisor)
|
||||
|
||||
def test_get_hypervisor(self):
|
||||
self.verify_get(self.proxy.get_hypervisor,
|
||||
hypervisor.Hypervisor)
|
||||
|
||||
def test_live_migrate_server(self):
|
||||
self._verify('openstack.compute.v2.server.Server.live_migrate',
|
||||
self.proxy.live_migrate_server,
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Compute Hypervisor resource and functions are reworked to comply 2.88
|
||||
microversion with deprecating misleading attributes.
|
Loading…
Reference in New Issue
Block a user