Add API param and UI changes on add secondary storage page

This commit is contained in:
Harikrishna Patnala 2025-12-22 20:11:34 +05:30
parent 988ca9a32a
commit 9a60a9287b
5 changed files with 121 additions and 9 deletions

View File

@ -29,6 +29,11 @@ import org.apache.cloudstack.api.response.ZoneResponse;
import com.cloud.exception.DiscoveryException;
import com.cloud.storage.ImageStore;
import com.cloud.user.Account;
import org.apache.commons.collections.MapUtils;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@APICommand(name = "addSecondaryStorage", description = "Adds secondary storage.", responseObject = ImageStoreResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@ -44,6 +49,9 @@ public class AddSecondaryStorageCmd extends BaseCmd {
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "The Zone ID for the secondary storage")
protected Long zoneId;
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].copytemplatesfromothersecondarystorages=true")
protected Map details;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -56,6 +64,20 @@ public class AddSecondaryStorageCmd extends BaseCmd {
return zoneId;
}
public Map<String, String> getDetails() {
Map<String, String> detailsMap = new HashMap<>();
if (MapUtils.isNotEmpty(details)) {
Collection<?> props = details.values();
for (Object prop : props) {
HashMap<String, String> detail = (HashMap<String, String>) prop;
for (Map.Entry<String, String> entry: detail.entrySet()) {
detailsMap.put(entry.getKey(),entry.getValue());
}
}
}
return detailsMap;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@ -68,7 +90,7 @@ public class AddSecondaryStorageCmd extends BaseCmd {
@Override
public void execute(){
try{
ImageStore result = _storageService.discoverImageStore(null, getUrl(), "NFS", getZoneId(), null);
ImageStore result = _storageService.discoverImageStore(null, getUrl(), "NFS", getZoneId(), getDetails());
ImageStoreResponse storeResponse = null;
if (result != null ) {
storeResponse = _responseGenerator.createImageStoreResponse(result);

View File

@ -549,7 +549,7 @@ public class TemplateServiceImpl implements TemplateService {
}
if (availHypers.contains(tmplt.getHypervisorType())) {
boolean copied = isCopyFromOtherStoragesEnabled(zoneId) && tryCopyingTemplateToImageStore(tmplt, store);
boolean copied = imageStoreDetailsUtil.isCopyTemplatesFromOtherStoragesEnabled(storeId, zoneId) && tryCopyingTemplateToImageStore(tmplt, store);
if (!copied) {
tryDownloadingTemplateToImageStore(tmplt, store);
}
@ -763,10 +763,6 @@ public class TemplateServiceImpl implements TemplateService {
return null;
}
protected boolean isCopyFromOtherStoragesEnabled(Long zoneId) {
return StorageManager.COPY_TEMPLATES_FROM_OTHER_SECONDARY_STORAGES.valueIn(zoneId);
}
protected void publishTemplateCreation(TemplateInfo tmplt) {
VMTemplateVO tmpltVo = _templateDao.findById(tmplt.getId());

View File

@ -78,4 +78,16 @@ public class ImageStoreDetailsUtil {
return getGlobalDefaultNfsVersion();
}
public boolean isCopyTemplatesFromOtherStoragesEnabled(Long storeId, Long zoneId) {
final Map<String, String> storeDetails = imageStoreDetailsDao.getDetails(storeId);
final String keyWithoutDots = StorageManager.COPY_TEMPLATES_FROM_OTHER_SECONDARY_STORAGES.key()
.replace(".", "");
if (storeDetails != null && storeDetails.containsKey(keyWithoutDots)) {
return Boolean.parseBoolean(storeDetails.get(keyWithoutDots));
}
return StorageManager.COPY_TEMPLATES_FROM_OTHER_SECONDARY_STORAGES.valueIn(zoneId);
}
}

View File

@ -589,6 +589,7 @@
"label.copy.consoleurl": "Copy console URL to clipboard",
"label.copyid": "Copy ID",
"label.copy.password": "Copy password",
"label.copy.templates.from.other.secondary.storages": "Copy templates from other storages instead of fetching from URLs",
"label.core": "Core",
"label.core.zone.type": "Core Zone type",
"label.counter": "Counter",

View File

@ -48,6 +48,10 @@
<a-form-item name="zone" ref="zone" :label="$t('label.zone')">
<a-select
v-model:value="form.zone"
@change="() => {
fetchCopyTemplatesConfig()
checkOtherSecondaryStorages()
}"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
@ -159,6 +163,17 @@
<a-input v-model:value="form.secondaryStorageNFSPath"/>
</a-form-item>
</div>
<div v-if="form.provider === 'NFS' && showCopyTemplatesToggle">
<a-form-item
name="copyTemplatesFromOtherSecondaryStorages"
ref="copyTemplatesFromOtherSecondaryStorages"
:label="$t('label.copy.templates.from.other.secondary.storages')">
<a-switch
v-model:checked="form.copyTemplatesFromOtherSecondaryStorages"
@change="onCopyTemplatesToggleChanged"
/>
</a-form-item>
</div>
<div :span="24" class="action-button">
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
@ -191,7 +206,9 @@ export default {
providers: ['NFS', 'SMB/CIFS', 'S3', 'Swift'],
zones: [],
loading: false,
secondaryStorageNFSStaging: false
secondaryStorageNFSStaging: false,
showCopyTemplatesToggle: false,
copyTemplatesTouched: false
}
},
created () {
@ -203,7 +220,8 @@ export default {
this.formRef = ref()
this.form = reactive({
provider: 'NFS',
secondaryStorageHttps: true
secondaryStorageHttps: true,
copyTemplatesFromOtherSecondaryStorages: true
})
this.rules = reactive({
zone: [{ required: true, message: this.$t('label.required') }],
@ -229,16 +247,57 @@ export default {
closeModal () {
this.$emit('close-action')
},
fetchCopyTemplatesConfig () {
if (!this.form.zone) {
return
}
api('listConfigurations', {
name: 'copy.templates.from.other.secondary.storages',
zoneid: this.form.zone
}).then(json => {
const items =
json?.listconfigurationsresponse?.configuration || []
items.forEach(item => {
if (item.name === 'copy.templates.from.other.secondary.storages') {
this.form.copyTemplatesFromOtherSecondaryStorages =
item.value === 'true'
}
})
})
},
listZones () {
api('listZones', { showicon: true }).then(json => {
if (json && json.listzonesresponse && json.listzonesresponse.zone) {
if (json?.listzonesresponse?.zone) {
this.zones = json.listzonesresponse.zone
if (this.zones.length > 0) {
this.form.zone = this.zones[0].id || ''
this.fetchCopyTemplatesConfig()
this.checkOtherSecondaryStorages()
}
}
})
},
checkOtherSecondaryStorages () {
api('listImageStores', {
listall: true
}).then(json => {
const stores = json?.listimagestoresresponse?.imagestore || []
this.showCopyTemplatesToggle = stores.some(store => {
if (store.providername !== 'NFS') {
return false
}
return store.zoneid !== this.form.zone || store.zoneid === this.form.zone
})
})
},
onCopyTemplatesToggleChanged (val) {
this.copyTemplatesTouched = true
},
nfsURL (server, path) {
var url
if (path.substring(0, 1) !== '/') {
@ -362,6 +421,23 @@ export default {
nfsParams.url = nfsUrl
}
if (
provider === 'NFS' &&
this.showCopyTemplatesToggle &&
this.copyTemplatesTouched
) {
const copyTemplatesKey = 'copytemplatesfromothersecondarystorages'
const detailIdx = Object.keys(data)
.filter(k => k.startsWith('details['))
.map(k => parseInt(k.match(/details\[(\d+)\]/)[1]))
.reduce((a, b) => Math.max(a, b), -1) + 1
data[`details[${detailIdx}].key`] = copyTemplatesKey
data[`details[${detailIdx}].value`] =
values.copyTemplatesFromOtherSecondaryStorages.toString()
}
this.loading = true
try {
@ -402,6 +478,11 @@ export default {
reject(error)
})
})
},
watch: {
'form.zone' () {
this.copyTemplatesTouched = false
}
}
}
}