Use code from AdvNetworking for MS SQL Cluster IP validation.

Use code borrowed from murano-conductor for generation of subnets
against which both MS SQL Cluster IPs should be validated (they should
belong to that subnet). Old validation of static IPs is no longer
sufficient because CIDR's are generated by murano-conductor during
deployment.

Valid subnet, once generated in dashboard for validation purposes, is
stored in murano-api environment for later use by murano-conductor
(DRY). Subnet is generated for every new environment, but is used only
during MS SQL Cluster IP validation.

This commit comes in a team with 3 following commits:
https://review.openstack.org/#/c/59982/
https://review.openstack.org/#/c/59983/
https://review.openstack.org/#/c/61072/

Change-Id: I24b3c057fe43e9a35a96ddf2c4defd4f64e9b4ea
This commit is contained in:
Timur Sufiev 2013-12-04 18:12:02 +04:00
parent fe2fd76b03
commit 1edac5ada9
7 changed files with 205 additions and 17 deletions

View File

@ -21,6 +21,8 @@ from muranoclient.v1.client import Client
from muranodashboard.environments.services import get_service_name
from muranoclient.common.exceptions import HTTPForbidden, HTTPNotFound
from consts import STATUS_ID_READY, STATUS_ID_NEW
from .network import get_network_params
log = logging.getLogger(__name__)
@ -178,9 +180,10 @@ def environments_list(request):
def environment_create(request, parameters):
body = get_network_params(request)
#name is required param
name = parameters['name']
env = muranoclient(request).environments.create(name)
body['name'] = parameters['name']
env = muranoclient(request).environments.create(body)
log.debug('Environment::Create {0}'.format(env))
return env

View File

