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). So this commit comes in a team with 2 following commits:
https://review.openstack.org/#/c/59982/ and
https://review.openstack.org/#/c/59983/

Change-Id: I02c1b1905dd15535f807bcbc49b4f8fc2ad435d7
This commit is contained in:
Timur Sufiev 2013-12-04 18:12:02 +04:00
parent 75b8d9a5f0
commit 7492333859
7 changed files with 206 additions and 17 deletions

View File

@ -207,8 +207,9 @@ def environment_deploy(request, environment_id):
return env
def environment_update(request, environment_id, name):
return muranoclient(request).environments.update(environment_id, name)
def environment_update(request, environment_id, name, **kwargs):
return muranoclient(request).environments.update(
environment_id, name, **kwargs)
def get_environment_name(request, environment_id):

View File

@ -16,9 +16,10 @@ 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 django.conf import settings
from muranodashboard.environments import api
from horizon import exceptions, messages
from openstack_dashboard.api import glance
@ -32,6 +33,8 @@ import horizon.tables as tables
import floppyforms
from django.template.loader import render_to_string
from .network import NeutronSubnetGetter
log = logging.getLogger(__name__)
@ -53,6 +56,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)
@ -548,8 +555,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:
@ -576,19 +587,64 @@ 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)
net_info = getattr(env, 'networking', {})
self.network_topology = getattr(settings, 'NETWORK_TOPOLOGY', 'routed')
if net_info:
self.existing_subnet = net_info.get('cidr')
topology = net_info.get('topology')
if topology:
self.network_topology = topology
self.router_id = net_info.get('routerId')
if self.network_topology != 'nova' and not self.existing_subnet:
getter = NeutronSubnetGetter(request.user.tenant_id,
self.router_id,
request.user.token.id)
self.existing_subnet = getter.get_subnet(environment_id)
if self.existing_subnet:
networking = {
'createNetwork': True,
'cidr': self.existing_subnet
}
name = api.get_environment_name(request, environment_id)
api.environment_update(request, environment_id, name,
networking=networking)
else:
raise RuntimeError('Cannot get subnet')
def make_neutron_validator(self):
def perform_checking(ip):
validate_ipv4_address(ip)
if 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

@ -0,0 +1,116 @@
# 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
class NeutronSubnetGetter(object):
def __init__(self, tenant_id, router_id, token):
conf = settings.ADVANCED_NETWORKING_CONFIG
self.env_count = conf.get('max_environments')
self.host_count = conf.get('max_hosts')
self.address = conf.get('env_ip_template')
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):
# TODO: should use it for getting cidr in future
assert environment_id
router_id = self.router_id or self._get_router_id()
return self._get_subnet(router_id)[0]

View File

@ -132,7 +132,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

@ -158,3 +158,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

@ -119,6 +119,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

@ -99,6 +99,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