Skip to content

Commit d7482da

Browse files
committed
added option in the UI for the storage pools
Added drop down to choose the primary storage pools to copy a snapshot Small fixes
1 parent 81ddcac commit d7482da

File tree

12 files changed

+197
-27
lines changed

12 files changed

+197
-27
lines changed

plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
4242
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
4343
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
44-
import org.apache.cloudstack.storage.snapshot.StorPoolSnapshotStrategy;
4544
import org.apache.commons.collections.CollectionUtils;
4645

4746
import javax.inject.Inject;
@@ -379,12 +378,13 @@ protected void runInContext() {
379378
}
380379
if (snapshots.contains(name)) {
381380
Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId(name, conn), clusterDao);
382-
conn = StorPoolSnapshotStrategy.getSpConnectionDesc(conn, clusterId);
381+
conn = StorPoolHelper.getSpConnectionDesc(conn, clusterId);
383382
SpApiResponse resp = StorPoolUtil.snapshotUnexport(name, location, conn);
384383
if (resp.getError() == null) {
384+
StorPoolUtil.spLog("Unexport of snapshot %s was successful", name);
385385
recoveredSnapshots.add(snapshot.getId());
386386
} else {
387-
logger.debug(String.format("Could not recover StorPool snapshot %s", resp.getError()));
387+
StorPoolUtil.spLog("Could not recover StorPool snapshot %s", resp.getError());
388388
}
389389
}
390390
}

plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,4 +306,15 @@ public static boolean isPoolSupportsAllFunctionalityFromPreviousVersion(StorageP
306306
}
307307
return true;
308308
}
309+
310+
public static StorPoolUtil.SpConnectionDesc getSpConnectionDesc(StorPoolUtil.SpConnectionDesc connectionLocal, Long clusterId) {
311+
312+
String subClusterEndPoint = StorPoolConfigurationManager.StorPoolSubclusterEndpoint.valueIn(clusterId);
313+
if (StringUtils.isNotEmpty(subClusterEndPoint)) {
314+
String host = subClusterEndPoint.split(";")[0].split("=")[1];
315+
String token = subClusterEndPoint.split(";")[1].split("=")[1];
316+
connectionLocal = new StorPoolUtil.SpConnectionDesc(host, token, connectionLocal.getTemplateName());
317+
}
318+
return connectionLocal;
319+
}
309320
}

plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,8 @@ public static SpApiResponse snapshotExport(String name, String location, SpConne
685685
public static SpApiResponse snapshotUnexport(String name, String location, SpConnectionDesc conn) {
686686
Map<String, Object> json = new HashMap<>();
687687
json.put("snapshot", name);
688-
json.put("location", location);
688+
json.put("force", true);
689+
json.put("all", true);
689690
return POST("SnapshotUnexport", json, conn);
690691
}
691692

plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
6060

6161
import org.apache.commons.collections.CollectionUtils;
62-
import org.apache.commons.lang3.StringUtils;
6362

6463
import org.apache.logging.log4j.LogManager;
6564
import org.apache.logging.log4j.Logger;
@@ -198,6 +197,12 @@ public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperat
198197
if (CollectionUtils.isEmpty(pools)) {
199198
return StrategyPriority.CANT_HANDLE;
200199
}
200+
List<SnapshotJoinVO> snapshots = snapshotJoinDao.listBySnapshotIdAndZoneId(zoneId, snapshot.getId());
201+
boolean snapshotNotOnStorPool = snapshots.stream().filter(s -> s.getStoreRole().equals(DataStoreRole.Primary)).count() == 0;
202+
203+
if (snapshotNotOnStorPool) {
204+
return StrategyPriority.CANT_HANDLE;
205+
}
201206
for (StoragePoolVO pool : pools) {
202207
SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Primary, pool.getId(), snapshot.getId());
203208
if (snapshotOnPrimary != null && (snapshotOnPrimary.getState().equals(State.Ready) || snapshotOnPrimary.getState().equals(State.Created))) {
@@ -393,7 +398,7 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp
393398
snapshot.getDataStore().getId(), storagePoolDetailsDao, _primaryDataStoreDao);
394399
String snapshotName = StorPoolStorageAdaptor.getVolumeNameFromPath(srcSnapshot.getPath(), false);
395400
Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId("~" + snapshotName, connectionLocal), clusterDao);
396-
connectionLocal = getSpConnectionDesc(connectionLocal, clusterId);
401+
connectionLocal = StorPoolHelper.getSpConnectionDesc(connectionLocal, clusterId);
397402
SpApiResponse resp = StorPoolUtil.snapshotExport("~" + snapshotName, location, connectionLocal);
398403
if (resp.getError() != null) {
399404
StorPoolUtil.spLog("Failed to export snapshot %s from %s due to %s", snapshotName, location, resp.getError());
@@ -403,6 +408,9 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp
403408
callback.complete(res);
404409
return;
405410
}
411+
String detail = "~" + snapshotName + ";" + location;
412+
SnapshotDetailsVO snapshotForRecovery = new SnapshotDetailsVO(snapshot.getId(), StorPoolUtil.SP_RECOVERED_SNAPSHOT, detail, true);
413+
_snapshotDetailsDao.persist(snapshotForRecovery);
406414
SpConnectionDesc connectionRemote = StorPoolUtil.getSpConnection(storagePoolVO.getUuid(),
407415
storagePoolVO.getId(), storagePoolDetailsDao, _primaryDataStoreDao);
408416
String localLocation = StorPoolConfigurationManager.StorPoolClusterLocation
@@ -420,9 +428,7 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp
420428
return;
421429
}
422430
StorPoolUtil.spLog("The snapshot [%s] was copied from remote", snapshotName);
423-
String detail = "~" + snapshotName + ";" + location;
424-
SnapshotDetailsVO snapshotForRecovery = new SnapshotDetailsVO(snapshot.getId(), StorPoolUtil.SP_RECOVERED_SNAPSHOT, detail, true);
425-
_snapshotDetailsDao.persist(snapshotForRecovery);
431+
426432
respFromRemote = StorPoolUtil.snapshotReconcile("~" + snapshotName, connectionRemote);
427433
if (respFromRemote.getError() != null) {
428434
StorPoolUtil.spLog("Failed to reconcile snapshot %s from %s due to %s", snapshotName, location, respFromRemote.getError());
@@ -449,15 +455,4 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp
449455
res.setResult(err);
450456
callback.complete(res);
451457
}
452-
453-
public static SpConnectionDesc getSpConnectionDesc(SpConnectionDesc connectionLocal, Long clusterId) {
454-
455-
String subClusterEndPoint = StorPoolConfigurationManager.StorPoolSubclusterEndpoint.valueIn(clusterId);
456-
if (StringUtils.isNotEmpty(subClusterEndPoint)) {
457-
String host = subClusterEndPoint.split(";")[0].split("=")[1];
458-
String token = subClusterEndPoint.split(";")[1].split("=")[1];
459-
connectionLocal = new SpConnectionDesc(host, token, connectionLocal.getTemplateName());
460-
}
461-
return connectionLocal;
462-
}
463458
}

