Merge "Add magnum baymodel calls to shade."
This commit is contained in:
commit
eb4ed45cf4
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Add support for Magnum baymodels, with the
|
||||
usual methods (search/list/get/create/update/delete).
|
@ -907,3 +907,24 @@ class CinderQuotasGet(task_manager.Task):
|
||||
class CinderQuotasDelete(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.cinder_client.quotas.delete(**self.args)
|
||||
|
||||
|
||||
class BaymodelList(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.magnum_client.baymodels.list(**self.args)
|
||||
|
||||
|
||||
class BaymodelCreate(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.magnum_client.baymodels.create(**self.args)
|
||||
|
||||
|
||||
class BaymodelDelete(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.magnum_client.baymodels.delete(self.args['id'])
|
||||
|
||||
|
||||
class BaymodelUpdate(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.magnum_client.baymodels.update(
|
||||
self.args['id'], self.args['patch'])
|
||||
|
@ -495,6 +495,13 @@ def normalize_flavors(flavors):
|
||||
return flavors
|
||||
|
||||
|
||||
def normalize_baymodels(baymodels):
|
||||
"""Normalize Magnum baymodels."""
|
||||
for baymodel in baymodels:
|
||||
baymodel['id'] = baymodel['uuid']
|
||||
return baymodels
|
||||
|
||||
|
||||
def valid_kwargs(*valid_args):
|
||||
# This decorator checks if argument passed as **kwargs to a function are
|
||||
# present in valid_args.
|
||||
@ -759,3 +766,21 @@ def range_filter(data, key, range_exp):
|
||||
if int(d[key]) == val_range[1]:
|
||||
filtered.append(d)
|
||||
return filtered
|
||||
|
||||
|
||||
def generate_patches_from_kwargs(operation, **kwargs):
|
||||
"""Given a set of parameters, returns a list with the
|
||||
valid patch values.
|
||||
|
||||
:param string operation: The operation to perform.
|
||||
:param list kwargs: Dict of parameters.
|
||||
|
||||
:returns: A list with the right patch values.
|
||||
"""
|
||||
patches = []
|
||||
for k, v in kwargs.items():
|
||||
patch = {'op': operation,
|
||||
'value': v,
|
||||
'path': '/%s' % k}
|
||||
patches.append(patch)
|
||||
return patches
|
||||
|
@ -30,6 +30,7 @@ import cinderclient.exceptions as cinder_exceptions
|
||||
import glanceclient
|
||||
import glanceclient.exc
|
||||
import heatclient.client
|
||||
import magnumclient.exceptions as magnum_exceptions
|
||||
from heatclient.common import event_utils
|
||||
from heatclient.common import template_utils
|
||||
from heatclient import exc as heat_exceptions
|
||||
@ -5662,3 +5663,158 @@ class OpenStackCloud(object):
|
||||
_tasks.RecordSetDelete(zone=zone['id'], recordset=name_or_id))
|
||||
|
||||
return True
|
||||
|
||||
@_utils.cache_on_arguments()
|
||||
def list_baymodels(self, detail=False):
|
||||
"""List Magnum baymodels.
|
||||
|
||||
:param bool detail. Flag to control if we need summarized or
|
||||
detailed output.
|
||||
|
||||
:returns: a list of dicts containing the baymodel details.
|
||||
|
||||
:raises: ``OpenStackCloudException``: if something goes wrong during
|
||||
the openstack API call.
|
||||
"""
|
||||
with _utils.shade_exceptions("Error fetching baymodel list"):
|
||||
baymodels = self.manager.submitTask(
|
||||
_tasks.BaymodelList(detail=detail))
|
||||
return _utils.normalize_baymodels(baymodels)
|
||||
|
||||
def search_baymodels(self, name_or_id=None, filters=None, detail=False):
|
||||
"""Search Magnum baymodels.
|
||||
|
||||
:param name_or_id: baymodel name or ID.
|
||||
:param filters: a dict containing additional filters to use.
|
||||
:param detail: a boolean to control if we need summarized or
|
||||
detailed output.
|
||||
|
||||
:returns: a list of dict containing the baymodels
|
||||
|
||||
:raises: ``OpenStackCloudException``: if something goes wrong during
|
||||
the openstack API call.
|
||||
"""
|
||||
baymodels = self.list_baymodels(detail=detail)
|
||||
return _utils._filter_list(
|
||||
baymodels, name_or_id, filters)
|
||||
|
||||
def get_baymodel(self, name_or_id, filters=None, detail=False):
|
||||
"""Get a baymodel by name or ID.
|
||||
|
||||
:param name_or_id: Name or ID of the baymodel.
|
||||
:param dict filters:
|
||||
A dictionary of meta data to use for further filtering. Elements
|
||||
of this dictionary may, themselves, be dictionaries. Example::
|
||||
|
||||
{
|
||||
'last_name': 'Smith',
|
||||
'other': {
|
||||
'gender': 'Female'
|
||||
}
|
||||
}
|
||||
|
||||
:returns: A baymodel dict or None if no matching baymodel is
|
||||
found.
|
||||
|
||||
"""
|
||||
return _utils._get_entity(self.search_baymodels, name_or_id,
|
||||
filters=filters, detail=detail)
|
||||
|
||||
def create_baymodel(self, name, image_id=None, keypair_id=None,
|
||||
coe=None, **kwargs):
|
||||
"""Create a Magnum baymodel.
|
||||
|
||||
:param string name: Name of the baymodel.
|
||||
:param string image_id: Name or ID of the image to use.
|
||||
:param string keypair_id: Name or ID of the keypair to use.
|
||||
:param string coe: Name of the coe for the baymodel.
|
||||
|
||||
Other arguments will be passed in kwargs.
|
||||
|
||||
:returns: a dict containing the baymodel description
|
||||
|
||||
:raises: ``OpenStackCloudException`` if something goes wrong during
|
||||
the openstack API call
|
||||
"""
|
||||
with _utils.shade_exceptions(
|
||||
"Error creating baymodel of name {baymodel_name}".format(
|
||||
baymodel_name=name)):
|
||||
baymodel = self.manager.submitTask(
|
||||
_tasks.BaymodelCreate(
|
||||
name=name, image_id=image_id,
|
||||
keypair_id=keypair_id, coe=coe, **kwargs))
|
||||
|
||||
self.list_baymodels.invalidate(self)
|
||||
return baymodel
|
||||
|
||||
def delete_baymodel(self, name_or_id):
|
||||
"""Delete a baymodel.
|
||||
|
||||
:param name_or_id: Name or unique ID of the baymodel.
|
||||
:returns: True if the delete succeeded, False if the
|
||||
baymodel was not found.
|
||||
|
||||
:raises: OpenStackCloudException on operation error.
|
||||
"""
|
||||
|
||||
self.list_baymodels.invalidate(self)
|
||||
baymodel = self.get_baymodel(name_or_id)
|
||||
|
||||
if not baymodel:
|
||||
self.log.debug(
|
||||
"Baymodel {name_or_id} does not exist".format(
|
||||
name_or_id=name_or_id),
|
||||
exc_info=True)
|
||||
return False
|
||||
|
||||
with _utils.shade_exceptions("Error in deleting baymodel"):
|
||||
try:
|
||||
self.manager.submitTask(
|
||||
_tasks.BaymodelDelete(id=baymodel['id']))
|
||||
except magnum_exceptions.NotFound:
|
||||
self.log.debug(
|
||||
"Baymodel {id} not found when deleting. Ignoring.".format(
|
||||
id=baymodel['id']))
|
||||
return False
|
||||
|
||||
self.list_baymodels.invalidate(self)
|
||||
return True
|
||||
|
||||
@_utils.valid_kwargs('name', 'image_id', 'flavor_id', 'master_flavor_id',
|
||||
'keypair_id', 'external_network_id', 'fixed_network',
|
||||
'dns_nameserver', 'docker_volume_size', 'labels',
|
||||
'coe', 'http_proxy', 'https_proxy', 'no_proxy',
|
||||
'network_driver', 'tls_disabled', 'public',
|
||||
'registry_enabled', 'volume_driver')
|
||||
def update_baymodel(self, name_or_id, operation, **kwargs):
|
||||
"""Update a Magnum baymodel.
|
||||
|
||||
:param name_or_id: Name or ID of the baymodel being updated.
|
||||
:param operation: Operation to perform - add, remove, replace.
|
||||
|
||||
Other arguments will be passed with kwargs.
|
||||
|
||||
:returns: a dict representing the updated baymodel.
|
||||
|
||||
:raises: OpenStackCloudException on operation error.
|
||||
"""
|
||||
self.list_baymodels.invalidate(self)
|
||||
baymodel = self.get_baymodel(name_or_id)
|
||||
if not baymodel:
|
||||
raise OpenStackCloudException(
|
||||
"Baymodel %s not found." % name_or_id)
|
||||
|
||||
if operation not in ['add', 'replace', 'remove']:
|
||||
raise TypeError(
|
||||
"%s operation not in 'add', 'replace', 'remove'" % operation)
|
||||
|
||||
patches = _utils.generate_patches_from_kwargs(operation, **kwargs)
|
||||
|
||||
with _utils.shade_exceptions(
|
||||
"Error updating baymodel {0}".format(name_or_id)):
|
||||
self.manager.submitTask(
|
||||
_tasks.BaymodelUpdate(
|
||||
id=baymodel['id'], patch=patches))
|
||||
|
||||
new_baymodel = self.get_baymodel(name_or_id)
|
||||
return new_baymodel
|
||||
|
113
shade/tests/functional/test_baymodels.py
Normal file
113
shade/tests/functional/test_baymodels.py
Normal file
@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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.
|
||||
|
||||
"""
|
||||
test_baymodels
|
||||
----------------------------------
|
||||
|
||||
Functional tests for `shade` baymodel methods.
|
||||
"""
|
||||
|
||||
from testtools import content
|
||||
|
||||
from shade.tests.functional import base
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
class TestBaymodel(base.BaseFunctionalTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBaymodel, self).setUp()
|
||||
if not self.demo_cloud.has_service('container'):
|
||||
self.skipTest('Container service not supported by cloud')
|
||||
self.baymodel = None
|
||||
|
||||
def test_baymodels(self):
|
||||
'''Test baymodels functionality'''
|
||||
name = 'fake-baymodel'
|
||||
server_type = 'vm'
|
||||
public = False
|
||||
image_id = 'fedora-atomic-f23-dib'
|
||||
tls_disabled = False
|
||||
registry_enabled = False
|
||||
coe = 'kubernetes'
|
||||
keypair_id = 'testkey'
|
||||
|
||||
self.addDetail('baymodel', content.text_content(name))
|
||||
self.addCleanup(self.cleanup, name)
|
||||
|
||||
# generate a keypair to add to nova
|
||||
ssh_directory = '/tmp/.ssh'
|
||||
if not os.path.isdir(ssh_directory):
|
||||
os.mkdir(ssh_directory)
|
||||
subprocess.call(
|
||||
['ssh-keygen', '-t', 'rsa', '-N', '', '-f',
|
||||
'%s/id_rsa_shade' % ssh_directory])
|
||||
|
||||
# add keypair to nova
|
||||
with open('%s/id_rsa_shade.pub' % ssh_directory) as f:
|
||||
key_content = f.read()
|
||||
self.demo_cloud.create_keypair('testkey', key_content)
|
||||
|
||||
# Test we can create a baymodel and we get it returned
|
||||
self.baymodel = self.demo_cloud.create_baymodel(
|
||||
name=name, image_id=image_id,
|
||||
keypair_id=keypair_id, coe=coe)
|
||||
self.assertEquals(self.baymodel['name'], name)
|
||||
self.assertEquals(self.baymodel['image_id'], image_id)
|
||||
self.assertEquals(self.baymodel['keypair_id'], keypair_id)
|
||||
self.assertEquals(self.baymodel['coe'], coe)
|
||||
self.assertEquals(self.baymodel['registry_enabled'], registry_enabled)
|
||||
self.assertEquals(self.baymodel['tls_disabled'], tls_disabled)
|
||||
self.assertEquals(self.baymodel['public'], public)
|
||||
self.assertEquals(self.baymodel['server_type'], server_type)
|
||||
|
||||
# Test that we can list baymodels
|
||||
baymodels = self.demo_cloud.list_baymodels()
|
||||
self.assertIsNotNone(baymodels)
|
||||
|
||||
# Test we get the same baymodel with the get_baymodel method
|
||||
baymodel_get = self.demo_cloud.get_baymodel(self.baymodel['uuid'])
|
||||
self.assertEquals(baymodel_get['uuid'], self.baymodel['uuid'])
|
||||
|
||||
# Test the get method also works by name
|
||||
baymodel_get = self.demo_cloud.get_baymodel(name)
|
||||
self.assertEquals(baymodel_get['name'], self.baymodel['name'])
|
||||
|
||||
# Test we can update a field on the baymodel and only that field
|
||||
# is updated
|
||||
baymodel_update = self.demo_cloud.update_baymodel(
|
||||
self.baymodel['uuid'], 'replace', tls_disabled=True)
|
||||
self.assertEquals(baymodel_update['uuid'],
|
||||
self.baymodel['uuid'])
|
||||
self.assertEquals(baymodel_update['tls_disabled'], True)
|
||||
|
||||
# Test we can delete and get True returned
|
||||
baymodel_delete = self.demo_cloud.delete_baymodel(
|
||||
self.baymodel['uuid'])
|
||||
self.assertTrue(baymodel_delete)
|
||||
|
||||
def cleanup(self, name):
|
||||
if self.baymodel:
|
||||
try:
|
||||
self.demo_cloud.delete_baymodel(self.baymodel['name'])
|
||||
except:
|
||||
pass
|
||||
|
||||
# delete keypair
|
||||
self.demo_cloud.delete_keypair('testkey')
|
||||
os.unlink('/tmp/.ssh/id_rsa_shade')
|
||||
os.unlink('/tmp/.ssh/id_rsa_shade.pub')
|
155
shade/tests/unit/test_baymodels.py
Normal file
155
shade/tests/unit/test_baymodels.py
Normal file
@ -0,0 +1,155 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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 mock
|
||||
import munch
|
||||
|
||||
import shade
|
||||
import testtools
|
||||
from shade.tests.unit import base
|
||||
|
||||
|
||||
baymodel_obj = munch.Munch(
|
||||
apiserver_port=None,
|
||||
uuid='fake-uuid',
|
||||
human_id=None,
|
||||
name='fake-baymodel',
|
||||
server_type='vm',
|
||||
public=False,
|
||||
image_id='fake-image',
|
||||
tls_disabled=False,
|
||||
registry_enabled=False,
|
||||
coe='fake-coe',
|
||||
keypair_id='fake-key',
|
||||
)
|
||||
|
||||
baymodel_detail_obj = munch.Munch(
|
||||
links={},
|
||||
labels={},
|
||||
apiserver_port=None,
|
||||
uuid='fake-uuid',
|
||||
human_id=None,
|
||||
name='fake-baymodel',
|
||||
server_type='vm',
|
||||
public=False,
|
||||
image_id='fake-image',
|
||||
tls_disabled=False,
|
||||
registry_enabled=False,
|
||||
coe='fake-coe',
|
||||
created_at='fake-date',
|
||||
updated_at=None,
|
||||
master_flavor_id=None,
|
||||
no_proxy=None,
|
||||
https_proxy=None,
|
||||
keypair_id='fake-key',
|
||||
docker_volume_size=1,
|
||||
external_network_id='public',
|
||||
cluster_distro='fake-distro',
|
||||
volume_driver=None,
|
||||
network_driver='fake-driver',
|
||||
fixed_network=None,
|
||||
flavor_id='fake-flavor',
|
||||
dns_nameserver='8.8.8.8',
|
||||
)
|
||||
|
||||
|
||||
class TestBaymodels(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBaymodels, self).setUp()
|
||||
self.cloud = shade.openstack_cloud(validate=False)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'magnum_client')
|
||||
def test_list_baymodels_without_detail(self, mock_magnum):
|
||||
mock_magnum.baymodels.list.return_value = [baymodel_obj, ]
|
||||
baymodels_list = self.cloud.list_baymodels()
|
||||
mock_magnum.baymodels.list.assert_called_with(detail=False)
|
||||
self.assertEqual(baymodels_list[0], baymodel_obj)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'magnum_client')
|
||||
def test_list_baymodels_with_detail(self, mock_magnum):
|
||||
mock_magnum.baymodels.list.return_value = [baymodel_detail_obj, ]
|
||||
baymodels_list = self.cloud.list_baymodels(detail=True)
|
||||
mock_magnum.baymodels.list.assert_called_with(detail=True)
|
||||
self.assertEqual(baymodels_list[0], baymodel_detail_obj)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'magnum_client')
|
||||
def test_search_baymodels_by_name(self, mock_magnum):
|
||||
mock_magnum.baymodels.list.return_value = [baymodel_obj, ]
|
||||
|
||||
baymodels = self.cloud.search_baymodels(name_or_id='fake-baymodel')
|
||||
mock_magnum.baymodels.list.assert_called_with(detail=False)
|
||||
|
||||
self.assertEquals(1, len(baymodels))
|
||||
self.assertEquals('fake-uuid', baymodels[0]['uuid'])
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'magnum_client')
|
||||
def test_search_baymodels_not_found(self, mock_magnum):
|
||||
mock_magnum.baymodels.list.return_value = [baymodel_obj, ]
|
||||
|
||||
baymodels = self.cloud.search_baymodels(name_or_id='non-existent')
|
||||
|
||||
mock_magnum.baymodels.list.assert_called_with(detail=False)
|
||||
self.assertEquals(0, len(baymodels))
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'search_baymodels')
|
||||
def test_get_baymodel(self, mock_search):
|
||||
mock_search.return_value = [baymodel_obj, ]
|
||||
r = self.cloud.get_baymodel('fake-baymodel')
|
||||
self.assertIsNotNone(r)
|
||||
self.assertDictEqual(baymodel_obj, r)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'search_baymodels')
|
||||
def test_get_baymodel_not_found(self, mock_search):
|
||||
mock_search.return_value = []
|
||||
r = self.cloud.get_baymodel('doesNotExist')
|
||||
self.assertIsNone(r)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'magnum_client')
|
||||
def test_create_baymodel(self, mock_magnum):
|
||||
self.cloud.create_baymodel(
|
||||
name=baymodel_obj.name, image_id=baymodel_obj.image_id,
|
||||
keypair_id=baymodel_obj.keypair_id, coe=baymodel_obj.coe)
|
||||
mock_magnum.baymodels.create.assert_called_once_with(
|
||||
name=baymodel_obj.name, image_id=baymodel_obj.image_id,
|
||||
keypair_id=baymodel_obj.keypair_id, coe=baymodel_obj.coe
|
||||
)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'magnum_client')
|
||||
def test_create_baymodel_exception(self, mock_magnum):
|
||||
mock_magnum.baymodels.create.side_effect = Exception()
|
||||
with testtools.ExpectedException(
|
||||
shade.OpenStackCloudException,
|
||||
"Error creating baymodel of name fake-baymodel"
|
||||
):
|
||||
self.cloud.create_baymodel('fake-baymodel')
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'magnum_client')
|
||||
def test_delete_baymodel(self, mock_magnum):
|
||||
mock_magnum.baymodels.list.return_value = [baymodel_obj]
|
||||
self.cloud.delete_baymodel('fake-uuid')
|
||||
mock_magnum.baymodels.delete.assert_called_once_with(
|
||||
'fake-uuid'
|
||||
)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'magnum_client')
|
||||
def test_update_baymodel(self, mock_magnum):
|
||||
new_name = 'new-baymodel'
|
||||
mock_magnum.baymodels.list.return_value = [baymodel_obj]
|
||||
self.cloud.update_baymodel('fake-uuid', 'replace', name=new_name)
|
||||
mock_magnum.baymodels.update.assert_called_once_with(
|
||||
'fake-uuid', [{'path': '/name', 'op': 'replace',
|
||||
'value': 'new-baymodel'}]
|
||||
)
|
Loading…
Reference in New Issue
Block a user