api,server,ui: snapshot copy, multi-zone replica (#7873)

This PR adds new functionality to copy snapshots across zones and take snapshots for multiple zones.

Copy functionality is similar to template copy. The source zone acts as the web server from where the destination zone(s) can download the snapshot files. For this purpose, a new API - `copySnapshot` has been added. The response for copySnapshot will be returning zone and download details from the first destination zone of the request. This behaviour is similar to the `copyTemplate` API.

In a similar manner, multiple zones can be selected while taking the snapshots or creating snapshot policies. For this snapshot will be taken in the base zone(in which volume is present) and then copied to the additional zones. A new parameter - `zoneids` has been added to `createSnapshot` and `createSnapshotPolicy` APIs.

As snapshots can be present on multiple zones (secondary stores), a new parameter `zoneid` has been added to delete the snapshot copy on a specific zone.

`listSnapshots` API has been updated to allow listing snapshot entries for different zones/datastores. New parameters - `showUnique`, `locationType` have been added.

Events generated during snapshot operations will now be linked to the snapshot itself rather than the volume of the snapshot.

`listSnapshotPolicies` and `createSnapshotPolicy` APIs will return zone details of the zones in which backup will be scheduled for the policy.

----
New API added
`copySnapshot`

Request and response params updated for APIs
```
- listSnapshots
- deleteSnapshot
- createTemplate
- listZones
- listSnapshotPolicies
- createSnapshotPolicy
```
UI updated for
- Snapshot detail view
- Create snapshot form
- Create snapshot policy form
- Create volume (from snapshot) form
- Create template (from snapshot) form

Doc PR: https://github.com/apache/cloudstack-documentation/pull/344
PR: https://github.com/apache/cloudstack/pull/7873
This commit is contained in:
Abhishek Kumar 2023-10-23 12:31:58 +05:30 committed by GitHub
parent 99ded8169b
commit 543c54c718
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
132 changed files with 7230 additions and 1104 deletions

View File

@ -30,6 +30,7 @@ import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.ha.HAConfig;
import org.apache.cloudstack.usage.Usage;
import org.apache.cloudstack.vm.schedule.VMSchedule;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterGuestIpv6Prefix;
@ -84,7 +85,6 @@ import com.cloud.user.User;
import com.cloud.vm.Nic;
import com.cloud.vm.NicSecondaryIp;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.vm.schedule.VMSchedule;
public class EventTypes {
@ -320,6 +320,7 @@ public class EventTypes {
public static final String EVENT_DOMAIN_UPDATE = "DOMAIN.UPDATE";
// Snapshots
public static final String EVENT_SNAPSHOT_COPY = "SNAPSHOT.COPY";
public static final String EVENT_SNAPSHOT_CREATE = "SNAPSHOT.CREATE";
public static final String EVENT_SNAPSHOT_ON_PRIMARY = "SNAPSHOT.ON_PRIMARY";
public static final String EVENT_SNAPSHOT_OFF_PRIMARY = "SNAPSHOT.OFF_PRIMARY";

View File

@ -19,9 +19,9 @@
package com.cloud.storage;
import java.net.MalformedURLException;
import java.util.List;
import java.util.Map;
import com.cloud.utils.fsm.NoTransitionException;
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd;
@ -37,6 +37,7 @@ import org.apache.cloudstack.framework.config.ConfigKey;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.user.Account;
import com.cloud.utils.fsm.NoTransitionException;
public interface VolumeApiService {
@ -105,10 +106,10 @@ public interface VolumeApiService {
Volume detachVolumeFromVM(DetachVolumeCmd cmd);
Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, Map<String, String> tags)
Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, Map<String, String> tags, List<Long> zoneIds)
throws ResourceAllocationException;
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) throws ResourceAllocationException;
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List<Long> zoneIds) throws ResourceAllocationException;
Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long owner, String chainInfo, String name);

View File

