Added Neutron API module

Implements bp join-tenant-network
Change-Id: Ibda1c5e5ff903b289247cb212a239ea49ffa869a
This commit is contained in:
Yulia Portnova 2013-11-07 16:03:18 +02:00 committed by Andrei V. Ostapenko
parent f9636fa2c9
commit cce07d59e1
8 changed files with 684 additions and 1 deletions

View File

@ -799,5 +799,16 @@
# Driver to use for share creation (string value)
#share_driver=manila.share.drivers.lvm.LVMShareDriver
#
# Option defined in manila.network.neutron.api
#
# Total option count: 173
#neutron_url = http://127.0.0.1:9696
#neutron_region_name = RegionOne
#neutron_admin_tenant_name = service
#neutron_auth_strategy = keystone
#neutron_admin_auth_url = http://127.0.0.1:35357/v2.0
#neutron_admin_password = %admin_pass
#neutron_admin_username = neutron
# Total option count: 180

View File

@ -134,6 +134,10 @@ class ManilaException(Exception):
super(ManilaException, self).__init__(message)
class NetworkException(ManilaException):
message = _("Exception due to network failure")
class GlanceConnectionFailed(ManilaException):
message = _("Connection to glance failed") + ": %(reason)s"
@ -435,6 +439,10 @@ class InvalidShare(ManilaException):
message = _("Invalid share: %(reason)s")
class PortLimitExceeded(QuotaError):
message = _("Maximum number of ports exceeded")
class ShareAccessNotFound(NotFound):
message = _("Access_id %(access_id)s not found")

View File

@ -0,0 +1,35 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Openstack Foundation
# 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 manila.openstack.common.importutils
from oslo.config import cfg
network_opts = [
cfg.StrOpt('network_api_class',
default='manila.network.neutron.api.API',
help='The full class name of the '
'network API class to use'),
]
cfg.CONF.register_opts(network_opts)
def API():
importutils = manila.openstack.common.importutils
network_api_class = cfg.CONF.network_api_class
cls = importutils.import_class(network_api_class)
return cls()

View File

@ -0,0 +1,53 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012 OpenStack Foundation
# 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 neutronclient.common import exceptions
from neutronclient.v2_0 import client as clientv20
from oslo.config import cfg
CONF = cfg.CONF
def _get_client(token=None):
params = {
'endpoint_url': CONF.neutron_url,
'timeout': CONF.neutron_url_timeout,
'insecure': CONF.neutron_api_insecure,
'ca_cert': CONF.neutron_ca_certificates_file,
}
if token:
params['token'] = token
params['auth_strategy'] = None
else:
params['username'] = CONF.neutron_admin_username
params['tenant_name'] = CONF.neutron_admin_tenant_name
params['password'] = CONF.neutron_admin_password
params['auth_url'] = CONF.neutron_admin_auth_url
params['auth_strategy'] = CONF.neutron_auth_strategy
return clientv20.Client(**params)
def get_client(context):
if context.is_admin:
token = None
elif not context.auth_token:
raise exceptions.Unauthorized()
else:
token = context.auth_token
return _get_client(token=token)

View File

