Skip to content

Commit a6d8bf6

Browse files
Add API param and UI changes on add secondary storage page
1 parent 1c448d3 commit a6d8bf6

File tree

5 files changed

+121
-9
lines changed

5 files changed

+121
-9
lines changed

api/src/main/java/org/apache/cloudstack/api/command/admin/host/AddSecondaryStorageCmd.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
import com.cloud.exception.DiscoveryException;
3030
import com.cloud.storage.ImageStore;
3131
import com.cloud.user.Account;
32+
import org.apache.commons.collections.MapUtils;
33+
34+
import java.util.Collection;
35+
import java.util.HashMap;
36+
import java.util.Map;
3237

3338
@APICommand(name = "addSecondaryStorage", description = "Adds secondary storage.", responseObject = ImageStoreResponse.class,
3439
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@@ -44,6 +49,9 @@ public class AddSecondaryStorageCmd extends BaseCmd {
4449
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the Zone ID for the secondary storage")
4550
protected Long zoneId;
4651

52+
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].copytemplatesfromothersecondarystorages=true")
53+
protected Map details;
54+
4755
/////////////////////////////////////////////////////
4856
/////////////////// Accessors ///////////////////////
4957
/////////////////////////////////////////////////////
@@ -56,6 +64,20 @@ public Long getZoneId() {
5664
return zoneId;
5765
}
5866

67+
public Map<String, String> getDetails() {
68+
Map<String, String> detailsMap = new HashMap<>();
69+
if (MapUtils.isNotEmpty(details)) {
70+
Collection<?> props = details.values();
71+
for (Object prop : props) {
72+
HashMap<String, String> detail = (HashMap<String, String>) prop;
73+
for (Map.Entry<String, String> entry: detail.entrySet()) {
74+
detailsMap.put(entry.getKey(),entry.getValue());
75+
}
76+
}
77+
}
78+
return detailsMap;
79+
}
80+
5981
/////////////////////////////////////////////////////
6082
/////////////// API Implementation///////////////////
6183
/////////////////////////////////////////////////////
@@ -68,7 +90,7 @@ public long getEntityOwnerId() {
6890
@Override
6991
public void execute(){
7092
try{
71-
ImageStore result = _storageService.discoverImageStore(null, getUrl(), "NFS", getZoneId(), null);
93+
ImageStore result = _storageService.discoverImageStore(null, getUrl(), "NFS", getZoneId(), getDetails());
7294
ImageStoreResponse storeResponse = null;
7395
if (result != null ) {
7496
storeResponse = _responseGenerator.createImageStoreResponse(result);

engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ public void handleTemplateSync(DataStore store) {
549549
}
550550

551551
if (availHypers.contains(tmplt.getHypervisorType())) {
552-
boolean copied = isCopyFromOtherStoragesEnabled(zoneId) && tryCopyingTemplateToImageStore(tmplt, store);
552+
boolean copied = imageStoreDetailsUtil.isCopyTemplatesFromOtherStoragesEnabled(storeId, zoneId) && tryCopyingTemplateToImageStore(tmplt, store);
553553
if (!copied) {
554554
tryDownloadingTemplateToImageStore(tmplt, store);
555555
}
@@ -763,10 +763,6 @@ protected Void copyTemplateToImageStoreCallback(AsyncCallbackDispatcher<Template
763763
return null;
764764
}
765765

766-
protected boolean isCopyFromOtherStoragesEnabled(Long zoneId) {
767-
return StorageManager.COPY_TEMPLATES_FROM_OTHER_SECONDARY_STORAGES.valueIn(zoneId);
768-
}
769-
770766
protected void publishTemplateCreation(TemplateInfo tmplt) {
771767
VMTemplateVO tmpltVo = _templateDao.findById(tmplt.getId());
772768

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,16 @@ public String getNfsVersionByUuid(String storeUuid){
7878
return getGlobalDefaultNfsVersion();
7979
}
8080

81+
public boolean isCopyTemplatesFromOtherStoragesEnabled(Long storeId, Long zoneId) {
82+
83+
final Map<String, String> storeDetails = imageStoreDetailsDao.getDetails(storeId);
84+
final String keyWithoutDots = StorageManager.COPY_TEMPLATES_FROM_OTHER_SECONDARY_STORAGES.key()
85+
.replace(".", "");
86+
87+
if (storeDetails != null && storeDetails.containsKey(keyWithoutDots)) {
88+
return Boolean.parseBoolean(storeDetails.get(keyWithoutDots));
89+
}
90+
91+
return StorageManager.COPY_TEMPLATES_FROM_OTHER_SECONDARY_STORAGES.valueIn(zoneId);
92+
}
8193
}

ui/public/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@
589589
"label.copy.consoleurl": "Copy console URL to clipboard",
590590
"label.copyid": "Copy ID",
591591
"label.copy.password": "Copy password",
592+
"label.copy.templates.from.other.secondary.storages": "Copy templates from other storages instead of fetching from URLs",
592593
"label.core": "Core",
593594
"label.core.zone.type": "Core Zone type",
594595
"label.counter": "Counter",

ui/src/views/infra/AddSecondaryStorage.vue

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@
4848
<a-form-item name="zone" ref="zone" :label="$t('label.zone')">
4949
<a-select
5050
v-model:value="form.zone"
51+
@change="() => {
52+
fetchCopyTemplatesConfig()
53+
checkOtherSecondaryStorages()
54+
}"
5155
showSearch
5256
optionFilterProp="label"
5357
:filterOption="(input, option) => {
@@ -159,6 +163,17 @@
159163
<a-input v-model:value="form.secondaryStorageNFSPath"/>
160164
</a-form-item>
161165
</div>
166+
<div v-if="form.provider === 'NFS' && showCopyTemplatesToggle">
167+
<a-form-item
168+
name="copyTemplatesFromOtherSecondaryStorages"
169+
ref="copyTemplatesFromOtherSecondaryStorages"
170+
:label="$t('label.copy.templates.from.other.secondary.storages')">
171+
<a-switch
172+
v-model:checked="form.copyTemplatesFromOtherSecondaryStorages"
173+
@change="onCopyTemplatesToggleChanged"
174+
/>
175+
</a-form-item>
176+
</div>
162177
<div :span="24" class="action-button">
163178
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
164179
<a-button type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
@@ -191,7 +206,9 @@ export default {
191206
providers: ['NFS', 'SMB/CIFS', 'S3', 'Swift'],
192207
zones: [],
193208
loading: false,
194-
secondaryStorageNFSStaging: false
209+
secondaryStorageNFSStaging: false,
210+
showCopyTemplatesToggle: false,
211+
copyTemplatesTouched: false
195212
}
196213
},
197214
created () {
@@ -203,7 +220,8 @@ export default {
203220
this.formRef = ref()
204221
this.form = reactive({
205222
provider: 'NFS',
206-
secondaryStorageHttps: true
223+
secondaryStorageHttps: true,
224+
copyTemplatesFromOtherSecondaryStorages: true
207225
})
208226
this.rules = reactive({
209227
zone: [{ required: true, message: this.$t('label.required') }],
@@ -229,16 +247,57 @@ export default {
229247
closeModal () {
230248
this.$emit('close-action')
231249
},
250+
fetchCopyTemplatesConfig () {
251+
if (!this.form.zone) {
252+
return
253+
}
254+
255+
api('listConfigurations', {
256+
name: 'copy.templates.from.other.secondary.storages',
257+
zoneid: this.form.zone
258+
}).then(json => {
259+
const items =
260+
json?.listconfigurationsresponse?.configuration || []
261+
262+
items.forEach(item => {
263+
if (item.name === 'copy.templates.from.other.secondary.storages') {
264+
this.form.copyTemplatesFromOtherSecondaryStorages =
265+
item.value === 'true'
266+
}
267+
})
268+
})
269+
},
232270
listZones () {
233271
api('listZones', { showicon: true }).then(json => {
234-
if (json && json.listzonesresponse && json.listzonesresponse.zone) {
272+
if (json?.listzonesresponse?.zone) {
235273
this.zones = json.listzonesresponse.zone
274+
236275
if (this.zones.length > 0) {
237276
this.form.zone = this.zones[0].id || ''
277+
this.fetchCopyTemplatesConfig()
278+
this.checkOtherSecondaryStorages()
238279
}
239280
}
240281
})
241282
},
283+
checkOtherSecondaryStorages () {
284+
api('listImageStores', {
285+
listall: true
286+
}).then(json => {
287+
const stores = json?.listimagestoresresponse?.imagestore || []
288+
289+
this.showCopyTemplatesToggle = stores.some(store => {
290+
if (store.providername !== 'NFS') {
291+
return false
292+
}
293+
294+
return store.zoneid !== this.form.zone || store.zoneid === this.form.zone
295+
})
296+
})
297+
},
298+
onCopyTemplatesToggleChanged (val) {
299+
this.copyTemplatesTouched = true
300+
},
242301
nfsURL (server, path) {
243302
var url
244303
if (path.substring(0, 1) !== '/') {
@@ -362,6 +421,23 @@ export default {
362421
nfsParams.url = nfsUrl
363422
}
364423
424+
if (
425+
provider === 'NFS' &&
426+
this.showCopyTemplatesToggle &&
427+
this.copyTemplatesTouched
428+
) {
429+
const copyTemplatesKey = 'copytemplatesfromothersecondarystorages'
430+
431+
const detailIdx = Object.keys(data)
432+
.filter(k => k.startsWith('details['))
433+
.map(k => parseInt(k.match(/details\[(\d+)\]/)[1]))
434+
.reduce((a, b) => Math.max(a, b), -1) + 1
435+
436+
data[`details[${detailIdx}].key`] = copyTemplatesKey
437+
data[`details[${detailIdx}].value`] =
438+
values.copyTemplatesFromOtherSecondaryStorages.toString()
439+
}
440+
365441
this.loading = true
366442
367443
try {
@@ -402,6 +478,11 @@ export default {
402478
reject(error)
403479
})
404480
})
481+
},
482+
watch: {
483+
'form.zone' () {
484+
this.copyTemplatesTouched = false
485+
}
405486
}
406487
}
407488
}

0 commit comments

Comments
 (0)