@ -18,18 +18,20 @@ package com.cloud.storage.snapshot;
import java.util.List;
import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd;
import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd;
import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd;
import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd;
import org.apache.cloudstack.api.command.user.snapshot.UpdateSnapshotPolicyCmd;
import com.cloud.api.commands.ListRecurringSnapshotScheduleCmd;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.StorageUnavailableException;
import com.cloud.storage.Snapshot;
import com.cloud.storage.Volume;
import com.cloud.user.Account;
import com.cloud.utils.Pair;
import org.apache.cloudstack.api.command.user.snapshot.UpdateSnapshotPolicyCmd;
public interface SnapshotApiService {
@ -50,7 +52,7 @@ public interface SnapshotApiService {
* @param snapshotId
* TODO
*/
boolean deleteSnapshot(long snapshotId);
boolean deleteSnapshot(long snapshotId, Long zoneId);
/**
* Creates a policy with specified schedule. maxSnaps specifies the number of most recent snapshots that are to be
@ -88,7 +90,7 @@ public interface SnapshotApiService {
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) throws ResourceAllocationException;
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, Boolean isFromVmSnapshot)
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, Boolean isFromVmSnapshot, List<Long> zoneIds)
throws ResourceAllocationException;
@ -124,4 +126,6 @@ public interface SnapshotApiService {
SnapshotPolicy updateSnapshotPolicy(UpdateSnapshotPolicyCmd updateSnapshotPolicyCmd);
void markVolumeSnapshotsAsDestroyed(Volume volume);
Snapshot copySnapshot(CopySnapshotCmd cmd) throws StorageUnavailableException, ResourceAllocationException;
}

View File

@ -76,8 +76,10 @@ public class ApiConstants {
public static final String CSR = "csr";
public static final String PRIVATE_KEY = "privatekey";
public static final String DATASTORE_HOST = "datastorehost";
public static final String DATASTORE_ID = "datastoreid";
public static final String DATASTORE_NAME = "datastorename";
public static final String DATASTORE_PATH = "datastorepath";
public static final String DATASTORE_STATE = "datastorestate";
public static final String DATASTORE_TYPE = "datastoretype";
public static final String DOMAIN_SUFFIX = "domainsuffix";
public static final String DNS_SEARCH_ORDER = "dnssearchorder";
@ -492,6 +494,7 @@ public class ApiConstants {
public static final String ZONE = "zone";
public static final String ZONE_ID = "zoneid";
public static final String ZONE_NAME = "zonename";
public static final String ZONE_WISE = "zonewise";
public static final String NETWORK_TYPE = "networktype";
public static final String PAGE = "page";
public static final String PAGE_SIZE = "pagesize";

View File

@ -0,0 +1,181 @@
// 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 java.util.ArrayList;
import java.util.List;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
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.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import com.cloud.dc.DataCenter;
import com.cloud.event.EventTypes;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.StorageUnavailableException;
import com.cloud.storage.Snapshot;
import com.cloud.user.Account;
@APICommand(name = "copySnapshot", description = "Copies a snapshot from one zone to another.",
responseObject = SnapshotResponse.class, responseView = ResponseObject.ResponseView.Restricted,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class CopySnapshotCmd extends BaseAsyncCmd implements UserCmd {
public static final Logger s_logger = Logger.getLogger(CopySnapshotCmd.class.getName());
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID,
entityType = SnapshotResponse.class, required = true, description = "the ID of the snapshot.")
private Long id;
@Parameter(name = ApiConstants.SOURCE_ZONE_ID,
type = CommandType.UUID,
entityType = ZoneResponse.class,
description = "The ID of the zone in which the snapshot is currently present. " +
"If not specified then the zone of snapshot's volume will be used.")
private Long sourceZoneId;
@Parameter(name = ApiConstants.DESTINATION_ZONE_ID,
type = CommandType.UUID,
entityType = ZoneResponse.class,
required = false,
description = "The ID of the zone the snapshot is being copied to.")
protected Long destZoneId;
@Parameter(name = ApiConstants.DESTINATION_ZONE_ID_LIST,
type=CommandType.LIST,
collectionType = CommandType.UUID,
entityType = ZoneResponse.class,
required = false,
description = "A comma-separated list of IDs of the zones that the snapshot needs to be copied to. " +
"Specify this list if the snapshot needs to copied to multiple zones in one go. " +
"Do not specify destzoneid and destzoneids together, however one of them is required.")
protected List<Long> destZoneIds;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public Long getSourceZoneId() {
return sourceZoneId;
}
public List<Long> getDestinationZoneIds() {
if (destZoneIds != null && destZoneIds.size() != 0) {
return destZoneIds;
}
if (destZoneId != null) {
List < Long > destIds = new ArrayList<>();
destIds.add(destZoneId);
return destIds;
}
return null;
}
@Override
public String getEventType() {
return EventTypes.EVENT_SNAPSHOT_COPY;
}
@Override
public String getEventDescription() {
StringBuilder descBuilder = new StringBuilder();
if (getDestinationZoneIds() != null) {
for (Long destId : getDestinationZoneIds()) {
descBuilder.append(", ");
descBuilder.append(_uuidMgr.getUuid(DataCenter.class, destId));
}
if (descBuilder.length() > 0) {
descBuilder.deleteCharAt(0);
}
}
return "copying snapshot: " + _uuidMgr.getUuid(Snapshot.class, getId()) + ((descBuilder.length() > 0) ? " to zones: " + descBuilder.toString() : "");
}
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.Snapshot;
}
@Override
public Long getApiResourceId() {
return getId();
}
@Override
public long getEntityOwnerId() {
Snapshot snapshot = _entityMgr.findById(Snapshot.class, getId());
if (snapshot != null) {
return snapshot.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public void execute() throws ResourceUnavailableException {
try {
if (destZoneId == null && CollectionUtils.isEmpty(destZoneIds))
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Either destzoneid or destzoneids parameters have to be specified.");
if (destZoneId != null && CollectionUtils.isNotEmpty(destZoneIds))
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Both destzoneid and destzoneids cannot be specified at the same time.");
CallContext.current().setEventDetails(getEventDescription());
Snapshot snapshot = _snapshotService.copySnapshot(this);
if (snapshot != null) {
SnapshotResponse response = _queryService.listSnapshot(this);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to copy snapshot");
}
} catch (StorageUnavailableException ex) {
s_logger.warn("Exception: ", ex);
throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage());
} catch (ResourceAllocationException ex) {
s_logger.warn("Exception: ", ex);
throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage());
}
}
}

View File

@ -18,6 +18,7 @@ package org.apache.cloudstack.api.command.user.snapshot;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.api.APICommand;
@ -32,6 +33,7 @@ import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.SnapshotPolicyResponse;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.commons.collections.MapUtils;
import org.apache.log4j.Logger;
@ -90,6 +92,15 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
@Parameter(name = ApiConstants.TAGS, type = CommandType.MAP, description = "Map of tags (key/value pairs)")
private Map tags;
@Parameter(name = ApiConstants.ZONE_ID_LIST,
type=CommandType.LIST,
collectionType = CommandType.UUID,
entityType = ZoneResponse.class,
description = "A comma-separated list of IDs of the zones in which the snapshot will be made available. " +
"The snapshot will always be made available in the zone in which the volume is present.",
since = "4.19.0")
protected List<Long> zoneIds;
private String syncObjectType = BaseAsyncCmd.snapshotHostSyncObject;
// ///////////////////////////////////////////////////
@ -148,6 +159,10 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
return _snapshotService.getHostIdForSnapshotOperation(volume);
}
public List<Long> getZoneIds() {
return zoneIds;
}
// ///////////////////////////////////////////////////
// ///////////// API Implementation///////////////////
// ///////////////////////////////////////////////////
@ -196,7 +211,7 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
@Override
public void create() throws ResourceAllocationException {
Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType());
Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType(), getZoneIds());
if (snapshot != null) {
setEntityId(snapshot.getId());
setEntityUuid(snapshot.getUuid());
@ -210,7 +225,7 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
Snapshot snapshot;
try {
snapshot =
_volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType(), getAsyncBackup(), getTags());
_volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType(), getAsyncBackup(), getTags(), getZoneIds());
if (snapshot != null) {
SnapshotResponse response = _responseGenerator.createSnapshotResponse(snapshot);

View File

@ -186,7 +186,7 @@ public class CreateSnapshotFromVMSnapshotCmd extends BaseAsyncCreateCmd {
} finally {
if (snapshot == null) {
try {
_snapshotService.deleteSnapshot(getEntityId());
_snapshotService.deleteSnapshot(getEntityId(), null);
} catch (Exception e) {
s_logger.debug("Failed to clean failed snapshot" + getEntityId());
}

View File

@ -18,6 +18,7 @@ package org.apache.cloudstack.api.command.user.snapshot;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.acl.RoleType;
@ -30,6 +31,7 @@ import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SnapshotPolicyResponse;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.commons.collections.MapUtils;
import org.apache.log4j.Logger;
@ -75,6 +77,14 @@ public class CreateSnapshotPolicyCmd extends BaseCmd {
@Parameter(name = ApiConstants.TAGS, type = CommandType.MAP, description = "Map of tags (key/value pairs)")
private Map tags;
@Parameter(name = ApiConstants.ZONE_ID_LIST,
type=CommandType.LIST,
collectionType = CommandType.UUID,
entityType = ZoneResponse.class,
description = "A list of IDs of the zones in which the snapshots will be made available." +
"The snapshots will always be made available in the zone in which the volume is present.")
protected List<Long> zoneIds;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -107,6 +117,10 @@ public class CreateSnapshotPolicyCmd extends BaseCmd {
return display;
}
public List<Long> getZoneIds() {
return zoneIds;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -16,6 +16,7 @@
// under the License.
package org.apache.cloudstack.api.command.user.snapshot;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.log4j.Logger;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
@ -48,6 +49,10 @@ public class DeleteSnapshotCmd extends BaseAsyncCmd {
@Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType = SnapshotResponse.class,
required=true, description="The ID of the snapshot")
private Long id;
@Parameter(name=ApiConstants.ZONE_ID, type=CommandType.UUID, entityType = ZoneResponse.class,
description="The ID of the zone for the snapshot", since = "4.19.0")
private Long zoneId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
@ -57,6 +62,10 @@ public class DeleteSnapshotCmd extends BaseAsyncCmd {
return id;
}
public Long getZoneId() {
return zoneId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@ -94,7 +103,7 @@ public class DeleteSnapshotCmd extends BaseAsyncCmd {
@Override
public void execute() {
CallContext.current().setEventDetails("Snapshot Id: " + this._uuidMgr.getUuid(Snapshot.class, getId()));
boolean result = _snapshotService.deleteSnapshot(getId());
boolean result = _snapshotService.deleteSnapshot(getId(), getZoneId());
if (result) {
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);

View File

@ -16,11 +16,8 @@
// under the License.
package org.apache.cloudstack.api.command.user.snapshot;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
@ -30,9 +27,9 @@ import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.log4j.Logger;
import com.cloud.storage.Snapshot;
import com.cloud.utils.Pair;
@APICommand(name = "listSnapshots", description = "Lists all available snapshots for the account.", responseObject = SnapshotResponse.class, entityType = {
Snapshot.class }, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@ -65,6 +62,13 @@ public class ListSnapshotsCmd extends BaseListTaggedResourcesCmd {
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "list snapshots by zone id")
private Long zoneId;
@Parameter(name = ApiConstants.SHOW_UNIQUE, type = CommandType.BOOLEAN, description = "If set to false, list templates across zones and their storages", since = "4.19.0")
private Boolean showUnique;
@Parameter(name = ApiConstants.LOCATION_TYPE, type = CommandType.STRING, description = "list snapshots by location type. Used only when showunique=false. " +
"Valid location types: 'primary', 'secondary'. Default is empty", since = "4.19.0")
private String locationType;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -93,6 +97,20 @@ public class ListSnapshotsCmd extends BaseListTaggedResourcesCmd {
return zoneId;
}
public boolean isShowUnique() {
if (Boolean.FALSE.equals(showUnique)) {
return false;
}
return true;
}
public String getLocationType() {
if (!isShowUnique()) {
return locationType;
}
return null;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@ -104,15 +122,7 @@ public class ListSnapshotsCmd extends BaseListTaggedResourcesCmd {
@Override
public void execute() {
Pair<List<? extends Snapshot>, Integer> result = _snapshotService.listSnapshots(this);
ListResponse<SnapshotResponse> response = new ListResponse<SnapshotResponse>();
List<SnapshotResponse> snapshotResponses = new ArrayList<SnapshotResponse>();
for (Snapshot snapshot : result.first()) {
SnapshotResponse snapshotResponse = _responseGenerator.createSnapshotResponse(snapshot);
snapshotResponse.setObjectName("snapshot");
snapshotResponses.add(snapshotResponse);
}
response.setResponses(snapshotResponses, result.second());
ListResponse<SnapshotResponse> response = _queryService.listSnapshots(this);
response.setResponseName(getCommandName());
setResponseObject(response);

View File

@ -29,6 +29,7 @@ import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
@ -135,6 +136,9 @@ public class CreateTemplateCmd extends BaseAsyncCreateCmd implements UserCmd {
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "create template for the project")
private Long projectId;
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the zone for the template. Can be specified with snapshot only", since = "4.19.0")
private Long zoneId;
// ///////////////////////////////////////////////////
// ///////////////// Accessors ///////////////////////
// ///////////////////////////////////////////////////
@ -209,6 +213,10 @@ public class CreateTemplateCmd extends BaseAsyncCreateCmd implements UserCmd {
return isDynamicallyScalable == null ? false : isDynamicallyScalable;
}
public Long getZoneId() {
return zoneId;
}
// ///////////////////////////////////////////////////
// ///////////// API Implementation///////////////////
// ///////////////////////////////////////////////////

View File

@ -16,10 +16,9 @@
// under the License.
package org.apache.cloudstack.api.command.user.zone;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
@ -30,6 +29,7 @@ import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.log4j.Logger;
@APICommand(name = "listZones", description = "Lists zones", responseObject = ZoneResponse.class, responseView = ResponseView.Restricted,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@ -44,6 +44,9 @@ public class ListZonesCmd extends BaseListCmd implements UserCmd {
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the ID of the zone")
private Long id;
@Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = ZoneResponse.class, description = "the IDs of the zones, mutually exclusive with id", since = "4.19.0")
private List<Long> ids;
@Parameter(name = ApiConstants.AVAILABLE,
type = CommandType.BOOLEAN,
description = "true if you want to retrieve all available Zones. False if you only want to return the Zones"
@ -76,6 +79,10 @@ public class ListZonesCmd extends BaseListCmd implements UserCmd {
return id;
}
public List<Long> getIds() {
return ids;
}
public Boolean isAvailable() {
return available;
}

View File

@ -58,8 +58,13 @@ public class SnapshotPolicyResponse extends BaseResponseWithTagInformation {
@Param(description = "is this policy for display to the regular user", since = "4.4", authorized = {RoleType.Admin})
private Boolean forDisplay;
@SerializedName(ApiConstants.ZONE)
@Param(description = "The list of zones in which snapshot backup is scheduled", responseObject = ZoneResponse.class, since = "4.19.0")
protected Set<ZoneResponse> zones;
public SnapshotPolicyResponse() {
tags = new LinkedHashSet<ResourceTagResponse>();
zones = new LinkedHashSet<>();
}
public String getId() {
@ -121,4 +126,8 @@ public class SnapshotPolicyResponse extends BaseResponseWithTagInformation {
public void setTags(Set<ResourceTagResponse> tags) {
this.tags = tags;
}
public void setZones(Set<ZoneResponse> zones) {
this.zones = zones;
}
}

View File

@ -18,6 +18,7 @@ package org.apache.cloudstack.api.response;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.apache.cloudstack.api.ApiConstants;
@ -29,7 +30,7 @@ import com.cloud.storage.Snapshot;
import com.google.gson.annotations.SerializedName;
@EntityReference(value = Snapshot.class)
public class SnapshotResponse extends BaseResponseWithTagInformation implements ControlledEntityResponse {
public class SnapshotResponse extends BaseResponseWithTagInformation implements ControlledViewEntityResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "ID of the snapshot")
private String id;
@ -90,6 +91,10 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements
@Param(description = "the state of the snapshot. BackedUp means that snapshot is ready to be used; Creating - the snapshot is being allocated on the primary storage; BackingUp - the snapshot is being backed up on secondary storage")
private Snapshot.State state;
@SerializedName(ApiConstants.STATUS)
@Param(description = "the status of the template")
private String status;
@SerializedName(ApiConstants.PHYSICAL_SIZE)
@Param(description = "physical size of backedup snapshot on image store")
private long physicalSize;
@ -98,6 +103,10 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements
@Param(description = "id of the availability zone")
private String zoneId;
@SerializedName(ApiConstants.ZONE_NAME)
@Param(description = "name of the availability zone")
private String zoneName;
@SerializedName(ApiConstants.REVERTABLE)
@Param(description = "indicates whether the underlying storage supports reverting the volume to this snapshot")
private boolean revertable;
@ -114,6 +123,26 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements
@Param(description = "virtual size of backedup snapshot on image store")
private long virtualSize;
@SerializedName(ApiConstants.DATASTORE_ID)
@Param(description = "ID of the datastore for the snapshot entry", since = "4.19.0")
private String datastoreId;
@SerializedName(ApiConstants.DATASTORE_NAME)
@Param(description = "name of the datastore for the snapshot entry", since = "4.19.0")
private String datastoreName;
@SerializedName(ApiConstants.DATASTORE_STATE)
@Param(description = "state of the snapshot on the datastore", since = "4.19.0")
private String datastoreState;
@SerializedName(ApiConstants.DATASTORE_TYPE)
@Param(description = "type of the datastore for the snapshot entry", since = "4.19.0")
private String datastoreType;
@SerializedName(ApiConstants.DOWNLOAD_DETAILS)
@Param(description = "download progress of a snapshot", since = "4.19.0")
private Map<String, String> downloadDetails;
public SnapshotResponse() {
tags = new LinkedHashSet<ResourceTagResponse>();
}
@ -190,7 +219,11 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements
this.state = state;
}
public void setPhysicaSize(long physicalSize) {
public void setStatus(String status) {
this.status = status;
}
public void setPhysicalSize(long physicalSize) {
this.physicalSize = physicalSize;
}
@ -208,6 +241,10 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements
this.zoneId = zoneId;
}
public void setZoneName(String zoneName) {
this.zoneName = zoneName;
}
public void setTags(Set<ResourceTagResponse> tags) {
this.tags = tags;
}
@ -231,4 +268,24 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements
public void setVirtualSize(long virtualSize) {
this.virtualSize = virtualSize;
}
public void setDatastoreId(String datastoreId) {
this.datastoreId = datastoreId;
}
public void setDatastoreName(String datastoreName) {
this.datastoreName = datastoreName;
}
public void setDatastoreState(String datastoreState) {
this.datastoreState = datastoreState;
}
public void setDatastoreType(String datastoreType) {
this.datastoreType = datastoreType;
}
public void setDownloadDetails(Map<String, String> downloadDetails) {
this.downloadDetails = downloadDetails;
}
}

View File

@ -95,7 +95,7 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso
@SerializedName("securitygroupsenabled")
@Param(description = "true if security groups support is enabled, false otherwise")
private boolean securityGroupsEnabled;
private Boolean securityGroupsEnabled;
@SerializedName("allocationstate")
@Param(description = "the allocation state of the cluster")
@ -115,7 +115,7 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso
@SerializedName(ApiConstants.LOCAL_STORAGE_ENABLED)
@Param(description = "true if local storage offering enabled, false otherwise")
private boolean localStorageEnabled;
private Boolean localStorageEnabled;
@SerializedName(ApiConstants.TAGS)
@Param(description = "the list of resource tags associated with zone.", responseObject = ResourceTagResponse.class, since = "4.3")
@ -131,7 +131,7 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso
@SerializedName(ApiConstants.ALLOW_USER_SPECIFY_VR_MTU)
@Param(description = "Allow end users to specify VR MTU", since = "4.18.0")
private boolean allowUserSpecifyVRMtu;
private Boolean allowUserSpecifyVRMtu;
@SerializedName(ApiConstants.ROUTER_PRIVATE_INTERFACE_MAX_MTU)
@Param(description = "The maximum value the MTU can have on the VR's private interfaces", since = "4.18.0")
@ -197,7 +197,7 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso
this.networkType = networkType;
}
public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) {
public void setSecurityGroupsEnabled(Boolean securityGroupsEnabled) {
this.securityGroupsEnabled = securityGroupsEnabled;
}
@ -221,7 +221,7 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso
this.domainName = domainName;
}
public void setLocalStorageEnabled(boolean localStorageEnabled) {
public void setLocalStorageEnabled(Boolean localStorageEnabled) {
this.localStorageEnabled = localStorageEnabled;
}
@ -241,6 +241,10 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso
this.ip6Dns2 = ip6Dns2;
}
public void setTags(Set<ResourceTagResponse> tags) {
this.tags = tags;
}
public void addTag(ResourceTagResponse tag) {
this.tags.add(tag);
}
@ -345,7 +349,7 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso
return resourceIconResponse;
}
public void setAllowUserSpecifyVRMtu(boolean allowUserSpecifyVRMtu) {
public void setAllowUserSpecifyVRMtu(Boolean allowUserSpecifyVRMtu) {
this.allowUserSpecifyVRMtu = allowUserSpecifyVRMtu;
}

View File

@ -44,6 +44,8 @@ import org.apache.cloudstack.api.command.user.project.ListProjectInvitationsCmd;
import org.apache.cloudstack.api.command.user.project.ListProjectsCmd;
import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd;
import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd;
import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd;
import org.apache.cloudstack.api.command.user.tag.ListTagsCmd;
import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMsCmd;
@ -73,6 +75,7 @@ import org.apache.cloudstack.api.response.ResourceTagResponse;
import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
import org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.StorageTagResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
@ -179,4 +182,8 @@ public interface QueryService {
ListResponse<ManagementServerResponse> listManagementServers(ListMgmtsCmd cmd);
List<RouterHealthCheckResultResponse> listRouterHealthChecks(GetRouterHealthCheckResultsCmd cmd);
ListResponse<SnapshotResponse> listSnapshots(ListSnapshotsCmd cmd);
SnapshotResponse listSnapshot(CopySnapshotCmd cmd);
}

View File

@ -23,6 +23,7 @@ import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.isNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.api.ResponseGenerator;
@ -92,7 +93,7 @@ public class CreateSnapshotCmdTest extends TestCase {
Snapshot snapshot = Mockito.mock(Snapshot.class);
try {
Mockito.when(volumeApiService.takeSnapshot(nullable(Long.class), nullable(Long.class), isNull(),
nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), nullable(Map.class))).thenReturn(snapshot);
nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), nullable(Map.class), nullable(List.class))).thenReturn(snapshot);
} catch (Exception e) {
Assert.fail("Received exception when success expected " + e.getMessage());
@ -125,7 +126,7 @@ public class CreateSnapshotCmdTest extends TestCase {
try {
Mockito.when(volumeApiService.takeSnapshot(nullable(Long.class), nullable(Long.class), nullable(Long.class),
nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), anyObject())).thenReturn(null);
nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), anyObject(), Mockito.anyList())).thenReturn(null);
} catch (Exception e) {
Assert.fail("Received exception when success expected " + e.getMessage());
}
@ -159,4 +160,14 @@ public class CreateSnapshotCmdTest extends TestCase {
ReflectionTestUtils.setField(createSnapshotCmd, "tags", tagsParams);
Assert.assertEquals(createSnapshotCmd.getTags(), expectedTags);
}
@Test
public void testGetZoneIds() {
final CreateSnapshotCmd cmd = new CreateSnapshotCmd();
List<Long> ids = List.of(400L, 500L);
ReflectionTestUtils.setField(cmd, "zoneIds", ids);
Assert.assertEquals(ids.size(), cmd.getZoneIds().size());
Assert.assertEquals(ids.get(0), cmd.getZoneIds().get(0));
Assert.assertEquals(ids.get(1), cmd.getZoneIds().get(1));
}
}

View File

@ -0,0 +1,133 @@
// 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 java.util.List;
import java.util.UUID;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.query.QueryService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.dc.DataCenter;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.storage.Snapshot;
import com.cloud.storage.snapshot.SnapshotApiService;
import com.cloud.utils.db.UUIDManager;
@RunWith(MockitoJUnitRunner.class)
public class CopySnapshotCmdTest {
@Test
public void testGetId() {
final CopySnapshotCmd cmd = new CopySnapshotCmd();
Long id = 100L;
ReflectionTestUtils.setField(cmd, "id", id);
Assert.assertEquals(id, cmd.getId());
}
@Test
public void testGetSourceZoneId() {
final CopySnapshotCmd cmd = new CopySnapshotCmd();
Long id = 200L;
ReflectionTestUtils.setField(cmd, "sourceZoneId", id);
Assert.assertEquals(id, cmd.getSourceZoneId());
}
@Test
public void testGetDestZoneIdWithSingleId() {
final CopySnapshotCmd cmd = new CopySnapshotCmd();
Long id = 300L;
ReflectionTestUtils.setField(cmd, "destZoneId", id);
Assert.assertEquals(1, cmd.getDestinationZoneIds().size());
Assert.assertEquals(id, cmd.getDestinationZoneIds().get(0));
}
@Test
public void testGetDestZoneIdWithMultipleId() {
final CopySnapshotCmd cmd = new CopySnapshotCmd();
List<Long> ids = List.of(400L, 500L);
ReflectionTestUtils.setField(cmd, "destZoneIds", ids);
Assert.assertEquals(ids.size(), cmd.getDestinationZoneIds().size());
Assert.assertEquals(ids.get(0), cmd.getDestinationZoneIds().get(0));
Assert.assertEquals(ids.get(1), cmd.getDestinationZoneIds().get(1));
}
@Test
public void testGetDestZoneIdWithBothParams() {
final CopySnapshotCmd cmd = new CopySnapshotCmd();
List<Long> ids = List.of(400L, 500L);
ReflectionTestUtils.setField(cmd, "destZoneIds", ids);
ReflectionTestUtils.setField(cmd, "destZoneId", 100L);
Assert.assertEquals(ids.size(), cmd.getDestinationZoneIds().size());
Assert.assertEquals(ids.get(0), cmd.getDestinationZoneIds().get(0));
Assert.assertEquals(ids.get(1), cmd.getDestinationZoneIds().get(1));
}
@Test (expected = ServerApiException.class)
public void testExecuteWrongNoParams() {
final CopySnapshotCmd cmd = new CopySnapshotCmd();
try {
cmd.execute();
} catch (ResourceUnavailableException e) {
Assert.fail(String.format("Exception: %s", e.getMessage()));
}
}
@Test (expected = ServerApiException.class)
public void testExecuteWrongBothParams() {
final CopySnapshotCmd cmd = new CopySnapshotCmd();
List<Long> ids = List.of(400L, 500L);
ReflectionTestUtils.setField(cmd, "destZoneIds", ids);
ReflectionTestUtils.setField(cmd, "destZoneId", 100L);
try {
cmd.execute();
} catch (ResourceUnavailableException e) {
Assert.fail(String.format("Exception: %s", e.getMessage()));
}
}
@Test
public void testExecuteSuccess() {
SnapshotApiService snapshotApiService = Mockito.mock(SnapshotApiService.class);
QueryService queryService = Mockito.mock(QueryService.class);
UUIDManager uuidManager = Mockito.mock(UUIDManager.class);
final CopySnapshotCmd cmd = new CopySnapshotCmd();
cmd._snapshotService = snapshotApiService;
cmd._queryService = queryService;
cmd._uuidMgr = uuidManager;
Snapshot snapshot = Mockito.mock(Snapshot.class);
final Long id = 100L;
ReflectionTestUtils.setField(cmd, "destZoneId", id);
SnapshotResponse snapshotResponse = Mockito.mock(SnapshotResponse.class);
try {
Mockito.when(snapshotApiService.copySnapshot(cmd)).thenReturn(snapshot);
Mockito.when(queryService.listSnapshot(cmd)).thenReturn(snapshotResponse);
Mockito.when(uuidManager.getUuid(DataCenter.class, id)).thenReturn(UUID.randomUUID().toString());
cmd.execute();
} catch (ResourceAllocationException | ResourceUnavailableException e) {
Assert.fail(String.format("Exception: %s", e.getMessage()));
}
}
}

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.api.command.user.snapshot;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
@ -43,4 +44,14 @@ public class CreateSnapshotPolicyCmdTest {
ReflectionTestUtils.setField(createSnapshotPolicyCmd, "tags", tagsParams);
Assert.assertEquals(createSnapshotPolicyCmd.getTags(), expectedTags);
}
@Test
public void testGetZoneIds() {
final CreateSnapshotPolicyCmd cmd = new CreateSnapshotPolicyCmd();
List<Long> ids = List.of(400L, 500L);
ReflectionTestUtils.setField(cmd, "zoneIds", ids);
Assert.assertEquals(ids.size(), cmd.getZoneIds().size());
Assert.assertEquals(ids.get(0), cmd.getZoneIds().get(0));
Assert.assertEquals(ids.get(1), cmd.getZoneIds().get(1));
}
}

View File

@ -0,0 +1,32 @@
// 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 org.junit.Assert;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;
public class DeleteSnapshotCmdTest {
@Test
public void testGetZoneId() {
final DeleteSnapshotCmd cmd = new DeleteSnapshotCmd();
Long id = 400L;
ReflectionTestUtils.setField(cmd, "zoneId", id);
Assert.assertEquals(id, cmd.getZoneId());
}
}

View File

@ -0,0 +1,60 @@
// 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 org.junit.Assert;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;
public class ListSnapshotsCmdTest {
@Test
public void testIsShowUniqueNoValue() {
final ListSnapshotsCmd cmd = new ListSnapshotsCmd();
Assert.assertTrue(cmd.isShowUnique());
}
@Test
public void testIsShowUniqueFalse() {
final ListSnapshotsCmd cmd = new ListSnapshotsCmd();
ReflectionTestUtils.setField(cmd, "showUnique", false);
Assert.assertFalse(cmd.isShowUnique());
}
@Test
public void testIsShowUniqueTrue() {
final ListSnapshotsCmd cmd = new ListSnapshotsCmd();
ReflectionTestUtils.setField(cmd, "showUnique", true);
Assert.assertTrue(cmd.isShowUnique());
}
@Test
public void testGetLocationTypeNoUnique() {
final ListSnapshotsCmd cmd = new ListSnapshotsCmd();
ReflectionTestUtils.setField(cmd, "locationType", "primary");
Assert.assertNull(cmd.getLocationType());
}
@Test
public void testGetLocationTypeUnique() {
final ListSnapshotsCmd cmd = new ListSnapshotsCmd();
ReflectionTestUtils.setField(cmd, "showUnique", false);
String value = "secondary";
ReflectionTestUtils.setField(cmd, "locationType", value);
Assert.assertEquals(value, cmd.getLocationType());
}
}

View File

@ -0,0 +1,32 @@
// 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.template;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;
public class CreateTemplateCmdTest {
@Test
public void testGetZoneId() {
final CreateTemplateCmd cmd = new CreateTemplateCmd();
Long id = 400L;
ReflectionTestUtils.setField(cmd, "zoneId", id);
Assert.assertEquals(id, cmd.getZoneId());
}
}

View File

@ -32,12 +32,20 @@ import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import com.cloud.utils.HumanReadableJson;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.BadCommand;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.SecStorageFirewallCfgCommand.PortConfig;
import com.cloud.exception.UnsupportedVersionException;
import com.cloud.serializer.GsonHelper;
import com.cloud.utils.HumanReadableJson;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
@ -49,16 +57,6 @@ import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.stream.JsonReader;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.BadCommand;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.SecStorageFirewallCfgCommand.PortConfig;
import com.cloud.exception.UnsupportedVersionException;
import com.cloud.serializer.GsonHelper;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
/**
* Request is a simple wrapper around command and answer to add sequencing,
* versioning, and flags. Note that the version here represents the changes
@ -253,6 +251,7 @@ public class Request {
jsonReader.setLenient(true);
_cmds = s_gson.fromJson(jsonReader, (Type)Command[].class);
} catch (JsonParseException e) {
s_logger.error("Caught problem while parsing JSON command " + _content, e);
_cmds = new Command[] { new BadCommand() };
} catch (RuntimeException e) {
s_logger.error("Caught problem with " + _content, e);

View File

@ -19,23 +19,23 @@
package com.cloud.storage.resource;
import com.cloud.serializer.GsonHelper;
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplainceCommand;
import org.apache.log4j.Logger;
import org.apache.cloudstack.storage.command.AttachCommand;
import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplainceCommand;
import org.apache.cloudstack.storage.command.CopyCommand;
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
import org.apache.cloudstack.storage.command.CreateObjectCommand;
import org.apache.cloudstack.storage.command.DeleteCommand;
import org.apache.cloudstack.storage.command.DettachCommand;
import org.apache.cloudstack.storage.command.IntroduceObjectCmd;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
import org.apache.cloudstack.storage.command.ResignatureCommand;
import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
@ -43,6 +43,7 @@ import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.DiskTO;
import com.cloud.serializer.GsonHelper;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Volume;
import com.google.gson.Gson;
@ -81,6 +82,8 @@ public class StorageSubsystemCommandHandlerBase implements StorageSubsystemComma
return processor.checkDataStoreStoragePolicyCompliance((CheckDataStoreStoragePolicyComplainceCommand) command);
} else if (command instanceof SyncVolumePathCommand) {
return processor.syncVolumePath((SyncVolumePathCommand) command);
} else if (command instanceof QuerySnapshotZoneCopyCommand) {
return execute((QuerySnapshotZoneCopyCommand)command);
}
return new Answer((Command)command, false, "not implemented yet");
@ -175,6 +178,10 @@ public class StorageSubsystemCommandHandlerBase implements StorageSubsystemComma
}
}
protected Answer execute(QuerySnapshotZoneCopyCommand cmd) {
return new QuerySnapshotZoneCopyAnswer(cmd, "Unsupported command");
}
private void logCommand(Command cmd) {
try {
s_logger.debug(String.format("Executing command %s: [%s].", cmd.getClass().getSimpleName(), s_gogger.toJson(cmd)));

View File

@ -19,6 +19,8 @@
package com.cloud.storage.template;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -27,7 +29,8 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType;
import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.Header;
@ -44,16 +47,12 @@ import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.log4j.Logger;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType;
import com.cloud.storage.StorageLayer;
import com.cloud.utils.Pair;
import com.cloud.utils.UriUtils;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.Proxy;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
/**
* Download a template file using HTTP
*
@ -247,7 +246,9 @@ public class HttpTemplateDownloader extends ManagedContextRunnable implements Te
while (!done && status != Status.ABORTED && offset <= remoteSize) {
if ((bytes = in.read(block, 0, CHUNK_SIZE)) > -1) {
offset = writeBlock(bytes, out, block, offset);
if (!verifyFormat.isVerifiedFormat() && (offset >= 1048576 || offset >= remoteSize)) { //let's check format after we get 1MB or full file
if (!ResourceType.SNAPSHOT.equals(resourceType) &&
!verifyFormat.isVerifiedFormat() &&
(offset >= 1048576 || offset >= remoteSize)) { //let's check format after we get 1MB or full file
verifyFormat.invoke();
}
} else {

View File

@ -0,0 +1,481 @@
// 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 com.cloud.storage.template;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.storage.command.DownloadCommand;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NoHttpResponseException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.cloud.storage.StorageLayer;
public class SimpleHttpMultiFileDownloader extends ManagedContextRunnable implements TemplateDownloader {
public static final Logger s_logger = Logger.getLogger(SimpleHttpMultiFileDownloader.class.getName());
private static final MultiThreadedHttpConnectionManager s_httpClientManager = new MultiThreadedHttpConnectionManager();
private static final int CHUNK_SIZE = 1024 * 1024; //1M
private String[] downloadUrls;
private String currentToFile;
public TemplateDownloader.Status currentStatus;
public TemplateDownloader.Status status;
private String errorString = null;
private long totalRemoteSize = 0;
private long currentRemoteSize = 0;
public long downloadTime = 0;
public long currentTotalBytes;
public long totalBytes = 0;
private final HttpClient client;
private GetMethod request;
private boolean resume = false;
private DownloadCompleteCallback completionCallback;
StorageLayer _storage;
boolean inited = true;
private String toDir;
private long maxTemplateSizeInBytes;
private DownloadCommand.ResourceType resourceType = DownloadCommand.ResourceType.TEMPLATE;
private final HttpMethodRetryHandler retryHandler;
private HashMap<String, String> urlFileMap;
public SimpleHttpMultiFileDownloader(StorageLayer storageLayer, String[] downloadUrls, String toDir,
DownloadCompleteCallback callback, long maxTemplateSizeInBytes,
DownloadCommand.ResourceType resourceType) {
_storage = storageLayer;
this.downloadUrls = downloadUrls;
this.toDir = toDir;
this.resourceType = resourceType;
this.maxTemplateSizeInBytes = maxTemplateSizeInBytes;
completionCallback = callback;
status = TemplateDownloader.Status.NOT_STARTED;
currentStatus = TemplateDownloader.Status.NOT_STARTED;
currentTotalBytes = 0;
client = new HttpClient(s_httpClientManager);
retryHandler = createRetryTwiceHandler();
urlFileMap = new HashMap<>();
}
private GetMethod createRequest(String downloadUrl) {
GetMethod request = new GetMethod(downloadUrl);
request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, retryHandler);
request.setFollowRedirects(true);
return request;
}
private void checkTemporaryDestination(String toDir) {
try {
File f = File.createTempFile("dnld", "tmp_", new File(toDir));
if (_storage != null) {
_storage.setWorldReadableAndWriteable(f);
}
currentToFile = f.getAbsolutePath();
} catch (IOException ex) {
errorString = "Unable to start download -- check url? ";
currentStatus = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
s_logger.warn("Exception in constructor -- " + ex.toString());
}
}
private HttpMethodRetryHandler createRetryTwiceHandler() {
return new HttpMethodRetryHandler() {
@Override
public boolean retryMethod(final HttpMethod method, final IOException exception, int executionCount) {
if (executionCount >= 2) {
// Do not retry if over max retry count
return false;
}
if (exception instanceof NoHttpResponseException) {
// Retry if the server dropped connection on us
return true;
}
if (!method.isRequestSent()) {
// Retry if the request has not been sent fully or
// if it's OK to retry methods that have been sent
return true;
}
// otherwise do not retry
return false;
}
};
}
private void tryAndGetTotalRemoteSize() {
for (String downloadUrl : downloadUrls) {
if (StringUtils.isBlank(downloadUrl)) {
continue;
}
HeadMethod headMethod = new HeadMethod(downloadUrl);
try {
if (client.executeMethod(headMethod) != HttpStatus.SC_OK) {
continue;
}
Header contentLengthHeader = headMethod.getResponseHeader("content-length");
if (contentLengthHeader == null) {
continue;
}
totalRemoteSize += Long.parseLong(contentLengthHeader.getValue());
} catch (IOException e) {
s_logger.warn(String.format("Cannot reach URL: %s while trying to get remote sizes due to: %s", downloadUrl, e.getMessage()), e);
} finally {
headMethod.releaseConnection();
}
}
}
private long downloadFile(String downloadUrl) {
s_logger.debug("Starting download for " + downloadUrl);
currentTotalBytes = 0;
currentRemoteSize = 0;
File file = null;
request = null;
try {
request = createRequest(downloadUrl);
checkTemporaryDestination(toDir);
urlFileMap.put(downloadUrl, currentToFile);
file = new File(currentToFile);
long localFileSize = checkLocalFileSizeForResume(resume, file);
if (checkServerResponse(localFileSize)) return 0;
if (!tryAndGetRemoteSize()) return 0;
if (!canHandleDownloadSize()) return 0;
checkAndSetDownloadSize();
try (InputStream in = request.getResponseBodyAsStream();
RandomAccessFile out = new RandomAccessFile(file, "rw");
) {
out.seek(localFileSize);
s_logger.info("Starting download from " + downloadUrl + " to " + currentToFile + " remoteSize=" + toHumanReadableSize(currentRemoteSize) + " , max size=" + toHumanReadableSize(maxTemplateSizeInBytes));
if (copyBytes(file, in, out)) return 0;
checkDownloadCompletion();
}
return currentTotalBytes;
} catch (HttpException hte) {
currentStatus = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
errorString = hte.getMessage();
} catch (IOException ioe) {
currentStatus = TemplateDownloader.Status.UNRECOVERABLE_ERROR; //probably a file write error?
// Let's not overwrite the original error message.
if (errorString == null) {
errorString = ioe.getMessage();
}
} finally {
if (currentStatus == Status.UNRECOVERABLE_ERROR && file != null && file.exists() && !file.isDirectory()) {
file.delete();
}
if (request != null) {
request.releaseConnection();
}
}
return 0;
}
@Override
public long download(boolean resume, DownloadCompleteCallback callback) {
if (skipDownloadOnStatus()) return 0;
if (resume) {
s_logger.error("Resume not allowed for this downloader");
status = Status.UNRECOVERABLE_ERROR;
return 0;
}
s_logger.debug("Starting downloads");
status = Status.IN_PROGRESS;
Date start = new Date();
tryAndGetTotalRemoteSize();
for (String downloadUrl : downloadUrls) {
if (StringUtils.isBlank(downloadUrl)) {
continue;
}
long bytes = downloadFile(downloadUrl);
if (currentStatus != Status.DOWNLOAD_FINISHED) {
break;
}
totalBytes += bytes;
}
status = currentStatus;
Date finish = new Date();
downloadTime += finish.getTime() - start.getTime();
if (callback != null) {
callback.downloadComplete(status);
}
return 0;
}
private boolean copyBytes(File file, InputStream in, RandomAccessFile out) throws IOException {
int bytes;
byte[] block = new byte[CHUNK_SIZE];
long offset = 0;
boolean done = false;
currentStatus = Status.IN_PROGRESS;
while (!done && currentStatus != Status.ABORTED && offset <= currentRemoteSize) {
if ((bytes = in.read(block, 0, CHUNK_SIZE)) > -1) {
offset = writeBlock(bytes, out, block, offset);
} else {
done = true;
}
}
out.getFD().sync();
return false;
}
private long writeBlock(int bytes, RandomAccessFile out, byte[] block, long offset) throws IOException {
out.write(block, 0, bytes);
offset += bytes;
out.seek(offset);
currentTotalBytes += bytes;
return offset;
}
private void checkDownloadCompletion() {
String downloaded = "(incomplete download)";
if (currentTotalBytes >= currentRemoteSize) {
currentStatus = Status.DOWNLOAD_FINISHED;
downloaded = "(download complete remote=" + toHumanReadableSize(currentRemoteSize) + " bytes)";
}
errorString = "Downloaded " + toHumanReadableSize(currentTotalBytes) + " bytes " + downloaded;
}
private boolean canHandleDownloadSize() {
if (currentRemoteSize > maxTemplateSizeInBytes) {
s_logger.info("Remote size is too large: " + toHumanReadableSize(currentRemoteSize) + " , max=" + toHumanReadableSize(maxTemplateSizeInBytes));
currentStatus = Status.UNRECOVERABLE_ERROR;
errorString = "Download file size is too large";
return false;
}
return true;
}
private void checkAndSetDownloadSize() {
if (currentRemoteSize == 0) {
currentRemoteSize = maxTemplateSizeInBytes;
}
if (totalRemoteSize == 0) {
totalRemoteSize = currentRemoteSize;
}
}
private boolean tryAndGetRemoteSize() {
Header contentLengthHeader = request.getResponseHeader("content-length");
boolean chunked = false;
long reportedRemoteSize = 0;
if (contentLengthHeader == null) {
Header chunkedHeader = request.getResponseHeader("Transfer-Encoding");
if (chunkedHeader == null || !"chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
currentStatus = Status.UNRECOVERABLE_ERROR;
errorString = " Failed to receive length of download ";
return false;
} else if ("chunked".equalsIgnoreCase(chunkedHeader.getValue())) {
chunked = true;
}
} else {
reportedRemoteSize = Long.parseLong(contentLengthHeader.getValue());
if (reportedRemoteSize == 0) {
currentStatus = Status.DOWNLOAD_FINISHED;
String downloaded = "(download complete remote=" + currentRemoteSize + "bytes)";
errorString = "Downloaded " + currentTotalBytes + " bytes " + downloaded;
downloadTime = 0;
return false;
}
}
if (currentRemoteSize == 0) {
currentRemoteSize = reportedRemoteSize;
}
return true;
}
private boolean checkServerResponse(long localFileSize) throws IOException {
int responseCode = 0;
if (localFileSize > 0) {
// require partial content support for resume
request.addRequestHeader("Range", "bytes=" + localFileSize + "-");
if (client.executeMethod(request) != HttpStatus.SC_PARTIAL_CONTENT) {
errorString = "HTTP Server does not support partial get";
currentStatus = Status.UNRECOVERABLE_ERROR;
return true;
}
} else if ((responseCode = client.executeMethod(request)) != HttpStatus.SC_OK) {
currentStatus = Status.UNRECOVERABLE_ERROR;
errorString = " HTTP Server returned " + responseCode + " (expected 200 OK) ";
return true; //FIXME: retry?
}
return false;
}
private long checkLocalFileSizeForResume(boolean resume, File file) {
// TODO check the status of this downloader as well?
long localFileSize = 0;
if (file.exists() && resume) {
localFileSize = file.length();
s_logger.info("Resuming download to file (current size)=" + toHumanReadableSize(localFileSize));
}
return localFileSize;
}
private boolean skipDownloadOnStatus() {
switch (currentStatus) {
case ABORTED:
case UNRECOVERABLE_ERROR:
case DOWNLOAD_FINISHED:
return true;
default:
}
return false;
}
public String[] getDownloadUrls() {
return downloadUrls;
}
public String getCurrentToFile() {
File file = new File(currentToFile);
return file.getAbsolutePath();
}
@Override
public TemplateDownloader.Status getStatus() {
return currentStatus;
}
@Override
public long getDownloadTime() {
return downloadTime;
}
@Override
public long getDownloadedBytes() {
return totalBytes;
}
@Override
@SuppressWarnings("fallthrough")
public boolean stopDownload() {
switch (getStatus()) {
case IN_PROGRESS:
if (request != null) {
request.abort();
}
currentStatus = TemplateDownloader.Status.ABORTED;
return true;
case UNKNOWN:
case NOT_STARTED:
case RECOVERABLE_ERROR:
case UNRECOVERABLE_ERROR:
case ABORTED:
currentStatus = TemplateDownloader.Status.ABORTED;
case DOWNLOAD_FINISHED:
File f = new File(currentToFile);
if (f.exists()) {
f.delete();
}
return true;
default:
return true;
}
}
@Override
public int getDownloadPercent() {
if (totalRemoteSize == 0) {
return 0;
}
return (int)(100.0 * totalBytes / totalRemoteSize);
}
@Override
protected void runInContext() {
try {
download(resume, completionCallback);
} catch (Throwable t) {
s_logger.warn("Caught exception during download " + t.getMessage(), t);
errorString = "Failed to install: " + t.getMessage();
currentStatus = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
}
}
@Override
public void setStatus(TemplateDownloader.Status status) {
this.currentStatus = status;
}
public boolean isResume() {
return resume;
}
@Override
public String getDownloadError() {
return errorString == null ? " " : errorString;
}
@Override
public String getDownloadLocalPath() {
return toDir;
}
@Override
public void setResume(boolean resume) {
this.resume = resume;
}
@Override
public long getMaxTemplateSizeInBytes() {
return maxTemplateSizeInBytes;
}
@Override
public void setDownloadError(String error) {
errorString = error;
}
@Override
public boolean isInited() {
return inited;
}
public DownloadCommand.ResourceType getResourceType() {
return resourceType;
}
public Map<String, String> getDownloadedFilesMap() {
return urlFileMap;
}
}

View File

@ -19,26 +19,25 @@
package com.cloud.storage.template;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Properties;
import java.util.Arrays;
import org.apache.log4j.Logger;
import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType;
import org.apache.log4j.Logger;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.StorageLayer;
import com.cloud.storage.template.Processor.FormatInfo;
import com.cloud.utils.NumbersUtil;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
public class TemplateLocation {
private static final Logger s_logger = Logger.getLogger(TemplateLocation.class);
public final static String Filename = "template.properties";
@ -65,6 +64,9 @@ public class TemplateLocation {
if (_templatePath.matches(".*" + "volumes" + ".*")) {
_file = _storage.getFile(_templatePath + "volume.properties");
_resourceType = ResourceType.VOLUME;
} else if (_templatePath.matches(".*" + "snapshots" + ".*")) {
_file = _storage.getFile(_templatePath + "snapshot.properties");
_resourceType = ResourceType.SNAPSHOT;
} else {
_file = _storage.getFile(_templatePath + Filename);
}
@ -170,6 +172,8 @@ public class TemplateLocation {
tmplInfo.installPath = _templatePath + _props.getProperty("filename"); // _templatePath endsWith /
if (_resourceType == ResourceType.VOLUME) {
tmplInfo.installPath = tmplInfo.installPath.substring(tmplInfo.installPath.indexOf("volumes"));
} else if (_resourceType == ResourceType.SNAPSHOT) {
tmplInfo.installPath = tmplInfo.installPath.substring(tmplInfo.installPath.indexOf("snapshots"));
} else {
tmplInfo.installPath = tmplInfo.installPath.substring(tmplInfo.installPath.indexOf("template"));
}

View File

@ -20,6 +20,7 @@
package org.apache.cloudstack.storage.command;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
@ -33,7 +34,7 @@ import com.cloud.storage.Storage.ImageFormat;
public class DownloadCommand extends AbstractDownloadCommand implements InternalIdentity {
public static enum ResourceType {
VOLUME, TEMPLATE
VOLUME, TEMPLATE, SNAPSHOT
}
private boolean hvm;
@ -96,6 +97,18 @@ public class DownloadCommand extends AbstractDownloadCommand implements Internal
resourceType = ResourceType.VOLUME;
}
public DownloadCommand(SnapshotObjectTO snapshot, Long maxDownloadSizeInBytes, String url) {
super(snapshot.getName(), url, null, snapshot.getAccountId());
_store = snapshot.getDataStore();
installPath = snapshot.getPath();
id = snapshot.getId();
if (_store instanceof NfsTO) {
setSecUrl(((NfsTO)_store).getUrl());
}
this.maxDownloadSizeInBytes = maxDownloadSizeInBytes;
this.resourceType = ResourceType.SNAPSHOT;
}
@Override
public long getId() {
return id;

View File

@ -0,0 +1,39 @@
// 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.storage.command;
import java.util.List;
import com.cloud.agent.api.Answer;
public class QuerySnapshotZoneCopyAnswer extends Answer {
private List<String> files;
public QuerySnapshotZoneCopyAnswer(QuerySnapshotZoneCopyCommand cmd, List<String> files) {
super(cmd);
this.files = files;
}
public QuerySnapshotZoneCopyAnswer(QuerySnapshotZoneCopyCommand cmd, String errMsg) {
super(null, false, errMsg);
}
public List<String> getFiles() {
return files;
}
}

View File

@ -0,0 +1,50 @@
// 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.storage.command;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
/*
Command to get the list of snapshot files for copying a snapshot to a different zone
*/
public class QuerySnapshotZoneCopyCommand extends StorageSubSystemCommand {
private SnapshotObjectTO snapshot;
public QuerySnapshotZoneCopyCommand(final SnapshotObjectTO snapshot) {
super();
this.snapshot = snapshot;
}
public SnapshotObjectTO getSnapshot() {
return snapshot;
}
public void setSnapshot(final SnapshotObjectTO snapshot) {
this.snapshot = snapshot;
}
@Override
public boolean executeInSequence() {
return false;
}
@Override
public void setExecuteInSequence(boolean inSeq) {}
}

View File

@ -42,6 +42,7 @@ public class SnapshotObjectTO implements DataTO {
private boolean quiescevm;
private String[] parents;
private Long physicalSize = (long) 0;
private long accountId;
public SnapshotObjectTO() {
@ -51,6 +52,7 @@ public class SnapshotObjectTO implements DataTO {
public SnapshotObjectTO(SnapshotInfo snapshot) {
this.path = snapshot.getPath();
this.setId(snapshot.getId());
this.accountId = snapshot.getAccountId();
VolumeInfo vol = snapshot.getBaseVolume();
if (vol != null) {
this.volume = (VolumeObjectTO)vol.getTO();
@ -168,6 +170,14 @@ public class SnapshotObjectTO implements DataTO {
return parents;
}
public long getAccountId() {
return accountId;
}
public void setAccountId(long accountId) {
this.accountId = accountId;
}
@Override
public String toString() {
return new StringBuilder("SnapshotTO[datastore=").append(dataStore).append("|volume=").append(volume).append("|path").append(path).append("]").toString();

View File

@ -0,0 +1,43 @@
// 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 com.cloud.storage.resource;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.agent.api.Answer;
@RunWith(MockitoJUnitRunner.class)
public class StorageSubsystemCommandHandlerBaseTest {
@Test
public void testHandleQuerySnapshotCommand() {
StorageSubsystemCommandHandlerBase storageSubsystemCommandHandlerBase = new StorageSubsystemCommandHandlerBase(Mockito.mock(StorageProcessor.class));
QuerySnapshotZoneCopyCommand querySnapshotZoneCopyCommand = new QuerySnapshotZoneCopyCommand(Mockito.mock(SnapshotObjectTO.class));
Answer answer = storageSubsystemCommandHandlerBase.handleStorageCommands(querySnapshotZoneCopyCommand);
Assert.assertTrue(answer instanceof QuerySnapshotZoneCopyAnswer);
QuerySnapshotZoneCopyAnswer querySnapshotZoneCopyAnswer = (QuerySnapshotZoneCopyAnswer)answer;
Assert.assertFalse(querySnapshotZoneCopyAnswer.getResult());
Assert.assertEquals("Unsupported command", querySnapshotZoneCopyAnswer.getDetails());
}
}

View File

@ -0,0 +1,36 @@
// 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.storage.command;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
public class DownloadCommandTest {
@Test
public void testDownloadCOmmandSnapshot() {
SnapshotObjectTO snapshotObjectTO = Mockito.mock(SnapshotObjectTO.class);
Long maxDownloadSizeInBytes = 1000L;
String url = "SOMEURL";
DownloadCommand cmd = new DownloadCommand(snapshotObjectTO, maxDownloadSizeInBytes, url);
Assert.assertEquals(DownloadCommand.ResourceType.SNAPSHOT, cmd.getResourceType());
Assert.assertEquals(maxDownloadSizeInBytes, cmd.getMaxDownloadSizeInBytes());
Assert.assertEquals(url, cmd.getUrl());
}
}

View File

@ -0,0 +1,46 @@
// 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.storage.command;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
public class QuerySnapshotZoneCopyAnswerTest {
@Test
public void testQuerySnapshotZoneCopyAnswerSuccess() {
QuerySnapshotZoneCopyCommand cmd = Mockito.mock(QuerySnapshotZoneCopyCommand.class);
List<String> files = List.of("File1", "File2");
QuerySnapshotZoneCopyAnswer answer = new QuerySnapshotZoneCopyAnswer(cmd, files);
Assert.assertTrue(answer.getResult());
Assert.assertEquals(files.size(), answer.getFiles().size());
Assert.assertEquals(files.get(0), answer.getFiles().get(0));
Assert.assertEquals(files.get(1), answer.getFiles().get(1));
}
@Test
public void testQuerySnapshotZoneCopyAnswerFailure() {
QuerySnapshotZoneCopyCommand cmd = Mockito.mock(QuerySnapshotZoneCopyCommand.class);
String err = "SOMEERROR";
QuerySnapshotZoneCopyAnswer answer = new QuerySnapshotZoneCopyAnswer(cmd, err);
Assert.assertFalse(answer.getResult());
Assert.assertEquals(err, answer.getDetails());
}
}

View File

@ -0,0 +1,43 @@
// 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.storage.to;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;
public class SnapshotObjectTOTest {
@Test
public void testAccountId() {
SnapshotObjectTO obj = new SnapshotObjectTO();
long accountId = 1L;
ReflectionTestUtils.setField(obj, "accountId", accountId);
Assert.assertEquals(accountId, obj.getAccountId());
accountId = 100L;
obj.setAccountId(accountId);
Assert.assertEquals(accountId, obj.getAccountId());
SnapshotInfo snapshot = Mockito.mock(SnapshotInfo.class);
Mockito.when(snapshot.getAccountId()).thenReturn(accountId);
Mockito.when(snapshot.getDataStore()).thenReturn(Mockito.mock(DataStore.class));
SnapshotObjectTO object = new SnapshotObjectTO(snapshot);
Assert.assertEquals(accountId, object.getAccountId());
}
}

View File

@ -54,4 +54,6 @@ public interface DataStoreManager {
List<DataStore> listImageCacheStores();
boolean isRegionStore(DataStore store);
Long getStoreZoneId(long storeId, DataStoreRole role);
}

View File

@ -27,11 +27,17 @@ public interface SnapshotDataFactory {
SnapshotInfo getSnapshot(DataObject obj, DataStore store);
SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role);
SnapshotInfo getSnapshot(long snapshotId, long storeId, DataStoreRole role);
SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role, boolean retrieveAnySnapshotFromVolume);
SnapshotInfo getSnapshotWithRoleAndZone(long snapshotId, DataStoreRole role, long zoneId);
List<SnapshotInfo> getSnapshots(long volumeId, DataStoreRole store);
SnapshotInfo getSnapshotOnPrimaryStore(long snapshotId);
SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role, long zoneId, boolean retrieveAnySnapshotFromVolume);
List<SnapshotInfo> getSnapshotsForVolumeAndStoreRole(long volumeId, DataStoreRole store);
List<SnapshotInfo> getSnapshots(long snapshotId, Long zoneId);
List<SnapshotInfo> listSnapshotOnCache(long snapshotId);

View File

@ -57,4 +57,6 @@ public interface SnapshotInfo extends DataObject, Snapshot {
void markBackedUp() throws CloudRuntimeException;
Snapshot getSnapshotVO();
long getAccountId();
}

View File

@ -17,6 +17,9 @@
package org.apache.cloudstack.engine.subsystem.api.storage;
import org.apache.cloudstack.framework.async.AsyncCallFuture;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.storage.Snapshot.Event;
public interface SnapshotService {
@ -35,4 +38,8 @@ public interface SnapshotService {
void processEventOnSnapshotObject(SnapshotInfo snapshot, Event event);
void cleanupOnSnapshotBackupFailure(SnapshotInfo snapshot);
AsyncCallFuture<SnapshotResult> copySnapshot(SnapshotInfo snapshot, String copyUrl, DataStore dataStore) throws ResourceUnavailableException;
AsyncCallFuture<CreateCmdResult> queryCopySnapshot(SnapshotInfo snapshot) throws ResourceUnavailableException;
}

View File

@ -28,11 +28,11 @@ public interface SnapshotStrategy {
SnapshotInfo backupSnapshot(SnapshotInfo snapshot);
boolean deleteSnapshot(Long snapshotId);
boolean deleteSnapshot(Long snapshotId, Long zoneId);
boolean revertSnapshot(SnapshotInfo snapshot);
StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op);
StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op);
void postSnapshotCreation(SnapshotInfo snapshot);
}

View File

@ -34,6 +34,8 @@ public interface StorageStrategyFactory {
SnapshotStrategy getSnapshotStrategy(Snapshot snapshot, SnapshotOperation op);
SnapshotStrategy getSnapshotStrategy(Snapshot snapshot, Long zoneId, SnapshotOperation op);
VMSnapshotStrategy getVmSnapshotStrategy(VMSnapshot vmSnapshot);
/**

View File

@ -16,6 +16,8 @@
// under the License.
package com.cloud.vm;
import java.util.List;
import com.cloud.storage.Snapshot;
public class VmWorkTakeVolumeSnapshot extends VmWork {
@ -29,8 +31,11 @@ public class VmWorkTakeVolumeSnapshot extends VmWork {
private Snapshot.LocationType locationType;
private boolean asyncBackup;
private List<Long> zoneIds;
public VmWorkTakeVolumeSnapshot(long userId, long accountId, long vmId, String handlerName,
Long volumeId, Long policyId, Long snapshotId, boolean quiesceVm, Snapshot.LocationType locationType, boolean asyncBackup) {
Long volumeId, Long policyId, Long snapshotId, boolean quiesceVm, Snapshot.LocationType locationType,
boolean asyncBackup, List<Long> zoneIds) {
super(userId, accountId, vmId, handlerName);
this.volumeId = volumeId;
this.policyId = policyId;
@ -38,6 +43,7 @@ public class VmWorkTakeVolumeSnapshot extends VmWork {
this.quiesceVm = quiesceVm;
this.locationType = locationType;
this.asyncBackup = asyncBackup;
this.zoneIds = zoneIds;
}
public Long getVolumeId() {
@ -61,4 +67,8 @@ public class VmWorkTakeVolumeSnapshot extends VmWork {
public boolean isAsyncBackup() {
return asyncBackup;
}
public List<Long> getZoneIds() {
return zoneIds;
}
}

View File

@ -0,0 +1,36 @@
// 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 com.cloud.vm;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
public class VmWorkTakeVolumeSnapshotTest {
@Test
public void testVmWorkTakeVolumeSnapshotZoneIds() {
List<Long> zoneIds = List.of(10L, 20L);
VmWorkTakeVolumeSnapshot work = new VmWorkTakeVolumeSnapshot(1L, 1L, 1L, "handler",
1L, 1L, 1L, false, null, false, zoneIds);
Assert.assertNotNull(work.getZoneIds());
Assert.assertEquals(zoneIds.size(), work.getZoneIds().size());
Assert.assertEquals(zoneIds.get(0), work.getZoneIds().get(0));
Assert.assertEquals(zoneIds.get(1), work.getZoneIds().get(1));
}
}

View File

@ -46,6 +46,7 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
import org.apache.log4j.Logger;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
@ -61,7 +62,6 @@ import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.SecondaryStorageVmVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.SecondaryStorageVmDao;
import org.apache.log4j.Logger;
public class DataMigrationUtility {
private static Logger LOGGER = Logger.getLogger(DataMigrationUtility.class);
@ -223,7 +223,7 @@ public class DataMigrationUtility {
if (snapshot.getState() == ObjectInDataStoreStateMachine.State.Ready &&
snapshotVO != null && snapshotVO.getHypervisorType() != Hypervisor.HypervisorType.Simulator
&& snapshot.getParentSnapshotId() == 0 ) {
SnapshotInfo snap = snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(), DataStoreRole.Image);
SnapshotInfo snap = snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(), snapshot.getDataStoreId(), snapshot.getRole());
if (snap != null) {
files.add(snap);
}

View File

@ -17,7 +17,6 @@
package org.apache.cloudstack.engine.orchestration;
import com.cloud.capacity.CapacityManager;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@ -58,6 +57,7 @@ import org.apache.commons.math3.stat.descriptive.moment.Mean;
import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation;
import org.apache.log4j.Logger;
import com.cloud.capacity.CapacityManager;
import com.cloud.server.StatsCollector;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.SnapshotVO;
@ -305,7 +305,7 @@ public class StorageOrchestrator extends ManagerBase implements StorageOrchestra
if (!snaps.isEmpty()) {
for (SnapshotDataStoreVO snap : snaps) {
SnapshotVO snapshotVO = snapshotDao.findById(snap.getSnapshotId());
SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(), DataStoreRole.Image);
SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(), snap.getDataStoreId(), DataStoreRole.Image);
SnapshotInfo parentSnapshot = snapshotInfo.getParent();
if (parentSnapshot == null && policy == MigrationPolicy.COMPLETE) {

View File

@ -558,7 +558,7 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
VolumeInfo vol = volFactory.getVolume(volume.getId());
DataStore store = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);
DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshot);
SnapshotInfo snapInfo = snapshotFactory.getSnapshot(snapshot.getId(), dataStoreRole);
SnapshotInfo snapInfo = snapshotFactory.getSnapshotWithRoleAndZone(snapshot.getId(), dataStoreRole, volume.getDataCenterId());
boolean kvmSnapshotOnlyInPrimaryStorage = snapshotHelper.isKvmSnapshotOnlyInPrimaryStorage(snapshot, dataStoreRole);

View File

@ -115,4 +115,6 @@ public interface DataCenterDao extends GenericDao<DataCenterVO, Long> {
List<DataCenterVO> findByKeyword(String keyword);
List<DataCenterVO> listAllZones();
List<DataCenterVO> listByIds(List<Long> ids);
}

View File

@ -433,4 +433,14 @@ public class DataCenterDaoImpl extends GenericDaoBase<DataCenterVO, Long> implem
return dcs;
}
@Override
public List<DataCenterVO> listByIds(List<Long> ids) {
SearchBuilder<DataCenterVO> idsSearch = createSearchBuilder();
idsSearch.and("ids", idsSearch.entity().getId(), SearchCriteria.Op.IN);
idsSearch.done();
SearchCriteria<DataCenterVO> sc = idsSearch.create();
sc.setParameters("ids", ids.toArray());
return listBy(sc);
}
}

View File

@ -16,9 +16,8 @@
// under the License.
package com.cloud.storage;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.utils.db.GenericDao;
import com.google.gson.annotations.Expose;
import java.util.Date;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
@ -32,8 +31,9 @@ import javax.persistence.Table;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.Date;
import java.util.UUID;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.utils.db.GenericDao;
import com.google.gson.annotations.Expose;
@Entity
@Table(name = "snapshots")

View File

@ -0,0 +1,118 @@
// 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 com.cloud.storage;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.apache.cloudstack.api.InternalIdentity;
import com.cloud.utils.db.GenericDao;
import com.cloud.utils.db.GenericDaoBase;
@Entity
@Table(name = "snapshot_zone_ref")
public class SnapshotZoneVO implements InternalIdentity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(name = "zone_id")
private long zoneId;
@Column(name = "snapshot_id")
private long snapshotId;
@Column(name = GenericDaoBase.CREATED_COLUMN)
private Date created = null;
@Column(name = "last_updated")
@Temporal(value = TemporalType.TIMESTAMP)
private Date lastUpdated = null;
@Temporal(value = TemporalType.TIMESTAMP)
@Column(name = GenericDao.REMOVED_COLUMN)
private Date removed;
protected SnapshotZoneVO() {
}
public SnapshotZoneVO(long zoneId, long snapshotId, Date lastUpdated) {
this.zoneId = zoneId;
this.snapshotId = snapshotId;
this.lastUpdated = lastUpdated;
}
@Override
public long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public long getZoneId() {
return zoneId;
}
public void setZoneId(long zoneId) {
this.zoneId = zoneId;
}
public long getSnapshotId() {
return snapshotId;
}
public void setSnapshotId(long snapshotId) {
this.snapshotId = snapshotId;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getLastUpdated() {
return lastUpdated;
}
public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}
public void setRemoved(Date removed) {
this.removed = removed;
}
public Date getRemoved() {
return removed;
}
}

View File

@ -0,0 +1,31 @@
// 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 com.cloud.storage.dao;
import java.util.List;
import com.cloud.storage.SnapshotZoneVO;
import com.cloud.utils.db.GenericDao;
public interface SnapshotZoneDao extends GenericDao<SnapshotZoneVO, Long> {
SnapshotZoneVO findByZoneSnapshot(long zoneId, long templateId);
void addSnapshotToZone(long snapshotId, long zoneId);
void removeSnapshotFromZone(long snapshotId, long zoneId);
void removeSnapshotFromZones(long snapshotId);
List<SnapshotZoneVO> listBySnapshot(long snapshotId);
}

View File

@ -0,0 +1,84 @@
// 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 com.cloud.storage.dao;
import java.util.Date;
import java.util.List;
import org.apache.log4j.Logger;
import com.cloud.storage.SnapshotZoneVO;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
public class SnapshotZoneDaoImpl extends GenericDaoBase<SnapshotZoneVO, Long> implements SnapshotZoneDao {
public static final Logger s_logger = Logger.getLogger(SnapshotZoneDaoImpl.class.getName());
protected final SearchBuilder<SnapshotZoneVO> ZoneSnapshotSearch;
public SnapshotZoneDaoImpl() {
ZoneSnapshotSearch = createSearchBuilder();
ZoneSnapshotSearch.and("zone_id", ZoneSnapshotSearch.entity().getZoneId(), SearchCriteria.Op.EQ);
ZoneSnapshotSearch.and("snapshot_id", ZoneSnapshotSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ);
ZoneSnapshotSearch.done();
}
@Override
public SnapshotZoneVO findByZoneSnapshot(long zoneId, long snapshotId) {
SearchCriteria<SnapshotZoneVO> sc = ZoneSnapshotSearch.create();
sc.setParameters("zone_id", zoneId);
sc.setParameters("snapshot_id", snapshotId);
return findOneBy(sc);
}
@Override
public void addSnapshotToZone(long snapshotId, long zoneId) {
SnapshotZoneVO snapshotZone = findByZoneSnapshot(zoneId, snapshotId);
if (snapshotZone == null) {
snapshotZone = new SnapshotZoneVO(zoneId, snapshotId, new Date());
persist(snapshotZone);
} else {
snapshotZone.setRemoved(GenericDaoBase.DATE_TO_NULL);
snapshotZone.setLastUpdated(new Date());
update(snapshotZone.getId(), snapshotZone);
}
}
@Override
public void removeSnapshotFromZone(long snapshotId, long zoneId) {
SearchCriteria<SnapshotZoneVO> sc = ZoneSnapshotSearch.create();
sc.setParameters("zone_id", zoneId);
sc.setParameters("snapshot_id", snapshotId);
remove(sc);
}
@Override
public void removeSnapshotFromZones(long snapshotId) {
SearchCriteria<SnapshotZoneVO> sc = ZoneSnapshotSearch.create();
sc.setParameters("snapshot_id", snapshotId);
remove(sc);
}
@Override
public List<SnapshotZoneVO> listBySnapshot(long snapshotId) {
SearchCriteria<SnapshotZoneVO> sc = ZoneSnapshotSearch.create();
sc.setParameters("snapshot_id", snapshotId);
return listBy(sc);
}
}

View File

@ -23,6 +23,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.utils.db.GenericDao;
import com.cloud.utils.fsm.StateDao;
@ -33,15 +34,21 @@ StateDao<ObjectInDataStoreStateMachine.State, ObjectInDataStoreStateMachine.Even
List<SnapshotDataStoreVO> listByStoreIdAndState(long id, ObjectInDataStoreStateMachine.State state);
List<SnapshotDataStoreVO> listBySnapshotIdAndState(long id, ObjectInDataStoreStateMachine.State state);
List<SnapshotDataStoreVO> listActiveOnCache(long id);
void deletePrimaryRecordsForStore(long id, DataStoreRole role);
SnapshotDataStoreVO findByStoreSnapshot(DataStoreRole role, long storeId, long snapshotId);
void removeBySnapshotStore(long snapshotId, long storeId, DataStoreRole role);
SnapshotDataStoreVO findParent(DataStoreRole role, Long storeId, Long volumeId);
SnapshotDataStoreVO findBySnapshot(long snapshotId, DataStoreRole role);
List<SnapshotDataStoreVO> listBySnapshot(long snapshotId, DataStoreRole role);
List<SnapshotDataStoreVO> listReadyBySnapshot(long snapshotId, DataStoreRole role);
SnapshotDataStoreVO findBySourceSnapshot(long snapshotId, DataStoreRole role);
@ -66,9 +73,7 @@ StateDao<ObjectInDataStoreStateMachine.State, ObjectInDataStoreStateMachine.Even
void updateVolumeIds(long oldVolId, long newVolId);
SnapshotDataStoreVO findByVolume(long volumeId, DataStoreRole role);
SnapshotDataStoreVO findByVolume(long snapshotId, long volumeId, DataStoreRole role);
List<SnapshotDataStoreVO> findByVolume(long snapshotId, long volumeId, DataStoreRole role);
/**
* List all snapshots in 'snapshot_store_ref' by volume and data store role. Therefore, it is possible to list all snapshots that are in the primary storage or in the secondary storage.
@ -85,10 +90,16 @@ StateDao<ObjectInDataStoreStateMachine.State, ObjectInDataStoreStateMachine.Even
* Removes the snapshot reference from the database according to its id and data store role.
* @return true if success, otherwise, false.
*/
boolean expungeReferenceBySnapshotIdAndDataStoreRole(long snapshotId, DataStoreRole dataStorerole);
boolean expungeReferenceBySnapshotIdAndDataStoreRole(long snapshotId, long storeId, DataStoreRole dataStorerole);
/**
* List all snapshots in 'snapshot_store_ref' with state 'Ready' by volume ID.
*/
List<SnapshotDataStoreVO> listReadyByVolumeId(long volumeId);
List<SnapshotDataStoreVO> listBySnasphotStoreDownloadStatus(long snapshotId, long storeId, VMTemplateStorageResourceAssoc.Status... status);
SnapshotDataStoreVO findOneBySnapshotAndDatastoreRole(long snapshotId, DataStoreRole role);
void updateDisplayForSnapshotStoreRole(long snapshotId, long storeId, DataStoreRole role, boolean display);
}

View File

@ -37,6 +37,7 @@ import org.springframework.stereotype.Component;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.Filter;
@ -61,8 +62,10 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
private SearchBuilder<SnapshotDataStoreVO> searchFilteringStoreIdEqStoreRoleEqStateNeqRefCntNeq;
protected SearchBuilder<SnapshotDataStoreVO> searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq;
private SearchBuilder<SnapshotDataStoreVO> stateSearch;
private SearchBuilder<SnapshotDataStoreVO> idStateNeqSearch;
protected SearchBuilder<SnapshotVO> snapshotVOSearch;
private SearchBuilder<SnapshotDataStoreVO> snapshotCreatedSearch;
private SearchBuilder<SnapshotDataStoreVO> storeSnapshotDownloadStatusSearch;
protected static final List<Hypervisor.HypervisorType> HYPERVISORS_SUPPORTING_SNAPSHOTS_CHAINING = List.of(Hypervisor.HypervisorType.XenServer);
@ -114,6 +117,12 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
stateSearch.and(STATE, stateSearch.entity().getState(), SearchCriteria.Op.IN);
stateSearch.done();
idStateNeqSearch = createSearchBuilder();
idStateNeqSearch.and(SNAPSHOT_ID, idStateNeqSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ);
idStateNeqSearch.and(STATE, idStateNeqSearch.entity().getState(), SearchCriteria.Op.NEQ);
idStateNeqSearch.done();
snapshotVOSearch = snapshotDao.createSearchBuilder();
snapshotVOSearch.and(VOLUME_ID, snapshotVOSearch.entity().getVolumeId(), SearchCriteria.Op.EQ);
snapshotVOSearch.done();
@ -123,6 +132,12 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
snapshotCreatedSearch.and(CREATED, snapshotCreatedSearch.entity().getCreated(), SearchCriteria.Op.BETWEEN);
snapshotCreatedSearch.done();
storeSnapshotDownloadStatusSearch = createSearchBuilder();
storeSnapshotDownloadStatusSearch.and(SNAPSHOT_ID, storeSnapshotDownloadStatusSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ);
storeSnapshotDownloadStatusSearch.and(STORE_ID, storeSnapshotDownloadStatusSearch.entity().getDataStoreId(), SearchCriteria.Op.EQ);
storeSnapshotDownloadStatusSearch.and("downloadState", storeSnapshotDownloadStatusSearch.entity().getDownloadState(), SearchCriteria.Op.IN);
storeSnapshotDownloadStatusSearch.done();
return true;
}
@ -179,6 +194,14 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
return listBy(sc);
}
@Override
public List<SnapshotDataStoreVO> listBySnapshotIdAndState(long id, ObjectInDataStoreStateMachine.State state) {
SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create();
sc.setParameters(SNAPSHOT_ID, id);
sc.setParameters(STATE, state);
return listBy(sc);
}
@Override
public void deletePrimaryRecordsForStore(long id, DataStoreRole role) {
SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStoreRoleEqStateNeqRefCntNeq.create();
@ -203,6 +226,15 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
return findOneBy(sc);
}
@Override
public void removeBySnapshotStore(long snapshotId, long storeId, DataStoreRole role) {
SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create();
sc.setParameters(STORE_ID, storeId);
sc.setParameters(SNAPSHOT_ID, snapshotId);
sc.setParameters(STORE_ROLE, role);
remove(sc);
}
@Override
public SnapshotDataStoreVO findLatestSnapshotForVolume(Long volumeId, DataStoreRole role) {
return findOldestOrLatestSnapshotForVolume(volumeId, role, false);
@ -257,10 +289,16 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
}
@Override
public SnapshotDataStoreVO findBySnapshot(long snapshotId, DataStoreRole role) {
public List<SnapshotDataStoreVO> listBySnapshot(long snapshotId, DataStoreRole role) {
SearchCriteria<SnapshotDataStoreVO> sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role);
return listBy(sc);
}
@Override
public List<SnapshotDataStoreVO> listReadyBySnapshot(long snapshotId, DataStoreRole role) {
SearchCriteria<SnapshotDataStoreVO> sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role);
sc.setParameters(STATE, State.Ready);
return findOneBy(sc);
return listBy(sc);
}
@Override
@ -279,26 +317,19 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
}
@Override
public SnapshotDataStoreVO findByVolume(long volumeId, DataStoreRole role) {
SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create();
sc.setParameters(VOLUME_ID, volumeId);
sc.setParameters(STORE_ROLE, role);
return findOneBy(sc);
}
@Override
public SnapshotDataStoreVO findByVolume(long snapshotId, long volumeId, DataStoreRole role) {
public List<SnapshotDataStoreVO> findByVolume(long snapshotId, long volumeId, DataStoreRole role) {
SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create();
sc.setParameters(SNAPSHOT_ID, snapshotId);
sc.setParameters(VOLUME_ID, volumeId);
sc.setParameters(STORE_ROLE, role);
return findOneBy(sc);
return listBy(sc);
}
@Override
public List<SnapshotDataStoreVO> findBySnapshotId(long snapshotId) {
SearchCriteria<SnapshotDataStoreVO> sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create();
SearchCriteria<SnapshotDataStoreVO> sc = idStateNeqSearch.create();
sc.setParameters(SNAPSHOT_ID, snapshotId);
sc.setParameters(STATE, State.Destroyed);
return listBy(sc);
}
@ -451,8 +482,8 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
}
@Override
public boolean expungeReferenceBySnapshotIdAndDataStoreRole(long snapshotId, DataStoreRole dataStoreRole) {
SnapshotDataStoreVO snapshotDataStoreVo = findOneBy(createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, dataStoreRole));
public boolean expungeReferenceBySnapshotIdAndDataStoreRole(long snapshotId, long storeId, DataStoreRole dataStoreRole) {
SnapshotDataStoreVO snapshotDataStoreVo = findByStoreSnapshot(dataStoreRole, storeId, snapshotId);
return snapshotDataStoreVo == null || expunge(snapshotDataStoreVo.getId());
}
@ -463,4 +494,30 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
sc.setParameters(STATE, State.Ready);
return listBy(sc);
}
@Override
public List<SnapshotDataStoreVO> listBySnasphotStoreDownloadStatus(long snapshotId, long storeId, VMTemplateStorageResourceAssoc.Status... status) {
SearchCriteria<SnapshotDataStoreVO> sc = storeSnapshotDownloadStatusSearch.create();
sc.setParameters("snapshot_id", snapshotId);
sc.setParameters("store_id", storeId);
sc.setParameters("downloadState", (Object[])status);
return search(sc, null);
}
@Override
public SnapshotDataStoreVO findOneBySnapshotAndDatastoreRole(long snapshotId, DataStoreRole role) {
SearchCriteria<SnapshotDataStoreVO> sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role);
sc.setParameters(STATE, State.Ready);
return findOneBy(sc);
}
@Override
public void updateDisplayForSnapshotStoreRole(long snapshotId, long storeId, DataStoreRole role, boolean display) {
SnapshotDataStoreVO ref = findByStoreSnapshot(role, storeId, snapshotId);
if (ref == null) {
return;
}
ref.setDisplay(display);
update(ref.getId(), ref);
}
}