@ -0,0 +1,134 @@
# Copyright (c) 2013 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
from django.conf import settings
from keystoneclient.v2_0 import client as ksclient
import netaddr
from netaddr.strategy import ipv4
from neutronclient.v2_0 import client as neutronclient
import logging
log = logging.getLogger(__name__)
class NeutronSubnetGetter(object):
def __init__(self, tenant_id, token, router_id=None):
conf = getattr(settings, 'ADVANCED_NETWORKING_CONFIG', {})
self.env_count = conf.get('max_environments', 100)
self.host_count = conf.get('max_hosts', 250)
self.address = conf.get('env_ip_template', '10.0.0.0')
self.tenant_id = tenant_id
self.router_id = router_id
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
endpoint_type = getattr(
settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL')
keystone_client = ksclient.Client(
auth_url=settings.OPENSTACK_KEYSTONE_URL,
tenant_id=tenant_id,
token=token,
cacert=cacert,
insecure=insecure)
if not keystone_client.authenticate():
raise ksclient.exceptions.Unauthorized()
neutron_url = keystone_client.service_catalog.url_for(
service_type='network', endpoint_type=endpoint_type)
self.neutron = neutronclient.Client(endpoint_url=neutron_url,
token=token,
ca_cert=cacert,
insecure=insecure)
def _get_router_id(self):
routers = self.neutron.list_routers(tenant_id=self.tenant_id).\
get("routers")
if not len(routers):
router_id = None
else:
router_id = routers[0]["id"]
if len(routers) > 1:
for router in routers:
if "murano" in router["name"].lower():
router_id = router["id"]
break
return router_id
def _get_subnet(self, router_id=None, count=1):
if router_id:
taken_cidrs = self._get_taken_cidrs_by_router(router_id)
else:
taken_cidrs = self._get_all_taken_cidrs()
results = []
for i in range(0, count):
res = self._generate_cidr(taken_cidrs)
results.append(res)
taken_cidrs.append(res)
return results
def _get_taken_cidrs_by_router(self, router_id):
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 = [subnet["cidr"] for subnet in all_subnets if
subnet["id"] in subnet_ids]
return filtered_cidrs
def _get_all_taken_cidrs(self):
return [subnet["cidr"] for subnet in
self.neutron.list_subnets()["subnets"]]
def _generate_cidr(self, taken_cidrs):
bits_for_envs = int(math.ceil(math.log(self.env_count, 2)))
bits_for_hosts = int(math.ceil(math.log(self.host_count, 2)))
width = ipv4.width
mask_width = width - bits_for_hosts - bits_for_envs
net = netaddr.IPNetwork(self.address + "/" + str(mask_width))
for subnet in net.subnet(width - bits_for_hosts):
if str(subnet) in taken_cidrs:
continue
return str(subnet)
return None
def get_subnet(self, environment_id=None):
# TODO: should use environment_id for getting cidr in future
router_id = self.router_id or self._get_router_id()
return self._get_subnet(router_id)[0]
def get_network_params(request):
network_topology = getattr(settings, 'NETWORK_TOPOLOGY', 'routed')
if network_topology != 'nova':
getter = NeutronSubnetGetter(request.user.tenant_id,
request.user.token.id)
existing_subnet = getter.get_subnet()
if existing_subnet:
return {'networking': {'topology': network_topology,
'createNetwork': True,
'cidr': existing_subnet}}
else:
log.error('Cannot get subnet')
return {'networking': {'topology': network_topology}}

View File

@ -16,7 +16,7 @@ import re
import json
from django import forms
from django.core.validators import RegexValidator, validate_ipv4_address
from netaddr import all_matching_cidrs
from netaddr import all_matching_cidrs, IPNetwork, IPAddress
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_text
from muranodashboard.environments import api
@ -32,6 +32,7 @@ import horizon.tables as tables
import floppyforms
from django.template.loader import render_to_string
log = logging.getLogger(__name__)
@ -53,6 +54,10 @@ def with_request(func):
"""
def update(self, initial, request=None, **kwargs):
initial_request = initial.get('request')
for key, value in initial.iteritems():
if key != 'request' and key not in kwargs:
kwargs[key] = value
if initial_request:
log.debug("Using 'request' value from initial dictionary")
func(self, initial_request, **kwargs)
@ -553,8 +558,12 @@ class BooleanField(forms.BooleanField, CustomPropertiesField):
class ClusterIPField(CharField):
existing_subnet = None
network_topology = None
router_id = None
@staticmethod
def validate_cluster_ip(request, ip_ranges):
def make_nova_validator(request, ip_ranges):
def perform_checking(ip):
validate_ipv4_address(ip)
if not all_matching_cidrs(ip, ip_ranges) and ip_ranges:
@ -581,19 +590,45 @@ class ClusterIPField(CharField):
_('Specified Cluster Static IP is already in use'))
return perform_checking
def update_network_params(self, request, environment_id):
env = api.environment_get(request, environment_id)
self.existing_subnet = env.networking.get('cidr')
self.network_topology = env.networking.get('topology')
def make_neutron_validator(self):
def perform_checking(ip):
validate_ipv4_address(ip)
if not self.existing_subnet:
raise forms.ValidationError(
_('Cannot get allowed subnet for the environment, '
'consult your admin'))
elif not IPAddress(ip) in IPNetwork(self.existing_subnet):
raise forms.ValidationError(
_('Specified IP address should belong to {0} '
'subnet'.format(self.existing_subnet)))
return perform_checking
@with_request
def update(self, request, **kwargs):
try:
network_list = novaclient(request).networks.list()
ip_ranges = [network.cidr for network in network_list]
ranges = ', '.join(ip_ranges)
except StandardError:
ip_ranges, ranges = [], ''
if ip_ranges:
self.help_text = _('Select IP from available range: ' + ranges)
else:
self.help_text = _('Specify valid fixed IP')
self.validators = [self.validate_cluster_ip(request, ip_ranges)]
def update(self, request, environment_id, **kwargs):
self.update_network_params(request, environment_id)
if self.network_topology == 'nova':
try:
network_list = novaclient(request).networks.list()
ip_ranges = [network.cidr for network in network_list]
ranges = ', '.join(ip_ranges)
except StandardError:
ip_ranges, ranges = [], ''
if ip_ranges:
self.help_text = _('Select IP from available range: ' + ranges)
else:
self.help_text = _('Specify valid fixed IP')
self.validators = [self.make_nova_validator(request, ip_ranges)]
elif self.network_topology == 'routed':
self.validators = [self.make_neutron_validator()]
else: # 'flat' topology
raise NotImplementedError('Flat topology is not implemented yet')
self.error_messages['invalid'] = validate_ipv4_address.message

View File

@ -133,7 +133,10 @@ class Wizard(ModalFormMixin, LazyWizard):
def get_form_initial(self, step):
init_dict = {}
if step != 'service_choice':
init_dict['request'] = self.request
init_dict.update({
'request': self.request,
'environment_id': self.kwargs.get('environment_id')
})
return self.initial_dict.get(step, init_dict)
def get_context_data(self, form, **kwargs):

View File

@ -159,3 +159,12 @@ except ImportError:
if DEBUG:
logging.basicConfig(level=logging.DEBUG)
ADVANCED_NETWORKING_CONFIG = {
# Maximum number of environments that can be processed simultaneously
'max_environments': 100,
# 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

@ -121,6 +121,8 @@ LOGGING['loggers']['muranoclient'] = {'handlers': ['murano-file'], 'level': 'ERR
#MURANO_METADATA_URL = "http://localhost:8084/v1"
#if murano-api set up with ssl uncomment next strings
#MURANO_API_INSECURE = True
ADVANCED_NETWORKING_CONFIG = {'max_environments': 100, 'max_hosts': 250, 'env_ip_template': '10.0.0.0'}
NETWORK_TOPOLOGY = 'routed'
#END_MURANO_DASHBOARD
EOF
if [ $? -ne 0 ];then

View File

@ -101,6 +101,8 @@ LOGGING['loggers']['muranoclient'] = {'handlers': ['murano-file'], 'level': 'ERR
#MURANO_METADATA_URL = "http://localhost:8084/v1"
#if murano-api set up with ssl uncomment next strings
#MURANO_API_INSECURE = True
ADVANCED_NETWORKING_CONFIG = {'max_environments': 100, 'max_hosts': 250, 'env_ip_template': '10.0.0.0'}
NETWORK_TOPOLOGY = 'routed'
#END_MURANO_DASHBOARD
EOF
if [ $? -ne 0 ];then