neutron/quantum/plugins/midonet/midonet_lib.py

304 lines
12 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2012 Midokura Japan K.K.
# Copyright (C) 2013 Midokura PTE LTD
# 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: Tomoe Sugihara, Midokura Japan KK
# @author: Ryu Ishimoto, Midokura Japan KK
from quantum.openstack.common import log as logging
LOG = logging.getLogger(__name__)
PREFIX = 'OS_SG_'
SUFFIX_IN = '_IN'
SUFFIX_OUT = '_OUT'
OS_ROUTER_IN_CHAIN_NAME_FORMAT = 'OS_ROUTER_IN_%s'
OS_ROUTER_OUT_CHAIN_NAME_FORMAT = 'OS_ROUTER_OUT_%s'
NAME_IDENTIFIABLE_PREFIX_LEN = len(PREFIX) + 36 # 36 = length of uuid
def sg_label(sg_id, sg_name):
"""Construct the security group ID used as chain identifier in MidoNet."""
return PREFIX + str(sg_id) + '_' + sg_name
port_group_name = sg_label
def chain_names(sg_id, sg_name):
"""Get inbound and outbound chain names."""
prefix = sg_label(sg_id, sg_name)
in_chain_name = prefix + SUFFIX_IN
out_chain_name = prefix + SUFFIX_OUT
return {'in': in_chain_name, 'out': out_chain_name}
class ChainManager:
def __init__(self, mido_api):
self.mido_api = mido_api
def create_for_sg(self, tenant_id, sg_id, sg_name):
"""Create a new chain for security group.
Creating a security group creates a pair of chains in MidoNet, one for
inbound and the other for outbound.
"""
LOG.debug(_("ChainManager.create_for_sg called: "
"tenant_id=%(tenant_id)s sg_id=%(sg_id)s "
"sg_name=%(sg_name)s "),
{'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name})
cnames = chain_names(sg_id, sg_name)
self.mido_api.add_chain().tenant_id(tenant_id).name(
cnames['in']).create()
self.mido_api.add_chain().tenant_id(tenant_id).name(
cnames['out']).create()
def delete_for_sg(self, tenant_id, sg_id, sg_name):
"""Delete a chain mapped to a security group.
Delete a SG means deleting all the chains (inbound and outbound)
associated with the SG in MidoNet.
"""
LOG.debug(_("ChainManager.delete_for_sg called: "
"tenant_id=%(tenant_id)s sg_id=%(sg_id)s "
"sg_name=%(sg_name)s "),
{'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name})
cnames = chain_names(sg_id, sg_name)
chains = self.mido_api.get_chains({'tenant_id': tenant_id})
for c in chains:
if c.get_name() == cnames['in'] or c.get_name() == cnames['out']:
LOG.debug(_('ChainManager.delete_for_sg: deleting chain=%r'),
c)
c.delete()
def get_router_chains(self, tenant_id, router_id):
"""Get router chains.
Returns a dictionary that has in/out chain resources key'ed with 'in'
and 'out' respectively, given the tenant_id and the router_id passed
in in the arguments.
"""
LOG.debug(_("ChainManager.get_router_chains called: "
"tenant_id=%(tenant_id)s router_id=%(router_id)s"),
{'tenant_id': tenant_id, 'router_id': router_id})
router_chain_names = self._get_router_chain_names(router_id)
chains = {}
for c in self.mido_api.get_chains({'tenant_id': tenant_id}):
if c.get_name() == router_chain_names['in']:
chains['in'] = c
elif c.get_name() == router_chain_names['out']:
chains['out'] = c
return chains
def create_router_chains(self, tenant_id, router_id):
"""Create a new chain on a router.
Creates chains for the router and returns the same dictionary as
get_router_chains() returns.
"""
LOG.debug(_("ChainManager.create_router_chains called: "
"tenant_id=%(tenant_id)s router_id=%(router_id)s"),
{'tenant_id': tenant_id, 'router_id': router_id})
chains = {}
router_chain_names = self._get_router_chain_names(router_id)
chains['in'] = self.mido_api.add_chain().tenant_id(tenant_id).name(
router_chain_names['in']).create()
chains['out'] = self.mido_api.add_chain().tenant_id(tenant_id).name(
router_chain_names['out']).create()
return chains
def get_sg_chains(self, tenant_id, sg_id):
"""Get a list of chains mapped to a security group."""
LOG.debug(_("ChainManager.get_sg_chains called: "
"tenant_id=%(tenant_id)s sg_id=%(sg_id)s"),
{'tenant_id': tenant_id, 'sg_id': sg_id})
cnames = chain_names(sg_id, sg_name='')
chain_name_prefix_for_id = cnames['in'][:NAME_IDENTIFIABLE_PREFIX_LEN]
chains = {}
for c in self.mido_api.get_chains({'tenant_id': tenant_id}):
if c.get_name().startswith(chain_name_prefix_for_id):
if c.get_name().endswith(SUFFIX_IN):
chains['in'] = c
if c.get_name().endswith(SUFFIX_OUT):
chains['out'] = c
assert 'in' in chains
assert 'out' in chains
return chains
def _get_router_chain_names(self, router_id):
LOG.debug(_("ChainManager.get_router_chain_names called: "
"router_id=%(router_id)s"), {'router_id': router_id})
in_name = OS_ROUTER_IN_CHAIN_NAME_FORMAT % router_id
out_name = OS_ROUTER_OUT_CHAIN_NAME_FORMAT % router_id
router_chain_names = {'in': in_name, 'out': out_name}
return router_chain_names
class PortGroupManager:
def __init__(self, mido_api):
self.mido_api = mido_api
def create(self, tenant_id, sg_id, sg_name):
LOG.debug(_("PortGroupManager.create called: "
"tenant_id=%(tenant_id)s sg_id=%(sg_id)s "
"sg_name=%(sg_name)s"),
{'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name})
pg_name = port_group_name(sg_id, sg_name)
self.mido_api.add_port_group().tenant_id(tenant_id).name(
pg_name).create()
def delete(self, tenant_id, sg_id, sg_name):
LOG.debug(_("PortGroupManager.delete called: "
"tenant_id=%(tenant_id)s sg_id=%(sg_id)s "
"sg_name=%(sg_name)s"),
{'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name})
pg_name = port_group_name(sg_id, sg_name)
pgs = self.mido_api.get_port_groups({'tenant_id': tenant_id})
for pg in pgs:
if pg.get_name() == pg_name:
LOG.debug(_("PortGroupManager.delete: deleting pg=%r"), pg)
pg.delete()
def get_for_sg(self, tenant_id, sg_id):
LOG.debug(_("PortGroupManager.get_for_sg called: "
"tenant_id=%(tenant_id)s sg_id=%(sg_id)s"),
{'tenant_id': tenant_id, 'sg_id': sg_id})
pg_name_prefix = port_group_name(
sg_id, sg_name='')[:NAME_IDENTIFIABLE_PREFIX_LEN]
port_groups = self.mido_api.get_port_groups({'tenant_id': tenant_id})
for pg in port_groups:
if pg.get_name().startswith(pg_name_prefix):
LOG.debug(_("PortGroupManager.get_for_sg exiting: pg=%r"), pg)
return pg
return None
class RuleManager:
OS_SG_KEY = 'os_sg_rule_id'
def __init__(self, mido_api):
self.mido_api = mido_api
self.chain_manager = ChainManager(mido_api)
self.pg_manager = PortGroupManager(mido_api)
def _properties(self, os_sg_rule_id):
return {self.OS_SG_KEY: str(os_sg_rule_id)}
def create_for_sg_rule(self, rule):
LOG.debug(_("RuleManager.create_for_sg_rule called: rule=%r"), rule)
direction = rule['direction']
protocol = rule['protocol']
port_range_max = rule['port_range_max']
rule_id = rule['id']
security_group_id = rule['security_group_id']
remote_group_id = rule['remote_group_id']
remote_ip_prefix = rule['remote_ip_prefix'] # watch out. not validated
tenant_id = rule['tenant_id']
port_range_min = rule['port_range_min']
# construct a corresponding rule
tp_src_start = tp_src_end = None
tp_dst_start = tp_dst_end = None
nw_src_address = None
nw_src_length = None
port_group_id = None
# handle source
if not remote_ip_prefix is None:
nw_src_address, nw_src_length = remote_ip_prefix.split('/')
elif not remote_group_id is None: # security group as a srouce
source_pg = self.pg_manager.get_for_sg(tenant_id, remote_group_id)
port_group_id = source_pg.get_id()
else:
raise Exception(_("Don't know what to do with rule=%r"), rule)
# dst ports
tp_dst_start, tp_dst_end = port_range_min, port_range_max
# protocol
if protocol == 'tcp':
nw_proto = 6
elif protocol == 'udp':
nw_proto = 17
elif protocol == 'icmp':
nw_proto = 1
# extract type and code from reporposed fields
icmp_type = rule['from_port']
icmp_code = rule['to_port']
# translate -1(wildcard in OS) to midonet wildcard
if icmp_type == -1:
icmp_type = None
if icmp_code == -1:
icmp_code = None
# set data for midonet rule
tp_src_start = tp_src_end = icmp_type
tp_dst_start = tp_dst_end = icmp_code
chains = self.chain_manager.get_sg_chains(tenant_id, security_group_id)
chain = None
if direction == 'egress':
chain = chains['in']
elif direction == 'ingress':
chain = chains['out']
else:
raise Exception(_("Don't know what to do with rule=%r"), rule)
# create an accept rule
properties = self._properties(rule_id)
LOG.debug(_("RuleManager.create_for_sg_rule: adding accept rule "
"%(rule_id)s in portgroup %(port_group_id)s"),
{'rule_id': rule_id, 'port_group_id': port_group_id})
chain.add_rule().port_group(port_group_id).type('accept').nw_proto(
nw_proto).nw_src_address(nw_src_address).nw_src_length(
nw_src_length).tp_src_start(tp_src_start).tp_src_end(
tp_src_end).tp_dst_start(tp_dst_start).tp_dst_end(
tp_dst_end).properties(properties).create()
def delete_for_sg_rule(self, rule):
LOG.debug(_("RuleManager.delete_for_sg_rule called: rule=%r"), rule)
tenant_id = rule['tenant_id']
security_group_id = rule['security_group_id']
rule_id = rule['id']
properties = self._properties(rule_id)
# search for the chains to find the rule to delete
chains = self.chain_manager.get_sg_chains(tenant_id, security_group_id)
for c in chains['in'], chains['out']:
rules = c.get_rules()
for r in rules:
if r.get_properties() == properties:
LOG.debug(_("RuleManager.delete_for_sg_rule: deleting "
"rule %r"), r)
r.delete()