/************************************************************************* * 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.database.activities; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.List; import org.apache.log4j.Logger; import com.eucalyptus.auth.Accounts; import com.eucalyptus.auth.AuthException; import com.eucalyptus.auth.euare.ServerCertificateType; import com.eucalyptus.autoscaling.common.msgs.AutoScalingGroupType; import com.eucalyptus.autoscaling.common.msgs.DescribeAutoScalingGroupsResponseType; import com.eucalyptus.cloudformation.Stack; import com.eucalyptus.compute.common.ResourceTag; import com.eucalyptus.compute.common.RunningInstancesItemType; import com.eucalyptus.configurable.ConfigurableProperty; import com.eucalyptus.configurable.PropertyDirectory; import com.eucalyptus.resources.AbstractEventHandler; import com.eucalyptus.resources.EventHandlerChain; import com.eucalyptus.resources.EventHandlerException; import com.eucalyptus.resources.StoredResult; import com.eucalyptus.resources.client.AutoScalingClient; import com.eucalyptus.resources.client.CloudFormationClient; import com.eucalyptus.resources.client.Ec2Client; import com.eucalyptus.resources.client.EuareClient; import com.google.common.collect.Lists; /** * @author Sang-Min Park * */ public class EventHandlerChainEnableVmDatabase extends EventHandlerChain<EnableDBInstanceEvent> { private static Logger LOG = Logger.getLogger( EventHandlerChainEnableVmDatabase.class ); @Override public EventHandlerChain<EnableDBInstanceEvent> build() { this.append(new CheckDatabaseStack(this)); this.append(new WaitOnVm(this)); this.append(new WaitOnDb(this)); this.append(new UpdateProperties(this)); return this; } private static String getSystemUserId(){ try{ return Accounts.lookupSystemAdmin().getUserId(); }catch(final Exception ex){ return null; } } public static class CheckDatabaseStack extends AbstractEventHandler<EnableDBInstanceEvent> { protected CheckDatabaseStack( EventHandlerChain<EnableDBInstanceEvent> chain) { super(chain); } @Override public void apply(EnableDBInstanceEvent evt) throws EventHandlerException { boolean stackFound = false; final String accountId = EventHandlerChainCreateDbInstance.getAccountByUser(evt.getUserId()); final String stackName = EventHandlerChainCreateDbInstance.getStackName(accountId); try{ Stack stack = CloudFormationClient.getInstance().describeStack(evt.getUserId(), stackName); if (stack != null) { stackFound = true; } }catch(final Exception ex){ stackFound = false; } if(!stackFound) throw new EventHandlerException("Could not find stack: " + stackName); } @Override public void rollback() throws EventHandlerException { ; } } public static class WaitOnVm extends AbstractEventHandler<EnableDBInstanceEvent> implements StoredResult<String> { private String instanceId = null; protected WaitOnVm(EventHandlerChain<EnableDBInstanceEvent> chain) { super(chain); } @Override public void apply(EnableDBInstanceEvent evt) throws EventHandlerException { final String tagKey = "Name"; final String tagValue = DatabaseServerProperties.DEFAULT_LAUNCHER_TAG; boolean vmFound = false; final int MAX_RETRY_SEC = 600; for(int i = 1; i<= MAX_RETRY_SEC; i++) { try{ final List<RunningInstancesItemType> instances = Ec2Client.getInstance().describeInstances(null, Lists.<String>newArrayList()); for(final RunningInstancesItemType instance : instances) { final List<ResourceTag> tags = instance.getTagSet(); final String state = instance.getStateName(); if (tags != null ) { for(final ResourceTag tag : tags) { if(tagKey.equals(tag.getKey()) && tagValue.equals(tag.getValue()) && "running".equals(state)) { vmFound = true; instanceId = instance.getInstanceId(); break; } } } if(vmFound) break; } }catch(final Exception ex){ vmFound = false; } if(vmFound) break; try{ Thread.sleep(1000); }catch(final Exception ex){ ; } if( i % 10 == 0) { LOG.info("Looking for running database VM"); } } if(!vmFound) throw new EventHandlerException("Cannot find a running database VM"); LOG.info("Database VM ("+instanceId +") is found"); } @Override public void rollback() throws EventHandlerException { ; } @Override public List<String> getResult() { if(this.instanceId != null){ return Lists.newArrayList(this.instanceId); }else { return Lists.newArrayList(); } } } public static class WaitOnDb extends AbstractEventHandler<EnableDBInstanceEvent> implements StoredResult<String> { private static final String PING_DB_NAME = "eucalyptus_cloudwatch_backend"; private String instanceIp = null; protected WaitOnDb(EventHandlerChain<EnableDBInstanceEvent> chain) { super(chain); } @Override public void apply(EnableDBInstanceEvent evt) throws EventHandlerException { // ping on remote database try { final String instanceId = this.getChain().findHandler(WaitOnVm.class).getResult().get(0); final List<RunningInstancesItemType> instances = Ec2Client.getInstance().describeInstances(getSystemUserId(), Lists.newArrayList(instanceId)); for(final RunningInstancesItemType instance : instances) { if(instanceId.equals(instance.getInstanceId())) { instanceIp = instance.getIpAddress(); break; } } }catch(final Exception ex) { throw new EventHandlerException("Failed to lookup vm ip", ex); } if(instanceIp == null) throw new EventHandlerException("Failed to lookup vm ip"); final String jdbcUrl = getJdbcUrl( instanceIp, evt.getPort() ); boolean connected = false; final int MAX_RETRY_SEC = 120; for(int i = 1; i<= MAX_RETRY_SEC; i++) { if (pingDatabase(jdbcUrl, evt.getMasterUserName(), evt.getMasterUserPassword())) { connected = true; break; } try{ Thread.sleep(1000); }catch(final Exception ex){ ; } if( i % 10 == 0) { LOG.info("Trying to connect to database at "+ instanceIp); } } if(!connected) { throw new EventHandlerException("Failed to connect to database at "+instanceIp); } LOG.info("Database host "+instanceIp+" is connected"); } public static String getJdbcUrl(final String instanceIp, final int port) { final String jdbcUrl = String.format( "jdbc:postgresql://%s:%s/%s", instanceIp, port, PING_DB_NAME); return jdbcUrl; } public static String getJdbcUrlWithSsl(final String instanceIp, final int port) { final String ssl = "ssl=true&sslfactory=com.eucalyptus.database.activities.VmDatabaseSSLSocketFactory"; final String jdbcUrl = String.format( "jdbc:postgresql://%s:%s/%s?%s", instanceIp, port, PING_DB_NAME, ssl ); return jdbcUrl; } public static boolean pingDatabase(final String jdbcUrl, final String userName, final String password) { try ( final Connection conn = DriverManager.getConnection( jdbcUrl, userName, password ) ) { try ( final PreparedStatement statement = conn.prepareStatement( "SELECT USER" ) ) { try ( final ResultSet result = statement.executeQuery( ) ) { return true; } } }catch(final Exception ex){ return false; } } @Override public void rollback() throws EventHandlerException { ; } @Override public List<String> getResult() { if(instanceIp != null) return Lists.newArrayList(instanceIp); else return Lists.newArrayList(); } } public static class UpdateProperties extends AbstractEventHandler<EnableDBInstanceEvent> { protected UpdateProperties(EventHandlerChain<EnableDBInstanceEvent> chain) { super(chain); } @Override public void apply(EnableDBInstanceEvent evt) throws EventHandlerException { String dbHost = null; try{ dbHost = this.getChain().findHandler(WaitOnDb.class).getResult().get(0); }catch(final Exception ex) { throw new EventHandlerException("Failed to look up database host"); } String accountId = EventHandlerChainCreateDbInstance.getAccountByUser(evt.getUserId()); try{ final ServerCertificateType serverCert = EuareClient.getInstance().getServerCertificate(evt.getUserId(), EventHandlerChainCreateDbInstance.getCertificateName(accountId)); final String certBody = serverCert.getCertificateBody(); final String bodyPem = certBody; final ConfigurableProperty certProp = PropertyDirectory.getPropertyEntry("services.database.appendonlysslcert"); certProp.setValue(bodyPem); LOG.debug("Certificate body is updated for postgresql ssl connection"); }catch(final Exception ex){ LOG.error("Failed to read the server certificate", ex); throw new EventHandlerException("Failed to read the server certificate"); } try{ final ConfigurableProperty portProp = PropertyDirectory.getPropertyEntry("services.database.appendonlyport"); portProp.setValue(String.format("%d", evt.getPort())); }catch(final Exception ex) { throw new EventHandlerException("Failed to set port property", ex); } try{ final ConfigurableProperty userNameProp = PropertyDirectory.getPropertyEntry("services.database.appendonlyuser"); userNameProp.setValue(evt.getMasterUserName()); }catch(final Exception ex) { throw new EventHandlerException("Failed to set username property", ex); } try{ final ConfigurableProperty passwordProp = PropertyDirectory.getPropertyEntry("services.database.appendonlypassword"); passwordProp.setValue(evt.getMasterUserPassword()); }catch(final Exception ex) { throw new EventHandlerException("Failed to set password property", ex); } /// this will trigger db pool init to the designated host try{ final ConfigurableProperty hostProp = PropertyDirectory.getPropertyEntry("services.database.appendonlyhost"); hostProp.setValue(dbHost); }catch(final Exception ex) { throw new EventHandlerException("Failed to set hostname property", ex); } LOG.info("services.database.appendonly* properties are updated"); } @Override public void rollback() throws EventHandlerException { ; } } }