# Copyright 2015, eBay 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. from django.utils.translation import ugettext_lazy as _ from horizon import exceptions from horizon import forms from horizon import workflows from openstack_dashboard.api import nova from octavia_dashboard import api __create_new__ = "Create New" class SetLBDetailsAction(workflows.Action): address = forms.ChoiceField(label=_("IP"), help_text=_("Select from existing VIP IPs")) name = forms.CharField(max_length=80, label=_("Name"), required=True) description = forms.CharField(widget=forms.Textarea(attrs={'rows': 2}), label=_( "Load Balancer Description"), required=False, help_text=_("Provide Load Balancer " "Description.")) all_vips = None is_update = False LOAD_BALANCING_CHOICES = ( ("RoundRobin", _("Round Robin")), ("LeastConnection", _("Least Connection")), ("LeastSessions", _("Least Sessions")) ) lb_method = forms.ChoiceField(label=_("Load Balancing Method"), choices=LOAD_BALANCING_CHOICES) PROTOCOL_CHOICES = ( ("HTTP", _("HTTP")), ("HTTPS", _("HTTPS")), ("TCP", _("TCP")), ("SSL", _("SSL")), ) protocol_type = forms.ChoiceField( label=_("LB Protocol"), choices=PROTOCOL_CHOICES) port = forms.IntegerField(label=_("LB Port"), required=False, min_value=1, max_value=65535, help_text=_("LB Port on which " "LB is listening.")) instance_port = forms.IntegerField(label=_("Instance Port"), required=False, min_value=1, max_value=65535, help_text=_("Instance Port on which " "service is running.")) def __init__(self, request, *args, **kwargs): super(SetLBDetailsAction, self).__init__(request, *args, **kwargs) self.all_vips = [] try: # todo - this should be obtained in view via an initial method self.all_vips = api.lbaasv2.list_loadbalancers(request) except Exception: pass if len(self.fields['address'].choices) == 0: del self.fields['address'] class Meta(object): name = _("LB Details") help_text_template = ("project/loadbalancersv2/_launch_lb_help.html") def clean(self): cleaned_data = super(SetLBDetailsAction, self).clean() lb_method = cleaned_data['lb_method'] if not (lb_method == 'RoundRobin' or lb_method == 'LeastConnection' or lb_method == 'LeastSessions'): raise forms.ValidationError(_("Please select an option for " "the load balancing method.")) if not self.is_update: all_vips = self.all_vips ipPortCombo = [] for vip in all_vips: vip = vip.readable() ipPortCombo.append('%s:%s' % (vip.address, vip.port)) data = self.data if 'address' in data \ and data['address'] != 'new' \ and data['address'] != '': address = data['address'] selected_lb_port = data['port'] selected_ip_port_combo = '%s:%s' % (address.split(':')[0], selected_lb_port) if selected_ip_port_combo in ipPortCombo: raise forms.ValidationError(_('Requested IP and port ' 'combination already ' 'exists %s ') % selected_ip_port_combo) instance_port = cleaned_data.get('instance_port', None) if not instance_port: raise forms.ValidationError( _('Please provide instance port')) return cleaned_data def populate_address_choices(self, request, context): if self.is_update: return [] try: vips = api.lbaasv2.list_loadbalancers(request) if len(vips) == 0: return [] distict_ips = set() for vip in vips: vip = vip.readable() distict_ips.add(vip.address) existing = [] for vip in vips: vip = vip.readable() if vip.address in distict_ips: item = ("%s:%s:%s" % (vip.address, vip.name, 443), "%s" % vip.address) existing.append(item) distict_ips.remove(vip.address) vip_list = [] if len(existing) > 0: vip_list.append(('new', __create_new__)) vip_list.append(('Select Existing', existing)) return vip_list except Exception: exceptions.handle(request, _('Unable to retrieve vips.')) return [] def get_help_text(self): extra = {} return super(SetLBDetailsAction, self).get_help_text(extra) class SetLBDetails(workflows.Step): action_class = SetLBDetailsAction contributes = ("name", "description", "lb_method", "protocol_type", "port", "source_id", "instance_port", "address", "monitor") def contribute(self, data, context): context = super(SetLBDetails, self).contribute(data, context) return context template_name = "project/loadbalancersv2/launch_lb.html" class UploadSSLAction(workflows.Action): update_cert = forms.BooleanField(label='Update SSL Certificate', required=False, widget=forms.HiddenInput()) cert_name = forms.CharField(max_length=80, label=_("Certificate Name"), required=False) cert = forms.CharField(widget=forms.Textarea(attrs={'rows': 3}), label=_("Certificate"), required=False, help_text=_("Certificate")) private_key = forms.CharField(widget=forms.Textarea(attrs={'rows': 3}), label=_("Private Key"), required=False, help_text=_("Private Key")) chain_cert = forms.CharField(widget=forms.Textarea(attrs={'rows': 3}), label=_("Certificate Chain (Optional)"), required=False, help_text=_("Intermediate Chain" " Certificates")) def clean(self): cleaned_data = super(UploadSSLAction, self).clean() data = self.data protocol = data.get('source_type') if protocol == 'HTTPS': use_common_cert = data.get('use_common_cert') if not use_common_cert: # check to see if ssl cert is provided cert_name = data.get('cert_name') cert = data.get('cert') private_key = data.get('private_key') if (not cert_name) \ or (not cert) \ or (not private_key): raise forms.ValidationError( _('Please provide all certificate parameters.')) return cleaned_data class Meta(object): name = _("SSL Certificate") help_text_template = ("project/loadbalancersv2/_ssl_cert_help.html") class UploadSSLStep(workflows.Step): action_class = UploadSSLAction contributes = ("cert_name", "cert", "private_key", "chain_cert", 'use_common_cert') template_name = "project/loadbalancersv2/ssl_cert.html" def contribute(self, data, context): post = self.workflow.request.POST context['cert_name'] = post['cert_name'] if 'cert_name' in post else '' context['cert'] = post['cert'] if 'cert' in post else '' context['private_key'] = post[ 'private_key'] if 'private_key' in post else '' context['chain_cert'] = post[ 'chain_cert'] if 'chain_cert' in post else '' context['use_common_cert'] = post[ 'use_common_cert'] if 'use_common_cert' in post else '' return context class SelectInstancesAction(workflows.MembershipAction): instance_details = {} def __init__(self, request, *args, **kwargs): super(SelectInstancesAction, self).__init__(request, *args, **kwargs) err_msg = _('Unable to retrieve members list. ' 'Please try again later.') default_role_field_name = self.get_default_role_field_name() self.fields[default_role_field_name] = forms.CharField(required=False, label='') self.fields[default_role_field_name].initial = 'member' role_member_field_name = self.get_member_field_name('member') self.fields[role_member_field_name] = forms.MultipleChoiceField( required=False, label='') # Get list of available instances all_instances = [] try: all_instances, has_more_data = nova.server_list(request) except Exception: exceptions.handle(request, err_msg) available_instances = [] for instance in all_instances: # skip shutoff instances # if instance.status == 'SHUTOFF': # continue instance_ip = self.get_ip(instance) # skip instances which has no network if not instance_ip: continue key = instance_ip value = instance.name + ' (' + self.get_ip(instance) + ')' available_instances.append((key, value)) self.instance_details[instance_ip] = (instance.name, instance.id) self.fields[self.get_member_field_name('member')].\ choices = available_instances def get_ip(self, instance): ipaddress = None for networks in instance.addresses.itervalues(): for ip in networks: # only one IP present ipaddress = ip break if ipaddress is not None: addr = ipaddress["addr"] else: addr = None # '10.10.10.10' return addr def clean(self): cleaned_data = super(SelectInstancesAction, self).clean() members = cleaned_data.get(self.get_member_field_name('member'), None) if not members: raise forms.ValidationError( _('Please select at least one member')) return cleaned_data class Meta(object): name = _("Instances") slug = "select_instances" class SelectInstancesStep(workflows.UpdateMembersStep): action_class = SelectInstancesAction help_text = _("Please select a list of instances that should handle" " traffic for this target load balancer. All instances " "must reside in the same Project as the target load " "balancer.") available_list_title = _("All Instances") members_list_title = _("Selected Instances") no_available_text = _("No instances found.") no_members_text = _("No members enabled.") show_roles = False contributes = ( "wanted_members", "instances_details", "monitor", "instance_port") template_name = "horizon/common/_workflow_step_update_members.html" def contribute(self, data, context): request = self.workflow.request if data: context["wanted_members"] = request.POST.getlist( self.get_member_field_name('member')) context["instances_details"] = self.action.instance_details context["monitor"] = request.POST.get("monitor") context["instance_port"] = request.POST.get("instance_port") return context class SelectMonitorAction(workflows.Action): MONITOR_CHOICES = ( ("tcp", _("TCP")), ("ping", _("PING")), ("http", _("HTTP")), ) monitor = forms.ChoiceField(label=_("Monitor"), choices=MONITOR_CHOICES) interval = forms.IntegerField(label=_("Health Check Interval" " (in seconds)"), required=False, min_value=1, max_value=600, help_text=_("Health Check Interval" " (in seconds)")) timeout = forms.IntegerField(label=_("Retry count before markdown"), required=False, min_value=1, max_value=100, help_text=_("Number of times health check " "should be attempted before " "marking down a member")) send = forms.CharField(widget=forms.Textarea(attrs={'rows': 1}), label=_("Send String"), required=False, help_text=_("Send String")) receive = forms.CharField(widget=forms.Textarea(attrs={'rows': 1}), label=_("Receive String"), required=False, help_text=_("Receive String")) class Meta(object): name = _("Monitor") help_text_template = ("project/loadbalancersv2/_monitor_help.html") class SelectMonitorStep(workflows.Step): action_class = SelectMonitorAction contributes = ("monitor", "interval", "timeout", "send", "receive") template_name = "project/loadbalancersv2/_monitor_create.html" def contribute(self, data, context): post = self.workflow.request.POST context['interval'] = post['interval'] if 'interval' in post else '' context['timeout'] = post['timeout'] if 'timeout' in post else '' context['send'] = post['send'] if 'send' in post else '' context['receive'] = post['receive'] if 'receive' in post else '' return context class LaunchLoadBalancer(workflows.Workflow): slug = "launch_loadbalancer" name = _("Launch Load Balancer") finalize_button_name = _("Launch") success_message = _('Launched %(count)s named "%(name)s".') failure_message = _('Unable to launch %(count)s named "%(name)s".') success_url = "horizon:project:loadbalancersv2:index" default_steps = (SetLBDetails, UploadSSLStep, SelectMonitorStep, SelectInstancesStep, ) attrs = {'data-help-text': 'LB creation may take a few minutes'} def format_status_message(self, message): name = self.context.get('name', 'unknown loadbalancer') count = self.context.get('count', 1) if int(count) > 1: return message % {"count": _("%s loadbalancers") % count, "name": name} else: return message % {"count": _("loadbalancer"), "name": name} def handle(self, request, context): try: protocol = context['source_type'] address = context['address'] if not address\ or address == "new": address = '' else: tokens = address.split(':') address = tokens[0] api.lbaasv2.\ create_loadbalancer_full(request, address=address, name=context['name'], description=context['description'], lb_method=context['lb_method'], monitor=context['monitor'], protocol=protocol, port=context[protocol], instance_port=context['instance_port'], # noqa wanted_members=context['wanted_members'], # noqa instances_details=context['instances_details'], # noqa cert_name=context['cert_name'], cert=context['cert'], private_key=context['private_key'], chain_cert=context['chain_cert'], use_common_cert=True if context['use_common_cert'] == 'on' else False, interval=context['interval'], timeout=context['timeout'], send=context['send'], receive=context['receive'], ) return True except Exception as e: exceptions.handle(request, e.message, ignore=False) return False