View File

@ -36,6 +36,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreState
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.fsm.StateObject;
@ -95,6 +96,22 @@ public class SnapshotDataStoreVO implements StateObject<ObjectInDataStoreStateMa
@Enumerated(EnumType.STRING)
ObjectInDataStoreStateMachine.State state;
@Column(name = "download_pct")
private int downloadPercent;
@Column(name = "download_state")
@Enumerated(EnumType.STRING)
private VMTemplateStorageResourceAssoc.Status downloadState;
@Column(name = "local_path")
private String localDownloadPath;
@Column(name = "error_str")
private String errorString;
@Column(name = "display")
private boolean display = true;
@Column(name = "ref_cnt")
Long refCnt = 0L;
@ -295,4 +312,44 @@ public class SnapshotDataStoreVO implements StateObject<ObjectInDataStoreStateMa
public void setCreated(Date created) {
this.created = created;
}
public int getDownloadPercent() {
return downloadPercent;
}
public void setDownloadPercent(int downloadPercent) {
this.downloadPercent = downloadPercent;
}
public VMTemplateStorageResourceAssoc.Status getDownloadState() {
return downloadState;
}
public void setDownloadState(VMTemplateStorageResourceAssoc.Status downloadState) {
this.downloadState = downloadState;
}
public void setLocalDownloadPath(String localPath) {
localDownloadPath = localPath;
}
public String getLocalDownloadPath() {
return localDownloadPath;
}
public void setErrorString(String errorString) {
this.errorString = errorString;
}
public String getErrorString() {
return errorString;
}
public boolean isDisplay() {
return display;
}
public void setDisplay(boolean display) {
this.display = display;
}
}

View File

@ -56,6 +56,7 @@
<bean id="serviceOfferingDaoImpl" class="com.cloud.service.dao.ServiceOfferingDaoImpl" />
<bean id="serviceOfferingDetailsDaoImpl" class="com.cloud.service.dao.ServiceOfferingDetailsDaoImpl"/>
<bean id="snapshotDaoImpl" class="com.cloud.storage.dao.SnapshotDaoImpl" />
<bean id="snapshotZoneDaoImpl" class="com.cloud.storage.dao.SnapshotZoneDaoImpl" />
<bean id="snapshotDataStoreDaoImpl" class="org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDaoImpl" />
<bean id="storagePoolDetailsDaoImpl" class="com.cloud.storage.dao.StoragePoolDetailsDaoImpl" />
<bean id="storagePoolHostDaoImpl" class="com.cloud.storage.dao.StoragePoolHostDaoImpl" />

View File

@ -176,6 +176,7 @@
<bean id="site2SiteCustomerGatewayDaoImpl" class="com.cloud.network.dao.Site2SiteCustomerGatewayDaoImpl" />
<bean id="site2SiteVpnConnectionDaoImpl" class="com.cloud.network.dao.Site2SiteVpnConnectionDaoImpl" />
<bean id="site2SiteVpnGatewayDaoImpl" class="com.cloud.network.dao.Site2SiteVpnGatewayDaoImpl" />
<bean id="snapshotJoinDaoImpl" class="com.cloud.api.query.dao.SnapshotJoinDaoImpl" />
<bean id="snapshotDetailsDaoImpl" class="com.cloud.storage.dao.SnapshotDetailsDaoImpl" />
<bean id="snapshotPolicyDaoImpl" class="com.cloud.storage.dao.SnapshotPolicyDaoImpl" />
<bean id="snapshotPolicyDetailsDaoImpl" class="org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDaoImpl" />

View File

