Add share's networks API

Add server side for share's networks. Implemented controller
will carry user requests to the DB and thus will allow
user to manage share's networks data.

Add share's networks support to the share API.

Partially implements bp: join-tenant-network

Change-Id: Ie4f3945255a049e80083f08a39d7f703a5c75c5e
This commit is contained in:
Aleks Chirko 2014-01-14 16:28:06 +02:00
parent d3131f40d2
commit c81ad66e7e
11 changed files with 567 additions and 19 deletions

View File

@ -28,6 +28,7 @@ from manila.api import versions
from manila.api.v1 import security_service
from manila.api.v1 import share_metadata
from manila.api.v1 import share_networks
from manila.api.v1 import share_snapshots
from manila.api.v1 import shares
@ -86,3 +87,9 @@ class APIRouter(manila.api.openstack.APIRouter):
security_service.create_resource()
mapper.resource("security-service", "security-services",
controller=self.resources['security_services'])
self.resources['share_networks'] = share_networks.create_resource()
mapper.resource(share_networks.RESOURCE_NAME,
'share-networks',
controller=self.resources['share_networks'],
member={'action': 'POST'})

View File

@ -113,25 +113,31 @@ class SecurityServiceController(wsgi.Controller):
search_opts = {}
search_opts.update(req.GET)
common.remove_invalid_options(
context, search_opts, self._get_security_services_search_options())
if 'all_tenants' in search_opts:
security_services = db.security_service_get_all(context)
del search_opts['all_tenants']
if 'share_network_id' in search_opts:
share_nw = db.share_network_get(context,
search_opts['share_network_id'])
security_services = share_nw['security_services']
else:
security_services = db.security_service_get_all_by_project(
context, context.project_id)
if search_opts:
results = []
not_found = object()
for service in security_services:
for opt, value in search_opts.iteritems():
if service.get(opt, not_found) != value:
break
else:
results.append(service)
security_services = results
common.remove_invalid_options(
context,
search_opts,
self._get_security_services_search_options())
if 'all_tenants' in search_opts:
security_services = db.security_service_get_all(context)
del search_opts['all_tenants']
else:
security_services = db.security_service_get_all_by_project(
context, context.project_id)
if search_opts:
results = []
not_found = object()
for service in security_services:
for opt, value in search_opts.iteritems():
if service.get(opt, not_found) != value:
break
else:
results.append(service)
security_services = results
limited_list = common.limited(security_services, req)

View File

