/*************************************************************************
* 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.worker;
import java.io.IOException;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.eucalyptus.auth.Accounts;
import com.eucalyptus.auth.euare.ServerCertificateMetadataType;
import com.eucalyptus.auth.principal.AccountIdentifiers;
import com.eucalyptus.auth.principal.UserPrincipal;
import com.eucalyptus.cloudformation.CloudFormation;
import com.eucalyptus.cloudformation.Parameter;
import com.eucalyptus.component.Topology;
import com.eucalyptus.compute.common.network.Networking;
import com.eucalyptus.compute.common.network.NetworkingFeature;
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.Certs;
import com.eucalyptus.crypto.util.PEMFiles;
import com.eucalyptus.imaging.ImagingServiceProperties;
import com.eucalyptus.resources.client.CloudFormationClient;
import com.eucalyptus.resources.client.Ec2Client;
import com.eucalyptus.resources.client.EuareClient;
import com.eucalyptus.util.DNSProperties;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.Exceptions;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import com.google.common.io.Resources;
/*
* In case we want to create multiple instances(ASG) of the imaging service,
* we should implement the additional methods here.
*/
@ConfigurableClass(root = "services.imaging.worker", description = "Parameters controlling image conversion service")
public class ImagingServiceLaunchers {
private static Logger LOG = Logger.getLogger(ImagingServiceLaunchers.class);
private static ImagingServiceLaunchers instance = new ImagingServiceLaunchers();
private static Map<String, String> launchStateTable = Maps.newConcurrentMap();
public static final String launcherId = "worker-01"; // version 4.0.0, but it
// can be any string
public static final String SERVER_CERTIFICATE_NAME = "euca-internal-imaging-service";
private static final String DEFAULT_SERVER_CERT_PATH = "/euca-internal";
@ConfigurableField(displayName = "configured",
description = "Prepare imaging service so a worker can be launched."
+ "If something goes south with the service there is a big chance that setting "
+ "it to false and back to true would solve issues.",
initial = "false", readonly = false, type = ConfigurableFieldType.BOOLEAN,
changeListener = EnabledChangeListener.class)
public static Boolean CONFIGURED = false;
private ImagingServiceLaunchers() {
}
public static ImagingServiceLaunchers getInstance() {
return instance;
}
public boolean shouldDisable() {
if (isLauncherLocked(launcherId)) {
// start up in progress
LOG.warn("Previous stack start up is still in progress");
return false;
}
return ImagingServiceProperties.stackExists(false);
}
public boolean shouldEnable() {
if (isLauncherLocked(launcherId))
return false;
return !ImagingServiceProperties.stackExists(false);
}
public boolean isWorkedEnabled() {
try {
if (isLauncherLocked(launcherId))
return false;
return ImagingServiceProperties.stackExists(false);
} catch (final Exception ex) {
return false;
}
}
public void enable() throws EucalyptusCloudException {
if (!this.shouldEnable())
throw new EucalyptusCloudException(
"Imaging service is active or still shutting down");
this.lockLauncher(launcherId);
try {
// check that CF is ENABLED
if (!Topology.isEnabled(CloudFormation.class))
throw new EucalyptusCloudException("CloudFormation is not enabled");
//check properties
if ("NULL".equals(ImagingServiceProperties.IMAGE))
throw new EucalyptusCloudException("You need to set 'services.imaging.worker.image'"
+ "before enabling the service");
UserPrincipal imagingUser = Accounts.lookupSystemAccountByAlias( AccountIdentifiers.IMAGING_SYSTEM_ACCOUNT );
// create default VPC if needed
if (Networking.getInstance().supports( NetworkingFeature.Vpc )) {
if (!Ec2Client.getInstance().hasDefaultVPC(imagingUser.getUserId())) {
Ec2Client.getInstance().createDefaultVPC(imagingUser.getAccountNumber());
}
}
// generate certificate
ServerCertificateMetadataType metadata = createAndUploadCertificate();
// use CF for stack creation
String template = getInstance().loadTemplate("worker-cf-template.json");
ArrayList<Parameter> params = new ArrayList<Parameter>();
params.add(new Parameter("KeyName", ImagingServiceProperties.KEYNAME));
params.add(new Parameter("CERTARN", metadata.getArn()));
params.add(new Parameter("ImageId", ImagingServiceProperties.IMAGE));
if (ImagingServiceProperties.INIT_SCRIPT != null)
params.add(new Parameter("InitScript", ImagingServiceProperties.INIT_SCRIPT));
params.add(new Parameter("VmExpirationDays",
ImagingServiceProperties.EXPIRATION_DAYS));
params.add(new Parameter("InstanceType",
ImagingServiceProperties.INSTANCE_TYPE));
params
.add(new Parameter("NtpServer", ImagingServiceProperties.NTP_SERVER));
params
.add(new Parameter("LogServer", ImagingServiceProperties.LOG_SERVER));
params.add(new Parameter("LogServerPort",
ImagingServiceProperties.LOG_SERVER_PORT));
List<String> zones = ImagingServiceProperties.listConfiguredZones();
params.add(new Parameter("NumberOfWorkers", Integer.toString( zones.size() )));
params.add(new Parameter("AvailabilityZones",
Joiner.on(",").join( zones )));
params.add(new Parameter("ImagingServiceUrl", String.format("imaging.%s",
DNSProperties.getDomain())));
params.add(new Parameter("EuareServiceUrl", String.format("euare.%s",
DNSProperties.getDomain())));
params.add(new Parameter("ComputeServiceUrl", String.format("compute.%s",
DNSProperties.getDomain())));
LOG.debug("Creating CF stack for the imaging worker");
CloudFormationClient.getInstance().createStack(
imagingUser.getUserId( ),
ImagingServiceProperties.IMAGING_WORKER_STACK_NAME, template, params);
LOG.debug("Done creating CF stack for the imaging worker");
} catch (final Exception ex) {
LOG.error(ex);
throw new EucalyptusCloudException(ex);
} finally {
this.releaseLauncher(launcherId);
}
}
private ServerCertificateMetadataType findCertificate()
throws EucalyptusCloudException {
try {
return EuareClient.getInstance().describeServerCertificate(
Accounts.lookupSystemAccountByAlias( AccountIdentifiers.IMAGING_SYSTEM_ACCOUNT ).getUserId( ),
SERVER_CERTIFICATE_NAME, DEFAULT_SERVER_CERT_PATH);
} catch (Exception ex) {
throw new EucalyptusCloudException("failed to describe server cert", ex);
}
}
private ServerCertificateMetadataType createAndUploadCertificate()
throws EucalyptusCloudException {
ServerCertificateMetadataType existingCert = findCertificate();
if (existingCert != null) {
LOG.debug("Certificate already exists, there is no reason to create new one");
return existingCert;
}
String certPem = null;
String pkPem = null;
try {
final KeyPair kp = Certs.generateKeyPair();
final X509Certificate kpCert = Certs.generateCertificate(kp, String
.format("Certificate-for-imaging-workers-(%s)",
SERVER_CERTIFICATE_NAME));
certPem = new String(PEMFiles.getBytes(kpCert));
pkPem = new String(PEMFiles.getBytes(kp));
} catch (final Exception ex) {
throw new EucalyptusCloudException("failed generating server cert", ex);
}
ServerCertificateMetadataType res;
try {
res = EuareClient.getInstance().uploadServerCertificate(
Accounts.lookupSystemAccountByAlias( AccountIdentifiers.IMAGING_SYSTEM_ACCOUNT ).getUserId( ),
SERVER_CERTIFICATE_NAME, DEFAULT_SERVER_CERT_PATH, certPem, pkPem,
null);
LOG.debug("Created new certificate " + res.getServerCertificateName());
} catch (final Exception ex) {
throw new EucalyptusCloudException("failed to upload server cert", ex);
}
return res;
}
public void disable() throws Exception {
if (!this.shouldDisable()) {
LOG.warn("Imaging service instances are not found in the system");
return;
}
this.lockLauncher(launcherId);
try {
CloudFormationClient.getInstance().deleteStack(
Accounts.lookupSystemAccountByAlias( AccountIdentifiers.IMAGING_SYSTEM_ACCOUNT ).getUserId( ),
ImagingServiceProperties.IMAGING_WORKER_STACK_NAME);
EuareClient.getInstance().deleteServerCertificate(
Accounts.lookupSystemAccountByAlias( AccountIdentifiers.IMAGING_SYSTEM_ACCOUNT ).getUserId( ),
SERVER_CERTIFICATE_NAME);
} catch (final Exception ex) {
throw ex;
} finally {
this.releaseLauncher(launcherId);
}
}
public void lockLauncher(final String launcherId) {
synchronized (launchStateTable) {
launchStateTable.put(launcherId, "ENABLING");
}
}
public void releaseLauncher(final String launcherId) {
synchronized (launchStateTable) {
launchStateTable.remove(launcherId);
}
}
public boolean isLauncherLocked(final String launcherId) {
synchronized (launchStateTable) {
return launchStateTable.containsKey(launcherId);
}
}
public static class EnabledChangeListener implements
PropertyChangeListener<String> {
@Override
public void fireChange(ConfigurableProperty t, String newValue)
throws ConfigurablePropertyException {
try {
if ("false".equalsIgnoreCase(newValue)
&& "true".equalsIgnoreCase(t.getValue())) {
ImagingServiceLaunchers.getInstance().disable();
} else if ("true".equalsIgnoreCase(newValue)
&& ("false".equalsIgnoreCase(t.getValue())
|| t.getValue()==null
|| "".equals(t.getValue()) )) {
ImagingServiceLaunchers.getInstance().enable();
} else if (!("true".equalsIgnoreCase(newValue) || "false"
.equalsIgnoreCase(newValue)))
throw new ConfigurablePropertyException("The '" + newValue
+ "' is not a valid value.");
} catch (final ConfigurablePropertyException ex) {
throw ex;
} catch (final Exception e) {
throw new ConfigurablePropertyException(
"Could not disable/enable imaging service workers due to: "
+ e.getMessage(), e);
}
}
}
private String loadTemplate(final String resourceName) {
try {
return Resources.toString(
Resources.getResource(getClass(), resourceName), Charsets.UTF_8);
} catch (final IOException e) {
throw Exceptions.toUndeclared(e);
}
}
}