@ -183,3 +183,116 @@ ALTER TABLE `cloud`.`kubernetes_cluster` MODIFY COLUMN `kubernetes_version_id` b
-- Set removed state for all removed accounts
UPDATE `cloud`.`account` SET state='removed' WHERE `removed` IS NOT NULL;
-- Add table for snapshot zone reference
CREATE TABLE `cloud`.`snapshot_zone_ref` (
`id` bigint unsigned NOT NULL auto_increment,
`zone_id` bigint unsigned NOT NULL,
`snapshot_id` bigint unsigned NOT NULL,
`created` DATETIME NOT NULL,
`last_updated` DATETIME,
`removed` datetime COMMENT 'date removed if not null',
PRIMARY KEY (`id`),
CONSTRAINT `fk_snapshot_zone_ref__zone_id` FOREIGN KEY `fk_snapshot_zone_ref__zone_id` (`zone_id`) REFERENCES `data_center` (`id`) ON DELETE CASCADE,
INDEX `i_snapshot_zone_ref__zone_id`(`zone_id`),
CONSTRAINT `fk_snapshot_zone_ref__snapshot_id` FOREIGN KEY `fk_snapshot_zone_ref__snapshot_id` (`snapshot_id`) REFERENCES `snapshots` (`id`) ON DELETE CASCADE,
INDEX `i_snapshot_zone_ref__snapshot_id`(`snapshot_id`),
INDEX `i_snapshot_zone_ref__removed`(`removed`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- Alter snapshot_store_ref table to add download related fields
ALTER TABLE `cloud`.`snapshot_store_ref`
ADD COLUMN `download_state` varchar(255) DEFAULT NULL COMMENT 'the state of the snapshot download' AFTER `volume_id`,
ADD COLUMN `download_pct` int unsigned DEFAULT NULL COMMENT 'the percentage of the snapshot download completed' AFTER `download_state`,
ADD COLUMN `error_str` varchar(255) DEFAULT NULL COMMENT 'the error message when the snapshot download occurs' AFTER `download_pct`,
ADD COLUMN `local_path` varchar(255) DEFAULT NULL COMMENT 'the path of the snapshot download' AFTER `error_str`,
ADD COLUMN `display` tinyint(1) unsigned NOT NULL DEFAULT 1 COMMENT '1 implies store reference is available for listing' AFTER `error_str`;
-- Create snapshot_view
DROP VIEW IF EXISTS `cloud`.`snapshot_view`;
CREATE VIEW `cloud`.`snapshot_view` AS
SELECT
`snapshots`.`id` AS `id`,
`snapshots`.`uuid` AS `uuid`,
`snapshots`.`name` AS `name`,
`snapshots`.`status` AS `status`,
`snapshots`.`disk_offering_id` AS `disk_offering_id`,
`snapshots`.`snapshot_type` AS `snapshot_type`,
`snapshots`.`type_description` AS `type_description`,
`snapshots`.`size` AS `size`,
`snapshots`.`created` AS `created`,
`snapshots`.`removed` AS `removed`,
`snapshots`.`location_type` AS `location_type`,
`snapshots`.`hypervisor_type` AS `hypervisor_type`,
`account`.`id` AS `account_id`,
`account`.`uuid` AS `account_uuid`,
`account`.`account_name` AS `account_name`,
`account`.`type` AS `account_type`,
`domain`.`id` AS `domain_id`,
`domain`.`uuid` AS `domain_uuid`,
`domain`.`name` AS `domain_name`,
`domain`.`path` AS `domain_path`,
`projects`.`id` AS `project_id`,
`projects`.`uuid` AS `project_uuid`,
`projects`.`name` AS `project_name`,
`volumes`.`id` AS `volume_id`,
`volumes`.`uuid` AS `volume_uuid`,
`volumes`.`name` AS `volume_name`,
`volumes`.`volume_type` AS `volume_type`,
`volumes`.`size` AS `volume_size`,
`data_center`.`id` AS `data_center_id`,
`data_center`.`uuid` AS `data_center_uuid`,
`data_center`.`name` AS `data_center_name`,
`snapshot_store_ref`.`store_id` AS `store_id`,
IFNULL(`image_store`.`uuid`, `storage_pool`.`uuid`) AS `store_uuid`,
IFNULL(`image_store`.`name`, `storage_pool`.`name`) AS `store_name`,
`snapshot_store_ref`.`store_role` AS `store_role`,
`snapshot_store_ref`.`state` AS `store_state`,
`snapshot_store_ref`.`download_state` AS `download_state`,
`snapshot_store_ref`.`download_pct` AS `download_pct`,
`snapshot_store_ref`.`error_str` AS `error_str`,
`snapshot_store_ref`.`size` AS `store_size`,
`snapshot_store_ref`.`created` AS `created_on_store`,
`resource_tags`.`id` AS `tag_id`,
`resource_tags`.`uuid` AS `tag_uuid`,
`resource_tags`.`key` AS `tag_key`,
`resource_tags`.`value` AS `tag_value`,
`resource_tags`.`domain_id` AS `tag_domain_id`,
`domain`.`uuid` AS `tag_domain_uuid`,
`domain`.`name` AS `tag_domain_name`,
`resource_tags`.`account_id` AS `tag_account_id`,
`account`.`account_name` AS `tag_account_name`,
`resource_tags`.`resource_id` AS `tag_resource_id`,
`resource_tags`.`resource_uuid` AS `tag_resource_uuid`,
`resource_tags`.`resource_type` AS `tag_resource_type`,
`resource_tags`.`customer` AS `tag_customer`,
CONCAT(`snapshots`.`id`,
'_',
IFNULL(`snapshot_store_ref`.`store_role`, 'UNKNOWN'),
'_',
IFNULL(`snapshot_store_ref`.`store_id`, 0)) AS `snapshot_store_pair`
FROM
((((((((((`snapshots`
JOIN `account` ON ((`account`.`id` = `snapshots`.`account_id`)))
JOIN `domain` ON ((`domain`.`id` = `account`.`domain_id`)))
LEFT JOIN `projects` ON ((`projects`.`project_account_id` = `account`.`id`)))
LEFT JOIN `volumes` ON ((`volumes`.`id` = `snapshots`.`volume_id`)))
LEFT JOIN `snapshot_store_ref` ON (((`snapshot_store_ref`.`snapshot_id` = `snapshots`.`id`)
AND (`snapshot_store_ref`.`state` != 'Destroyed')
AND (`snapshot_store_ref`.`display` = 1))))
LEFT JOIN `image_store` ON ((ISNULL(`image_store`.`removed`)
AND (`snapshot_store_ref`.`store_role` = 'Image')
AND (`snapshot_store_ref`.`store_id` IS NOT NULL)
AND (`image_store`.`id` = `snapshot_store_ref`.`store_id`))))
LEFT JOIN `storage_pool` ON ((ISNULL(`storage_pool`.`removed`)
AND (`snapshot_store_ref`.`store_role` = 'Primary')
AND (`snapshot_store_ref`.`store_id` IS NOT NULL)
AND (`storage_pool`.`id` = `snapshot_store_ref`.`store_id`))))
LEFT JOIN `snapshot_zone_ref` ON (((`snapshot_zone_ref`.`snapshot_id` = `snapshots`.`id`)
AND ISNULL(`snapshot_store_ref`.`store_id`)
AND ISNULL(`snapshot_zone_ref`.`removed`))))
LEFT JOIN `data_center` ON (((`image_store`.`data_center_id` = `data_center`.`id`)
OR (`storage_pool`.`data_center_id` = `data_center`.`id`)
OR (`snapshot_zone_ref`.`zone_id` = `data_center`.`id`))))
LEFT JOIN `resource_tags` ON ((`resource_tags`.`resource_id` = `snapshots`.`id`)
AND (`resource_tags`.`resource_type` = 'Snapshot')));

View File

@ -218,7 +218,7 @@ public class SecondaryStorageServiceImpl implements SecondaryStorageService {
private void updateDataObject(DataObject srcData, DataObject destData) {
if (destData instanceof SnapshotInfo) {
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySourceSnapshot(srcData.getId(), DataStoreRole.Image);
SnapshotDataStoreVO destSnapshotStore = snapshotStoreDao.findBySnapshot(srcData.getId(), DataStoreRole.Image);
SnapshotDataStoreVO destSnapshotStore = snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Image, srcData.getDataStore().getId(), srcData.getId());
if (snapshotStore != null && destSnapshotStore != null) {
destSnapshotStore.setPhysicalSize(snapshotStore.getPhysicalSize());
destSnapshotStore.setCreated(snapshotStore.getCreated());

View File

@ -257,4 +257,10 @@ public class ImageStoreProviderManagerImpl implements ImageStoreProviderManager,
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] { ImageStoreAllocationAlgorithm };
}
@Override
public long getImageStoreZoneId(long dataStoreId) {
ImageStoreVO dataStore = dataStoreDao.findById(dataStoreId);
return dataStore.getDataCenterId();
}
}

View File

@ -0,0 +1,47 @@
// 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.storage.image.manager;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ImageStoreProviderManagerImplTest {
@Mock
ImageStoreDao imageStoreDao;
@InjectMocks
ImageStoreProviderManagerImpl imageStoreProviderManager = new ImageStoreProviderManagerImpl();
@Test
public void testGetImageStoreZoneId() {
final long storeId = 1L;
final long zoneId = 1L;
ImageStoreVO imageStoreVO = Mockito.mock(ImageStoreVO.class);
Mockito.when(imageStoreVO.getDataCenterId()).thenReturn(zoneId);
Mockito.when(imageStoreDao.findById(storeId)).thenReturn(imageStoreVO);
long value = imageStoreProviderManager.getImageStoreZoneId(storeId);
Assert.assertEquals(zoneId, value);
}
}

View File

@ -21,7 +21,6 @@ package org.apache.cloudstack.storage.snapshot;
import javax.inject.Inject;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
@ -48,7 +47,7 @@ public class CephSnapshotStrategy extends StorageSystemSnapshotStrategy {
private static final Logger s_logger = Logger.getLogger(CephSnapshotStrategy.class);
@Override
public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
long volumeId = snapshot.getVolumeId();
VolumeVO volumeVO = volumeDao.findByIdIncludingRemoved(volumeId);
boolean baseVolumeExists = volumeVO.getRemoved() == null;
@ -56,7 +55,7 @@ public class CephSnapshotStrategy extends StorageSystemSnapshotStrategy {
return StrategyPriority.CANT_HANDLE;
}
if (!isSnapshotStoredOnRbdStoragePool(snapshot)) {
if (!isSnapshotStoredOnRbdStoragePoolAndOperationForSameZone(snapshot, zoneId)) {
return StrategyPriority.CANT_HANDLE;
}
@ -81,12 +80,18 @@ public class CephSnapshotStrategy extends StorageSystemSnapshotStrategy {
return true;
}
protected boolean isSnapshotStoredOnRbdStoragePool(Snapshot snapshot) {
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
protected boolean isSnapshotStoredOnRbdStoragePoolAndOperationForSameZone(Snapshot snapshot, Long zoneId) {
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
if (snapshotStore == null) {
return false;
}
StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(snapshotStore.getDataStoreId());
return storagePoolVO != null && storagePoolVO.getPoolType() == StoragePoolType.RBD;
if (storagePoolVO == null) {
return false;
}
if (zoneId != null && !zoneId.equals(storagePoolVO.getDataCenterId())) {
return false;
}
return storagePoolVO.getPoolType() == StoragePoolType.RBD;
}
}

View File

@ -16,18 +16,13 @@
// under the License.
package org.apache.cloudstack.storage.snapshot;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
import com.cloud.storage.VolumeDetailVO;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.log4j.Logger;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
@ -41,11 +36,15 @@ import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.jobs.AsyncJob;
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.utils.identity.ManagementServerNode;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.api.to.DataTO;
import com.cloud.event.EventTypes;
@ -57,28 +56,28 @@ import com.cloud.storage.CreateSnapshotPayload;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolStatus;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeDetailVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.dao.SnapshotZoneDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.storage.snapshot.SnapshotManager;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.storage.snapshot.SnapshotManager;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.db.DB;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
private static final String SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER = "secondary storage";
private static final String PRIMARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER = "primary storage";
private static final Logger s_logger = Logger.getLogger(DefaultSnapshotStrategy.class);
@ -100,6 +99,18 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
private SnapshotDetailsDao _snapshotDetailsDao;
@Inject
VolumeDetailsDao _volumeDetailsDaoImpl;
@Inject
SnapshotZoneDao snapshotZoneDao;
public SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) {
List<SnapshotDataStoreVO> snaps = snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
for (SnapshotDataStoreVO ref : snaps) {
if (zoneId == dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole())) {
return ref;
}
}
return null;
}
@Override
public SnapshotInfo backupSnapshot(SnapshotInfo snapshot) {
@ -107,7 +118,8 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
if (parentSnapshot != null && snapshot.getPath().equalsIgnoreCase(parentSnapshot.getPath())) {
// don't need to backup this snapshot
SnapshotDataStoreVO parentSnapshotOnBackupStore = snapshotStoreDao.findBySnapshot(parentSnapshot.getId(), DataStoreRole.Image);
SnapshotDataStoreVO parentSnapshotOnBackupStore = getSnapshotImageStoreRef(parentSnapshot.getId(),
dataStoreMgr.getStoreZoneId(parentSnapshot.getDataStore().getId(), parentSnapshot.getDataStore().getRole()));
if (parentSnapshotOnBackupStore != null && parentSnapshotOnBackupStore.getState() == State.Ready) {
DataStore store = dataStoreMgr.getDataStore(parentSnapshotOnBackupStore.getDataStoreId(), parentSnapshotOnBackupStore.getRole());
@ -159,7 +171,7 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
if (prevBackupId == 0) {
break;
}
parentSnapshotOnBackupStore = snapshotStoreDao.findBySnapshot(prevBackupId, DataStoreRole.Image);
parentSnapshotOnBackupStore = getSnapshotImageStoreRef(prevBackupId, volume.getDataCenterId());
if (parentSnapshotOnBackupStore == null) {
break;
}
@ -181,20 +193,19 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
return snapshotSvr.backupSnapshot(snapshot);
}
private final List<Snapshot.State> snapshotStatesAbleToDeleteSnapshot = Arrays.asList(Snapshot.State.Destroying, Snapshot.State.Destroyed, Snapshot.State.Error);
protected boolean deleteSnapshotChain(SnapshotInfo snapshot, String storage) {
protected boolean deleteSnapshotChain(SnapshotInfo snapshot, String storageToString) {
DataTO snapshotTo = snapshot.getTO();
s_logger.debug(String.format("Deleting %s chain of snapshots.", snapshotTo));
boolean result = false;
boolean resultIsSet = false;
final List<Snapshot.State> snapshotStatesAbleToDeleteSnapshot = Arrays.asList(Snapshot.State.BackedUp, Snapshot.State.Destroying, Snapshot.State.Destroyed, Snapshot.State.Error);
try {
while (snapshot != null && snapshotStatesAbleToDeleteSnapshot.contains(snapshot.getState())) {
SnapshotInfo child = snapshot.getChild();
if (child != null) {
s_logger.debug(String.format("Snapshot [%s] has child [%s], not deleting it on the storage [%s]", snapshotTo, child.getTO(), storage));
s_logger.debug(String.format("Snapshot [%s] has child [%s], not deleting it on the storage [%s]", snapshotTo, child.getTO(), storageToString));
break;
}
@ -207,8 +218,6 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
//NOTE: if both snapshots share the same path, it's for xenserver's empty delta snapshot. We can't delete the snapshot on the backend, as parent snapshot still reference to it
//Instead, mark it as destroyed in the db.
s_logger.debug(String.format("Snapshot [%s] is an empty delta snapshot; therefore, we will only mark it as destroyed in the database.", snapshotTo));
snapshot.processEvent(Event.DestroyRequested);
snapshot.processEvent(Event.OperationSuccessed);
deleted = true;
if (!resultIsSet) {
result = true;
@ -233,22 +242,25 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
resultIsSet = true;
}
} catch (Exception e) {
s_logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storage, e.getMessage()), e);
s_logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storageToString, e.getMessage()), e);
}
}
snapshot = parent;
}
} catch (Exception e) {
s_logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storage, e.getMessage()), e);
s_logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storageToString, e.getMessage()), e);
}
return result;
}
@Override
public boolean deleteSnapshot(Long snapshotId) {
public boolean deleteSnapshot(Long snapshotId, Long zoneId) {
SnapshotVO snapshotVO = snapshotDao.findById(snapshotId);
if (zoneId != null && List.of(Snapshot.State.Allocated, Snapshot.State.CreatedOnPrimary).contains(snapshotVO.getState())) {
throw new InvalidParameterValueException(String.format("Snapshot in %s can not be deleted for a zone", snapshotVO.getState()));
}
if (snapshotVO.getState() == Snapshot.State.Allocated) {
snapshotDao.remove(snapshotId);
return true;
@ -260,10 +272,21 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
if (Snapshot.State.Error.equals(snapshotVO.getState())) {
List<SnapshotDataStoreVO> storeRefs = snapshotStoreDao.findBySnapshotId(snapshotId);
List<Long> deletedRefs = new ArrayList<>();
for (SnapshotDataStoreVO ref : storeRefs) {
snapshotStoreDao.expunge(ref.getId());
boolean refZoneIdMatch = false;
if (zoneId != null) {
Long refZoneId = dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole());
refZoneIdMatch = zoneId.equals(refZoneId);
}
if (zoneId == null || refZoneIdMatch) {
snapshotStoreDao.expunge(ref.getId());
deletedRefs.add(ref.getId());
}
}
if (deletedRefs.size() == storeRefs.size()) {
snapshotDao.remove(snapshotId);
}
snapshotDao.remove(snapshotId);
return true;
}
@ -278,20 +301,26 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
throw new InvalidParameterValueException("Can't delete snapshotshot " + snapshotId + " due to it is in " + snapshotVO.getState() + " Status");
}
return destroySnapshotEntriesAndFiles(snapshotVO);
return destroySnapshotEntriesAndFiles(snapshotVO, zoneId);
}
/**
* Destroys the snapshot entries and files on both primary and secondary storage (if it exists).
* @return true if destroy successfully, else false.
*/
protected boolean destroySnapshotEntriesAndFiles(SnapshotVO snapshotVo) {
if (!deleteSnapshotInfos(snapshotVo)) {
protected boolean destroySnapshotEntriesAndFiles(SnapshotVO snapshotVo, Long zoneId) {
if (!deleteSnapshotInfos(snapshotVo, zoneId)) {
return false;
}
if (zoneId != null) {
snapshotZoneDao.removeSnapshotFromZone(snapshotVo.getId(), zoneId);
} else {
snapshotZoneDao.removeSnapshotFromZones(snapshotVo.getId());
}
if (CollectionUtils.isNotEmpty(retrieveSnapshotEntries(snapshotVo.getId(), null))) {
return true;
}
updateSnapshotToDestroyed(snapshotVo);
return true;
}
@ -303,12 +332,12 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
snapshotDao.update(snapshotVo.getId(), snapshotVo);
}
protected boolean deleteSnapshotInfos(SnapshotVO snapshotVo) {
Map<String, SnapshotInfo> snapshotInfos = retrieveSnapshotEntries(snapshotVo.getId());
protected boolean deleteSnapshotInfos(SnapshotVO snapshotVo, Long zoneId) {
List<SnapshotInfo> snapshotInfos = retrieveSnapshotEntries(snapshotVo.getId(), zoneId);
boolean result = false;
for (var infoEntry : snapshotInfos.entrySet()) {
if (BooleanUtils.toBooleanDefaultIfNull(deleteSnapshotInfo(infoEntry.getValue(), infoEntry.getKey(), snapshotVo), false)) {
for (var snapshotInfo : snapshotInfos) {
if (BooleanUtils.toBooleanDefaultIfNull(deleteSnapshotInfo(snapshotInfo, snapshotVo), false)) {
result = true;
}
}
@ -320,50 +349,53 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
* Destroys the snapshot entry and file.
* @return true if destroy successfully, else false.
*/
protected Boolean deleteSnapshotInfo(SnapshotInfo snapshotInfo, String storage, SnapshotVO snapshotVo) {
if (snapshotInfo == null) {
s_logger.debug(String.format("Could not find %s entry on %s. Skipping deletion on %s.", snapshotVo, storage, storage));
return SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER.equals(storage) ? null : true;
}
protected Boolean deleteSnapshotInfo(SnapshotInfo snapshotInfo, SnapshotVO snapshotVo) {
DataStore dataStore = snapshotInfo.getDataStore();
String storageToString = String.format("%s {uuid: \"%s\", name: \"%s\"}", storage, dataStore.getUuid(), dataStore.getName());
String storageToString = String.format("%s {uuid: \"%s\", name: \"%s\"}", dataStore.getRole().name(), dataStore.getUuid(), dataStore.getName());
List<SnapshotDataStoreVO> snapshotStoreRefs = snapshotStoreDao.findBySnapshotId(snapshotVo.getId());
boolean isLastSnapshotRef = CollectionUtils.isEmpty(snapshotStoreRefs) || snapshotStoreRefs.size() == 1;
try {
SnapshotObject snapshotObject = castSnapshotInfoToSnapshotObject(snapshotInfo);
snapshotObject.processEvent(Snapshot.Event.DestroyRequested);
if (SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER.equals(storage)) {
if (isLastSnapshotRef) {
snapshotObject.processEvent(Snapshot.Event.DestroyRequested);
}
if (!DataStoreRole.Primary.equals(dataStore.getRole())) {
verifyIfTheSnapshotIsBeingUsedByAnyVolume(snapshotObject);
if (deleteSnapshotChain(snapshotInfo, storageToString)) {
s_logger.debug(String.format("%s was deleted on %s. We will mark the snapshot as destroyed.", snapshotVo, storageToString));
} else {
s_logger.debug(String.format("%s was not deleted on %s; however, we will mark the snapshot as destroyed for future garbage collecting.", snapshotVo,
storageToString));
}
snapshotObject.processEvent(Snapshot.Event.OperationSucceeded);
snapshotStoreDao.updateDisplayForSnapshotStoreRole(snapshotVo.getId(), dataStore.getId(), dataStore.getRole(), false);
if (isLastSnapshotRef) {
snapshotObject.processEvent(Snapshot.Event.OperationSucceeded);
}
return true;
} else if (deleteSnapshotInPrimaryStorage(snapshotInfo, snapshotVo, storageToString, snapshotObject)) {
} else if (deleteSnapshotInPrimaryStorage(snapshotInfo, snapshotVo, storageToString, snapshotObject, isLastSnapshotRef)) {
snapshotStoreDao.updateDisplayForSnapshotStoreRole(snapshotVo.getId(), dataStore.getId(), dataStore.getRole(), false);
return true;
}
s_logger.debug(String.format("Failed to delete %s on %s.", snapshotVo, storageToString));
snapshotObject.processEvent(Snapshot.Event.OperationFailed);
if (isLastSnapshotRef) {
snapshotObject.processEvent(Snapshot.Event.OperationFailed);
}
} catch (NoTransitionException ex) {
s_logger.warn(String.format("Failed to delete %s on %s due to %s.", snapshotVo, storageToString, ex.getMessage()), ex);
}
return false;
}
protected boolean deleteSnapshotInPrimaryStorage(SnapshotInfo snapshotInfo, SnapshotVO snapshotVo, String storageToString, SnapshotObject snapshotObject) throws NoTransitionException {
protected boolean deleteSnapshotInPrimaryStorage(SnapshotInfo snapshotInfo, SnapshotVO snapshotVo,
String storageToString, SnapshotObject snapshotObject, boolean isLastSnapshotRef) throws NoTransitionException {
try {
if (snapshotSvr.deleteSnapshot(snapshotInfo)) {
snapshotObject.processEvent(Snapshot.Event.OperationSucceeded);
s_logger.debug(String.format("%s was deleted on %s. We will mark the snapshot as destroyed.", snapshotVo, storageToString));
String msg = String.format("%s was deleted on %s.", snapshotVo, storageToString);
if (isLastSnapshotRef) {
msg = String.format("%s We will mark the snapshot as destroyed.", msg);
snapshotObject.processEvent(Snapshot.Event.OperationSucceeded);
}
s_logger.debug(msg);
return true;
}
} catch (CloudRuntimeException ex) {
@ -396,18 +428,15 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
/**
* Retrieves the snapshot infos on primary and secondary storage.
* @param snapshotId The snapshot to retrieve the infos.
* @return A map of snapshot infos.
* @return A list of snapshot infos.
*/
protected Map<String, SnapshotInfo> retrieveSnapshotEntries(long snapshotId) {
Map<String, SnapshotInfo> snapshotInfos = new LinkedHashMap<>();
snapshotInfos.put(SECONDARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER, snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Image, false));
snapshotInfos.put(PRIMARY_STORAGE_SNAPSHOT_ENTRY_IDENTIFIER, snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Primary, false));
return snapshotInfos;
protected List<SnapshotInfo> retrieveSnapshotEntries(long snapshotId, Long zoneId) {
return snapshotDataFactory.getSnapshots(snapshotId, zoneId);
}
@Override
public boolean revertSnapshot(SnapshotInfo snapshot) {
if (canHandle(snapshot, SnapshotOperation.REVERT) == StrategyPriority.CANT_HANDLE) {
if (canHandle(snapshot, null, SnapshotOperation.REVERT) == StrategyPriority.CANT_HANDLE) {
throw new CloudRuntimeException("Reverting not supported. Create a template or volume based on the snapshot instead.");
}
@ -542,19 +571,31 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
}
@Override
public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
if (SnapshotOperation.REVERT.equals(op)) {
long volumeId = snapshot.getVolumeId();
VolumeVO volumeVO = volumeDao.findById(volumeId);
if (volumeVO != null && ImageFormat.QCOW2.equals(volumeVO.getFormat())) {
if (isSnapshotStoredOnSameZoneStoreForQCOW2Volume(snapshot, volumeVO)) {
return StrategyPriority.DEFAULT;
}
return StrategyPriority.CANT_HANDLE;
}
if (zoneId != null && SnapshotOperation.DELETE.equals(op)) {
s_logger.debug(String.format("canHandle for zone ID: %d, operation: %s - %s", zoneId, op, StrategyPriority.DEFAULT));
}
return StrategyPriority.DEFAULT;
}
protected boolean isSnapshotStoredOnSameZoneStoreForQCOW2Volume(Snapshot snapshot, VolumeVO volumeVO) {
if (volumeVO == null || !ImageFormat.QCOW2.equals(volumeVO.getFormat())) {
return false;
}
List<SnapshotDataStoreVO> snapshotStores = snapshotStoreDao.listBySnapshotIdAndState(snapshot.getId(), State.Ready);
return CollectionUtils.isNotEmpty(snapshotStores) &&
snapshotStores.stream().anyMatch(s -> Objects.equals(
dataStoreMgr.getStoreZoneId(s.getDataStoreId(), s.getRole()), volumeVO.getDataCenterId()));
}
}

View File

@ -45,7 +45,7 @@ public class ScaleIOSnapshotStrategy extends StorageSystemSnapshotStrategy {
private static final Logger LOG = Logger.getLogger(ScaleIOSnapshotStrategy.class);
@Override
public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
long volumeId = snapshot.getVolumeId();
VolumeVO volumeVO = volumeDao.findByIdIncludingRemoved(volumeId);
boolean baseVolumeExists = volumeVO.getRemoved() == null;
@ -53,7 +53,7 @@ public class ScaleIOSnapshotStrategy extends StorageSystemSnapshotStrategy {
return StrategyPriority.CANT_HANDLE;
}
if (!isSnapshotStoredOnScaleIOStoragePool(snapshot)) {
if (!isSnapshotStoredOnScaleIOStoragePoolAndOperationForSameZone(snapshot, zoneId)) {
return StrategyPriority.CANT_HANDLE;
}
@ -82,12 +82,18 @@ public class ScaleIOSnapshotStrategy extends StorageSystemSnapshotStrategy {
return true;
}
protected boolean isSnapshotStoredOnScaleIOStoragePool(Snapshot snapshot) {
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
protected boolean isSnapshotStoredOnScaleIOStoragePoolAndOperationForSameZone(Snapshot snapshot, Long zoneId) {
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
if (snapshotStore == null) {
return false;
}
StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(snapshotStore.getDataStoreId());
return storagePoolVO != null && storagePoolVO.getPoolType() == Storage.StoragePoolType.PowerFlex;
if (storagePoolVO == null) {
return false;
}
if (zoneId != null && !zoneId.equals(storagePoolVO.getDataCenterId())) {
return false;
}
return storagePoolVO.getPoolType() == Storage.StoragePoolType.PowerFlex;
}
}

View File

@ -64,7 +64,7 @@ public class SnapshotDataFactoryImpl implements SnapshotDataFactory {
}
@Override
public List<SnapshotInfo> getSnapshots(long volumeId, DataStoreRole role) {
public List<SnapshotInfo> getSnapshotsForVolumeAndStoreRole(long volumeId, DataStoreRole role) {
List<SnapshotDataStoreVO> allSnapshotsFromVolumeAndDataStore = snapshotStoreDao.listAllByVolumeAndDataStore(volumeId, role);
if (CollectionUtils.isEmpty(allSnapshotsFromVolumeAndDataStore)) {
return new ArrayList<>();
@ -84,23 +84,90 @@ public class SnapshotDataFactoryImpl implements SnapshotDataFactory {
}
@Override
public SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role) {
return getSnapshot(snapshotId, role, true);
public List<SnapshotInfo> getSnapshots(long snapshotId, Long zoneId) {
SnapshotVO snapshot = snapshotDao.findById(snapshotId);
if (snapshot == null) { //snapshot may have been removed;
return new ArrayList<>();
}
List<SnapshotDataStoreVO> allSnapshotsAndDataStore = snapshotStoreDao.findBySnapshotId(snapshotId);
if (CollectionUtils.isEmpty(allSnapshotsAndDataStore)) {
return new ArrayList<>();
}
List<SnapshotInfo> infos = new ArrayList<>();
for (SnapshotDataStoreVO snapshotDataStoreVO : allSnapshotsAndDataStore) {
Long entryZoneId = storeMgr.getStoreZoneId(snapshotDataStoreVO.getDataStoreId(), snapshotDataStoreVO.getRole());
if (zoneId != null && !zoneId.equals(entryZoneId)) {
continue;
}
DataStore store = storeMgr.getDataStore(snapshotDataStoreVO.getDataStoreId(), snapshotDataStoreVO.getRole());
SnapshotObject info = SnapshotObject.getSnapshotObject(snapshot, store);
infos.add(info);
}
return infos;
}
@Override
public SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role, boolean retrieveAnySnapshotFromVolume) {
public SnapshotInfo getSnapshot(long snapshotId, long storeId, DataStoreRole role) {
SnapshotVO snapshot = snapshotDao.findById(snapshotId);
if (snapshot == null) {
return null;
}
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshotId, role);
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findByStoreSnapshot(role, storeId, snapshotId);
if (snapshotStore == null) {
return null;
}
DataStore store = storeMgr.getDataStore(snapshotStore.getDataStoreId(), role);
return SnapshotObject.getSnapshotObject(snapshot, store);
}
@Override
public SnapshotInfo getSnapshotWithRoleAndZone(long snapshotId, DataStoreRole role, long zoneId) {
return getSnapshot(snapshotId, role, zoneId, true);
}
@Override
public SnapshotInfo getSnapshotOnPrimaryStore(long snapshotId) {
SnapshotVO snapshot = snapshotDao.findById(snapshotId);
if (snapshot == null) {
return null;
}
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshotId, DataStoreRole.Primary);
if (snapshotStore == null) {
return null;
}
DataStore store = storeMgr.getDataStore(snapshotStore.getDataStoreId(), snapshotStore.getRole());
SnapshotObject so = SnapshotObject.getSnapshotObject(snapshot, store);
return so;
}
@Override
public SnapshotInfo getSnapshot(long snapshotId, DataStoreRole role, long zoneId, boolean retrieveAnySnapshotFromVolume) {
SnapshotVO snapshot = snapshotDao.findById(snapshotId);
if (snapshot == null) {
return null;
}
List<SnapshotDataStoreVO> snapshotStores = snapshotStoreDao.listReadyBySnapshot(snapshotId, role);
SnapshotDataStoreVO snapshotStore = null;
for (SnapshotDataStoreVO ref : snapshotStores) {
if (zoneId == storeMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole())) {
snapshotStore = ref;
break;
}
}
if (snapshotStore == null) {
if (!retrieveAnySnapshotFromVolume) {
return null;
}
snapshotStore = snapshotStoreDao.findByVolume(snapshotId, snapshot.getVolumeId(), role);
snapshotStores = snapshotStoreDao.findByVolume(snapshotId, snapshot.getVolumeId(), role);
for (SnapshotDataStoreVO ref : snapshotStores) {
if (zoneId == storeMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole())); {
snapshotStore = ref;
break;
}
}
if (snapshotStore == null) {
return null;
}

View File

@ -26,6 +26,7 @@ import javax.inject.Inject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
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;
@ -64,6 +65,7 @@ public class SnapshotObject implements SnapshotInfo {
private DataStore store;
private Object payload;
private Boolean fullBackup;
private String url;
@Inject
protected SnapshotDao snapshotDao;
@Inject
@ -80,8 +82,12 @@ public class SnapshotObject implements SnapshotInfo {
SnapshotDataStoreDao snapshotStoreDao;
@Inject
StorageStrategyFactory storageStrategyFactory;
@Inject
DataStoreManager dataStoreManager;
private String installPath; // temporarily set installPath before passing to resource for entries with empty installPath for object store migration case
private Long zoneId = null;
public SnapshotObject() {
}
@ -142,7 +148,7 @@ public class SnapshotObject implements SnapshotInfo {
List<SnapshotInfo> children = new ArrayList<>();
if (vos != null) {
for (SnapshotDataStoreVO vo : vos) {
SnapshotInfo info = snapshotFactory.getSnapshot(vo.getSnapshotId(), DataStoreRole.Image);
SnapshotInfo info = snapshotFactory.getSnapshot(vo.getSnapshotId(), vo.getDataStoreId(), DataStoreRole.Image);
if (info != null) {
children.add(info);
}
@ -164,7 +170,7 @@ public class SnapshotObject implements SnapshotInfo {
@Override
public long getPhysicalSize() {
long physicalSize = 0;
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Image);
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Image, store.getId(), snapshot.getId());
if (snapshotStore != null) {
physicalSize = snapshotStore.getPhysicalSize();
}
@ -194,9 +200,16 @@ public class SnapshotObject implements SnapshotInfo {
@Override
public String getUri() {
if (url != null) {
return url;
}
return snapshot.getUuid();
}
public void setUrl(String url) {
this.url = url;
}
@Override
public DataStore getDataStore() {
return store;
@ -309,7 +322,10 @@ public class SnapshotObject implements SnapshotInfo {
@Override
public Long getDataCenterId() {
return snapshot.getDataCenterId();
if (zoneId == null) {
zoneId = dataStoreManager.getStoreZoneId(store.getId(), store.getRole());
}
return zoneId;
}
public void processEvent(Snapshot.Event event) throws NoTransitionException {

View File

@ -25,8 +25,11 @@ import javax.inject.Inject;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionService;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
@ -42,16 +45,25 @@ import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.async.AsyncRpcContext;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.jobs.AsyncJob;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.log4j.Logger;
import com.cloud.storage.CreateSnapshotPayload;
import com.cloud.agent.api.Answer;
import com.cloud.configuration.Config;
import com.cloud.dc.DataCenter;
import com.cloud.event.EventTypes;
import com.cloud.event.UsageEventUtils;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.storage.CreateSnapshotPayload;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
@ -82,6 +94,10 @@ public class SnapshotServiceImpl implements SnapshotService {
private SnapshotDetailsDao _snapshotDetailsDao;
@Inject
VolumeDataFactory volFactory;
@Inject
EndPointSelector epSelector;
@Inject
ConfigurationDao _configDao;
static private class CreateSnapshotContext<T> extends AsyncRpcContext<T> {
final SnapshotInfo snapshot;
@ -120,6 +136,20 @@ public class SnapshotServiceImpl implements SnapshotService {
}
static private class PrepareCopySnapshotContext<T> extends AsyncRpcContext<T> {
final SnapshotInfo snapshot;
final String copyUrlBase;
final AsyncCallFuture<CreateCmdResult> future;
public PrepareCopySnapshotContext(AsyncCompletionCallback<T> callback, SnapshotInfo snapshot, String copyUrlBase, AsyncCallFuture<CreateCmdResult> future) {
super(callback);
this.snapshot = snapshot;
this.copyUrlBase = copyUrlBase;
this.future = future;
}
}
static private class RevertSnapshotContext<T> extends AsyncRpcContext<T> {
final SnapshotInfo snapshot;
final AsyncCallFuture<SnapshotResult> future;
@ -132,6 +162,30 @@ public class SnapshotServiceImpl implements SnapshotService {
}
private String generateCopyUrlBase(String hostname, String dir) {
String scheme = "http";
boolean _sslCopy = false;
String sslCfg = _configDao.getValue(Config.SecStorageEncryptCopy.toString());
String _ssvmUrlDomain = _configDao.getValue("secstorage.ssl.cert.domain");
if (sslCfg != null) {
_sslCopy = Boolean.parseBoolean(sslCfg);
}
if(_sslCopy && (_ssvmUrlDomain == null || _ssvmUrlDomain.isEmpty())){
s_logger.warn("Empty secondary storage url domain, ignoring SSL");
_sslCopy = false;
}
if (_sslCopy) {
if(_ssvmUrlDomain.startsWith("*")) {
hostname = hostname.replace(".", "-");
hostname = hostname + _ssvmUrlDomain.substring(1);
} else {
hostname = _ssvmUrlDomain;
}
scheme = "https";
}
return scheme + "://" + hostname + "/copy/SecStorage/" + dir;
}
protected Void createSnapshotAsyncCallback(AsyncCallbackDispatcher<SnapshotServiceImpl, CreateCmdResult> callback, CreateSnapshotContext<CreateCmdResult> context) {
CreateCmdResult result = callback.getResult();
SnapshotObject snapshot = (SnapshotObject)context.snapshot;
@ -251,7 +305,13 @@ public class SnapshotServiceImpl implements SnapshotService {
// find the image store where the parent snapshot backup is located
SnapshotDataStoreVO parentSnapshotOnBackupStore = null;
if (parentSnapshot != null) {
parentSnapshotOnBackupStore = _snapshotStoreDao.findBySnapshot(parentSnapshot.getId(), DataStoreRole.Image);
List<SnapshotDataStoreVO> snaps = _snapshotStoreDao.listReadyBySnapshot(snapshot.getId(), DataStoreRole.Image);
for (SnapshotDataStoreVO ref : snaps) {
if (snapshot.getDataCenterId() != null && snapshot.getDataCenterId().equals(dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole()))) {
parentSnapshotOnBackupStore = ref;
break;
}
}
}
if (parentSnapshotOnBackupStore == null) {
return dataStoreMgr.getImageStoreWithFreeCapacity(snapshot.getDataCenterId());
@ -356,6 +416,49 @@ public class SnapshotServiceImpl implements SnapshotService {
return null;
}
protected Void copySnapshotZoneAsyncCallback(AsyncCallbackDispatcher<SnapshotServiceImpl, CreateCmdResult> callback, CopySnapshotContext<CommandResult> context) {
CreateCmdResult result = callback.getResult();
SnapshotInfo destSnapshot = context.destSnapshot;
AsyncCallFuture<SnapshotResult> future = context.future;
SnapshotResult snapResult = new SnapshotResult(destSnapshot, result.getAnswer());
if (result.isFailed()) {
snapResult.setResult(result.getResult());
destSnapshot.processEvent(Event.OperationFailed);
future.complete(snapResult);
return null;
}
try {
Answer answer = result.getAnswer();
destSnapshot.processEvent(Event.OperationSuccessed);
snapResult = new SnapshotResult(_snapshotFactory.getSnapshot(destSnapshot.getId(), destSnapshot.getDataStore()), answer);
future.complete(snapResult);
} catch (Exception e) {
s_logger.debug("Failed to update snapshot state", e);
snapResult.setResult(e.toString());
future.complete(snapResult);
}
return null;
}
protected Void prepareCopySnapshotZoneAsyncCallback(AsyncCallbackDispatcher<SnapshotServiceImpl, QuerySnapshotZoneCopyAnswer> callback, PrepareCopySnapshotContext<CommandResult> context) {
QuerySnapshotZoneCopyAnswer answer = callback.getResult();
if (answer == null || !answer.getResult()) {
CreateCmdResult result = new CreateCmdResult(null, answer);
result.setResult(answer != null ? answer.getDetails() : "Unsupported answer");
context.future.complete(result);
return null;
}
List<String> files = answer.getFiles();
final String copyUrlBase = context.copyUrlBase;
StringBuilder url = new StringBuilder();
for (String file : files) {
url.append(copyUrlBase).append("/").append(file).append("\n");
}
CreateCmdResult result = new CreateCmdResult(url.toString().trim(), answer);
context.future.complete(result);
return null;
}
protected Void deleteSnapshotCallback(AsyncCallbackDispatcher<SnapshotServiceImpl, CommandResult> callback, DeleteSnapshotContext<CommandResult> context) {
CommandResult result = callback.getResult();
@ -432,7 +535,7 @@ public class SnapshotServiceImpl implements SnapshotService {
@Override
public boolean revertSnapshot(SnapshotInfo snapshot) {
PrimaryDataStore store = null;
SnapshotInfo snapshotOnPrimaryStore = _snapshotFactory.getSnapshot(snapshot.getId(), DataStoreRole.Primary);
SnapshotInfo snapshotOnPrimaryStore = _snapshotFactory.getSnapshotOnPrimaryStore(snapshot.getId());
if (snapshotOnPrimaryStore == null) {
s_logger.warn("Cannot find an entry for snapshot " + snapshot.getId() + " on primary storage pools, searching with volume's primary storage pool");
VolumeInfo volumeInfo = volFactory.getVolume(snapshot.getVolumeId(), DataStoreRole.Primary);
@ -608,4 +711,56 @@ public class SnapshotServiceImpl implements SnapshotService {
}
@Override
public AsyncCallFuture<SnapshotResult> copySnapshot(SnapshotInfo snapshot, String copyUrl, DataStore store) throws ResourceUnavailableException {
SnapshotObject snapshotForCopy = (SnapshotObject)_snapshotFactory.getSnapshot(snapshot, store);
snapshotForCopy.setUrl(copyUrl);
if (s_logger.isDebugEnabled()) {
s_logger.debug("Mark snapshot_store_ref entry as Creating");
}
AsyncCallFuture<SnapshotResult> future = new AsyncCallFuture<SnapshotResult>();
DataObject snapshotOnStore = store.create(snapshotForCopy);
((SnapshotObject)snapshotOnStore).setUrl(copyUrl);
snapshotOnStore.processEvent(Event.CreateOnlyRequested);
if (s_logger.isDebugEnabled()) {
s_logger.debug("Invoke datastore driver createAsync to create snapshot on destination store");
}
try {
CopySnapshotContext<CommandResult> context = new CopySnapshotContext<>(null, (SnapshotObject)snapshotOnStore, snapshotForCopy, future);
AsyncCallbackDispatcher<SnapshotServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().copySnapshotZoneAsyncCallback(null, null)).setContext(context);
store.getDriver().createAsync(store, snapshotOnStore, caller);
} catch (CloudRuntimeException ex) {
// clean up already persisted snapshot_store_ref entry
SnapshotDataStoreVO snapshotStoreVO = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Image, store.getId(), snapshot.getId());
if (snapshotStoreVO != null) {
snapshotForCopy.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed);
}
SnapshotResult res = new SnapshotResult((SnapshotObject)snapshotOnStore, null);
res.setResult(ex.getMessage());
future.complete(res);
}
return future;
}
@Override
public AsyncCallFuture<CreateCmdResult> queryCopySnapshot(SnapshotInfo snapshot) throws ResourceUnavailableException {
AsyncCallFuture<CreateCmdResult> future = new AsyncCallFuture<>();
EndPoint ep = epSelector.select(snapshot);
if (ep == null) {
s_logger.error(String.format("Failed to find endpoint for generating copy URL for snapshot %d with store %d", snapshot.getId(), snapshot.getDataStore().getId()));
throw new ResourceUnavailableException("No secondary VM in running state in source snapshot zone", DataCenter.class, snapshot.getDataCenterId());
}
DataStore store = snapshot.getDataStore();
String copyUrlBase = generateCopyUrlBase(ep.getPublicAddr(), ((ImageStoreEntity)store).getMountPoint());
PrepareCopySnapshotContext<CreateCmdResult> context = new PrepareCopySnapshotContext<>(null, snapshot, copyUrlBase, future);
AsyncCallbackDispatcher<SnapshotServiceImpl, QuerySnapshotZoneCopyAnswer> caller = AsyncCallbackDispatcher.create(this);
caller.setCallback(caller.getTarget().prepareCopySnapshotZoneAsyncCallback(null, null)).setContext(context);
caller.setContext(context);
QuerySnapshotZoneCopyCommand cmd = new QuerySnapshotZoneCopyCommand((SnapshotObjectTO)(snapshot.getTO()));
ep.sendMessageAsync(cmd, caller);
return future;
}
}

View File

@ -44,6 +44,7 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
@ -150,7 +151,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
}
@Override
public boolean deleteSnapshot(Long snapshotId) {
public boolean deleteSnapshot(Long snapshotId, Long zoneId) {
Preconditions.checkArgument(snapshotId != null, "'snapshotId' cannot be 'null'.");
SnapshotVO snapshotVO = snapshotDao.findById(snapshotId);
@ -181,7 +182,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
*/
@ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_OFF_PRIMARY, eventDescription = "deleting snapshot", async = true)
private boolean cleanupSnapshotOnPrimaryStore(long snapshotId) {
SnapshotObject snapshotObj = (SnapshotObject)snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Primary);
SnapshotObject snapshotObj = (SnapshotObject)snapshotDataFactory.getSnapshotOnPrimaryStore(snapshotId);
if (snapshotObj == null) {
s_logger.debug("Can't find snapshot; deleting it in DB");
@ -293,7 +294,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
verifySnapshotType(snapshotInfo);
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshotInfo.getId(), DataStoreRole.Primary);
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshotInfo.getId(), DataStoreRole.Primary);
if (snapshotStore != null) {
long snapshotStoragePoolId = snapshotStore.getDataStoreId();
@ -911,7 +912,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
}
@Override
public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
Snapshot.LocationType locationType = snapshot.getLocationType();
// If the snapshot exists on Secondary Storage, we can't delete it.
@ -920,20 +921,26 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
return StrategyPriority.CANT_HANDLE;
}
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Image);
List<SnapshotDataStoreVO> snapshotOnImageStores = snapshotStoreDao.listReadyBySnapshot(snapshot.getId(), DataStoreRole.Image);
// If the snapshot exists on Secondary Storage, we can't delete it.
if (snapshotStore != null) {
if (CollectionUtils.isNotEmpty(snapshotOnImageStores)) {
return StrategyPriority.CANT_HANDLE;
}
snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
if (snapshotStore == null) {
return StrategyPriority.CANT_HANDLE;
}
long snapshotStoragePoolId = snapshotStore.getDataStoreId();
if (zoneId != null) { // If zoneId is present, then it should be same as the zoneId of primary store
StoragePoolVO storagePoolVO = storagePoolDao.findById(snapshotStoragePoolId);
if (!zoneId.equals(storagePoolVO.getDataCenterId())) {
return StrategyPriority.CANT_HANDLE;
}
}
boolean storageSystemSupportsCapability = storageSystemSupportsCapability(snapshotStoragePoolId, DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString());
@ -953,7 +960,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase {
boolean acceptableFormat = isAcceptableRevertFormat(volumeVO);
if (acceptableFormat) {
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
boolean usingBackendSnapshot = usingBackendSnapshotFor(snapshot.getId());

View File

@ -55,7 +55,6 @@ import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.CreateSnapshotPayload;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
@ -390,7 +389,7 @@ public class StorageVMSnapshotStrategy extends DefaultVMSnapshotStrategy {
//The snapshot could not be deleted separately, that's why we set snapshot state to BackedUp for operation delete VM snapshots and rollback
SnapshotStrategy strategy = storageStrategyFactory.getSnapshotStrategy(snapshot, SnapshotOperation.DELETE);
if (strategy != null) {
boolean snapshotForDelete = strategy.deleteSnapshot(snapshot.getId());
boolean snapshotForDelete = strategy.deleteSnapshot(snapshot.getId(), null);
if (!snapshotForDelete) {
throw new CloudRuntimeException("Failed to delete snapshot");
}
@ -415,7 +414,7 @@ public class StorageVMSnapshotStrategy extends DefaultVMSnapshotStrategy {
protected void revertDiskSnapshot(VMSnapshot vmSnapshot) {
List<VMSnapshotDetailsVO> listSnapshots = vmSnapshotDetailsDao.findDetails(vmSnapshot.getId(), STORAGE_SNAPSHOT);
for (VMSnapshotDetailsVO vmSnapshotDetailsVO : listSnapshots) {
SnapshotInfo sInfo = snapshotDataFactory.getSnapshot(Long.parseLong(vmSnapshotDetailsVO.getValue()), DataStoreRole.Primary);
SnapshotInfo sInfo = snapshotDataFactory.getSnapshotOnPrimaryStore(Long.parseLong(vmSnapshotDetailsVO.getValue()));
SnapshotStrategy snapshotStrategy = storageStrategyFactory.getSnapshotStrategy(sInfo, SnapshotOperation.REVERT);
if (snapshotStrategy == null) {
throw new CloudRuntimeException(String.format("Could not find strategy for snapshot uuid [%s]", sInfo.getId()));

View File

@ -81,10 +81,10 @@ public class CephSnapshotStrategyTest {
VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
Mockito.when(volumeVO.getRemoved()).thenReturn(removed);
Mockito.when(volumeDao.findByIdIncludingRemoved(Mockito.anyLong())).thenReturn(volumeVO);
Mockito.lenient().doReturn(isSnapshotStoredOnRbdStoragePool).when(cephSnapshotStrategy).isSnapshotStoredOnRbdStoragePool(Mockito.any());
Mockito.lenient().doReturn(isSnapshotStoredOnRbdStoragePool).when(cephSnapshotStrategy).isSnapshotStoredOnRbdStoragePoolAndOperationForSameZone(Mockito.any(), Mockito.any());
for (int i = 0; i < snapshotOps.length - 1; i++) {
StrategyPriority strategyPriority = cephSnapshotStrategy.canHandle(snapshot, snapshotOps[i]);
StrategyPriority strategyPriority = cephSnapshotStrategy.canHandle(snapshot, null, snapshotOps[i]);
if (snapshotOps[i] == SnapshotOperation.REVERT && isSnapshotStoredOnRbdStoragePool) {
Assert.assertEquals(StrategyPriority.HIGHEST, strategyPriority);
} else {

View File

@ -18,17 +18,16 @@
package org.apache.cloudstack.storage.snapshot;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.cloud.storage.VolumeDetailVO;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
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.SnapshotService;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -41,7 +40,13 @@ import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Storage;
import com.cloud.storage.VolumeDetailVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotZoneDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
@RunWith(MockitoJUnitRunner.class)
@ -74,27 +79,31 @@ public class DefaultSnapshotStrategyTest {
@Mock
SnapshotService snapshotServiceMock;
Map<String, SnapshotInfo> mapStringSnapshotInfoInstance = new LinkedHashMap<>();
@Mock
SnapshotZoneDao snapshotZoneDaoMock;
@Mock
SnapshotDataStoreDao snapshotDataStoreDao;
@Mock
DataStoreManager dataStoreManager;
List<SnapshotInfo> mockSnapshotInfos = new ArrayList<>();
@Before
public void setup() {
mapStringSnapshotInfoInstance.put("secondary storage", snapshotInfo1Mock);
mapStringSnapshotInfoInstance.put("primary storage", snapshotInfo1Mock);
mockSnapshotInfos.add(snapshotInfo1Mock);
mockSnapshotInfos.add(snapshotInfo2Mock);
}
@Test
public void validateRetrieveSnapshotEntries() {
Long snapshotId = 1l;
Mockito.doReturn(snapshotInfo1Mock, snapshotInfo2Mock).when(snapshotDataFactoryMock).getSnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class), Mockito.anyBoolean());
Map<String, SnapshotInfo> result = defaultSnapshotStrategySpy.retrieveSnapshotEntries(snapshotId);
Mockito.doReturn(mockSnapshotInfos).when(snapshotDataFactoryMock).getSnapshots(Mockito.anyLong(), Mockito.any());
List<SnapshotInfo> result = defaultSnapshotStrategySpy.retrieveSnapshotEntries(snapshotId, null);
Mockito.verify(snapshotDataFactoryMock).getSnapshot(snapshotId, DataStoreRole.Image, false);
Mockito.verify(snapshotDataFactoryMock).getSnapshot(snapshotId, DataStoreRole.Primary, false);
Assert.assertTrue(result.containsKey("secondary storage"));
Assert.assertTrue(result.containsKey("primary storage"));
Assert.assertEquals(snapshotInfo1Mock, result.get("secondary storage"));
Assert.assertEquals(snapshotInfo2Mock, result.get("primary storage"));
Assert.assertTrue(result.contains(snapshotInfo1Mock));
Assert.assertTrue(result.contains(snapshotInfo2Mock));
}
@Test
@ -107,38 +116,29 @@ public class DefaultSnapshotStrategyTest {
@Test
public void validateDestroySnapshotEntriesAndFilesFailToDeleteReturnsFalse() {
Mockito.doReturn(false).when(defaultSnapshotStrategySpy).deleteSnapshotInfos(Mockito.any());
Assert.assertFalse(defaultSnapshotStrategySpy.destroySnapshotEntriesAndFiles(snapshotVoMock));
Mockito.doReturn(false).when(defaultSnapshotStrategySpy).deleteSnapshotInfos(Mockito.any(), Mockito.any());
Assert.assertFalse(defaultSnapshotStrategySpy.destroySnapshotEntriesAndFiles(snapshotVoMock, null));
}
@Test
public void validateDestroySnapshotEntriesAndFilesDeletesSuccessfullyReturnsTrue() {
Mockito.doReturn(true).when(defaultSnapshotStrategySpy).deleteSnapshotInfos(Mockito.any());
Assert.assertTrue(defaultSnapshotStrategySpy.destroySnapshotEntriesAndFiles(snapshotVoMock));
Mockito.doReturn(true).when(defaultSnapshotStrategySpy).deleteSnapshotInfos(Mockito.any(), Mockito.any());
Mockito.doNothing().when(snapshotZoneDaoMock).removeSnapshotFromZones(Mockito.anyLong());
Assert.assertTrue(defaultSnapshotStrategySpy.destroySnapshotEntriesAndFiles(snapshotVoMock, null));
}
@Test
public void validateDeleteSnapshotInfosFailToDeleteReturnsFalse() {
Mockito.doReturn(mapStringSnapshotInfoInstance).when(defaultSnapshotStrategySpy).retrieveSnapshotEntries(Mockito.anyLong());
Mockito.doReturn(false).when(defaultSnapshotStrategySpy).deleteSnapshotInfo(Mockito.any(), Mockito.anyString(), Mockito.any());
Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInfos(snapshotVoMock));
Mockito.doReturn(mockSnapshotInfos).when(defaultSnapshotStrategySpy).retrieveSnapshotEntries(Mockito.anyLong(), Mockito.any());
Mockito.doReturn(false).when(defaultSnapshotStrategySpy).deleteSnapshotInfo(Mockito.any(), Mockito.any());
Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInfos(snapshotVoMock, null));
}
@Test
public void validateDeleteSnapshotInfosDeletesSuccessfullyReturnsTrue() {
Mockito.doReturn(mapStringSnapshotInfoInstance).when(defaultSnapshotStrategySpy).retrieveSnapshotEntries(Mockito.anyLong());
Mockito.doReturn(true).when(defaultSnapshotStrategySpy).deleteSnapshotInfo(Mockito.any(), Mockito.anyString(), Mockito.any());
Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInfos(snapshotVoMock));
}
@Test
public void validateDeleteSnapshotInfoSnapshotInfoIsNullOnSecondaryStorageReturnsTrue() {
Assert.assertNull(defaultSnapshotStrategySpy.deleteSnapshotInfo(null, "secondary storage", snapshotVoMock));
}
@Test
public void validateDeleteSnapshotInfoSnapshotInfoIsNullOnPrimaryStorageReturnsFalse() {
Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInfo(null, "primary storage", snapshotVoMock));
Mockito.doReturn(mockSnapshotInfos).when(defaultSnapshotStrategySpy).retrieveSnapshotEntries(Mockito.anyLong(), Mockito.any());
Mockito.doReturn(true).when(defaultSnapshotStrategySpy).deleteSnapshotInfo(Mockito.any(), Mockito.any());
Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInfos(snapshotVoMock, null));
}
@Test
@ -147,8 +147,9 @@ public class DefaultSnapshotStrategyTest {
Mockito.doReturn(snapshotObjectMock).when(defaultSnapshotStrategySpy).castSnapshotInfoToSnapshotObject(snapshotInfo1Mock);
Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
Mockito.doReturn(true).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Primary);
boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "primary storage", snapshotVoMock);
boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock);
Assert.assertTrue(result);
}
@ -158,8 +159,9 @@ public class DefaultSnapshotStrategyTest {
Mockito.doReturn(snapshotObjectMock).when(defaultSnapshotStrategySpy).castSnapshotInfoToSnapshotObject(snapshotInfo1Mock);
Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
Mockito.doReturn(false).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Primary);
boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "primary storage", snapshotVoMock);
boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock);
Assert.assertFalse(result);
}
@ -169,8 +171,9 @@ public class DefaultSnapshotStrategyTest {
Mockito.doReturn(snapshotObjectMock).when(defaultSnapshotStrategySpy).castSnapshotInfoToSnapshotObject(snapshotInfo1Mock);
Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
Mockito.doThrow(CloudRuntimeException.class).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Primary);
boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "primary storage", snapshotVoMock);
boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock);
Assert.assertFalse(result);
}
@ -181,8 +184,9 @@ public class DefaultSnapshotStrategyTest {
Mockito.doNothing().when(defaultSnapshotStrategySpy).verifyIfTheSnapshotIsBeingUsedByAnyVolume(snapshotObjectMock);
Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
Mockito.doReturn(true).when(defaultSnapshotStrategySpy).deleteSnapshotChain(Mockito.any(), Mockito.anyString());
Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Image);
boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "secondary storage", snapshotVoMock);
boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock);
Assert.assertTrue(result);
}
@ -193,8 +197,9 @@ public class DefaultSnapshotStrategyTest {
Mockito.doNothing().when(defaultSnapshotStrategySpy).verifyIfTheSnapshotIsBeingUsedByAnyVolume(snapshotObjectMock);
Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
Mockito.doReturn(false).when(defaultSnapshotStrategySpy).deleteSnapshotChain(Mockito.any(), Mockito.anyString());
Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Image);
boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "secondary storage", snapshotVoMock);
boolean result = defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock);
Assert.assertTrue(result);
}
@ -203,8 +208,9 @@ public class DefaultSnapshotStrategyTest {
Mockito.doReturn(dataStoreMock).when(snapshotInfo1Mock).getDataStore();
Mockito.doReturn(snapshotObjectMock).when(defaultSnapshotStrategySpy).castSnapshotInfoToSnapshotObject(snapshotInfo1Mock);
Mockito.doThrow(NoTransitionException.class).when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
Mockito.when(dataStoreMock.getRole()).thenReturn(DataStoreRole.Image);
Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, "secondary storage", snapshotVoMock));
Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInfo(snapshotInfo1Mock, snapshotVoMock));
}
@Test
@ -231,18 +237,97 @@ public class DefaultSnapshotStrategyTest {
public void deleteSnapshotInPrimaryStorageTestReturnTrueIfDeleteReturnsTrue() throws NoTransitionException {
Mockito.doReturn(true).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
Mockito.doNothing().when(snapshotObjectMock).processEvent(Mockito.any(Snapshot.Event.class));
Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, snapshotObjectMock));
Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, snapshotObjectMock, true));
}
@Test
public void deleteSnapshotInPrimaryStorageTestReturnTrueIfDeleteNotLastRefReturnsTrue() throws NoTransitionException {
Mockito.doReturn(true).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
Assert.assertTrue(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, snapshotObjectMock, false));
}
@Test
public void deleteSnapshotInPrimaryStorageTestReturnFalseIfDeleteReturnsFalse() throws NoTransitionException {
Mockito.doReturn(false).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, null));
Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, null, true));
}
@Test
public void deleteSnapshotInPrimaryStorageTestReturnFalseIfDeleteThrowsException() throws NoTransitionException {
Mockito.doThrow(CloudRuntimeException.class).when(snapshotServiceMock).deleteSnapshot(Mockito.any());
Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, null));
Assert.assertFalse(defaultSnapshotStrategySpy.deleteSnapshotInPrimaryStorage(null, null, null, null, true));
}
@Test
public void testGetSnapshotImageStoreRefNull() {
SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
Mockito.when(ref1.getDataStoreId()).thenReturn(1L);
Mockito.when(ref1.getRole()).thenReturn(DataStoreRole.Image);
Mockito.when(snapshotDataStoreDao.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(ref1));
Mockito.when(dataStoreManager.getStoreZoneId(1L, DataStoreRole.Image)).thenReturn(2L);
Assert.assertNull(defaultSnapshotStrategySpy.getSnapshotImageStoreRef(1L, 1L));
}
@Test
public void testGetSnapshotImageStoreRefNotNull() {
SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
Mockito.when(ref1.getDataStoreId()).thenReturn(1L);
Mockito.when(ref1.getRole()).thenReturn(DataStoreRole.Image);
Mockito.when(snapshotDataStoreDao.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(ref1));
Mockito.when(dataStoreManager.getStoreZoneId(1L, DataStoreRole.Image)).thenReturn(1L);
Assert.assertNotNull(defaultSnapshotStrategySpy.getSnapshotImageStoreRef(1L, 1L));
}
@Test
public void testIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeNull() {
Assert.assertFalse(defaultSnapshotStrategySpy.isSnapshotStoredOnSameZoneStoreForQCOW2Volume(Mockito.mock(Snapshot.class), null));
}
@Test
public void testIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeVHD() {
VolumeVO volumeVO = Mockito.mock((VolumeVO.class));
Mockito.when(volumeVO.getFormat()).thenReturn(Storage.ImageFormat.VHD);
Assert.assertFalse(defaultSnapshotStrategySpy.isSnapshotStoredOnSameZoneStoreForQCOW2Volume(Mockito.mock(Snapshot.class), volumeVO));
}
private void prepareMocksForIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeTest(Long matchingZoneId) {
SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
Mockito.when(ref1.getDataStoreId()).thenReturn(201L);
Mockito.when(ref1.getRole()).thenReturn(DataStoreRole.Image);
SnapshotDataStoreVO ref2 = Mockito.mock(SnapshotDataStoreVO.class);
Mockito.when(ref2.getDataStoreId()).thenReturn(202L);
Mockito.when(ref2.getRole()).thenReturn(DataStoreRole.Image);
SnapshotDataStoreVO ref3 = Mockito.mock(SnapshotDataStoreVO.class);
Mockito.when(ref3.getDataStoreId()).thenReturn(203L);
Mockito.when(ref3.getRole()).thenReturn(DataStoreRole.Image);
Mockito.when(snapshotDataStoreDao.listBySnapshotIdAndState(1L, ObjectInDataStoreStateMachine.State.Ready)).thenReturn(List.of(ref1, ref2, ref3));
Mockito.when(dataStoreManager.getStoreZoneId(201L, DataStoreRole.Image)).thenReturn(111L);
Mockito.when(dataStoreManager.getStoreZoneId(202L, DataStoreRole.Image)).thenReturn(matchingZoneId != null ? matchingZoneId : 112L);
Mockito.when(dataStoreManager.getStoreZoneId(203L, DataStoreRole.Image)).thenReturn(113L);
}
@Test
public void testIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeNoRef() {
Snapshot snapshot = Mockito.mock((Snapshot.class));
Mockito.when(snapshot.getId()).thenReturn(1L);
VolumeVO volumeVO = Mockito.mock((VolumeVO.class));
Mockito.when(volumeVO.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
Mockito.when(snapshotDataStoreDao.listBySnapshotIdAndState(1L, ObjectInDataStoreStateMachine.State.Ready)).thenReturn(new ArrayList<>());
Assert.assertFalse(defaultSnapshotStrategySpy.isSnapshotStoredOnSameZoneStoreForQCOW2Volume(snapshot, volumeVO));
prepareMocksForIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeTest(null);
Assert.assertFalse(defaultSnapshotStrategySpy.isSnapshotStoredOnSameZoneStoreForQCOW2Volume(snapshot, volumeVO));
}
@Test
public void testIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeHasRef() {
Snapshot snapshot = Mockito.mock((Snapshot.class));
Mockito.when(snapshot.getId()).thenReturn(1L);
VolumeVO volumeVO = Mockito.mock((VolumeVO.class));
Mockito.when(volumeVO.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
Mockito.when(volumeVO.getDataCenterId()).thenReturn(100L);
prepareMocksForIsSnapshotStoredOnSameZoneStoreForQCOW2VolumeTest(100L);
Assert.assertTrue(defaultSnapshotStrategySpy.isSnapshotStoredOnSameZoneStoreForQCOW2Volume(snapshot, volumeVO));
}
}

View File

@ -62,7 +62,7 @@ public class SnapshotDataFactoryImplTest {
public void getSnapshotsByVolumeAndDataStoreTestNoSnapshotDataStoreVOFound() {
Mockito.doReturn(new ArrayList<>()).when(snapshotStoreDaoMock).listAllByVolumeAndDataStore(volumeMockId, DataStoreRole.Primary);
List<SnapshotInfo> snapshots = snapshotDataFactoryImpl.getSnapshots(volumeMockId, DataStoreRole.Primary);
List<SnapshotInfo> snapshots = snapshotDataFactoryImpl.getSnapshotsForVolumeAndStoreRole(volumeMockId, DataStoreRole.Primary);
Assert.assertTrue(snapshots.isEmpty());
}
@ -91,7 +91,7 @@ public class SnapshotDataFactoryImplTest {
Mockito.doReturn(dataStoreMock).when(dataStoreManagerMock).getDataStore(dataStoreId, dataStoreRole);
Mockito.doReturn(snapshotVoMock).when(snapshotDaoMock).findById(snapshotId);
List<SnapshotInfo> snapshots = snapshotDataFactoryImpl.getSnapshots(volumeMockId, dataStoreRole);
List<SnapshotInfo> snapshots = snapshotDataFactoryImpl.getSnapshotsForVolumeAndStoreRole(volumeMockId, dataStoreRole);
Assert.assertEquals(1, snapshots.size());

View File

@ -18,7 +18,6 @@
*/
package org.apache.cloudstack.storage.snapshot;
import com.cloud.storage.DataStoreRole;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
@ -41,6 +40,8 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.cloud.storage.DataStoreRole;
@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class SnapshotServiceImplTest {
@ -65,7 +66,7 @@ public class SnapshotServiceImplTest {
Mockito.when(snapshot.getId()).thenReturn(1L);
Mockito.when(snapshot.getVolumeId()).thenReturn(1L);
Mockito.when(_snapshotFactory.getSnapshot(1L, DataStoreRole.Primary)).thenReturn(null);
Mockito.when(_snapshotFactory.getSnapshotOnPrimaryStore(1L)).thenReturn(null);
Mockito.when(volFactory.getVolume(1L, DataStoreRole.Primary)).thenReturn(volumeInfo);
PrimaryDataStore store = Mockito.mock(PrimaryDataStore.class);
@ -82,5 +83,4 @@ public class SnapshotServiceImplTest {
Assert.assertTrue(snapshotService.revertSnapshot(snapshot));
}
}
}

View File

@ -170,4 +170,16 @@ public class DataStoreManagerImpl implements DataStoreManager {
public void setImageDataStoreMgr(ImageStoreProviderManager imageDataStoreMgr) {
this.imageDataStoreMgr = imageDataStoreMgr;
}
@Override
public Long getStoreZoneId(long storeId, DataStoreRole role) {
try {
if (role == DataStoreRole.Primary) {
return primaryStoreMgr.getPrimaryDataStoreZoneId(storeId);
} else {
return imageDataStoreMgr.getImageStoreZoneId(storeId);
}
} catch (CloudRuntimeException ignored) {}
return null;
}
}

View File

@ -40,6 +40,4 @@ public interface ObjectInDataStoreManager {
DataObjectInStore findObject(long objId, DataObjectType type, long dataStoreId, DataStoreRole role, String deployAsIsConfiguration);
DataObjectInStore findObject(DataObject obj, DataStore store);
DataStore findStore(long objId, DataObjectType type, DataStoreRole role);
}

View File

@ -382,27 +382,4 @@ public class ObjectInDataStoreManagerImpl implements ObjectInDataStoreManager {
}
@Override
public DataStore findStore(long objId, DataObjectType type, DataStoreRole role) {
DataStore store = null;
if (role == DataStoreRole.Image) {
DataObjectInStore vo = null;
switch (type) {
case TEMPLATE:
vo = templateDataStoreDao.findByTemplate(objId, role);
break;
case SNAPSHOT:
vo = snapshotDataStoreDao.findBySnapshot(objId, role);
break;
case VOLUME:
vo = volumeDataStoreDao.findByVolume(objId);
break;
}
if (vo != null) {
store = this.storeMgr.getDataStore(vo.getDataStoreId(), role);
}
}
return store;
}
}

View File

@ -30,4 +30,6 @@ public interface PrimaryDataStoreProviderManager {
boolean registerDriver(String providerName, PrimaryDataStoreDriver driver);
boolean registerHostListener(String providerName, HypervisorHostListener listener);
public long getPrimaryDataStoreZoneId(long dataStoreId);
}

View File

@ -66,10 +66,15 @@ public class StorageStrategyFactoryImpl implements StorageStrategyFactory {
@Override
public SnapshotStrategy getSnapshotStrategy(final Snapshot snapshot, final SnapshotOperation op) {
return getSnapshotStrategy(snapshot, null, op);
}
@Override
public SnapshotStrategy getSnapshotStrategy(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
return bestMatch(snapshotStrategies, new CanHandle<SnapshotStrategy>() {
@Override
public StrategyPriority canHandle(SnapshotStrategy strategy) {
return strategy.canHandle(snapshot, op);
return strategy.canHandle(snapshot, zoneId, op);
}
});
}

View File

@ -32,17 +32,11 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.agent.api.to.NfsTO;
import com.cloud.agent.api.to.OVFInformationTO;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Upload;
import org.apache.cloudstack.storage.image.deployasis.DeployAsIsHelper;
import org.apache.log4j.Logger;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
@ -53,11 +47,15 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.CopyCommand;
import org.apache.cloudstack.storage.command.DeleteCommand;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
import org.apache.cloudstack.storage.endpoint.DefaultEndPointSelector;
import org.apache.cloudstack.storage.image.deployasis.DeployAsIsHelper;
import org.apache.log4j.Logger;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
@ -68,6 +66,8 @@ import com.cloud.agent.api.storage.GetDatadisksCommand;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.DatadiskTO;
import com.cloud.agent.api.to.NfsTO;
import com.cloud.agent.api.to.OVFInformationTO;
import com.cloud.alert.AlertManager;
import com.cloud.configuration.Config;
import com.cloud.exception.AgentUnavailableException;
@ -76,7 +76,8 @@ import com.cloud.host.Host;
import com.cloud.host.dao.HostDao;
import com.cloud.secstorage.CommandExecLogDao;
import com.cloud.secstorage.CommandExecLogVO;
import com.cloud.storage.StorageManager;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Upload;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VolumeVO;
@ -84,8 +85,6 @@ import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VMTemplateZoneDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.download.DownloadMonitor;
import com.cloud.user.ResourceLimitService;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.db.TransactionLegacy;
import com.cloud.utils.exception.CloudRuntimeException;
@ -107,6 +106,8 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver {
@Inject
TemplateDataStoreDao _templateStoreDao;
@Inject
SnapshotDataStoreDao snapshotDataStoreDao;
@Inject
EndPointSelector _epSelector;
@Inject
ConfigurationDao configDao;
@ -117,21 +118,17 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver {
@Inject
DefaultEndPointSelector _defaultEpSelector;
@Inject
AccountDao _accountDao;
@Inject
ResourceLimitService _resourceLimitMgr;
@Inject
DeployAsIsHelper deployAsIsHelper;
@Inject
HostDao hostDao;
@Inject
CommandExecLogDao _cmdExecLogDao;
@Inject
StorageManager storageMgr;
@Inject
protected SecondaryStorageVmDao _secStorageVmDao;
@Inject
AgentManager agentMgr;
@Inject
DataStoreManager dataStoreManager;
protected String _proxy = null;
@ -192,6 +189,12 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver {
LOGGER.debug("Downloading volume to data store " + dataStore.getId());
}
_downloadMonitor.downloadVolumeToStorage(data, caller);
} else if (data.getType() == DataObjectType.SNAPSHOT) {
caller.setCallback(caller.getTarget().createSnapshotAsyncCallback(null, null));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Downloading volume to data store " + dataStore.getId());
}
_downloadMonitor.downloadSnapshotToStorage(data, caller);
}
}
@ -313,6 +316,53 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver {
return null;
}
protected Void createSnapshotAsyncCallback(AsyncCallbackDispatcher<? extends BaseImageStoreDriverImpl, DownloadAnswer> callback, CreateContext<CreateCmdResult> context) {
DownloadAnswer answer = callback.getResult();
DataObject obj = context.data;
DataStore store = obj.getDataStore();
SnapshotDataStoreVO snapshotStoreVO = snapshotDataStoreDao.findByStoreSnapshot(DataStoreRole.Image, store.getId(), obj.getId());
if (snapshotStoreVO != null) {
if (VMTemplateStorageResourceAssoc.Status.DOWNLOADED.equals(snapshotStoreVO.getDownloadState())) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Snapshot is already in DOWNLOADED state, ignore further incoming DownloadAnswer");
}
return null;
}
SnapshotDataStoreVO updateBuilder = snapshotDataStoreDao.createForUpdate();
updateBuilder.setDownloadPercent(answer.getDownloadPct());
updateBuilder.setDownloadState(answer.getDownloadStatus());
updateBuilder.setLastUpdated(new Date());
updateBuilder.setErrorString(answer.getErrorString());
updateBuilder.setJobId(answer.getJobId());
updateBuilder.setLocalDownloadPath(answer.getDownloadPath());
updateBuilder.setInstallPath(answer.getInstallPath());
updateBuilder.setSize(answer.getTemplateSize());
updateBuilder.setPhysicalSize(answer.getTemplatePhySicalSize());
snapshotDataStoreDao.update(snapshotStoreVO.getId(), updateBuilder);
}
AsyncCompletionCallback<CreateCmdResult> caller = context.getParentCallback();
if (List.of(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR,
VMTemplateStorageResourceAssoc.Status.ABANDONED,
VMTemplateStorageResourceAssoc.Status.UNKNOWN).contains(answer.getDownloadStatus())) {
CreateCmdResult result = new CreateCmdResult(null, null);
result.setSuccess(false);
result.setResult(answer.getErrorString());
caller.complete(result);
String msg = "Failed to copy snapshot: " + obj.getUuid() + " with error: " + answer.getErrorString();
Long zoneId = dataStoreManager.getStoreZoneId(store.getId(), store.getRole());
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED,
zoneId, null, msg, msg);
LOGGER.error(msg);
} else if (answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED) {
CreateCmdResult result = new CreateCmdResult(null, null);
caller.complete(result);
}
return null;
}
@Override
public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CommandResult> callback) {
CommandResult result = new CommandResult();
@ -331,7 +381,7 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver {
result.setResult(answer.getDetails());
}
} catch (Exception ex) {
LOGGER.debug("Unable to destoy " + data.getType().toString() + ": " + data.getId(), ex);
LOGGER.debug("Unable to destroy " + data.getType().toString() + ": " + data.getId(), ex);
result.setResult(ex.toString());
}
callback.complete(result);