@ -0,0 +1,227 @@
# Copyright 2014 NetApp
# 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.
"""The shares api."""
import webob
from webob import exc
from manila.api.openstack import wsgi
from manila.api.views import share_networks as share_networks_views
from manila.api import xmlutil
from manila.common import constants
from manila.db import api as db_api
from manila import exception
from manila.openstack.common import log as logging
RESOURCE_NAME = 'share_network'
RESOURCES_NAME = 'share_networks'
LOG = logging.getLogger(__name__)
SHARE_NETWORK_ATTRS = ('id',
'project_id',
'created_at',
'updated_at',
'neutron_net_id',
'neutron_subnet_id',
'network_type',
'segmentation_id',
'cidr',
'ip_version',
'name',
'description',
'status')
def _make_share_network(elem):
for attr in SHARE_NETWORK_ATTRS:
elem.set(attr)
class ShareNetworkTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement(RESOURCE_NAME, selector=RESOURCE_NAME)
_make_share_network(root)
return xmlutil.MasterTemplate(root, 1)
class ShareNetworksTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement(RESOURCES_NAME)
elem = xmlutil.SubTemplateElement(root, RESOURCE_NAME,
selector=RESOURCES_NAME)
_make_share_network(elem)
return xmlutil.MasterTemplate(root, 1)
class ShareNetworkController(wsgi.Controller):
"""The Share Network API controller for the OpenStack API."""
_view_builder_class = share_networks_views.ViewBuilder
@wsgi.serializers(xml=ShareNetworkTemplate)
def show(self, req, id):
"""Return data about the requested network info."""
context = req.environ['manila.context']
try:
share_network = db_api.share_network_get(context, id)
except exception.ShareNetworkNotFound as e:
msg = "%s" % e
raise exc.HTTPNotFound(explanation=msg)
return self._view_builder.build_share_network(share_network)
def delete(self, req, id):
"""Delete specified share network."""
context = req.environ['manila.context']
try:
share_network = db_api.share_network_get(context, id)
except exception.ShareNetworkNotFound as e:
msg = "%s" % e
raise exc.HTTPNotFound(explanation=msg)
if share_network['status'] == constants.STATUS_ACTIVE:
msg = "Network %s is in use" % id
raise exc.HTTPBadRequest(explanation=msg)
db_api.share_network_delete(context, id)
return webob.Response(status_int=202)
@wsgi.serializers(xml=ShareNetworksTemplate)
def index(self, req):
"""Returns a summary list of share's networks."""
context = req.environ['manila.context']
search_opts = {}
search_opts.update(req.GET)
if search_opts.pop('all_tenants', None):
networks = db_api.share_network_get_all(context)
else:
networks = db_api.share_network_get_all_by_project(
context,
context.project_id)
if search_opts:
for key, value in search_opts.iteritems():
networks = [network for network in networks
if network[key] == value]
return self._view_builder.build_share_networks(networks)
@wsgi.serializers(xml=ShareNetworkTemplate)
def update(self, req, id, body):
"""Update specified share network."""
context = req.environ['manila.context']
if not body or RESOURCE_NAME not in body:
raise exc.HTTPUnprocessableEntity()
try:
share_network = db_api.share_network_get(context, id)
except exception.ShareNetworkNotFound as e:
msg = "%s" % e
raise exc.HTTPNotFound(explanation=msg)
if share_network['status'] == constants.STATUS_ACTIVE:
msg = "Network %s is in use" % id
raise exc.HTTPBadRequest(explanation=msg)
update_values = body[RESOURCE_NAME]
try:
share_network = db_api.share_network_update(context,
id,
update_values)
except exception.DBError:
msg = "Could not save supplied data due to database error"
raise exc.HTTPBadRequest(explanation=msg)
return self._view_builder.build_share_network(share_network)
@wsgi.serializers(xml=ShareNetworkTemplate)
def create(self, req, body):
"""Creates a new share network."""
context = req.environ['manila.context']
if not body or RESOURCE_NAME not in body:
raise exc.HTTPUnprocessableEntity()
values = body[RESOURCE_NAME]
values['project_id'] = context.project_id
try:
share_network = db_api.share_network_create(context, values)
except exception.DBError:
msg = "Could not save supplied data due to database error"
raise exc.HTTPBadRequest(explanation=msg)
return self._view_builder.build_share_network(share_network)
@wsgi.serializers(xml=ShareNetworkTemplate)
def action(self, req, id, body):
_actions = {
'add_security_service': self._add_security_service,
'remove_security_service': self._remove_security_service,
}
for action, data in body.iteritems():
try:
return _actions[action](req, id, data)
except KeyError:
msg = _("Share networks does not have %s action") % action
raise exc.HTTPBadRequest(explanation=msg)
def _add_security_service(self, req, id, data):
context = req.environ['manila.context']
try:
share_network = db_api.share_network_add_security_service(
context,
id,
data['security_service_id'])
except KeyError:
msg = "Malformed request body"
raise exc.HTTPBadRequest(explanation=msg)
except exception.NotFound as e:
msg = "%s" % e
raise exc.HTTPNotFound(explanation=msg)
except exception.ShareNetworkSecurityServiceAssociationError as e:
msg = "%s" % e
raise exc.HTTPBadRequest(explanation=msg)
return self._view_builder.build_share_network(share_network)
def _remove_security_service(self, req, id, data):
context = req.environ['manila.context']
try:
share_network = db_api.share_network_remove_security_service(
context,
id,
data['security_service_id'])
except KeyError:
msg = "Malformed request body"
raise exc.HTTPBadRequest(explanation=msg)
except exception.NotFound as e:
msg = "%s" % e
raise exc.HTTPNotFound(explanation=msg)
except exception.ShareNetworkSecurityServiceDissociationError as e:
msg = "%s" % e
raise exc.HTTPBadRequest(explanation=msg)
return self._view_builder.build_share_network(share_network)
def create_resource():
return wsgi.Resource(ShareNetworkController())

