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:
parent
fe2fd76b03
commit
1edac5ada9
@ -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
|
||||
|
||||
|
134
muranodashboard/environments/network.py
Normal file
134
muranodashboard/environments/network.py
Normal 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}}
|
@ -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
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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
|
||||
|
2
setup.sh
2
setup.sh
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user