View File

@ -80,4 +80,5 @@ public interface ImageStoreProviderManager {
List<DataStore> listImageStoresWithFreeCapacity(List<DataStore> imageStores);
List<DataStore> orderImageStoresOnFreeCapacity(List<DataStore> imageStores);
long getImageStoreZoneId(long dataStoreId);
}

View File

@ -25,14 +25,17 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation;
import org.apache.cloudstack.storage.helper.StorageStrategyFactoryImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.host.Host;
import com.cloud.storage.Snapshot;
@RunWith(MockitoJUnitRunner.class)
public class StrategyPriorityTest {
@Test
@ -42,31 +45,31 @@ public class StrategyPriorityTest {
SnapshotStrategy hyperStrategy = mock(SnapshotStrategy.class);
SnapshotStrategy highestStrategy = mock(SnapshotStrategy.class);
doReturn(StrategyPriority.CANT_HANDLE).when(cantHandleStrategy).canHandle(any(Snapshot.class), any(SnapshotOperation.class));
doReturn(StrategyPriority.DEFAULT).when(defaultStrategy).canHandle(any(Snapshot.class), any(SnapshotOperation.class));
doReturn(StrategyPriority.HYPERVISOR).when(hyperStrategy).canHandle(any(Snapshot.class), any(SnapshotOperation.class));
doReturn(StrategyPriority.HIGHEST).when(highestStrategy).canHandle(any(Snapshot.class), any(SnapshotOperation.class));
doReturn(StrategyPriority.CANT_HANDLE).when(cantHandleStrategy).canHandle(any(Snapshot.class), Mockito.nullable(Long.class), any(SnapshotStrategy.SnapshotOperation.class));
doReturn(StrategyPriority.DEFAULT).when(defaultStrategy).canHandle(any(Snapshot.class), Mockito.nullable(Long.class), any(SnapshotStrategy.SnapshotOperation.class));
doReturn(StrategyPriority.HYPERVISOR).when(hyperStrategy).canHandle(any(Snapshot.class), Mockito.nullable(Long.class), any(SnapshotStrategy.SnapshotOperation.class));
doReturn(StrategyPriority.HIGHEST).when(highestStrategy).canHandle(any(Snapshot.class), Mockito.nullable(Long.class), any(SnapshotStrategy.SnapshotOperation.class));
List<SnapshotStrategy> strategies = new ArrayList<SnapshotStrategy>(5);
List<SnapshotStrategy> strategies = new ArrayList<>(5);
SnapshotStrategy strategy = null;
StorageStrategyFactoryImpl factory = new StorageStrategyFactoryImpl();
factory.setSnapshotStrategies(strategies);
strategies.add(cantHandleStrategy);
strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotOperation.TAKE);
strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotStrategy.SnapshotOperation.TAKE);
assertEquals("A strategy was found when it shouldn't have been.", null, strategy);
strategies.add(defaultStrategy);
strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotOperation.TAKE);
strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotStrategy.SnapshotOperation.TAKE);
assertEquals("Default strategy was not picked.", defaultStrategy, strategy);
strategies.add(hyperStrategy);
strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotOperation.TAKE);
strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotStrategy.SnapshotOperation.TAKE);
assertEquals("Hypervisor strategy was not picked.", hyperStrategy, strategy);
strategies.add(highestStrategy);
strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotOperation.TAKE);
strategy = factory.getSnapshotStrategy(mock(Snapshot.class), SnapshotStrategy.SnapshotOperation.TAKE);
assertEquals("Highest strategy was not picked.", highestStrategy, strategy);
}