View File

@ -198,6 +198,8 @@ class ShareController(wsgi.Controller):
else:
kwargs['snapshot'] = None
kwargs['share_network_id'] = share.get('share_network_id')
display_name = share.get('display_name')
display_description = share.get('display_description')
new_share = self.share_api.create(context,

View File

@ -0,0 +1,51 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 OpenStack LLC.
# 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.
from manila.api import common
class ViewBuilder(common.ViewBuilder):
"""Model a server API response as a python dictionary."""
_collection_name = 'share_networks'
def build_share_network(self, share_network):
"""View of a share network."""
return {'share_network': self._build_share_network_view(share_network)}
def build_share_networks(self, share_networks):
return {'share_networks':
[self._build_share_network_view(share_network)
for share_network in share_networks]}
def _build_share_network_view(self, share_network):
return {
'id': share_network.get('id'),
'project_id': share_network.get('project_id'),
'created_at': share_network.get('created_at'),
'updated_at': share_network.get('updated_at'),
'neutron_net_id': share_network.get('neutron_net_id'),
'neutron_subnet_id': share_network.get('neutron_subnet_id'),
'network_type': share_network.get('network_type'),
'segmentation_id': share_network.get('segmentation_id'),
'cidr': share_network.get('cidr'),
'ip_version': share_network.get('ip_version'),
'name': share_network.get('name'),
'description': share_network.get('description'),
'status': share_network.get('status'),
}

View File

@ -60,6 +60,7 @@ class ViewBuilder(common.ViewBuilder):
'name': share.get('display_name'),
'description': share.get('display_description'),
'snapshot_id': share.get('snapshot_id'),
'share_network_id': share.get('share_network_id'),
'share_proto': share.get('share_proto'),
'export_location': share.get('export_location'),
'metadata': metadata,

View File

@ -49,7 +49,8 @@ class API(base.Base):
super(API, self).__init__(db_driver)
def create(self, context, share_proto, size, name, description,
snapshot=None, availability_zone=None, metadata=None):
snapshot=None, availability_zone=None, metadata=None,
share_network_id=None):
"""Create new share."""
policy.check_policy(context, 'create')
@ -125,6 +126,7 @@ class API(base.Base):
'user_id': context.user_id,
'project_id': context.project_id,
'snapshot_id': snapshot_id,
'share_network_id': share_network_id,
'availability_zone': availability_zone,
'metadata': metadata,
'status': "creating",

View File

@ -39,6 +39,7 @@ def stub_share(id, **kwargs):
'display_description': 'displaydesc',
'created_at': datetime.datetime(1, 1, 1, 1, 1, 1),
'snapshot_id': '2',
'share_network_id': None
}
share.update(kwargs)
return share

View File

