86a3fa3abe
The Instance class got an boolean field 'assignFloatingIp', false by default. If this is set 'true', the instance will attempt to assign a floating ip in one of its networks (starting from default ones) NetworkExplorer class got a functionality to dicover the ID of the external network: - for newly created networks it will use their specified external router's external_gateway_info - for existing networks it will iterate among all their routers Change-Id: I754cd41ffacbf2a2ec62ac23c3a5a7883d1e13b5 Closes-Bug: #1314193
160 lines
5.9 KiB
Python
160 lines
5.9 KiB
Python
# Copyright (c) 2014 Mirantis Inc.
|
|
#
|
|
# 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 math
|
|
|
|
import keystoneclient.apiclient.exceptions as ks_exc
|
|
import keystoneclient.v2_0.client as ksclient
|
|
import netaddr
|
|
from netaddr.strategy import ipv4
|
|
import neutronclient.v2_0.client as nclient
|
|
|
|
import muranoapi.common.config as config
|
|
import muranoapi.dsl.helpers as helpers
|
|
import muranoapi.dsl.murano_class as murano_class
|
|
import muranoapi.dsl.murano_object as murano_object
|
|
|
|
|
|
@murano_class.classname('io.murano.system.NetworkExplorer')
|
|
class NetworkExplorer(murano_object.MuranoObject):
|
|
# noinspection PyAttributeOutsideInit
|
|
def initialize(self, _context):
|
|
environment = helpers.get_environment(_context)
|
|
self._tenant_id = environment.tenant_id
|
|
keystone_settings = config.CONF.keystone
|
|
neutron_settings = config.CONF.neutron
|
|
self._settings = config.CONF.networking
|
|
|
|
keystone_client = ksclient.Client(
|
|
endpoint=keystone_settings.auth_url,
|
|
cacert=keystone_settings.ca_file or None,
|
|
cert=keystone_settings.cert_file or None,
|
|
key=keystone_settings.key_file or None,
|
|
insecure=keystone_settings.insecure)
|
|
|
|
if not keystone_client.authenticate(
|
|
auth_url=keystone_settings.auth_url,
|
|
tenant_id=environment.tenant_id,
|
|
token=environment.token):
|
|
raise ks_exc.AuthorizationFailure()
|
|
|
|
neutron_url = keystone_client.service_catalog.url_for(
|
|
service_type='network',
|
|
endpoint_type=neutron_settings.endpoint_type)
|
|
|
|
self._neutron = \
|
|
nclient.Client(endpoint_url=neutron_url,
|
|
token=environment.token,
|
|
ca_cert=neutron_settings.ca_cert or None,
|
|
insecure=neutron_settings.insecure)
|
|
|
|
self._available_cidrs = self._generate_possible_cidrs()
|
|
|
|
# noinspection PyPep8Naming
|
|
def getDefaultRouter(self):
|
|
routers = self._neutron.list_routers(tenant_id=self._tenant_id).\
|
|
get("routers")
|
|
if len(routers) == 0:
|
|
return "NOT_FOUND"
|
|
else:
|
|
router_id = routers[0]["id"]
|
|
|
|
if len(routers) > 1:
|
|
for router in routers:
|
|
if "murano" in router["name"].lower():
|
|
return router["id"]
|
|
|
|
return router_id
|
|
|
|
# noinspection PyPep8Naming
|
|
def getAvailableCidr(self, routerId, netId):
|
|
"""
|
|
Uses hash of network IDs to minimize the collisions:
|
|
different nets will attempt to pick different cidrs out of available
|
|
range.
|
|
If the cidr is taken will pick another one
|
|
"""
|
|
taken_cidrs = self._get_cidrs_taken_by_router(routerId)
|
|
id_hash = hash(netId)
|
|
num_fails = 0
|
|
while num_fails < len(self._available_cidrs):
|
|
cidr = self._available_cidrs[
|
|
(id_hash + num_fails) % len(self._available_cidrs)]
|
|
if any(self._cidrs_overlap(cidr, taken_cidr) for taken_cidr in
|
|
taken_cidrs):
|
|
num_fails += 1
|
|
else:
|
|
return str(cidr)
|
|
return None
|
|
|
|
# noinspection PyPep8Naming
|
|
def getDefaultDns(self):
|
|
return self._settings.default_dns
|
|
|
|
# noinspection PyPep8Naming
|
|
def getExternalNetworkIdForRouter(self, routerId):
|
|
router = self._neutron.show_router(routerId).get('router')
|
|
if not router or 'external_gateway_info' not in router:
|
|
return None
|
|
return router['external_gateway_info'].get('network_id')
|
|
|
|
# noinspection PyPep8Naming
|
|
def getExternalNetworkIdForNetwork(self, networkId):
|
|
network = self._neutron.show_network(networkId).get('network')
|
|
if network.get('router:external', False):
|
|
return networkId
|
|
|
|
# Get router interfaces of the network
|
|
router_ports = self._neutron.list_ports(
|
|
**{'device_owner': 'network:router_interface',
|
|
'network_id': networkId}).get('ports')
|
|
|
|
# For each router this network is connected to
|
|
# check if the router has external_gateway set
|
|
for router_port in router_ports:
|
|
ext_net_id = self.getExternalNetworkIdForRouter(
|
|
router_port.get('device_id'))
|
|
if ext_net_id:
|
|
return ext_net_id
|
|
return None
|
|
|
|
def _get_cidrs_taken_by_router(self, router_id):
|
|
if not router_id:
|
|
return []
|
|
ports = self._neutron.list_ports(device_id=router_id)["ports"]
|
|
subnet_ids = []
|
|
for port in ports:
|
|
for fixed_ip in port["fixed_ips"]:
|
|
subnet_ids.append(fixed_ip["subnet_id"])
|
|
|
|
all_subnets = self._neutron.list_subnets()["subnets"]
|
|
filtered_cidrs = [netaddr.IPNetwork(subnet["cidr"]) for subnet in
|
|
all_subnets if subnet["id"] in subnet_ids]
|
|
|
|
return filtered_cidrs
|
|
|
|
@staticmethod
|
|
def _cidrs_overlap(cidr1, cidr2):
|
|
return (cidr1 in cidr2) or (cidr2 in cidr1)
|
|
|
|
def _generate_possible_cidrs(self):
|
|
bits_for_envs = int(
|
|
math.ceil(math.log(self._settings.max_environments, 2)))
|
|
bits_for_hosts = int(math.ceil(math.log(self._settings.max_hosts, 2)))
|
|
width = ipv4.width
|
|
mask_width = width - bits_for_hosts - bits_for_envs
|
|
net = netaddr.IPNetwork(
|
|
"{0}/{1}".format(self._settings.env_ip_template, mask_width))
|
|
return list(net.subnet(width - bits_for_hosts))
|