/************************************************************************* * 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. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.cluster; import static com.eucalyptus.auth.policy.PolicySpec.*; import static com.eucalyptus.compute.common.internal.vm.VmInstances.TerminatedInstanceException; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentSkipListSet; import javax.annotation.Nonnull; import org.apache.log4j.Logger; import com.eucalyptus.auth.Permissions; import com.eucalyptus.auth.Regions; import com.eucalyptus.auth.euare.identity.region.RegionConfigurations; import com.eucalyptus.cluster.common.ClusterController; import com.eucalyptus.cluster.common.internal.Cluster; import com.eucalyptus.component.ComponentIds; import com.eucalyptus.component.annotation.ComponentNamed; import com.eucalyptus.compute.ClientComputeException; import com.eucalyptus.compute.ComputeException; import com.eucalyptus.compute.common.CloudMetadatas; import com.eucalyptus.compute.common.ClusterInfoType; import com.eucalyptus.compute.common.Compute; import com.eucalyptus.compute.common.ImageMetadata.Platform; import com.eucalyptus.cluster.common.internal.ResourceState.VmTypeAvailability; import com.eucalyptus.component.ServiceConfiguration; import com.eucalyptus.component.Topology; import com.eucalyptus.compute.common.RegionInfoType; import com.eucalyptus.compute.common.backend.DescribeAvailabilityZonesResponseType; import com.eucalyptus.compute.common.backend.DescribeAvailabilityZonesType; import com.eucalyptus.compute.common.backend.DescribeRegionsResponseType; import com.eucalyptus.compute.common.backend.DescribeRegionsType; import com.eucalyptus.compute.common.backend.MigrateInstancesResponseType; import com.eucalyptus.compute.common.backend.MigrateInstancesType; import com.eucalyptus.context.Context; import com.eucalyptus.context.Contexts; import com.eucalyptus.crypto.util.B64; import com.eucalyptus.entities.Entities; import com.eucalyptus.compute.common.internal.tags.FilterSupport; import com.eucalyptus.compute.common.internal.tags.Filters; import com.eucalyptus.util.CollectionUtils; import com.eucalyptus.util.EucalyptusCloudException; import com.eucalyptus.util.NonNullFunction; import com.eucalyptus.util.async.AsyncRequests; import com.eucalyptus.compute.common.internal.vm.VmInstance; import com.eucalyptus.vm.VmInstances; import com.eucalyptus.compute.common.internal.vmtypes.VmType; import com.eucalyptus.vmtypes.VmTypes; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Strings; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.eucalyptus.cluster.common.msgs.ClusterGetConsoleOutputResponseType; import com.eucalyptus.cluster.common.msgs.ClusterGetConsoleOutputType; @ComponentNamed("computeClusterEndpoint") public class ClusterEndpoint { private static Logger LOG = Logger.getLogger( ClusterEndpoint.class ); private Map<String, NonNullFunction<Predicate<Object>,List<ClusterInfoType>>> describeKeywords = ImmutableMap.<String, NonNullFunction<Predicate<Object>,List<ClusterInfoType>>>of( "verbose", new NonNullFunction<Predicate<Object>,List<ClusterInfoType>>( ) { @Nonnull @Override public List<ClusterInfoType> apply( final Predicate<Object> filterPredicate ) { List<ClusterInfoType> verbose = Lists.newArrayList( ); for ( Cluster c : Iterables.filter( Clusters.list( ), filterPredicate ) ) { verbose.addAll( describeSystemInfo.apply( c ) ); } return verbose; } } ); public MigrateInstancesResponseType migrateInstances( final MigrateInstancesType request ) throws EucalyptusCloudException { final MigrateInstancesResponseType reply = request.getReply( ); final Context context = Contexts.lookup( ); if ( !context.isAdministrator( ) || !Permissions.isAuthorized( VENDOR_EC2, EC2_RESOURCE_INSTANCE, "", null, EC2_MIGRATEINSTANCES, context.getAuthContext() ) ) { throw new EucalyptusCloudException( "Authorization failed." ); } if ( !Strings.isNullOrEmpty( request.getSourceHost( ) ) ) { final Predicate<VmInstance> filterHost = new Predicate<VmInstance>( ) { @Override public boolean apply( VmInstance input ) { String vmHost = URI.create( input.getServiceTag( ) ).getHost( ); return Strings.nullToEmpty( vmHost ).equals( request.getSourceHost( ) ); } }; for ( ServiceConfiguration ccConfig : Topology.enabledServices( ClusterController.class ) ) { try { Cluster cluster = Clusters.lookupAny( ccConfig );//lookup the cluster if ( !cluster.hasNode( request.getSourceHost( ) ) ) { continue; } final List<VmInstance> instances = VmInstances.list(filterHost); for(final VmInstance instance : instances){ try{ updatePasswordIfWindows(instance, ccConfig); }catch(final Exception ex){ ; } } try { Migrations.using( cluster ).migrateInstances( request.getSourceHost( ), request.getAllowHosts( ), request.getDestinationHosts( ) );//submit the migration request return reply.markWinning( ); } catch ( Exception ex ) { LOG.error( ex ); throw new EucalyptusCloudException( "Migrating off of node " + request.getSourceHost( ) + " failed because of: " + Strings.nullToEmpty( ex.getMessage( ) ).replaceAll( ".*:status=", "" ), ex ); } } catch ( EucalyptusCloudException ex ) { throw ex; } catch ( NoSuchElementException ex ) { // Ignore and continue } catch ( Exception ex ) { LOG.error( ex ); throw new EucalyptusCloudException( "Migrating off of node " + request.getSourceHost( ) + " failed because of: " + ex.getMessage( ), ex ); } } throw new EucalyptusCloudException( "No ENABLED cluster found which can service the requested node: " + request.getSourceHost( ) ); } else if ( !Strings.isNullOrEmpty( request.getInstanceId( ) ) ) { final VmInstance vm; try { vm = VmInstances.lookup( request.getInstanceId( ) ); if ( !VmInstance.VmState.RUNNING.apply( vm ) ) { throw new EucalyptusCloudException( "Cannot migrate a " + vm.getState( ).name( ).toLowerCase( ) + " instance: " + request.getInstanceId( ) ); } } catch ( TerminatedInstanceException ex ) { throw new EucalyptusCloudException( "Cannot migrate a terminated instance: " + request.getInstanceId( ), ex ); } catch ( NoSuchElementException ex ) { throw new EucalyptusCloudException( "Failed to lookup requested instance: " + request.getInstanceId( ), ex ); } try { ServiceConfiguration ccConfig = Topology.lookup( ClusterController.class, vm.lookupPartition( ) ); Cluster cluster = Clusters.lookupAny( ccConfig ); // update windows password try{ updatePasswordIfWindows(vm, ccConfig); }catch(final Exception ex){ } try { Migrations.using( cluster ).migrateInstance( request.getInstanceId( ), request.getAllowHosts( ), request.getDestinationHosts( ) ); return reply.markWinning( ); } catch ( Exception ex ) { LOG.error( ex ); throw new EucalyptusCloudException( "Migrating instance " + request.getInstanceId( ) + " failed because of: " + Strings.nullToEmpty( ex.getMessage( ) ).replaceAll( ".*:status=", "" ), ex ); } } catch ( NoSuchElementException ex ) { throw new EucalyptusCloudException( "Failed to lookup ENABLED cluster for instance " + request.getInstanceId( ), ex ); } } else { throw new EucalyptusCloudException( "Either the sourceHost or instanceId must be provided" ); } } private void updatePasswordIfWindows(final VmInstance vm, final ServiceConfiguration ccConfig) throws Exception{ if ( Platform.windows.name().equals(vm.getPlatform()) && (vm.getPasswordData( ) == null || vm.getPasswordData().length()<=0) ) { try { final ClusterGetConsoleOutputResponseType consoleOutput = AsyncRequests.sendSync( ccConfig, new ClusterGetConsoleOutputType( vm.getInstanceId() ) ); final String tempCo = B64.standard.decString( String.valueOf( consoleOutput.getOutput( ) ) ).replaceAll( "[\r\n]*", "" ); final String passwordData = tempCo.replaceAll( ".*<Password>", "" ).replaceAll( "</Password>.*", "" ); if ( tempCo.matches( ".*<Password>[\\w=+/]*</Password>.*" ) ) { Entities.asTransaction( VmInstance.class, new Predicate<String>() { @Override public boolean apply( final String passwordData ) { final VmInstance vmMerge = Entities.merge( vm ); vmMerge.updatePasswordData( passwordData ); return true; } } ).apply( passwordData ); vm.updatePasswordData( passwordData ); } } catch ( Exception e ) { throw new ComputeException( "InternalError", "Error processing request: " + e.getMessage( ) ); } } } public DescribeAvailabilityZonesResponseType DescribeAvailabilityZones( DescribeAvailabilityZonesType request ) throws EucalyptusCloudException { final DescribeAvailabilityZonesResponseType reply = request.getReply( ); final List<String> args = request.getAvailabilityZoneSet( ); final Predicate<Object> filterPredicate = Filters.generateFor( request.getFilterSet(), Cluster.class ) .withOptionalInternalFilter( "zone-name", Iterables.filter( args, Predicates.not( Predicates.in( describeKeywords.keySet( ) ) ) ) ) .generate( ) .asPredicate( ); final boolean admin = Contexts.lookup( ).hasAdministrativePrivileges( ); for ( String keyword : describeKeywords.keySet( ) ) { if ( args.remove( keyword ) && admin ) { reply.getAvailabilityZoneInfo( ).addAll( describeKeywords.get( keyword ).apply( filterPredicate ) ); return reply; } } final List<Cluster> clusters; if ( args.isEmpty( ) ) { clusters = Clusters.list( ); Iterables.addAll( clusters, Iterables.filter( Clusters.listDisabled( ), Predicates.not( CollectionUtils.propertyPredicate( Collections2.transform( clusters, CloudMetadatas.toDisplayName() ), CloudMetadatas.toDisplayName() ) ) ) ); } else { clusters = Lists.newArrayList(); for ( final String partitionName : request.getAvailabilityZoneSet( ) ) { try { clusters.add( Iterables.find( Clusters.list( ), new Predicate<Cluster>( ) { @Override public boolean apply( Cluster input ) { return partitionName.equals( input.getConfiguration( ).getPartition( ) ); } } ) ); } catch ( NoSuchElementException e ) { try { clusters.add( Clusters.lookupAny( partitionName ) ); } catch ( NoSuchElementException ex ) { if ( !describeKeywords.containsValue( partitionName ) ) { throw new ClientComputeException("InvalidParameterValue", "Invalid availability zone: [" + partitionName + "]"); } } } } } for ( final Cluster c : Iterables.filter( clusters, filterPredicate ) ) { reply.getAvailabilityZoneInfo( ).addAll( this.getDescriptionEntry( c ) ); } return reply; } private List<ClusterInfoType> getDescriptionEntry( Cluster c ) { final List<ClusterInfoType> ret = Lists.newArrayList( ); ret.add( new ClusterInfoType( c.getConfiguration( ).getPartition( ), ClusterFunctions.STATE.apply( c ), region( ) ) ); NavigableSet<String> tagList = new ConcurrentSkipListSet<>( ); if ( tagList.size( ) == 1 ) tagList = c.getNodeTags( ); else tagList.retainAll( c.getNodeTags( ) ); return ret; } private static String INFO_FSTRING = "|- %s"; private static String HEADER_STRING = "free / max cpu ram disk"; private static String STATE_FSTRING = "%04d / %04d %2d %4d %4d"; private static ClusterInfoType s( String left, String right ) { return new ClusterInfoType( String.format( INFO_FSTRING, left ), right ); } private static String region( ) { return RegionConfigurations.getRegionName( ).or( "" ); } private static NonNullFunction<Cluster, List<ClusterInfoType>> describeSystemInfo = new NonNullFunction<Cluster, List<ClusterInfoType>>( ) { @Nonnull @Override public List<ClusterInfoType> apply( Cluster cluster ) { List<ClusterInfoType> info = new ArrayList<>( ); try { info.add( new ClusterInfoType( cluster.getConfiguration( ).getPartition( ), cluster.getConfiguration( ).getHostName( ) + " " + cluster.getConfiguration( ).getFullName( ) ) ); info.add( new ClusterInfoType( String.format( INFO_FSTRING, "vm types" ), HEADER_STRING ) ); for ( VmType v : VmTypes.list( ) ) { VmTypeAvailability va = cluster.getNodeState( ).getAvailability( v ); info.add( s( v.getName( ), String.format( STATE_FSTRING, va.getAvailable( ), va.getMax( ), v.getCpu( ), v.getMemory( ), v.getDisk( ) ) ) ); } } catch ( Exception e ) { LOG.error( e, e ); } return info; } }; public DescribeRegionsResponseType DescribeRegions( final DescribeRegionsType request ) throws EucalyptusCloudException { final DescribeRegionsResponseType reply = request.getReply( ); try { final List<com.eucalyptus.auth.RegionService> regions = Regions.getRegionServicesByType( ComponentIds.lookup( Compute.class ).name( ) ); final Predicate<Object> filterPredicate = Filters.generateFor( request.getFilterSet(), com.eucalyptus.auth.RegionService.class ) .withOptionalInternalFilter( "region-name", request.getRegions() ) .generate() .asPredicate(); for ( final com.eucalyptus.auth.RegionService item : Iterables.filter( regions, filterPredicate ) ) { reply.getRegionInfo( ).add( new RegionInfoType( item.getRegionName( ), item.getServiceEndpoint( ) ) ); } } catch ( Exception ex ) { LOG.error( "Error describing regions: " + ex.getMessage( ), ex ); } return reply; } public static class RegionFilterSupport extends FilterSupport<com.eucalyptus.auth.RegionService> { public RegionFilterSupport() { super( builderFor( com.eucalyptus.auth.RegionService.class ) .withStringProperty( "endpoint", com.eucalyptus.auth.RegionService.serviceEndpoint( ) ) .withStringProperty( "region-name", com.eucalyptus.auth.RegionService.regionName( ) ) ); } } private enum ClusterFunctions implements Function<Cluster,String> { STATE { @Override public String apply( final Cluster cluster ) { return Clusters.list( ).contains( cluster ) ? "available" : "unavailable"; } }, } public static class AvailabilityZoneFilterSupport extends FilterSupport<Cluster> { public AvailabilityZoneFilterSupport() { super( builderFor( Cluster.class ) .withUnsupportedProperty( "message" ) .withStringProperty( "region-name", (r)->region( ) ) .withStringProperty( "state", ClusterFunctions.STATE ) .withStringProperty( "zone-name", CloudMetadatas.toDisplayName() ) ); } } }