Added NetworkExplorer engine object class

Added a NetworkExplorer class (maps to io.murano.system.NetworkExplorer) to explore
the Network Environment of an active tenant.
The class is able to locate the default router (if present) and allocate available
CIDR range for the selected router. The latter requires some configuration options
(for proper CIDR segmentation)

This commit adds python-neutronclient to the requirements, as NetworkExplorer has
to directly interact with Neutron

This classes is a crucial part of AdvancedNetworking implementation

Partial-Bug: #1308921

Change-Id: Ib9daa1b1521d9bc17a53d7e131be6c9f43faa013
This commit is contained in:
Alexander Tivelkov 2014-04-24 20:36:45 +04:00
parent b9ecbbe50d
commit 961818d505
5 changed files with 148 additions and 0 deletions

View File

@ -159,3 +159,13 @@ url = http://localhost:8082
#key_file =
# If set then the server's certificate will not be verified
insecure = False
[networking]
# Maximum number of environments that use a single router per tenant
max_environments = 20
# Maximum number of VMs per environment
max_hosts = 250
# Template IP address for generating environment subnet cidrs
env_ip_template = 10.0.0.0

View File

@ -87,6 +87,12 @@ murano_opts = [
cfg.StrOpt('endpoint_type', default='publicURL')
]
networking_opts = [
cfg.IntOpt('max_environments', default=20),
cfg.IntOpt('max_hosts', default=250),
cfg.StrOpt('env_ip_template', default='10.0.0.0'),
]
stats_opt = [
cfg.IntOpt('period', default=5,
help=_('Statistics collection interval in minutes.'
@ -115,6 +121,7 @@ CONF.register_cli_opt(metadata_dir)
CONF.register_cli_opt(packages_cache)
CONF.register_cli_opt(package_size_limit)
CONF.register_opts(stats_opt, group='stats')
CONF.register_opts(networking_opts, group='networking')
CONF.import_opt('connection',
'muranoapi.openstack.common.db.options',

View File

@ -0,0 +1,128 @@
# 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
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))

View File

@ -20,6 +20,7 @@ from muranoapi.engine.system import agent
from muranoapi.engine.system import agent_listener
from muranoapi.engine.system import heat_stack
from muranoapi.engine.system import instance_reporter
from muranoapi.engine.system import net_explorer
from muranoapi.engine.system import resource_manager
from muranoapi.engine.system import status_reporter
@ -50,3 +51,4 @@ def register(class_loader, package_loader):
class_loader.import_class(ResourceManagerWrapper)
class_loader.import_class(instance_reporter.InstanceReportNotifier)
class_loader.import_class(status_reporter.StatusReporter)
class_loader.import_class(net_explorer.NetworkExplorer)

View File

@ -34,6 +34,7 @@ passlib
jsonschema>=2.0.0,<3.0.0
python-keystoneclient>=0.7.0
python-heatclient>=0.2.3
python-neutronclient>=2.3.4,<3
oslo.config>=1.2.0
oslo.messaging>=1.3.0a9