5a29ced8b2
Change-Id: Ie9798fe1bb4c6d511c601306ed1d366d97ff09f7 Signed-off-by: Pino de Candia <giuseppe.decandia@gmail.com>
151 lines
5.1 KiB
Python
151 lines
5.1 KiB
Python
# 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 dragonflow.db.models.core import Chassis
|
|
from dragonflow.db.models.l2 import LogicalPort
|
|
from dragonflow.db.models.l3 import LogicalRouter
|
|
from dragonflow.db.models.l3 import PAT
|
|
from dragonflow.db.models.l3 import PATEntry
|
|
from oslo_log import log as logging
|
|
import random
|
|
from tatu import dns
|
|
from tatu.config import CONF, NEUTRON, NOVA, DRAGONFLOW
|
|
from tatu.db import models as tatu_db
|
|
from tatu.utils import dash_uuid
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
#TODO(pino): periodically refresh this list
|
|
PATS = None
|
|
|
|
|
|
def getAllPats():
|
|
if not CONF.tatu.use_pat_bastions:
|
|
return []
|
|
return DRAGONFLOW.get_all(PAT)
|
|
|
|
|
|
def _sync_pats():
|
|
if not CONF.tatu.use_pat_bastions:
|
|
return
|
|
# TODO(pino): re-bind PATs when hypervisors fail (here and on notification)
|
|
all_chassis = DRAGONFLOW.get_all(Chassis)
|
|
# Filter the chassis that already have PATS assigned
|
|
# TODO: This doesn't work now because the p.chassis is a ChassisProxy
|
|
free_chassis = set(all_chassis).difference(p.chassis for p in PATS)
|
|
# Don't make more PATs than there are free chassis
|
|
num_to_make = min(CONF.tatu.num_total_pats - len(PATS),
|
|
len(free_chassis))
|
|
if num_to_make > 0:
|
|
assigned_chassis = random.sample(free_chassis, num_to_make)
|
|
for c in assigned_chassis:
|
|
_add_pat(c)
|
|
dns.sync_bastions(str(p.ip_address) for p in PATS)
|
|
global PATS
|
|
PATS = DRAGONFLOW.get_all(PAT)
|
|
|
|
|
|
def _add_pat(chassis):
|
|
# Find the public network and allocate a new port.
|
|
networks = NEUTRON.list_networks(name='public')
|
|
network_id = networks['networks'][0]['id']
|
|
body = {
|
|
"port": {
|
|
"admin_state_up": True,
|
|
"name": 'TatuPAT', # TODO(pino): set device owner to Tatu?
|
|
"network_id": network_id,
|
|
"port_security_enabled": False,
|
|
"security_groups": [],
|
|
}
|
|
}
|
|
neutron_port = NEUTRON.create_port(body)
|
|
lport = DRAGONFLOW.get(LogicalPort(id=neutron_port['port']['id']))
|
|
ip = _get_ip4_from_lport(lport)
|
|
pat = PAT(
|
|
id = str(ip),
|
|
topic = 'tatu', # TODO(pino): What topic? Admin project_id?
|
|
ip_address = ip,
|
|
lport = lport,
|
|
chassis = chassis,
|
|
)
|
|
# We only need to store the PAT in dragonflow's DB, not API/MySQL
|
|
DRAGONFLOW.create(pat)
|
|
PATS.append(pat)
|
|
|
|
|
|
def _get_ip4_from_lport(lport):
|
|
for ip in lport.ips:
|
|
if ip.version is 4:
|
|
return ip
|
|
return None
|
|
|
|
|
|
def _df_find_lrouter_by_lport(lport):
|
|
lrouters = DRAGONFLOW.get_all(LogicalRouter)
|
|
for lr in lrouters:
|
|
for lp in lr.ports:
|
|
if lp.lswitch.id == lport.lswitch.id:
|
|
return lr
|
|
return None
|
|
|
|
|
|
def ip_port_tuples_to_string(tuples):
|
|
return ','.join('{}:{}'.format(t[0], t[1]) for t in tuples)
|
|
|
|
|
|
def string_to_ip_port_tuples(s):
|
|
return [tuple(ip_port.split(':')) for ip_port in s.split(',')]
|
|
|
|
def deletePatEntries(ip_port_tuples):
|
|
LOG.debug("Delete PATEntry for each of tuples: {}".format(ip_port_tuples))
|
|
pat_entries = DRAGONFLOW.get_all(PATEntry)
|
|
tuples = set(ip_port_tuples)
|
|
for entry in pat_entries:
|
|
if (entry.pat.id, str(entry.pat_l4_port)) in tuples:
|
|
DRAGONFLOW.delete(entry)
|
|
|
|
|
|
def create_pat_entries(sql_session, instance_id,
|
|
fixed_l4_port=CONF.tatu.ssh_port,
|
|
num=CONF.tatu.num_pat_bastions_per_server):
|
|
ip_port_tuples = []
|
|
server = NOVA.servers.get(dash_uuid(instance_id))
|
|
ifaces = server.interface_list()
|
|
for iface in ifaces:
|
|
lport = DRAGONFLOW.get(LogicalPort(id=iface.port_id))
|
|
# TODO(pino): no router? consider SNAT of source IP to 169.254.169.254
|
|
lrouter = _df_find_lrouter_by_lport(lport)
|
|
if lrouter is None: continue
|
|
# Reserve N l4 ports on distinct IPs.
|
|
pats = PATS
|
|
if (num < len(PATS)):
|
|
pats = random.sample(pats, num)
|
|
for pat in pats:
|
|
pat_l4_port = tatu_db.reserve_l4_port(sql_session, str(pat.ip_address))
|
|
pat_entry = PATEntry(
|
|
id = '{}:{}'.format(pat.id, pat_l4_port),
|
|
topic = 'tatu',
|
|
pat = pat,
|
|
pat_l4_port = pat_l4_port,
|
|
fixed_ip_address = _get_ip4_from_lport(lport),
|
|
fixed_l4_port = fixed_l4_port,
|
|
lport = lport,
|
|
lrouter = lrouter,
|
|
)
|
|
DRAGONFLOW.create(pat_entry)
|
|
ip_port_tuples.append((str(pat.ip_address), pat_l4_port))
|
|
# if we got here, we now have the required pat_entries
|
|
break
|
|
return ip_port_tuples
|
|
|
|
|
|
_sync_pats()
|