View File

@ -19,12 +19,6 @@
package org.apache.cloudstack.storage.datastore.db;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -33,6 +27,13 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
@RunWith(MockitoJUnitRunner.class)
public class SnapshotDataStoreDaoImplTest {
@ -61,17 +62,15 @@ public class SnapshotDataStoreDaoImplTest {
@Test
public void validateExpungeReferenceBySnapshotIdAndDataStoreRoleNullReference(){
Mockito.doReturn(searchCriteriaMock).when(snapshotDataStoreDaoImplSpy).createSearchCriteriaBySnapshotIdAndStoreRole(Mockito.anyLong(), Mockito.any());
Mockito.doReturn(null).when(snapshotDataStoreDaoImplSpy).findOneBy(searchCriteriaMock);
Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(0, DataStoreRole.Image));
Mockito.doReturn(null).when(snapshotDataStoreDaoImplSpy).findByStoreSnapshot(Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(0, 1L, DataStoreRole.Image));
}
@Test
public void validateExpungeReferenceBySnapshotIdAndDataStoreRole(){
Mockito.doReturn(searchCriteriaMock).when(snapshotDataStoreDaoImplSpy).createSearchCriteriaBySnapshotIdAndStoreRole(Mockito.anyLong(), Mockito.any());
Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findOneBy(searchCriteriaMock);
Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findByStoreSnapshot(Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
Mockito.doReturn(true).when(snapshotDataStoreDaoImplSpy).expunge(Mockito.anyLong());
Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(0, DataStoreRole.Image));
Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(0, 1L, DataStoreRole.Image));
}
@Test
@ -112,33 +111,30 @@ public class SnapshotDataStoreDaoImplTest {
@Test
public void expungeReferenceBySnapshotIdAndDataStoreRoleTestSnapshotDataStoreIsNullReturnTrue() {
Mockito.doReturn(searchCriteriaMock).when(snapshotDataStoreDaoImplSpy).createSearchCriteriaBySnapshotIdAndStoreRole(Mockito.anyLong(), Mockito.any());
Mockito.doReturn(null).when(snapshotDataStoreDaoImplSpy).findOneBy(Mockito.any());
Mockito.doReturn(null).when(snapshotDataStoreDaoImplSpy).findByStoreSnapshot(Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
for (DataStoreRole value : DataStoreRole.values()) {
Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, value));
Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, 1, value));
}
}
@Test
public void expungeReferenceBySnapshotIdAndDataStoreRoleTestSnapshotDataStoreIsNotNullAndExpungeIsTrueReturnTrue() {
Mockito.doReturn(searchCriteriaMock).when(snapshotDataStoreDaoImplSpy).createSearchCriteriaBySnapshotIdAndStoreRole(Mockito.anyLong(), Mockito.any());
Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findOneBy(Mockito.any());
Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findByStoreSnapshot(Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
Mockito.doReturn(true).when(snapshotDataStoreDaoImplSpy).expunge(Mockito.anyLong());
for (DataStoreRole value : DataStoreRole.values()) {
Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, value));
Assert.assertTrue(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, 1, value));
}
}
@Test
public void expungeReferenceBySnapshotIdAndDataStoreRoleTestSnapshotDataStoreIsNotNullAndExpungeIsFalseReturnTrue() {
Mockito.doReturn(searchCriteriaMock).when(snapshotDataStoreDaoImplSpy).createSearchCriteriaBySnapshotIdAndStoreRole(Mockito.anyLong(), Mockito.any());
Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findOneBy(Mockito.any());
Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoImplSpy).findByStoreSnapshot(Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
Mockito.doReturn(false).when(snapshotDataStoreDaoImplSpy).expunge(Mockito.anyLong());
for (DataStoreRole value : DataStoreRole.values()) {
Assert.assertFalse(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, value));
Assert.assertFalse(snapshotDataStoreDaoImplSpy.expungeReferenceBySnapshotIdAndDataStoreRole(1, 1, value));
}
}

View File

@ -16,14 +16,15 @@
// under the License.
package org.apache.cloudstack.storage.image.db;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import java.util.Arrays;
import java.util.List;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
public class TemplateDataStoreDaoImplTest {

View File

@ -85,4 +85,13 @@ public class PrimaryDataStoreProviderManagerImpl implements PrimaryDataStoreProv
public boolean registerHostListener(String providerName, HypervisorHostListener listener) {
return storageMgr.registerHostListener(providerName, listener);
}
@Override
public long getPrimaryDataStoreZoneId(long dataStoreId) {
StoragePoolVO dataStoreVO = dataStoreDao.findByIdIncludingRemoved(dataStoreId);
if (dataStoreVO == null) {
throw new CloudRuntimeException("Unable to locate datastore with id " + dataStoreId);
}
return dataStoreVO.getDataCenterId();
}
}

View File

@ -0,0 +1,48 @@
// 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.storage.datastore.manager;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PrimaryDataStoreProviderManagerImplTest {
@Mock
PrimaryDataStoreDao primaryDataStoreDao;
@InjectMocks
PrimaryDataStoreProviderManagerImpl primaryDataStoreProviderManager = new PrimaryDataStoreProviderManagerImpl();
@Test
public void testGetImageStoreZoneId() {
final long storeId = 1L;
final long zoneId = 1L;
StoragePoolVO storagePoolVO = Mockito.mock(StoragePoolVO.class);
Mockito.when(storagePoolVO.getDataCenterId()).thenReturn(zoneId);
Mockito.when(primaryDataStoreDao.findByIdIncludingRemoved(storeId)).thenReturn(storagePoolVO);
long value = primaryDataStoreProviderManager.getPrimaryDataStoreZoneId(storeId);
Assert.assertEquals(zoneId, value);
}
}

View File

@ -17,6 +17,8 @@
package org.apache.cloudstack.framework.jobs.impl;
import static com.cloud.utils.HumanReadableJson.getHumanReadableBytesJson;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
@ -33,10 +35,7 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.storage.dao.VolumeDetailsDao;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.log4j.Logger;
import org.apache.log4j.NDC;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
@ -49,27 +48,29 @@ import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao;
import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao;
import org.apache.cloudstack.framework.jobs.dao.AsyncJobJoinMapDao;
import org.apache.cloudstack.framework.jobs.dao.AsyncJobJournalDao;
import org.apache.cloudstack.framework.jobs.dao.SyncQueueItemDao;
import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao;
import org.apache.cloudstack.framework.messagebus.MessageBus;
import org.apache.cloudstack.framework.messagebus.MessageDetector;
import org.apache.cloudstack.framework.messagebus.PublishScope;
import org.apache.cloudstack.jobs.JobInfo;
import org.apache.cloudstack.jobs.JobInfo.Status;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.management.ManagementServerHost;
import org.apache.cloudstack.utils.identity.ManagementServerNode;
import org.apache.log4j.Logger;
import org.apache.log4j.MDC;
import org.apache.log4j.NDC;
import com.cloud.cluster.ClusterManagerListener;
import org.apache.cloudstack.management.ManagementServerHost;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Snapshot;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.dao.SnapshotDetailsVO;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.Predicate;
@ -94,9 +95,6 @@ import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.exception.ExceptionUtil;
import com.cloud.utils.mgmt.JmxUtil;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.storage.dao.VolumeDao;
import static com.cloud.utils.HumanReadableJson.getHumanReadableBytesJson;
public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager, ClusterManagerListener, Configurable {
// Advanced
@ -1115,7 +1113,7 @@ public class AsyncJobManagerImpl extends ManagerBase implements AsyncJobManager,
}
final List<SnapshotDetailsVO> snapshotList = _snapshotDetailsDao.findDetails(AsyncJob.Constants.MS_ID, Long.toString(msid), false);
for (final SnapshotDetailsVO snapshotDetailsVO : snapshotList) {
SnapshotInfo snapshot = snapshotFactory.getSnapshot(snapshotDetailsVO.getResourceId(), DataStoreRole.Primary);
SnapshotInfo snapshot = snapshotFactory.getSnapshotOnPrimaryStore(snapshotDetailsVO.getResourceId());
if (snapshot == null) {
_snapshotDetailsDao.remove(snapshotDetailsVO.getId());
continue;

View File

@ -556,22 +556,33 @@ public class PresetVariableHelper {
value.setName(snapshotVo.getName());
value.setSize(ByteScaleUtils.bytesToMebibytes(snapshotVo.getSize()));
value.setSnapshotType(Snapshot.Type.values()[snapshotVo.getSnapshotType()]);
value.setStorage(getPresetVariableValueStorage(getSnapshotDataStoreId(snapshotId), usageType));
value.setStorage(getPresetVariableValueStorage(getSnapshotDataStoreId(snapshotId, usageRecord.getZoneId()), usageType));
value.setTags(getPresetVariableValueResourceTags(snapshotId, ResourceObjectType.Snapshot));
}
protected SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) {
List<SnapshotDataStoreVO> snaps = snapshotDataStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
for (SnapshotDataStoreVO ref : snaps) {
ImageStoreVO store = imageStoreDao.findById(ref.getDataStoreId());
if (store != null && zoneId == store.getDataCenterId()) {
return ref;
}
}
return null;
}
/**
* If {@link SnapshotInfo#BackupSnapshotAfterTakingSnapshot} is enabled, returns the secondary storage's ID where the snapshot is. Otherwise, returns the primary storage's ID
* where the snapshot is.
*/
protected long getSnapshotDataStoreId(Long snapshotId) {
protected long getSnapshotDataStoreId(Long snapshotId, long zoneId) {
if (backupSnapshotAfterTakingSnapshot) {
SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findBySnapshot(snapshotId, DataStoreRole.Image);
SnapshotDataStoreVO snapshotStore = getSnapshotImageStoreRef(snapshotId, zoneId);
validateIfObjectIsNull(snapshotStore, snapshotId, "data store for snapshot");
return snapshotStore.getDataStoreId();
}
SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findBySnapshot(snapshotId, DataStoreRole.Primary);
SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findOneBySnapshotAndDatastoreRole(snapshotId, DataStoreRole.Primary);
validateIfObjectIsNull(snapshotStore, snapshotId, "data store for snapshot");
return snapshotStore.getDataStoreId();
}

View File

@ -863,7 +863,7 @@ public class PresetVariableHelperTest {
Mockito.doReturn(expected.getName()).when(snapshotVoMock).getName();
Mockito.doReturn(expected.getSize()).when(snapshotVoMock).getSize();
Mockito.doReturn((short) 3).when(snapshotVoMock).getSnapshotType();
Mockito.doReturn(1l).when(presetVariableHelperSpy).getSnapshotDataStoreId(Mockito.anyLong());
Mockito.doReturn(1l).when(presetVariableHelperSpy).getSnapshotDataStoreId(Mockito.anyLong(), Mockito.anyLong());
Mockito.doReturn(expected.getStorage()).when(presetVariableHelperSpy).getPresetVariableValueStorage(Mockito.anyLong(), Mockito.anyInt());
Mockito.doReturn(expected.getTags()).when(presetVariableHelperSpy).getPresetVariableValueResourceTags(Mockito.anyLong(), Mockito.any(ResourceObjectType.class));
@ -891,19 +891,19 @@ public class PresetVariableHelperTest {
SnapshotDataStoreVO snapshotDataStoreVoMock = Mockito.mock(SnapshotDataStoreVO.class);
Long expected = 1l;
Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoMock).findBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class));
Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoMock).findOneBySnapshotAndDatastoreRole(Mockito.anyLong(), Mockito.any(DataStoreRole.class));
Mockito.doReturn(expected).when(snapshotDataStoreVoMock).getDataStoreId();
presetVariableHelperSpy.backupSnapshotAfterTakingSnapshot = false;
Long result = presetVariableHelperSpy.getSnapshotDataStoreId(1l);
Long result = presetVariableHelperSpy.getSnapshotDataStoreId(1l, 1l);
Assert.assertEquals(expected, result);
Arrays.asList(DataStoreRole.values()).forEach(role -> {
if (role == DataStoreRole.Primary) {
Mockito.verify(snapshotDataStoreDaoMock).findBySnapshot(Mockito.anyLong(), Mockito.eq(role));
Mockito.verify(snapshotDataStoreDaoMock).findOneBySnapshotAndDatastoreRole(Mockito.anyLong(), Mockito.eq(role));
} else {
Mockito.verify(snapshotDataStoreDaoMock, Mockito.never()).findBySnapshot(Mockito.anyLong(), Mockito.eq(role));
Mockito.verify(snapshotDataStoreDaoMock, Mockito.never()).findOneBySnapshotAndDatastoreRole(Mockito.anyLong(), Mockito.eq(role));
}
});
}
@ -913,19 +913,22 @@ public class PresetVariableHelperTest {
SnapshotDataStoreVO snapshotDataStoreVoMock = Mockito.mock(SnapshotDataStoreVO.class);
Long expected = 2l;
Mockito.doReturn(snapshotDataStoreVoMock).when(snapshotDataStoreDaoMock).findBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class));
ImageStoreVO imageStore = Mockito.mock(ImageStoreVO.class);
Mockito.when(imageStoreDaoMock.findById(Mockito.anyLong())).thenReturn(imageStore);
Mockito.when(imageStore.getDataCenterId()).thenReturn(1L);
Mockito.when(snapshotDataStoreDaoMock.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(snapshotDataStoreVoMock));
Mockito.doReturn(expected).when(snapshotDataStoreVoMock).getDataStoreId();
presetVariableHelperSpy.backupSnapshotAfterTakingSnapshot = true;
Long result = presetVariableHelperSpy.getSnapshotDataStoreId(2l);
Long result = presetVariableHelperSpy.getSnapshotDataStoreId(2l, 1L);
Assert.assertEquals(expected, result);
Arrays.asList(DataStoreRole.values()).forEach(role -> {
if (role == DataStoreRole.Image) {
Mockito.verify(snapshotDataStoreDaoMock).findBySnapshot(Mockito.anyLong(), Mockito.eq(role));
Mockito.verify(snapshotDataStoreDaoMock).listReadyBySnapshot(Mockito.anyLong(), Mockito.eq(role));
} else {
Mockito.verify(snapshotDataStoreDaoMock, Mockito.never()).findBySnapshot(Mockito.anyLong(), Mockito.eq(role));
Mockito.verify(snapshotDataStoreDaoMock, Mockito.never()).listReadyBySnapshot(Mockito.anyLong(), Mockito.eq(role));
}
});
}
@ -1148,4 +1151,26 @@ public class PresetVariableHelperTest {
Assert.assertEquals(expected.getExternalId(), result.getExternalId());
validateFieldNamesToIncludeInToString(Arrays.asList("id", "name", "externalId"), result);
}
@Test
public void testGetSnapshotImageStoreRefNull() {
SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
Mockito.when(ref1.getDataStoreId()).thenReturn(1L);
Mockito.when(snapshotDataStoreDaoMock.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(ref1));
ImageStoreVO store = Mockito.mock(ImageStoreVO.class);
Mockito.when(store.getDataCenterId()).thenReturn(2L);
Mockito.when(imageStoreDaoMock.findById(1L)).thenReturn(store);
Assert.assertNull(presetVariableHelperSpy.getSnapshotImageStoreRef(1L, 1L));
}
@Test
public void testGetSnapshotImageStoreRefNotNull() {
SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
Mockito.when(ref1.getDataStoreId()).thenReturn(1L);
Mockito.when(snapshotDataStoreDaoMock.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(ref1));
ImageStoreVO store = Mockito.mock(ImageStoreVO.class);
Mockito.when(store.getDataCenterId()).thenReturn(1L);
Mockito.when(imageStoreDaoMock.findById(1L)).thenReturn(store);
Assert.assertNotNull(presetVariableHelperSpy.getSnapshotImageStoreRef(1L, 1L));
}
}

