Add magnum baymodel calls to shade.

Change-Id: Icba6929d32ae2cc47ccb776e24bc8beac6b717d2
This commit is contained in:
Yolanda Robla 2016-04-19 21:33:21 +02:00 committed by Yolanda Robla
parent 1c25357f18
commit 8732fa7ec3
6 changed files with 474 additions and 0 deletions

View File

@ -0,0 +1,4 @@
---
features:
- Add support for Magnum baymodels, with the
usual methods (search/list/get/create/update/delete).

View File

@ -907,3 +907,24 @@ class CinderQuotasGet(task_manager.Task):
class CinderQuotasDelete(task_manager.Task): class CinderQuotasDelete(task_manager.Task):
def main(self, client): def main(self, client):
return client.cinder_client.quotas.delete(**self.args) 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'])

View File

@ -495,6 +495,13 @@ def normalize_flavors(flavors):
return 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): def valid_kwargs(*valid_args):
# This decorator checks if argument passed as **kwargs to a function are # This decorator checks if argument passed as **kwargs to a function are
# present in valid_args. # present in valid_args.
@ -759,3 +766,21 @@ def range_filter(data, key, range_exp):
if int(d[key]) == val_range[1]: if int(d[key]) == val_range[1]:
filtered.append(d) filtered.append(d)
return filtered 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

View File

@ -30,6 +30,7 @@ import cinderclient.exceptions as cinder_exceptions
import glanceclient import glanceclient
import glanceclient.exc import glanceclient.exc
import heatclient.client import heatclient.client
import magnumclient.exceptions as magnum_exceptions
from heatclient.common import event_utils from heatclient.common import event_utils
from heatclient.common import template_utils from heatclient.common import template_utils
from heatclient import exc as heat_exceptions from heatclient import exc as heat_exceptions
@ -5652,3 +5653,158 @@ class OpenStackCloud(object):
_tasks.RecordSetDelete(zone=zone['id'], recordset=name_or_id)) _tasks.RecordSetDelete(zone=zone['id'], recordset=name_or_id))
return True 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

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

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