Fix NFS/CIFS share creation failure issue

When the image count is over 25, there might not get
manila-service-image, because current manila shares
creation is using novaclient to get image info, but
novaclient can only get 25 images due to pagination
of glance server, So this change is to switch to use
glanceclient instead of novaclient to get image info,
because glanceclient can iter all image info, while
novaclient is rarely maintained with stuff of image
API.

Change-Id: Id905d47600bda9923cebae617749c8286552ec94
Closes-Bug: #1741425
This commit is contained in:
junboli 2018-01-08 11:02:11 +08:00
parent f28ef954ba
commit 51dd510636
8 changed files with 242 additions and 5 deletions

33
manila/image/__init__.py Normal file
View File

@ -0,0 +1,33 @@
# All Rights Reserved.
#
# 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 oslo_config.cfg
import oslo_utils.importutils
_glance_opts = [
oslo_config.cfg.StrOpt('image_api_class',
default='manila.image.glance.API',
help='The full class name of the '
'Glance API class to use.'),
]
oslo_config.cfg.CONF.register_opts(_glance_opts)
def API():
importutils = oslo_utils.importutils
glance_api_class = oslo_config.cfg.CONF.image_api_class
cls = importutils.import_class(glance_api_class)
return cls()

71
manila/image/glance.py Normal file
View File

@ -0,0 +1,71 @@
# All Rights Reserved.
#
# 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.
"""
Handles all requests to Glance.
"""
from glanceclient import client as glance_client
from glanceclient import exc as glance_exception
from keystoneauth1 import loading as ks_loading
from oslo_config import cfg
from manila.common import client_auth
from manila.common.config import core_opts
from manila.db import base
GLANCE_GROUP = 'glance'
AUTH_OBJ = None
glance_opts = [
cfg.StrOpt('api_microversion',
default='2',
help='Version of Glance API to be used.'),
cfg.StrOpt('region_name',
default='RegionOne',
help='Region name for connecting to glance.'),
]
CONF = cfg.CONF
CONF.register_opts(core_opts)
CONF.register_opts(glance_opts, GLANCE_GROUP)
ks_loading.register_session_conf_options(CONF, GLANCE_GROUP)
ks_loading.register_auth_conf_options(CONF, GLANCE_GROUP)
def list_opts():
return client_auth.AuthClientLoader.list_opts(GLANCE_GROUP)
def glanceclient(context):
global AUTH_OBJ
if not AUTH_OBJ:
AUTH_OBJ = client_auth.AuthClientLoader(
client_class=glance_client.Client,
exception_module=glance_exception,
cfg_group=GLANCE_GROUP)
return AUTH_OBJ.get_client(context,
version=CONF[GLANCE_GROUP].api_microversion,
region_name=CONF[GLANCE_GROUP].region_name)
class API(base.Base):
"""API for interacting with glanceclient."""
def image_list(self, context):
client = glanceclient(context)
if hasattr(client, 'images'):
return client.images.list()
return client.glance.list()

View File