server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4011,6 +4011,7 @@ private boolean canCopyOnPrimary(List<Long> poolIds, VolumeInfo volume, boolean
40114011
} else {
40124012
return false;
40134013
}
4014+
snapshotHelper.checkIfThereAreMoreThanOnePoolInTheZone(poolIds);
40144015
return true;
40154016
}
40164017

server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,7 @@ protected void validatePolicyZones(List<Long> zoneIds, List<Long> poolIds, Volum
10271027
}
10281028
}
10291029
if (hasPools) {
1030+
snapshotHelper.checkIfThereAreMoreThanOnePoolInTheZone(poolIds);
10301031
for (Long poolId : poolIds) {
10311032
getCheckedDestinationStorageForSnapshotCopy(poolId, isRootAdminCaller);
10321033
}
@@ -2096,6 +2097,7 @@ public Snapshot copySnapshot(CopySnapshotCmd cmd) throws StorageUnavailableExcep
20962097
throw new InvalidParameterValueException(String.format("There is no snapshot ID: %s ready on image store", snapshot.getUuid()));
20972098
}
20982099
if (canCopyBetweenStoragePools) {
2100+
snapshotHelper.checkIfThereAreMoreThanOnePoolInTheZone(storagePoolIds);
20992101
copySnapshotToPrimaryDifferentZone(storagePoolIds, snapshot);
21002102
}
21012103
List<String> failedZones = copySnapshotToZones(snapshot, srcSecStore, new ArrayList<>(dataCenterVOs.values()));

