From cd70ede3c2da80b3793f6878dc69c7d692b8a1aa Mon Sep 17 00:00:00 2001 From: Syed Ahmed Date: Thu, 19 Apr 2018 11:22:01 -0400 Subject: [PATCH] Add ability to archive snapshots on primary storage --- .../storage/snapshot/SnapshotApiService.java | 7 ++ .../user/snapshot/ArchiveSnapshotCmd.java | 92 +++++++++++++++++++ .../storage/snapshot/SnapshotServiceImpl.java | 9 +- .../SnapshotStateMachineManagerImpl.java | 1 + .../cloud/server/ManagementServerImpl.java | 2 + .../storage/snapshot/SnapshotManagerImpl.java | 24 +++++ .../storage/snapshot/SnapshotManagerTest.java | 14 +++ 7 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ArchiveSnapshotCmd.java diff --git a/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java b/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java index eb1393543c0..a80391b3525 100644 --- a/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java +++ b/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java @@ -100,6 +100,13 @@ public interface SnapshotApiService { */ Snapshot createSnapshot(Long volumeId, Long policyId, Long snapshotId, Account snapshotOwner); + /** + * Archives a snapshot from primary storage to secondary storage. + * @param id Snapshot ID + * @return Archived Snapshot object + */ + Snapshot archiveSnapshot(Long id); + /** * @param vol * @return diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ArchiveSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ArchiveSnapshotCmd.java new file mode 100644 index 00000000000..4cf6e853efd --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ArchiveSnapshotCmd.java @@ -0,0 +1,92 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.snapshot; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.storage.Snapshot; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; + +@APICommand(name = "archiveSnapshot", description = "Archives (moves) a snapshot on primary storage to secondary storage", + responseObject = SnapshotResponse.class, entityType = {Snapshot.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ArchiveSnapshotCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(CreateSnapshotCmd.class.getName()); + private static final String s_name = "createsnapshotresponse"; + + @ACL(accessType = SecurityChecker.AccessType.OperateEntry) + @Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType = SnapshotResponse.class, + required=true, description="The ID of the snapshot") + private Long id; + + @Override + public String getEventType() { + return EventTypes.EVENT_SNAPSHOT_CREATE; + } + + @Override + public String getEventDescription() { + return "Archiving snapshot " + id + " to secondary storage"; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + CallContext.current().setEventDetails("Snapshot Id: " + this._uuidMgr.getUuid(Snapshot.class,getId())); + Snapshot snapshot = _snapshotService.archiveSnapshot(getId()); + if (snapshot != null) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to archive snapshot"); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + Snapshot snapshot = _entityMgr.findById(Snapshot.class, getId()); + if (snapshot != null) { + return snapshot.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked + } + + public Long getId() { + return id; + } +} diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java index 601959bdcbd..9c513709677 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java @@ -262,6 +262,7 @@ public class SnapshotServiceImpl implements SnapshotService { SnapshotObject snapObj = (SnapshotObject)snapshot; AsyncCallFuture future = new AsyncCallFuture(); SnapshotResult result = new SnapshotResult(snapshot, null); + Snapshot.State origState = snapObj.getState(); try { snapObj.processEvent(Snapshot.Event.BackupToSecondary); @@ -281,7 +282,13 @@ public class SnapshotServiceImpl implements SnapshotService { s_logger.debug("Failed to copy snapshot", e); result.setResult("Failed to copy snapshot:" + e.toString()); try { - snapObj.processEvent(Snapshot.Event.OperationFailed); + // When error archiving an already existing snapshot, emit OperationNotPerformed. + // This will ensure that the original snapshot does not get deleted + if (origState.equals(Snapshot.State.BackedUp)) { + snapObj.processEvent(Snapshot.Event.OperationNotPerformed); + } else { + snapObj.processEvent(Snapshot.Event.OperationFailed); + } } catch (NoTransitionException e1) { s_logger.debug("Failed to change state: " + e1.toString()); } diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStateMachineManagerImpl.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStateMachineManagerImpl.java index 287c3783f89..57f8938540b 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStateMachineManagerImpl.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStateMachineManagerImpl.java @@ -42,6 +42,7 @@ public class SnapshotStateMachineManagerImpl implements SnapshotStateMachineMana stateMachine.addTransition(Snapshot.State.CreatedOnPrimary, Event.OperationNotPerformed, Snapshot.State.BackedUp); stateMachine.addTransition(Snapshot.State.BackingUp, Event.OperationSucceeded, Snapshot.State.BackedUp); stateMachine.addTransition(Snapshot.State.BackingUp, Event.OperationFailed, Snapshot.State.Error); + stateMachine.addTransition(Snapshot.State.BackingUp, Event.OperationNotPerformed, State.BackedUp); stateMachine.addTransition(Snapshot.State.BackedUp, Event.DestroyRequested, Snapshot.State.Destroying); stateMachine.addTransition(Snapshot.State.BackedUp, Event.CopyingRequested, Snapshot.State.Copying); stateMachine.addTransition(Snapshot.State.BackedUp, Event.BackupToSecondary, Snapshot.State.BackingUp); diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index faa81f317b0..1ccc737c7af 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -418,6 +418,7 @@ import org.apache.cloudstack.api.command.user.securitygroup.DeleteSecurityGroupC import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd; import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupEgressCmd; import org.apache.cloudstack.api.command.user.securitygroup.RevokeSecurityGroupIngressCmd; +import org.apache.cloudstack.api.command.user.snapshot.ArchiveSnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotFromVMSnapshotCmd; import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd; @@ -2820,6 +2821,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(CreateSnapshotCmd.class); cmdList.add(CreateSnapshotFromVMSnapshotCmd.class); cmdList.add(DeleteSnapshotCmd.class); + cmdList.add(ArchiveSnapshotCmd.class); cmdList.add(CreateSnapshotPolicyCmd.class); cmdList.add(UpdateSnapshotPolicyCmd.class); cmdList.add(DeleteSnapshotPoliciesCmd.class); diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java index bd49c05f43e..ccb75d356d8 100755 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -381,6 +381,30 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement return snapshot; } + @Override + public Snapshot archiveSnapshot(Long snapshotId) { + SnapshotInfo snapshotOnPrimary = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Primary); + + if (snapshotOnPrimary == null || !snapshotOnPrimary.getStatus().equals(ObjectInDataStoreStateMachine.State.Ready)) { + throw new CloudRuntimeException("Can only archive snapshots present on primary storage. " + + "Cannot find snapshot " + snapshotId + " on primary storage"); + } + + SnapshotInfo snapshotOnSecondary = snapshotSrv.backupSnapshot(snapshotOnPrimary); + SnapshotVO snapshotVO = _snapshotDao.findById(snapshotOnSecondary.getId()); + snapshotVO.setLocationType(Snapshot.LocationType.SECONDARY); + _snapshotDao.persist(snapshotVO); + + try { + snapshotSrv.deleteSnapshot(snapshotOnPrimary); + } catch (Exception e) { + throw new CloudRuntimeException("Snapshot archived to Secondary Storage but there was an error deleting " + + " the snapshot on Primary Storage. Please manually delete the primary snapshot " + snapshotId, e); + } + + return snapshotOnSecondary; + } + @Override public Snapshot backupSnapshot(Long snapshotId) { SnapshotInfo snapshot = snapshotFactory.getSnapshot(snapshotId, DataStoreRole.Image); diff --git a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java index 39eb703300b..973485f652f 100755 --- a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java +++ b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java @@ -23,6 +23,7 @@ import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy; @@ -336,4 +337,17 @@ public class SnapshotManagerTest { Snapshot snapshot = _snapshotMgr.backupSnapshotFromVmSnapshot(TEST_SNAPSHOT_ID, TEST_VM_ID, TEST_VOLUME_ID, TEST_VM_SNAPSHOT_ID); Assert.assertNull(snapshot); } + + @Test(expected = CloudRuntimeException.class) + public void testArchiveSnapshotSnapshotNotOnPrimary() { + when(snapshotFactory.getSnapshot(anyLong(), Mockito.eq(DataStoreRole.Primary))).thenReturn(null); + _snapshotMgr.archiveSnapshot(TEST_SNAPSHOT_ID); + } + + @Test(expected = CloudRuntimeException.class) + public void testArchiveSnapshotSnapshotNotReady() { + when(snapshotFactory.getSnapshot(anyLong(), Mockito.eq(DataStoreRole.Primary))).thenReturn(snapshotInfoMock); + when(snapshotInfoMock.getStatus()).thenReturn(ObjectInDataStoreStateMachine.State.Destroyed); + _snapshotMgr.archiveSnapshot(TEST_SNAPSHOT_ID); + } }