Skip to content

Commit 6f209d8

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 9cccac3 commit 6f209d8

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
@@ -683,7 +683,8 @@ public static SpApiResponse snapshotExport(String name, String location, SpConne
683683
public static SpApiResponse snapshotUnexport(String name, String location, SpConnectionDesc conn) {
684684
Map<String, Object> json = new HashMap<>();
685685
json.put("snapshot", name);
686-
json.put("location", location);
686+
json.put("force", true);
687+
json.put("all", true);
687688
return POST("SnapshotUnexport", json, conn);
688689
}
689690

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
@@ -57,7 +57,6 @@
5757
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
5858
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
5959
import org.apache.commons.collections.CollectionUtils;
60-
import org.apache.commons.lang3.StringUtils;
6160
import org.apache.logging.log4j.LogManager;
6261
import org.apache.logging.log4j.Logger;
6362
import org.springframework.stereotype.Component;
@@ -196,6 +195,12 @@ public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperat
196195
if (CollectionUtils.isEmpty(pools)) {
197196
return StrategyPriority.CANT_HANDLE;
198197
}
198+
List<SnapshotJoinVO> snapshots = snapshotJoinDao.listBySnapshotIdAndZoneId(zoneId, snapshot.getId());
199+
boolean snapshotNotOnStorPool = snapshots.stream().filter(s -> s.getStoreRole().equals(DataStoreRole.Primary)).count() == 0;
200+
201+
if (snapshotNotOnStorPool) {
202+
return StrategyPriority.CANT_HANDLE;
203+
}
199204
for (StoragePoolVO pool : pools) {
200205
SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Primary, pool.getId(), snapshot.getId());
201206
if (snapshotOnPrimary != null && (snapshotOnPrimary.getState().equals(State.Ready) || snapshotOnPrimary.getState().equals(State.Created))) {
@@ -391,7 +396,7 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp
391396
snapshot.getDataStore().getId(), storagePoolDetailsDao, _primaryDataStoreDao);
392397
String snapshotName = StorPoolStorageAdaptor.getVolumeNameFromPath(srcSnapshot.getPath(), false);
393398
Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId("~" + snapshotName, connectionLocal), clusterDao);
394-
connectionLocal = getSpConnectionDesc(connectionLocal, clusterId);
399+
connectionLocal = StorPoolHelper.getSpConnectionDesc(connectionLocal, clusterId);
395400
SpApiResponse resp = StorPoolUtil.snapshotExport("~" + snapshotName, location, connectionLocal);
396401
if (resp.getError() != null) {
397402
StorPoolUtil.spLog("Failed to export snapshot %s from %s due to %s", snapshotName, location, resp.getError());
@@ -401,6 +406,9 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp
401406
callback.complete(res);
402407
return;
403408
}
409+
String detail = "~" + snapshotName + ";" + location;
410+
SnapshotDetailsVO snapshotForRecovery = new SnapshotDetailsVO(snapshot.getId(), StorPoolUtil.SP_RECOVERED_SNAPSHOT, detail, true);
411+
_snapshotDetailsDao.persist(snapshotForRecovery);
404412
SpConnectionDesc connectionRemote = StorPoolUtil.getSpConnection(storagePoolVO.getUuid(),
405413
storagePoolVO.getId(), storagePoolDetailsDao, _primaryDataStoreDao);
406414
String localLocation = StorPoolConfigurationManager.StorPoolClusterLocation
@@ -418,9 +426,7 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp
418426
return;
419427
}
420428
StorPoolUtil.spLog("The snapshot [%s] was copied from remote", snapshotName);
421-
String detail = "~" + snapshotName + ";" + location;
422-
SnapshotDetailsVO snapshotForRecovery = new SnapshotDetailsVO(snapshot.getId(), StorPoolUtil.SP_RECOVERED_SNAPSHOT, detail, true);
423-
_snapshotDetailsDao.persist(snapshotForRecovery);
429+
424430
respFromRemote = StorPoolUtil.snapshotReconcile("~" + snapshotName, connectionRemote);
425431
if (respFromRemote.getError() != null) {
426432
StorPoolUtil.spLog("Failed to reconcile snapshot %s from %s due to %s", snapshotName, location, respFromRemote.getError());
@@ -447,15 +453,4 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp
447453
res.setResult(err);
448454
callback.complete(res);
449455
}
450-
451-
public static SpConnectionDesc getSpConnectionDesc(SpConnectionDesc connectionLocal, Long clusterId) {
452-
453-
String subClusterEndPoint = StorPoolConfigurationManager.StorPoolSubclusterEndpoint.valueIn(clusterId);
454-
if (StringUtils.isNotEmpty(subClusterEndPoint)) {
455-
String host = subClusterEndPoint.split(";")[0].split("=")[1];
456-
String token = subClusterEndPoint.split(";")[1].split("=")[1];
457-
connectionLocal = new SpConnectionDesc(host, token, connectionLocal.getTemplateName());
458-
}
459-
return connectionLocal;
460-
}
461456
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3876,6 +3876,7 @@ private boolean canCopyOnPrimary(List<Long> poolIds, VolumeInfo volume, boolean
38763876
} else {
38773877
return false;
38783878
}
3879+
snapshotHelper.checkIfThereAreMoreThanOnePoolInTheZone(poolIds);
38793880
return true;
38803881
}
38813882

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
}
@@ -2101,6 +2102,7 @@ public Snapshot copySnapshot(CopySnapshotCmd cmd) throws StorageUnavailableExcep
21012102
throw new InvalidParameterValueException(String.format("There is no snapshot ID: %s ready on image store", snapshot.getUuid()));
21022103
}
21032104
if (canCopyBetweenStoragePools) {
2105+
snapshotHelper.checkIfThereAreMoreThanOnePoolInTheZone(storagePoolIds);
21042106
copySnapshotToPrimaryDifferentZone(storagePoolIds, snapshot);
21052107
}
21062108
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
@@ -1678,15 +1678,15 @@ public VirtualMachineTemplate createPrivateTemplate(CreateTemplateCmd command) t
16781678
if (kvmSnapshotOnlyInPrimaryStorage && keepOnPrimary) {
16791679
skipCopyToSecondary = true;
16801680
}
1681-
if (dataStoreRole == DataStoreRole.Image || !skipCopyToSecondary) {
1681+
if (dataStoreRole == DataStoreRole.Image) {
16821682
snapInfo = snapshotHelper.backupSnapshotToSecondaryStorageIfNotExists(snapInfo, dataStoreRole, snapshot, kvmSnapshotOnlyInPrimaryStorage);
16831683
_accountMgr.checkAccess(caller, null, true, snapInfo);
16841684
DataStore snapStore = snapInfo.getDataStore();
16851685

16861686
if (snapStore != null) {
16871687
store = snapStore; // pick snapshot image store to create template
16881688
}
1689-
} else if (skipCopyToSecondary) {
1689+
} else if (keepOnPrimary) {
16901690
ImageStoreVO imageStore = _imgStoreDao.findOneByZoneAndProtocol(zoneId, "nfs");
16911691
if (imageStore == null) {
16921692
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;
@@ -289,4 +291,18 @@ protected Set<Long> getSnapshotIdsOnlyInPrimaryStorage(long volumeId) {
289291

290292
return snapshotIdsOnlyInPrimaryStorage;
291293
}
294+
295+
public void checkIfThereAreMoreThanOnePoolInTheZone(List<Long> poolIds) {
296+
List<Long> poolsInOneZone = new ArrayList<>();
297+
for (Long poolId : poolIds) {
298+
StoragePoolVO pool = primaryDataStoreDao.findById(poolId);
299+
if (pool != null) {
300+
poolsInOneZone.add(pool.getDataCenterId());
301+
}
302+
}
303+
boolean moreThanOnePoolForZone = poolsInOneZone.stream().filter(itr -> Collections.frequency(poolsInOneZone, itr) > 1).count() > 1;
304+
if (moreThanOnePoolForZone) {
305+
throw new CloudRuntimeException("Cannot copy the snapshot on multiple storage pools in one zone");
306+
}
307+
}
292308
}

ui/public/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,6 +1959,7 @@
19591959
"label.select.source.vcenter.datacenter": "Select the source VMware vCenter Datacenter",
19601960
"label.select.tier": "Select Network Tier",
19611961
"label.select.zones": "Select zones",
1962+
"label.select.storagepools": "Select storage pools",
19621963
"label.select.2fa.provider": "Select the provider",
19631964
"label.selected.storage": "Selected storage",
19641965
"label.self": "Mine",
@@ -2118,6 +2119,7 @@
21182119
"label.storagemotionenabled": "Storage motion enabled",
21192120
"label.storagepolicy": "Storage policy",
21202121
"label.storagepool": "Storage pool",
2122+
"label.storagepools": "Storage pools",
21212123
"label.storagepool.tooltip": "Destination Storage Pool. Volume should be located in this Storage Pool",
21222124
"label.storagetags": "Storage tags",
21232125
"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)