Merge "Complete compute aggregate functions"

This commit is contained in:
Zuul 2020-11-18 12:08:02 +00:00 committed by Gerrit Code Review
commit e169c495de
6 changed files with 141 additions and 16 deletions

View File

@ -274,6 +274,20 @@ class Proxy(proxy.Proxy):
"""
return self._get(_aggregate.Aggregate, aggregate)
def find_aggregate(self, name_or_id, ignore_missing=True):
"""Find a single aggregate
:param name_or_id: The name or ID of an aggregate.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be raised when
the resource does not exist. When set to ``True``, None will be
returned when attempting to find a nonexistent resource.
:returns: One :class:`~openstack.compute.v2.aggregate.Aggregate`
or None
"""
return self._find(_aggregate.Aggregate, name_or_id,
ignore_missing=ignore_missing)
def create_aggregate(self, **attrs):
"""Create a new host aggregate from attributes
@ -358,6 +372,26 @@ class Proxy(proxy.Proxy):
aggregate = self._get_resource(_aggregate.Aggregate, aggregate)
return aggregate.set_metadata(self, metadata)
def aggregate_precache_images(self, aggregate, images):
"""Requests image precaching on an aggregate
:param aggregate: Either the ID of a aggregate or a
:class:`~openstack.compute.v2.aggregate.Aggregate` instance.
:param images: Single image id or list of image ids.
:returns: ``None``
"""
aggregate = self._get_resource(_aggregate.Aggregate, aggregate)
# We need to ensure we pass list of image IDs
if isinstance(images, str):
images = [images]
image_data = []
for img in images:
image_data.append({'id': img})
return aggregate.precache_images(self, image_data)
# ========== Images ==========
def delete_image(self, image, ignore_missing=True):
"""Delete an image

View File

@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from openstack import exceptions
from openstack import resource
from openstack import utils
@ -30,25 +30,31 @@ class Aggregate(resource.Resource):
# Properties
#: Availability zone of aggregate
availability_zone = resource.Body('availability_zone')
#: The date and time when the resource was created.
created_at = resource.Body('created_at')
#: The date and time when the resource was deleted.
deleted_at = resource.Body('deleted_at')
#: Deleted?
deleted = resource.Body('deleted')
is_deleted = resource.Body('deleted', type=bool)
#: Name of aggregate
name = resource.Body('name')
#: Hosts
hosts = resource.Body('hosts')
hosts = resource.Body('hosts', type=list)
#: Metadata
metadata = resource.Body('metadata')
metadata = resource.Body('metadata', type=dict)
#: The date and time when the resource was updated
updated_at = resource.Body('updated_at')
#: UUID
uuid = resource.Body('uuid')
# uuid introduced in 2.41
_max_microversion = '2.41'
# Image pre-caching introduced in 2.81
_max_microversion = '2.81'
def _action(self, session, body, microversion=None):
"""Preform aggregate actions given the message body."""
url = utils.urljoin(self.base_path, self.id, 'action')
headers = {'Accept': ''}
response = session.post(
url, json=body, headers=headers, microversion=microversion)
url, json=body, microversion=microversion)
exceptions.raise_from_response(response)
aggregate = Aggregate()
aggregate._translate_response(response=response)
return aggregate
@ -67,3 +73,12 @@ class Aggregate(resource.Resource):
"""Creates or replaces metadata for an aggregate."""
body = {'set_metadata': {'metadata': metadata}}
return self._action(session, body)
def precache_images(self, session, images):
"""Requests image pre-caching"""
body = {'cache': images}
url = utils.urljoin(self.base_path, self.id, 'images')
response = session.post(
url, json=body, microversion=self._max_microversion)
exceptions.raise_from_response(response)
# This API has no result

View File

@ -1895,7 +1895,9 @@ class Resource(dict):
connection=session._get_connection(),
**params)
return match.fetch(session, **params)
except exceptions.NotFoundException:
except (exceptions.NotFoundException, exceptions.BadRequestException):
# NOTE(gtema): There are few places around openstack that return
# 400 if we try to GET resource and it doesn't exist.
pass
if ('name' in cls._query_mapping._mapping.keys()

View File

@ -60,7 +60,10 @@ class TestAggregate(base.TestCase):
sot = aggregate.Aggregate(**EXAMPLE)
self.assertEqual(EXAMPLE['name'], sot.name)
self.assertEqual(EXAMPLE['availability_zone'], sot.availability_zone)
self.assertEqual(EXAMPLE['deleted'], sot.deleted)
self.assertEqual(EXAMPLE['deleted'], sot.is_deleted)
self.assertEqual(EXAMPLE['deleted_at'], sot.deleted_at)
self.assertEqual(EXAMPLE['created_at'], sot.created_at)
self.assertEqual(EXAMPLE['updated_at'], sot.updated_at)
self.assertEqual(EXAMPLE['hosts'], sot.hosts)
self.assertEqual(EXAMPLE['id'], sot.id)
self.assertEqual(EXAMPLE['uuid'], sot.uuid)
@ -73,9 +76,8 @@ class TestAggregate(base.TestCase):
url = 'os-aggregates/4/action'
body = {"add_host": {"host": "host1"}}
headers = {'Accept': ''}
self.sess.post.assert_called_with(
url, json=body, headers=headers, microversion=None)
url, json=body, microversion=None)
def test_remove_host(self):
sot = aggregate.Aggregate(**EXAMPLE)
@ -84,9 +86,8 @@ class TestAggregate(base.TestCase):
url = 'os-aggregates/4/action'
body = {"remove_host": {"host": "host1"}}
headers = {'Accept': ''}
self.sess.post.assert_called_with(
url, json=body, headers=headers, microversion=None)
url, json=body, microversion=None)
def test_set_metadata(self):
sot = aggregate.Aggregate(**EXAMPLE)
@ -95,6 +96,15 @@ class TestAggregate(base.TestCase):
url = 'os-aggregates/4/action'
body = {"set_metadata": {"metadata": {"key: value"}}}
headers = {'Accept': ''}
self.sess.post.assert_called_with(
url, json=body, headers=headers, microversion=None)
url, json=body, microversion=None)
def test_precache_image(self):
sot = aggregate.Aggregate(**EXAMPLE)
sot.precache_images(self.sess, ['1'])
url = 'os-aggregates/4/images'
body = {"cache": ['1']}
self.sess.post.assert_called_with(
url, json=body, microversion=sot._max_microversion)

View File

@ -12,6 +12,7 @@
from unittest import mock
from openstack.compute.v2 import _proxy
from openstack.compute.v2 import aggregate
from openstack.compute.v2 import availability_zone as az
from openstack.compute.v2 import extension
from openstack.compute.v2 import flavor
@ -248,6 +249,63 @@ class TestKeyPair(TestComputeProxy):
)
class TestAggregate(TestComputeProxy):
def test_aggregate_create(self):
self.verify_create(self.proxy.create_aggregate, aggregate.Aggregate)
def test_aggregate_delete(self):
self.verify_delete(
self.proxy.delete_aggregate, aggregate.Aggregate, False)
def test_aggregate_delete_ignore(self):
self.verify_delete(
self.proxy.delete_aggregate, aggregate.Aggregate, True)
def test_aggregate_find(self):
self.verify_find(self.proxy.find_aggregate, aggregate.Aggregate)
def test_aggregates(self):
self.verify_list_no_kwargs(self.proxy.aggregates, aggregate.Aggregate)
def test_aggregate_get(self):
self.verify_get(self.proxy.get_aggregate, aggregate.Aggregate)
def test_aggregate_update(self):
self.verify_update(self.proxy.update_aggregate, aggregate.Aggregate)
def test_aggregate_add_host(self):
self._verify("openstack.compute.v2.aggregate.Aggregate.add_host",
self.proxy.add_host_to_aggregate,
method_args=["value", "host"],
expected_args=["host"])
def test_aggregate_remove_host(self):
self._verify("openstack.compute.v2.aggregate.Aggregate.remove_host",
self.proxy.remove_host_from_aggregate,
method_args=["value", "host"],
expected_args=["host"])
def test_aggregate_set_metadata(self):
self._verify("openstack.compute.v2.aggregate.Aggregate.set_metadata",
self.proxy.set_aggregate_metadata,
method_args=["value", {'a': 'b'}],
expected_args=[{'a': 'b'}])
def test_aggregate_precache_image(self):
self._verify(
"openstack.compute.v2.aggregate.Aggregate.precache_images",
self.proxy.aggregate_precache_images,
method_args=["value", '1'],
expected_args=[[{'id': '1'}]])
def test_aggregate_precache_images(self):
self._verify(
"openstack.compute.v2.aggregate.Aggregate.precache_images",
self.proxy.aggregate_precache_images,
method_args=["value", ['1', '2']],
expected_args=[[{'id': '1'}, {'id': '2'}]])
class TestCompute(TestComputeProxy):
def test_extension_find(self):
self.verify_find(self.proxy.find_extension, extension.Extension)

View File

@ -0,0 +1,6 @@
---
features:
- Complete compute.aggregate functions to the latest state
fixes:
- aggregate.deleted property is renamed to 'is_deleted' to comply with the
naming convention