Merge "Add cookie infrastructure to Dragonflow"
This commit is contained in:
commit
c67d036b69
|
@ -97,3 +97,16 @@ class UnknownResourceException(DragonflowException):
|
|||
|
||||
class InvalidDBHostConfiguration(DragonflowException):
|
||||
message = _('The DB host string %(host)s is invalid.')
|
||||
|
||||
|
||||
class OutOfCookieSpaceException(DragonflowException):
|
||||
message = _('Out of cookie space.')
|
||||
|
||||
|
||||
class MaskOverlapException(DragonflowException):
|
||||
message = _('Cookie mask overlap for cookie %(app_name)s/%(name)s')
|
||||
|
||||
|
||||
class CookieOverflowExcpetion(DragonflowException):
|
||||
message = _('Cookie overflow: '
|
||||
'Value: %(cookie)s Offset: %(offset)s Mask: %(mask)s')
|
||||
|
|
|
@ -101,17 +101,8 @@ CT_ZONE_REG = 0x1d402
|
|||
MIN_PORT = 1
|
||||
MAX_PORT = 65535
|
||||
|
||||
"""
|
||||
Cookie Mask
|
||||
global cookie is used by flows of all table, but local cookie is used
|
||||
by flows of a small part of table. In order to avoid conflict,
|
||||
global cookies should not overlapped with each other, but local cookies
|
||||
could be overlapped for saving space of cookie.
|
||||
all cookie's mask should be kept here to avoid conflict.
|
||||
"""
|
||||
#TODO(oanson) Remove once Aging app is fully updated to use cookie framework
|
||||
GLOBAL_AGING_COOKIE_MASK = 0x1
|
||||
SECURITY_GROUP_RULE_COOKIE_MASK = 0x1fffffffe
|
||||
SECURITY_GROUP_RULE_COOKIE_SHIFT_LEN = 1
|
||||
GLOBAL_INIT_AGING_COOKIE = 0x1
|
||||
|
||||
# These two globals are constant, as defined by the metadata service API. VMs
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
# 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 collections
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from dragonflow._i18n import _LI, _LE
|
||||
from dragonflow.common import exceptions
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
GLOBAL_APP_NAME = 'global cookie namespace'
|
||||
|
||||
|
||||
"""Dictionary to hold a map from a task name to its cookie info"""
|
||||
_cookies = {}
|
||||
# Maximum number of bits that can be encoded. Taken from OVS
|
||||
_cookie_max_bits = 64
|
||||
# Maximum number of bits allocated to global cookies
|
||||
_cookie_max_bits_global = 32
|
||||
# Turn on all bits in the cookie mask. There are 64 (_cookie_max_bits)
|
||||
# bits. -1 is all (infinite) bits on. Shift right and left again to have all
|
||||
# bits but the least 64 bits on. Bitwise not to have only the 64 LSBits on.
|
||||
_cookie_mask_all = ~((-1 >> _cookie_max_bits) << _cookie_max_bits)
|
||||
# Maximum number of bits allocated to local cookies (total bits - global bits)
|
||||
_cookie_max_bits_local = _cookie_max_bits - _cookie_max_bits_global
|
||||
# Number of allocated bits for a given application (including global)
|
||||
_cookies_used_bits = collections.defaultdict(int)
|
||||
|
||||
|
||||
# A class holding the cookie's offset and bit-mask
|
||||
CookieBitPair = collections.namedtuple('CookieBitPair', ('offset', 'mask'))
|
||||
|
||||
|
||||
def register_cookie_bits(name, length, is_local=False, app_name=None):
|
||||
"""Register this many cookie bits for the given 'task'.
|
||||
There are two types of cookies: global and local.
|
||||
Global cookies are global accross all applications. All applications share
|
||||
the information, and the cookie bits can only be assigned once.
|
||||
Local cookies are local to a specific application. That application is
|
||||
responsible to the data encoded in the cookie. Therefore, local cookie
|
||||
bits can be reused between applications, i.e. different applications can
|
||||
use the same local cookie bits to write different things.
|
||||
This function raises an error if there are not enough bits to allocate.
|
||||
:param name: The name of the 'task'
|
||||
:type name: string
|
||||
:param length: The length of the cookie to allocate
|
||||
:type length: int
|
||||
:param is_local: The cookie space is local, as defined above.
|
||||
:type is_local: bool
|
||||
:param app_name: Owner application of the cookie (None for global)
|
||||
:type app_name: string
|
||||
"""
|
||||
if not is_local:
|
||||
app_name = GLOBAL_APP_NAME
|
||||
shift = 0
|
||||
max_bits = _cookie_max_bits_global
|
||||
else:
|
||||
shift = _cookie_max_bits_global
|
||||
max_bits = _cookie_max_bits_local
|
||||
if not app_name:
|
||||
raise TypeError(_LE("app_name must be provided "
|
||||
"if is_local is True"))
|
||||
if (app_name, name) in _cookies:
|
||||
LOG.info(_LI("Cookie for %(app_name)s/%(name)s already registered."),
|
||||
{"app_name": app_name, "name": name})
|
||||
return
|
||||
start = _cookies_used_bits[app_name]
|
||||
if start + length > max_bits:
|
||||
LOG.error(_LE("Out of cookie space: "
|
||||
"offset: %(offset)d length: %(length)d"),
|
||||
{"offset": start, "length": length})
|
||||
raise exceptions.OutOfCookieSpaceException()
|
||||
_cookies_used_bits[app_name] = start + length
|
||||
start += shift
|
||||
mask = (_cookie_mask_all >> (_cookie_max_bits - length)) << start
|
||||
_cookies[(app_name, name)] = CookieBitPair(start, mask)
|
||||
LOG.info(_LI("Registered cookie for %(app_name)s/%(name)s, "
|
||||
"mask: %(mask)x, offset: %(offset)d, length: %(length)d"),
|
||||
{"app_name": app_name, "name": name,
|
||||
"mask": mask, "offset": start, "length": length})
|
||||
|
||||
|
||||
def get_cookie(name, value, old_cookie=0, old_mask=0,
|
||||
is_local=False, app_name=None):
|
||||
"""Encode the given cookie value as the registered cookie. i.e. shift
|
||||
it to the correct location, and verify there are no overflows.
|
||||
:param name: The name of the 'task'
|
||||
:type name: string
|
||||
:param value: The value of the cookie to encode
|
||||
:type value: int
|
||||
:param old_cookie: Encode this cookie alongside other cookie values
|
||||
:type old_cookie: int
|
||||
:param old_mask: The mask (i.e. encoded relevant bits) in old_cookie
|
||||
:type old_mask: int
|
||||
:param is_local: The cookie space is local, as defined in
|
||||
register_cookie_bits
|
||||
:type is_local: bool
|
||||
:param app_name: Owner application of the cookie (None for global)
|
||||
:type app_name: string
|
||||
"""
|
||||
if not is_local:
|
||||
app_name = GLOBAL_APP_NAME
|
||||
else:
|
||||
if not app_name:
|
||||
raise TypeError(_LE("app_name must be provided "
|
||||
"if is_local is True"))
|
||||
pair = _cookies[(app_name, name)]
|
||||
mask_overlap = old_mask & pair.mask
|
||||
if mask_overlap != 0:
|
||||
if mask_overlap != pair.mask:
|
||||
raise exceptions.MaskOverlapException(app_name=app_name, name=name)
|
||||
return old_cookie, old_mask
|
||||
result_unmasked = (value << pair.offset)
|
||||
result = (result_unmasked & pair.mask)
|
||||
if result != result_unmasked:
|
||||
raise exceptions.CookieOverflowExcpetion(cookie=value,
|
||||
offset=pair.offset, mask=pair.mask)
|
||||
return result | (old_cookie & ~pair.mask), pair.mask | old_mask
|
|
@ -21,6 +21,7 @@ from ryu.lib import addrconv
|
|||
from dragonflow._i18n import _LE
|
||||
from dragonflow.common import exceptions
|
||||
from dragonflow.controller.common import constants as const
|
||||
from dragonflow.controller.common import cookies
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
@ -29,6 +30,11 @@ ACTIVE_PORT_DETECTION_APP = \
|
|||
"active_port_detection_app.ActivePortDetectionApp"
|
||||
|
||||
|
||||
AGING_COOKIE_NAME = 'aging'
|
||||
AGING_COOKIE_LEN = 1
|
||||
cookies.register_cookie_bits(AGING_COOKIE_NAME, AGING_COOKIE_LEN)
|
||||
|
||||
|
||||
def ipv4_text_to_int(ip_text):
|
||||
try:
|
||||
return struct.unpack('!I', addrconv.ipv4.text_to_bin(ip_text))[0]
|
||||
|
@ -54,11 +60,9 @@ def get_aging_cookie():
|
|||
return _aging_cookie
|
||||
|
||||
|
||||
def set_aging_cookie_bits(cookie):
|
||||
# clear aging bits before using
|
||||
c = cookie & (~const.GLOBAL_AGING_COOKIE_MASK)
|
||||
c |= (_aging_cookie & const.GLOBAL_AGING_COOKIE_MASK)
|
||||
return c
|
||||
def set_aging_cookie_bits(old_cookie, old_cookie_mask):
|
||||
return cookies.get_cookie(AGING_COOKIE_NAME, _aging_cookie,
|
||||
old_cookie, old_cookie_mask)
|
||||
|
||||
|
||||
def get_xor_cookie(cookie):
|
||||
|
|
|
@ -18,6 +18,7 @@ from ryu.lib.packet import ethernet
|
|||
from ryu.lib.packet import packet
|
||||
from ryu.ofproto import ether
|
||||
|
||||
from dragonflow.controller.common import cookies
|
||||
from dragonflow.controller.common import utils
|
||||
from dragonflow.controller import df_db_notifier
|
||||
|
||||
|
@ -101,7 +102,7 @@ class DFlowApp(df_db_notifier.DBNotifyInterface):
|
|||
if out_group is None:
|
||||
out_group = datapath.ofproto.OFPG_ANY
|
||||
|
||||
cookie = utils.set_aging_cookie_bits(cookie)
|
||||
cookie, cookie_mask = utils.set_aging_cookie_bits(cookie, cookie_mask)
|
||||
|
||||
message = datapath.ofproto_parser.OFPFlowMod(datapath, cookie,
|
||||
cookie_mask,
|
||||
|
@ -142,3 +143,13 @@ class DFlowApp(df_db_notifier.DBNotifyInterface):
|
|||
dst_ip=dst_ip))
|
||||
|
||||
self.send_packet(port, arp_request_pkt)
|
||||
|
||||
def register_local_cookie_bits(self, name, length):
|
||||
cookies.register_cookie_bits(name, length,
|
||||
True, self.__class__.__name__)
|
||||
|
||||
def get_local_cookie(self, name, value, old_cookie=0, old_mask=0):
|
||||
return cookies.get_cookie(name, value,
|
||||
old_cookie=old_cookie, old_mask=old_mask,
|
||||
is_local=True,
|
||||
app_name=self.__class__.__name__)
|
||||
|
|
|
@ -30,6 +30,7 @@ from dragonflow.db import models
|
|||
|
||||
ROUTE_TO_ADD = 'route_to_add'
|
||||
ROUTE_ADDED = 'route_added'
|
||||
COOKIE_NAME = 'tunnel_key'
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -40,6 +41,7 @@ class L3ProactiveApp(df_base_app.DFlowApp):
|
|||
self.router_port_rarp_cache = {}
|
||||
self.api.register_table_handler(const.L3_LOOKUP_TABLE,
|
||||
self.packet_in_handler)
|
||||
self.register_local_cookie_bits(COOKIE_NAME, 24)
|
||||
|
||||
def switch_features_handler(self, ev):
|
||||
self.router_port_rarp_cache.clear()
|
||||
|
@ -317,8 +319,10 @@ class L3ProactiveApp(df_base_app.DFlowApp):
|
|||
|
||||
inst = [action_inst, goto_inst]
|
||||
|
||||
cookie, cookie_mask = self.get_local_cookie(COOKIE_NAME, tunnel_key)
|
||||
self.mod_flow(
|
||||
cookie=tunnel_key,
|
||||
cookie=cookie,
|
||||
cookie_mask=cookie_mask,
|
||||
inst=inst,
|
||||
table_id=const.L3_LOOKUP_TABLE,
|
||||
priority=const.PRIORITY_VERY_HIGH,
|
||||
|
@ -425,8 +429,11 @@ class L3ProactiveApp(df_base_app.DFlowApp):
|
|||
|
||||
inst = [action_inst, goto_inst]
|
||||
|
||||
cookie, cookie_mask = self.get_local_cookie(COOKIE_NAME,
|
||||
dst_router_tunnel_key)
|
||||
self.mod_flow(
|
||||
cookie=dst_router_tunnel_key,
|
||||
cookie=cookie,
|
||||
cookie_mask=cookie_mask,
|
||||
inst=inst,
|
||||
table_id=const.L3_LOOKUP_TABLE,
|
||||
priority=const.PRIORITY_MEDIUM,
|
||||
|
@ -514,10 +521,10 @@ class L3ProactiveApp(df_base_app.DFlowApp):
|
|||
match=match)
|
||||
|
||||
match = parser.OFPMatch()
|
||||
cookie = tunnel_key
|
||||
cookie, cookie_mask = self.get_local_cookie(COOKIE_NAME, tunnel_key)
|
||||
self.mod_flow(
|
||||
cookie=cookie,
|
||||
cookie_mask=cookie,
|
||||
cookie_mask=cookie_mask,
|
||||
table_id=const.L3_LOOKUP_TABLE,
|
||||
command=ofproto.OFPFC_DELETE,
|
||||
priority=const.PRIORITY_MEDIUM,
|
||||
|
|
|
@ -29,8 +29,8 @@ LOG = log.getLogger(__name__)
|
|||
|
||||
SG_CT_STATE_MASK = const.CT_STATE_NEW | const.CT_STATE_EST | \
|
||||
const.CT_STATE_REL | const.CT_STATE_INV | const.CT_STATE_TRK
|
||||
COOKIE_FULLMASK = 0xffffffffffffffff
|
||||
SG_PRIORITY_OFFSET = 2
|
||||
COOKIE_NAME = 'sg rule'
|
||||
|
||||
DEST_FIELD_NAME_BY_PROTOCOL_NUMBER = {
|
||||
n_const.PROTO_NUM_TCP: 'tcp_dst',
|
||||
|
@ -75,6 +75,7 @@ class SGApp(df_base_app.DFlowApp):
|
|||
netaddr.IPSet
|
||||
)
|
||||
self.secgroup_ip_refs = collections.defaultdict(set)
|
||||
self.register_local_cookie_bits(COOKIE_NAME, 32)
|
||||
|
||||
@staticmethod
|
||||
def _get_cidr_difference(cidr_set, new_cidr_set):
|
||||
|
@ -179,10 +180,8 @@ class SGApp(df_base_app.DFlowApp):
|
|||
results.append(result)
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def _get_rule_cookie(rule_id):
|
||||
rule_cookie = rule_id << const.SECURITY_GROUP_RULE_COOKIE_SHIFT_LEN
|
||||
return rule_cookie & const.SECURITY_GROUP_RULE_COOKIE_MASK
|
||||
def _get_rule_cookie(self, rule_id):
|
||||
return self.get_local_cookie(COOKIE_NAME, rule_id)
|
||||
|
||||
def _inc_ip_reference_and_check(self, secgroup_id, ip, lport_id):
|
||||
"""
|
||||
|
@ -570,9 +569,10 @@ class SGApp(df_base_app.DFlowApp):
|
|||
parameters_merge[ipv4_match_item] = \
|
||||
SGApp._get_network_and_mask(added_cidr_item)
|
||||
match = parser.OFPMatch(**parameters_merge)
|
||||
cookie, cookie_mask = self._get_rule_cookie(rule_id)
|
||||
self.mod_flow(
|
||||
cookie=SGApp._get_rule_cookie(rule_id),
|
||||
cookie_mask=COOKIE_FULLMASK,
|
||||
cookie=cookie,
|
||||
cookie_mask=cookie_mask,
|
||||
inst=inst,
|
||||
table_id=table_id,
|
||||
priority=priority,
|
||||
|
@ -644,9 +644,10 @@ class SGApp(df_base_app.DFlowApp):
|
|||
parameters_merge = match_item.copy()
|
||||
parameters_merge.update(address_item)
|
||||
match = parser.OFPMatch(**parameters_merge)
|
||||
cookie, cookie_mask = self._get_rule_cookie(rule_id)
|
||||
self.mod_flow(
|
||||
cookie=SGApp._get_rule_cookie(rule_id),
|
||||
cookie_mask=COOKIE_FULLMASK,
|
||||
cookie=cookie,
|
||||
cookie_mask=cookie_mask,
|
||||
inst=inst,
|
||||
table_id=table_id,
|
||||
priority=priority,
|
||||
|
@ -673,9 +674,10 @@ class SGApp(df_base_app.DFlowApp):
|
|||
rule_id)
|
||||
return
|
||||
|
||||
cookie, cookie_mask = self._get_rule_cookie(rule_id)
|
||||
self.mod_flow(
|
||||
cookie=SGApp._get_rule_cookie(rule_id),
|
||||
cookie_mask=const.SECURITY_GROUP_RULE_COOKIE_MASK,
|
||||
cookie=cookie,
|
||||
cookie_mask=cookie_mask,
|
||||
table_id=table_id,
|
||||
command=ofproto.OFPFC_DELETE)
|
||||
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
# 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 collections
|
||||
import mock
|
||||
|
||||
from dragonflow.common import exceptions
|
||||
from dragonflow.controller.common import cookies
|
||||
from dragonflow.tests import base as tests_base
|
||||
|
||||
|
||||
class TestCookies(tests_base.BaseTestCase):
|
||||
@mock.patch.object(cookies, '_cookies_used_bits',
|
||||
collections.defaultdict(int))
|
||||
@mock.patch.object(cookies, '_cookies', {})
|
||||
def test_register_cookie_bits(self):
|
||||
_cookies = cookies._cookies
|
||||
used_bits = cookies._cookies_used_bits
|
||||
cookies.register_cookie_bits('test1', 3)
|
||||
cookies.register_cookie_bits('test2', 5)
|
||||
cookies.register_cookie_bits('test3', 5, True, 'app')
|
||||
cookies.register_cookie_bits('test4', 4, True, 'app')
|
||||
self.assertEqual(cookies.CookieBitPair(0, 0x7),
|
||||
_cookies[(cookies.GLOBAL_APP_NAME, 'test1')])
|
||||
self.assertEqual(cookies.CookieBitPair(3, 0x1f << 3),
|
||||
_cookies[(cookies.GLOBAL_APP_NAME, 'test2')])
|
||||
self.assertEqual(cookies.CookieBitPair(32, 0x1f << 32),
|
||||
_cookies[('app', 'test3')])
|
||||
self.assertEqual(cookies.CookieBitPair(37, 0xf << (32 + 5)),
|
||||
_cookies[('app', 'test4')])
|
||||
self.assertEqual(8, used_bits[cookies.GLOBAL_APP_NAME])
|
||||
self.assertEqual(9, used_bits['app'])
|
||||
|
||||
@mock.patch.object(cookies, '_cookies_used_bits',
|
||||
collections.defaultdict(int))
|
||||
@mock.patch.object(cookies, '_cookies', {})
|
||||
def test_register_and_get_cookies(self):
|
||||
cookies.register_cookie_bits('test1', 3)
|
||||
cookies.register_cookie_bits('test2', 5)
|
||||
cookies.register_cookie_bits('test3', 5, True, 'app')
|
||||
cookies.register_cookie_bits('test4', 4, True, 'app')
|
||||
self.assertEqual((3, 0x7), cookies.get_cookie('test1', 3))
|
||||
self.assertEqual((5 << 3, 0x1f << 3), cookies.get_cookie('test2', 5))
|
||||
self.assertEqual((10 << 32, 0x1f << 32),
|
||||
cookies.get_cookie('test3', 10,
|
||||
is_local=True, app_name='app'))
|
||||
self.assertEqual((13 << (32 + 5) | 10 << 32, 0xf << 37 | 0x1f << 32),
|
||||
cookies.get_cookie('test4', 13,
|
||||
old_cookie=10 << 32,
|
||||
old_mask=0x1f << 32,
|
||||
is_local=True, app_name='app'))
|
||||
cookie, mask = cookies.get_cookie('test1', 2)
|
||||
self.assertEqual((2, 0x7),
|
||||
cookies.get_cookie('test1', 3, cookie, mask))
|
||||
|
||||
@mock.patch.object(cookies, '_cookies_used_bits',
|
||||
collections.defaultdict(int))
|
||||
@mock.patch.object(cookies, '_cookies', {})
|
||||
def test_register_cookie_bits_errors(self):
|
||||
self.assertRaises(TypeError,
|
||||
cookies.register_cookie_bits, 't1', 3, True)
|
||||
self.assertRaises(exceptions.OutOfCookieSpaceException,
|
||||
cookies.register_cookie_bits, 't1', 33)
|
||||
self.assertRaises(exceptions.OutOfCookieSpaceException,
|
||||
cookies.register_cookie_bits, 't1', 33, True, 'app')
|
||||
cookies.register_cookie_bits('t1', 10)
|
||||
cookies.register_cookie_bits('t1', 10, True, 'app')
|
||||
self.assertRaises(exceptions.OutOfCookieSpaceException,
|
||||
cookies.register_cookie_bits, 't2', 23)
|
||||
self.assertRaises(exceptions.OutOfCookieSpaceException,
|
||||
cookies.register_cookie_bits, 't2', 23, True, 'app')
|
||||
|
||||
@mock.patch.object(cookies, '_cookies_used_bits',
|
||||
collections.defaultdict(int))
|
||||
@mock.patch.object(cookies, '_cookies', {})
|
||||
def test_get_cookies_errors(self):
|
||||
cookies.register_cookie_bits('test1', 3)
|
||||
cookies.register_cookie_bits('test2', 5)
|
||||
cookies.register_cookie_bits('test3', 5, True, 'app')
|
||||
cookies.register_cookie_bits('test4', 4, True, 'app')
|
||||
self.assertRaises(TypeError,
|
||||
cookies.get_cookie, 'test3', 3, is_local=True)
|
||||
self.assertRaises(exceptions.CookieOverflowExcpetion,
|
||||
cookies.get_cookie, 'test1', 9)
|
||||
self.assertRaises(exceptions.MaskOverlapException,
|
||||
cookies.get_cookie, 'test2', 9, 0, 0x8)
|
Loading…
Reference in New Issue