@ -0,0 +1,168 @@
# Copyright 2013 OpenStack Foundation
# 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.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from neutronclient.common import exceptions as neutron_client_exc
from oslo.config import cfg
from manila import context
from manila.db import base
from manila import exception
from manila.network import neutron
from manila.openstack.common.gettextutils import _
from manila.openstack.common import log as logging
neutron_opts = [
cfg.StrOpt('neutron_url',
default='http://127.0.0.1:9696',
deprecated_name='quantum_url',
help='URL for connecting to neutron'),
cfg.IntOpt('neutron_url_timeout',
default=30,
deprecated_name='quantum_url_timeout',
help='timeout value for connecting to neutron in seconds'),
cfg.StrOpt('neutron_admin_username',
default='neutron',
deprecated_name='quantum_admin_username',
help='username for connecting to neutron in admin context'),
cfg.StrOpt('neutron_admin_password',
deprecated_name='quantum_admin_password',
help='password for connecting to neutron in admin context',
secret=True),
cfg.StrOpt('neutron_admin_tenant_name',
default='service',
deprecated_name='quantum_admin_tenant_name',
help='tenant name for connecting to neutron in admin context'),
cfg.StrOpt('neutron_region_name',
deprecated_name='quantum_region_name',
help='region name for connecting to neutron in admin context'),
cfg.StrOpt('neutron_admin_auth_url',
deprecated_name='quantum_admin_auth_url',
default='http://localhost:5000/v2.0',
help='auth url for connecting to neutron in admin context'),
cfg.BoolOpt('neutron_api_insecure',
default=False,
deprecated_name='quantum_api_insecure',
help='if set, ignore any SSL validation issues'),
cfg.StrOpt('neutron_auth_strategy',
default='keystone',
deprecated_name='quantum_auth_strategy',
help='auth strategy for connecting to '
'neutron in admin context'),
# TODO(berrange) temporary hack until Neutron can pass over the
# name of the OVS bridge it is configured with
cfg.StrOpt('neutron_ovs_bridge',
default='br-int',
deprecated_name='quantum_ovs_bridge',
help='Name of Integration Bridge used by Open vSwitch'),
cfg.StrOpt('neutron_ca_certificates_file',
help='Location of ca certificates file to use for '
'neutron client requests.'),
]
PORTBINDING_EXT = 'Port Binding'
CONF = cfg.CONF
CONF.register_opts(neutron_opts)
LOG = logging.getLogger(__name__)
class API(base.Base):
"""API for interacting with the neutron 2.x API."""
def __init__(self):
super(API, self).__init__()
self.last_neutron_extension_sync = None
self.extensions = {}
self.client = neutron.get_client(context.get_admin_context())
def get_all_tenant_networks(self, tenant_id):
search_opts = {'tenant_id': tenant_id, 'shared': False}
nets = self.client.list_networks(**search_opts).get('networks', [])
return nets
def create_port(self, tenant_id, network_id, host_id=None, subnet_id=None,
fixed_ip=None, device_owner=None, device_id=None,
mac_address=None, security_group_ids=None, dhcp_opts=None):
try:
port_req_body = {'port': {}}
port_req_body['port']['network_id'] = network_id
port_req_body['port']['admin_state_up'] = True
port_req_body['port']['tenant_id'] = tenant_id
if security_group_ids:
port_req_body['port']['security_groups'] = security_group_ids
if mac_address:
port_req_body['port']['mac_address'] = mac_address
if self._has_port_binding_extension() and host_id:
port_req_body['port']['binding:host_id'] = host_id
if dhcp_opts is not None:
port_req_body['port']['extra_dhcp_opts'] = dhcp_opts
if subnet_id:
fixed_ip_dict = {'subnet_id': subnet_id}
if fixed_ip:
fixed_ip_dict.update({'ip_address': fixed_ip})
port_req_body['port']['fixed_ips'] = [fixed_ip_dict]
if device_owner:
port_req_body['port']['device_owner'] = device_owner
if device_id:
port_req_body['port']['device_id'] = device_id
port = self.client.create_port(port_req_body)
return port
except neutron_client_exc.NeutronClientException as e:
LOG.exception(_('Neutron error creating port on network %s') %
network_id)
if e.status_code == 409:
raise exception.PortLimitExceeded()
raise exception.NetworkException(code=e.status_code,
message=e.message)
def delete_port(self, port_id):
try:
self.client.delete_port(port_id)
except neutron_client_exc.NeutronClientException as e:
raise exception.NetworkException(code=e.status_code,
message=e.message)
def list_ports(self, **search_opts):
"""List ports for the client based on search options."""
return self.client.list_ports(**search_opts).get('ports')
def show_port(self, port_id):
"""Return the port for the client given the port id."""
try:
return self.client.show_port(port_id).get('port')
except neutron_client_exc.NeutronClientException as e:
raise exception.NetworkException(code=e.status_code,
message=e.message)
def get_all_networks(self):
"""Get all networks for client."""
return self.client.list_networks().get('networks')
def get_network(self, network_uuid):
"""Get specific network for client."""
try:
network = self.client.show_network(network_uuid).get('network', {})
return network
except neutron_client_exc.NeutronClientException as e:
raise exception.NetworkException(code=e.status_code,
message=e.message)
def _has_port_binding_extension(self):
if not self.extensions:
extensions_list = self.client.list_extensions()['extensions']
self.extensions = dict((ext['name'], ext)
for ext in extensions_list)
return PORTBINDING_EXT in self.extensions