View File

@ -19,17 +19,25 @@
package com.cloud.storage.resource;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.cloud.hypervisor.vmware.manager.VmwareManager;
import com.cloud.utils.NumbersUtil;
import org.apache.log4j.Logger;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.command.CopyCommand;
import org.apache.cloudstack.storage.command.DeleteCommand;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyAnswer;
import org.apache.cloudstack.storage.command.QuerySnapshotZoneCopyCommand;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.to.DataObjectType;
@ -38,9 +46,11 @@ import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.NfsTO;
import com.cloud.agent.api.to.S3TO;
import com.cloud.agent.api.to.SwiftTO;
import com.cloud.hypervisor.vmware.manager.VmwareManager;
import com.cloud.hypervisor.vmware.manager.VmwareStorageManager;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.resource.VmwareStorageProcessor.VmwareStorageProcessorConfigurableFields;
import com.cloud.utils.NumbersUtil;
public class VmwareStorageSubsystemCommandHandler extends StorageSubsystemCommandHandlerBase {
@ -202,4 +212,32 @@ public class VmwareStorageSubsystemCommandHandler extends StorageSubsystemComman
}
}
@Override
protected Answer execute(QuerySnapshotZoneCopyCommand cmd) {
SnapshotObjectTO snapshot = cmd.getSnapshot();
String parentPath = storageResource.getRootDir(snapshot.getDataStore().getUrl(), _nfsVersion);
String path = snapshot.getPath();
File snapFile = new File(parentPath + File.separator + path);
if (snapFile.exists() && !snapFile.isDirectory()) {
return new QuerySnapshotZoneCopyAnswer(cmd, List.of(path));
}
int index = path.lastIndexOf(File.separator);
String snapDir = path.substring(0, index);
List<String> files = new ArrayList<>();
try (Stream<Path> stream = Files.list(Paths.get(parentPath + File.separator + snapDir))) {
List<String> fileNames = stream
.filter(file -> !Files.isDirectory(file))
.map(Path::getFileName)
.map(Path::toString)
.collect(Collectors.toList());
for (String file : fileNames) {
file = snapDir + "/" + file;
s_logger.debug(String.format("Found snapshot file %s", file));
files.add(file);
}
} catch (IOException ioe) {
s_logger.error("Error preparing file list for snapshot copy", ioe);
}
return new QuerySnapshotZoneCopyAnswer(cmd, files);
}
}

View File