@ -33,6 +33,7 @@ from manila import compute
from manila import context from manila import context
from manila import exception from manila import exception
from manila.i18n import _ from manila.i18n import _
from manila import image
from manila.network.linux import ip_lib from manila.network.linux import ip_lib
from manila.network.neutron import api as neutron from manila.network.neutron import api as neutron
from manila import utils from manila import utils
@ -207,6 +208,7 @@ class ServiceInstanceManager(object):
self.admin_context = context.get_admin_context() self.admin_context = context.get_admin_context()
self._execute = utils.execute self._execute = utils.execute
self.image_api = image.API()
self.compute_api = compute.API() self.compute_api = compute.API()
self.path_to_private_key = self.get_config_option( self.path_to_private_key = self.get_config_option(
@ -482,7 +484,7 @@ class ServiceInstanceManager(object):
def _get_service_image(self, context): def _get_service_image(self, context):
"""Returns ID of service image for service vm creating.""" """Returns ID of service image for service vm creating."""
service_image_name = self.get_config_option("service_image_name") service_image_name = self.get_config_option("service_image_name")
images = [image.id for image in self.compute_api.image_list(context) images = [image.id for image in self.image_api.image_list(context)
if image.name == service_image_name if image.name == service_image_name
and image.status == 'active'] and image.status == 'active']
if len(images) == 1: if len(images) == 1:

View File

View File

@ -0,0 +1,125 @@
# All Rights Reserved.
#
# 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
from manila import context
from manila.image import glance
from manila import test
from manila.tests import utils as test_utils
class FakeGlanceClient(object):
class Image(object):
def list(self, *args, **kwargs):
return [{'id': 'id1'}, {'id': 'id2'}]
def __getattr__(self, item):
return None
def __init__(self):
self.image = self.Image()
def get_fake_auth_obj():
return type('FakeAuthObj', (object, ), {'get_client': mock.Mock()})
class GlanceClientTestCase(test.TestCase):
@mock.patch('manila.image.glance.AUTH_OBJ', None)
def test_no_auth_obj(self):
mock_client_loader = self.mock_object(
glance.client_auth, 'AuthClientLoader')
fake_context = 'fake_context'
data = {
'glance': {
'api_microversion': 'foo_api_microversion',
'region_name': 'foo_region_name'
}
}
with test_utils.create_temp_config_with_opts(data):
glance.glanceclient(fake_context)
mock_client_loader.return_value.get_client.assert_called_once_with(
fake_context,
version=data['glance']['api_microversion'],
region_name=data['glance']['region_name']
)
@mock.patch('manila.image.glance.AUTH_OBJ', get_fake_auth_obj())
def test_with_auth_obj(self):
fake_context = 'fake_context'
data = {
'glance': {
'api_microversion': 'foo_api_microversion',
'region_name': 'foo_region_name'
}
}
with test_utils.create_temp_config_with_opts(data):
glance.glanceclient(fake_context)
glance.AUTH_OBJ.get_client.assert_called_once_with(
fake_context,
version=data['glance']['api_microversion'],
region_name=data['glance']['region_name']
)
class GlanceApiTestCase(test.TestCase):
def setUp(self):
super(GlanceApiTestCase, self).setUp()
self.api = glance.API()
self.glanceclient = FakeGlanceClient()
self.ctx = context.get_admin_context()
self.mock_object(glance, 'glanceclient',
mock.Mock(return_value=self.glanceclient))
def test_image_list_glanceclient_has_no_proxy(self):
image_list = ['fake', 'image', 'list']
class FakeGlanceClient(object):
def list(self):
return image_list
self.glanceclient.glance = FakeGlanceClient()
result = self.api.image_list(self.ctx)
self.assertEqual(image_list, result)
def test_image_list_glanceclient_has_proxy(self):
image_list1 = ['fake', 'image', 'list1']
image_list2 = ['fake', 'image', 'list2']
class FakeImagesClient(object):
def list(self):
return image_list1
class FakeGlanceClient(object):
def list(self):
return image_list2
self.glanceclient.images = FakeImagesClient()
self.glanceclient.glance = FakeGlanceClient()
result = self.api.image_list(self.ctx)
self.assertEqual(image_list1, result)

View File

@ -678,7 +678,7 @@ class ServiceInstanceManagerTestCase(test.TestCase):
fake_image3 = fake_compute.FakeImage( fake_image3 = fake_compute.FakeImage(
name='another-image', name='another-image',
status='active') status='active')
self.mock_object(self._manager.compute_api, 'image_list', self.mock_object(self._manager.image_api, 'image_list',
mock.Mock(return_value=[fake_image1, mock.Mock(return_value=[fake_image1,
fake_image2, fake_image2,
fake_image3])) fake_image3]))
@ -687,7 +687,7 @@ class ServiceInstanceManagerTestCase(test.TestCase):
self.assertEqual(fake_image1.id, result) self.assertEqual(fake_image1.id, result)
def test_get_service_image_not_found(self): def test_get_service_image_not_found(self):
self.mock_object(self._manager.compute_api, 'image_list', self.mock_object(self._manager.image_api, 'image_list',
mock.Mock(return_value=[])) mock.Mock(return_value=[]))
self.assertRaises( self.assertRaises(
exception.ServiceInstanceException, exception.ServiceInstanceException,
@ -696,7 +696,7 @@ class ServiceInstanceManagerTestCase(test.TestCase):
fake_error_image = fake_compute.FakeImage( fake_error_image = fake_compute.FakeImage(
name='service_image_name', name='service_image_name',
status='error') status='error')
self.mock_object(self._manager.compute_api, 'image_list', self.mock_object(self._manager.image_api, 'image_list',
mock.Mock(return_value=[fake_error_image])) mock.Mock(return_value=[fake_error_image]))
self.assertRaises( self.assertRaises(
exception.ServiceInstanceException, exception.ServiceInstanceException,
@ -707,7 +707,7 @@ class ServiceInstanceManagerTestCase(test.TestCase):
name=fake_get_config_option('service_image_name'), name=fake_get_config_option('service_image_name'),
status='active') status='active')
fake_images = [fake_image, fake_image] fake_images = [fake_image, fake_image]
self.mock_object(self._manager.compute_api, 'image_list', self.mock_object(self._manager.image_api, 'image_list',
mock.Mock(return_value=fake_images)) mock.Mock(return_value=fake_images))
self.assertRaises( self.assertRaises(
exception.ServiceInstanceException, exception.ServiceInstanceException,

View File

@ -0,0 +1,5 @@
---
fixes:
- |
Switch to use glance client to retrive image list, novaclient is rarely
maintained with glance API.

View File

@ -42,4 +42,5 @@ stevedore>=1.20.0 # Apache-2.0
tooz>=1.58.0 # Apache-2.0 tooz>=1.58.0 # Apache-2.0
python-cinderclient>=3.3.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0
python-novaclient>=9.1.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0
python-glanceclient>=2.8.0 # Apache-2.0
WebOb>=1.7.1 # MIT WebOb>=1.7.1 # MIT