View File

@ -0,0 +1,118 @@
# Copyright 2013 OpenStack Foundation
# 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.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from oslo.config import cfg
from manila.openstack.common import log as logging
from manila.openstack.common import uuidutils
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class API(object):
"""Fake Network API"""
network = {
"status": "ACTIVE",
"subnets": ["fake_subnet_id"],
"name": "fake_network",
"tenant_id": "fake_tenant_id",
"shared": False,
"id": "fake_id",
"router:external": False,
}
port = {
"status": "ACTIVE",
"allowed_address_pairs": [],
"admin_state_up": True,
"network_id": "fake_network_id",
"tenant_id": "fake_tenant_id",
"extra_dhcp_opts": [],
"device_owner": "fake",
"binding:capabilities": {"port_filter": True},
"mac_address": "00:00:00:00:00:00",
"fixed_ips": [
{"subnet_id": "56537094-98d7-430a-b513-81c4dc6d9903",
"ip_address": "10.12.12.10"}
],
"id": "fake_port_id",
"security_groups": ["fake_sec_group_id"],
"device_id": "fake_device_id"
}
def get_all_tenant_networks(self, tenant_id):
net1 = self.network.copy()
net1['tenant_id'] = tenant_id
net1['id'] = uuidutils.generate_uuid()
net2 = self.network.copy()
net2['tenant_id'] = tenant_id
net2['id'] = uuidutils.generate_uuid()
return [net1, net2]
def create_port(self, tenant_id, network_id, subnet_id=None,
fixed_ip=None, device_owner=None, device_id=None):
port = self.port.copy()
port['network_id'] = network_id
port['admin_state_up'] = True
port['tenant_id'] = tenant_id
if fixed_ip:
fixed_ip_dict = {'ip_address': fixed_ip}
if subnet_id:
fixed_ip_dict.update({'subnet_id': subnet_id})
port['fixed_ips'] = [fixed_ip_dict]
if device_owner:
port['device_owner'] = device_owner
if device_id:
port['device_id'] = device_id
return port
def list_ports(self, **search_opts):
"""List ports for the client based on search options."""
ports = []
for i in range(2):
ports.append(self.port.copy())
for port in ports:
port['id'] = uuidutils.generate_uuid()
for key, val in search_opts.items():
port[key] = val
if 'id' in search_opts:
return ports
return ports
def show_port(self, port_id):
"""Return the port for the client given the port id."""
port = self.port.copy()
port['id'] = port_id
return port
def get_all_networks(self):
"""Get all networks for client."""
net1 = self.network.copy()
net2 = self.network.copy()
net1['id'] = uuidutils.generate_uuid()
net2['id'] = uuidutils.generate_uuid()
return [net1, net2]
def get_network(self, network_uuid):
"""Get specific network for client."""
network = self.network.copy()
network['id'] = network_uuid
return network

View File