@ -22,13 +22,6 @@ import java.util.Map;
import javax.inject.Inject;
import com.cloud.agent.api.storage.MigrateVolumeCommand;
import com.cloud.agent.api.storage.ResizeVolumeCommand;
import com.cloud.agent.api.to.StorageFilerTO;
import com.cloud.host.HostVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
@ -69,13 +62,17 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.MigrateVolumeCommand;
import com.cloud.agent.api.storage.ResizeVolumeCommand;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.DiskTO;
import com.cloud.agent.api.to.StorageFilerTO;
import com.cloud.alert.AlertManager;
import com.cloud.configuration.Config;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.server.ManagementServerImpl;
import com.cloud.storage.DataStoreRole;
@ -97,7 +94,10 @@ import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.dao.VMInstanceDao;
import com.google.common.base.Preconditions;
public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@ -915,7 +915,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
List<SnapshotVO> snapshots = snapshotDao.listByVolumeId(srcVolumeId);
if (CollectionUtils.isNotEmpty(snapshots)) {
for (SnapshotVO snapshot : snapshots) {
SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findByStoreSnapshot(DataStoreRole.Primary, srcPoolId, snapshot.getId());
if (snapshotStore == null) {
continue;
}
@ -1086,7 +1086,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
List<SnapshotVO> snapshots = snapshotDao.listByVolumeId(srcData.getId());
if (CollectionUtils.isNotEmpty(snapshots)) {
for (SnapshotVO snapshot : snapshots) {
SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findByStoreSnapshot(DataStoreRole.Primary, srcPoolId, snapshot.getId());
if (snapshotStore == null) {
continue;
}

View File

@ -18,6 +18,52 @@
*/
package org.apache.cloudstack.storage.datastore.driver;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.RemoteHostEndPoint;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.cloudstack.storage.datastore.util.StorPoolHelper;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.storage.volume.VolumeObject;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand;
@ -61,50 +107,6 @@ import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.RemoteHostEndPoint;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.cloudstack.storage.datastore.util.StorPoolHelper;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.storage.volume.VolumeObject;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.log4j.Logger;
import javax.inject.Inject;
import java.util.List;
import java.util.Map;
public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@ -142,6 +144,18 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
private StoragePoolDetailsDao storagePoolDetailsDao;
@Inject
private StoragePoolHostDao storagePoolHostDao;
@Inject
DataStoreManager dataStoreManager;
private SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) {
List<SnapshotDataStoreVO> snaps = snapshotDataStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
for (SnapshotDataStoreVO ref : snaps) {
if (zoneId == dataStoreManager.getStoreZoneId(ref.getDataStoreId(), ref.getRole())) {
return ref;
}
}
return null;
}
@Override
public Map<String, String> getCapabilities() {
@ -468,7 +482,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
} else if (resp.getError().getName().equals("objectDoesNotExist")) {
//check if snapshot is on secondary storage
StorPoolUtil.spLog("Snapshot %s does not exists on StorPool, will try to create a volume from a snopshot on secondary storage", snapshotName);
SnapshotDataStoreVO snap = snapshotDataStoreDao.findBySnapshot(sinfo.getId(), DataStoreRole.Image);
SnapshotDataStoreVO snap = getSnapshotImageStoreRef(sinfo.getId(), vinfo.getDataCenterId());
if (snap != null && StorPoolStorageAdaptor.getVolumeNameFromPath(snap.getInstallPath(), false) == null) {
resp = StorPoolUtil.volumeCreate(srcData.getUuid(), null, size, null, "no", "snapshot", sinfo.getBaseVolume().getMaxIops(), conn);
if (resp.getError() == null) {

View File

@ -16,10 +16,13 @@
// under the License.
package org.apache.cloudstack.storage.snapshot;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
@ -36,6 +39,7 @@ import org.apache.cloudstack.storage.datastore.util.StorPoolHelper;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
@ -48,6 +52,7 @@ import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.dao.SnapshotDetailsVO;
import com.cloud.storage.dao.SnapshotZoneDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
@ -73,6 +78,10 @@ public class StorPoolSnapshotStrategy implements SnapshotStrategy {
private SnapshotDataFactory snapshotDataFactory;
@Inject
private StoragePoolDetailsDao storagePoolDetailsDao;
@Inject
DataStoreManager dataStoreMgr;
@Inject
SnapshotZoneDao snapshotZoneDao;
@Override
public SnapshotInfo backupSnapshot(SnapshotInfo snapshotInfo) {
@ -92,7 +101,7 @@ public class StorPoolSnapshotStrategy implements SnapshotStrategy {
}
@Override
public boolean deleteSnapshot(Long snapshotId) {
public boolean deleteSnapshot(Long snapshotId, Long zoneId) {
final SnapshotVO snapshotVO = _snapshotDao.findById(snapshotId);
VolumeVO volume = _volumeDao.findByIdIncludingRemoved(snapshotVO.getVolumeId());
@ -108,11 +117,7 @@ public class StorPoolSnapshotStrategy implements SnapshotStrategy {
final String err = String.format("Failed to clean-up Storpool snapshot %s. Error: %s", name, resp.getError());
StorPoolUtil.spLog(err);
} else {
SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshotId, snapshotVO.getUuid());
if (snapshotDetails != null) {
_snapshotDetailsDao.removeDetails(snapshotId);
}
res = deleteSnapshotFromDb(snapshotId);
res = deleteSnapshotFromDbIfNeeded(snapshotVO, zoneId);
StorPoolUtil.spLog("StorpoolSnapshotStrategy.deleteSnapshot: executed successfully=%s, snapshot uuid=%s, name=%s", res, snapshotVO.getUuid(), name);
}
} catch (Exception e) {
@ -125,13 +130,22 @@ public class StorPoolSnapshotStrategy implements SnapshotStrategy {
}
@Override
public StrategyPriority canHandle(Snapshot snapshot, SnapshotOperation op) {
public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) {
log.debug(String.format("StorpoolSnapshotStrategy.canHandle: snapshot=%s, uuid=%s, op=%s", snapshot.getName(), snapshot.getUuid(), op));
if (op != SnapshotOperation.DELETE) {
return StrategyPriority.CANT_HANDLE;
}
SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
if (snapshotOnPrimary == null) {
return StrategyPriority.CANT_HANDLE;
}
if (zoneId != null) { // If zoneId is present, then it should be same as the zoneId of primary store
StoragePoolVO storagePoolVO = _primaryDataStoreDao.findById(snapshotOnPrimary.getDataStoreId());
if (!zoneId.equals(storagePoolVO.getDataCenterId())) {
return StrategyPriority.CANT_HANDLE;
}
}
String name = StorPoolHelper.getSnapshotName(snapshot.getId(), snapshot.getUuid(), _snapshotStoreDao, _snapshotDetailsDao);
if (name != null) {
StorPoolUtil.spLog("StorpoolSnapshotStrategy.canHandle: globalId=%s", name);
@ -147,6 +161,7 @@ public class StorPoolSnapshotStrategy implements SnapshotStrategy {
private boolean deleteSnapshotChain(SnapshotInfo snapshot) {
log.debug("delete snapshot chain for snapshot: " + snapshot.getId());
final SnapshotInfo snapOnImage = snapshot;
boolean result = false;
boolean resultIsSet = false;
try {
@ -174,8 +189,7 @@ public class StorPoolSnapshotStrategy implements SnapshotStrategy {
}
}
if (!deleted) {
SnapshotInfo snap = snapshotDataFactory.getSnapshot(snapshot.getId(), DataStoreRole.Image);
if (StorPoolStorageAdaptor.getVolumeNameFromPath(snap.getPath(), true) == null) {
if (StorPoolStorageAdaptor.getVolumeNameFromPath(snapOnImage.getPath(), true) == null) {
try {
boolean r = snapshotSvr.deleteSnapshot(snapshot);
if (r) {
@ -204,8 +218,64 @@ public class StorPoolSnapshotStrategy implements SnapshotStrategy {
return result;
}
private boolean deleteSnapshotFromDb(Long snapshotId) {
SnapshotVO snapshotVO = _snapshotDao.findById(snapshotId);
protected boolean areLastSnapshotRef(long snapshotId) {
List<SnapshotDataStoreVO> snapshotStoreRefs = _snapshotStoreDao.findBySnapshotId(snapshotId);
if (CollectionUtils.isEmpty(snapshotStoreRefs) || snapshotStoreRefs.size() == 1) {
return true;
}
return snapshotStoreRefs.size() == 2 && DataStoreRole.Primary.equals(snapshotStoreRefs.get(1).getRole());
}
protected boolean deleteSnapshotOnImageAndPrimary(long snapshotId, DataStore store) {
SnapshotInfo snapshotOnImage = snapshotDataFactory.getSnapshot(snapshotId, store);
SnapshotObject obj = (SnapshotObject)snapshotOnImage;
boolean areLastSnapshotRef = areLastSnapshotRef(snapshotId);
try {
if (areLastSnapshotRef) {
obj.processEvent(Snapshot.Event.DestroyRequested);
}
} catch (NoTransitionException e) {
log.debug("Failed to set the state to destroying: ", e);
return false;
}
try {
boolean result = deleteSnapshotChain(snapshotOnImage);
_snapshotStoreDao.updateDisplayForSnapshotStoreRole(snapshotId, store.getId(), store.getRole(), false);
if (areLastSnapshotRef) {
obj.processEvent(Snapshot.Event.OperationSucceeded);
}
if (result) {
SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshotOnImage.getSnapshotId(), DataStoreRole.Primary);
if (snapshotOnPrimary != null) {
snapshotOnPrimary.setState(State.Destroyed);
_snapshotStoreDao.update(snapshotOnPrimary.getId(), snapshotOnPrimary);
}
}
} catch (Exception e) {
log.debug("Failed to delete snapshot: ", e);
try {
if (areLastSnapshotRef) {
obj.processEvent(Snapshot.Event.OperationFailed);
}
} catch (NoTransitionException e1) {
log.debug("Failed to change snapshot state: " + e.toString());
}
return false;
}
return true;
}
private boolean deleteSnapshotFromDbIfNeeded(SnapshotVO snapshotVO, Long zoneId) {
final long snapshotId = snapshotVO.getId();
SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshotId, snapshotVO.getUuid());
if (snapshotDetails != null) {
_snapshotDetailsDao.removeDetails(snapshotId);
}
if (zoneId != null && List.of(Snapshot.State.Allocated, Snapshot.State.CreatedOnPrimary).contains(snapshotVO.getState())) {
throw new InvalidParameterValueException(String.format("Snapshot in %s can not be deleted for a zone", snapshotVO.getState()));
}
if (snapshotVO.getState() == Snapshot.State.Allocated) {
_snapshotDao.remove(snapshotId);
@ -218,10 +288,21 @@ public class StorPoolSnapshotStrategy implements SnapshotStrategy {
if (Snapshot.State.Error.equals(snapshotVO.getState())) {
List<SnapshotDataStoreVO> storeRefs = _snapshotStoreDao.findBySnapshotId(snapshotId);
List<Long> deletedRefs = new ArrayList<>();
for (SnapshotDataStoreVO ref : storeRefs) {
_snapshotStoreDao.expunge(ref.getId());
boolean refZoneIdMatch = false;
if (zoneId != null) {
Long refZoneId = dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole());
refZoneIdMatch = zoneId.equals(refZoneId);
}
if (zoneId == null || refZoneIdMatch) {
_snapshotStoreDao.expunge(ref.getId());
deletedRefs.add(ref.getId());
}
}
if (deletedRefs.size() == storeRefs.size()) {
_snapshotDao.remove(snapshotId);
}
_snapshotDao.remove(snapshotId);
return true;
}
@ -233,46 +314,26 @@ public class StorPoolSnapshotStrategy implements SnapshotStrategy {
if (!Snapshot.State.BackedUp.equals(snapshotVO.getState()) && !Snapshot.State.Error.equals(snapshotVO.getState()) &&
!Snapshot.State.Destroying.equals(snapshotVO.getState())) {
throw new InvalidParameterValueException("Can't delete snapshotshot " + snapshotId + " due to it is in " + snapshotVO.getState() + " Status");
throw new InvalidParameterValueException("Can't delete snapshot " + snapshotId + " due to it is in " + snapshotVO.getState() + " Status");
}
SnapshotInfo snapshotOnImage = snapshotDataFactory.getSnapshot(snapshotId, DataStoreRole.Image);
if (snapshotOnImage == null) {
log.debug("Can't find snapshot on backup storage, delete it in db");
_snapshotDao.remove(snapshotId);
return true;
List<SnapshotDataStoreVO> storeRefs = _snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
if (zoneId != null) {
storeRefs.removeIf(ref -> !zoneId.equals(dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole())));
}
SnapshotObject obj = (SnapshotObject)snapshotOnImage;
try {
obj.processEvent(Snapshot.Event.DestroyRequested);
} catch (NoTransitionException e) {
log.debug("Failed to set the state to destroying: ", e);
return false;
}
try {
boolean result = deleteSnapshotChain(snapshotOnImage);
obj.processEvent(Snapshot.Event.OperationSucceeded);
if (result) {
SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findBySnapshot(snapshotId, DataStoreRole.Primary);
if (snapshotOnPrimary != null) {
snapshotOnPrimary.setState(State.Destroyed);
_snapshotStoreDao.update(snapshotOnPrimary.getId(), snapshotOnPrimary);
}
for (SnapshotDataStoreVO ref : storeRefs) {
if (!deleteSnapshotOnImageAndPrimary(snapshotId, dataStoreMgr.getDataStore(ref.getDataStoreId(), ref.getRole()))) {
return false;
}
} catch (Exception e) {
log.debug("Failed to delete snapshot: ", e);
try {
obj.processEvent(Snapshot.Event.OperationFailed);
} catch (NoTransitionException e1) {
log.debug("Failed to change snapshot state: " + e.toString());
}
return false;
}
if (zoneId != null) {
snapshotZoneDao.removeSnapshotFromZone(snapshotVO.getId(), zoneId);
} else {
snapshotZoneDao.removeSnapshotFromZones(snapshotVO.getId());
}
return true;
}
@Override
public SnapshotInfo takeSnapshot(SnapshotInfo snapshot) {
return null;

View File

@ -18,8 +18,8 @@
# $Id: createtmplt.sh 9132 2010-06-04 20:17:43Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/storage/secondary/createtmplt.sh $
# createtmplt.sh -- install a volume
# $Id: createvolume.sh 9132 2010-06-04 20:17:43Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/storage/secondary/createvolume.sh $
# createvolume.sh -- install a volume
usage() {
printf "Usage: %s: -t <volume-fs> -n <volumename> -f <root disk file> -c <md5 cksum> -d <descr> -h [-u] [-v]\n" $(basename $0) >&2

View File

@ -16,6 +16,78 @@
// under the License.
package com.cloud.api;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiConstants.DomainDetails;
import org.apache.cloudstack.api.ApiConstants.HostDetails;
import org.apache.cloudstack.api.ApiConstants.VMDetails;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.AsyncJobResponse;
import org.apache.cloudstack.api.response.BackupOfferingResponse;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.api.response.BackupScheduleResponse;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.DomainRouterResponse;
import org.apache.cloudstack.api.response.EventResponse;
import org.apache.cloudstack.api.response.HostForMigrationResponse;
import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.HostTagResponse;
import org.apache.cloudstack.api.response.ImageStoreResponse;
import org.apache.cloudstack.api.response.InstanceGroupResponse;
import org.apache.cloudstack.api.response.NetworkOfferingResponse;
import org.apache.cloudstack.api.response.ProjectAccountResponse;
import org.apache.cloudstack.api.response.ProjectInvitationResponse;
import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.ResourceIconResponse;
import org.apache.cloudstack.api.response.ResourceTagResponse;
import org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.StorageTagResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.UserResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.api.response.VpcOfferingResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.backup.BackupSchedule;
import org.apache.cloudstack.backup.dao.BackupDao;
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
import org.apache.cloudstack.backup.dao.BackupScheduleDao;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.jobs.AsyncJob;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao;
import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO;
import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import com.cloud.agent.api.VgpuTypesInfo;
import com.cloud.api.query.dao.AccountJoinDao;
import com.cloud.api.query.dao.AffinityGroupJoinDao;
@ -35,6 +107,7 @@ import com.cloud.api.query.dao.ProjectJoinDao;
import com.cloud.api.query.dao.ResourceTagJoinDao;
import com.cloud.api.query.dao.SecurityGroupJoinDao;
import com.cloud.api.query.dao.ServiceOfferingJoinDao;
import com.cloud.api.query.dao.SnapshotJoinDao;
import com.cloud.api.query.dao.StoragePoolJoinDao;
import com.cloud.api.query.dao.TemplateJoinDao;
import com.cloud.api.query.dao.UserAccountJoinDao;
@ -60,6 +133,7 @@ import com.cloud.api.query.vo.ProjectJoinVO;
import com.cloud.api.query.vo.ResourceTagJoinVO;
import com.cloud.api.query.vo.SecurityGroupJoinVO;
import com.cloud.api.query.vo.ServiceOfferingJoinVO;
import com.cloud.api.query.vo.SnapshotJoinVO;
import com.cloud.api.query.vo.StoragePoolJoinVO;
import com.cloud.api.query.vo.TemplateJoinVO;
import com.cloud.api.query.vo.UserAccountJoinVO;
@ -274,72 +348,6 @@ import com.cloud.vm.dao.UserVmDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.snapshot.VMSnapshot;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants.DomainDetails;
import org.apache.cloudstack.api.ApiConstants.HostDetails;
import org.apache.cloudstack.api.ApiConstants.VMDetails;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.AsyncJobResponse;
import org.apache.cloudstack.api.response.BackupOfferingResponse;
import org.apache.cloudstack.api.response.BackupResponse;
import org.apache.cloudstack.api.response.BackupScheduleResponse;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.DomainRouterResponse;
import org.apache.cloudstack.api.response.EventResponse;
import org.apache.cloudstack.api.response.HostForMigrationResponse;
import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.HostTagResponse;
import org.apache.cloudstack.api.response.ImageStoreResponse;
import org.apache.cloudstack.api.response.InstanceGroupResponse;
import org.apache.cloudstack.api.response.NetworkOfferingResponse;
import org.apache.cloudstack.api.response.ProjectAccountResponse;
import org.apache.cloudstack.api.response.ProjectInvitationResponse;
import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.ResourceIconResponse;
import org.apache.cloudstack.api.response.ResourceTagResponse;
import org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.StorageTagResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.UserResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.api.response.VpcOfferingResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.backup.BackupSchedule;
import org.apache.cloudstack.backup.dao.BackupDao;
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
import org.apache.cloudstack.backup.dao.BackupScheduleDao;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.jobs.AsyncJob;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao;
import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
public class ApiDBUtils {
private static ManagementServer s_ms;
@ -441,6 +449,7 @@ public class ApiDBUtils {
static AccountJoinDao s_accountJoinDao;
static AsyncJobJoinDao s_jobJoinDao;
static TemplateJoinDao s_templateJoinDao;
static SnapshotJoinDao s_snapshotJoinDao;
static PhysicalNetworkTrafficTypeDao s_physicalNetworkTrafficTypeDao;
static PhysicalNetworkServiceProviderDao s_physicalNetworkServiceProviderDao;
@ -471,6 +480,7 @@ public class ApiDBUtils {
static BackupOfferingDao s_backupOfferingDao;
static NicDao s_nicDao;
static ResourceManagerUtil s_resourceManagerUtil;
static SnapshotPolicyDetailsDao s_snapshotPolicyDetailsDao;
@Inject
private ManagementServer ms;
@ -662,6 +672,8 @@ public class ApiDBUtils {
private AsyncJobJoinDao jobJoinDao;
@Inject
private TemplateJoinDao templateJoinDao;
@Inject
private SnapshotJoinDao snapshotJoinDao;
@Inject
private PhysicalNetworkTrafficTypeDao physicalNetworkTrafficTypeDao;
@ -725,6 +737,8 @@ public class ApiDBUtils {
private ResourceIconDao resourceIconDao;
@Inject
private ResourceManagerUtil resourceManagerUtil;
@Inject
SnapshotPolicyDetailsDao snapshotPolicyDetailsDao;
@PostConstruct
void init() {
@ -820,6 +834,7 @@ public class ApiDBUtils {
s_accountJoinDao = accountJoinDao;
s_jobJoinDao = jobJoinDao;
s_templateJoinDao = templateJoinDao;
s_snapshotJoinDao = snapshotJoinDao;
s_physicalNetworkTrafficTypeDao = physicalNetworkTrafficTypeDao;
s_physicalNetworkServiceProviderDao = physicalNetworkServiceProviderDao;
@ -832,6 +847,7 @@ public class ApiDBUtils {
s_vpcOfferingDao = vpcOfferingDao;
s_vpcOfferingJoinDao = vpcOfferingJoinDao;
s_snapshotPolicyDao = snapshotPolicyDao;
s_snapshotPolicyDetailsDao = snapshotPolicyDetailsDao;
s_asyncJobDao = asyncJobDao;
s_hostDetailsDao = hostDetailsDao;
s_clusterDetailsDao = clusterDetailsDao;
@ -1649,6 +1665,20 @@ public class ApiDBUtils {
return s_snapshotPolicyDao.findById(policyId);
}
public static List<DataCenterVO> findSnapshotPolicyZones(SnapshotPolicy policy, Volume volume) {
List<SnapshotPolicyDetailVO> zoneDetails = s_snapshotPolicyDetailsDao.findDetails(policy.getId(), ApiConstants.ZONE_ID);
List<Long> zoneIds = new ArrayList<>();
for (SnapshotPolicyDetailVO detail : zoneDetails) {
try {
zoneIds.add(Long.valueOf(detail.getValue()));
} catch (NumberFormatException ignored) {}
}
if (volume != null && !zoneIds.contains(volume.getDataCenterId())) {
zoneIds.add(0, volume.getDataCenterId());
}
return s_zoneDao.listByIds(zoneIds);
}
public static VpcOffering findVpcOfferingById(long offeringId) {
return s_vpcOfferingDao.findById(offeringId);
}
@ -2083,6 +2113,10 @@ public class ApiDBUtils {
return s_templateJoinDao.newTemplateResponse(detailsView, view, vr);
}
public static SnapshotResponse newSnapshotResponse(ResponseView view, boolean isShowUnique, SnapshotJoinVO vr) {
return s_snapshotJoinDao.newSnapshotResponse(view, isShowUnique, vr);
}
public static TemplateResponse newIsoResponse(TemplateJoinVO vr) {
return s_templateJoinDao.newIsoResponse(vr);
}
@ -2091,6 +2125,10 @@ public class ApiDBUtils {
return s_templateJoinDao.setTemplateResponse(detailsView, view, vrData, vr);
}
public static SnapshotResponse fillSnapshotDetails(SnapshotResponse vrData, SnapshotJoinVO vr) {
return s_snapshotJoinDao.setSnapshotResponse(vrData, vr);
}
public static List<TemplateJoinVO> newTemplateView(VirtualMachineTemplate vr) {
return s_templateJoinDao.newTemplateView(vr);
}

View File

@ -38,7 +38,6 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.hypervisor.Hypervisor;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroup;
@ -263,6 +262,7 @@ import com.cloud.gpu.GPU;
import com.cloud.host.ControlState;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.HypervisorCapabilities;
import com.cloud.network.GuestVlan;
import com.cloud.network.GuestVlanRange;
@ -646,6 +646,7 @@ public class ApiResponseHelper implements ResponseGenerator {
DataCenter zone = ApiDBUtils.findZoneById(volume.getDataCenterId());
if (zone != null) {
snapshotResponse.setZoneId(zone.getUuid());
snapshotResponse.setZoneName(zone.getName());
}
if (volume.getVolumeType() == Volume.Type.ROOT && volume.getInstanceId() != null) {
@ -673,7 +674,7 @@ public class ApiResponseHelper implements ResponseGenerator {
} else {
DataStoreRole dataStoreRole = getDataStoreRole(snapshot, _snapshotStoreDao, _dataStoreMgr);
snapshotInfo = snapshotfactory.getSnapshot(snapshot.getId(), dataStoreRole);
snapshotInfo = snapshotfactory.getSnapshotWithRoleAndZone(snapshot.getId(), dataStoreRole, volume.getDataCenterId());
}
if (snapshotInfo == null) {
@ -681,7 +682,7 @@ public class ApiResponseHelper implements ResponseGenerator {
snapshotResponse.setRevertable(false);
} else {
snapshotResponse.setRevertable(snapshotInfo.isRevertable());
snapshotResponse.setPhysicaSize(snapshotInfo.getPhysicalSize());
snapshotResponse.setPhysicalSize(snapshotInfo.getPhysicalSize());
}
// set tag information
@ -700,7 +701,7 @@ public class ApiResponseHelper implements ResponseGenerator {
}
public static DataStoreRole getDataStoreRole(Snapshot snapshot, SnapshotDataStoreDao snapshotStoreDao, DataStoreManager dataStoreMgr) {
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
SnapshotDataStoreVO snapshotStore = snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
if (snapshotStore == null) {
return DataStoreRole.Image;
@ -807,6 +808,16 @@ public class ApiResponseHelper implements ResponseGenerator {
CollectionUtils.addIgnoreNull(tagResponses, tagResponse);
}
policyResponse.setTags(new HashSet<>(tagResponses));
List<ZoneResponse> zoneResponses = new ArrayList<>();
List<DataCenterVO> zones = ApiDBUtils.findSnapshotPolicyZones(policy, vol);
for (DataCenterVO zone : zones) {
ZoneResponse zoneResponse = new ZoneResponse();
zoneResponse.setId(zone.getUuid());
zoneResponse.setName(zone.getName());
zoneResponse.setTags(null);
zoneResponses.add(zoneResponse);
}
policyResponse.setZones(new HashSet<>(zoneResponses));
return policyResponse;
}
@ -1936,7 +1947,7 @@ public class ApiResponseHelper implements ResponseGenerator {
// it seems that the volume can actually be removed from the DB at some point if it's deleted
// if volume comes back null, use another technique to try to discover the zone
if (volume == null) {
SnapshotDataStoreVO snapshotStore = _snapshotStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
SnapshotDataStoreVO snapshotStore = _snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary);
if (snapshotStore != null) {
long storagePoolId = snapshotStore.getDataStoreId();
@ -2837,6 +2848,23 @@ public class ApiResponseHelper implements ResponseGenerator {
response.setDomainName(domain.getName());
}
private void populateOwner(ControlledViewEntityResponse response, ControlledEntity object) {
Account account = ApiDBUtils.findAccountById(object.getAccountId());
if (account.getType() == Account.Type.PROJECT) {
// find the project
Project project = ApiDBUtils.findProjectByProjectAccountId(account.getId());
response.setProjectId(project.getUuid());
response.setProjectName(project.getName());
} else {
response.setAccountName(account.getAccountName());
}
Domain domain = ApiDBUtils.findDomainById(object.getDomainId());
response.setDomainId(domain.getUuid());
response.setDomainName(domain.getName());
}
public static void populateOwner(ControlledViewEntityResponse response, ControlledViewEntity object) {
if (object.getAccountType() == Account.Type.PROJECT) {

View File

@ -79,6 +79,8 @@ import org.apache.cloudstack.api.command.user.project.ListProjectInvitationsCmd;
import org.apache.cloudstack.api.command.user.project.ListProjectsCmd;
import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd;
import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd;
import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd;
import org.apache.cloudstack.api.command.user.tag.ListTagsCmd;
import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMsCmd;
@ -108,6 +110,7 @@ import org.apache.cloudstack.api.response.ResourceTagResponse;
import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
import org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.StorageTagResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
@ -154,6 +157,7 @@ import com.cloud.api.query.dao.ProjectJoinDao;
import com.cloud.api.query.dao.ResourceTagJoinDao;
import com.cloud.api.query.dao.SecurityGroupJoinDao;
import com.cloud.api.query.dao.ServiceOfferingJoinDao;
import com.cloud.api.query.dao.SnapshotJoinDao;
import com.cloud.api.query.dao.StoragePoolJoinDao;
import com.cloud.api.query.dao.TemplateJoinDao;
import com.cloud.api.query.dao.UserAccountJoinDao;
@ -178,6 +182,7 @@ import com.cloud.api.query.vo.ProjectJoinVO;
import com.cloud.api.query.vo.ResourceTagJoinVO;
import com.cloud.api.query.vo.SecurityGroupJoinVO;
import com.cloud.api.query.vo.ServiceOfferingJoinVO;
import com.cloud.api.query.vo.SnapshotJoinVO;
import com.cloud.api.query.vo.StoragePoolJoinVO;
import com.cloud.api.query.vo.TemplateJoinVO;
import com.cloud.api.query.vo.UserAccountJoinVO;
@ -228,6 +233,8 @@ import com.cloud.service.dao.ServiceOfferingDetailsDao;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.ScopeType;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage.TemplateType;
@ -236,6 +243,7 @@ import com.cloud.storage.StoragePoolTagVO;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiServiceImpl;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.StoragePoolTagsDao;
import com.cloud.storage.dao.VMTemplateDao;
@ -461,6 +469,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
@Inject
private ManagementServerHostDao msHostDao;
@Inject
private SnapshotJoinDao snapshotJoinDao;
@Inject
EntityManager entityManager;
@ -3392,6 +3403,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
Account account = CallContext.current().getCallingAccount();
Long domainId = cmd.getDomainId();
Long id = cmd.getId();
List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
String keyword = cmd.getKeyword();
String name = cmd.getName();
String networkType = cmd.getNetworkType();
@ -3418,6 +3430,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
sc.addAnd("networkType", SearchCriteria.Op.EQ, networkType);
}
if (CollectionUtils.isNotEmpty(ids)) {
sc.addAnd("id", SearchCriteria.Op.IN, ids.toArray());
}
if (id != null) {
sc.addAnd("id", SearchCriteria.Op.EQ, id);
} else if (name != null) {
@ -4496,6 +4512,190 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
return responseGenerator.createHealthCheckResponse(_routerDao.findById(routerId), result);
}
@Override
public ListResponse<SnapshotResponse> listSnapshots(ListSnapshotsCmd cmd) {
Account caller = CallContext.current().getCallingAccount();
Pair<List<SnapshotJoinVO>, Integer> result = searchForSnapshotsWithParams(cmd.getId(), cmd.getIds(),
cmd.getVolumeId(), cmd.getSnapshotName(), cmd.getKeyword(), cmd.getTags(),
cmd.getSnapshotType(), cmd.getIntervalType(), cmd.getZoneId(), cmd.getLocationType(),
cmd.isShowUnique(), cmd.getAccountName(), cmd.getDomainId(), cmd.getProjectId(),
cmd.getStartIndex(), cmd.getPageSizeVal(), cmd.listAll(), cmd.isRecursive(), caller);
ListResponse<SnapshotResponse> response = new ListResponse<>();
ResponseView respView = ResponseView.Restricted;
if (CallContext.current().getCallingAccount().getType() == Account.Type.ADMIN) {
respView = ResponseView.Full;
}
List<SnapshotResponse> templateResponses = ViewResponseHelper.createSnapshotResponse(respView, cmd.isShowUnique(), result.first().toArray(new SnapshotJoinVO[result.first().size()]));
response.setResponses(templateResponses, result.second());
return response;
}
@Override
public SnapshotResponse listSnapshot(CopySnapshotCmd cmd) {
Account caller = CallContext.current().getCallingAccount();
List<Long> zoneIds = cmd.getDestinationZoneIds();
Pair<List<SnapshotJoinVO>, Integer> result = searchForSnapshotsWithParams(cmd.getId(), null,
null, null, null, null,
null, null, zoneIds.get(0), Snapshot.LocationType.SECONDARY.name(),
false, null, null, null,
null, null, true, false, caller);
ResponseView respView = ResponseView.Restricted;
if (CallContext.current().getCallingAccount().getType() == Account.Type.ADMIN) {
respView = ResponseView.Full;
}
List<SnapshotResponse> templateResponses = ViewResponseHelper.createSnapshotResponse(respView, false, result.first().get(0));
return templateResponses.get(0);
}
private Pair<List<SnapshotJoinVO>, Integer> searchForSnapshotsWithParams(final Long id, List<Long> ids,
final Long volumeId, final String name, final String keyword, final Map<String, String> tags,
final String snapshotTypeStr, final String intervalTypeStr, final Long zoneId, final String locationTypeStr,
final boolean isShowUnique, final String accountName, Long domainId, final Long projectId,
final Long startIndex, final Long pageSize,final boolean listAll, boolean isRecursive, final Account caller) {
ids = getIdsListFromCmd(id, ids);
Snapshot.LocationType locationType = null;
if (locationTypeStr != null) {
try {
locationType = Snapshot.LocationType.valueOf(locationTypeStr.trim().toUpperCase());
} catch (IllegalArgumentException e) {
throw new InvalidParameterValueException(String.format("Invalid %s specified, %s", ApiConstants.LOCATION_TYPE, locationTypeStr));
}
}
Filter searchFilter = new Filter(SnapshotJoinVO.class, "snapshotStorePair", SortKeyAscending.value(), startIndex, pageSize);
List<Long> permittedAccountIds = new ArrayList<>();
Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(domainId, isRecursive, null);
_accountMgr.buildACLSearchParameters(caller, id, accountName, projectId, permittedAccountIds, domainIdRecursiveListProject, listAll, false);
ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
domainId = domainIdRecursiveListProject.first();
isRecursive = domainIdRecursiveListProject.second();
// Verify parameters
if (volumeId != null) {
VolumeVO volume = volumeDao.findById(volumeId);
if (volume != null) {
_accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume);
}
}
SearchBuilder<SnapshotJoinVO> sb = snapshotJoinDao.createSearchBuilder();
if (isShowUnique) {
sb.select(null, Func.DISTINCT, sb.entity().getId()); // select distinct snapshotId
} else {
sb.select(null, Func.DISTINCT, sb.entity().getSnapshotStorePair()); // select distinct (snapshotId, store_role, store_id) key
}
_accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccountIds, listProjectResourcesCriteria);
sb.and("statusNEQ", sb.entity().getStatus(), SearchCriteria.Op.NEQ); //exclude those Destroyed snapshot, not showing on UI
sb.and("volumeId", sb.entity().getVolumeId(), SearchCriteria.Op.EQ);
sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN);
sb.and("snapshotTypeEQ", sb.entity().getSnapshotType(), SearchCriteria.Op.IN);
sb.and("snapshotTypeNEQ", sb.entity().getSnapshotType(), SearchCriteria.Op.NIN);
sb.and("dataCenterId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ);
sb.and("locationType", sb.entity().getStoreRole(), SearchCriteria.Op.EQ);
if (tags != null && !tags.isEmpty()) {
SearchBuilder<ResourceTagVO> tagSearch = _resourceTagDao.createSearchBuilder();
for (int count = 0; count < tags.size(); count++) {
tagSearch.or().op("key" + String.valueOf(count), tagSearch.entity().getKey(), SearchCriteria.Op.EQ);
tagSearch.and("value" + String.valueOf(count), tagSearch.entity().getValue(), SearchCriteria.Op.EQ);
tagSearch.cp();
}
tagSearch.and("resourceType", tagSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
sb.groupBy(sb.entity().getId());
sb.join("tagSearch", tagSearch, sb.entity().getId(), tagSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER);
}
SearchCriteria<SnapshotJoinVO> sc = sb.create();
_accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccountIds, listProjectResourcesCriteria);
sc.setParameters("statusNEQ", Snapshot.State.Destroyed);
if (volumeId != null) {
sc.setParameters("volumeId", volumeId);
}
if (tags != null && !tags.isEmpty()) {
int count = 0;
sc.setJoinParameters("tagSearch", "resourceType", ResourceObjectType.Snapshot.toString());
for (String key : tags.keySet()) {
sc.setJoinParameters("tagSearch", "key" + String.valueOf(count), key);
sc.setJoinParameters("tagSearch", "value" + String.valueOf(count), tags.get(key));
count++;
}
}
if (zoneId != null) {
sc.setParameters("dataCenterId", zoneId);
}
setIdsListToSearchCriteria(sc, ids);
if (name != null) {
sc.setParameters("name", name);
}
if (id != null) {
sc.setParameters("id", id);
}
if (locationType != null) {
sc.setParameters("locationType", Snapshot.LocationType.PRIMARY.equals(locationType) ? locationType.name() : DataStoreRole.Image.name());
}
if (keyword != null) {
SearchCriteria<SnapshotJoinVO> ssc = snapshotJoinDao.createSearchCriteria();
ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
sc.addAnd("name", SearchCriteria.Op.SC, ssc);
}
if (snapshotTypeStr != null) {
Snapshot.Type snapshotType = SnapshotVO.getSnapshotType(snapshotTypeStr);
if (snapshotType == null) {
throw new InvalidParameterValueException("Unsupported snapshot type " + snapshotTypeStr);
}
if (snapshotType == Snapshot.Type.RECURRING) {
sc.setParameters("snapshotTypeEQ", Snapshot.Type.HOURLY.ordinal(), Snapshot.Type.DAILY.ordinal(), Snapshot.Type.WEEKLY.ordinal(), Snapshot.Type.MONTHLY.ordinal());
} else {
sc.setParameters("snapshotTypeEQ", snapshotType.ordinal());
}
} else if (intervalTypeStr != null && volumeId != null) {
Snapshot.Type type = SnapshotVO.getSnapshotType(intervalTypeStr);
if (type == null) {
throw new InvalidParameterValueException("Unsupported snapshot interval type " + intervalTypeStr);
}
sc.setParameters("snapshotTypeEQ", type.ordinal());
} else {
// Show only MANUAL and RECURRING snapshot types
sc.setParameters("snapshotTypeNEQ", Snapshot.Type.TEMPLATE.ordinal(), Snapshot.Type.GROUP.ordinal());
}
Pair<List<SnapshotJoinVO>, Integer> snapshotDataPair;
if (isShowUnique) {
snapshotDataPair = snapshotJoinDao.searchAndDistinctCount(sc, searchFilter, new String[]{"snapshot_view.id"});
} else {
snapshotDataPair = snapshotJoinDao.searchAndDistinctCount(sc, searchFilter, new String[]{"snapshot_view.snapshot_store_pair"});
}
Integer count = snapshotDataPair.second();
if (count == 0) {
// empty result
return snapshotDataPair;
}
List<SnapshotJoinVO> snapshotData = snapshotDataPair.first();
List<SnapshotJoinVO> snapshots;
if (isShowUnique) {
snapshots = snapshotJoinDao.findByDistinctIds(zoneId, snapshotData.stream().map(SnapshotJoinVO::getId).toArray(Long[]::new));
} else {
snapshots = snapshotJoinDao.searchBySnapshotStorePair(snapshotData.stream().map(SnapshotJoinVO::getSnapshotStorePair).toArray(String[]::new));
}
return new Pair<>(snapshots, count);
}
@Override
public String getConfigComponentName() {
return QueryService.class.getSimpleName();

View File

@ -49,6 +49,7 @@ import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.ResourceTagResponse;
import org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.StorageTagResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
@ -78,6 +79,7 @@ import com.cloud.api.query.vo.ProjectJoinVO;
import com.cloud.api.query.vo.ResourceTagJoinVO;
import com.cloud.api.query.vo.SecurityGroupJoinVO;
import com.cloud.api.query.vo.ServiceOfferingJoinVO;
import com.cloud.api.query.vo.SnapshotJoinVO;
import com.cloud.api.query.vo.StoragePoolJoinVO;
import com.cloud.api.query.vo.TemplateJoinVO;
import com.cloud.api.query.vo.UserAccountJoinVO;
@ -592,6 +594,23 @@ public class ViewResponseHelper {
return new ArrayList<TemplateResponse>(vrDataList.values());
}
public static List<SnapshotResponse> createSnapshotResponse(ResponseView view, boolean isShowUnique, SnapshotJoinVO... snapshots) {
LinkedHashMap<String, SnapshotResponse> vrDataList = new LinkedHashMap<>();
for (SnapshotJoinVO vr : snapshots) {
SnapshotResponse vrData = vrDataList.get(vr.getSnapshotStorePair());
if (vrData == null) {
// first time encountering this snapshot
vrData = ApiDBUtils.newSnapshotResponse(view, isShowUnique, vr);
}
else{
// update tags
vrData = ApiDBUtils.fillSnapshotDetails(vrData, vr);
}
vrDataList.put(vr.getSnapshotStorePair(), vrData);
}
return new ArrayList<SnapshotResponse>(vrDataList.values());
}
public static List<TemplateResponse> createTemplateUpdateResponse(ResponseView view, TemplateJoinVO... templates) {
LinkedHashMap<Long, TemplateResponse> vrDataList = new LinkedHashMap<>();
for (TemplateJoinVO vr : templates) {

View File

@ -0,0 +1,41 @@
// 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 com.cloud.api.query.dao;
import java.util.List;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.response.SnapshotResponse;
import com.cloud.api.query.vo.SnapshotJoinVO;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDao;
import com.cloud.utils.db.SearchCriteria;
public interface SnapshotJoinDao extends GenericDao<SnapshotJoinVO, Long> {
SnapshotResponse newSnapshotResponse(ResponseObject.ResponseView view, boolean isShowUnique, SnapshotJoinVO snapshotJoinVO);
SnapshotResponse setSnapshotResponse(SnapshotResponse snapshotResponse, SnapshotJoinVO snapshot);
Pair<List<SnapshotJoinVO>, Integer> searchIncludingRemovedAndCount(final SearchCriteria<SnapshotJoinVO> sc, final Filter filter);
List<SnapshotJoinVO> searchBySnapshotStorePair(String... pairs);
List<SnapshotJoinVO> findByDistinctIds(Long zoneId, Long... ids);
}

View File

@ -0,0 +1,248 @@
// 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 com.cloud.api.query.dao;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.response.SnapshotResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.query.QueryService;
import org.apache.log4j.Logger;
import com.cloud.api.ApiResponseHelper;
import com.cloud.api.query.vo.SnapshotJoinVO;
import com.cloud.storage.Snapshot;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.user.Account;
import com.cloud.user.AccountService;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
public class SnapshotJoinDaoImpl extends GenericDaoBaseWithTagInformation<SnapshotJoinVO, SnapshotResponse> implements SnapshotJoinDao {
public static final Logger s_logger = Logger.getLogger(SnapshotJoinDaoImpl.class);
@Inject
private AccountService accountService;
@Inject
private AnnotationDao annotationDao;
@Inject
private ConfigurationDao configDao;
@Inject
SnapshotDataFactory snapshotDataFactory;
private final SearchBuilder<SnapshotJoinVO> snapshotStorePairSearch;
private final SearchBuilder<SnapshotJoinVO> snapshotIdsSearch;
SnapshotJoinDaoImpl() {
snapshotStorePairSearch = createSearchBuilder();
snapshotStorePairSearch.and("snapshotStoreState", snapshotStorePairSearch.entity().getStoreState(), SearchCriteria.Op.IN);
snapshotStorePairSearch.and("snapshotStoreIdIN", snapshotStorePairSearch.entity().getSnapshotStorePair(), SearchCriteria.Op.IN);
snapshotStorePairSearch.done();
snapshotIdsSearch = createSearchBuilder();
snapshotIdsSearch.and("zoneId", snapshotIdsSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ);
snapshotIdsSearch.and("idsIN", snapshotIdsSearch.entity().getId(), SearchCriteria.Op.IN);
snapshotIdsSearch.groupBy(snapshotIdsSearch.entity().getId());
snapshotIdsSearch.done();
}
private void setSnapshotInfoDetailsInResponse(SnapshotJoinVO snapshot, SnapshotResponse snapshotResponse, boolean isShowUnique) {
if (!isShowUnique) {
return;
}
if (snapshot.getDataCenterId() == null) {
return;
}
SnapshotInfo snapshotInfo = null;
snapshotInfo = snapshotDataFactory.getSnapshotWithRoleAndZone(snapshot.getId(), snapshot.getStoreRole(), snapshot.getDataCenterId());
if (snapshotInfo == null) {
s_logger.debug("Unable to find info for image store snapshot with uuid " + snapshot.getUuid());
snapshotResponse.setRevertable(false);
} else {
snapshotResponse.setRevertable(snapshotInfo.isRevertable());
snapshotResponse.setPhysicalSize(snapshotInfo.getPhysicalSize());
}
}
private String getSnapshotStatus(SnapshotJoinVO snapshot) {
String status = snapshot.getStatus().toString();
if (snapshot.getDownloadState() == null) {
return status;
}
if (snapshot.getDownloadState() != VMTemplateStorageResourceAssoc.Status.DOWNLOADED) {
status = "Processing";
if (snapshot.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) {
status = snapshot.getDownloadPercent() + "% Downloaded";
} else if (snapshot.getErrorString() == null) {
status = snapshot.getStoreState().toString();
} else {
status = snapshot.getErrorString();
}
}
return status;
}
@Override
public SnapshotResponse newSnapshotResponse(ResponseObject.ResponseView view, boolean isShowUnique, SnapshotJoinVO snapshot) {
final Account caller = CallContext.current().getCallingAccount();
SnapshotResponse snapshotResponse = new SnapshotResponse();
snapshotResponse.setId(snapshot.getUuid());
// populate owner.
ApiResponseHelper.populateOwner(snapshotResponse, snapshot);
if (snapshot.getVolumeId() != null) {
snapshotResponse.setVolumeId(snapshot.getVolumeUuid());
snapshotResponse.setVolumeName(snapshot.getVolumeName());
snapshotResponse.setVolumeType(snapshot.getVolumeType().name());
snapshotResponse.setVirtualSize(snapshot.getVolumeSize());
}
snapshotResponse.setZoneId(snapshot.getDataCenterUuid());
snapshotResponse.setZoneName(snapshot.getDataCenterName());
snapshotResponse.setCreated(snapshot.getCreated());
snapshotResponse.setName(snapshot.getName());
String intervalType = null;
if (snapshot.getSnapshotType() >= 0 && snapshot.getSnapshotType() < Snapshot.Type.values().length) {
intervalType = Snapshot.Type.values()[snapshot.getSnapshotType()].name();
}
snapshotResponse.setIntervalType(intervalType);
snapshotResponse.setState(snapshot.getStatus());
snapshotResponse.setLocationType(snapshot.getLocationType() != null ? snapshot.getLocationType().name() : null);
if (!isShowUnique) {
snapshotResponse.setDatastoreState(snapshot.getStoreState() != null ? snapshot.getStoreState().name() : null);
if (view.equals(ResponseObject.ResponseView.Full)) {
snapshotResponse.setDatastoreId(snapshot.getStoreUuid());
snapshotResponse.setDatastoreName(snapshot.getStoreName());
snapshotResponse.setDatastoreType(snapshot.getStoreRole() != null ? snapshot.getStoreRole().name() : null);
}
// If the user is an 'Admin' or 'the owner of template' or template belongs to a project, add the template download status
if (view == ResponseObject.ResponseView.Full ||
snapshot.getAccountId() == caller.getId() ||
snapshot.getAccountType() == Account.Type.PROJECT) {
String status = getSnapshotStatus(snapshot);
if (status != null) {
snapshotResponse.setStatus(status);
}
}
Map<String, String> downloadDetails = new HashMap<>();
downloadDetails.put("downloadPercent", Integer.toString(snapshot.getDownloadPercent()));
downloadDetails.put("downloadState", (snapshot.getDownloadState() != null ? snapshot.getDownloadState().toString() : ""));
snapshotResponse.setDownloadDetails(downloadDetails);
}
setSnapshotInfoDetailsInResponse(snapshot, snapshotResponse, isShowUnique);
setSnapshotResponse(snapshotResponse, snapshot);
snapshotResponse.setObjectName("snapshot");
return snapshotResponse;
}
@Override
public SnapshotResponse setSnapshotResponse(SnapshotResponse snapshotResponse, SnapshotJoinVO snapshot) {
// update tag information
long tag_id = snapshot.getTagId();
if (tag_id > 0) {
addTagInformation(snapshot, snapshotResponse);
}
if (snapshotResponse.hasAnnotation() == null) {
snapshotResponse.setHasAnnotation(annotationDao.hasAnnotations(snapshot.getUuid(), AnnotationService.EntityType.SNAPSHOT.name(),
accountService.isRootAdmin(CallContext.current().getCallingAccount().getId())));
}
return snapshotResponse;
}
@Override
public Pair<List<SnapshotJoinVO>, Integer> searchIncludingRemovedAndCount(final SearchCriteria<SnapshotJoinVO> sc, final Filter filter) {
List<SnapshotJoinVO> objects = searchIncludingRemoved(sc, filter, null, false);
Integer count = getDistinctCount(sc);
return new Pair<>(objects, count);
}
@Override
public List<SnapshotJoinVO> searchBySnapshotStorePair(String... pairs) {
// set detail batch query size
int DETAILS_BATCH_SIZE = 2000;
String batchCfg = configDao.getValue("detail.batch.query.size");
if (batchCfg != null) {
DETAILS_BATCH_SIZE = Integer.parseInt(batchCfg);
}
// query details by batches
Filter searchFilter = new Filter(SnapshotJoinVO.class, "snapshotStorePair", QueryService.SortKeyAscending.value(), null, null);
List<SnapshotJoinVO> uvList = new ArrayList<>();
// query details by batches
int curr_index = 0;
if (pairs.length > DETAILS_BATCH_SIZE) {
while ((curr_index + DETAILS_BATCH_SIZE) <= pairs.length) {
String[] labels = new String[DETAILS_BATCH_SIZE];
for (int k = 0, j = curr_index; j < curr_index + DETAILS_BATCH_SIZE; j++, k++) {
labels[k] = pairs[j];
}
SearchCriteria<SnapshotJoinVO> sc = snapshotStorePairSearch.create();
sc.setParameters("snapshotStoreIdIN", labels);
List<SnapshotJoinVO> snaps = searchIncludingRemoved(sc, searchFilter, null, false);
if (snaps != null) {
uvList.addAll(snaps);
}
curr_index += DETAILS_BATCH_SIZE;
}
}
if (curr_index < pairs.length) {
int batch_size = (pairs.length - curr_index);
String[] labels = new String[batch_size];
for (int k = 0, j = curr_index; j < curr_index + batch_size; j++, k++) {
labels[k] = pairs[j];
}
SearchCriteria<SnapshotJoinVO> sc = snapshotStorePairSearch.create();
sc.setParameters("snapshotStoreIdIN", labels);
List<SnapshotJoinVO> vms = searchIncludingRemoved(sc, searchFilter, null, false);
if (vms != null) {
uvList.addAll(vms);
}
}
return uvList;
}
@Override
public List<SnapshotJoinVO> findByDistinctIds(Long zoneId, Long... ids) {
if (ids == null || ids.length == 0) {
return new ArrayList<>();
}
Filter searchFilter = new Filter(SnapshotJoinVO.class, "snapshotStorePair", QueryService.SortKeyAscending.value(), null, null);
SearchCriteria<SnapshotJoinVO> sc = snapshotIdsSearch.create();
if (zoneId != null) {
sc.setParameters("zoneId", zoneId);
}
sc.setParameters("idsIN", ids);
return searchIncludingRemoved(sc, searchFilter, null, false);
}
}

View File

@ -0,0 +1,352 @@
// 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 com.cloud.api.query.vo;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Snapshot;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.storage.Volume;
import com.cloud.user.Account;
import com.cloud.utils.db.GenericDao;
@Entity
@Table(name = "snapshot_view")
public class SnapshotJoinVO extends BaseViewWithTagInformationVO implements ControlledViewEntity {
@Column(name = "uuid")
private String uuid;
@Column(name = "name")
private String name;
@Column(name = "status")
@Enumerated(value = EnumType.STRING)
private Snapshot.State status;
@Column(name = "disk_offering_id")
Long diskOfferingId;
@Column(name = "snapshot_type")
short snapshotType;
@Column(name = "type_description")
String typeDescription;
@Column(name = "size")
long size;
@Column(name = GenericDao.CREATED_COLUMN)
Date created;
@Column(name = GenericDao.REMOVED_COLUMN)
Date removed;
@Column(name = "location_type")
@Enumerated(value = EnumType.STRING)
private Snapshot.LocationType locationType;
@Column(name = "hypervisor_type")
@Enumerated(value = EnumType.STRING)
Hypervisor.HypervisorType hypervisorType;
@Column(name = "account_id")
private long accountId;
@Column(name = "account_uuid")
private String accountUuid;
@Column(name = "account_name")
private String accountName = null;
@Column(name = "account_type")
@Enumerated(value = EnumType.ORDINAL)
private Account.Type accountType;
@Column(name = "domain_id")
private long domainId;
@Column(name = "domain_uuid")
private String domainUuid;
@Column(name = "domain_name")
private String domainName = null;
@Column(name = "domain_path")
private String domainPath = null;
@Column(name = "project_id")
private Long projectId;
@Column(name = "project_uuid")
private String projectUuid;
@Column(name = "project_name")
private String projectName;
@Column(name = "data_center_id")
private Long dataCenterId;
@Column(name = "data_center_uuid")
private String dataCenterUuid;
@Column(name = "data_center_name")
private String dataCenterName;
@Column(name = "volume_id")
private Long volumeId;
@Column(name = "volume_uuid")
private String volumeUuid;
@Column(name = "volume_name")
private String volumeName;
@Column(name = "volume_type")
@Enumerated(EnumType.STRING)
Volume.Type volumeType = Volume.Type.UNKNOWN;
@Column(name = "volume_size")
Long volumeSize;
@Column(name = "store_id")
private Long storeId;
@Column(name = "store_uuid")
private String storeUuid;
@Column(name = "store_name")
private String storeName;
@Column(name = "store_role")
@Enumerated(EnumType.STRING)
private DataStoreRole storeRole;
@Column(name = "store_state")
@Enumerated(EnumType.STRING)
private ObjectInDataStoreStateMachine.State storeState;
@Column(name = "download_state")
@Enumerated(EnumType.STRING)
private VMTemplateStorageResourceAssoc.Status downloadState;
@Column(name = "download_pct")
private int downloadPercent;
@Column(name = "error_str")
private String errorString;
@Column(name = "store_size")
private long storeSize;
@Column(name = "created_on_store")
private Date createdOnStore = null;
@Column(name = "snapshot_store_pair")
private String snapshotStorePair;
@Override
public String getUuid() {
return uuid;
}
@Override
public String getName() {
return name;
}
public Snapshot.State getStatus() {
return status;
}
public Long getDiskOfferingId() {
return diskOfferingId;
}
public short getSnapshotType() {
return snapshotType;
}
public String getTypeDescription() {
return typeDescription;
}
public long getSize() {
return size;
}
public Date getCreated() {
return created;
}
public Date getRemoved() {
return removed;
}
public Snapshot.LocationType getLocationType() {
return locationType;
}
public Hypervisor.HypervisorType getHypervisorType() {
return hypervisorType;
}
@Override
public long getAccountId() {
return accountId;
}
@Override
public String getAccountUuid() {
return accountUuid;
}
@Override
public String getAccountName() {
return accountName;
}
@Override
public Account.Type getAccountType() {
return accountType;
}
@Override
public long getDomainId() {
return domainId;
}
@Override
public String getDomainUuid() {
return domainUuid;
}
@Override
public String getDomainName() {
return domainName;
}
@Override
public String getDomainPath() {
return domainPath;
}
public long getProjectId() {
return projectId;
}
@Override
public String getProjectUuid() {
return projectUuid;
}
@Override
public String getProjectName() {
return projectName;
}
public Long getDataCenterId() {
return dataCenterId;
}
public String getDataCenterUuid() {
return dataCenterUuid;
}
public String getDataCenterName() {
return dataCenterName;
}
public Long getVolumeId() {
return volumeId;
}
public String getVolumeUuid() {
return volumeUuid;
}
public String getVolumeName() {
return volumeName;
}
public Volume.Type getVolumeType() {
return volumeType;
}
public Long getVolumeSize() {
return volumeSize;
}
public Long getStoreId() {
return storeId;
}
public String getStoreUuid() {
return storeUuid;
}
public String getStoreName() {
return storeName;
}
public DataStoreRole getStoreRole() {
return storeRole;
}
public ObjectInDataStoreStateMachine.State getStoreState() {
return storeState;
}
public VMTemplateStorageResourceAssoc.Status getDownloadState() {
return downloadState;
}
public int getDownloadPercent() {
return downloadPercent;
}
public String getErrorString() {
return errorString;
}
public long getStoreSize() {
return storeSize;
}
public Date getCreatedOnStore() {
return createdOnStore;
}
public String getSnapshotStorePair() {
return snapshotStorePair;
}
@Override
public Class<?> getEntityType() {
return Snapshot.class;
}
}

Some files were not shown because too many files have changed in this diff Show More