5651e10182
Return an HTTP Forbidden code (403) instead of an HTTP Not Found code (404) if a tenant is trying to update it's own object. This is a safe adjustment since the tenant already knows this object exists so pretending it doesn't isn't improving secuirty as much as it is causing confusion. Closes-Bug: #1352907 Change-Id: I021ba6f890dfbabddd53e75c63083f5da0ecfdec
346 lines
15 KiB
Python
346 lines
15 KiB
Python
# Copyright 2013 NEC Corporation
|
|
# 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.
|
|
#
|
|
# @author: Akihiro Motoki, NEC Corporation
|
|
|
|
from oslo.config import cfg
|
|
from testtools import matchers
|
|
from webob import exc
|
|
|
|
from neutron.common import exceptions as n_exc
|
|
from neutron import context
|
|
from neutron.extensions import portbindings
|
|
from neutron.tests.unit import _test_extension_portbindings as test_bindings
|
|
from neutron.tests.unit.nec import test_nec_plugin
|
|
from neutron.tests.unit import test_security_groups_rpc as test_sg_rpc
|
|
|
|
|
|
class TestNecPortBinding(test_bindings.PortBindingsTestCase,
|
|
test_nec_plugin.NecPluginV2TestCase):
|
|
VIF_TYPE = portbindings.VIF_TYPE_OVS
|
|
HAS_PORT_FILTER = True
|
|
ENABLE_SG = True
|
|
FIREWALL_DRIVER = test_sg_rpc.FIREWALL_HYBRID_DRIVER
|
|
|
|
def setUp(self):
|
|
test_sg_rpc.set_firewall_driver(self.FIREWALL_DRIVER)
|
|
cfg.CONF.set_override(
|
|
'enable_security_group', self.ENABLE_SG,
|
|
group='SECURITYGROUP')
|
|
super(TestNecPortBinding, self).setUp()
|
|
|
|
|
|
class TestNecPortBindingNoSG(TestNecPortBinding):
|
|
HAS_PORT_FILTER = False
|
|
ENABLE_SG = False
|
|
FIREWALL_DRIVER = test_sg_rpc.FIREWALL_NOOP_DRIVER
|
|
|
|
|
|
class TestNecPortBindingHost(
|
|
test_bindings.PortBindingsHostTestCaseMixin,
|
|
test_nec_plugin.NecPluginV2TestCase):
|
|
pass
|
|
|
|
|
|
class TestNecPortBindingPortInfo(test_nec_plugin.NecPluginV2TestCase):
|
|
def _get_portinfo(self, datapath_id=None, port_no=None):
|
|
if datapath_id is None:
|
|
datapath_id = '0xabc'
|
|
if port_no is None:
|
|
port_no = 1
|
|
return {'datapath_id': datapath_id,
|
|
'port_no': port_no}
|
|
|
|
def _check_response_portbinding_profile(self, port, datapath_id=None,
|
|
port_no=None):
|
|
expected = self._get_portinfo(datapath_id, port_no)
|
|
profile = port[portbindings.PROFILE]
|
|
self.assertEqual(len(profile), 2)
|
|
self.assertEqual(profile['datapath_id'],
|
|
expected['datapath_id'])
|
|
self.assertEqual(profile['port_no'],
|
|
expected['port_no'])
|
|
|
|
def _check_response_portbinding_no_profile(self, port):
|
|
self.assertIn('status', port)
|
|
self.assertNotIn(portbindings.PROFILE, port)
|
|
|
|
def _get_non_admin_context(self):
|
|
return context.Context(user_id=None,
|
|
tenant_id=self._tenant_id,
|
|
is_admin=False,
|
|
read_deleted="no")
|
|
|
|
def test_port_create_portinfo(self):
|
|
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
|
|
with self.port(arg_list=(portbindings.PROFILE,),
|
|
**profile_arg) as port:
|
|
port_id = port['port']['id']
|
|
# Check a response of create_port
|
|
self._check_response_portbinding_profile(port['port'])
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
|
|
# Check a response of get_port
|
|
ctx = context.get_admin_context()
|
|
port = self._show('ports', port_id, neutron_context=ctx)['port']
|
|
self._check_response_portbinding_profile(port)
|
|
# By default user is admin - now test non admin user
|
|
ctx = self._get_non_admin_context()
|
|
non_admin_port = self._show(
|
|
'ports', port_id, neutron_context=ctx)['port']
|
|
self._check_response_portbinding_no_profile(non_admin_port)
|
|
# port-update with non admin user should fail
|
|
self._update('ports', port_id,
|
|
{'port': profile_arg},
|
|
expected_code=exc.HTTPForbidden.code,
|
|
neutron_context=ctx)
|
|
|
|
def test_port_update_portinfo(self):
|
|
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
|
|
with self.port() as port:
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
|
|
port_id = port['port']['id']
|
|
# Check a response of create_port
|
|
self._check_response_portbinding_no_profile(port['port'])
|
|
# Check a response of update_port
|
|
ctx = context.get_admin_context()
|
|
port = self._update('ports', port_id, {'port': profile_arg},
|
|
neutron_context=ctx)['port']
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
|
|
self._check_response_portbinding_profile(port)
|
|
port = self._show('ports', port_id, neutron_context=ctx)['port']
|
|
self._check_response_portbinding_profile(port)
|
|
|
|
def test_port_update_portinfo_detail(self):
|
|
with self.port() as port:
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
|
|
self.assertEqual(self.ofc.delete_ofc_port.call_count, 0)
|
|
port_id = port['port']['id']
|
|
ctx = context.get_admin_context()
|
|
|
|
# add portinfo
|
|
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
|
|
port = self._update('ports', port_id, {'port': profile_arg},
|
|
neutron_context=ctx)['port']
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
|
|
self.assertEqual(self.ofc.delete_ofc_port.call_count, 0)
|
|
|
|
# portinfo unchanged
|
|
port = self._update('ports', port_id, {'port': profile_arg},
|
|
neutron_context=ctx)['port']
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
|
|
self.assertEqual(self.ofc.delete_ofc_port.call_count, 0)
|
|
|
|
# modify portinfo
|
|
profile_arg = {portbindings.PROFILE:
|
|
self._get_portinfo(datapath_id='0x1234567890',
|
|
port_no=99)}
|
|
port = self._update('ports', port_id, {'port': profile_arg},
|
|
neutron_context=ctx)['port']
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 2)
|
|
self.assertEqual(self.ofc.delete_ofc_port.call_count, 1)
|
|
|
|
# delete portinfo with an empty dict
|
|
profile_arg = {portbindings.PROFILE: {}}
|
|
port = self._update('ports', port_id, {'port': profile_arg},
|
|
neutron_context=ctx)['port']
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 2)
|
|
self.assertEqual(self.ofc.delete_ofc_port.call_count, 2)
|
|
|
|
def test_port_update_portinfo_detail_clear_with_none(self):
|
|
with self.port() as port:
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
|
|
self.assertEqual(self.ofc.delete_ofc_port.call_count, 0)
|
|
port_id = port['port']['id']
|
|
ctx = context.get_admin_context()
|
|
|
|
# add portinfo
|
|
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
|
|
port = self._update('ports', port_id, {'port': profile_arg},
|
|
neutron_context=ctx)['port']
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
|
|
self.assertEqual(self.ofc.delete_ofc_port.call_count, 0)
|
|
|
|
# delete portinfo with None
|
|
profile_arg = {portbindings.PROFILE: None}
|
|
port = self._update('ports', port_id, {'port': profile_arg},
|
|
neutron_context=ctx)['port']
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
|
|
self.assertEqual(self.ofc.delete_ofc_port.call_count, 1)
|
|
|
|
def test_port_create_portinfo_with_empty_dict(self):
|
|
profile_arg = {portbindings.PROFILE: {}}
|
|
with self.port(arg_list=(portbindings.PROFILE,),
|
|
**profile_arg) as port:
|
|
port_id = port['port']['id']
|
|
|
|
# Check a response of create_port
|
|
self._check_response_portbinding_no_profile(port['port'])
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
|
|
# add portinfo
|
|
ctx = context.get_admin_context()
|
|
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
|
|
port = self._update('ports', port_id, {'port': profile_arg},
|
|
neutron_context=ctx)['port']
|
|
self._check_response_portbinding_profile(port)
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
|
|
self.assertEqual(self.ofc.delete_ofc_port.call_count, 0)
|
|
|
|
def test_port_create_portinfo_with_none(self):
|
|
profile_arg = {portbindings.PROFILE: None}
|
|
with self.port(arg_list=(portbindings.PROFILE,),
|
|
**profile_arg) as port:
|
|
port_id = port['port']['id']
|
|
|
|
# Check a response of create_port
|
|
self._check_response_portbinding_no_profile(port['port'])
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
|
|
# add portinfo
|
|
ctx = context.get_admin_context()
|
|
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
|
|
port = self._update('ports', port_id, {'port': profile_arg},
|
|
neutron_context=ctx)['port']
|
|
self._check_response_portbinding_profile(port)
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
|
|
self.assertEqual(self.ofc.delete_ofc_port.call_count, 0)
|
|
|
|
def test_port_update_for_existing_port_with_different_padding_dpid(self):
|
|
ctx = context.get_admin_context()
|
|
with self.port() as port:
|
|
port_id = port['port']['id']
|
|
portinfo = {'id': port_id, 'port_no': 123}
|
|
self.rpcapi_update_ports(datapath_id='0x000000000000abcd',
|
|
added=[portinfo])
|
|
self.assertEqual(1, self.ofc.create_ofc_port.call_count)
|
|
self.assertEqual(0, self.ofc.delete_ofc_port.call_count)
|
|
|
|
profile_arg = {portbindings.PROFILE:
|
|
self._get_portinfo(datapath_id='0xabcd',
|
|
port_no=123)}
|
|
self._update('ports', port_id, {'port': profile_arg},
|
|
neutron_context=ctx)
|
|
# Check create_ofc_port/delete_ofc_port are not called.
|
|
self.assertEqual(1, self.ofc.create_ofc_port.call_count)
|
|
self.assertEqual(0, self.ofc.delete_ofc_port.call_count)
|
|
|
|
def test_port_create_portinfo_non_admin(self):
|
|
with self.network(set_context=True, tenant_id='test') as net1:
|
|
with self.subnet(network=net1) as subnet1:
|
|
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
|
|
try:
|
|
with self.port(subnet=subnet1,
|
|
expected_res_status=403,
|
|
arg_list=(portbindings.PROFILE,),
|
|
set_context=True, tenant_id='test',
|
|
**profile_arg):
|
|
pass
|
|
except exc.HTTPClientError:
|
|
pass
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
|
|
|
|
def test_port_update_portinfo_non_admin(self):
|
|
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
|
|
with self.network() as net1:
|
|
with self.subnet(network=net1) as subnet1:
|
|
with self.port(subnet=subnet1) as port:
|
|
# By default user is admin - now test non admin user
|
|
port_id = port['port']['id']
|
|
ctx = self._get_non_admin_context()
|
|
port = self._update('ports', port_id,
|
|
{'port': profile_arg},
|
|
expected_code=exc.HTTPForbidden.code,
|
|
neutron_context=ctx)
|
|
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
|
|
|
|
def test_port_create_portinfo_validation_called(self):
|
|
# Check validate_portinfo is called.
|
|
profile_arg = {portbindings.PROFILE:
|
|
{'datapath_id': '0xabc',
|
|
'port_no': 0xffff + 1}}
|
|
try:
|
|
with self.port(arg_list=(portbindings.PROFILE,),
|
|
expected_res_status=400,
|
|
**profile_arg):
|
|
pass
|
|
except exc.HTTPClientError:
|
|
pass
|
|
|
|
|
|
class TestNecPortBindingValidatePortInfo(test_nec_plugin.NecPluginV2TestCase):
|
|
|
|
def test_validate_portinfo_ok(self):
|
|
profile = {'datapath_id': '0x1234567890abcdef',
|
|
'port_no': 123}
|
|
portinfo = self.plugin._validate_portinfo(profile)
|
|
# NOTE(mriedem): Handle long integer conversion universally.
|
|
self.assertEqual(
|
|
0x1234567890abcdef,
|
|
int(portinfo['datapath_id'].replace('L', ''), 16)
|
|
)
|
|
self.assertEqual(portinfo['port_no'], 123)
|
|
|
|
def test_validate_portinfo_ok_without_0x(self):
|
|
profile = {'datapath_id': '1234567890abcdef',
|
|
'port_no': 123}
|
|
portinfo = self.plugin._validate_portinfo(profile)
|
|
# NOTE(mriedem): Handle long integer conversion universally.
|
|
self.assertEqual(
|
|
0x1234567890abcdef,
|
|
int(portinfo['datapath_id'].replace('L', ''), 16)
|
|
)
|
|
self.assertEqual(portinfo['port_no'], 123)
|
|
|
|
def _test_validate_exception(self, profile, expected_msg):
|
|
e = self.assertRaises(n_exc.InvalidInput,
|
|
self.plugin._validate_portinfo, profile)
|
|
self.assertThat(str(e), matchers.StartsWith(expected_msg))
|
|
|
|
def test_validate_portinfo_dict_validation(self):
|
|
expected_msg = ("Invalid input for operation: "
|
|
"Validation of dictionary's keys failed.")
|
|
|
|
profile = {'port_no': 123}
|
|
self._test_validate_exception(profile, expected_msg)
|
|
|
|
profile = {'datapath_id': '0xabcdef'}
|
|
self._test_validate_exception(profile, expected_msg)
|
|
|
|
def test_validate_portinfo_negative_port_number(self):
|
|
profile = {'datapath_id': '0x1234567890abcdef',
|
|
'port_no': -1}
|
|
expected_msg = ("Invalid input for operation: "
|
|
"'-1' should be non-negative.")
|
|
self._test_validate_exception(profile, expected_msg)
|
|
|
|
def test_validate_portinfo_invalid_datapath_id(self):
|
|
expected_msg = ("Invalid input for operation: "
|
|
"datapath_id should be a hex string")
|
|
|
|
# non hexidecimal datapath_id
|
|
profile = {'datapath_id': 'INVALID',
|
|
'port_no': 123}
|
|
self._test_validate_exception(profile, expected_msg)
|
|
|
|
# Too big datapath_id
|
|
profile = {'datapath_id': '0x10000000000000000',
|
|
'port_no': 123}
|
|
self._test_validate_exception(profile, expected_msg)
|
|
|
|
def test_validate_portinfo_too_big_port_number(self):
|
|
profile = {'datapath_id': '0x1234567890abcdef',
|
|
'port_no': 65536}
|
|
expected_msg = ("Invalid input for operation: "
|
|
"port_no should be [0:65535]")
|
|
self._test_validate_exception(profile, expected_msg)
|