/*************************************************************************
* Copyright 2009-2014 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.imaging;
import java.net.InetAddress;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import com.eucalyptus.resources.PropertyChangeListeners;
import com.eucalyptus.resources.client.CloudFormationClient;
import com.eucalyptus.resources.client.Ec2Client;
import com.eucalyptus.util.EucalyptusCloudException;
import org.apache.log4j.Logger;
import com.eucalyptus.auth.Accounts;
import com.eucalyptus.auth.principal.AccountIdentifiers;
import com.eucalyptus.auth.principal.Policy;
import com.eucalyptus.bootstrap.Bootstrap;
import com.eucalyptus.bootstrap.Bootstrapper;
import com.eucalyptus.bootstrap.DependsLocal;
import com.eucalyptus.bootstrap.Provides;
import com.eucalyptus.bootstrap.RunDuring;
import com.eucalyptus.cloudformation.CloudFormation;
import com.eucalyptus.cloudformation.Stack;
import com.eucalyptus.component.Components;
import com.eucalyptus.component.Faults;
import com.eucalyptus.component.Faults.CheckException;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.Topology;
import com.eucalyptus.component.id.Eucalyptus;
import com.eucalyptus.compute.common.CloudMetadatas;
import com.eucalyptus.compute.common.ClusterInfoType;
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.crypto.util.B64;
import com.eucalyptus.imaging.common.ImagingBackend;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@ConfigurableClass(root = "services.imaging.worker", description = "Parameters controlling image conversion service")
public class ImagingServiceProperties {
private static Logger LOG = Logger.getLogger(ImagingServiceProperties.class);
// CONFIGURED is in the ImagingServiceLaunchers
@ConfigurableField(displayName = "image",
description = "EMI containing imaging worker",
initial = "NULL", readonly = false, type = ConfigurableFieldType.KEYVALUE,
changeListener = PropertyChangeListeners.EmiChangeListener.class)
public static String IMAGE = "NULL";
@ConfigurableField(displayName = "instance_type",
description = "instance type for imaging worker",
initial = "m1.small", readonly = false, type = ConfigurableFieldType.KEYVALUE,
changeListener = PropertyChangeListeners.InstanceTypeChangeListener.class)
public static String INSTANCE_TYPE = "m1.small";
@ConfigurableField(displayName = "availability_zones",
description = "availability zones for imaging worker",
initial = "", readonly = false, type = ConfigurableFieldType.KEYVALUE,
changeListener = PropertyChangeListeners.AvailabilityZonesChangeListener.class)
public static String AVAILABILITY_ZONES = "";
@ConfigurableField(displayName = "keyname",
description = "keyname to use when debugging imaging worker",
readonly = false, type = ConfigurableFieldType.KEYVALUE,
changeListener = KeyNameChangeListener.class)
public static String KEYNAME = null;
@ConfigurableField(displayName = "ntp_server",
description = "address of the NTP server used by imaging worker",
readonly = false, type = ConfigurableFieldType.KEYVALUE,
changeListener = PropertyChangeListeners.NTPServerChangeListener.class)
public static String NTP_SERVER = null;
@ConfigurableField(displayName = "log_server",
description = "address/ip of the server that collects logs from imaging wokrers",
readonly = false, type = ConfigurableFieldType.KEYVALUE,
changeListener = LogServerAddressChangeListener.class)
public static String LOG_SERVER = null;
@ConfigurableField(displayName = "log_server_port",
description = "UDP port that log server is listerning to",
readonly = false, initial = "514", type = ConfigurableFieldType.KEYVALUE,
changeListener = LogServerPortChangeListener.class)
public static String LOG_SERVER_PORT = "514";
@ConfigurableField(displayName = "enabled",
description = "enabling imaging worker healthcheck", initial = "true",
readonly = false, type = ConfigurableFieldType.BOOLEAN)
public static Boolean HEALTHCHECK = true;
@ConfigurableField(displayName = "expiration_days",
description = "the days after which imaging work VMs expire",
initial = "180", readonly = false, type = ConfigurableFieldType.KEYVALUE,
changeListener = PropertyChangeListeners.PositiveNumberChangeListener.class)
public static String EXPIRATION_DAYS = "180";
@ConfigurableField(displayName = "init_script",
description = "bash script that will be executed before service configuration and start up",
readonly = false,
type = ConfigurableFieldType.KEYVALUE)
public static String INIT_SCRIPT = null;
public static String getCredentialsString() {
final String credStr = String.format("euca-%s:%s",
B64.standard.encString("setup-credential"),
EXPIRATION_DAYS);
return credStr;
}
public static final String IMAGING_WORKER_STACK_NAME = "euca-internal-imaging-service";
@Provides(ImagingBackend.class)
@RunDuring(Bootstrap.Stage.Final)
@DependsLocal(ImagingBackend.class)
public static class ImagingBackendServicePropertyBootstrapper extends
Bootstrapper.Simple {
private static ImagingBackendServicePropertyBootstrapper singleton;
private static final Callable<String> imageNotConfiguredFaultRunnable = Faults
.forComponent(ImagingBackend.class).havingId(1015).logOnFirstRun();
public static Bootstrapper getInstance() {
synchronized (ImagingBackendServicePropertyBootstrapper.class) {
if (singleton == null) {
singleton = new ImagingBackendServicePropertyBootstrapper();
LOG.info("Creating Imaging Bootstrapper instance.");
} else {
LOG.debug("Returning Imaging Balancing Bootstrapper instance.");
}
}
return singleton;
}
private static int CheckCounter = 0;
private static boolean StackCheckResult = true;
@Override
public boolean check() throws Exception {
if (CloudMetadatas.isMachineImageIdentifier(IMAGE)) {
if ( CheckCounter >= 3 && Topology.isEnabled( Eucalyptus.class )
&& Topology.isEnabled( CloudFormation.class )) {
try {
final Stack stack = CloudFormationClient
.getInstance().describeStack(
Accounts.lookupSystemAccountByAlias( AccountIdentifiers.IMAGING_SYSTEM_ACCOUNT ).getUserId( ),
IMAGING_WORKER_STACK_NAME);
if ("CREATE_COMPLETE".equals(stack.getStackStatus()))
StackCheckResult = true;
else
StackCheckResult = false;
} catch (final Exception ex) {
StackCheckResult = false;
}
CheckCounter = 0;
} else
CheckCounter++;
return StackCheckResult;
} else {
try {
// GRZE: do this bit in the way that it allows getting the
// information with out needing to spelunk log files.
final ServiceConfiguration localService = Components.lookup(
ImagingBackend.class).getLocalServiceConfiguration();
final CheckException ex = Faults.failure(localService,
imageNotConfiguredFaultRunnable.call().split("\n")[1]);
Faults.submit(localService, localService.lookupStateMachine()
.getTransitionRecord(), ex);
} catch (Exception e) {
LOG.debug(e);
}
return false;
}
}
}
public static class KeyNameChangeListener implements PropertyChangeListener<String> {
@Override
public void fireChange(ConfigurableProperty t, String keyname)
throws ConfigurablePropertyException {
if(t.getValue()!=null && t.getValue().equals(keyname))
return;
if (keyname == null || keyname.isEmpty())
return;
try {
Ec2Client.getInstance().describeKeyPairs(Accounts.lookupSystemAccountByAlias(
AccountIdentifiers.IMAGING_SYSTEM_ACCOUNT ).getUserId( ),
Lists.newArrayList(keyname));
} catch (final Exception e) {
throw new ConfigurablePropertyException("Could not change key name due to: "
+ e.getMessage() + ". Are you using keypair that belongs to "
+ AccountIdentifiers.IMAGING_SYSTEM_ACCOUNT + " account?");
}
}
}
public static class LogServerAddressChangeListener implements PropertyChangeListener<String> {
@Override
public void fireChange(ConfigurableProperty t, String newValue)
throws ConfigurablePropertyException {
if(t.getValue()!=null && t.getValue().equals(newValue))
return;
try {
// check address
InetAddress.getByName(newValue);
} catch (final Exception e) {
throw new ConfigurablePropertyException(
"Could not change log server to " + newValue + " due to: " + e.getMessage());
}
}
}
public static class LogServerPortChangeListener implements
PropertyChangeListener<String> {
@Override
public void fireChange(ConfigurableProperty t, String newValue)
throws ConfigurablePropertyException {
if(t.getValue()!=null && t.getValue().equals(newValue))
return;
try {
int i = Integer.parseInt(newValue);
if (i<=0 || i>65535)
throw new ConfigurablePropertyException("Invalid port number");
} catch (final NumberFormatException ex) {
throw new ConfigurablePropertyException("Invalid number");
} catch (final Exception e) {
throw new ConfigurablePropertyException(
"Could not change log server port to " + newValue + " due to: " + e.getMessage());
}
}
}
public static boolean stackExists(boolean checkStackStatus) {
try {
Stack stack = CloudFormationClient.getInstance().describeStack(
Accounts.lookupSystemAccountByAlias( AccountIdentifiers.IMAGING_SYSTEM_ACCOUNT ).getUserId( ),
ImagingServiceProperties.IMAGING_WORKER_STACK_NAME);
if (stack != null) {
LOG.debug("Found stack " + ImagingServiceProperties.IMAGING_WORKER_STACK_NAME);
if ( checkStackStatus )
return "CREATE_COMPLETE".equalsIgnoreCase( stack.getStackStatus() );
else
return true;
} else {
LOG.debug("Imaging worker stack does not exist");
return false;
}
} catch (Exception ex) {
LOG.warn("Could not describe stack " + ImagingServiceProperties.IMAGING_WORKER_STACK_NAME);
return false;
}
}
static final Set<String> configuredZones = Sets.newHashSet();
public static List<String> listConfiguredZones() throws Exception {
if (configuredZones.size() <= 0) {
List<String> allZones = Lists.newArrayList();
try {
final List<ClusterInfoType> clusters = Ec2Client
.getInstance().describeAvailabilityZones(null, false);
for (final ClusterInfoType c : clusters)
allZones.add(c.getZoneName());
} catch (final Exception ex) {
throw new Exception("failed to lookup availability zones", ex);
}
if (ImagingServiceProperties.AVAILABILITY_ZONES != null
&& ImagingServiceProperties.AVAILABILITY_ZONES
.length() > 0) {
if (ImagingServiceProperties.AVAILABILITY_ZONES
.contains(",")) {
final String[] tokens = ImagingServiceProperties.AVAILABILITY_ZONES
.split(",");
for (final String zone : tokens) {
if (allZones.contains(zone))
configuredZones.add(zone);
}
} else {
if (allZones
.contains(ImagingServiceProperties.AVAILABILITY_ZONES))
configuredZones
.add(ImagingServiceProperties.AVAILABILITY_ZONES);
}
} else {
configuredZones.addAll(allZones);
}
}
return Lists.newArrayList(configuredZones);
}
}