/*************************************************************************
* Copyright 2009-2016 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.loadbalancing;
import com.eucalyptus.auth.Accounts;
import com.eucalyptus.auth.principal.AccountIdentifiers;
import com.eucalyptus.bootstrap.Bootstrap;
import com.eucalyptus.component.Topology;
import com.eucalyptus.compute.common.Compute;
import com.eucalyptus.configurable.ConfigurableClass;
import com.eucalyptus.configurable.ConfigurableField;
import com.eucalyptus.configurable.ConfigurableFieldType;
import com.eucalyptus.configurable.ConfigurableProperty;
import com.eucalyptus.configurable.ConfigurablePropertyException;
import com.eucalyptus.configurable.PropertyChangeListener;
import com.eucalyptus.loadbalancing.workflow.LoadBalancingWorkflows;
import com.eucalyptus.resources.client.Ec2Client;
import com.eucalyptus.util.EucalyptusCloudException;
import com.google.common.collect.Lists;
import com.google.common.net.HostSpecifier;
import org.apache.log4j.Logger;
import org.springframework.util.StringUtils;
/**
* @author Sang-Min Park (sangmin.park@hpe.com)
*
*/
@ConfigurableClass(root = "services.loadbalancing.worker", description = "Parameters controlling loadbalancing")
public class LoadBalancingWorkerProperties {
private static Logger LOG = Logger.getLogger( LoadBalancingWorkerProperties.class );
@ConfigurableField( displayName = "image",
description = "EMI containing haproxy and the controller",
initial = "NULL",
readonly = false,
type = ConfigurableFieldType.KEYVALUE,
changeListener = ElbEmiChangeListener.class)
public static String IMAGE = "NULL";
@ConfigurableField( displayName = "instance_type",
description = "instance type for loadbalancer instances",
initial = "m1.medium",
readonly = false,
type = ConfigurableFieldType.KEYVALUE,
changeListener = ElbInstanceTypeChangeListener.class)
public static String INSTANCE_TYPE = "m1.medium";
@ConfigurableField( displayName = "keyname",
description = "keyname to use when debugging loadbalancer VMs",
readonly = false,
type = ConfigurableFieldType.KEYVALUE,
changeListener = ElbKeyNameChangeListener.class)
public static String KEYNAME = null;
@ConfigurableField( displayName = "ntp_server",
description = "the address of the NTP server used by loadbalancer VMs",
readonly = false,
type = ConfigurableFieldType.KEYVALUE,
changeListener = ElbNTPServerChangeListener.class
)
public static String NTP_SERVER = null;
@ConfigurableField( displayName = "app_cookie_duration",
description = "duration of app-controlled cookie to be kept in-memory (hours)",
initial = "24", // 24 hours by default
readonly = false,
type = ConfigurableFieldType.KEYVALUE,
changeListener = ElbAppCookieDurationChangeListener.class)
public static String APP_COOKIE_DURATION = "24";
@ConfigurableField( displayName = "expiration_days",
description = "the days after which the loadbalancer Vms expire",
initial = "365", // 1 year by default
readonly = false,
type = ConfigurableFieldType.KEYVALUE,
changeListener = ElbVmExpirationDaysChangeListener.class)
public static String EXPIRATION_DAYS = "365";
@ConfigurableField(displayName = "init_script",
description = "bash script that will be executed before service configuration and start up",
readonly = false,
type = ConfigurableFieldType.KEYVALUE,
changeListener = InitScriptChangeListener.class)
public static String INIT_SCRIPT = null;
@ConfigurableField( displayName = "failure_threshold_for_recycle",
description = "number of activity failure that will trigger recycling workers",
initial = "24", // 24 hours by default
readonly = false,
type = ConfigurableFieldType.KEYVALUE,
changeListener = FailureThresholdChangeListener.class)
public static String FAILURE_THRESHOLD_FOR_RECYCLE = "24";
@ConfigurableField( displayName = "use_elastic_ip",
description = "flag to indicate the workers use elastic IP",
initial = "false", // 24 hours by default
readonly = false,
type = ConfigurableFieldType.KEYVALUE,
changeListener = UseElasticIpChangeListener.class)
public static String USE_ELASTIC_IP = "false";
public static boolean useElasticIp() {
return Boolean.parseBoolean(USE_ELASTIC_IP);
}
public static class UseElasticIpChangeListener implements PropertyChangeListener<String> {
@Override
public void fireChange(ConfigurableProperty t, String newValue)
throws ConfigurablePropertyException {
if (!("true".equals(newValue) || "false".equals(newValue))) {
throw new ConfigurablePropertyException("The value must be true or false");
}
}
}
public static class FailureThresholdChangeListener implements PropertyChangeListener<String> {
@Override
public void fireChange(ConfigurableProperty t, String newValue)
throws ConfigurablePropertyException {
try{
final int threshold = Integer.parseInt(newValue);
}catch(final Exception ex) {
throw new ConfigurablePropertyException("The value must be number type");
}
}
}
public static class InitScriptChangeListener implements PropertyChangeListener<String> {
@Override
public void fireChange(ConfigurableProperty t, String newValue)
throws ConfigurablePropertyException {
try {
// init script can be empty
if (t.getValue() != null && !t.getValue().equals(newValue))
onPropertyChange(null, null, null, (String) newValue);
} catch (final Exception e) {
throw new ConfigurablePropertyException("Could not change init script", e);
}
}
}
public static class ElbEmiChangeListener implements PropertyChangeListener {
@Override
public void fireChange( ConfigurableProperty t, Object newValue ) throws ConfigurablePropertyException {
try {
if ( newValue instanceof String ) {
if(t.getValue()!=null && ! t.getValue().equals(newValue))
onPropertyChange((String)newValue, null, null, null);
}
} catch ( final Exception e ) {
throw new ConfigurablePropertyException("Could not change EMI ID due to: " + e.getMessage());
}
}
}
public static class ElbInstanceTypeChangeListener implements PropertyChangeListener {
@Override
public void fireChange( ConfigurableProperty t, Object newValue ) throws ConfigurablePropertyException {
try {
if ( newValue instanceof String ) {
if( newValue.equals( "" ) )
throw new EucalyptusCloudException("Instance type cannot be unset");
if(t.getValue()!=null && ! t.getValue().equals(newValue))
onPropertyChange(null, (String)newValue, null, null);
}
} catch ( final Exception e ) {
throw new ConfigurablePropertyException("Could not change instance type due to: " + e.getMessage());
}
}
}
public static class ElbKeyNameChangeListener implements PropertyChangeListener<String> {
@Override
public void fireChange( ConfigurableProperty t, String keyname ) throws ConfigurablePropertyException {
try {
if(t.getValue()!=null && !t.getValue().equals(keyname)) {
if ( keyname != null && !keyname.isEmpty() ) {
// find out if there are any old elbs are deployed
boolean oldElbExist = false;
for (LoadBalancer lb:LoadBalancers.listLoadbalancers()){
if (!lb.useSystemAccount()) {
oldElbExist = true;
break;
}
}
try {
Ec2Client.getInstance().describeKeyPairs(Accounts.lookupSystemAccountByAlias(
AccountIdentifiers.ELB_SYSTEM_ACCOUNT ).getUserId( ), Lists.newArrayList(keyname));
} catch(Exception ex) {
throw new ConfigurablePropertyException("Could not change key name due to: " + ex.getMessage()
+ ". Do you have keypair " + keyname + " that belongs to "
+ AccountIdentifiers.ELB_SYSTEM_ACCOUNT + " account?");
}
if (oldElbExist) {
try {
Ec2Client.getInstance().describeKeyPairs(null,
Lists.newArrayList(keyname));
} catch(Exception ex) {
throw new ConfigurablePropertyException("Could not change key name due to: " + ex.getMessage()
+ ". Do you have keypair " + keyname + " that belongs to system account?");
}
}
}
onPropertyChange(null, null, keyname, null);
}
} catch ( final ConfigurablePropertyException e ) {
throw e;
} catch ( final Exception e ) {
throw new ConfigurablePropertyException("Could not change key name due to: " + e.getMessage());
}
}
}
public static class ElbVmExpirationDaysChangeListener implements PropertyChangeListener<String> {
@Override
public void fireChange(ConfigurableProperty t, String newValue)
throws ConfigurablePropertyException {
try{
final int newExp = Integer.parseInt(newValue);
if(newExp <= 0 )
throw new Exception();
}catch(final Exception ex) {
throw new ConfigurablePropertyException("The value must be number type and bigger than 0");
}
}
}
private static void onPropertyChange(final String emi, final String instanceType,
final String keyname, String initScript) throws EucalyptusCloudException{
if (!( Bootstrap.isOperational() && Topology.isEnabled( Compute.class ) ) )
return;
if ((emi!=null && emi.length()>0) ||
(instanceType!=null && instanceType.length()>0) ||
(keyname!=null) || (initScript != null) ){
if(!LoadBalancingWorkflows.modifyServicePropertiesSync(emi, instanceType,
keyname, initScript)) {
throw new EucalyptusCloudException("Failed to modify properties. Check log files for error details");
}
}
}
public static class ElbNTPServerChangeListener implements PropertyChangeListener {
@Override
public void fireChange( ConfigurableProperty t, Object newValue ) throws ConfigurablePropertyException {
try {
if ( newValue instanceof String ) {
if(((String) newValue).contains(",")){
final String[] addresses = ((String)newValue).split(",");
if((addresses.length-1) != StringUtils.countOccurrencesOf((String) newValue, ","))
throw new EucalyptusCloudException("Invalid address");
for(final String address : addresses){
if(!HostSpecifier.isValid(String.format("%s.com",address)))
throw new EucalyptusCloudException("Invalid address");
}
}else{
final String address = (String) newValue;
if( !address.equals("") ){
if(!HostSpecifier.isValid(String.format("%s.com", address)))
throw new EucalyptusCloudException("Invalid address");
}
}
}else
throw new EucalyptusCloudException("Address is not string type");
} catch ( final Exception e ) {
throw new ConfigurablePropertyException("Could not change ntp server address", e);
}
}
}
public static class ElbAppCookieDurationChangeListener implements PropertyChangeListener {
@Override
public void fireChange( ConfigurableProperty t, Object newValue ) throws ConfigurablePropertyException {
try {
if ( newValue instanceof String ) {
try{
final int appCookieDuration = Integer.parseInt((String)newValue);
if(appCookieDuration <= 0)
throw new Exception();
}catch(final NumberFormatException ex){
throw new ConfigurablePropertyException("Duration must be in number type and bigger than 0 (in hours)");
}
}
}catch (final ConfigurablePropertyException ex){
throw ex;
}catch (final Exception ex) {
throw new ConfigurablePropertyException("Could not change ELB app cookie duration", ex);
}
}
}
}