/*************************************************************************
* 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.imaging.backend;
import static com.eucalyptus.auth.policy.PolicySpec.VENDOR_IMAGINGSERVICE;
import static com.eucalyptus.auth.policy.PolicySpec.EC2_RESOURCE_INSTANCE;
import org.apache.log4j.Logger;
import com.eucalyptus.auth.Permissions;
import com.eucalyptus.auth.principal.AccountIdentifiers;
import com.eucalyptus.blockstorage.Volumes;
import com.eucalyptus.component.annotation.ComponentNamed;
import com.eucalyptus.context.Context;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.imaging.backend.AbstractTaskScheduler.WorkerTask;
import com.eucalyptus.imaging.common.backend.msgs.GetInstanceImportTaskResponseType;
import com.eucalyptus.imaging.common.backend.msgs.GetInstanceImportTaskType;
import com.eucalyptus.imaging.common.backend.msgs.PutInstanceImportTaskStatusResponseType;
import com.eucalyptus.imaging.common.backend.msgs.PutInstanceImportTaskStatusType;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.RestrictedTypes;
@ComponentNamed
public class ImagingBackendService {
private static Logger LOG = Logger.getLogger( ImagingBackendService.class );
private static final int MAX_TIMEOUT_AND_RETRY = 1;
public PutInstanceImportTaskStatusResponseType PutInstanceImportTaskStatus( PutInstanceImportTaskStatusType request ) throws EucalyptusCloudException {
final PutInstanceImportTaskStatusResponseType reply = request.getReply( );
try{
if ( !isAuthorized( ) ) {
throw new ImagingServiceException( ImagingServiceException.DEFAULT_CODE, "Not authorized to put import task status." );
}
}catch(final ImagingServiceException ex){
throw ex;
}catch(final Exception ex){
throw new ImagingServiceException( ImagingServiceException.DEFAULT_CODE, "Not authorized to put import task status." );
}
try{
final String remoteHost = request.getSourceIp();
ImagingWorkers.verifyWorker(request.getInstanceId(), remoteHost);
}catch(final Exception ex){
LOG.warn("Failed to verify worker", ex);
throw new ImagingServiceException(ImagingServiceException.DEFAULT_CODE, "Not authorized to put import task status." );
}
reply.setCancelled(false);
try{
final String taskId = request.getImportTaskId();
final String volumeId = request.getVolumeId();
if(taskId==null)
throw new Exception("Task id is null");
ImagingTask imagingTask = null;
try{
imagingTask= ImagingTasks.lookup(taskId);
}catch(final Exception ex){
reply.setCancelled(true);
throw new Exception("imaging task with "+taskId+" is not found");
}
final String instanceId = request.getInstanceId();
if(instanceId!=null){
ImagingWorkers.markUpdate(request.getInstanceId());
}
if(ImportTaskState.CONVERTING.equals(imagingTask.getState()) &&
instanceId.equals(imagingTask.getWorkerId())){
//EXTANT, FAILED, DONE
final WorkerTaskState workerState = WorkerTaskState.fromString(request.getStatus());
if(WorkerTaskState.EXTANT.equals(workerState) || WorkerTaskState.DONE.equals(workerState)){
if(imagingTask instanceof VolumeImagingTask){
try{
final long bytesConverted= request.getBytesConverted();
if(bytesConverted>0)
ImagingTasks.updateBytesConverted(taskId, volumeId, bytesConverted);
}catch(final Exception ex){
LOG.warn("Failed to update bytes converted("+taskId+")");
}
}
}
switch(workerState){
case EXTANT:
if(imagingTask.isTimedOut()){
try{
if(imagingTask.getTimeoutCount() < MAX_TIMEOUT_AND_RETRY) {
LOG.warn(String.format("Imaging import task %s has timed out; will retry later", imagingTask.getDisplayName()));
ImagingTasks.timeoutTask(taskId);
ImagingTasks.killAndRerunTask(taskId);
}else{
LOG.warn(String.format("Imaging import task %s has timed out and failed", imagingTask.getDisplayName()));
ImagingTasks.setState(imagingTask, ImportTaskState.FAILED, ImportTaskState.STATE_MSG_CONVERSION_TIMEOUT);
}
}catch(final Exception ex){
LOG.error("Failed to handle timed-out task", ex);
ImagingTasks.setState(imagingTask, ImportTaskState.FAILED, ImportTaskState.STATE_MSG_CONVERSION_TIMEOUT);
}finally{
reply.setCancelled(true);
}
}
break;
case DONE:
try{
ImagingTasks.updateVolumeStatus((VolumeImagingTask)imagingTask, volumeId, ImportTaskState.COMPLETED, null);
Volumes.setSystemManagedFlag(null, volumeId, false);
}catch(final Exception ex){
ImagingTasks.transitState(imagingTask, ImportTaskState.CONVERTING,
ImportTaskState.FAILED, ImportTaskState.STATE_MSG_FAILED_UNEXPECTED);
LOG.error("Failed to update volume's state", ex);
break;
}
try{
if(imagingTask instanceof ImportVolumeImagingTask){
ImagingTasks.transitState(imagingTask, ImportTaskState.CONVERTING,
ImportTaskState.COMPLETED, ImportTaskState.STATE_MSG_DONE);
}else if(ImagingTasks.isConversionDone((VolumeImagingTask)imagingTask)){
ImagingTasks.transitState(imagingTask, ImportTaskState.CONVERTING,
ImportTaskState.INSTANTIATING, ImportTaskState.STATE_MSG_LAUNCHING_INSTANCE);
}
}catch(final Exception ex){
LOG.error("Failed to update imaging task's state to completed", ex);
}
break;
case FAILED:
String stateMsg = ImportTaskState.STATE_MSG_CONVERSION_FAILED;
if(request.getMessage()!=null)
stateMsg = request.getMessage();
ImagingTasks.setState(imagingTask, ImportTaskState.FAILED, stateMsg);
LOG.warn(String.format("Worker reported failed conversion: %s-%s", stateMsg, request.getErrorCode() !=null ? request.getErrorCode() : "no error code"));
final String errorCode = request.getErrorCode();
if(errorCode!=null && errorCode.length()>0 && ImagingWorkers.isFatalError(errorCode)){
LOG.warn(String.format("A task %s experienced fatal error. Worker instance %s is being retired",
request.getImportTaskId(), request.getInstanceId()));
ImagingWorkers.retireWorker(request.getInstanceId());
}
break;
}
}else{ // state other than "CONVERTING" is not valid and worker should stop working
reply.setCancelled(true);
}
}catch(final Exception ex){
LOG.warn("Failed to update the task's state", ex);
}
if(reply.getCancelled()!=null && reply.getCancelled().booleanValue()){
LOG.warn(String.format("Imaging task %s has been cancelled", request.getImportTaskId()));
}
return reply;
}
public GetInstanceImportTaskResponseType GetInstanceImportTask( GetInstanceImportTaskType request ) throws EucalyptusCloudException {
final GetInstanceImportTaskResponseType reply = request.getReply( );
try{
if ( !isAuthorized( ) ) {
throw new ImagingServiceException( ImagingServiceException.DEFAULT_CODE, "Not authorized to get import task." );
}
}catch(final ImagingServiceException ex){
throw ex;
}catch(final Exception ex){
throw new ImagingServiceException( ImagingServiceException.DEFAULT_CODE, "Not authorized to get import task." );
}
try{
final String remoteHost = request.getSourceIp();
ImagingWorkers.verifyWorker(request.getInstanceId(), remoteHost);
}catch(final Exception ex){
LOG.warn("Failed to verify worker", ex);
throw new ImagingServiceException(ImagingServiceException.DEFAULT_CODE, "Not authorized to get instance import task." );
}
try{
ImagingWorker worker = ImagingWorkers.getWorker(request.getInstanceId());
if(worker!=null) {
ImagingWorkers.markUpdate(request.getInstanceId());
if(!ImagingWorkers.canAllocate(request.getInstanceId())){
LOG.warn(String.format("The worker (%s) is marked invalid", request.getInstanceId()));
return reply;
}
}else {
worker = ImagingWorkers.createWorker(request.getInstanceId());
}
final ImagingTask prevTask = ImagingTasks.getConvertingTaskByWorkerId(request.getInstanceId());
if(prevTask!=null){
/// this should NOT happen; if it does, it's worker script's bug
ImagingTasks.killAndRerunTask(prevTask.getDisplayName());
LOG.info(String.format("A task (%s:%s) is gone missing and will be re-scheduled",
prevTask.getDisplayName(), request.getInstanceId()));
}
final WorkerTask task = AbstractTaskScheduler.getScheduler().getTask(worker.getAvailabilityZone());
if(task!=null){
reply.setImportTaskId(task.getImportTaskId());
reply.setImportTaskType(task.getImportTaskType().toString());
if(task.getVolumeTask()!=null)
reply.setVolumeTask(task.getVolumeTask());
else if(task.getInstanceStoreTask()!=null)
reply.setInstanceStoreTask(task.getInstanceStoreTask());
if(request.getInstanceId()!=null){
ImagingTasks.setWorkerId(task.getImportTaskId(), request.getInstanceId());
}
}
}catch(final Exception ex){
LOG.error("Failed to schedule a task", ex);
}
return reply;
}
private static boolean isAuthorized( ) {
final Context context = Contexts.lookup();
return context.hasAdministrativePrivileges( ) ||
( AccountIdentifiers.IMAGING_SYSTEM_ACCOUNT.equals( context.getAccountAlias( ) ) && Permissions.isAuthorized(
VENDOR_IMAGINGSERVICE,
EC2_RESOURCE_INSTANCE, // resource type should not affect evaluation
"",
null,
RestrictedTypes.getIamActionByMessageType( ),
context.getAuthContext() ) );
}
}