@ -0,0 +1,289 @@
# Copyright 2013 OpenStack Foundation
# 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.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from mock import Mock
from mock import patch
from neutronclient.common import exceptions as neutron_client_exc
from neutronclient.v2_0 import client as clientv20
from oslo.config import cfg
import unittest
from manila import context
from manila.db import base
from manila import exception
from manila.network import neutron
from manila.network.neutron import api as neutron_api
from manila.tests.db import fakes
CONF = cfg.CONF
class FakeNeutronClient(object):
def create_port(self, body):
return body
def delete_port(self, port_id):
pass
def show_port(self, port_id):
pass
def list_ports(self, **search_opts):
pass
def list_networks(self):
pass
def show_network(self, network_uuid):
pass
def list_extensions(self):
pass
class NeutronApiTest(unittest.TestCase):
def setUp(self):
super(NeutronApiTest, self).setUp()
self._create_neutron_api()
@patch.object(base, 'Base', fakes.FakeModel)
@patch.object(context, 'get_admin_context',
Mock(return_value='context'))
@patch.object(neutron, 'get_client',
Mock(return_value=FakeNeutronClient()))
def _create_neutron_api(self):
self.neutron_api = neutron_api.API()
@patch.object(base, 'Base', fakes.FakeModel)
@patch.object(context, 'get_admin_context',
Mock(return_value='context'))
@patch.object(neutron, 'get_client', Mock())
def test_create_api_object(self):
with patch.object(base.Base, '__init__', Mock()):
neutron_api_obj = neutron_api.API()
base.Base.__init__.assert_called_once()
neutron.get_client.assert_called_once_with('context')
def test_create_port_with_all_args(self):
port_args = {'tenant_id': 'test tenant', 'network_id': 'test net',
'host_id': 'test host', 'subnet_id': 'test subnet',
'fixed_ip': 'test ip', 'device_owner': 'test owner',
'device_id': 'test device', 'mac_address': 'test mac',
'security_group_ids': 'test group',
'dhcp_opts': 'test dhcp'}
with patch.object(self.neutron_api, '_has_port_binding_extension',
Mock(return_value=True)):
port = self.neutron_api.create_port(**port_args)
self.assertEqual(port['port']['tenant_id'], port_args['tenant_id'])
self.assertEqual(port['port']['network_id'],
port_args['network_id'])
self.assertEqual(port['port']['binding:host_id'],
port_args['host_id'])
self.assertEqual(port['port']['fixed_ips'][0]['subnet_id'],
port_args['subnet_id'])
self.assertEqual(port['port']['fixed_ips'][0]['ip_address'],
port_args['fixed_ip'])
self.assertEqual(port['port']['device_owner'],
port_args['device_owner'])
self.assertEqual(port['port']['device_id'], port_args['device_id'])
self.assertEqual(port['port']['mac_address'],
port_args['mac_address'])
self.assertEqual(port['port']['security_groups'],
port_args['security_group_ids'])
self.assertEqual(port['port']['extra_dhcp_opts'],
port_args['dhcp_opts'])
def test_create_port_with_required_args(self):
port_args = {'tenant_id': 'test tenant', 'network_id': 'test net'}
with patch.object(self.neutron_api, '_has_port_binding_extension',
Mock(return_value=True)):
port = self.neutron_api.create_port(**port_args)
self.assertEqual(port['port']['tenant_id'], port_args['tenant_id'])
self.assertEqual(port['port']['network_id'],
port_args['network_id'])
@patch.object(neutron_api.LOG, 'exception', Mock())
def test_create_port_exception(self):
port_args = {'tenant_id': 'test tenant', 'network_id': 'test net'}
client_create_port_mock = Mock(side_effect=
neutron_client_exc.NeutronClientException)
with patch.object(self.neutron_api, '_has_port_binding_extension',
Mock(return_value=True)):
with patch.object(self.neutron_api.client, 'create_port',
client_create_port_mock):
self.assertRaises(exception.NetworkException,
self.neutron_api.create_port,
**port_args)
client_create_port_mock.assert_called_once()
neutron_api.LOG.exception.assert_called_once()
@patch.object(neutron_api.LOG, 'exception', Mock())
def test_create_port_exception_status_409(self):
port_args = {'tenant_id': 'test tenant', 'network_id': 'test net'}
client_create_port_mock = Mock(side_effect=
neutron_client_exc.NeutronClientException(status_code=409))
with patch.object(self.neutron_api, '_has_port_binding_extension',
Mock(return_value=True)):
with patch.object(self.neutron_api.client, 'create_port',
client_create_port_mock):
self.assertRaises(exception.PortLimitExceeded,
self.neutron_api.create_port,
**port_args)
client_create_port_mock.assert_called_once()
neutron_api.LOG.exception.assert_called_once()
def test_delete_port(self):
port_id = 'test port id'
with patch.object(self.neutron_api.client, 'delete_port',
Mock()) as client_delete_port_mock:
self.neutron_api.delete_port(port_id)
client_delete_port_mock.assert_called_once_with(port_id)
def test_list_ports(self):
search_opts = {'test_option': 'test_value'}
fake_ports = [{'fake port': 'fake port info'}]
client_list_ports_mock = Mock(return_value={'ports': fake_ports})
with patch.object(self.neutron_api.client, 'list_ports',
client_list_ports_mock):
ports = self.neutron_api.list_ports(**search_opts)
client_list_ports_mock.assert_called_once_with(**search_opts)
self.assertEqual(ports, fake_ports)
def test_show_port(self):
port_id = 'test port id'
fake_port = {'fake port': 'fake port info'}
client_show_port_mock = Mock(return_value={'port': fake_port})
with patch.object(self.neutron_api.client, 'show_port',
client_show_port_mock):
port = self.neutron_api.show_port(port_id)
client_show_port_mock.assert_called_once_with(port_id)
self.assertEqual(port, fake_port)
def test_get_network(self):
network_id = 'test network id'
fake_network = {'fake network': 'fake network info'}
client_show_network_mock = Mock(return_value={'network': fake_network})
with patch.object(self.neutron_api.client, 'show_network',
client_show_network_mock):
network = self.neutron_api.get_network(network_id)
client_show_network_mock.assert_called_once_with(network_id)
self.assertEqual(network, fake_network)
def test_get_all_network(self):
fake_networks = [{'fake network': 'fake network info'}]
client_list_networks_mock = Mock(
return_value={'networks': fake_networks})
with patch.object(self.neutron_api.client, 'list_networks',
client_list_networks_mock):
networks = self.neutron_api.get_all_networks()
client_list_networks_mock.assert_called_once()
self.assertEqual(networks, fake_networks)
def test_has_port_binding_extension_01(self):
fake_extensions = [{'name': neutron_api.PORTBINDING_EXT}]
client_list_ext_mock = Mock(
return_value={'extensions': fake_extensions})
with patch.object(self.neutron_api.client, 'list_extensions',
client_list_ext_mock):
result = self.neutron_api._has_port_binding_extension()
client_list_ext_mock.assert_called_once()
self.assertTrue(result)
def test_has_port_binding_extension_02(self):
client_list_ext_mock = Mock()
self.neutron_api.extensions =\
{neutron_api.PORTBINDING_EXT: 'extension description'}
with patch.object(self.neutron_api.client, 'list_extensions',
client_list_ext_mock):
result = self.neutron_api._has_port_binding_extension()
client_list_ext_mock.assert_not_called_once()
self.assertTrue(result)
def test_has_port_binding_extension_03(self):
client_list_ext_mock = Mock()
self.neutron_api.extensions =\
{'neutron extension X': 'extension description'}
with patch.object(self.neutron_api.client, 'list_extensions',
client_list_ext_mock):
result = self.neutron_api._has_port_binding_extension()
client_list_ext_mock.assert_not_called_once()
self.assertFalse(result)
class TestNeutronClient(unittest.TestCase):
@patch.object(clientv20.Client, '__init__', Mock(return_value=None))
def test_get_client_with_token(self):
client_args = {'endpoint_url': CONF.neutron_url,
'timeout': CONF.neutron_url_timeout,
'insecure': CONF.neutron_api_insecure,
'ca_cert': CONF.neutron_ca_certificates_file,
'token': 'test_token',
'auth_strategy': None}
my_context = context.RequestContext('test_user', 'test_tenant',
auth_token='test_token',
is_admin=False)
neutron.get_client(my_context)
clientv20.Client.__init__.assert_called_once_with(**client_args)
@patch.object(clientv20.Client, '__init__', Mock(return_value=None))
def test_get_client_no_token(self):
my_context = context.RequestContext('test_user', 'test_tenant',
is_admin=False)
self.assertRaises(neutron_client_exc.Unauthorized,
neutron.get_client,
my_context)
@patch.object(clientv20.Client, '__init__', Mock(return_value=None))
def test_get_client_admin_context(self):
client_args = {'endpoint_url': CONF.neutron_url,
'timeout': CONF.neutron_url_timeout,
'insecure': CONF.neutron_api_insecure,
'ca_cert': CONF.neutron_ca_certificates_file,
'username': CONF.neutron_admin_username,
'tenant_name': CONF.neutron_admin_tenant_name,
'password': CONF.neutron_admin_password,
'auth_url': CONF.neutron_admin_auth_url,
'auth_strategy': CONF.neutron_auth_strategy}
my_context = context.RequestContext('test_user', 'test_tenant',
is_admin=True)
neutron.get_client(my_context)
clientv20.Client.__init__.assert_called_once_with(**client_args)

View File

@ -13,6 +13,7 @@ oslo.config>=1.1.0
paramiko>=1.8.0
paste
pastedeploy>=1.5.0
python-neutronclient>=2.3.0,<3
python-glanceclient>=0.5.0,<2
python-keystoneclient>=0.3.0
python-swiftclient>=1.2,<2