server/src/main/java/com/cloud/template/TemplateManagerImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,15 +1683,15 @@ public VirtualMachineTemplate createPrivateTemplate(CreateTemplateCmd command) t
16831683
if (kvmSnapshotOnlyInPrimaryStorage && keepOnPrimary) {
16841684
skipCopyToSecondary = true;
16851685
}
1686-
if (dataStoreRole == DataStoreRole.Image || !skipCopyToSecondary) {
1686+
if (dataStoreRole == DataStoreRole.Image) {
16871687
snapInfo = snapshotHelper.backupSnapshotToSecondaryStorageIfNotExists(snapInfo, dataStoreRole, snapshot, kvmSnapshotOnlyInPrimaryStorage);
16881688
_accountMgr.checkAccess(caller, null, true, snapInfo);
16891689
DataStore snapStore = snapInfo.getDataStore();
16901690

16911691
if (snapStore != null) {
16921692
store = snapStore; // pick snapshot image store to create template
16931693
}
1694-
} else if (skipCopyToSecondary) {
1694+
} else if (keepOnPrimary) {
16951695
ImageStoreVO imageStore = _imgStoreDao.findOneByZoneAndProtocol(zoneId, "nfs");
16961696
if (imageStore == null) {
16971697
throw new CloudRuntimeException(String.format("Could not find an NFS secondary storage pool on zone %s to use as a temporary location " +

server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@
5151
import org.apache.logging.log4j.Logger;
5252

5353
import javax.inject.Inject;
54+
import java.util.ArrayList;
5455
import java.util.Arrays;
56+
import java.util.Collections;
5557
import java.util.HashSet;
5658
import java.util.List;
5759
import java.util.Map;
@@ -301,4 +303,18 @@ protected Set<Long> getSnapshotIdsOnlyInPrimaryStorage(long volumeId) {
301303

302304
return snapshotIdsOnlyInPrimaryStorage;
303305
}
306+
307+
public void checkIfThereAreMoreThanOnePoolInTheZone(List<Long> poolIds) {
308+
List<Long> poolsInOneZone = new ArrayList<>();
309+
for (Long poolId : poolIds) {
310+
StoragePoolVO pool = primaryDataStoreDao.findById(poolId);
311+
if (pool != null) {
312+
poolsInOneZone.add(pool.getDataCenterId());
313+
}
314+
}
315+
boolean moreThanOnePoolForZone = poolsInOneZone.stream().filter(itr -> Collections.frequency(poolsInOneZone, itr) > 1).count() > 1;
316+
if (moreThanOnePoolForZone) {
317+
throw new CloudRuntimeException("Cannot copy the snapshot on multiple storage pools in one zone");
318+
}
319+
}
304320
}

ui/public/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2046,6 +2046,7 @@
20462046
"label.select.source.vcenter.datacenter": "Select the source VMware vCenter Datacenter",
20472047
"label.select.tier": "Select Network Tier",
20482048
"label.select.zones": "Select zones",
2049+
"label.select.storagepools": "Select storage pools",
20492050
"label.select.2fa.provider": "Select the provider",
20502051
"label.selected.storage": "Selected storage",
20512052
"label.self": "Mine",
@@ -2212,6 +2213,7 @@
22122213
"label.storagemotionenabled": "Storage motion enabled",
22132214
"label.storagepolicy": "Storage policy",
22142215
"label.storagepool": "Storage pool",
2216+
"label.storagepools": "Storage pools",
22152217
"label.storagepool.tooltip": "Destination Storage Pool. Volume should be located in this Storage Pool",
22162218
"label.storagetags": "Storage tags",
22172219
"label.storagetype": "Storage type",

ui/src/views/storage/FormSchedule.vue

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,32 @@
169169
</a-select>
170170
</a-form-item>
171171
</a-col>
172+
<a-col :md="24" :lg="24" v-if="resourceType === 'Volume'">
173+
<a-form-item ref="storageids" name="storageids">
174+
<template #label>
175+
<tooltip-label :title="$t('label.storagepools')" :tooltip="''"/>
176+
</template>
177+
<a-select
178+
id="storagepool-selection"
179+
v-model:value="form.storageids"
180+
mode="multiple"
181+
showSearch
182+
optionFilterProp="label"
183+
:filterOption="(input, option) => {
184+
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
185+
}"
186+
:loading="storagePoolLoading"
187+
:placeholder="''">
188+
<a-select-option v-for="opt in this.storagePools" :key="opt.id" :label="opt.name || opt.description">
189+
<span>
190+
<resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
191+
<global-outlined v-else style="margin-right: 5px"/>
192+
{{ opt.name || opt.description }}
193+
</span>
194+
</a-select-option>
195+
</a-select>
196+
</a-form-item>
197+
</a-col>
172198
</a-row>
173199
<a-divider/>
174200
<div class="tagsTitle">{{ $t('label.tags') }}</div>
@@ -272,7 +298,8 @@ export default {
272298
timeZoneMap: [],
273299
fetching: false,
274300
listDayOfWeek: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'],
275-
zones: []
301+
zones: [],
302+
storagePools: []
276303
}
277304
},
278305
created () {
@@ -307,6 +334,7 @@ export default {
307334
})
308335
if (this.resourceType === 'Volume') {
309336
this.fetchZoneData()
337+
this.fetchStoragePoolData()
310338
}
311339
},
312340
fetchZoneData () {
@@ -323,6 +351,20 @@ export default {
323351
this.zoneLoading = false
324352
})
325353
},
354+
fetchStoragePoolData () {
355+
const params = {}
356+
params.showicon = true
357+
this.storagePoolsLoading = true
358+
api('listStoragePools', params).then(json => {
359+
const listStoragePools = json.liststoragepoolsresponse.storagepool
360+
if (listStoragePools) {
361+
this.storagePools = listStoragePools
362+
this.storagePools = this.storagePools.filter(pool => pool.storagecapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES && pool.zoneid !== this.resource.zoneid)
363+
}
364+
}).finally(() => {
365+
this.storagePoolsLoading = false
366+
})
367+
},
326368
fetchTimeZone (value) {
327369
this.timeZoneMap = []
328370
this.fetching = true
@@ -422,6 +464,9 @@ export default {
422464
if (values.zoneids && values.zoneids.length > 0) {
423465
params.zoneids = values.zoneids.join()
424466
}
467+
if (values.storageids && values.storageids.length > 0) {
468+
params.storageids = values.storageids.join()
469+
}
425470
switch (values.intervaltype) {
426471
case 'hourly':
427472
params.schedule = values.time

0 commit comments

Comments
 (0)