@ -0,0 +1,248 @@
# Copyright 2014 NetApp
# 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
import unittest
from webob import exc as webob_exc
from manila.api.v1 import share_networks
from manila.common import constants
from manila.db import api as db_api
from manila import exception
from manila.tests.api import fakes
fake_share_network = {'id': 'fake network id',
'project_id': 'fake project',
'created_at': None,
'updated_at': None,
'neutron_net_id': 'fake net id',
'neutron_subnet_id': 'fake subnet id',
'network_type': 'vlan',
'segmentation_id': 1000,
'cidr': '10.0.0.0/24',
'ip_version': 4,
'name': 'fake name',
'description': 'fake description',
'status': constants.STATUS_INACTIVE,
'shares': [],
'network_allocations': [],
'security_services': []
}
class ShareNetworkAPITest(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(ShareNetworkAPITest, self).__init__(*args, **kwargs)
self.controller = share_networks.ShareNetworkController()
self.req = fakes.HTTPRequest.blank('/share-networks')
self.context = self.req.environ['manila.context']
self.body = {share_networks.RESOURCE_NAME: {'name': 'fake name'}}
def _check_share_network_view(self, view, share_nw):
self.assertEqual(view['id'], share_nw['id'])
self.assertEqual(view['project_id'], share_nw['project_id'])
self.assertEqual(view['created_at'], share_nw['created_at'])
self.assertEqual(view['updated_at'], share_nw['updated_at'])
self.assertEqual(view['neutron_net_id'],
share_nw['neutron_net_id'])
self.assertEqual(view['neutron_subnet_id'],
share_nw['neutron_subnet_id'])
self.assertEqual(view['network_type'], share_nw['network_type'])
self.assertEqual(view['segmentation_id'],
share_nw['segmentation_id'])
self.assertEqual(view['cidr'], share_nw['cidr'])
self.assertEqual(view['ip_version'], share_nw['ip_version'])
self.assertEqual(view['name'], share_nw['name'])
self.assertEqual(view['description'], share_nw['description'])
self.assertEqual(view['status'], share_nw['status'])
self.assertEqual(view['created_at'], None)
self.assertEqual(view['updated_at'], None)
self.assertFalse('shares' in view)
self.assertFalse('network_allocations' in view)
self.assertFalse('security_services' in view)
def test_create_nominal(self):
with mock.patch.object(db_api,
'share_network_create',
mock.Mock(return_value=fake_share_network)):
result = self.controller.create(self.req, self.body)
db_api.share_network_create.assert_called_once_with(
self.req.environ['manila.context'],
self.body[share_networks.RESOURCE_NAME])
self._check_share_network_view(
result[share_networks.RESOURCE_NAME],
fake_share_network)
def test_create_db_api_exception(self):
with mock.patch.object(db_api,
'share_network_create',
mock.Mock(side_effect=exception.DBError)):
self.assertRaises(webob_exc.HTTPBadRequest,
self.controller.create,
self.req,
self.body)
def test_create_wrong_body(self):
body = None
self.assertRaises(webob_exc.HTTPUnprocessableEntity,
self.controller.create,
self.req,
body)
@mock.patch.object(db_api, 'share_network_get',
mock.Mock(return_value=fake_share_network))
def test_delete_nominal(self):
share_nw = 'fake network id'
with mock.patch.object(db_api, 'share_network_delete'):
self.controller.delete(self.req, share_nw)
db_api.share_network_delete.assert_called_once_with(
self.req.environ['manila.context'],
share_nw)
@mock.patch.object(db_api, 'share_network_get', mock.Mock())
def test_delete_not_found(self):
share_nw = 'fake network id'
db_api.share_network_get.side_effect = exception.ShareNetworkNotFound(
share_network_id=share_nw)
self.assertRaises(webob_exc.HTTPNotFound,
self.controller.delete,
self.req,
share_nw)
@mock.patch.object(db_api, 'share_network_get', mock.Mock())
def test_delete_in_use(self):
share_nw = fake_share_network.copy()
share_nw['status'] = constants.STATUS_ACTIVE
db_api.share_network_get.return_value = share_nw
self.assertRaises(webob_exc.HTTPBadRequest,
self.controller.delete,
self.req,
share_nw['id'])
def test_show_nominal(self):
share_nw = 'fake network id'
with mock.patch.object(db_api,
'share_network_get',
mock.Mock(return_value=fake_share_network)):
result = self.controller.show(self.req, share_nw)
db_api.share_network_get.assert_called_once_with(
self.req.environ['manila.context'],
share_nw)
self._check_share_network_view(
result[share_networks.RESOURCE_NAME],
fake_share_network)
def test_show_not_found(self):
share_nw = 'fake network id'
test_exception = exception.ShareNetworkNotFound()
with mock.patch.object(db_api,
'share_network_get',
mock.Mock(side_effect=test_exception)):
self.assertRaises(webob_exc.HTTPNotFound,
self.controller.show,
self.req,
share_nw)
def test_index_no_filters(self):
networks = [fake_share_network]
with mock.patch.object(db_api,
'share_network_get_all_by_project',
mock.Mock(return_value=networks)):
result = self.controller.index(self.req)
db_api.share_network_get_all_by_project.assert_called_once_with(
self.context,
self.context.project_id)
self.assertEqual(len(result[share_networks.RESOURCES_NAME]), 1)
self._check_share_network_view(
result[share_networks.RESOURCES_NAME][0],
fake_share_network)
@mock.patch.object(db_api, 'share_network_get', mock.Mock())
def test_update_nominal(self):
share_nw = 'fake network id'
db_api.share_network_get.return_value = fake_share_network
body = {share_networks.RESOURCE_NAME: {'name': 'new name'}}
with mock.patch.object(db_api,
'share_network_update',
mock.Mock(return_value=fake_share_network)):
result = self.controller.update(self.req, share_nw, body)
db_api.share_network_update.assert_called_once_with(
self.req.environ['manila.context'],
share_nw,
body[share_networks.RESOURCE_NAME])
self._check_share_network_view(
result[share_networks.RESOURCE_NAME],
fake_share_network)
@mock.patch.object(db_api, 'share_network_get', mock.Mock())
def test_update_not_found(self):
share_nw = 'fake network id'
db_api.share_network_get.side_effect = exception.ShareNetworkNotFound(
share_network_id=share_nw)
self.assertRaises(webob_exc.HTTPNotFound,
self.controller.update,
self.req,
share_nw,
self.body)
@mock.patch.object(db_api, 'share_network_get', mock.Mock())
def test_update_in_use(self):
share_nw = fake_share_network.copy()
share_nw['status'] = constants.STATUS_ACTIVE
db_api.share_network_get.return_value = share_nw
self.assertRaises(webob_exc.HTTPBadRequest,
self.controller.update,
self.req,
share_nw['id'],
self.body)
@mock.patch.object(db_api, 'share_network_get', mock.Mock())
def test_update_db_api_exception(self):
share_nw = 'fake network id'
db_api.share_network_get.return_value = fake_share_network
body = {share_networks.RESOURCE_NAME: {'neutron_subnet_id':
'new subnet'}}
with mock.patch.object(db_api,
'share_network_update',
mock.Mock(side_effect=exception.DBError)):
self.assertRaises(webob_exc.HTTPBadRequest,
self.controller.update,
self.req,
share_nw,
body)

View File

@ -145,6 +145,7 @@ class ShareApiTest(test.TestCase):
'metadata': {},
'size': 1,
'snapshot_id': '2',
'share_network_id': None,
'status': 'fakestatus',
'links': [{'href': 'http://localhost/v1/fake/shares/1',
'rel': 'self'},
@ -246,6 +247,7 @@ class ShareApiTest(test.TestCase):
'metadata': {},
'id': '1',
'snapshot_id': '2',
'share_network_id': None,
'created_at': datetime.datetime(1, 1, 1, 1, 1, 1),
'size': 1,
'links': [

View File

@ -40,6 +40,7 @@ def fake_share(id, **kwargs):
'user_id': 'fakeuser',
'project_id': 'fakeproject',
'snapshot_id': None,
'share_network_id': None,
'availability_zone': 'fakeaz',
'status': 'fakestatus',
'display_name': 'fakename',