mike-tutkowski closed pull request #2585: Add ability to archive snapshots on 
primary storage
URL: https://github.com/apache/cloudstack/pull/2585
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

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 @@
      */
     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 SnapshotInfo backupSnapshot(SnapshotInfo snapshot) {
         SnapshotObject snapObj = (SnapshotObject)snapshot;
         AsyncCallFuture<SnapshotResult> future = new 
AsyncCallFuture<SnapshotResult>();
         SnapshotResult result = new SnapshotResult(snapshot, null);
+        Snapshot.State origState = snapObj.getState();
         try {
             snapObj.processEvent(Snapshot.Event.BackupToSecondary);
 
@@ -281,7 +282,13 @@ public SnapshotInfo backupSnapshot(SnapshotInfo snapshot) {
             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 SnapshotStateMachineManagerImpl() {
         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.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 long getMemoryOrCpuCapacityByHost(final Long 
hostId, final short capacity
         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 Snapshot createSnapshot(Long volumeId, Long 
policyId, Long snapshotId, Ac
         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.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 void testBackupSnapshotFromVmSnapshotF3() {
         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);
+    }
 }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to